Merge branch 'pci/hotplug'
- Simplify SHPC existence/permission checks (Bjorn Helgaas) - Remove hotplug sample skeleton driver (Lukas Wunner) - Convert pciehp to threaded IRQ handling (Lukas Wunner) - Improve pciehp tolerance of missed events and initially unstable links (Lukas Wunner) - Clear spurious pciehp events on resume (Lukas Wunner) - Add pciehp runtime PM support, including for Thunderbolt controllers (Lukas Wunner) - Support interrupts from pciehp bridges in D3hot (Lukas Wunner) * pci/hotplug: PCI: pciehp: Deduplicate presence check on probe & resume PCI: pciehp: Avoid implicit fallthroughs in switch statements PCI: Whitelist Thunderbolt ports for runtime D3 PCI: Whitelist native hotplug ports for runtime D3 PCI: sysfs: Resume to D0 on function reset PCI: pciehp: Resume parent to D0 on config space access PCI: pciehp: Resume to D0 on enable/disable PCI: pciehp: Support interrupts sent from D3hot PCI: pciehp: Obey compulsory command delay after resume PCI: pciehp: Clear spurious events earlier on resume PCI: portdrv: Deduplicate PM callback iterator PCI: pciehp: Avoid slot access during reset PCI: pciehp: Always enable occupied slot on probe PCI: pciehp: Become resilient to missed events PCI: pciehp: Tolerate initially unstable link PCI: pciehp: Declare pciehp_enable/disable_slot() static PCI: pciehp: Drop enable/disable lock PCI: pciehp: Enable/disable exclusively from IRQ thread PCI: pciehp: Track enable/disable status PCI: pciehp: Publish to user space last on probe PCI: hotplug: Demidlayer registration with the core PCI: pciehp: Drop slot workqueue PCI: pciehp: Handle events synchronously PCI: pciehp: Stop blinking on slot enable failure PCI: pciehp: Convert to threaded polling PCI: pciehp: Convert to threaded IRQ PCI: pciehp: Document struct slot and struct controller PCI: pciehp: Declare pciehp_unconfigure_device() void PCI: pciehp: Drop unnecessary NULL pointer check PCI: pciehp: Fix unprotected list iteration in IRQ handler PCI: pciehp: Fix use-after-free on unplug PCI: hotplug: Don't leak pci_slot on registration failure PCI: hotplug: Delete skeleton driver PCI: shpchp: Separate existence of SHPC and permission to use it
This commit is contained in:
commit
c0638a4553
|
@ -73,20 +73,6 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev)
|
||||||
acpi_handle chandle, handle;
|
acpi_handle chandle, handle;
|
||||||
struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL };
|
struct acpi_buffer string = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||||
|
|
||||||
/*
|
|
||||||
* Per PCI firmware specification, we should run the ACPI _OSC
|
|
||||||
* method to get control of hotplug hardware before using it. If
|
|
||||||
* an _OSC is missing, we look for an OSHP to do the same thing.
|
|
||||||
* To handle different BIOS behavior, we look for _OSC on a root
|
|
||||||
* bridge preferentially (according to PCI fw spec). Later for
|
|
||||||
* OSHP within the scope of the hotplug controller and its parents,
|
|
||||||
* up to the host bridge under which this controller exists.
|
|
||||||
*/
|
|
||||||
if (shpchp_is_native(pdev))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* If _OSC exists, we should not evaluate OSHP */
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there's no ACPI host bridge (i.e., ACPI support is compiled
|
* If there's no ACPI host bridge (i.e., ACPI support is compiled
|
||||||
* into the kernel but the hardware platform doesn't support ACPI),
|
* into the kernel but the hardware platform doesn't support ACPI),
|
||||||
|
@ -97,9 +83,25 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev)
|
||||||
if (!root)
|
if (!root)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (root->osc_support_set)
|
/*
|
||||||
goto no_control;
|
* If _OSC exists, it determines whether we're allowed to manage
|
||||||
|
* the SHPC. We executed it while enumerating the host bridge.
|
||||||
|
*/
|
||||||
|
if (root->osc_support_set) {
|
||||||
|
if (host->native_shpc_hotplug)
|
||||||
|
return 0;
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the absence of _OSC, we're always allowed to manage the SHPC.
|
||||||
|
* However, if an OSHP method is present, we must execute it so the
|
||||||
|
* firmware can transfer control to the OS, e.g., direct interrupts
|
||||||
|
* to the OS instead of to the firmware.
|
||||||
|
*
|
||||||
|
* N.B. The PCI Firmware Spec (r3.2, sec 4.8) does not endorse
|
||||||
|
* searching up the ACPI hierarchy, so the loops below are suspect.
|
||||||
|
*/
|
||||||
handle = ACPI_HANDLE(&pdev->dev);
|
handle = ACPI_HANDLE(&pdev->dev);
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
/*
|
/*
|
||||||
|
@ -128,7 +130,7 @@ int acpi_get_hp_hw_control_from_firmware(struct pci_dev *pdev)
|
||||||
if (ACPI_FAILURE(status))
|
if (ACPI_FAILURE(status))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
no_control:
|
|
||||||
pci_info(pdev, "Cannot get control of SHPC hotplug\n");
|
pci_info(pdev, "Cannot get control of SHPC hotplug\n");
|
||||||
kfree(string.pointer);
|
kfree(string.pointer);
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
|
@ -254,20 +254,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* release_slot - free up the memory used by a slot
|
|
||||||
* @hotplug_slot: slot to free
|
|
||||||
*/
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
|
|
||||||
pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot));
|
|
||||||
|
|
||||||
kfree(slot->hotplug_slot);
|
|
||||||
kfree(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* callback routine to initialize 'struct slot' for each slot */
|
/* callback routine to initialize 'struct slot' for each slot */
|
||||||
int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot,
|
int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot,
|
||||||
unsigned int sun)
|
unsigned int sun)
|
||||||
|
@ -287,7 +273,6 @@ int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot,
|
||||||
slot->hotplug_slot->info = &slot->info;
|
slot->hotplug_slot->info = &slot->info;
|
||||||
|
|
||||||
slot->hotplug_slot->private = slot;
|
slot->hotplug_slot->private = slot;
|
||||||
slot->hotplug_slot->release = &release_slot;
|
|
||||||
slot->hotplug_slot->ops = &acpi_hotplug_slot_ops;
|
slot->hotplug_slot->ops = &acpi_hotplug_slot_ops;
|
||||||
|
|
||||||
slot->acpi_slot = acpiphp_slot;
|
slot->acpi_slot = acpiphp_slot;
|
||||||
|
@ -324,13 +309,12 @@ error:
|
||||||
void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot)
|
void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot)
|
||||||
{
|
{
|
||||||
struct slot *slot = acpiphp_slot->slot;
|
struct slot *slot = acpiphp_slot->slot;
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
pr_info("Slot [%s] unregistered\n", slot_name(slot));
|
pr_info("Slot [%s] unregistered\n", slot_name(slot));
|
||||||
|
|
||||||
retval = pci_hp_deregister(slot->hotplug_slot);
|
pci_hp_deregister(slot->hotplug_slot);
|
||||||
if (retval)
|
kfree(slot->hotplug_slot);
|
||||||
pr_err("pci_hp_deregister failed with error %d\n", retval);
|
kfree(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -195,10 +195,8 @@ get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
static void release_slot(struct slot *slot)
|
||||||
{
|
{
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
|
|
||||||
kfree(slot->hotplug_slot->info);
|
kfree(slot->hotplug_slot->info);
|
||||||
kfree(slot->hotplug_slot);
|
kfree(slot->hotplug_slot);
|
||||||
pci_dev_put(slot->dev);
|
pci_dev_put(slot->dev);
|
||||||
|
@ -253,7 +251,6 @@ cpci_hp_register_bus(struct pci_bus *bus, u8 first, u8 last)
|
||||||
snprintf(name, SLOT_NAME_SIZE, "%02x:%02x", bus->number, i);
|
snprintf(name, SLOT_NAME_SIZE, "%02x:%02x", bus->number, i);
|
||||||
|
|
||||||
hotplug_slot->private = slot;
|
hotplug_slot->private = slot;
|
||||||
hotplug_slot->release = &release_slot;
|
|
||||||
hotplug_slot->ops = &cpci_hotplug_slot_ops;
|
hotplug_slot->ops = &cpci_hotplug_slot_ops;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -308,12 +305,8 @@ cpci_hp_unregister_bus(struct pci_bus *bus)
|
||||||
slots--;
|
slots--;
|
||||||
|
|
||||||
dbg("deregistering slot %s", slot_name(slot));
|
dbg("deregistering slot %s", slot_name(slot));
|
||||||
status = pci_hp_deregister(slot->hotplug_slot);
|
pci_hp_deregister(slot->hotplug_slot);
|
||||||
if (status) {
|
release_slot(slot);
|
||||||
err("pci_hp_deregister failed with error %d",
|
|
||||||
status);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
up_write(&list_rwsem);
|
up_write(&list_rwsem);
|
||||||
|
@ -623,6 +616,7 @@ cleanup_slots(void)
|
||||||
list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) {
|
list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) {
|
||||||
list_del(&slot->slot_list);
|
list_del(&slot->slot_list);
|
||||||
pci_hp_deregister(slot->hotplug_slot);
|
pci_hp_deregister(slot->hotplug_slot);
|
||||||
|
release_slot(slot);
|
||||||
}
|
}
|
||||||
cleanup_null:
|
cleanup_null:
|
||||||
up_write(&list_rwsem);
|
up_write(&list_rwsem);
|
||||||
|
|
|
@ -266,17 +266,6 @@ static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start,
|
||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, slot_name(slot));
|
|
||||||
|
|
||||||
kfree(slot->hotplug_slot->info);
|
|
||||||
kfree(slot->hotplug_slot);
|
|
||||||
kfree(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ctrl_slot_cleanup(struct controller *ctrl)
|
static int ctrl_slot_cleanup(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
struct slot *old_slot, *next_slot;
|
struct slot *old_slot, *next_slot;
|
||||||
|
@ -285,9 +274,11 @@ static int ctrl_slot_cleanup(struct controller *ctrl)
|
||||||
ctrl->slot = NULL;
|
ctrl->slot = NULL;
|
||||||
|
|
||||||
while (old_slot) {
|
while (old_slot) {
|
||||||
/* memory will be freed by the release_slot callback */
|
|
||||||
next_slot = old_slot->next;
|
next_slot = old_slot->next;
|
||||||
pci_hp_deregister(old_slot->hotplug_slot);
|
pci_hp_deregister(old_slot->hotplug_slot);
|
||||||
|
kfree(old_slot->hotplug_slot->info);
|
||||||
|
kfree(old_slot->hotplug_slot);
|
||||||
|
kfree(old_slot);
|
||||||
old_slot = next_slot;
|
old_slot = next_slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,7 +669,6 @@ static int ctrl_slot_setup(struct controller *ctrl,
|
||||||
((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04;
|
((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04;
|
||||||
|
|
||||||
/* register this slot with the hotplug pci core */
|
/* register this slot with the hotplug pci core */
|
||||||
hotplug_slot->release = &release_slot;
|
|
||||||
hotplug_slot->private = slot;
|
hotplug_slot->private = slot;
|
||||||
snprintf(name, SLOT_NAME_SIZE, "%u", slot->number);
|
snprintf(name, SLOT_NAME_SIZE, "%u", slot->number);
|
||||||
hotplug_slot->ops = &cpqphp_hotplug_slot_ops;
|
hotplug_slot->ops = &cpqphp_hotplug_slot_ops;
|
||||||
|
|
|
@ -673,7 +673,20 @@ static void free_slots(void)
|
||||||
|
|
||||||
list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head,
|
list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head,
|
||||||
ibm_slot_list) {
|
ibm_slot_list) {
|
||||||
pci_hp_deregister(slot_cur->hotplug_slot);
|
pci_hp_del(slot_cur->hotplug_slot);
|
||||||
|
slot_cur->ctrl = NULL;
|
||||||
|
slot_cur->bus_on = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't want to actually remove the resources,
|
||||||
|
* since ibmphp_free_resources() will do just that.
|
||||||
|
*/
|
||||||
|
ibmphp_unconfigure_card(&slot_cur, -1);
|
||||||
|
|
||||||
|
pci_hp_destroy(slot_cur->hotplug_slot);
|
||||||
|
kfree(slot_cur->hotplug_slot->info);
|
||||||
|
kfree(slot_cur->hotplug_slot);
|
||||||
|
kfree(slot_cur);
|
||||||
}
|
}
|
||||||
debug("%s -- exit\n", __func__);
|
debug("%s -- exit\n", __func__);
|
||||||
}
|
}
|
||||||
|
|
|
@ -699,25 +699,6 @@ static int fillslotinfo(struct hotplug_slot *hotplug_slot)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot;
|
|
||||||
|
|
||||||
if (!hotplug_slot || !hotplug_slot->private)
|
|
||||||
return;
|
|
||||||
|
|
||||||
slot = hotplug_slot->private;
|
|
||||||
kfree(slot->hotplug_slot->info);
|
|
||||||
kfree(slot->hotplug_slot);
|
|
||||||
slot->ctrl = NULL;
|
|
||||||
slot->bus_on = NULL;
|
|
||||||
|
|
||||||
/* we don't want to actually remove the resources, since free_resources will do just that */
|
|
||||||
ibmphp_unconfigure_card(&slot, -1);
|
|
||||||
|
|
||||||
kfree(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct pci_driver ibmphp_driver;
|
static struct pci_driver ibmphp_driver;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -941,7 +922,6 @@ static int __init ebda_rsrc_controller(void)
|
||||||
tmp_slot->hotplug_slot = hp_slot_ptr;
|
tmp_slot->hotplug_slot = hp_slot_ptr;
|
||||||
|
|
||||||
hp_slot_ptr->private = tmp_slot;
|
hp_slot_ptr->private = tmp_slot;
|
||||||
hp_slot_ptr->release = release_slot;
|
|
||||||
|
|
||||||
rc = fillslotinfo(hp_slot_ptr);
|
rc = fillslotinfo(hp_slot_ptr);
|
||||||
if (rc)
|
if (rc)
|
||||||
|
|
|
@ -396,8 +396,9 @@ static struct hotplug_slot *get_slot_from_name(const char *name)
|
||||||
* @owner: caller module owner
|
* @owner: caller module owner
|
||||||
* @mod_name: caller module name
|
* @mod_name: caller module name
|
||||||
*
|
*
|
||||||
* Registers a hotplug slot with the pci hotplug subsystem, which will allow
|
* Prepares a hotplug slot for in-kernel use and immediately publishes it to
|
||||||
* userspace interaction to the slot.
|
* user space in one go. Drivers may alternatively carry out the two steps
|
||||||
|
* separately by invoking pci_hp_initialize() and pci_hp_add().
|
||||||
*
|
*
|
||||||
* Returns 0 if successful, anything else for an error.
|
* Returns 0 if successful, anything else for an error.
|
||||||
*/
|
*/
|
||||||
|
@ -406,45 +407,91 @@ int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus,
|
||||||
struct module *owner, const char *mod_name)
|
struct module *owner, const char *mod_name)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
|
result = __pci_hp_initialize(slot, bus, devnr, name, owner, mod_name);
|
||||||
|
if (result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result = pci_hp_add(slot);
|
||||||
|
if (result)
|
||||||
|
pci_hp_destroy(slot);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(__pci_hp_register);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __pci_hp_initialize - prepare hotplug slot for in-kernel use
|
||||||
|
* @slot: pointer to the &struct hotplug_slot to initialize
|
||||||
|
* @bus: bus this slot is on
|
||||||
|
* @devnr: slot number
|
||||||
|
* @name: name registered with kobject core
|
||||||
|
* @owner: caller module owner
|
||||||
|
* @mod_name: caller module name
|
||||||
|
*
|
||||||
|
* Allocate and fill in a PCI slot for use by a hotplug driver. Once this has
|
||||||
|
* been called, the driver may invoke hotplug_slot_name() to get the slot's
|
||||||
|
* unique name. The driver must be prepared to handle a ->reset_slot callback
|
||||||
|
* from this point on.
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative int on error.
|
||||||
|
*/
|
||||||
|
int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus,
|
||||||
|
int devnr, const char *name, struct module *owner,
|
||||||
|
const char *mod_name)
|
||||||
|
{
|
||||||
struct pci_slot *pci_slot;
|
struct pci_slot *pci_slot;
|
||||||
|
|
||||||
if (slot == NULL)
|
if (slot == NULL)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
if ((slot->info == NULL) || (slot->ops == NULL))
|
if ((slot->info == NULL) || (slot->ops == NULL))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
if (slot->release == NULL) {
|
|
||||||
dbg("Why are you trying to register a hotplug slot without a proper release function?\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
slot->ops->owner = owner;
|
slot->ops->owner = owner;
|
||||||
slot->ops->mod_name = mod_name;
|
slot->ops->mod_name = mod_name;
|
||||||
|
|
||||||
mutex_lock(&pci_hp_mutex);
|
|
||||||
/*
|
/*
|
||||||
* No problems if we call this interface from both ACPI_PCI_SLOT
|
* No problems if we call this interface from both ACPI_PCI_SLOT
|
||||||
* driver and call it here again. If we've already created the
|
* driver and call it here again. If we've already created the
|
||||||
* pci_slot, the interface will simply bump the refcount.
|
* pci_slot, the interface will simply bump the refcount.
|
||||||
*/
|
*/
|
||||||
pci_slot = pci_create_slot(bus, devnr, name, slot);
|
pci_slot = pci_create_slot(bus, devnr, name, slot);
|
||||||
if (IS_ERR(pci_slot)) {
|
if (IS_ERR(pci_slot))
|
||||||
result = PTR_ERR(pci_slot);
|
return PTR_ERR(pci_slot);
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
slot->pci_slot = pci_slot;
|
slot->pci_slot = pci_slot;
|
||||||
pci_slot->hotplug = slot;
|
pci_slot->hotplug = slot;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(__pci_hp_initialize);
|
||||||
|
|
||||||
list_add(&slot->slot_list, &pci_hotplug_slot_list);
|
/**
|
||||||
|
* pci_hp_add - publish hotplug slot to user space
|
||||||
|
* @slot: pointer to the &struct hotplug_slot to publish
|
||||||
|
*
|
||||||
|
* Make a hotplug slot's sysfs interface available and inform user space of its
|
||||||
|
* addition by sending a uevent. The hotplug driver must be prepared to handle
|
||||||
|
* all &struct hotplug_slot_ops callbacks from this point on.
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative int on error.
|
||||||
|
*/
|
||||||
|
int pci_hp_add(struct hotplug_slot *slot)
|
||||||
|
{
|
||||||
|
struct pci_slot *pci_slot = slot->pci_slot;
|
||||||
|
int result;
|
||||||
|
|
||||||
result = fs_add_slot(pci_slot);
|
result = fs_add_slot(pci_slot);
|
||||||
kobject_uevent(&pci_slot->kobj, KOBJ_ADD);
|
if (result)
|
||||||
dbg("Added slot %s to the list\n", name);
|
|
||||||
out:
|
|
||||||
mutex_unlock(&pci_hp_mutex);
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
kobject_uevent(&pci_slot->kobj, KOBJ_ADD);
|
||||||
|
mutex_lock(&pci_hp_mutex);
|
||||||
|
list_add(&slot->slot_list, &pci_hotplug_slot_list);
|
||||||
|
mutex_unlock(&pci_hp_mutex);
|
||||||
|
dbg("Added slot %s to the list\n", hotplug_slot_name(slot));
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(__pci_hp_register);
|
EXPORT_SYMBOL_GPL(pci_hp_add);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem
|
* pci_hp_deregister - deregister a hotplug_slot with the PCI hotplug subsystem
|
||||||
|
@ -455,35 +502,62 @@ EXPORT_SYMBOL_GPL(__pci_hp_register);
|
||||||
*
|
*
|
||||||
* Returns 0 if successful, anything else for an error.
|
* Returns 0 if successful, anything else for an error.
|
||||||
*/
|
*/
|
||||||
int pci_hp_deregister(struct hotplug_slot *slot)
|
void pci_hp_deregister(struct hotplug_slot *slot)
|
||||||
|
{
|
||||||
|
pci_hp_del(slot);
|
||||||
|
pci_hp_destroy(slot);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(pci_hp_deregister);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pci_hp_del - unpublish hotplug slot from user space
|
||||||
|
* @slot: pointer to the &struct hotplug_slot to unpublish
|
||||||
|
*
|
||||||
|
* Remove a hotplug slot's sysfs interface.
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative int on error.
|
||||||
|
*/
|
||||||
|
void pci_hp_del(struct hotplug_slot *slot)
|
||||||
{
|
{
|
||||||
struct hotplug_slot *temp;
|
struct hotplug_slot *temp;
|
||||||
struct pci_slot *pci_slot;
|
|
||||||
|
|
||||||
if (!slot)
|
if (WARN_ON(!slot))
|
||||||
return -ENODEV;
|
return;
|
||||||
|
|
||||||
mutex_lock(&pci_hp_mutex);
|
mutex_lock(&pci_hp_mutex);
|
||||||
temp = get_slot_from_name(hotplug_slot_name(slot));
|
temp = get_slot_from_name(hotplug_slot_name(slot));
|
||||||
if (temp != slot) {
|
if (WARN_ON(temp != slot)) {
|
||||||
mutex_unlock(&pci_hp_mutex);
|
mutex_unlock(&pci_hp_mutex);
|
||||||
return -ENODEV;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
list_del(&slot->slot_list);
|
list_del(&slot->slot_list);
|
||||||
|
mutex_unlock(&pci_hp_mutex);
|
||||||
pci_slot = slot->pci_slot;
|
|
||||||
fs_remove_slot(pci_slot);
|
|
||||||
dbg("Removed slot %s from the list\n", hotplug_slot_name(slot));
|
dbg("Removed slot %s from the list\n", hotplug_slot_name(slot));
|
||||||
|
fs_remove_slot(slot->pci_slot);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(pci_hp_del);
|
||||||
|
|
||||||
slot->release(slot);
|
/**
|
||||||
|
* pci_hp_destroy - remove hotplug slot from in-kernel use
|
||||||
|
* @slot: pointer to the &struct hotplug_slot to destroy
|
||||||
|
*
|
||||||
|
* Destroy a PCI slot used by a hotplug driver. Once this has been called,
|
||||||
|
* the driver may no longer invoke hotplug_slot_name() to get the slot's
|
||||||
|
* unique name. The driver no longer needs to handle a ->reset_slot callback
|
||||||
|
* from this point on.
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative int on error.
|
||||||
|
*/
|
||||||
|
void pci_hp_destroy(struct hotplug_slot *slot)
|
||||||
|
{
|
||||||
|
struct pci_slot *pci_slot = slot->pci_slot;
|
||||||
|
|
||||||
|
slot->pci_slot = NULL;
|
||||||
pci_slot->hotplug = NULL;
|
pci_slot->hotplug = NULL;
|
||||||
pci_destroy_slot(pci_slot);
|
pci_destroy_slot(pci_slot);
|
||||||
mutex_unlock(&pci_hp_mutex);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(pci_hp_deregister);
|
EXPORT_SYMBOL_GPL(pci_hp_destroy);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pci_hp_change_slot_info - changes the slot's information structure in the core
|
* pci_hp_change_slot_info - changes the slot's information structure in the core
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/sched/signal.h> /* signal_pending() */
|
#include <linux/sched/signal.h> /* signal_pending() */
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/rwsem.h>
|
||||||
#include <linux/workqueue.h>
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
#include "../pcie/portdrv.h"
|
#include "../pcie/portdrv.h"
|
||||||
|
@ -57,49 +58,111 @@ do { \
|
||||||
dev_warn(&ctrl->pcie->device, format, ## arg)
|
dev_warn(&ctrl->pcie->device, format, ## arg)
|
||||||
|
|
||||||
#define SLOT_NAME_SIZE 10
|
#define SLOT_NAME_SIZE 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct slot - PCIe hotplug slot
|
||||||
|
* @state: current state machine position
|
||||||
|
* @ctrl: pointer to the slot's controller structure
|
||||||
|
* @hotplug_slot: pointer to the structure registered with the PCI hotplug core
|
||||||
|
* @work: work item to turn the slot on or off after 5 seconds in response to
|
||||||
|
* an Attention Button press
|
||||||
|
* @lock: protects reads and writes of @state;
|
||||||
|
* protects scheduling, execution and cancellation of @work
|
||||||
|
*/
|
||||||
struct slot {
|
struct slot {
|
||||||
u8 state;
|
u8 state;
|
||||||
struct controller *ctrl;
|
struct controller *ctrl;
|
||||||
struct hotplug_slot *hotplug_slot;
|
struct hotplug_slot *hotplug_slot;
|
||||||
struct delayed_work work; /* work for button event */
|
struct delayed_work work;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
struct mutex hotplug_lock;
|
|
||||||
struct workqueue_struct *wq;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct event_info {
|
|
||||||
u32 event_type;
|
|
||||||
struct slot *p_slot;
|
|
||||||
struct work_struct work;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct controller - PCIe hotplug controller
|
||||||
|
* @ctrl_lock: serializes writes to the Slot Control register
|
||||||
|
* @pcie: pointer to the controller's PCIe port service device
|
||||||
|
* @reset_lock: prevents access to the Data Link Layer Link Active bit in the
|
||||||
|
* Link Status register and to the Presence Detect State bit in the Slot
|
||||||
|
* Status register during a slot reset which may cause them to flap
|
||||||
|
* @slot: pointer to the controller's slot structure
|
||||||
|
* @queue: wait queue to wake up on reception of a Command Completed event,
|
||||||
|
* used for synchronous writes to the Slot Control register
|
||||||
|
* @slot_cap: cached copy of the Slot Capabilities register
|
||||||
|
* @slot_ctrl: cached copy of the Slot Control register
|
||||||
|
* @poll_thread: thread to poll for slot events if no IRQ is available,
|
||||||
|
* enabled with pciehp_poll_mode module parameter
|
||||||
|
* @cmd_started: jiffies when the Slot Control register was last written;
|
||||||
|
* the next write is allowed 1 second later, absent a Command Completed
|
||||||
|
* interrupt (PCIe r4.0, sec 6.7.3.2)
|
||||||
|
* @cmd_busy: flag set on Slot Control register write, cleared by IRQ handler
|
||||||
|
* on reception of a Command Completed event
|
||||||
|
* @link_active_reporting: cached copy of Data Link Layer Link Active Reporting
|
||||||
|
* Capable bit in Link Capabilities register; if this bit is zero, the
|
||||||
|
* Data Link Layer Link Active bit in the Link Status register will never
|
||||||
|
* be set and the driver is thus confined to wait 1 second before assuming
|
||||||
|
* the link to a hotplugged device is up and accessing it
|
||||||
|
* @notification_enabled: whether the IRQ was requested successfully
|
||||||
|
* @power_fault_detected: whether a power fault was detected by the hardware
|
||||||
|
* that has not yet been cleared by the user
|
||||||
|
* @pending_events: used by the IRQ handler to save events retrieved from the
|
||||||
|
* Slot Status register for later consumption by the IRQ thread
|
||||||
|
* @request_result: result of last user request submitted to the IRQ thread
|
||||||
|
* @requester: wait queue to wake up on completion of user request,
|
||||||
|
* used for synchronous slot enable/disable request via sysfs
|
||||||
|
*/
|
||||||
struct controller {
|
struct controller {
|
||||||
struct mutex ctrl_lock; /* controller lock */
|
struct mutex ctrl_lock;
|
||||||
struct pcie_device *pcie; /* PCI Express port service */
|
struct pcie_device *pcie;
|
||||||
|
struct rw_semaphore reset_lock;
|
||||||
struct slot *slot;
|
struct slot *slot;
|
||||||
wait_queue_head_t queue; /* sleep & wake process */
|
wait_queue_head_t queue;
|
||||||
u32 slot_cap;
|
u32 slot_cap;
|
||||||
u16 slot_ctrl;
|
u16 slot_ctrl;
|
||||||
struct timer_list poll_timer;
|
struct task_struct *poll_thread;
|
||||||
unsigned long cmd_started; /* jiffies */
|
unsigned long cmd_started; /* jiffies */
|
||||||
unsigned int cmd_busy:1;
|
unsigned int cmd_busy:1;
|
||||||
unsigned int link_active_reporting:1;
|
unsigned int link_active_reporting:1;
|
||||||
unsigned int notification_enabled:1;
|
unsigned int notification_enabled:1;
|
||||||
unsigned int power_fault_detected;
|
unsigned int power_fault_detected;
|
||||||
|
atomic_t pending_events;
|
||||||
|
int request_result;
|
||||||
|
wait_queue_head_t requester;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define INT_PRESENCE_ON 1
|
/**
|
||||||
#define INT_PRESENCE_OFF 2
|
* DOC: Slot state
|
||||||
#define INT_POWER_FAULT 3
|
*
|
||||||
#define INT_BUTTON_PRESS 4
|
* @OFF_STATE: slot is powered off, no subordinate devices are enumerated
|
||||||
#define INT_LINK_UP 5
|
* @BLINKINGON_STATE: slot will be powered on after the 5 second delay,
|
||||||
#define INT_LINK_DOWN 6
|
* green led is blinking
|
||||||
|
* @BLINKINGOFF_STATE: slot will be powered off after the 5 second delay,
|
||||||
#define STATIC_STATE 0
|
* green led is blinking
|
||||||
|
* @POWERON_STATE: slot is currently powering on
|
||||||
|
* @POWEROFF_STATE: slot is currently powering off
|
||||||
|
* @ON_STATE: slot is powered on, subordinate devices have been enumerated
|
||||||
|
*/
|
||||||
|
#define OFF_STATE 0
|
||||||
#define BLINKINGON_STATE 1
|
#define BLINKINGON_STATE 1
|
||||||
#define BLINKINGOFF_STATE 2
|
#define BLINKINGOFF_STATE 2
|
||||||
#define POWERON_STATE 3
|
#define POWERON_STATE 3
|
||||||
#define POWEROFF_STATE 4
|
#define POWEROFF_STATE 4
|
||||||
|
#define ON_STATE 5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOC: Flags to request an action from the IRQ thread
|
||||||
|
*
|
||||||
|
* These are stored together with events read from the Slot Status register,
|
||||||
|
* hence must be greater than its 16-bit width.
|
||||||
|
*
|
||||||
|
* %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or
|
||||||
|
* an Attention Button press after the 5 second delay
|
||||||
|
* %RERUN_ISR: Used by the IRQ handler to inform the IRQ thread that the
|
||||||
|
* hotplug port was inaccessible when the interrupt occurred, requiring
|
||||||
|
* that the IRQ handler is rerun by the IRQ thread after it has made the
|
||||||
|
* hotplug port accessible by runtime resuming its parents to D0
|
||||||
|
*/
|
||||||
|
#define DISABLE_SLOT (1 << 16)
|
||||||
|
#define RERUN_ISR (1 << 17)
|
||||||
|
|
||||||
#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP)
|
#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP)
|
||||||
#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP)
|
#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP)
|
||||||
|
@ -113,15 +176,17 @@ struct controller {
|
||||||
|
|
||||||
int pciehp_sysfs_enable_slot(struct slot *slot);
|
int pciehp_sysfs_enable_slot(struct slot *slot);
|
||||||
int pciehp_sysfs_disable_slot(struct slot *slot);
|
int pciehp_sysfs_disable_slot(struct slot *slot);
|
||||||
void pciehp_queue_interrupt_event(struct slot *slot, u32 event_type);
|
void pciehp_request(struct controller *ctrl, int action);
|
||||||
|
void pciehp_handle_button_press(struct slot *slot);
|
||||||
|
void pciehp_handle_disable_request(struct slot *slot);
|
||||||
|
void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events);
|
||||||
int pciehp_configure_device(struct slot *p_slot);
|
int pciehp_configure_device(struct slot *p_slot);
|
||||||
int pciehp_unconfigure_device(struct slot *p_slot);
|
void pciehp_unconfigure_device(struct slot *p_slot);
|
||||||
void pciehp_queue_pushbutton_work(struct work_struct *work);
|
void pciehp_queue_pushbutton_work(struct work_struct *work);
|
||||||
struct controller *pcie_init(struct pcie_device *dev);
|
struct controller *pcie_init(struct pcie_device *dev);
|
||||||
int pcie_init_notification(struct controller *ctrl);
|
int pcie_init_notification(struct controller *ctrl);
|
||||||
int pciehp_enable_slot(struct slot *p_slot);
|
void pcie_shutdown_notification(struct controller *ctrl);
|
||||||
int pciehp_disable_slot(struct slot *p_slot);
|
void pcie_clear_hotplug_events(struct controller *ctrl);
|
||||||
void pcie_reenable_notification(struct controller *ctrl);
|
|
||||||
int pciehp_power_on_slot(struct slot *slot);
|
int pciehp_power_on_slot(struct slot *slot);
|
||||||
void pciehp_power_off_slot(struct slot *slot);
|
void pciehp_power_off_slot(struct slot *slot);
|
||||||
void pciehp_get_power_status(struct slot *slot, u8 *status);
|
void pciehp_get_power_status(struct slot *slot, u8 *status);
|
||||||
|
|
|
@ -26,11 +26,12 @@
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/time.h>
|
#include <linux/time.h>
|
||||||
|
|
||||||
|
#include "../pci.h"
|
||||||
|
|
||||||
/* Global variables */
|
/* Global variables */
|
||||||
bool pciehp_debug;
|
bool pciehp_debug;
|
||||||
bool pciehp_poll_mode;
|
bool pciehp_poll_mode;
|
||||||
int pciehp_poll_time;
|
int pciehp_poll_time;
|
||||||
static bool pciehp_force;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* not really modular, but the easiest way to keep compat with existing
|
* not really modular, but the easiest way to keep compat with existing
|
||||||
|
@ -39,11 +40,9 @@ static bool pciehp_force;
|
||||||
module_param(pciehp_debug, bool, 0644);
|
module_param(pciehp_debug, bool, 0644);
|
||||||
module_param(pciehp_poll_mode, bool, 0644);
|
module_param(pciehp_poll_mode, bool, 0644);
|
||||||
module_param(pciehp_poll_time, int, 0644);
|
module_param(pciehp_poll_time, int, 0644);
|
||||||
module_param(pciehp_force, bool, 0644);
|
|
||||||
MODULE_PARM_DESC(pciehp_debug, "Debugging mode enabled or not");
|
MODULE_PARM_DESC(pciehp_debug, "Debugging mode enabled or not");
|
||||||
MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not");
|
MODULE_PARM_DESC(pciehp_poll_mode, "Using polling mechanism for hot-plug events or not");
|
||||||
MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds");
|
MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds");
|
||||||
MODULE_PARM_DESC(pciehp_force, "Force pciehp, even if OSHP is missing");
|
|
||||||
|
|
||||||
#define PCIE_MODULE_NAME "pciehp"
|
#define PCIE_MODULE_NAME "pciehp"
|
||||||
|
|
||||||
|
@ -56,17 +55,6 @@ static int get_latch_status(struct hotplug_slot *slot, u8 *value);
|
||||||
static int get_adapter_status(struct hotplug_slot *slot, u8 *value);
|
static int get_adapter_status(struct hotplug_slot *slot, u8 *value);
|
||||||
static int reset_slot(struct hotplug_slot *slot, int probe);
|
static int reset_slot(struct hotplug_slot *slot, int probe);
|
||||||
|
|
||||||
/**
|
|
||||||
* release_slot - free up the memory used by a slot
|
|
||||||
* @hotplug_slot: slot to free
|
|
||||||
*/
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
kfree(hotplug_slot->ops);
|
|
||||||
kfree(hotplug_slot->info);
|
|
||||||
kfree(hotplug_slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int init_slot(struct controller *ctrl)
|
static int init_slot(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
struct slot *slot = ctrl->slot;
|
struct slot *slot = ctrl->slot;
|
||||||
|
@ -107,15 +95,14 @@ static int init_slot(struct controller *ctrl)
|
||||||
/* register this slot with the hotplug pci core */
|
/* register this slot with the hotplug pci core */
|
||||||
hotplug->info = info;
|
hotplug->info = info;
|
||||||
hotplug->private = slot;
|
hotplug->private = slot;
|
||||||
hotplug->release = &release_slot;
|
|
||||||
hotplug->ops = ops;
|
hotplug->ops = ops;
|
||||||
slot->hotplug_slot = hotplug;
|
slot->hotplug_slot = hotplug;
|
||||||
snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl));
|
snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl));
|
||||||
|
|
||||||
retval = pci_hp_register(hotplug,
|
retval = pci_hp_initialize(hotplug,
|
||||||
ctrl->pcie->port->subordinate, 0, name);
|
ctrl->pcie->port->subordinate, 0, name);
|
||||||
if (retval)
|
if (retval)
|
||||||
ctrl_err(ctrl, "pci_hp_register failed: error %d\n", retval);
|
ctrl_err(ctrl, "pci_hp_initialize failed: error %d\n", retval);
|
||||||
out:
|
out:
|
||||||
if (retval) {
|
if (retval) {
|
||||||
kfree(ops);
|
kfree(ops);
|
||||||
|
@ -127,7 +114,12 @@ out:
|
||||||
|
|
||||||
static void cleanup_slot(struct controller *ctrl)
|
static void cleanup_slot(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
pci_hp_deregister(ctrl->slot->hotplug_slot);
|
struct hotplug_slot *hotplug_slot = ctrl->slot->hotplug_slot;
|
||||||
|
|
||||||
|
pci_hp_destroy(hotplug_slot);
|
||||||
|
kfree(hotplug_slot->ops);
|
||||||
|
kfree(hotplug_slot->info);
|
||||||
|
kfree(hotplug_slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -136,8 +128,11 @@ static void cleanup_slot(struct controller *ctrl)
|
||||||
static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status)
|
static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status)
|
||||||
{
|
{
|
||||||
struct slot *slot = hotplug_slot->private;
|
struct slot *slot = hotplug_slot->private;
|
||||||
|
struct pci_dev *pdev = slot->ctrl->pcie->port;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pciehp_set_attention_status(slot, status);
|
pciehp_set_attention_status(slot, status);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +155,11 @@ static int disable_slot(struct hotplug_slot *hotplug_slot)
|
||||||
static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
{
|
{
|
||||||
struct slot *slot = hotplug_slot->private;
|
struct slot *slot = hotplug_slot->private;
|
||||||
|
struct pci_dev *pdev = slot->ctrl->pcie->port;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pciehp_get_power_status(slot, value);
|
pciehp_get_power_status(slot, value);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,16 +174,22 @@ static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
{
|
{
|
||||||
struct slot *slot = hotplug_slot->private;
|
struct slot *slot = hotplug_slot->private;
|
||||||
|
struct pci_dev *pdev = slot->ctrl->pcie->port;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pciehp_get_latch_status(slot, value);
|
pciehp_get_latch_status(slot, value);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
{
|
{
|
||||||
struct slot *slot = hotplug_slot->private;
|
struct slot *slot = hotplug_slot->private;
|
||||||
|
struct pci_dev *pdev = slot->ctrl->pcie->port;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pciehp_get_adapter_status(slot, value);
|
pciehp_get_adapter_status(slot, value);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,12 +200,40 @@ static int reset_slot(struct hotplug_slot *hotplug_slot, int probe)
|
||||||
return pciehp_reset_slot(slot, probe);
|
return pciehp_reset_slot(slot, probe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pciehp_check_presence() - synthesize event if presence has changed
|
||||||
|
*
|
||||||
|
* On probe and resume, an explicit presence check is necessary to bring up an
|
||||||
|
* occupied slot or bring down an unoccupied slot. This can't be triggered by
|
||||||
|
* events in the Slot Status register, they may be stale and are therefore
|
||||||
|
* cleared. Secondly, sending an interrupt for "events that occur while
|
||||||
|
* interrupt generation is disabled [when] interrupt generation is subsequently
|
||||||
|
* enabled" is optional per PCIe r4.0, sec 6.7.3.4.
|
||||||
|
*/
|
||||||
|
static void pciehp_check_presence(struct controller *ctrl)
|
||||||
|
{
|
||||||
|
struct slot *slot = ctrl->slot;
|
||||||
|
u8 occupied;
|
||||||
|
|
||||||
|
down_read(&ctrl->reset_lock);
|
||||||
|
mutex_lock(&slot->lock);
|
||||||
|
|
||||||
|
pciehp_get_adapter_status(slot, &occupied);
|
||||||
|
if ((occupied && (slot->state == OFF_STATE ||
|
||||||
|
slot->state == BLINKINGON_STATE)) ||
|
||||||
|
(!occupied && (slot->state == ON_STATE ||
|
||||||
|
slot->state == BLINKINGOFF_STATE)))
|
||||||
|
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
|
||||||
|
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
up_read(&ctrl->reset_lock);
|
||||||
|
}
|
||||||
|
|
||||||
static int pciehp_probe(struct pcie_device *dev)
|
static int pciehp_probe(struct pcie_device *dev)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
struct controller *ctrl;
|
struct controller *ctrl;
|
||||||
struct slot *slot;
|
struct slot *slot;
|
||||||
u8 occupied, poweron;
|
|
||||||
|
|
||||||
/* If this is not a "hotplug" service, we have no business here. */
|
/* If this is not a "hotplug" service, we have no business here. */
|
||||||
if (dev->service != PCIE_PORT_SERVICE_HP)
|
if (dev->service != PCIE_PORT_SERVICE_HP)
|
||||||
|
@ -238,21 +270,20 @@ static int pciehp_probe(struct pcie_device *dev)
|
||||||
goto err_out_free_ctrl_slot;
|
goto err_out_free_ctrl_slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if slot is occupied */
|
/* Publish to user space */
|
||||||
slot = ctrl->slot;
|
slot = ctrl->slot;
|
||||||
pciehp_get_adapter_status(slot, &occupied);
|
rc = pci_hp_add(slot->hotplug_slot);
|
||||||
pciehp_get_power_status(slot, &poweron);
|
if (rc) {
|
||||||
if (occupied && pciehp_force) {
|
ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc);
|
||||||
mutex_lock(&slot->hotplug_lock);
|
goto err_out_shutdown_notification;
|
||||||
pciehp_enable_slot(slot);
|
|
||||||
mutex_unlock(&slot->hotplug_lock);
|
|
||||||
}
|
}
|
||||||
/* If empty slot's power status is on, turn power off */
|
|
||||||
if (!occupied && poweron && POWER_CTRL(ctrl))
|
pciehp_check_presence(ctrl);
|
||||||
pciehp_power_off_slot(slot);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_out_shutdown_notification:
|
||||||
|
pcie_shutdown_notification(ctrl);
|
||||||
err_out_free_ctrl_slot:
|
err_out_free_ctrl_slot:
|
||||||
cleanup_slot(ctrl);
|
cleanup_slot(ctrl);
|
||||||
err_out_release_ctlr:
|
err_out_release_ctlr:
|
||||||
|
@ -264,6 +295,8 @@ static void pciehp_remove(struct pcie_device *dev)
|
||||||
{
|
{
|
||||||
struct controller *ctrl = get_service_data(dev);
|
struct controller *ctrl = get_service_data(dev);
|
||||||
|
|
||||||
|
pci_hp_del(ctrl->slot->hotplug_slot);
|
||||||
|
pcie_shutdown_notification(ctrl);
|
||||||
cleanup_slot(ctrl);
|
cleanup_slot(ctrl);
|
||||||
pciehp_release_ctrl(ctrl);
|
pciehp_release_ctrl(ctrl);
|
||||||
}
|
}
|
||||||
|
@ -274,27 +307,28 @@ static int pciehp_suspend(struct pcie_device *dev)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int pciehp_resume_noirq(struct pcie_device *dev)
|
||||||
|
{
|
||||||
|
struct controller *ctrl = get_service_data(dev);
|
||||||
|
struct slot *slot = ctrl->slot;
|
||||||
|
|
||||||
|
/* pci_restore_state() just wrote to the Slot Control register */
|
||||||
|
ctrl->cmd_started = jiffies;
|
||||||
|
ctrl->cmd_busy = true;
|
||||||
|
|
||||||
|
/* clear spurious events from rediscovery of inserted card */
|
||||||
|
if (slot->state == ON_STATE || slot->state == BLINKINGOFF_STATE)
|
||||||
|
pcie_clear_hotplug_events(ctrl);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int pciehp_resume(struct pcie_device *dev)
|
static int pciehp_resume(struct pcie_device *dev)
|
||||||
{
|
{
|
||||||
struct controller *ctrl;
|
struct controller *ctrl = get_service_data(dev);
|
||||||
struct slot *slot;
|
|
||||||
u8 status;
|
|
||||||
|
|
||||||
ctrl = get_service_data(dev);
|
pciehp_check_presence(ctrl);
|
||||||
|
|
||||||
/* reinitialize the chipset's event detection logic */
|
|
||||||
pcie_reenable_notification(ctrl);
|
|
||||||
|
|
||||||
slot = ctrl->slot;
|
|
||||||
|
|
||||||
/* Check if slot is occupied */
|
|
||||||
pciehp_get_adapter_status(slot, &status);
|
|
||||||
mutex_lock(&slot->hotplug_lock);
|
|
||||||
if (status)
|
|
||||||
pciehp_enable_slot(slot);
|
|
||||||
else
|
|
||||||
pciehp_disable_slot(slot);
|
|
||||||
mutex_unlock(&slot->hotplug_lock);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif /* PM */
|
#endif /* PM */
|
||||||
|
@ -309,6 +343,7 @@ static struct pcie_port_service_driver hpdriver_portdrv = {
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
.suspend = pciehp_suspend,
|
.suspend = pciehp_suspend,
|
||||||
|
.resume_noirq = pciehp_resume_noirq,
|
||||||
.resume = pciehp_resume,
|
.resume = pciehp_resume,
|
||||||
#endif /* PM */
|
#endif /* PM */
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,28 +17,11 @@
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
#include "../pci.h"
|
#include "../pci.h"
|
||||||
#include "pciehp.h"
|
#include "pciehp.h"
|
||||||
|
|
||||||
static void interrupt_event_handler(struct work_struct *work);
|
|
||||||
|
|
||||||
void pciehp_queue_interrupt_event(struct slot *p_slot, u32 event_type)
|
|
||||||
{
|
|
||||||
struct event_info *info;
|
|
||||||
|
|
||||||
info = kmalloc(sizeof(*info), GFP_ATOMIC);
|
|
||||||
if (!info) {
|
|
||||||
ctrl_err(p_slot->ctrl, "dropped event %d (ENOMEM)\n", event_type);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
INIT_WORK(&info->work, interrupt_event_handler);
|
|
||||||
info->event_type = event_type;
|
|
||||||
info->p_slot = p_slot;
|
|
||||||
queue_work(p_slot->wq, &info->work);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The following routines constitute the bulk of the
|
/* The following routines constitute the bulk of the
|
||||||
hotplug controller logic
|
hotplug controller logic
|
||||||
*/
|
*/
|
||||||
|
@ -119,14 +102,11 @@ err_exit:
|
||||||
* remove_board - Turns off slot and LEDs
|
* remove_board - Turns off slot and LEDs
|
||||||
* @p_slot: slot where board is being removed
|
* @p_slot: slot where board is being removed
|
||||||
*/
|
*/
|
||||||
static int remove_board(struct slot *p_slot)
|
static void remove_board(struct slot *p_slot)
|
||||||
{
|
{
|
||||||
int retval;
|
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
|
|
||||||
retval = pciehp_unconfigure_device(p_slot);
|
pciehp_unconfigure_device(p_slot);
|
||||||
if (retval)
|
|
||||||
return retval;
|
|
||||||
|
|
||||||
if (POWER_CTRL(ctrl)) {
|
if (POWER_CTRL(ctrl)) {
|
||||||
pciehp_power_off_slot(p_slot);
|
pciehp_power_off_slot(p_slot);
|
||||||
|
@ -141,86 +121,30 @@ static int remove_board(struct slot *p_slot)
|
||||||
|
|
||||||
/* turn off Green LED */
|
/* turn off Green LED */
|
||||||
pciehp_green_led_off(p_slot);
|
pciehp_green_led_off(p_slot);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct power_work_info {
|
static int pciehp_enable_slot(struct slot *slot);
|
||||||
struct slot *p_slot;
|
static int pciehp_disable_slot(struct slot *slot);
|
||||||
struct work_struct work;
|
|
||||||
unsigned int req;
|
|
||||||
#define DISABLE_REQ 0
|
|
||||||
#define ENABLE_REQ 1
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
void pciehp_request(struct controller *ctrl, int action)
|
||||||
* pciehp_power_thread - handle pushbutton events
|
|
||||||
* @work: &struct work_struct describing work to be done
|
|
||||||
*
|
|
||||||
* Scheduled procedure to handle blocking stuff for the pushbuttons.
|
|
||||||
* Handles all pending events and exits.
|
|
||||||
*/
|
|
||||||
static void pciehp_power_thread(struct work_struct *work)
|
|
||||||
{
|
{
|
||||||
struct power_work_info *info =
|
atomic_or(action, &ctrl->pending_events);
|
||||||
container_of(work, struct power_work_info, work);
|
if (!pciehp_poll_mode)
|
||||||
struct slot *p_slot = info->p_slot;
|
irq_wake_thread(ctrl->pcie->irq, ctrl);
|
||||||
int ret;
|
|
||||||
|
|
||||||
switch (info->req) {
|
|
||||||
case DISABLE_REQ:
|
|
||||||
mutex_lock(&p_slot->hotplug_lock);
|
|
||||||
pciehp_disable_slot(p_slot);
|
|
||||||
mutex_unlock(&p_slot->hotplug_lock);
|
|
||||||
mutex_lock(&p_slot->lock);
|
|
||||||
p_slot->state = STATIC_STATE;
|
|
||||||
mutex_unlock(&p_slot->lock);
|
|
||||||
break;
|
|
||||||
case ENABLE_REQ:
|
|
||||||
mutex_lock(&p_slot->hotplug_lock);
|
|
||||||
ret = pciehp_enable_slot(p_slot);
|
|
||||||
mutex_unlock(&p_slot->hotplug_lock);
|
|
||||||
if (ret)
|
|
||||||
pciehp_green_led_off(p_slot);
|
|
||||||
mutex_lock(&p_slot->lock);
|
|
||||||
p_slot->state = STATIC_STATE;
|
|
||||||
mutex_unlock(&p_slot->lock);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
kfree(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pciehp_queue_power_work(struct slot *p_slot, int req)
|
|
||||||
{
|
|
||||||
struct power_work_info *info;
|
|
||||||
|
|
||||||
p_slot->state = (req == ENABLE_REQ) ? POWERON_STATE : POWEROFF_STATE;
|
|
||||||
|
|
||||||
info = kmalloc(sizeof(*info), GFP_KERNEL);
|
|
||||||
if (!info) {
|
|
||||||
ctrl_err(p_slot->ctrl, "no memory to queue %s request\n",
|
|
||||||
(req == ENABLE_REQ) ? "poweron" : "poweroff");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
info->p_slot = p_slot;
|
|
||||||
INIT_WORK(&info->work, pciehp_power_thread);
|
|
||||||
info->req = req;
|
|
||||||
queue_work(p_slot->wq, &info->work);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pciehp_queue_pushbutton_work(struct work_struct *work)
|
void pciehp_queue_pushbutton_work(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct slot *p_slot = container_of(work, struct slot, work.work);
|
struct slot *p_slot = container_of(work, struct slot, work.work);
|
||||||
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
|
|
||||||
mutex_lock(&p_slot->lock);
|
mutex_lock(&p_slot->lock);
|
||||||
switch (p_slot->state) {
|
switch (p_slot->state) {
|
||||||
case BLINKINGOFF_STATE:
|
case BLINKINGOFF_STATE:
|
||||||
pciehp_queue_power_work(p_slot, DISABLE_REQ);
|
pciehp_request(ctrl, DISABLE_SLOT);
|
||||||
break;
|
break;
|
||||||
case BLINKINGON_STATE:
|
case BLINKINGON_STATE:
|
||||||
pciehp_queue_power_work(p_slot, ENABLE_REQ);
|
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -228,18 +152,15 @@ void pciehp_queue_pushbutton_work(struct work_struct *work)
|
||||||
mutex_unlock(&p_slot->lock);
|
mutex_unlock(&p_slot->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
void pciehp_handle_button_press(struct slot *p_slot)
|
||||||
* Note: This function must be called with slot->lock held
|
|
||||||
*/
|
|
||||||
static void handle_button_press_event(struct slot *p_slot)
|
|
||||||
{
|
{
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
u8 getstatus;
|
|
||||||
|
|
||||||
|
mutex_lock(&p_slot->lock);
|
||||||
switch (p_slot->state) {
|
switch (p_slot->state) {
|
||||||
case STATIC_STATE:
|
case OFF_STATE:
|
||||||
pciehp_get_power_status(p_slot, &getstatus);
|
case ON_STATE:
|
||||||
if (getstatus) {
|
if (p_slot->state == ON_STATE) {
|
||||||
p_slot->state = BLINKINGOFF_STATE;
|
p_slot->state = BLINKINGOFF_STATE;
|
||||||
ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n",
|
ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n",
|
||||||
slot_name(p_slot));
|
slot_name(p_slot));
|
||||||
|
@ -251,7 +172,7 @@ static void handle_button_press_event(struct slot *p_slot)
|
||||||
/* blink green LED and turn off amber */
|
/* blink green LED and turn off amber */
|
||||||
pciehp_green_led_blink(p_slot);
|
pciehp_green_led_blink(p_slot);
|
||||||
pciehp_set_attention_status(p_slot, 0);
|
pciehp_set_attention_status(p_slot, 0);
|
||||||
queue_delayed_work(p_slot->wq, &p_slot->work, 5*HZ);
|
schedule_delayed_work(&p_slot->work, 5 * HZ);
|
||||||
break;
|
break;
|
||||||
case BLINKINGOFF_STATE:
|
case BLINKINGOFF_STATE:
|
||||||
case BLINKINGON_STATE:
|
case BLINKINGON_STATE:
|
||||||
|
@ -262,118 +183,104 @@ static void handle_button_press_event(struct slot *p_slot)
|
||||||
*/
|
*/
|
||||||
ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot));
|
ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot));
|
||||||
cancel_delayed_work(&p_slot->work);
|
cancel_delayed_work(&p_slot->work);
|
||||||
if (p_slot->state == BLINKINGOFF_STATE)
|
if (p_slot->state == BLINKINGOFF_STATE) {
|
||||||
|
p_slot->state = ON_STATE;
|
||||||
pciehp_green_led_on(p_slot);
|
pciehp_green_led_on(p_slot);
|
||||||
else
|
} else {
|
||||||
|
p_slot->state = OFF_STATE;
|
||||||
pciehp_green_led_off(p_slot);
|
pciehp_green_led_off(p_slot);
|
||||||
|
}
|
||||||
pciehp_set_attention_status(p_slot, 0);
|
pciehp_set_attention_status(p_slot, 0);
|
||||||
ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n",
|
ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n",
|
||||||
slot_name(p_slot));
|
slot_name(p_slot));
|
||||||
p_slot->state = STATIC_STATE;
|
|
||||||
break;
|
|
||||||
case POWEROFF_STATE:
|
|
||||||
case POWERON_STATE:
|
|
||||||
/*
|
|
||||||
* Ignore if the slot is on power-on or power-off state;
|
|
||||||
* this means that the previous attention button action
|
|
||||||
* to hot-add or hot-remove is undergoing
|
|
||||||
*/
|
|
||||||
ctrl_info(ctrl, "Slot(%s): Button ignored\n",
|
|
||||||
slot_name(p_slot));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n",
|
ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n",
|
||||||
slot_name(p_slot), p_slot->state);
|
slot_name(p_slot), p_slot->state);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Note: This function must be called with slot->lock held
|
|
||||||
*/
|
|
||||||
static void handle_link_event(struct slot *p_slot, u32 event)
|
|
||||||
{
|
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
|
||||||
|
|
||||||
switch (p_slot->state) {
|
|
||||||
case BLINKINGON_STATE:
|
|
||||||
case BLINKINGOFF_STATE:
|
|
||||||
cancel_delayed_work(&p_slot->work);
|
|
||||||
/* Fall through */
|
|
||||||
case STATIC_STATE:
|
|
||||||
pciehp_queue_power_work(p_slot, event == INT_LINK_UP ?
|
|
||||||
ENABLE_REQ : DISABLE_REQ);
|
|
||||||
break;
|
|
||||||
case POWERON_STATE:
|
|
||||||
if (event == INT_LINK_UP) {
|
|
||||||
ctrl_info(ctrl, "Slot(%s): Link Up event ignored; already powering on\n",
|
|
||||||
slot_name(p_slot));
|
|
||||||
} else {
|
|
||||||
ctrl_info(ctrl, "Slot(%s): Link Down event queued; currently getting powered on\n",
|
|
||||||
slot_name(p_slot));
|
|
||||||
pciehp_queue_power_work(p_slot, DISABLE_REQ);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case POWEROFF_STATE:
|
|
||||||
if (event == INT_LINK_UP) {
|
|
||||||
ctrl_info(ctrl, "Slot(%s): Link Up event queued; currently getting powered off\n",
|
|
||||||
slot_name(p_slot));
|
|
||||||
pciehp_queue_power_work(p_slot, ENABLE_REQ);
|
|
||||||
} else {
|
|
||||||
ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n",
|
|
||||||
slot_name(p_slot));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n",
|
|
||||||
slot_name(p_slot), p_slot->state);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void interrupt_event_handler(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct event_info *info = container_of(work, struct event_info, work);
|
|
||||||
struct slot *p_slot = info->p_slot;
|
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
|
||||||
|
|
||||||
mutex_lock(&p_slot->lock);
|
|
||||||
switch (info->event_type) {
|
|
||||||
case INT_BUTTON_PRESS:
|
|
||||||
handle_button_press_event(p_slot);
|
|
||||||
break;
|
|
||||||
case INT_POWER_FAULT:
|
|
||||||
if (!POWER_CTRL(ctrl))
|
|
||||||
break;
|
|
||||||
pciehp_set_attention_status(p_slot, 1);
|
|
||||||
pciehp_green_led_off(p_slot);
|
|
||||||
break;
|
|
||||||
case INT_PRESENCE_ON:
|
|
||||||
pciehp_queue_power_work(p_slot, ENABLE_REQ);
|
|
||||||
break;
|
|
||||||
case INT_PRESENCE_OFF:
|
|
||||||
/*
|
|
||||||
* Regardless of surprise capability, we need to
|
|
||||||
* definitely remove a card that has been pulled out!
|
|
||||||
*/
|
|
||||||
pciehp_queue_power_work(p_slot, DISABLE_REQ);
|
|
||||||
break;
|
|
||||||
case INT_LINK_UP:
|
|
||||||
case INT_LINK_DOWN:
|
|
||||||
handle_link_event(p_slot, info->event_type);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mutex_unlock(&p_slot->lock);
|
mutex_unlock(&p_slot->lock);
|
||||||
|
|
||||||
kfree(info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pciehp_handle_disable_request(struct slot *slot)
|
||||||
|
{
|
||||||
|
struct controller *ctrl = slot->ctrl;
|
||||||
|
|
||||||
|
mutex_lock(&slot->lock);
|
||||||
|
switch (slot->state) {
|
||||||
|
case BLINKINGON_STATE:
|
||||||
|
case BLINKINGOFF_STATE:
|
||||||
|
cancel_delayed_work(&slot->work);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
slot->state = POWEROFF_STATE;
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
|
||||||
|
ctrl->request_result = pciehp_disable_slot(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events)
|
||||||
|
{
|
||||||
|
struct controller *ctrl = slot->ctrl;
|
||||||
|
bool link_active;
|
||||||
|
u8 present;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: This function must be called with slot->hotplug_lock held
|
* If the slot is on and presence or link has changed, turn it off.
|
||||||
|
* Even if it's occupied again, we cannot assume the card is the same.
|
||||||
*/
|
*/
|
||||||
int pciehp_enable_slot(struct slot *p_slot)
|
mutex_lock(&slot->lock);
|
||||||
|
switch (slot->state) {
|
||||||
|
case BLINKINGOFF_STATE:
|
||||||
|
cancel_delayed_work(&slot->work);
|
||||||
|
/* fall through */
|
||||||
|
case ON_STATE:
|
||||||
|
slot->state = POWEROFF_STATE;
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
if (events & PCI_EXP_SLTSTA_DLLSC)
|
||||||
|
ctrl_info(ctrl, "Slot(%s): Link Down\n",
|
||||||
|
slot_name(slot));
|
||||||
|
if (events & PCI_EXP_SLTSTA_PDC)
|
||||||
|
ctrl_info(ctrl, "Slot(%s): Card not present\n",
|
||||||
|
slot_name(slot));
|
||||||
|
pciehp_disable_slot(slot);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Turn the slot on if it's occupied or link is up */
|
||||||
|
mutex_lock(&slot->lock);
|
||||||
|
pciehp_get_adapter_status(slot, &present);
|
||||||
|
link_active = pciehp_check_link_active(ctrl);
|
||||||
|
if (!present && !link_active) {
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (slot->state) {
|
||||||
|
case BLINKINGON_STATE:
|
||||||
|
cancel_delayed_work(&slot->work);
|
||||||
|
/* fall through */
|
||||||
|
case OFF_STATE:
|
||||||
|
slot->state = POWERON_STATE;
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
if (present)
|
||||||
|
ctrl_info(ctrl, "Slot(%s): Card present\n",
|
||||||
|
slot_name(slot));
|
||||||
|
if (link_active)
|
||||||
|
ctrl_info(ctrl, "Slot(%s): Link Up\n",
|
||||||
|
slot_name(slot));
|
||||||
|
ctrl->request_result = pciehp_enable_slot(slot);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __pciehp_enable_slot(struct slot *p_slot)
|
||||||
{
|
{
|
||||||
u8 getstatus = 0;
|
u8 getstatus = 0;
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
|
@ -404,17 +311,29 @@ int pciehp_enable_slot(struct slot *p_slot)
|
||||||
return board_added(p_slot);
|
return board_added(p_slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static int pciehp_enable_slot(struct slot *slot)
|
||||||
* Note: This function must be called with slot->hotplug_lock held
|
{
|
||||||
*/
|
struct controller *ctrl = slot->ctrl;
|
||||||
int pciehp_disable_slot(struct slot *p_slot)
|
int ret;
|
||||||
|
|
||||||
|
pm_runtime_get_sync(&ctrl->pcie->port->dev);
|
||||||
|
ret = __pciehp_enable_slot(slot);
|
||||||
|
if (ret && ATTN_BUTTN(ctrl))
|
||||||
|
pciehp_green_led_off(slot); /* may be blinking */
|
||||||
|
pm_runtime_put(&ctrl->pcie->port->dev);
|
||||||
|
|
||||||
|
mutex_lock(&slot->lock);
|
||||||
|
slot->state = ret ? OFF_STATE : ON_STATE;
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __pciehp_disable_slot(struct slot *p_slot)
|
||||||
{
|
{
|
||||||
u8 getstatus = 0;
|
u8 getstatus = 0;
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
|
|
||||||
if (!p_slot->ctrl)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (POWER_CTRL(p_slot->ctrl)) {
|
if (POWER_CTRL(p_slot->ctrl)) {
|
||||||
pciehp_get_power_status(p_slot, &getstatus);
|
pciehp_get_power_status(p_slot, &getstatus);
|
||||||
if (!getstatus) {
|
if (!getstatus) {
|
||||||
|
@ -424,32 +343,50 @@ int pciehp_disable_slot(struct slot *p_slot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return remove_board(p_slot);
|
remove_board(p_slot);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pciehp_disable_slot(struct slot *slot)
|
||||||
|
{
|
||||||
|
struct controller *ctrl = slot->ctrl;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pm_runtime_get_sync(&ctrl->pcie->port->dev);
|
||||||
|
ret = __pciehp_disable_slot(slot);
|
||||||
|
pm_runtime_put(&ctrl->pcie->port->dev);
|
||||||
|
|
||||||
|
mutex_lock(&slot->lock);
|
||||||
|
slot->state = OFF_STATE;
|
||||||
|
mutex_unlock(&slot->lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pciehp_sysfs_enable_slot(struct slot *p_slot)
|
int pciehp_sysfs_enable_slot(struct slot *p_slot)
|
||||||
{
|
{
|
||||||
int retval = -ENODEV;
|
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
|
|
||||||
mutex_lock(&p_slot->lock);
|
mutex_lock(&p_slot->lock);
|
||||||
switch (p_slot->state) {
|
switch (p_slot->state) {
|
||||||
case BLINKINGON_STATE:
|
case BLINKINGON_STATE:
|
||||||
cancel_delayed_work(&p_slot->work);
|
case OFF_STATE:
|
||||||
case STATIC_STATE:
|
|
||||||
p_slot->state = POWERON_STATE;
|
|
||||||
mutex_unlock(&p_slot->lock);
|
mutex_unlock(&p_slot->lock);
|
||||||
mutex_lock(&p_slot->hotplug_lock);
|
/*
|
||||||
retval = pciehp_enable_slot(p_slot);
|
* The IRQ thread becomes a no-op if the user pulls out the
|
||||||
mutex_unlock(&p_slot->hotplug_lock);
|
* card before the thread wakes up, so initialize to -ENODEV.
|
||||||
mutex_lock(&p_slot->lock);
|
*/
|
||||||
p_slot->state = STATIC_STATE;
|
ctrl->request_result = -ENODEV;
|
||||||
break;
|
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
|
||||||
|
wait_event(ctrl->requester,
|
||||||
|
!atomic_read(&ctrl->pending_events));
|
||||||
|
return ctrl->request_result;
|
||||||
case POWERON_STATE:
|
case POWERON_STATE:
|
||||||
ctrl_info(ctrl, "Slot(%s): Already in powering on state\n",
|
ctrl_info(ctrl, "Slot(%s): Already in powering on state\n",
|
||||||
slot_name(p_slot));
|
slot_name(p_slot));
|
||||||
break;
|
break;
|
||||||
case BLINKINGOFF_STATE:
|
case BLINKINGOFF_STATE:
|
||||||
|
case ON_STATE:
|
||||||
case POWEROFF_STATE:
|
case POWEROFF_STATE:
|
||||||
ctrl_info(ctrl, "Slot(%s): Already enabled\n",
|
ctrl_info(ctrl, "Slot(%s): Already enabled\n",
|
||||||
slot_name(p_slot));
|
slot_name(p_slot));
|
||||||
|
@ -461,32 +398,28 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot)
|
||||||
}
|
}
|
||||||
mutex_unlock(&p_slot->lock);
|
mutex_unlock(&p_slot->lock);
|
||||||
|
|
||||||
return retval;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pciehp_sysfs_disable_slot(struct slot *p_slot)
|
int pciehp_sysfs_disable_slot(struct slot *p_slot)
|
||||||
{
|
{
|
||||||
int retval = -ENODEV;
|
|
||||||
struct controller *ctrl = p_slot->ctrl;
|
struct controller *ctrl = p_slot->ctrl;
|
||||||
|
|
||||||
mutex_lock(&p_slot->lock);
|
mutex_lock(&p_slot->lock);
|
||||||
switch (p_slot->state) {
|
switch (p_slot->state) {
|
||||||
case BLINKINGOFF_STATE:
|
case BLINKINGOFF_STATE:
|
||||||
cancel_delayed_work(&p_slot->work);
|
case ON_STATE:
|
||||||
case STATIC_STATE:
|
|
||||||
p_slot->state = POWEROFF_STATE;
|
|
||||||
mutex_unlock(&p_slot->lock);
|
mutex_unlock(&p_slot->lock);
|
||||||
mutex_lock(&p_slot->hotplug_lock);
|
pciehp_request(ctrl, DISABLE_SLOT);
|
||||||
retval = pciehp_disable_slot(p_slot);
|
wait_event(ctrl->requester,
|
||||||
mutex_unlock(&p_slot->hotplug_lock);
|
!atomic_read(&ctrl->pending_events));
|
||||||
mutex_lock(&p_slot->lock);
|
return ctrl->request_result;
|
||||||
p_slot->state = STATIC_STATE;
|
|
||||||
break;
|
|
||||||
case POWEROFF_STATE:
|
case POWEROFF_STATE:
|
||||||
ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",
|
ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",
|
||||||
slot_name(p_slot));
|
slot_name(p_slot));
|
||||||
break;
|
break;
|
||||||
case BLINKINGON_STATE:
|
case BLINKINGON_STATE:
|
||||||
|
case OFF_STATE:
|
||||||
case POWERON_STATE:
|
case POWERON_STATE:
|
||||||
ctrl_info(ctrl, "Slot(%s): Already disabled\n",
|
ctrl_info(ctrl, "Slot(%s): Already disabled\n",
|
||||||
slot_name(p_slot));
|
slot_name(p_slot));
|
||||||
|
@ -498,5 +431,5 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot)
|
||||||
}
|
}
|
||||||
mutex_unlock(&p_slot->lock);
|
mutex_unlock(&p_slot->lock);
|
||||||
|
|
||||||
return retval;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,9 @@
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/signal.h>
|
#include <linux/signal.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/timer.h>
|
#include <linux/kthread.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/time.h>
|
#include <linux/time.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
@ -31,47 +32,24 @@ static inline struct pci_dev *ctrl_dev(struct controller *ctrl)
|
||||||
return ctrl->pcie->port;
|
return ctrl->pcie->port;
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t pcie_isr(int irq, void *dev_id);
|
static irqreturn_t pciehp_isr(int irq, void *dev_id);
|
||||||
static void start_int_poll_timer(struct controller *ctrl, int sec);
|
static irqreturn_t pciehp_ist(int irq, void *dev_id);
|
||||||
|
static int pciehp_poll(void *data);
|
||||||
/* This is the interrupt polling timeout function. */
|
|
||||||
static void int_poll_timeout(struct timer_list *t)
|
|
||||||
{
|
|
||||||
struct controller *ctrl = from_timer(ctrl, t, poll_timer);
|
|
||||||
|
|
||||||
/* Poll for interrupt events. regs == NULL => polling */
|
|
||||||
pcie_isr(0, ctrl);
|
|
||||||
|
|
||||||
if (!pciehp_poll_time)
|
|
||||||
pciehp_poll_time = 2; /* default polling interval is 2 sec */
|
|
||||||
|
|
||||||
start_int_poll_timer(ctrl, pciehp_poll_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This function starts the interrupt polling timer. */
|
|
||||||
static void start_int_poll_timer(struct controller *ctrl, int sec)
|
|
||||||
{
|
|
||||||
/* Clamp to sane value */
|
|
||||||
if ((sec <= 0) || (sec > 60))
|
|
||||||
sec = 2;
|
|
||||||
|
|
||||||
ctrl->poll_timer.expires = jiffies + sec * HZ;
|
|
||||||
add_timer(&ctrl->poll_timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int pciehp_request_irq(struct controller *ctrl)
|
static inline int pciehp_request_irq(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
int retval, irq = ctrl->pcie->irq;
|
int retval, irq = ctrl->pcie->irq;
|
||||||
|
|
||||||
/* Install interrupt polling timer. Start with 10 sec delay */
|
|
||||||
if (pciehp_poll_mode) {
|
if (pciehp_poll_mode) {
|
||||||
timer_setup(&ctrl->poll_timer, int_poll_timeout, 0);
|
ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl,
|
||||||
start_int_poll_timer(ctrl, 10);
|
"pciehp_poll-%s",
|
||||||
return 0;
|
slot_name(ctrl->slot));
|
||||||
|
return PTR_ERR_OR_ZERO(ctrl->poll_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Installs the interrupt handler */
|
/* Installs the interrupt handler */
|
||||||
retval = request_irq(irq, pcie_isr, IRQF_SHARED, MY_NAME, ctrl);
|
retval = request_threaded_irq(irq, pciehp_isr, pciehp_ist,
|
||||||
|
IRQF_SHARED, MY_NAME, ctrl);
|
||||||
if (retval)
|
if (retval)
|
||||||
ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n",
|
ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n",
|
||||||
irq);
|
irq);
|
||||||
|
@ -81,7 +59,7 @@ static inline int pciehp_request_irq(struct controller *ctrl)
|
||||||
static inline void pciehp_free_irq(struct controller *ctrl)
|
static inline void pciehp_free_irq(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
if (pciehp_poll_mode)
|
if (pciehp_poll_mode)
|
||||||
del_timer_sync(&ctrl->poll_timer);
|
kthread_stop(ctrl->poll_thread);
|
||||||
else
|
else
|
||||||
free_irq(ctrl->pcie->irq, ctrl);
|
free_irq(ctrl->pcie->irq, ctrl);
|
||||||
}
|
}
|
||||||
|
@ -293,6 +271,11 @@ int pciehp_check_link_status(struct controller *ctrl)
|
||||||
found = pci_bus_check_dev(ctrl->pcie->port->subordinate,
|
found = pci_bus_check_dev(ctrl->pcie->port->subordinate,
|
||||||
PCI_DEVFN(0, 0));
|
PCI_DEVFN(0, 0));
|
||||||
|
|
||||||
|
/* ignore link or presence changes up to this point */
|
||||||
|
if (found)
|
||||||
|
atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC),
|
||||||
|
&ctrl->pending_events);
|
||||||
|
|
||||||
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
|
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
|
||||||
ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
|
ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
|
||||||
if ((lnk_status & PCI_EXP_LNKSTA_LT) ||
|
if ((lnk_status & PCI_EXP_LNKSTA_LT) ||
|
||||||
|
@ -339,7 +322,9 @@ int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot,
|
||||||
struct pci_dev *pdev = ctrl_dev(slot->ctrl);
|
struct pci_dev *pdev = ctrl_dev(slot->ctrl);
|
||||||
u16 slot_ctrl;
|
u16 slot_ctrl;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
|
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
*status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6;
|
*status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -350,7 +335,9 @@ void pciehp_get_attention_status(struct slot *slot, u8 *status)
|
||||||
struct pci_dev *pdev = ctrl_dev(ctrl);
|
struct pci_dev *pdev = ctrl_dev(ctrl);
|
||||||
u16 slot_ctrl;
|
u16 slot_ctrl;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
|
pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__,
|
ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__,
|
||||||
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl);
|
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_ctrl);
|
||||||
|
|
||||||
|
@ -425,9 +412,12 @@ int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot,
|
||||||
{
|
{
|
||||||
struct slot *slot = hotplug_slot->private;
|
struct slot *slot = hotplug_slot->private;
|
||||||
struct controller *ctrl = slot->ctrl;
|
struct controller *ctrl = slot->ctrl;
|
||||||
|
struct pci_dev *pdev = ctrl_dev(ctrl);
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
pcie_write_cmd_nowait(ctrl, status << 6,
|
pcie_write_cmd_nowait(ctrl, status << 6,
|
||||||
PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC);
|
PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC);
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,20 +529,35 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
struct controller *ctrl = (struct controller *)dev_id;
|
struct controller *ctrl = (struct controller *)dev_id;
|
||||||
struct pci_dev *pdev = ctrl_dev(ctrl);
|
struct pci_dev *pdev = ctrl_dev(ctrl);
|
||||||
struct pci_bus *subordinate = pdev->subordinate;
|
struct device *parent = pdev->dev.parent;
|
||||||
struct pci_dev *dev;
|
|
||||||
struct slot *slot = ctrl->slot;
|
|
||||||
u16 status, events;
|
u16 status, events;
|
||||||
u8 present;
|
|
||||||
bool link;
|
|
||||||
|
|
||||||
/* Interrupts cannot originate from a controller that's asleep */
|
/*
|
||||||
|
* Interrupts only occur in D3hot or shallower (PCIe r4.0, sec 6.7.3.4).
|
||||||
|
*/
|
||||||
if (pdev->current_state == PCI_D3cold)
|
if (pdev->current_state == PCI_D3cold)
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep the port accessible by holding a runtime PM ref on its parent.
|
||||||
|
* Defer resume of the parent to the IRQ thread if it's suspended.
|
||||||
|
* Mask the interrupt until then.
|
||||||
|
*/
|
||||||
|
if (parent) {
|
||||||
|
pm_runtime_get_noresume(parent);
|
||||||
|
if (!pm_runtime_active(parent)) {
|
||||||
|
pm_runtime_put(parent);
|
||||||
|
disable_irq_nosync(irq);
|
||||||
|
atomic_or(RERUN_ISR, &ctrl->pending_events);
|
||||||
|
return IRQ_WAKE_THREAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status);
|
pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status);
|
||||||
if (status == (u16) ~0) {
|
if (status == (u16) ~0) {
|
||||||
ctrl_info(ctrl, "%s: no response from device\n", __func__);
|
ctrl_info(ctrl, "%s: no response from device\n", __func__);
|
||||||
|
if (parent)
|
||||||
|
pm_runtime_put(parent);
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,86 +576,119 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
|
||||||
if (ctrl->power_fault_detected)
|
if (ctrl->power_fault_detected)
|
||||||
events &= ~PCI_EXP_SLTSTA_PFD;
|
events &= ~PCI_EXP_SLTSTA_PFD;
|
||||||
|
|
||||||
if (!events)
|
if (!events) {
|
||||||
|
if (parent)
|
||||||
|
pm_runtime_put(parent);
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
/* Capture link status before clearing interrupts */
|
|
||||||
if (events & PCI_EXP_SLTSTA_DLLSC)
|
|
||||||
link = pciehp_check_link_active(ctrl);
|
|
||||||
|
|
||||||
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events);
|
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events);
|
||||||
ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events);
|
ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events);
|
||||||
|
if (parent)
|
||||||
|
pm_runtime_put(parent);
|
||||||
|
|
||||||
/* Check Command Complete Interrupt Pending */
|
/*
|
||||||
|
* Command Completed notifications are not deferred to the
|
||||||
|
* IRQ thread because it may be waiting for their arrival.
|
||||||
|
*/
|
||||||
if (events & PCI_EXP_SLTSTA_CC) {
|
if (events & PCI_EXP_SLTSTA_CC) {
|
||||||
ctrl->cmd_busy = 0;
|
ctrl->cmd_busy = 0;
|
||||||
smp_mb();
|
smp_mb();
|
||||||
wake_up(&ctrl->queue);
|
wake_up(&ctrl->queue);
|
||||||
|
|
||||||
|
if (events == PCI_EXP_SLTSTA_CC)
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
|
||||||
|
events &= ~PCI_EXP_SLTSTA_CC;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subordinate) {
|
if (pdev->ignore_hotplug) {
|
||||||
list_for_each_entry(dev, &subordinate->devices, bus_list) {
|
ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events);
|
||||||
if (dev->ignore_hotplug) {
|
|
||||||
ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n",
|
|
||||||
events, pci_name(dev));
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Save pending events for consumption by IRQ thread. */
|
||||||
|
atomic_or(events, &ctrl->pending_events);
|
||||||
|
return IRQ_WAKE_THREAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static irqreturn_t pciehp_ist(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
struct controller *ctrl = (struct controller *)dev_id;
|
||||||
|
struct pci_dev *pdev = ctrl_dev(ctrl);
|
||||||
|
struct slot *slot = ctrl->slot;
|
||||||
|
irqreturn_t ret;
|
||||||
|
u32 events;
|
||||||
|
|
||||||
|
pci_config_pm_runtime_get(pdev);
|
||||||
|
|
||||||
|
/* rerun pciehp_isr() if the port was inaccessible on interrupt */
|
||||||
|
if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) {
|
||||||
|
ret = pciehp_isr(irq, dev_id);
|
||||||
|
enable_irq(irq);
|
||||||
|
if (ret != IRQ_WAKE_THREAD) {
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronize_hardirq(irq);
|
||||||
|
events = atomic_xchg(&ctrl->pending_events, 0);
|
||||||
|
if (!events) {
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
|
return IRQ_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check Attention Button Pressed */
|
/* Check Attention Button Pressed */
|
||||||
if (events & PCI_EXP_SLTSTA_ABP) {
|
if (events & PCI_EXP_SLTSTA_ABP) {
|
||||||
ctrl_info(ctrl, "Slot(%s): Attention button pressed\n",
|
ctrl_info(ctrl, "Slot(%s): Attention button pressed\n",
|
||||||
slot_name(slot));
|
slot_name(slot));
|
||||||
pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS);
|
pciehp_handle_button_press(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check Link Status Changed at higher precedence than Presence
|
* Disable requests have higher priority than Presence Detect Changed
|
||||||
* Detect Changed. The PDS value may be set to "card present" from
|
* or Data Link Layer State Changed events.
|
||||||
* out-of-band detection, which may be in conflict with a Link Down
|
|
||||||
* and cause the wrong event to queue.
|
|
||||||
*/
|
*/
|
||||||
if (events & PCI_EXP_SLTSTA_DLLSC) {
|
down_read(&ctrl->reset_lock);
|
||||||
ctrl_info(ctrl, "Slot(%s): Link %s\n", slot_name(slot),
|
if (events & DISABLE_SLOT)
|
||||||
link ? "Up" : "Down");
|
pciehp_handle_disable_request(slot);
|
||||||
pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP :
|
else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC))
|
||||||
INT_LINK_DOWN);
|
pciehp_handle_presence_or_link_change(slot, events);
|
||||||
} else if (events & PCI_EXP_SLTSTA_PDC) {
|
up_read(&ctrl->reset_lock);
|
||||||
present = !!(status & PCI_EXP_SLTSTA_PDS);
|
|
||||||
ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot),
|
|
||||||
present ? "" : "not ");
|
|
||||||
pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON :
|
|
||||||
INT_PRESENCE_OFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check Power Fault Detected */
|
/* Check Power Fault Detected */
|
||||||
if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) {
|
if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) {
|
||||||
ctrl->power_fault_detected = 1;
|
ctrl->power_fault_detected = 1;
|
||||||
ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot));
|
ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot));
|
||||||
pciehp_queue_interrupt_event(slot, INT_POWER_FAULT);
|
pciehp_set_attention_status(slot, 1);
|
||||||
|
pciehp_green_led_off(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pci_config_pm_runtime_put(pdev);
|
||||||
|
wake_up(&ctrl->requester);
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t pcie_isr(int irq, void *dev_id)
|
static int pciehp_poll(void *data)
|
||||||
{
|
{
|
||||||
irqreturn_t rc, handled = IRQ_NONE;
|
struct controller *ctrl = data;
|
||||||
|
|
||||||
/*
|
schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */
|
||||||
* To guarantee that all interrupt events are serviced, we need to
|
|
||||||
* re-inspect Slot Status register after clearing what is presumed
|
|
||||||
* to be the last pending interrupt.
|
|
||||||
*/
|
|
||||||
do {
|
|
||||||
rc = pciehp_isr(irq, dev_id);
|
|
||||||
if (rc == IRQ_HANDLED)
|
|
||||||
handled = IRQ_HANDLED;
|
|
||||||
} while (rc == IRQ_HANDLED);
|
|
||||||
|
|
||||||
/* Return IRQ_HANDLED if we handled one or more events */
|
while (!kthread_should_stop()) {
|
||||||
return handled;
|
/* poll for interrupt events or user requests */
|
||||||
|
while (pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD ||
|
||||||
|
atomic_read(&ctrl->pending_events))
|
||||||
|
pciehp_ist(IRQ_NOTCONNECTED, ctrl);
|
||||||
|
|
||||||
|
if (pciehp_poll_time <= 0 || pciehp_poll_time > 60)
|
||||||
|
pciehp_poll_time = 2; /* clamp to sane value */
|
||||||
|
|
||||||
|
schedule_timeout_idle(pciehp_poll_time * HZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcie_enable_notification(struct controller *ctrl)
|
static void pcie_enable_notification(struct controller *ctrl)
|
||||||
|
@ -691,17 +729,6 @@ static void pcie_enable_notification(struct controller *ctrl)
|
||||||
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd);
|
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pcie_reenable_notification(struct controller *ctrl)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Clear both Presence and Data Link Layer Changed to make sure
|
|
||||||
* those events still fire after we have re-enabled them.
|
|
||||||
*/
|
|
||||||
pcie_capability_write_word(ctrl->pcie->port, PCI_EXP_SLTSTA,
|
|
||||||
PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
|
|
||||||
pcie_enable_notification(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pcie_disable_notification(struct controller *ctrl)
|
static void pcie_disable_notification(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
u16 mask;
|
u16 mask;
|
||||||
|
@ -715,6 +742,12 @@ static void pcie_disable_notification(struct controller *ctrl)
|
||||||
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
|
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pcie_clear_hotplug_events(struct controller *ctrl)
|
||||||
|
{
|
||||||
|
pcie_capability_write_word(ctrl_dev(ctrl), PCI_EXP_SLTSTA,
|
||||||
|
PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary
|
* pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary
|
||||||
* bus reset of the bridge, but at the same time we want to ensure that it is
|
* bus reset of the bridge, but at the same time we want to ensure that it is
|
||||||
|
@ -732,6 +765,8 @@ int pciehp_reset_slot(struct slot *slot, int probe)
|
||||||
if (probe)
|
if (probe)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
down_write(&ctrl->reset_lock);
|
||||||
|
|
||||||
if (!ATTN_BUTTN(ctrl)) {
|
if (!ATTN_BUTTN(ctrl)) {
|
||||||
ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
|
ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
|
||||||
stat_mask |= PCI_EXP_SLTSTA_PDC;
|
stat_mask |= PCI_EXP_SLTSTA_PDC;
|
||||||
|
@ -742,8 +777,6 @@ int pciehp_reset_slot(struct slot *slot, int probe)
|
||||||
pcie_write_cmd(ctrl, 0, ctrl_mask);
|
pcie_write_cmd(ctrl, 0, ctrl_mask);
|
||||||
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
|
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
|
||||||
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
|
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
|
||||||
if (pciehp_poll_mode)
|
|
||||||
del_timer_sync(&ctrl->poll_timer);
|
|
||||||
|
|
||||||
pci_reset_bridge_secondary_bus(ctrl->pcie->port);
|
pci_reset_bridge_secondary_bus(ctrl->pcie->port);
|
||||||
|
|
||||||
|
@ -751,8 +784,8 @@ int pciehp_reset_slot(struct slot *slot, int probe)
|
||||||
pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask);
|
pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask);
|
||||||
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
|
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
|
||||||
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask);
|
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask);
|
||||||
if (pciehp_poll_mode)
|
|
||||||
int_poll_timeout(&ctrl->poll_timer);
|
up_write(&ctrl->reset_lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -765,7 +798,7 @@ int pcie_init_notification(struct controller *ctrl)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcie_shutdown_notification(struct controller *ctrl)
|
void pcie_shutdown_notification(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
if (ctrl->notification_enabled) {
|
if (ctrl->notification_enabled) {
|
||||||
pcie_disable_notification(ctrl);
|
pcie_disable_notification(ctrl);
|
||||||
|
@ -776,32 +809,29 @@ static void pcie_shutdown_notification(struct controller *ctrl)
|
||||||
|
|
||||||
static int pcie_init_slot(struct controller *ctrl)
|
static int pcie_init_slot(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
|
struct pci_bus *subordinate = ctrl_dev(ctrl)->subordinate;
|
||||||
struct slot *slot;
|
struct slot *slot;
|
||||||
|
|
||||||
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
|
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
|
||||||
if (!slot)
|
if (!slot)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
slot->wq = alloc_ordered_workqueue("pciehp-%u", 0, PSN(ctrl));
|
down_read(&pci_bus_sem);
|
||||||
if (!slot->wq)
|
slot->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE;
|
||||||
goto abort;
|
up_read(&pci_bus_sem);
|
||||||
|
|
||||||
slot->ctrl = ctrl;
|
slot->ctrl = ctrl;
|
||||||
mutex_init(&slot->lock);
|
mutex_init(&slot->lock);
|
||||||
mutex_init(&slot->hotplug_lock);
|
|
||||||
INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
|
INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work);
|
||||||
ctrl->slot = slot;
|
ctrl->slot = slot;
|
||||||
return 0;
|
return 0;
|
||||||
abort:
|
|
||||||
kfree(slot);
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcie_cleanup_slot(struct controller *ctrl)
|
static void pcie_cleanup_slot(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
struct slot *slot = ctrl->slot;
|
struct slot *slot = ctrl->slot;
|
||||||
cancel_delayed_work(&slot->work);
|
|
||||||
destroy_workqueue(slot->wq);
|
cancel_delayed_work_sync(&slot->work);
|
||||||
kfree(slot);
|
kfree(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,6 +856,7 @@ struct controller *pcie_init(struct pcie_device *dev)
|
||||||
{
|
{
|
||||||
struct controller *ctrl;
|
struct controller *ctrl;
|
||||||
u32 slot_cap, link_cap;
|
u32 slot_cap, link_cap;
|
||||||
|
u8 occupied, poweron;
|
||||||
struct pci_dev *pdev = dev->port;
|
struct pci_dev *pdev = dev->port;
|
||||||
|
|
||||||
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
|
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
|
||||||
|
@ -847,6 +878,8 @@ struct controller *pcie_init(struct pcie_device *dev)
|
||||||
|
|
||||||
ctrl->slot_cap = slot_cap;
|
ctrl->slot_cap = slot_cap;
|
||||||
mutex_init(&ctrl->ctrl_lock);
|
mutex_init(&ctrl->ctrl_lock);
|
||||||
|
init_rwsem(&ctrl->reset_lock);
|
||||||
|
init_waitqueue_head(&ctrl->requester);
|
||||||
init_waitqueue_head(&ctrl->queue);
|
init_waitqueue_head(&ctrl->queue);
|
||||||
dbg_ctrl(ctrl);
|
dbg_ctrl(ctrl);
|
||||||
|
|
||||||
|
@ -855,16 +888,11 @@ struct controller *pcie_init(struct pcie_device *dev)
|
||||||
if (link_cap & PCI_EXP_LNKCAP_DLLLARC)
|
if (link_cap & PCI_EXP_LNKCAP_DLLLARC)
|
||||||
ctrl->link_active_reporting = 1;
|
ctrl->link_active_reporting = 1;
|
||||||
|
|
||||||
/*
|
/* Clear all remaining event bits in Slot Status register. */
|
||||||
* Clear all remaining event bits in Slot Status register except
|
|
||||||
* Presence Detect Changed. We want to make sure possible
|
|
||||||
* hotplug event is triggered when the interrupt is unmasked so
|
|
||||||
* that we don't lose that event.
|
|
||||||
*/
|
|
||||||
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
|
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
|
||||||
PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |
|
PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD |
|
||||||
PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC |
|
PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC |
|
||||||
PCI_EXP_SLTSTA_DLLSC);
|
PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC);
|
||||||
|
|
||||||
ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c LLActRep%c%s\n",
|
ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c LLActRep%c%s\n",
|
||||||
(slot_cap & PCI_EXP_SLTCAP_PSN) >> 19,
|
(slot_cap & PCI_EXP_SLTCAP_PSN) >> 19,
|
||||||
|
@ -883,6 +911,19 @@ struct controller *pcie_init(struct pcie_device *dev)
|
||||||
if (pcie_init_slot(ctrl))
|
if (pcie_init_slot(ctrl))
|
||||||
goto abort_ctrl;
|
goto abort_ctrl;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If empty slot's power status is on, turn power off. The IRQ isn't
|
||||||
|
* requested yet, so avoid triggering a notification with this command.
|
||||||
|
*/
|
||||||
|
if (POWER_CTRL(ctrl)) {
|
||||||
|
pciehp_get_adapter_status(ctrl->slot, &occupied);
|
||||||
|
pciehp_get_power_status(ctrl->slot, &poweron);
|
||||||
|
if (!occupied && poweron) {
|
||||||
|
pcie_disable_notification(ctrl);
|
||||||
|
pciehp_power_off_slot(ctrl->slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ctrl;
|
return ctrl;
|
||||||
|
|
||||||
abort_ctrl:
|
abort_ctrl:
|
||||||
|
@ -893,7 +934,6 @@ abort:
|
||||||
|
|
||||||
void pciehp_release_ctrl(struct controller *ctrl)
|
void pciehp_release_ctrl(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
pcie_shutdown_notification(ctrl);
|
|
||||||
pcie_cleanup_slot(ctrl);
|
pcie_cleanup_slot(ctrl);
|
||||||
kfree(ctrl);
|
kfree(ctrl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,8 @@ int pciehp_configure_device(struct slot *p_slot)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pciehp_unconfigure_device(struct slot *p_slot)
|
void pciehp_unconfigure_device(struct slot *p_slot)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
|
||||||
u8 presence = 0;
|
u8 presence = 0;
|
||||||
struct pci_dev *dev, *temp;
|
struct pci_dev *dev, *temp;
|
||||||
struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate;
|
struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate;
|
||||||
|
@ -107,5 +106,4 @@ int pciehp_unconfigure_device(struct slot *p_slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
pci_unlock_rescan_remove();
|
pci_unlock_rescan_remove();
|
||||||
return rc;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,348 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0+
|
|
||||||
/*
|
|
||||||
* PCI Hot Plug Controller Skeleton Driver - 0.3
|
|
||||||
*
|
|
||||||
* Copyright (C) 2001,2003 Greg Kroah-Hartman (greg@kroah.com)
|
|
||||||
* Copyright (C) 2001,2003 IBM Corp.
|
|
||||||
*
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This driver is to be used as a skeleton driver to show how to interface
|
|
||||||
* with the pci hotplug core easily.
|
|
||||||
*
|
|
||||||
* Send feedback to <greg@kroah.com>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/moduleparam.h>
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/pci.h>
|
|
||||||
#include <linux/pci_hotplug.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
|
|
||||||
#define SLOT_NAME_SIZE 10
|
|
||||||
struct slot {
|
|
||||||
u8 number;
|
|
||||||
struct hotplug_slot *hotplug_slot;
|
|
||||||
struct list_head slot_list;
|
|
||||||
char name[SLOT_NAME_SIZE];
|
|
||||||
};
|
|
||||||
|
|
||||||
static LIST_HEAD(slot_list);
|
|
||||||
|
|
||||||
#define MY_NAME "pcihp_skeleton"
|
|
||||||
|
|
||||||
#define dbg(format, arg...) \
|
|
||||||
do { \
|
|
||||||
if (debug) \
|
|
||||||
printk(KERN_DEBUG "%s: " format "\n", \
|
|
||||||
MY_NAME, ## arg); \
|
|
||||||
} while (0)
|
|
||||||
#define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg)
|
|
||||||
#define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg)
|
|
||||||
#define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg)
|
|
||||||
|
|
||||||
/* local variables */
|
|
||||||
static bool debug;
|
|
||||||
static int num_slots;
|
|
||||||
|
|
||||||
#define DRIVER_VERSION "0.3"
|
|
||||||
#define DRIVER_AUTHOR "Greg Kroah-Hartman <greg@kroah.com>"
|
|
||||||
#define DRIVER_DESC "Hot Plug PCI Controller Skeleton Driver"
|
|
||||||
|
|
||||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
||||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
module_param(debug, bool, 0644);
|
|
||||||
MODULE_PARM_DESC(debug, "Debugging mode enabled or not");
|
|
||||||
|
|
||||||
static int enable_slot(struct hotplug_slot *slot);
|
|
||||||
static int disable_slot(struct hotplug_slot *slot);
|
|
||||||
static int set_attention_status(struct hotplug_slot *slot, u8 value);
|
|
||||||
static int hardware_test(struct hotplug_slot *slot, u32 value);
|
|
||||||
static int get_power_status(struct hotplug_slot *slot, u8 *value);
|
|
||||||
static int get_attention_status(struct hotplug_slot *slot, u8 *value);
|
|
||||||
static int get_latch_status(struct hotplug_slot *slot, u8 *value);
|
|
||||||
static int get_adapter_status(struct hotplug_slot *slot, u8 *value);
|
|
||||||
|
|
||||||
static struct hotplug_slot_ops skel_hotplug_slot_ops = {
|
|
||||||
.enable_slot = enable_slot,
|
|
||||||
.disable_slot = disable_slot,
|
|
||||||
.set_attention_status = set_attention_status,
|
|
||||||
.hardware_test = hardware_test,
|
|
||||||
.get_power_status = get_power_status,
|
|
||||||
.get_attention_status = get_attention_status,
|
|
||||||
.get_latch_status = get_latch_status,
|
|
||||||
.get_adapter_status = get_adapter_status,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int enable_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in code here to enable the specified slot
|
|
||||||
*/
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int disable_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in code here to disable the specified slot
|
|
||||||
*/
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case 0:
|
|
||||||
/*
|
|
||||||
* Fill in code here to turn light off
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
default:
|
|
||||||
/*
|
|
||||||
* Fill in code here to turn light on
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
switch (value) {
|
|
||||||
case 0:
|
|
||||||
/* Specify a test here */
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
/* Specify another test here */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in logic to get the current power status of the specific
|
|
||||||
* slot and store it in the *value location.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in logic to get the current attention status of the specific
|
|
||||||
* slot and store it in the *value location.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in logic to get the current latch status of the specific
|
|
||||||
* slot and store it in the *value location.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
int retval = 0;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in logic to get the current adapter status of the specific
|
|
||||||
* slot and store it in the *value location.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
|
|
||||||
dbg("%s - physical_slot = %s\n", __func__, hotplug_slot->name);
|
|
||||||
kfree(slot->hotplug_slot->info);
|
|
||||||
kfree(slot->hotplug_slot);
|
|
||||||
kfree(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void make_slot_name(struct slot *slot)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Stupid way to make a filename out of the slot name.
|
|
||||||
* replace this if your hardware provides a better way to name slots.
|
|
||||||
*/
|
|
||||||
snprintf(slot->hotplug_slot->name, SLOT_NAME_SIZE, "%d", slot->number);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init_slots - initialize 'struct slot' structures for each slot
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static int __init init_slots(void)
|
|
||||||
{
|
|
||||||
struct slot *slot;
|
|
||||||
struct hotplug_slot *hotplug_slot;
|
|
||||||
struct hotplug_slot_info *info;
|
|
||||||
int retval;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a structure for each slot, and register that slot
|
|
||||||
* with the pci_hotplug subsystem.
|
|
||||||
*/
|
|
||||||
for (i = 0; i < num_slots; ++i) {
|
|
||||||
slot = kzalloc(sizeof(*slot), GFP_KERNEL);
|
|
||||||
if (!slot) {
|
|
||||||
retval = -ENOMEM;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
hotplug_slot = kzalloc(sizeof(*hotplug_slot), GFP_KERNEL);
|
|
||||||
if (!hotplug_slot) {
|
|
||||||
retval = -ENOMEM;
|
|
||||||
goto error_slot;
|
|
||||||
}
|
|
||||||
slot->hotplug_slot = hotplug_slot;
|
|
||||||
|
|
||||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
|
||||||
if (!info) {
|
|
||||||
retval = -ENOMEM;
|
|
||||||
goto error_hpslot;
|
|
||||||
}
|
|
||||||
hotplug_slot->info = info;
|
|
||||||
|
|
||||||
slot->number = i;
|
|
||||||
|
|
||||||
hotplug_slot->name = slot->name;
|
|
||||||
hotplug_slot->private = slot;
|
|
||||||
hotplug_slot->release = &release_slot;
|
|
||||||
make_slot_name(slot);
|
|
||||||
hotplug_slot->ops = &skel_hotplug_slot_ops;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the slot info structure with some known
|
|
||||||
* good values.
|
|
||||||
*/
|
|
||||||
get_power_status(hotplug_slot, &info->power_status);
|
|
||||||
get_attention_status(hotplug_slot, &info->attention_status);
|
|
||||||
get_latch_status(hotplug_slot, &info->latch_status);
|
|
||||||
get_adapter_status(hotplug_slot, &info->adapter_status);
|
|
||||||
|
|
||||||
dbg("registering slot %d\n", i);
|
|
||||||
retval = pci_hp_register(slot->hotplug_slot);
|
|
||||||
if (retval) {
|
|
||||||
err("pci_hp_register failed with error %d\n", retval);
|
|
||||||
goto error_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* add slot to our internal list */
|
|
||||||
list_add(&slot->slot_list, &slot_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
error_info:
|
|
||||||
kfree(info);
|
|
||||||
error_hpslot:
|
|
||||||
kfree(hotplug_slot);
|
|
||||||
error_slot:
|
|
||||||
kfree(slot);
|
|
||||||
error:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit cleanup_slots(void)
|
|
||||||
{
|
|
||||||
struct slot *slot, *next;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Unregister all of our slots with the pci_hotplug subsystem.
|
|
||||||
* Memory will be freed in release_slot() callback after slot's
|
|
||||||
* lifespan is finished.
|
|
||||||
*/
|
|
||||||
list_for_each_entry_safe(slot, next, &slot_list, slot_list) {
|
|
||||||
list_del(&slot->slot_list);
|
|
||||||
pci_hp_deregister(slot->hotplug_slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int __init pcihp_skel_init(void)
|
|
||||||
{
|
|
||||||
int retval;
|
|
||||||
|
|
||||||
info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
|
|
||||||
/*
|
|
||||||
* Do specific initialization stuff for your driver here
|
|
||||||
* like initializing your controller hardware (if any) and
|
|
||||||
* determining the number of slots you have in the system
|
|
||||||
* right now.
|
|
||||||
*/
|
|
||||||
num_slots = 5;
|
|
||||||
|
|
||||||
return init_slots();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit pcihp_skel_exit(void)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Clean everything up.
|
|
||||||
*/
|
|
||||||
cleanup_slots();
|
|
||||||
}
|
|
||||||
|
|
||||||
module_init(pcihp_skel_init);
|
|
||||||
module_exit(pcihp_skel_exit);
|
|
|
@ -538,9 +538,8 @@ static struct hotplug_slot_ops php_slot_ops = {
|
||||||
.disable_slot = pnv_php_disable_slot,
|
.disable_slot = pnv_php_disable_slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void pnv_php_release(struct hotplug_slot *slot)
|
static void pnv_php_release(struct pnv_php_slot *php_slot)
|
||||||
{
|
{
|
||||||
struct pnv_php_slot *php_slot = slot->private;
|
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
/* Remove from global or child list */
|
/* Remove from global or child list */
|
||||||
|
@ -596,7 +595,6 @@ static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn)
|
||||||
php_slot->power_state_check = false;
|
php_slot->power_state_check = false;
|
||||||
php_slot->slot.ops = &php_slot_ops;
|
php_slot->slot.ops = &php_slot_ops;
|
||||||
php_slot->slot.info = &php_slot->slot_info;
|
php_slot->slot.info = &php_slot->slot_info;
|
||||||
php_slot->slot.release = pnv_php_release;
|
|
||||||
php_slot->slot.private = php_slot;
|
php_slot->slot.private = php_slot;
|
||||||
|
|
||||||
INIT_LIST_HEAD(&php_slot->children);
|
INIT_LIST_HEAD(&php_slot->children);
|
||||||
|
@ -924,6 +922,7 @@ static void pnv_php_unregister_one(struct device_node *dn)
|
||||||
|
|
||||||
php_slot->state = PNV_PHP_STATE_OFFLINE;
|
php_slot->state = PNV_PHP_STATE_OFFLINE;
|
||||||
pci_hp_deregister(&php_slot->slot);
|
pci_hp_deregister(&php_slot->slot);
|
||||||
|
pnv_php_release(php_slot);
|
||||||
pnv_php_put_slot(php_slot);
|
pnv_php_put_slot(php_slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -404,13 +404,13 @@ static void __exit cleanup_slots(void)
|
||||||
/*
|
/*
|
||||||
* Unregister all of our slots with the pci_hotplug subsystem,
|
* Unregister all of our slots with the pci_hotplug subsystem,
|
||||||
* and free up all memory that we had allocated.
|
* and free up all memory that we had allocated.
|
||||||
* memory will be freed in release_slot callback.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
list_for_each_entry_safe(slot, next, &rpaphp_slot_head,
|
list_for_each_entry_safe(slot, next, &rpaphp_slot_head,
|
||||||
rpaphp_slot_list) {
|
rpaphp_slot_list) {
|
||||||
list_del(&slot->rpaphp_slot_list);
|
list_del(&slot->rpaphp_slot_list);
|
||||||
pci_hp_deregister(slot->hotplug_slot);
|
pci_hp_deregister(slot->hotplug_slot);
|
||||||
|
dealloc_slot_struct(slot);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,6 @@
|
||||||
#include "rpaphp.h"
|
#include "rpaphp.h"
|
||||||
|
|
||||||
/* free up the memory used by a slot */
|
/* free up the memory used by a slot */
|
||||||
static void rpaphp_release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = (struct slot *) hotplug_slot->private;
|
|
||||||
dealloc_slot_struct(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dealloc_slot_struct(struct slot *slot)
|
void dealloc_slot_struct(struct slot *slot)
|
||||||
{
|
{
|
||||||
kfree(slot->hotplug_slot->info);
|
kfree(slot->hotplug_slot->info);
|
||||||
|
@ -56,7 +50,6 @@ struct slot *alloc_slot_struct(struct device_node *dn,
|
||||||
slot->power_domain = power_domain;
|
slot->power_domain = power_domain;
|
||||||
slot->hotplug_slot->private = slot;
|
slot->hotplug_slot->private = slot;
|
||||||
slot->hotplug_slot->ops = &rpaphp_hotplug_slot_ops;
|
slot->hotplug_slot->ops = &rpaphp_hotplug_slot_ops;
|
||||||
slot->hotplug_slot->release = &rpaphp_release_slot;
|
|
||||||
|
|
||||||
return (slot);
|
return (slot);
|
||||||
|
|
||||||
|
@ -90,10 +83,8 @@ int rpaphp_deregister_slot(struct slot *slot)
|
||||||
__func__, slot->name);
|
__func__, slot->name);
|
||||||
|
|
||||||
list_del(&slot->rpaphp_slot_list);
|
list_del(&slot->rpaphp_slot_list);
|
||||||
|
pci_hp_deregister(php_slot);
|
||||||
retval = pci_hp_deregister(php_slot);
|
dealloc_slot_struct(slot);
|
||||||
if (retval)
|
|
||||||
err("Problem unregistering a slot %s\n", slot->name);
|
|
||||||
|
|
||||||
dbg("%s - Exit: rc[%d]\n", __func__, retval);
|
dbg("%s - Exit: rc[%d]\n", __func__, retval);
|
||||||
return retval;
|
return retval;
|
||||||
|
|
|
@ -130,15 +130,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
|
|
||||||
kfree(slot->hotplug_slot->info);
|
|
||||||
kfree(slot->hotplug_slot);
|
|
||||||
kfree(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct hotplug_slot_ops s390_hotplug_slot_ops = {
|
static struct hotplug_slot_ops s390_hotplug_slot_ops = {
|
||||||
.enable_slot = enable_slot,
|
.enable_slot = enable_slot,
|
||||||
.disable_slot = disable_slot,
|
.disable_slot = disable_slot,
|
||||||
|
@ -175,7 +166,6 @@ int zpci_init_slot(struct zpci_dev *zdev)
|
||||||
hotplug_slot->info = info;
|
hotplug_slot->info = info;
|
||||||
|
|
||||||
hotplug_slot->ops = &s390_hotplug_slot_ops;
|
hotplug_slot->ops = &s390_hotplug_slot_ops;
|
||||||
hotplug_slot->release = &release_slot;
|
|
||||||
|
|
||||||
get_power_status(hotplug_slot, &info->power_status);
|
get_power_status(hotplug_slot, &info->power_status);
|
||||||
get_adapter_status(hotplug_slot, &info->adapter_status);
|
get_adapter_status(hotplug_slot, &info->adapter_status);
|
||||||
|
@ -209,5 +199,8 @@ void zpci_exit_slot(struct zpci_dev *zdev)
|
||||||
continue;
|
continue;
|
||||||
list_del(&slot->slot_list);
|
list_del(&slot->slot_list);
|
||||||
pci_hp_deregister(slot->hotplug_slot);
|
pci_hp_deregister(slot->hotplug_slot);
|
||||||
|
kfree(slot->hotplug_slot->info);
|
||||||
|
kfree(slot->hotplug_slot);
|
||||||
|
kfree(slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -628,7 +628,6 @@ static int sn_hotplug_slot_register(struct pci_bus *pci_bus)
|
||||||
goto alloc_err;
|
goto alloc_err;
|
||||||
}
|
}
|
||||||
bss_hotplug_slot->ops = &sn_hotplug_slot_ops;
|
bss_hotplug_slot->ops = &sn_hotplug_slot_ops;
|
||||||
bss_hotplug_slot->release = &sn_release_slot;
|
|
||||||
|
|
||||||
rc = pci_hp_register(bss_hotplug_slot, pci_bus, device, name);
|
rc = pci_hp_register(bss_hotplug_slot, pci_bus, device, name);
|
||||||
if (rc)
|
if (rc)
|
||||||
|
@ -656,8 +655,10 @@ alloc_err:
|
||||||
sn_release_slot(bss_hotplug_slot);
|
sn_release_slot(bss_hotplug_slot);
|
||||||
|
|
||||||
/* destroy anything else on the list */
|
/* destroy anything else on the list */
|
||||||
while ((bss_hotplug_slot = sn_hp_destroy()))
|
while ((bss_hotplug_slot = sn_hp_destroy())) {
|
||||||
pci_hp_deregister(bss_hotplug_slot);
|
pci_hp_deregister(bss_hotplug_slot);
|
||||||
|
sn_release_slot(bss_hotplug_slot);
|
||||||
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -703,8 +704,10 @@ static void __exit sn_pci_hotplug_exit(void)
|
||||||
{
|
{
|
||||||
struct hotplug_slot *bss_hotplug_slot;
|
struct hotplug_slot *bss_hotplug_slot;
|
||||||
|
|
||||||
while ((bss_hotplug_slot = sn_hp_destroy()))
|
while ((bss_hotplug_slot = sn_hp_destroy())) {
|
||||||
pci_hp_deregister(bss_hotplug_slot);
|
pci_hp_deregister(bss_hotplug_slot);
|
||||||
|
sn_release_slot(bss_hotplug_slot);
|
||||||
|
}
|
||||||
|
|
||||||
if (!list_empty(&sn_hp_list))
|
if (!list_empty(&sn_hp_list))
|
||||||
printk(KERN_ERR "%s: internal list is not empty\n", __FILE__);
|
printk(KERN_ERR "%s: internal list is not empty\n", __FILE__);
|
||||||
|
|
|
@ -61,22 +61,6 @@ static struct hotplug_slot_ops shpchp_hotplug_slot_ops = {
|
||||||
.get_adapter_status = get_adapter_status,
|
.get_adapter_status = get_adapter_status,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* release_slot - free up the memory used by a slot
|
|
||||||
* @hotplug_slot: slot to free
|
|
||||||
*/
|
|
||||||
static void release_slot(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
struct slot *slot = hotplug_slot->private;
|
|
||||||
|
|
||||||
ctrl_dbg(slot->ctrl, "%s: physical_slot = %s\n",
|
|
||||||
__func__, slot_name(slot));
|
|
||||||
|
|
||||||
kfree(slot->hotplug_slot->info);
|
|
||||||
kfree(slot->hotplug_slot);
|
|
||||||
kfree(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int init_slots(struct controller *ctrl)
|
static int init_slots(struct controller *ctrl)
|
||||||
{
|
{
|
||||||
struct slot *slot;
|
struct slot *slot;
|
||||||
|
@ -125,7 +109,6 @@ static int init_slots(struct controller *ctrl)
|
||||||
|
|
||||||
/* register this slot with the hotplug pci core */
|
/* register this slot with the hotplug pci core */
|
||||||
hotplug_slot->private = slot;
|
hotplug_slot->private = slot;
|
||||||
hotplug_slot->release = &release_slot;
|
|
||||||
snprintf(name, SLOT_NAME_SIZE, "%d", slot->number);
|
snprintf(name, SLOT_NAME_SIZE, "%d", slot->number);
|
||||||
hotplug_slot->ops = &shpchp_hotplug_slot_ops;
|
hotplug_slot->ops = &shpchp_hotplug_slot_ops;
|
||||||
|
|
||||||
|
@ -171,6 +154,9 @@ void cleanup_slots(struct controller *ctrl)
|
||||||
cancel_delayed_work(&slot->work);
|
cancel_delayed_work(&slot->work);
|
||||||
destroy_workqueue(slot->wq);
|
destroy_workqueue(slot->wq);
|
||||||
pci_hp_deregister(slot->hotplug_slot);
|
pci_hp_deregister(slot->hotplug_slot);
|
||||||
|
kfree(slot->hotplug_slot->info);
|
||||||
|
kfree(slot->hotplug_slot);
|
||||||
|
kfree(slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,11 +256,30 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool shpc_capable(struct pci_dev *bridge)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* It is assumed that AMD GOLAM chips support SHPC but they do not
|
||||||
|
* have SHPC capability.
|
||||||
|
*/
|
||||||
|
if (bridge->vendor == PCI_VENDOR_ID_AMD &&
|
||||||
|
bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (pci_find_capability(bridge, PCI_CAP_ID_SHPC))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
struct controller *ctrl;
|
struct controller *ctrl;
|
||||||
|
|
||||||
|
if (!shpc_capable(pdev))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
if (acpi_get_hp_hw_control_from_firmware(pdev))
|
if (acpi_get_hp_hw_control_from_firmware(pdev))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
|
@ -303,6 +308,7 @@ static int shpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
||||||
if (rc)
|
if (rc)
|
||||||
goto err_cleanup_slots;
|
goto err_cleanup_slots;
|
||||||
|
|
||||||
|
pdev->shpc_managed = 1;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_cleanup_slots:
|
err_cleanup_slots:
|
||||||
|
@ -319,6 +325,7 @@ static void shpc_remove(struct pci_dev *dev)
|
||||||
{
|
{
|
||||||
struct controller *ctrl = pci_get_drvdata(dev);
|
struct controller *ctrl = pci_get_drvdata(dev);
|
||||||
|
|
||||||
|
dev->shpc_managed = 0;
|
||||||
shpchp_remove_ctrl_files(ctrl);
|
shpchp_remove_ctrl_files(ctrl);
|
||||||
ctrl->hpc_ops->release_ctlr(ctrl);
|
ctrl->hpc_ops->release_ctlr(ctrl);
|
||||||
kfree(ctrl);
|
kfree(ctrl);
|
||||||
|
|
|
@ -403,24 +403,7 @@ bool pciehp_is_native(struct pci_dev *bridge)
|
||||||
*/
|
*/
|
||||||
bool shpchp_is_native(struct pci_dev *bridge)
|
bool shpchp_is_native(struct pci_dev *bridge)
|
||||||
{
|
{
|
||||||
const struct pci_host_bridge *host;
|
return bridge->shpc_managed;
|
||||||
|
|
||||||
if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_SHPC))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* It is assumed that AMD GOLAM chips support SHPC but they do not
|
|
||||||
* have SHPC capability.
|
|
||||||
*/
|
|
||||||
if (bridge->vendor == PCI_VENDOR_ID_AMD &&
|
|
||||||
bridge->device == PCI_DEVICE_ID_AMD_GOLAM_7450)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!pci_find_capability(bridge, PCI_CAP_ID_SHPC))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
host = pci_find_host_bridge(bridge->bus);
|
|
||||||
return host->native_shpc_hotplug;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1448,7 +1448,9 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
|
||||||
if (val != 1)
|
if (val != 1)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
pm_runtime_get_sync(dev);
|
||||||
result = pci_reset_function(pdev);
|
result = pci_reset_function(pdev);
|
||||||
|
pm_runtime_put(dev);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
|
|
@ -2289,7 +2289,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
|
||||||
* @bridge: Bridge to check
|
* @bridge: Bridge to check
|
||||||
*
|
*
|
||||||
* This function checks if it is possible to move the bridge to D3.
|
* This function checks if it is possible to move the bridge to D3.
|
||||||
* Currently we only allow D3 for recent enough PCIe ports.
|
* Currently we only allow D3 for recent enough PCIe ports and Thunderbolt.
|
||||||
*/
|
*/
|
||||||
bool pci_bridge_d3_possible(struct pci_dev *bridge)
|
bool pci_bridge_d3_possible(struct pci_dev *bridge)
|
||||||
{
|
{
|
||||||
|
@ -2304,18 +2304,27 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hotplug interrupts cannot be delivered if the link is down,
|
* Hotplug ports handled by firmware in System Management Mode
|
||||||
* so parents of a hotplug port must stay awake. In addition,
|
|
||||||
* hotplug ports handled by firmware in System Management Mode
|
|
||||||
* may not be put into D3 by the OS (Thunderbolt on non-Macs).
|
* may not be put into D3 by the OS (Thunderbolt on non-Macs).
|
||||||
* For simplicity, disallow in general for now.
|
|
||||||
*/
|
*/
|
||||||
if (bridge->is_hotplug_bridge)
|
if (bridge->is_hotplug_bridge && !pciehp_is_native(bridge))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (pci_bridge_d3_force)
|
if (pci_bridge_d3_force)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
/* Even the oldest 2010 Thunderbolt controller supports D3. */
|
||||||
|
if (bridge->is_thunderbolt)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hotplug ports handled natively by the OS were not validated
|
||||||
|
* by vendors for runtime D3 at least until 2018 because there
|
||||||
|
* was no OS support.
|
||||||
|
*/
|
||||||
|
if (bridge->is_hotplug_bridge)
|
||||||
|
return false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* It should be safe to put PCIe ports from 2015 or newer
|
* It should be safe to put PCIe ports from 2015 or newer
|
||||||
* to D3.
|
* to D3.
|
||||||
|
|
|
@ -50,6 +50,7 @@ struct pcie_port_service_driver {
|
||||||
int (*probe) (struct pcie_device *dev);
|
int (*probe) (struct pcie_device *dev);
|
||||||
void (*remove) (struct pcie_device *dev);
|
void (*remove) (struct pcie_device *dev);
|
||||||
int (*suspend) (struct pcie_device *dev);
|
int (*suspend) (struct pcie_device *dev);
|
||||||
|
int (*resume_noirq) (struct pcie_device *dev);
|
||||||
int (*resume) (struct pcie_device *dev);
|
int (*resume) (struct pcie_device *dev);
|
||||||
|
|
||||||
/* Device driver may resume normal operations */
|
/* Device driver may resume normal operations */
|
||||||
|
@ -82,6 +83,7 @@ extern struct bus_type pcie_port_bus_type;
|
||||||
int pcie_port_device_register(struct pci_dev *dev);
|
int pcie_port_device_register(struct pci_dev *dev);
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
int pcie_port_device_suspend(struct device *dev);
|
int pcie_port_device_suspend(struct device *dev);
|
||||||
|
int pcie_port_device_resume_noirq(struct device *dev);
|
||||||
int pcie_port_device_resume(struct device *dev);
|
int pcie_port_device_resume(struct device *dev);
|
||||||
#endif
|
#endif
|
||||||
void pcie_port_device_remove(struct pci_dev *dev);
|
void pcie_port_device_remove(struct pci_dev *dev);
|
||||||
|
|
|
@ -353,14 +353,19 @@ error_disable:
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
static int suspend_iter(struct device *dev, void *data)
|
typedef int (*pcie_pm_callback_t)(struct pcie_device *);
|
||||||
|
|
||||||
|
static int pm_iter(struct device *dev, void *data)
|
||||||
{
|
{
|
||||||
struct pcie_port_service_driver *service_driver;
|
struct pcie_port_service_driver *service_driver;
|
||||||
|
size_t offset = *(size_t *)data;
|
||||||
|
pcie_pm_callback_t cb;
|
||||||
|
|
||||||
if ((dev->bus == &pcie_port_bus_type) && dev->driver) {
|
if ((dev->bus == &pcie_port_bus_type) && dev->driver) {
|
||||||
service_driver = to_service_driver(dev->driver);
|
service_driver = to_service_driver(dev->driver);
|
||||||
if (service_driver->suspend)
|
cb = *(pcie_pm_callback_t *)((void *)service_driver + offset);
|
||||||
service_driver->suspend(to_pcie_device(dev));
|
if (cb)
|
||||||
|
return cb(to_pcie_device(dev));
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -371,20 +376,14 @@ static int suspend_iter(struct device *dev, void *data)
|
||||||
*/
|
*/
|
||||||
int pcie_port_device_suspend(struct device *dev)
|
int pcie_port_device_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
return device_for_each_child(dev, NULL, suspend_iter);
|
size_t off = offsetof(struct pcie_port_service_driver, suspend);
|
||||||
|
return device_for_each_child(dev, &off, pm_iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int resume_iter(struct device *dev, void *data)
|
int pcie_port_device_resume_noirq(struct device *dev)
|
||||||
{
|
{
|
||||||
struct pcie_port_service_driver *service_driver;
|
size_t off = offsetof(struct pcie_port_service_driver, resume_noirq);
|
||||||
|
return device_for_each_child(dev, &off, pm_iter);
|
||||||
if ((dev->bus == &pcie_port_bus_type) &&
|
|
||||||
(dev->driver)) {
|
|
||||||
service_driver = to_service_driver(dev->driver);
|
|
||||||
if (service_driver->resume)
|
|
||||||
service_driver->resume(to_pcie_device(dev));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -393,7 +392,8 @@ static int resume_iter(struct device *dev, void *data)
|
||||||
*/
|
*/
|
||||||
int pcie_port_device_resume(struct device *dev)
|
int pcie_port_device_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
return device_for_each_child(dev, NULL, resume_iter);
|
size_t off = offsetof(struct pcie_port_service_driver, resume);
|
||||||
|
return device_for_each_child(dev, &off, pm_iter);
|
||||||
}
|
}
|
||||||
#endif /* PM */
|
#endif /* PM */
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,12 @@ static int pcie_port_runtime_idle(struct device *dev)
|
||||||
|
|
||||||
static const struct dev_pm_ops pcie_portdrv_pm_ops = {
|
static const struct dev_pm_ops pcie_portdrv_pm_ops = {
|
||||||
.suspend = pcie_port_device_suspend,
|
.suspend = pcie_port_device_suspend,
|
||||||
|
.resume_noirq = pcie_port_device_resume_noirq,
|
||||||
.resume = pcie_port_device_resume,
|
.resume = pcie_port_device_resume,
|
||||||
.freeze = pcie_port_device_suspend,
|
.freeze = pcie_port_device_suspend,
|
||||||
.thaw = pcie_port_device_resume,
|
.thaw = pcie_port_device_resume,
|
||||||
.poweroff = pcie_port_device_suspend,
|
.poweroff = pcie_port_device_suspend,
|
||||||
|
.restore_noirq = pcie_port_device_resume_noirq,
|
||||||
.restore = pcie_port_device_resume,
|
.restore = pcie_port_device_resume,
|
||||||
.runtime_suspend = pcie_port_runtime_suspend,
|
.runtime_suspend = pcie_port_runtime_suspend,
|
||||||
.runtime_resume = pcie_port_runtime_resume,
|
.runtime_resume = pcie_port_runtime_resume,
|
||||||
|
|
|
@ -858,12 +858,6 @@ static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void asus_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
kfree(hotplug_slot->info);
|
|
||||||
kfree(hotplug_slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct hotplug_slot_ops asus_hotplug_slot_ops = {
|
static struct hotplug_slot_ops asus_hotplug_slot_ops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.get_adapter_status = asus_get_adapter_status,
|
.get_adapter_status = asus_get_adapter_status,
|
||||||
|
@ -905,7 +899,6 @@ static int asus_setup_pci_hotplug(struct asus_wmi *asus)
|
||||||
goto error_info;
|
goto error_info;
|
||||||
|
|
||||||
asus->hotplug_slot->private = asus;
|
asus->hotplug_slot->private = asus;
|
||||||
asus->hotplug_slot->release = &asus_cleanup_pci_hotplug;
|
|
||||||
asus->hotplug_slot->ops = &asus_hotplug_slot_ops;
|
asus->hotplug_slot->ops = &asus_hotplug_slot_ops;
|
||||||
asus_get_adapter_status(asus->hotplug_slot,
|
asus_get_adapter_status(asus->hotplug_slot,
|
||||||
&asus->hotplug_slot->info->adapter_status);
|
&asus->hotplug_slot->info->adapter_status);
|
||||||
|
@ -1051,8 +1044,11 @@ static void asus_wmi_rfkill_exit(struct asus_wmi *asus)
|
||||||
* asus_unregister_rfkill_notifier()
|
* asus_unregister_rfkill_notifier()
|
||||||
*/
|
*/
|
||||||
asus_rfkill_hotplug(asus);
|
asus_rfkill_hotplug(asus);
|
||||||
if (asus->hotplug_slot)
|
if (asus->hotplug_slot) {
|
||||||
pci_hp_deregister(asus->hotplug_slot);
|
pci_hp_deregister(asus->hotplug_slot);
|
||||||
|
kfree(asus->hotplug_slot->info);
|
||||||
|
kfree(asus->hotplug_slot);
|
||||||
|
}
|
||||||
if (asus->hotplug_workqueue)
|
if (asus->hotplug_workqueue)
|
||||||
destroy_workqueue(asus->hotplug_workqueue);
|
destroy_workqueue(asus->hotplug_workqueue);
|
||||||
|
|
||||||
|
|
|
@ -726,12 +726,6 @@ static int eeepc_get_adapter_status(struct hotplug_slot *hotplug_slot,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void eeepc_cleanup_pci_hotplug(struct hotplug_slot *hotplug_slot)
|
|
||||||
{
|
|
||||||
kfree(hotplug_slot->info);
|
|
||||||
kfree(hotplug_slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
|
static struct hotplug_slot_ops eeepc_hotplug_slot_ops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.get_adapter_status = eeepc_get_adapter_status,
|
.get_adapter_status = eeepc_get_adapter_status,
|
||||||
|
@ -758,7 +752,6 @@ static int eeepc_setup_pci_hotplug(struct eeepc_laptop *eeepc)
|
||||||
goto error_info;
|
goto error_info;
|
||||||
|
|
||||||
eeepc->hotplug_slot->private = eeepc;
|
eeepc->hotplug_slot->private = eeepc;
|
||||||
eeepc->hotplug_slot->release = &eeepc_cleanup_pci_hotplug;
|
|
||||||
eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
|
eeepc->hotplug_slot->ops = &eeepc_hotplug_slot_ops;
|
||||||
eeepc_get_adapter_status(eeepc->hotplug_slot,
|
eeepc_get_adapter_status(eeepc->hotplug_slot,
|
||||||
&eeepc->hotplug_slot->info->adapter_status);
|
&eeepc->hotplug_slot->info->adapter_status);
|
||||||
|
@ -837,8 +830,11 @@ static void eeepc_rfkill_exit(struct eeepc_laptop *eeepc)
|
||||||
eeepc->wlan_rfkill = NULL;
|
eeepc->wlan_rfkill = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eeepc->hotplug_slot)
|
if (eeepc->hotplug_slot) {
|
||||||
pci_hp_deregister(eeepc->hotplug_slot);
|
pci_hp_deregister(eeepc->hotplug_slot);
|
||||||
|
kfree(eeepc->hotplug_slot->info);
|
||||||
|
kfree(eeepc->hotplug_slot);
|
||||||
|
}
|
||||||
|
|
||||||
if (eeepc->bluetooth_rfkill) {
|
if (eeepc->bluetooth_rfkill) {
|
||||||
rfkill_unregister(eeepc->bluetooth_rfkill);
|
rfkill_unregister(eeepc->bluetooth_rfkill);
|
||||||
|
|
|
@ -388,6 +388,7 @@ struct pci_dev {
|
||||||
unsigned int is_virtfn:1;
|
unsigned int is_virtfn:1;
|
||||||
unsigned int reset_fn:1;
|
unsigned int reset_fn:1;
|
||||||
unsigned int is_hotplug_bridge:1;
|
unsigned int is_hotplug_bridge:1;
|
||||||
|
unsigned int shpc_managed:1; /* SHPC owned by shpchp */
|
||||||
unsigned int is_thunderbolt:1; /* Thunderbolt controller */
|
unsigned int is_thunderbolt:1; /* Thunderbolt controller */
|
||||||
unsigned int __aer_firmware_first_valid:1;
|
unsigned int __aer_firmware_first_valid:1;
|
||||||
unsigned int __aer_firmware_first:1;
|
unsigned int __aer_firmware_first:1;
|
||||||
|
|
|
@ -80,15 +80,12 @@ struct hotplug_slot_info {
|
||||||
* @ops: pointer to the &struct hotplug_slot_ops to be used for this slot
|
* @ops: pointer to the &struct hotplug_slot_ops to be used for this slot
|
||||||
* @info: pointer to the &struct hotplug_slot_info for the initial values for
|
* @info: pointer to the &struct hotplug_slot_info for the initial values for
|
||||||
* this slot.
|
* this slot.
|
||||||
* @release: called during pci_hp_deregister to free memory allocated in a
|
|
||||||
* hotplug_slot structure.
|
|
||||||
* @private: used by the hotplug pci controller driver to store whatever it
|
* @private: used by the hotplug pci controller driver to store whatever it
|
||||||
* needs.
|
* needs.
|
||||||
*/
|
*/
|
||||||
struct hotplug_slot {
|
struct hotplug_slot {
|
||||||
struct hotplug_slot_ops *ops;
|
struct hotplug_slot_ops *ops;
|
||||||
struct hotplug_slot_info *info;
|
struct hotplug_slot_info *info;
|
||||||
void (*release) (struct hotplug_slot *slot);
|
|
||||||
void *private;
|
void *private;
|
||||||
|
|
||||||
/* Variables below this are for use only by the hotplug pci core. */
|
/* Variables below this are for use only by the hotplug pci core. */
|
||||||
|
@ -104,13 +101,23 @@ static inline const char *hotplug_slot_name(const struct hotplug_slot *slot)
|
||||||
int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *pbus, int nr,
|
int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *pbus, int nr,
|
||||||
const char *name, struct module *owner,
|
const char *name, struct module *owner,
|
||||||
const char *mod_name);
|
const char *mod_name);
|
||||||
int pci_hp_deregister(struct hotplug_slot *slot);
|
int __pci_hp_initialize(struct hotplug_slot *slot, struct pci_bus *bus, int nr,
|
||||||
|
const char *name, struct module *owner,
|
||||||
|
const char *mod_name);
|
||||||
|
int pci_hp_add(struct hotplug_slot *slot);
|
||||||
|
|
||||||
|
void pci_hp_del(struct hotplug_slot *slot);
|
||||||
|
void pci_hp_destroy(struct hotplug_slot *slot);
|
||||||
|
void pci_hp_deregister(struct hotplug_slot *slot);
|
||||||
|
|
||||||
int __must_check pci_hp_change_slot_info(struct hotplug_slot *slot,
|
int __must_check pci_hp_change_slot_info(struct hotplug_slot *slot,
|
||||||
struct hotplug_slot_info *info);
|
struct hotplug_slot_info *info);
|
||||||
|
|
||||||
/* use a define to avoid include chaining to get THIS_MODULE & friends */
|
/* use a define to avoid include chaining to get THIS_MODULE & friends */
|
||||||
#define pci_hp_register(slot, pbus, devnr, name) \
|
#define pci_hp_register(slot, pbus, devnr, name) \
|
||||||
__pci_hp_register(slot, pbus, devnr, name, THIS_MODULE, KBUILD_MODNAME)
|
__pci_hp_register(slot, pbus, devnr, name, THIS_MODULE, KBUILD_MODNAME)
|
||||||
|
#define pci_hp_initialize(slot, bus, nr, name) \
|
||||||
|
__pci_hp_initialize(slot, bus, nr, name, THIS_MODULE, KBUILD_MODNAME)
|
||||||
|
|
||||||
/* PCI Setting Record (Type 0) */
|
/* PCI Setting Record (Type 0) */
|
||||||
struct hpp_type0 {
|
struct hpp_type0 {
|
||||||
|
|
Loading…
Reference in New Issue