@@ -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
@@ -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)
@@ -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);
@@ -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;
}
@@ -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;
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(+)