fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / pci / hotplug / fakephp.c
index 8453795..e27907c 100644 (file)
  * Enabling PCI devices is left as an exercise for the reader...
  *
  */
-#include <linux/config.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/pci.h>
+#include <linux/pci_hotplug.h>
 #include <linux/init.h>
-#include "pci_hotplug.h"
+#include <linux/string.h>
+#include <linux/slab.h>
 #include "../pci.h"
 
-#if !defined(CONFIG_HOTPLUG_PCI_FAKE_MODULE)
+#if !defined(MODULE)
        #define MY_NAME "fakephp"
 #else
        #define MY_NAME THIS_MODULE->name
@@ -93,15 +94,13 @@ static int add_slot(struct pci_dev *dev)
        struct hotplug_slot *slot;
        int retval = -ENOMEM;
 
-       slot = kmalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
+       slot = kzalloc(sizeof(struct hotplug_slot), GFP_KERNEL);
        if (!slot)
                goto error;
-       memset(slot, 0, sizeof(*slot));
 
-       slot->info = kmalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL);
+       slot->info = kzalloc(sizeof(struct hotplug_slot_info), GFP_KERNEL);
        if (!slot->info)
                goto error_slot;
-       memset(slot->info, 0, sizeof(struct hotplug_slot_info));
 
        slot->info->power_status = 1;
        slot->info->max_bus_speed = PCI_SPEED_UNKNOWN;
@@ -165,14 +164,136 @@ static void remove_slot(struct dummy_slot *dslot)
                err("Problem unregistering a slot %s\n", dslot->slot->name);
 }
 
+/**
+ * Rescan slot.
+ * Tries hard not to re-enable already existing devices
+ * also handles scanning of subfunctions
+ *
+ * @param temp   Device template. Should be set: bus and devfn.
+ */
+static void pci_rescan_slot(struct pci_dev *temp)
+{
+       struct pci_bus *bus = temp->bus;
+       struct pci_dev *dev;
+       int func;
+       int retval;
+       u8 hdr_type;
+
+       if (!pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type)) {
+               temp->hdr_type = hdr_type & 0x7f;
+               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",
+                                       bus->name, temp->devfn >> 3,
+                                       temp->devfn & 7);
+                               retval = pci_bus_add_device(dev);
+                               if (retval)
+                                       dev_err(&dev->dev, "error adding "
+                                               "device, continuing.\n");
+                               else
+                                       add_slot(dev);
+                       }
+               }
+               /* multifunction device? */
+               if (!(hdr_type & 0x80))
+                       return;
+
+               /* continue scanning for other functions */
+               for (func = 1, temp->devfn++; func < 8; func++, temp->devfn++) {
+                       if (pci_read_config_byte(temp, PCI_HEADER_TYPE, &hdr_type))
+                               continue;
+                       temp->hdr_type = hdr_type & 0x7f;
+
+                       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",
+                                               bus->name, temp->devfn >> 3,
+                                               temp->devfn & 7);
+                                       retval = pci_bus_add_device(dev);
+                                       if (retval)
+                                               dev_err(&dev->dev, "error adding "
+                                                       "device, continuing.\n");
+                                       else
+                                               add_slot(dev);
+                               }
+                       }
+               }
+       }
+}
+
+
+/**
+ * Rescan PCI bus.
+ * call pci_rescan_slot for each possible function of the bus
+ *
+ * @param 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);
+       if (!dev)
+               return;
+
+       dev->bus = (struct pci_bus*)bus;
+       dev->sysdata = bus->sysdata;
+       for (devfn = 0; devfn < 0x100; devfn += 8) {
+               dev->devfn = devfn;
+               pci_rescan_slot(dev);
+       }
+       kfree(dev);
+}
+
+/* recursively scan all buses */
+static void pci_rescan_buses(const struct list_head *list)
+{
+       const struct list_head *l;
+       list_for_each(l,list) {
+               const struct pci_bus *b = pci_bus_b(l);
+               pci_rescan_bus(b);
+               pci_rescan_buses(&b->children);
+       }
+}
+
+/* initiate rescan of all pci buses */
+static inline void pci_rescan(void) {
+       pci_rescan_buses(&pci_root_buses);
+}
+
+
 static int enable_slot(struct hotplug_slot *hotplug_slot)
 {
+       /* 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)
+{
+       struct dummy_slot *dslot;
+
+       list_for_each_entry(dslot, &slot_list, node) {
+               if (dslot->dev == dev)
+                       return dslot->slot;
+       }
+       return NULL;
+}
+
+
 static int disable_slot(struct hotplug_slot *slot)
 {
        struct dummy_slot *dslot;
+       struct hotplug_slot *hslot;
+       struct pci_dev *dev;
+       int func;
 
        if (!slot)
                return -ENODEV;
@@ -185,6 +306,24 @@ static int disable_slot(struct hotplug_slot *slot)
                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");
+               }
+       }
 
        /* remove the device from the pci core */
        pci_remove_bus_device(dslot->dev);
@@ -227,6 +366,6 @@ module_exit(dummyphp_exit);
 MODULE_AUTHOR(DRIVER_AUTHOR);
 MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_LICENSE("GPL");
-MODULE_PARM(debug, "i");
+module_param(debug, bool, S_IRUGO | S_IWUSR);
 MODULE_PARM_DESC(debug, "Debugging mode enabled or not");