[PATCH] ACPI: properties: expose device properties through sysfs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This change exposes ACPI device properties via sysfs under
<device>/firmware_node/properties/*.  This is a logical extension of
the changes that expose _ADR, _STR, _HID, etc... in the same
directory.

Integer properties are represented by a file containing a single hex
number (printf format 0x%llx).

String properties are represented by a file containing a single
string.

Arrays of integers properties are represented by a file containing a
space delimited string of hex numbers (printf format 0x%llx).

Example:
  0x1 0x2 0x3

Arrays of strings properties are represented by a file containing a
newline delimited string of escaped strings (printf format %*pE).

Example:
  foo
  bar
  has\nnewline

Device reference properties are represented by a symlink to the
referenced device plus a <property>_args node containing an integer
array of reference arguments.  In the case of multiple local
references, the references at index 1..N are represented the same way
using the naming convention <property><N>, <property><N>_args.

Examples:
  (a property containing a single reference)

  phy-handle -> ../../../device:00
  phy-handle_args: 0x0

  (a property containing three references)

  ref -> ../device:01
  ref_args: 0x1 0x2
  ref1 -> ../device:02
  ref1_args: 0x3 0x4
  ref2 -> ../device:03
  ref2_args: 0x5 0x6

The bulk of the credit for this idea and implementation goes to Dustin
Byford <dustin@xxxxxxxxxxxxxxxxxxx>.  I only massaged it a little to
work with a more recent kernel version.

Signed-off-by: Curt Brune <curt@xxxxxxxxxxxxxxxxxxx>
---
 Documentation/acpi/dsd/sysfs-properties.txt |  45 ++++
 drivers/acpi/device_sysfs.c                 | 379 ++++++++++++++++++++++++++++
 drivers/acpi/internal.h                     |   1 +
 drivers/acpi/scan.c                         |   2 +
 include/acpi/acpi_bus.h                     |   1 +
 5 files changed, 428 insertions(+)
 create mode 100644 Documentation/acpi/dsd/sysfs-properties.txt

diff --git a/Documentation/acpi/dsd/sysfs-properties.txt b/Documentation/acpi/dsd/sysfs-properties.txt
new file mode 100644
index 000000000000..c70fd5b52d47
--- /dev/null
+++ b/Documentation/acpi/dsd/sysfs-properties.txt
@@ -0,0 +1,45 @@
+ACPI device properties are exported via sysfs under
+<device>/firmware_node/properties/*.  This is a logical extension of
+the changes that expose _ADR, _STR, _HID, etc... in the same
+directory.
+
+Integer properties are represented by a file containing a single hex
+number (printf format 0x%llx).
+
+String properties are represented by a file containing a single
+string.
+
+Arrays of integers properties are represented by a file containing a
+space delimited string of hex numbers (printf format 0x%llx).
+
+Example:
+  0x1 0x2 0x3
+
+Arrays of strings properties are represented by a file containing a
+newline delimited string of escaped strings (printf format %*pE).
+
+Example:
+  foo
+  bar
+  has\nnewline
+
+Device reference properties are represented by a symlink to the
+referenced device plus a <property>_args node containing an integer
+array of reference arguments.  In the case of multiple local
+references, the references at index 1..N are represented the same way
+using the naming convention <property><N>, <property><N>_args.
+
+Examples:
+  (a property containing a single reference)
+
+  phy-handle -> ../../../device:00
+  phy-handle_args: 0x0
+
+  (a property containing three references)
+
+  ref -> ../device:01
+  ref_args: 0x1 0x2
+  ref1 -> ../device:02
+  ref1_args: 0x3 0x4
+  ref2 -> ../device:03
+  ref2_args: 0x5 0x6
diff --git a/drivers/acpi/device_sysfs.c b/drivers/acpi/device_sysfs.c
index 24418932612e..ffc05b975cd2 100644
--- a/drivers/acpi/device_sysfs.c
+++ b/drivers/acpi/device_sysfs.c
@@ -26,6 +26,25 @@
 
 #include "internal.h"
 
+static LIST_HEAD(acpi_deferred_property_list);
+struct acpi_deferred_property_link {
+	struct acpi_device *adev;
+	char *propname;
+	struct list_head list;
+};
+
+struct acpi_property_attribute {
+	struct attribute attr;
+	char *name;
+	int ref_idx;
+	ssize_t (*show)(struct kobject *kobj, struct device_attribute *attr,
+			char *buf);
+};
+
+#define to_acpi_property_attr(x) \
+	container_of(x, struct acpi_property_attribute, attr)
+
+
 static ssize_t acpi_object_path(acpi_handle handle, char *buf)
 {
 	struct acpi_buffer path = {ACPI_ALLOCATE_BUFFER, NULL};
@@ -504,6 +523,360 @@ static ssize_t status_show(struct device *dev, struct device_attribute *attr,
 }
 static DEVICE_ATTR_RO(status);
 
+static inline
+int __acpi_dev_get_property_reference(const struct acpi_device *adev,
+				      const char *propname, int index,
+				      struct acpi_reference_args *args)
+{
+	return acpi_node_get_property_reference(&adev->fwnode,
+						propname,
+						index,
+						args);
+}
+
+static ssize_t __acpi_property_show_ref_args(struct acpi_device *adev,
+					     char *name, int idx, char *buf)
+{
+	struct acpi_reference_args args;
+	char *out;
+	int err;
+	int arg;
+
+	err = __acpi_dev_get_property_reference(adev, name, idx, &args);
+	if (err)
+		return err;
+
+	out = buf;
+	for (arg = 0; arg < args.nargs; arg++) {
+		err = sprintf(out, "0x%llx ", args.args[arg]);
+		if (err < 0)
+			return err;
+
+		out += err;
+	}
+
+	*(out - 1) = '\n';
+	return out - buf;
+}
+
+static ssize_t __acpi_property_print_scalar(char *buf,
+					    const union acpi_object *obj,
+					    size_t size)
+{
+	switch (obj->type) {
+	case ACPI_TYPE_INTEGER:
+		return snprintf(buf, size, "0x%llx ", obj->integer.value);
+	case ACPI_TYPE_STRING:
+		return snprintf(buf, size, "%*pE\n",
+				(int)strlen(obj->string.pointer),
+				obj->string.pointer);
+	default:
+		return -EPROTO;
+	}
+}
+
+static ssize_t __acpi_property_show(struct acpi_device *adev, char *propname,
+				    char *buf)
+{
+	static const int max = PAGE_SIZE - 2;
+	const union acpi_object *obj;
+	char *out;
+	int err;
+
+	err = acpi_dev_get_property(adev, propname, ACPI_TYPE_ANY, &obj);
+	if (err)
+		return err;
+
+	out = buf;
+	if (obj->type == ACPI_TYPE_PACKAGE) {
+		int element;
+
+		for (element = 0; element < obj->package.count; element++) {
+			err = __acpi_property_print_scalar(out,
+				&obj->package.elements[element],
+				max - (out - buf));
+			if (err < 0)
+				return err;
+			out += err;
+		}
+	} else {
+		err = __acpi_property_print_scalar(out, obj, max);
+		if (err < 0)
+			return err;
+
+		out += err;
+	}
+
+	*(out - 1) = '\n';
+	return out - buf;
+}
+
+static ssize_t acpi_property_show(struct kobject *kobj,
+				  struct attribute *attr,
+				  char *buf)
+{
+	struct device *dev = kobj_to_dev(kobj->parent);
+	struct acpi_device *adev = to_acpi_device(dev);
+	struct acpi_property_attribute *prop_attr = to_acpi_property_attr(attr);
+
+	if (prop_attr->ref_idx >= 0)
+		return __acpi_property_show_ref_args(adev, prop_attr->name,
+						     prop_attr->ref_idx, buf);
+	else
+		return __acpi_property_show(adev, prop_attr->name, buf);
+}
+
+static const struct sysfs_ops acpi_property_sysfs_ops = {
+	.show	= acpi_property_show,
+};
+
+static struct kobj_type acpi_property_ktype = {
+	.sysfs_ops = &acpi_property_sysfs_ops,
+};
+
+static int acpi_property_create_file(struct acpi_device *adev,
+				     char *propname, char *filename,
+				     int ref_idx)
+{
+	struct acpi_property_attribute *prop_attr;
+	int err;
+
+	prop_attr = devm_kzalloc(&adev->dev, sizeof(*prop_attr), GFP_KERNEL);
+	if (!prop_attr)
+		return -ENOMEM;
+
+	prop_attr->name = propname;
+	prop_attr->ref_idx = ref_idx;
+	sysfs_attr_init(&prop_attr->attr);
+	prop_attr->attr.name = filename;
+	prop_attr->attr.mode = 0444;
+
+	err = sysfs_create_file(&adev->data.kobj, &prop_attr->attr);
+	if (err) {
+		dev_err(&adev->dev, "failed to create property file: %s\n",
+			filename);
+		devm_kfree(&adev->dev, prop_attr);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+int acpi_property_add_deferred(void)
+{
+	struct acpi_deferred_property_link *link, *tmp;
+	int idx;
+	int resolved = 0;
+	int scanned = 0;
+
+	if (list_empty(&acpi_deferred_property_list))
+		return 0;
+
+	list_for_each_entry_safe(link, tmp, &acpi_deferred_property_list,
+				 list) {
+		struct acpi_reference_args args;
+		int err;
+
+		scanned++;
+
+		idx = 0;
+		while (true) {
+			char *sysfs_name;
+			err = __acpi_dev_get_property_reference(link->adev,
+								link->propname,
+								idx,
+								&args);
+			if (err)
+				break;
+
+			if (idx == 0)
+				sysfs_name = devm_kasprintf(&link->adev->dev,
+							    GFP_KERNEL, "%s",
+							    link->propname);
+			else
+				sysfs_name = devm_kasprintf(&link->adev->dev,
+							    GFP_KERNEL, "%s%u",
+							    link->propname,
+							    idx);
+			if (!sysfs_name)
+				return -ENOMEM;
+
+			err = sysfs_create_link(&link->adev->data.kobj,
+						&args.adev->dev.kobj,
+						sysfs_name);
+			if (err)
+				return err;
+
+			dev_dbg(&link->adev->dev,
+				"created deferred property link: %s\n",
+				sysfs_name);
+
+			if (args.nargs > 0) {
+				char *sysfs_args_name;
+
+				sysfs_args_name = devm_kasprintf(&link->adev->dev,
+								 GFP_KERNEL,
+								 "%s_args",
+								 sysfs_name);
+				if (!sysfs_args_name)
+					return -ENOMEM;
+
+				err = acpi_property_create_file(link->adev,
+								link->propname,
+								sysfs_args_name,
+								idx);
+				if (err)
+					return err;
+
+				dev_dbg(&link->adev->dev,
+					"created deferred property args: %s\n",
+					sysfs_args_name);
+			}
+
+			idx++;
+		}
+		list_del(&link->list);
+		devm_kfree(&link->adev->dev, link);
+		resolved++;
+	}
+
+	pr_debug("acpi: resolved %d of %d deferred property links\n",
+		 resolved, scanned);
+
+	return resolved;
+}
+
+static int acpi_property_defer(struct acpi_device *adev,
+			       const union acpi_object *property)
+{
+	char *propname;
+	struct acpi_deferred_property_link *link;
+
+	propname = property->package.elements[0].string.pointer;
+
+	dev_dbg(&adev->dev,
+		"deferring property add for ref %s\n", propname);
+
+	link = devm_kmalloc(&adev->dev, sizeof(*link), GFP_KERNEL);
+	if (!link)
+		return -ENOMEM;
+
+	link->adev = adev;
+	link->propname = propname;
+	list_add_tail(&link->list,
+		      &acpi_deferred_property_list);
+
+	return 0;
+}
+
+static int acpi_property_add(struct acpi_device *adev,
+			     const union acpi_object *property)
+{
+	char *propname;
+
+	propname = property->package.elements[0].string.pointer;
+
+	if ((property->package.elements[1].type == ACPI_TYPE_PACKAGE) ||
+	    (property->package.elements[1].type == ACPI_TYPE_LOCAL_REFERENCE))
+		return acpi_property_defer(adev, property);
+	else
+		return acpi_property_create_file(adev, propname, propname, -1);
+}
+
+static void acpi_property_remove_attr(struct acpi_device *adev,
+				      const char *property)
+{
+	struct attribute attr = { 0 };
+	const union acpi_object *obj;
+	struct acpi_reference_args args;
+	char *sysfs_name;
+	int idx;
+	int err;
+
+	err = acpi_dev_get_property(adev, property, ACPI_TYPE_ANY, &obj);
+	if (err)
+		return;
+
+	attr.name = property;
+	sysfs_remove_file(&adev->data.kobj, &attr);
+
+	idx = 0;
+	while (__acpi_dev_get_property_reference(adev, property, idx,
+						 &args) == 0) {
+		if (idx == 0)
+			sysfs_name = kasprintf(GFP_KERNEL, "%s", property);
+		else
+			sysfs_name = kasprintf(GFP_KERNEL, "%s%u", property,
+					       idx);
+		if (!sysfs_name)
+			continue;
+
+		sysfs_remove_link(&adev->data.kobj, sysfs_name);
+		kfree(sysfs_name);
+
+		if (args.nargs > 0) {
+			attr.name = kasprintf(GFP_KERNEL, "%s_args",
+					      sysfs_name);
+			if (!attr.name)
+				continue;
+
+			sysfs_remove_file(&adev->data.kobj, &attr);
+			kfree(attr.name);
+		}
+
+		idx++;
+	}
+}
+
+static int acpi_add_properties(struct acpi_device *adev)
+{
+	const union acpi_object *properties;
+	int err;
+	int i;
+
+	if (!adev->data.pointer || !adev->data.properties)
+		return -EINVAL;
+
+	properties = adev->data.properties;
+	err = kobject_init_and_add(&adev->data.kobj, &acpi_property_ktype,
+				   &adev->dev.kobj, "properties");
+	if (err)
+		return err;
+
+	for (i = 0; i < properties->package.count; i++) {
+		const union acpi_object *property;
+
+		property = &properties->package.elements[i];
+		err = acpi_property_add(adev, property);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static void acpi_remove_properties(struct acpi_device *adev)
+{
+	const union acpi_object *properties;
+	int i;
+
+	if (!adev->data.pointer || !adev->data.properties)
+		return;
+
+	properties = adev->data.properties;
+	for (i = 0; i < properties->package.count; i++) {
+		const union acpi_object *property;
+		const union acpi_object *propname;
+
+		property = &properties->package.elements[i];
+		propname = &property->package.elements[0];
+
+		acpi_property_remove_attr(adev, propname->string.pointer);
+	}
+
+	kobject_put(&adev->data.kobj);
+}
+
 /**
  * acpi_device_setup_files - Create sysfs attributes of an ACPI device.
  * @dev: ACPI device object.
@@ -592,6 +965,9 @@ int acpi_device_setup_files(struct acpi_device *dev)
 
 	acpi_expose_nondev_subnodes(&dev->dev.kobj, &dev->data);
 
+	if (dev->data.of_compatible)
+		acpi_add_properties(dev);
+
 end:
 	return result;
 }
@@ -604,6 +980,9 @@ void acpi_device_remove_files(struct acpi_device *dev)
 {
 	acpi_hide_nondev_subnodes(&dev->data);
 
+	if (dev->data.of_compatible)
+		acpi_remove_properties(dev);
+
 	if (dev->flags.power_manageable) {
 		device_remove_file(&dev->dev, &dev_attr_power_state);
 		if (dev->power.flags.power_resources)
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index 4361c4415b4f..d0d21cf77e32 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -231,6 +231,7 @@ static inline void suspend_nvs_restore(void) {}
 
 void acpi_init_properties(struct acpi_device *adev);
 void acpi_free_properties(struct acpi_device *adev);
+int acpi_property_add_deferred(void);
 
 #ifdef CONFIG_X86
 void acpi_extract_apple_properties(struct acpi_device *adev);
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 602f8ff212f2..84b8257d38a4 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -2009,6 +2009,8 @@ int acpi_bus_scan(acpi_handle handle)
 				    acpi_bus_check_add, NULL, NULL, &device);
 
 	if (device) {
+		while (acpi_property_add_deferred() > 0)
+			;
 		acpi_bus_attach(device);
 		return 0;
 	}
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index fa1505292f6c..73041ceb7736 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -344,6 +344,7 @@ struct acpi_device_physical_node {
 
 /* ACPI Device Specific Data (_DSD) */
 struct acpi_device_data {
+	struct kobject kobj;
 	const union acpi_object *pointer;
 	const union acpi_object *properties;
 	const union acpi_object *of_compatible;
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux