]> git.hungrycats.org Git - linux/commitdiff
[PATCH] ppc64: base support for dynamic update of OF device, tree from Nathan Lynch
authorAndrew Morton <akpm@osdl.org>
Mon, 19 Jan 2004 02:12:49 +0000 (18:12 -0800)
committerLinus Torvalds <torvalds@home.osdl.org>
Mon, 19 Jan 2004 02:12:49 +0000 (18:12 -0800)
From: Anton Blanchard <anton@samba.org>

base support for dynamic update of OF device, tree from Nathan Lynch

arch/ppc64/kernel/prom.c
include/asm-ppc64/prom.h

index 8fb06614d5d8abec7fd6b7bf45cafcb9633b79e2..e95886c883af9b0a3a25ad656d9be14e61c825df 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/spinlock.h>
 #include <linux/types.h>
 #include <linux/pci.h>
+#include <linux/proc_fs.h>
 #include <asm/prom.h>
 #include <asm/rtas.h>
 #include <asm/lmb.h>
@@ -44,6 +45,7 @@
 #include <asm/bitops.h>
 #include <asm/naca.h>
 #include <asm/pci.h>
+#include <asm/pci_dma.h>
 #include <asm/bootinfo.h>
 #include <asm/ppcdebug.h>
 #include "open_pic.h"
@@ -149,6 +151,10 @@ char *bootdevice = 0;
 int boot_cpuid = 0;
 
 struct device_node *allnodes = 0;
+/* use when traversing tree through the allnext, child, sibling,
+ * or parent members of struct device_node.
+ */
+static rwlock_t devtree_lock = RW_LOCK_UNLOCKED;
 
 #define UNDEFINED_IRQ 0xffff
 unsigned short real_irq_to_virt_map[NR_HW_IRQS];
@@ -168,6 +174,11 @@ static int prom_next_node(phandle *);
 static struct bi_record * prom_bi_rec_verify(struct bi_record *);
 static unsigned long prom_bi_rec_reserve(unsigned long);
 static struct device_node *find_phandle(phandle);
+static void of_node_cleanup(struct device_node *);
+static struct device_node *derive_parent(const char *);
+static void add_node_proc_entries(struct device_node *);
+static void remove_node_proc_entries(struct device_node *);
+static int of_finish_dynamic_node(struct device_node *);
 
 #ifdef DEBUG_PROM
 void prom_dump_lmb(void);
@@ -2006,8 +2017,8 @@ find_path_device(const char *path)
 /*******
  *
  * New implementation of the OF "find" APIs, return a refcounted
- * object, call of_node_put() when done. Currently, still lacks
- * locking as old implementation, this is being done for ppc64.
+ * object, call of_node_put() when done.  The device tree and list
+ * are protected by a rw_lock.
  *
  * Note that property management will need some locking as well,
  * this isn't dealt with yet.
@@ -2028,14 +2039,18 @@ find_path_device(const char *path)
 struct device_node *of_find_node_by_name(struct device_node *from,
        const char *name)
 {
-       struct device_node *np = from ? from->allnext : allnodes;
+       struct device_node *np;
 
+       read_lock(&devtree_lock);
+       np = from ? from->allnext : allnodes;
        for (; np != 0; np = np->allnext)
-               if (np->name != 0 && strcasecmp(np->name, name) == 0)
+               if (np->name != 0 && strcasecmp(np->name, name) == 0
+                   && of_node_get(np))
                        break;
        if (from)
                of_node_put(from);
-       return of_node_get(np);
+       read_unlock(&devtree_lock);
+       return np;
 }
 
 /**
@@ -2052,14 +2067,18 @@ struct device_node *of_find_node_by_name(struct device_node *from,
 struct device_node *of_find_node_by_type(struct device_node *from,
        const char *type)
 {
-       struct device_node *np = from ? from->allnext : allnodes;
+       struct device_node *np;
 
+       read_lock(&devtree_lock);
+       np = from ? from->allnext : allnodes;
        for (; np != 0; np = np->allnext)
-               if (np->type != 0 && strcasecmp(np->type, type) == 0)
+               if (np->type != 0 && strcasecmp(np->type, type) == 0
+                   && of_node_get(np))
                        break;
        if (from)
                of_node_put(from);
-       return of_node_get(np);
+       read_unlock(&devtree_lock);
+       return np;
 }
 
 /**
@@ -2079,18 +2098,21 @@ struct device_node *of_find_node_by_type(struct device_node *from,
 struct device_node *of_find_compatible_node(struct device_node *from,
        const char *type, const char *compatible)
 {
-       struct device_node *np = from ? from->allnext : allnodes;
+       struct device_node *np;
 
+       read_lock(&devtree_lock);
+       np = from ? from->allnext : allnodes;
        for (; np != 0; np = np->allnext) {
                if (type != NULL
                    && !(np->type != 0 && strcasecmp(np->type, type) == 0))
                        continue;
-               if (device_is_compatible(np, compatible))
+               if (device_is_compatible(np, compatible) && of_node_get(np))
                        break;
        }
        if (from)
                of_node_put(from);
-       return of_node_get(np);
+       read_unlock(&devtree_lock);
+       return np;
 }
 
 /**
@@ -2104,10 +2126,13 @@ struct device_node *of_find_node_by_path(const char *path)
 {
        struct device_node *np = allnodes;
 
+       read_lock(&devtree_lock);
        for (; np != 0; np = np->allnext)
-               if (np->full_name != 0 && strcasecmp(np->full_name, path) == 0)
+               if (np->full_name != 0 && strcasecmp(np->full_name, path) == 0
+                   && of_node_get(np))
                        break;
-       return of_node_get(np);
+       read_unlock(&devtree_lock);
+       return np;
 }
 
 /**
@@ -2120,11 +2145,17 @@ struct device_node *of_find_node_by_path(const char *path)
  */
 struct device_node *of_find_all_nodes(struct device_node *prev)
 {
-       struct device_node *np = prev ? prev->allnext : allnodes;
+       struct device_node *np;
 
+       read_lock(&devtree_lock);
+       np = prev ? prev->allnext : allnodes;
+       for (; np != 0; np = np->allnext)
+               if (of_node_get(np))
+                       break;
        if (prev)
                of_node_put(prev);
-       return of_node_get(np);
+       read_unlock(&devtree_lock);
+       return np;
 }
 
 /**
@@ -2136,7 +2167,15 @@ struct device_node *of_find_all_nodes(struct device_node *prev)
  */
 struct device_node *of_get_parent(const struct device_node *node)
 {
-       return node ? of_node_get(node->parent) : NULL;
+       struct device_node *np;
+
+       if (!node)
+               return NULL;
+
+       read_lock(&devtree_lock);
+       np = of_node_get(node->parent);
+       read_unlock(&devtree_lock);
+       return np;
 }
 
 /**
@@ -2150,13 +2189,16 @@ struct device_node *of_get_parent(const struct device_node *node)
 struct device_node *of_get_next_child(const struct device_node *node,
        struct device_node *prev)
 {
-       struct device_node *next = prev ? prev->sibling : node->child;
+       struct device_node *next;
 
+       read_lock(&devtree_lock);
+       next = prev ? prev->sibling : node->child;
        for (; next != 0; next = next->sibling)
                if (of_node_get(next))
                        break;
        if (prev)
                of_node_put(prev);
+       read_unlock(&devtree_lock);
        return next;
 }
 
@@ -2169,7 +2211,11 @@ struct device_node *of_get_next_child(const struct device_node *node,
  */
 struct device_node *of_node_get(struct device_node *node)
 {
-       return node;
+       if (node && !OF_IS_STALE(node)) {
+               atomic_inc(&node->_users);
+               return node;
+       }
+       return NULL;
 }
 
 /**
@@ -2180,6 +2226,335 @@ struct device_node *of_node_get(struct device_node *node)
  */
 void of_node_put(struct device_node *node)
 {
+       if (!node)
+               return;
+
+       WARN_ON(0 == atomic_read(&node->_users));
+
+       if (OF_IS_STALE(node)) {
+               if (atomic_dec_and_test(&node->_users)) {
+                       of_node_cleanup(node);
+                       return;
+               }
+       }
+       else
+               atomic_dec(&node->_users);
+}
+
+/**
+ *     of_node_cleanup - release a dynamically allocated node
+ *     @arg:  Node to be released
+ */
+static void of_node_cleanup(struct device_node *node)
+{
+       struct property *prop = node->properties;
+
+       if (!OF_IS_DYNAMIC(node))
+               return;
+       while (prop) {
+               struct property *next = prop->next;
+               kfree(prop->name);
+               kfree(prop->value);
+               kfree(prop);
+               prop = next;
+       }
+       kfree(node->intrs);
+       kfree(node->addrs);
+       kfree(node->full_name);
+       kfree(node);
+}
+
+/**
+ *     derive_parent - basically like dirname(1)
+ *     @path:  the full_name of a node to be added to the tree
+ *
+ *     Returns the node which should be the parent of the node
+ *     described by path.  E.g., for path = "/foo/bar", returns
+ *     the node with full_name = "/foo".
+ */
+static struct device_node *derive_parent(const char *path)
+{
+       struct device_node *parent = NULL;
+       char *parent_path = "/";
+       size_t parent_path_len = strrchr(path, '/') - path + 1;
+
+       /* reject if path is "/" */
+       if (!strcmp(path, "/"))
+               return NULL;
+
+       if (strrchr(path, '/') != path) {
+               parent_path = kmalloc(parent_path_len, GFP_KERNEL);
+               if (!parent_path)
+                       return NULL;
+               strlcpy(parent_path, path, parent_path_len);
+       }
+       parent = of_find_node_by_path(parent_path);
+       if (strcmp(parent_path, "/"))
+               kfree(parent_path);
+       return parent;
+}
+
+/*
+ * Routines for "runtime" addition and removal of device tree nodes.
+ */
+
+/*
+ * Given a path and a property list, construct an OF device node, add
+ * it to the device tree and global list, and place it in
+ * /proc/device-tree.  This function may sleep.
+ */
+int of_add_node(const char *path, struct property *proplist)
+{
+       struct device_node *np;
+       int err = 0;
+
+       np = kmalloc(sizeof(struct device_node), GFP_KERNEL);
+       if (!np)
+               return -ENOMEM;
+
+       memset(np, 0, sizeof(*np));
+
+       np->full_name = kmalloc(strlen(path) + 1, GFP_KERNEL);
+       if (!np->full_name) {
+               kfree(np);
+               return -ENOMEM;
+       }
+       strcpy(np->full_name, path);
+
+       np->properties = proplist;
+       OF_MARK_DYNAMIC(np);
+       of_node_get(np);
+       np->parent = derive_parent(path);
+       if (!np->parent) {
+               kfree(np);
+               return -EINVAL; /* could also be ENOMEM, though */
+       }
+
+       if (0 != (err = of_finish_dynamic_node(np))) {
+               kfree(np);
+               return err;
+       }
+
+       write_lock(&devtree_lock);
+       np->sibling = np->parent->child;
+       np->allnext = allnodes;
+       np->parent->child = np;
+       allnodes = np;
+       write_unlock(&devtree_lock);
+
+       add_node_proc_entries(np);
+
+       of_node_put(np->parent);
+       of_node_put(np);
+       return 0;
+}
+
+/*
+ * Remove an OF device node from the system.
+ */
+int of_remove_node(struct device_node *np)
+{
+       struct device_node *parent, *child;
+
+       parent = of_get_parent(np);
+       child = of_get_next_child(np, NULL);
+       if (child && !child->child && !child->sibling) {
+               /* For now, we will allow removal of a
+                * node with one and only one child, so
+                * that we can support removing a slot with
+                * an IOA in it.  More general support for
+                * subtree removal to be implemented later, if
+                * necessary.
+                */
+               of_remove_node(child);
+       }
+       else if (child) {
+               of_node_put(child);
+               of_node_put(parent);
+               return -EINVAL;
+       }
+       of_node_put(child);
+
+       write_lock(&devtree_lock);
+       OF_MARK_STALE(np);
+       remove_node_proc_entries(np);
+       if (allnodes == np)
+               allnodes = np->allnext;
+       else {
+               struct device_node *prev;
+               for (prev = allnodes;
+                    prev->allnext != np;
+                    prev = prev->allnext)
+                       ;
+               prev->allnext = np->allnext;
+       }
+
+       if (np->parent->child == np)
+               np->parent->child = np->sibling;
+       else {
+               struct device_node *prevsib;
+               for (prevsib = np->parent->child;
+                    prevsib->sibling != np;
+                    prevsib = prevsib->sibling)
+                       ;
+               prevsib->sibling = np->sibling;
+       }
+       write_unlock(&devtree_lock);
+       of_node_put(parent);
+       return 0;
+}
+
+/*
+ * Add a node to /proc/device-tree.
+ */
+static void add_node_proc_entries(struct device_node *np)
+{
+       struct proc_dir_entry *ent;
+
+       ent = proc_mkdir(strrchr(np->full_name, '/') + 1, np->parent->pde);
+       if (ent)
+               proc_device_tree_add_node(np, ent);
+}
+
+static void remove_node_proc_entries(struct device_node *np)
+{
+       struct property *pp = np->properties;
+       struct device_node *parent = np->parent;
+
+       while (pp) {
+               remove_proc_entry(pp->name, np->pde);
+               pp = pp->next;
+       }
+
+       /* Assuming that symlinks have the same parent directory as
+        * np->pde.
+        */
+       if (np->name_link)
+               remove_proc_entry(np->name_link->name, parent->pde);
+       if (np->addr_link)
+               remove_proc_entry(np->addr_link->name, parent->pde);
+       if (np->pde)
+               remove_proc_entry(np->pde->name, parent->pde);
+}
+
+/*
+ * Fix up the uninitialized fields in a new device node:
+ * name, type, n_addrs, addrs, n_intrs, intrs, and pci-specific fields
+ *
+ * A lot of boot-time code is duplicated here, because functions such
+ * as finish_node_interrupts, interpret_pci_props, etc. cannot use the
+ * slab allocator.
+ *
+ * This should probably be split up into smaller chunks.
+ */
+
+static int of_finish_dynamic_node(struct device_node *node)
+{
+       struct device_node *parent = of_get_parent(node);
+       u32 *regs;
+       unsigned int *ints;
+       int intlen, intrcells;
+       int i, j, n, err = 0;
+       unsigned int *irq;
+       struct device_node *ic;
+ 
+       node->name = get_property(node, "name", 0);
+       node->type = get_property(node, "device_type", 0);
+
+       if (!parent) {
+               err = -ENODEV;
+               goto out;
+       }
+
+       /* do the work of interpret_pci_props */
+       if (parent->type && !strcmp(parent->type, "pci")) {
+               struct address_range *adr;
+               struct pci_reg_property *pci_addrs;
+               int i, l;
+
+               pci_addrs = (struct pci_reg_property *)
+                       get_property(node, "assigned-addresses", &l);
+               if (pci_addrs != 0 && l >= sizeof(struct pci_reg_property)) {
+                       i = 0;
+                       adr = kmalloc(sizeof(struct address_range) * 
+                                     (l / sizeof(struct pci_reg_property)),
+                                     GFP_KERNEL);
+                       if (!adr) {
+                               err = -ENOMEM;
+                               goto out;
+                       }
+                       while ((l -= sizeof(struct pci_reg_property)) >= 0) {
+                               adr[i].space = pci_addrs[i].addr.a_hi;
+                               adr[i].address = pci_addrs[i].addr.a_lo;
+                               adr[i].size = pci_addrs[i].size_lo;
+                               ++i;
+                       }
+                       node->addrs = adr;
+                       node->n_addrs = i;
+               }
+       }
+
+       /* now do the work of finish_node_interrupts */
+
+       ints = (unsigned int *) get_property(node, "interrupts", &intlen);
+       if (!ints)
+               goto out;
+
+       intrcells = prom_n_intr_cells(node);
+       intlen /= intrcells * sizeof(unsigned int);
+       node->n_intrs = intlen;
+       node->intrs = kmalloc(sizeof(struct interrupt_info) * intlen,
+                             GFP_KERNEL);
+       if (!node->intrs) {
+               err = -ENOMEM;
+               goto out;
+       }
+
+       for (i = 0; i < intlen; ++i) {
+               node->intrs[i].line = 0;
+               node->intrs[i].sense = 1;
+               n = map_interrupt(&irq, &ic, node, ints, intrcells);
+               if (n <= 0)
+                       continue;
+               node->intrs[i].line = openpic_to_irq(virt_irq_create_mapping(irq[0]));
+               if (n > 1)
+                       node->intrs[i].sense = irq[1];
+               if (n > 2) {
+                       printk(KERN_DEBUG "hmmm, got %d intr cells for %s:", n,
+                              node->full_name);
+                       for (j = 0; j < n; ++j)
+                               printk(" %d", irq[j]);
+                       printk("\n");
+               }
+               ints += intrcells;
+       }
+
+       /* now do the rough equivalent of update_dn_pci_info, this
+        * probably is not correct for phb's, but should work for
+       * IOAs and slots.
+        */
+
+       node->phb = parent->phb;
+
+       regs = (u32 *)get_property(node, "reg", 0);
+       if (regs) {
+               node->busno = (regs[0] >> 16) & 0xff;
+               node->devfn = (regs[0] >> 8) & 0xff;
+       }
+
+       /* fixing up tce_table */
+
+       if(strcmp(node->name, "pci") == 0 &&
+                get_property(node, "ibm,dma-window", NULL)) {
+                node->bussubno = node->busno;
+                create_pci_bus_tce_table((unsigned long)node);
+        }
+       else
+               node->tce_table = parent->tce_table;
+
+out:
+       of_node_put(parent);
+       return err;
 }
 
 /*
index 063ecbf9e5b877bfb0f478c9bd8a14afbba3aace..6d5df1631148109283740448c8e78075cd9b59ed 100644 (file)
@@ -14,6 +14,8 @@
  * as published by the Free Software Foundation; either version
  * 2 of the License, or (at your option) any later version.
  */
+#include <linux/proc_fs.h>
+#include <asm/atomic.h>
 
 #define PTRRELOC(x)     ((typeof(x))((unsigned long)(x) - offset))
 #define PTRUNRELOC(x)   ((typeof(x))((unsigned long)(x) + offset))
@@ -144,8 +146,44 @@ struct device_node {
        struct  device_node *sibling;
        struct  device_node *next;      /* next device of same type */
        struct  device_node *allnext;   /* next in list of all nodes */
+       struct  proc_dir_entry *pde;       /* this node's proc directory */
+       struct  proc_dir_entry *name_link; /* name symlink */
+       struct  proc_dir_entry *addr_link; /* addr symlink */
+       atomic_t _users;                 /* reference count */
+       unsigned long _flags;
 };
 
+/* flag descriptions */
+#define OF_STALE   0 /* node is slated for deletion */
+#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
+
+#define OF_IS_STALE(x) test_bit(OF_STALE, &x->_flags)
+#define OF_MARK_STALE(x) set_bit(OF_STALE, &x->_flags)
+#define OF_IS_DYNAMIC(x) test_bit(OF_DYNAMIC, &x->_flags)
+#define OF_MARK_DYNAMIC(x) set_bit(OF_DYNAMIC, &x->_flags)
+
+/*
+ * Until 32-bit ppc can add proc_dir_entries to its device_node
+ * definition, we cannot refer to pde, name_link, and addr_link
+ * in arch-independent code.
+ */
+#define HAVE_ARCH_DEVTREE_FIXUPS
+
+static inline void set_node_proc_entry(struct device_node *dn, struct proc_dir_entry *de)
+{
+       dn->pde = de;
+}
+
+static void inline set_node_name_link(struct device_node *dn, struct proc_dir_entry *de)
+{
+       dn->name_link = de;
+}
+
+static void inline set_node_addr_link(struct device_node *dn, struct proc_dir_entry *de)
+{
+       dn->addr_link = de;
+}
+
 typedef u32 prom_arg_t;
 
 struct prom_args {
@@ -196,6 +234,10 @@ extern struct device_node *of_get_next_child(const struct device_node *node,
 extern struct device_node *of_node_get(struct device_node *node);
 extern void of_node_put(struct device_node *node);
 
+/* For updating the device tree at runtime */
+extern int of_add_node(const char *path, struct property *proplist);
+extern int of_remove_node(struct device_node *np);
+
 /* Other Prototypes */
 extern void abort(void);
 extern unsigned long prom_init(unsigned long, unsigned long, unsigned long,