From patchwork Mon Sep 10 21:44:37 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Curt Brune X-Patchwork-Id: 10594789 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5E0F1112B for ; Mon, 10 Sep 2018 21:44:43 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4819A28E9B for ; Mon, 10 Sep 2018 21:44:43 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 3C07529237; Mon, 10 Sep 2018 21:44:43 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2446C28E9B for ; Mon, 10 Sep 2018 21:44:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726217AbeIKCko (ORCPT ); Mon, 10 Sep 2018 22:40:44 -0400 Received: from mail-pg1-f195.google.com ([209.85.215.195]:44560 "EHLO mail-pg1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726137AbeIKCkn (ORCPT ); Mon, 10 Sep 2018 22:40:43 -0400 Received: by mail-pg1-f195.google.com with SMTP id r1-v6so11114585pgp.11 for ; Mon, 10 Sep 2018 14:44:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cumulusnetworks.com; s=google; h=from:to:cc:subject:date:message-id; bh=g07bNpyJNGMthHQ0Zt16yCMnkAqopgc+yDkCDMOvY/M=; b=B4nDdWb8tca8kaygDtfD68YWwNvG7hXb2uUsadk2ZYTm38WsXZJirV10Cf6Y2E0jqz POOu9szKLnPHGfZAZ/OtQO2r9XGr/YBeHpoNnLhcjmuFhBctcYIxiB6lifQCKOcpj4aU 83gfsxGLXNt+llwLhR7zKl/ZoVrJeMuseEopI= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=g07bNpyJNGMthHQ0Zt16yCMnkAqopgc+yDkCDMOvY/M=; b=kBGZBVth+7rl5eOnvG1EfC0RwYEufbt8sVsvHZUbTH4/L3CcCvoPfq2gmhqVQfHArs m0aNaWjXyHeT0lGtMFUyVi+tMPxdJMXZFqmGWAUWZiAEONbgqldw6rxA9A6bLl62V6JH SMiF7FIplBgV/kjjL9RugvGVqbVOhoKTTfkBHjnPO0iRaSTLOY+GvebjF7F/doGeFqrj AHWQO+XuRFH6fOfqvGzkCe3VfXZjO0NQ6ymgNZcC6XlVN+W9zfnjWPYZv1s5353VZyf6 IL1DSn8qgdWZQkHtDguKYlzb1/gf5+u2HSSwHrwodu4XX3d/GYEQh6iuAEbd48vb8ZNM Jb6A== X-Gm-Message-State: APzg51Bz49btryxUoObmBzoC6ZaDxUOue2ucj+cz3J3tAMc2SLJu1nZn dd8jRNU20ggTuj9JruUyeoHF4Q== X-Google-Smtp-Source: ANB0VdZ9n81S5GNaxCbOQ3jGrGom3w12WezSMaClqVTTUJnM8DB9SR/heAqWSPWk4XvLgVu4Y/q2Fw== X-Received: by 2002:a63:1d47:: with SMTP id d7-v6mr24881732pgm.180.1536615880092; Mon, 10 Sep 2018 14:44:40 -0700 (PDT) Received: from monster-08.mvlab.cumulusnetworks.com. (fw.cumulusnetworks.com. [216.129.126.126]) by smtp.googlemail.com with ESMTPSA id r81-v6sm22896129pfa.18.2018.09.10.14.44.39 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 10 Sep 2018 14:44:39 -0700 (PDT) From: Curt Brune To: rafael.j.wysocki@intel.com Cc: sakari.ailus@linux.intel.com, dan.carpenter@oracle.com, linux-acpi@vger.kernel.org Subject: [PATCH v3] ACPI: properties: expose device properties through sysfs Date: Mon, 10 Sep 2018 14:44:37 -0700 Message-Id: <1536615877-34136-2-git-send-email-curt@cumulusnetworks.com> X-Mailer: git-send-email 2.1.4 Sender: linux-acpi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-acpi@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This change exposes ACPI device properties via sysfs under /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 _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 , _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 . I only massaged it a little to work with a more recent kernel version. Signed-off-by: Curt Brune --- 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(+) 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 +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 + _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 + , _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;