of: overlay: Add per overlay sysfs attributes
* A per overlay can_remove sysfs attribute that reports whether the
overlay can be removed or not due to another overlapping overlay.
* A target sysfs attribute listing the target of each fragment, in a
group named after the name of the fragment.
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://lore.kernel.org/1462799403-10083-6-git-send-email-pantelis.antoniou@konsulko.com
[geert: Setup ovinfo[cnt].info for symbols]
[geert: Spelling s/changset/changeset/]
[geert: Rebase to v4.15-rc1]
[geert: Rebase on top of commit 39a751a4cb7e4798 ("of: change overlay apply input data from unflattened to FDT") in v4.17-rc1]
[geert: Use "%pOF" instead of of_node_full_name()]
[geert: Rebase on top of commit cdb4f26a63c39131 ("kobject: kobj_type: remove default_attrs") in v5.18-rc2]
[geert: Rebase on top of commit 067c098766c6af66 ("of: overlay: rework overlay apply and remove kfree()s") in v5.19-rc1]
[geert: Remove unused fragment.ovcs]
[geert: Add kerneldoc for new fragment/overlay_changeset fields]
Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
index d195862..e1feaaa 100644
--- a/drivers/of/overlay.c
+++ b/drivers/of/overlay.c
@@ -25,6 +25,20 @@
#include "of_private.h"
+/* fwd. decl */
+struct overlay_changeset;
+struct fragment;
+
+/* an attribute for each fragment */
+struct fragment_attribute {
+ struct attribute attr;
+ ssize_t (*show)(struct kobject *kobj, struct fragment_attribute *fattr,
+ char *buf);
+ ssize_t (*store)(struct kobject *kobj, struct fragment_attribute *fattr,
+ const char *buf, size_t count);
+ struct fragment *fragment;
+};
+
/**
* struct target - info about current target node as recursing through overlay
* @np: node where current level of overlay will be applied
@@ -47,12 +61,20 @@ struct target {
/**
* struct fragment - info about fragment nodes in overlay expanded device tree
- * @overlay: pointer to the __overlay__ node
- * @target: target of the overlay operation
+ * @overlay: pointer to the __overlay__ node
+ * @target: target of the overlay operation
+ * @info: info node that contains the target and overlay
+ * @attr_group: attribute group for the fragment
+ * @attrs: list of attributes for the fragment
+ * @target_attr: attribute for the fragment
*/
struct fragment {
struct device_node *overlay;
struct device_node *target;
+ struct device_node *info;
+ struct attribute_group attr_group;
+ struct attribute *attrs[2];
+ struct fragment_attribute target_attr;
};
/**
@@ -65,6 +87,7 @@ struct fragment {
* @notify_state: most recent notify action used on overlay
* @count: count of fragment structures
* @fragments: fragment nodes in the overlay expanded device tree
+ * @attr_groups: list of attribute groups for all fragments
* @symbols_fragment: last element of @fragments[] is the __symbols__ node
* @cset: changeset to apply fragments to live device tree
* @kobj: kernel object handle
@@ -78,6 +101,7 @@ struct overlay_changeset {
enum of_overlay_notify_action notify_state;
int count;
struct fragment *fragments;
+ const struct attribute_group **attr_groups;
bool symbols_fragment;
struct of_changeset cset;
struct kobject kobj;
@@ -117,6 +141,7 @@ static int devicetree_corrupt(void)
static int build_changeset_next_level(struct overlay_changeset *ovcs,
struct target *target, const struct device_node *overlay_node);
+static int overlay_removal_is_ok(struct overlay_changeset *ovcs);
/*
* of_resolve_phandles() finds the largest phandle in the live tree.
@@ -739,6 +764,16 @@ static struct device_node *find_target(const struct device_node *info_node,
return NULL;
}
+static ssize_t target_show(struct kobject *kobj,
+ struct fragment_attribute *fattr, char *buf)
+{
+ struct fragment *fragment = fattr->fragment;
+
+ return snprintf(buf, PAGE_SIZE, "%pOF\n", fragment->target);
+}
+
+static const struct fragment_attribute target_template_attr = __ATTR_RO(target);
+
/**
* init_overlay_changeset() - initialize overlay changeset from overlay tree
* @ovcs: Overlay changeset to build
@@ -759,7 +794,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
struct device_node *node, *overlay_node;
struct fragment *fragment;
struct fragment *fragments;
- int cnt, ret;
+ int cnt, i, ret;
/*
* None of the resources allocated by this function will be freed in
@@ -821,6 +856,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
goto err_out;
}
+ fragment->info = of_node_get(node);
cnt++;
}
@@ -842,6 +878,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
goto err_out;
}
+ fragment->info = of_node_get(node);
cnt++;
}
@@ -853,6 +890,34 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
ovcs->count = cnt;
+ ovcs->attr_groups = kcalloc(cnt + 1, sizeof(struct attribute_group *),
+ GFP_KERNEL);
+ if (ovcs->attr_groups == NULL) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ fragment = &ovcs->fragments[i];
+
+ ovcs->attr_groups[i] = &fragment->attr_group;
+
+ fragment->target_attr = target_template_attr;
+ /* make lockdep happy */
+ sysfs_attr_init(&fragment->target_attr.attr);
+ fragment->target_attr.fragment = fragment;
+
+ fragment->attrs[0] = &fragment->target_attr.attr;
+ fragment->attrs[1] = NULL;
+
+ /* NOTE: direct reference to the full_name */
+ fragment->attr_group.name =
+ kbasename(fragment->info->full_name);
+ fragment->attr_group.attrs = fragment->attrs;
+
+ }
+ ovcs->attr_groups[i] = NULL;
+
return 0;
err_out:
@@ -874,10 +939,12 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
ovcs->id = 0;
}
+ kfree(ovcs->attr_groups);
for (i = 0; i < ovcs->count; i++) {
of_node_put(ovcs->fragments[i].target);
of_node_put(ovcs->fragments[i].overlay);
+ of_node_put(ovcs->fragments[i].info);
}
kfree(ovcs->fragments);
kobject_put(&ovcs->kobj);
@@ -939,8 +1006,26 @@ static const struct attribute *overlay_global_attrs[] = {
NULL
};
+static ssize_t can_remove_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct overlay_changeset *ovcs = kobj_to_ovcs(kobj);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", overlay_removal_is_ok(ovcs));
+}
+
+static struct kobj_attribute can_remove_attr = __ATTR_RO(can_remove);
+
+static struct attribute *overlay_changeset_attrs[] = {
+ &can_remove_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(overlay_changeset);
+
static struct kobj_type overlay_changeset_ktype = {
.release = overlay_changeset_release,
+ .sysfs_ops = &kobj_sysfs_ops, /* default kobj sysfs ops */
+ .default_groups = overlay_changeset_groups,
};
static struct kset *ov_kset;
@@ -1137,7 +1222,15 @@ int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
if (ret) {
pr_err("%s: kobject_add() failed for tree@%s\n", __func__,
ovcs->overlay_root->full_name);
+ goto out_unlock;
}
+
+ ret = sysfs_create_groups(&ovcs->kobj, ovcs->attr_groups);
+ if (ret) {
+ pr_err("%s: sysfs_create_groups() failed for tree@%s\n",
+ __func__, ovcs->overlay_root->full_name);
+ }
+
goto out_unlock;
err_free_ovcs:
@@ -1294,6 +1387,9 @@ int of_overlay_remove(int *ovcs_id)
if (ret)
goto err_unlock;
+ if (ovcs->kobj.state_in_sysfs)
+ sysfs_remove_groups(&ovcs->kobj, ovcs->attr_groups);
+
ret_apply = 0;
ret = __of_changeset_revert_entries(&ovcs->cset, &ret_apply);
if (ret) {