*/
#include "sysemu.h"
+#include "qemu-objects.h"
+#include "monitor.h"
#include "pci_bridge.h"
#include "pcie.h"
#include "msix.h"
#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)
{
if (dev->exp.aer_log.log_max > PCIE_AER_LOG_MAX_LIMIT) {
return -EINVAL;
}
- dev->exp.aer_log.log = qemu_mallocz(sizeof dev->exp.aer_log.log[0] *
+ 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,
void pcie_aer_exit(PCIDevice *dev)
{
- qemu_free(dev->exp.aer_log.log);
+ g_free(dev->exp.aer_log.log);
}
static void pcie_aer_update_uncor_status(PCIDevice *dev)
return true;
}
-/* Get parent port to send up error message on.
- * TODO: clean up and open-code this logic */
-static PCIDevice *pcie_aer_parent_port(PCIDevice *dev)
-{
- PCIDevice *parent_port;
- if (pci_is_express(dev) &&
- pcie_cap_get_type(dev) == PCI_EXP_TYPE_ROOT_PORT) {
- /* 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.
- */
- parent_port = NULL;
- } else {
- parent_port = pci_bridge_get_device(dev->bus);
- }
- if (parent_port) {
- if (!pci_is_express(parent_port)) {
- /* just ignore it */
- return NULL;
- }
- }
- return parent_port;
-}
-
/*
* return value:
* true: error message is sent up
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 {
+ qemu_set_irq(dev->irq[dev->exp.aer_intx], 1);
+ }
+}
+
/*
- * return value:
- * true: error message is sent up
- * false: error message is masked
- *
* 6.2.6 Error Message Control
* Figure 6-3
* root port part
*/
-static bool pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *msg)
+static void pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *msg)
{
- bool msg_sent;
uint16_t cmd;
uint8_t *aer_cap;
uint32_t root_cmd;
- uint32_t root_status;
- bool msi_trigger;
+ uint32_t root_status, prev_status;
- msg_sent = false;
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);
- root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
- msi_trigger = false;
+ prev_status = root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
if (cmd & PCI_COMMAND_SERR) {
/* System Error.
if (root_status & PCI_ERR_ROOT_COR_RCV) {
root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
} else {
- if (root_cmd & PCI_ERR_ROOT_CMD_COR_EN) {
- msi_trigger = true;
- }
- pci_set_word(aer_cap + PCI_ERR_ROOT_COR_SRC, msg->source_id);
+ 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:
- if (!(root_status & PCI_ERR_ROOT_NONFATAL_RCV) &&
- root_cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) {
- msi_trigger = true;
- }
root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
break;
case PCI_ERR_ROOT_CMD_FATAL_EN:
- if (!(root_status & PCI_ERR_ROOT_FATAL_RCV) &&
- root_cmd & PCI_ERR_ROOT_CMD_FATAL_EN) {
- msi_trigger = true;
- }
if (!(root_status & PCI_ERR_ROOT_UNCOR_RCV)) {
root_status |= PCI_ERR_ROOT_FIRST_FATAL;
}
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_SRC, msg->source_id);
+ 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);
- if (root_cmd & msg->severity) {
- /* 6.2.4.1.2 Interrupt Generation */
- if (pci_msi_enabled(dev)) {
- if (msi_trigger) {
- pci_msi_notify(dev, pcie_aer_root_get_vector(dev));
- }
- } else {
- qemu_set_irq(dev->irq[dev->exp.aer_intx], 1);
- }
- msg_sent = true;
+ /* 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;
}
- return msg_sent;
+
+ pcie_aer_root_notify(dev);
}
/*
* 6.2.6 Error Message Control Figure 6-3
*
- * Returns true in case the error needs to
- * be propagated up.
- * TODO: open-code.
+ * Walk up the bus tree from the device, propagate the error message.
*/
-static bool pcie_send_aer_msg(PCIDevice *dev, const PCIEAERMsg *msg)
+static void pcie_aer_msg(PCIDevice *dev, const PCIEAERMsg *msg)
{
uint8_t type;
- bool msg_sent;
-
- assert(pci_is_express(dev));
- type = pcie_cap_get_type(dev);
- if (type == PCI_EXP_TYPE_ROOT_PORT ||
- type == PCI_EXP_TYPE_UPSTREAM ||
- type == PCI_EXP_TYPE_DOWNSTREAM) {
- msg_sent = pcie_aer_msg_vbridge(dev, msg);
- if (!msg_sent) {
+ 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;
}
- }
- msg_sent = pcie_aer_msg_alldev(dev, msg);
- if (type == PCI_EXP_TYPE_ROOT_PORT && msg_sent) {
- pcie_aer_msg_root_port(dev, msg);
- }
- return msg_sent;
-}
-static void pcie_aer_msg(PCIDevice *dev, const PCIEAERMsg *msg)
-{
- bool send_to_parent;
- while (dev) {
- if (!pcie_send_aer_msg(dev, msg)) {
+ 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;
}
- dev = pcie_aer_parent_port(dev);
+ 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(dev->bus);
}
}
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 = ffsl(err->status) - 1;
+ uint8_t first_bit = ffs(err->status) - 1;
uint32_t errcap = pci_get_long(aer_cap + PCI_ERR_CAP);
int i;
assert(err->status);
- assert(err->status & (err->status - 1));
+ assert(!(err->status & (err->status - 1)));
errcap &= ~(PCI_ERR_CAP_FEP_MASK | PCI_ERR_CAP_TLP);
errcap |= PCI_ERR_CAP_FEP(first_bit);
int fep = PCI_ERR_CAP_FEP(errcap);
assert(err->status);
- assert(err->status & (err->status - 1));
+ assert(!(err->status & (err->status - 1)));
if (errcap & PCI_ERR_CAP_MHRE &&
(pci_get_long(aer_cap + PCI_ERR_UNCOR_STATUS) & (1U << fep))) {
/*
* 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 responsiblity to determine which function should
+ * It is also caller's responsibility to determine which function should
* report the rerror.
*
* 6.2.4 Error Logging
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)
*/
}
-static bool pcie_aer_root_does_trigger(uint32_t cmd, uint32_t status)
-{
- return
- ((cmd & PCI_ERR_ROOT_CMD_COR_EN) && (status & PCI_ERR_ROOT_COR_RCV)) ||
- ((cmd & PCI_ERR_ROOT_CMD_NONFATAL_EN) &&
- (status & PCI_ERR_ROOT_NONFATAL_RCV)) ||
- ((cmd & PCI_ERR_ROOT_CMD_FATAL_EN) &&
- (status & PCI_ERR_ROOT_FATAL_RCV));
-}
-
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;
-
- /* root command register */
+ 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);
- if (root_cmd & PCI_ERR_ROOT_CMD_EN_MASK) {
- /* 6.2.4.1.2 Interrupt Generation */
-
- /* 0 -> 1 */
- uint32_t root_cmd_set = (root_cmd_prev ^ root_cmd) & root_cmd;
- uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
+ /* 6.2.4.1.2 Interrupt Generation */
+ if (!msix_enabled(dev) && !msi_enabled(dev)) {
+ qemu_set_irq(dev->irq[dev->exp.aer_intx], !!(root_cmd & enabled_cmd));
+ return;
+ }
- if (pci_msi_enabled(dev)) {
- if (pcie_aer_root_does_trigger(root_cmd_set, root_status)) {
- pci_msi_notify(dev, pcie_aer_root_get_vector(dev));
- }
- } else {
- int int_level = pcie_aer_root_does_trigger(root_cmd, root_status);
- qemu_set_irq(dev->irq[dev->exp.aer_intx], int_level);
- }
+ 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 = {
}
};
-#define VMSTATE_PCIE_AER_ERRS(_field, _state, _field_num, _vmsd, _type) { \
- .name = (stringify(_field)), \
- .version_id = 0, \
- .num_offset = vmstate_offset_value(_state, _field_num, uint16_t), \
- .size = sizeof(_type), \
- .vmsd = &(_vmsd), \
- .flags = VMS_POINTER | VMS_VARRAY_UINT16 | VMS_STRUCT, \
- .offset = vmstate_offset_pointer(_state, _field, _type), \
-}
-
const VMStateDescription vmstate_pcie_aer_log = {
.name = "PCIE_AER_ERROR_LOG",
.version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT16(log_num, PCIEAERLog),
VMSTATE_UINT16(log_max, PCIEAERLog),
- VMSTATE_PCIE_AER_ERRS(log, PCIEAERLog, log_num,
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT16(log, PCIEAERLog, log_num,
vmstate_pcie_aer_err, PCIEAERErr),
VMSTATE_END_OF_LIST()
}
};
+
+void pcie_aer_inject_error_print(Monitor *mon, const QObject *data)
+{
+ QDict *qdict;
+ int devfn;
+ assert(qobject_type(data) == QTYPE_QDICT);
+ qdict = qobject_to_qdict(data);
+
+ devfn = (int)qdict_get_int(qdict, "devfn");
+ monitor_printf(mon, "OK id: %s domain: %x, bus: %x devfn: %x.%x\n",
+ qdict_get_str(qdict, "id"),
+ (int) qdict_get_int(qdict, "domain"),
+ (int) qdict_get_int(qdict, "bus"),
+ PCI_SLOT(devfn), PCI_FUNC(devfn));
+}
+
+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 = "TRAIN",
+ .val = PCI_ERR_UNC_TRAIN,
+ .correctable = false,
+ }, {
+ .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,
+ },
+};
+
+static 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;
+}
+
+int do_pcie_aer_inject_error(Monitor *mon,
+ const QDict *qdict, QObject **ret_data)
+{
+ const char *id = qdict_get_str(qdict, "id");
+ const char *error_name;
+ uint32_t error_status;
+ bool correctable;
+ PCIDevice *dev;
+ PCIEAERErr err;
+ int ret;
+
+ ret = pci_qdev_find_device(id, &dev);
+ if (ret < 0) {
+ monitor_printf(mon,
+ "id or pci device path is invalid or device not "
+ "found. %s\n", id);
+ return ret;
+ }
+ if (!pci_is_express(dev)) {
+ monitor_printf(mon, "the device doesn't support pci express. %s\n",
+ id);
+ return -ENOSYS;
+ }
+
+ error_name = qdict_get_str(qdict, "error_status");
+ if (pcie_aer_parse_error_string(error_name, &error_status, &correctable)) {
+ char *e = NULL;
+ error_status = strtoul(error_name, &e, 0);
+ correctable = qdict_get_try_bool(qdict, "correctable", 0);
+ if (!e || *e != '\0') {
+ monitor_printf(mon, "invalid error status value. \"%s\"",
+ error_name);
+ return -EINVAL;
+ }
+ }
+ err.status = error_status;
+ err.source_id = (pci_bus_num(dev->bus) << 8) | dev->devfn;
+
+ err.flags = 0;
+ if (correctable) {
+ err.flags |= PCIE_AER_ERR_IS_CORRECTABLE;
+ }
+ if (qdict_get_try_bool(qdict, "advisory_non_fatal", 0)) {
+ err.flags |= PCIE_AER_ERR_MAYBE_ADVISORY;
+ }
+ if (qdict_haskey(qdict, "header0")) {
+ err.flags |= PCIE_AER_ERR_HEADER_VALID;
+ }
+ if (qdict_haskey(qdict, "prefix0")) {
+ err.flags |= PCIE_AER_ERR_TLP_PREFIX_PRESENT;
+ }
+
+ err.header[0] = qdict_get_try_int(qdict, "header0", 0);
+ err.header[1] = qdict_get_try_int(qdict, "header1", 0);
+ err.header[2] = qdict_get_try_int(qdict, "header2", 0);
+ err.header[3] = qdict_get_try_int(qdict, "header3", 0);
+
+ err.prefix[0] = qdict_get_try_int(qdict, "prefix0", 0);
+ err.prefix[1] = qdict_get_try_int(qdict, "prefix1", 0);
+ err.prefix[2] = qdict_get_try_int(qdict, "prefix2", 0);
+ err.prefix[3] = qdict_get_try_int(qdict, "prefix3", 0);
+
+ ret = pcie_aer_inject_error(dev, &err);
+ *ret_data = qobject_from_jsonf("{'id': %s, "
+ "'domain': %d, 'bus': %d, 'devfn': %d, "
+ "'ret': %d}",
+ id,
+ pci_find_domain(dev->bus),
+ pci_bus_num(dev->bus), dev->devfn,
+ ret);
+ assert(*ret_data);
+
+ return 0;
+}