Import QEMU upstream snapshot d2e570c

Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
2026-08-31 02:15:30 +02:00
commit cf256aa081
11315 changed files with 3598369 additions and 0 deletions
+18
View File
@@ -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
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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()
}
};
+249
View File
@@ -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);
}
+24
View File
@@ -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
+199
View File
@@ -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;
}
+92
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+511
View File
@@ -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)
+275
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+957
View File
@@ -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;
}
+386
View File
@@ -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;
}
}
+136
View File
@@ -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)
+245
View File
@@ -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)
+519
View File
@@ -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
View File
@@ -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,
};
+50
View File
@@ -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;
}
+30
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
#include "trace/trace-hw_pci.h"