Re: [PATCH] PCI: hotplug: Add OCTEON PCI hotplug controller driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, 20 Aug 2024, Shijith Thotton wrote:

> 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>
> 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.
> 
>  MAINTAINERS                    |   6 +
>  drivers/pci/hotplug/Kconfig    |  10 +
>  drivers/pci/hotplug/Makefile   |   1 +
>  drivers/pci/hotplug/octep_hp.c | 351 +++++++++++++++++++++++++++++++++
>  4 files changed, 368 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..efeb542d4993
> --- /dev/null
> +++ b/drivers/pci/hotplug/octep_hp.c
> @@ -0,0 +1,351 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (C) 2024 Marvell. */
> +
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/pci_hotplug.h>
> +#include <linux/slab.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"
> +
> +/* Interrupt vectors for hotplug enable and disable events. */
> +enum octep_hp_vec_type {
> +	OCTEP_HP_VEC_ENA,
> +	OCTEP_HP_VEC_DIS,

If I understand your code right, these cannot be arbitrary numbers but has 
to be specific numbers to match the vector number so you should explicitly 
set them to those values.

> +};
> +
> +struct octep_hp_cmd {
> +	struct list_head list;

Missing include for list_head.

> +	enum octep_hp_vec_type vec_type;
> +	u64 slot_mask;
> +};
> +
> +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_controller {
> +	void __iomem *base;
> +	struct pci_dev *pdev;
> +	struct work_struct work;

Missing include.

> +	struct list_head slot_list;
> +	struct mutex slot_lock; /* Protects slot_list */

Missing include.

> +	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)
> +{
> +	mutex_lock(&hp_ctrl->slot_lock);

Use guard().

> +	if (hp_slot->hp_pdev) {
> +		dev_dbg(&hp_slot->hp_pdev->dev, "Slot %u already enabled\n", hp_slot->slot_number);
> +		mutex_unlock(&hp_ctrl->slot_lock);
> +		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);

Missing include for dev_dbg().

> +	mutex_unlock(&hp_ctrl->slot_lock);
> +}
> +
> +static void octep_hp_disable_pdev(struct octep_hp_controller *hp_ctrl,
> +				  struct octep_hp_slot *hp_slot)
> +{
> +	mutex_lock(&hp_ctrl->slot_lock);

Use guard().

> +	if (!hp_slot->hp_pdev) {
> +		dev_dbg(&hp_ctrl->pdev->dev, "Slot %u already disabled\n", hp_slot->slot_number);
> +		mutex_unlock(&hp_ctrl->slot_lock);
> +		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;
> +	mutex_unlock(&hp_ctrl->slot_lock);
> +}
> +
> +static int octep_hp_enable_slot(struct hotplug_slot *slot)
> +{
> +	struct octep_hp_slot *hp_slot = container_of(slot, struct octep_hp_slot, slot);

Missing include for container_of().

> +
> +	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 int 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 -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, SLOT_NAME_SIZE, "octep_hp_%u", slot_number);

SLOT_NAME_SIZE -> sizeof(slot_name)

> +	ret = pci_hp_register(&hp_slot->slot, hp_ctrl->pdev->bus, PCI_SLOT(pdev->devfn), slot_name);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register hotplug slot %u\n", slot_number);
> +		kfree(hp_slot);
> +		return ret;
> +	}
> +
> +	octep_hp_disable_pdev(hp_ctrl, hp_slot);
> +	list_add_tail(&hp_slot->list, &hp_ctrl->slot_list);
> +
> +	return 0;
> +}
> +
> +static bool octep_hp_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 */
> +	list_for_each_entry(hp_slot, &hp_ctrl->slot_list, list) {
> +		if (hp_cmd->slot_mask & BIT(hp_slot->slot_number)) {

Reverse logic & use continue ?

> +			if (hp_cmd->vec_type == OCTEP_HP_VEC_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 = container_of(work, struct octep_hp_controller, work);
> +	struct octep_hp_cmd *hp_cmd;
> +	unsigned long flags;
> +
> +	/* 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 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_vec_type vec_type;
> +	struct octep_hp_cmd *hp_cmd;
> +	u64 slot_mask;
> +
> +	vec_type = pci_irq_vector(pdev, OCTEP_HP_INTR_VECTOR(OCTEP_HP_VEC_ENA)) == irq ?
> +		OCTEP_HP_VEC_ENA : OCTEP_HP_VEC_DIS;

This is a bit unusual and complicated to do all this in one statement. But 
can't you just store that ena irq number into struct octep_hp_controller 
so you don't need to make the call here every time?

> +	slot_mask = readq(hp_ctrl->base + OCTEP_HP_INTR_OFFSET(vec_type));
> +	if (!slot_mask) {
> +		dev_err(&pdev->dev, "Invalid slot mask %llx\n", slot_mask);
> +		return IRQ_HANDLED;
> +	}
> +
> +	hp_cmd = kzalloc(sizeof(*hp_cmd), GFP_ATOMIC);
> +	if (!hp_cmd)
> +		return IRQ_HANDLED;
> +
> +	hp_cmd->slot_mask = slot_mask;
> +	hp_cmd->vec_type = vec_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);
> +
> +	/* Clear the interrupt */
> +	writeq(slot_mask, hp_ctrl->base + OCTEP_HP_INTR_OFFSET(vec_type));
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void octep_hp_deregister_slots(struct octep_hp_controller *hp_ctrl)
> +{
> +	struct octep_hp_slot *hp_slot, *tmp;
> +
> +	/* Deregister all the hotplug slots */
> +	list_for_each_entry_safe(hp_slot, tmp, &hp_ctrl->slot_list, list) {
> +		pci_hp_deregister(&hp_slot->slot);
> +		octep_hp_enable_pdev(hp_ctrl, hp_slot);
> +		list_del(&hp_slot->list);
> +		kfree(hp_slot);
> +	}
> +}
> +
> +static void octep_hp_controller_cleanup(void *data)
> +{
> +	struct octep_hp_controller *hp_ctrl = data;
> +
> +	pci_free_irq_vectors(hp_ctrl->pdev);
> +	flush_work(&hp_ctrl->work);
> +	octep_hp_deregister_slots(hp_ctrl);
> +}
> +
> +static int octep_hp_controller_setup(struct pci_dev *pdev, struct octep_hp_controller *hp_ctrl)
> +{
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	ret = pcim_enable_device(pdev);
> +	if (ret) {
> +		dev_err(dev, "Failed to enable PCI device\n");
> +		return ret;
> +	}
> +
> +	ret = pcim_iomap_regions(pdev, BIT(0), OCTEP_HP_DRV_NAME);
> +	if (ret) {
> +		dev_err(dev, "Failed to request MMIO region\n");
> +		return ret;
> +	}
> +
> +	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;
> +	hp_ctrl->base = pcim_iomap_table(pdev)[0];
> +	if (!hp_ctrl->base) {
> +		dev_err(dev, "Failed to get device resource map\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = pci_alloc_irq_vectors(pdev, 1, OCTEP_HP_INTR_VECTOR(OCTEP_HP_VEC_DIS) + 1,
> +				    PCI_IRQ_MSIX);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to alloc MSI-X vectors");
> +		return ret;
> +	}
> +
> +	ret = devm_add_action(dev, octep_hp_controller_cleanup, hp_ctrl);
> +	if (ret) {
> +		dev_err(dev, "Failed to add action for controller cleanup\n");
> +		return ret;
> +	}
> +
> +	ret = devm_request_irq(dev, pci_irq_vector(pdev, OCTEP_HP_INTR_VECTOR(OCTEP_HP_VEC_ENA)),
> +			       octep_hp_intr_handler, 0, "octep_hp_ena", hp_ctrl);
> +	if (ret) {
> +		dev_err(dev, "Failed to register slot enable interrupt handle\n");
> +		return ret;
> +	}
> +
> +	ret = devm_request_irq(dev, pci_irq_vector(pdev, OCTEP_HP_INTR_VECTOR(OCTEP_HP_VEC_DIS)),
> +			       octep_hp_intr_handler, 0, "octep_hp_dis", hp_ctrl);
> +	if (ret) {
> +		dev_err(dev, "Failed to register slot disable interrupt handle\n");
> +		return ret;
> +	}
> +
> +	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;
> +	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) {
> +		dev_err(&pdev->dev, "Failed to setup octep controller\n");

So both octep_hp_controller_setup() and this function print an error? 
Wouldn't the one from octep_hp_controller_setup() suffice?

-- 
 i.

> +		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.

Reflow the comment to 80 chars.

> +	 */
> +	for_each_pci_dev(tmp_pdev) {
> +		if (octep_hp_slot(hp_ctrl, tmp_pdev)) {

Reverse logic & use continue.

> +			ret = octep_hp_register_slot(hp_ctrl, tmp_pdev, slot_number++);
> +			if (ret) {
> +				dev_err(&pdev->dev, "Failed to register hotplug slots.\n");
> +				return ret;
> +			}
> +		}
> +	}
> +
> +	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) },
> +	{ 0 },
> +};
> +
> +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");
> 




[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux