Skip to content

Commit

Permalink
PCI: hotplug: Demidlayer registration with the core
Browse files Browse the repository at this point in the history
When a hotplug driver calls pci_hp_register(), all steps necessary for
registration are carried out in one go, including creation of a kobject
and addition to sysfs.  That's a problem for pciehp once it's converted
to enable/disable the slot exclusively from the IRQ thread:  The thread
needs to be spawned after creation of the kobject (because it uses the
kobject's name), but before addition to sysfs (because it will handle
enable/disable requests submitted via sysfs).

pci_hp_deregister() does offer a ->release callback that's invoked
after deletion from sysfs and before destruction of the kobject.  But
because pci_hp_register() doesn't offer a counterpart, hotplug drivers'
->probe and ->remove code becomes asymmetric, which is error prone
as recently discovered use-after-free bugs in pciehp's ->remove hook
have shown.

In a sense, this appears to be a case of the midlayer antipattern:

   "The core thesis of the "midlayer mistake" is that midlayers are
    bad and should not exist.  That common functionality which it is
    so tempting to put in a midlayer should instead be provided as
    library routines which can [be] used, augmented, or ignored by
    each bottom level driver independently.  Thus every subsystem
    that supports multiple implementations (or drivers) should
    provide a very thin top layer which calls directly into the
    bottom layer drivers, and a rich library of support code that
    eases the implementation of those drivers.  This library is
    available to, but not forced upon, those drivers."
        --  Neil Brown (2009), https://lwn.net/Articles/336262/

The presence of midlayer traits in the PCI hotplug core might be ascribed
to its age:  When it was introduced in February 2002, the blessings of a
library approach might not have been well known:
https://git.kernel.org/tglx/history/c/a8a2069f432c

For comparison, the driver core does offer split functions for creating
a kobject (device_initialize()) and addition to sysfs (device_add()) as
an alternative to carrying out everything at once (device_register()).
This was introduced in October 2002:
https://git.kernel.org/tglx/history/c/8b290eb19962

The odd ->release callback in the PCI hotplug core was added in 2003:
https://git.kernel.org/tglx/history/c/69f8d663b595

Clearly, a library approach would not force every hotplug driver to
implement a ->release callback, but rather allow the driver to remove
the sysfs files, release its data structures and finally destroy the
kobject.  Alternatively, a driver may choose to remove everything with
pci_hp_deregister(), then release its data structures.

To this end, offer drivers pci_hp_initialize() and pci_hp_add() as a
split-up version of pci_hp_register().  Likewise, offer pci_hp_del()
and pci_hp_destroy() as a split-up version of pci_hp_deregister().

Eliminate the ->release callback and move its code into each driver's
teardown routine.

Declare pci_hp_deregister() void, in keeping with the usual kernel
pattern that enablement can fail, but disablement cannot.  It only
returned an error if the caller passed in a NULL pointer or a slot which
has never or is no longer registered or is sharing its name with another
slot.  Those would be bugs, so WARN about them.  Few hotplug drivers
actually checked the return value and those that did only printed a
useless error message to dmesg.  Remove that.

For most drivers the conversion was straightforward since it doesn't
matter whether the code in the ->release callback is executed before or
after destruction of the kobject.  But in the case of ibmphp, it was
unclear to me whether setting slot_cur->ctrl and slot_cur->bus_on to
NULL needs to happen before the kobject is destroyed, so I erred on
the side of caution and ensured that the order stays the same.  Another
nontrivial case is pnv_php, I've found the list and kref logic difficult
to understand, however my impression was that it is safe to delete the
list element and drop the references until after the kobject is
destroyed.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Acked-by: Andy Shevchenko <andy.shevchenko@gmail.com>  # drivers/platform/x86
Cc: Rafael J. Wysocki <rjw@rjwysocki.net>
Cc: Len Brown <lenb@kernel.org>
Cc: Scott Murray <scott@spiteful.org>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Gavin Shan <gwshan@linux.vnet.ibm.com>
Cc: Sebastian Ott <sebott@linux.vnet.ibm.com>
Cc: Gerald Schaefer <gerald.schaefer@de.ibm.com>
Cc: Corentin Chary <corentin.chary@gmail.com>
Cc: Darren Hart <dvhart@infradead.org>
Cc: Andy Shevchenko <andy@infradead.org>
  • Loading branch information
Lukas Wunner authored and Bjorn Helgaas committed Jul 23, 2018
1 parent 55a6b7a commit 51bbf9b
Show file tree
Hide file tree
Showing 16 changed files with 168 additions and 178 deletions.
22 changes: 3 additions & 19 deletions drivers/pci/hotplug/acpiphp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,20 +254,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
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 */
int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot,
unsigned int sun)
Expand All @@ -287,7 +273,6 @@ int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot,
slot->hotplug_slot->info = &slot->info;

slot->hotplug_slot->private = slot;
slot->hotplug_slot->release = &release_slot;
slot->hotplug_slot->ops = &acpi_hotplug_slot_ops;

slot->acpi_slot = acpiphp_slot;
Expand Down Expand Up @@ -324,13 +309,12 @@ int acpiphp_register_hotplug_slot(struct acpiphp_slot *acpiphp_slot,
void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *acpiphp_slot)
{
struct slot *slot = acpiphp_slot->slot;
int retval = 0;

pr_info("Slot [%s] unregistered\n", slot_name(slot));

retval = pci_hp_deregister(slot->hotplug_slot);
if (retval)
pr_err("pci_hp_deregister failed with error %d\n", retval);
pci_hp_deregister(slot->hotplug_slot);
kfree(slot->hotplug_slot);
kfree(slot);
}


Expand Down
14 changes: 4 additions & 10 deletions drivers/pci/hotplug/cpci_hotplug_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,8 @@ get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value)
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);
pci_dev_put(slot->dev);
Expand Down Expand Up @@ -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);

hotplug_slot->private = slot;
hotplug_slot->release = &release_slot;
hotplug_slot->ops = &cpci_hotplug_slot_ops;

/*
Expand Down Expand Up @@ -308,12 +305,8 @@ cpci_hp_unregister_bus(struct pci_bus *bus)
slots--;

dbg("deregistering slot %s", slot_name(slot));
status = pci_hp_deregister(slot->hotplug_slot);
if (status) {
err("pci_hp_deregister failed with error %d",
status);
break;
}
pci_hp_deregister(slot->hotplug_slot);
release_slot(slot);
}
}
up_write(&list_rwsem);
Expand Down Expand Up @@ -623,6 +616,7 @@ cleanup_slots(void)
list_for_each_entry_safe(slot, tmp, &slot_list, slot_list) {
list_del(&slot->slot_list);
pci_hp_deregister(slot->hotplug_slot);
release_slot(slot);
}
cleanup_null:
up_write(&list_rwsem);
Expand Down
16 changes: 3 additions & 13 deletions drivers/pci/hotplug/cpqphp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,6 @@ static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start,
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)
{
struct slot *old_slot, *next_slot;
Expand All @@ -285,9 +274,11 @@ static int ctrl_slot_cleanup(struct controller *ctrl)
ctrl->slot = NULL;

while (old_slot) {
/* memory will be freed by the release_slot callback */
next_slot = old_slot->next;
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;
}

Expand Down Expand Up @@ -678,7 +669,6 @@ static int ctrl_slot_setup(struct controller *ctrl,
((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04;

/* register this slot with the hotplug pci core */
hotplug_slot->release = &release_slot;
hotplug_slot->private = slot;
snprintf(name, SLOT_NAME_SIZE, "%u", slot->number);
hotplug_slot->ops = &cpqphp_hotplug_slot_ops;
Expand Down
15 changes: 14 additions & 1 deletion drivers/pci/hotplug/ibmphp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,20 @@ static void free_slots(void)

list_for_each_entry_safe(slot_cur, next, &ibmphp_slot_head,
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__);
}
Expand Down
20 changes: 0 additions & 20 deletions drivers/pci/hotplug/ibmphp_ebda.c
Original file line number Diff line number Diff line change
Expand Up @@ -699,25 +699,6 @@ static int fillslotinfo(struct hotplug_slot *hotplug_slot)
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;

/*
Expand Down Expand Up @@ -941,7 +922,6 @@ static int __init ebda_rsrc_controller(void)
tmp_slot->hotplug_slot = hp_slot_ptr;

hp_slot_ptr->private = tmp_slot;
hp_slot_ptr->release = release_slot;

rc = fillslotinfo(hp_slot_ptr);
if (rc)
Expand Down
139 changes: 102 additions & 37 deletions drivers/pci/hotplug/pci_hotplug_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,9 @@ static struct hotplug_slot *get_slot_from_name(const char *name)
* @owner: caller module owner
* @mod_name: caller module name
*
* Registers a hotplug slot with the pci hotplug subsystem, which will allow
* userspace interaction to the slot.
* Prepares a hotplug slot for in-kernel use and immediately publishes it to
* 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.
*/
Expand All @@ -406,54 +407,91 @@ int __pci_hp_register(struct hotplug_slot *slot, struct pci_bus *bus,
struct module *owner, const char *mod_name)
{
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;

if (slot == NULL)
return -ENODEV;
if ((slot->info == NULL) || (slot->ops == NULL))
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->mod_name = mod_name;

mutex_lock(&pci_hp_mutex);
/*
* No problems if we call this interface from both ACPI_PCI_SLOT
* driver and call it here again. If we've already created the
* pci_slot, the interface will simply bump the refcount.
*/
pci_slot = pci_create_slot(bus, devnr, name, slot);
if (IS_ERR(pci_slot)) {
result = PTR_ERR(pci_slot);
goto out;
}
if (IS_ERR(pci_slot))
return PTR_ERR(pci_slot);

slot->pci_slot = pci_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);
if (result)
goto err_list_del;
return result;

kobject_uevent(&pci_slot->kobj, KOBJ_ADD);
dbg("Added slot %s to the list\n", name);
goto out;

err_list_del:
list_del(&slot->slot_list);
pci_slot->hotplug = NULL;
pci_destroy_slot(pci_slot);
out:
mutex_lock(&pci_hp_mutex);
list_add(&slot->slot_list, &pci_hotplug_slot_list);
mutex_unlock(&pci_hp_mutex);
return result;
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
Expand All @@ -464,35 +502,62 @@ EXPORT_SYMBOL_GPL(__pci_hp_register);
*
* 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 pci_slot *pci_slot;

if (!slot)
return -ENODEV;
if (WARN_ON(!slot))
return;

mutex_lock(&pci_hp_mutex);
temp = get_slot_from_name(hotplug_slot_name(slot));
if (temp != slot) {
if (WARN_ON(temp != slot)) {
mutex_unlock(&pci_hp_mutex);
return -ENODEV;
return;
}

list_del(&slot->slot_list);

pci_slot = slot->pci_slot;
fs_remove_slot(pci_slot);
mutex_unlock(&pci_hp_mutex);
dbg("Removed slot %s from the list\n", hotplug_slot_name(slot));
fs_remove_slot(slot->pci_slot);
}
EXPORT_SYMBOL_GPL(pci_hp_del);

/**
* 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->release(slot);
slot->pci_slot = NULL;
pci_slot->hotplug = NULL;
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
Expand Down
Loading

0 comments on commit 51bbf9b

Please sign in to comment.