linux-acpi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface
  2007-12-17  0:23 [PATCH 0/5] WMI Carlos Corbacho
@ 2007-12-17  0:23 ` Carlos Corbacho
  0 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2007-12-17  0:23 UTC (permalink / raw)
  To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett

Create a 'wmi' class, and populate it with a virtual device for each GUID.
This also allows us to autoload WMI drivers based on GUID
via MODULE_ALIAS.

Under each GUID are a set of files that can be read and written to from
userspace (these will be fully documented in a later patch).

v1 (2007-11-03)

* Initial release

v2 (2007-11-07)

* Split out into a separate patch

v3 (2007-11-20)

* Convert kobject storage to using kernel list structures.
* Change instance handling - store input data on a per instance basis,
  rather than a per GUID.
* Add locking to methods - method_id (write) and data (read & write)
  should all be mutually exclusive - we do not want the input data to
  change when we are trying to execute a method.
* Change method calling semantics: when input is written to 'data',
  store it, but don't execute the method until 'data' is read. This is
  due to the fact that it is perfectly acceptable to have a WMI method
  that does not take any input (and it is easier to trigger an execute
  on reading the file, and not return anything if there is nothing to
  return, rather than trying to write values that aren't required, or
  may cause ACPI evaluation to fail on the method).
* Do not try and convert String's from ASCII to Unicode - instead, only
  handle ASCII (as per ACPI), and leave it up to userspace to convert
  to/ from whatever encoding they wish to use.

v4 (2007-11-28)

* Add code to remove sysfs files on module removal

v5 (2007-12-08)

* Convert to a class device, instead of trying to manually create and
  manipulate kobject's - this allows us to add module autoloading for WMI
  based drivers, and will (hopefully) be less likely to break with the
  latest round of kobject changes.
* Convert GUIDs to use a 'flat' structure - add two new files:
  instance and instance_count, and use these to provide the instance
  to call for a given GUID (while still communicating the maximum number
  of instances available - for now, we will always respect instance_count
  when setting instance - so any instance > instance_count will be
  discarded).

v6 (2007-12-12)

* Add string file - this reports back if the GUID has the string flag set.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/acpi/wmi.c |  463 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 461 insertions(+), 2 deletions(-)


diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index 75a87ff..b6f635a 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -30,6 +30,8 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
 #include <linux/list.h>
 #include <linux/acpi.h>
 #include <acpi/acpi_bus.h>
@@ -64,6 +66,11 @@ struct wmi_block {
 	struct list_head list;
 	struct guid_block gblock;
 	acpi_handle handle;
+	struct device dev;
+	u8 instance;
+	u32 method_id;
+	void *pointer;
+	acpi_size length;
 };
 
 static struct wmi_block wmi_blocks;
@@ -195,6 +202,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 wmi_block **out)
 {
 	char tmp[16], guid_input[16];
@@ -499,6 +533,421 @@ bool wmi_has_guid(const char *guid_string)
 }
 EXPORT_SYMBOL_GPL(wmi_has_guid);
 
+/*
+ * sysfs interface
+ */
+static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count) {
+	u8 instance;
+	const char *guid;
+	struct acpi_buffer in;
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	acpi_status status;
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	guid = block->gblock.guid;
+	if (!guid)
+		return -EINVAL;
+
+	instance = block->instance;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		mutex_lock(&wmi_data_lock);
+		if (block->pointer) {
+			in.pointer = block->pointer;
+			in.length = block->length;
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, &in, &out);
+		} else {
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, NULL, &out);
+		}
+		mutex_unlock(&wmi_data_lock);
+	} else {
+		status = wmi_query_block(guid, instance, &out);
+	}
+
+	obj = (union acpi_object *) out.pointer;
+
+	switch (obj->type) {
+	case (ACPI_TYPE_STRING):
+	case (ACPI_TYPE_BUFFER):
+		buf = obj->buffer.pointer;
+		break;
+	default:
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count){
+	struct wmi_block *block;
+	struct acpi_buffer in;
+	acpi_status status;
+	struct device *dev;
+	char guid_string[37];
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (count == 0)
+		return count;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		block->pointer = kzalloc(count, GFP_KERNEL);
+		if (!block->pointer)
+			return count;
+
+		memcpy(block->pointer, buf, count);
+		block->length = count;
+	} else {
+		in.pointer = buf;
+		in.length = count;
+		wmi_gtoa(block->gblock.guid, guid_string);
+		status = wmi_set_block(guid_string, block->instance, &in);
+	}
+
+	return count;
+}
+
+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) {
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	wmi_get_event_data(block->gblock.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,
+};
+
+static ssize_t show_type(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return sprintf(buf, "Error\n");
+
+	block = &wblock->gblock;
+
+	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 DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
+
+static ssize_t show_instance_count(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->gblock.instance_count);
+}
+static DEVICE_ATTR(instance_count, S_IRUGO, show_instance_count, NULL);
+
+static ssize_t show_instance(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->instance);
+}
+
+static ssize_t set_instance(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 instance;
+
+	instance = simple_strtoul(buf, NULL, 10);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (instance <= block->gblock.instance_count) {
+		block->instance = instance;
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(instance, S_IWUGO | S_IRUGO, show_instance, set_instance);
+
+static ssize_t show_method_id(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->method_id);
+}
+
+static ssize_t set_method_id(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 method_id;
+
+	method_id = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&wmi_data_lock);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block) {
+		mutex_unlock(&wmi_data_lock);
+		return -EINVAL;
+	}
+
+	block->method_id = method_id;
+	mutex_unlock(&wmi_data_lock);
+
+	return count;
+}
+static DEVICE_ATTR(method_id, S_IWUGO | S_IRUGO, show_method_id,
+	set_method_id);
+
+static ssize_t show_event_notification(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", gblock->notify_id & 0xFF);
+}
+static DEVICE_ATTR(notification, S_IRUGO, show_event_notification, NULL);
+
+static ssize_t show_string(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", (gblock->flags & ACPI_WMI_STRING) >> 2);
+}
+static DEVICE_ATTR(string, S_IRUGO, show_string, NULL);
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	char guid_string[37];
+
+	struct wmi_block *wblock;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return -ENOMEM;
+
+	wmi_gtoa(wblock->gblock.guid, guid_string);
+
+	strcpy(&env->buf[env->buflen - 1], "wmi:");
+	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+	env->buflen += 40;
+
+	return 0;
+}
+
+static struct class wmi_class = {
+	.name = "wmi",
+	.dev_release = (void(*)(struct device *)) kfree,
+	.dev_uevent = wmi_dev_uevent,
+};
+
+static int wmi_create_devs(void)
+{
+	int result;
+	char guid_string[37];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Create devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		guid_dev->class = &wmi_class;
+
+		gblock = &wblock->gblock;
+
+		wmi_gtoa(gblock->guid, guid_string);
+		memcpy(guid_dev->bus_id, guid_string, 36);
+
+		result = device_register(guid_dev);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_type);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_string);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			result = device_create_file(guid_dev,
+				&dev_attr_notification);
+			if (result)
+				return result;
+
+			result = device_create_bin_file(guid_dev,
+				&wmi_attr_event_data);
+			if (result)
+				return result;
+			break;
+		}
+
+		result = device_create_file(guid_dev, &dev_attr_instance_count);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_instance);
+		if (result)
+			return result;
+
+		result = device_create_bin_file(guid_dev, &wmi_attr_data);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_METHOD) {
+			result = device_create_file(guid_dev,
+				&dev_attr_method_id);
+			if (result)
+				return result;
+		}
+	}
+
+	return 0;
+}
+
+static void wmi_remove_devs(void)
+{
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Delete devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		gblock = &wblock->gblock;
+
+		device_remove_file(guid_dev, &dev_attr_type);
+		device_remove_file(guid_dev, &dev_attr_string);
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			device_remove_file(guid_dev, &dev_attr_notification);
+			device_remove_bin_file(guid_dev, &wmi_attr_event_data);
+		} else {
+			device_remove_file(guid_dev, &dev_attr_instance_count);
+			device_remove_file(guid_dev, &dev_attr_instance);
+			device_remove_bin_file(guid_dev, &wmi_attr_data);
+
+			if (gblock->flags & ACPI_WMI_METHOD) {
+				device_remove_file(guid_dev,
+					&dev_attr_method_id);
+			}
+		}
+		device_unregister(guid_dev);
+	}
+}
+
+static int wmi_class_init(void)
+{
+	int ret;
+
+	ret = class_register(&wmi_class);
+	if (ret)
+		return ret;
+
+	return wmi_create_devs();
+}
+
+static void wmi_class_exit(void)
+{
+	wmi_remove_devs();
+	class_unregister(&wmi_class);
+}
+
 /**
  * parse_wdg - Parse the _WDG method for the GUID data blocks
  */
@@ -654,6 +1103,7 @@ static int __init acpi_wmi_add(struct acpi_device *device)
 
 static int __init acpi_wmi_init(void)
 {
+	int ret;
 	acpi_status result;
 
 	if (acpi_disabled)
@@ -665,10 +1115,17 @@ static int __init acpi_wmi_init(void)
 
 	if (ACPI_FAILURE(result)) {
 		printk(KERN_INFO PREFIX "Error loading mapper\n");
-	} else {
-		printk(KERN_INFO PREFIX "Mapper loaded\n");
+		return -ENODEV;
+	}
+
+	ret = wmi_class_init();
+	if (ret) {
+		acpi_bus_unregister_driver(&acpi_wmi_driver);
+		return ret;
 	}
 
+	printk(KERN_INFO PREFIX "Mapper loaded\n");
+
 	return result;
 }
 
@@ -677,6 +1134,8 @@ static void __exit acpi_wmi_exit(void)
 	struct list_head *p, *tmp;
 	struct wmi_block *wblock;
 
+	wmi_class_exit();
+
 	acpi_bus_unregister_driver(&acpi_wmi_driver);
 
 	list_for_each_safe(p, tmp, &wmi_blocks.list) {


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface
  2007-12-27 15:38 [PATCH 0/5] WMI Carlos Corbacho
@ 2007-12-27 15:38 ` Carlos Corbacho
  0 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2007-12-27 15:38 UTC (permalink / raw)
  To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett

Create a 'wmi' class, and populate it with a virtual device for each GUID.
This also allows us to autoload WMI drivers based on GUID
via MODULE_ALIAS.

Under each GUID are a set of files that can be read and written to from
userspace (these will be fully documented in a later patch).

v1 (2007-11-03)

* Initial release

v2 (2007-11-07)

* Split out into a separate patch

v3 (2007-11-20)

* Convert kobject storage to using kernel list structures.
* Change instance handling - store input data on a per instance basis,
  rather than a per GUID.
* Add locking to methods - method_id (write) and data (read & write)
  should all be mutually exclusive - we do not want the input data to
  change when we are trying to execute a method.
* Change method calling semantics: when input is written to 'data',
  store it, but don't execute the method until 'data' is read. This is
  due to the fact that it is perfectly acceptable to have a WMI method
  that does not take any input (and it is easier to trigger an execute
  on reading the file, and not return anything if there is nothing to
  return, rather than trying to write values that aren't required, or
  may cause ACPI evaluation to fail on the method).
* Do not try and convert String's from ASCII to Unicode - instead, only
  handle ASCII (as per ACPI), and leave it up to userspace to convert
  to/ from whatever encoding they wish to use.

v4 (2007-11-28)

* Add code to remove sysfs files on module removal

v5 (2007-12-08)

* Convert to a class device, instead of trying to manually create and
  manipulate kobject's - this allows us to add module autoloading for WMI
  based drivers, and will (hopefully) be less likely to break with the
  latest round of kobject changes.
* Convert GUIDs to use a 'flat' structure - add two new files:
  instance and instance_count, and use these to provide the instance
  to call for a given GUID (while still communicating the maximum number
  of instances available - for now, we will always respect instance_count
  when setting instance - so any instance > instance_count will be
  discarded).

v6 (2007-12-12)

* Add string file - this reports back if the GUID has the string flag set.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/acpi/wmi.c |  463 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 461 insertions(+), 2 deletions(-)


diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index 0e443af..52015ba 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -30,6 +30,8 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
 #include <linux/list.h>
 #include <linux/acpi.h>
 #include <acpi/acpi_bus.h>
@@ -66,6 +68,11 @@ struct wmi_block {
 	acpi_handle handle;
 	wmi_notify_handler handler;
 	void *handler_data;
+	struct device dev;
+	u8 instance;
+	u32 method_id;
+	void *pointer;
+	acpi_size length;
 };
 
 static struct wmi_block wmi_blocks;
@@ -194,6 +201,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 wmi_block **out)
 {
 	char tmp[16], guid_input[16];
@@ -517,6 +551,421 @@ bool wmi_has_guid(const char *guid_string)
 EXPORT_SYMBOL_GPL(wmi_has_guid);
 
 /*
+ * sysfs interface
+ */
+static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count) {
+	u8 instance;
+	const char *guid;
+	struct acpi_buffer in;
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	acpi_status status;
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	guid = block->gblock.guid;
+	if (!guid)
+		return -EINVAL;
+
+	instance = block->instance;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		mutex_lock(&wmi_data_lock);
+		if (block->pointer) {
+			in.pointer = block->pointer;
+			in.length = block->length;
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, &in, &out);
+		} else {
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, NULL, &out);
+		}
+		mutex_unlock(&wmi_data_lock);
+	} else {
+		status = wmi_query_block(guid, instance, &out);
+	}
+
+	obj = (union acpi_object *) out.pointer;
+
+	switch (obj->type) {
+	case (ACPI_TYPE_STRING):
+	case (ACPI_TYPE_BUFFER):
+		buf = obj->buffer.pointer;
+		break;
+	default:
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count){
+	struct wmi_block *block;
+	struct acpi_buffer in;
+	acpi_status status;
+	struct device *dev;
+	char guid_string[37];
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (count == 0)
+		return count;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		block->pointer = kzalloc(count, GFP_KERNEL);
+		if (!block->pointer)
+			return count;
+
+		memcpy(block->pointer, buf, count);
+		block->length = count;
+	} else {
+		in.pointer = buf;
+		in.length = count;
+		wmi_gtoa(block->gblock.guid, guid_string);
+		status = wmi_set_block(guid_string, block->instance, &in);
+	}
+
+	return count;
+}
+
+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) {
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	wmi_get_event_data(block->gblock.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,
+};
+
+static ssize_t show_type(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return sprintf(buf, "Error\n");
+
+	block = &wblock->gblock;
+
+	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 DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
+
+static ssize_t show_instance_count(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->gblock.instance_count);
+}
+static DEVICE_ATTR(instance_count, S_IRUGO, show_instance_count, NULL);
+
+static ssize_t show_instance(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->instance);
+}
+
+static ssize_t set_instance(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 instance;
+
+	instance = simple_strtoul(buf, NULL, 10);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (instance <= block->gblock.instance_count) {
+		block->instance = instance;
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(instance, S_IWUGO | S_IRUGO, show_instance, set_instance);
+
+static ssize_t show_method_id(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->method_id);
+}
+
+static ssize_t set_method_id(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 method_id;
+
+	method_id = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&wmi_data_lock);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block) {
+		mutex_unlock(&wmi_data_lock);
+		return -EINVAL;
+	}
+
+	block->method_id = method_id;
+	mutex_unlock(&wmi_data_lock);
+
+	return count;
+}
+static DEVICE_ATTR(method_id, S_IWUGO | S_IRUGO, show_method_id,
+	set_method_id);
+
+static ssize_t show_event_notification(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", gblock->notify_id & 0xFF);
+}
+static DEVICE_ATTR(notification, S_IRUGO, show_event_notification, NULL);
+
+static ssize_t show_string(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", (gblock->flags & ACPI_WMI_STRING) >> 2);
+}
+static DEVICE_ATTR(string, S_IRUGO, show_string, NULL);
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	char guid_string[37];
+
+	struct wmi_block *wblock;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return -ENOMEM;
+
+	wmi_gtoa(wblock->gblock.guid, guid_string);
+
+	strcpy(&env->buf[env->buflen - 1], "wmi:");
+	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+	env->buflen += 40;
+
+	return 0;
+}
+
+static struct class wmi_class = {
+	.name = "wmi",
+	.dev_release = (void(*)(struct device *)) kfree,
+	.dev_uevent = wmi_dev_uevent,
+};
+
+static int wmi_create_devs(void)
+{
+	int result;
+	char guid_string[37];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Create devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		guid_dev->class = &wmi_class;
+
+		gblock = &wblock->gblock;
+
+		wmi_gtoa(gblock->guid, guid_string);
+		memcpy(guid_dev->bus_id, guid_string, 36);
+
+		result = device_register(guid_dev);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_type);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_string);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			result = device_create_file(guid_dev,
+				&dev_attr_notification);
+			if (result)
+				return result;
+
+			result = device_create_bin_file(guid_dev,
+				&wmi_attr_event_data);
+			if (result)
+				return result;
+			break;
+		}
+
+		result = device_create_file(guid_dev, &dev_attr_instance_count);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_instance);
+		if (result)
+			return result;
+
+		result = device_create_bin_file(guid_dev, &wmi_attr_data);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_METHOD) {
+			result = device_create_file(guid_dev,
+				&dev_attr_method_id);
+			if (result)
+				return result;
+		}
+	}
+
+	return 0;
+}
+
+static void wmi_remove_devs(void)
+{
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Delete devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		gblock = &wblock->gblock;
+
+		device_remove_file(guid_dev, &dev_attr_type);
+		device_remove_file(guid_dev, &dev_attr_string);
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			device_remove_file(guid_dev, &dev_attr_notification);
+			device_remove_bin_file(guid_dev, &wmi_attr_event_data);
+		} else {
+			device_remove_file(guid_dev, &dev_attr_instance_count);
+			device_remove_file(guid_dev, &dev_attr_instance);
+			device_remove_bin_file(guid_dev, &wmi_attr_data);
+
+			if (gblock->flags & ACPI_WMI_METHOD) {
+				device_remove_file(guid_dev,
+					&dev_attr_method_id);
+			}
+		}
+		device_unregister(guid_dev);
+	}
+}
+
+static int wmi_class_init(void)
+{
+	int ret;
+
+	ret = class_register(&wmi_class);
+	if (ret)
+		return ret;
+
+	return wmi_create_devs();
+}
+
+static void wmi_class_exit(void)
+{
+	wmi_remove_devs();
+	class_unregister(&wmi_class);
+}
+
+/*
  * Parse the _WDG method for the GUID data blocks
  */
 static __init acpi_status parse_wdg(acpi_handle handle)
@@ -673,6 +1122,7 @@ static int __init acpi_wmi_add(struct acpi_device *device)
 
 static int __init acpi_wmi_init(void)
 {
+	int ret;
 	acpi_status result;
 
 	if (acpi_disabled)
@@ -684,10 +1134,17 @@ static int __init acpi_wmi_init(void)
 
 	if (result < 0) {
 		printk(KERN_INFO PREFIX "Error loading mapper\n");
-	} else {
-		printk(KERN_INFO PREFIX "Mapper loaded\n");
+		return -ENODEV;
+	}
+
+	ret = wmi_class_init();
+	if (ret) {
+		acpi_bus_unregister_driver(&acpi_wmi_driver);
+		return ret;
 	}
 
+	printk(KERN_INFO PREFIX "Mapper loaded\n");
+
 	return result;
 }
 
@@ -696,6 +1153,8 @@ static void __exit acpi_wmi_exit(void)
 	struct list_head *p, *tmp;
 	struct wmi_block *wblock;
 
+	wmi_class_exit();
+
 	acpi_bus_unregister_driver(&acpi_wmi_driver);
 
 	list_for_each_safe(p, tmp, &wmi_blocks.list) {


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 0/5] WMI
@ 2008-01-12  2:20 Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
                   ` (4 more replies)
  0 siblings, 5 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12  2:20 UTC (permalink / raw)
  To: linux-acpi; +Cc: Len Brown, Matthew Garrett, Alexey Starikovskiy

ANYWR - A New Year WMI release

Len,

Can you please review at least the first two patches in this series, as I
would really like to try and get them into 2.6.25.

-Carlos

===

Patch #1: (WMI - driver and in kernel interface) - No change

Len: For you to review, and to go upstream.

====

Patch #2: (acer-wmi)

Added more device autodetection. Also, fixed setting bluetooth and wireless
on older AMW0 V2 laptops, and improved the error handling code on driver
registration.

(I am currently working on rfkill support, but have been running into some
strange problems with rfkill-input, so I'll release that as a future patch
on top of this when it's done. Until then, though, this patch still works
as-is).

Len: For you to review, and to go upstream.

====

Patch #3: (tc1100-wmi) - No Change

RFC only, needs actual testing on the hardware, and probably very broken.
Waiting on Matthew Garrett to get some free time to test this.

====

Patch #4: (WMI sysfs interface) - No Change

RFC only (see patch #5 for reason). Adds interface under
/sys/devices/virtual/wmi

Matthew: Do you want me to fold patch #5 into this?

====

Patch #5: (WMI sysfs workaround) - No change

Temporary hack, needed to get patch #4 working, due to a limitation on bus_id
length (Kay Sievers is apparently working on this, ref Greg KH[1]).

[1] http://lkml.org/lkml/2007/12/4/30
---

Carlos Corbacho (5):
      [WIP] ACPI: WMI: Limit size of device string to 19 characters
      [RFC] ACPI: WMI: Add sysfs userspace interface
      [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets
      acer-wmi: Add driver for newer Acer laptops
      ACPI: WMI: Add ACPI-WMI mapping driver


 MAINTAINERS               |    7 
 drivers/acpi/Kconfig      |   11 
 drivers/acpi/Makefile     |    1 
 drivers/acpi/wmi.c        | 1171 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/misc/Kconfig      |   25 +
 drivers/misc/Makefile     |    2 
 drivers/misc/acer-wmi.c   | 1100 ++++++++++++++++++++++++++++++++++++++++++
 drivers/misc/tc1100-wmi.c |  290 +++++++++++
 include/linux/acpi.h      |   20 +
 9 files changed, 2627 insertions(+), 0 deletions(-)
 create mode 100644 drivers/acpi/wmi.c
 create mode 100644 drivers/misc/acer-wmi.c
 create mode 100644 drivers/misc/tc1100-wmi.c



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver
  2008-01-12  2:20 [PATCH 0/5] WMI Carlos Corbacho
@ 2008-01-12  2:20 ` Carlos Corbacho
  2008-01-12 13:00   ` Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12  2:20 UTC (permalink / raw)
  To: linux-acpi
  Cc: Carlos Corbacho, Len Brown, Matthew Garrett, Alexey Starikovskiy

The following is an implementation of the Windows Management
Instrumentation (WMI) ACPI interface mapper (PNP0C14).

What it does:

Parses the _WDG method and exports functions to process WMI method calls,
data block query/ set commands (both based on GUID) and does basic event
handling.

How: WMI presents an in kernel interface here (essentially, a minimal
wrapper around ACPI)

(const char *guid assume the 36 character ASCII representation of
a GUID - e.g. 67C3371D-95A3-4C37-BB61-DD47B491DAAB)

wmi_evaluate_method(const char *guid, u8 instance, u32 method_id,
const struct acpi_buffer *in, struct acpi_buffer *out)

wmi_query_block(const char *guid, u8 instance,
struct acpi_buffer *out)

wmi_set_block(const char *guid, u38 instance,
const struct acpi_buffer *in)

wmi_install_notify_handler(acpi_notify_handler handler);

wmi_remove_notify_handler(void);

wmi_get_event_data(u32 event, struct acpi_buffer *out)

wmi_has_guid(const char guid*)

wmi_has_guid() is a helper function to find if a GUID exists or not on the
system (a quick and easy way for WMI dependant drivers to see if the
the method/ block they want exists, since GUIDs are supposed to be unique).

Event handling - allow a WMI based driver to register a notifier handler
for each GUID with WMI. When a notification is sent to a GUID in WMI, the
handler registered with WMI is then called (it is left to the caller to
ask for the WMI event data associated with the GUID, if needed).

What it won't do:

Unicode - The MS article[1] calls for converting between ASCII and Unicode (or
vice versa) if a GUID is marked as "string". This is left up to the calling
driver.

Handle a MOF[1] - the WMI mapper just exports methods, data and events to
userspace. MOF handling is down to userspace.

Userspace interface - this will be added later.

[1] http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx

===
ChangeLog
==

v1 (2007-10-02):

* Initial release

v2 (2007-10-05):

* Cleaned up code - split up super "wmi_evaluate_block" -> each external
  symbol now handles its own ACPI calls, rather than handing off to
  a "super" method (and in turn, is a lot simpler to read)
* Added a find_guid() symbol - return true if a given GUID exists on
  the system
* wmi_* functions now return type acpi_status (since they are just
  fancy wrappers around acpi_evaluate_object())
* Removed extra debug code

v3 (2007-10-27)

* More code clean up - now passes checkpatch.pl
* Change data block calls - ref MS spec, method ID is not required for
  them, so drop it from the function parameters.
* Const'ify guid in the function call parameters.
* Fix _WDG buffer handling - copy the data to our own private structure.
* Change WMI from tristate to bool - otherwise the external functions are
  not exported in linux/acpi.h if you try to build WMI as a module.
* Fix more flag comparisons.
* Add a maintainers entry - since I wrote this, I should take the blame
  for it.

v4 (2007-10-30)

* Add missing brace from after fixing checkpatch errors.
* Rewrote event handling - allow external drivers to register with WMI to
  handle WMI events
* Clean up flags and sanitise flag handling

v5 (2007-11-03)

* Add sysfs interface for userspace. Export events over netlink again.
* Remove module left overs, fully convert to built-in driver.
* Tweak in-kernel API to use u8 for instance, since this is what the GUID
  blocks use (so instance cannot be greater than u8).
* Export wmi_get_event_data() for in kernel WMI drivers.

v6 (2007-11-07)

* Split out userspace into a different patch

v7 (2007-11-20)

* Fix driver to handle multiple PNP0C14 devices - store all GUIDs using
  the kernel's built in list functions, and just keep adding to the list
  every time we handle a PNP0C14 devices - GUIDs will always be unique,
  and WMI callers do not know or care about different devices.
* Change WMI event handler registration to use its' own event handling
  struct; we should not pass an acpi_handle down to any WMI based drivers
  - they should be able to function with only the calls provided in WMI.
* Update my e-mail address

v8 (2007-11-28)

* Convert back to a module.
* Update Kconfig to default to building as a module.
* Remove an erroneous printk.
* Simply comments for string flag (since we now leave the handling to the
  caller).

v9 (2007-12-07)

* Add back missing MODULE_DEVICE_TABLE for autoloading
* Checkpatch fixes

v10 (2007-12-12)

* Workaround broken GUIDs declared expensive without a WCxx method.
* Minor cleanups

v11 (2007-12-17)

* More fixing for broken GUIDs declared expensive without a WCxx method.
* Add basic EmbeddedControl region handling.

v12 (2007-12-18)

* Changed EC region handling code, as per Alexey's comments.

v13 (2007-12-27)

* Changed event handling so that we can have one event handler registered
  per GUID, as per Matthew Garrett's suggestion.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Alexey Starikovskiy <aystarik@gmail.com>
---

 MAINTAINERS           |    7 
 drivers/acpi/Kconfig  |   11 +
 drivers/acpi/Makefile |    1 
 drivers/acpi/wmi.c    |  712 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/acpi.h  |   20 +
 5 files changed, 751 insertions(+), 0 deletions(-)
 create mode 100644 drivers/acpi/wmi.c


diff --git a/MAINTAINERS b/MAINTAINERS
index 79c711e..d7aa746 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -259,6 +259,13 @@ L:	linux-acpi@vger.kernel.org
 W:	http://acpi.sourceforge.net/
 S:	Supported
 
+ACPI WMI DRIVER
+P:      Carlos Corbacho
+M:      carlos@strangeworlds.co.uk
+L:      linux-acpi@vger.kernel.org
+W:      http://www.lesswatts.org/projects/acpi/
+S:      Supported
+
 ADM1025 HARDWARE MONITOR DRIVER
 P:	Jean Delvare
 M:	khali@linux-fr.org
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index ccf6ea9..48cca7a 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -199,6 +199,17 @@ config ACPI_NUMA
 	depends on (X86 || IA64)
 	default y if IA64_GENERIC || IA64_SGI_SN2
 
+config ACPI_WMI
+	tristate "WMI (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	default m
+	help
+	  This driver adds support for the ACPI-WMI mapper device (PNP0C14)
+	  found on some systems.
+
+	  NOTE: You will need another driver or userspace application on top of
+	  this to actually use anything defined in the ACPI-WMI mapper.
+
 config ACPI_ASUS
         tristate "ASUS/Medion Laptop Extras"
 	depends on X86
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 456446f..f29812a 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_ACPI_THERMAL)	+= thermal.o
 obj-$(CONFIG_ACPI_SYSTEM)	+= system.o event.o
 obj-$(CONFIG_ACPI_DEBUG)	+= debug.o
 obj-$(CONFIG_ACPI_NUMA)		+= numa.o
+obj-$(CONFIG_ACPI_WMI)		+= wmi.o
 obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-$(CONFIG_ACPI_HOTPLUG_MEMORY)	+= acpi_memhotplug.o
diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
new file mode 100644
index 0000000..41656b2
--- /dev/null
+++ b/drivers/acpi/wmi.c
@@ -0,0 +1,712 @@
+/*
+ *  ACPI-WMI mapping driver
+ *
+ *  Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ *  GUID parsing code from ldm.c is:
+ *   Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org>
+ *   Copyright (c) 2001-2007 Anton Altaparmakov
+ *   Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at
+ *  your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+ACPI_MODULE_NAME("wmi");
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
+MODULE_LICENSE("GPL");
+
+#define ACPI_WMI_CLASS "wmi"
+
+#undef PREFIX
+#define PREFIX "ACPI: WMI: "
+
+static DEFINE_MUTEX(wmi_data_lock);
+
+struct guid_block {
+	char guid[16];
+	union {
+		char object_id[2];
+		struct {
+			unsigned char notify_id;
+			unsigned char reserved;
+		};
+	};
+	u8 instance_count;
+	u8 flags;
+};
+
+struct wmi_block {
+	struct list_head list;
+	struct guid_block gblock;
+	acpi_handle handle;
+	wmi_notify_handler handler;
+	void *handler_data;
+};
+
+static struct wmi_block wmi_blocks;
+
+/*
+ * If the GUID data block is marked as expensive, we must enable and
+ * explicitily disable data collection.
+ */
+#define ACPI_WMI_EXPENSIVE   0x1
+#define ACPI_WMI_METHOD      0x2	/* GUID is a method */
+#define ACPI_WMI_STRING      0x4	/* GUID takes & returns a string */
+#define ACPI_WMI_EVENT       0x8	/* GUID is an event */
+
+static int acpi_wmi_remove(struct acpi_device *device, int type);
+static int acpi_wmi_add(struct acpi_device *device);
+
+static const struct acpi_device_id wmi_device_ids[] = {
+	{"PNP0C14", 0},
+	{"pnp0c14", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
+
+static struct acpi_driver acpi_wmi_driver = {
+	.name = "wmi",
+	.class = ACPI_WMI_CLASS,
+	.ids = wmi_device_ids,
+	.ops = {
+		.add = acpi_wmi_add,
+		.remove = acpi_wmi_remove,
+		},
+};
+
+/*
+ * GUID parsing functions
+ */
+
+/**
+ * wmi_parse_hexbyte - Convert a ASCII hex number to a byte
+ * @src:  Pointer to at least 2 characters to convert.
+ *
+ * Convert a two character ASCII hex string to a number.
+ *
+ * Return:  0-255  Success, the byte was parsed correctly
+ *          -1     Error, an invalid character was supplied
+ */
+static int wmi_parse_hexbyte(const u8 *src)
+{
+	unsigned int x; /* For correct wrapping */
+	int h;
+
+	/* high part */
+	x = src[0];
+	if (x - '0' <= '9' - '0') {
+		h = x - '0';
+	} else if (x - 'a' <= 'f' - 'a') {
+		h = x - 'a' + 10;
+	} else if (x - 'A' <= 'F' - 'A') {
+		h = x - 'A' + 10;
+	} else {
+		return -1;
+	}
+	h <<= 4;
+
+	/* low part */
+	x = src[1];
+	if (x - '0' <= '9' - '0')
+		return h | (x - '0');
+	if (x - 'a' <= 'f' - 'a')
+		return h | (x - 'a' + 10);
+	if (x - 'A' <= 'F' - 'A')
+		return h | (x - 'A' + 10);
+	return -1;
+}
+
+/**
+ * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary
+ * @src:   Memory block holding binary GUID (16 bytes)
+ * @dest:  Memory block to hold byte swapped binary GUID (16 bytes)
+ *
+ * Byte swap a binary GUID to match it's real GUID value
+ */
+static void wmi_swap_bytes(u8 *src, u8 *dest)
+{
+	int i;
+
+	for (i = 0; i <= 3; i++)
+		memcpy(dest + i, src + (3 - i), 1);
+
+	for (i = 0; i <= 1; i++)
+		memcpy(dest + 4 + i, src + (5 - i), 1);
+
+	for (i = 0; i <= 1; i++)
+		memcpy(dest + 6 + i, src + (7 - i), 1);
+
+	memcpy(dest + 8, src + 8, 8);
+}
+
+/**
+ * wmi_parse_guid - Convert GUID from ASCII to binary
+ * @src:   36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dest:  Memory block to hold binary GUID (16 bytes)
+ *
+ * N.B. The GUID need not be NULL terminated.
+ *
+ * Return:  'true'   @dest contains binary GUID
+ *          'false'  @dest contents are undefined
+ */
+static bool wmi_parse_guid(const u8 *src, u8 *dest)
+{
+	static const int size[] = { 4, 2, 2, 2, 6 };
+	int i, j, v;
+
+	if (src[8]  != '-' || src[13] != '-' ||
+		src[18] != '-' || src[23] != '-')
+		return false;
+
+	for (j = 0; j < 5; j++, src++) {
+		for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) {
+			v = wmi_parse_hexbyte(src);
+			if (v < 0)
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static bool find_guid(const char *guid_string, struct wmi_block **out)
+{
+	char tmp[16], guid_input[16];
+	struct wmi_block *wblock;
+	struct guid_block *block;
+	struct list_head *p;
+
+	wmi_parse_guid(guid_string, tmp);
+	wmi_swap_bytes(tmp, guid_input);
+
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		block = &wblock->gblock;
+
+		if (memcmp(block->guid, guid_input, 16) == 0) {
+			if (out)
+				*out = wblock;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Exported WMI functions
+ */
+/**
+ * wmi_evaluate_method - Evaluate a WMI method
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * @method_id: Method ID to call
+ * &in: Buffer containing input for the method call
+ * &out: Empty buffer to return the method results
+ *
+ * Call an ACPI-WMI method
+ */
+acpi_status wmi_evaluate_method(const char *guid_string, u8 instance,
+u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	acpi_status status;
+	struct acpi_object_list input;
+	union acpi_object params[3];
+	char method[4] = "WM";
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_BAD_ADDRESS;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (!block->flags & ACPI_WMI_METHOD)
+		return AE_BAD_DATA;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	input.count = 2;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = instance;
+	params[1].type = ACPI_TYPE_INTEGER;
+	params[1].integer.value = method_id;
+
+	if (in) {
+		input.count = 3;
+
+		if (block->flags & ACPI_WMI_STRING) {
+			params[2].type = ACPI_TYPE_STRING;
+		} else {
+			params[2].type = ACPI_TYPE_BUFFER;
+		}
+		params[2].buffer.length = in->length;
+		params[2].buffer.pointer = in->pointer;
+	}
+
+	strncat(method, block->object_id, 2);
+
+	status = acpi_evaluate_object(handle, method, &input, out);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_evaluate_method);
+
+/**
+ * wmi_query_block - Return contents of a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &out: Empty buffer to return the contents of the data block to
+ *
+ * Return the contents of an ACPI-WMI data block to a buffer
+ */
+acpi_status wmi_query_block(const char *guid_string, u8 instance,
+struct acpi_buffer *out)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	acpi_status status, wc_status = AE_ERROR;
+	struct acpi_object_list input, wc_input;
+	union acpi_object wc_params[1], wq_params[1];
+	char method[4];
+	char wc_method[4] = "WC";
+
+	if (guid_string == NULL || out == NULL)
+		return AE_BAD_PARAMETER;
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_BAD_ADDRESS;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	/* Check GUID is a data block */
+	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+		return AE_BAD_ADDRESS;
+
+	input.count = 1;
+	input.pointer = wq_params;
+	wq_params[0].type = ACPI_TYPE_INTEGER;
+	wq_params[0].integer.value = instance;
+
+	/*
+	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
+	 * enable collection.
+	 */
+	if (block->flags & ACPI_WMI_EXPENSIVE) {
+		wc_input.count = 1;
+		wc_input.pointer = wc_params;
+		wc_params[0].type = ACPI_TYPE_INTEGER;
+		wc_params[0].integer.value = 1;
+
+		strncat(wc_method, block->object_id, 2);
+
+		/*
+		 * Some GUIDs break the specification by declaring themselves
+		 * expensive, but have no corresponding WCxx method. So we
+		 * should not fail if this happens.
+		 */
+		wc_status = acpi_evaluate_object(handle, wc_method,
+			&wc_input, NULL);
+	}
+
+	strcpy(method, "WQ");
+	strncat(method, block->object_id, 2);
+
+	status = acpi_evaluate_object(handle, method, NULL, out);
+
+	/*
+	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
+	 * the WQxx method failed - we should disable collection anyway.
+	 */
+	if ((block->flags & ACPI_WMI_EXPENSIVE) && wc_status) {
+		wc_params[0].integer.value = 0;
+		status = acpi_evaluate_object(handle,
+		wc_method, &wc_input, NULL);
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_query_block);
+
+/**
+ * wmi_set_block - Write to a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &in: Buffer containing new values for the data block
+ *
+ * Write the contents of the input buffer to an ACPI-WMI data block
+ */
+acpi_status wmi_set_block(const char *guid_string, u8 instance,
+const struct acpi_buffer *in)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	struct acpi_object_list input;
+	union acpi_object params[2];
+	char method[4] = "WS";
+
+	if (!guid_string || !in)
+		return AE_BAD_DATA;
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_BAD_ADDRESS;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	/* Check GUID is a data block */
+	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+		return AE_BAD_ADDRESS;
+
+	input.count = 2;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = instance;
+
+	if (block->flags & ACPI_WMI_STRING) {
+		params[1].type = ACPI_TYPE_STRING;
+	} else {
+		params[1].type = ACPI_TYPE_BUFFER;
+	}
+	params[1].buffer.length = in->length;
+	params[1].buffer.pointer = in->pointer;
+
+	strncat(method, block->object_id, 2);
+
+	return acpi_evaluate_object(handle, method, &input, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_set_block);
+
+/**
+ * wmi_install_notify_handler - Register handler for WMI events
+ * @handler: Function to handle notifications
+ * @data: Data to be returned to handler when event is fired
+ *
+ * Register a handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_install_notify_handler(const char *guid,
+wmi_notify_handler handler, void *data)
+{
+	struct wmi_block *block;
+
+	if (!guid || !handler)
+		return AE_BAD_PARAMETER;
+
+	find_guid(guid, &block);
+	if (!block)
+		return AE_NOT_EXIST;
+
+	if (block->handler)
+		return AE_ALREADY_ACQUIRED;
+
+	block->handler = handler;
+	block->handler_data = data;
+
+	return AE_OK;
+}
+EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
+
+/**
+ * wmi_uninstall_notify_handler - Unregister handler for WMI events
+ *
+ * Unregister handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_remove_notify_handler(const char *guid)
+{
+	struct wmi_block *block;
+
+	if (!guid)
+		return AE_BAD_PARAMETER;
+
+	find_guid(guid, &block);
+	if (!block)
+		return AE_NOT_EXIST;
+
+	if (!block->handler)
+		return AE_NULL_ENTRY;
+
+	block->handler = NULL;
+	block->handler_data = NULL;
+
+	return AE_OK;
+}
+EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
+
+/**
+ * wmi_get_event_data - Get WMI data associated with an event
+ *
+ * @event - Event to find
+ * &out - Buffer to hold event data
+ *
+ * Returns extra data associated with an event in WMI.
+ */
+acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
+{
+	struct acpi_object_list input;
+	union acpi_object params[1];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+
+	input.count = 1;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = event;
+
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		gblock = &wblock->gblock;
+
+		if ((gblock->flags & ACPI_WMI_EVENT) &&
+			(gblock->notify_id == event))
+			return acpi_evaluate_object(wblock->handle, "_WED",
+				&input, out);
+	}
+
+	return AE_NOT_FOUND;
+}
+EXPORT_SYMBOL_GPL(wmi_get_event_data);
+
+/**
+ * wmi_has_guid - Check if a GUID is available
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ *
+ * Check if a given GUID is defined by _WDG
+ */
+bool wmi_has_guid(const char *guid_string)
+{
+	return find_guid(guid_string, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_has_guid);
+
+/*
+ * Parse the _WDG method for the GUID data blocks
+ */
+static __init acpi_status parse_wdg(acpi_handle handle)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	acpi_status status;
+	u32 i, total;
+
+	status = acpi_evaluate_object(handle, "_WDG", NULL, &out);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+
+	if (obj->type != ACPI_TYPE_BUFFER)
+		return AE_ERROR;
+
+	total = obj->buffer.length / sizeof(struct guid_block);
+
+	gblock = kzalloc(obj->buffer.length, GFP_KERNEL);
+	if (!gblock)
+		return AE_NO_MEMORY;
+
+	memcpy(gblock, obj->buffer.pointer, obj->buffer.length);
+
+	for (i = 0; i < total; i++) {
+		wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
+		if (!wblock)
+			return AE_NO_MEMORY;
+
+		wblock->gblock = gblock[i];
+		wblock->handle = handle;
+		list_add_tail(&wblock->list, &wmi_blocks.list);
+	}
+
+	kfree(out.pointer);
+	kfree(gblock);
+
+	return status;
+}
+
+/*
+ * WMI can have EmbeddedControl access regions. In which case, we just want to
+ * hand these off to the EC driver.
+ */
+static acpi_status
+acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
+		      u32 bits, acpi_integer *value,
+		      void *handler_context, void *region_context)
+{
+	int result = 0, i = 0;
+	u8 temp = 0;
+
+	if ((address > 0xFF) || !value)
+		return AE_BAD_PARAMETER;
+
+	if (function != ACPI_READ && function != ACPI_WRITE)
+		return AE_BAD_PARAMETER;
+
+	if (bits != 8)
+		return AE_BAD_PARAMETER;
+
+	if (function == ACPI_READ) {
+		result = ec_read(address, &temp);
+		(*value) |= ((acpi_integer)temp) << i;
+	} else {
+		temp = 0xff & ((*value) >> i);
+		result = ec_write(address, temp);
+	}
+
+	switch (result) {
+	case -EINVAL:
+		return AE_BAD_PARAMETER;
+		break;
+	case -ENODEV:
+		return AE_NOT_FOUND;
+		break;
+	case -ETIME:
+		return AE_TIME;
+		break;
+	default:
+		return AE_OK;
+	}
+}
+
+static void acpi_wmi_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct acpi_device *device = data;
+
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		block = &wblock->gblock;
+
+		if ((block->flags & ACPI_WMI_EVENT) &&
+			(block->notify_id == event)) {
+			if (wblock->handler)
+				wblock->handler(event, wblock->handler_data);
+
+			acpi_bus_generate_netlink_event(
+				device->pnp.device_class, device->dev.bus_id,
+				event, 0);
+			break;
+		}
+	}
+}
+
+static int acpi_wmi_remove(struct acpi_device *device, int type)
+{
+	acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+		acpi_wmi_notify);
+
+	acpi_remove_address_space_handler(device->handle,
+				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
+
+	return 0;
+}
+
+static int __init acpi_wmi_add(struct acpi_device *device)
+{
+	acpi_status status;
+	int result = 0;
+
+	status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+		acpi_wmi_notify, device);
+	if (ACPI_FAILURE(status)) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Error installing notify handler\n"));
+		return -ENODEV;
+	}
+
+	status = acpi_install_address_space_handler(device->handle,
+						    ACPI_ADR_SPACE_EC,
+						    &acpi_wmi_ec_space_handler,
+						    NULL, NULL);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	status = parse_wdg(device->handle);
+	if (ACPI_FAILURE(status)) {
+		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
+				  "Error installing EC region handler\n"));
+		return -ENODEV;
+	}
+
+	return result;
+}
+
+static int __init acpi_wmi_init(void)
+{
+	acpi_status result;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	INIT_LIST_HEAD(&wmi_blocks.list);
+
+	result = acpi_bus_register_driver(&acpi_wmi_driver);
+
+	if (result < 0) {
+		printk(KERN_INFO PREFIX "Error loading mapper\n");
+	} else {
+		printk(KERN_INFO PREFIX "Mapper loaded\n");
+	}
+
+	return result;
+}
+
+static void __exit acpi_wmi_exit(void)
+{
+	struct list_head *p, *tmp;
+	struct wmi_block *wblock;
+
+	acpi_bus_unregister_driver(&acpi_wmi_driver);
+
+	list_for_each_safe(p, tmp, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		list_del(p);
+		kfree(wblock);
+	}
+
+	printk(KERN_INFO PREFIX "Mapper unloaded\n");
+}
+
+subsys_initcall(acpi_wmi_init);
+module_exit(acpi_wmi_exit);
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 4497964..aeffe08 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -176,6 +176,26 @@ extern int ec_transaction(u8 command,
 
 #endif /*CONFIG_ACPI_EC*/
 
+#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
+
+typedef void (*wmi_notify_handler) (u32 value, void *context);
+
+extern acpi_status wmi_evaluate_method(const char *guid, u8 instance,
+					u32 method_id,
+					const struct acpi_buffer *in,
+					struct acpi_buffer *out);
+extern acpi_status wmi_query_block(const char *guid, u8 instance,
+					struct acpi_buffer *out);
+extern acpi_status wmi_set_block(const char *guid, u8 instance,
+					const struct acpi_buffer *in);
+extern acpi_status wmi_install_notify_handler(const char *guid,
+					wmi_notify_handler handler, void *data);
+extern acpi_status wmi_remove_notify_handler(const char *guid);
+extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out);
+extern bool wmi_has_guid(const char *guid);
+
+#endif	/* CONFIG_ACPI_WMI */
+
 extern int acpi_blacklisted(void);
 extern void acpi_bios_year(char *s);
 


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops
  2008-01-12  2:20 [PATCH 0/5] WMI Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
@ 2008-01-12  2:20 ` Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12  2:20 UTC (permalink / raw)
  To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett

This is a driver for newer Acer (and Wistron) laptops. It adds wireless
radio and bluetooth control, and on some laptops, exposes the mail LED and
LCD backlight.

v1:

* Initial release

v2:

* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
  work is available)
* Checkpatch fixes

v3:

* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220

v4:

* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
  WMID laptops.

v5:

* Add EC quirk for Medion MD 98000
* Add autodetection for AMW0, and mail LED on AMW0 and AMW0 V2.
* Improve error handling
* Fix AMW0 V2 bluetooth and wireless, by using both WMID and AMW0 methods
  to ensure that the correct value is always set.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/misc/Kconfig    |   16 +
 drivers/misc/Makefile   |    1 
 drivers/misc/acer-wmi.c | 1100 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1117 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/acer-wmi.c


diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b1f9a40..d72acbf 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -92,6 +92,22 @@ config TIFM_7XX1
           To compile this driver as a module, choose M here: the module will
 	  be called tifm_7xx1.
 
+config ACER_WMI
+        tristate "Acer Laptop WMI-ACPI Extras (EXPERIMENTAL)"
+	depends on X86
+	depends on EXPERIMENTAL
+	depends on ACPI
+	depends on ACPI_WMI
+	depends on LEDS_CLASS
+	depends on BACKLIGHT_CLASS_DEVICE
+	---help---
+	  This is a driver for newer Acer (and Wistron) laptops. It adds
+	  wireless radio and bluetooth control, and on some laptops,
+	  exposes the mail LED and LCD backlight.
+
+	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
+	  here.
+
 config ASUS_LAPTOP
         tristate "Asus Laptop Extras (EXPERIMENTAL)"
         depends on X86
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685..3da1491 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ obj- := misc.o	# Dummy rule to force built-in.o to be made
 obj-$(CONFIG_IBM_ASM)		+= ibmasm/
 obj-$(CONFIG_HDPU_FEATURES)	+= hdpuftrs/
 obj-$(CONFIG_MSI_LAPTOP)     += msi-laptop.o
+obj-$(CONFIG_ACER_WMI)     += acer-wmi.o
 obj-$(CONFIG_ASUS_LAPTOP)     += asus-laptop.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
 obj-$(CONFIG_LKDTM)		+= lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 0000000..99f0076
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1100 @@
+/*
+ *  Acer Laptop WMI Extras
+ *
+ *  Copyright (C) 2007-2008	Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ *  Based on acer_acpi:
+ *    Copyright (C) 2005-2007	E.M. Smith
+ *    Copyright (C) 2007-2008	Carlos Corbacho <cathectic@gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define ACER_WMI_VERSION	"0.1"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/dmi.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/i8042.h>
+
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
+MODULE_LICENSE("GPL");
+
+#define ACER_LOGPREFIX "acer-wmi: "
+#define ACER_ERR KERN_ERR ACER_LOGPREFIX
+#define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX
+#define ACER_INFO KERN_INFO ACER_LOGPREFIX
+
+/*
+ * The following defines quirks to get some specific functions to work
+ * which are known to not be supported over ACPI-WMI (such as the mail LED
+ * on WMID based Acer's)
+ */
+struct acer_quirks {
+	const char *vendor;
+	const char *model;
+	u16 quirks;
+};
+
+/*
+ * Magic Number
+ * Meaning is unknown - this number is required for writing to ACPI for AMW0
+ * (it's also used in acerhk when directly accessing the BIOS)
+ */
+#define ACER_AMW0_WRITE	0x9610
+
+/*
+ * Bit masks for the AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK  0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK   0x31
+
+/*
+ * Method IDs for WMID interface
+ */
+#define ACER_WMID_GET_WIRELESS_METHODID		1
+#define ACER_WMID_GET_BLUETOOTH_METHODID	2
+#define ACER_WMID_GET_BRIGHTNESS_METHODID	3
+#define ACER_WMID_SET_WIRELESS_METHODID		4
+#define ACER_WMID_SET_BLUETOOTH_METHODID	5
+#define ACER_WMID_SET_BRIGHTNESS_METHODID	6
+#define ACER_WMID_GET_THREEG_METHODID		10
+#define ACER_WMID_SET_THREEG_METHODID		11
+
+/*
+ * Acer ACPI method GUIDs
+ */
+#define AMW0_GUID1		"67C3371D-95A3-4C37-BB61-DD47B491DAAB"
+#define WMID_GUID1		"6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"
+#define WMID_GUID2		"95764E09-FB56-4e83-B31A-37761F60994A"
+
+MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB");
+MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3");
+
+/* Temporary workaround until the WMI sysfs interface goes in */
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+
+/*
+ * Interface capability flags
+ */
+#define ACER_CAP_MAILLED		(1<<0)
+#define ACER_CAP_WIRELESS		(1<<1)
+#define ACER_CAP_BLUETOOTH		(1<<2)
+#define ACER_CAP_BRIGHTNESS		(1<<3)
+#define ACER_CAP_THREEG			(1<<4)
+#define ACER_CAP_ANY			(0xFFFFFFFF)
+
+/*
+ * Interface type flags
+ */
+enum interface_flags {
+	ACER_AMW0,
+	ACER_AMW0_V2,
+	ACER_WMID,
+};
+
+#define ACER_DEFAULT_WIRELESS  0
+#define ACER_DEFAULT_BLUETOOTH 0
+#define ACER_DEFAULT_MAILLED   0
+#define ACER_DEFAULT_THREEG    0
+
+static int max_brightness = 0xF;
+
+static int wireless = -1;
+static int bluetooth = -1;
+static int mailled = -1;
+static int brightness = -1;
+static int threeg = -1;
+static int force_series;
+
+module_param(mailled, int, 0444);
+module_param(wireless, int, 0444);
+module_param(bluetooth, int, 0444);
+module_param(brightness, int, 0444);
+module_param(threeg, int, 0444);
+module_param(force_series, int, 0444);
+MODULE_PARM_DESC(wireless, "Set initial state of Wireless hardware");
+MODULE_PARM_DESC(bluetooth, "Set initial state of Bluetooth hardware");
+MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
+MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
+MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
+MODULE_PARM_DESC(force_series, "Force a different laptop series");
+
+struct acer_data {
+	int mailled;
+	int wireless;
+	int bluetooth;
+	int threeg;
+	int brightness;
+};
+
+/* Each low-level interface must define at least some of the following */
+struct wmi_interface {
+	/* The WMI device type */
+	u32 type;
+
+	/* The capabilities this interface provides */
+	u32 capability;
+
+	/* Private data for the current interface */
+	struct acer_data data;
+};
+
+/* The static interface pointer, points to the currently detected interface */
+static struct wmi_interface *interface;
+
+/*
+ * Embedded Controller quirks
+ * Some laptops require us to directly access the EC to either enable or query
+ * features that are not available through WMI.
+ */
+
+struct quirk_entry {
+	u8 wireless;
+	u8 mailled;
+	u8 brightness;
+	u8 bluetooth;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+	if (quirks->mailled)
+		interface->capability |= ACER_CAP_MAILLED;
+
+	if (quirks->brightness)
+		interface->capability |= ACER_CAP_BRIGHTNESS;
+}
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 0;
+}
+
+static struct quirk_entry quirk_unknown = {
+};
+
+static struct quirk_entry quirk_acer_travelmate_2490 = {
+	.mailled = 1,
+};
+
+/* This AMW0 laptop has no bluetooth */
+static struct quirk_entry quirk_medion_md_98300 = {
+	.wireless = 1,
+};
+
+static struct dmi_system_id acer_quirks[] = {
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 3100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3100"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5100"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5630",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5650",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5680",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer TravelMate 2490",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Medion MD 98300",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "MEDION"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "WAM2030"),
+		},
+		.driver_data = &quirk_medion_md_98300,
+	},
+	{}
+};
+
+/* Find which quirks are needed for a particular vendor/ model pair */
+static void find_quirks(void)
+{
+	if (!force_series) {
+		dmi_check_system(acer_quirks);
+	} else if (force_series == 2490) {
+		quirks = &quirk_acer_travelmate_2490;
+	}
+
+	if (quirks == NULL)
+		quirks = &quirk_unknown;
+
+	set_quirks();
+}
+
+/*
+ * General interface convenience methods
+ */
+
+static bool has_cap(u32 cap)
+{
+	if ((interface->capability & cap) != 0)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * AMW0 (V1) interface
+ */
+struct wmab_args {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+};
+
+struct wmab_ret {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+	u32 eex;
+};
+
+static acpi_status wmab_execute(struct wmab_args *regbuf,
+struct acpi_buffer *result)
+{
+	struct acpi_buffer input;
+	acpi_status status;
+	input.length = sizeof(struct wmab_args);
+	input.pointer = (u8 *)regbuf;
+
+	status = wmi_evaluate_method(AMW0_GUID1, 1, 1, &input, result);
+
+	return status;
+}
+
+static acpi_status AMW0_get_u32(u32 *value, u32 cap,
+struct wmi_interface *iface)
+{
+	int err;
+	u8 result;
+
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		switch (quirks->mailled) {
+		default:
+			err = ec_read(0x0A, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 7) & 0x01;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_WIRELESS:
+		switch (quirks->wireless) {
+		case 1:
+			err = ec_read(0x7B, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result & 0x01;
+			return AE_OK;
+		default:
+			err = ec_read(0x0A, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 2) & 0x01;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_BLUETOOTH:
+		switch (quirks->bluetooth) {
+		default:
+			err = ec_read(0x0A, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 4) & 0x01;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		switch (quirks->brightness) {
+		default:
+			err = ec_read(0x83, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result;
+			return AE_OK;
+		}
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	return AE_OK;
+}
+
+static acpi_status AMW0_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
+{
+	struct wmab_args args;
+
+	args.eax = ACER_AMW0_WRITE;
+	args.ebx = value ? (1<<8) : 0;
+	args.ecx = args.edx = 0;
+
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_MAILLED_MASK;
+		break;
+	case ACER_CAP_WIRELESS:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_WIRELESS_MASK;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		if (value > max_brightness)
+			return AE_BAD_PARAMETER;
+		switch (quirks->brightness) {
+		case 1:
+			return ec_write(0x83, value);
+		default:
+			return AE_BAD_ADDRESS;
+		break;
+		}
+	default:
+		return AE_BAD_ADDRESS;
+	}
+
+	/* Actually do the set */
+	return wmab_execute(&args, NULL);
+}
+
+static acpi_status AMW0_find_mailled(void)
+{
+	struct wmab_args args;
+	struct wmab_ret ret;
+	acpi_status status = AE_OK;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	args.eax = 0x86;
+	args.ebx = args.ecx = args.edx = 0;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	if (ret.eex & 0x1)
+		interface->capability |= ACER_CAP_MAILLED;
+
+	kfree(out.pointer);
+
+	return AE_OK;
+}
+
+static acpi_status AMW0_set_capabilities(void)
+{
+	struct wmab_args args;
+	struct wmab_ret ret;
+	acpi_status status = AE_OK;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	args.eax = ACER_AMW0_WRITE;
+	args.ecx = args.edx = 0;
+
+	args.ebx = 0xa2 << 8;
+	args.ebx |= ACER_AMW0_WIRELESS_MASK;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	if (ret.eax & 0x1)
+		interface->capability |= ACER_CAP_WIRELESS;
+
+	args.ebx = 2 << 8;
+	args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER
+	&& obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	if (ret.eax & 0x1)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	kfree(out.pointer);
+
+	return AE_OK;
+}
+
+static struct wmi_interface AMW0_interface = {
+	.type = ACER_AMW0,
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+	.type = ACER_AMW0_V2,
+};
+
+/*
+ * New interface (The WMID interface)
+ */
+static acpi_status
+WMI_execute_u32(u32 method_id, u32 in, u32 *out)
+{
+	struct acpi_buffer input = { (acpi_size) sizeof(u32), (void *)(&in) };
+	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	u32 tmp;
+	acpi_status status;
+
+	status = wmi_evaluate_method(WMID_GUID1, 1, method_id, &input, &result);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) result.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+		obj->buffer.length == sizeof(u32)) {
+		tmp = *((u32 *) obj->buffer.pointer);
+	} else {
+		tmp = 0;
+	}
+
+	if (out)
+		*out = tmp;
+
+	kfree(result.pointer);
+
+	return status;
+}
+
+static acpi_status WMID_get_u32(u32 *value, u32 cap,
+struct wmi_interface *iface)
+{
+	acpi_status status;
+	u8 tmp;
+	u32 result, method_id = 0;
+
+	switch (cap) {
+	case ACER_CAP_WIRELESS:
+		method_id = ACER_WMID_GET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		method_id = ACER_WMID_GET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		method_id = ACER_WMID_GET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		method_id = ACER_WMID_GET_THREEG_METHODID;
+		break;
+	case ACER_CAP_MAILLED:
+		if (quirks->mailled == 1) {
+			ec_read(0x9f, &tmp);
+			*value = tmp;
+			return 0;
+		}
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	status = WMI_execute_u32(method_id, 0, &result);
+
+	if (ACPI_SUCCESS(status))
+		*value = (u8)result;
+
+	return status;
+}
+
+static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
+{
+	u32 method_id = 0;
+	char param;
+
+	switch (cap) {
+	case ACER_CAP_BRIGHTNESS:
+		if (value > max_brightness)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_WIRELESS:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_THREEG_METHODID;
+		break;
+	case ACER_CAP_MAILLED:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		if (quirks->mailled == 1) {
+			param = value ? 0x92 : 0x93;
+			i8042_command(&param, 0x1059);
+			return 0;
+		}
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+static acpi_status WMID_set_capabilities(void)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	acpi_status status;
+	u32 devices;
+
+	status = wmi_query_block(WMID_GUID2, 1, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+		obj->buffer.length == sizeof(u32)) {
+		devices = *((u32 *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	/* Not sure on the meaning of the relevant bits yet to detect these */
+	interface->capability |= ACER_CAP_WIRELESS;
+	interface->capability |= ACER_CAP_THREEG;
+
+	/* WMID always provides brightness methods */
+	interface->capability |= ACER_CAP_BRIGHTNESS;
+
+	if (devices & 0x10)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	if (!(devices & 0x20))
+		max_brightness = 0x9;
+
+	return status;
+}
+
+static struct wmi_interface wmid_interface = {
+	.type = ACER_WMID,
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_u32(u32 *value, u32 cap)
+{
+	acpi_status status = AE_BAD_ADDRESS;
+
+	switch (interface->type) {
+	case ACER_AMW0:
+		status = AMW0_get_u32(value, cap, interface);
+		break;
+	case ACER_AMW0_V2:
+		if (cap == ACER_CAP_MAILLED) {
+			status = AMW0_get_u32(value, cap, interface);
+			break;
+		}
+	case ACER_WMID:
+		status = WMID_get_u32(value, cap, interface);
+		break;
+	}
+
+	return status;
+}
+
+static acpi_status set_u32(u32 value, u32 cap)
+{
+	if (interface->capability & cap) {
+		switch (interface->type) {
+		case ACER_AMW0:
+			return AMW0_set_u32(value, cap, interface);
+		case ACER_AMW0_V2:
+		case ACER_WMID:
+			return WMID_set_u32(value, cap, interface);
+		default:
+			return AE_BAD_PARAMETER;
+		}
+	}
+	return AE_BAD_PARAMETER;
+}
+
+static void __init acer_commandline_init(void)
+{
+	/*
+	 * These will all fail silently if the value given is invalid, or the
+	 * capability isn't available on the given interface
+	 */
+	set_u32(mailled, ACER_CAP_MAILLED);
+	set_u32(wireless, ACER_CAP_WIRELESS);
+	set_u32(bluetooth, ACER_CAP_BLUETOOTH);
+	set_u32(threeg, ACER_CAP_THREEG);
+	set_u32(brightness, ACER_CAP_BRIGHTNESS);
+}
+
+/*
+ * LED device (Mail LED only, no other LEDs known yet)
+ */
+static void mail_led_set(struct led_classdev *led_cdev,
+enum led_brightness value)
+{
+	set_u32(value, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+	.name = "acer-mail:green",
+	.brightness_set = mail_led_set,
+};
+
+static int __init acer_led_init(struct device *dev)
+{
+	return led_classdev_register(dev, &mail_led);
+}
+
+static void __exit acer_led_exit(void)
+{
+	led_classdev_unregister(&mail_led);
+}
+
+/*
+ * Backlight device
+ */
+static struct backlight_device *acer_backlight_device;
+
+static int read_brightness(struct backlight_device *bd)
+{
+	u32 value;
+	get_u32(&value, ACER_CAP_BRIGHTNESS);
+	return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+	set_u32(bd->props.brightness, ACER_CAP_BRIGHTNESS);
+	return 0;
+}
+
+static struct backlight_ops acer_bl_ops = {
+	.get_brightness = read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int __init acer_backlight_init(struct device *dev)
+{
+	struct backlight_device *bd;
+
+	bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops);
+	if (IS_ERR(bd)) {
+		printk(ACER_ERR "Could not register Acer backlight device\n");
+		acer_backlight_device = NULL;
+		return PTR_ERR(bd);
+	}
+
+	acer_backlight_device = bd;
+
+	bd->props.max_brightness = max_brightness;
+	bd->props.brightness = read_brightness(NULL);
+	backlight_update_status(bd);
+	return 0;
+}
+
+static void __exit acer_backlight_exit(void)
+{
+	backlight_device_unregister(acer_backlight_device);
+}
+
+/*
+ * Read/ write bool sysfs macro
+ */
+#define show_set_bool(value, cap) \
+static ssize_t \
+show_bool_##value(struct device *dev, struct device_attribute *attr, \
+	char *buf) \
+{ \
+	u32 result; \
+	acpi_status status = get_u32(&result, cap); \
+	if (ACPI_SUCCESS(status)) \
+		return sprintf(buf, "%u\n", result); \
+	return sprintf(buf, "Read error\n"); \
+} \
+\
+static ssize_t \
+set_bool_##value(struct device *dev, struct device_attribute *attr, \
+	const char *buf, size_t count) \
+{ \
+	u32 tmp = simple_strtoul(buf, NULL, 10); \
+	acpi_status status = set_u32(tmp, cap); \
+		if (ACPI_FAILURE(status)) \
+			return -EINVAL; \
+	return count; \
+} \
+static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \
+	show_bool_##value, set_bool_##value);
+
+show_set_bool(wireless, ACER_CAP_WIRELESS);
+show_set_bool(bluetooth, ACER_CAP_BLUETOOTH);
+show_set_bool(threeg, ACER_CAP_THREEG);
+
+/*
+ * Read interface sysfs macro
+ */
+static ssize_t show_interface(struct device *dev, struct device_attribute *attr,
+	char *buf)
+{
+	switch (interface->type) {
+	case ACER_AMW0:
+		return sprintf(buf, "AMW0\n");
+	case ACER_AMW0_V2:
+		return sprintf(buf, "AMW0 v2\n");
+	case ACER_WMID:
+		return sprintf(buf, "WMID\n");
+	default:
+		return sprintf(buf, "Error!\n");
+	}
+}
+
+static DEVICE_ATTR(interface, S_IWUGO | S_IRUGO | S_IWUSR,
+	show_interface, NULL);
+
+/*
+ * Platform device
+ */
+static int __devinit acer_platform_probe(struct platform_device *device)
+{
+	int err;
+
+	if (has_cap(ACER_CAP_MAILLED)) {
+		err = acer_led_init(&device->dev);
+		if (err)
+			goto error_mailled;
+	}
+
+	if (has_cap(ACER_CAP_BRIGHTNESS)) {
+		err = acer_backlight_init(&device->dev);
+		if (err)
+			goto error_brightness;
+	}
+
+	return 0;
+
+error_brightness:
+	acer_led_exit();
+error_mailled:
+	return err;
+}
+
+static int acer_platform_remove(struct platform_device *device)
+{
+	if (has_cap(ACER_CAP_MAILLED))
+		acer_led_exit();
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		acer_backlight_exit();
+	return 0;
+}
+
+static int acer_platform_suspend(struct platform_device *dev,
+pm_message_t state)
+{
+	u32 value;
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return -ENOMEM;
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		get_u32(&value, ACER_CAP_WIRELESS);
+		data->wireless = value;
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		get_u32(&value, ACER_CAP_BLUETOOTH);
+		data->bluetooth = value;
+	}
+
+	if (has_cap(ACER_CAP_MAILLED)) {
+		get_u32(&value, ACER_CAP_MAILLED);
+		data->mailled = value;
+	}
+
+	if (has_cap(ACER_CAP_BRIGHTNESS)) {
+		get_u32(&value, ACER_CAP_BRIGHTNESS);
+		data->brightness = value;
+	}
+
+	return 0;
+}
+
+static int acer_platform_resume(struct platform_device *device)
+{
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return -ENOMEM;
+
+	if (has_cap(ACER_CAP_WIRELESS))
+		set_u32(data->wireless, ACER_CAP_WIRELESS);
+
+	if (has_cap(ACER_CAP_BLUETOOTH))
+		set_u32(data->bluetooth, ACER_CAP_BLUETOOTH);
+
+	if (has_cap(ACER_CAP_THREEG))
+		set_u32(data->threeg, ACER_CAP_THREEG);
+
+	if (has_cap(ACER_CAP_MAILLED))
+		set_u32(data->mailled, ACER_CAP_MAILLED);
+
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		set_u32(data->brightness, ACER_CAP_BRIGHTNESS);
+
+	return 0;
+}
+
+static struct platform_driver acer_platform_driver = {
+	.driver = {
+		.name = "acer-wmi",
+		.owner = THIS_MODULE,
+	},
+	.probe = acer_platform_probe,
+	.remove = acer_platform_remove,
+	.suspend = acer_platform_suspend,
+	.resume = acer_platform_resume,
+};
+
+static struct platform_device *acer_platform_device;
+
+static int remove_sysfs(struct platform_device *device)
+{
+	if (has_cap(ACER_CAP_WIRELESS))
+		device_remove_file(&device->dev, &dev_attr_wireless);
+
+	if (has_cap(ACER_CAP_BLUETOOTH))
+		device_remove_file(&device->dev, &dev_attr_bluetooth);
+
+	if (has_cap(ACER_CAP_THREEG))
+		device_remove_file(&device->dev, &dev_attr_threeg);
+
+	device_remove_file(&device->dev, &dev_attr_interface);
+
+	return 0;
+}
+
+static int create_sysfs(void)
+{
+	int retval = -ENOMEM;
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		retval = device_create_file(&acer_platform_device->dev,
+			&dev_attr_wireless);
+		if (retval)
+			goto error_sysfs;
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		retval = device_create_file(&acer_platform_device->dev,
+			&dev_attr_bluetooth);
+		if (retval)
+			goto error_sysfs;
+	}
+
+	if (has_cap(ACER_CAP_THREEG)) {
+		retval = device_create_file(&acer_platform_device->dev,
+			&dev_attr_threeg);
+		if (retval)
+			goto error_sysfs;
+	}
+
+	retval = device_create_file(&acer_platform_device->dev,
+		&dev_attr_interface);
+	if (retval)
+		goto error_sysfs;
+
+	return 0;
+
+error_sysfs:
+		remove_sysfs(acer_platform_device);
+	return retval;
+}
+
+static int __init acer_wmi_init(void)
+{
+	int err;
+
+	printk(ACER_INFO "Acer Laptop ACPI-WMI Extras version %s\n",
+			ACER_WMI_VERSION);
+
+	find_quirks();
+
+	/*
+	 * Detect which ACPI-WMI interface we're using.
+	 */
+	if (wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+		interface = &AMW0_V2_interface;
+
+	if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+		interface = &wmid_interface;
+
+	if (wmi_has_guid(WMID_GUID2) && interface) {
+		if (ACPI_FAILURE(WMID_set_capabilities())) {
+			printk(ACER_ERR "Unable to detect available devices\n");
+			return -ENODEV;
+		}
+	} else if (!wmi_has_guid(WMID_GUID2) && interface) {
+		printk(ACER_ERR "Unable to detect available devices\n");
+		return -ENODEV;
+	}
+
+	if (wmi_has_guid(AMW0_GUID1) && !wmi_has_guid(WMID_GUID1)) {
+		interface = &AMW0_interface;
+
+		if (!quirks->brightness) {
+			quirks->brightness = 1;
+			interface->capability |= ACER_CAP_BRIGHTNESS;
+		}
+
+		if (ACPI_FAILURE(AMW0_set_capabilities())) {
+			printk(ACER_ERR "Unable to detect available devices\n");
+			return -ENODEV;
+		}
+	}
+
+	if (wmi_has_guid(AMW0_GUID1)) {
+		if (ACPI_FAILURE(AMW0_find_mailled())) {
+			printk(ACER_ERR "Unable to detect mail LED\n");
+			return -ENODEV;
+		}
+	}
+
+	if (!interface) {
+		printk(ACER_ERR "No or unsupported WMI interface, unable to ");
+		printk(KERN_CONT "load.\n");
+		return -ENODEV;
+	}
+
+	if (platform_driver_register(&acer_platform_driver)) {
+		printk(ACER_ERR "Unable to register platform driver.\n");
+		goto error_platform_register;
+	}
+	acer_platform_device = platform_device_alloc("acer-wmi", -1);
+	platform_device_add(acer_platform_device);
+
+	err = create_sysfs();
+	if (err)
+		return err;
+
+	/* Override any initial settings with values from the commandline */
+	acer_commandline_init();
+
+	return 0;
+
+error_platform_register:
+	return -ENODEV;
+}
+
+static void __exit acer_wmi_exit(void)
+{
+	remove_sysfs(acer_platform_device);
+	platform_device_del(acer_platform_device);
+	platform_driver_unregister(&acer_platform_driver);
+
+	printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n");
+	return;
+}
+
+module_init(acer_wmi_init);
+module_exit(acer_wmi_exit);


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets
  2008-01-12  2:20 [PATCH 0/5] WMI Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
@ 2008-01-12  2:20 ` Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 5/5] [WIP] ACPI: WMI: Limit size of device string to 19 characters Carlos Corbacho
  4 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12  2:20 UTC (permalink / raw)
  To: linux-acpi
  Cc: Carlos Corbacho, Len Brown, Matthew Garrett, Jamey Hicks,
	Joshua Wise

This is based on the 2004 out-of-tree work of Jamey Hicks, to add
support via WMI for controlling the jog dial and wireless on these
tablets.

v1:

Original release

v2:

As per Joshua Wise's comments, change bluetooth to jogdial (an error from
the original driver).

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Jamey Hicks <jamey.hicks@nokia.com>
CC: Joshua Wise <joshua@joshuawise.com>
---

 drivers/misc/Kconfig      |    9 +
 drivers/misc/Makefile     |    1 
 drivers/misc/tc1100-wmi.c |  290 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 300 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/tc1100-wmi.c


diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index d72acbf..205fd6a 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -142,6 +142,15 @@ config FUJITSU_LAPTOP
 
 	  If you have a Fujitsu laptop, say Y or M here.
 
+config TC1100_WMI
+	tristate "HP Compaq TC1100 Tablet WMI Extras"
+	depends on X86 && !X86_64
+	depends on ACPI
+	depends on ACPI_WMI
+	---help---
+	  This is a driver for the WMI extensions (wireless and bluetooth power
+	  control) of the HP Compaq TC1100 tablet.
+
 config MSI_LAPTOP
         tristate "MSI Laptop Extras"
         depends on X86
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3da1491..51196c0 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_MSI_LAPTOP)     += msi-laptop.o
 obj-$(CONFIG_ACER_WMI)     += acer-wmi.o
 obj-$(CONFIG_ASUS_LAPTOP)     += asus-laptop.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
+obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_LKDTM)		+= lkdtm.o
 obj-$(CONFIG_TIFM_CORE)       	+= tifm_core.o
 obj-$(CONFIG_TIFM_7XX1)       	+= tifm_7xx1.o
diff --git a/drivers/misc/tc1100-wmi.c b/drivers/misc/tc1100-wmi.c
new file mode 100644
index 0000000..f25e4c9
--- /dev/null
+++ b/drivers/misc/tc1100-wmi.c
@@ -0,0 +1,290 @@
+/*
+ *  HP Compaq TC1100 Tablet WMI Extras Driver
+ *
+ *  Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *  Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com>
+ *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
+ *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at
+ *  your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi.h>
+#include <acpi/actypes.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/platform_device.h>
+
+#define GUID "C364AC71-36DB-495A-8494-B439D472A505"
+
+#define TC1100_INSTANCE_WIRELESS		1
+#define TC1100_INSTANCE_JOGDIAL		2
+
+#define TC1100_LOGPREFIX "tc1100-wmi: "
+#define TC1100_INFO KERN_INFO TC1100_LOGPREFIX
+
+MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho");
+MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505");
+
+static int tc1100_probe(struct platform_device *device);
+static int tc1100_remove(struct platform_device *device);
+static int tc1100_suspend(struct platform_device *device, pm_message_t state);
+static int tc1100_resume(struct platform_device *device);
+
+static struct platform_driver tc1100_driver = {
+	.driver = {
+		.name = "tc1100-wmi",
+		.owner = THIS_MODULE,
+	},
+	.probe = tc1100_probe,
+	.remove = tc1100_remove,
+	.suspend = tc1100_suspend,
+	.resume = tc1100_resume,
+};
+
+static struct platform_device *tc1100_device;
+
+struct tc1100_data {
+	u32 wireless;
+	u32 jogdial;
+};
+
+static struct tc1100_data suspend_data;
+
+/* --------------------------------------------------------------------------
+				Device Management
+   -------------------------------------------------------------------------- */
+
+static int get_state(u32 *out, u8 instance)
+{
+	u32 tmp;
+	acpi_status status;
+	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	if (!out)
+		return -EINVAL;
+
+	if (instance > 2)
+		return -ENODEV;
+
+	status = wmi_query_block(GUID, instance, &result);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	obj = (union acpi_object *) result.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+		obj->buffer.length == sizeof(u32)) {
+		tmp = *((u32 *) obj->buffer.pointer);
+	} else {
+		tmp = 0;
+	}
+
+	if (result.length > 0 && result.pointer)
+		kfree(result.pointer);
+
+	switch (instance) {
+	case TC1100_INSTANCE_WIRELESS:
+		*out = (tmp == 3) ? 1 : 0;
+		return 0;
+	case TC1100_INSTANCE_JOGDIAL:
+		*out = (tmp == 1) ? 1 : 0;
+		return 0;
+	default:
+		return -ENODEV;
+	}
+}
+
+static int set_state(u32 *in, u8 instance)
+{
+	u32 value;
+	acpi_status status;
+	struct acpi_buffer input;
+
+	if (!in)
+		return -EINVAL;
+
+	if (instance > 2)
+		return -ENODEV;
+
+	switch (instance) {
+	case TC1100_INSTANCE_WIRELESS:
+		value = (*in) ? 1 : 2;
+		break;
+	case TC1100_INSTANCE_JOGDIAL:
+		value = (*in) ? 0 : 1;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	input.length = sizeof(u32);
+	input.pointer = &value;
+
+	status = wmi_set_block(GUID, instance, &input);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
+				FS Interface (/sys)
+   -------------------------------------------------------------------------- */
+
+/*
+ * Read/ write bool sysfs macro
+ */
+#define show_set_bool(value, instance) \
+static ssize_t \
+show_bool_##value(struct device *dev, struct device_attribute *attr, \
+	char *buf) \
+{ \
+	u32 result; \
+	acpi_status status = get_state(&result, instance); \
+	if (ACPI_SUCCESS(status)) \
+		return sprintf(buf, "%d\n", result); \
+	return sprintf(buf, "Read error\n"); \
+} \
+\
+static ssize_t \
+set_bool_##value(struct device *dev, struct device_attribute *attr, \
+	const char *buf, size_t count) \
+{ \
+	u32 tmp = simple_strtoul(buf, NULL, 10); \
+	acpi_status status = set_state(&tmp, instance); \
+		if (ACPI_FAILURE(status)) \
+			return -EINVAL; \
+	return count; \
+} \
+static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \
+	show_bool_##value, set_bool_##value);
+
+show_set_bool(wireless, TC1100_INSTANCE_WIRELESS);
+show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL);
+
+static void remove_fs(void)
+{
+	device_remove_file(&tc1100_device->dev, &dev_attr_wireless);
+	device_remove_file(&tc1100_device->dev, &dev_attr_jogdial);
+}
+
+static int add_fs(void)
+{
+	int ret;
+
+	ret = device_create_file(&tc1100_device->dev, &dev_attr_wireless);
+	if (ret)
+		goto add_sysfs_error;
+
+	ret = device_create_file(&tc1100_device->dev, &dev_attr_jogdial);
+	if (ret)
+		goto add_sysfs_error;
+
+	return ret;
+
+add_sysfs_error:
+	remove_fs();
+	return ret;
+}
+
+/* --------------------------------------------------------------------------
+				Driver Model
+   -------------------------------------------------------------------------- */
+
+static int tc1100_probe(struct platform_device *device)
+{
+	int result = 0;
+
+	result = add_fs();
+	return result;
+}
+
+
+static int tc1100_remove(struct platform_device *device)
+{
+	remove_fs();
+	return 0;
+}
+
+static int tc1100_suspend(struct platform_device *dev, pm_message_t state)
+{
+	int ret;
+
+	ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+	if (ret)
+		return ret;
+
+	ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int tc1100_resume(struct platform_device *dev)
+{
+	int ret;
+
+	ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+	if (ret)
+		return ret;
+
+	ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int __init tc1100_init(void)
+{
+	int result = 0;
+
+	if (!wmi_has_guid(GUID))
+		return -ENODEV;
+
+	result = platform_driver_register(&tc1100_driver);
+	if (result)
+		return result;
+
+	tc1100_device = platform_device_alloc("tc1100-wmi", -1);
+	platform_device_add(tc1100_device);
+
+	printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras loaded\n");
+
+	return result;
+}
+
+static void __exit tc1100_exit(void)
+{
+	platform_device_del(tc1100_device);
+	platform_driver_unregister(&tc1100_driver);
+
+	printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras unloaded\n");
+}
+
+module_init(tc1100_init);
+module_exit(tc1100_exit);


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface
  2008-01-12  2:20 [PATCH 0/5] WMI Carlos Corbacho
                   ` (2 preceding siblings ...)
  2008-01-12  2:20 ` [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
@ 2008-01-12  2:20 ` Carlos Corbacho
  2008-01-12  2:20 ` [PATCH 5/5] [WIP] ACPI: WMI: Limit size of device string to 19 characters Carlos Corbacho
  4 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12  2:20 UTC (permalink / raw)
  To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett

Create a 'wmi' class, and populate it with a virtual device for each GUID.
This also allows us to autoload WMI drivers based on GUID
via MODULE_ALIAS.

Under each GUID are a set of files that can be read and written to from
userspace (these will be fully documented in a later patch).

v1 (2007-11-03)

* Initial release

v2 (2007-11-07)

* Split out into a separate patch

v3 (2007-11-20)

* Convert kobject storage to using kernel list structures.
* Change instance handling - store input data on a per instance basis,
  rather than a per GUID.
* Add locking to methods - method_id (write) and data (read & write)
  should all be mutually exclusive - we do not want the input data to
  change when we are trying to execute a method.
* Change method calling semantics: when input is written to 'data',
  store it, but don't execute the method until 'data' is read. This is
  due to the fact that it is perfectly acceptable to have a WMI method
  that does not take any input (and it is easier to trigger an execute
  on reading the file, and not return anything if there is nothing to
  return, rather than trying to write values that aren't required, or
  may cause ACPI evaluation to fail on the method).
* Do not try and convert String's from ASCII to Unicode - instead, only
  handle ASCII (as per ACPI), and leave it up to userspace to convert
  to/ from whatever encoding they wish to use.

v4 (2007-11-28)

* Add code to remove sysfs files on module removal

v5 (2007-12-08)

* Convert to a class device, instead of trying to manually create and
  manipulate kobject's - this allows us to add module autoloading for WMI
  based drivers, and will (hopefully) be less likely to break with the
  latest round of kobject changes.
* Convert GUIDs to use a 'flat' structure - add two new files:
  instance and instance_count, and use these to provide the instance
  to call for a given GUID (while still communicating the maximum number
  of instances available - for now, we will always respect instance_count
  when setting instance - so any instance > instance_count will be
  discarded).

v6 (2007-12-12)

* Add string file - this reports back if the GUID has the string flag set.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/acpi/wmi.c |  463 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 461 insertions(+), 2 deletions(-)


diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index 41656b2..b4bc1ad 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -30,6 +30,8 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
 #include <linux/list.h>
 #include <linux/acpi.h>
 #include <acpi/acpi_bus.h>
@@ -66,6 +68,11 @@ struct wmi_block {
 	acpi_handle handle;
 	wmi_notify_handler handler;
 	void *handler_data;
+	struct device dev;
+	u8 instance;
+	u32 method_id;
+	void *pointer;
+	acpi_size length;
 };
 
 static struct wmi_block wmi_blocks;
@@ -194,6 +201,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 wmi_block **out)
 {
 	char tmp[16], guid_input[16];
@@ -517,6 +551,421 @@ bool wmi_has_guid(const char *guid_string)
 EXPORT_SYMBOL_GPL(wmi_has_guid);
 
 /*
+ * sysfs interface
+ */
+static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count) {
+	u8 instance;
+	const char *guid;
+	struct acpi_buffer in;
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	acpi_status status;
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	guid = block->gblock.guid;
+	if (!guid)
+		return -EINVAL;
+
+	instance = block->instance;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		mutex_lock(&wmi_data_lock);
+		if (block->pointer) {
+			in.pointer = block->pointer;
+			in.length = block->length;
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, &in, &out);
+		} else {
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, NULL, &out);
+		}
+		mutex_unlock(&wmi_data_lock);
+	} else {
+		status = wmi_query_block(guid, instance, &out);
+	}
+
+	obj = (union acpi_object *) out.pointer;
+
+	switch (obj->type) {
+	case (ACPI_TYPE_STRING):
+	case (ACPI_TYPE_BUFFER):
+		buf = obj->buffer.pointer;
+		break;
+	default:
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count){
+	struct wmi_block *block;
+	struct acpi_buffer in;
+	acpi_status status;
+	struct device *dev;
+	char guid_string[37];
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (count == 0)
+		return count;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		block->pointer = kzalloc(count, GFP_KERNEL);
+		if (!block->pointer)
+			return count;
+
+		memcpy(block->pointer, buf, count);
+		block->length = count;
+	} else {
+		in.pointer = buf;
+		in.length = count;
+		wmi_gtoa(block->gblock.guid, guid_string);
+		status = wmi_set_block(guid_string, block->instance, &in);
+	}
+
+	return count;
+}
+
+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) {
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	wmi_get_event_data(block->gblock.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,
+};
+
+static ssize_t show_type(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return sprintf(buf, "Error\n");
+
+	block = &wblock->gblock;
+
+	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 DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
+
+static ssize_t show_instance_count(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->gblock.instance_count);
+}
+static DEVICE_ATTR(instance_count, S_IRUGO, show_instance_count, NULL);
+
+static ssize_t show_instance(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->instance);
+}
+
+static ssize_t set_instance(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 instance;
+
+	instance = simple_strtoul(buf, NULL, 10);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (instance <= block->gblock.instance_count) {
+		block->instance = instance;
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(instance, S_IWUGO | S_IRUGO, show_instance, set_instance);
+
+static ssize_t show_method_id(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->method_id);
+}
+
+static ssize_t set_method_id(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 method_id;
+
+	method_id = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&wmi_data_lock);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block) {
+		mutex_unlock(&wmi_data_lock);
+		return -EINVAL;
+	}
+
+	block->method_id = method_id;
+	mutex_unlock(&wmi_data_lock);
+
+	return count;
+}
+static DEVICE_ATTR(method_id, S_IWUGO | S_IRUGO, show_method_id,
+	set_method_id);
+
+static ssize_t show_event_notification(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", gblock->notify_id & 0xFF);
+}
+static DEVICE_ATTR(notification, S_IRUGO, show_event_notification, NULL);
+
+static ssize_t show_string(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", (gblock->flags & ACPI_WMI_STRING) >> 2);
+}
+static DEVICE_ATTR(string, S_IRUGO, show_string, NULL);
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	char guid_string[37];
+
+	struct wmi_block *wblock;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return -ENOMEM;
+
+	wmi_gtoa(wblock->gblock.guid, guid_string);
+
+	strcpy(&env->buf[env->buflen - 1], "wmi:");
+	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+	env->buflen += 40;
+
+	return 0;
+}
+
+static struct class wmi_class = {
+	.name = "wmi",
+	.dev_release = (void(*)(struct device *)) kfree,
+	.dev_uevent = wmi_dev_uevent,
+};
+
+static int wmi_create_devs(void)
+{
+	int result;
+	char guid_string[37];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Create devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		guid_dev->class = &wmi_class;
+
+		gblock = &wblock->gblock;
+
+		wmi_gtoa(gblock->guid, guid_string);
+		memcpy(guid_dev->bus_id, guid_string, 36);
+
+		result = device_register(guid_dev);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_type);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_string);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			result = device_create_file(guid_dev,
+				&dev_attr_notification);
+			if (result)
+				return result;
+
+			result = device_create_bin_file(guid_dev,
+				&wmi_attr_event_data);
+			if (result)
+				return result;
+			break;
+		}
+
+		result = device_create_file(guid_dev, &dev_attr_instance_count);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_instance);
+		if (result)
+			return result;
+
+		result = device_create_bin_file(guid_dev, &wmi_attr_data);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_METHOD) {
+			result = device_create_file(guid_dev,
+				&dev_attr_method_id);
+			if (result)
+				return result;
+		}
+	}
+
+	return 0;
+}
+
+static void wmi_remove_devs(void)
+{
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Delete devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		gblock = &wblock->gblock;
+
+		device_remove_file(guid_dev, &dev_attr_type);
+		device_remove_file(guid_dev, &dev_attr_string);
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			device_remove_file(guid_dev, &dev_attr_notification);
+			device_remove_bin_file(guid_dev, &wmi_attr_event_data);
+		} else {
+			device_remove_file(guid_dev, &dev_attr_instance_count);
+			device_remove_file(guid_dev, &dev_attr_instance);
+			device_remove_bin_file(guid_dev, &wmi_attr_data);
+
+			if (gblock->flags & ACPI_WMI_METHOD) {
+				device_remove_file(guid_dev,
+					&dev_attr_method_id);
+			}
+		}
+		device_unregister(guid_dev);
+	}
+}
+
+static int wmi_class_init(void)
+{
+	int ret;
+
+	ret = class_register(&wmi_class);
+	if (ret)
+		return ret;
+
+	return wmi_create_devs();
+}
+
+static void wmi_class_exit(void)
+{
+	wmi_remove_devs();
+	class_unregister(&wmi_class);
+}
+
+/*
  * Parse the _WDG method for the GUID data blocks
  */
 static __init acpi_status parse_wdg(acpi_handle handle)
@@ -673,6 +1122,7 @@ static int __init acpi_wmi_add(struct acpi_device *device)
 
 static int __init acpi_wmi_init(void)
 {
+	int ret;
 	acpi_status result;
 
 	if (acpi_disabled)
@@ -684,10 +1134,17 @@ static int __init acpi_wmi_init(void)
 
 	if (result < 0) {
 		printk(KERN_INFO PREFIX "Error loading mapper\n");
-	} else {
-		printk(KERN_INFO PREFIX "Mapper loaded\n");
+		return -ENODEV;
+	}
+
+	ret = wmi_class_init();
+	if (ret) {
+		acpi_bus_unregister_driver(&acpi_wmi_driver);
+		return ret;
 	}
 
+	printk(KERN_INFO PREFIX "Mapper loaded\n");
+
 	return result;
 }
 
@@ -696,6 +1153,8 @@ static void __exit acpi_wmi_exit(void)
 	struct list_head *p, *tmp;
 	struct wmi_block *wblock;
 
+	wmi_class_exit();
+
 	acpi_bus_unregister_driver(&acpi_wmi_driver);
 
 	list_for_each_safe(p, tmp, &wmi_blocks.list) {


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 5/5] [WIP] ACPI: WMI: Limit size of device string to 19 characters
  2008-01-12  2:20 [PATCH 0/5] WMI Carlos Corbacho
                   ` (3 preceding siblings ...)
  2008-01-12  2:20 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
@ 2008-01-12  2:20 ` Carlos Corbacho
  4 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12  2:20 UTC (permalink / raw)
  To: linux-acpi; +Cc: Len Brown, Matthew Garrett

Until Kay Sievers bus_id variable length code goes in, this is a
temporary workaround.

NOT-Signed-Off-By: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/acpi/wmi.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)


diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index b4bc1ad..aa240a3 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -864,7 +864,7 @@ static int wmi_create_devs(void)
 		gblock = &wblock->gblock;
 
 		wmi_gtoa(gblock->guid, guid_string);
-		memcpy(guid_dev->bus_id, guid_string, 36);
+		memcpy(guid_dev->bus_id, guid_string, 19);
 
 		result = device_register(guid_dev);
 		if (result)


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver
  2008-01-12  2:20 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
@ 2008-01-12 13:00   ` Carlos Corbacho
  0 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-12 13:00 UTC (permalink / raw)
  To: linux-acpi; +Cc: Len Brown, Matthew Garrett, Alexey Starikovskiy

On Saturday 12 January 2008 09:17:55 Len Brown wrote:
>  CC [M]  drivers/acpi/wmi.o
> drivers/acpi/wmi.c: In function ‘acpi_wmi_add’:
> drivers/acpi/wmi.c:1101: error: ‘_COMPONENT’ undeclared (first use in this
> function) drivers/acpi/wmi.c:1101: error: (Each undeclared identifier is
> reported only once drivers/acpi/wmi.c:1101: error: for each function it
> appears in.)
> make[2]: *** [drivers/acpi/wmi.o] Error 1
> make[1]: *** [drivers/acpi] Error 2
> make: *** [drivers] Error 2

Ok - apparently WMI doesn't play nice if CONFIG_ACPI_DEBUG is enabled.

Fixed, new version is here:
---
ACPI: WMI: Add ACPI-WMI mapping driver

The following is an implementation of the Windows Management
Instrumentation (WMI) ACPI interface mapper (PNP0C14).

What it does:

Parses the _WDG method and exports functions to process WMI method calls,
data block query/ set commands (both based on GUID) and does basic event
handling.

How: WMI presents an in kernel interface here (essentially, a minimal
wrapper around ACPI)

(const char *guid assume the 36 character ASCII representation of
a GUID - e.g. 67C3371D-95A3-4C37-BB61-DD47B491DAAB)

wmi_evaluate_method(const char *guid, u8 instance, u32 method_id,
const struct acpi_buffer *in, struct acpi_buffer *out)

wmi_query_block(const char *guid, u8 instance,
struct acpi_buffer *out)

wmi_set_block(const char *guid, u38 instance,
const struct acpi_buffer *in)

wmi_install_notify_handler(acpi_notify_handler handler);

wmi_remove_notify_handler(void);

wmi_get_event_data(u32 event, struct acpi_buffer *out)

wmi_has_guid(const char guid*)

wmi_has_guid() is a helper function to find if a GUID exists or not on the
system (a quick and easy way for WMI dependant drivers to see if the
the method/ block they want exists, since GUIDs are supposed to be unique).

Event handling - allow a WMI based driver to register a notifier handler
for each GUID with WMI. When a notification is sent to a GUID in WMI, the
handler registered with WMI is then called (it is left to the caller to
ask for the WMI event data associated with the GUID, if needed).

What it won't do:

Unicode - The MS article[1] calls for converting between ASCII and Unicode (or
vice versa) if a GUID is marked as "string". This is left up to the calling
driver.

Handle a MOF[1] - the WMI mapper just exports methods, data and events to
userspace. MOF handling is down to userspace.

Userspace interface - this will be added later.

[1] http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx

===
ChangeLog
==

v1 (2007-10-02):

* Initial release

v2 (2007-10-05):

* Cleaned up code - split up super "wmi_evaluate_block" -> each external
  symbol now handles its own ACPI calls, rather than handing off to
  a "super" method (and in turn, is a lot simpler to read)
* Added a find_guid() symbol - return true if a given GUID exists on
  the system
* wmi_* functions now return type acpi_status (since they are just
  fancy wrappers around acpi_evaluate_object())
* Removed extra debug code

v3 (2007-10-27)

* More code clean up - now passes checkpatch.pl
* Change data block calls - ref MS spec, method ID is not required for
  them, so drop it from the function parameters.
* Const'ify guid in the function call parameters.
* Fix _WDG buffer handling - copy the data to our own private structure.
* Change WMI from tristate to bool - otherwise the external functions are
  not exported in linux/acpi.h if you try to build WMI as a module.
* Fix more flag comparisons.
* Add a maintainers entry - since I wrote this, I should take the blame
  for it.

v4 (2007-10-30)

* Add missing brace from after fixing checkpatch errors.
* Rewrote event handling - allow external drivers to register with WMI to
  handle WMI events
* Clean up flags and sanitise flag handling

v5 (2007-11-03)

* Add sysfs interface for userspace. Export events over netlink again.
* Remove module left overs, fully convert to built-in driver.
* Tweak in-kernel API to use u8 for instance, since this is what the GUID
  blocks use (so instance cannot be greater than u8).
* Export wmi_get_event_data() for in kernel WMI drivers.

v6 (2007-11-07)

* Split out userspace into a different patch

v7 (2007-11-20)

* Fix driver to handle multiple PNP0C14 devices - store all GUIDs using
  the kernel's built in list functions, and just keep adding to the list
  every time we handle a PNP0C14 devices - GUIDs will always be unique,
  and WMI callers do not know or care about different devices.
* Change WMI event handler registration to use its' own event handling
  struct; we should not pass an acpi_handle down to any WMI based drivers
  - they should be able to function with only the calls provided in WMI.
* Update my e-mail address

v8 (2007-11-28)

* Convert back to a module.
* Update Kconfig to default to building as a module.
* Remove an erroneous printk.
* Simply comments for string flag (since we now leave the handling to the
  caller).

v9 (2007-12-07)

* Add back missing MODULE_DEVICE_TABLE for autoloading
* Checkpatch fixes

v10 (2007-12-12)

* Workaround broken GUIDs declared expensive without a WCxx method.
* Minor cleanups

v11 (2007-12-17)

* More fixing for broken GUIDs declared expensive without a WCxx method.
* Add basic EmbeddedControl region handling.

v12 (2007-12-18)

* Changed EC region handling code, as per Alexey's comments.

v13 (2007-12-27)

* Changed event handling so that we can have one event handler registered
  per GUID, as per Matthew Garrett's suggestion.

v14 (2008-01-12)

* Remove ACPI debug statements

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Alexey Starikovskiy <aystarik@gmail.com>
---

 MAINTAINERS           |    7 
 drivers/acpi/Kconfig  |   11 +
 drivers/acpi/Makefile |    1 
 drivers/acpi/wmi.c    |  710 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/acpi.h  |   20 +
 5 files changed, 749 insertions(+), 0 deletions(-)
 create mode 100644 drivers/acpi/wmi.c


diff --git a/MAINTAINERS b/MAINTAINERS
index b4f611c..64c24e3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -259,6 +259,13 @@ L:	linux-acpi@vger.kernel.org
 W:	http://acpi.sourceforge.net/
 S:	Supported
 
+ACPI WMI DRIVER
+P:      Carlos Corbacho
+M:      carlos@strangeworlds.co.uk
+L:      linux-acpi@vger.kernel.org
+W:      http://www.lesswatts.org/projects/acpi/
+S:      Supported
+
 ADM1025 HARDWARE MONITOR DRIVER
 P:	Jean Delvare
 M:	khali@linux-fr.org
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 4dbe33f..25a0261 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -202,6 +202,17 @@ config ACPI_NUMA
 	depends on (X86 || IA64)
 	default y if IA64_GENERIC || IA64_SGI_SN2
 
+config ACPI_WMI
+	tristate "WMI (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	default m
+	help
+	  This driver adds support for the ACPI-WMI mapper device (PNP0C14)
+	  found on some systems.
+
+	  NOTE: You will need another driver or userspace application on top of
+	  this to actually use anything defined in the ACPI-WMI mapper.
+
 config ACPI_ASUS
         tristate "ASUS/Medion Laptop Extras"
 	depends on X86
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index 456446f..f29812a 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_ACPI_THERMAL)	+= thermal.o
 obj-$(CONFIG_ACPI_SYSTEM)	+= system.o event.o
 obj-$(CONFIG_ACPI_DEBUG)	+= debug.o
 obj-$(CONFIG_ACPI_NUMA)		+= numa.o
+obj-$(CONFIG_ACPI_WMI)		+= wmi.o
 obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-$(CONFIG_ACPI_HOTPLUG_MEMORY)	+= acpi_memhotplug.o
diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
new file mode 100644
index 0000000..18ff608
--- /dev/null
+++ b/drivers/acpi/wmi.c
@@ -0,0 +1,710 @@
+/*
+ *  ACPI-WMI mapping driver
+ *
+ *  Copyright (C) 2007-2008 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ *  GUID parsing code from ldm.c is:
+ *   Copyright (C) 2001,2002 Richard Russon <ldm@flatcap.org>
+ *   Copyright (c) 2001-2007 Anton Altaparmakov
+ *   Copyright (C) 2001,2002 Jakob Kemi <jakob.kemi@telia.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at
+ *  your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+ACPI_MODULE_NAME("wmi");
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
+MODULE_LICENSE("GPL");
+
+#define ACPI_WMI_CLASS "wmi"
+
+#undef PREFIX
+#define PREFIX "ACPI: WMI: "
+
+static DEFINE_MUTEX(wmi_data_lock);
+
+struct guid_block {
+	char guid[16];
+	union {
+		char object_id[2];
+		struct {
+			unsigned char notify_id;
+			unsigned char reserved;
+		};
+	};
+	u8 instance_count;
+	u8 flags;
+};
+
+struct wmi_block {
+	struct list_head list;
+	struct guid_block gblock;
+	acpi_handle handle;
+	wmi_notify_handler handler;
+	void *handler_data;
+};
+
+static struct wmi_block wmi_blocks;
+
+/*
+ * If the GUID data block is marked as expensive, we must enable and
+ * explicitily disable data collection.
+ */
+#define ACPI_WMI_EXPENSIVE   0x1
+#define ACPI_WMI_METHOD      0x2	/* GUID is a method */
+#define ACPI_WMI_STRING      0x4	/* GUID takes & returns a string */
+#define ACPI_WMI_EVENT       0x8	/* GUID is an event */
+
+static int acpi_wmi_remove(struct acpi_device *device, int type);
+static int acpi_wmi_add(struct acpi_device *device);
+
+static const struct acpi_device_id wmi_device_ids[] = {
+	{"PNP0C14", 0},
+	{"pnp0c14", 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, wmi_device_ids);
+
+static struct acpi_driver acpi_wmi_driver = {
+	.name = "wmi",
+	.class = ACPI_WMI_CLASS,
+	.ids = wmi_device_ids,
+	.ops = {
+		.add = acpi_wmi_add,
+		.remove = acpi_wmi_remove,
+		},
+};
+
+/*
+ * GUID parsing functions
+ */
+
+/**
+ * wmi_parse_hexbyte - Convert a ASCII hex number to a byte
+ * @src:  Pointer to at least 2 characters to convert.
+ *
+ * Convert a two character ASCII hex string to a number.
+ *
+ * Return:  0-255  Success, the byte was parsed correctly
+ *          -1     Error, an invalid character was supplied
+ */
+static int wmi_parse_hexbyte(const u8 *src)
+{
+	unsigned int x; /* For correct wrapping */
+	int h;
+
+	/* high part */
+	x = src[0];
+	if (x - '0' <= '9' - '0') {
+		h = x - '0';
+	} else if (x - 'a' <= 'f' - 'a') {
+		h = x - 'a' + 10;
+	} else if (x - 'A' <= 'F' - 'A') {
+		h = x - 'A' + 10;
+	} else {
+		return -1;
+	}
+	h <<= 4;
+
+	/* low part */
+	x = src[1];
+	if (x - '0' <= '9' - '0')
+		return h | (x - '0');
+	if (x - 'a' <= 'f' - 'a')
+		return h | (x - 'a' + 10);
+	if (x - 'A' <= 'F' - 'A')
+		return h | (x - 'A' + 10);
+	return -1;
+}
+
+/**
+ * wmi_swap_bytes - Rearrange GUID bytes to match GUID binary
+ * @src:   Memory block holding binary GUID (16 bytes)
+ * @dest:  Memory block to hold byte swapped binary GUID (16 bytes)
+ *
+ * Byte swap a binary GUID to match it's real GUID value
+ */
+static void wmi_swap_bytes(u8 *src, u8 *dest)
+{
+	int i;
+
+	for (i = 0; i <= 3; i++)
+		memcpy(dest + i, src + (3 - i), 1);
+
+	for (i = 0; i <= 1; i++)
+		memcpy(dest + 4 + i, src + (5 - i), 1);
+
+	for (i = 0; i <= 1; i++)
+		memcpy(dest + 6 + i, src + (7 - i), 1);
+
+	memcpy(dest + 8, src + 8, 8);
+}
+
+/**
+ * wmi_parse_guid - Convert GUID from ASCII to binary
+ * @src:   36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dest:  Memory block to hold binary GUID (16 bytes)
+ *
+ * N.B. The GUID need not be NULL terminated.
+ *
+ * Return:  'true'   @dest contains binary GUID
+ *          'false'  @dest contents are undefined
+ */
+static bool wmi_parse_guid(const u8 *src, u8 *dest)
+{
+	static const int size[] = { 4, 2, 2, 2, 6 };
+	int i, j, v;
+
+	if (src[8]  != '-' || src[13] != '-' ||
+		src[18] != '-' || src[23] != '-')
+		return false;
+
+	for (j = 0; j < 5; j++, src++) {
+		for (i = 0; i < size[j]; i++, src += 2, *dest++ = v) {
+			v = wmi_parse_hexbyte(src);
+			if (v < 0)
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static bool find_guid(const char *guid_string, struct wmi_block **out)
+{
+	char tmp[16], guid_input[16];
+	struct wmi_block *wblock;
+	struct guid_block *block;
+	struct list_head *p;
+
+	wmi_parse_guid(guid_string, tmp);
+	wmi_swap_bytes(tmp, guid_input);
+
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		block = &wblock->gblock;
+
+		if (memcmp(block->guid, guid_input, 16) == 0) {
+			if (out)
+				*out = wblock;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Exported WMI functions
+ */
+/**
+ * wmi_evaluate_method - Evaluate a WMI method
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * @method_id: Method ID to call
+ * &in: Buffer containing input for the method call
+ * &out: Empty buffer to return the method results
+ *
+ * Call an ACPI-WMI method
+ */
+acpi_status wmi_evaluate_method(const char *guid_string, u8 instance,
+u32 method_id, const struct acpi_buffer *in, struct acpi_buffer *out)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	acpi_status status;
+	struct acpi_object_list input;
+	union acpi_object params[3];
+	char method[4] = "WM";
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_BAD_ADDRESS;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (!block->flags & ACPI_WMI_METHOD)
+		return AE_BAD_DATA;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	input.count = 2;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = instance;
+	params[1].type = ACPI_TYPE_INTEGER;
+	params[1].integer.value = method_id;
+
+	if (in) {
+		input.count = 3;
+
+		if (block->flags & ACPI_WMI_STRING) {
+			params[2].type = ACPI_TYPE_STRING;
+		} else {
+			params[2].type = ACPI_TYPE_BUFFER;
+		}
+		params[2].buffer.length = in->length;
+		params[2].buffer.pointer = in->pointer;
+	}
+
+	strncat(method, block->object_id, 2);
+
+	status = acpi_evaluate_object(handle, method, &input, out);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_evaluate_method);
+
+/**
+ * wmi_query_block - Return contents of a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &out: Empty buffer to return the contents of the data block to
+ *
+ * Return the contents of an ACPI-WMI data block to a buffer
+ */
+acpi_status wmi_query_block(const char *guid_string, u8 instance,
+struct acpi_buffer *out)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	acpi_status status, wc_status = AE_ERROR;
+	struct acpi_object_list input, wc_input;
+	union acpi_object wc_params[1], wq_params[1];
+	char method[4];
+	char wc_method[4] = "WC";
+
+	if (guid_string == NULL || out == NULL)
+		return AE_BAD_PARAMETER;
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_BAD_ADDRESS;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	/* Check GUID is a data block */
+	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+		return AE_BAD_ADDRESS;
+
+	input.count = 1;
+	input.pointer = wq_params;
+	wq_params[0].type = ACPI_TYPE_INTEGER;
+	wq_params[0].integer.value = instance;
+
+	/*
+	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method first to
+	 * enable collection.
+	 */
+	if (block->flags & ACPI_WMI_EXPENSIVE) {
+		wc_input.count = 1;
+		wc_input.pointer = wc_params;
+		wc_params[0].type = ACPI_TYPE_INTEGER;
+		wc_params[0].integer.value = 1;
+
+		strncat(wc_method, block->object_id, 2);
+
+		/*
+		 * Some GUIDs break the specification by declaring themselves
+		 * expensive, but have no corresponding WCxx method. So we
+		 * should not fail if this happens.
+		 */
+		wc_status = acpi_evaluate_object(handle, wc_method,
+			&wc_input, NULL);
+	}
+
+	strcpy(method, "WQ");
+	strncat(method, block->object_id, 2);
+
+	status = acpi_evaluate_object(handle, method, NULL, out);
+
+	/*
+	 * If ACPI_WMI_EXPENSIVE, call the relevant WCxx method, even if
+	 * the WQxx method failed - we should disable collection anyway.
+	 */
+	if ((block->flags & ACPI_WMI_EXPENSIVE) && wc_status) {
+		wc_params[0].integer.value = 0;
+		status = acpi_evaluate_object(handle,
+		wc_method, &wc_input, NULL);
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(wmi_query_block);
+
+/**
+ * wmi_set_block - Write to a WMI block
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @instance: Instance index
+ * &in: Buffer containing new values for the data block
+ *
+ * Write the contents of the input buffer to an ACPI-WMI data block
+ */
+acpi_status wmi_set_block(const char *guid_string, u8 instance,
+const struct acpi_buffer *in)
+{
+	struct guid_block *block = NULL;
+	struct wmi_block *wblock = NULL;
+	acpi_handle handle;
+	struct acpi_object_list input;
+	union acpi_object params[2];
+	char method[4] = "WS";
+
+	if (!guid_string || !in)
+		return AE_BAD_DATA;
+
+	if (!find_guid(guid_string, &wblock))
+		return AE_BAD_ADDRESS;
+
+	block = &wblock->gblock;
+	handle = wblock->handle;
+
+	if (block->instance_count < instance)
+		return AE_BAD_PARAMETER;
+
+	/* Check GUID is a data block */
+	if (block->flags & (ACPI_WMI_EVENT | ACPI_WMI_METHOD))
+		return AE_BAD_ADDRESS;
+
+	input.count = 2;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = instance;
+
+	if (block->flags & ACPI_WMI_STRING) {
+		params[1].type = ACPI_TYPE_STRING;
+	} else {
+		params[1].type = ACPI_TYPE_BUFFER;
+	}
+	params[1].buffer.length = in->length;
+	params[1].buffer.pointer = in->pointer;
+
+	strncat(method, block->object_id, 2);
+
+	return acpi_evaluate_object(handle, method, &input, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_set_block);
+
+/**
+ * wmi_install_notify_handler - Register handler for WMI events
+ * @handler: Function to handle notifications
+ * @data: Data to be returned to handler when event is fired
+ *
+ * Register a handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_install_notify_handler(const char *guid,
+wmi_notify_handler handler, void *data)
+{
+	struct wmi_block *block;
+
+	if (!guid || !handler)
+		return AE_BAD_PARAMETER;
+
+	find_guid(guid, &block);
+	if (!block)
+		return AE_NOT_EXIST;
+
+	if (block->handler)
+		return AE_ALREADY_ACQUIRED;
+
+	block->handler = handler;
+	block->handler_data = data;
+
+	return AE_OK;
+}
+EXPORT_SYMBOL_GPL(wmi_install_notify_handler);
+
+/**
+ * wmi_uninstall_notify_handler - Unregister handler for WMI events
+ *
+ * Unregister handler for events sent to the ACPI-WMI mapper device.
+ */
+acpi_status wmi_remove_notify_handler(const char *guid)
+{
+	struct wmi_block *block;
+
+	if (!guid)
+		return AE_BAD_PARAMETER;
+
+	find_guid(guid, &block);
+	if (!block)
+		return AE_NOT_EXIST;
+
+	if (!block->handler)
+		return AE_NULL_ENTRY;
+
+	block->handler = NULL;
+	block->handler_data = NULL;
+
+	return AE_OK;
+}
+EXPORT_SYMBOL_GPL(wmi_remove_notify_handler);
+
+/**
+ * wmi_get_event_data - Get WMI data associated with an event
+ *
+ * @event - Event to find
+ * &out - Buffer to hold event data
+ *
+ * Returns extra data associated with an event in WMI.
+ */
+acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out)
+{
+	struct acpi_object_list input;
+	union acpi_object params[1];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+
+	input.count = 1;
+	input.pointer = params;
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = event;
+
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		gblock = &wblock->gblock;
+
+		if ((gblock->flags & ACPI_WMI_EVENT) &&
+			(gblock->notify_id == event))
+			return acpi_evaluate_object(wblock->handle, "_WED",
+				&input, out);
+	}
+
+	return AE_NOT_FOUND;
+}
+EXPORT_SYMBOL_GPL(wmi_get_event_data);
+
+/**
+ * wmi_has_guid - Check if a GUID is available
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ *
+ * Check if a given GUID is defined by _WDG
+ */
+bool wmi_has_guid(const char *guid_string)
+{
+	return find_guid(guid_string, NULL);
+}
+EXPORT_SYMBOL_GPL(wmi_has_guid);
+
+/*
+ * Parse the _WDG method for the GUID data blocks
+ */
+static __init acpi_status parse_wdg(acpi_handle handle)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	acpi_status status;
+	u32 i, total;
+
+	status = acpi_evaluate_object(handle, "_WDG", NULL, &out);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+
+	if (obj->type != ACPI_TYPE_BUFFER)
+		return AE_ERROR;
+
+	total = obj->buffer.length / sizeof(struct guid_block);
+
+	gblock = kzalloc(obj->buffer.length, GFP_KERNEL);
+	if (!gblock)
+		return AE_NO_MEMORY;
+
+	memcpy(gblock, obj->buffer.pointer, obj->buffer.length);
+
+	for (i = 0; i < total; i++) {
+		wblock = kzalloc(sizeof(struct wmi_block), GFP_KERNEL);
+		if (!wblock)
+			return AE_NO_MEMORY;
+
+		wblock->gblock = gblock[i];
+		wblock->handle = handle;
+		list_add_tail(&wblock->list, &wmi_blocks.list);
+	}
+
+	kfree(out.pointer);
+	kfree(gblock);
+
+	return status;
+}
+
+/*
+ * WMI can have EmbeddedControl access regions. In which case, we just want to
+ * hand these off to the EC driver.
+ */
+static acpi_status
+acpi_wmi_ec_space_handler(u32 function, acpi_physical_address address,
+		      u32 bits, acpi_integer *value,
+		      void *handler_context, void *region_context)
+{
+	int result = 0, i = 0;
+	u8 temp = 0;
+
+	if ((address > 0xFF) || !value)
+		return AE_BAD_PARAMETER;
+
+	if (function != ACPI_READ && function != ACPI_WRITE)
+		return AE_BAD_PARAMETER;
+
+	if (bits != 8)
+		return AE_BAD_PARAMETER;
+
+	if (function == ACPI_READ) {
+		result = ec_read(address, &temp);
+		(*value) |= ((acpi_integer)temp) << i;
+	} else {
+		temp = 0xff & ((*value) >> i);
+		result = ec_write(address, temp);
+	}
+
+	switch (result) {
+	case -EINVAL:
+		return AE_BAD_PARAMETER;
+		break;
+	case -ENODEV:
+		return AE_NOT_FOUND;
+		break;
+	case -ETIME:
+		return AE_TIME;
+		break;
+	default:
+		return AE_OK;
+	}
+}
+
+static void acpi_wmi_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct acpi_device *device = data;
+
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+		block = &wblock->gblock;
+
+		if ((block->flags & ACPI_WMI_EVENT) &&
+			(block->notify_id == event)) {
+			if (wblock->handler)
+				wblock->handler(event, wblock->handler_data);
+
+			acpi_bus_generate_netlink_event(
+				device->pnp.device_class, device->dev.bus_id,
+				event, 0);
+			break;
+		}
+	}
+}
+
+static int acpi_wmi_remove(struct acpi_device *device, int type)
+{
+	acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+		acpi_wmi_notify);
+
+	acpi_remove_address_space_handler(device->handle,
+				ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler);
+
+	return 0;
+}
+
+static int __init acpi_wmi_add(struct acpi_device *device)
+{
+	acpi_status status;
+	int result = 0;
+
+	status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
+		acpi_wmi_notify, device);
+	if (ACPI_FAILURE(status)) {
+		printk(KERN_ERR PREFIX "Error installing notify handler\n");
+		return -ENODEV;
+	}
+
+	status = acpi_install_address_space_handler(device->handle,
+						    ACPI_ADR_SPACE_EC,
+						    &acpi_wmi_ec_space_handler,
+						    NULL, NULL);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	status = parse_wdg(device->handle);
+	if (ACPI_FAILURE(status)) {
+		printk(KERN_ERR PREFIX "Error installing EC region handler\n");
+		return -ENODEV;
+	}
+
+	return result;
+}
+
+static int __init acpi_wmi_init(void)
+{
+	acpi_status result;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	INIT_LIST_HEAD(&wmi_blocks.list);
+
+	result = acpi_bus_register_driver(&acpi_wmi_driver);
+
+	if (result < 0) {
+		printk(KERN_INFO PREFIX "Error loading mapper\n");
+	} else {
+		printk(KERN_INFO PREFIX "Mapper loaded\n");
+	}
+
+	return result;
+}
+
+static void __exit acpi_wmi_exit(void)
+{
+	struct list_head *p, *tmp;
+	struct wmi_block *wblock;
+
+	acpi_bus_unregister_driver(&acpi_wmi_driver);
+
+	list_for_each_safe(p, tmp, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		list_del(p);
+		kfree(wblock);
+	}
+
+	printk(KERN_INFO PREFIX "Mapper unloaded\n");
+}
+
+subsys_initcall(acpi_wmi_init);
+module_exit(acpi_wmi_exit);
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 4497964..aeffe08 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -176,6 +176,26 @@ extern int ec_transaction(u8 command,
 
 #endif /*CONFIG_ACPI_EC*/
 
+#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE)
+
+typedef void (*wmi_notify_handler) (u32 value, void *context);
+
+extern acpi_status wmi_evaluate_method(const char *guid, u8 instance,
+					u32 method_id,
+					const struct acpi_buffer *in,
+					struct acpi_buffer *out);
+extern acpi_status wmi_query_block(const char *guid, u8 instance,
+					struct acpi_buffer *out);
+extern acpi_status wmi_set_block(const char *guid, u8 instance,
+					const struct acpi_buffer *in);
+extern acpi_status wmi_install_notify_handler(const char *guid,
+					wmi_notify_handler handler, void *data);
+extern acpi_status wmi_remove_notify_handler(const char *guid);
+extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out);
+extern bool wmi_has_guid(const char *guid);
+
+#endif	/* CONFIG_ACPI_WMI */
+
 extern int acpi_blacklisted(void);
 extern void acpi_bios_year(char *s);
 


-
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface
  2008-01-18 23:58 [PATCH 0/5] WMI Carlos Corbacho
@ 2008-01-18 23:58 ` Carlos Corbacho
  0 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-01-18 23:58 UTC (permalink / raw)
  To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett

Create a 'wmi' class, and populate it with a virtual device for each GUID.
This also allows us to autoload WMI drivers based on GUID
via MODULE_ALIAS.

Under each GUID are a set of files that can be read and written to from
userspace (these will be fully documented in a later patch).

v1 (2007-11-03)

* Initial release

v2 (2007-11-07)

* Split out into a separate patch

v3 (2007-11-20)

* Convert kobject storage to using kernel list structures.
* Change instance handling - store input data on a per instance basis,
  rather than a per GUID.
* Add locking to methods - method_id (write) and data (read & write)
  should all be mutually exclusive - we do not want the input data to
  change when we are trying to execute a method.
* Change method calling semantics: when input is written to 'data',
  store it, but don't execute the method until 'data' is read. This is
  due to the fact that it is perfectly acceptable to have a WMI method
  that does not take any input (and it is easier to trigger an execute
  on reading the file, and not return anything if there is nothing to
  return, rather than trying to write values that aren't required, or
  may cause ACPI evaluation to fail on the method).
* Do not try and convert String's from ASCII to Unicode - instead, only
  handle ASCII (as per ACPI), and leave it up to userspace to convert
  to/ from whatever encoding they wish to use.

v4 (2007-11-28)

* Add code to remove sysfs files on module removal

v5 (2007-12-08)

* Convert to a class device, instead of trying to manually create and
  manipulate kobject's - this allows us to add module autoloading for WMI
  based drivers, and will (hopefully) be less likely to break with the
  latest round of kobject changes.
* Convert GUIDs to use a 'flat' structure - add two new files:
  instance and instance_count, and use these to provide the instance
  to call for a given GUID (while still communicating the maximum number
  of instances available - for now, we will always respect instance_count
  when setting instance - so any instance > instance_count will be
  discarded).

v6 (2007-12-12)

* Add string file - this reports back if the GUID has the string flag set.

v8 (2008-010-18)

* Add new in kernel function, wmi_get_device(), to take advantage of the
  class and virtual devices, and return the virtual device for a GUID.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/acpi/wmi.c   |  484 ++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/acpi.h |    1 
 2 files changed, 483 insertions(+), 2 deletions(-)


diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index 18ff608..faf535d 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -30,6 +30,8 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
 #include <linux/list.h>
 #include <linux/acpi.h>
 #include <acpi/acpi_bus.h>
@@ -66,6 +68,11 @@ struct wmi_block {
 	acpi_handle handle;
 	wmi_notify_handler handler;
 	void *handler_data;
+	struct device dev;
+	u8 instance;
+	u32 method_id;
+	void *pointer;
+	acpi_size length;
 };
 
 static struct wmi_block wmi_blocks;
@@ -194,6 +201,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 wmi_block **out)
 {
 	char tmp[16], guid_input[16];
@@ -516,6 +550,442 @@ bool wmi_has_guid(const char *guid_string)
 }
 EXPORT_SYMBOL_GPL(wmi_has_guid);
 
+/**
+ * wmi_get_device - Get the virtual device associated with a GUID
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dev: Pointer to pointer to the device
+ *
+ * Return a pointer to a pointer to virtual device associated with a GUID
+ */
+int wmi_get_device(const char *guid_string, struct device **dev)
+{
+	struct wmi_block *block;
+	bool ret;
+
+	ret = find_guid(guid_string, &block);
+	if (!ret)
+		return -ENODEV;
+
+	*dev = &block->dev;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wmi_get_device);
+
+/*
+ * sysfs interface
+ */
+static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count) {
+	u8 instance;
+	const char *guid;
+	struct acpi_buffer in;
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	acpi_status status;
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	guid = block->gblock.guid;
+	if (!guid)
+		return -EINVAL;
+
+	instance = block->instance;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		mutex_lock(&wmi_data_lock);
+		if (block->pointer) {
+			in.pointer = block->pointer;
+			in.length = block->length;
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, &in, &out);
+		} else {
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, NULL, &out);
+		}
+		mutex_unlock(&wmi_data_lock);
+	} else {
+		status = wmi_query_block(guid, instance, &out);
+	}
+
+	obj = (union acpi_object *) out.pointer;
+
+	switch (obj->type) {
+	case (ACPI_TYPE_STRING):
+	case (ACPI_TYPE_BUFFER):
+		buf = obj->buffer.pointer;
+		break;
+	default:
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count){
+	struct wmi_block *block;
+	struct acpi_buffer in;
+	acpi_status status;
+	struct device *dev;
+	char guid_string[37];
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (count == 0)
+		return count;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		block->pointer = kzalloc(count, GFP_KERNEL);
+		if (!block->pointer)
+			return count;
+
+		memcpy(block->pointer, buf, count);
+		block->length = count;
+	} else {
+		in.pointer = buf;
+		in.length = count;
+		wmi_gtoa(block->gblock.guid, guid_string);
+		status = wmi_set_block(guid_string, block->instance, &in);
+	}
+
+	return count;
+}
+
+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) {
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	wmi_get_event_data(block->gblock.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,
+};
+
+static ssize_t show_type(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return sprintf(buf, "Error\n");
+
+	block = &wblock->gblock;
+
+	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 DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
+
+static ssize_t show_instance_count(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->gblock.instance_count);
+}
+static DEVICE_ATTR(instance_count, S_IRUGO, show_instance_count, NULL);
+
+static ssize_t show_instance(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->instance);
+}
+
+static ssize_t set_instance(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 instance;
+
+	instance = simple_strtoul(buf, NULL, 10);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (instance <= block->gblock.instance_count) {
+		block->instance = instance;
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(instance, S_IWUGO | S_IRUGO, show_instance, set_instance);
+
+static ssize_t show_method_id(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->method_id);
+}
+
+static ssize_t set_method_id(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 method_id;
+
+	method_id = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&wmi_data_lock);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block) {
+		mutex_unlock(&wmi_data_lock);
+		return -EINVAL;
+	}
+
+	block->method_id = method_id;
+	mutex_unlock(&wmi_data_lock);
+
+	return count;
+}
+static DEVICE_ATTR(method_id, S_IWUGO | S_IRUGO, show_method_id,
+	set_method_id);
+
+static ssize_t show_event_notification(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", gblock->notify_id & 0xFF);
+}
+static DEVICE_ATTR(notification, S_IRUGO, show_event_notification, NULL);
+
+static ssize_t show_string(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", (gblock->flags & ACPI_WMI_STRING) >> 2);
+}
+static DEVICE_ATTR(string, S_IRUGO, show_string, NULL);
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	char guid_string[37];
+
+	struct wmi_block *wblock;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return -ENOMEM;
+
+	wmi_gtoa(wblock->gblock.guid, guid_string);
+
+	strcpy(&env->buf[env->buflen - 1], "wmi:");
+	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+	env->buflen += 40;
+
+	return 0;
+}
+
+static struct class wmi_class = {
+	.name = "wmi",
+	.dev_release = (void(*)(struct device *)) kfree,
+	.dev_uevent = wmi_dev_uevent,
+};
+
+static int wmi_create_devs(void)
+{
+	int result;
+	char guid_string[37];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Create devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		guid_dev->class = &wmi_class;
+
+		gblock = &wblock->gblock;
+
+		wmi_gtoa(gblock->guid, guid_string);
+		memcpy(guid_dev->bus_id, guid_string, 36);
+
+		result = device_register(guid_dev);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_type);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_string);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			result = device_create_file(guid_dev,
+				&dev_attr_notification);
+			if (result)
+				return result;
+
+			result = device_create_bin_file(guid_dev,
+				&wmi_attr_event_data);
+			if (result)
+				return result;
+			break;
+		}
+
+		result = device_create_file(guid_dev, &dev_attr_instance_count);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_instance);
+		if (result)
+			return result;
+
+		result = device_create_bin_file(guid_dev, &wmi_attr_data);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_METHOD) {
+			result = device_create_file(guid_dev,
+				&dev_attr_method_id);
+			if (result)
+				return result;
+		}
+	}
+
+	return 0;
+}
+
+static void wmi_remove_devs(void)
+{
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Delete devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		gblock = &wblock->gblock;
+
+		device_remove_file(guid_dev, &dev_attr_type);
+		device_remove_file(guid_dev, &dev_attr_string);
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			device_remove_file(guid_dev, &dev_attr_notification);
+			device_remove_bin_file(guid_dev, &wmi_attr_event_data);
+		} else {
+			device_remove_file(guid_dev, &dev_attr_instance_count);
+			device_remove_file(guid_dev, &dev_attr_instance);
+			device_remove_bin_file(guid_dev, &wmi_attr_data);
+
+			if (gblock->flags & ACPI_WMI_METHOD) {
+				device_remove_file(guid_dev,
+					&dev_attr_method_id);
+			}
+		}
+		device_unregister(guid_dev);
+	}
+}
+
+static int wmi_class_init(void)
+{
+	int ret;
+
+	ret = class_register(&wmi_class);
+	if (ret)
+		return ret;
+
+	return wmi_create_devs();
+}
+
+static void wmi_class_exit(void)
+{
+	wmi_remove_devs();
+	class_unregister(&wmi_class);
+}
+
 /*
  * Parse the _WDG method for the GUID data blocks
  */
@@ -671,6 +1141,7 @@ static int __init acpi_wmi_add(struct acpi_device *device)
 
 static int __init acpi_wmi_init(void)
 {
+	int ret;
 	acpi_status result;
 
 	if (acpi_disabled)
@@ -682,10 +1153,17 @@ static int __init acpi_wmi_init(void)
 
 	if (result < 0) {
 		printk(KERN_INFO PREFIX "Error loading mapper\n");
-	} else {
-		printk(KERN_INFO PREFIX "Mapper loaded\n");
+		return -ENODEV;
+	}
+
+	ret = wmi_class_init();
+	if (ret) {
+		acpi_bus_unregister_driver(&acpi_wmi_driver);
+		return ret;
 	}
 
+	printk(KERN_INFO PREFIX "Mapper loaded\n");
+
 	return result;
 }
 
@@ -694,6 +1172,8 @@ static void __exit acpi_wmi_exit(void)
 	struct list_head *p, *tmp;
 	struct wmi_block *wblock;
 
+	wmi_class_exit();
+
 	acpi_bus_unregister_driver(&acpi_wmi_driver);
 
 	list_for_each_safe(p, tmp, &wmi_blocks.list) {
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index aeffe08..ee64517 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -193,6 +193,7 @@ extern acpi_status wmi_install_notify_handler(const char *guid,
 extern acpi_status wmi_remove_notify_handler(const char *guid);
 extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out);
 extern bool wmi_has_guid(const char *guid);
+extern int wmi_get_device(const char *guid, struct device **dev);
 
 #endif	/* CONFIG_ACPI_WMI */
 


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface
  2008-02-02 12:17 [PATCH 0/5] WMI patches for acpi-test Carlos Corbacho
@ 2008-02-02 12:17 ` Carlos Corbacho
  0 siblings, 0 replies; 11+ messages in thread
From: Carlos Corbacho @ 2008-02-02 12:17 UTC (permalink / raw)
  To: linux-acpi; +Cc: Carlos Corbacho, Len Brown, Matthew Garrett

Create a 'wmi' class, and populate it with a virtual device for each GUID.
This also allows us to autoload WMI drivers based on GUID
via MODULE_ALIAS.

Under each GUID are a set of files that can be read and written to from
userspace (these will be fully documented in a later patch).

v1 (2007-11-03)

* Initial release

v2 (2007-11-07)

* Split out into a separate patch

v3 (2007-11-20)

* Convert kobject storage to using kernel list structures.
* Change instance handling - store input data on a per instance basis,
  rather than a per GUID.
* Add locking to methods - method_id (write) and data (read & write)
  should all be mutually exclusive - we do not want the input data to
  change when we are trying to execute a method.
* Change method calling semantics: when input is written to 'data',
  store it, but don't execute the method until 'data' is read. This is
  due to the fact that it is perfectly acceptable to have a WMI method
  that does not take any input (and it is easier to trigger an execute
  on reading the file, and not return anything if there is nothing to
  return, rather than trying to write values that aren't required, or
  may cause ACPI evaluation to fail on the method).
* Do not try and convert String's from ASCII to Unicode - instead, only
  handle ASCII (as per ACPI), and leave it up to userspace to convert
  to/ from whatever encoding they wish to use.

v4 (2007-11-28)

* Add code to remove sysfs files on module removal

v5 (2007-12-08)

* Convert to a class device, instead of trying to manually create and
  manipulate kobject's - this allows us to add module autoloading for WMI
  based drivers, and will (hopefully) be less likely to break with the
  latest round of kobject changes.
* Convert GUIDs to use a 'flat' structure - add two new files:
  instance and instance_count, and use these to provide the instance
  to call for a given GUID (while still communicating the maximum number
  of instances available - for now, we will always respect instance_count
  when setting instance - so any instance > instance_count will be
  discarded).

v6 (2007-12-12)

* Add string file - this reports back if the GUID has the string flag set.

v8 (2008-010-18)

* Add new in kernel function, wmi_get_device(), to take advantage of the
  class and virtual devices, and return the virtual device for a GUID.

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Len Brown <lenb@kernel.org>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
---

 drivers/acpi/wmi.c   |  484 ++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/acpi.h |    1 
 2 files changed, 483 insertions(+), 2 deletions(-)


diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c
index 36b84ab..9562607 100644
--- a/drivers/acpi/wmi.c
+++ b/drivers/acpi/wmi.c
@@ -30,6 +30,8 @@
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/types.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
 #include <linux/list.h>
 #include <linux/acpi.h>
 #include <acpi/acpi_bus.h>
@@ -66,6 +68,11 @@ struct wmi_block {
 	acpi_handle handle;
 	wmi_notify_handler handler;
 	void *handler_data;
+	struct device dev;
+	u8 instance;
+	u32 method_id;
+	void *pointer;
+	acpi_size length;
 };
 
 static struct wmi_block wmi_blocks;
@@ -194,6 +201,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 wmi_block **out)
 {
 	char tmp[16], guid_input[16];
@@ -516,6 +550,442 @@ bool wmi_has_guid(const char *guid_string)
 }
 EXPORT_SYMBOL_GPL(wmi_has_guid);
 
+/**
+ * wmi_get_device - Get the virtual device associated with a GUID
+ * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
+ * @dev: Pointer to pointer to the device
+ *
+ * Return a pointer to a pointer to virtual device associated with a GUID
+ */
+int wmi_get_device(const char *guid_string, struct device **dev)
+{
+	struct wmi_block *block;
+	bool ret;
+
+	ret = find_guid(guid_string, &block);
+	if (!ret)
+		return -ENODEV;
+
+	*dev = &block->dev;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(wmi_get_device);
+
+/*
+ * sysfs interface
+ */
+static ssize_t wmi_data_read(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count) {
+	u8 instance;
+	const char *guid;
+	struct acpi_buffer in;
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	acpi_status status;
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	guid = block->gblock.guid;
+	if (!guid)
+		return -EINVAL;
+
+	instance = block->instance;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		mutex_lock(&wmi_data_lock);
+		if (block->pointer) {
+			in.pointer = block->pointer;
+			in.length = block->length;
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, &in, &out);
+		} else {
+			status = wmi_evaluate_method(guid, instance,
+				block->method_id, NULL, &out);
+		}
+		mutex_unlock(&wmi_data_lock);
+	} else {
+		status = wmi_query_block(guid, instance, &out);
+	}
+
+	obj = (union acpi_object *) out.pointer;
+
+	switch (obj->type) {
+	case (ACPI_TYPE_STRING):
+	case (ACPI_TYPE_BUFFER):
+		buf = obj->buffer.pointer;
+		break;
+	default:
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static ssize_t wmi_data_write(struct kobject *kobj, struct bin_attribute
+			*bin_attr, char *buf, loff_t offset, size_t count){
+	struct wmi_block *block;
+	struct acpi_buffer in;
+	acpi_status status;
+	struct device *dev;
+	char guid_string[37];
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (count == 0)
+		return count;
+
+	if (block->gblock.flags & ACPI_WMI_METHOD) {
+		block->pointer = kzalloc(count, GFP_KERNEL);
+		if (!block->pointer)
+			return count;
+
+		memcpy(block->pointer, buf, count);
+		block->length = count;
+	} else {
+		in.pointer = buf;
+		in.length = count;
+		wmi_gtoa(block->gblock.guid, guid_string);
+		status = wmi_set_block(guid_string, block->instance, &in);
+	}
+
+	return count;
+}
+
+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) {
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	struct wmi_block *block;
+	struct device *dev;
+
+	dev = container_of(kobj, struct device, kobj);
+	if (!dev)
+		return -ENODEV;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	wmi_get_event_data(block->gblock.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,
+};
+
+static ssize_t show_type(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct guid_block *block;
+	struct wmi_block *wblock;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return sprintf(buf, "Error\n");
+
+	block = &wblock->gblock;
+
+	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 DEVICE_ATTR(type, S_IRUGO, show_type, NULL);
+
+static ssize_t show_instance_count(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->gblock.instance_count);
+}
+static DEVICE_ATTR(instance_count, S_IRUGO, show_instance_count, NULL);
+
+static ssize_t show_instance(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->instance);
+}
+
+static ssize_t set_instance(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 instance;
+
+	instance = simple_strtoul(buf, NULL, 10);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return -EINVAL;
+
+	if (instance <= block->gblock.instance_count) {
+		block->instance = instance;
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(instance, S_IWUGO | S_IRUGO, show_instance, set_instance);
+
+static ssize_t show_method_id(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block)
+		return sprintf(buf, "Error\n");
+
+	return sprintf(buf, "%d\n", block->method_id);
+}
+
+static ssize_t set_method_id(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct wmi_block *block;
+	u8 method_id;
+
+	method_id = simple_strtoul(buf, NULL, 10);
+
+	mutex_lock(&wmi_data_lock);
+
+	block = container_of(dev, struct wmi_block, dev);
+	if (!block) {
+		mutex_unlock(&wmi_data_lock);
+		return -EINVAL;
+	}
+
+	block->method_id = method_id;
+	mutex_unlock(&wmi_data_lock);
+
+	return count;
+}
+static DEVICE_ATTR(method_id, S_IWUGO | S_IRUGO, show_method_id,
+	set_method_id);
+
+static ssize_t show_event_notification(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", gblock->notify_id & 0xFF);
+}
+static DEVICE_ATTR(notification, S_IRUGO, show_event_notification, NULL);
+
+static ssize_t show_string(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct wmi_block *block;
+	struct guid_block *gblock;
+
+	block = container_of(dev, struct wmi_block, dev);
+	gblock = &block->gblock;
+
+	return sprintf(buf, "%d\n", (gblock->flags & ACPI_WMI_STRING) >> 2);
+}
+static DEVICE_ATTR(string, S_IRUGO, show_string, NULL);
+
+static int wmi_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	char guid_string[37];
+
+	struct wmi_block *wblock;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+
+	wblock = container_of(dev, struct wmi_block, dev);
+	if (!wblock)
+		return -ENOMEM;
+
+	wmi_gtoa(wblock->gblock.guid, guid_string);
+
+	strcpy(&env->buf[env->buflen - 1], "wmi:");
+	memcpy(&env->buf[env->buflen - 1 + 4], guid_string, 36);
+	env->buflen += 40;
+
+	return 0;
+}
+
+static struct class wmi_class = {
+	.name = "wmi",
+	.dev_release = (void(*)(struct device *)) kfree,
+	.dev_uevent = wmi_dev_uevent,
+};
+
+static int wmi_create_devs(void)
+{
+	int result;
+	char guid_string[37];
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Create devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		guid_dev->class = &wmi_class;
+
+		gblock = &wblock->gblock;
+
+		wmi_gtoa(gblock->guid, guid_string);
+		memcpy(guid_dev->bus_id, guid_string, 36);
+
+		result = device_register(guid_dev);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_type);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_string);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			result = device_create_file(guid_dev,
+				&dev_attr_notification);
+			if (result)
+				return result;
+
+			result = device_create_bin_file(guid_dev,
+				&wmi_attr_event_data);
+			if (result)
+				return result;
+			break;
+		}
+
+		result = device_create_file(guid_dev, &dev_attr_instance_count);
+		if (result)
+			return result;
+
+		result = device_create_file(guid_dev, &dev_attr_instance);
+		if (result)
+			return result;
+
+		result = device_create_bin_file(guid_dev, &wmi_attr_data);
+		if (result)
+			return result;
+
+		if (gblock->flags & ACPI_WMI_METHOD) {
+			result = device_create_file(guid_dev,
+				&dev_attr_method_id);
+			if (result)
+				return result;
+		}
+	}
+
+	return 0;
+}
+
+static void wmi_remove_devs(void)
+{
+	struct guid_block *gblock;
+	struct wmi_block *wblock;
+	struct list_head *p;
+	struct device *guid_dev;
+
+	/* Delete devices for all the GUIDs */
+	list_for_each(p, &wmi_blocks.list) {
+		wblock = list_entry(p, struct wmi_block, list);
+
+		guid_dev = &wblock->dev;
+		gblock = &wblock->gblock;
+
+		device_remove_file(guid_dev, &dev_attr_type);
+		device_remove_file(guid_dev, &dev_attr_string);
+
+		if (gblock->flags & ACPI_WMI_EVENT) {
+			device_remove_file(guid_dev, &dev_attr_notification);
+			device_remove_bin_file(guid_dev, &wmi_attr_event_data);
+		} else {
+			device_remove_file(guid_dev, &dev_attr_instance_count);
+			device_remove_file(guid_dev, &dev_attr_instance);
+			device_remove_bin_file(guid_dev, &wmi_attr_data);
+
+			if (gblock->flags & ACPI_WMI_METHOD) {
+				device_remove_file(guid_dev,
+					&dev_attr_method_id);
+			}
+		}
+		device_unregister(guid_dev);
+	}
+}
+
+static int wmi_class_init(void)
+{
+	int ret;
+
+	ret = class_register(&wmi_class);
+	if (ret)
+		return ret;
+
+	return wmi_create_devs();
+}
+
+static void wmi_class_exit(void)
+{
+	wmi_remove_devs();
+	class_unregister(&wmi_class);
+}
+
 /*
  * Parse the _WDG method for the GUID data blocks
  */
@@ -671,6 +1141,7 @@ static int __init acpi_wmi_add(struct acpi_device *device)
 
 static int __init acpi_wmi_init(void)
 {
+	int ret;
 	acpi_status result;
 
 	if (acpi_disabled)
@@ -682,10 +1153,17 @@ static int __init acpi_wmi_init(void)
 
 	if (result < 0) {
 		printk(KERN_INFO PREFIX "Error loading mapper\n");
-	} else {
-		printk(KERN_INFO PREFIX "Mapper loaded\n");
+		return -ENODEV;
+	}
+
+	ret = wmi_class_init();
+	if (ret) {
+		acpi_bus_unregister_driver(&acpi_wmi_driver);
+		return ret;
 	}
 
+	printk(KERN_INFO PREFIX "Mapper loaded\n");
+
 	return result;
 }
 
@@ -694,6 +1172,8 @@ static void __exit acpi_wmi_exit(void)
 	struct list_head *p, *tmp;
 	struct wmi_block *wblock;
 
+	wmi_class_exit();
+
 	acpi_bus_unregister_driver(&acpi_wmi_driver);
 
 	list_for_each_safe(p, tmp, &wmi_blocks.list) {
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 0ba5b00..de6883d 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -194,6 +194,7 @@ extern acpi_status wmi_install_notify_handler(const char *guid,
 extern acpi_status wmi_remove_notify_handler(const char *guid);
 extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out);
 extern bool wmi_has_guid(const char *guid);
+extern int wmi_get_device(const char *guid, struct device **dev);
 
 #endif	/* CONFIG_ACPI_WMI */
 


^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2008-02-02 12:21 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-01-12  2:20 [PATCH 0/5] WMI Carlos Corbacho
2008-01-12  2:20 ` [PATCH 1/5] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
2008-01-12 13:00   ` Carlos Corbacho
2008-01-12  2:20 ` [PATCH 2/5] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
2008-01-12  2:20 ` [PATCH 3/5] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
2008-01-12  2:20 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2008-01-12  2:20 ` [PATCH 5/5] [WIP] ACPI: WMI: Limit size of device string to 19 characters Carlos Corbacho
  -- strict thread matches above, loose matches on Subject: below --
2008-02-02 12:17 [PATCH 0/5] WMI patches for acpi-test Carlos Corbacho
2008-02-02 12:17 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2008-01-18 23:58 [PATCH 0/5] WMI Carlos Corbacho
2008-01-18 23:58 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2007-12-27 15:38 [PATCH 0/5] WMI Carlos Corbacho
2007-12-27 15:38 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2007-12-17  0:23 [PATCH 0/5] WMI Carlos Corbacho
2007-12-17  0:23 ` [PATCH 4/5] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).