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) {