Hi Helgaas, Please check this patch. >This patch introduces a PCI hotplug controller driver for the OCTEON >PCIe device, a multi-function PCIe device where the first function acts >as a hotplug controller. It is equipped with MSI-x interrupts to notify >the host of hotplug events from the OCTEON firmware. > >The driver facilitates the hotplugging of non-controller functions >within the same device. During probe, non-controller functions are >removed and registered as PCI hotplug slots. The slots are added back >only upon request from the device firmware. The driver also allows the >enabling and disabling of the slots via sysfs slot entries, provided by >the PCI hotplug framework. > >Signed-off-by: Shijith Thotton <sthotton@xxxxxxxxxxx> >Co-developed-by: Vamsi Attunuru <vattunuru@xxxxxxxxxxx> >Signed-off-by: Vamsi Attunuru <vattunuru@xxxxxxxxxxx> >--- > >This patch introduces a PCI hotplug controller driver for OCTEON PCIe hotplug >controller. The OCTEON PCIe device is a multi-function device where the first >function acts as a PCI hotplug controller. > > +--------------------------------+ > | Root Port | > +--------------------------------+ > | > PCIe > | >+---------------------------------------------------------------+ >| OCTEON PCIe Multifunction Device | >+---------------------------------------------------------------+ > | | | | > | | | | >+---------------------+ +----------------+ +-----+ +----------------+ >| Function 0 | | Function 1 | | ... | | Function 7 | >| (Hotplug controller)| | (Hotplug slot) | | | | (Hotplug slot) | >+---------------------+ +----------------+ +-----+ +----------------+ > | > | >+-------------------------+ >| Controller Firmware | >+-------------------------+ > >The hotplug controller driver facilitates the hotplugging of non-controller >functions in the same device. During the probe of the driver, the non-controller >function are removed and registered as PCI hotplug slots. They are added back >only upon request from the device firmware. The driver also allows the user to >enable/disable the functions using sysfs slot entries provided by PCI hotplug >framework. > >This solution adopts a hardware + software approach for several reasons: > >1. To reduce hardware implementation cost. Supporting complete hotplug > capability within the card would require a PCI switch implemented within. > >2. In the multi-function device, non-controller functions can act as emulated > devices. The firmware can dynamically enable or disable them at runtime. > >3. Not all root ports support PCI hotplug. This approach provides greater > flexibility and compatibility across different hardware configurations. > >The hotplug controller function is lightweight and is equipped with MSI-x >interrupts to notify the host about hotplug events. Upon receiving an >interrupt, the hotplug register is read, and the required function is enabled >or disabled. > >This driver will be beneficial for managing PCI hotplug events on the OCTEON >PCIe device without requiring complex hardware solutions. > >Changes in v2: >- Added missing include files. >- Used dev_err_probe() for error handling. >- Used guard() for mutex locking. >- Splited cleanup actions and added per-slot cleanup action. >- Fixed coding style issues. >- Added co-developed-by tag. > >Changes in v3: >- Explicit assignment of enum values. >- Use pcim_iomap_region() instead of pcim_iomap_regions(). > > MAINTAINERS | 6 + > drivers/pci/hotplug/Kconfig | 10 + > drivers/pci/hotplug/Makefile | 1 + > drivers/pci/hotplug/octep_hp.c | 409 >+++++++++++++++++++++++++++++++++ > 4 files changed, 426 insertions(+) > create mode 100644 drivers/pci/hotplug/octep_hp.c > >diff --git a/MAINTAINERS b/MAINTAINERS >index 42decde38320..7b5a618eed1c 100644 >--- a/MAINTAINERS >+++ b/MAINTAINERS >@@ -13677,6 +13677,12 @@ R: schalla@xxxxxxxxxxx > R: vattunuru@xxxxxxxxxxx > F: drivers/vdpa/octeon_ep/ > >+MARVELL OCTEON HOTPLUG CONTROLLER DRIVER >+R: Shijith Thotton <sthotton@xxxxxxxxxxx> >+R: Vamsi Attunuru <vattunuru@xxxxxxxxxxx> >+S: Supported >+F: drivers/pci/hotplug/octep_hp.c >+ > MATROX FRAMEBUFFER DRIVER > L: linux-fbdev@xxxxxxxxxxxxxxx > S: Orphan >diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig >index 1472aef0fb81..2e38fd25f7ef 100644 >--- a/drivers/pci/hotplug/Kconfig >+++ b/drivers/pci/hotplug/Kconfig >@@ -173,4 +173,14 @@ config HOTPLUG_PCI_S390 > > When in doubt, say Y. > >+config HOTPLUG_PCI_OCTEONEP >+ bool "OCTEON PCI device Hotplug controller driver" >+ depends on HOTPLUG_PCI >+ help >+ Say Y here if you have an OCTEON PCIe device with a hotplug >+ controller. This driver enables the non-controller functions of the >+ device to be registered as hotplug slots. >+ >+ When in doubt, say N. >+ > endif # HOTPLUG_PCI >diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile >index 240c99517d5e..40aaf31fe338 100644 >--- a/drivers/pci/hotplug/Makefile >+++ b/drivers/pci/hotplug/Makefile >@@ -20,6 +20,7 @@ obj-$(CONFIG_HOTPLUG_PCI_RPA) += >rpaphp.o > obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o > obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o > obj-$(CONFIG_HOTPLUG_PCI_S390) += s390_pci_hpc.o >+obj-$(CONFIG_HOTPLUG_PCI_OCTEONEP) += octep_hp.o > > # acpiphp_ibm extends acpiphp, so should be linked afterwards. > >diff --git a/drivers/pci/hotplug/octep_hp.c b/drivers/pci/hotplug/octep_hp.c >new file mode 100644 >index 000000000000..77dc2740f43e >--- /dev/null >+++ b/drivers/pci/hotplug/octep_hp.c >@@ -0,0 +1,409 @@ >+// SPDX-License-Identifier: GPL-2.0-only >+/* Copyright (C) 2024 Marvell. */ >+ >+#include <linux/cleanup.h> >+#include <linux/container_of.h> >+#include <linux/delay.h> >+#include <linux/dev_printk.h> >+#include <linux/init.h> >+#include <linux/interrupt.h> >+#include <linux/io-64-nonatomic-lo-hi.h> >+#include <linux/kernel.h> >+#include <linux/list.h> >+#include <linux/module.h> >+#include <linux/mutex.h> >+#include <linux/pci.h> >+#include <linux/pci_hotplug.h> >+#include <linux/slab.h> >+#include <linux/spinlock.h> >+#include <linux/workqueue.h> >+ >+#define OCTEP_HP_INTR_OFFSET(x) (0x20400 + ((x) << 4)) >+#define OCTEP_HP_INTR_VECTOR(x) (16 + (x)) >+#define OCTEP_HP_DRV_NAME "octep_hp" >+ >+/* >+ * Type of MSI-X interrupts. >+ * The macros OCTEP_HP_INTR_VECTOR and OCTEP_HP_INTR_OFFSET are >used to >+ * generate the vector and offset for an interrupt type. >+ */ >+enum octep_hp_intr_type { >+ OCTEP_HP_INTR_INVALID = -1, >+ OCTEP_HP_INTR_ENA = 0, >+ OCTEP_HP_INTR_DIS = 1, >+ OCTEP_HP_INTR_MAX = 2, >+}; >+ >+struct octep_hp_cmd { >+ struct list_head list; >+ enum octep_hp_intr_type intr_type; >+ u64 intr_val; >+}; >+ >+struct octep_hp_slot { >+ struct list_head list; >+ struct hotplug_slot slot; >+ u16 slot_number; >+ struct pci_dev *hp_pdev; >+ unsigned int hp_devfn; >+ struct octep_hp_controller *ctrl; >+}; >+ >+struct octep_hp_intr_info { >+ enum octep_hp_intr_type type; >+ int number; >+ char name[16]; >+}; >+ >+struct octep_hp_controller { >+ void __iomem *base; >+ struct pci_dev *pdev; >+ struct octep_hp_intr_info intr[OCTEP_HP_INTR_MAX]; >+ struct work_struct work; >+ struct list_head slot_list; >+ struct mutex slot_lock; /* Protects slot_list */ >+ struct list_head hp_cmd_list; >+ spinlock_t hp_cmd_lock; /* Protects hp_cmd_list */ >+}; >+ >+static void octep_hp_enable_pdev(struct octep_hp_controller *hp_ctrl, >+ struct octep_hp_slot *hp_slot) >+{ >+ guard(mutex)(&hp_ctrl->slot_lock); >+ if (hp_slot->hp_pdev) { >+ dev_dbg(&hp_slot->hp_pdev->dev, "Slot %u already >enabled\n", >+ hp_slot->slot_number); >+ return; >+ } >+ >+ /* Scan the device and add it to the bus */ >+ hp_slot->hp_pdev = pci_scan_single_device(hp_ctrl->pdev->bus, >+ hp_slot->hp_devfn); >+ pci_bus_assign_resources(hp_ctrl->pdev->bus); >+ pci_bus_add_device(hp_slot->hp_pdev); >+ >+ dev_dbg(&hp_slot->hp_pdev->dev, "Enabled slot %u\n", >+ hp_slot->slot_number); >+} >+ >+static void octep_hp_disable_pdev(struct octep_hp_controller *hp_ctrl, >+ struct octep_hp_slot *hp_slot) >+{ >+ guard(mutex)(&hp_ctrl->slot_lock); >+ if (!hp_slot->hp_pdev) { >+ dev_dbg(&hp_ctrl->pdev->dev, "Slot %u already disabled\n", >+ hp_slot->slot_number); >+ return; >+ } >+ >+ dev_dbg(&hp_slot->hp_pdev->dev, "Disabling slot %u\n", >+ hp_slot->slot_number); >+ >+ /* Remove the device from the bus */ >+ pci_stop_and_remove_bus_device_locked(hp_slot->hp_pdev); >+ hp_slot->hp_pdev = NULL; >+} >+ >+static int octep_hp_enable_slot(struct hotplug_slot *slot) >+{ >+ struct octep_hp_slot *hp_slot = >+ container_of(slot, struct octep_hp_slot, slot); >+ >+ octep_hp_enable_pdev(hp_slot->ctrl, hp_slot); >+ return 0; >+} >+ >+static int octep_hp_disable_slot(struct hotplug_slot *slot) >+{ >+ struct octep_hp_slot *hp_slot = >+ container_of(slot, struct octep_hp_slot, slot); >+ >+ octep_hp_disable_pdev(hp_slot->ctrl, hp_slot); >+ return 0; >+} >+ >+static struct hotplug_slot_ops octep_hp_slot_ops = { >+ .enable_slot = octep_hp_enable_slot, >+ .disable_slot = octep_hp_disable_slot, >+}; >+ >+#define SLOT_NAME_SIZE 16 >+static struct octep_hp_slot * >+octep_hp_register_slot(struct octep_hp_controller *hp_ctrl, >+ struct pci_dev *pdev, u16 slot_number) >+{ >+ char slot_name[SLOT_NAME_SIZE]; >+ struct octep_hp_slot *hp_slot; >+ int ret; >+ >+ hp_slot = kzalloc(sizeof(*hp_slot), GFP_KERNEL); >+ if (!hp_slot) >+ return ERR_PTR(-ENOMEM); >+ >+ hp_slot->ctrl = hp_ctrl; >+ hp_slot->hp_pdev = pdev; >+ hp_slot->hp_devfn = pdev->devfn; >+ hp_slot->slot_number = slot_number; >+ hp_slot->slot.ops = &octep_hp_slot_ops; >+ >+ snprintf(slot_name, sizeof(slot_name), "octep_hp_%u", slot_number); >+ ret = pci_hp_register(&hp_slot->slot, hp_ctrl->pdev->bus, >+ PCI_SLOT(pdev->devfn), slot_name); >+ if (ret) { >+ kfree(hp_slot); >+ return ERR_PTR(ret); >+ } >+ >+ list_add_tail(&hp_slot->list, &hp_ctrl->slot_list); >+ octep_hp_disable_pdev(hp_ctrl, hp_slot); >+ >+ return hp_slot; >+} >+ >+static void octep_hp_deregister_slot(void *data) >+{ >+ struct octep_hp_slot *hp_slot = data; >+ struct octep_hp_controller *hp_ctrl = hp_slot->ctrl; >+ >+ pci_hp_deregister(&hp_slot->slot); >+ octep_hp_enable_pdev(hp_ctrl, hp_slot); >+ list_del(&hp_slot->list); >+ kfree(hp_slot); >+} >+ >+static bool octep_hp_is_slot(struct octep_hp_controller *hp_ctrl, >+ struct pci_dev *pdev) >+{ >+ /* Check if the PCI device can be hotplugged */ >+ return pdev != hp_ctrl->pdev && pdev->bus == hp_ctrl->pdev->bus && >+ PCI_SLOT(pdev->devfn) == PCI_SLOT(hp_ctrl->pdev->devfn); >+} >+ >+static void octep_hp_cmd_handler(struct octep_hp_controller *hp_ctrl, >+ struct octep_hp_cmd *hp_cmd) >+{ >+ struct octep_hp_slot *hp_slot; >+ >+ /* >+ * Enable or disable the slots based on the slot mask. >+ * intr_val is a bit mask where each bit represents a slot. >+ */ >+ list_for_each_entry(hp_slot, &hp_ctrl->slot_list, list) { >+ if (!(hp_cmd->intr_val & BIT(hp_slot->slot_number))) >+ continue; >+ >+ if (hp_cmd->intr_type == OCTEP_HP_INTR_ENA) >+ octep_hp_enable_pdev(hp_ctrl, hp_slot); >+ else >+ octep_hp_disable_pdev(hp_ctrl, hp_slot); >+ } >+} >+ >+static void octep_hp_work_handler(struct work_struct *work) >+{ >+ struct octep_hp_controller *hp_ctrl; >+ struct octep_hp_cmd *hp_cmd; >+ unsigned long flags; >+ >+ hp_ctrl = container_of(work, struct octep_hp_controller, work); >+ >+ /* Process all the hotplug commands */ >+ spin_lock_irqsave(&hp_ctrl->hp_cmd_lock, flags); >+ while (!list_empty(&hp_ctrl->hp_cmd_list)) { >+ hp_cmd = list_first_entry(&hp_ctrl->hp_cmd_list, >+ struct octep_hp_cmd, list); >+ list_del(&hp_cmd->list); >+ spin_unlock_irqrestore(&hp_ctrl->hp_cmd_lock, flags); >+ >+ octep_hp_cmd_handler(hp_ctrl, hp_cmd); >+ kfree(hp_cmd); >+ >+ spin_lock_irqsave(&hp_ctrl->hp_cmd_lock, flags); >+ } >+ spin_unlock_irqrestore(&hp_ctrl->hp_cmd_lock, flags); >+} >+ >+static enum octep_hp_intr_type octep_hp_intr_type(struct >octep_hp_intr_info *intr, >+ int irq) >+{ >+ enum octep_hp_intr_type type; >+ >+ for (type = OCTEP_HP_INTR_ENA; type < OCTEP_HP_INTR_MAX; >type++) { >+ if (intr[type].number == irq) >+ return type; >+ } >+ >+ return OCTEP_HP_INTR_INVALID; >+} >+ >+static irqreturn_t octep_hp_intr_handler(int irq, void *data) >+{ >+ struct octep_hp_controller *hp_ctrl = data; >+ struct pci_dev *pdev = hp_ctrl->pdev; >+ enum octep_hp_intr_type type; >+ struct octep_hp_cmd *hp_cmd; >+ u64 intr_val; >+ >+ type = octep_hp_intr_type(hp_ctrl->intr, irq); >+ if (type == OCTEP_HP_INTR_INVALID) { >+ dev_err(&pdev->dev, "Invalid interrupt %d\n", irq); >+ return IRQ_HANDLED; >+ } >+ >+ /* Read and clear the interrupt */ >+ intr_val = readq(hp_ctrl->base + OCTEP_HP_INTR_OFFSET(type)); >+ writeq(intr_val, hp_ctrl->base + OCTEP_HP_INTR_OFFSET(type)); >+ >+ hp_cmd = kzalloc(sizeof(*hp_cmd), GFP_ATOMIC); >+ if (!hp_cmd) >+ return IRQ_HANDLED; >+ >+ hp_cmd->intr_val = intr_val; >+ hp_cmd->intr_type = type; >+ >+ /* Add the command to the list and schedule the work */ >+ spin_lock(&hp_ctrl->hp_cmd_lock); >+ list_add_tail(&hp_cmd->list, &hp_ctrl->hp_cmd_list); >+ spin_unlock(&hp_ctrl->hp_cmd_lock); >+ schedule_work(&hp_ctrl->work); >+ >+ return IRQ_HANDLED; >+} >+ >+static void octep_hp_irq_cleanup(void *data) >+{ >+ struct octep_hp_controller *hp_ctrl = data; >+ >+ pci_free_irq_vectors(hp_ctrl->pdev); >+ flush_work(&hp_ctrl->work); >+} >+ >+static int octep_hp_request_irq(struct octep_hp_controller *hp_ctrl, >+ enum octep_hp_intr_type type) >+{ >+ struct pci_dev *pdev = hp_ctrl->pdev; >+ struct octep_hp_intr_info *intr; >+ int irq; >+ >+ irq = pci_irq_vector(pdev, OCTEP_HP_INTR_VECTOR(type)); >+ if (irq < 0) >+ return irq; >+ >+ intr = &hp_ctrl->intr[type]; >+ intr->number = irq; >+ intr->type = type; >+ snprintf(intr->name, sizeof(intr->name), "octep_hp_%d", type); >+ >+ return devm_request_irq(&pdev->dev, irq, octep_hp_intr_handler, >+ IRQF_SHARED, intr->name, hp_ctrl); >+} >+ >+static int octep_hp_controller_setup(struct pci_dev *pdev, >+ struct octep_hp_controller *hp_ctrl) >+{ >+ struct device *dev = &pdev->dev; >+ enum octep_hp_intr_type type; >+ int ret; >+ >+ ret = pcim_enable_device(pdev); >+ if (ret) >+ return dev_err_probe(dev, ret, "Failed to enable PCI device\n"); >+ >+ hp_ctrl->base = pcim_iomap_region(pdev, 0, OCTEP_HP_DRV_NAME); >+ if (IS_ERR(hp_ctrl->base)) >+ return dev_err_probe(dev, PTR_ERR(hp_ctrl->base), >+ "Failed to map PCI device region\n"); >+ >+ pci_set_master(pdev); >+ pci_set_drvdata(pdev, hp_ctrl); >+ >+ INIT_LIST_HEAD(&hp_ctrl->slot_list); >+ INIT_LIST_HEAD(&hp_ctrl->hp_cmd_list); >+ mutex_init(&hp_ctrl->slot_lock); >+ spin_lock_init(&hp_ctrl->hp_cmd_lock); >+ INIT_WORK(&hp_ctrl->work, octep_hp_work_handler); >+ hp_ctrl->pdev = pdev; >+ >+ ret = pci_alloc_irq_vectors(pdev, 1, >+ >OCTEP_HP_INTR_VECTOR(OCTEP_HP_INTR_MAX), >+ PCI_IRQ_MSIX); >+ if (ret < 0) >+ return dev_err_probe(dev, ret, "Failed to alloc MSI-X >vectors\n"); >+ >+ ret = devm_add_action(&pdev->dev, octep_hp_irq_cleanup, hp_ctrl); >+ if (ret) >+ return dev_err_probe(&pdev->dev, ret, "Failed to add IRQ >cleanup action\n"); >+ >+ for (type = OCTEP_HP_INTR_ENA; type < OCTEP_HP_INTR_MAX; >type++) { >+ ret = octep_hp_request_irq(hp_ctrl, type); >+ if (ret) >+ return dev_err_probe(dev, ret, >+ "Failed to request IRQ for vector >%d\n", >+ OCTEP_HP_INTR_VECTOR(type)); >+ } >+ >+ return 0; >+} >+ >+static int octep_hp_pci_probe(struct pci_dev *pdev, >+ const struct pci_device_id *id) >+{ >+ struct octep_hp_controller *hp_ctrl; >+ struct pci_dev *tmp_pdev = NULL; >+ struct octep_hp_slot *hp_slot; >+ u16 slot_number = 0; >+ int ret; >+ >+ hp_ctrl = devm_kzalloc(&pdev->dev, sizeof(*hp_ctrl), GFP_KERNEL); >+ if (!hp_ctrl) >+ return -ENOMEM; >+ >+ ret = octep_hp_controller_setup(pdev, hp_ctrl); >+ if (ret) >+ return ret; >+ >+ /* >+ * Register all hotplug slots. Hotplug controller is the first function >+ * of the PCI device. The hotplug slots are the remaining functions of >+ * the PCI device. They are removed from the bus and are added back >when >+ * the hotplug event is triggered. >+ */ >+ for_each_pci_dev(tmp_pdev) { >+ if (!octep_hp_is_slot(hp_ctrl, tmp_pdev)) >+ continue; >+ >+ hp_slot = octep_hp_register_slot(hp_ctrl, tmp_pdev, >slot_number); >+ if (IS_ERR(hp_slot)) >+ return dev_err_probe(&pdev->dev, PTR_ERR(hp_slot), >+ "Failed to register hotplug slot >%u\n", >+ slot_number); >+ >+ ret = devm_add_action(&pdev->dev, octep_hp_deregister_slot, >+ hp_slot); >+ if (ret) >+ return dev_err_probe(&pdev->dev, ret, >+ "Failed to add action for >deregistering slot %u\n", >+ slot_number); >+ slot_number++; >+ } >+ >+ return 0; >+} >+ >+#define OCTEP_DEVID_HP_CONTROLLER 0xa0e3 >+static struct pci_device_id octep_hp_pci_map[] = { >+ { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, >OCTEP_DEVID_HP_CONTROLLER) }, >+ { }, >+}; >+ >+static struct pci_driver octep_hp = { >+ .name = OCTEP_HP_DRV_NAME, >+ .id_table = octep_hp_pci_map, >+ .probe = octep_hp_pci_probe, >+}; >+ >+module_pci_driver(octep_hp); >+ >+MODULE_LICENSE("GPL"); >+MODULE_AUTHOR("Marvell"); >+MODULE_DESCRIPTION("OCTEON PCIe device hotplug controller driver"); >-- >2.25.1 Thanks, Shijith