]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/pci/hotplug/fakephp.c
PCI: introduce pci_slot
[linux-2.6-omap-h63xx.git] / drivers / pci / hotplug / fakephp.c
index d7a293e3faf5debc9fb8d3df0b571bde3e663c33..40337a06c18ab7defa6160af363df122143545a9 100644 (file)
@@ -39,6 +39,7 @@
 #include <linux/init.h>
 #include <linux/string.h>
 #include <linux/slab.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,18 @@ 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));
        if (retval) {
                err("pci_hp_register failed with error %d\n", retval);
                goto error_dslot;
@@ -141,17 +150,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,6 +173,14 @@ 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);
+}
+
 /**
  * pci_rescan_slot - Rescan slot
  * @temp: Device template. Should be set: bus and devfn.
@@ -267,31 +284,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;
 
@@ -299,38 +308,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_get_slot(dslot->dev->bus,
-                                       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;
-                               }
-                               pci_dev_put(dev);
-                       } 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);
+               /* 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);
 
-       /* blow away this sysfs entry and other parts. */
-       remove_slot(dslot);
+               /* blow away this sysfs entry and other parts. */
+               remove_slot(dslot);
 
+               pci_dev_put(dev);
+       }
        return 0;
 }
 
@@ -340,6 +340,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);
@@ -351,6 +352,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();
 }