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