2019-05-27 14:55:21 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2015-07-18 04:53:43 +08:00
|
|
|
/*
|
|
|
|
* drivers/acpi/device_sysfs.c - ACPI device sysfs attributes and modalias.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2015, Intel Corp.
|
|
|
|
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
|
|
|
* Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
|
|
|
|
*
|
|
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
*
|
|
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/export.h>
|
|
|
|
#include <linux/nls.h>
|
|
|
|
|
|
|
|
#include "internal.h"
|
|
|
|
|
2015-08-27 10:37:19 +08:00
|
|
|
static ssize_t acpi_object_path(acpi_handle handle, char *buf)
|
|
|
|
{
|
|
|
|
struct acpi_buffer path = {ACPI_ALLOCATE_BUFFER, NULL};
|
|
|
|
int result;
|
|
|
|
|
|
|
|
result = acpi_get_name(handle, ACPI_FULL_PATHNAME, &path);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
2016-05-01 00:03:39 +08:00
|
|
|
result = sprintf(buf, "%s\n", (char *)path.pointer);
|
2015-08-27 10:37:19 +08:00
|
|
|
kfree(path.pointer);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct acpi_data_node_attr {
|
|
|
|
struct attribute attr;
|
|
|
|
ssize_t (*show)(struct acpi_data_node *, char *);
|
|
|
|
ssize_t (*store)(struct acpi_data_node *, const char *, size_t count);
|
|
|
|
};
|
|
|
|
|
|
|
|
#define DATA_NODE_ATTR(_name) \
|
|
|
|
static struct acpi_data_node_attr data_node_##_name = \
|
|
|
|
__ATTR(_name, 0444, data_node_show_##_name, NULL)
|
|
|
|
|
|
|
|
static ssize_t data_node_show_path(struct acpi_data_node *dn, char *buf)
|
|
|
|
{
|
2016-11-22 07:02:19 +08:00
|
|
|
return dn->handle ? acpi_object_path(dn->handle, buf) : 0;
|
2015-08-27 10:37:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
DATA_NODE_ATTR(path);
|
|
|
|
|
|
|
|
static struct attribute *acpi_data_node_default_attrs[] = {
|
|
|
|
&data_node_path.attr,
|
|
|
|
NULL
|
|
|
|
};
|
2021-12-28 21:14:23 +08:00
|
|
|
ATTRIBUTE_GROUPS(acpi_data_node_default);
|
2015-08-27 10:37:19 +08:00
|
|
|
|
|
|
|
#define to_data_node(k) container_of(k, struct acpi_data_node, kobj)
|
|
|
|
#define to_attr(a) container_of(a, struct acpi_data_node_attr, attr)
|
|
|
|
|
|
|
|
static ssize_t acpi_data_node_attr_show(struct kobject *kobj,
|
|
|
|
struct attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct acpi_data_node *dn = to_data_node(kobj);
|
|
|
|
struct acpi_data_node_attr *dn_attr = to_attr(attr);
|
|
|
|
|
|
|
|
return dn_attr->show ? dn_attr->show(dn, buf) : -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct sysfs_ops acpi_data_node_sysfs_ops = {
|
|
|
|
.show = acpi_data_node_attr_show,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void acpi_data_node_release(struct kobject *kobj)
|
|
|
|
{
|
|
|
|
struct acpi_data_node *dn = to_data_node(kobj);
|
2021-03-27 20:08:23 +08:00
|
|
|
|
2015-08-27 10:37:19 +08:00
|
|
|
complete(&dn->kobj_done);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct kobj_type acpi_data_node_ktype = {
|
|
|
|
.sysfs_ops = &acpi_data_node_sysfs_ops,
|
2021-12-28 21:14:23 +08:00
|
|
|
.default_groups = acpi_data_node_default_groups,
|
2015-08-27 10:37:19 +08:00
|
|
|
.release = acpi_data_node_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void acpi_expose_nondev_subnodes(struct kobject *kobj,
|
|
|
|
struct acpi_device_data *data)
|
|
|
|
{
|
|
|
|
struct list_head *list = &data->subnodes;
|
|
|
|
struct acpi_data_node *dn;
|
|
|
|
|
|
|
|
if (list_empty(list))
|
|
|
|
return;
|
|
|
|
|
|
|
|
list_for_each_entry(dn, list, sibling) {
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
init_completion(&dn->kobj_done);
|
|
|
|
ret = kobject_init_and_add(&dn->kobj, &acpi_data_node_ktype,
|
2016-01-08 03:24:29 +08:00
|
|
|
kobj, "%s", dn->name);
|
2016-11-22 07:02:19 +08:00
|
|
|
if (!ret)
|
2015-08-27 10:37:19 +08:00
|
|
|
acpi_expose_nondev_subnodes(&dn->kobj, &dn->data);
|
2016-11-22 07:02:19 +08:00
|
|
|
else if (dn->handle)
|
|
|
|
acpi_handle_err(dn->handle, "Failed to expose (%d)\n", ret);
|
2015-08-27 10:37:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void acpi_hide_nondev_subnodes(struct acpi_device_data *data)
|
|
|
|
{
|
|
|
|
struct list_head *list = &data->subnodes;
|
|
|
|
struct acpi_data_node *dn;
|
|
|
|
|
|
|
|
if (list_empty(list))
|
|
|
|
return;
|
|
|
|
|
|
|
|
list_for_each_entry_reverse(dn, list, sibling) {
|
|
|
|
acpi_hide_nondev_subnodes(&dn->data);
|
|
|
|
kobject_put(&dn->kobj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
/**
|
|
|
|
* create_pnp_modalias - Create hid/cid(s) string for modalias and uevent
|
|
|
|
* @acpi_dev: ACPI device object.
|
|
|
|
* @modalias: Buffer to print into.
|
|
|
|
* @size: Size of the buffer.
|
|
|
|
*
|
|
|
|
* Creates hid/cid(s) string needed for modalias and uevent
|
|
|
|
* e.g. on a device with hid:IBM0001 and cid:ACPI0001 you get:
|
|
|
|
* char *modalias: "acpi:IBM0001:ACPI0001"
|
|
|
|
* Return: 0: no _HID and no _CID
|
|
|
|
* -EINVAL: output error
|
|
|
|
* -ENOMEM: output is truncated
|
2021-03-27 20:08:23 +08:00
|
|
|
*/
|
2015-07-18 04:53:43 +08:00
|
|
|
static int create_pnp_modalias(struct acpi_device *acpi_dev, char *modalias,
|
|
|
|
int size)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
int count;
|
|
|
|
struct acpi_hardware_id *id;
|
|
|
|
|
2017-10-16 03:24:49 +08:00
|
|
|
/* Avoid unnecessarily loading modules for non present devices. */
|
|
|
|
if (!acpi_device_is_present(acpi_dev))
|
|
|
|
return 0;
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
/*
|
|
|
|
* Since we skip ACPI_DT_NAMESPACE_HID from the modalias below, 0 should
|
|
|
|
* be returned if ACPI_DT_NAMESPACE_HID is the only ACPI/PNP ID in the
|
|
|
|
* device's list.
|
|
|
|
*/
|
|
|
|
count = 0;
|
|
|
|
list_for_each_entry(id, &acpi_dev->pnp.ids, list)
|
|
|
|
if (strcmp(id->id, ACPI_DT_NAMESPACE_HID))
|
|
|
|
count++;
|
|
|
|
|
|
|
|
if (!count)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
len = snprintf(modalias, size, "acpi:");
|
|
|
|
if (len <= 0)
|
|
|
|
return len;
|
|
|
|
|
|
|
|
size -= len;
|
|
|
|
|
|
|
|
list_for_each_entry(id, &acpi_dev->pnp.ids, list) {
|
|
|
|
if (!strcmp(id->id, ACPI_DT_NAMESPACE_HID))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
count = snprintf(&modalias[len], size, "%s:", id->id);
|
|
|
|
if (count < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (count >= size)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
len += count;
|
|
|
|
size -= count;
|
|
|
|
}
|
|
|
|
modalias[len] = '\0';
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* create_of_modalias - Creates DT compatible string for modalias and uevent
|
|
|
|
* @acpi_dev: ACPI device object.
|
|
|
|
* @modalias: Buffer to print into.
|
|
|
|
* @size: Size of the buffer.
|
|
|
|
*
|
|
|
|
* Expose DT compatible modalias as of:NnameTCcompatible. This function should
|
|
|
|
* only be called for devices having ACPI_DT_NAMESPACE_HID in their list of
|
|
|
|
* ACPI/PNP IDs.
|
|
|
|
*/
|
|
|
|
static int create_of_modalias(struct acpi_device *acpi_dev, char *modalias,
|
|
|
|
int size)
|
|
|
|
{
|
|
|
|
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
|
|
|
|
const union acpi_object *of_compatible, *obj;
|
2019-03-12 00:41:03 +08:00
|
|
|
acpi_status status;
|
2015-07-18 04:53:43 +08:00
|
|
|
int len, count;
|
|
|
|
int i, nval;
|
|
|
|
char *c;
|
|
|
|
|
2019-03-12 00:41:03 +08:00
|
|
|
status = acpi_get_name(acpi_dev->handle, ACPI_SINGLE_NAME, &buf);
|
|
|
|
if (ACPI_FAILURE(status))
|
|
|
|
return -ENODEV;
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
/* DT strings are all in lower case */
|
|
|
|
for (c = buf.pointer; *c != '\0'; c++)
|
|
|
|
*c = tolower(*c);
|
|
|
|
|
|
|
|
len = snprintf(modalias, size, "of:N%sT", (char *)buf.pointer);
|
|
|
|
ACPI_FREE(buf.pointer);
|
|
|
|
|
|
|
|
if (len <= 0)
|
|
|
|
return len;
|
|
|
|
|
|
|
|
of_compatible = acpi_dev->data.of_compatible;
|
|
|
|
if (of_compatible->type == ACPI_TYPE_PACKAGE) {
|
|
|
|
nval = of_compatible->package.count;
|
|
|
|
obj = of_compatible->package.elements;
|
|
|
|
} else { /* Must be ACPI_TYPE_STRING. */
|
|
|
|
nval = 1;
|
|
|
|
obj = of_compatible;
|
|
|
|
}
|
|
|
|
for (i = 0; i < nval; i++, obj++) {
|
|
|
|
count = snprintf(&modalias[len], size, "C%s",
|
|
|
|
obj->string.pointer);
|
|
|
|
if (count < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (count >= size)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
len += count;
|
|
|
|
size -= count;
|
|
|
|
}
|
|
|
|
modalias[len] = '\0';
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int __acpi_device_uevent_modalias(struct acpi_device *adev,
|
|
|
|
struct kobj_uevent_env *env)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
|
|
|
|
if (!adev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (list_empty(&adev->pnp.ids))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (add_uevent_var(env, "MODALIAS="))
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2021-01-22 20:53:02 +08:00
|
|
|
if (adev->data.of_compatible)
|
|
|
|
len = create_of_modalias(adev, &env->buf[env->buflen - 1],
|
|
|
|
sizeof(env->buf) - env->buflen);
|
|
|
|
else
|
|
|
|
len = create_pnp_modalias(adev, &env->buf[env->buflen - 1],
|
|
|
|
sizeof(env->buf) - env->buflen);
|
2015-07-18 04:53:43 +08:00
|
|
|
if (len < 0)
|
|
|
|
return len;
|
|
|
|
|
|
|
|
env->buflen += len;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* acpi_device_uevent_modalias - uevent modalias for ACPI-enumerated devices.
|
2021-06-15 15:19:14 +08:00
|
|
|
* @dev: Struct device to get ACPI device node.
|
|
|
|
* @env: Environment variables of the kobject uevent.
|
2015-07-18 04:53:43 +08:00
|
|
|
*
|
|
|
|
* Create the uevent modalias field for ACPI-enumerated devices.
|
|
|
|
*
|
|
|
|
* Because other buses do not support ACPI HIDs & CIDs, e.g. for a device with
|
|
|
|
* hid:IBM0001 and cid:ACPI0001 you get: "acpi:IBM0001:ACPI0001".
|
|
|
|
*/
|
|
|
|
int acpi_device_uevent_modalias(struct device *dev, struct kobj_uevent_env *env)
|
|
|
|
{
|
|
|
|
return __acpi_device_uevent_modalias(acpi_companion_match(dev), env);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(acpi_device_uevent_modalias);
|
|
|
|
|
|
|
|
static int __acpi_device_modalias(struct acpi_device *adev, char *buf, int size)
|
|
|
|
{
|
|
|
|
int len, count;
|
|
|
|
|
|
|
|
if (!adev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (list_empty(&adev->pnp.ids))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
len = create_pnp_modalias(adev, buf, size - 1);
|
|
|
|
if (len < 0) {
|
|
|
|
return len;
|
|
|
|
} else if (len > 0) {
|
|
|
|
buf[len++] = '\n';
|
|
|
|
size -= len;
|
|
|
|
}
|
|
|
|
if (!adev->data.of_compatible)
|
|
|
|
return len;
|
|
|
|
|
|
|
|
count = create_of_modalias(adev, buf + len, size - 1);
|
|
|
|
if (count < 0) {
|
|
|
|
return count;
|
|
|
|
} else if (count > 0) {
|
|
|
|
len += count;
|
|
|
|
buf[len++] = '\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* acpi_device_modalias - modalias sysfs attribute for ACPI-enumerated devices.
|
2021-06-15 15:19:14 +08:00
|
|
|
* @dev: Struct device to get ACPI device node.
|
|
|
|
* @buf: The buffer to save pnp_modalias and of_modalias.
|
|
|
|
* @size: Size of buffer.
|
2015-07-18 04:53:43 +08:00
|
|
|
*
|
|
|
|
* Create the modalias sysfs attribute for ACPI-enumerated devices.
|
|
|
|
*
|
|
|
|
* Because other buses do not support ACPI HIDs & CIDs, e.g. for a device with
|
|
|
|
* hid:IBM0001 and cid:ACPI0001 you get: "acpi:IBM0001:ACPI0001".
|
|
|
|
*/
|
|
|
|
int acpi_device_modalias(struct device *dev, char *buf, int size)
|
|
|
|
{
|
|
|
|
return __acpi_device_modalias(acpi_companion_match(dev), buf, size);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(acpi_device_modalias);
|
|
|
|
|
|
|
|
static ssize_t
|
2020-12-17 20:45:36 +08:00
|
|
|
modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
|
2016-05-01 00:03:39 +08:00
|
|
|
{
|
2015-07-18 04:53:43 +08:00
|
|
|
return __acpi_device_modalias(to_acpi_device(dev), buf, 1024);
|
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(modalias);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
static ssize_t real_power_state_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct acpi_device *adev = to_acpi_device(dev);
|
|
|
|
int state;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = acpi_device_get_power(adev, &state);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", acpi_power_state_string(state));
|
|
|
|
}
|
|
|
|
|
2017-12-20 02:15:08 +08:00
|
|
|
static DEVICE_ATTR_RO(real_power_state);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
static ssize_t power_state_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct acpi_device *adev = to_acpi_device(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", acpi_power_state_string(adev->power.state));
|
|
|
|
}
|
|
|
|
|
2017-12-20 02:15:08 +08:00
|
|
|
static DEVICE_ATTR_RO(power_state);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
static ssize_t
|
2020-12-17 20:45:36 +08:00
|
|
|
eject_store(struct device *d, struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2015-07-18 04:53:43 +08:00
|
|
|
{
|
|
|
|
struct acpi_device *acpi_device = to_acpi_device(d);
|
|
|
|
acpi_object_type not_used;
|
|
|
|
acpi_status status;
|
|
|
|
|
|
|
|
if (!count || buf[0] != '1')
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if ((!acpi_device->handler || !acpi_device->handler->hotplug.enabled)
|
|
|
|
&& !acpi_device->driver)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
status = acpi_get_type(acpi_device->handle, ¬_used);
|
|
|
|
if (ACPI_FAILURE(status) || !acpi_device->flags.ejectable)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2021-04-13 06:23:58 +08:00
|
|
|
acpi_dev_get(acpi_device);
|
2015-07-18 04:53:43 +08:00
|
|
|
status = acpi_hotplug_schedule(acpi_device, ACPI_OST_EC_OSPM_EJECT);
|
|
|
|
if (ACPI_SUCCESS(status))
|
|
|
|
return count;
|
|
|
|
|
2021-04-13 06:23:58 +08:00
|
|
|
acpi_dev_put(acpi_device);
|
2015-07-18 04:53:43 +08:00
|
|
|
acpi_evaluate_ost(acpi_device->handle, ACPI_OST_EC_OSPM_EJECT,
|
|
|
|
ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL);
|
|
|
|
return status == AE_NO_MEMORY ? -ENOMEM : -EAGAIN;
|
|
|
|
}
|
|
|
|
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_WO(eject);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
static ssize_t
|
2020-12-17 20:45:36 +08:00
|
|
|
hid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
2016-05-01 00:03:39 +08:00
|
|
|
{
|
2015-07-18 04:53:43 +08:00
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", acpi_device_hid(acpi_dev));
|
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(hid);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
2020-12-17 20:45:36 +08:00
|
|
|
static ssize_t uid_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2015-07-18 04:53:43 +08:00
|
|
|
{
|
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", acpi_dev->pnp.unique_id);
|
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(uid);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
2020-12-17 20:45:36 +08:00
|
|
|
static ssize_t adr_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2015-07-18 04:53:43 +08:00
|
|
|
{
|
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
|
2019-05-01 20:53:22 +08:00
|
|
|
if (acpi_dev->pnp.bus_address > U32_MAX)
|
|
|
|
return sprintf(buf, "0x%016llx\n", acpi_dev->pnp.bus_address);
|
|
|
|
else
|
|
|
|
return sprintf(buf, "0x%08llx\n", acpi_dev->pnp.bus_address);
|
2015-07-18 04:53:43 +08:00
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(adr);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
2020-12-17 20:45:36 +08:00
|
|
|
static ssize_t path_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2015-08-27 10:37:19 +08:00
|
|
|
{
|
2015-07-18 04:53:43 +08:00
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
|
2015-08-27 10:37:19 +08:00
|
|
|
return acpi_object_path(acpi_dev->handle, buf);
|
2015-07-18 04:53:43 +08:00
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(path);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
/* sysfs file that shows description text from the ACPI _STR method */
|
|
|
|
static ssize_t description_show(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
2021-03-27 20:08:23 +08:00
|
|
|
char *buf)
|
|
|
|
{
|
2015-07-18 04:53:43 +08:00
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
int result;
|
|
|
|
|
|
|
|
if (acpi_dev->pnp.str_obj == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The _STR object contains a Unicode identifier for a device.
|
|
|
|
* We need to convert to utf-8 so it can be displayed.
|
|
|
|
*/
|
|
|
|
result = utf16s_to_utf8s(
|
|
|
|
(wchar_t *)acpi_dev->pnp.str_obj->buffer.pointer,
|
|
|
|
acpi_dev->pnp.str_obj->buffer.length,
|
|
|
|
UTF16_LITTLE_ENDIAN, buf,
|
ACPI: sysfs: Fix a buffer overrun problem with description_show()
Currently, a device description can be obtained using ACPI, if the _STR
method exists for a particular device, and then exposed to the userspace
via a sysfs object as a string value.
If the _STR method is available for a given device then the data
(usually a Unicode string) is read and stored in a buffer (of the
ACPI_TYPE_BUFFER type) with a pointer to said buffer cached in the
struct acpi_device_pnp for later access.
The description_show() function is responsible for exposing the device
description to the userspace via a corresponding sysfs object and
internally calls the utf16s_to_utf8s() function with a pointer to the
buffer that contains the Unicode string so that it can be converted from
UTF16 encoding to UTF8 and thus allowing for the value to be safely
stored and later displayed.
When invoking the utf16s_to_utf8s() function, the description_show()
function also sets a limit of the data that can be saved into a provided
buffer as a result of the character conversion to be a total of
PAGE_SIZE, and upon completion, the utf16s_to_utf8s() function returns
an integer value denoting the number of bytes that have been written
into the provided buffer.
Following the execution of the utf16s_to_utf8s() a newline character
will be added at the end of the resulting buffer so that when the value
is read in the userspace through the sysfs object then it would include
newline making it more accessible when working with the sysfs file
system in the shell, etc. Normally, this wouldn't be a problem, but if
the function utf16s_to_utf8s() happens to return the number of bytes
written to be precisely PAGE_SIZE, then we would overrun the buffer and
write the newline character outside the allotted space which can have
undefined consequences or result in a failure.
To fix this buffer overrun, ensure that there always is enough space
left for the newline character to be safely appended.
Fixes: d1efe3c324ea ("ACPI: Add new sysfs interface to export device description")
Signed-off-by: Krzysztof Wilczyński <kw@linux.com>
Reviewed-by: Bjorn Helgaas <bhelgaas@google.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
2021-06-04 01:12:01 +08:00
|
|
|
PAGE_SIZE - 1);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
buf[result++] = '\n';
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2017-12-20 02:15:08 +08:00
|
|
|
static DEVICE_ATTR_RO(description);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
static ssize_t
|
2020-12-17 20:45:36 +08:00
|
|
|
sun_show(struct device *dev, struct device_attribute *attr,
|
2021-03-27 20:08:23 +08:00
|
|
|
char *buf)
|
|
|
|
{
|
2015-07-18 04:53:43 +08:00
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
acpi_status status;
|
|
|
|
unsigned long long sun;
|
|
|
|
|
|
|
|
status = acpi_evaluate_integer(acpi_dev->handle, "_SUN", NULL, &sun);
|
|
|
|
if (ACPI_FAILURE(status))
|
2016-05-01 00:03:38 +08:00
|
|
|
return -EIO;
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
return sprintf(buf, "%llu\n", sun);
|
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(sun);
|
2015-07-18 04:53:43 +08:00
|
|
|
|
2016-05-01 00:03:37 +08:00
|
|
|
static ssize_t
|
2020-12-17 20:45:36 +08:00
|
|
|
hrv_show(struct device *dev, struct device_attribute *attr,
|
2021-03-27 20:08:23 +08:00
|
|
|
char *buf)
|
|
|
|
{
|
2016-05-01 00:03:37 +08:00
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
acpi_status status;
|
|
|
|
unsigned long long hrv;
|
|
|
|
|
|
|
|
status = acpi_evaluate_integer(acpi_dev->handle, "_HRV", NULL, &hrv);
|
|
|
|
if (ACPI_FAILURE(status))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
return sprintf(buf, "%llu\n", hrv);
|
|
|
|
}
|
2020-12-17 20:45:36 +08:00
|
|
|
static DEVICE_ATTR_RO(hrv);
|
2016-05-01 00:03:37 +08:00
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
static ssize_t status_show(struct device *dev, struct device_attribute *attr,
|
2021-03-27 20:08:23 +08:00
|
|
|
char *buf)
|
|
|
|
{
|
2015-07-18 04:53:43 +08:00
|
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
|
|
acpi_status status;
|
|
|
|
unsigned long long sta;
|
|
|
|
|
|
|
|
status = acpi_evaluate_integer(acpi_dev->handle, "_STA", NULL, &sta);
|
|
|
|
if (ACPI_FAILURE(status))
|
2016-05-01 00:03:38 +08:00
|
|
|
return -EIO;
|
2015-07-18 04:53:43 +08:00
|
|
|
|
|
|
|
return sprintf(buf, "%llu\n", sta);
|
|
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(status);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* acpi_device_setup_files - Create sysfs attributes of an ACPI device.
|
|
|
|
* @dev: ACPI device object.
|
|
|
|
*/
|
|
|
|
int acpi_device_setup_files(struct acpi_device *dev)
|
|
|
|
{
|
|
|
|
struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
|
|
|
|
acpi_status status;
|
|
|
|
int result = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Devices gotten from FADT don't have a "path" attribute
|
|
|
|
*/
|
|
|
|
if (dev->handle) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_path);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!list_empty(&dev->pnp.ids)) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_hid);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_modalias);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If device has _STR, 'description' file is created
|
|
|
|
*/
|
|
|
|
if (acpi_has_method(dev->handle, "_STR")) {
|
|
|
|
status = acpi_evaluate_object(dev->handle, "_STR",
|
|
|
|
NULL, &buffer);
|
|
|
|
if (ACPI_FAILURE(status))
|
|
|
|
buffer.pointer = NULL;
|
|
|
|
dev->pnp.str_obj = buffer.pointer;
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_description);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dev->pnp.type.bus_address)
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_adr);
|
|
|
|
if (dev->pnp.unique_id)
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_uid);
|
|
|
|
|
|
|
|
if (acpi_has_method(dev->handle, "_SUN")) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_sun);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2016-05-01 00:03:37 +08:00
|
|
|
if (acpi_has_method(dev->handle, "_HRV")) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_hrv);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
if (acpi_has_method(dev->handle, "_STA")) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_status);
|
|
|
|
if (result)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2016-05-01 00:03:39 +08:00
|
|
|
/*
|
|
|
|
* If device has _EJ0, 'eject' file is created that is used to trigger
|
|
|
|
* hot-removal function from userland.
|
|
|
|
*/
|
2015-07-18 04:53:43 +08:00
|
|
|
if (acpi_has_method(dev->handle, "_EJ0")) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_eject);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dev->flags.power_manageable) {
|
|
|
|
result = device_create_file(&dev->dev, &dev_attr_power_state);
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
if (dev->power.flags.power_resources)
|
|
|
|
result = device_create_file(&dev->dev,
|
|
|
|
&dev_attr_real_power_state);
|
|
|
|
}
|
|
|
|
|
2015-08-27 10:37:19 +08:00
|
|
|
acpi_expose_nondev_subnodes(&dev->dev.kobj, &dev->data);
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
end:
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* acpi_device_remove_files - Remove sysfs attributes of an ACPI device.
|
|
|
|
* @dev: ACPI device object.
|
|
|
|
*/
|
|
|
|
void acpi_device_remove_files(struct acpi_device *dev)
|
|
|
|
{
|
2015-08-27 10:37:19 +08:00
|
|
|
acpi_hide_nondev_subnodes(&dev->data);
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
if (dev->flags.power_manageable) {
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_power_state);
|
|
|
|
if (dev->power.flags.power_resources)
|
|
|
|
device_remove_file(&dev->dev,
|
|
|
|
&dev_attr_real_power_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If device has _STR, remove 'description' file
|
|
|
|
*/
|
|
|
|
if (acpi_has_method(dev->handle, "_STR")) {
|
|
|
|
kfree(dev->pnp.str_obj);
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_description);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If device has _EJ0, remove 'eject' file.
|
|
|
|
*/
|
|
|
|
if (acpi_has_method(dev->handle, "_EJ0"))
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_eject);
|
|
|
|
|
|
|
|
if (acpi_has_method(dev->handle, "_SUN"))
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_sun);
|
|
|
|
|
2016-05-01 00:03:37 +08:00
|
|
|
if (acpi_has_method(dev->handle, "_HRV"))
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_hrv);
|
|
|
|
|
2015-07-18 04:53:43 +08:00
|
|
|
if (dev->pnp.unique_id)
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_uid);
|
|
|
|
if (dev->pnp.type.bus_address)
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_adr);
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_modalias);
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_hid);
|
|
|
|
if (acpi_has_method(dev->handle, "_STA"))
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_status);
|
|
|
|
if (dev->handle)
|
|
|
|
device_remove_file(&dev->dev, &dev_attr_path);
|
|
|
|
}
|