Commit 50405fa2 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] ppc64: base support for dynamic update of OF device, tree from Nathan Lynch

From: Anton Blanchard <anton@samba.org>

base support for dynamic update of OF device, tree from Nathan Lynch
parent 40a4a0aa
......@@ -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;
}
/*
......
......@@ -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,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment