Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
config PCI
|
||||
bool
|
||||
|
||||
config PCI_EXPRESS
|
||||
bool
|
||||
select PCI
|
||||
|
||||
config PCI_DEVICES
|
||||
bool
|
||||
|
||||
config PCIE_DEVICES
|
||||
bool
|
||||
|
||||
config MSI_NONBROKEN
|
||||
# selected by interrupt controllers that do not support MSI,
|
||||
# or support it and have a good implementation. See commit
|
||||
# 47d2b0f33c664533b8dbd5cb17faa8e6a01afe1f.
|
||||
bool
|
||||
@@ -0,0 +1,22 @@
|
||||
pci_ss = ss.source_set()
|
||||
pci_ss.add(files(
|
||||
'msi.c',
|
||||
'msix.c',
|
||||
'pci.c',
|
||||
'pci_bridge.c',
|
||||
'pci_host.c',
|
||||
'pci-hmp-cmds.c',
|
||||
'pci-qmp-cmds.c',
|
||||
'pcie_sriov.c',
|
||||
'shpc.c',
|
||||
'slotid_cap.c'
|
||||
))
|
||||
# The functions in these modules can be used by devices too. Since we
|
||||
# allow plugging PCIe devices into PCI buses, include them even if
|
||||
# CONFIG_PCI_EXPRESS=n.
|
||||
pci_ss.add(files('pcie.c', 'pcie_aer.c'))
|
||||
pci_ss.add(files('pcie_doe.c'))
|
||||
system_ss.add(when: 'CONFIG_PCI_EXPRESS', if_true: files('pcie_port.c', 'pcie_host.c'))
|
||||
system_ss.add_all(when: 'CONFIG_PCI', if_true: pci_ss)
|
||||
|
||||
stub_ss.add(files('pci-stub.c'))
|
||||
+490
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* msi.c
|
||||
*
|
||||
* Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "hw/xen/xen.h"
|
||||
#include "qemu/range.h"
|
||||
#include "qapi/error.h"
|
||||
#include "system/xen.h"
|
||||
|
||||
#include "hw/i386/kvm/xen_evtchn.h"
|
||||
|
||||
/* PCI_MSI_ADDRESS_LO */
|
||||
#define PCI_MSI_ADDRESS_LO_MASK (~0x3)
|
||||
|
||||
/* If we get rid of cap allocator, we won't need those. */
|
||||
#define PCI_MSI_32_SIZEOF 0x0a
|
||||
#define PCI_MSI_64_SIZEOF 0x0e
|
||||
#define PCI_MSI_32M_SIZEOF 0x14
|
||||
#define PCI_MSI_64M_SIZEOF 0x18
|
||||
|
||||
#define PCI_MSI_VECTORS_MAX 32
|
||||
|
||||
/*
|
||||
* Flag for interrupt controllers to declare broken MSI/MSI-X support.
|
||||
* values: false - broken; true - non-broken.
|
||||
*
|
||||
* Setting this flag to false will remove MSI/MSI-X capability from all devices.
|
||||
*
|
||||
* It is preferable for controllers to set this to true (non-broken) even if
|
||||
* they do not actually support MSI/MSI-X: guests normally probe the controller
|
||||
* type and do not attempt to enable MSI/MSI-X with interrupt controllers not
|
||||
* supporting such, so removing the capability is not required, and
|
||||
* it seems cleaner to have a given device look the same for all boards.
|
||||
*
|
||||
* TODO: some existing controllers violate the above rule. Identify and fix them.
|
||||
*/
|
||||
bool msi_nonbroken;
|
||||
|
||||
/* If we get rid of cap allocator, we won't need this. */
|
||||
static inline uint8_t msi_cap_sizeof(uint16_t flags)
|
||||
{
|
||||
switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) {
|
||||
case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT:
|
||||
return PCI_MSI_64M_SIZEOF;
|
||||
case PCI_MSI_FLAGS_64BIT:
|
||||
return PCI_MSI_64_SIZEOF;
|
||||
case PCI_MSI_FLAGS_MASKBIT:
|
||||
return PCI_MSI_32M_SIZEOF;
|
||||
case 0:
|
||||
return PCI_MSI_32_SIZEOF;
|
||||
default:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//#define MSI_DEBUG
|
||||
|
||||
#ifdef MSI_DEBUG
|
||||
# define MSI_DPRINTF(fmt, ...) \
|
||||
fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
|
||||
#else
|
||||
# define MSI_DPRINTF(fmt, ...) do { } while (0)
|
||||
#endif
|
||||
#define MSI_DEV_PRINTF(dev, fmt, ...) \
|
||||
MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
|
||||
|
||||
static inline unsigned int msi_nr_vectors(uint16_t flags)
|
||||
{
|
||||
return 1U <<
|
||||
((flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE));
|
||||
}
|
||||
|
||||
static inline uint8_t msi_flags_off(const PCIDevice* dev)
|
||||
{
|
||||
return dev->msi_cap + PCI_MSI_FLAGS;
|
||||
}
|
||||
|
||||
static inline uint8_t msi_address_lo_off(const PCIDevice* dev)
|
||||
{
|
||||
return dev->msi_cap + PCI_MSI_ADDRESS_LO;
|
||||
}
|
||||
|
||||
static inline uint8_t msi_address_hi_off(const PCIDevice* dev)
|
||||
{
|
||||
return dev->msi_cap + PCI_MSI_ADDRESS_HI;
|
||||
}
|
||||
|
||||
static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit)
|
||||
{
|
||||
return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32);
|
||||
}
|
||||
|
||||
static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit)
|
||||
{
|
||||
return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32);
|
||||
}
|
||||
|
||||
static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit)
|
||||
{
|
||||
return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32);
|
||||
}
|
||||
|
||||
/*
|
||||
* Special API for POWER to configure the vectors through
|
||||
* a side channel. Should never be used by devices.
|
||||
*/
|
||||
void msi_set_message(PCIDevice *dev, MSIMessage msg)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
|
||||
if (msi64bit) {
|
||||
pci_set_quad(dev->config + msi_address_lo_off(dev), msg.address);
|
||||
} else {
|
||||
pci_set_long(dev->config + msi_address_lo_off(dev), msg.address);
|
||||
}
|
||||
pci_set_word(dev->config + msi_data_off(dev, msi64bit), msg.data);
|
||||
}
|
||||
|
||||
static MSIMessage msi_prepare_message(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
unsigned int nr_vectors = msi_nr_vectors(flags);
|
||||
MSIMessage msg;
|
||||
|
||||
assert(vector < nr_vectors);
|
||||
|
||||
if (msi64bit) {
|
||||
msg.address = pci_get_quad(dev->config + msi_address_lo_off(dev));
|
||||
} else {
|
||||
msg.address = pci_get_long(dev->config + msi_address_lo_off(dev));
|
||||
}
|
||||
|
||||
/* upper bit 31:16 is zero */
|
||||
msg.data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
|
||||
if (nr_vectors > 1) {
|
||||
msg.data &= ~(nr_vectors - 1);
|
||||
msg.data |= vector;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
MSIMessage msi_get_message(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
return dev->msi_prepare_message(dev, vector);
|
||||
}
|
||||
|
||||
bool msi_enabled(const PCIDevice *dev)
|
||||
{
|
||||
return msi_present(dev) &&
|
||||
(pci_get_word(dev->config + msi_flags_off(dev)) &
|
||||
PCI_MSI_FLAGS_ENABLE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make PCI device @dev MSI-capable.
|
||||
* Non-zero @offset puts capability MSI at that offset in PCI config
|
||||
* space.
|
||||
* @nr_vectors is the number of MSI vectors (1, 2, 4, 8, 16 or 32).
|
||||
* If @msi64bit, make the device capable of sending a 64-bit message
|
||||
* address.
|
||||
* If @msi_per_vector_mask, make the device support per-vector masking.
|
||||
* @errp is for returning errors.
|
||||
* Return 0 on success; set @errp and return -errno on error.
|
||||
*
|
||||
* -ENOTSUP means lacking msi support for a msi-capable platform.
|
||||
* -EINVAL means capability overlap, happens when @offset is non-zero,
|
||||
* also means a programming error, except device assignment, which can check
|
||||
* if a real HW is broken.
|
||||
*/
|
||||
int msi_init(struct PCIDevice *dev, uint8_t offset,
|
||||
unsigned int nr_vectors, bool msi64bit,
|
||||
bool msi_per_vector_mask, Error **errp)
|
||||
{
|
||||
unsigned int vectors_order;
|
||||
uint16_t flags;
|
||||
uint8_t cap_size;
|
||||
int config_offset;
|
||||
|
||||
if (!msi_nonbroken) {
|
||||
error_setg(errp, "MSI is not supported by interrupt controller");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
MSI_DEV_PRINTF(dev,
|
||||
"init offset: 0x%"PRIx8" vector: %"PRId8
|
||||
" 64bit %d mask %d\n",
|
||||
offset, nr_vectors, msi64bit, msi_per_vector_mask);
|
||||
|
||||
assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
|
||||
assert(nr_vectors > 0);
|
||||
assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
|
||||
/* the nr of MSI vectors is up to 32 */
|
||||
vectors_order = ctz32(nr_vectors);
|
||||
|
||||
flags = vectors_order << ctz32(PCI_MSI_FLAGS_QMASK);
|
||||
if (msi64bit) {
|
||||
flags |= PCI_MSI_FLAGS_64BIT;
|
||||
}
|
||||
if (msi_per_vector_mask) {
|
||||
flags |= PCI_MSI_FLAGS_MASKBIT;
|
||||
}
|
||||
|
||||
cap_size = msi_cap_sizeof(flags);
|
||||
config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset,
|
||||
cap_size, errp);
|
||||
if (config_offset < 0) {
|
||||
return config_offset;
|
||||
}
|
||||
|
||||
dev->msi_cap = config_offset;
|
||||
dev->cap_present |= QEMU_PCI_CAP_MSI;
|
||||
|
||||
pci_set_word(dev->config + msi_flags_off(dev), flags);
|
||||
pci_set_word(dev->wmask + msi_flags_off(dev),
|
||||
PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
|
||||
pci_set_long(dev->wmask + msi_address_lo_off(dev),
|
||||
PCI_MSI_ADDRESS_LO_MASK);
|
||||
if (msi64bit) {
|
||||
pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
|
||||
}
|
||||
pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
|
||||
|
||||
if (msi_per_vector_mask) {
|
||||
/* Make mask bits 0 to nr_vectors - 1 writable. */
|
||||
pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
|
||||
0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors));
|
||||
}
|
||||
|
||||
dev->msi_prepare_message = msi_prepare_message;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msi_uninit(struct PCIDevice *dev)
|
||||
{
|
||||
uint16_t flags;
|
||||
uint8_t cap_size;
|
||||
|
||||
if (!msi_present(dev)) {
|
||||
return;
|
||||
}
|
||||
flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
cap_size = msi_cap_sizeof(flags);
|
||||
pci_del_capability(dev, PCI_CAP_ID_MSI, cap_size);
|
||||
dev->cap_present &= ~QEMU_PCI_CAP_MSI;
|
||||
dev->msi_prepare_message = NULL;
|
||||
|
||||
MSI_DEV_PRINTF(dev, "uninit\n");
|
||||
}
|
||||
|
||||
void msi_reset(PCIDevice *dev)
|
||||
{
|
||||
uint16_t flags;
|
||||
bool msi64bit;
|
||||
|
||||
if (!msi_present(dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
|
||||
msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
|
||||
pci_set_word(dev->config + msi_flags_off(dev), flags);
|
||||
pci_set_long(dev->config + msi_address_lo_off(dev), 0);
|
||||
if (msi64bit) {
|
||||
pci_set_long(dev->config + msi_address_hi_off(dev), 0);
|
||||
}
|
||||
pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0);
|
||||
if (flags & PCI_MSI_FLAGS_MASKBIT) {
|
||||
pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
|
||||
pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
|
||||
}
|
||||
MSI_DEV_PRINTF(dev, "reset\n");
|
||||
}
|
||||
|
||||
bool msi_is_masked(const PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
uint32_t mask, data;
|
||||
bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
assert(vector < PCI_MSI_VECTORS_MAX);
|
||||
|
||||
if (!(flags & PCI_MSI_FLAGS_MASKBIT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data = pci_get_word(dev->config + msi_data_off(dev, msi64bit));
|
||||
if (xen_enabled() && xen_is_pirq_msi(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mask = pci_get_long(dev->config +
|
||||
msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT));
|
||||
return mask & (1U << vector);
|
||||
}
|
||||
|
||||
void msi_set_mask(PCIDevice *dev, int vector, bool mask, Error **errp)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
uint32_t irq_state, vector_mask, pending;
|
||||
|
||||
if (vector >= PCI_MSI_VECTORS_MAX) {
|
||||
error_setg(errp, "msi: vector %d not allocated. max vector is %d",
|
||||
vector, (PCI_MSI_VECTORS_MAX - 1));
|
||||
return;
|
||||
}
|
||||
|
||||
vector_mask = (1U << vector);
|
||||
|
||||
irq_state = pci_get_long(dev->config + msi_mask_off(dev, msi64bit));
|
||||
|
||||
if (mask) {
|
||||
irq_state |= vector_mask;
|
||||
} else {
|
||||
irq_state &= ~vector_mask;
|
||||
}
|
||||
|
||||
pci_set_long(dev->config + msi_mask_off(dev, msi64bit), irq_state);
|
||||
|
||||
pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
|
||||
if (!mask && (pending & vector_mask)) {
|
||||
pending &= ~vector_mask;
|
||||
pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
|
||||
msi_notify(dev, vector);
|
||||
}
|
||||
}
|
||||
|
||||
void msi_notify(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
unsigned int nr_vectors = msi_nr_vectors(flags);
|
||||
MSIMessage msg;
|
||||
|
||||
assert(vector < nr_vectors);
|
||||
if (msi_is_masked(dev, vector)) {
|
||||
assert(flags & PCI_MSI_FLAGS_MASKBIT);
|
||||
pci_long_test_and_set_mask(
|
||||
dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
|
||||
MSI_DEV_PRINTF(dev, "pending vector 0x%x\n", vector);
|
||||
return;
|
||||
}
|
||||
|
||||
msg = msi_get_message(dev, vector);
|
||||
|
||||
MSI_DEV_PRINTF(dev,
|
||||
"notify vector 0x%x"
|
||||
" address: 0x%"PRIx64" data: 0x%"PRIx32"\n",
|
||||
vector, msg.address, msg.data);
|
||||
msi_send_message(dev, msg);
|
||||
}
|
||||
|
||||
void msi_send_message(PCIDevice *dev, MSIMessage msg)
|
||||
{
|
||||
dev->msi_trigger(dev, msg);
|
||||
}
|
||||
|
||||
/* Normally called by pci_default_write_config(). */
|
||||
void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
|
||||
bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
|
||||
unsigned int nr_vectors;
|
||||
uint8_t log_num_vecs;
|
||||
uint8_t log_max_vecs;
|
||||
unsigned int vector;
|
||||
uint32_t pending;
|
||||
|
||||
if (!msi_present(dev) ||
|
||||
!ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef MSI_DEBUG
|
||||
MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n",
|
||||
addr, val, len);
|
||||
MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32,
|
||||
flags,
|
||||
pci_get_long(dev->config + msi_address_lo_off(dev)));
|
||||
if (msi64bit) {
|
||||
fprintf(stderr, " address-hi: 0x%"PRIx32,
|
||||
pci_get_long(dev->config + msi_address_hi_off(dev)));
|
||||
}
|
||||
fprintf(stderr, " data: 0x%"PRIx16,
|
||||
pci_get_word(dev->config + msi_data_off(dev, msi64bit)));
|
||||
if (flags & PCI_MSI_FLAGS_MASKBIT) {
|
||||
fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32,
|
||||
pci_get_long(dev->config + msi_mask_off(dev, msi64bit)),
|
||||
pci_get_long(dev->config + msi_pending_off(dev, msi64bit)));
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
#endif
|
||||
|
||||
if (xen_mode == XEN_EMULATE) {
|
||||
for (vector = 0; vector < msi_nr_vectors(flags); vector++) {
|
||||
MSIMessage msg = msi_prepare_message(dev, vector);
|
||||
|
||||
xen_evtchn_snoop_msi(dev, false, vector, msg.address, msg.data,
|
||||
msi_is_masked(dev, vector));
|
||||
}
|
||||
}
|
||||
|
||||
if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now MSI is enabled, clear INTx# interrupts.
|
||||
* the driver is prohibited from writing enable bit to mask
|
||||
* a service request. But the guest OS could do this.
|
||||
* So we just discard the interrupts as moderate fallback.
|
||||
*
|
||||
* 6.8.3.3. Enabling Operation
|
||||
* While enabled for MSI or MSI-X operation, a function is prohibited
|
||||
* from using its INTx# pin (if implemented) to request
|
||||
* service (MSI, MSI-X, and INTx# are mutually exclusive).
|
||||
*/
|
||||
pci_device_deassert_intx(dev);
|
||||
|
||||
/*
|
||||
* nr_vectors might be set bigger than capable. So clamp it.
|
||||
* This is not legal by spec, so we can do anything we like,
|
||||
* just don't crash the host
|
||||
*/
|
||||
log_num_vecs =
|
||||
(flags & PCI_MSI_FLAGS_QSIZE) >> ctz32(PCI_MSI_FLAGS_QSIZE);
|
||||
log_max_vecs =
|
||||
(flags & PCI_MSI_FLAGS_QMASK) >> ctz32(PCI_MSI_FLAGS_QMASK);
|
||||
if (log_num_vecs > log_max_vecs) {
|
||||
flags &= ~PCI_MSI_FLAGS_QSIZE;
|
||||
flags |= log_max_vecs << ctz32(PCI_MSI_FLAGS_QSIZE);
|
||||
pci_set_word(dev->config + msi_flags_off(dev), flags);
|
||||
}
|
||||
|
||||
if (!msi_per_vector_mask) {
|
||||
/* if per vector masking isn't supported,
|
||||
there is no pending interrupt. */
|
||||
return;
|
||||
}
|
||||
|
||||
nr_vectors = msi_nr_vectors(flags);
|
||||
|
||||
/* This will discard pending interrupts, if any. */
|
||||
pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
|
||||
pending &= 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors);
|
||||
pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending);
|
||||
|
||||
/* deliver pending interrupts which are unmasked */
|
||||
for (vector = 0; vector < nr_vectors; ++vector) {
|
||||
if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pci_long_test_and_clear_mask(
|
||||
dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
|
||||
msi_notify(dev, vector);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int msi_nr_vectors_allocated(const PCIDevice *dev)
|
||||
{
|
||||
uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
|
||||
return msi_nr_vectors(flags);
|
||||
}
|
||||
+719
@@ -0,0 +1,719 @@
|
||||
/*
|
||||
* MSI-X device support
|
||||
*
|
||||
* This module includes support for MSI-X in pci devices.
|
||||
*
|
||||
* Author: Michael S. Tsirkin <[email protected]>
|
||||
*
|
||||
* Copyright (c) 2009, Red Hat Inc, Michael S. Tsirkin ([email protected])
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*
|
||||
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||
* GNU GPL, version 2 or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "hw/pci/msix.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/xen/xen.h"
|
||||
#include "system/xen.h"
|
||||
#include "migration/qemu-file-types.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qemu/range.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "hw/i386/kvm/xen_evtchn.h"
|
||||
|
||||
/* MSI enable bit and maskall bit are in byte 1 in FLAGS register */
|
||||
#define MSIX_CONTROL_OFFSET (PCI_MSIX_FLAGS + 1)
|
||||
#define MSIX_ENABLE_MASK (PCI_MSIX_FLAGS_ENABLE >> 8)
|
||||
#define MSIX_MASKALL_MASK (PCI_MSIX_FLAGS_MASKALL >> 8)
|
||||
|
||||
static MSIMessage msix_prepare_message(PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
uint8_t *table_entry = dev->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
|
||||
MSIMessage msg;
|
||||
|
||||
msg.address = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
|
||||
msg.data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA);
|
||||
return msg;
|
||||
}
|
||||
|
||||
MSIMessage msix_get_message(PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
return dev->msix_prepare_message(dev, vector);
|
||||
}
|
||||
|
||||
/*
|
||||
* Special API for POWER to configure the vectors through
|
||||
* a side channel. Should never be used by devices.
|
||||
*/
|
||||
void msix_set_message(PCIDevice *dev, int vector, struct MSIMessage msg)
|
||||
{
|
||||
uint8_t *table_entry = dev->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
|
||||
|
||||
pci_set_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR, msg.address);
|
||||
pci_set_long(table_entry + PCI_MSIX_ENTRY_DATA, msg.data);
|
||||
table_entry[PCI_MSIX_ENTRY_VECTOR_CTRL] &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT;
|
||||
}
|
||||
|
||||
static uint8_t msix_pending_mask(int vector)
|
||||
{
|
||||
return 1 << (vector % 8);
|
||||
}
|
||||
|
||||
static uint8_t *msix_pending_byte(PCIDevice *dev, int vector)
|
||||
{
|
||||
return dev->msix_pba + vector / 8;
|
||||
}
|
||||
|
||||
int msix_is_pending(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
return *msix_pending_byte(dev, vector) & msix_pending_mask(vector);
|
||||
}
|
||||
|
||||
void msix_set_pending(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
*msix_pending_byte(dev, vector) |= msix_pending_mask(vector);
|
||||
}
|
||||
|
||||
void msix_clr_pending(PCIDevice *dev, int vector)
|
||||
{
|
||||
*msix_pending_byte(dev, vector) &= ~msix_pending_mask(vector);
|
||||
}
|
||||
|
||||
static bool msix_vector_masked(PCIDevice *dev, unsigned int vector, bool fmask)
|
||||
{
|
||||
unsigned offset = vector * PCI_MSIX_ENTRY_SIZE;
|
||||
uint8_t *data = &dev->msix_table[offset + PCI_MSIX_ENTRY_DATA];
|
||||
/* MSIs on Xen can be remapped into pirqs. In those cases, masking
|
||||
* and unmasking go through the PV evtchn path. */
|
||||
if (xen_enabled() && xen_is_pirq_msi(pci_get_long(data))) {
|
||||
return false;
|
||||
}
|
||||
return fmask || dev->msix_table[offset + PCI_MSIX_ENTRY_VECTOR_CTRL] &
|
||||
PCI_MSIX_ENTRY_CTRL_MASKBIT;
|
||||
}
|
||||
|
||||
bool msix_is_masked(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
return msix_vector_masked(dev, vector, dev->msix_function_masked);
|
||||
}
|
||||
|
||||
static void msix_fire_vector_notifier(PCIDevice *dev,
|
||||
unsigned int vector, bool is_masked)
|
||||
{
|
||||
MSIMessage msg;
|
||||
int ret;
|
||||
|
||||
if (!dev->msix_vector_use_notifier) {
|
||||
return;
|
||||
}
|
||||
if (is_masked) {
|
||||
dev->msix_vector_release_notifier(dev, vector);
|
||||
} else {
|
||||
msg = msix_get_message(dev, vector);
|
||||
ret = dev->msix_vector_use_notifier(dev, vector, msg);
|
||||
assert(ret >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void msix_handle_mask_update(PCIDevice *dev, int vector, bool was_masked)
|
||||
{
|
||||
bool is_masked = msix_is_masked(dev, vector);
|
||||
|
||||
if (xen_mode == XEN_EMULATE) {
|
||||
MSIMessage msg = msix_prepare_message(dev, vector);
|
||||
|
||||
xen_evtchn_snoop_msi(dev, true, vector, msg.address, msg.data,
|
||||
is_masked);
|
||||
}
|
||||
|
||||
if (is_masked == was_masked) {
|
||||
return;
|
||||
}
|
||||
|
||||
msix_fire_vector_notifier(dev, vector, is_masked);
|
||||
|
||||
if (!is_masked && msix_is_pending(dev, vector)) {
|
||||
msix_clr_pending(dev, vector);
|
||||
msix_notify(dev, vector);
|
||||
}
|
||||
}
|
||||
|
||||
void msix_set_mask(PCIDevice *dev, int vector, bool mask)
|
||||
{
|
||||
unsigned offset;
|
||||
bool was_masked;
|
||||
|
||||
assert(vector < dev->msix_entries_nr);
|
||||
|
||||
offset = vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL;
|
||||
|
||||
was_masked = msix_is_masked(dev, vector);
|
||||
|
||||
if (mask) {
|
||||
dev->msix_table[offset] |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
|
||||
} else {
|
||||
dev->msix_table[offset] &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT;
|
||||
}
|
||||
|
||||
msix_handle_mask_update(dev, vector, was_masked);
|
||||
}
|
||||
|
||||
static bool msix_masked(PCIDevice *dev)
|
||||
{
|
||||
return dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] & MSIX_MASKALL_MASK;
|
||||
}
|
||||
|
||||
static void msix_update_function_masked(PCIDevice *dev)
|
||||
{
|
||||
dev->msix_function_masked = !msix_enabled(dev) || msix_masked(dev);
|
||||
}
|
||||
|
||||
/* Handle MSI-X capability config write. */
|
||||
void msix_write_config(PCIDevice *dev, uint32_t addr,
|
||||
uint32_t val, int len)
|
||||
{
|
||||
unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
|
||||
int vector;
|
||||
bool was_masked;
|
||||
|
||||
if (!msix_present(dev) || !range_covers_byte(addr, len, enable_pos)) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_msix_write_config(dev->name, msix_enabled(dev), msix_masked(dev));
|
||||
|
||||
was_masked = dev->msix_function_masked;
|
||||
msix_update_function_masked(dev);
|
||||
|
||||
if (!msix_enabled(dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pci_device_deassert_intx(dev);
|
||||
|
||||
if (dev->msix_function_masked == was_masked) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
|
||||
msix_handle_mask_update(dev, vector,
|
||||
msix_vector_masked(dev, vector, was_masked));
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t msix_table_mmio_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
PCIDevice *dev = opaque;
|
||||
|
||||
assert(addr + size <= dev->msix_entries_nr * PCI_MSIX_ENTRY_SIZE);
|
||||
return pci_get_long(dev->msix_table + addr);
|
||||
}
|
||||
|
||||
static void msix_table_mmio_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
PCIDevice *dev = opaque;
|
||||
int vector = addr / PCI_MSIX_ENTRY_SIZE;
|
||||
bool was_masked;
|
||||
|
||||
assert(addr + size <= dev->msix_entries_nr * PCI_MSIX_ENTRY_SIZE);
|
||||
|
||||
was_masked = msix_is_masked(dev, vector);
|
||||
pci_set_long(dev->msix_table + addr, val);
|
||||
msix_handle_mask_update(dev, vector, was_masked);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps msix_table_mmio_ops = {
|
||||
.read = msix_table_mmio_read,
|
||||
.write = msix_table_mmio_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 8,
|
||||
},
|
||||
.impl = {
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static uint64_t msix_pba_mmio_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
PCIDevice *dev = opaque;
|
||||
if (dev->msix_vector_poll_notifier) {
|
||||
unsigned vector_start = addr * 8;
|
||||
unsigned vector_end = MIN((addr + size) * 8, dev->msix_entries_nr);
|
||||
dev->msix_vector_poll_notifier(dev, vector_start, vector_end);
|
||||
}
|
||||
|
||||
return pci_get_long(dev->msix_pba + addr);
|
||||
}
|
||||
|
||||
static void msix_pba_mmio_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
PCIDevice *dev = opaque;
|
||||
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"PCI [%s:%02x:%02x.%x] attempt to write to MSI-X "
|
||||
"PBA at 0x%" FMT_PCIBUS ", ignoring.\n",
|
||||
pci_root_bus_path(dev), pci_dev_bus_num(dev),
|
||||
PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn),
|
||||
addr);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps msix_pba_mmio_ops = {
|
||||
.read = msix_pba_mmio_read,
|
||||
.write = msix_pba_mmio_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 8,
|
||||
},
|
||||
.impl = {
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void msix_mask_all(struct PCIDevice *dev, unsigned nentries)
|
||||
{
|
||||
int vector;
|
||||
|
||||
for (vector = 0; vector < nentries; ++vector) {
|
||||
unsigned offset =
|
||||
vector * PCI_MSIX_ENTRY_SIZE + PCI_MSIX_ENTRY_VECTOR_CTRL;
|
||||
bool was_masked = msix_is_masked(dev, vector);
|
||||
|
||||
dev->msix_table[offset] |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
|
||||
msix_handle_mask_update(dev, vector, was_masked);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make PCI device @dev MSI-X capable
|
||||
* @nentries is the max number of MSI-X vectors that the device support.
|
||||
* @table_bar is the MemoryRegion that MSI-X table structure resides.
|
||||
* @table_bar_nr is number of base address register corresponding to @table_bar.
|
||||
* @table_offset indicates the offset that the MSI-X table structure starts with
|
||||
* in @table_bar.
|
||||
* @pba_bar is the MemoryRegion that the Pending Bit Array structure resides.
|
||||
* @pba_bar_nr is number of base address register corresponding to @pba_bar.
|
||||
* @pba_offset indicates the offset that the Pending Bit Array structure
|
||||
* starts with in @pba_bar.
|
||||
* Non-zero @cap_pos puts capability MSI-X at that offset in PCI config space.
|
||||
* @errp is for returning errors.
|
||||
*
|
||||
* Return 0 on success; set @errp and return -errno on error:
|
||||
* -ENOTSUP means lacking msi support for a msi-capable platform.
|
||||
* -EINVAL means capability overlap, happens when @cap_pos is non-zero,
|
||||
* also means a programming error, except device assignment, which can check
|
||||
* if a real HW is broken.
|
||||
*/
|
||||
int msix_init(struct PCIDevice *dev, uint32_t nentries,
|
||||
MemoryRegion *table_bar, uint8_t table_bar_nr,
|
||||
unsigned table_offset, MemoryRegion *pba_bar,
|
||||
uint8_t pba_bar_nr, unsigned pba_offset, uint8_t cap_pos,
|
||||
Error **errp)
|
||||
{
|
||||
int cap;
|
||||
unsigned table_size, pba_size;
|
||||
uint8_t *config;
|
||||
|
||||
/* Nothing to do if MSI is not supported by interrupt controller */
|
||||
if (!msi_nonbroken) {
|
||||
error_setg(errp, "MSI-X is not supported by interrupt controller");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (nentries < 1 || nentries > PCI_MSIX_FLAGS_QSIZE + 1) {
|
||||
error_setg(errp, "The number of MSI-X vectors is invalid");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
table_size = nentries * PCI_MSIX_ENTRY_SIZE;
|
||||
pba_size = QEMU_ALIGN_UP(nentries, 64) / 8;
|
||||
|
||||
/* Sanity test: table & pba don't overlap, fit within BARs, min aligned */
|
||||
if ((table_bar_nr == pba_bar_nr &&
|
||||
ranges_overlap(table_offset, table_size, pba_offset, pba_size)) ||
|
||||
table_offset + table_size > memory_region_size(table_bar) ||
|
||||
pba_offset + pba_size > memory_region_size(pba_bar) ||
|
||||
(table_offset | pba_offset) & PCI_MSIX_FLAGS_BIRMASK) {
|
||||
error_setg(errp, "table & pba overlap, or they don't fit in BARs,"
|
||||
" or don't align");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cap = pci_add_capability(dev, PCI_CAP_ID_MSIX,
|
||||
cap_pos, MSIX_CAP_LENGTH, errp);
|
||||
if (cap < 0) {
|
||||
return cap;
|
||||
}
|
||||
|
||||
dev->msix_cap = cap;
|
||||
dev->cap_present |= QEMU_PCI_CAP_MSIX;
|
||||
config = dev->config + cap;
|
||||
|
||||
pci_set_word(config + PCI_MSIX_FLAGS, nentries - 1);
|
||||
dev->msix_entries_nr = nentries;
|
||||
dev->msix_function_masked = true;
|
||||
|
||||
pci_set_long(config + PCI_MSIX_TABLE, table_offset | table_bar_nr);
|
||||
pci_set_long(config + PCI_MSIX_PBA, pba_offset | pba_bar_nr);
|
||||
|
||||
/* Make flags bit writable. */
|
||||
dev->wmask[cap + MSIX_CONTROL_OFFSET] |= MSIX_ENABLE_MASK |
|
||||
MSIX_MASKALL_MASK;
|
||||
|
||||
dev->msix_table = g_malloc0(table_size);
|
||||
dev->msix_pba = g_malloc0(pba_size);
|
||||
dev->msix_entry_used = g_malloc0(nentries * sizeof *dev->msix_entry_used);
|
||||
|
||||
msix_mask_all(dev, nentries);
|
||||
|
||||
memory_region_init_io(&dev->msix_table_mmio, OBJECT(dev), &msix_table_mmio_ops, dev,
|
||||
"msix-table", table_size);
|
||||
memory_region_add_subregion(table_bar, table_offset, &dev->msix_table_mmio);
|
||||
memory_region_init_io(&dev->msix_pba_mmio, OBJECT(dev), &msix_pba_mmio_ops, dev,
|
||||
"msix-pba", pba_size);
|
||||
memory_region_add_subregion(pba_bar, pba_offset, &dev->msix_pba_mmio);
|
||||
|
||||
dev->msix_prepare_message = msix_prepare_message;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msix_init_exclusive_bar(PCIDevice *dev, uint32_t nentries,
|
||||
uint8_t bar_nr, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
char *name;
|
||||
uint32_t bar_size = 4096;
|
||||
uint32_t bar_pba_offset = bar_size / 2;
|
||||
uint32_t bar_pba_size = QEMU_ALIGN_UP(nentries, 64) / 8;
|
||||
|
||||
/* Sanity-check nentries before we use it in BAR size calculations */
|
||||
if (nentries < 1 || nentries > PCI_MSIX_FLAGS_QSIZE + 1) {
|
||||
error_setg(errp, "The number of MSI-X vectors is invalid");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Migration compatibility dictates that this remains a 4k
|
||||
* BAR with the vector table in the lower half and PBA in
|
||||
* the upper half for nentries which is lower or equal to 128.
|
||||
* No need to care about using more than 65 entries for legacy
|
||||
* machine types who has at most 64 queues.
|
||||
*/
|
||||
if (nentries * PCI_MSIX_ENTRY_SIZE > bar_pba_offset) {
|
||||
bar_pba_offset = nentries * PCI_MSIX_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
if (bar_pba_offset + bar_pba_size > 4096) {
|
||||
bar_size = bar_pba_offset + bar_pba_size;
|
||||
}
|
||||
|
||||
bar_size = pow2ceil(bar_size);
|
||||
|
||||
name = g_strdup_printf("%s-msix", dev->name);
|
||||
memory_region_init(&dev->msix_exclusive_bar, OBJECT(dev), name, bar_size);
|
||||
g_free(name);
|
||||
|
||||
ret = msix_init(dev, nentries, &dev->msix_exclusive_bar, bar_nr,
|
||||
0, &dev->msix_exclusive_bar,
|
||||
bar_nr, bar_pba_offset,
|
||||
0, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
pci_register_bar(dev, bar_nr, PCI_BASE_ADDRESS_SPACE_MEMORY,
|
||||
&dev->msix_exclusive_bar);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void msix_free_irq_entries(PCIDevice *dev)
|
||||
{
|
||||
int vector;
|
||||
|
||||
for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
|
||||
dev->msix_entry_used[vector] = 0;
|
||||
msix_clr_pending(dev, vector);
|
||||
}
|
||||
}
|
||||
|
||||
static void msix_clear_all_vectors(PCIDevice *dev)
|
||||
{
|
||||
int vector;
|
||||
|
||||
for (vector = 0; vector < dev->msix_entries_nr; ++vector) {
|
||||
msix_clr_pending(dev, vector);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean up resources for the device. */
|
||||
void msix_uninit(PCIDevice *dev, MemoryRegion *table_bar, MemoryRegion *pba_bar)
|
||||
{
|
||||
if (!msix_present(dev)) {
|
||||
return;
|
||||
}
|
||||
pci_del_capability(dev, PCI_CAP_ID_MSIX, MSIX_CAP_LENGTH);
|
||||
dev->msix_cap = 0;
|
||||
msix_free_irq_entries(dev);
|
||||
dev->msix_entries_nr = 0;
|
||||
memory_region_del_subregion(pba_bar, &dev->msix_pba_mmio);
|
||||
g_free(dev->msix_pba);
|
||||
dev->msix_pba = NULL;
|
||||
memory_region_del_subregion(table_bar, &dev->msix_table_mmio);
|
||||
g_free(dev->msix_table);
|
||||
dev->msix_table = NULL;
|
||||
g_free(dev->msix_entry_used);
|
||||
dev->msix_entry_used = NULL;
|
||||
dev->cap_present &= ~QEMU_PCI_CAP_MSIX;
|
||||
dev->msix_prepare_message = NULL;
|
||||
}
|
||||
|
||||
void msix_uninit_exclusive_bar(PCIDevice *dev)
|
||||
{
|
||||
if (msix_present(dev)) {
|
||||
msix_uninit(dev, &dev->msix_exclusive_bar, &dev->msix_exclusive_bar);
|
||||
}
|
||||
}
|
||||
|
||||
void msix_save(PCIDevice *dev, QEMUFile *f)
|
||||
{
|
||||
unsigned n = dev->msix_entries_nr;
|
||||
|
||||
if (!msix_present(dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_put_buffer(f, dev->msix_table, n * PCI_MSIX_ENTRY_SIZE);
|
||||
qemu_put_buffer(f, dev->msix_pba, DIV_ROUND_UP(n, 8));
|
||||
}
|
||||
|
||||
/* Should be called after restoring the config space. */
|
||||
void msix_load(PCIDevice *dev, QEMUFile *f)
|
||||
{
|
||||
unsigned n = dev->msix_entries_nr;
|
||||
unsigned int vector;
|
||||
|
||||
if (!msix_present(dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
msix_clear_all_vectors(dev);
|
||||
qemu_get_buffer(f, dev->msix_table, n * PCI_MSIX_ENTRY_SIZE);
|
||||
qemu_get_buffer(f, dev->msix_pba, DIV_ROUND_UP(n, 8));
|
||||
msix_update_function_masked(dev);
|
||||
|
||||
for (vector = 0; vector < n; vector++) {
|
||||
msix_handle_mask_update(dev, vector, true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Does device support MSI-X? */
|
||||
int msix_present(PCIDevice *dev)
|
||||
{
|
||||
return dev->cap_present & QEMU_PCI_CAP_MSIX;
|
||||
}
|
||||
|
||||
/* Is MSI-X enabled? */
|
||||
int msix_enabled(PCIDevice *dev)
|
||||
{
|
||||
return (dev->cap_present & QEMU_PCI_CAP_MSIX) &&
|
||||
(dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &
|
||||
MSIX_ENABLE_MASK);
|
||||
}
|
||||
|
||||
/* Send an MSI-X message */
|
||||
void msix_notify(PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
MSIMessage msg;
|
||||
|
||||
assert(vector < dev->msix_entries_nr);
|
||||
|
||||
if (!dev->msix_entry_used[vector]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msix_is_masked(dev, vector)) {
|
||||
msix_set_pending(dev, vector);
|
||||
return;
|
||||
}
|
||||
|
||||
msg = msix_get_message(dev, vector);
|
||||
|
||||
msi_send_message(dev, msg);
|
||||
}
|
||||
|
||||
void msix_reset(PCIDevice *dev)
|
||||
{
|
||||
if (!msix_present(dev)) {
|
||||
return;
|
||||
}
|
||||
msix_clear_all_vectors(dev);
|
||||
dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &=
|
||||
~dev->wmask[dev->msix_cap + MSIX_CONTROL_OFFSET];
|
||||
memset(dev->msix_table, 0, dev->msix_entries_nr * PCI_MSIX_ENTRY_SIZE);
|
||||
memset(dev->msix_pba, 0, QEMU_ALIGN_UP(dev->msix_entries_nr, 64) / 8);
|
||||
msix_mask_all(dev, dev->msix_entries_nr);
|
||||
}
|
||||
|
||||
/* PCI spec suggests that devices make it possible for software to configure
|
||||
* less vectors than supported by the device, but does not specify a standard
|
||||
* mechanism for devices to do so.
|
||||
*
|
||||
* We support this by asking devices to declare vectors software is going to
|
||||
* actually use, and checking this on the notification path. Devices that
|
||||
* don't want to follow the spec suggestion can declare all vectors as used. */
|
||||
|
||||
/* Mark vector as used. */
|
||||
void msix_vector_use(PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
assert(vector < dev->msix_entries_nr);
|
||||
dev->msix_entry_used[vector]++;
|
||||
}
|
||||
|
||||
/* Mark vector as unused. */
|
||||
void msix_vector_unuse(PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
assert(vector < dev->msix_entries_nr);
|
||||
if (!dev->msix_entry_used[vector]) {
|
||||
return;
|
||||
}
|
||||
if (--dev->msix_entry_used[vector]) {
|
||||
return;
|
||||
}
|
||||
msix_clr_pending(dev, vector);
|
||||
}
|
||||
|
||||
void msix_unuse_all_vectors(PCIDevice *dev)
|
||||
{
|
||||
if (!msix_present(dev)) {
|
||||
return;
|
||||
}
|
||||
msix_free_irq_entries(dev);
|
||||
}
|
||||
|
||||
unsigned int msix_nr_vectors_allocated(const PCIDevice *dev)
|
||||
{
|
||||
return dev->msix_entries_nr;
|
||||
}
|
||||
|
||||
static int msix_set_notifier_for_vector(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
MSIMessage msg;
|
||||
|
||||
if (msix_is_masked(dev, vector)) {
|
||||
return 0;
|
||||
}
|
||||
msg = msix_get_message(dev, vector);
|
||||
return dev->msix_vector_use_notifier(dev, vector, msg);
|
||||
}
|
||||
|
||||
static void msix_unset_notifier_for_vector(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
if (msix_is_masked(dev, vector)) {
|
||||
return;
|
||||
}
|
||||
dev->msix_vector_release_notifier(dev, vector);
|
||||
}
|
||||
|
||||
int msix_set_vector_notifiers(PCIDevice *dev,
|
||||
MSIVectorUseNotifier use_notifier,
|
||||
MSIVectorReleaseNotifier release_notifier,
|
||||
MSIVectorPollNotifier poll_notifier)
|
||||
{
|
||||
int vector, ret;
|
||||
|
||||
assert(use_notifier && release_notifier);
|
||||
|
||||
dev->msix_vector_use_notifier = use_notifier;
|
||||
dev->msix_vector_release_notifier = release_notifier;
|
||||
dev->msix_vector_poll_notifier = poll_notifier;
|
||||
|
||||
if ((dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &
|
||||
(MSIX_ENABLE_MASK | MSIX_MASKALL_MASK)) == MSIX_ENABLE_MASK) {
|
||||
for (vector = 0; vector < dev->msix_entries_nr; vector++) {
|
||||
ret = msix_set_notifier_for_vector(dev, vector);
|
||||
if (ret < 0) {
|
||||
goto undo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dev->msix_vector_poll_notifier) {
|
||||
dev->msix_vector_poll_notifier(dev, 0, dev->msix_entries_nr);
|
||||
}
|
||||
return 0;
|
||||
|
||||
undo:
|
||||
while (--vector >= 0) {
|
||||
msix_unset_notifier_for_vector(dev, vector);
|
||||
}
|
||||
dev->msix_vector_use_notifier = NULL;
|
||||
dev->msix_vector_release_notifier = NULL;
|
||||
dev->msix_vector_poll_notifier = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void msix_unset_vector_notifiers(PCIDevice *dev)
|
||||
{
|
||||
int vector;
|
||||
|
||||
assert(dev->msix_vector_use_notifier &&
|
||||
dev->msix_vector_release_notifier);
|
||||
|
||||
if ((dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &
|
||||
(MSIX_ENABLE_MASK | MSIX_MASKALL_MASK)) == MSIX_ENABLE_MASK) {
|
||||
for (vector = 0; vector < dev->msix_entries_nr; vector++) {
|
||||
msix_unset_notifier_for_vector(dev, vector);
|
||||
}
|
||||
}
|
||||
dev->msix_vector_use_notifier = NULL;
|
||||
dev->msix_vector_release_notifier = NULL;
|
||||
dev->msix_vector_poll_notifier = NULL;
|
||||
}
|
||||
|
||||
static int put_msix_state(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field, JSONWriter *vmdesc)
|
||||
{
|
||||
msix_save(pv, f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_msix_state(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field)
|
||||
{
|
||||
msix_load(pv, f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateInfo vmstate_info_msix = {
|
||||
.name = "msix state",
|
||||
.get = get_msix_state,
|
||||
.put = put_msix_state,
|
||||
};
|
||||
|
||||
const VMStateDescription vmstate_msix = {
|
||||
.name = "msix",
|
||||
.fields = (const VMStateField[]) {
|
||||
{
|
||||
.name = "msix",
|
||||
.info = &vmstate_info_msix,
|
||||
.flags = VMS_SINGLE | VMS_NO_STATE,
|
||||
},
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* HMP commands related to PCI
|
||||
*
|
||||
* Copyright IBM, Corp. 2011
|
||||
*
|
||||
* Authors:
|
||||
* Anthony Liguori <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*
|
||||
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||
* GNU GPL, version 2 or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/pci_device.h"
|
||||
#include "monitor/hmp.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "pci-internal.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qobject/qdict.h"
|
||||
#include "qapi/qapi-commands-pci.h"
|
||||
#include "qemu/cutils.h"
|
||||
|
||||
static void hmp_info_pci_device(Monitor *mon, const PciDeviceInfo *dev)
|
||||
{
|
||||
PciMemoryRegionList *region;
|
||||
|
||||
monitor_printf(mon, " Bus %2" PRId64 ", ", dev->bus);
|
||||
monitor_printf(mon, "device %3" PRId64 ", function %" PRId64 ":\n",
|
||||
dev->slot, dev->function);
|
||||
monitor_printf(mon, " ");
|
||||
|
||||
if (dev->class_info->desc) {
|
||||
monitor_puts(mon, dev->class_info->desc);
|
||||
} else {
|
||||
monitor_printf(mon, "Class %04" PRId64, dev->class_info->q_class);
|
||||
}
|
||||
|
||||
monitor_printf(mon, ": PCI device %04" PRIx64 ":%04" PRIx64 "\n",
|
||||
dev->id->vendor, dev->id->device);
|
||||
if (dev->id->has_subsystem_vendor && dev->id->has_subsystem) {
|
||||
monitor_printf(mon, " PCI subsystem %04" PRIx64 ":%04" PRIx64 "\n",
|
||||
dev->id->subsystem_vendor, dev->id->subsystem);
|
||||
}
|
||||
|
||||
if (dev->has_irq) {
|
||||
monitor_printf(mon, " IRQ %" PRId64 ", pin %c\n",
|
||||
dev->irq, (char)('A' + dev->irq_pin - 1));
|
||||
}
|
||||
|
||||
if (dev->pci_bridge) {
|
||||
monitor_printf(mon, " BUS %" PRId64 ".\n",
|
||||
dev->pci_bridge->bus->number);
|
||||
monitor_printf(mon, " secondary bus %" PRId64 ".\n",
|
||||
dev->pci_bridge->bus->secondary);
|
||||
monitor_printf(mon, " subordinate bus %" PRId64 ".\n",
|
||||
dev->pci_bridge->bus->subordinate);
|
||||
|
||||
monitor_printf(mon, " IO range [0x%04"PRIx64", 0x%04"PRIx64"]\n",
|
||||
dev->pci_bridge->bus->io_range->base,
|
||||
dev->pci_bridge->bus->io_range->limit);
|
||||
|
||||
monitor_printf(mon,
|
||||
" memory range [0x%08"PRIx64", 0x%08"PRIx64"]\n",
|
||||
dev->pci_bridge->bus->memory_range->base,
|
||||
dev->pci_bridge->bus->memory_range->limit);
|
||||
|
||||
monitor_printf(mon, " prefetchable memory range "
|
||||
"[0x%08"PRIx64", 0x%08"PRIx64"]\n",
|
||||
dev->pci_bridge->bus->prefetchable_range->base,
|
||||
dev->pci_bridge->bus->prefetchable_range->limit);
|
||||
}
|
||||
|
||||
for (region = dev->regions; region; region = region->next) {
|
||||
uint64_t addr, size;
|
||||
|
||||
addr = region->value->address;
|
||||
size = region->value->size;
|
||||
|
||||
monitor_printf(mon, " BAR%" PRId64 ": ", region->value->bar);
|
||||
|
||||
if (!strcmp(region->value->type, "io")) {
|
||||
if (addr != PCI_BAR_UNMAPPED) {
|
||||
monitor_printf(mon, "I/O at 0x%04" PRIx64
|
||||
" [0x%04" PRIx64 "]\n",
|
||||
addr, addr + size - 1);
|
||||
} else {
|
||||
monitor_printf(mon, "I/O (not mapped)\n");
|
||||
}
|
||||
} else {
|
||||
if (addr != PCI_BAR_UNMAPPED) {
|
||||
monitor_printf(mon, "%d bit%s memory at 0x%08" PRIx64
|
||||
" [0x%08" PRIx64 "]\n",
|
||||
region->value->mem_type_64 ? 64 : 32,
|
||||
region->value->prefetch ? " prefetchable" : "",
|
||||
addr, addr + size - 1);
|
||||
} else {
|
||||
monitor_printf(mon, "%d bit%s memory (not mapped)\n",
|
||||
region->value->mem_type_64 ? 64 : 32,
|
||||
region->value->prefetch ? " prefetchable" : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monitor_printf(mon, " id \"%s\"\n", dev->qdev_id);
|
||||
|
||||
if (dev->pci_bridge) {
|
||||
if (dev->pci_bridge->has_devices) {
|
||||
PciDeviceInfoList *cdev;
|
||||
for (cdev = dev->pci_bridge->devices; cdev; cdev = cdev->next) {
|
||||
hmp_info_pci_device(mon, cdev->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_info_pci(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
PciInfoList *info_list, *info;
|
||||
|
||||
info_list = qmp_query_pci(&error_abort);
|
||||
|
||||
for (info = info_list; info; info = info->next) {
|
||||
PciDeviceInfoList *dev;
|
||||
|
||||
for (dev = info->value->devices; dev; dev = dev->next) {
|
||||
hmp_info_pci_device(mon, dev->value);
|
||||
}
|
||||
}
|
||||
|
||||
qapi_free_PciInfoList(info_list);
|
||||
}
|
||||
|
||||
void pcibus_dev_print(Monitor *mon, DeviceState *dev, int indent)
|
||||
{
|
||||
PCIDevice *d = (PCIDevice *)dev;
|
||||
int class = pci_get_word(d->config + PCI_CLASS_DEVICE);
|
||||
const pci_class_desc *desc = get_class_desc(class);
|
||||
char ctxt[64];
|
||||
PCIIORegion *r;
|
||||
int i;
|
||||
|
||||
if (desc->desc) {
|
||||
snprintf(ctxt, sizeof(ctxt), "%s", desc->desc);
|
||||
} else {
|
||||
snprintf(ctxt, sizeof(ctxt), "Class %04x", class);
|
||||
}
|
||||
|
||||
monitor_printf(mon, "%*sclass %s, addr %02x:%02x.%x, "
|
||||
"pci id %04x:%04x (sub %04x:%04x)\n",
|
||||
indent, "", ctxt, pci_dev_bus_num(d),
|
||||
PCI_SLOT(d->devfn), PCI_FUNC(d->devfn),
|
||||
pci_get_word(d->config + PCI_VENDOR_ID),
|
||||
pci_get_word(d->config + PCI_DEVICE_ID),
|
||||
pci_get_word(d->config + PCI_SUBSYSTEM_VENDOR_ID),
|
||||
pci_get_word(d->config + PCI_SUBSYSTEM_ID));
|
||||
for (i = 0; i < PCI_NUM_REGIONS; i++) {
|
||||
r = &d->io_regions[i];
|
||||
if (!r->size) {
|
||||
continue;
|
||||
}
|
||||
monitor_printf(mon, "%*sbar %d: %s at 0x%"FMT_PCIBUS
|
||||
" [0x%"FMT_PCIBUS"]\n",
|
||||
indent, "",
|
||||
i, r->type & PCI_BASE_ADDRESS_SPACE_IO ? "i/o" : "mem",
|
||||
r->addr, r->addr + r->size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_pcie_aer_inject_error(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
Error *err = NULL;
|
||||
const char *id = qdict_get_str(qdict, "id");
|
||||
const char *error_name;
|
||||
uint32_t error_status;
|
||||
unsigned int num;
|
||||
bool correctable;
|
||||
PCIDevice *dev;
|
||||
PCIEAERErr aer_err;
|
||||
int ret;
|
||||
|
||||
ret = pci_qdev_find_device(id, &dev);
|
||||
if (ret == -ENODEV) {
|
||||
error_setg(&err, "device '%s' not found", id);
|
||||
goto out;
|
||||
}
|
||||
if (ret < 0 || !pci_is_express(dev)) {
|
||||
error_setg(&err, "device '%s' is not a PCIe device", id);
|
||||
goto out;
|
||||
}
|
||||
|
||||
error_name = qdict_get_str(qdict, "error_status");
|
||||
if (pcie_aer_parse_error_string(error_name, &error_status, &correctable)) {
|
||||
if (qemu_strtoui(error_name, NULL, 0, &num) < 0) {
|
||||
error_setg(&err, "invalid error status value '%s'", error_name);
|
||||
goto out;
|
||||
}
|
||||
error_status = num;
|
||||
correctable = qdict_get_try_bool(qdict, "correctable", false);
|
||||
} else {
|
||||
if (qdict_haskey(qdict, "correctable")) {
|
||||
error_setg(&err, "-c is only valid with numeric error status");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
aer_err.status = error_status;
|
||||
aer_err.source_id = pci_requester_id(dev);
|
||||
|
||||
aer_err.flags = 0;
|
||||
if (correctable) {
|
||||
aer_err.flags |= PCIE_AER_ERR_IS_CORRECTABLE;
|
||||
}
|
||||
if (qdict_get_try_bool(qdict, "advisory_non_fatal", false)) {
|
||||
aer_err.flags |= PCIE_AER_ERR_MAYBE_ADVISORY;
|
||||
}
|
||||
if (qdict_haskey(qdict, "header0")) {
|
||||
aer_err.flags |= PCIE_AER_ERR_HEADER_VALID;
|
||||
}
|
||||
if (qdict_haskey(qdict, "prefix0")) {
|
||||
aer_err.flags |= PCIE_AER_ERR_TLP_PREFIX_PRESENT;
|
||||
}
|
||||
|
||||
aer_err.header[0] = qdict_get_try_int(qdict, "header0", 0);
|
||||
aer_err.header[1] = qdict_get_try_int(qdict, "header1", 0);
|
||||
aer_err.header[2] = qdict_get_try_int(qdict, "header2", 0);
|
||||
aer_err.header[3] = qdict_get_try_int(qdict, "header3", 0);
|
||||
|
||||
aer_err.prefix[0] = qdict_get_try_int(qdict, "prefix0", 0);
|
||||
aer_err.prefix[1] = qdict_get_try_int(qdict, "prefix1", 0);
|
||||
aer_err.prefix[2] = qdict_get_try_int(qdict, "prefix2", 0);
|
||||
aer_err.prefix[3] = qdict_get_try_int(qdict, "prefix3", 0);
|
||||
|
||||
ret = pcie_aer_inject_error(dev, &aer_err);
|
||||
if (ret < 0) {
|
||||
error_setg_errno(&err, -ret, "failed to inject error");
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
monitor_printf(mon, "OK id: %s root bus: %s, bus: %x devfn: %x.%x\n",
|
||||
id, pci_root_bus_path(dev), pci_dev_bus_num(dev),
|
||||
PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn));
|
||||
|
||||
out:
|
||||
hmp_handle_error(mon, err);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef HW_PCI_PCI_INTERNAL_H
|
||||
#define HW_PCI_PCI_INTERNAL_H
|
||||
|
||||
#include "qemu/queue.h"
|
||||
|
||||
typedef struct {
|
||||
uint16_t class;
|
||||
const char *desc;
|
||||
const char *fw_name;
|
||||
uint16_t fw_ign_bits;
|
||||
} pci_class_desc;
|
||||
|
||||
typedef QLIST_HEAD(, PCIHostState) PCIHostStateList;
|
||||
|
||||
extern PCIHostStateList pci_host_bridges;
|
||||
|
||||
const pci_class_desc *get_class_desc(int class);
|
||||
PCIBus *pci_find_bus_nr(PCIBus *bus, int bus_num);
|
||||
void pcibus_dev_print(Monitor *mon, DeviceState *dev, int indent);
|
||||
|
||||
int pcie_aer_parse_error_string(const char *error_name,
|
||||
uint32_t *status, bool *correctable);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* QMP commands related to PCI
|
||||
*
|
||||
* Copyright (c) 2004 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/pci_bridge.h"
|
||||
#include "pci-internal.h"
|
||||
#include "qapi/qapi-commands-pci.h"
|
||||
|
||||
static PciDeviceInfoList *qmp_query_pci_devices(PCIBus *bus, int bus_num);
|
||||
|
||||
static PciMemoryRegionList *qmp_query_pci_regions(const PCIDevice *dev)
|
||||
{
|
||||
PciMemoryRegionList *head = NULL, **tail = &head;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < PCI_NUM_REGIONS; i++) {
|
||||
const PCIIORegion *r = &dev->io_regions[i];
|
||||
PciMemoryRegion *region;
|
||||
|
||||
if (!r->size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
region = g_malloc0(sizeof(*region));
|
||||
|
||||
if (r->type & PCI_BASE_ADDRESS_SPACE_IO) {
|
||||
region->type = g_strdup("io");
|
||||
} else {
|
||||
region->type = g_strdup("memory");
|
||||
region->has_prefetch = true;
|
||||
region->prefetch = !!(r->type & PCI_BASE_ADDRESS_MEM_PREFETCH);
|
||||
region->has_mem_type_64 = true;
|
||||
region->mem_type_64 = !!(r->type & PCI_BASE_ADDRESS_MEM_TYPE_64);
|
||||
}
|
||||
|
||||
region->bar = i;
|
||||
region->address = r->addr;
|
||||
region->size = r->size;
|
||||
|
||||
QAPI_LIST_APPEND(tail, region);
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static PciBridgeInfo *qmp_query_pci_bridge(PCIDevice *dev, PCIBus *bus,
|
||||
int bus_num)
|
||||
{
|
||||
PciBridgeInfo *info;
|
||||
PciMemoryRange *range;
|
||||
|
||||
info = g_new0(PciBridgeInfo, 1);
|
||||
|
||||
info->bus = g_new0(PciBusInfo, 1);
|
||||
info->bus->number = dev->config[PCI_PRIMARY_BUS];
|
||||
info->bus->secondary = dev->config[PCI_SECONDARY_BUS];
|
||||
info->bus->subordinate = dev->config[PCI_SUBORDINATE_BUS];
|
||||
|
||||
range = info->bus->io_range = g_new0(PciMemoryRange, 1);
|
||||
range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO);
|
||||
range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO);
|
||||
|
||||
range = info->bus->memory_range = g_new0(PciMemoryRange, 1);
|
||||
range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
|
||||
range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
|
||||
|
||||
range = info->bus->prefetchable_range = g_new0(PciMemoryRange, 1);
|
||||
range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
|
||||
range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
|
||||
|
||||
if (dev->config[PCI_SECONDARY_BUS] != 0) {
|
||||
PCIBus *child_bus = pci_find_bus_nr(bus,
|
||||
dev->config[PCI_SECONDARY_BUS]);
|
||||
if (child_bus) {
|
||||
info->has_devices = true;
|
||||
info->devices = qmp_query_pci_devices(child_bus,
|
||||
dev->config[PCI_SECONDARY_BUS]);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static PciDeviceInfo *qmp_query_pci_device(PCIDevice *dev, PCIBus *bus,
|
||||
int bus_num)
|
||||
{
|
||||
const pci_class_desc *desc;
|
||||
PciDeviceInfo *info;
|
||||
uint8_t type;
|
||||
int class;
|
||||
|
||||
info = g_new0(PciDeviceInfo, 1);
|
||||
info->bus = bus_num;
|
||||
info->slot = PCI_SLOT(dev->devfn);
|
||||
info->function = PCI_FUNC(dev->devfn);
|
||||
|
||||
info->class_info = g_new0(PciDeviceClass, 1);
|
||||
class = pci_get_word(dev->config + PCI_CLASS_DEVICE);
|
||||
info->class_info->q_class = class;
|
||||
desc = get_class_desc(class);
|
||||
if (desc->desc) {
|
||||
info->class_info->desc = g_strdup(desc->desc);
|
||||
}
|
||||
|
||||
info->id = g_new0(PciDeviceId, 1);
|
||||
info->id->vendor = pci_get_word(dev->config + PCI_VENDOR_ID);
|
||||
info->id->device = pci_get_word(dev->config + PCI_DEVICE_ID);
|
||||
info->regions = qmp_query_pci_regions(dev);
|
||||
info->qdev_id = g_strdup(dev->qdev.id ? dev->qdev.id : "");
|
||||
|
||||
info->irq_pin = dev->config[PCI_INTERRUPT_PIN];
|
||||
if (dev->config[PCI_INTERRUPT_PIN] != 0) {
|
||||
info->has_irq = true;
|
||||
info->irq = dev->config[PCI_INTERRUPT_LINE];
|
||||
}
|
||||
|
||||
type = dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
|
||||
if (type == PCI_HEADER_TYPE_BRIDGE) {
|
||||
info->pci_bridge = qmp_query_pci_bridge(dev, bus, bus_num);
|
||||
} else if (type == PCI_HEADER_TYPE_NORMAL) {
|
||||
info->id->has_subsystem = info->id->has_subsystem_vendor = true;
|
||||
info->id->subsystem = pci_get_word(dev->config + PCI_SUBSYSTEM_ID);
|
||||
info->id->subsystem_vendor =
|
||||
pci_get_word(dev->config + PCI_SUBSYSTEM_VENDOR_ID);
|
||||
} else if (type == PCI_HEADER_TYPE_CARDBUS) {
|
||||
info->id->has_subsystem = info->id->has_subsystem_vendor = true;
|
||||
info->id->subsystem = pci_get_word(dev->config + PCI_CB_SUBSYSTEM_ID);
|
||||
info->id->subsystem_vendor =
|
||||
pci_get_word(dev->config + PCI_CB_SUBSYSTEM_VENDOR_ID);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static PciDeviceInfoList *qmp_query_pci_devices(PCIBus *bus, int bus_num)
|
||||
{
|
||||
PciDeviceInfoList *head = NULL, **tail = &head;
|
||||
PCIDevice *dev;
|
||||
int devfn;
|
||||
|
||||
for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
|
||||
dev = bus->devices[devfn];
|
||||
if (dev) {
|
||||
QAPI_LIST_APPEND(tail, qmp_query_pci_device(dev, bus, bus_num));
|
||||
}
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static PciInfo *qmp_query_pci_bus(PCIBus *bus, int bus_num)
|
||||
{
|
||||
PciInfo *info = NULL;
|
||||
|
||||
bus = pci_find_bus_nr(bus, bus_num);
|
||||
if (bus) {
|
||||
info = g_malloc0(sizeof(*info));
|
||||
info->bus = bus_num;
|
||||
info->devices = qmp_query_pci_devices(bus, bus_num);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
PciInfoList *qmp_query_pci(Error **errp)
|
||||
{
|
||||
PciInfoList *head = NULL, **tail = &head;
|
||||
PCIHostState *host_bridge;
|
||||
|
||||
QLIST_FOREACH(host_bridge, &pci_host_bridges, next) {
|
||||
QAPI_LIST_APPEND(tail,
|
||||
qmp_query_pci_bus(host_bridge->bus,
|
||||
pci_bus_num(host_bridge->bus)));
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* PCI stubs for platforms that don't support pci bus.
|
||||
*
|
||||
* Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "monitor/hmp.h"
|
||||
#include "qapi/qapi-commands-pci.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "hw/pci/msix.h"
|
||||
|
||||
bool msi_nonbroken;
|
||||
bool pci_available;
|
||||
|
||||
PciInfoList *qmp_query_pci(Error **errp)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void hmp_info_pci(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
}
|
||||
|
||||
void hmp_pcie_aer_inject_error(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
monitor_printf(mon, "PCI devices not supported\n");
|
||||
}
|
||||
|
||||
/* kvm-all wants this */
|
||||
MSIMessage pci_get_msi_message(PCIDevice *dev, int vector)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
uint16_t pci_requester_id(PCIDevice *dev)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
/* Required by ahci.c */
|
||||
bool msi_enabled(const PCIDevice *dev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void msi_notify(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
/* Required by target/i386/kvm.c */
|
||||
bool msi_is_masked(const PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
MSIMessage msi_get_message(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
int msix_enabled(PCIDevice *dev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool msix_is_masked(PCIDevice *dev, unsigned vector)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
||||
MSIMessage msix_get_message(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
+3485
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,511 @@
|
||||
/*
|
||||
* QEMU PCI bus manager
|
||||
*
|
||||
* Copyright (c) 2004 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to dea
|
||||
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
|
||||
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
/*
|
||||
* split out from pci.c
|
||||
* Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/units.h"
|
||||
#include "hw/pci/pci_bridge.h"
|
||||
#include "hw/pci/pci_bus.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/range.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/acpi/acpi_aml_interface.h"
|
||||
#include "hw/acpi/pci.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
|
||||
/* PCI bridge subsystem vendor ID helper functions */
|
||||
#define PCI_SSVID_SIZEOF 8
|
||||
#define PCI_SSVID_SVID 4
|
||||
#define PCI_SSVID_SSID 6
|
||||
|
||||
int pci_bridge_ssvid_init(PCIDevice *dev, uint8_t offset,
|
||||
uint16_t svid, uint16_t ssid,
|
||||
Error **errp)
|
||||
{
|
||||
int pos;
|
||||
|
||||
pos = pci_add_capability(dev, PCI_CAP_ID_SSVID, offset,
|
||||
PCI_SSVID_SIZEOF, errp);
|
||||
if (pos < 0) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
pci_set_word(dev->config + pos + PCI_SSVID_SVID, svid);
|
||||
pci_set_word(dev->config + pos + PCI_SSVID_SSID, ssid);
|
||||
return pos;
|
||||
}
|
||||
|
||||
/* Accessor function to get parent bridge device from pci bus. */
|
||||
PCIDevice *pci_bridge_get_device(PCIBus *bus)
|
||||
{
|
||||
return bus->parent_dev;
|
||||
}
|
||||
|
||||
/* Accessor function to get secondary bus from pci-to-pci bridge device */
|
||||
PCIBus *pci_bridge_get_sec_bus(PCIBridge *br)
|
||||
{
|
||||
return &br->sec_bus;
|
||||
}
|
||||
|
||||
static uint32_t pci_config_get_io_base(const PCIDevice *d,
|
||||
uint32_t base, uint32_t base_upper16)
|
||||
{
|
||||
uint32_t val;
|
||||
|
||||
val = ((uint32_t)d->config[base] & PCI_IO_RANGE_MASK) << 8;
|
||||
if (d->config[base] & PCI_IO_RANGE_TYPE_32) {
|
||||
val |= (uint32_t)pci_get_word(d->config + base_upper16) << 16;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static pcibus_t pci_config_get_memory_base(const PCIDevice *d, uint32_t base)
|
||||
{
|
||||
return ((pcibus_t)pci_get_word(d->config + base) & PCI_MEMORY_RANGE_MASK)
|
||||
<< 16;
|
||||
}
|
||||
|
||||
static pcibus_t pci_config_get_pref_base(const PCIDevice *d,
|
||||
uint32_t base, uint32_t upper)
|
||||
{
|
||||
pcibus_t tmp;
|
||||
pcibus_t val;
|
||||
|
||||
tmp = (pcibus_t)pci_get_word(d->config + base);
|
||||
val = (tmp & PCI_PREF_RANGE_MASK) << 16;
|
||||
if (tmp & PCI_PREF_RANGE_TYPE_64) {
|
||||
val |= (pcibus_t)pci_get_long(d->config + upper) << 32;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/* accessor function to get bridge filtering base address */
|
||||
pcibus_t pci_bridge_get_base(const PCIDevice *bridge, uint8_t type)
|
||||
{
|
||||
pcibus_t base;
|
||||
if (type & PCI_BASE_ADDRESS_SPACE_IO) {
|
||||
base = pci_config_get_io_base(bridge,
|
||||
PCI_IO_BASE, PCI_IO_BASE_UPPER16);
|
||||
} else {
|
||||
if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) {
|
||||
base = pci_config_get_pref_base(
|
||||
bridge, PCI_PREF_MEMORY_BASE, PCI_PREF_BASE_UPPER32);
|
||||
} else {
|
||||
base = pci_config_get_memory_base(bridge, PCI_MEMORY_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
/* accessor function to get bridge filtering limit */
|
||||
pcibus_t pci_bridge_get_limit(const PCIDevice *bridge, uint8_t type)
|
||||
{
|
||||
pcibus_t limit;
|
||||
if (type & PCI_BASE_ADDRESS_SPACE_IO) {
|
||||
limit = pci_config_get_io_base(bridge,
|
||||
PCI_IO_LIMIT, PCI_IO_LIMIT_UPPER16);
|
||||
limit |= 0xfff; /* PCI bridge spec 3.2.5.6. */
|
||||
} else {
|
||||
if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) {
|
||||
limit = pci_config_get_pref_base(
|
||||
bridge, PCI_PREF_MEMORY_LIMIT, PCI_PREF_LIMIT_UPPER32);
|
||||
} else {
|
||||
limit = pci_config_get_memory_base(bridge, PCI_MEMORY_LIMIT);
|
||||
}
|
||||
limit |= 0xfffff; /* PCI bridge spec 3.2.5.{1, 8}. */
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
static void pci_bridge_init_alias(PCIBridge *bridge, MemoryRegion *alias,
|
||||
uint8_t type, const char *name,
|
||||
MemoryRegion *space,
|
||||
MemoryRegion *parent_space,
|
||||
bool enabled)
|
||||
{
|
||||
PCIDevice *bridge_dev = PCI_DEVICE(bridge);
|
||||
pcibus_t base = pci_bridge_get_base(bridge_dev, type);
|
||||
pcibus_t limit = pci_bridge_get_limit(bridge_dev, type);
|
||||
/* TODO: this doesn't handle base = 0 limit = 2^64 - 1 correctly.
|
||||
* Apparently no way to do this with existing memory APIs. */
|
||||
pcibus_t size = enabled && limit >= base ? limit + 1 - base : 0;
|
||||
|
||||
memory_region_init_alias(alias, OBJECT(bridge), name, space, base, size);
|
||||
memory_region_add_subregion_overlap(parent_space, base, alias, 1);
|
||||
}
|
||||
|
||||
static void pci_bridge_init_vga_aliases(PCIBridge *br, PCIBus *parent,
|
||||
MemoryRegion *alias_vga)
|
||||
{
|
||||
PCIDevice *pd = PCI_DEVICE(br);
|
||||
uint16_t brctl = pci_get_word(pd->config + PCI_BRIDGE_CONTROL);
|
||||
|
||||
memory_region_init_alias(&alias_vga[QEMU_PCI_VGA_IO_LO], OBJECT(br),
|
||||
"pci_bridge_vga_io_lo", &br->address_space_io,
|
||||
QEMU_PCI_VGA_IO_LO_BASE, QEMU_PCI_VGA_IO_LO_SIZE);
|
||||
memory_region_init_alias(&alias_vga[QEMU_PCI_VGA_IO_HI], OBJECT(br),
|
||||
"pci_bridge_vga_io_hi", &br->address_space_io,
|
||||
QEMU_PCI_VGA_IO_HI_BASE, QEMU_PCI_VGA_IO_HI_SIZE);
|
||||
memory_region_init_alias(&alias_vga[QEMU_PCI_VGA_MEM], OBJECT(br),
|
||||
"pci_bridge_vga_mem", &br->address_space_mem,
|
||||
QEMU_PCI_VGA_MEM_BASE, QEMU_PCI_VGA_MEM_SIZE);
|
||||
|
||||
if (brctl & PCI_BRIDGE_CTL_VGA) {
|
||||
pci_register_vga(pd, &alias_vga[QEMU_PCI_VGA_MEM],
|
||||
&alias_vga[QEMU_PCI_VGA_IO_LO],
|
||||
&alias_vga[QEMU_PCI_VGA_IO_HI]);
|
||||
}
|
||||
}
|
||||
|
||||
static void pci_bridge_region_init(PCIBridge *br)
|
||||
{
|
||||
PCIDevice *pd = PCI_DEVICE(br);
|
||||
PCIBus *parent = pci_get_bus(pd);
|
||||
PCIBridgeWindows *w = &br->windows;
|
||||
uint16_t cmd = pci_get_word(pd->config + PCI_COMMAND);
|
||||
|
||||
pci_bridge_init_alias(br, &w->alias_pref_mem,
|
||||
PCI_BASE_ADDRESS_MEM_PREFETCH,
|
||||
"pci_bridge_pref_mem",
|
||||
&br->address_space_mem,
|
||||
parent->address_space_mem,
|
||||
cmd & PCI_COMMAND_MEMORY);
|
||||
pci_bridge_init_alias(br, &w->alias_mem,
|
||||
PCI_BASE_ADDRESS_SPACE_MEMORY,
|
||||
"pci_bridge_mem",
|
||||
&br->address_space_mem,
|
||||
parent->address_space_mem,
|
||||
cmd & PCI_COMMAND_MEMORY);
|
||||
pci_bridge_init_alias(br, &w->alias_io,
|
||||
PCI_BASE_ADDRESS_SPACE_IO,
|
||||
"pci_bridge_io",
|
||||
&br->address_space_io,
|
||||
parent->address_space_io,
|
||||
cmd & PCI_COMMAND_IO);
|
||||
|
||||
pci_bridge_init_vga_aliases(br, parent, w->alias_vga);
|
||||
}
|
||||
|
||||
static void pci_bridge_region_del(PCIBridge *br, PCIBridgeWindows *w)
|
||||
{
|
||||
PCIDevice *pd = PCI_DEVICE(br);
|
||||
PCIBus *parent = pci_get_bus(pd);
|
||||
|
||||
memory_region_del_subregion(parent->address_space_io, &w->alias_io);
|
||||
memory_region_del_subregion(parent->address_space_mem, &w->alias_mem);
|
||||
memory_region_del_subregion(parent->address_space_mem, &w->alias_pref_mem);
|
||||
pci_unregister_vga(pd);
|
||||
}
|
||||
|
||||
static void pci_bridge_region_cleanup(PCIBridge *br, PCIBridgeWindows *w)
|
||||
{
|
||||
object_unparent(OBJECT(&w->alias_io));
|
||||
object_unparent(OBJECT(&w->alias_mem));
|
||||
object_unparent(OBJECT(&w->alias_pref_mem));
|
||||
object_unparent(OBJECT(&w->alias_vga[QEMU_PCI_VGA_IO_LO]));
|
||||
object_unparent(OBJECT(&w->alias_vga[QEMU_PCI_VGA_IO_HI]));
|
||||
object_unparent(OBJECT(&w->alias_vga[QEMU_PCI_VGA_MEM]));
|
||||
}
|
||||
|
||||
void pci_bridge_update_mappings(PCIBridge *br)
|
||||
{
|
||||
PCIBridgeWindows *w = &br->windows;
|
||||
|
||||
/* Make updates atomic to: handle the case of one VCPU updating the bridge
|
||||
* while another accesses an unaffected region. */
|
||||
memory_region_transaction_begin();
|
||||
pci_bridge_region_del(br, w);
|
||||
pci_bridge_region_cleanup(br, w);
|
||||
pci_bridge_region_init(br);
|
||||
memory_region_transaction_commit();
|
||||
}
|
||||
|
||||
/* default write_config function for PCI-to-PCI bridge */
|
||||
void pci_bridge_write_config(PCIDevice *d,
|
||||
uint32_t address, uint32_t val, int len)
|
||||
{
|
||||
PCIBridge *s = PCI_BRIDGE(d);
|
||||
uint16_t oldctl = pci_get_word(d->config + PCI_BRIDGE_CONTROL);
|
||||
uint16_t newctl;
|
||||
|
||||
pci_default_write_config(d, address, val, len);
|
||||
|
||||
if (ranges_overlap(address, len, PCI_COMMAND, 2) ||
|
||||
|
||||
/* io base/limit */
|
||||
ranges_overlap(address, len, PCI_IO_BASE, 2) ||
|
||||
|
||||
/* memory base/limit, prefetchable base/limit and
|
||||
io base/limit upper 16 */
|
||||
ranges_overlap(address, len, PCI_MEMORY_BASE, 20) ||
|
||||
|
||||
/* vga enable */
|
||||
ranges_overlap(address, len, PCI_BRIDGE_CONTROL, 2)) {
|
||||
pci_bridge_update_mappings(s);
|
||||
}
|
||||
|
||||
newctl = pci_get_word(d->config + PCI_BRIDGE_CONTROL);
|
||||
if (~oldctl & newctl & PCI_BRIDGE_CTL_BUS_RESET) {
|
||||
/* Trigger hot reset on 0->1 transition. */
|
||||
bus_cold_reset(BUS(&s->sec_bus));
|
||||
}
|
||||
}
|
||||
|
||||
void pci_bridge_disable_base_limit(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *conf = dev->config;
|
||||
|
||||
pci_byte_test_and_set_mask(conf + PCI_IO_BASE,
|
||||
PCI_IO_RANGE_MASK & 0xff);
|
||||
pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT,
|
||||
PCI_IO_RANGE_MASK & 0xff);
|
||||
pci_word_test_and_set_mask(conf + PCI_MEMORY_BASE,
|
||||
PCI_MEMORY_RANGE_MASK & 0xffff);
|
||||
pci_word_test_and_clear_mask(conf + PCI_MEMORY_LIMIT,
|
||||
PCI_MEMORY_RANGE_MASK & 0xffff);
|
||||
pci_word_test_and_set_mask(conf + PCI_PREF_MEMORY_BASE,
|
||||
PCI_PREF_RANGE_MASK & 0xffff);
|
||||
pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT,
|
||||
PCI_PREF_RANGE_MASK & 0xffff);
|
||||
pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0);
|
||||
pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0);
|
||||
}
|
||||
|
||||
/* reset bridge specific configuration registers */
|
||||
void pci_bridge_reset(DeviceState *qdev)
|
||||
{
|
||||
PCIDevice *dev = PCI_DEVICE(qdev);
|
||||
uint8_t *conf = dev->config;
|
||||
|
||||
conf[PCI_PRIMARY_BUS] = 0;
|
||||
conf[PCI_SECONDARY_BUS] = 0;
|
||||
conf[PCI_SUBORDINATE_BUS] = 0;
|
||||
conf[PCI_SEC_LATENCY_TIMER] = 0;
|
||||
|
||||
/*
|
||||
* the default values for base/limit registers aren't specified
|
||||
* in the PCI-to-PCI-bridge spec. So we don't touch them here.
|
||||
* Each implementation can override it.
|
||||
* typical implementation does
|
||||
* zero base/limit registers or
|
||||
* disable forwarding: pci_bridge_disable_base_limit()
|
||||
* If disable forwarding is wanted, call pci_bridge_disable_base_limit()
|
||||
* after this function.
|
||||
*/
|
||||
pci_byte_test_and_clear_mask(conf + PCI_IO_BASE,
|
||||
PCI_IO_RANGE_MASK & 0xff);
|
||||
pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT,
|
||||
PCI_IO_RANGE_MASK & 0xff);
|
||||
pci_word_test_and_clear_mask(conf + PCI_MEMORY_BASE,
|
||||
PCI_MEMORY_RANGE_MASK & 0xffff);
|
||||
pci_word_test_and_clear_mask(conf + PCI_MEMORY_LIMIT,
|
||||
PCI_MEMORY_RANGE_MASK & 0xffff);
|
||||
pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_BASE,
|
||||
PCI_PREF_RANGE_MASK & 0xffff);
|
||||
pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT,
|
||||
PCI_PREF_RANGE_MASK & 0xffff);
|
||||
pci_set_long(conf + PCI_PREF_BASE_UPPER32, 0);
|
||||
pci_set_long(conf + PCI_PREF_LIMIT_UPPER32, 0);
|
||||
|
||||
pci_set_word(conf + PCI_BRIDGE_CONTROL, 0);
|
||||
}
|
||||
|
||||
/* default qdev initialization function for PCI-to-PCI bridge */
|
||||
void pci_bridge_initfn(PCIDevice *dev, const char *typename)
|
||||
{
|
||||
PCIBus *parent = pci_get_bus(dev);
|
||||
PCIBridge *br = PCI_BRIDGE(dev);
|
||||
PCIBus *sec_bus = &br->sec_bus;
|
||||
|
||||
pci_word_test_and_set_mask(dev->config + PCI_STATUS,
|
||||
PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK);
|
||||
|
||||
/*
|
||||
* TODO: We implement VGA Enable in the Bridge Control Register
|
||||
* therefore per the PCI to PCI bridge spec we must also implement
|
||||
* VGA Palette Snooping. When done, set this bit writable:
|
||||
*
|
||||
* pci_word_test_and_set_mask(dev->wmask + PCI_COMMAND,
|
||||
* PCI_COMMAND_VGA_PALETTE);
|
||||
*/
|
||||
|
||||
pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_PCI);
|
||||
dev->config[PCI_HEADER_TYPE] =
|
||||
(dev->config[PCI_HEADER_TYPE] & PCI_HEADER_TYPE_MULTI_FUNCTION) |
|
||||
PCI_HEADER_TYPE_BRIDGE;
|
||||
pci_set_word(dev->config + PCI_SEC_STATUS,
|
||||
PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK);
|
||||
|
||||
/*
|
||||
* If we don't specify the name, the bus will be addressed as <id>.0, where
|
||||
* id is the device id.
|
||||
* Since PCI Bridge devices have a single bus each, we don't need the index:
|
||||
* let users address the bus using the device name.
|
||||
*/
|
||||
if (!br->bus_name && dev->qdev.id && *dev->qdev.id) {
|
||||
br->bus_name = dev->qdev.id;
|
||||
}
|
||||
|
||||
qbus_init(sec_bus, sizeof(br->sec_bus), typename, DEVICE(dev),
|
||||
br->bus_name);
|
||||
sec_bus->parent_dev = dev;
|
||||
sec_bus->map_irq = br->map_irq ? br->map_irq : pci_swizzle_map_irq_fn;
|
||||
sec_bus->address_space_mem = &br->address_space_mem;
|
||||
memory_region_init(&br->address_space_mem, OBJECT(br), "pci_bridge_pci", UINT64_MAX);
|
||||
address_space_init(&br->as_mem, &br->address_space_mem,
|
||||
"pci_bridge_pci_mem");
|
||||
sec_bus->address_space_io = &br->address_space_io;
|
||||
memory_region_init(&br->address_space_io, OBJECT(br), "pci_bridge_io",
|
||||
4 * GiB);
|
||||
address_space_init(&br->as_io, &br->address_space_io, "pci_bridge_pci_io");
|
||||
pci_bridge_region_init(br);
|
||||
QLIST_INIT(&sec_bus->child);
|
||||
QLIST_INSERT_HEAD(&parent->child, sec_bus, sibling);
|
||||
|
||||
/* For express secondary buses, secondary latency timer is RO 0 */
|
||||
if (pci_bus_is_express(sec_bus) && !br->pcie_writeable_slt_bug) {
|
||||
dev->wmask[PCI_SEC_LATENCY_TIMER] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* default qdev clean up function for PCI-to-PCI bridge */
|
||||
void pci_bridge_exitfn(PCIDevice *pci_dev)
|
||||
{
|
||||
PCIBridge *s = PCI_BRIDGE(pci_dev);
|
||||
assert(QLIST_EMPTY(&s->sec_bus.child));
|
||||
QLIST_REMOVE(&s->sec_bus, sibling);
|
||||
address_space_destroy(&s->as_mem);
|
||||
address_space_destroy(&s->as_io);
|
||||
pci_bridge_region_del(s, &s->windows);
|
||||
pci_bridge_region_cleanup(s, &s->windows);
|
||||
/* object_unparent() is called automatically during device deletion */
|
||||
}
|
||||
|
||||
/*
|
||||
* before qdev initialization(qdev_init()), this function sets bus_name and
|
||||
* map_irq callback which are necessary for pci_bridge_initfn() to
|
||||
* initialize bus.
|
||||
*/
|
||||
void pci_bridge_map_irq(PCIBridge *br, const char* bus_name,
|
||||
pci_map_irq_fn map_irq)
|
||||
{
|
||||
br->map_irq = map_irq;
|
||||
br->bus_name = bus_name;
|
||||
}
|
||||
|
||||
|
||||
int pci_bridge_qemu_reserve_cap_init(PCIDevice *dev, int cap_offset,
|
||||
PCIResReserve res_reserve, Error **errp)
|
||||
{
|
||||
if (res_reserve.mem_pref_32 != (uint64_t)-1 &&
|
||||
res_reserve.mem_pref_64 != (uint64_t)-1) {
|
||||
error_setg(errp,
|
||||
"PCI resource reserve cap: PREF32 and PREF64 conflict");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (res_reserve.mem_non_pref != (uint64_t)-1 &&
|
||||
res_reserve.mem_non_pref >= 4 * GiB) {
|
||||
error_setg(errp,
|
||||
"PCI resource reserve cap: mem-reserve must be less than 4G");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (res_reserve.mem_pref_32 != (uint64_t)-1 &&
|
||||
res_reserve.mem_pref_32 >= 4 * GiB) {
|
||||
error_setg(errp,
|
||||
"PCI resource reserve cap: pref32-reserve must be less than 4G");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (res_reserve.bus == (uint32_t)-1 &&
|
||||
res_reserve.io == (uint64_t)-1 &&
|
||||
res_reserve.mem_non_pref == (uint64_t)-1 &&
|
||||
res_reserve.mem_pref_32 == (uint64_t)-1 &&
|
||||
res_reserve.mem_pref_64 == (uint64_t)-1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t cap_len = sizeof(PCIBridgeQemuCap);
|
||||
PCIBridgeQemuCap cap = {
|
||||
.len = cap_len,
|
||||
.type = REDHAT_PCI_CAP_RESOURCE_RESERVE,
|
||||
.bus_res = cpu_to_le32(res_reserve.bus),
|
||||
.io = cpu_to_le64(res_reserve.io),
|
||||
.mem = cpu_to_le32(res_reserve.mem_non_pref),
|
||||
.mem_pref_32 = cpu_to_le32(res_reserve.mem_pref_32),
|
||||
.mem_pref_64 = cpu_to_le64(res_reserve.mem_pref_64)
|
||||
};
|
||||
|
||||
int offset = pci_add_capability(dev, PCI_CAP_ID_VNDR,
|
||||
cap_offset, cap_len, errp);
|
||||
if (offset < 0) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
memcpy(dev->config + offset + PCI_CAP_FLAGS,
|
||||
(char *)&cap + PCI_CAP_FLAGS,
|
||||
cap_len - PCI_CAP_FLAGS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const Property pci_bridge_properties[] = {
|
||||
DEFINE_PROP_BOOL("x-pci-express-writeable-slt-bug", PCIBridge,
|
||||
pcie_writeable_slt_bug, false),
|
||||
};
|
||||
|
||||
static void pci_bridge_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass);
|
||||
DeviceClass *k = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_props(k, pci_bridge_properties);
|
||||
adevc->build_dev_aml = build_pci_bridge_aml;
|
||||
}
|
||||
|
||||
static const TypeInfo pci_bridge_type_info = {
|
||||
.name = TYPE_PCI_BRIDGE,
|
||||
.parent = TYPE_PCI_DEVICE,
|
||||
.instance_size = sizeof(PCIBridge),
|
||||
.class_init = pci_bridge_class_init,
|
||||
.abstract = true,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_ACPI_DEV_AML_IF },
|
||||
{ },
|
||||
},
|
||||
};
|
||||
|
||||
static void pci_bridge_register_types(void)
|
||||
{
|
||||
type_register_static(&pci_bridge_type_info);
|
||||
}
|
||||
|
||||
type_init(pci_bridge_register_types)
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* pci_host.c
|
||||
*
|
||||
* Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/pci_bridge.h"
|
||||
#include "hw/pci/pci_host.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/pci/pci_bus.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* debug PCI */
|
||||
//#define DEBUG_PCI
|
||||
|
||||
#ifdef DEBUG_PCI
|
||||
#define PCI_DPRINTF(fmt, ...) \
|
||||
do { printf("pci_host_data: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define PCI_DPRINTF(fmt, ...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* PCI address
|
||||
* bit 16 - 24: bus number
|
||||
* bit 8 - 15: devfun number
|
||||
* bit 0 - 7: offset in configuration space of a given pci device
|
||||
*/
|
||||
|
||||
/* the helper function to get a PCIDevice* for a given pci address */
|
||||
static inline PCIDevice *pci_dev_find_by_addr(PCIBus *bus, uint32_t addr)
|
||||
{
|
||||
uint8_t bus_num = addr >> 16;
|
||||
uint8_t devfn = addr >> 8;
|
||||
|
||||
return pci_find_device(bus, bus_num, devfn);
|
||||
}
|
||||
|
||||
static void pci_adjust_config_limit(PCIBus *bus, uint32_t *limit)
|
||||
{
|
||||
if ((*limit > PCI_CONFIG_SPACE_SIZE) &&
|
||||
!pci_bus_allows_extended_config_space(bus)) {
|
||||
*limit = PCI_CONFIG_SPACE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_pci_dev_ejected(PCIDevice *pci_dev)
|
||||
{
|
||||
/*
|
||||
* device unplug was requested and the guest acked it,
|
||||
* so we stop responding config accesses even if the
|
||||
* device is not deleted (failover flow)
|
||||
*/
|
||||
return pci_dev && pci_dev->partially_hotplugged &&
|
||||
!pci_dev->qdev.pending_deleted_event;
|
||||
}
|
||||
|
||||
void pci_host_config_write_common(PCIDevice *pci_dev, uint32_t addr,
|
||||
uint32_t limit, uint32_t val, uint32_t len)
|
||||
{
|
||||
pci_adjust_config_limit(pci_get_bus(pci_dev), &limit);
|
||||
if (limit <= addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 4) {
|
||||
PCI_DPRINTF("%s: invalid length access: addr " HWADDR_FMT_plx " \
|
||||
len %d val %"PRIx32"\n", __func__, addr, len, val);
|
||||
return;
|
||||
}
|
||||
|
||||
/* non-zero functions are only exposed when function 0 is present,
|
||||
* allowing direct removal of unexposed functions.
|
||||
*/
|
||||
if ((pci_dev->qdev.hotplugged && !pci_get_function_0(pci_dev)) ||
|
||||
!pci_dev->enabled || is_pci_dev_ejected(pci_dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_pci_cfg_write(pci_dev->name, pci_dev_bus_num(pci_dev),
|
||||
PCI_SLOT(pci_dev->devfn),
|
||||
PCI_FUNC(pci_dev->devfn), addr, val);
|
||||
pci_dev->config_write(pci_dev, addr, val, MIN(len, limit - addr));
|
||||
}
|
||||
|
||||
uint32_t pci_host_config_read_common(PCIDevice *pci_dev, uint32_t addr,
|
||||
uint32_t limit, uint32_t len)
|
||||
{
|
||||
uint32_t ret;
|
||||
|
||||
pci_adjust_config_limit(pci_get_bus(pci_dev), &limit);
|
||||
if (limit <= addr) {
|
||||
return ~0x0;
|
||||
}
|
||||
|
||||
if (len > 4) {
|
||||
PCI_DPRINTF("%s: invalid length access: addr " HWADDR_FMT_plx " \
|
||||
len %d val %"PRIx32"\n", __func__, addr, len, val);
|
||||
return ~0x0;
|
||||
}
|
||||
|
||||
/* non-zero functions are only exposed when function 0 is present,
|
||||
* allowing direct removal of unexposed functions.
|
||||
*/
|
||||
if ((pci_dev->qdev.hotplugged && !pci_get_function_0(pci_dev)) ||
|
||||
!pci_dev->enabled || is_pci_dev_ejected(pci_dev)) {
|
||||
return ~0x0;
|
||||
}
|
||||
|
||||
ret = pci_dev->config_read(pci_dev, addr, MIN(len, limit - addr));
|
||||
trace_pci_cfg_read(pci_dev->name, pci_dev_bus_num(pci_dev),
|
||||
PCI_SLOT(pci_dev->devfn),
|
||||
PCI_FUNC(pci_dev->devfn), addr, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pci_data_write(PCIBus *s, uint32_t addr, uint32_t val, unsigned len)
|
||||
{
|
||||
PCIDevice *pci_dev = pci_dev_find_by_addr(s, addr);
|
||||
uint32_t config_addr = addr & (PCI_CONFIG_SPACE_SIZE - 1);
|
||||
|
||||
if (!pci_dev) {
|
||||
trace_pci_cfg_write("empty", extract32(addr, 16, 8),
|
||||
extract32(addr, 11, 5), extract32(addr, 8, 3),
|
||||
config_addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
pci_host_config_write_common(pci_dev, config_addr, PCI_CONFIG_SPACE_SIZE,
|
||||
val, len);
|
||||
}
|
||||
|
||||
uint32_t pci_data_read(PCIBus *s, uint32_t addr, unsigned len)
|
||||
{
|
||||
PCIDevice *pci_dev = pci_dev_find_by_addr(s, addr);
|
||||
uint32_t config_addr = addr & (PCI_CONFIG_SPACE_SIZE - 1);
|
||||
|
||||
if (!pci_dev) {
|
||||
trace_pci_cfg_read("empty", extract32(addr, 16, 8),
|
||||
extract32(addr, 11, 5), extract32(addr, 8, 3),
|
||||
config_addr, ~0x0);
|
||||
return ~0x0;
|
||||
}
|
||||
|
||||
return pci_host_config_read_common(pci_dev, config_addr,
|
||||
PCI_CONFIG_SPACE_SIZE, len);
|
||||
}
|
||||
|
||||
static void pci_host_config_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned len)
|
||||
{
|
||||
PCIHostState *s = opaque;
|
||||
|
||||
PCI_DPRINTF("%s addr " HWADDR_FMT_plx " len %d val %"PRIx64"\n",
|
||||
__func__, addr, len, val);
|
||||
if (addr != 0 || len != 4) {
|
||||
return;
|
||||
}
|
||||
s->config_reg = val;
|
||||
}
|
||||
|
||||
static uint64_t pci_host_config_read(void *opaque, hwaddr addr,
|
||||
unsigned len)
|
||||
{
|
||||
PCIHostState *s = opaque;
|
||||
uint32_t val = s->config_reg;
|
||||
|
||||
PCI_DPRINTF("%s addr " HWADDR_FMT_plx " len %d val %"PRIx32"\n",
|
||||
__func__, addr, len, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void pci_host_data_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned len)
|
||||
{
|
||||
PCIHostState *s = opaque;
|
||||
|
||||
if (s->config_reg & (1u << 31))
|
||||
pci_data_write(s->bus, s->config_reg | (addr & 3), val, len);
|
||||
}
|
||||
|
||||
static uint64_t pci_host_data_read(void *opaque,
|
||||
hwaddr addr, unsigned len)
|
||||
{
|
||||
PCIHostState *s = opaque;
|
||||
|
||||
if (!(s->config_reg & (1U << 31))) {
|
||||
return 0xffffffff;
|
||||
}
|
||||
return pci_data_read(s->bus, s->config_reg | (addr & 3), len);
|
||||
}
|
||||
|
||||
const MemoryRegionOps pci_host_conf_le_ops = {
|
||||
.read = pci_host_config_read,
|
||||
.write = pci_host_config_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
const MemoryRegionOps pci_host_conf_be_ops = {
|
||||
.read = pci_host_config_read,
|
||||
.write = pci_host_config_write,
|
||||
.endianness = DEVICE_BIG_ENDIAN,
|
||||
};
|
||||
|
||||
const MemoryRegionOps pci_host_data_le_ops = {
|
||||
.read = pci_host_data_read,
|
||||
.write = pci_host_data_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static bool pci_host_needed(void *opaque)
|
||||
{
|
||||
PCIHostState *s = opaque;
|
||||
return s->mig_enabled;
|
||||
}
|
||||
|
||||
const VMStateDescription vmstate_pcihost = {
|
||||
.name = "PCIHost",
|
||||
.needed = pci_host_needed,
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(config_reg, PCIHostState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const Property pci_host_properties_common[] = {
|
||||
DEFINE_PROP_BOOL("x-config-reg-migration-enabled", PCIHostState,
|
||||
mig_enabled, true),
|
||||
DEFINE_PROP_BOOL(PCI_HOST_BYPASS_IOMMU, PCIHostState, bypass_iommu, false),
|
||||
};
|
||||
|
||||
static void pci_host_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
device_class_set_props(dc, pci_host_properties_common);
|
||||
dc->vmsd = &vmstate_pcihost;
|
||||
set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo pci_host_type_info = {
|
||||
.name = TYPE_PCI_HOST_BRIDGE,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.abstract = true,
|
||||
.class_size = sizeof(PCIHostBridgeClass),
|
||||
.instance_size = sizeof(PCIHostState),
|
||||
.class_init = pci_host_class_init,
|
||||
};
|
||||
|
||||
static void pci_host_register_types(void)
|
||||
{
|
||||
type_register_static(&pci_host_type_info);
|
||||
}
|
||||
|
||||
type_init(pci_host_register_types)
|
||||
+1400
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,957 @@
|
||||
/*
|
||||
* pcie_aer.c
|
||||
*
|
||||
* Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/pci/pci_bridge.h"
|
||||
#include "hw/pci/pcie.h"
|
||||
#include "hw/pci/msix.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "hw/pci/pci_bus.h"
|
||||
#include "hw/pci/pcie_regs.h"
|
||||
#include "pci-internal.h"
|
||||
|
||||
//#define DEBUG_PCIE
|
||||
#ifdef DEBUG_PCIE
|
||||
# define PCIE_DPRINTF(fmt, ...) \
|
||||
fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__)
|
||||
#else
|
||||
# define PCIE_DPRINTF(fmt, ...) do {} while (0)
|
||||
#endif
|
||||
#define PCIE_DEV_PRINTF(dev, fmt, ...) \
|
||||
PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__)
|
||||
|
||||
#define PCI_ERR_SRC_COR_OFFS 0
|
||||
#define PCI_ERR_SRC_UNCOR_OFFS 2
|
||||
|
||||
/* From 6.2.7 Error Listing and Rules. Table 6-2, 6-3 and 6-4 */
|
||||
static uint32_t pcie_aer_uncor_default_severity(uint32_t status)
|
||||
{
|
||||
switch (status) {
|
||||
case PCI_ERR_UNC_INTN:
|
||||
case PCI_ERR_UNC_DLP:
|
||||
case PCI_ERR_UNC_SDN:
|
||||
case PCI_ERR_UNC_RX_OVER:
|
||||
case PCI_ERR_UNC_FCP:
|
||||
case PCI_ERR_UNC_MALF_TLP:
|
||||
return PCI_ERR_ROOT_CMD_FATAL_EN;
|
||||
case PCI_ERR_UNC_POISON_TLP:
|
||||
case PCI_ERR_UNC_ECRC:
|
||||
case PCI_ERR_UNC_UNSUP:
|
||||
case PCI_ERR_UNC_COMP_TIME:
|
||||
case PCI_ERR_UNC_COMP_ABORT:
|
||||
case PCI_ERR_UNC_UNX_COMP:
|
||||
case PCI_ERR_UNC_ACSV:
|
||||
case PCI_ERR_UNC_MCBTLP:
|
||||
case PCI_ERR_UNC_ATOP_EBLOCKED:
|
||||
case PCI_ERR_UNC_TLP_PRF_BLOCKED:
|
||||
return PCI_ERR_ROOT_CMD_NONFATAL_EN;
|
||||
default:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
return PCI_ERR_ROOT_CMD_FATAL_EN;
|
||||
}
|
||||
|
||||
static int aer_log_add_err(PCIEAERLog *aer_log, const PCIEAERErr *err)
|
||||
{
|
||||
if (aer_log->log_num == aer_log->log_max) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(&aer_log->log[aer_log->log_num], err, sizeof *err);
|
||||
aer_log->log_num++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aer_log_del_err(PCIEAERLog *aer_log, PCIEAERErr *err)
|
||||
{
|
||||
assert(aer_log->log_num);
|
||||
*err = aer_log->log[0];
|
||||
aer_log->log_num--;
|
||||
memmove(&aer_log->log[0], &aer_log->log[1],
|
||||
aer_log->log_num * sizeof *err);
|
||||
}
|
||||
|
||||
static void aer_log_clear_all_err(PCIEAERLog *aer_log)
|
||||
{
|
||||
aer_log->log_num = 0;
|
||||
}
|
||||
|
||||
int pcie_aer_init(PCIDevice *dev, uint8_t cap_ver, uint16_t offset,
|
||||
uint16_t size, Error **errp)
|
||||
{
|
||||
pcie_add_capability(dev, PCI_EXT_CAP_ID_ERR, cap_ver,
|
||||
offset, size);
|
||||
dev->exp.aer_cap = offset;
|
||||
|
||||
/* clip down the value to avoid unreasonable memory usage */
|
||||
if (dev->exp.aer_log.log_max > PCIE_AER_LOG_MAX_LIMIT) {
|
||||
error_setg(errp, "Invalid aer_log_max %d. The max number of aer log "
|
||||
"is %d", dev->exp.aer_log.log_max, PCIE_AER_LOG_MAX_LIMIT);
|
||||
return -EINVAL;
|
||||
}
|
||||
dev->exp.aer_log.log = g_malloc0(sizeof dev->exp.aer_log.log[0] *
|
||||
dev->exp.aer_log.log_max);
|
||||
|
||||
pci_set_long(dev->w1cmask + offset + PCI_ERR_UNCOR_STATUS,
|
||||
PCI_ERR_UNC_SUPPORTED);
|
||||
|
||||
if (dev->cap_present & QEMU_PCIE_ERR_UNC_MASK) {
|
||||
pci_set_long(dev->config + offset + PCI_ERR_UNCOR_MASK,
|
||||
PCI_ERR_UNC_MASK_DEFAULT);
|
||||
pci_set_long(dev->wmask + offset + PCI_ERR_UNCOR_MASK,
|
||||
PCI_ERR_UNC_SUPPORTED);
|
||||
}
|
||||
|
||||
pci_set_long(dev->config + offset + PCI_ERR_UNCOR_SEVER,
|
||||
PCI_ERR_UNC_SEVERITY_DEFAULT);
|
||||
pci_set_long(dev->wmask + offset + PCI_ERR_UNCOR_SEVER,
|
||||
PCI_ERR_UNC_SUPPORTED);
|
||||
|
||||
pci_long_test_and_set_mask(dev->w1cmask + offset + PCI_ERR_COR_STATUS,
|
||||
PCI_ERR_COR_SUPPORTED);
|
||||
|
||||
pci_set_long(dev->config + offset + PCI_ERR_COR_MASK,
|
||||
PCI_ERR_COR_MASK_DEFAULT);
|
||||
pci_set_long(dev->wmask + offset + PCI_ERR_COR_MASK,
|
||||
PCI_ERR_COR_SUPPORTED);
|
||||
|
||||
/* capabilities and control. multiple header logging is supported */
|
||||
if (dev->exp.aer_log.log_max > 0) {
|
||||
pci_set_long(dev->config + offset + PCI_ERR_CAP,
|
||||
PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC |
|
||||
PCI_ERR_CAP_MHRC);
|
||||
pci_set_long(dev->wmask + offset + PCI_ERR_CAP,
|
||||
PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE |
|
||||
PCI_ERR_CAP_MHRE);
|
||||
} else {
|
||||
pci_set_long(dev->config + offset + PCI_ERR_CAP,
|
||||
PCI_ERR_CAP_ECRC_GENC | PCI_ERR_CAP_ECRC_CHKC);
|
||||
pci_set_long(dev->wmask + offset + PCI_ERR_CAP,
|
||||
PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE);
|
||||
}
|
||||
|
||||
switch (pcie_cap_get_type(dev)) {
|
||||
case PCI_EXP_TYPE_ROOT_PORT:
|
||||
/* this case will be set by pcie_aer_root_init() */
|
||||
/* fallthrough */
|
||||
case PCI_EXP_TYPE_DOWNSTREAM:
|
||||
case PCI_EXP_TYPE_UPSTREAM:
|
||||
pci_word_test_and_set_mask(dev->wmask + PCI_BRIDGE_CONTROL,
|
||||
PCI_BRIDGE_CTL_SERR);
|
||||
pci_long_test_and_set_mask(dev->w1cmask + PCI_STATUS,
|
||||
PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
|
||||
break;
|
||||
default:
|
||||
/* nothing */
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pcie_aer_exit(PCIDevice *dev)
|
||||
{
|
||||
g_free(dev->exp.aer_log.log);
|
||||
}
|
||||
|
||||
static void pcie_aer_update_uncor_status(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
PCIEAERLog *aer_log = &dev->exp.aer_log;
|
||||
|
||||
uint16_t i;
|
||||
for (i = 0; i < aer_log->log_num; i++) {
|
||||
pci_long_test_and_set_mask(aer_cap + PCI_ERR_UNCOR_STATUS,
|
||||
dev->exp.aer_log.log[i].status);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* return value:
|
||||
* true: error message needs to be sent up
|
||||
* false: error message is masked
|
||||
*
|
||||
* 6.2.6 Error Message Control
|
||||
* Figure 6-3
|
||||
* all pci express devices part
|
||||
*/
|
||||
static bool
|
||||
pcie_aer_msg_alldev(PCIDevice *dev, const PCIEAERMsg *msg)
|
||||
{
|
||||
uint16_t devctl = pci_get_word(dev->config + dev->exp.exp_cap +
|
||||
PCI_EXP_DEVCTL);
|
||||
if (!(pcie_aer_msg_is_uncor(msg) &&
|
||||
(pci_get_word(dev->config + PCI_COMMAND) & PCI_COMMAND_SERR)) &&
|
||||
!((msg->severity == PCI_ERR_ROOT_CMD_NONFATAL_EN) &&
|
||||
(devctl & PCI_EXP_DEVCTL_NFERE)) &&
|
||||
!((msg->severity == PCI_ERR_ROOT_CMD_COR_EN) &&
|
||||
(devctl & PCI_EXP_DEVCTL_CERE)) &&
|
||||
!((msg->severity == PCI_ERR_ROOT_CMD_FATAL_EN) &&
|
||||
(devctl & PCI_EXP_DEVCTL_FERE))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Signaled System Error
|
||||
*
|
||||
* 7.5.1.1 Command register
|
||||
* Bit 8 SERR# Enable
|
||||
*
|
||||
* When Set, this bit enables reporting of Non-fatal and Fatal
|
||||
* errors detected by the Function to the Root Complex. Note that
|
||||
* errors are reported if enabled either through this bit or through
|
||||
* the PCI Express specific bits in the Device Control register (see
|
||||
* Section 7.8.4).
|
||||
*/
|
||||
pci_word_test_and_set_mask(dev->config + PCI_STATUS,
|
||||
PCI_STATUS_SIG_SYSTEM_ERROR);
|
||||
|
||||
if (!(msg->severity &
|
||||
pci_get_word(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* send up error message */
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* return value:
|
||||
* true: error message is sent up
|
||||
* false: error message is masked
|
||||
*
|
||||
* 6.2.6 Error Message Control
|
||||
* Figure 6-3
|
||||
* virtual pci bridge part
|
||||
*/
|
||||
static bool pcie_aer_msg_vbridge(PCIDevice *dev, const PCIEAERMsg *msg)
|
||||
{
|
||||
uint16_t bridge_control = pci_get_word(dev->config + PCI_BRIDGE_CONTROL);
|
||||
|
||||
if (pcie_aer_msg_is_uncor(msg)) {
|
||||
/* Received System Error */
|
||||
pci_word_test_and_set_mask(dev->config + PCI_SEC_STATUS,
|
||||
PCI_SEC_STATUS_RCV_SYSTEM_ERROR);
|
||||
}
|
||||
|
||||
if (!(bridge_control & PCI_BRIDGE_CTL_SERR)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void pcie_aer_root_set_vector(PCIDevice *dev, unsigned int vector)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
assert(vector < PCI_ERR_ROOT_IRQ_MAX);
|
||||
pci_long_test_and_clear_mask(aer_cap + PCI_ERR_ROOT_STATUS,
|
||||
PCI_ERR_ROOT_IRQ);
|
||||
pci_long_test_and_set_mask(aer_cap + PCI_ERR_ROOT_STATUS,
|
||||
vector << PCI_ERR_ROOT_IRQ_SHIFT);
|
||||
}
|
||||
|
||||
static unsigned int pcie_aer_root_get_vector(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
|
||||
return (root_status & PCI_ERR_ROOT_IRQ) >> PCI_ERR_ROOT_IRQ_SHIFT;
|
||||
}
|
||||
|
||||
/* Given a status register, get corresponding bits in the command register */
|
||||
static uint32_t pcie_aer_status_to_cmd(uint32_t status)
|
||||
{
|
||||
uint32_t cmd = 0;
|
||||
if (status & PCI_ERR_ROOT_COR_RCV) {
|
||||
cmd |= PCI_ERR_ROOT_CMD_COR_EN;
|
||||
}
|
||||
if (status & PCI_ERR_ROOT_NONFATAL_RCV) {
|
||||
cmd |= PCI_ERR_ROOT_CMD_NONFATAL_EN;
|
||||
}
|
||||
if (status & PCI_ERR_ROOT_FATAL_RCV) {
|
||||
cmd |= PCI_ERR_ROOT_CMD_FATAL_EN;
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static void pcie_aer_root_notify(PCIDevice *dev)
|
||||
{
|
||||
if (msix_enabled(dev)) {
|
||||
msix_notify(dev, pcie_aer_root_get_vector(dev));
|
||||
} else if (msi_enabled(dev)) {
|
||||
msi_notify(dev, pcie_aer_root_get_vector(dev));
|
||||
} else if (pci_intx(dev) != -1) {
|
||||
pci_irq_assert(dev);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 6.2.6 Error Message Control
|
||||
* Figure 6-3
|
||||
* root port part
|
||||
*/
|
||||
static void pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *msg)
|
||||
{
|
||||
uint16_t cmd;
|
||||
uint8_t *aer_cap;
|
||||
uint32_t root_cmd;
|
||||
uint32_t root_status, prev_status;
|
||||
|
||||
cmd = pci_get_word(dev->config + PCI_COMMAND);
|
||||
aer_cap = dev->config + dev->exp.aer_cap;
|
||||
root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
|
||||
prev_status = root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
|
||||
|
||||
if (cmd & PCI_COMMAND_SERR) {
|
||||
/* System Error.
|
||||
*
|
||||
* The way to report System Error is platform specific and
|
||||
* it isn't implemented in qemu right now.
|
||||
* So just discard the error for now.
|
||||
* OS which cares of aer would receive errors via
|
||||
* native aer mechanisms, so this wouldn't matter.
|
||||
*/
|
||||
}
|
||||
|
||||
/* Error Message Received: Root Error Status register */
|
||||
switch (msg->severity) {
|
||||
case PCI_ERR_ROOT_CMD_COR_EN:
|
||||
if (root_status & PCI_ERR_ROOT_COR_RCV) {
|
||||
root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
|
||||
} else {
|
||||
pci_set_word(aer_cap + PCI_ERR_ROOT_ERR_SRC + PCI_ERR_SRC_COR_OFFS,
|
||||
msg->source_id);
|
||||
}
|
||||
root_status |= PCI_ERR_ROOT_COR_RCV;
|
||||
break;
|
||||
case PCI_ERR_ROOT_CMD_NONFATAL_EN:
|
||||
root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
|
||||
break;
|
||||
case PCI_ERR_ROOT_CMD_FATAL_EN:
|
||||
if (!(root_status & PCI_ERR_ROOT_UNCOR_RCV)) {
|
||||
root_status |= PCI_ERR_ROOT_FIRST_FATAL;
|
||||
}
|
||||
root_status |= PCI_ERR_ROOT_FATAL_RCV;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
if (pcie_aer_msg_is_uncor(msg)) {
|
||||
if (root_status & PCI_ERR_ROOT_UNCOR_RCV) {
|
||||
root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
|
||||
} else {
|
||||
pci_set_word(aer_cap + PCI_ERR_ROOT_ERR_SRC +
|
||||
PCI_ERR_SRC_UNCOR_OFFS, msg->source_id);
|
||||
}
|
||||
root_status |= PCI_ERR_ROOT_UNCOR_RCV;
|
||||
}
|
||||
pci_set_long(aer_cap + PCI_ERR_ROOT_STATUS, root_status);
|
||||
|
||||
/* 6.2.4.1.2 Interrupt Generation */
|
||||
/* All the above did was set some bits in the status register.
|
||||
* Specifically these that match message severity.
|
||||
* The below code relies on this fact. */
|
||||
if (!(root_cmd & msg->severity) ||
|
||||
(pcie_aer_status_to_cmd(prev_status) & root_cmd)) {
|
||||
/* Condition is not being set or was already true so nothing to do. */
|
||||
return;
|
||||
}
|
||||
|
||||
pcie_aer_root_notify(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* 6.2.6 Error Message Control Figure 6-3
|
||||
*
|
||||
* Walk up the bus tree from the device, propagate the error message.
|
||||
*/
|
||||
static void pcie_aer_msg(PCIDevice *dev, const PCIEAERMsg *msg)
|
||||
{
|
||||
uint8_t type;
|
||||
|
||||
while (dev) {
|
||||
if (!pci_is_express(dev)) {
|
||||
/* just ignore it */
|
||||
/* TODO: Shouldn't we set PCI_STATUS_SIG_SYSTEM_ERROR?
|
||||
* Consider e.g. a PCI bridge above a PCI Express device. */
|
||||
return;
|
||||
}
|
||||
|
||||
type = pcie_cap_get_type(dev);
|
||||
if ((type == PCI_EXP_TYPE_ROOT_PORT ||
|
||||
type == PCI_EXP_TYPE_UPSTREAM ||
|
||||
type == PCI_EXP_TYPE_DOWNSTREAM) &&
|
||||
!pcie_aer_msg_vbridge(dev, msg)) {
|
||||
return;
|
||||
}
|
||||
if (!pcie_aer_msg_alldev(dev, msg)) {
|
||||
return;
|
||||
}
|
||||
if (type == PCI_EXP_TYPE_ROOT_PORT) {
|
||||
pcie_aer_msg_root_port(dev, msg);
|
||||
/* Root port can notify system itself,
|
||||
or send the error message to root complex event collector. */
|
||||
/*
|
||||
* if root port is associated with an event collector,
|
||||
* return the root complex event collector here.
|
||||
* For now root complex event collector isn't supported.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
dev = pci_bridge_get_device(pci_get_bus(dev));
|
||||
}
|
||||
}
|
||||
|
||||
static void pcie_aer_update_log(PCIDevice *dev, const PCIEAERErr *err)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
uint8_t first_bit = ctz32(err->status);
|
||||
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
|
||||
int i;
|
||||
|
||||
assert(err->status);
|
||||
assert(!(err->status & (err->status - 1)));
|
||||
|
||||
errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
|
||||
errcap |= PCI_ERR_CAP_FEP(first_bit);
|
||||
|
||||
if (err->flags & PCIE_AER_ERR_HEADER_VALID) {
|
||||
for (i = 0; i < ARRAY_SIZE(err->header); ++i) {
|
||||
/* 7.10.8 Header Log Register */
|
||||
uint8_t *header_log =
|
||||
aer_cap + PCI_ERR_HEADER_LOG + i * sizeof err->header[0];
|
||||
stl_be_p(header_log, err->header[i]);
|
||||
}
|
||||
} else {
|
||||
assert(!(err->flags & PCIE_AER_ERR_TLP_PREFIX_PRESENT));
|
||||
memset(aer_cap + PCI_ERR_HEADER_LOG, 0, PCI_ERR_HEADER_LOG_SIZE);
|
||||
}
|
||||
|
||||
if ((err->flags & PCIE_AER_ERR_TLP_PREFIX_PRESENT) &&
|
||||
(pci_get_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCAP2) &
|
||||
PCI_EXP_DEVCAP2_EETLPP)) {
|
||||
for (i = 0; i < ARRAY_SIZE(err->prefix); ++i) {
|
||||
/* 7.10.12 tlp prefix log register */
|
||||
uint8_t *prefix_log =
|
||||
aer_cap + PCI_ERR_TLP_PREFIX_LOG + i * sizeof err->prefix[0];
|
||||
stl_be_p(prefix_log, err->prefix[i]);
|
||||
}
|
||||
errcap |= PCI_ERR_CAP_TLP;
|
||||
} else {
|
||||
memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0,
|
||||
PCI_ERR_TLP_PREFIX_LOG_SIZE);
|
||||
}
|
||||
pci_set_long(aer_cap + PCI_ERR_CAP, errcap);
|
||||
}
|
||||
|
||||
static void pcie_aer_clear_log(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
|
||||
pci_long_test_and_clear_mask(aer_cap + PCI_ERR_CAP,
|
||||
PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
|
||||
|
||||
memset(aer_cap + PCI_ERR_HEADER_LOG, 0, PCI_ERR_HEADER_LOG_SIZE);
|
||||
memset(aer_cap + PCI_ERR_TLP_PREFIX_LOG, 0, PCI_ERR_TLP_PREFIX_LOG_SIZE);
|
||||
}
|
||||
|
||||
static void pcie_aer_clear_error(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
|
||||
PCIEAERLog *aer_log = &dev->exp.aer_log;
|
||||
PCIEAERErr err;
|
||||
|
||||
if (!(errcap & PCI_ERR_CAP_MHRE) || !aer_log->log_num) {
|
||||
pcie_aer_clear_log(dev);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If more errors are queued, set corresponding bits in uncorrectable
|
||||
* error status.
|
||||
* We emulate uncorrectable error status register as W1CS.
|
||||
* So set bit in uncorrectable error status here again for multiple
|
||||
* error recording support.
|
||||
*
|
||||
* 6.2.4.2 Multiple Error Handling(Advanced Error Reporting Capability)
|
||||
*/
|
||||
pcie_aer_update_uncor_status(dev);
|
||||
|
||||
aer_log_del_err(aer_log, &err);
|
||||
pcie_aer_update_log(dev, &err);
|
||||
}
|
||||
|
||||
static int pcie_aer_record_error(PCIDevice *dev,
|
||||
const PCIEAERErr *err)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
|
||||
int fep = PCI_ERR_CAP_FEP(errcap);
|
||||
|
||||
assert(err->status);
|
||||
assert(!(err->status & (err->status - 1)));
|
||||
|
||||
if (errcap & PCI_ERR_CAP_MHRE &&
|
||||
(pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & (1U << fep))) {
|
||||
/* Not first error. queue error */
|
||||
if (aer_log_add_err(&dev->exp.aer_log, err) < 0) {
|
||||
/* overflow */
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
pcie_aer_update_log(dev, err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct PCIEAERInject {
|
||||
PCIDevice *dev;
|
||||
uint8_t *aer_cap;
|
||||
const PCIEAERErr *err;
|
||||
uint16_t devctl;
|
||||
uint16_t devsta;
|
||||
uint32_t error_status;
|
||||
bool unsupported_request;
|
||||
bool log_overflow;
|
||||
PCIEAERMsg msg;
|
||||
} PCIEAERInject;
|
||||
|
||||
static bool pcie_aer_inject_cor_error(PCIEAERInject *inj,
|
||||
uint32_t uncor_status,
|
||||
bool is_advisory_nonfatal)
|
||||
{
|
||||
PCIDevice *dev = inj->dev;
|
||||
|
||||
inj->devsta |= PCI_EXP_DEVSTA_CED;
|
||||
if (inj->unsupported_request) {
|
||||
inj->devsta |= PCI_EXP_DEVSTA_URD;
|
||||
}
|
||||
pci_set_word(dev->config + dev->exp.exp_cap + PCI_EXP_DEVSTA, inj->devsta);
|
||||
|
||||
if (inj->aer_cap) {
|
||||
uint32_t mask;
|
||||
pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_COR_STATUS,
|
||||
inj->error_status);
|
||||
mask = pci_get_long(inj->aer_cap + PCI_ERR_COR_MASK);
|
||||
if (mask & inj->error_status) {
|
||||
return false;
|
||||
}
|
||||
if (is_advisory_nonfatal) {
|
||||
uint32_t uncor_mask =
|
||||
pci_get_long(inj->aer_cap + PCI_ERR_UNCOR_MASK);
|
||||
if (!(uncor_mask & uncor_status)) {
|
||||
inj->log_overflow = !!pcie_aer_record_error(dev, inj->err);
|
||||
}
|
||||
pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_UNCOR_STATUS,
|
||||
uncor_status);
|
||||
}
|
||||
}
|
||||
|
||||
if (inj->unsupported_request && !(inj->devctl & PCI_EXP_DEVCTL_URRE)) {
|
||||
return false;
|
||||
}
|
||||
if (!(inj->devctl & PCI_EXP_DEVCTL_CERE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inj->msg.severity = PCI_ERR_ROOT_CMD_COR_EN;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pcie_aer_inject_uncor_error(PCIEAERInject *inj, bool is_fatal)
|
||||
{
|
||||
PCIDevice *dev = inj->dev;
|
||||
uint16_t cmd;
|
||||
|
||||
if (is_fatal) {
|
||||
inj->devsta |= PCI_EXP_DEVSTA_FED;
|
||||
} else {
|
||||
inj->devsta |= PCI_EXP_DEVSTA_NFED;
|
||||
}
|
||||
if (inj->unsupported_request) {
|
||||
inj->devsta |= PCI_EXP_DEVSTA_URD;
|
||||
}
|
||||
pci_set_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVSTA, inj->devsta);
|
||||
|
||||
if (inj->aer_cap) {
|
||||
uint32_t mask = pci_get_long(inj->aer_cap + PCI_ERR_UNCOR_MASK);
|
||||
if (mask & inj->error_status) {
|
||||
pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_UNCOR_STATUS,
|
||||
inj->error_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
inj->log_overflow = !!pcie_aer_record_error(dev, inj->err);
|
||||
pci_long_test_and_set_mask(inj->aer_cap + PCI_ERR_UNCOR_STATUS,
|
||||
inj->error_status);
|
||||
}
|
||||
|
||||
cmd = pci_get_word(dev->config + PCI_COMMAND);
|
||||
if (inj->unsupported_request &&
|
||||
!(inj->devctl & PCI_EXP_DEVCTL_URRE) && !(cmd & PCI_COMMAND_SERR)) {
|
||||
return false;
|
||||
}
|
||||
if (is_fatal) {
|
||||
if (!((cmd & PCI_COMMAND_SERR) ||
|
||||
(inj->devctl & PCI_EXP_DEVCTL_FERE))) {
|
||||
return false;
|
||||
}
|
||||
inj->msg.severity = PCI_ERR_ROOT_CMD_FATAL_EN;
|
||||
} else {
|
||||
if (!((cmd & PCI_COMMAND_SERR) ||
|
||||
(inj->devctl & PCI_EXP_DEVCTL_NFERE))) {
|
||||
return false;
|
||||
}
|
||||
inj->msg.severity = PCI_ERR_ROOT_CMD_NONFATAL_EN;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* non-Function specific error must be recorded in all functions.
|
||||
* It is the responsibility of the caller of this function.
|
||||
* It is also caller's responsibility to determine which function should
|
||||
* report the error.
|
||||
*
|
||||
* 6.2.4 Error Logging
|
||||
* 6.2.5 Sequence of Device Error Signaling and Logging Operations
|
||||
* Figure 6-2: Flowchart Showing Sequence of Device Error Signaling and Logging
|
||||
* Operations
|
||||
*/
|
||||
int pcie_aer_inject_error(PCIDevice *dev, const PCIEAERErr *err)
|
||||
{
|
||||
uint8_t *aer_cap = NULL;
|
||||
uint16_t devctl = 0;
|
||||
uint16_t devsta = 0;
|
||||
uint32_t error_status = err->status;
|
||||
PCIEAERInject inj;
|
||||
|
||||
if (!pci_is_express(dev)) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
|
||||
error_status &= PCI_ERR_COR_SUPPORTED;
|
||||
} else {
|
||||
error_status &= PCI_ERR_UNC_SUPPORTED;
|
||||
}
|
||||
|
||||
/* invalid status bit. one and only one bit must be set */
|
||||
if (!error_status || (error_status & (error_status - 1))) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (dev->exp.aer_cap) {
|
||||
uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
|
||||
aer_cap = dev->config + dev->exp.aer_cap;
|
||||
devctl = pci_get_long(exp_cap + PCI_EXP_DEVCTL);
|
||||
devsta = pci_get_long(exp_cap + PCI_EXP_DEVSTA);
|
||||
}
|
||||
|
||||
inj.dev = dev;
|
||||
inj.aer_cap = aer_cap;
|
||||
inj.err = err;
|
||||
inj.devctl = devctl;
|
||||
inj.devsta = devsta;
|
||||
inj.error_status = error_status;
|
||||
inj.unsupported_request = !(err->flags & PCIE_AER_ERR_IS_CORRECTABLE) &&
|
||||
err->status == PCI_ERR_UNC_UNSUP;
|
||||
inj.log_overflow = false;
|
||||
|
||||
if (err->flags & PCIE_AER_ERR_IS_CORRECTABLE) {
|
||||
if (!pcie_aer_inject_cor_error(&inj, 0, false)) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
bool is_fatal =
|
||||
pcie_aer_uncor_default_severity(error_status) ==
|
||||
PCI_ERR_ROOT_CMD_FATAL_EN;
|
||||
if (aer_cap) {
|
||||
is_fatal =
|
||||
error_status & pci_get_long(aer_cap + PCI_ERR_UNCOR_SEVER);
|
||||
}
|
||||
if (!is_fatal && (err->flags & PCIE_AER_ERR_MAYBE_ADVISORY)) {
|
||||
inj.error_status = PCI_ERR_COR_ADV_NONFATAL;
|
||||
if (!pcie_aer_inject_cor_error(&inj, error_status, true)) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (!pcie_aer_inject_uncor_error(&inj, is_fatal)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* send up error message */
|
||||
inj.msg.source_id = err->source_id;
|
||||
pcie_aer_msg(dev, &inj.msg);
|
||||
|
||||
if (inj.log_overflow) {
|
||||
PCIEAERErr header_log_overflow = {
|
||||
.status = PCI_ERR_COR_HL_OVERFLOW,
|
||||
.flags = PCIE_AER_ERR_IS_CORRECTABLE,
|
||||
};
|
||||
int ret = pcie_aer_inject_error(dev, &header_log_overflow);
|
||||
assert(!ret);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pcie_aer_write_config(PCIDevice *dev,
|
||||
uint32_t addr, uint32_t val, int len)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
|
||||
uint32_t first_error = 1U << PCI_ERR_CAP_FEP(errcap);
|
||||
uint32_t uncorsta = pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS);
|
||||
|
||||
/* uncorrectable error */
|
||||
if (!(uncorsta & first_error)) {
|
||||
/* the bit that corresponds to the first error is cleared */
|
||||
pcie_aer_clear_error(dev);
|
||||
} else if (errcap & PCI_ERR_CAP_MHRE) {
|
||||
/* When PCI_ERR_CAP_MHRE is enabled and the first error isn't cleared
|
||||
* nothing should happen. So we have to revert the modification to
|
||||
* the register.
|
||||
*/
|
||||
pcie_aer_update_uncor_status(dev);
|
||||
} else {
|
||||
/* capability & control
|
||||
* PCI_ERR_CAP_MHRE might be cleared, so clear of header log.
|
||||
*/
|
||||
aer_log_clear_all_err(&dev->exp.aer_log);
|
||||
}
|
||||
}
|
||||
|
||||
void pcie_aer_root_init(PCIDevice *dev)
|
||||
{
|
||||
uint16_t pos = dev->exp.aer_cap;
|
||||
|
||||
pci_set_long(dev->wmask + pos + PCI_ERR_ROOT_COMMAND,
|
||||
PCI_ERR_ROOT_CMD_EN_MASK);
|
||||
pci_set_long(dev->w1cmask + pos + PCI_ERR_ROOT_STATUS,
|
||||
PCI_ERR_ROOT_STATUS_REPORT_MASK);
|
||||
/* PCI_ERR_ROOT_IRQ is RO but devices change it using a
|
||||
* device-specific method.
|
||||
*/
|
||||
pci_set_long(dev->cmask + pos + PCI_ERR_ROOT_STATUS,
|
||||
~PCI_ERR_ROOT_IRQ);
|
||||
}
|
||||
|
||||
void pcie_aer_root_reset(PCIDevice *dev)
|
||||
{
|
||||
uint8_t* aer_cap = dev->config + dev->exp.aer_cap;
|
||||
|
||||
pci_set_long(aer_cap + PCI_ERR_ROOT_COMMAND, 0);
|
||||
|
||||
/*
|
||||
* Advanced Error Interrupt Message Number in Root Error Status Register
|
||||
* must be updated by chip dependent code because it's chip dependent
|
||||
* which number is used.
|
||||
*/
|
||||
}
|
||||
|
||||
void pcie_aer_root_write_config(PCIDevice *dev,
|
||||
uint32_t addr, uint32_t val, int len,
|
||||
uint32_t root_cmd_prev)
|
||||
{
|
||||
uint8_t *aer_cap = dev->config + dev->exp.aer_cap;
|
||||
uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
|
||||
uint32_t enabled_cmd = pcie_aer_status_to_cmd(root_status);
|
||||
uint32_t root_cmd = pci_get_long(aer_cap + PCI_ERR_ROOT_COMMAND);
|
||||
/* 6.2.4.1.2 Interrupt Generation */
|
||||
if (!msix_enabled(dev) && !msi_enabled(dev)) {
|
||||
if (pci_intx(dev) != -1) {
|
||||
pci_set_irq(dev, !!(root_cmd & enabled_cmd));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((root_cmd_prev & enabled_cmd) || !(root_cmd & enabled_cmd)) {
|
||||
/* Send MSI on transition from false to true. */
|
||||
return;
|
||||
}
|
||||
|
||||
pcie_aer_root_notify(dev);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_pcie_aer_err = {
|
||||
.name = "PCIE_AER_ERROR",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(status, PCIEAERErr),
|
||||
VMSTATE_UINT16(source_id, PCIEAERErr),
|
||||
VMSTATE_UINT16(flags, PCIEAERErr),
|
||||
VMSTATE_UINT32_ARRAY(header, PCIEAERErr, 4),
|
||||
VMSTATE_UINT32_ARRAY(prefix, PCIEAERErr, 4),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static bool pcie_aer_state_log_num_valid(void *opaque, int version_id)
|
||||
{
|
||||
PCIEAERLog *s = opaque;
|
||||
|
||||
return s->log_num <= s->log_max;
|
||||
}
|
||||
|
||||
const VMStateDescription vmstate_pcie_aer_log = {
|
||||
.name = "PCIE_AER_ERROR_LOG",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT16(log_num, PCIEAERLog),
|
||||
VMSTATE_UINT16_EQUAL(log_max, PCIEAERLog),
|
||||
VMSTATE_VALIDATE("log_num <= log_max", pcie_aer_state_log_num_valid),
|
||||
VMSTATE_STRUCT_VARRAY_POINTER_UINT16(log, PCIEAERLog, log_num,
|
||||
vmstate_pcie_aer_err, PCIEAERErr),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
typedef struct PCIEAERErrorName {
|
||||
const char *name;
|
||||
uint32_t val;
|
||||
bool correctable;
|
||||
} PCIEAERErrorName;
|
||||
|
||||
/*
|
||||
* AER error name -> value conversion table
|
||||
* This naming scheme is same to linux aer-injection tool.
|
||||
*/
|
||||
static const struct PCIEAERErrorName pcie_aer_error_list[] = {
|
||||
{
|
||||
.name = "DLP",
|
||||
.val = PCI_ERR_UNC_DLP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "SDN",
|
||||
.val = PCI_ERR_UNC_SDN,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "POISON_TLP",
|
||||
.val = PCI_ERR_UNC_POISON_TLP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "FCP",
|
||||
.val = PCI_ERR_UNC_FCP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "COMP_TIME",
|
||||
.val = PCI_ERR_UNC_COMP_TIME,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "COMP_ABORT",
|
||||
.val = PCI_ERR_UNC_COMP_ABORT,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "UNX_COMP",
|
||||
.val = PCI_ERR_UNC_UNX_COMP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "RX_OVER",
|
||||
.val = PCI_ERR_UNC_RX_OVER,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "MALF_TLP",
|
||||
.val = PCI_ERR_UNC_MALF_TLP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "ECRC",
|
||||
.val = PCI_ERR_UNC_ECRC,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "UNSUP",
|
||||
.val = PCI_ERR_UNC_UNSUP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "ACSV",
|
||||
.val = PCI_ERR_UNC_ACSV,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "INTN",
|
||||
.val = PCI_ERR_UNC_INTN,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "MCBTLP",
|
||||
.val = PCI_ERR_UNC_MCBTLP,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "ATOP_EBLOCKED",
|
||||
.val = PCI_ERR_UNC_ATOP_EBLOCKED,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "TLP_PRF_BLOCKED",
|
||||
.val = PCI_ERR_UNC_TLP_PRF_BLOCKED,
|
||||
.correctable = false,
|
||||
}, {
|
||||
.name = "RCVR",
|
||||
.val = PCI_ERR_COR_RCVR,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "BAD_TLP",
|
||||
.val = PCI_ERR_COR_BAD_TLP,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "BAD_DLLP",
|
||||
.val = PCI_ERR_COR_BAD_DLLP,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "REP_ROLL",
|
||||
.val = PCI_ERR_COR_REP_ROLL,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "REP_TIMER",
|
||||
.val = PCI_ERR_COR_REP_TIMER,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "ADV_NONFATAL",
|
||||
.val = PCI_ERR_COR_ADV_NONFATAL,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "INTERNAL",
|
||||
.val = PCI_ERR_COR_INTERNAL,
|
||||
.correctable = true,
|
||||
}, {
|
||||
.name = "HL_OVERFLOW",
|
||||
.val = PCI_ERR_COR_HL_OVERFLOW,
|
||||
.correctable = true,
|
||||
},
|
||||
};
|
||||
|
||||
int pcie_aer_parse_error_string(const char *error_name,
|
||||
uint32_t *status, bool *correctable)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pcie_aer_error_list); i++) {
|
||||
const PCIEAERErrorName *e = &pcie_aer_error_list[i];
|
||||
if (strcmp(error_name, e->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*status = e->val;
|
||||
*correctable = e->correctable;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* PCIe Data Object Exchange
|
||||
*
|
||||
* Copyright (C) 2021 Avery Design Systems, Inc.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/range.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/pcie.h"
|
||||
#include "hw/pci/pcie_doe.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "hw/pci/msix.h"
|
||||
|
||||
#define DWORD_BYTE 4
|
||||
|
||||
typedef struct DoeDiscoveryReq {
|
||||
DOEHeader header;
|
||||
uint8_t index;
|
||||
uint8_t reserved[3];
|
||||
} QEMU_PACKED DoeDiscoveryReq;
|
||||
|
||||
typedef struct DoeDiscoveryRsp {
|
||||
DOEHeader header;
|
||||
uint16_t vendor_id;
|
||||
uint8_t data_obj_type;
|
||||
uint8_t next_index;
|
||||
} QEMU_PACKED DoeDiscoveryRsp;
|
||||
|
||||
static bool pcie_doe_discovery(DOECap *doe_cap)
|
||||
{
|
||||
DoeDiscoveryReq *req = pcie_doe_get_write_mbox_ptr(doe_cap);
|
||||
DoeDiscoveryRsp rsp;
|
||||
uint8_t index = req->index;
|
||||
DOEProtocol *prot;
|
||||
|
||||
/* Discard request if length does not match DoeDiscoveryReq */
|
||||
if (pcie_doe_get_obj_len(req) <
|
||||
DIV_ROUND_UP(sizeof(DoeDiscoveryReq), DWORD_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rsp.header = (DOEHeader) {
|
||||
.vendor_id = PCI_VENDOR_ID_PCI_SIG,
|
||||
.data_obj_type = PCI_SIG_DOE_DISCOVERY,
|
||||
.length = DIV_ROUND_UP(sizeof(DoeDiscoveryRsp), DWORD_BYTE),
|
||||
};
|
||||
|
||||
/* Point to the requested protocol, index 0 must be Discovery */
|
||||
if (index == 0) {
|
||||
rsp.vendor_id = PCI_VENDOR_ID_PCI_SIG;
|
||||
rsp.data_obj_type = PCI_SIG_DOE_DISCOVERY;
|
||||
} else {
|
||||
if (index < doe_cap->protocol_num) {
|
||||
prot = &doe_cap->protocols[index - 1];
|
||||
rsp.vendor_id = prot->vendor_id;
|
||||
rsp.data_obj_type = prot->data_obj_type;
|
||||
} else {
|
||||
rsp.vendor_id = 0xFFFF;
|
||||
rsp.data_obj_type = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
if (index + 1 == doe_cap->protocol_num) {
|
||||
rsp.next_index = 0;
|
||||
} else {
|
||||
rsp.next_index = index + 1;
|
||||
}
|
||||
|
||||
pcie_doe_set_rsp(doe_cap, &rsp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void pcie_doe_reset_write_mbox(DOECap *st)
|
||||
{
|
||||
st->write_mbox_len = 0;
|
||||
|
||||
memset(st->write_mbox, 0, PCI_DOE_DW_SIZE_MAX * DWORD_BYTE);
|
||||
}
|
||||
|
||||
static void pcie_doe_reset_mbox(DOECap *st)
|
||||
{
|
||||
st->read_mbox_idx = 0;
|
||||
st->read_mbox_len = 0;
|
||||
|
||||
memset(st->read_mbox, 0, PCI_DOE_DW_SIZE_MAX * DWORD_BYTE);
|
||||
|
||||
pcie_doe_reset_write_mbox(st);
|
||||
}
|
||||
|
||||
void pcie_doe_init(PCIDevice *dev, DOECap *doe_cap, uint16_t offset,
|
||||
DOEProtocol *protocols, bool intr, uint16_t vec)
|
||||
{
|
||||
pcie_add_capability(dev, PCI_EXT_CAP_ID_DOE, 0x1, offset,
|
||||
PCI_DOE_SIZEOF);
|
||||
|
||||
doe_cap->pdev = dev;
|
||||
doe_cap->offset = offset;
|
||||
|
||||
if (intr && (msi_present(dev) || msix_present(dev))) {
|
||||
doe_cap->cap.intr = intr;
|
||||
doe_cap->cap.vec = vec;
|
||||
}
|
||||
|
||||
doe_cap->write_mbox = g_malloc0(PCI_DOE_DW_SIZE_MAX * DWORD_BYTE);
|
||||
doe_cap->read_mbox = g_malloc0(PCI_DOE_DW_SIZE_MAX * DWORD_BYTE);
|
||||
|
||||
pcie_doe_reset_mbox(doe_cap);
|
||||
|
||||
doe_cap->protocols = protocols;
|
||||
for (; protocols->vendor_id; protocols++) {
|
||||
doe_cap->protocol_num++;
|
||||
}
|
||||
assert(doe_cap->protocol_num < PCI_DOE_PROTOCOL_NUM_MAX);
|
||||
|
||||
/* Increment to allow for the discovery protocol */
|
||||
doe_cap->protocol_num++;
|
||||
}
|
||||
|
||||
void pcie_doe_fini(DOECap *doe_cap)
|
||||
{
|
||||
g_free(doe_cap->read_mbox);
|
||||
g_free(doe_cap->write_mbox);
|
||||
g_free(doe_cap);
|
||||
}
|
||||
|
||||
uint32_t pcie_doe_build_protocol(DOEProtocol *p)
|
||||
{
|
||||
return DATA_OBJ_BUILD_HEADER1(p->vendor_id, p->data_obj_type);
|
||||
}
|
||||
|
||||
void *pcie_doe_get_write_mbox_ptr(DOECap *doe_cap)
|
||||
{
|
||||
return doe_cap->write_mbox;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the response to read mailbox buffer
|
||||
* This might be called in self-defined handle_request() if a DOE response is
|
||||
* required in the corresponding protocol
|
||||
*/
|
||||
void pcie_doe_set_rsp(DOECap *doe_cap, void *rsp)
|
||||
{
|
||||
uint32_t len = pcie_doe_get_obj_len(rsp);
|
||||
|
||||
memcpy(doe_cap->read_mbox + doe_cap->read_mbox_len, rsp, len * DWORD_BYTE);
|
||||
doe_cap->read_mbox_len += len;
|
||||
}
|
||||
|
||||
uint32_t pcie_doe_get_obj_len(void *obj)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Only lower 18 bits are valid */
|
||||
len = DATA_OBJ_LEN_MASK(((DOEHeader *)obj)->length);
|
||||
|
||||
/* PCIe r6.0 Table 6.29: a value of 00000h indicates 2^18 DW */
|
||||
return (len) ? len : PCI_DOE_DW_SIZE_MAX;
|
||||
}
|
||||
|
||||
static void pcie_doe_irq_assert(DOECap *doe_cap)
|
||||
{
|
||||
PCIDevice *dev = doe_cap->pdev;
|
||||
|
||||
if (doe_cap->cap.intr && doe_cap->ctrl.intr) {
|
||||
if (doe_cap->status.intr) {
|
||||
return;
|
||||
}
|
||||
doe_cap->status.intr = 1;
|
||||
|
||||
if (msix_enabled(dev)) {
|
||||
msix_notify(dev, doe_cap->cap.vec);
|
||||
} else if (msi_enabled(dev)) {
|
||||
msi_notify(dev, doe_cap->cap.vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pcie_doe_set_ready(DOECap *doe_cap, bool rdy)
|
||||
{
|
||||
doe_cap->status.ready = rdy;
|
||||
|
||||
if (rdy) {
|
||||
pcie_doe_irq_assert(doe_cap);
|
||||
}
|
||||
}
|
||||
|
||||
static void pcie_doe_set_error(DOECap *doe_cap, bool err)
|
||||
{
|
||||
doe_cap->status.error = err;
|
||||
|
||||
if (err) {
|
||||
pcie_doe_irq_assert(doe_cap);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check incoming request in write_mbox for protocol format
|
||||
*/
|
||||
static void pcie_doe_prepare_rsp(DOECap *doe_cap)
|
||||
{
|
||||
bool success = false;
|
||||
int p;
|
||||
bool (*handle_request)(DOECap *) = NULL;
|
||||
|
||||
if (doe_cap->status.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (doe_cap->write_mbox[0] ==
|
||||
DATA_OBJ_BUILD_HEADER1(PCI_VENDOR_ID_PCI_SIG, PCI_SIG_DOE_DISCOVERY)) {
|
||||
handle_request = pcie_doe_discovery;
|
||||
} else {
|
||||
for (p = 0; p < doe_cap->protocol_num - 1; p++) {
|
||||
if (doe_cap->write_mbox[0] ==
|
||||
pcie_doe_build_protocol(&doe_cap->protocols[p])) {
|
||||
handle_request = doe_cap->protocols[p].handle_request;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PCIe r6 DOE 6.30.1:
|
||||
* If the number of DW transferred does not match the
|
||||
* indicated Length for a data object, then the
|
||||
* data object must be silently discarded.
|
||||
*/
|
||||
if (handle_request && (doe_cap->write_mbox_len ==
|
||||
pcie_doe_get_obj_len(pcie_doe_get_write_mbox_ptr(doe_cap)))) {
|
||||
success = handle_request(doe_cap);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
pcie_doe_set_ready(doe_cap, 1);
|
||||
} else {
|
||||
pcie_doe_reset_mbox(doe_cap);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from DOE config space.
|
||||
* Return false if the address not within DOE_CAP range.
|
||||
*/
|
||||
bool pcie_doe_read_config(DOECap *doe_cap, uint32_t addr, int size,
|
||||
uint32_t *buf)
|
||||
{
|
||||
uint32_t shift;
|
||||
uint16_t doe_offset = doe_cap->offset;
|
||||
|
||||
if (!range_covers_byte(doe_offset + PCI_EXP_DOE_CAP,
|
||||
PCI_DOE_SIZEOF - 4, addr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
addr -= doe_offset;
|
||||
*buf = 0;
|
||||
|
||||
if (range_covers_byte(PCI_EXP_DOE_CAP, DWORD_BYTE, addr)) {
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_REG, INTR_SUPP,
|
||||
doe_cap->cap.intr);
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_REG, DOE_INTR_MSG_NUM,
|
||||
doe_cap->cap.vec);
|
||||
} else if (range_covers_byte(PCI_EXP_DOE_CTRL, DWORD_BYTE, addr)) {
|
||||
/* Must return ABORT=0 and GO=0 */
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_CONTROL, DOE_INTR_EN,
|
||||
doe_cap->ctrl.intr);
|
||||
} else if (range_covers_byte(PCI_EXP_DOE_STATUS, DWORD_BYTE, addr)) {
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_STATUS, DOE_BUSY,
|
||||
doe_cap->status.busy);
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_STATUS, DOE_INTR_STATUS,
|
||||
doe_cap->status.intr);
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_STATUS, DOE_ERROR,
|
||||
doe_cap->status.error);
|
||||
*buf = FIELD_DP32(*buf, PCI_DOE_CAP_STATUS, DATA_OBJ_RDY,
|
||||
doe_cap->status.ready);
|
||||
/* Mailbox should be DW accessed */
|
||||
} else if (addr == PCI_EXP_DOE_RD_DATA_MBOX && size == DWORD_BYTE) {
|
||||
if (doe_cap->status.ready && !doe_cap->status.error) {
|
||||
*buf = doe_cap->read_mbox[doe_cap->read_mbox_idx];
|
||||
}
|
||||
}
|
||||
|
||||
/* Process Alignment */
|
||||
shift = addr % DWORD_BYTE;
|
||||
*buf = extract32(*buf, shift * 8, size * 8);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to DOE config space.
|
||||
* Return if the address not within DOE_CAP range or receives an abort
|
||||
*/
|
||||
void pcie_doe_write_config(DOECap *doe_cap,
|
||||
uint32_t addr, uint32_t val, int size)
|
||||
{
|
||||
uint16_t doe_offset = doe_cap->offset;
|
||||
uint32_t shift;
|
||||
|
||||
if (!range_covers_byte(doe_offset + PCI_EXP_DOE_CAP,
|
||||
PCI_DOE_SIZEOF - 4, addr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Process Alignment */
|
||||
shift = addr % DWORD_BYTE;
|
||||
addr -= (doe_offset + shift);
|
||||
val = deposit32(val, shift * 8, size * 8, val);
|
||||
|
||||
switch (addr) {
|
||||
case PCI_EXP_DOE_CTRL:
|
||||
if (FIELD_EX32(val, PCI_DOE_CAP_CONTROL, DOE_ABORT)) {
|
||||
pcie_doe_set_ready(doe_cap, 0);
|
||||
pcie_doe_set_error(doe_cap, 0);
|
||||
pcie_doe_reset_mbox(doe_cap);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FIELD_EX32(val, PCI_DOE_CAP_CONTROL, DOE_GO)) {
|
||||
pcie_doe_prepare_rsp(doe_cap);
|
||||
}
|
||||
|
||||
if (FIELD_EX32(val, PCI_DOE_CAP_CONTROL, DOE_INTR_EN)) {
|
||||
doe_cap->ctrl.intr = 1;
|
||||
/* Clear interrupt bit located within the first byte */
|
||||
} else if (shift == 0) {
|
||||
doe_cap->ctrl.intr = 0;
|
||||
}
|
||||
break;
|
||||
case PCI_EXP_DOE_STATUS:
|
||||
if (FIELD_EX32(val, PCI_DOE_CAP_STATUS, DOE_INTR_STATUS)) {
|
||||
doe_cap->status.intr = 0;
|
||||
}
|
||||
break;
|
||||
case PCI_EXP_DOE_RD_DATA_MBOX:
|
||||
/* Mailbox should be DW accessed */
|
||||
if (size != DWORD_BYTE) {
|
||||
return;
|
||||
}
|
||||
doe_cap->read_mbox_idx++;
|
||||
if (doe_cap->read_mbox_idx == doe_cap->read_mbox_len) {
|
||||
pcie_doe_reset_mbox(doe_cap);
|
||||
pcie_doe_set_ready(doe_cap, 0);
|
||||
} else if (doe_cap->read_mbox_idx > doe_cap->read_mbox_len) {
|
||||
/* Underflow */
|
||||
pcie_doe_set_error(doe_cap, 1);
|
||||
}
|
||||
break;
|
||||
case PCI_EXP_DOE_WR_DATA_MBOX:
|
||||
/* Mailbox should be DW accessed */
|
||||
if (size != DWORD_BYTE) {
|
||||
return;
|
||||
}
|
||||
if (doe_cap->write_mbox_len < PCI_DOE_DW_SIZE_MAX) {
|
||||
doe_cap->write_mbox[doe_cap->write_mbox_len] = val;
|
||||
doe_cap->write_mbox_len++;
|
||||
} else {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"Mailbox write length (%d) overflow\n",
|
||||
doe_cap->write_mbox_len);
|
||||
/*
|
||||
* Too much data has been written, it can't
|
||||
* "match the Length indicated in DOE Data Object Header 2"
|
||||
* so we drop the entire object.
|
||||
*/
|
||||
pcie_doe_reset_write_mbox(doe_cap);
|
||||
}
|
||||
break;
|
||||
case PCI_EXP_DOE_CAP:
|
||||
/* fallthrough */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* pcie_host.c
|
||||
* utility functions for pci express host bridge.
|
||||
*
|
||||
* Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci_device.h"
|
||||
#include "hw/pci/pcie_host.h"
|
||||
#include "qemu/module.h"
|
||||
|
||||
/* a helper function to get a PCIDevice for a given mmconfig address */
|
||||
static inline PCIDevice *pcie_dev_find_by_mmcfg_addr(PCIBus *s,
|
||||
uint32_t mmcfg_addr)
|
||||
{
|
||||
return pci_find_device(s, PCIE_MMCFG_BUS(mmcfg_addr),
|
||||
PCIE_MMCFG_DEVFN(mmcfg_addr));
|
||||
}
|
||||
|
||||
static void pcie_mmcfg_data_write(void *opaque, hwaddr mmcfg_addr,
|
||||
uint64_t val, unsigned len)
|
||||
{
|
||||
PCIExpressHost *e = opaque;
|
||||
PCIBus *s = e->pci.bus;
|
||||
PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, mmcfg_addr);
|
||||
uint32_t addr;
|
||||
uint32_t limit;
|
||||
|
||||
if (!pci_dev) {
|
||||
return;
|
||||
}
|
||||
addr = PCIE_MMCFG_CONFOFFSET(mmcfg_addr);
|
||||
limit = pci_config_size(pci_dev);
|
||||
pci_host_config_write_common(pci_dev, addr, limit, val, len);
|
||||
}
|
||||
|
||||
static uint64_t pcie_mmcfg_data_read(void *opaque,
|
||||
hwaddr mmcfg_addr,
|
||||
unsigned len)
|
||||
{
|
||||
PCIExpressHost *e = opaque;
|
||||
PCIBus *s = e->pci.bus;
|
||||
PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, mmcfg_addr);
|
||||
uint32_t addr;
|
||||
uint32_t limit;
|
||||
|
||||
if (!pci_dev) {
|
||||
return ~0x0;
|
||||
}
|
||||
addr = PCIE_MMCFG_CONFOFFSET(mmcfg_addr);
|
||||
limit = pci_config_size(pci_dev);
|
||||
return pci_host_config_read_common(pci_dev, addr, limit, len);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps pcie_mmcfg_ops = {
|
||||
.read = pcie_mmcfg_data_read,
|
||||
.write = pcie_mmcfg_data_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static void pcie_host_init(Object *obj)
|
||||
{
|
||||
PCIExpressHost *e = PCIE_HOST_BRIDGE(obj);
|
||||
|
||||
e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
|
||||
memory_region_init_io(&e->mmio, OBJECT(e), &pcie_mmcfg_ops, e, "pcie-mmcfg-mmio",
|
||||
PCIE_MMCFG_SIZE_MAX);
|
||||
}
|
||||
|
||||
void pcie_host_mmcfg_unmap(PCIExpressHost *e)
|
||||
{
|
||||
if (e->base_addr != PCIE_BASE_ADDR_UNMAPPED) {
|
||||
memory_region_del_subregion(get_system_memory(), &e->mmio);
|
||||
e->base_addr = PCIE_BASE_ADDR_UNMAPPED;
|
||||
}
|
||||
}
|
||||
|
||||
void pcie_host_mmcfg_init(PCIExpressHost *e, uint32_t size)
|
||||
{
|
||||
assert(!(size & (size - 1))); /* power of 2 */
|
||||
assert(size >= PCIE_MMCFG_SIZE_MIN);
|
||||
assert(size <= PCIE_MMCFG_SIZE_MAX);
|
||||
e->size = size;
|
||||
memory_region_set_size(&e->mmio, e->size);
|
||||
}
|
||||
|
||||
void pcie_host_mmcfg_map(PCIExpressHost *e, hwaddr addr,
|
||||
uint32_t size)
|
||||
{
|
||||
pcie_host_mmcfg_init(e, size);
|
||||
e->base_addr = addr;
|
||||
memory_region_add_subregion(get_system_memory(), e->base_addr, &e->mmio);
|
||||
}
|
||||
|
||||
void pcie_host_mmcfg_update(PCIExpressHost *e,
|
||||
int enable,
|
||||
hwaddr addr,
|
||||
uint32_t size)
|
||||
{
|
||||
memory_region_transaction_begin();
|
||||
pcie_host_mmcfg_unmap(e);
|
||||
if (enable) {
|
||||
pcie_host_mmcfg_map(e, addr, size);
|
||||
}
|
||||
memory_region_transaction_commit();
|
||||
}
|
||||
|
||||
static const TypeInfo pcie_host_type_info = {
|
||||
.name = TYPE_PCIE_HOST_BRIDGE,
|
||||
.parent = TYPE_PCI_HOST_BRIDGE,
|
||||
.abstract = true,
|
||||
.instance_size = sizeof(PCIExpressHost),
|
||||
.instance_init = pcie_host_init,
|
||||
};
|
||||
|
||||
static void pcie_host_register_types(void)
|
||||
{
|
||||
type_register_static(&pcie_host_type_info);
|
||||
}
|
||||
|
||||
type_init(pcie_host_register_types)
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* pcie_port.c
|
||||
*
|
||||
* Copyright (c) 2010 Isaku Yamahata <yamahata at valinux co jp>
|
||||
* VA Linux Systems Japan K.K.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pcie_port.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "qemu/module.h"
|
||||
#include "hw/core/hotplug.h"
|
||||
|
||||
void pcie_port_init_reg(PCIDevice *d)
|
||||
{
|
||||
/* Unlike pci bridge,
|
||||
66MHz and fast back to back don't apply to pci express port. */
|
||||
pci_set_word(d->config + PCI_STATUS, 0);
|
||||
pci_set_word(d->config + PCI_SEC_STATUS, 0);
|
||||
|
||||
/*
|
||||
* Unlike conventional pci bridge, for some bits the spec states:
|
||||
* Does not apply to PCI Express and must be hardwired to 0.
|
||||
*/
|
||||
pci_word_test_and_clear_mask(d->wmask + PCI_BRIDGE_CONTROL,
|
||||
PCI_BRIDGE_CTL_MASTER_ABORT |
|
||||
PCI_BRIDGE_CTL_FAST_BACK |
|
||||
PCI_BRIDGE_CTL_DISCARD |
|
||||
PCI_BRIDGE_CTL_SEC_DISCARD |
|
||||
PCI_BRIDGE_CTL_DISCARD_STATUS |
|
||||
PCI_BRIDGE_CTL_DISCARD_SERR);
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
* (chassis number, pcie physical slot number) -> pcie slot conversion
|
||||
*/
|
||||
struct PCIEChassis {
|
||||
uint8_t number;
|
||||
|
||||
QLIST_HEAD(, PCIESlot) slots;
|
||||
QLIST_ENTRY(PCIEChassis) next;
|
||||
};
|
||||
|
||||
static QLIST_HEAD(, PCIEChassis) chassis = QLIST_HEAD_INITIALIZER(chassis);
|
||||
|
||||
static struct PCIEChassis *pcie_chassis_find(uint8_t chassis_number)
|
||||
{
|
||||
struct PCIEChassis *c;
|
||||
QLIST_FOREACH(c, &chassis, next) {
|
||||
if (c->number == chassis_number) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void pcie_chassis_create(uint8_t chassis_number)
|
||||
{
|
||||
struct PCIEChassis *c;
|
||||
c = pcie_chassis_find(chassis_number);
|
||||
if (c) {
|
||||
return;
|
||||
}
|
||||
c = g_malloc0(sizeof(*c));
|
||||
c->number = chassis_number;
|
||||
QLIST_INIT(&c->slots);
|
||||
QLIST_INSERT_HEAD(&chassis, c, next);
|
||||
}
|
||||
|
||||
static PCIESlot *pcie_chassis_find_slot_with_chassis(struct PCIEChassis *c,
|
||||
uint8_t slot)
|
||||
{
|
||||
PCIESlot *s;
|
||||
QLIST_FOREACH(s, &c->slots, next) {
|
||||
if (s->slot == slot) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int pcie_chassis_add_slot(struct PCIESlot *slot)
|
||||
{
|
||||
struct PCIEChassis *c;
|
||||
c = pcie_chassis_find(slot->chassis);
|
||||
if (!c) {
|
||||
return -ENODEV;
|
||||
}
|
||||
if (pcie_chassis_find_slot_with_chassis(c, slot->slot)) {
|
||||
return -EBUSY;
|
||||
}
|
||||
QLIST_INSERT_HEAD(&c->slots, slot, next);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pcie_chassis_del_slot(PCIESlot *s)
|
||||
{
|
||||
QLIST_REMOVE(s, next);
|
||||
}
|
||||
|
||||
static const Property pcie_port_props[] = {
|
||||
DEFINE_PROP_UINT8("port", PCIEPort, port, 0),
|
||||
DEFINE_PROP_UINT16("aer_log_max", PCIEPort,
|
||||
parent_obj.parent_obj.exp.aer_log.log_max,
|
||||
PCIE_AER_LOG_MAX_DEFAULT),
|
||||
};
|
||||
|
||||
static void pcie_port_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
device_class_set_props(dc, pcie_port_props);
|
||||
}
|
||||
|
||||
PCIDevice *pcie_find_port_by_pn(PCIBus *bus, uint8_t pn)
|
||||
{
|
||||
int devfn;
|
||||
|
||||
for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
|
||||
PCIDevice *d = bus->devices[devfn];
|
||||
PCIEPort *port;
|
||||
|
||||
if (!d || !pci_is_express(d) || !d->exp.exp_cap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!object_dynamic_cast(OBJECT(d), TYPE_PCIE_PORT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
port = PCIE_PORT(d);
|
||||
if (port->port == pn) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find first port in devfn number order */
|
||||
PCIDevice *pcie_find_port_first(PCIBus *bus)
|
||||
{
|
||||
int devfn;
|
||||
|
||||
for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
|
||||
PCIDevice *d = bus->devices[devfn];
|
||||
|
||||
if (!d || !pci_is_express(d) || !d->exp.exp_cap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (object_dynamic_cast(OBJECT(d), TYPE_PCIE_PORT)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int pcie_count_ds_ports(PCIBus *bus)
|
||||
{
|
||||
int dsp_count = 0;
|
||||
int devfn;
|
||||
|
||||
for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) {
|
||||
PCIDevice *d = bus->devices[devfn];
|
||||
|
||||
if (!d || !pci_is_express(d) || !d->exp.exp_cap) {
|
||||
continue;
|
||||
}
|
||||
if (object_dynamic_cast(OBJECT(d), TYPE_PCIE_PORT)) {
|
||||
dsp_count++;
|
||||
}
|
||||
}
|
||||
return dsp_count;
|
||||
}
|
||||
|
||||
static bool pcie_slot_is_hotpluggable_bus(HotplugHandler *plug_handler,
|
||||
BusState *bus)
|
||||
{
|
||||
PCIESlot *s = PCIE_SLOT(bus->parent);
|
||||
return s->hotplug;
|
||||
}
|
||||
|
||||
static const TypeInfo pcie_port_type_info = {
|
||||
.name = TYPE_PCIE_PORT,
|
||||
.parent = TYPE_PCI_BRIDGE,
|
||||
.instance_size = sizeof(PCIEPort),
|
||||
.abstract = true,
|
||||
.class_init = pcie_port_class_init,
|
||||
};
|
||||
|
||||
static const Property pcie_slot_props[] = {
|
||||
DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0),
|
||||
DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0),
|
||||
DEFINE_PROP_BOOL("hotplug", PCIESlot, hotplug, true),
|
||||
DEFINE_PROP_BOOL("x-do-not-expose-native-hotplug-cap", PCIESlot,
|
||||
hide_native_hotplug_cap, false),
|
||||
};
|
||||
|
||||
static void pcie_slot_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);
|
||||
|
||||
device_class_set_props(dc, pcie_slot_props);
|
||||
hc->pre_plug = pcie_cap_slot_pre_plug_cb;
|
||||
hc->plug = pcie_cap_slot_plug_cb;
|
||||
hc->unplug = pcie_cap_slot_unplug_cb;
|
||||
hc->unplug_request = pcie_cap_slot_unplug_request_cb;
|
||||
hc->is_hotpluggable_bus = pcie_slot_is_hotpluggable_bus;
|
||||
}
|
||||
|
||||
static const TypeInfo pcie_slot_type_info = {
|
||||
.name = TYPE_PCIE_SLOT,
|
||||
.parent = TYPE_PCIE_PORT,
|
||||
.instance_size = sizeof(PCIESlot),
|
||||
.abstract = true,
|
||||
.class_init = pcie_slot_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_HOTPLUG_HANDLER },
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
static void pcie_port_register_types(void)
|
||||
{
|
||||
type_register_static(&pcie_port_type_info);
|
||||
type_register_static(&pcie_slot_type_info);
|
||||
}
|
||||
|
||||
type_init(pcie_port_register_types)
|
||||
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* pcie_sriov.c:
|
||||
*
|
||||
* Implementation of SR/IOV emulation support.
|
||||
*
|
||||
* Copyright (c) 2015-2017 Knut Omang <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/pci_device.h"
|
||||
#include "hw/pci/pcie.h"
|
||||
#include "hw/pci/pci_bus.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "qemu/range.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
|
||||
static GHashTable *pfs;
|
||||
|
||||
static void unparent_vfs(PCIDevice *dev, uint16_t total_vfs)
|
||||
{
|
||||
for (uint16_t i = 0; i < total_vfs; i++) {
|
||||
PCIDevice *vf = dev->exp.sriov_pf.vf[i];
|
||||
object_unparent(OBJECT(vf));
|
||||
object_unref(OBJECT(vf));
|
||||
}
|
||||
g_free(dev->exp.sriov_pf.vf);
|
||||
dev->exp.sriov_pf.vf = NULL;
|
||||
}
|
||||
|
||||
static void register_vfs(PCIDevice *dev)
|
||||
{
|
||||
uint16_t num_vfs;
|
||||
uint16_t i;
|
||||
uint16_t sriov_cap = dev->exp.sriov_cap;
|
||||
|
||||
assert(sriov_cap > 0);
|
||||
num_vfs = pci_get_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF);
|
||||
|
||||
trace_sriov_register_vfs(dev->name, PCI_SLOT(dev->devfn),
|
||||
PCI_FUNC(dev->devfn), num_vfs);
|
||||
for (i = 0; i < num_vfs; i++) {
|
||||
pci_set_enabled(dev->exp.sriov_pf.vf[i], true);
|
||||
}
|
||||
|
||||
pci_set_word(dev->wmask + sriov_cap + PCI_SRIOV_NUM_VF, 0);
|
||||
}
|
||||
|
||||
static void unregister_vfs(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *cfg = dev->config + dev->exp.sriov_cap;
|
||||
uint16_t i;
|
||||
|
||||
trace_sriov_unregister_vfs(dev->name, PCI_SLOT(dev->devfn),
|
||||
PCI_FUNC(dev->devfn));
|
||||
for (i = 0; i < pci_get_word(cfg + PCI_SRIOV_TOTAL_VF); i++) {
|
||||
pci_set_enabled(dev->exp.sriov_pf.vf[i], false);
|
||||
}
|
||||
|
||||
pci_set_word(dev->wmask + dev->exp.sriov_cap + PCI_SRIOV_NUM_VF, 0xffff);
|
||||
}
|
||||
|
||||
static void consume_config(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *cfg = dev->config + dev->exp.sriov_cap;
|
||||
|
||||
if (pci_get_word(cfg + PCI_SRIOV_CTRL) & PCI_SRIOV_CTRL_VFE) {
|
||||
register_vfs(dev);
|
||||
} else {
|
||||
uint8_t *wmask = dev->wmask + dev->exp.sriov_cap;
|
||||
uint16_t num_vfs = pci_get_word(cfg + PCI_SRIOV_NUM_VF);
|
||||
uint16_t wmask_val = PCI_SRIOV_CTRL_MSE | PCI_SRIOV_CTRL_ARI;
|
||||
|
||||
unregister_vfs(dev);
|
||||
|
||||
if (num_vfs <= pci_get_word(cfg + PCI_SRIOV_TOTAL_VF)) {
|
||||
wmask_val |= PCI_SRIOV_CTRL_VFE;
|
||||
}
|
||||
|
||||
pci_set_word(wmask + PCI_SRIOV_CTRL, wmask_val);
|
||||
}
|
||||
}
|
||||
|
||||
static bool pcie_sriov_pf_init_common(PCIDevice *dev, uint16_t offset,
|
||||
uint16_t vf_dev_id, uint16_t init_vfs,
|
||||
uint16_t total_vfs, uint16_t vf_offset,
|
||||
uint16_t vf_stride, Error **errp)
|
||||
{
|
||||
int32_t devfn = dev->devfn + vf_offset;
|
||||
uint8_t *cfg = dev->config + offset;
|
||||
uint8_t *wmask;
|
||||
|
||||
if (!pci_is_express(dev)) {
|
||||
error_setg(errp, "PCI Express is required for SR-IOV PF");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pci_is_vf(dev)) {
|
||||
error_setg(errp, "a device cannot be a SR-IOV PF and a VF at the same time");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (total_vfs &&
|
||||
(uint32_t)devfn + (uint32_t)(total_vfs - 1) * vf_stride >= PCI_DEVFN_MAX) {
|
||||
error_setg(errp, "VF addr overflows");
|
||||
return false;
|
||||
}
|
||||
|
||||
pcie_add_capability(dev, PCI_EXT_CAP_ID_SRIOV, 1,
|
||||
offset, PCI_EXT_CAP_SRIOV_SIZEOF);
|
||||
dev->exp.sriov_cap = offset;
|
||||
dev->exp.sriov_pf.vf = NULL;
|
||||
|
||||
pci_set_word(cfg + PCI_SRIOV_VF_OFFSET, vf_offset);
|
||||
pci_set_word(cfg + PCI_SRIOV_VF_STRIDE, vf_stride);
|
||||
|
||||
/*
|
||||
* Mandatory page sizes to support.
|
||||
* Device implementations can call pcie_sriov_pf_add_sup_pgsize()
|
||||
* to set more bits:
|
||||
*/
|
||||
pci_set_word(cfg + PCI_SRIOV_SUP_PGSIZE, SRIOV_SUP_PGSIZE_MINREQ);
|
||||
|
||||
/*
|
||||
* Default is to use 4K pages, software can modify it
|
||||
* to any of the supported bits
|
||||
*/
|
||||
pci_set_word(cfg + PCI_SRIOV_SYS_PGSIZE, 0x1);
|
||||
|
||||
/* Set up device ID and initial/total number of VFs available */
|
||||
pci_set_word(cfg + PCI_SRIOV_VF_DID, vf_dev_id);
|
||||
pci_set_word(cfg + PCI_SRIOV_INITIAL_VF, init_vfs);
|
||||
pci_set_word(cfg + PCI_SRIOV_TOTAL_VF, total_vfs);
|
||||
pci_set_word(cfg + PCI_SRIOV_NUM_VF, 0);
|
||||
|
||||
/* Write enable control bits */
|
||||
wmask = dev->wmask + offset;
|
||||
pci_set_word(wmask + PCI_SRIOV_CTRL,
|
||||
PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE | PCI_SRIOV_CTRL_ARI);
|
||||
pci_set_word(wmask + PCI_SRIOV_NUM_VF, 0xffff);
|
||||
pci_set_word(wmask + PCI_SRIOV_SYS_PGSIZE, 0x553);
|
||||
|
||||
qdev_prop_set_bit(&dev->qdev, "multifunction", true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pcie_sriov_pf_init(PCIDevice *dev, uint16_t offset,
|
||||
const char *vfname, uint16_t vf_dev_id,
|
||||
uint16_t init_vfs, uint16_t total_vfs,
|
||||
uint16_t vf_offset, uint16_t vf_stride,
|
||||
Error **errp)
|
||||
{
|
||||
BusState *bus = qdev_get_parent_bus(&dev->qdev);
|
||||
int32_t devfn = dev->devfn + vf_offset;
|
||||
|
||||
if (pfs && g_hash_table_contains(pfs, dev->qdev.id)) {
|
||||
error_setg(errp, "attaching user-created SR-IOV VF unsupported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pcie_sriov_pf_init_common(dev, offset, vf_dev_id, init_vfs,
|
||||
total_vfs, vf_offset, vf_stride, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dev->exp.sriov_pf.vf = g_new(PCIDevice *, total_vfs);
|
||||
|
||||
for (uint16_t i = 0; i < total_vfs; i++) {
|
||||
PCIDevice *vf = pci_new(devfn, vfname);
|
||||
vf->exp.sriov_vf.pf = dev;
|
||||
vf->exp.sriov_vf.vf_number = i;
|
||||
|
||||
if (!qdev_realize(&vf->qdev, bus, errp)) {
|
||||
object_unparent(OBJECT(vf));
|
||||
object_unref(vf);
|
||||
unparent_vfs(dev, i);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* set vid/did according to sr/iov spec - they are not used */
|
||||
pci_config_set_vendor_id(vf->config, 0xffff);
|
||||
pci_config_set_device_id(vf->config, 0xffff);
|
||||
|
||||
dev->exp.sriov_pf.vf[i] = vf;
|
||||
devfn += vf_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void pcie_sriov_pf_exit(PCIDevice *dev)
|
||||
{
|
||||
uint8_t *cfg;
|
||||
|
||||
if (dev->exp.sriov_cap == 0) {
|
||||
return;
|
||||
}
|
||||
cfg = dev->config + dev->exp.sriov_cap;
|
||||
|
||||
if (dev->exp.sriov_pf.vf_user_created) {
|
||||
uint16_t ven_id = pci_get_word(dev->config + PCI_VENDOR_ID);
|
||||
uint16_t total_vfs = pci_get_word(cfg + PCI_SRIOV_TOTAL_VF);
|
||||
uint16_t vf_dev_id = pci_get_word(cfg + PCI_SRIOV_VF_DID);
|
||||
|
||||
unregister_vfs(dev);
|
||||
|
||||
for (uint16_t i = 0; i < total_vfs; i++) {
|
||||
dev->exp.sriov_pf.vf[i]->exp.sriov_vf.pf = NULL;
|
||||
|
||||
pci_config_set_vendor_id(dev->exp.sriov_pf.vf[i]->config, ven_id);
|
||||
pci_config_set_device_id(dev->exp.sriov_pf.vf[i]->config, vf_dev_id);
|
||||
}
|
||||
} else {
|
||||
unparent_vfs(dev, pci_get_word(cfg + PCI_SRIOV_TOTAL_VF));
|
||||
}
|
||||
}
|
||||
|
||||
void pcie_sriov_pf_init_vf_bar(PCIDevice *dev, int region_num,
|
||||
uint8_t type, dma_addr_t size)
|
||||
{
|
||||
uint32_t addr;
|
||||
uint64_t wmask;
|
||||
uint16_t sriov_cap = dev->exp.sriov_cap;
|
||||
|
||||
assert(sriov_cap > 0);
|
||||
assert(region_num >= 0);
|
||||
assert(region_num < PCI_NUM_REGIONS);
|
||||
assert(region_num != PCI_ROM_SLOT);
|
||||
|
||||
wmask = ~(size - 1);
|
||||
addr = sriov_cap + PCI_SRIOV_BAR + region_num * 4;
|
||||
|
||||
pci_set_long(dev->config + addr, type);
|
||||
if (!(type & PCI_BASE_ADDRESS_SPACE_IO) &&
|
||||
type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
|
||||
pci_set_quad(dev->wmask + addr, wmask);
|
||||
pci_set_quad(dev->cmask + addr, ~0ULL);
|
||||
} else {
|
||||
pci_set_long(dev->wmask + addr, wmask & 0xffffffff);
|
||||
pci_set_long(dev->cmask + addr, 0xffffffff);
|
||||
}
|
||||
dev->exp.sriov_pf.vf_bar_type[region_num] = type;
|
||||
}
|
||||
|
||||
static gint compare_vf_devfns(gconstpointer a, gconstpointer b)
|
||||
{
|
||||
return (*(PCIDevice **)a)->devfn - (*(PCIDevice **)b)->devfn;
|
||||
}
|
||||
|
||||
int16_t pcie_sriov_pf_init_from_user_created_vfs(PCIDevice *dev,
|
||||
uint16_t offset,
|
||||
Error **errp)
|
||||
{
|
||||
GPtrArray *pf;
|
||||
PCIDevice **vfs;
|
||||
BusState *bus = qdev_get_parent_bus(DEVICE(dev));
|
||||
uint16_t ven_id = pci_get_word(dev->config + PCI_VENDOR_ID);
|
||||
uint16_t size = PCI_EXT_CAP_SRIOV_SIZEOF;
|
||||
uint16_t vf_dev_id;
|
||||
uint16_t vf_offset;
|
||||
uint16_t vf_stride;
|
||||
uint16_t i;
|
||||
|
||||
if (!pfs || !dev->qdev.id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pf = g_hash_table_lookup(pfs, dev->qdev.id);
|
||||
if (!pf) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pf->len > UINT16_MAX) {
|
||||
error_setg(errp, "too many VFs");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_ptr_array_sort(pf, compare_vf_devfns);
|
||||
vfs = (void *)pf->pdata;
|
||||
|
||||
if (vfs[0]->devfn <= dev->devfn) {
|
||||
error_setg(errp, "a VF function number is less than the PF function number");
|
||||
return -1;
|
||||
}
|
||||
|
||||
vf_dev_id = pci_get_word(vfs[0]->config + PCI_DEVICE_ID);
|
||||
vf_offset = vfs[0]->devfn - dev->devfn;
|
||||
vf_stride = pf->len < 2 ? 0 : vfs[1]->devfn - vfs[0]->devfn;
|
||||
|
||||
for (i = 0; i < pf->len; i++) {
|
||||
if (bus != qdev_get_parent_bus(&vfs[i]->qdev)) {
|
||||
error_setg(errp, "SR-IOV VF parent bus mismatches with PF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ven_id != pci_get_word(vfs[i]->config + PCI_VENDOR_ID)) {
|
||||
error_setg(errp, "SR-IOV VF vendor ID mismatches with PF");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vf_dev_id != pci_get_word(vfs[i]->config + PCI_DEVICE_ID)) {
|
||||
error_setg(errp, "inconsistent SR-IOV VF device IDs");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < PCI_NUM_REGIONS; j++) {
|
||||
if (vfs[i]->io_regions[j].size != vfs[0]->io_regions[j].size ||
|
||||
vfs[i]->io_regions[j].type != vfs[0]->io_regions[j].type) {
|
||||
error_setg(errp, "inconsistent SR-IOV BARs");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (vfs[i]->devfn - vfs[0]->devfn != vf_stride * i) {
|
||||
error_setg(errp, "inconsistent SR-IOV stride");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pcie_sriov_pf_init_common(dev, offset, vf_dev_id, pf->len,
|
||||
pf->len, vf_offset, vf_stride, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!pcie_find_capability(dev, PCI_EXT_CAP_ID_ARI)) {
|
||||
pcie_ari_init(dev, offset + size);
|
||||
size += PCI_ARI_SIZEOF;
|
||||
}
|
||||
|
||||
for (i = 0; i < pf->len; i++) {
|
||||
vfs[i]->exp.sriov_vf.pf = dev;
|
||||
vfs[i]->exp.sriov_vf.vf_number = i;
|
||||
|
||||
/* set vid/did according to sr/iov spec - they are not used */
|
||||
pci_config_set_vendor_id(vfs[i]->config, 0xffff);
|
||||
pci_config_set_device_id(vfs[i]->config, 0xffff);
|
||||
}
|
||||
|
||||
dev->exp.sriov_pf.vf = vfs;
|
||||
dev->exp.sriov_pf.vf_user_created = true;
|
||||
|
||||
for (i = 0; i < PCI_NUM_REGIONS; i++) {
|
||||
PCIIORegion *region = &vfs[0]->io_regions[i];
|
||||
|
||||
if (region->size) {
|
||||
pcie_sriov_pf_init_vf_bar(dev, i, region->type, region->size);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool pcie_sriov_register_device(PCIDevice *dev, Error **errp)
|
||||
{
|
||||
if (!dev->exp.sriov_pf.vf && dev->qdev.id &&
|
||||
pfs && g_hash_table_contains(pfs, dev->qdev.id)) {
|
||||
error_setg(errp, "attaching user-created SR-IOV VF unsupported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dev->sriov_pf) {
|
||||
PCIDevice *pci_pf;
|
||||
GPtrArray *pf;
|
||||
|
||||
if (!PCI_DEVICE_GET_CLASS(dev)->sriov_vf_user_creatable) {
|
||||
error_setg(errp, "user cannot create SR-IOV VF with this device type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pci_is_express(dev)) {
|
||||
error_setg(errp, "PCI Express is required for SR-IOV VF");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pci_qdev_find_device(dev->sriov_pf, &pci_pf)) {
|
||||
error_setg(errp, "PCI device specified as SR-IOV PF already exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pfs) {
|
||||
pfs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
}
|
||||
|
||||
pf = g_hash_table_lookup(pfs, dev->sriov_pf);
|
||||
if (!pf) {
|
||||
pf = g_ptr_array_new();
|
||||
g_hash_table_insert(pfs, g_strdup(dev->sriov_pf), pf);
|
||||
}
|
||||
|
||||
g_ptr_array_add(pf, dev);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void pcie_sriov_unregister_device(PCIDevice *dev)
|
||||
{
|
||||
if (dev->sriov_pf && pfs) {
|
||||
GPtrArray *pf = g_hash_table_lookup(pfs, dev->sriov_pf);
|
||||
|
||||
if (pf) {
|
||||
g_ptr_array_remove_fast(pf, dev);
|
||||
|
||||
if (!pf->len) {
|
||||
g_hash_table_remove(pfs, dev->sriov_pf);
|
||||
g_ptr_array_free(pf, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pcie_sriov_config_write(PCIDevice *dev, uint32_t address,
|
||||
uint32_t val, int len)
|
||||
{
|
||||
uint32_t off;
|
||||
uint16_t sriov_cap = dev->exp.sriov_cap;
|
||||
|
||||
if (!sriov_cap || address < sriov_cap) {
|
||||
return;
|
||||
}
|
||||
off = address - sriov_cap;
|
||||
if (off >= PCI_EXT_CAP_SRIOV_SIZEOF) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_sriov_config_write(dev->name, PCI_SLOT(dev->devfn),
|
||||
PCI_FUNC(dev->devfn), off, val, len);
|
||||
|
||||
consume_config(dev);
|
||||
}
|
||||
|
||||
void pcie_sriov_pf_post_load(PCIDevice *dev)
|
||||
{
|
||||
if (dev->exp.sriov_cap) {
|
||||
consume_config(dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Reset SR/IOV */
|
||||
void pcie_sriov_pf_reset(PCIDevice *dev)
|
||||
{
|
||||
uint16_t sriov_cap = dev->exp.sriov_cap;
|
||||
if (!sriov_cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
pci_set_word(dev->config + sriov_cap + PCI_SRIOV_CTRL, 0);
|
||||
unregister_vfs(dev);
|
||||
|
||||
pci_set_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF, 0);
|
||||
pci_set_word(dev->wmask + sriov_cap + PCI_SRIOV_CTRL,
|
||||
PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE | PCI_SRIOV_CTRL_ARI);
|
||||
|
||||
/*
|
||||
* Default is to use 4K pages, software can modify it
|
||||
* to any of the supported bits
|
||||
*/
|
||||
pci_set_word(dev->config + sriov_cap + PCI_SRIOV_SYS_PGSIZE, 0x1);
|
||||
|
||||
for (uint16_t i = 0; i < PCI_NUM_REGIONS; i++) {
|
||||
pci_set_quad(dev->config + sriov_cap + PCI_SRIOV_BAR + i * 4,
|
||||
dev->exp.sriov_pf.vf_bar_type[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add optional supported page sizes to the mask of supported page sizes */
|
||||
void pcie_sriov_pf_add_sup_pgsize(PCIDevice *dev, uint16_t opt_sup_pgsize)
|
||||
{
|
||||
uint8_t *cfg = dev->config + dev->exp.sriov_cap;
|
||||
uint8_t *wmask = dev->wmask + dev->exp.sriov_cap;
|
||||
|
||||
uint16_t sup_pgsize = pci_get_word(cfg + PCI_SRIOV_SUP_PGSIZE);
|
||||
|
||||
sup_pgsize |= opt_sup_pgsize;
|
||||
|
||||
/*
|
||||
* Make sure the new bits are set, and that system page size
|
||||
* also can be set to any of the new values according to spec:
|
||||
*/
|
||||
pci_set_word(cfg + PCI_SRIOV_SUP_PGSIZE, sup_pgsize);
|
||||
pci_set_word(wmask + PCI_SRIOV_SYS_PGSIZE, sup_pgsize);
|
||||
}
|
||||
|
||||
|
||||
uint16_t pcie_sriov_vf_number(PCIDevice *dev)
|
||||
{
|
||||
assert(dev->exp.sriov_vf.pf);
|
||||
return dev->exp.sriov_vf.vf_number;
|
||||
}
|
||||
|
||||
PCIDevice *pcie_sriov_get_pf(PCIDevice *dev)
|
||||
{
|
||||
return dev->exp.sriov_vf.pf;
|
||||
}
|
||||
|
||||
PCIDevice *pcie_sriov_get_vf_at_index(PCIDevice *dev, int n)
|
||||
{
|
||||
assert(!pci_is_vf(dev));
|
||||
if (n < pcie_sriov_num_vfs(dev)) {
|
||||
return dev->exp.sriov_pf.vf[n];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint16_t pcie_sriov_num_vfs(PCIDevice *dev)
|
||||
{
|
||||
uint16_t sriov_cap = dev->exp.sriov_cap;
|
||||
uint8_t *cfg = dev->config + sriov_cap;
|
||||
|
||||
return sriov_cap &&
|
||||
(pci_get_word(cfg + PCI_SRIOV_CTRL) & PCI_SRIOV_CTRL_VFE) ?
|
||||
pci_get_word(cfg + PCI_SRIOV_NUM_VF) : 0;
|
||||
}
|
||||
+788
@@ -0,0 +1,788 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/host-utils.h"
|
||||
#include "qemu/range.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/pci/shpc.h"
|
||||
#include "migration/qemu-file-types.h"
|
||||
#include "hw/pci/pci.h"
|
||||
#include "hw/pci/pci_bus.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* TODO: model power only and disabled slot states. */
|
||||
/* TODO: handle SERR and wakeups */
|
||||
/* TODO: consider enabling 66MHz support */
|
||||
|
||||
/* TODO: remove fully only on state DISABLED and LED off.
|
||||
* track state to properly record this. */
|
||||
|
||||
/* SHPC Working Register Set */
|
||||
#define SHPC_BASE_OFFSET 0x00 /* 4 bytes */
|
||||
#define SHPC_SLOTS_33 0x04 /* 4 bytes. Also encodes PCI-X slots. */
|
||||
#define SHPC_SLOTS_66 0x08 /* 4 bytes. */
|
||||
#define SHPC_NSLOTS 0x0C /* 1 byte */
|
||||
#define SHPC_FIRST_DEV 0x0D /* 1 byte */
|
||||
#define SHPC_PHYS_SLOT 0x0E /* 2 byte */
|
||||
#define SHPC_PHYS_NUM_MAX 0x7ff
|
||||
#define SHPC_PHYS_NUM_UP 0x2000
|
||||
#define SHPC_PHYS_MRL 0x4000
|
||||
#define SHPC_PHYS_BUTTON 0x8000
|
||||
#define SHPC_SEC_BUS 0x10 /* 2 bytes */
|
||||
#define SHPC_SEC_BUS_33 0x0
|
||||
#define SHPC_SEC_BUS_66 0x1 /* Unused */
|
||||
#define SHPC_SEC_BUS_MASK 0x7
|
||||
#define SHPC_MSI_CTL 0x12 /* 1 byte */
|
||||
#define SHPC_PROG_IFC 0x13 /* 1 byte */
|
||||
#define SHPC_PROG_IFC_1_0 0x1
|
||||
#define SHPC_CMD_CODE 0x14 /* 1 byte */
|
||||
#define SHPC_CMD_TRGT 0x15 /* 1 byte */
|
||||
#define SHPC_CMD_TRGT_MIN 0x1
|
||||
#define SHPC_CMD_TRGT_MAX 0x1f
|
||||
#define SHPC_CMD_STATUS 0x16 /* 2 bytes */
|
||||
#define SHPC_CMD_STATUS_BUSY 0x1
|
||||
#define SHPC_CMD_STATUS_MRL_OPEN 0x2
|
||||
#define SHPC_CMD_STATUS_INVALID_CMD 0x4
|
||||
#define SHPC_CMD_STATUS_INVALID_MODE 0x8
|
||||
#define SHPC_INT_LOCATOR 0x18 /* 4 bytes */
|
||||
#define SHPC_INT_COMMAND 0x1
|
||||
#define SHPC_SERR_LOCATOR 0x1C /* 4 bytes */
|
||||
#define SHPC_SERR_INT 0x20 /* 4 bytes */
|
||||
#define SHPC_INT_DIS 0x1
|
||||
#define SHPC_SERR_DIS 0x2
|
||||
#define SHPC_CMD_INT_DIS 0x4
|
||||
#define SHPC_ARB_SERR_DIS 0x8
|
||||
#define SHPC_CMD_DETECTED 0x10000
|
||||
#define SHPC_ARB_DETECTED 0x20000
|
||||
/* 4 bytes * slot # (start from 0) */
|
||||
#define SHPC_SLOT_REG(s) (0x24 + (s) * 4)
|
||||
/* 2 bytes */
|
||||
#define SHPC_SLOT_STATUS(s) (0x0 + SHPC_SLOT_REG(s))
|
||||
|
||||
/* Same slot state masks are used for command and status registers */
|
||||
#define SHPC_SLOT_STATE_MASK 0x03
|
||||
#define SHPC_SLOT_STATE_SHIFT \
|
||||
ctz32(SHPC_SLOT_STATE_MASK)
|
||||
|
||||
#define SHPC_STATE_NO 0x0
|
||||
#define SHPC_STATE_PWRONLY 0x1
|
||||
#define SHPC_STATE_ENABLED 0x2
|
||||
#define SHPC_STATE_DISABLED 0x3
|
||||
|
||||
#define SHPC_SLOT_PWR_LED_MASK 0xC
|
||||
#define SHPC_SLOT_PWR_LED_SHIFT \
|
||||
ctz32(SHPC_SLOT_PWR_LED_MASK)
|
||||
#define SHPC_SLOT_ATTN_LED_MASK 0x30
|
||||
#define SHPC_SLOT_ATTN_LED_SHIFT \
|
||||
ctz32(SHPC_SLOT_ATTN_LED_MASK)
|
||||
|
||||
#define SHPC_LED_NO 0x0
|
||||
#define SHPC_LED_ON 0x1
|
||||
#define SHPC_LED_BLINK 0x2
|
||||
#define SHPC_LED_OFF 0x3
|
||||
|
||||
#define SHPC_SLOT_STATUS_PWR_FAULT 0x40
|
||||
#define SHPC_SLOT_STATUS_BUTTON 0x80
|
||||
#define SHPC_SLOT_STATUS_MRL_OPEN 0x100
|
||||
#define SHPC_SLOT_STATUS_66 0x200
|
||||
#define SHPC_SLOT_STATUS_PRSNT_MASK 0xC00
|
||||
#define SHPC_SLOT_STATUS_PRSNT_EMPTY 0x3
|
||||
#define SHPC_SLOT_STATUS_PRSNT_25W 0x1
|
||||
#define SHPC_SLOT_STATUS_PRSNT_15W 0x2
|
||||
#define SHPC_SLOT_STATUS_PRSNT_7_5W 0x0
|
||||
|
||||
#define SHPC_SLOT_STATUS_PRSNT_PCIX 0x3000
|
||||
|
||||
|
||||
/* 1 byte */
|
||||
#define SHPC_SLOT_EVENT_LATCH(s) (0x2 + SHPC_SLOT_REG(s))
|
||||
/* 1 byte */
|
||||
#define SHPC_SLOT_EVENT_SERR_INT_DIS(d, s) (0x3 + SHPC_SLOT_REG(s))
|
||||
#define SHPC_SLOT_EVENT_PRESENCE 0x01
|
||||
#define SHPC_SLOT_EVENT_ISOLATED_FAULT 0x02
|
||||
#define SHPC_SLOT_EVENT_BUTTON 0x04
|
||||
#define SHPC_SLOT_EVENT_MRL 0x08
|
||||
#define SHPC_SLOT_EVENT_CONNECTED_FAULT 0x10
|
||||
/* Bits below are used for Serr/Int disable only */
|
||||
#define SHPC_SLOT_EVENT_MRL_SERR_DIS 0x20
|
||||
#define SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS 0x40
|
||||
|
||||
#define SHPC_MIN_SLOTS 1
|
||||
#define SHPC_MAX_SLOTS 31
|
||||
#define SHPC_SIZEOF(d) SHPC_SLOT_REG((d)->shpc->nslots)
|
||||
|
||||
/* SHPC Slot identifiers */
|
||||
|
||||
/* Hotplug supported at 31 slots out of the total 32. We reserve slot 0,
|
||||
and give the rest of them physical *and* pci numbers starting from 1, so
|
||||
they match logical numbers. Note: this means that multiple slots must have
|
||||
different chassis number values, to make chassis+physical slot unique.
|
||||
TODO: make this configurable? */
|
||||
#define SHPC_IDX_TO_LOGICAL(slot) ((slot) + 1)
|
||||
#define SHPC_LOGICAL_TO_IDX(target) ((target) - 1)
|
||||
#define SHPC_IDX_TO_PCI(slot) ((slot) + 1)
|
||||
#define SHPC_PCI_TO_IDX(pci_slot) ((pci_slot) - 1)
|
||||
#define SHPC_IDX_TO_PHYSICAL(slot) ((slot) + 1)
|
||||
|
||||
static const char *shpc_led_state_to_str(uint8_t value)
|
||||
{
|
||||
switch (value) {
|
||||
case SHPC_LED_ON:
|
||||
return "on";
|
||||
case SHPC_LED_BLINK:
|
||||
return "blink";
|
||||
case SHPC_LED_OFF:
|
||||
return "off";
|
||||
default:
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *shpc_slot_state_to_str(uint8_t value)
|
||||
{
|
||||
switch (value) {
|
||||
case SHPC_STATE_PWRONLY:
|
||||
return "power-only";
|
||||
case SHPC_STATE_ENABLED:
|
||||
return "enabled";
|
||||
case SHPC_STATE_DISABLED:
|
||||
return "disabled";
|
||||
default:
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t shpc_get_status(SHPCDevice *shpc, int slot, uint16_t msk)
|
||||
{
|
||||
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
|
||||
uint16_t result = (pci_get_word(status) & msk) >> ctz32(msk);
|
||||
|
||||
assert(result <= UINT8_MAX);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void shpc_set_status(SHPCDevice *shpc,
|
||||
int slot, uint8_t value, uint16_t msk)
|
||||
{
|
||||
uint8_t *status = shpc->config + SHPC_SLOT_STATUS(slot);
|
||||
pci_word_test_and_clear_mask(status, msk);
|
||||
pci_word_test_and_set_mask(status, value << ctz32(msk));
|
||||
}
|
||||
|
||||
static void shpc_interrupt_update(PCIDevice *d)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
int slot;
|
||||
int level = 0;
|
||||
uint32_t serr_int;
|
||||
uint32_t int_locator = 0;
|
||||
|
||||
/* Update interrupt locator register */
|
||||
for (slot = 0; slot < shpc->nslots; ++slot) {
|
||||
uint8_t event = shpc->config[SHPC_SLOT_EVENT_LATCH(slot)];
|
||||
uint8_t disable = shpc->config[SHPC_SLOT_EVENT_SERR_INT_DIS(d, slot)];
|
||||
uint32_t mask = 1U << SHPC_IDX_TO_LOGICAL(slot);
|
||||
if (event & ~disable) {
|
||||
int_locator |= mask;
|
||||
}
|
||||
}
|
||||
serr_int = pci_get_long(shpc->config + SHPC_SERR_INT);
|
||||
if ((serr_int & SHPC_CMD_DETECTED) && !(serr_int & SHPC_CMD_INT_DIS)) {
|
||||
int_locator |= SHPC_INT_COMMAND;
|
||||
}
|
||||
pci_set_long(shpc->config + SHPC_INT_LOCATOR, int_locator);
|
||||
level = (!(serr_int & SHPC_INT_DIS) && int_locator) ? 1 : 0;
|
||||
if (msi_enabled(d) && shpc->msi_requested != level)
|
||||
msi_notify(d, 0);
|
||||
else
|
||||
pci_set_irq(d, level);
|
||||
shpc->msi_requested = level;
|
||||
}
|
||||
|
||||
static void shpc_set_sec_bus_speed(SHPCDevice *shpc, uint8_t speed)
|
||||
{
|
||||
switch (speed) {
|
||||
case SHPC_SEC_BUS_33:
|
||||
shpc->config[SHPC_SEC_BUS] &= ~SHPC_SEC_BUS_MASK;
|
||||
shpc->config[SHPC_SEC_BUS] |= speed;
|
||||
break;
|
||||
default:
|
||||
pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
|
||||
SHPC_CMD_STATUS_INVALID_MODE);
|
||||
}
|
||||
}
|
||||
|
||||
void shpc_reset(PCIDevice *d)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
int nslots = shpc->nslots;
|
||||
int i;
|
||||
memset(shpc->config, 0, SHPC_SIZEOF(d));
|
||||
pci_set_byte(shpc->config + SHPC_NSLOTS, nslots);
|
||||
pci_set_long(shpc->config + SHPC_SLOTS_33, nslots);
|
||||
pci_set_long(shpc->config + SHPC_SLOTS_66, 0);
|
||||
pci_set_byte(shpc->config + SHPC_FIRST_DEV, SHPC_IDX_TO_PCI(0));
|
||||
pci_set_word(shpc->config + SHPC_PHYS_SLOT,
|
||||
SHPC_IDX_TO_PHYSICAL(0) |
|
||||
SHPC_PHYS_NUM_UP |
|
||||
SHPC_PHYS_MRL |
|
||||
SHPC_PHYS_BUTTON);
|
||||
pci_set_long(shpc->config + SHPC_SERR_INT, SHPC_INT_DIS |
|
||||
SHPC_SERR_DIS |
|
||||
SHPC_CMD_INT_DIS |
|
||||
SHPC_ARB_SERR_DIS);
|
||||
pci_set_byte(shpc->config + SHPC_PROG_IFC, SHPC_PROG_IFC_1_0);
|
||||
pci_set_word(shpc->config + SHPC_SEC_BUS, SHPC_SEC_BUS_33);
|
||||
for (i = 0; i < shpc->nslots; ++i) {
|
||||
pci_set_byte(shpc->config + SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
|
||||
SHPC_SLOT_EVENT_PRESENCE |
|
||||
SHPC_SLOT_EVENT_ISOLATED_FAULT |
|
||||
SHPC_SLOT_EVENT_BUTTON |
|
||||
SHPC_SLOT_EVENT_MRL |
|
||||
SHPC_SLOT_EVENT_CONNECTED_FAULT |
|
||||
SHPC_SLOT_EVENT_MRL_SERR_DIS |
|
||||
SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
|
||||
if (shpc->sec_bus->devices[PCI_DEVFN(SHPC_IDX_TO_PCI(i), 0)]) {
|
||||
shpc_set_status(shpc, i, SHPC_STATE_ENABLED, SHPC_SLOT_STATE_MASK);
|
||||
shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_MRL_OPEN);
|
||||
shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_7_5W,
|
||||
SHPC_SLOT_STATUS_PRSNT_MASK);
|
||||
shpc_set_status(shpc, i, SHPC_LED_ON, SHPC_SLOT_PWR_LED_MASK);
|
||||
} else {
|
||||
shpc_set_status(shpc, i, SHPC_STATE_DISABLED, SHPC_SLOT_STATE_MASK);
|
||||
shpc_set_status(shpc, i, 1, SHPC_SLOT_STATUS_MRL_OPEN);
|
||||
shpc_set_status(shpc, i, SHPC_SLOT_STATUS_PRSNT_EMPTY,
|
||||
SHPC_SLOT_STATUS_PRSNT_MASK);
|
||||
shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_PWR_LED_MASK);
|
||||
}
|
||||
shpc_set_status(shpc, i, SHPC_LED_OFF, SHPC_SLOT_ATTN_LED_MASK);
|
||||
shpc_set_status(shpc, i, 0, SHPC_SLOT_STATUS_66);
|
||||
}
|
||||
shpc_set_sec_bus_speed(shpc, SHPC_SEC_BUS_33);
|
||||
shpc->msi_requested = 0;
|
||||
shpc_interrupt_update(d);
|
||||
}
|
||||
|
||||
static void shpc_invalid_command(SHPCDevice *shpc)
|
||||
{
|
||||
pci_word_test_and_set_mask(shpc->config + SHPC_CMD_STATUS,
|
||||
SHPC_CMD_STATUS_INVALID_CMD);
|
||||
}
|
||||
|
||||
static void shpc_free_devices_in_slot(SHPCDevice *shpc, int slot)
|
||||
{
|
||||
HotplugHandler *hotplug_ctrl;
|
||||
int devfn;
|
||||
int pci_slot = SHPC_IDX_TO_PCI(slot);
|
||||
for (devfn = PCI_DEVFN(pci_slot, 0);
|
||||
devfn <= PCI_DEVFN(pci_slot, PCI_FUNC_MAX - 1);
|
||||
++devfn) {
|
||||
PCIDevice *affected_dev = shpc->sec_bus->devices[devfn];
|
||||
if (affected_dev) {
|
||||
hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(affected_dev));
|
||||
hotplug_handler_unplug(hotplug_ctrl, DEVICE(affected_dev),
|
||||
&error_abort);
|
||||
object_unparent(OBJECT(affected_dev));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool shpc_slot_is_off(uint8_t state, uint8_t power, uint8_t attn)
|
||||
{
|
||||
return state == SHPC_STATE_DISABLED && power == SHPC_LED_OFF;
|
||||
}
|
||||
|
||||
static void shpc_slot_command(PCIDevice *d, uint8_t target,
|
||||
uint8_t state, uint8_t power, uint8_t attn)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
int slot = SHPC_LOGICAL_TO_IDX(target);
|
||||
uint8_t old_state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
|
||||
uint8_t old_power = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
|
||||
uint8_t old_attn = shpc_get_status(shpc, slot, SHPC_SLOT_ATTN_LED_MASK);
|
||||
|
||||
if (target < SHPC_CMD_TRGT_MIN || slot >= shpc->nslots) {
|
||||
shpc_invalid_command(shpc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_state == SHPC_STATE_ENABLED && state == SHPC_STATE_PWRONLY) {
|
||||
shpc_invalid_command(shpc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (power == SHPC_LED_NO) {
|
||||
power = old_power;
|
||||
} else {
|
||||
/* TODO: send event to monitor */
|
||||
shpc_set_status(shpc, slot, power, SHPC_SLOT_PWR_LED_MASK);
|
||||
}
|
||||
|
||||
if (attn == SHPC_LED_NO) {
|
||||
attn = old_attn;
|
||||
} else {
|
||||
/* TODO: send event to monitor */
|
||||
shpc_set_status(shpc, slot, attn, SHPC_SLOT_ATTN_LED_MASK);
|
||||
}
|
||||
|
||||
if (state == SHPC_STATE_NO) {
|
||||
state = old_state;
|
||||
} else {
|
||||
shpc_set_status(shpc, slot, state, SHPC_SLOT_STATE_MASK);
|
||||
}
|
||||
|
||||
if (trace_event_get_state_backends(TRACE_SHPC_SLOT_COMMAND)) {
|
||||
DeviceState *parent = DEVICE(d);
|
||||
int pci_slot = SHPC_IDX_TO_PCI(slot);
|
||||
DeviceState *child =
|
||||
DEVICE(shpc->sec_bus->devices[PCI_DEVFN(pci_slot, 0)]);
|
||||
|
||||
trace_shpc_slot_command(
|
||||
parent->canonical_path, pci_slot,
|
||||
child ? child->canonical_path : "no-child",
|
||||
shpc_led_state_to_str(old_power),
|
||||
shpc_led_state_to_str(power),
|
||||
shpc_led_state_to_str(old_attn),
|
||||
shpc_led_state_to_str(attn),
|
||||
shpc_slot_state_to_str(old_state),
|
||||
shpc_slot_state_to_str(state));
|
||||
}
|
||||
|
||||
if (!shpc_slot_is_off(old_state, old_power, old_attn) &&
|
||||
shpc_slot_is_off(state, power, attn))
|
||||
{
|
||||
shpc_free_devices_in_slot(shpc, slot);
|
||||
shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
|
||||
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
|
||||
SHPC_SLOT_STATUS_PRSNT_MASK);
|
||||
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
|
||||
SHPC_SLOT_EVENT_MRL |
|
||||
SHPC_SLOT_EVENT_PRESENCE;
|
||||
}
|
||||
}
|
||||
|
||||
static void shpc_command(PCIDevice *d)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
uint8_t code = pci_get_byte(shpc->config + SHPC_CMD_CODE);
|
||||
uint8_t speed;
|
||||
uint8_t target;
|
||||
uint8_t attn;
|
||||
uint8_t power;
|
||||
uint8_t state;
|
||||
int i;
|
||||
|
||||
/* Clear status from the previous command. */
|
||||
pci_word_test_and_clear_mask(shpc->config + SHPC_CMD_STATUS,
|
||||
SHPC_CMD_STATUS_BUSY |
|
||||
SHPC_CMD_STATUS_MRL_OPEN |
|
||||
SHPC_CMD_STATUS_INVALID_CMD |
|
||||
SHPC_CMD_STATUS_INVALID_MODE);
|
||||
switch (code) {
|
||||
case 0x00 ... 0x3f:
|
||||
target = shpc->config[SHPC_CMD_TRGT] & SHPC_CMD_TRGT_MAX;
|
||||
state = (code & SHPC_SLOT_STATE_MASK) >> SHPC_SLOT_STATE_SHIFT;
|
||||
power = (code & SHPC_SLOT_PWR_LED_MASK) >> SHPC_SLOT_PWR_LED_SHIFT;
|
||||
attn = (code & SHPC_SLOT_ATTN_LED_MASK) >> SHPC_SLOT_ATTN_LED_SHIFT;
|
||||
shpc_slot_command(d, target, state, power, attn);
|
||||
break;
|
||||
case 0x40 ... 0x47:
|
||||
speed = code & SHPC_SEC_BUS_MASK;
|
||||
shpc_set_sec_bus_speed(shpc, speed);
|
||||
break;
|
||||
case 0x48:
|
||||
/* Power only all slots */
|
||||
/* first verify no slots are enabled */
|
||||
for (i = 0; i < shpc->nslots; ++i) {
|
||||
state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
|
||||
if (state == SHPC_STATE_ENABLED) {
|
||||
shpc_invalid_command(shpc);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < shpc->nslots; ++i) {
|
||||
if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
|
||||
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
|
||||
SHPC_STATE_PWRONLY, SHPC_LED_ON, SHPC_LED_NO);
|
||||
} else {
|
||||
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
|
||||
SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x49:
|
||||
/* Enable all slots */
|
||||
/* TODO: Spec says this shall fail if some are already enabled.
|
||||
* This doesn't make sense - why not? a spec bug? */
|
||||
for (i = 0; i < shpc->nslots; ++i) {
|
||||
state = shpc_get_status(shpc, i, SHPC_SLOT_STATE_MASK);
|
||||
if (state == SHPC_STATE_ENABLED) {
|
||||
shpc_invalid_command(shpc);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < shpc->nslots; ++i) {
|
||||
if (!(shpc_get_status(shpc, i, SHPC_SLOT_STATUS_MRL_OPEN))) {
|
||||
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
|
||||
SHPC_STATE_ENABLED, SHPC_LED_ON, SHPC_LED_NO);
|
||||
} else {
|
||||
shpc_slot_command(d, i + SHPC_CMD_TRGT_MIN,
|
||||
SHPC_STATE_NO, SHPC_LED_OFF, SHPC_LED_NO);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
shpc_invalid_command(shpc);
|
||||
break;
|
||||
}
|
||||
done:
|
||||
pci_long_test_and_set_mask(shpc->config + SHPC_SERR_INT, SHPC_CMD_DETECTED);
|
||||
}
|
||||
|
||||
static void shpc_write(PCIDevice *d, unsigned addr, uint64_t val, int l)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
int i;
|
||||
if (addr >= SHPC_SIZEOF(d)) {
|
||||
return;
|
||||
}
|
||||
l = MIN(l, SHPC_SIZEOF(d) - addr);
|
||||
|
||||
/* TODO: code duplicated from pci.c */
|
||||
for (i = 0; i < l; val >>= 8, ++i) {
|
||||
unsigned a = addr + i;
|
||||
uint8_t wmask = shpc->wmask[a];
|
||||
uint8_t w1cmask = shpc->w1cmask[a];
|
||||
assert(!(wmask & w1cmask));
|
||||
shpc->config[a] = (shpc->config[a] & ~wmask) | (val & wmask);
|
||||
shpc->config[a] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
|
||||
}
|
||||
if (ranges_overlap(addr, l, SHPC_CMD_CODE, 2)) {
|
||||
shpc_command(d);
|
||||
}
|
||||
shpc_interrupt_update(d);
|
||||
}
|
||||
|
||||
static uint64_t shpc_read(PCIDevice *d, unsigned addr, int l)
|
||||
{
|
||||
uint64_t val = 0x0;
|
||||
if (addr >= SHPC_SIZEOF(d)) {
|
||||
return val;
|
||||
}
|
||||
l = MIN(l, SHPC_SIZEOF(d) - addr);
|
||||
memcpy(&val, d->shpc->config + addr, l);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* SHPC Bridge Capability */
|
||||
#define SHPC_CAP_LENGTH 0x08
|
||||
#define SHPC_CAP_DWORD_SELECT 0x2 /* 1 byte */
|
||||
#define SHPC_CAP_CxP 0x3 /* 1 byte: CSP, CIP */
|
||||
#define SHPC_CAP_DWORD_DATA 0x4 /* 4 bytes */
|
||||
#define SHPC_CAP_CSP_MASK 0x4
|
||||
#define SHPC_CAP_CIP_MASK 0x8
|
||||
|
||||
static uint8_t shpc_cap_dword(PCIDevice *d)
|
||||
{
|
||||
return pci_get_byte(d->config + d->shpc->cap + SHPC_CAP_DWORD_SELECT);
|
||||
}
|
||||
|
||||
/* Update dword data capability register */
|
||||
static void shpc_cap_update_dword(PCIDevice *d)
|
||||
{
|
||||
unsigned data;
|
||||
data = shpc_read(d, shpc_cap_dword(d) * 4, 4);
|
||||
pci_set_long(d->config + d->shpc->cap + SHPC_CAP_DWORD_DATA, data);
|
||||
}
|
||||
|
||||
/* Add SHPC capability to the config space for the device. */
|
||||
static int shpc_cap_add_config(PCIDevice *d, Error **errp)
|
||||
{
|
||||
uint8_t *config;
|
||||
int config_offset;
|
||||
config_offset = pci_add_capability(d, PCI_CAP_ID_SHPC,
|
||||
0, SHPC_CAP_LENGTH,
|
||||
errp);
|
||||
if (config_offset < 0) {
|
||||
return config_offset;
|
||||
}
|
||||
config = d->config + config_offset;
|
||||
|
||||
pci_set_byte(config + SHPC_CAP_DWORD_SELECT, 0);
|
||||
pci_set_byte(config + SHPC_CAP_CxP, 0);
|
||||
pci_set_long(config + SHPC_CAP_DWORD_DATA, 0);
|
||||
d->shpc->cap = config_offset;
|
||||
/* Make dword select and data writable. */
|
||||
pci_set_byte(d->wmask + config_offset + SHPC_CAP_DWORD_SELECT, 0xff);
|
||||
pci_set_long(d->wmask + config_offset + SHPC_CAP_DWORD_DATA, 0xffffffff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t shpc_mmio_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
return shpc_read(opaque, addr, size);
|
||||
}
|
||||
|
||||
static void shpc_mmio_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
shpc_write(opaque, addr, val, size);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps shpc_mmio_ops = {
|
||||
.read = shpc_mmio_read,
|
||||
.write = shpc_mmio_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
/* SHPC ECN requires dword accesses, but the original 1.0 spec doesn't.
|
||||
* It's easier to support all sizes than worry about it.
|
||||
*/
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static bool shpc_device_get_slot(PCIDevice *affected_dev, int *slot,
|
||||
SHPCDevice *shpc, Error **errp)
|
||||
{
|
||||
int pci_slot = PCI_SLOT(affected_dev->devfn);
|
||||
*slot = SHPC_PCI_TO_IDX(pci_slot);
|
||||
|
||||
if (pci_slot < SHPC_IDX_TO_PCI(0) || *slot >= shpc->nslots) {
|
||||
error_setg(errp, "Unsupported PCI slot %d for standard hotplug "
|
||||
"controller. Valid slots are between %d and %d.",
|
||||
pci_slot, SHPC_IDX_TO_PCI(0),
|
||||
SHPC_IDX_TO_PCI(shpc->nslots) - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void shpc_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||||
Error **errp)
|
||||
{
|
||||
PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
|
||||
SHPCDevice *shpc = pci_hotplug_dev->shpc;
|
||||
int slot;
|
||||
|
||||
if (!shpc_device_get_slot(PCI_DEVICE(dev), &slot, shpc, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Don't send event when device is enabled during qemu machine creation:
|
||||
* it is present on boot, no hotplug event is necessary. We do send an
|
||||
* event when the device is disabled later. */
|
||||
if (!dev->hotplugged) {
|
||||
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
|
||||
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
|
||||
SHPC_SLOT_STATUS_PRSNT_MASK);
|
||||
return;
|
||||
}
|
||||
|
||||
/* This could be a cancellation of the previous removal.
|
||||
* We check MRL state to figure out. */
|
||||
if (shpc_get_status(shpc, slot, SHPC_SLOT_STATUS_MRL_OPEN)) {
|
||||
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_MRL_OPEN);
|
||||
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_7_5W,
|
||||
SHPC_SLOT_STATUS_PRSNT_MASK);
|
||||
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
|
||||
SHPC_SLOT_EVENT_BUTTON |
|
||||
SHPC_SLOT_EVENT_MRL |
|
||||
SHPC_SLOT_EVENT_PRESENCE;
|
||||
} else {
|
||||
/* Press attention button to cancel removal */
|
||||
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
|
||||
SHPC_SLOT_EVENT_BUTTON;
|
||||
}
|
||||
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
|
||||
shpc_interrupt_update(pci_hotplug_dev);
|
||||
}
|
||||
|
||||
void shpc_device_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||||
Error **errp)
|
||||
{
|
||||
qdev_unrealize(dev);
|
||||
}
|
||||
|
||||
void shpc_device_unplug_request_cb(HotplugHandler *hotplug_dev,
|
||||
DeviceState *dev, Error **errp)
|
||||
{
|
||||
PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev);
|
||||
SHPCDevice *shpc = pci_hotplug_dev->shpc;
|
||||
uint8_t state;
|
||||
uint8_t led;
|
||||
int slot;
|
||||
|
||||
if (!shpc_device_get_slot(PCI_DEVICE(dev), &slot, shpc, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = shpc_get_status(shpc, slot, SHPC_SLOT_STATE_MASK);
|
||||
led = shpc_get_status(shpc, slot, SHPC_SLOT_PWR_LED_MASK);
|
||||
|
||||
if (led == SHPC_LED_BLINK) {
|
||||
error_setg(errp, "Hot-unplug failed: "
|
||||
"guest is busy (power indicator blinking)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SHPC_STATE_DISABLED && led == SHPC_LED_OFF) {
|
||||
shpc_free_devices_in_slot(shpc, slot);
|
||||
shpc_set_status(shpc, slot, 1, SHPC_SLOT_STATUS_MRL_OPEN);
|
||||
shpc_set_status(shpc, slot, SHPC_SLOT_STATUS_PRSNT_EMPTY,
|
||||
SHPC_SLOT_STATUS_PRSNT_MASK);
|
||||
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |=
|
||||
SHPC_SLOT_EVENT_MRL |
|
||||
SHPC_SLOT_EVENT_PRESENCE;
|
||||
} else {
|
||||
shpc->config[SHPC_SLOT_EVENT_LATCH(slot)] |= SHPC_SLOT_EVENT_BUTTON;
|
||||
}
|
||||
shpc_set_status(shpc, slot, 0, SHPC_SLOT_STATUS_66);
|
||||
shpc_interrupt_update(pci_hotplug_dev);
|
||||
}
|
||||
|
||||
/* Initialize the SHPC structure in bridge's BAR. */
|
||||
int shpc_init(PCIDevice *d, PCIBus *sec_bus, MemoryRegion *bar,
|
||||
unsigned offset, Error **errp)
|
||||
{
|
||||
int i, ret;
|
||||
int nslots = SHPC_MAX_SLOTS; /* TODO: qdev property? */
|
||||
SHPCDevice *shpc = d->shpc = g_malloc0(sizeof(*d->shpc));
|
||||
shpc->sec_bus = sec_bus;
|
||||
ret = shpc_cap_add_config(d, errp);
|
||||
if (ret) {
|
||||
g_free(d->shpc);
|
||||
return ret;
|
||||
}
|
||||
if (nslots < SHPC_MIN_SLOTS) {
|
||||
return 0;
|
||||
}
|
||||
if (nslots > SHPC_MAX_SLOTS ||
|
||||
SHPC_IDX_TO_PCI(nslots) > PCI_SLOT_MAX) {
|
||||
/* TODO: report an error message that makes sense. */
|
||||
return -EINVAL;
|
||||
}
|
||||
shpc->nslots = nslots;
|
||||
shpc->config = g_malloc0(SHPC_SIZEOF(d));
|
||||
shpc->cmask = g_malloc0(SHPC_SIZEOF(d));
|
||||
shpc->wmask = g_malloc0(SHPC_SIZEOF(d));
|
||||
shpc->w1cmask = g_malloc0(SHPC_SIZEOF(d));
|
||||
|
||||
shpc_reset(d);
|
||||
|
||||
pci_set_long(shpc->config + SHPC_BASE_OFFSET, offset);
|
||||
|
||||
pci_set_byte(shpc->wmask + SHPC_CMD_CODE, 0xff);
|
||||
pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
|
||||
pci_set_byte(shpc->wmask + SHPC_CMD_TRGT, SHPC_CMD_TRGT_MAX);
|
||||
pci_set_long(shpc->wmask + SHPC_SERR_INT,
|
||||
SHPC_INT_DIS |
|
||||
SHPC_SERR_DIS |
|
||||
SHPC_CMD_INT_DIS |
|
||||
SHPC_ARB_SERR_DIS);
|
||||
pci_set_long(shpc->w1cmask + SHPC_SERR_INT,
|
||||
SHPC_CMD_DETECTED |
|
||||
SHPC_ARB_DETECTED);
|
||||
for (i = 0; i < nslots; ++i) {
|
||||
pci_set_byte(shpc->wmask +
|
||||
SHPC_SLOT_EVENT_SERR_INT_DIS(d, i),
|
||||
SHPC_SLOT_EVENT_PRESENCE |
|
||||
SHPC_SLOT_EVENT_ISOLATED_FAULT |
|
||||
SHPC_SLOT_EVENT_BUTTON |
|
||||
SHPC_SLOT_EVENT_MRL |
|
||||
SHPC_SLOT_EVENT_CONNECTED_FAULT |
|
||||
SHPC_SLOT_EVENT_MRL_SERR_DIS |
|
||||
SHPC_SLOT_EVENT_CONNECTED_FAULT_SERR_DIS);
|
||||
pci_set_byte(shpc->w1cmask +
|
||||
SHPC_SLOT_EVENT_LATCH(i),
|
||||
SHPC_SLOT_EVENT_PRESENCE |
|
||||
SHPC_SLOT_EVENT_ISOLATED_FAULT |
|
||||
SHPC_SLOT_EVENT_BUTTON |
|
||||
SHPC_SLOT_EVENT_MRL |
|
||||
SHPC_SLOT_EVENT_CONNECTED_FAULT);
|
||||
}
|
||||
|
||||
/* TODO: init cmask */
|
||||
memory_region_init_io(&shpc->mmio, OBJECT(d), &shpc_mmio_ops,
|
||||
d, "shpc-mmio", SHPC_SIZEOF(d));
|
||||
shpc_cap_update_dword(d);
|
||||
memory_region_add_subregion(bar, offset, &shpc->mmio);
|
||||
|
||||
qbus_set_hotplug_handler(BUS(sec_bus), OBJECT(d));
|
||||
|
||||
d->cap_present |= QEMU_PCI_CAP_SHPC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int shpc_bar_size(PCIDevice *d)
|
||||
{
|
||||
return pow2roundup32(SHPC_SLOT_REG(SHPC_MAX_SLOTS));
|
||||
}
|
||||
|
||||
void shpc_cleanup(PCIDevice *d, MemoryRegion *bar)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
d->cap_present &= ~QEMU_PCI_CAP_SHPC;
|
||||
memory_region_del_subregion(bar, &shpc->mmio);
|
||||
/* TODO: cleanup config space changes? */
|
||||
}
|
||||
|
||||
void shpc_free(PCIDevice *d)
|
||||
{
|
||||
SHPCDevice *shpc = d->shpc;
|
||||
if (!shpc) {
|
||||
return;
|
||||
}
|
||||
g_free(shpc->config);
|
||||
g_free(shpc->cmask);
|
||||
g_free(shpc->wmask);
|
||||
g_free(shpc->w1cmask);
|
||||
g_free(shpc);
|
||||
d->shpc = NULL;
|
||||
}
|
||||
|
||||
void shpc_cap_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
|
||||
{
|
||||
if (!ranges_overlap(addr, l, d->shpc->cap, SHPC_CAP_LENGTH)) {
|
||||
return;
|
||||
}
|
||||
if (ranges_overlap(addr, l, d->shpc->cap + SHPC_CAP_DWORD_DATA, 4)) {
|
||||
unsigned dword_data;
|
||||
dword_data = pci_get_long(d->shpc->config + d->shpc->cap
|
||||
+ SHPC_CAP_DWORD_DATA);
|
||||
shpc_write(d, shpc_cap_dword(d) * 4, dword_data, 4);
|
||||
}
|
||||
/* Update cap dword data in case guest is going to read it. */
|
||||
shpc_cap_update_dword(d);
|
||||
}
|
||||
|
||||
static int shpc_save(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field, JSONWriter *vmdesc)
|
||||
{
|
||||
PCIDevice *d = container_of(pv, PCIDevice, shpc);
|
||||
qemu_put_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int shpc_load(QEMUFile *f, void *pv, size_t size,
|
||||
const VMStateField *field)
|
||||
{
|
||||
PCIDevice *d = container_of(pv, PCIDevice, shpc);
|
||||
int ret = qemu_get_buffer(f, d->shpc->config, SHPC_SIZEOF(d));
|
||||
if (ret != SHPC_SIZEOF(d)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Make sure we don't lose notifications. An extra interrupt is harmless. */
|
||||
d->shpc->msi_requested = 0;
|
||||
shpc_interrupt_update(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const VMStateInfo shpc_vmstate_info = {
|
||||
.name = "shpc",
|
||||
.get = shpc_load,
|
||||
.put = shpc_save,
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/pci/slotid_cap.h"
|
||||
#include "hw/pci/pci_device.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
#define SLOTID_CAP_LENGTH 4
|
||||
#define SLOTID_NSLOTS_SHIFT ctz32(PCI_SID_ESR_NSLOTS)
|
||||
|
||||
int slotid_cap_init(PCIDevice *d, int nslots,
|
||||
uint8_t chassis,
|
||||
unsigned offset,
|
||||
Error **errp)
|
||||
{
|
||||
int cap;
|
||||
|
||||
if (!chassis) {
|
||||
error_setg(errp, "Bridge chassis not specified. Each bridge is required"
|
||||
" to be assigned a unique chassis id > 0.");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (nslots < 0 || nslots > (PCI_SID_ESR_NSLOTS >> SLOTID_NSLOTS_SHIFT)) {
|
||||
/* TODO: error report? */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cap = pci_add_capability(d, PCI_CAP_ID_SLOTID, offset,
|
||||
SLOTID_CAP_LENGTH, errp);
|
||||
if (cap < 0) {
|
||||
return cap;
|
||||
}
|
||||
/* We make each chassis unique, this way each bridge is First in Chassis */
|
||||
d->config[cap + PCI_SID_ESR] = PCI_SID_ESR_FIC |
|
||||
(nslots << SLOTID_NSLOTS_SHIFT);
|
||||
d->cmask[cap + PCI_SID_ESR] = 0xff;
|
||||
d->config[cap + PCI_SID_CHASSIS_NR] = chassis;
|
||||
/* Note: Chassis number register is non-volatile,
|
||||
so we don't reset it. */
|
||||
/* TODO: store in eeprom? */
|
||||
d->wmask[cap + PCI_SID_CHASSIS_NR] = 0xff;
|
||||
|
||||
d->cap_present |= QEMU_PCI_CAP_SLOTID;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void slotid_cap_cleanup(PCIDevice *d)
|
||||
{
|
||||
/* TODO: cleanup config space? */
|
||||
d->cap_present &= ~QEMU_PCI_CAP_SLOTID;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# See docs/devel/tracing.rst for syntax documentation.
|
||||
|
||||
# pci.c
|
||||
pci_pm_bad_transition(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, uint8_t old, uint8_t new) "%s %02x:%02x.%x REJECTED PM transition D%d->D%d"
|
||||
pci_pm_transition(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, uint8_t old, uint8_t new) "%s %02x:%02x.%x PM transition D%d->D%d"
|
||||
pci_update_mappings_del(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, int bar, uint64_t addr, uint64_t size) "%s %02x:%02x.%x %d,0x%"PRIx64"+0x%"PRIx64
|
||||
pci_update_mappings_add(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, int bar, uint64_t addr, uint64_t size) "%s %02x:%02x.%x %d,0x%"PRIx64"+0x%"PRIx64
|
||||
pci_route_irq(int dev_irq, const char *dev_path, int parent_irq, const char *parent_path) "IRQ %d @%s -> IRQ %d @%s"
|
||||
pci_bad_rom_magic(uint16_t bad_rom_magic, uint16_t good_rom_magic) "Bad ROM magic number: %04"PRIX16". Should be: %04"PRIX16
|
||||
pci_bad_pcir_offset(uint16_t pcir_offset) "Bad PCIR offset 0x%"PRIx16" or signature"
|
||||
pci_rom_and_pci_ids(char *romfile, uint16_t vendor_id, uint16_t device_id, uint16_t rom_vendor_id, uint16_t rom_device_id) "%s: ROM ID %04"PRIx16":%04"PRIx16" | PCI ID %04"PRIx16":%04"PRIx16
|
||||
pci_rom_checksum_change(uint8_t old_checksum, uint8_t new_checksum) "ROM checksum changed from %02"PRIx8" to %02"PRIx8
|
||||
|
||||
# pci_host.c
|
||||
pci_cfg_read(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, unsigned offs, unsigned val) "%s %02x:%02x.%x @0x%x -> 0x%x"
|
||||
pci_cfg_write(const char *dev, uint32_t bus, uint32_t slot, uint32_t func, unsigned offs, unsigned val) "%s %02x:%02x.%x @0x%x <- 0x%x"
|
||||
|
||||
# msix.c
|
||||
msix_write_config(char *name, bool enabled, bool masked) "dev %s enabled %d masked %d"
|
||||
|
||||
# hw/pci/pcie_sriov.c
|
||||
sriov_register_vfs(const char *name, int slot, int function, int num_vfs) "%s %02x:%x: creating %d vf devs"
|
||||
sriov_unregister_vfs(const char *name, int slot, int function) "%s %02x:%x: Unregistering vf devs"
|
||||
sriov_config_write(const char *name, int slot, int fun, uint32_t offset, uint32_t val, uint32_t len) "%s %02x:%x: sriov offset 0x%x val 0x%x len %d"
|
||||
|
||||
# pcie.c
|
||||
pcie_cap_slot_write_config(const char *parent, const char *child, const char *pds, const char *old_pic, const char *new_pic, const char *old_aic, const char *new_aic, const char *old_power, const char *new_power) "%s > %s: pds: %s, pic: %s->%s, aic: %s->%s, power: %s->%s"
|
||||
|
||||
# shpc.c
|
||||
shpc_slot_command(const char *parent, int pci_slot, const char *child, const char *old_pic, const char *new_pic, const char *old_aic, const char *new_aic, const char *old_state, const char *new_state) "%s[%d] > %s: pic: %s->%s, aic: %s->%s, state: %s->%s"
|
||||
@@ -0,0 +1 @@
|
||||
#include "trace/trace-hw_pci.h"
|
||||
Reference in New Issue
Block a user