Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / pci / hotplug / rpadlpar_core.c
index 86b384e..46825fe 100644 (file)
  */
 #include <linux/init.h>
 #include <linux/pci.h>
+#include <linux/string.h>
+
 #include <asm/pci-bridge.h>
-#include <asm/semaphore.h>
+#include <linux/mutex.h>
 #include <asm/rtas.h>
+#include <asm/vio.h>
+
 #include "../pci.h"
 #include "rpaphp.h"
 #include "rpadlpar.h"
 
-static DECLARE_MUTEX(rpadlpar_sem);
+static DEFINE_MUTEX(rpadlpar_mutex);
+
+#define DLPAR_MODULE_NAME "rpadlpar_io"
 
 #define NODE_TYPE_VIO  1
 #define NODE_TYPE_SLOT 2
 #define NODE_TYPE_PHB  3
 
-static struct device_node *find_php_slot_vio_node(char *drc_name)
+static struct device_node *find_vio_slot_node(char *drc_name)
 {
-       struct device_node *child;
        struct device_node *parent = of_find_node_by_name(NULL, "vdevice");
-       char *loc_code;
+       struct device_node *dn = NULL;
+       char *name;
+       int rc;
 
        if (!parent)
                return NULL;
 
-       for (child = of_get_next_child(parent, NULL);
-               child; child = of_get_next_child(parent, child)) {
-               loc_code = get_property(child, "ibm,loc-code", NULL);
-               if (loc_code && !strncmp(loc_code, drc_name, strlen(drc_name)))
-                       return child;
+       while ((dn = of_get_next_child(parent, dn))) {
+               rc = rpaphp_get_drc_props(dn, NULL, &name, NULL, NULL);
+               if ((rc == 0) && (!strcmp(drc_name, name)))
+                       break;
        }
 
-       return NULL;
+       return dn;
 }
 
 /* Find dlpar-capable pci node that contains the specified name and type */
@@ -67,7 +73,7 @@ static struct device_node *find_php_slot_pci_node(char *drc_name,
        return np;
 }
 
-static struct device_node *find_newly_added_node(char *drc_name, int *node_type)
+static struct device_node *find_dlpar_node(char *drc_name, int *node_type)
 {
        struct device_node *dn;
 
@@ -83,7 +89,7 @@ static struct device_node *find_newly_added_node(char *drc_name, int *node_type)
                return dn;
        }
 
-       dn = find_php_slot_vio_node(drc_name);
+       dn = find_vio_slot_node(drc_name);
        if (dn) {
                *node_type = NODE_TYPE_VIO;
                return dn;
@@ -92,145 +98,99 @@ static struct device_node *find_newly_added_node(char *drc_name, int *node_type)
        return NULL;
 }
 
-static struct slot *find_slot(char *drc_name)
+static struct slot *find_slot(struct device_node *dn)
 {
        struct list_head *tmp, *n;
        struct slot *slot;
 
-        list_for_each_safe(tmp, n, &rpaphp_slot_head) {
-                slot = list_entry(tmp, struct slot, rpaphp_slot_list);
-                if (strcmp(slot->location, drc_name) == 0)
-                        return slot;
-        }
+       list_for_each_safe(tmp, n, &rpaphp_slot_head) {
+               slot = list_entry(tmp, struct slot, rpaphp_slot_list);
+               if (slot->dn == dn)
+                       return slot;
+       }
 
-        return NULL;
+       return NULL;
 }
 
-static void rpadlpar_claim_one_bus(struct pci_bus *b)
+static struct pci_dev *dlpar_find_new_dev(struct pci_bus *parent,
+                                       struct device_node *dev_dn)
 {
-       struct list_head *ld;
-       struct pci_bus *child_bus;
+       struct pci_dev *tmp = NULL;
+       struct device_node *child_dn;
 
-       for (ld = b->devices.next; ld != &b->devices; ld = ld->next) {
-               struct pci_dev *dev = pci_dev_b(ld);
-               int i;
-
-               for (i = 0; i < PCI_NUM_RESOURCES; i++) {
-                       struct resource *r = &dev->resource[i];
-
-                       if (r->parent || !r->start || !r->flags)
-                               continue;
-                       rpaphp_claim_resource(dev, i);
-               }
+       list_for_each_entry(tmp, &parent->devices, bus_list) {
+               child_dn = pci_device_to_OF_node(tmp);
+               if (child_dn == dev_dn)
+                       return tmp;
        }
-
-       list_for_each_entry(child_bus, &b->children, node)
-               rpadlpar_claim_one_bus(child_bus);
+       return NULL;
 }
 
-static int pci_add_secondary_bus(struct device_node *dn,
-               struct pci_dev *bridge_dev)
+static void dlpar_pci_add_bus(struct device_node *dn)
 {
-       struct pci_controller *hose = dn->phb;
-       struct pci_bus *child;
-       u8 sec_busno;
-
-       /* Get busno of downstream bus */
-       pci_read_config_byte(bridge_dev, PCI_SECONDARY_BUS, &sec_busno);
-
-       /* Allocate and add to children of bridge_dev->bus */
-       child = pci_add_new_bus(bridge_dev->bus, bridge_dev, sec_busno);
-       if (!child) {
-               printk(KERN_ERR "%s: could not add secondary bus\n", __FUNCTION__);
-               return -ENOMEM;
-       }
+       struct pci_dn *pdn = PCI_DN(dn);
+       struct pci_controller *phb = pdn->phb;
+       struct pci_dev *dev = NULL;
 
-       sprintf(child->name, "PCI Bus #%02x", child->number);
+       eeh_add_device_tree_early(dn);
 
-       /* Fixup subordinate bridge bases and resources */
-       pcibios_fixup_bus(child);
+       /* Add EADS device to PHB bus, adding new entry to bus->devices */
+       dev = of_create_pci_dev(dn, phb->bus, pdn->devfn);
+       if (!dev) {
+               printk(KERN_ERR "%s: failed to create pci dev for %s\n",
+                               __FUNCTION__, dn->full_name);
+               return;
+       }
 
-       /* Claim new bus resources */
-       rpadlpar_claim_one_bus(bridge_dev->bus);
+       if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||
+           dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
+               of_scan_pci_bridge(dn, dev);
 
-       if (hose->last_busno < child->number)
-               hose->last_busno = child->number;
+       pcibios_fixup_new_pci_devices(dev->subordinate,0);
 
-       dn->bussubno = child->number;
+       /* Claim new bus resources */
+       pcibios_claim_one_bus(dev->bus);
 
        /* ioremap() for child bus, which may or may not succeed */
-       remap_bus_range(child);
-
-       return 0;
-}
-
-static struct pci_dev *dlpar_pci_add_bus(struct device_node *dn)
-{
-       struct pci_controller *hose = dn->phb;
-       struct pci_dev *dev = NULL;
-
-       /* Scan phb bus for EADS device, adding new one to bus->devices */
-       if (!pci_scan_single_device(hose->bus, dn->devfn)) {
-               printk(KERN_ERR "%s: found no device on bus\n", __FUNCTION__);
-               return NULL;
-       }
+       remap_bus_range(dev->subordinate);
 
        /* Add new devices to global lists.  Register in proc, sysfs. */
-       pci_bus_add_devices(hose->bus);
-
-       /* Confirm new bridge dev was created */
-       dev = rpaphp_find_pci_dev(dn);
-       if (!dev) {
-               printk(KERN_ERR "%s: failed to add pci device\n", __FUNCTION__);
-               return NULL;
-       }
-
-       if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
-               printk(KERN_ERR "%s: unexpected header type %d\n",
-                       __FUNCTION__, dev->hdr_type);
-               return NULL;
-       }
-
-       if (pci_add_secondary_bus(dn, dev))
-               return NULL;
-
-       return dev;
+       pci_bus_add_devices(phb->bus);
 }
 
-static int dlpar_pci_remove_bus(struct pci_dev *bridge_dev)
+static int dlpar_add_pci_slot(char *drc_name, struct device_node *dn)
 {
-       struct pci_bus *secondary_bus;
+       struct pci_dev *dev;
+       struct pci_controller *phb;
 
-       if (!bridge_dev) {
-               printk(KERN_ERR "%s: unexpected null device\n",
-                       __FUNCTION__);
+       if (pcibios_find_pci_bus(dn))
                return -EINVAL;
-       }
 
-       secondary_bus = bridge_dev->subordinate;
-
-       if (unmap_bus_range(secondary_bus)) {
-               printk(KERN_ERR "%s: failed to unmap bus range\n",
-                       __FUNCTION__);
-               return -ERANGE;
-       }
-
-       pci_remove_bus_device(bridge_dev);
-       return 0;
-}
+       /* Add pci bus */
+       dlpar_pci_add_bus(dn);
 
-static inline int dlpar_add_pci_slot(char *drc_name, struct device_node *dn)
-{
-       struct pci_dev *dev;
+       /* Confirm new bridge dev was created */
+       phb = PCI_DN(dn)->phb;
+       dev = dlpar_find_new_dev(phb->bus, dn);
 
-       /* Add pci bus */
-       dev = dlpar_pci_add_bus(dn);
        if (!dev) {
                printk(KERN_ERR "%s: unable to add bus %s\n", __FUNCTION__,
                        drc_name);
                return -EIO;
        }
 
+       if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) {
+               printk(KERN_ERR "%s: unexpected header type %d, unable to add bus %s\n",
+                       __FUNCTION__, dev->hdr_type, drc_name);
+               return -EIO;
+       }
+
+       /* Add hotplug slot */
+       if (rpaphp_add_slot(dn)) {
+               printk(KERN_ERR "%s: unable to add hotplug slot %s\n",
+                       __FUNCTION__, drc_name);
+               return -EIO;
+       }
        return 0;
 }
 
@@ -255,47 +215,69 @@ static int dlpar_remove_root_bus(struct pci_controller *phb)
        return 0;
 }
 
-static int dlpar_remove_phb(struct slot *slot)
+static int dlpar_remove_phb(char *drc_name, struct device_node *dn)
 {
-       struct pci_controller *phb;
-       struct device_node *dn;
+       struct slot *slot;
+       struct pci_dn *pdn;
        int rc = 0;
 
-       dn = slot->dn;
-       if (!dn) {
-               printk(KERN_ERR "%s: unexpected NULL slot device node\n",
-                               __FUNCTION__);
-               return -EIO;
-       }
-
-       phb = dn->phb;
-       if (!phb) {
-               printk(KERN_ERR "%s: unexpected NULL phb pointer\n",
-                               __FUNCTION__);
-               return -EIO;
-       }
+       if (!pcibios_find_pci_bus(dn))
+               return -EINVAL;
 
-       if (rpaphp_remove_slot(slot)) {
-               printk(KERN_ERR "%s: unable to remove hotplug slot %s\n",
-                       __FUNCTION__, slot->location);
-               return -EIO;
+       slot = find_slot(dn);
+       if (slot) {
+               /* Remove hotplug slot */
+               if (rpaphp_deregister_slot(slot)) {
+                       printk(KERN_ERR
+                               "%s: unable to remove hotplug slot %s\n",
+                               __FUNCTION__, drc_name);
+                       return -EIO;
+               }
        }
 
-       rc = dlpar_remove_root_bus(phb);
+       pdn = dn->data;
+       BUG_ON(!pdn || !pdn->phb);
+       rc = dlpar_remove_root_bus(pdn->phb);
        if (rc < 0)
                return rc;
 
+       pdn->phb = NULL;
+
        return 0;
 }
 
-static int dlpar_add_phb(struct device_node *dn)
+static int dlpar_add_phb(char *drc_name, struct device_node *dn)
 {
        struct pci_controller *phb;
 
+       if (PCI_DN(dn) && PCI_DN(dn)->phb) {
+               /* PHB already exists */
+               return -EINVAL;
+       }
+
        phb = init_phb_dynamic(dn);
        if (!phb)
+               return -EIO;
+
+       if (rpaphp_add_slot(dn)) {
+               printk(KERN_ERR "%s: unable to add hotplug slot %s\n",
+                       __FUNCTION__, drc_name);
+               return -EIO;
+       }
+       return 0;
+}
+
+static int dlpar_add_vio_slot(char *drc_name, struct device_node *dn)
+{
+       if (vio_find_node(dn))
                return -EINVAL;
 
+       if (!vio_register_device_node(dn)) {
+               printk(KERN_ERR
+                       "%s: failed to register vio node %s\n",
+                       __FUNCTION__, drc_name);
+               return -EIO;
+       }
        return 0;
 }
 
@@ -316,18 +298,13 @@ int dlpar_add_slot(char *drc_name)
 {
        struct device_node *dn = NULL;
        int node_type;
-       int rc = 0;
+       int rc = -EIO;
 
-       if (down_interruptible(&rpadlpar_sem))
+       if (mutex_lock_interruptible(&rpadlpar_mutex))
                return -ERESTARTSYS;
 
-       /* Check for existing hotplug slot */
-       if (find_slot(drc_name)) {
-               rc = -EINVAL;
-               goto exit;
-       }
-
-       dn = find_newly_added_node(drc_name, &node_type);
+       /* Find newly added node */
+       dn = find_dlpar_node(drc_name, &node_type);
        if (!dn) {
                rc = -ENODEV;
                goto exit;
@@ -335,26 +312,19 @@ int dlpar_add_slot(char *drc_name)
 
        switch (node_type) {
                case NODE_TYPE_VIO:
-                       /* Just add hotplug slot */
+                       rc = dlpar_add_vio_slot(drc_name, dn);
                        break;
                case NODE_TYPE_SLOT:
                        rc = dlpar_add_pci_slot(drc_name, dn);
                        break;
                case NODE_TYPE_PHB:
-                       rc = dlpar_add_phb(dn);
+                       rc = dlpar_add_phb(drc_name, dn);
                        break;
-               default:
-                       printk("%s: unexpected node type\n", __FUNCTION__);
-                       return -EIO;
        }
 
-       if (!rc && rpaphp_add_slot(dn)) {
-               printk(KERN_ERR "%s: unable to add hotplug slot %s\n",
-                       __FUNCTION__, drc_name);
-               rc = -EIO;
-       }
+       printk(KERN_INFO "%s: slot %s added\n", DLPAR_MODULE_NAME, drc_name);
 exit:
-       up(&rpadlpar_sem);
+       mutex_unlock(&rpadlpar_mutex);
        return rc;
 }
 
@@ -366,17 +336,17 @@ exit:
  * of an I/O Slot.
  * Return Codes:
  * 0                   Success
- * -EIO                        Internal  Error
+ * -EINVAL             Vio dev doesn't exist
  */
-int dlpar_remove_vio_slot(struct slot *slot, char *drc_name)
+static int dlpar_remove_vio_slot(char *drc_name, struct device_node *dn)
 {
-       /* Remove hotplug slot */
+       struct vio_dev *vio_dev;
 
-       if (rpaphp_remove_slot(slot)) {
-               printk(KERN_ERR "%s: unable to remove hotplug slot %s\n",
-                       __FUNCTION__, drc_name);
-               return -EIO;
-       }
+       vio_dev = vio_find_node(dn);
+       if (!vio_dev)
+               return -EINVAL;
+
+       vio_unregister_device(vio_dev);
        return 0;
 }
 
@@ -391,31 +361,40 @@ int dlpar_remove_vio_slot(struct slot *slot, char *drc_name)
  * -ENODEV             Not a valid drc_name
  * -EIO                        Internal PCI Error
  */
-int dlpar_remove_pci_slot(struct slot *slot, char *drc_name)
+int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn)
 {
-       struct pci_dev *bridge_dev;
+       struct pci_bus *bus;
+       struct slot *slot;
 
-       bridge_dev = slot->bridge;
-       if (!bridge_dev) {
-               printk(KERN_ERR "%s: unexpected null bridge device\n",
-                       __FUNCTION__);
-               return -EIO;
-       }
+       bus = pcibios_find_pci_bus(dn);
+       if (!bus)
+               return -EINVAL;
 
-       /* Remove hotplug slot */
-       if (rpaphp_remove_slot(slot)) {
-               printk(KERN_ERR "%s: unable to remove hotplug slot %s\n",
-                       __FUNCTION__, drc_name);
-               return -EIO;
+       slot = find_slot(dn);
+       if (slot) {
+               /* Remove hotplug slot */
+               if (rpaphp_deregister_slot(slot)) {
+                       printk(KERN_ERR
+                               "%s: unable to remove hotplug slot %s\n",
+                               __FUNCTION__, drc_name);
+                       return -EIO;
+               }
+       } else {
+               struct pci_dev *dev, *tmp;
+               list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) {
+                       eeh_remove_bus_device(dev);
+                       pci_remove_bus_device(dev);
+               }
        }
 
-       /* Remove pci bus */
-
-       if (dlpar_pci_remove_bus(bridge_dev)) {
-               printk(KERN_ERR "%s: unable to remove pci bus %s\n",
-                       __FUNCTION__, drc_name);
-               return -EIO;
+       if (unmap_bus_range(bus)) {
+               printk(KERN_ERR "%s: failed to unmap bus range\n",
+                       __FUNCTION__);
+               return -ERANGE;
        }
+
+       BUG_ON(!bus->self);
+       pci_remove_bus_device(bus->self);
        return 0;
 }
 
@@ -434,40 +413,33 @@ int dlpar_remove_pci_slot(struct slot *slot, char *drc_name)
  */
 int dlpar_remove_slot(char *drc_name)
 {
-       struct slot *slot;
+       struct device_node *dn;
+       int node_type;
        int rc = 0;
 
-       if (down_interruptible(&rpadlpar_sem))
+       if (mutex_lock_interruptible(&rpadlpar_mutex))
                return -ERESTARTSYS;
 
-       if (!find_php_slot_vio_node(drc_name) &&
-           !find_php_slot_pci_node(drc_name, "SLOT") &&
-           !find_php_slot_pci_node(drc_name, "PHB")) {
+       dn = find_dlpar_node(drc_name, &node_type);
+       if (!dn) {
                rc = -ENODEV;
                goto exit;
        }
 
-       slot = find_slot(drc_name);
-       if (!slot) {
-               rc = -EINVAL;
-               goto exit;
-       }
-       
-       if (slot->type == PHB) {
-               rc = dlpar_remove_phb(slot);
-       } else {
-               switch (slot->dev_type) {
-                       case PCI_DEV:
-                               rc = dlpar_remove_pci_slot(slot, drc_name);
-                               break;
-
-                       case VIO_DEV:
-                               rc = dlpar_remove_vio_slot(slot, drc_name);
-                               break;
-               }
+       switch (node_type) {
+               case NODE_TYPE_VIO:
+                       rc = dlpar_remove_vio_slot(drc_name, dn);
+                       break;
+               case NODE_TYPE_PHB:
+                       rc = dlpar_remove_phb(drc_name, dn);
+                       break;
+               case NODE_TYPE_SLOT:
+                       rc = dlpar_remove_pci_slot(drc_name, dn);
+                       break;
        }
+       printk(KERN_INFO "%s: slot %s removed\n", DLPAR_MODULE_NAME, drc_name);
 exit:
-       up(&rpadlpar_sem);
+       mutex_unlock(&rpadlpar_mutex);
        return rc;
 }