| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include <linux/kernel.h> |
| #include <linux/notifier.h> |
| #include <linux/proc_fs.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| |
| #include <asm/prom.h> |
| #include <asm/machdep.h> |
| #include <linux/uaccess.h> |
| #include <asm/mmu.h> |
| |
| #include "of_helpers.h" |
| |
| static int pSeries_reconfig_add_node(const char *path, struct property *proplist) |
| { |
| <------>struct device_node *np; |
| <------>int err = -ENOMEM; |
| |
| <------>np = kzalloc(sizeof(*np), GFP_KERNEL); |
| <------>if (!np) |
| <------><------>goto out_err; |
| |
| <------>np->full_name = kstrdup(kbasename(path), GFP_KERNEL); |
| <------>if (!np->full_name) |
| <------><------>goto out_err; |
| |
| <------>np->properties = proplist; |
| <------>of_node_set_flag(np, OF_DYNAMIC); |
| <------>of_node_init(np); |
| |
| <------>np->parent = pseries_of_derive_parent(path); |
| <------>if (IS_ERR(np->parent)) { |
| <------><------>err = PTR_ERR(np->parent); |
| <------><------>goto out_err; |
| <------>} |
| |
| <------>err = of_attach_node(np); |
| <------>if (err) { |
| <------><------>printk(KERN_ERR "Failed to add device node %s\n", path); |
| <------><------>goto out_err; |
| <------>} |
| |
| <------>of_node_put(np->parent); |
| |
| <------>return 0; |
| |
| out_err: |
| <------>if (np) { |
| <------><------>of_node_put(np->parent); |
| <------><------>kfree(np->full_name); |
| <------><------>kfree(np); |
| <------>} |
| <------>return err; |
| } |
| |
| static int pSeries_reconfig_remove_node(struct device_node *np) |
| { |
| <------>struct device_node *parent, *child; |
| |
| <------>parent = of_get_parent(np); |
| <------>if (!parent) |
| <------><------>return -EINVAL; |
| |
| <------>if ((child = of_get_next_child(np, NULL))) { |
| <------><------>of_node_put(child); |
| <------><------>of_node_put(parent); |
| <------><------>return -EBUSY; |
| <------>} |
| |
| <------>of_detach_node(np); |
| <------>of_node_put(parent); |
| <------>return 0; |
| } |
| |
| |
| |
| |
| |
| |
| |
| static void release_prop_list(const struct property *prop) |
| { |
| <------>struct property *next; |
| <------>for (; prop; prop = next) { |
| <------><------>next = prop->next; |
| <------><------>kfree(prop->name); |
| <------><------>kfree(prop->value); |
| <------><------>kfree(prop); |
| <------>} |
| |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static char * parse_next_property(char *buf, char *end, char **name, int *length, |
| <------><------><------><------> unsigned char **value) |
| { |
| <------>char *tmp; |
| |
| <------>*name = buf; |
| |
| <------>tmp = strchr(buf, ' '); |
| <------>if (!tmp) { |
| <------><------>printk(KERN_ERR "property parse failed in %s at line %d\n", |
| <------><------> __func__, __LINE__); |
| <------><------>return NULL; |
| <------>} |
| <------>*tmp = '\0'; |
| |
| <------>if (++tmp >= end) { |
| <------><------>printk(KERN_ERR "property parse failed in %s at line %d\n", |
| <------><------> __func__, __LINE__); |
| <------><------>return NULL; |
| <------>} |
| |
| <------> |
| <------>*length = -1; |
| <------>*length = simple_strtoul(tmp, &tmp, 10); |
| <------>if (*length == -1) { |
| <------><------>printk(KERN_ERR "property parse failed in %s at line %d\n", |
| <------><------> __func__, __LINE__); |
| <------><------>return NULL; |
| <------>} |
| <------>if (*tmp != ' ' || ++tmp >= end) { |
| <------><------>printk(KERN_ERR "property parse failed in %s at line %d\n", |
| <------><------> __func__, __LINE__); |
| <------><------>return NULL; |
| <------>} |
| |
| <------> |
| <------>*value = tmp; |
| <------>tmp += *length; |
| <------>if (tmp > end) { |
| <------><------>printk(KERN_ERR "property parse failed in %s at line %d\n", |
| <------><------> __func__, __LINE__); |
| <------><------>return NULL; |
| <------>} |
| <------>else if (tmp < end && *tmp != ' ' && *tmp != '\0') { |
| <------><------>printk(KERN_ERR "property parse failed in %s at line %d\n", |
| <------><------> __func__, __LINE__); |
| <------><------>return NULL; |
| <------>} |
| <------>tmp++; |
| |
| <------> |
| <------>return tmp; |
| } |
| |
| static struct property *new_property(const char *name, const int length, |
| <------><------><------><------> const unsigned char *value, struct property *last) |
| { |
| <------>struct property *new = kzalloc(sizeof(*new), GFP_KERNEL); |
| |
| <------>if (!new) |
| <------><------>return NULL; |
| |
| <------>if (!(new->name = kstrdup(name, GFP_KERNEL))) |
| <------><------>goto cleanup; |
| <------>if (!(new->value = kmalloc(length + 1, GFP_KERNEL))) |
| <------><------>goto cleanup; |
| |
| <------>memcpy(new->value, value, length); |
| <------>*(((char *)new->value) + length) = 0; |
| <------>new->length = length; |
| <------>new->next = last; |
| <------>return new; |
| |
| cleanup: |
| <------>kfree(new->name); |
| <------>kfree(new->value); |
| <------>kfree(new); |
| <------>return NULL; |
| } |
| |
| static int do_add_node(char *buf, size_t bufsize) |
| { |
| <------>char *path, *end, *name; |
| <------>struct device_node *np; |
| <------>struct property *prop = NULL; |
| <------>unsigned char* value; |
| <------>int length, rv = 0; |
| |
| <------>end = buf + bufsize; |
| <------>path = buf; |
| <------>buf = strchr(buf, ' '); |
| <------>if (!buf) |
| <------><------>return -EINVAL; |
| <------>*buf = '\0'; |
| <------>buf++; |
| |
| <------>if ((np = of_find_node_by_path(path))) { |
| <------><------>of_node_put(np); |
| <------><------>return -EINVAL; |
| <------>} |
| |
| <------> |
| <------>while (buf < end && |
| <------> (buf = parse_next_property(buf, end, &name, &length, &value))) { |
| <------><------>struct property *last = prop; |
| |
| <------><------>prop = new_property(name, length, value, last); |
| <------><------>if (!prop) { |
| <------><------><------>rv = -ENOMEM; |
| <------><------><------>prop = last; |
| <------><------><------>goto out; |
| <------><------>} |
| <------>} |
| <------>if (!buf) { |
| <------><------>rv = -EINVAL; |
| <------><------>goto out; |
| <------>} |
| |
| <------>rv = pSeries_reconfig_add_node(path, prop); |
| |
| out: |
| <------>if (rv) |
| <------><------>release_prop_list(prop); |
| <------>return rv; |
| } |
| |
| static int do_remove_node(char *buf) |
| { |
| <------>struct device_node *node; |
| <------>int rv = -ENODEV; |
| |
| <------>if ((node = of_find_node_by_path(buf))) |
| <------><------>rv = pSeries_reconfig_remove_node(node); |
| |
| <------>of_node_put(node); |
| <------>return rv; |
| } |
| |
| static char *parse_node(char *buf, size_t bufsize, struct device_node **npp) |
| { |
| <------>char *handle_str; |
| <------>phandle handle; |
| <------>*npp = NULL; |
| |
| <------>handle_str = buf; |
| |
| <------>buf = strchr(buf, ' '); |
| <------>if (!buf) |
| <------><------>return NULL; |
| <------>*buf = '\0'; |
| <------>buf++; |
| |
| <------>handle = simple_strtoul(handle_str, NULL, 0); |
| |
| <------>*npp = of_find_node_by_phandle(handle); |
| <------>return buf; |
| } |
| |
| static int do_add_property(char *buf, size_t bufsize) |
| { |
| <------>struct property *prop = NULL; |
| <------>struct device_node *np; |
| <------>unsigned char *value; |
| <------>char *name, *end; |
| <------>int length; |
| <------>end = buf + bufsize; |
| <------>buf = parse_node(buf, bufsize, &np); |
| |
| <------>if (!np) |
| <------><------>return -ENODEV; |
| |
| <------>if (parse_next_property(buf, end, &name, &length, &value) == NULL) |
| <------><------>return -EINVAL; |
| |
| <------>prop = new_property(name, length, value, NULL); |
| <------>if (!prop) |
| <------><------>return -ENOMEM; |
| |
| <------>of_add_property(np, prop); |
| |
| <------>return 0; |
| } |
| |
| static int do_remove_property(char *buf, size_t bufsize) |
| { |
| <------>struct device_node *np; |
| <------>char *tmp; |
| <------>buf = parse_node(buf, bufsize, &np); |
| |
| <------>if (!np) |
| <------><------>return -ENODEV; |
| |
| <------>tmp = strchr(buf,' '); |
| <------>if (tmp) |
| <------><------>*tmp = '\0'; |
| |
| <------>if (strlen(buf) == 0) |
| <------><------>return -EINVAL; |
| |
| <------>return of_remove_property(np, of_find_property(np, buf, NULL)); |
| } |
| |
| static int do_update_property(char *buf, size_t bufsize) |
| { |
| <------>struct device_node *np; |
| <------>unsigned char *value; |
| <------>char *name, *end, *next_prop; |
| <------>int length; |
| <------>struct property *newprop; |
| <------>buf = parse_node(buf, bufsize, &np); |
| <------>end = buf + bufsize; |
| |
| <------>if (!np) |
| <------><------>return -ENODEV; |
| |
| <------>next_prop = parse_next_property(buf, end, &name, &length, &value); |
| <------>if (!next_prop) |
| <------><------>return -EINVAL; |
| |
| <------>if (!strlen(name)) |
| <------><------>return -ENODEV; |
| |
| <------>newprop = new_property(name, length, value, NULL); |
| <------>if (!newprop) |
| <------><------>return -ENOMEM; |
| |
| <------>if (!strcmp(name, "slb-size") || !strcmp(name, "ibm,slb-size")) |
| <------><------>slb_set_size(*(int *)value); |
| |
| <------>return of_update_property(np, newprop); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static ssize_t ofdt_write(struct file *file, const char __user *buf, size_t count, |
| <------><------><------> loff_t *off) |
| { |
| <------>int rv; |
| <------>char *kbuf; |
| <------>char *tmp; |
| |
| <------>kbuf = memdup_user_nul(buf, count); |
| <------>if (IS_ERR(kbuf)) |
| <------><------>return PTR_ERR(kbuf); |
| |
| <------>tmp = strchr(kbuf, ' '); |
| <------>if (!tmp) { |
| <------><------>rv = -EINVAL; |
| <------><------>goto out; |
| <------>} |
| <------>*tmp = '\0'; |
| <------>tmp++; |
| |
| <------>if (!strcmp(kbuf, "add_node")) |
| <------><------>rv = do_add_node(tmp, count - (tmp - kbuf)); |
| <------>else if (!strcmp(kbuf, "remove_node")) |
| <------><------>rv = do_remove_node(tmp); |
| <------>else if (!strcmp(kbuf, "add_property")) |
| <------><------>rv = do_add_property(tmp, count - (tmp - kbuf)); |
| <------>else if (!strcmp(kbuf, "remove_property")) |
| <------><------>rv = do_remove_property(tmp, count - (tmp - kbuf)); |
| <------>else if (!strcmp(kbuf, "update_property")) |
| <------><------>rv = do_update_property(tmp, count - (tmp - kbuf)); |
| <------>else |
| <------><------>rv = -EINVAL; |
| out: |
| <------>kfree(kbuf); |
| <------>return rv ? rv : count; |
| } |
| |
| static const struct proc_ops ofdt_proc_ops = { |
| <------>.proc_write = ofdt_write, |
| <------>.proc_lseek = noop_llseek, |
| }; |
| |
| |
| static int proc_ppc64_create_ofdt(void) |
| { |
| <------>struct proc_dir_entry *ent; |
| |
| <------>ent = proc_create("powerpc/ofdt", 0200, NULL, &ofdt_proc_ops); |
| <------>if (ent) |
| <------><------>proc_set_size(ent, 0); |
| |
| <------>return 0; |
| } |
| machine_device_initcall(pseries, proc_ppc64_create_ofdt); |
| |