* Add a ioctls for adding/removing nodes using binary blobs. Signed-off-by: Alan Tull <at...@altera.com> --- drivers/of/Makefile | 1 + drivers/of/dynamic.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/of/dynamic.h | 26 +++++ 3 files changed, 314 insertions(+), 0 deletions(-) create mode 100644 drivers/of/dynamic.c create mode 100644 drivers/of/dynamic.h
diff --git a/drivers/of/Makefile b/drivers/of/Makefile index e027f44..5c08045 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_OF_PROMTREE) += pdt.o obj-$(CONFIG_OF_ADDRESS) += address.o obj-$(CONFIG_OF_IRQ) += irq.o obj-$(CONFIG_OF_DEVICE) += device.o platform.o +obj-$(CONFIG_OF_DYNAMIC) += dynamic.o obj-$(CONFIG_OF_I2C) += of_i2c.o obj-$(CONFIG_OF_NET) += of_net.o obj-$(CONFIG_OF_SELFTEST) += selftest.o diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c new file mode 100644 index 0000000..0dce177 --- /dev/null +++ b/drivers/of/dynamic.c @@ -0,0 +1,287 @@ +/* + * Dynamic Device Tree support + * + * Copyright (C) 2012 Altera Corporation + * Author: Alan Tull <at...@altera.com> + * + * Some code taken from reconfig.c + * Copyright (C) 2005 Nathan Lynch + * Copyright (C) 2005 IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include <linux/device.h> +#include <linux/cdev.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/uaccess.h> +#include <linux/mutex.h> +#include "dynamic.h" + +static int major; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); + +static DEFINE_MUTEX(of_dynamic_ioctl_mutex); + +/* + * Routines for "runtime" addition and removal of device tree nodes. + */ +#ifdef CONFIG_PROC_DEVICETREE +/* + * Add a node to /proc/device-tree. + */ +static void add_node_proc_entries(struct device_node *np) +{ + struct proc_dir_entry *ent; + + pr_debug("%s parent........... %s\n", __func__, np->parent->name); + pr_debug("%s full_name........ %s\n", __func__, np->full_name); + + 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; + } + if (np->pde) + remove_proc_entry(np->pde->name, parent->pde); +} +#else /* !CONFIG_PROC_DEVICETREE */ +static void add_node_proc_entries(struct device_node *np) +{ + return; +} + +static void remove_node_proc_entries(struct device_node *np) +{ + return; +} +#endif /* CONFIG_PROC_DEVICETREE */ + +/* + * 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 ERR_PTR(-EINVAL); + + if (strrchr(path, '/') != path) { + parent_path = kmalloc(parent_path_len, GFP_KERNEL); + if (!parent_path) + return ERR_PTR(-ENOMEM); + strlcpy(parent_path, path, parent_path_len); + } + parent = of_find_node_by_path(parent_path); + if (!parent) + return ERR_PTR(-EINVAL); + if (strcmp(parent_path, "/")) + kfree(parent_path); + return parent; +} + +static void next_real_node(struct device_node **npp) +{ + /* The blob to be added is not a complete device tree. Some nodes' + parents are only there to indicate the path to add the node + in the existing tree. Look for nodes that have real properties + besides name (i.e. compatible) and add those recursively. */ + + /* Mark the nodes we don't attach for removal */ + while ((*npp) && !of_get_property(*npp, "compatible", NULL)) { + of_node_set_flag(*npp, OF_DETACHED); + *npp = (*npp)->next; + } +} + +static int add_nodes_to_tree(struct device_node *np) +{ + struct device_node *tree_node; + + while (np) { + next_real_node(&np); + if (np) { + tree_node = of_find_node_by_path(np->full_name); + if (tree_node) { + pr_err("%s Error: node %s exists in device tree\n", + __func__, np->full_name); + of_node_put(tree_node); + return -EEXIST; + } + + np->parent = derive_parent(np->full_name); + if (IS_ERR(np->parent)) + return -EINVAL; + of_attach_node(np); + add_node_proc_entries(np); + + of_node_put(np->parent); + np = np->next; + } + } + + return 0; +} + +static int remove_nodes_from_tree(struct device_node *np) +{ + struct device_node *tree_node; + + while (np) { + next_real_node(&np); + if (np) { + tree_node = of_find_node_by_path(np->full_name); + if (!tree_node) { + pr_err("%s Error: node %s does not exist in device tree\n", + __func__, np->full_name); + return -ENOENT; + } + + remove_node_proc_entries(tree_node); + of_detach_node(tree_node); + of_node_put(tree_node); + + of_node_put(np->parent); + np = np->next; + } + } + + return 0; +} + +static long of_dynamic_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + void *blob = NULL; + struct device_node *np; + void __user *user_arg = (void __user *)arg; + struct of_dynamic_blob_info blob_info; + + pr_debug("%s\n", __func__); + + mutex_lock(&of_dynamic_ioctl_mutex); + + if (copy_from_user(&blob_info, user_arg, sizeof(blob_info))) { + pr_err("%s copy_from_user error\n", __func__); + ret = -EINVAL; + goto ioctl_out; + } + + blob = kmalloc(blob_info.size, GFP_KERNEL); + if (!blob) { + ret = -ENOMEM; + goto ioctl_out; + } + + if (copy_from_user(blob, blob_info.blob, blob_info.size)) { + ret = -EFAULT; + goto ioctl_out; + } + + of_fdt_unflatten_tree(blob, &np); + + switch (cmd) { + case OF_DYNAMIC_ADD_NODE: + pr_debug("%s : cmd = OF_DYNAMIC_ADD_NODE size = %d\n", + __func__, blob_info.size); + ret = add_nodes_to_tree(np); + break; + + case OF_DYNAMIC_REMOVE_NODE: + pr_debug("%s : cmd = OF_DYNAMIC_REMOVE_NODE\n", __func__); + ret = remove_nodes_from_tree(np); + break; + + default: + pr_debug("%s : unknown ioctl %d blob size = %d\n", + __func__, cmd, blob_info.size); + ret = -EINVAL; + break; + } + +ioctl_out: + kfree(blob); + mutex_unlock(&of_dynamic_ioctl_mutex); + return ret; +} + +static const struct file_operations of_dynamic_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = of_dynamic_unlocked_ioctl, +}; + +static struct cdev of_dynamic_dev; + +static int __init of_dynamic_init(void) +{ + int rc; + dev_t dev; + + if (major) { + dev = MKDEV(major, 0); + rc = register_chrdev_region(dev, 1, "of_dynamic"); + } else { + rc = alloc_chrdev_region(&dev, 0, 1, "of_dynamic"); + major = MAJOR(dev); + } + if (rc < 0) { + pr_err("of_dynamic chrdev_region err: %d\n", rc); + return rc; + } + + cdev_init(&of_dynamic_dev, &of_dynamic_fops); + rc = cdev_add(&of_dynamic_dev, dev, 1); + if (rc < 0) + return rc; + + return 0; +} + +static void __exit of_dynamic_exit(void) +{ + unregister_chrdev_region(MKDEV(major, 0), 1); +} + +module_init(of_dynamic_init); +module_exit(of_dynamic_exit); + +MODULE_AUTHOR("Alan Tull <at...@altera.com>"); +MODULE_DESCRIPTION("Dynamic device tree"); +MODULE_LICENSE("GPL"); diff --git a/drivers/of/dynamic.h b/drivers/of/dynamic.h new file mode 100644 index 0000000..4ee4e96 --- /dev/null +++ b/drivers/of/dynamic.h @@ -0,0 +1,26 @@ +/* + * Dynamic Device Tree support + * + * Copyrignt (C) 2012 Altera Corporation + * + */ + +#ifndef __OF_DYNAMIC_H +#define __OF_DYNAMIC_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +struct of_dynamic_blob_info { + __u32 size; + __u8 *blob; +}; + +/* + * TODO pick some unused ioctls (see Documentation/ioctl/ioctl-number-txt) + * and ask mec to register them + */ +#define OF_DYNAMIC_ADD_NODE _IOW('W', 0, struct of_dynamic_blob_info *) +#define OF_DYNAMIC_REMOVE_NODE _IOW('W', 1, struct of_dynamic_blob_info *) + +#endif /* __OF_DYNAMIC_H */ -- 1.7.1 _______________________________________________ devicetree-discuss mailing list devicetree-discuss@lists.ozlabs.org https://lists.ozlabs.org/listinfo/devicetree-discuss