From mboxrd@z Thu Jan 1 00:00:00 1970 From: Subject: [PATCH 1/1] proc support add/remove dtb dynamically Date: Fri, 28 Sep 2012 13:25:18 -0500 Message-ID: <1348856718-25854-2-git-send-email-atull@altera.com> References: <1348856718-25854-1-git-send-email-atull@altera.com> Mime-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Return-path: In-Reply-To: <1348856718-25854-1-git-send-email-atull-EIB2kfCEclfQT0dZR+AlfA@public.gmane.org> List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: devicetree-discuss-bounces+gldd-devicetree-discuss=m.gmane.org-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org Sender: "devicetree-discuss" To: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org Cc: Rob Herring List-Id: devicetree@vger.kernel.org From: Alan Tull Command format: 1. First specify 'add:' or 'remove:' + the path of the parent node 2. Then cat the dtb. You must be root to do this. To add a my_periph.dtb under parent node /soc/apb_periphs: $ echo "add:/soc/apb_periphs" > /proc/ofdt $ cat my_periph.dtb > /proc/ofdt To remove the blob: $ echo "remove:/soc/apb_periphs" > /proc/ofdt $ cat my_periph.dtb > /proc/ofdt The .dts used to generate the .dtb blob only has only the nodes to be added, something like this: /dts-v1/; / { i2c0: i2c@ffc04000 { compatible = "snps,designware-i2c"; reg = <0xffc04000 0x1000>; interrupts = <0 158 4>; emptyfifo_hold_master = <1>; }; i2c1: i2c@ffc05000 { compatible = "snps,designware-i2c"; reg = <0xffc05000 0x1000>; interrupts = <0 159 4>; emptyfifo_hold_master = <1>; }; }; Signed-off-by: Alan Tull --- drivers/of/Makefile | 1 + drivers/of/dynamic.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 drivers/of/dynamic.c 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..9a884ae --- /dev/null +++ b/drivers/of/dynamic.c @@ -0,0 +1,362 @@ +/* + * Dynamic Device Tree support + * + * Copyright (C) 2012 Altera Corporation + * Author: Alan Tull + * + * Some code taken from arch/powerpc/platforms/pseries/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 +#include +#include +#include +#include +#include +#include +#include +#include + +static int proc_cmd; +static char *parent_path; +#define OF_DYNAMIC_UNDEFINED 0 +#define OF_DYNAMIC_ADD_NODE 1 +#define OF_DYNAMIC_REMOVE_NODE 2 + +#define OF_DYNAMIC_ADD_NODE_STR "add:" +#define OF_DYNAMIC_REMOVE_NODE_STR "remove:" + +/* + * Routines for "runtime" addition and removal of device tree nodes. + * From arch/powerpc/platforms/pseries/reconfig.c + */ +#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; + + 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) { + BUG_ON(!np->pde); + BUG_ON(!pp->name); + if (strlen(pp->name) == 0) + return; + remove_proc_entry(pp->name, np->pde); + pp = pp->next; + } + if (np->pde) { + if (strlen(np->pde->name) == 0) + return; + 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 */ + +/* + * Creates a node's full name by concatenating the parent path with the blob + * node name. + * Returns pointer to a char buffer or NULL. + */ +static char *create_full_name(struct device_node *np, char *parent_full_name) +{ + int full_name_size; + char *full_name; + + full_name_size = strlen(parent_full_name) + strlen(np->full_name) + 1; + full_name = kmalloc(full_name_size, GFP_KERNEL); + if (!full_name) + return NULL; + + strcpy(full_name, parent_full_name); + strcat(full_name, np->full_name); + + return full_name; +} + +static int add_node(struct device_node *np, struct device_node *tree_parent) +{ + struct device_node *tree_np; + char *full_name; + + /* Add parent path to blob node's name */ + full_name = create_full_name(np, tree_parent->full_name); + if (!full_name) + return -ENOMEM; + kfree(np->full_name); + np->full_name = full_name; + + /* Make sure node does not already exist in tree */ + tree_np = of_find_node_by_path(full_name); + if (tree_np) { + pr_err("Node already exists in device tree: %s\n", full_name); + of_node_put(tree_np); + return -EEXIST; + } + + pr_info("Adding device tree node: %s\n", full_name); + np->parent = tree_parent; + of_attach_node(np); + add_node_proc_entries(np); + + return 0; +} + +/* Add node and its siblings to existing tree */ +static int add_nodes_to_tree(struct device_node *blob_nodes) +{ + struct device_node *real_blob_node, *tree_parent, *sibling, *np; + int ret = 0; + + /* First node is blank. Start with its children. */ + real_blob_node = blob_nodes->child; + if (!real_blob_node) { + pr_err("First node not found in blob.\n"); + return -EINVAL; + } + + /* Find parent in existing tree */ + tree_parent = of_find_node_by_path(parent_path); + if (!tree_parent) { + pr_err("Tree parent not found at %s\n", parent_path); + return -EINVAL; + } + + /* Add node and its siblings to existing tree */ + for (np = real_blob_node ; np && !ret ; np = sibling) { + /* save sibling before it gets changed by of_attach_node */ + sibling = np->sibling; + ret = add_node(np, tree_parent); + } + + /* of_find_node_by_path did of_node_get */ + of_node_put(tree_parent); + + return ret; +} + +static int remove_node(struct device_node *blob_np) +{ + struct device_node *parent, *child, *tree_np; + char *full_name; + + full_name = create_full_name(blob_np, parent_path); + if (!full_name) + return -ENOMEM; + + tree_np = of_find_node_by_path(full_name); + if (!tree_np) { + pr_err("Node does not exist in device tree: %s\n", full_name); + kfree(full_name); + return -EINVAL; + } + kfree(full_name); + + parent = of_get_parent(tree_np); + if (!parent) + return -EINVAL; + + /* TODO delete nodes recursively */ + /* don't delete node if it has children */ + child = of_get_next_child(tree_np, NULL); + if (child) { + of_node_put(child); + of_node_put(parent); + return -EBUSY; + } + + pr_info("Removing device tree node: %s\n", full_name); + remove_node_proc_entries(tree_np); + of_detach_node(tree_np); + + of_node_put(parent); + + return 0; +} + +static int remove_nodes_from_tree(struct device_node *blob_nodes) +{ + struct device_node *real_blob_node, *np; + + /* First node is blank. Start with its children. */ + real_blob_node = blob_nodes->child; + if (!real_blob_node) + return -EINVAL; + + /* Remove node and its siblings from tree. If remove_node() + has errors, continue and ignore errors from remove_node. */ + for (np = real_blob_node ; np ; np = np->sibling) + remove_node(np); + + return 0; +} + +static int check_device_tree_magic(struct boot_param_header *blob) +{ + BUG_ON(!blob); + + if (be32_to_cpu(blob->magic) != OF_DT_HEADER) + return -EFAULT; + + return 0; +} + +static int set_parent_path(char *path) +{ + int path_len; + + kfree(parent_path); + + path_len = strlen(path); + if (path[path_len - 1] == '\n') + path_len--; + + parent_path = kmalloc(path_len + 1, GFP_KERNEL); + if (!parent_path) + return -ENOMEM; + + strncpy(parent_path, path, path_len); + parent_path[path_len] = '\0'; + + return 0; +} + +/* + * Proc entry command interface + * + * Specify 'add:' or 'remove:' + path of the parent node. Then cat the dtb. + * You must be root to do this. + * To add a blob: + * $ echo "add:/soc/apb_periphs" > /proc/ofdt + * $ cat my_periph.dtb > /proc/ofdt + * + * To remove a blob: + * $ echo "remove:/soc/apb_periphs" > /proc/ofdt + * $ cat my_periph.dtb > /proc/ofdt + */ +static ssize_t dynamic_write(struct file *file, const char __user *buf, + size_t count, loff_t *off) +{ + void *kbuf; + char *path; + struct device_node *np; + int ret = 0; + + kbuf = kmalloc(count + 1, GFP_KERNEL); + if (!kbuf) { + ret = -ENOMEM; + goto out; + } + if (copy_from_user(kbuf, buf, count)) { + ret = -EFAULT; + goto out; + } + path = kbuf; + path[count] = '\0'; + + /* See if this is a dtb */ + if (!check_device_tree_magic(kbuf)) { + of_fdt_unflatten_tree(kbuf, &np); + + switch (proc_cmd) { + case OF_DYNAMIC_ADD_NODE: + ret = add_nodes_to_tree(np); + break; + + case OF_DYNAMIC_REMOVE_NODE: + ret = remove_nodes_from_tree(np); + break; + + default: + pr_err("need to specify [add|remove]:/path/to/parent/node\n"); + break; + } + } else if ((count > sizeof(OF_DYNAMIC_ADD_NODE_STR)) && + (count < PATH_MAX) && + !strncmp(kbuf, OF_DYNAMIC_ADD_NODE_STR, + sizeof(OF_DYNAMIC_ADD_NODE_STR) - 1)) { + proc_cmd = OF_DYNAMIC_ADD_NODE; + path += sizeof(OF_DYNAMIC_ADD_NODE_STR) - 1; + ret = set_parent_path(path); + if (ret) + goto out; + pr_info("cmd: OF_DYNAMIC_ADD_NODE. parent=%s\n", + parent_path); + + } else if ((count > sizeof(OF_DYNAMIC_REMOVE_NODE_STR)) + && (count < PATH_MAX) && + !strncmp(kbuf, OF_DYNAMIC_REMOVE_NODE_STR, + sizeof(OF_DYNAMIC_REMOVE_NODE_STR) - 1)) { + proc_cmd = OF_DYNAMIC_REMOVE_NODE; + path += sizeof(OF_DYNAMIC_REMOVE_NODE_STR) - 1; + ret = set_parent_path(path); + if (ret) + goto out; + pr_info("cmd: OF_DYNAMIC_REMOVE_NODE. parent=%s\n", + parent_path); + + } else + pr_err("Invalid device tree blob header or command\n"); + +out: + kfree(kbuf); + return ret ? ret : count; +} + +static const struct file_operations dynamic_fops = { + .write = dynamic_write, + .llseek = noop_llseek, +}; + +/* create /proc/ofdt write-only by root */ +static int proc_dynamic_create_ofdt(void) +{ + struct proc_dir_entry *ent; + + proc_cmd = OF_DYNAMIC_UNDEFINED; + parent_path = NULL; + + ent = proc_create("ofdt", S_IWUSR, NULL, &dynamic_fops); + if (ent) + ent->size = 0; + + return 0; +} +device_initcall(proc_dynamic_create_ofdt); -- 1.7.9.5