Subject: [PATCH 2/3] powerpc/scom: Replace debugfs interface with cleaner sysfs one

From: Benjamin Herrenschmidt <[email-masked]>
Date: Thu, 10 Oct 2013 19:18:35 +1100

The debugfs interface was essentially unused, and racy for anything
other than manual use by a developer. This provides a more useful
sysfs based one which can be used by programs without racing with
each other essentially by providing a file to read/write with an
offset being the scom address << 8.

It requires 8 bytes aligned and multiple of 8 bytes sized accesses

Signed-off-by: Benjamin Herrenschmidt <[email-masked]>
---
 arch/powerpc/configs/chroma_defconfig |   2 +-
 arch/powerpc/sysdev/Kconfig           |   6 +-
 arch/powerpc/sysdev/scom.c            | 210 +++++++++++++++++++++-------------
 3 files changed, 135 insertions(+), 83 deletions(-)

diff --git a/arch/powerpc/configs/chroma_defconfig b/arch/powerpc/configs/chroma_defconfig
index 4f35fc4..2891464 100644
--- a/arch/powerpc/configs/chroma_defconfig
+++ b/arch/powerpc/configs/chroma_defconfig
@@ -42,7 +42,7 @@ CONFIG_MODULE_UNLOAD=y
 CONFIG_MODULE_FORCE_UNLOAD=y
 CONFIG_MODVERSIONS=y
 CONFIG_MODULE_SRCVERSION_ALL=y
-CONFIG_SCOM_DEBUGFS=y
+CONFIG_SCOM_SYSFS=y
 CONFIG_PPC_A2_DD2=y
 CONFIG_KVM_GUEST=y
 CONFIG_NO_HZ=y
diff --git a/arch/powerpc/sysdev/Kconfig b/arch/powerpc/sysdev/Kconfig
index 13ec968..30c26bb 100644
--- a/arch/powerpc/sysdev/Kconfig
+++ b/arch/powerpc/sysdev/Kconfig
@@ -26,9 +26,9 @@ source "arch/powerpc/sysdev/xics/Kconfig"
 config PPC_SCOM
 	bool
 
-config SCOM_DEBUGFS
-	bool "Expose SCOM controllers via debugfs"
-	depends on PPC_SCOM && DEBUG_FS
+config SCOM_SYSFS
+	bool "Expose SCOM controllers via sysfs"
+	depends on PPC_SCOM
 	default n
 
 config GE_FPGA
diff --git a/arch/powerpc/sysdev/scom.c b/arch/powerpc/sysdev/scom.c
index 3963d99..72eda2d 100644
--- a/arch/powerpc/sysdev/scom.c
+++ b/arch/powerpc/sysdev/scom.c
@@ -19,9 +19,10 @@
  */
 
 #include <linux/kernel.h>
-#include <linux/debugfs.h>
 #include <linux/slab.h>
 #include <linux/export.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
 #include <asm/debug.h>
 #include <asm/prom.h>
 #include <asm/scom.h>
@@ -95,116 +96,167 @@ scom_map_t scom_map_device(struct device_node *dev, int index)
 }
 EXPORT_SYMBOL_GPL(scom_map_device);
 
-#ifdef CONFIG_SCOM_DEBUGFS
-struct scom_debug_entry {
-	struct device_node *dn;
-	unsigned long addr;
-	scom_map_t map;
-	spinlock_t lock;
-	char name[8];
-	struct debugfs_blob_wrapper blob;
+#ifdef CONFIG_SCOM_SYSFS
+
+struct scom_chip_dir {
+	struct kobject kobj;
+	struct device_node *devnode;
 };
 
-static int scom_addr_set(void *data, u64 val)
+static struct scom_chip_dir *kobj_to_scom_chip_dir(struct kobject *k)
 {
-	struct scom_debug_entry *ent = data;
-
-	ent->addr = 0;
-	scom_unmap(ent->map);
-
-	ent->map = scom_map(ent->dn, val, 1);
-	if (scom_map_ok(ent->map))
-		ent->addr = val;
-	else
-		return -EFAULT;
-
-	return 0;
+	return container_of(k, struct scom_chip_dir, kobj);
 }
 
-static int scom_addr_get(void *data, u64 *val)
+static ssize_t scom_devspec_show(struct kobject *k, struct kobj_attribute *attr,
+				 char *buf)
 {
-	struct scom_debug_entry *ent = data;
-	*val = ent->addr;
-	return 0;
-}
-DEFINE_SIMPLE_ATTRIBUTE(scom_addr_fops, scom_addr_get, scom_addr_set,
-			"0x%llx\n");
+	struct scom_chip_dir *dir = kobj_to_scom_chip_dir(k);
 
-static int scom_val_set(void *data, u64 val)
-{
-	struct scom_debug_entry *ent = data;
+	return sprintf(buf, "%s", dir->devnode->full_name);
+}
 
-	if (!scom_map_ok(ent->map))
-		return -EFAULT;
+static struct kobj_attribute scom_devspec_attr =
+	__ATTR(devspec, S_IRUGO, scom_devspec_show, NULL);
 
-	scom_write(ent->map, 0, val);
+static struct attribute *scom_dir_default_attrs[] = {
+	&scom_devspec_attr.attr,
+	NULL,
+};
 
-	return 0;
+static void scom_dir_release(struct kobject *kobj)
+{
+	struct scom_chip_dir *dir = kobj_to_scom_chip_dir(kobj);
+	kfree(dir);
 }
 
-static int scom_val_get(void *data, u64 *val)
-{
-	struct scom_debug_entry *ent = data;
+static struct kobj_type scom_dir_type = {
+	.release = scom_dir_release,
+	.sysfs_ops = &kobj_sysfs_ops,
+	.default_attrs = scom_dir_default_attrs,
+};
 
-	if (!scom_map_ok(ent->map))
-		return -EFAULT;
+static ssize_t scom_sysfs_read(struct file *filp, struct kobject *kobj,
+			       struct bin_attribute *bin_attr,
+			       char *buf, loff_t off, size_t count)
+{
+	struct scom_chip_dir *dir = kobj_to_scom_chip_dir(kobj);
+	u64 reg, reg_cnt, *buf64;
+	scom_map_t map;
+	int rc;
+
+	if (off & 7 || count & 7)
+		return -EINVAL;
+	reg = off >> 3;
+	reg_cnt = count >> 3;
+
+	map = scom_map(dir->devnode, reg, reg_cnt);
+	if (!scom_map_ok(map))
+		return -EINVAL;
+
+	buf64 = (u64 *)buf;
+	for (reg = 0; reg < reg_cnt; reg++) {
+		rc = scom_read(map, reg, buf64++);
+		if (rc) {
+			count = rc;
+			break;
+		}
+	}
+	scom_unmap(map);
+	return count;
+}
 
-	return scom_read(ent->map, 0, val);
+static ssize_t scom_sysfs_write(struct file* filp, struct kobject *kobj,
+				struct bin_attribute *bin_attr,
+				char *buf, loff_t off, size_t count)
+{
+	struct scom_chip_dir *dir = kobj_to_scom_chip_dir(kobj);
+	u64 reg, reg_cnt, *buf64;
+	scom_map_t map;
+	int rc;
+
+	if (off & 7 || count & 7)
+		return -EINVAL;
+	reg = off >> 3;
+	reg_cnt = count >> 3;
+
+	map = scom_map(dir->devnode, reg, reg_cnt);
+	if (!scom_map_ok(map))
+		return -EINVAL;
+
+	buf64 = (u64 *)buf;
+	for (reg = 0; reg < reg_cnt; reg++) {
+		rc = scom_write(map, reg,  *(buf64++));
+		if (rc) {
+			count = rc;
+			break;
+		}
+	}
+	scom_unmap(map);
+	return count;
 }
-DEFINE_SIMPLE_ATTRIBUTE(scom_val_fops, scom_val_get, scom_val_set,
-			"0x%llx\n");
 
-static int scom_debug_init_one(struct dentry *root, struct device_node *dn,
-			       int i)
+static struct bin_attribute scom_access_attr = {
+	.attr =	{
+		.name = "access",
+		.mode = S_IRUSR | S_IWUSR,
+	},
+	.size = 0,
+	.read = scom_sysfs_read,
+	.write = scom_sysfs_write,
+};
+
+static int scom_sysfs_init_chip(struct kobject *root, struct device_node *dn,
+				int index)
 {
-	struct scom_debug_entry *ent;
-	struct dentry *dir;
+	struct scom_chip_dir *dir;
+	int rc;
 
-	ent = kzalloc(sizeof(*ent), GFP_KERNEL);
-	if (!ent)
+	dir = kzalloc(sizeof(*dir), GFP_KERNEL);
+	if (!dir)
 		return -ENOMEM;
-
-	ent->dn = of_node_get(dn);
-	ent->map = SCOM_MAP_INVALID;
-	spin_lock_init(&ent->lock);
-	snprintf(ent->name, 8, "scom%d", i);
-	ent->blob.data = (void*) dn->full_name;
-	ent->blob.size = strlen(dn->full_name);
-
-	dir = debugfs_create_dir(ent->name, root);
-	if (!dir) {
-		of_node_put(dn);
-		kfree(ent);
-		return -1;
+	dir->devnode = of_node_get(dn);
+
+	rc = kobject_init_and_add(&dir->kobj, &scom_dir_type,
+				  root, "%08x", index);
+	if (rc) {
+		pr_err("scom: Failed to create kobj for %s, err %d\n",
+		       dn->full_name, rc);
+		return rc;
 	}
 
-	debugfs_create_file("addr", 0600, dir, ent, &scom_addr_fops);
-	debugfs_create_file("value", 0600, dir, ent, &scom_val_fops);
-	debugfs_create_blob("devspec", 0400, dir, &ent->blob);
+	rc = sysfs_create_bin_file(&dir->kobj, &scom_access_attr);
+	if (rc) {
+		pr_err("scom: Failed to create access file for %s, err %d\n",
+		       dn->full_name, rc);
+		return rc;
+	}
 
 	return 0;
 }
 
-static int scom_debug_init(void)
+static int scom_sysfs_init(void)
 {
 	struct device_node *dn;
-	struct dentry *root;
-	int i, rc;
+	struct kobject *root = NULL;
+	int i;
 
-	root = debugfs_create_dir("scom", powerpc_debugfs_root);
-	if (!root)
-		return -1;
-
-	i = rc = 0;
+	i = 0;
 	for_each_node_with_property(dn, "scom-controller") {
 		int id = of_get_ibm_chip_id(dn);
 		if (id == -1)
 			id = i;
-		rc |= scom_debug_init_one(root, dn, id);
+		if (!root)
+			root = kobject_create_and_add("scom", firmware_kobj);
+		if (!root) {
+			pr_err("scom: Failed to create sysfs root\n");
+			return -ENOMEM;
+		}
+		scom_sysfs_init_chip(root, dn, id);
 		i++;
 	}
-
-	return rc;
+	return 0;
 }
-device_initcall(scom_debug_init);
-#endif /* CONFIG_SCOM_DEBUGFS */
+subsys_initcall(scom_sysfs_init);
+
+#endif /* CONFIG_SCOM_SYSFS */