@@ -156,3 +156,12 @@ config PCIE_EDR
the PCI Firmware Specification r3.2. Enable this if you want to
support hybrid DPC model which uses both firmware and OS to
implement DPC.
+
+config PCIE_CXL_PMU
+ bool "CXL performance monitoring units on RP and Switch Ports"
+ depends on AUXILIARY_BUS
+ help
+ CXL root ports, switch upstream and switch downstream ports may
+ have one or more CXL PMU devices. Enable this option to look
+ for these and register a device to which the cxl_pmu driver
+ may bind
\ No newline at end of file
@@ -13,3 +13,4 @@ obj-$(CONFIG_PCIE_PME) += pme.o
obj-$(CONFIG_PCIE_DPC) += dpc.o
obj-$(CONFIG_PCIE_PTM) += ptm.o
obj-$(CONFIG_PCIE_EDR) += edr.o
+obj-$(CONFIG_PCIE_CXL_PMU) += cxlpmu.o
new file mode 100644
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Discovery of presence of CXL PMU instances and the maximum irqnum.
+ * Registers a auxiliary_device to which a driver can bind after the
+ * CXL bus is available and new devices can be added to ti.
+ */
+#include <linux/kernel.h>
+#include <linux/idr.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+
+#include "portdrv.h"
+#include "../../cxl/cxl.h"
+#include "../../cxl/cxlpci.h"
+#include "../../cxl/pmu.h"
+
+static DEFINE_IDA(pcie_cxl_pmu_ida);
+static void cpmu_adev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+ ida_free(&pcie_cxl_pmu_ida, adev->id);
+}
+
+int pcie_cxl_pmu_get_irqs(struct pci_dev *dev, u32 *max_irq,
+ struct list_head *aux_dev_list)
+{
+ u32 regblocks, regloc_size;
+ int i, regloc, ret;
+ bool found = false;
+
+ regloc = pci_find_dvsec_capability(dev, PCI_VENDOR_ID_CXL,
+ CXL_DVSEC_REG_LOCATOR);
+ if (!regloc)
+ return -ENODEV;
+
+ pci_read_config_dword(dev, regloc + PCI_DVSEC_HEADER1, ®loc_size);
+ regloc_size = FIELD_GET(PCI_DVSEC_HEADER1_LENGTH_MASK, regloc_size);
+
+ regloc += CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET;
+ regblocks = (regloc_size - CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET) / 8;
+
+ for (i = 0; i < regblocks; i++, regloc += 8) {
+ u32 reg_lo, reg_hi;
+ u8 reg_type;
+ struct resource *res;
+ void __iomem *base;
+ u64 offset, val;
+ int bar;
+
+ pci_read_config_dword(dev, regloc, ®_lo);
+ reg_type = FIELD_GET(CXL_DVSEC_REG_LOCATOR_BLOCK_ID_MASK,
+ reg_lo);
+ if (reg_type != CXL_REGLOC_RBI_PMU)
+ continue;
+
+ found = true;
+ /* Now we need to map just enough to get the irq */
+ bar = FIELD_GET(CXL_DVSEC_REG_LOCATOR_BIR_MASK, reg_lo);
+ pci_read_config_dword(dev, regloc + 4, ®_hi);
+
+ offset = ((u64) reg_hi << 32) |
+ (reg_lo & CXL_DVSEC_REG_LOCATOR_BLOCK_OFF_LOW_MASK);
+ if (offset > pci_resource_len(dev, bar)) {
+ pci_warn(dev, "CPMU BAR%d: %pr: too small\n",
+ bar, &dev->resource[bar]);
+ continue;
+ }
+ /*
+ * Map only the CPMU region because other parts are in control
+ * of the CXL port driver.
+ */
+ res = request_mem_region(pci_resource_start(dev, bar) + offset,
+ CXL_PMU_REGMAP_SIZE, NULL);
+ if (!res) {
+ pci_err(dev, "CPMU: could not map\n");
+ continue;
+ }
+
+ base = ioremap(pci_resource_start(dev, bar) + offset,
+ CXL_PMU_REGMAP_SIZE);
+ if (!base) {
+ pci_err(dev, "CPU: ioremap fail\n");
+ release_mem_region(res->start, resource_size(res));
+ continue;
+ }
+ if (max_irq) {
+ val = readq(base + CXL_PMU_CAP_REG);
+ if (FIELD_GET(CXL_PMU_CAP_INT, val))
+ *max_irq = max(*max_irq,
+ (u32)FIELD_GET(CXL_PMU_CAP_MSI_N_MSK, val));
+ }
+ iounmap(base);
+ release_mem_region(res->start, resource_size(res));
+
+ if (aux_dev_list) {
+ struct pcie_port_aux_dev *pcie_adev;
+ int id;
+
+ pcie_adev = devm_kzalloc(&dev->dev, sizeof(*pcie_adev),
+ GFP_KERNEL);
+ if (!pcie_adev)
+ return -ENOMEM;
+
+ /* Cleanup handled by release after devm_pcie_port_aux_dev_init() */
+ id = ida_alloc(&pcie_cxl_pmu_ida, GFP_KERNEL);
+ if (id < 0)
+ return -ENOMEM;
+
+ pcie_adev->adev.name = "cpmu";
+ pcie_adev->adev.id = id;
+ pcie_adev->adev.dev.parent = &dev->dev;
+ pcie_adev->adev.dev.release = cpmu_adev_release;
+ pcie_adev->addr = pci_resource_start(dev, bar) + offset;
+ pcie_adev->optional = true;
+
+ ret = devm_pcie_port_aux_dev_init(&dev->dev, pcie_adev);
+ if (ret)
+ return ret;
+
+ list_add(&pcie_adev->node, aux_dev_list);
+ }
+ }
+ if (!found)
+ return -ENODEV;
+
+ return 0;
+}
@@ -59,7 +59,7 @@ static void release_pcie_device(struct device *dev)
static int pcie_message_numbers(struct pci_dev *dev, int mask,
u32 *pme, u32 *aer, u32 *dpc)
{
- u32 nvec = 0, pos;
+ u32 nvec = 0, pos, max_cpmu = 0;
u16 reg16;
/*
@@ -100,6 +100,9 @@ static int pcie_message_numbers(struct pci_dev *dev, int mask,
}
}
+ if (!pcie_cxl_pmu_get_irqs(dev, &max_cpmu, NULL))
+ nvec = max(nvec, max_cpmu + 1);
+
return nvec;
}
@@ -278,6 +281,12 @@ static int get_port_device_capability(struct pci_dev *dev,
services |= PCIE_PORT_SERVICE_BWNOTIF;
}
+ if (pci_pcie_type(dev) == PCI_EXP_TYPE_UPSTREAM ||
+ pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM ||
+ pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT) {
+ pcie_cxl_pmu_get_irqs(dev, NULL, aux_dev_list);
+ }
+
return services;
}
@@ -52,6 +52,17 @@ int pcie_dpc_init(void);
static inline int pcie_dpc_init(void) { return 0; }
#endif
+struct list_head;
+#ifdef CONFIG_PCIE_CXL_PMU
+int pcie_cxl_pmu_get_irqs(struct pci_dev *dev, u32 *max_irq, struct list_head *aux_dev_list);
+#else
+static inline int pcie_cxl_pmu_get_irqs(struct pci_dev *dev, u32 *max_irq,
+ struct list_head *aux_dev_list)
+{
+ return 0;
+}
+#endif
+
struct pcie_port_aux_dev {
struct auxiliary_device adev;
u64 addr;
As CXL PMU instances can be found in root and switch ports and they want to use an interrupt they must be hung of the portdrv instance. Use the new auxiliary bus to do this. Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> --- drivers/pci/pcie/Kconfig | 9 +++ drivers/pci/pcie/Makefile | 1 + drivers/pci/pcie/cxlpmu.c | 129 +++++++++++++++++++++++++++++++++++++ drivers/pci/pcie/portdrv.c | 11 +++- drivers/pci/pcie/portdrv.h | 11 ++++ 5 files changed, 160 insertions(+), 1 deletion(-)