[RFC PATCH 7/9] pci: pcie/cxl: Register an auxiliary device for each CPMU instance

Jonathan Cameron
2024-05-29T16:44:41Z

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(-)

diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig
index 840f87eb4b28..8f43fdec333d 100644
--- a/drivers/pci/pcie/Kconfig
+++ b/drivers/pci/pcie/Kconfig
@@ -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
diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile
index 6461aa93fe76..037959bf91bb 100644
--- a/drivers/pci/pcie/Makefile
+++ b/drivers/pci/pcie/Makefile
@@ -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
diff --git a/drivers/pci/pcie/cxlpmu.c b/drivers/pci/pcie/cxlpmu.c
new file mode 100644
index 000000000000..7716663e567c
--- /dev/null
+++ b/drivers/pci/pcie/cxlpmu.c
@@ -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, &regloc_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, &reg_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, &reg_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;
+}
diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c
index 6314da76de9f..7274ee55a8c3 100644
--- a/drivers/pci/pcie/portdrv.c
+++ b/drivers/pci/pcie/portdrv.c
@@ -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;
}

diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h
index 243a6c9e9bf1..8fe6fad7b24a 100644
--- a/drivers/pci/pcie/portdrv.h
+++ b/drivers/pci/pcie/portdrv.h
@@ -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;
--
2.39.2