[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID:
<PH0PR18MB442568DB4B04F4871E03C946D9532@PH0PR18MB4425.namprd18.prod.outlook.com>
Date: Wed, 6 Nov 2024 07:45:55 +0000
From: Shijith Thotton <sthotton@...vell.com>
To: "bhelgaas@...gle.com" <bhelgaas@...gle.com>,
Krzysztof WilczyĆski <kw@...ux.com>,
Rob Herring
<robh@...nel.org>,
Manivannan Sadhasivam <manivannanece23@...il.com>
CC: "ilpo.jarvinen@...ux.intel.com" <ilpo.jarvinen@...ux.intel.com>,
"Jonathan.Cameron@...wei.com" <Jonathan.Cameron@...wei.com>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
"linux-pci@...r.kernel.org" <linux-pci@...r.kernel.org>,
"rafael@...nel.org"
<rafael@...nel.org>,
"scott@...amperecomputing.com"
<scott@...amperecomputing.com>,
Jerin Jacob <jerinj@...vell.com>,
Srujana
Challa <schalla@...vell.com>,
Vamsi Krishna Attunuru <vattunuru@...vell.com>,
Andrew Murray <andrew.murray@....com>,
Lorenzo Pieralisi
<lorenzo.pieralisi@....com>
Subject: RE: [PATCH v3] PCI: hotplug: Add OCTEON PCI hotplug controller driver
Just a gentle reminder to review the patch. Let me know if any adjustments are
needed. Thanks!
>
>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@...vell.com>
>Co-developed-by: Vamsi Attunuru <vattunuru@...vell.com>
>Signed-off-by: Vamsi Attunuru <vattunuru@...vell.com>
>---
>
>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@...vell.com
> R: vattunuru@...vell.com
> F: drivers/vdpa/octeon_ep/
>
>+MARVELL OCTEON HOTPLUG CONTROLLER DRIVER
>+R: Shijith Thotton <sthotton@...vell.com>
>+R: Vamsi Attunuru <vattunuru@...vell.com>
>+S: Supported
>+F: drivers/pci/hotplug/octep_hp.c
>+
> MATROX FRAMEBUFFER DRIVER
> L: linux-fbdev@...r.kernel.org
> 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
Powered by blists - more mailing lists