mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 16:56:26 +00:00
Merge branch 'pci/hotplug-octeon'
- Add hotplug controller driver for Marvell OCTEON multi-function device where function 0 has a management console interface to enable/disable and provision various personalities for the other functions (Shijith Thotton) * pci/hotplug-octeon: PCI: hotplug: Add OCTEON PCI hotplug controller driver
This commit is contained in:
commit
665e4a3456
@ -13882,6 +13882,12 @@ R: schalla@marvell.com
|
||||
R: vattunuru@marvell.com
|
||||
F: drivers/vdpa/octeon_ep/
|
||||
|
||||
MARVELL OCTEON HOTPLUG DRIVER
|
||||
R: Shijith Thotton <sthotton@marvell.com>
|
||||
R: Vamsi Attunuru <vattunuru@marvell.com>
|
||||
S: Supported
|
||||
F: drivers/pci/hotplug/octep_hp.c
|
||||
|
||||
MATROX FRAMEBUFFER DRIVER
|
||||
L: linux-fbdev@vger.kernel.org
|
||||
S: Orphan
|
||||
|
@ -118,6 +118,16 @@ config HOTPLUG_PCI_CPCI_GENERIC
|
||||
|
||||
When in doubt, say N.
|
||||
|
||||
config HOTPLUG_PCI_OCTEONEP
|
||||
bool "Marvell OCTEON PCI Hotplug 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.
|
||||
|
||||
config HOTPLUG_PCI_SHPC
|
||||
bool "SHPC PCI Hotplug driver"
|
||||
help
|
||||
|
@ -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.
|
||||
|
||||
|
427
drivers/pci/hotplug/octep_hp.c
Normal file
427
drivers/pci/hotplug/octep_hp.c
Normal file
@ -0,0 +1,427 @@
|
||||
// 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. OCTEP_HP_INTR_VECTOR() and
|
||||
* OCTEP_HP_INTR_OFFSET() 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) {
|
||||
pci_dbg(hp_slot->hp_pdev, "Slot %s is already enabled\n",
|
||||
hotplug_slot_name(&hp_slot->slot));
|
||||
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 %s\n",
|
||||
hotplug_slot_name(&hp_slot->slot));
|
||||
}
|
||||
|
||||
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) {
|
||||
pci_dbg(hp_ctrl->pdev, "Slot %s is already disabled\n",
|
||||
hotplug_slot_name(&hp_slot->slot));
|
||||
return;
|
||||
}
|
||||
|
||||
pci_dbg(hp_slot->hp_pdev, "Disabling slot %s\n",
|
||||
hotplug_slot_name(&hp_slot->slot));
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
pci_info(pdev, "Registered slot %s for device %s\n",
|
||||
slot_name, pci_name(pdev));
|
||||
|
||||
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 const char *octep_hp_cmd_name(enum octep_hp_intr_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case OCTEP_HP_INTR_ENA:
|
||||
return "hotplug enable";
|
||||
case OCTEP_HP_INTR_DIS:
|
||||
return "hotplug disable";
|
||||
default:
|
||||
return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
pci_info(hp_ctrl->pdev, "Received %s command for slot %s\n",
|
||||
octep_hp_cmd_name(hp_cmd->intr_type),
|
||||
hotplug_slot_name(&hp_slot->slot));
|
||||
|
||||
switch (hp_cmd->intr_type) {
|
||||
case OCTEP_HP_INTR_ENA:
|
||||
octep_hp_enable_pdev(hp_ctrl, hp_slot);
|
||||
break;
|
||||
case OCTEP_HP_INTR_DIS:
|
||||
octep_hp_disable_pdev(hp_ctrl, hp_slot);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
pci_err(pdev, "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, *next;
|
||||
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. The hotplug slot functions are logically removed from
|
||||
* the bus during probing and are re-enabled by the driver when a
|
||||
* hotplug event is received.
|
||||
*/
|
||||
list_for_each_entry_safe(tmp_pdev, next, &pdev->bus->devices, bus_list) {
|
||||
if (tmp_pdev == 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 PCI_DEVICE_ID_CAVIUM_OCTEP_HP_CTLR 0xa0e3
|
||||
static struct pci_device_id octep_hp_pci_map[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_OCTEP_HP_CTLR) },
|
||||
{ },
|
||||
};
|
||||
|
||||
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("Marvell OCTEON PCI Hotplug driver");
|
Loading…
Reference in New Issue
Block a user