[PATCH 1/1] dynamic device tree char driver

Alan Tull atull at altera.com
Fri Aug 17 05:43:46 EST 2012


 * Add a ioctls for adding/removing nodes using binary blobs.

Signed-off-by: Alan Tull <atull 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 <atull 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 <atull 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




More information about the devicetree-discuss mailing list