Just a gentle reminder to review the patch when you get a chance. let me know if any adjustments are needed. Thanks! >Just a gentle reminder. >Could you please comments if there are improvements needed? > >>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 >>