[v3] ACPI: properties: expose device properties through sysfs
diff mbox series

Message ID 1536615877-34136-2-git-send-email-curt@cumulusnetworks.com
State New
Headers show
Series
  • [v3] ACPI: properties: expose device properties through sysfs
Related show

Commit Message

Curt Brune Sept. 10, 2018, 9:44 p.m. UTC
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@cumulusnetworks.com>.  I only massaged it a little to
work with a more recent kernel version.

Signed-off-by: Curt Brune <curt@cumulusnetworks.com>
---
 Documentation/ABI/testing/sysfs-bus-acpi |  52 ++++
 drivers/acpi/device_sysfs.c              | 442 +++++++++++++++++++++++++++++++
 drivers/acpi/internal.h                  |   1 +
 drivers/acpi/scan.c                      |   2 +
 include/acpi/acpi_bus.h                  |   1 +
 5 files changed, 498 insertions(+)

Patch
diff mbox series

diff --git a/Documentation/ABI/testing/sysfs-bus-acpi b/Documentation/ABI/testing/sysfs-bus-acpi
index e7898cfe5fb1..d649baec7ffa 100644
--- a/Documentation/ABI/testing/sysfs-bus-acpi
+++ b/Documentation/ABI/testing/sysfs-bus-acpi
@@ -93,3 +93,55 @@  Description:
 		hardware, if the _HRV control method is present.  It is mostly
 		useful for non-PCI devices because lspci can list the hardware
 		version for PCI devices.
+
+What:		/sys/bus/acpi/devices/.../properties
+Date:		September, 2018
+Contact:	Curt Brune <curt@cumulusnetworks.com>
+Description:
+
+		Export ACPI device properties (DSD) to user space.
+		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 545e91420cde..710b5a6d290f 100644
--- a/drivers/acpi/device_sysfs.c
+++ b/drivers/acpi/device_sysfs.c
@@ -26,6 +26,46 @@ 
 
 #include "internal.h"
 
+static LIST_HEAD(acpi_deferred_property_list);
+
+/**
+ * struct acpi_deferred_property_link - link list node
+ * @adev: ACPI device, parent of the property
+ * @propname: property name
+ * @list: link list head
+ *
+ * Some acpi properties refer to devices that are not yet
+ * instantiated.  Maintain a list of these properties and process them
+ * later.
+ */
+struct acpi_deferred_property_link {
+	struct acpi_device *adev;
+	char *propname;
+	struct list_head list;
+};
+
+/**
+ * struct acpi_property_attribute - property attributes
+ * @attr: sysfs attribute
+ * @name: property name
+ * @ref_idx: index for reference type properties
+ * @show: sysfs read file callback
+ *
+ * A collection of acpi proptery attributes used by the sysfs
+ * infrastructure.
+ */
+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};
@@ -508,6 +548,402 @@  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 fwnode_reference_args *args)
+{
+	return acpi_node_get_property_reference(&adev->fwnode,
+						propname,
+						index,
+						args);
+}
+
+/**
+ * __acpi_property_show_ref_args - show property reference args
+ * @adev: ACPI device, parent of the property
+ * @name: property name
+ * @idx: property reference index
+ * @buf: sysfs output buffer
+ *
+ * Show the property's reference arguments as unsigned integers.
+ */
+static ssize_t __acpi_property_show_ref_args(struct acpi_device *adev,
+					     char *name, int idx, char *buf)
+{
+	struct fwnode_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;
+}
+
+/**
+ * __acpi_property_print_scalar - print simple scalar property values
+ * @buf: sysfs output buffer
+ * @obj: ACPI object
+ * @size: size of @buf
+ *
+ * Output the simple ACPI scalar types: string and integer.
+ */
+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;
+
+		/* Process all elements of the package */
+		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;
+}
+
+/**
+ * acpi_property_defer - defer adding a property value
+ * @adev: ACPI device
+ * @property: device property value
+ *
+ * Add a property to the deferred property list for later processing.
+ *
+ */
+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;
+
+	/*
+	 * Properties of type ACPI_TYPE_PACKAGE and
+	 * ACPI_TYPE_LOCAL_REFERENCE *might* refer to ACPI devices
+	 * that are not yet instantiated.  To be safe, defer the
+	 * creation of the sysfs nodes for these properies until after
+	 * the entire ACPI bus is processed.
+	 */
+	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 fwnode_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_property_add_deferred - Process deferred ACPI properties.
+ *
+ * Process the deferred ACPI properties that were gathered during the
+ * initial acpi bus scan, exporting them via sysfs.
+ */
+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 fwnode_reference_args args;
+		int err;
+
+		scanned++;
+
+		idx = 0;
+		while (true) {
+			char *sysfs_name;
+			struct acpi_device *adev;
+
+			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;
+
+			adev = to_acpi_device_node(args.fwnode);
+			err = sysfs_create_link(&link->adev->data.kobj,
+						&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;
+}
+
 /**
  * acpi_device_setup_files - Create sysfs attributes of an ACPI device.
  * @dev: ACPI device object.
@@ -596,6 +1032,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;
 }
@@ -608,6 +1047,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 530a3f675490..a593e3d64e63 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -233,6 +233,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 e1b6231cfa1c..e44353258b46 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -2059,6 +2059,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 ba4dd54f2c82..016ea60f7f8f 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -348,6 +348,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;