]> git.proxmox.com Git - qemu.git/blobdiff - hw/pcie_aer.c
PPC: Fix TLB invalidation bug within the PPC interrupt handler.
[qemu.git] / hw / pcie_aer.c
index 9505c933431ed7b1979f556a5dfdf31ebae91349..3b6981c7b767ffdcda3cb24e133b16dc4bf6d100 100644 (file)
@@ -19,6 +19,8 @@
  */
 
 #include "sysemu.h"
+#include "qemu-objects.h"
+#include "monitor.h"
 #include "pci_bridge.h"
 #include "pcie.h"
 #include "msix.h"
@@ -36,6 +38,9 @@
 #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)
 {
@@ -106,7 +111,7 @@ int pcie_aer_init(PCIDevice *dev, uint16_t offset)
     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,
@@ -160,7 +165,7 @@ int pcie_aer_init(PCIDevice *dev, uint16_t offset)
 
 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)
@@ -273,30 +278,33 @@ static uint32_t pcie_aer_status_to_cmd(uint32_t status)
     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, prev_status;
-    bool msi_trigger;
 
-    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);
     prev_status = root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
-    msi_trigger = false;
 
     if (cmd & PCI_COMMAND_SERR) {
         /* System Error.
@@ -315,25 +323,15 @@ static bool pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *msg)
         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;
         }
@@ -347,7 +345,8 @@ static bool pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *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_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;
     }
@@ -360,18 +359,10 @@ static bool pcie_aer_msg_root_port(PCIDevice *dev, const PCIEAERMsg *msg)
     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 msg_sent;
+        return;
     }
 
-    msg_sent = true;
-    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 msg_sent;
+    pcie_aer_root_notify(dev);
 }
 
 /*
@@ -424,7 +415,7 @@ static void pcie_aer_update_log(PCIDevice *dev, const PCIEAERErr *err)
     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);
@@ -504,7 +495,7 @@ static int pcie_aer_record_error(PCIDevice *dev,
     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))) {
@@ -626,7 +617,7 @@ static bool pcie_aer_inject_uncor_error(PCIEAERInject *inj, bool is_fatal)
 /*
  * 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
@@ -762,44 +753,26 @@ 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;
-        uint32_t root_status = pci_get_long(aer_cap + PCI_ERR_ROOT_STATUS);
-        bool trigger = pcie_aer_root_does_trigger(root_cmd_set, 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 (msix_enabled(dev)) {
-            if (trigger) {
-                msix_notify(dev, pcie_aer_root_get_vector(dev));
-            }
-        } else if (msi_enabled(dev)) {
-            if (trigger) {
-                msi_notify(dev, pcie_aer_root_get_vector(dev));
-            }
-        } else {
-            qemu_set_irq(dev->irq[dev->exp.aer_intx], trigger);
-        }
+    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 = {
@@ -817,16 +790,6 @@ 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,
@@ -835,8 +798,230 @@ const VMStateDescription vmstate_pcie_aer_log = {
     .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;
+}