Merge tag 'cxl-for-6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl
[sfrench/cifs-2.6.git] / drivers / pci / pcie / aer.c
index dcd35993004e301f431fd13cef84911e7ad834c2..42a3bd35a3e118d8eb656d1d24b9f0fa0f4afaf7 100644 (file)
@@ -760,9 +760,10 @@ int cper_severity_to_aer(int cper_severity)
        }
 }
 EXPORT_SYMBOL_GPL(cper_severity_to_aer);
+#endif
 
-void cper_print_aer(struct pci_dev *dev, int aer_severity,
-                   struct aer_capability_regs *aer)
+void pci_print_aer(struct pci_dev *dev, int aer_severity,
+                  struct aer_capability_regs *aer)
 {
        int layer, agent, tlp_header_valid = 0;
        u32 status, mask;
@@ -801,7 +802,7 @@ void cper_print_aer(struct pci_dev *dev, int aer_severity,
        trace_aer_event(dev_name(&dev->dev), (status & ~mask),
                        aer_severity, tlp_header_valid, &aer->header_log);
 }
-#endif
+EXPORT_SYMBOL_NS_GPL(pci_print_aer, CXL);
 
 /**
  * add_error_device - list device to be handled
@@ -934,14 +935,153 @@ static bool find_source_device(struct pci_dev *parent,
        return true;
 }
 
+#ifdef CONFIG_PCIEAER_CXL
+
 /**
- * handle_error_source - handle logging error into an event log
+ * pci_aer_unmask_internal_errors - unmask internal errors
+ * @dev: pointer to the pcie_dev data structure
+ *
+ * Unmasks internal errors in the Uncorrectable and Correctable Error
+ * Mask registers.
+ *
+ * Note: AER must be enabled and supported by the device which must be
+ * checked in advance, e.g. with pcie_aer_is_native().
+ */
+static void pci_aer_unmask_internal_errors(struct pci_dev *dev)
+{
+       int aer = dev->aer_cap;
+       u32 mask;
+
+       pci_read_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, &mask);
+       mask &= ~PCI_ERR_UNC_INTN;
+       pci_write_config_dword(dev, aer + PCI_ERR_UNCOR_MASK, mask);
+
+       pci_read_config_dword(dev, aer + PCI_ERR_COR_MASK, &mask);
+       mask &= ~PCI_ERR_COR_INTERNAL;
+       pci_write_config_dword(dev, aer + PCI_ERR_COR_MASK, mask);
+}
+
+static bool is_cxl_mem_dev(struct pci_dev *dev)
+{
+       /*
+        * The capability, status, and control fields in Device 0,
+        * Function 0 DVSEC control the CXL functionality of the
+        * entire device (CXL 3.0, 8.1.3).
+        */
+       if (dev->devfn != PCI_DEVFN(0, 0))
+               return false;
+
+       /*
+        * CXL Memory Devices must have the 502h class code set (CXL
+        * 3.0, 8.1.12.1).
+        */
+       if ((dev->class >> 8) != PCI_CLASS_MEMORY_CXL)
+               return false;
+
+       return true;
+}
+
+static bool cxl_error_is_native(struct pci_dev *dev)
+{
+       struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
+
+       return (pcie_ports_native || host->native_aer);
+}
+
+static bool is_internal_error(struct aer_err_info *info)
+{
+       if (info->severity == AER_CORRECTABLE)
+               return info->status & PCI_ERR_COR_INTERNAL;
+
+       return info->status & PCI_ERR_UNC_INTN;
+}
+
+static int cxl_rch_handle_error_iter(struct pci_dev *dev, void *data)
+{
+       struct aer_err_info *info = (struct aer_err_info *)data;
+       const struct pci_error_handlers *err_handler;
+
+       if (!is_cxl_mem_dev(dev) || !cxl_error_is_native(dev))
+               return 0;
+
+       /* protect dev->driver */
+       device_lock(&dev->dev);
+
+       err_handler = dev->driver ? dev->driver->err_handler : NULL;
+       if (!err_handler)
+               goto out;
+
+       if (info->severity == AER_CORRECTABLE) {
+               if (err_handler->cor_error_detected)
+                       err_handler->cor_error_detected(dev);
+       } else if (err_handler->error_detected) {
+               if (info->severity == AER_NONFATAL)
+                       err_handler->error_detected(dev, pci_channel_io_normal);
+               else if (info->severity == AER_FATAL)
+                       err_handler->error_detected(dev, pci_channel_io_frozen);
+       }
+out:
+       device_unlock(&dev->dev);
+       return 0;
+}
+
+static void cxl_rch_handle_error(struct pci_dev *dev, struct aer_err_info *info)
+{
+       /*
+        * Internal errors of an RCEC indicate an AER error in an
+        * RCH's downstream port. Check and handle them in the CXL.mem
+        * device driver.
+        */
+       if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC &&
+           is_internal_error(info))
+               pcie_walk_rcec(dev, cxl_rch_handle_error_iter, info);
+}
+
+static int handles_cxl_error_iter(struct pci_dev *dev, void *data)
+{
+       bool *handles_cxl = data;
+
+       if (!*handles_cxl)
+               *handles_cxl = is_cxl_mem_dev(dev) && cxl_error_is_native(dev);
+
+       /* Non-zero terminates iteration */
+       return *handles_cxl;
+}
+
+static bool handles_cxl_errors(struct pci_dev *rcec)
+{
+       bool handles_cxl = false;
+
+       if (pci_pcie_type(rcec) == PCI_EXP_TYPE_RC_EC &&
+           pcie_aer_is_native(rcec))
+               pcie_walk_rcec(rcec, handles_cxl_error_iter, &handles_cxl);
+
+       return handles_cxl;
+}
+
+static void cxl_rch_enable_rcec(struct pci_dev *rcec)
+{
+       if (!handles_cxl_errors(rcec))
+               return;
+
+       pci_aer_unmask_internal_errors(rcec);
+       pci_info(rcec, "CXL: Internal errors unmasked");
+}
+
+#else
+static inline void cxl_rch_enable_rcec(struct pci_dev *dev) { }
+static inline void cxl_rch_handle_error(struct pci_dev *dev,
+                                       struct aer_err_info *info) { }
+#endif
+
+/**
+ * pci_aer_handle_error - handle logging error into an event log
  * @dev: pointer to pci_dev data structure of error source device
  * @info: comprehensive error information
  *
  * Invoked when an error being detected by Root Port.
  */
-static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info)
+static void pci_aer_handle_error(struct pci_dev *dev, struct aer_err_info *info)
 {
        int aer = dev->aer_cap;
 
@@ -965,6 +1105,12 @@ static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info)
                pcie_do_recovery(dev, pci_channel_io_normal, aer_root_reset);
        else if (info->severity == AER_FATAL)
                pcie_do_recovery(dev, pci_channel_io_frozen, aer_root_reset);
+}
+
+static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info)
+{
+       cxl_rch_handle_error(dev, info);
+       pci_aer_handle_error(dev, info);
        pci_dev_put(dev);
 }
 
@@ -997,7 +1143,7 @@ static void aer_recover_work_func(struct work_struct *work)
                               PCI_SLOT(entry.devfn), PCI_FUNC(entry.devfn));
                        continue;
                }
-               cper_print_aer(pdev, entry.severity, entry.regs);
+               pci_print_aer(pdev, entry.severity, entry.regs);
                /*
                 * Memory for aer_capability_regs(entry.regs) is being allocated from the
                 * ghes_estatus_pool to protect it from overwriting when multiple sections
@@ -1348,6 +1494,7 @@ static int aer_probe(struct pcie_device *dev)
                return status;
        }
 
+       cxl_rch_enable_rcec(port);
        aer_enable_rootport(rpc);
        pci_info(port, "enabled with IRQ %d\n", dev->irq);
        return 0;