* [PATCH 0/5] WMI
@ 2007-12-18 23:51 Carlos Corbacho
2007-12-18 23:51 ` [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
` (4 more replies)
0 siblings, 5 replies; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-18 23:51 UTC (permalink / raw)
To: linux-acpi; +Cc: Len Brown, Matthew Garrett, Alexey Starikovskiy
YAWR - Yet Another WMI release
Patch #1: (WMI - driver and in kernel interface)
Updated EC region handling as per Alexey's comments
Len:
For review, and hopefully to go up to 2.6.25
(Bar the EC stuff, this code has also been inflic^^^^tested by acer_acpi users
with the 0.10 RCs and 0.10 releases, so I'm confident on Acer hardware at least
that it won't break horribly).
Patch #2: (acer-wmi) - No Change
For review, and (rather tentatively here) 2.6.25 material?
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
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]). This
should _NOT_ be applied to any upstream kernel.
-Carlos
[1] http://lkml.org/lkml/2007/12/4/30
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver
2007-12-18 23:51 [PATCH 0/5] WMI Carlos Corbacho
@ 2007-12-18 23:51 ` Carlos Corbacho
2007-12-26 21:17 ` Matthew Garrett
2007-12-18 23:51 ` [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
` (3 subsequent siblings)
4 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-18 23:51 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
with WMI. When a notification is sent to WMI, the handler registered with
WMI is then called (it is left to the caller to ask for the WMI event data,
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.
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 | 696 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/acpi.h | 20 +
5 files changed, 735 insertions(+), 0 deletions(-)
create mode 100644 drivers/acpi/wmi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9507b42..68101ad 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 b9f923e..5fe266c 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -195,6 +195,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..1392aa9
--- /dev/null
+++ b/drivers/acpi/wmi.c
@@ -0,0 +1,696 @@
+/*
+ * 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;
+};
+
+static struct wmi_block wmi_blocks;
+
+static wmi_notify_handler wmi_external_handler;
+static void *wmi_external_data;
+
+/*
+ * 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(wmi_notify_handler handler, void *data)
+{
+ if (!handler)
+ return AE_BAD_PARAMETER;
+
+ if (!wmi_external_handler)
+ return AE_ALREADY_ACQUIRED;
+
+ wmi_external_handler = handler;
+ wmi_external_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(void)
+{
+ if (wmi_external_handler) {
+ wmi_external_handler = NULL;
+ wmi_external_data = NULL;
+ return AE_OK;
+ }
+ return AE_ERROR;
+}
+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(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 (wmi_external_handler)
+ wmi_external_handler(event, wmi_external_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 (ACPI_FAILURE(result)) {
+ 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..0ec11e7 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(wmi_notify_handler handler,
+ void *data);
+extern acpi_status wmi_remove_notify_handler(void);
+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] 23+ messages in thread
* [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops
2007-12-18 23:51 [PATCH 0/5] WMI Carlos Corbacho
2007-12-18 23:51 ` [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
@ 2007-12-18 23:51 ` Carlos Corbacho
2007-12-19 1:58 ` Carlos Corbacho
2007-12-18 23:51 ` [PATCH 3/4] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
` (2 subsequent siblings)
4 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-18 23:51 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
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 | 1009 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1026 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..ebdf70c
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1009 @@
+/*
+ * Acer Laptop WMI Extras
+ *
+ * Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ * Based on acer_acpi:
+ * Copyright (C) 2005-2007 E.M. Smith
+ * Copyright (C) 2007 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 (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 EC)
+ */
+#define ACER_AMW0_WRITE 0x9610
+
+/*
+ * Bit masks for the old AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK 0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK 0x31
+
+/*
+ * Method IDs for new 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"
+
+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;
+ u8 max_brightness;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+ if (quirks->mailled != 0)
+ interface->capability |= ACER_CAP_MAILLED;
+
+ if (quirks->brightness != 0)
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+
+ if (quirks->bluetooth != 0)
+ interface->capability |= ACER_CAP_BLUETOOTH;
+
+ if (quirks->wireless != 0)
+ interface->capability |= ACER_CAP_WIRELESS;
+
+ if (quirks->max_brightness != 0)
+ max_brightness = quirks->max_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,
+};
+
+static struct quirk_entry quirk_acer_travelmate_5720 = {
+ .max_brightness = 0x9,
+};
+
+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 Extensa 5220",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Extensa 5220"),
+ },
+ .driver_data = &quirk_acer_travelmate_5720,
+ },
+ {
+ .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 = "Acer TravelMate 5720",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 5720"),
+ },
+ .driver_data = &quirk_acer_travelmate_5720,
+ },
+ {}
+};
+
+/* 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;
+}
+
+/*
+ * Old interface (now known as the AMW0 interface)
+ */
+struct wmab_args {
+ u32 eax;
+ u32 ebx;
+ u32 ecx;
+ u32 edx;
+};
+
+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_bool(bool *value, u32 cap,
+struct wmi_interface *iface)
+{
+ u8 result;
+
+ switch (cap) {
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 7) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_WIRELESS:
+ switch (quirks->wireless) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 2) & 0x01;
+ return 0;
+ }
+ break;
+ case ACER_CAP_BLUETOOTH:
+ switch (quirks->bluetooth) {
+ default:
+ ec_read(0x0A, &result);
+ *value = (result >> 4) & 0x01;
+ return 0;
+ }
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_bool(bool 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:
+ args.ebx |= ACER_AMW0_MAILLED_MASK;
+ break;
+ case ACER_CAP_WIRELESS:
+ args.ebx |= ACER_AMW0_WIRELESS_MASK;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+
+ /* Actually do the set */
+ return WMAB_execute(&args, NULL);
+}
+
+static acpi_status AMW0_get_u8(u8 *value, u32 cap, struct wmi_interface *iface)
+{
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ return ec_read(0x83, value);
+ }
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static acpi_status AMW0_set_u8(u8 value, u32 cap, struct wmi_interface *iface)
+{
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ switch (quirks->brightness) {
+ default:
+ return ec_write(0x83, value);
+ break;
+ }
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return AE_OK;
+}
+
+static struct wmi_interface AMW0_interface = {
+ .type = ACER_AMW0,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH
+ ),
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+ .type = ACER_AMW0_V2,
+ .capability = (
+ ACER_CAP_MAILLED |
+ ACER_CAP_WIRELESS |
+ ACER_CAP_BLUETOOTH |
+ ACER_CAP_BRIGHTNESS
+ ),
+};
+
+/*
+ * 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;
+
+ if (result.length > 0 && result.pointer)
+ kfree(result.pointer);
+
+ return status;
+}
+
+static acpi_status WMID_get_u8(u8 *value, u32 cap, struct wmi_interface *iface)
+{
+ acpi_status status;
+ u32 result;
+ u32 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:
+ switch (quirks->mailled) {
+ case 1:
+ ec_read(0x9f, value);
+ *value &= 0x01;
+ return 0;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ 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_u8(u8 value, u32 cap, struct wmi_interface *iface)
+{
+ u32 method_id = 0;
+ char param;
+
+ switch (cap) {
+ case ACER_CAP_BRIGHTNESS:
+ method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+ break;
+ case ACER_CAP_WIRELESS:
+ method_id = ACER_WMID_SET_WIRELESS_METHODID;
+ break;
+ case ACER_CAP_BLUETOOTH:
+ method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+ break;
+ case ACER_CAP_THREEG:
+ method_id = ACER_WMID_SET_THREEG_METHODID;
+ break;
+ case ACER_CAP_MAILLED:
+ switch (quirks->mailled) {
+ case 1:
+ param = value? 0x92 : 0x93;
+ i8042_command(¶m, 0x1059);
+ return 0;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ default:
+ return AE_BAD_ADDRESS;
+ }
+ return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+
+static struct wmi_interface wmid_interface = {
+ .type = ACER_WMID,
+ .capability = (
+ ACER_CAP_WIRELESS
+ | ACER_CAP_BRIGHTNESS
+ | ACER_CAP_BLUETOOTH
+ | ACER_CAP_THREEG
+ ),
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_bool(bool *value, u32 cap)
+{
+ acpi_status status = AE_BAD_ADDRESS;
+ u8 tmp = 0;
+
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_get_bool(value, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_get_bool(value, cap, interface);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_get_u8(&tmp, cap, interface);
+ *value = (tmp == 1) ? 1 : 0;
+ break;
+ }
+ return status;
+}
+
+static acpi_status set_bool(int value, u32 cap)
+{
+ acpi_status status = AE_BAD_PARAMETER;
+
+ if ((value == 0 || value == 1) && (interface->capability & cap)) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ status = AMW0_set_bool(value == 1, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ if (cap == ACER_CAP_MAILLED) {
+ status = AMW0_set_bool(value == 1, cap,
+ interface);
+ break;
+ }
+ case ACER_WMID:
+ status = WMID_set_u8(value == 1, cap, interface);
+ break;
+ }
+ }
+ return status;
+}
+
+static acpi_status get_u8(u8 *value, u32 cap)
+{
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_get_u8(value, cap, interface);
+ break;
+ case ACER_AMW0_V2:
+ case ACER_WMID:
+ return WMID_get_u8(value, cap, interface);
+ break;
+ default:
+ return AE_BAD_ADDRESS;
+ }
+}
+
+static acpi_status set_u8(u8 value, u8 min, u8 max, u32 cap)
+{
+ if ((value >= min && value <= max) && (interface->capability & cap)) {
+ switch (interface->type) {
+ case ACER_AMW0:
+ return AMW0_set_u8(value, cap, interface);
+ case ACER_AMW0_V2:
+ case ACER_WMID:
+ return WMID_set_u8(value, cap, interface);
+ default:
+ return AE_BAD_PARAMETER;
+ }
+ }
+ return AE_BAD_PARAMETER;
+}
+
+/* Each _u8 needs a small wrapper that sets the boundary values */
+static acpi_status set_brightness(u8 value)
+{
+ return set_u8(value, 0, max_brightness, ACER_CAP_BRIGHTNESS);
+}
+
+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_bool(mailled, ACER_CAP_MAILLED);
+ set_bool(wireless, ACER_CAP_WIRELESS);
+ set_bool(bluetooth, ACER_CAP_BLUETOOTH);
+ set_bool(threeg, ACER_CAP_THREEG);
+ set_brightness((u8)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)
+{
+ bool tmp = value;
+ set_bool(tmp, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+ .name = "acer-mail:green",
+ .brightness_set = mail_led_set,
+};
+
+static void acer_led_init(struct device *dev)
+{
+ led_classdev_register(dev, &mail_led);
+}
+
+static void 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)
+{
+ u8 value;
+ get_u8(&value, ACER_CAP_BRIGHTNESS);
+ return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+ set_brightness(bd->props.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) \
+{ \
+ bool result; \
+ acpi_status status = get_bool(&result, cap); \
+ 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) \
+{ \
+ bool tmp = simple_strtoul(buf, NULL, 10); \
+ acpi_status status = set_bool(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)
+{
+ if (has_cap(ACER_CAP_MAILLED))
+ acer_led_init(&device->dev);
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ acer_backlight_init(&device->dev);
+ return 0;
+}
+
+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)
+{
+ bool value;
+ u8 u8value;
+ struct acer_data *data = &interface->data;
+
+ if (!data)
+ return -ENOMEM;
+
+ if (has_cap(ACER_CAP_WIRELESS)) {
+ get_bool(&value, ACER_CAP_WIRELESS);
+ data->wireless = value;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ get_bool(&value, ACER_CAP_BLUETOOTH);
+ data->bluetooth = value;
+ }
+
+ if (has_cap(ACER_CAP_MAILLED)) {
+ get_bool(&value, ACER_CAP_MAILLED);
+ data->mailled = value;
+ }
+
+ if (has_cap(ACER_CAP_BRIGHTNESS)) {
+ get_u8(&u8value, 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_bool(data->wireless, ACER_CAP_WIRELESS);
+
+ if (has_cap(ACER_CAP_BLUETOOTH))
+ set_bool(data->bluetooth, ACER_CAP_BLUETOOTH);
+
+ if (has_cap(ACER_CAP_THREEG))
+ set_bool(data->threeg, ACER_CAP_THREEG);
+
+ if (has_cap(ACER_CAP_MAILLED))
+ set_bool(data->mailled, ACER_CAP_MAILLED);
+
+ if (has_cap(ACER_CAP_BRIGHTNESS))
+ set_brightness((u8)data->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;
+ }
+
+ if (has_cap(ACER_CAP_BLUETOOTH)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_bluetooth);
+ if (retval)
+ goto error;
+ }
+
+ if (has_cap(ACER_CAP_THREEG)) {
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_threeg);
+ if (retval)
+ goto error;
+ }
+
+ retval = device_create_file(&acer_platform_device->dev,
+ &dev_attr_interface);
+ if (retval)
+ goto error;
+
+ return 0;
+
+error:
+ remove_sysfs(acer_platform_device);
+ return retval;
+}
+
+static int __init acer_wmi_init(void)
+{
+ 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)) {
+ interface = &AMW0_interface;
+
+ if (!quirks)
+ return -ENOMEM;
+
+ if (wmi_has_guid(WMID_GUID1)) {
+ interface = &AMW0_V2_interface;
+ } else {
+ if (!quirks->brightness) {
+ quirks->brightness = 1;
+ interface->capability |= ACER_CAP_BRIGHTNESS;
+ }
+ }
+ } else if (wmi_has_guid(WMID_GUID1)) {
+ interface = &wmid_interface;
+ } else {
+ 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);
+
+ create_sysfs();
+
+ /* 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] 23+ messages in thread
* [PATCH 3/4] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets
2007-12-18 23:51 [PATCH 0/5] WMI Carlos Corbacho
2007-12-18 23:51 ` [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
2007-12-18 23:51 ` [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
@ 2007-12-18 23:51 ` Carlos Corbacho
2007-12-18 23:52 ` [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2007-12-26 22:48 ` [PATCH, RFC] HP WMI hotkey driver Matthew Garrett
4 siblings, 0 replies; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-18 23:51 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] 23+ messages in thread
* [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface
2007-12-18 23:51 [PATCH 0/5] WMI Carlos Corbacho
` (2 preceding siblings ...)
2007-12-18 23:51 ` [PATCH 3/4] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
@ 2007-12-18 23:52 ` Carlos Corbacho
2007-12-26 19:38 ` Matthew Garrett
2007-12-26 22:48 ` [PATCH, RFC] HP WMI hotkey driver Matthew Garrett
4 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-18 23:52 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 1392aa9..f9f289e 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];
@@ -501,6 +535,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)
@@ -657,6 +1106,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)
@@ -668,10 +1118,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;
}
@@ -680,6 +1137,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] 23+ messages in thread
* Re: [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops
2007-12-18 23:51 ` [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
@ 2007-12-19 1:58 ` Carlos Corbacho
2007-12-19 7:45 ` Matthew Garrett
0 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-19 1:58 UTC (permalink / raw)
To: linux-acpi; +Cc: Len Brown, Matthew Garrett
On Tuesday 18 December 2007 23:51:49 Carlos Corbacho wrote:
> 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.
Matthew,
>From a distribution (and I suppose support) perspective, what would be a
sensible default for wireless and bluetooth? (I suppose the wireless bit
probably also applies to tc1100-wmi).
At the moment, the current behaviour is to just leave these devices alone
(although they can be overridden from the command line).
Would it be better to default to switching the wireless and bluetooth on
instead, and leave a command line override for those who would rather they
default to off?
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops
2007-12-19 1:58 ` Carlos Corbacho
@ 2007-12-19 7:45 ` Matthew Garrett
0 siblings, 0 replies; 23+ messages in thread
From: Matthew Garrett @ 2007-12-19 7:45 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown
On Wed, Dec 19, 2007 at 01:58:10AM +0000, Carlos Corbacho wrote:
> From a distribution (and I suppose support) perspective, what would be a
> sensible default for wireless and bluetooth? (I suppose the wireless bit
> probably also applies to tc1100-wmi).
I suspect the right thing to do is to hook into rfkill and then leave
policy up to the distribution.
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface
2007-12-18 23:52 ` [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
@ 2007-12-26 19:38 ` Matthew Garrett
2007-12-27 0:54 ` Carlos Corbacho
0 siblings, 1 reply; 23+ messages in thread
From: Matthew Garrett @ 2007-12-26 19:38 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown
+ memcpy(guid_dev->bus_id, guid_string, 36);
bus_id is only 20 bytes long at the moment...
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver
2007-12-18 23:51 ` [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
@ 2007-12-26 21:17 ` Matthew Garrett
2007-12-27 1:09 ` Carlos Corbacho
0 siblings, 1 reply; 23+ messages in thread
From: Matthew Garrett @ 2007-12-26 21:17 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown, Alexey Starikovskiy
+acpi_status wmi_install_notify_handler(wmi_notify_handler handler, void
*data)
+{
+ if (!handler)
+ return AE_BAD_PARAMETER;
+
+ if (!wmi_external_handler)
+ return AE_ALREADY_ACQUIRED;
Are you sure the negation on the second if is correct? I'm also a bit
unhappy about only being able to register one notification handler, if
we consider the case where vendors add and remove WMI interfaces over
time - it might make sense to have individual small drivers rather than
one big one, which would mean a notification handler per GUID. Any
thoughts?
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH, RFC] HP WMI hotkey driver
2007-12-18 23:51 [PATCH 0/5] WMI Carlos Corbacho
` (3 preceding siblings ...)
2007-12-18 23:52 ` [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
@ 2007-12-26 22:48 ` Matthew Garrett
2008-01-03 16:58 ` Dmitry Torokhov
4 siblings, 1 reply; 23+ messages in thread
From: Matthew Garrett @ 2007-12-26 22:48 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown, Alexey Starikovskiy, linux-input
This adds a driver for hotkey events generated through the WMI subsystem
on some recent HP laptops. Mine only has a single key that works this
way, so I'd be interested in getting some feedback from anyone else who
has a recent HP machine which doesn't send hotkey events through the
keyboard controller. It depends on Carlos's WMI patches, plus a couple
of bugfixes I've suggested on linux-acpi (limit the memcpy into bus_id
to 20 bytes and make sure it's null terminated, and invert the sense in
wmi_register_notify so it's actually possible to register a callback.
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 8f5c7b9..2f96736 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -183,4 +183,13 @@ config HP_SDC_RTC
Say Y here if you want to support the built-in real time clock
of the HP SDC controller.
+config INPUT_HP_WMI
+ tristate "HP WMI hotkey interface"
+ depends on ACPI_WMI
+ help
+ Say Y here if you want to support WMI-based hotkeys on HP laptops.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hp_wmi.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 3585b50..1bc3b15 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
+obj-$(CONFIG_INPUT_HP_WMI) += hp_wmi.o
\ No newline at end of file
diff --git a/drivers/input/misc/hp_wmi.c b/drivers/input/misc/hp_wmi.c
new file mode 100644
index 0000000..17c7d4b
--- /dev/null
+++ b/drivers/input/misc/hp_wmi.c
@@ -0,0 +1,189 @@
+/*
+ * HP WMI hotkeys
+ *
+ * Copyright (C) 2007 Matthew Garrett <mjg59@srcf.ucam.org>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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 <linux/input.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Matthew Garrett");
+MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
+
+#define HPWMI_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
+
+struct key_entry {
+ char type; /* See KE_* below */
+ u8 code;
+ union {
+ u16 keycode; /* For KE_KEY */
+ struct { /* For KE_SW */
+ u8 code;
+ u8 value;
+ } sw;
+ };
+};
+
+enum {KE_KEY, KE_END };
+
+static struct key_entry hp_wmi_keymap[] = {
+ { KE_KEY, 0x04, {KEY_HELP} },
+ { KE_END, 0 }
+};
+
+static struct input_dev *hp_wmi_input_dev;
+
+static struct key_entry *hp_wmi_get_entry_by_scancode (int code)
+{
+ struct key_entry *key;
+
+ for (key = hp_wmi_keymap; key->type != KE_END; key++)
+ if (code == key->code)
+ return key;
+
+ return NULL;
+}
+
+static struct key_entry *hp_wmi_get_entry_by_keycode(int keycode)
+{
+ struct key_entry *key;
+
+ for (key = hp_wmi_keymap; key->type != KE_END; key++)
+ if (key->type == KE_KEY && keycode == key->keycode)
+ return key;
+
+ return NULL;
+}
+
+static int hp_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+ struct key_entry *key = hp_wmi_get_entry_by_scancode(scancode);
+
+ if (key && key->type == KE_KEY) {
+ *keycode = key->keycode;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int hp_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode)
+{
+ struct key_entry *key;
+
+ int old_keycode;
+
+ if (keycode < 0 || keycode > KEY_MAX)
+ return -EINVAL;
+
+ key = hp_wmi_get_entry_by_scancode(scancode);
+ if (key && key->type == KE_KEY) {
+ old_keycode = key->keycode;
+ key->keycode = keycode;
+ set_bit(keycode, dev->keybit);
+ if (!hp_wmi_get_entry_by_keycode(old_keycode))
+ clear_bit(old_keycode, dev->keybit);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+void hp_wmi_notify (u32 value, void* context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ static struct key_entry *key;
+ union acpi_object *obj;
+
+ wmi_get_event_data (value, &response);
+
+ obj = (union acpi_object *) response.pointer;
+
+ if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == 8) {
+ key = hp_wmi_get_entry_by_scancode
+ (*((u8 *) obj->buffer.pointer));
+ if (key) {
+ input_report_key (hp_wmi_input_dev, key->keycode, 1);
+ input_sync (hp_wmi_input_dev);
+ input_report_key (hp_wmi_input_dev, key->keycode, 0);
+ input_sync (hp_wmi_input_dev);
+ } else
+ printk (KERN_INFO "HP WMI: Unknown key pressed\n");
+ } else
+ printk (KERN_INFO "HP WMI: Unknown response received\n");
+
+ return;
+}
+
+static int __init hp_wmi_init(void)
+{
+ int err;
+ const struct key_entry *key;
+
+ if (!wmi_has_guid(HPWMI_GUID)) {
+ printk ("Unable to locate guid\n");
+ return -ENODEV;
+ }
+
+ err = wmi_install_notify_handler (hp_wmi_notify, NULL);
+ if (err)
+ return err;
+
+ hp_wmi_input_dev = input_allocate_device();
+
+ hp_wmi_input_dev->name = "HP WMI hotkeys";
+ hp_wmi_input_dev->phys = "wmi/input0";
+ hp_wmi_input_dev->id.bustype = BUS_HOST;
+ hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode;
+ hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode;
+
+ for (key = hp_wmi_keymap; key->type != KE_END; key++) {
+ set_bit(EV_KEY, hp_wmi_input_dev->evbit);
+ set_bit(key->keycode, hp_wmi_input_dev->keybit);
+ }
+
+ err = input_register_device (hp_wmi_input_dev);
+
+ if (err) {
+ input_free_device (hp_wmi_input_dev);
+ return err;
+ }
+
+ return 0;
+}
+
+static void __exit hp_wmi_exit(void)
+{
+ wmi_remove_notify_handler();
+ input_unregister_device (hp_wmi_input_dev);
+ input_free_device (hp_wmi_input_dev);
+}
+
+module_init(hp_wmi_init);
+module_exit(hp_wmi_exit);
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface
2007-12-26 19:38 ` Matthew Garrett
@ 2007-12-27 0:54 ` Carlos Corbacho
2007-12-27 1:05 ` Carlos Corbacho
0 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-27 0:54 UTC (permalink / raw)
To: Matthew Garrett; +Cc: linux-acpi, Len Brown
On Wednesday 26 December 2007 19:38:22 Matthew Garrett wrote:
> + memcpy(guid_dev->bus_id, guid_string, 36);
>
> bus_id is only 20 bytes long at the moment...
That's what the last patch in the series is the hack for (shortens this to 20
so you can at least see what this would like look, and test the GUID based
autoloading) - although I think I forgot to resend that one with the last WMI
series.
Until this gets fixed upstream, the sysfs interface patch remains an RFC only.
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface
2007-12-27 0:54 ` Carlos Corbacho
@ 2007-12-27 1:05 ` Carlos Corbacho
0 siblings, 0 replies; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-27 1:05 UTC (permalink / raw)
To: Matthew Garrett; +Cc: linux-acpi, Len Brown
On Thursday 27 December 2007 00:54:01 Carlos Corbacho wrote:
> On Wednesday 26 December 2007 19:38:22 Matthew Garrett wrote:
> > + memcpy(guid_dev->bus_id, guid_string, 36);
> >
> > bus_id is only 20 bytes long at the moment...
>
> That's what the last patch in the series is the hack for (shortens this to
> 20 so you can at least see what this would like look, and test the GUID
> based autoloading) - although I think I forgot to resend that one with the
> last WMI series.
>
> Until this gets fixed upstream, the sysfs interface patch remains an RFC
> only.
The reason I'm a bit concerned about folding in the 20 byte limit is that I
don't want to hit the case where we get a system with a bunch of GUIDs that
only differ by the last 16 bytes and then fail to register the WMI driver.
Although I suppose in the short term, we don't know of such systems, nor do we
have any real WMI drivers for them anyway, so this might be an acceptable
trade off. So if you think the risk is worth it, I'll fold in the bus_id
limit.
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver
2007-12-26 21:17 ` Matthew Garrett
@ 2007-12-27 1:09 ` Carlos Corbacho
2007-12-27 2:21 ` Matthew Garrett
0 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2007-12-27 1:09 UTC (permalink / raw)
To: Matthew Garrett; +Cc: linux-acpi, Len Brown, Alexey Starikovskiy
On Wednesday 26 December 2007 21:17:18 Matthew Garrett wrote:
> + if (!wmi_external_handler)
> + return AE_ALREADY_ACQUIRED;
I'm not sure (mostly because I don't like dealing with typedefs around
pointers, and this bit is also untested).
> I'm also a bit
> unhappy about only being able to register one notification handler, if
> we consider the case where vendors add and remove WMI interfaces over
> time - it might make sense to have individual small drivers rather than
> one big one, which would mean a notification handler per GUID. Any
> thoughts?
Yes, a notification handler per GUID does make sense, so I'll add that to WMI.
Also, many thanks for reviewing this.
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver
2007-12-27 1:09 ` Carlos Corbacho
@ 2007-12-27 2:21 ` Matthew Garrett
0 siblings, 0 replies; 23+ messages in thread
From: Matthew Garrett @ 2007-12-27 2:21 UTC (permalink / raw)
To: Carlos Corbacho; +Cc: linux-acpi, Len Brown, Alexey Starikovskiy
On Thu, Dec 27, 2007 at 01:09:39AM +0000, Carlos Corbacho wrote:
> On Wednesday 26 December 2007 21:17:18 Matthew Garrett wrote:
> > + if (!wmi_external_handler)
> > + return AE_ALREADY_ACQUIRED;
>
> I'm not sure (mostly because I don't like dealing with typedefs around
> pointers, and this bit is also untested).
At the moment, it's impossible to register a handler - I always get
AE_ALREADY_ACQUIRED. Getting rid of the ! makes things work like I
expected.
> > I'm also a bit
> > unhappy about only being able to register one notification handler, if
> > we consider the case where vendors add and remove WMI interfaces over
> > time - it might make sense to have individual small drivers rather than
> > one big one, which would mean a notification handler per GUID. Any
> > thoughts?
>
> Yes, a notification handler per GUID does make sense, so I'll add that to WMI.
Excellent, thanks!
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver
2007-12-26 22:48 ` [PATCH, RFC] HP WMI hotkey driver Matthew Garrett
@ 2008-01-03 16:58 ` Dmitry Torokhov
2008-01-14 17:41 ` [PATCH, RFC] HP WMI hotkey driver, RFKill query? Matthew Garrett
0 siblings, 1 reply; 23+ messages in thread
From: Dmitry Torokhov @ 2008-01-03 16:58 UTC (permalink / raw)
To: Matthew Garrett
Cc: Carlos Corbacho, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
Hi Matthew,
On Dec 26, 2007 5:48 PM, Matthew Garrett <mjg59@srcf.ucam.org> wrote:
> + } else
> + printk (KERN_INFO "HP WMI: Unknown response received\n");
> +
> + return;
No need for empty returns.
> +}
> +
> +static int __init hp_wmi_init(void)
> +{
> + int err;
> + const struct key_entry *key;
> +
> + if (!wmi_has_guid(HPWMI_GUID)) {
> + printk ("Unable to locate guid\n");
> + return -ENODEV;
> + }
> +
> + err = wmi_install_notify_handler (hp_wmi_notify, NULL);
> + if (err)
> + return err;
> +
> + hp_wmi_input_dev = input_allocate_device();
> +
> + hp_wmi_input_dev->name = "HP WMI hotkeys";
> + hp_wmi_input_dev->phys = "wmi/input0";
> + hp_wmi_input_dev->id.bustype = BUS_HOST;
> + hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode;
> + hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode;
> +
There is no sysfs device to attach the input device to, by any chance?
> + for (key = hp_wmi_keymap; key->type != KE_END; key++) {
> + set_bit(EV_KEY, hp_wmi_input_dev->evbit);
> + set_bit(key->keycode, hp_wmi_input_dev->keybit);
> + }
> +
> + err = input_register_device (hp_wmi_input_dev);
> +
> + if (err) {
> + input_free_device (hp_wmi_input_dev);
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static void __exit hp_wmi_exit(void)
> +{
> + wmi_remove_notify_handler();
> + input_unregister_device (hp_wmi_input_dev);
> + input_free_device (hp_wmi_input_dev);
Do not call input_free_device after input_unregister_device, it may
cause freeing already freed memory.
--
Dmitry
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-03 16:58 ` Dmitry Torokhov
@ 2008-01-14 17:41 ` Matthew Garrett
2008-01-14 18:27 ` Carlos Corbacho
0 siblings, 1 reply; 23+ messages in thread
From: Matthew Garrett @ 2008-01-14 17:41 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Carlos Corbacho, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
On Thu, Jan 03, 2008 at 11:58:02AM -0500, Dmitry Torokhov wrote:
> There is no sysfs device to attach the input device to, by any chance?
Not as far as I can tell, but I may be misinterpreting this. Carlos?
I've fixed up the other bits and updated it to match the latest WMI
code. I've also added a couple of extra key events. The main one I'm
worried about is KEY_WLAN. The button automatically disables the
hardware, so it's not required that userspace do anything - however, it
would be nice for network-manager to get the additional notification. Is
KEY_WLAN the correct thing to be sending here? There's the added
confusion of it also triggering the removal of the bluetooth hardware,
but since the entire device vanishes in that case I think userspace will
already notice.
Signed-off-by: Matthew Garrett <mjg59@srcf.ucam.org>
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 8f5c7b9..6a6dd9a 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -183,4 +183,13 @@ config HP_SDC_RTC
Say Y here if you want to support the built-in real time clock
of the HP SDC controller.
+config INPUT_HP_WMI
+ tristate "HP WMI hotkey interface"
+ depends on ACPI_WMI
+ help
+ Say Y here if you want to support WMI-based hotkeys on HP laptops.
+
+ To compile this driver as a module, choose M here: the module will
+ be called hp_wmiinput.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 3585b50..8a3bf11 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
+obj-$(CONFIG_INPUT_HP_WMI) += hp_wmiinput.o
\ No newline at end of file
diff --git a/drivers/input/misc/hp_wmiinput.c b/drivers/input/misc/hp_wmiinput.c
new file mode 100644
index 0000000..85da258
--- /dev/null
+++ b/drivers/input/misc/hp_wmiinput.c
@@ -0,0 +1,191 @@
+/*
+ * HP WMI hotkeys
+ *
+ * Copyright (C) 2007 Matthew Garrett <mjg59@srcf.ucam.org>
+ *
+ * Portions based on wistron_btns.c:
+ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
+ * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org>
+ * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru>
+ *
+ * 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 <linux/input.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Matthew Garrett");
+MODULE_DESCRIPTION("HP laptop WMI hotkeys driver");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C");
+
+#define HPWMI_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C"
+
+struct key_entry {
+ char type; /* See KE_* below */
+ u8 code;
+ union {
+ u16 keycode; /* For KE_KEY */
+ struct { /* For KE_SW */
+ u8 code;
+ u8 value;
+ } sw;
+ };
+};
+
+enum {KE_KEY, KE_END };
+
+static struct key_entry hp_wmi_keymap[] = {
+ /* 0x01 seems to be docking stations? */
+ { KE_KEY, 0x02, {KEY_BRIGHTNESSUP} },
+ { KE_KEY, 0x03, {KEY_BRIGHTNESSDOWN} },
+ { KE_KEY, 0x04, {KEY_HELP} },
+ { KE_KEY, 0x05, {KEY_WLAN} },
+ { KE_END, 0 }
+};
+
+static struct input_dev *hp_wmi_input_dev;
+
+static struct key_entry *hp_wmi_get_entry_by_scancode (int code)
+{
+ struct key_entry *key;
+
+ for (key = hp_wmi_keymap; key->type != KE_END; key++)
+ if (code == key->code)
+ return key;
+
+ return NULL;
+}
+
+static struct key_entry *hp_wmi_get_entry_by_keycode(int keycode)
+{
+ struct key_entry *key;
+
+ for (key = hp_wmi_keymap; key->type != KE_END; key++)
+ if (key->type == KE_KEY && keycode == key->keycode)
+ return key;
+
+ return NULL;
+}
+
+static int hp_wmi_getkeycode(struct input_dev *dev, int scancode, int *keycode)
+{
+ struct key_entry *key = hp_wmi_get_entry_by_scancode(scancode);
+
+ if (key && key->type == KE_KEY) {
+ *keycode = key->keycode;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int hp_wmi_setkeycode(struct input_dev *dev, int scancode, int keycode)
+{
+ struct key_entry *key;
+
+ int old_keycode;
+
+ if (keycode < 0 || keycode > KEY_MAX)
+ return -EINVAL;
+
+ key = hp_wmi_get_entry_by_scancode(scancode);
+ if (key && key->type == KE_KEY) {
+ old_keycode = key->keycode;
+ key->keycode = keycode;
+ set_bit(keycode, dev->keybit);
+ if (!hp_wmi_get_entry_by_keycode(old_keycode))
+ clear_bit(old_keycode, dev->keybit);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+void hp_wmi_notify (u32 value, void* context)
+{
+ struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+ static struct key_entry *key;
+ union acpi_object *obj;
+
+ wmi_get_event_data (value, &response);
+
+ obj = (union acpi_object *) response.pointer;
+
+ if (obj && obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == 8) {
+ int eventcode = *((u8 *) obj->buffer.pointer);
+ key = hp_wmi_get_entry_by_scancode (eventcode);
+ if (key) {
+ input_report_key (hp_wmi_input_dev, key->keycode, 1);
+ input_sync (hp_wmi_input_dev);
+ input_report_key (hp_wmi_input_dev, key->keycode, 0);
+ input_sync (hp_wmi_input_dev);
+ } else
+ printk (KERN_INFO "HP WMI: Unknown key pressed - %x\n",
+ eventcode);
+ } else
+ printk (KERN_INFO "HP WMI: Unknown response received\n");
+}
+
+static int __init hp_wmi_init(void)
+{
+ int err;
+ const struct key_entry *key;
+
+ if (!wmi_has_guid(HPWMI_GUID)) {
+ printk ("Unable to locate guid\n");
+ return -ENODEV;
+ }
+
+ err = wmi_install_notify_handler (HPWMI_GUID, hp_wmi_notify, NULL);
+ if (err)
+ return err;
+
+ hp_wmi_input_dev = input_allocate_device();
+
+ hp_wmi_input_dev->name = "HP WMI hotkeys";
+ hp_wmi_input_dev->phys = "wmi/input0";
+ hp_wmi_input_dev->id.bustype = BUS_HOST;
+ hp_wmi_input_dev->getkeycode = hp_wmi_getkeycode;
+ hp_wmi_input_dev->setkeycode = hp_wmi_setkeycode;
+
+ for (key = hp_wmi_keymap; key->type != KE_END; key++) {
+ set_bit(EV_KEY, hp_wmi_input_dev->evbit);
+ set_bit(key->keycode, hp_wmi_input_dev->keybit);
+ }
+
+ err = input_register_device (hp_wmi_input_dev);
+
+ if (err) {
+ input_free_device (hp_wmi_input_dev);
+ return err;
+ }
+
+ return 0;
+}
+
+static void __exit hp_wmi_exit(void)
+{
+ wmi_remove_notify_handler(HPWMI_GUID);
+ input_unregister_device (hp_wmi_input_dev);
+}
+
+module_init(hp_wmi_init);
+module_exit(hp_wmi_exit);
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-14 17:41 ` [PATCH, RFC] HP WMI hotkey driver, RFKill query? Matthew Garrett
@ 2008-01-14 18:27 ` Carlos Corbacho
2008-01-14 22:25 ` Carlos Corbacho
0 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2008-01-14 18:27 UTC (permalink / raw)
To: Matthew Garrett
Cc: Dmitry Torokhov, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
On Monday 14 January 2008 17:41:07 Matthew Garrett wrote:
> On Thu, Jan 03, 2008 at 11:58:02AM -0500, Dmitry Torokhov wrote:
>
> > There is no sysfs device to attach the input device to, by any chance?
>
> Not as far as I can tell, but I may be misinterpreting this. Carlos?
Correct. What, if anything, should/ could WMI export here?
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-14 18:27 ` Carlos Corbacho
@ 2008-01-14 22:25 ` Carlos Corbacho
2008-01-15 20:54 ` Dmitry Torokhov
0 siblings, 1 reply; 23+ messages in thread
From: Carlos Corbacho @ 2008-01-14 22:25 UTC (permalink / raw)
To: Matthew Garrett
Cc: Dmitry Torokhov, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
On Monday 14 January 2008 18:27:33 Carlos Corbacho wrote:
> On Monday 14 January 2008 17:41:07 Matthew Garrett wrote:
> > On Thu, Jan 03, 2008 at 11:58:02AM -0500, Dmitry Torokhov wrote:
> > > There is no sysfs device to attach the input device to, by any chance?
> >
> > Not as far as I can tell, but I may be misinterpreting this. Carlos?
What would be better - to have WMI itself as the parent device, or should I
add something like this to WMI:
wmi_get_dev(const char *guid, struct device *dev)
to return the virtual device associated with a GUID, and then allow that to be
used as the parent? (trade off is that this driver would then become
dependent on the WMI sysfs patch going in as well, rather than just on the
in-kernel WMI support).
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-14 22:25 ` Carlos Corbacho
@ 2008-01-15 20:54 ` Dmitry Torokhov
2008-01-16 1:38 ` Carlos Corbacho
0 siblings, 1 reply; 23+ messages in thread
From: Dmitry Torokhov @ 2008-01-15 20:54 UTC (permalink / raw)
To: Carlos Corbacho
Cc: Matthew Garrett, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
On Mon, Jan 14, 2008 at 10:25:10PM +0000, Carlos Corbacho wrote:
> On Monday 14 January 2008 18:27:33 Carlos Corbacho wrote:
> > On Monday 14 January 2008 17:41:07 Matthew Garrett wrote:
> > > On Thu, Jan 03, 2008 at 11:58:02AM -0500, Dmitry Torokhov wrote:
> > > > There is no sysfs device to attach the input device to, by any chance?
> > >
> > > Not as far as I can tell, but I may be misinterpreting this. Carlos?
>
> What would be better - to have WMI itself as the parent device, or should I
> add something like this to WMI:
>
> wmi_get_dev(const char *guid, struct device *dev)
>
> to return the virtual device associated with a GUID, and then allow that to be
> used as the parent? (trade off is that this driver would then become
> dependent on the WMI sysfs patch going in as well, rather than just on the
> in-kernel WMI support).
>
If there is a [planned] WMI sysfs device then I think input device should
use it to form proper sysfs hierarchy. What are the roadblocks for getting
WMI sysfs in?
--
Dmitry
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-15 20:54 ` Dmitry Torokhov
@ 2008-01-16 1:38 ` Carlos Corbacho
2008-01-16 1:49 ` Matthew Garrett
2008-02-07 3:54 ` Len Brown
0 siblings, 2 replies; 23+ messages in thread
From: Carlos Corbacho @ 2008-01-16 1:38 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Matthew Garrett, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
On Tuesday 15 January 2008 20:54:29 Dmitry Torokhov wrote:
> If there is a [planned] WMI sysfs device then I think input device should
> use it to form proper sysfs hierarchy. What are the roadblocks for getting
> WMI sysfs in?
The question is, would exporting the GUID virtual device and using that as the
parent make more sense? So, in the case of hp-wmi, it only uses the GUID
95F24279-4D7B-4334-9387-ACCDC67EF61C, so should we use the virtual device
associated with that GUID as the parent, or just have WMI itself as the
parent device? I'm really not sure which would be the better way to go.
Unfortunately, as you can see, a GUID is a 36 character string, which is
longer than the current 20 byte length of bus_id in a 'struct device'; so
either we have to have a temporary hack to shorten the GUID length when
creating the device, or wait for bus_id to switch to a variable length
(although it's not certain if this change will hit 2.6.25, or will be put off
until 2.6.26).
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-16 1:38 ` Carlos Corbacho
@ 2008-01-16 1:49 ` Matthew Garrett
2008-02-07 3:54 ` Len Brown
1 sibling, 0 replies; 23+ messages in thread
From: Matthew Garrett @ 2008-01-16 1:49 UTC (permalink / raw)
To: Carlos Corbacho
Cc: Dmitry Torokhov, linux-acpi, Len Brown, Alexey Starikovskiy,
linux-input
On Wed, Jan 16, 2008 at 01:38:35AM +0000, Carlos Corbacho wrote:
> The question is, would exporting the GUID virtual device and using that as the
> parent make more sense? So, in the case of hp-wmi, it only uses the GUID
> 95F24279-4D7B-4334-9387-ACCDC67EF61C, so should we use the virtual device
> associated with that GUID as the parent, or just have WMI itself as the
> parent device? I'm really not sure which would be the better way to go.
It's possible for a driver to bind itself to multiple GUIDs, and I guess
it's /potentially/ possible for a machine to have multiple event GUIDs.
I'm not sure whether those should be created as separate input devices,
or whether it would make sense for a driver to bind them into a single
input driver. If the latter, wmi probably ought to be the parent?
--
Matthew Garrett | mjg59@srcf.ucam.org
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-01-16 1:38 ` Carlos Corbacho
2008-01-16 1:49 ` Matthew Garrett
@ 2008-02-07 3:54 ` Len Brown
2008-02-08 15:26 ` Carlos Corbacho
1 sibling, 1 reply; 23+ messages in thread
From: Len Brown @ 2008-02-07 3:54 UTC (permalink / raw)
To: Carlos Corbacho
Cc: Dmitry Torokhov, Matthew Garrett, linux-acpi, Alexey Starikovskiy,
linux-input
On Tuesday 15 January 2008 20:38, Carlos Corbacho wrote:
> On Tuesday 15 January 2008 20:54:29 Dmitry Torokhov wrote:
> > If there is a [planned] WMI sysfs device then I think input device should
> > use it to form proper sysfs hierarchy. What are the roadblocks for getting
> > WMI sysfs in?
>
> The question is, would exporting the GUID virtual device and using that as the
> parent make more sense? So, in the case of hp-wmi, it only uses the GUID
> 95F24279-4D7B-4334-9387-ACCDC67EF61C, so should we use the virtual device
> associated with that GUID as the parent, or just have WMI itself as the
> parent device? I'm really not sure which would be the better way to go.
Why not used generic names like dev0, dev1 etc.
and have the guid be an attribute?
> Unfortunately, as you can see, a GUID is a 36 character string, which is
> longer than the current 20 byte length of bus_id in a 'struct device'; so
> either we have to have a temporary hack to shorten the GUID length when
> creating the device, or wait for bus_id to switch to a variable length
> (although it's not certain if this change will hit 2.6.25, or will be put off
> until 2.6.26).
>
> -Carlos
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH, RFC] HP WMI hotkey driver, RFKill query?
2008-02-07 3:54 ` Len Brown
@ 2008-02-08 15:26 ` Carlos Corbacho
0 siblings, 0 replies; 23+ messages in thread
From: Carlos Corbacho @ 2008-02-08 15:26 UTC (permalink / raw)
To: Len Brown
Cc: Dmitry Torokhov, Matthew Garrett, linux-acpi, Alexey Starikovskiy,
linux-input
On Thursday 07 February 2008 03:54:24 Len Brown wrote:
> > The question is, would exporting the GUID virtual device and using that
> > as the parent make more sense? So, in the case of hp-wmi, it only uses
> > the GUID 95F24279-4D7B-4334-9387-ACCDC67EF61C, so should we use the
> > virtual device associated with that GUID as the parent, or just have WMI
> > itself as the parent device? I'm really not sure which would be the
> > better way to go.
>
> Why not used generic names like dev0, dev1 etc.
> and have the guid be an attribute?
Well, we'll have the virtual devices for the GUIDs anyway once I re-spin the
relevant patch, so would it not be better to just reuse those, rather than
start adding new virtual devices?
-Carlos
--
E-Mail: carlos@strangeworlds.co.uk
Web: strangeworlds.co.uk
GPG Key ID: 0x23EE722D
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2008-02-08 15:26 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-12-18 23:51 [PATCH 0/5] WMI Carlos Corbacho
2007-12-18 23:51 ` [PATCH 1/4] ACPI: WMI: Add ACPI-WMI mapping driver Carlos Corbacho
2007-12-26 21:17 ` Matthew Garrett
2007-12-27 1:09 ` Carlos Corbacho
2007-12-27 2:21 ` Matthew Garrett
2007-12-18 23:51 ` [PATCH 2/4] acer-wmi: Add driver for newer Acer laptops Carlos Corbacho
2007-12-19 1:58 ` Carlos Corbacho
2007-12-19 7:45 ` Matthew Garrett
2007-12-18 23:51 ` [PATCH 3/4] [RFC] tc1100-wmi: Add driver for HP Compaq TC1100 Tablets Carlos Corbacho
2007-12-18 23:52 ` [PATCH 4/4] [RFC] ACPI: WMI: Add sysfs userspace interface Carlos Corbacho
2007-12-26 19:38 ` Matthew Garrett
2007-12-27 0:54 ` Carlos Corbacho
2007-12-27 1:05 ` Carlos Corbacho
2007-12-26 22:48 ` [PATCH, RFC] HP WMI hotkey driver Matthew Garrett
2008-01-03 16:58 ` Dmitry Torokhov
2008-01-14 17:41 ` [PATCH, RFC] HP WMI hotkey driver, RFKill query? Matthew Garrett
2008-01-14 18:27 ` Carlos Corbacho
2008-01-14 22:25 ` Carlos Corbacho
2008-01-15 20:54 ` Dmitry Torokhov
2008-01-16 1:38 ` Carlos Corbacho
2008-01-16 1:49 ` Matthew Garrett
2008-02-07 3:54 ` Len Brown
2008-02-08 15:26 ` 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).