From: Carlos Corbacho <cathectic@xxxxxxxxx> Userspace: There is a userspace interface in /sys/firmware/acpi/wmi for WMI methods and data. /sys/firmware/acpi/wmi/ | |-> <GUID>/ |-> type (method, data, event) Method & data blocks |-> <instance>/ |-> data (binary data file - write input data to file, read file to execute method or retrieve data). Method only |-> method_id (write value of method id to execute) Events - passed to userspace via netlink. However, the extra WMI data associated with an event is exposed through sysfs. |-> notification (ACPI event value) |-> data (binary data file - WMI data associated with the event) Signed-off-by: Carlos Corbacho <cathectic@xxxxxxxxx> --- drivers/acpi/wmi.c | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 332 insertions(+), 0 deletions(-) diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c index 88b326b..59c3d76 100644 --- a/drivers/acpi/wmi.c +++ b/drivers/acpi/wmi.c @@ -42,6 +42,7 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/types.h> +#include <linux/sysfs.h> #include <acpi/acpi_drivers.h> #define ACPI_WMI_CLASS "wmi" @@ -202,6 +203,33 @@ static bool wmi_parse_guid(const u8 *src, u8 *dest) return true; } +/* + * Convert a raw GUID to the ACII string representation + */ +static int wmi_gtoa(const char *in, char *out) +{ + int i; + + for (i = 3; i >= 0; i--) + out += sprintf(out, "%02X", in[i] & 0xFF); + + out += sprintf(out, "-"); + out += sprintf(out, "%02X", in[5] & 0xFF); + out += sprintf(out, "%02X", in[4] & 0xFF); + out += sprintf(out, "-"); + out += sprintf(out, "%02X", in[7] & 0xFF); + out += sprintf(out, "%02X", in[6] & 0xFF); + out += sprintf(out, "-"); + out += sprintf(out, "%02X", in[8] & 0xFF); + out += sprintf(out, "%02X", in[9] & 0xFF); + out += sprintf(out, "-"); + + for (i = 10; i <= 15; i++) + out += sprintf(out, "%02X", in[i] & 0xFF); + + return 0; +} + static bool find_guid(const char *guid_string, struct guid_block **out) { char tmp[16], guid_input[16]; @@ -479,6 +507,308 @@ bool wmi_has_guid(const char *guid_string) } EXPORT_SYMBOL_GPL(wmi_has_guid); +/* + * sysfs interface + */ +struct wmi_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, char *buf); + ssize_t (*store)(struct kobject *kobj, const char *buf, ssize_t count); +}; + +#define WMI_ATTR(_name, _mode, _show, _store) \ +struct wmi_attribute wmi_attr_##_name = __ATTR(_name, _mode, _show, _store); + +#define to_attr(a) container_of(a, struct wmi_attribute, attr) + +static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct wmi_attribute *wmi_attr = to_attr(attr); + ssize_t ret = 0; + + if (wmi_attr->show) + ret = wmi_attr->show(kobj, buf); + return ret; +} + +static ssize_t store(struct kobject *kobj, struct attribute *attr, const + char *buf, size_t count) +{ + struct wmi_attribute *wmi_attr = to_attr(attr); + ssize_t ret = 0; + + if (wmi_attr->store) + ret = wmi_attr->store(kobj, buf, count); + return ret; +} + +static struct sysfs_ops wmi_sysfs_ops = { + .show = show, + .store = store, +}; + +static struct kobj_type ktype_wmi = { + .sysfs_ops = &wmi_sysfs_ops, +}; + +struct guid_kobjects { + struct kobject guid_kobj; + struct kobject *instance_kobjs; + struct kobject *params; + void *data; + size_t data_size; + u8 instances; + u8 method_id; +}; + +static struct kobject wmi_kobj; +static struct guid_kobjects *wmi_guid_kobj; + +static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute + *bin_attr, char *buf, loff_t offset, size_t count) { + u8 method_id; + int i; + u8 instance; + const char *guid; + struct guid_kobjects *gkobj; + struct acpi_buffer in; + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + + guid = kobject_name(kobj->parent); + + for (i = 0; i < guids.total; i++) { + gkobj = &wmi_guid_kobj[i]; + if (memcmp(kobject_name(&gkobj->guid_kobj), guid, 36) == 0) { + instance = simple_strtoul(kobject_name(kobj), NULL, 10); + + method_id = gkobj->method_id; + + if (guids.pointer[i].flags & ACPI_WMI_METHOD) { + if (gkobj->data) { + in.pointer = gkobj->data; + in.length = gkobj->data_size; + wmi_evaluate_method(guid, instance, + method_id, &in, &out); + } else { + wmi_evaluate_method(guid, instance, + method_id, NULL, &out); + } + break; + } else { + wmi_query_block(guid, instance, &out); + break; + } + } + } + + obj = (union acpi_object *) out.pointer; + buf = obj->buffer.pointer; + + return 0; +} + +static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute + *bin_attr, char *buf, loff_t offset, size_t count){ + int i; + + for (i = 0; i < guids.total; i++) { + if (memcmp(kobject_name(&wmi_guid_kobj[i].guid_kobj), + kobject_name(kobj->parent), 36) == 0) { + kfree(wmi_guid_kobj[i].data); + wmi_guid_kobj[i].data = kzalloc(count, GFP_KERNEL); + memcpy(wmi_guid_kobj[i].data, buf, count); + wmi_guid_kobj[i].data_size = count; + return count; + } + } + return -EINVAL; +} + +static struct bin_attribute wmi_attr_data = { + .attr = {.name = "data", .mode = 0600}, + .size = 0, + .read = wmi_data_read, + .write = wmi_data_write, +}; + +static ssize_t wmi_event_data_read(struct kobject *kobj, struct bin_attribute + *bin_attr, char *buf, loff_t offset, size_t count) { + int i; + const char *guid; + struct guid_kobjects *gkobj; + struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obj; + + guid = kobject_name(kobj->parent); + + for (i = 0; i < guids.total; i++) { + gkobj = &wmi_guid_kobj[i]; + if (memcmp(kobject_name(&gkobj->guid_kobj), guid, 36) == 0) + wmi_get_event_data(guids.pointer[i].notify_id, &out); + } + + obj = (union acpi_object *) out.pointer; + buf = obj->buffer.pointer; + + return 0; +} + +static struct bin_attribute wmi_attr_event_data = { + .attr = {.name = "data", .mode = 0400}, + .size = 0, + .read = wmi_event_data_read, +}; + +/* sysfs calls */ +static ssize_t +show_guid_type(struct kobject *kobj, char *buf) +{ + struct guid_block *block; + + find_guid(kobject_name(kobj), &block); + + if (block->flags & ACPI_WMI_METHOD) { + return sprintf(buf, "method\n"); + } else if (block->flags & ACPI_WMI_EVENT) { + return sprintf(buf, "event\n"); + } else { + return sprintf(buf, "data\n"); + } +} +static WMI_ATTR(type, S_IRUGO, show_guid_type, NULL); + +static ssize_t show_guid_method_id(struct kobject *kobj, char *buf) +{ + int i; + + for (i = 0; i < guids.total; i++) { + if (memcmp(kobject_name(&wmi_guid_kobj[i].guid_kobj), + kobject_name(kobj->parent), 36) == 0) + return sprintf(buf, "%d\n", wmi_guid_kobj[i].method_id); + } + return sprintf(buf, "Error\n"); +} + +static ssize_t set_guid_method_id(struct kobject *kobj, const char *buf, + ssize_t count) +{ + int i; + u8 method_id; + + method_id = simple_strtoul(buf, NULL, 10); + + for (i = 0; i < guids.total; i++) { + if (memcmp(kobject_name(&wmi_guid_kobj[i].guid_kobj), + kobject_name(kobj->parent), 36) == 0) { + wmi_guid_kobj[i].method_id = method_id; + return count; + } + } + return -EINVAL; +} +static WMI_ATTR(method_id, S_IWUGO | S_IRUGO, show_guid_method_id, + set_guid_method_id); + +static ssize_t show_event_notification(struct kobject *kobj, char *buf) +{ + int i; + + for (i = 0; i < guids.total; i++) { + if (memcmp(kobject_name(&wmi_guid_kobj[i].guid_kobj), + kobject_name(kobj), 36) == 0) + return sprintf(buf, "%d\n", + guids.pointer[i].notify_id & 0xFF); + } + return sprintf(buf, "Error\n"); +} +static WMI_ATTR(notification, S_IRUGO, show_event_notification, NULL); + +static int wmi_sysfs_init(void) +{ + int i, j, result; + u8 instances; + char guid_string[37]; + char temp[4]; + struct kobject *guid_kobj; + struct kobject *instance_kobjs; + struct guid_block *block; + + wmi_guid_kobj = kzalloc(sizeof(struct guid_kobjects) * guids.total, + GFP_KERNEL); + + wmi_kobj.parent = &acpi_subsys.kobj; + wmi_kobj.ktype = &ktype_wmi; + kobject_set_name(&wmi_kobj, "wmi"); + + result = kobject_register(&wmi_kobj); + if (result) + return result; + + /* Create directories for all the GUIDs */ + for (i = 0; i < guids.total; i++) { + guid_kobj = &wmi_guid_kobj[i].guid_kobj; + guid_kobj->parent = &wmi_kobj; + guid_kobj->ktype = &ktype_wmi; + + block = guids.pointer + i; + wmi_gtoa(block->guid, guid_string); + kobject_set_name(guid_kobj, guid_string); + + result = kobject_register(guid_kobj); + if (result) + return result; + + result = sysfs_create_file(guid_kobj, &wmi_attr_type.attr); + if (result) + return result; + + if (guids.pointer[i].flags & ACPI_WMI_EVENT) { + result = sysfs_create_file(guid_kobj, + &wmi_attr_notification.attr); + if (result) + return result; + + result = sysfs_create_bin_file(guid_kobj, + &wmi_attr_event_data); + if (result) + return result; + break; + } + + /* Create directories for all the instances */ + instances = guids.pointer[i].instance_count; + wmi_guid_kobj[i].instances = instances; + wmi_guid_kobj[i].instance_kobjs = kzalloc(sizeof(struct kobject) + * instances, GFP_KERNEL); + instance_kobjs = wmi_guid_kobj[i].instance_kobjs; + for (j = 0; j < instances; j++) { + instance_kobjs[j].parent = guid_kobj; + instance_kobjs[j].ktype = &ktype_wmi; + sprintf(temp, "%d", j+1); + kobject_set_name(&instance_kobjs[j], temp); + + result = kobject_register(&instance_kobjs[j]); + if (result) + return result; + + /* Create the relevant files under each instance */ + result = sysfs_create_bin_file(&instance_kobjs[j], + &wmi_attr_data); + if (result) + return result; + + if (guids.pointer[i].flags & ACPI_WMI_METHOD) { + result = sysfs_create_file(&instance_kobjs[j], + &wmi_attr_method_id.attr); + if (result) + return result; + } + } + } + return 0; +} + /** * parse_wdg - Parse the _WDG method for the GUID data blocks */ @@ -551,6 +881,8 @@ static int __init acpi_wmi_add(struct acpi_device *device) if (ACPI_FAILURE(status)) return -ENODEV; + result = wmi_sysfs_init(); + return result; } -- 1.5.3.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