Skip to content

Commit

Permalink
of: Make device nodes kobjects so they show up in sysfs
Browse files Browse the repository at this point in the history
Device tree nodes are already treated as objects, and we already want to
expose them to userspace which is done using the /proc filesystem today.
Right now the kernel has to do a lot of work to keep the /proc view in
sync with the in-kernel representation. If device_nodes are switched to
be kobjects then the device tree code can be a whole lot simpler. It
also turns out that switching to using /sysfs from /proc results in
smaller code and data size, and the userspace ABI won't change if
/proc/device-tree symlinks to /sys/firmware/devicetree/base.

v7: Add missing sysfs_bin_attr_init()
v6: Add __of_add_property() early init fixes from Pantelis
v5: Rename firmware/ofw to firmware/devicetree
    Fix updating property values in sysfs
v4: Fixed build error on Powerpc
    Fixed handling of dynamic nodes on powerpc
v3: Fixed handling of duplicate attribute and child node names
v2: switch to using sysfs bin_attributes which solve the problem of
    reporting incorrect property size.

Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Tested-by: Sascha Hauer <s.hauer@pengutronix.de>
Cc: Rob Herring <rob.herring@calxeda.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: David S. Miller <davem@davemloft.net>
Cc: Nathan Fontenot <nfont@linux.vnet.ibm.com>
Cc: Pantelis Antoniou <panto@antoniou-consulting.com>
  • Loading branch information
Grant Likely committed Mar 11, 2014
1 parent dab2310 commit 75b57ec
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 18 deletions.
28 changes: 28 additions & 0 deletions Documentation/ABI/testing/sysfs-firmware-ofw
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
What: /sys/firmware/devicetree/*
Date: November 2013
Contact: Grant Likely <grant.likely@linaro.org>
Description:
When using OpenFirmware or a Flattened Device Tree to enumerate
hardware, the device tree structure will be exposed in this
directory.

It is possible for multiple device-tree directories to exist.
Some device drivers use a separate detached device tree which
have no attachment to the system tree and will appear in a
different subdirectory under /sys/firmware/devicetree.

Userspace must not use the /sys/firmware/devicetree/base
path directly, but instead should follow /proc/device-tree
symlink. It is possible that the absolute path will change
in the future, but the symlink is the stable ABI.

The /proc/device-tree symlink replaces the devicetree /proc
filesystem support, and has largely the same semantics and
should be compatible with existing userspace.

The contents of /sys/firmware/devicetree/ is a
hierarchy of directories, one per device tree node. The
directory name is the resolved path component name (node
name plus address). Properties are represented as files
in the directory. The contents of each file is the exact
binary data from the device tree.
2 changes: 0 additions & 2 deletions arch/powerpc/platforms/pseries/dlpar.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/

#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/notifier.h>
#include <linux/spinlock.h>
#include <linux/cpu.h>
Expand Down Expand Up @@ -87,7 +86,6 @@ static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa,
}

of_node_set_flag(dn, OF_DYNAMIC);
kref_init(&dn->kref);

return dn;
}
Expand Down
2 changes: 0 additions & 2 deletions arch/powerpc/platforms/pseries/reconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/

#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/notifier.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
Expand Down Expand Up @@ -70,7 +69,6 @@ static int pSeries_reconfig_add_node(const char *path, struct property *proplist

np->properties = proplist;
of_node_set_flag(np, OF_DYNAMIC);
kref_init(&np->kref);

np->parent = derive_parent(path);
if (IS_ERR(np->parent)) {
Expand Down
2 changes: 1 addition & 1 deletion arch/powerpc/sysdev/msi_bitmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ void __init test_of_node(void)

/* There should really be a struct device_node allocator */
memset(&of_node, 0, sizeof(of_node));
kref_init(&of_node.kref);
kref_init(&of_node.kobj.kref);
of_node.full_name = node_name;

check(0 == msi_bitmap_alloc(&bmp, size, &of_node));
Expand Down
174 changes: 166 additions & 8 deletions drivers/of/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <linux/of.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/proc_fs.h>

#include "of_private.h"
Expand All @@ -35,6 +36,12 @@ struct device_node *of_chosen;
struct device_node *of_aliases;
static struct device_node *of_stdout;

static struct kset *of_kset;

/*
* Used to protect the of_aliases; but also overloaded to hold off addition of
* nodes to sysfs
*/
DEFINE_MUTEX(of_aliases_mutex);

/* use when traversing tree through the allnext, child, sibling,
Expand Down Expand Up @@ -92,14 +99,14 @@ int __weak of_node_to_nid(struct device_node *np)
struct device_node *of_node_get(struct device_node *node)
{
if (node)
kref_get(&node->kref);
kobject_get(&node->kobj);
return node;
}
EXPORT_SYMBOL(of_node_get);

static inline struct device_node *kref_to_device_node(struct kref *kref)
static inline struct device_node *kobj_to_device_node(struct kobject *kobj)
{
return container_of(kref, struct device_node, kref);
return container_of(kobj, struct device_node, kobj);
}

/**
Expand All @@ -109,16 +116,15 @@ static inline struct device_node *kref_to_device_node(struct kref *kref)
* In of_node_put() this function is passed to kref_put()
* as the destructor.
*/
static void of_node_release(struct kref *kref)
static void of_node_release(struct kobject *kobj)
{
struct device_node *node = kref_to_device_node(kref);
struct device_node *node = kobj_to_device_node(kobj);
struct property *prop = node->properties;

/* We should never be releasing nodes that haven't been detached. */
if (!of_node_check_flag(node, OF_DETACHED)) {
pr_err("ERROR: Bad of_node_put() on %s\n", node->full_name);
dump_stack();
kref_init(&node->kref);
return;
}

Expand Down Expand Up @@ -151,11 +157,140 @@ static void of_node_release(struct kref *kref)
void of_node_put(struct device_node *node)
{
if (node)
kref_put(&node->kref, of_node_release);
kobject_put(&node->kobj);
}
EXPORT_SYMBOL(of_node_put);
#else
static void of_node_release(struct kobject *kobj)
{
/* Without CONFIG_OF_DYNAMIC, no nodes gets freed */
}
#endif /* CONFIG_OF_DYNAMIC */

struct kobj_type of_node_ktype = {
.release = of_node_release,
};

static ssize_t of_node_property_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t offset, size_t count)
{
struct property *pp = container_of(bin_attr, struct property, attr);
return memory_read_from_buffer(buf, count, &offset, pp->value, pp->length);
}

static const char *safe_name(struct kobject *kobj, const char *orig_name)
{
const char *name = orig_name;
struct kernfs_node *kn;
int i = 0;

/* don't be a hero. After 16 tries give up */
while (i < 16 && (kn = sysfs_get_dirent(kobj->sd, name))) {
sysfs_put(kn);
if (name != orig_name)
kfree(name);
name = kasprintf(GFP_KERNEL, "%s#%i", orig_name, ++i);
}

if (name != orig_name)
pr_warn("device-tree: Duplicate name in %s, renamed to \"%s\"\n",
kobject_name(kobj), name);
return name;
}

static int __of_add_property_sysfs(struct device_node *np, struct property *pp)
{
int rc;

/* Important: Don't leak passwords */
bool secure = strncmp(pp->name, "security-", 9) == 0;

sysfs_bin_attr_init(&pp->attr);
pp->attr.attr.name = safe_name(&np->kobj, pp->name);
pp->attr.attr.mode = secure ? S_IRUSR : S_IRUGO;
pp->attr.size = secure ? 0 : pp->length;
pp->attr.read = of_node_property_read;

rc = sysfs_create_bin_file(&np->kobj, &pp->attr);
WARN(rc, "error adding attribute %s to node %s\n", pp->name, np->full_name);
return rc;
}

static int __of_node_add(struct device_node *np)
{
const char *name;
struct property *pp;
int rc;

np->kobj.kset = of_kset;
if (!np->parent) {
/* Nodes without parents are new top level trees */
rc = kobject_add(&np->kobj, NULL, safe_name(&of_kset->kobj, "base"));
} else {
name = safe_name(&np->parent->kobj, kbasename(np->full_name));
if (!name || !name[0])
return -EINVAL;

rc = kobject_add(&np->kobj, &np->parent->kobj, "%s", name);
}
if (rc)
return rc;

for_each_property_of_node(np, pp)
__of_add_property_sysfs(np, pp);

return 0;
}

int of_node_add(struct device_node *np)
{
int rc = 0;
kobject_init(&np->kobj, &of_node_ktype);
mutex_lock(&of_aliases_mutex);
if (of_kset)
rc = __of_node_add(np);
mutex_unlock(&of_aliases_mutex);
return rc;
}

#if defined(CONFIG_OF_DYNAMIC)
static void of_node_remove(struct device_node *np)
{
struct property *pp;

for_each_property_of_node(np, pp)
sysfs_remove_bin_file(&np->kobj, &pp->attr);

kobject_del(&np->kobj);
}
#endif

static int __init of_init(void)
{
struct device_node *np;

/* Create the kset, and register existing nodes */
mutex_lock(&of_aliases_mutex);
of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
if (!of_kset) {
mutex_unlock(&of_aliases_mutex);
return -ENOMEM;
}
for_each_of_allnodes(np)
__of_node_add(np);
mutex_unlock(&of_aliases_mutex);

#if !defined(CONFIG_PROC_DEVICETREE)
/* Symlink to the new tree when PROC_DEVICETREE is disabled */
if (of_allnodes)
proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");
#endif /* CONFIG_PROC_DEVICETREE */

return 0;
}
core_initcall(of_init);

static struct property *__of_find_property(const struct device_node *np,
const char *name, int *lenp)
{
Expand Down Expand Up @@ -1546,6 +1681,14 @@ int of_add_property(struct device_node *np, struct property *prop)
raw_spin_lock_irqsave(&devtree_lock, flags);
rc = __of_add_property(np, prop);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
if (rc)
return rc;

/* at early boot, bail hear and defer setup to of_init() */
if (!of_kset)
return 0;

__of_add_property_sysfs(np, prop);

#ifdef CONFIG_PROC_DEVICETREE
/* try to add to proc as well if it was initialized */
Expand Down Expand Up @@ -1593,6 +1736,12 @@ int of_remove_property(struct device_node *np, struct property *prop)
if (!found)
return -ENODEV;

/* at early boot, bail hear and defer setup to of_init() */
if (!of_kset)
return 0;

sysfs_remove_bin_file(&np->kobj, &prop->attr);

#ifdef CONFIG_PROC_DEVICETREE
/* try to remove the proc node as well */
if (np->pde)
Expand Down Expand Up @@ -1643,13 +1792,20 @@ int of_update_property(struct device_node *np, struct property *newprop)
next = &(*next)->next;
}
raw_spin_unlock_irqrestore(&devtree_lock, flags);
if (rc)
return rc;

/* Update the sysfs attribute */
if (oldprop)
sysfs_remove_bin_file(&np->kobj, &oldprop->attr);
__of_add_property_sysfs(np, newprop);

if (!found)
return -ENODEV;

#ifdef CONFIG_PROC_DEVICETREE
/* try to add to proc as well if it was initialized */
if (!rc && np->pde)
if (np->pde)
proc_device_tree_update_prop(np->pde, newprop, oldprop);
#endif /* CONFIG_PROC_DEVICETREE */

Expand Down Expand Up @@ -1723,6 +1879,7 @@ int of_attach_node(struct device_node *np)
of_node_clear_flag(np, OF_DETACHED);
raw_spin_unlock_irqrestore(&devtree_lock, flags);

of_node_add(np);
of_add_proc_dt_entry(np);
return 0;
}
Expand Down Expand Up @@ -1795,6 +1952,7 @@ int of_detach_node(struct device_node *np)
raw_spin_unlock_irqrestore(&devtree_lock, flags);

of_remove_proc_dt_entry(np);
of_node_remove(np);
return rc;
}
#endif /* defined(CONFIG_OF_DYNAMIC) */
Expand Down
3 changes: 2 additions & 1 deletion drivers/of/fdt.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ static void * unflatten_dt_node(struct boot_param_header *blob,
dad->next->sibling = np;
dad->next = np;
}
kref_init(&np->kref);
}
/* process properties */
while (1) {
Expand Down Expand Up @@ -327,6 +326,8 @@ static void * unflatten_dt_node(struct boot_param_header *blob,
np->name = "<NULL>";
if (!np->type)
np->type = "<NULL>";

of_node_add(np);
}
while (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) {
if (tag == OF_DT_NOP)
Expand Down
4 changes: 2 additions & 2 deletions drivers/of/pdt.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,6 @@ static struct device_node * __init of_pdt_create_node(phandle node,
of_pdt_incr_unique_id(dp);
dp->parent = parent;

kref_init(&dp->kref);

dp->name = of_pdt_get_one_property(node, "name");
dp->type = of_pdt_get_one_property(node, "device_type");
dp->phandle = node;
Expand Down Expand Up @@ -215,6 +213,7 @@ static struct device_node * __init of_pdt_build_tree(struct device_node *parent,
*nextp = &dp->allnext;

dp->full_name = of_pdt_build_full_name(dp);
of_node_add(dp);

dp->child = of_pdt_build_tree(dp,
of_pdt_prom_ops->getchild(node), nextp);
Expand Down Expand Up @@ -245,6 +244,7 @@ void __init of_pdt_build_devicetree(phandle root_node, struct of_pdt_ops *ops)
of_allnodes->path_component_name = "";
#endif
of_allnodes->full_name = "/";
of_node_add(of_allnodes);

nextp = &of_allnodes->allnext;
of_allnodes->child = of_pdt_build_tree(of_allnodes,
Expand Down
3 changes: 3 additions & 0 deletions drivers/of/testcase-data/tests-phandle.dtsi
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

/ {
testcase-data {
security-password = "password";
duplicate-name = "duplicate";
duplicate-name { };
phandle-tests {
provider0: provider0 {
#phandle-cells = <0>;
Expand Down
Loading

0 comments on commit 75b57ec

Please sign in to comment.