PCI Hotplug core: add 'name' param pci_hp_register interface
[safe/jmp/linux-2.6] / drivers / pci / hotplug / fakephp.c
index 05a4f0f..3069f21 100644 (file)
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/pci.h>
+#include <linux/pci_hotplug.h>
 #include <linux/init.h>
 #include <linux/string.h>
 #include <linux/slab.h>
-#include "pci_hotplug.h"
+#include <linux/workqueue.h>
 #include "../pci.h"
 
 #if !defined(MODULE)
@@ -63,10 +64,17 @@ struct dummy_slot {
        struct list_head node;
        struct hotplug_slot *slot;
        struct pci_dev *dev;
+       struct work_struct remove_work;
+       unsigned long removed;
+       char name[8];
 };
 
 static int debug;
 static LIST_HEAD(slot_list);
+static struct workqueue_struct *dummyphp_wq;
+
+static void pci_rescan_worker(struct work_struct *work);
+static DECLARE_WORK(pci_rescan_work, pci_rescan_worker);
 
 static int enable_slot (struct hotplug_slot *slot);
 static int disable_slot (struct hotplug_slot *slot);
@@ -93,6 +101,7 @@ static int add_slot(struct pci_dev *dev)
        struct dummy_slot *dslot;
        struct hotplug_slot *slot;
        int retval = -ENOMEM;
+       static int count = 1;
 
        slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
        if (!slot)
@@ -106,18 +115,19 @@ static int add_slot(struct pci_dev *dev)
        slot->info->max_bus_speed = PCI_SPEED_UNKNOWN;
        slot->info->cur_bus_speed = PCI_SPEED_UNKNOWN;
 
-       slot->name = &dev->dev.bus_id[0];
-       dbg("slot->name = %s\n", slot->name);
-
-       dslot = kmalloc(sizeof(struct dummy_slot), GFP_KERNEL);
+       dslot = kzalloc(sizeof(struct dummy_slot), GFP_KERNEL);
        if (!dslot)
                goto error_info;
 
+       slot->name = dslot->name;
+       snprintf(slot->name, sizeof(dslot->name), "fake%d", count++);
+       dbg("slot->name = %s\n", slot->name);
        slot->ops = &dummy_hotplug_slot_ops;
        slot->release = &dummy_release;
        slot->private = dslot;
 
-       retval = pci_hp_register(slot);
+       retval = pci_hp_register(slot, dev->bus, PCI_SLOT(dev->devfn),
+                                slot->name);
        if (retval) {
                err("pci_hp_register failed with error %d\n", retval);
                goto error_dslot;
@@ -141,17 +151,17 @@ error:
 static int __init pci_scan_buses(void)
 {
        struct pci_dev *dev = NULL;
-       int retval = 0;
+       int lastslot = 0;
 
        while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
-               retval = add_slot(dev);
-               if (retval) {
-                       pci_dev_put(dev);
-                       break;
-               }
+               if (PCI_FUNC(dev->devfn) > 0 &&
+                               lastslot == PCI_SLOT(dev->devfn))
+                       continue;
+               lastslot = PCI_SLOT(dev->devfn);
+               add_slot(dev);
        }
 
-       return retval;
+       return 0;
 }
 
 static void remove_slot(struct dummy_slot *dslot)
@@ -164,12 +174,20 @@ static void remove_slot(struct dummy_slot *dslot)
                err("Problem unregistering a slot %s\n", dslot->slot->name);
 }
 
+/* called from the single-threaded workqueue handler to remove a slot */
+static void remove_slot_worker(struct work_struct *work)
+{
+       struct dummy_slot *dslot =
+               container_of(work, struct dummy_slot, remove_work);
+       remove_slot(dslot);
+}
+
 /**
- * Rescan slot.
- * Tries hard not to re-enable already existing devices
- * also handles scanning of subfunctions
+ * pci_rescan_slot - Rescan slot
+ * @temp: Device template. Should be set: bus and devfn.
  *
- * @param temp   Device template. Should be set: bus and devfn.
+ * Tries hard not to re-enable already existing devices;
+ * also handles scanning of subfunctions.
  */
 static void pci_rescan_slot(struct pci_dev *temp)
 {
@@ -181,7 +199,9 @@ static void pci_rescan_slot(struct pci_dev *temp)
 
        if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) {
                temp->hdr_type = hdr_type & 0x7f;
-               if (!pci_find_slot(bus->number, temp->devfn)) {
+               if ((dev = pci_get_slot(bus, temp->devfn)) != NULL)
+                       pci_dev_put(dev);
+               else {
                        dev = pci_scan_single_device(bus, temp->devfn);
                        if (dev) {
                                dbg("New device on %s function %x:%x\n",
@@ -205,7 +225,9 @@ static void pci_rescan_slot(struct pci_dev *temp)
                                continue;
                        temp->hdr_type = hdr_type & 0x7f;
 
-                       if (!pci_find_slot(bus->number, temp->devfn)) {
+                       if ((dev = pci_get_slot(bus, temp->devfn)) != NULL)
+                               pci_dev_put(dev);
+                       else {
                                dev = pci_scan_single_device(bus, temp->devfn);
                                if (dev) {
                                        dbg("New device on %s function %x:%x\n",
@@ -225,16 +247,16 @@ static void pci_rescan_slot(struct pci_dev *temp)
 
 
 /**
- * Rescan PCI bus.
- * call pci_rescan_slot for each possible function of the bus
+ * pci_rescan_bus - Rescan PCI bus
+ * @bus: the PCI bus to rescan
  *
- * @param bus
+ * Call pci_rescan_slot for each possible function of the bus.
  */
 static void pci_rescan_bus(const struct pci_bus *bus)
 {
        unsigned int devfn;
        struct pci_dev *dev;
-       dev = kzalloc(sizeof(struct pci_dev), GFP_KERNEL);
+       dev = alloc_pci_dev();
        if (!dev)
                return;
 
@@ -263,31 +285,23 @@ static inline void pci_rescan(void) {
        pci_rescan_buses(&pci_root_buses);
 }
 
-
-static int enable_slot(struct hotplug_slot *hotplug_slot)
+/* called from the single-threaded workqueue handler to rescan all pci buses */
+static void pci_rescan_worker(struct work_struct *work)
 {
-       /* mis-use enable_slot for rescanning of the pci bus */
        pci_rescan();
-       return -ENODEV;
 }
 
-/* find the hotplug_slot for the pci_dev */
-static struct hotplug_slot *get_slot_from_dev(struct pci_dev *dev)
+static int enable_slot(struct hotplug_slot *hotplug_slot)
 {
-       struct dummy_slot *dslot;
-
-       list_for_each_entry(dslot, &slot_list, node) {
-               if (dslot->dev == dev)
-                       return dslot->slot;
-       }
-       return NULL;
+       /* mis-use enable_slot for rescanning of the pci bus */
+       cancel_work_sync(&pci_rescan_work);
+       queue_work(dummyphp_wq, &pci_rescan_work);
+       return 0;
 }
 
-
 static int disable_slot(struct hotplug_slot *slot)
 {
        struct dummy_slot *dslot;
-       struct hotplug_slot *hslot;
        struct pci_dev *dev;
        int func;
 
@@ -295,37 +309,29 @@ static int disable_slot(struct hotplug_slot *slot)
                return -ENODEV;
        dslot = slot->private;
 
-       dbg("%s - physical_slot = %s\n", __FUNCTION__, slot->name);
+       dbg("%s - physical_slot = %s\n", __func__, slot->name);
 
-       /* don't disable bridged devices just yet, we can't handle them easily... */
-       if (dslot->dev->subordinate) {
-               err("Can't remove PCI devices with other PCI devices behind it yet.\n");
-               return -ENODEV;
-       }
-       /* search for subfunctions and disable them first */
-       if (!(dslot->dev->devfn & 7)) {
-               for (func = 1; func < 8; func++) {
-                       dev = pci_find_slot(dslot->dev->bus->number,
-                                       dslot->dev->devfn + func);
-                       if (dev) {
-                               hslot = get_slot_from_dev(dev);
-                               if (hslot)
-                                       disable_slot(hslot);
-                               else {
-                                       err("Hotplug slot not found for subfunction of PCI device\n");
-                                       return -ENODEV;
-                               }
-                       } else
-                               dbg("No device in slot found\n");
+       for (func = 7; func >= 0; func--) {
+               dev = pci_get_slot(dslot->dev->bus, dslot->dev->devfn + func);
+               if (!dev)
+                       continue;
+
+               if (test_and_set_bit(0, &dslot->removed)) {
+                       dbg("Slot already scheduled for removal\n");
+                       return -ENODEV;
                }
-       }
 
-       /* remove the device from the pci core */
-       pci_remove_bus_device(dslot->dev);
+               /* remove the device from the pci core */
+               pci_remove_bus_device(dev);
 
-       /* blow away this sysfs entry and other parts. */
-       remove_slot(dslot);
+               /* queue work item to blow away this sysfs entry and other
+                * parts.
+                */
+               INIT_WORK(&dslot->remove_work, remove_slot_worker);
+               queue_work(dummyphp_wq, &dslot->remove_work);
 
+               pci_dev_put(dev);
+       }
        return 0;
 }
 
@@ -335,6 +341,7 @@ static void cleanup_slots (void)
        struct list_head *next;
        struct dummy_slot *dslot;
 
+       destroy_workqueue(dummyphp_wq);
        list_for_each_safe (tmp, next, &slot_list) {
                dslot = list_entry (tmp, struct dummy_slot, node);
                remove_slot(dslot);
@@ -346,6 +353,10 @@ static int __init dummyphp_init(void)
 {
        info(DRIVER_DESC "\n");
 
+       dummyphp_wq = create_singlethread_workqueue(MY_NAME);
+       if (!dummyphp_wq)
+               return -ENOMEM;
+
        return pci_scan_buses();
 }