Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 21 Jun 2009 18:52:51 +0200
From:      Michael <freebsdusb@bindone.de>
To:        Rui Paulo <rpaulo@freebsd.org>
Cc:        freebsd-acpi@freebsd.org
Subject:   Re: Two new acpi modules, acpi_wmi and acpi_hp
Message-ID:  <4A3E6563.7040400@bindone.de>
In-Reply-To: <C0DCC4DA-5E4C-46B2-9B84-4EBBC5920B3D@freebsd.org>
References:  <4A3E1784.2050406@bindone.de> <5AFA3C27-D3DB-48DA-9AF3-9E14D11EEC61@freebsd.org> <C0DCC4DA-5E4C-46B2-9B84-4EBBC5920B3D@freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format.
--------------000302080806090206000000
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

Rui Paulo wrote:
> On 21 Jun 2009, at 15:08, Rui Paulo wrote:
> 
>> On 21 Jun 2009, at 12:20, Michael wrote:
>>
>>> Hello,
>>>
>>> I wrote two new acpi modules last year and finally found the time to fix
>>> them, add some missing features and write man pages.
>>
>> Michael, this is great work! I'll review and see if we can ship it
>> with 8.0!
> 
> Michael, this looks good, but there are two style nits that we need to fix.
> * we should try not going over the 80 column mark
> * add a empty new line before every return
> 
> If you could send me a new patch for 8.0 with these fixes, I'll commit
> it (unless someone else has objections).
> 
> Regards,
> -- 
> Rui Paulo
> 

Hi Rui,

I figured that adding newlines in conditional returns makes no sense
(see my last post), so I did reformat everything to be (close to) 80
columns a line add added newlines in front of returns at the end of a
function.

Please find attached the complete patch again (against clean
7.2-RELEASE) and the files that changed (acpi_hp.c and acpi_wmi.c).

Btw, what is the normal adoption path for 7-STABLE (I would assume first
it gets into HEAD and if there's interest there will be a backport to 7?
It should at least work in there, even so for HP laptops my guess is
that most users will use 8 anyway because of wireless support).

cheers
Michael

--------------000302080806090206000000
Content-Type: text/plain;
 name="acpi_wmi_and_acpi_hp_v2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="acpi_wmi_and_acpi_hp_v2.patch"

--- sys/i386/conf/NOTES~	2008-12-29 04:48:16.000000000 +0100
+++ sys/i386/conf/NOTES	2009-06-21 12:10:16.000000000 +0100
@@ -471,6 +471,9 @@
 options 	ACPI_DEBUG
 #!options 	ACPI_NO_SEMAPHORES
 
+# ACPI WMI Mapping driver
+device          acpi_wmi
+
 # ACPI Asus Desktop Extras. (voltage, temp, fan)
 device		acpi_aiboost
 
@@ -480,6 +483,9 @@
 # ACPI Fujitsu Extras (Buttons)
 device		acpi_fujitsu
 
+# ACPI extras driver for HP laptops
+device         acpi_hp
+
 # ACPI extras driver for IBM laptops
 device         acpi_ibm
 
--- sys/modules/acpi/Makefile~	2008-12-29 04:41:59.000000000 +0100
+++ sys/modules/acpi/Makefile	2009-06-21 12:10:59.000000000 +0100
@@ -4,7 +4,7 @@
 SUBDIR=		acpi
 .endif
 
-SUBDIR+=	acpi_aiboost acpi_asus acpi_fujitsu acpi_ibm		\
+SUBDIR+=	acpi_wmi acpi_aiboost acpi_asus acpi_fujitsu acpi_hp acpi_ibm		\
 		acpi_panasonic acpi_sony acpi_toshiba acpi_video	\
 		acpi_dock
 
--- share/man/man4/man4.i386/acpi_hp.4~	2009-06-21 12:09:58.000000000 +0200
+++ share/man/man4/man4.i386/acpi_hp.4	2009-06-21 12:10:21.229934511 +0200
@@ -0,0 +1,273 @@
+.\" Copyright (c) 2009 Michael Gmelin
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 21, 2009
+.Dt ACPI_HP 4 i386
+.Os
+.Sh NAME
+.Nm acpi_hp
+.Nd "ACPI extras driver for HP laptops"
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device acpi_hp"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+acpi_hp_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for ACPI-controlled features found on HP laptops
+that use a WMI enabled BIOS (e.g. HP Compaq 8510p and 6510p).
+.Pp
+The main purpose of this driver is to provide an interface,
+accessible via
+.Xr sysctl 8 ,
+.Xr devd 8 and
+.Xr devfs 8 ,
+through which applications can determine and change the status of
+various laptop components and BIOS settings.
+.Pp
+.Ss Xr devd 8 Ss Events
+Devd events received by
+.Xr devd 8
+provide the following information:
+.Pp
+.Bl -tag -width "subsystem" -offset indent -compact
+.It system
+.Qq Li ACPI
+.It subsystem
+.Qq Li HP
+.It type
+The source of the event in the ACPI namespace.
+The value depends on the model.
+.It notify
+Event code (see below).
+.El
+.Pp
+Event codes:
+.Pp
+.Bl -tag -width "0xc0" -offset indent -compact
+.It Li 0xc0
+WLAN on air status changed to 0 (not on air)
+.It Li 0xc1
+WLAN on air status changed to 1 (on air)
+.It Li 0xd0
+Bluetooth on air status changed to 0 (not on air)
+.It Li 0xd1
+Bluetooth on air status changed to 1 (on air)
+.It Li 0xe0
+WWAN on air status changed to 0 (not on air)
+.It Li 0xe1
+WWAN on air status changed to 1 (on air)
+.El
+.Ss Xr devfs 8 Ss Device
+You can read /dev/hpcmi to see your current BIOS settings. The detail level
+can be adjusted by setting the sysctl 
+.Va cmi_detail
+as described below.
+.Sh SYSCTL VARIABLES
+The following sysctls are currently implemented:
+.Ss WLAN:
+.Bl -tag -width indent
+.It Va dev.acpi_hp.0.wlan_enabled
+Toggle WLAN chip activity. 
+.It Va dev.acpi_hp.0.wlan_radio
+(read-only)
+WLAN radio status (controlled by hardware switch)
+.It Va dev.acpi_hp.0.wlan_on_air
+(read-only)
+WLAN on air (chip enabled, hardware switch enabled + enabled in BIOS)
+.It Va dev.acpi_hp.0.wlan_enabled_if_radio_on
+If set to 1, the WLAN chip will be enabled if the radio is turned on
+.It Va dev.acpi_hp.0.wlan_disable_if_radio_off
+If set to 1, the WLAN chip will be disabled if the radio is turned off
+.El
+.Ss Bluetooth:
+.Bl -tag -width indent
+.It Va dev.acpi_hp.0.bt_enabled
+Toggle Bluetooth chip activity. 
+.It Va dev.acpi_hp.0.bt_radio
+(read-only)
+Bluetooth radio status (controlled by hardware switch)
+.It Va dev.acpi_hp.0.bt_on_air
+(read-only)
+Bluetooth on air (chip enabled, hardware switch enabled + enabled in BIOS)
+.It Va dev.acpi_hp.0.bt_enabled_if_radio_on
+If set to 1, the Bluetooth chip will be enabled if the radio is turned on
+.It Va dev.acpi_hp.0.bt_disable_if_radio_off
+If set to 1, the Bluetooth chip will be disabled if the radio is turned off
+.El
+.Ss WWAN:
+.Bl -tag -width indent
+.It Va dev.acpi_hp.0.wwan_enabled
+Toggle WWAN chip activity. 
+.It Va dev.acpi_hp.0.wwan_radio
+(read-only)
+WWAN radio status (controlled by hardware switch)
+.It Va dev.acpi_hp.0.wwan_on_air
+(read-only)
+WWAN on air (chip enabled, hardware switch enabled + enabled in BIOS)
+.It Va dev.acpi_hp.0.wwan_enabled_if_radio_on
+If set to 1, the WWAN chip will be enabled if the radio is turned on
+.It Va dev.acpi_hp.0.wwan_disable_if_radio_off
+If set to 1, the WWAN chip will be disabled if the radio is turned off
+.El
+.Ss Misc:
+.Bl -tag -width indent
+.It Va dev.acpi_hp.0.als_enabled
+Toggle ambient light sensor (ALS)
+.It Va dev.acpi_hp.0.display
+(read-only)
+Display status (bitmask)
+.It Va dev.acpi_hp.0.hdd_temperature
+(read-only)
+HDD temperature
+.It Va dev.acpi_hp.0.is_docked
+(read-only)
+Docking station status (1 if docked)
+.It Va dev.acpi_hp.0.cmi_detail
+Bitmask to control detail level in /dev/hpcmi output (values can be ORed).
+.Bl -tag -width "0x01" -offset indent -compact
+.It Li 0x01
+Show path component of BIOS setting
+.It Li 0x02
+Show a list of valid options for the BIOS setting
+.It Li 0x04
+Show additional flags of BIOS setting (ReadOnly etc.)
+.El
+.El
+.Pp
+Defaults for these sysctls can be set in
+.Xr sysctl.conf 5 .
+.Sh FILES
+.Bl -tag -width ".Pa /dev/hpcmi"
+.It Pa /dev/hpcmi
+Interface to read BIOS settings
+.El
+.Sh EXAMPLES
+The following can be added to
+.Xr devd.conf 5
+in order disable the LAN interface when WLAN on air and reenable if it's
+not:
+.Bd -literal -offset indent
+notify 0 {
+	match "system"          "ACPI";
+	match "subsystem"       "HP";
+	match "notify"          "0xc0";
+	action                  "ifconfig em0 up";
+};
+
+notify 0 {
+	match "system"          "ACPI";
+	match "subsystem"       "HP";
+	match "notify"          "0xc1";
+	action                  "ifconfig em0 down";
+};
+.Ed
+.Pp
+Enable the ambient light sensor:
+.Bd -literal -offset indent
+sysctl dev.acpi_hp.0.als_enabled=1
+.Ed
+.Pp
+Enable Bluetooth:
+.Bd -literal -offset indent
+sysctl dev.acpi_hp.0.bt_enabled=1
+.Ed
+.Pp
+Get BIOS settings:
+.Bd -literal -offset indent
+cat /dev/hpcmi
+
+Serial Port                                Disable              
+Infrared Port                              Enable               
+Parallel Port                              Disable              
+Flash Media Reader                         Disable              
+USB Ports including Express Card slot      Enable               
+1394 Port                                  Enable               
+Cardbus Slot                               Disable              
+Express Card Slot                          Disable   
+(...)
+.Ed
+.Pp
+Set maximum detail level for /dev/hpcmi output:
+.Bd -literal -offset indent
+sysctl dev.acpi_hp.0.cmi_detail=7
+.Ed
+.Pp
+
+
+
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr acpi_wmi 4 ,
+.Xr sysctl.conf 5 ,
+.Xr devd 8 ,
+.Xr devfs 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Fx CURRENT .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Michael Gmelin Aq freebsd@grem.de
+.Pp
+It has been inspired by hp-wmi driver, which implements a subset of these   
+features (hotkeys) on Linux.
+.Pp
+.Bl -tag -width indent
+.It HP CMI whitepaper:
+http://h20331.www2.hp.com/Hpsub/downloads/cmi_whitepaper.pdf
+.It wmi-hp for Linux:
+http://www.kernel.org
+.It WMI and ACPI:
+http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
+.El
+.Pp
+This manual page was written by
+.An Michael Gmelin Aq freebsd@grem.de
+.Sh BUGS
+This driver is experimental and has only been tested on CURRENT i386 on an
+HP Compaq 8510p which featured all supported wireless devices (WWAN/BT/WLAN).
+Expect undefined results when operating on different hardware.
+.Pp
+Loading the driver is slow. Reading from /dev/hpcmi is even slower.
+.Pp
+Additional features like HP specific sensor readings or writing BIOS
+settings are not supported.
--- sys/dev/acpi_support/acpi_hp.c~	2009-06-21 18:50:08.924078040 +0200
+++ sys/dev/acpi_support/acpi_hp.c	2009-06-21 18:50:54.166723000 +0200
@@ -0,0 +1,1182 @@
+/*-
+ * Copyright (c) 2009 Michael Gmelin <freebsd@grem.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Driver for extra ACPI-controlled features found on HP laptops
+ * that use a WMI enabled BIOS (e.g. HP Compaq 8510p and 6510p).
+ * Allows to control and read status of integrated hardware and read
+ * BIOS settings through CMI.
+ * Inspired by the hp-wmi driver, which implements a subset of these
+ * features (hotkeys) on Linux.
+ *
+ * HP CMI whitepaper:
+ *     http://h20331.www2.hp.com/Hpsub/downloads/cmi_whitepaper.pdf
+ * wmi-hp for Linux:
+ *     http://www.kernel.org
+ * WMI and ACPI:
+ *     http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
+ */
+
+#include "opt_acpi.h"
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/proc.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/sbuf.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <contrib/dev/acpica/acpi.h>
+#include <dev/acpica/acpivar.h>
+#include "acpi_wmi_if.h"
+
+#define _COMPONENT	ACPI_OEM
+ACPI_MODULE_NAME("HP")
+
+#define ACPI_HP_WMI_EVENT_GUID		"95F24279-4D7B-4334-9387-ACCDC67EF61C"
+#define ACPI_HP_WMI_BIOS_GUID		"5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
+#define ACPI_HP_WMI_CMI_GUID		"2D114B49-2DFB-4130-B8FE-4A3C09E75133"
+
+#define ACPI_HP_WMI_DISPLAY_COMMAND	0x1
+#define ACPI_HP_WMI_HDDTEMP_COMMAND	0x2
+#define ACPI_HP_WMI_ALS_COMMAND		0x3
+#define ACPI_HP_WMI_DOCK_COMMAND	0x4
+#define ACPI_HP_WMI_WIRELESS_COMMAND	0x5
+
+#define ACPI_HP_METHOD_WLAN_ENABLED			1
+#define ACPI_HP_METHOD_WLAN_RADIO			2
+#define ACPI_HP_METHOD_WLAN_ON_AIR			3
+#define ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON		4
+#define ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF	5
+#define ACPI_HP_METHOD_BLUETOOTH_ENABLED		6
+#define ACPI_HP_METHOD_BLUETOOTH_RADIO			7
+#define ACPI_HP_METHOD_BLUETOOTH_ON_AIR			8
+#define ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON	9
+#define ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF	10
+#define ACPI_HP_METHOD_WWAN_ENABLED			11
+#define ACPI_HP_METHOD_WWAN_RADIO			12
+#define ACPI_HP_METHOD_WWAN_ON_AIR			13
+#define ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON		14
+#define ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF	15
+#define ACPI_HP_METHOD_ALS				16
+#define ACPI_HP_METHOD_DISPLAY				17
+#define ACPI_HP_METHOD_HDDTEMP				18
+#define ACPI_HP_METHOD_DOCK				19
+#define ACPI_HP_METHOD_CMI_DETAIL			20
+
+#define HP_MASK_WWAN_ON_AIR			0x1000000
+#define HP_MASK_BLUETOOTH_ON_AIR		0x10000
+#define HP_MASK_WLAN_ON_AIR			0x100
+#define HP_MASK_WWAN_RADIO			0x8000000
+#define HP_MASK_BLUETOOTH_RADIO			0x80000
+#define HP_MASK_WLAN_RADIO			0x800
+#define HP_MASK_WWAN_ENABLED			0x2000000
+#define HP_MASK_BLUETOOTH_ENABLED		0x20000
+#define HP_MASK_WLAN_ENABLED			0x200
+
+#define ACPI_HP_CMI_DETAIL_PATHS		0x01
+#define ACPI_HP_CMI_DETAIL_ENUMS		0x02
+#define ACPI_HP_CMI_DETAIL_FLAGS		0x04
+
+struct acpi_hp_inst_seq_pair {
+	UINT32	sequence;	/* sequence number as suggested by cmi bios */
+	UINT8	instance;	/* object instance on guid */
+};
+
+struct acpi_hp_softc {
+	device_t	dev;
+	ACPI_HANDLE	handle;
+	device_t	wmi_dev;
+	int		has_notify;		/* notification GUID found */
+	int		has_cmi;		/* CMI GUID found */
+	int		cmi_detail;		/* CMI detail level
+						   (set by sysctl) */
+	int		wlan_enable_if_radio_on;	/* set by sysctl */
+	int		wlan_disable_if_radio_off;	/* set by sysctl */
+	int		bluetooth_enable_if_radio_on;	/* set by sysctl */
+	int		bluetooth_disable_if_radio_off;	/* set by sysctl */
+	int		wwan_enable_if_radio_on;	/* set by sysctl */
+	int		wwan_disable_if_radio_off;	/* set by sysctl */
+	int		was_wlan_on_air;		/* last known WLAN
+							   on air status */
+	int		was_bluetooth_on_air;		/* last known BT
+							   on air status */
+	int		was_wwan_on_air;		/* last known WWAN
+							   on air status */
+	struct sysctl_ctx_list	*sysctl_ctx;
+	struct sysctl_oid	*sysctl_tree;
+	struct cdev	*hpcmi_dev_t;		/* hpcmi device handle */
+	struct sbuf	hpcmi_sbuf;		/* /dev/hpcmi output sbuf */
+	pid_t		hpcmi_open_pid;		/* pid operating on
+						   /dev/hpcmi */
+	int		hpcmi_bufptr;		/* current pointer position
+						   in /dev/hpcmi output buffer
+						 */
+	int		cmi_order_size;		/* size of cmi_order list */
+	struct acpi_hp_inst_seq_pair cmi_order[128];	/* list of CMI
+			     instances ordered by BIOS suggested sequence */
+};
+
+static struct {
+	char	*name;
+	int	method;
+	char	*description;
+	int	access;
+} acpi_hp_sysctls[] = {
+	{
+		.name		= "wlan_enabled",
+		.method		= ACPI_HP_METHOD_WLAN_ENABLED,
+		.description	= "Enable/Disable WLAN (WiFi)",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "wlan_radio",
+		.method		= ACPI_HP_METHOD_WLAN_RADIO,
+		.description	= "WLAN radio status",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "wlan_on_air",
+		.method		= ACPI_HP_METHOD_WLAN_ON_AIR,
+		.description	= "WLAN radio ready to use (enabled and radio)",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "wlan_enable_if_radio_on",
+		.method		= ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON,
+		.description	= "Enable WLAN if radio is turned on",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "wlan_disable_if_radio_off",
+		.method		= ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF,
+		.description	= "Disable WLAN if radio is turned off",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "bt_enabled",
+		.method		= ACPI_HP_METHOD_BLUETOOTH_ENABLED,
+		.description	= "Enable/Disable Bluetooth",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "bt_radio",
+		.method		= ACPI_HP_METHOD_BLUETOOTH_RADIO,
+		.description	= "Bluetooth radio status",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "bt_on_air",
+		.method		= ACPI_HP_METHOD_BLUETOOTH_ON_AIR,
+		.description	= "Bluetooth radio ready to use"
+				    " (enabled and radio)",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "bt_enable_if_radio_on",
+		.method		= ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON,
+		.description	= "Enable bluetooth if radio is turned on",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "bt_disable_if_radio_off",
+		.method		= ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF,
+		.description	= "Disable bluetooth if radio is turned off",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "wwan_enabled",
+		.method		= ACPI_HP_METHOD_WWAN_ENABLED,
+		.description	= "Enable/Disable WWAN (UMTS)",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "wwan_radio",
+		.method		= ACPI_HP_METHOD_WWAN_RADIO,
+		.description	= "WWAN radio status",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "wwan_on_air",
+		.method		= ACPI_HP_METHOD_WWAN_ON_AIR,
+		.description	= "WWAN radio ready to use (enabled and radio)",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "wwan_enable_if_radio_on",
+		.method		= ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON,
+		.description	= "Enable WWAN if radio is turned on",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "wwan_disable_if_radio_off",
+		.method		= ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF,
+		.description	= "Disable WWAN if radio is turned off",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "als_enabled",
+		.method		= ACPI_HP_METHOD_ALS,
+		.description	= "Enable/Disable ALS (Ambient light sensor)",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+	{
+		.name		= "display",
+		.method		= ACPI_HP_METHOD_DISPLAY,
+		.description	= "Display status",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "hdd_temperature",
+		.method		= ACPI_HP_METHOD_HDDTEMP,
+		.description	= "HDD temperature",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "is_docked",
+		.method		= ACPI_HP_METHOD_DOCK,
+		.description	= "Docking station status",
+		.access		= CTLTYPE_INT | CTLFLAG_RD
+	},
+	{
+		.name		= "cmi_detail",
+		.method		= ACPI_HP_METHOD_CMI_DETAIL,
+		.description	= "Details shown in CMI output "
+				    "(cat /dev/hpcmi)",
+		.access		= CTLTYPE_INT | CTLFLAG_RW
+	},
+
+	{ NULL, 0, NULL, 0 }
+};
+
+ACPI_SERIAL_DECL(hp, "HP ACPI-WMI Mapping");
+
+static int	acpi_hp_probe(device_t dev);
+static int	acpi_hp_attach(device_t dev);
+static int	acpi_hp_detach(device_t dev);
+
+static void	acpi_hp_evaluate_auto_on_off(struct acpi_hp_softc* sc);
+static int	acpi_hp_sysctl(SYSCTL_HANDLER_ARGS);
+static int	acpi_hp_sysctl_set(struct acpi_hp_softc *sc, int method,
+		    int arg, int oldarg);
+static int	acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method);
+static int	acpi_hp_exec_wmi_command(device_t wmi_dev, int command,
+		    int is_write, int val);
+static void	acpi_hp_notify(ACPI_HANDLE h, UINT32 notify, void *context);
+static int	acpi_hp_get_cmi_block(device_t wmi_dev, const char* guid,
+		    UINT8 instance, char* outbuf, size_t outsize,
+		    UINT32* sequence, int detail);
+static void	acpi_hp_hex_decode(char* buffer);
+
+static d_open_t	acpi_hp_hpcmi_open;
+static d_close_t acpi_hp_hpcmi_close;
+static d_read_t	acpi_hp_hpcmi_read;
+
+/* handler /dev/hpcmi device */
+static struct cdevsw hpcmi_cdevsw = {
+	.d_version = D_VERSION,
+	.d_open = acpi_hp_hpcmi_open,
+	.d_close = acpi_hp_hpcmi_close,
+	.d_read = acpi_hp_hpcmi_read,
+	.d_name = "hpcmi",
+};
+
+static device_method_t acpi_hp_methods[] = {
+	DEVMETHOD(device_probe, acpi_hp_probe),
+	DEVMETHOD(device_attach, acpi_hp_attach),
+	DEVMETHOD(device_detach, acpi_hp_detach),
+	{0, 0}
+};
+
+static driver_t	acpi_hp_driver = {
+	"acpi_hp",
+	acpi_hp_methods,
+	sizeof(struct acpi_hp_softc),
+};
+
+static devclass_t acpi_hp_devclass;
+
+DRIVER_MODULE(acpi_hp, acpi, acpi_hp_driver, acpi_hp_devclass,
+		0, 0);
+MODULE_DEPEND(acpi_hp, acpi_wmi, 1, 1, 1);
+MODULE_DEPEND(acpi_hp, acpi, 1, 1, 1);
+
+static void	
+acpi_hp_evaluate_auto_on_off(struct acpi_hp_softc *sc)
+{
+	int wireless;
+	int new_wlan_status;
+	int new_bluetooth_status;
+	int new_wwan_status;
+
+	wireless = acpi_hp_exec_wmi_command(sc->wmi_dev,
+		    ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+	new_wlan_status = -1;
+	new_bluetooth_status = -1;
+	new_wwan_status = -1;
+
+	device_printf(sc->wmi_dev, "Wireless status is %x\n", wireless);
+	if (sc->wlan_disable_if_radio_off && !(wireless & HP_MASK_WLAN_RADIO)
+	    &&  (wireless & HP_MASK_WLAN_ENABLED)) {
+		acpi_hp_exec_wmi_command(sc->wmi_dev,
+		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x100);
+		new_wlan_status = 0;
+	}
+	else if (sc->wlan_enable_if_radio_on && (wireless & HP_MASK_WLAN_RADIO)
+		&&  !(wireless & HP_MASK_WLAN_ENABLED)) {
+		acpi_hp_exec_wmi_command(sc->wmi_dev,
+		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x101);
+		new_wlan_status = 1;
+	}
+	if (sc->bluetooth_disable_if_radio_off &&
+	    !(wireless & HP_MASK_BLUETOOTH_RADIO) &&
+	    (wireless & HP_MASK_BLUETOOTH_ENABLED)) {
+		acpi_hp_exec_wmi_command(sc->wmi_dev,
+		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x200);
+		new_bluetooth_status = 0;
+	}
+	else if (sc->bluetooth_enable_if_radio_on &&
+		(wireless & HP_MASK_BLUETOOTH_RADIO) &&
+		!(wireless & HP_MASK_BLUETOOTH_ENABLED)) {
+		acpi_hp_exec_wmi_command(sc->wmi_dev,
+		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x202);
+		new_bluetooth_status = 1;
+	}
+	if (sc->wwan_disable_if_radio_off &&
+	    !(wireless & HP_MASK_WWAN_RADIO) &&
+	    (wireless & HP_MASK_WWAN_ENABLED)) {
+		acpi_hp_exec_wmi_command(sc->wmi_dev,
+		ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x400);
+		new_wwan_status = 0;
+	}
+	else if (sc->wwan_enable_if_radio_on &&
+		(wireless & HP_MASK_WWAN_RADIO) &&
+		!(wireless & HP_MASK_WWAN_ENABLED)) {
+		acpi_hp_exec_wmi_command(sc->wmi_dev,
+		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x404);
+		new_wwan_status = 1;
+	}
+
+	if (new_wlan_status == -1) {
+		new_wlan_status = (wireless & HP_MASK_WLAN_ON_AIR);
+		if ((new_wlan_status?1:0) != sc->was_wlan_on_air) {
+			sc->was_wlan_on_air = sc->was_wlan_on_air?0:1;
+			device_printf(sc->wmi_dev,
+			    "WLAN on air changed to %i "
+			    "(new_wlan_status is %i)\n",
+			    sc->was_wlan_on_air, new_wlan_status);
+			acpi_UserNotify("HP", sc->handle,
+			    0xc0+sc->was_wlan_on_air);
+		}
+	}
+	if (new_bluetooth_status == -1) {
+		new_bluetooth_status = (wireless & HP_MASK_BLUETOOTH_ON_AIR);
+		if ((new_bluetooth_status?1:0) != sc->was_bluetooth_on_air) {
+			sc->was_bluetooth_on_air = sc->was_bluetooth_on_air?
+			    0:1;
+			device_printf(sc->wmi_dev, "BLUETOOTH on air changed"
+			    " to %i (new_bluetooth_status is %i)\n",
+			    sc->was_bluetooth_on_air, new_bluetooth_status);
+			acpi_UserNotify("HP", sc->handle,
+			    0xd0+sc->was_bluetooth_on_air);
+		}
+	}
+	if (new_wwan_status == -1) {
+		new_wwan_status = (wireless & HP_MASK_WWAN_ON_AIR);
+		if ((new_wwan_status?1:0) != sc->was_wwan_on_air) {
+			sc->was_wwan_on_air = sc->was_wwan_on_air?0:1;
+			device_printf(sc->wmi_dev, "WWAN on air changed to %i"
+			    " (new_wwan_status is %i)\n",
+			    sc->was_wwan_on_air, new_wwan_status);
+			acpi_UserNotify("HP", sc->handle,
+			    0xe0+sc->was_wwan_on_air);
+		}
+	}
+}
+
+static int
+acpi_hp_probe(device_t dev)
+{
+	if (acpi_disabled("hp") || device_get_unit(dev) != 0)
+		return (ENXIO);
+	device_set_desc(dev, "HP ACPI-WMI Mapping");
+
+	return (0);
+}
+
+static int
+acpi_hp_attach(device_t dev)
+{
+	struct acpi_hp_softc	*sc;
+	struct acpi_softc	*acpi_sc;
+	devclass_t		wmi_devclass;
+	int arg;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
+
+	sc = device_get_softc(dev);
+	sc->dev = dev;
+	sc->handle = acpi_get_handle(dev);
+	sc->has_notify = 0;
+	sc->has_cmi = 0;
+	sc->bluetooth_enable_if_radio_on = 0;
+	sc->bluetooth_disable_if_radio_off = 0;
+	sc->wlan_enable_if_radio_on = 0;
+	sc->wlan_disable_if_radio_off = 0;
+	sc->wlan_enable_if_radio_on = 0;
+	sc->wlan_disable_if_radio_off = 0;
+	sc->was_wlan_on_air = 0;
+	sc->was_bluetooth_on_air = 0;
+	sc->was_wwan_on_air = 0;
+	sc->cmi_detail = 0;
+	sc->cmi_order_size = -1;
+	memset(sc->cmi_order, 0, sizeof(sc->cmi_order));
+	acpi_sc = acpi_device_get_parent_softc(dev);
+
+	if (!(wmi_devclass = devclass_find ("acpi_wmi"))) {
+		device_printf(dev, "Couldn't find acpi_wmi devclass\n");
+		return (EINVAL);
+	}
+	if (!(sc->wmi_dev = devclass_get_device(wmi_devclass, 0))) {
+		device_printf(dev, "Couldn't find acpi_wmi device\n");
+		return (EINVAL);
+	}
+	if (!ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev,
+	    ACPI_HP_WMI_BIOS_GUID)) {
+		device_printf(dev,
+		    "WMI device does not provide the HP BIOS GUID\n");
+		return (EINVAL);
+	}
+	if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev,
+	    ACPI_HP_WMI_EVENT_GUID)) {
+		device_printf(dev,
+		    "HP event GUID detected, installing event handler\n");
+		if (ACPI_WMI_INSTALL_EVENT_HANDLER(sc->wmi_dev,
+		    ACPI_HP_WMI_EVENT_GUID, acpi_hp_notify, dev)) {
+			device_printf(dev,
+			    "Could not install notification handler!\n");
+		}
+		else {
+			sc->has_notify = 1;
+		}
+	}
+	if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_HP_WMI_CMI_GUID)) {
+		device_printf(dev, "HP CMI GUID detected\n");
+		sc->has_cmi = 1;
+	}
+
+	if (sc->has_cmi) {
+		sc->hpcmi_dev_t = make_dev(&hpcmi_cdevsw, 0, UID_ROOT,
+			    GID_WHEEL, 0644, "hpcmi");
+		sc->hpcmi_dev_t->si_drv1 = sc;
+		sc->hpcmi_open_pid = 0;
+		sc->hpcmi_bufptr = -1;
+	}
+
+	ACPI_SERIAL_BEGIN(hp);
+
+	sc->sysctl_ctx = device_get_sysctl_ctx(dev);
+	sc->sysctl_tree = device_get_sysctl_tree(dev);
+	for (int i = 0; acpi_hp_sysctls[i].name != NULL; ++i) {
+		arg = 0;
+		if ((!sc->has_notify &&
+		    (acpi_hp_sysctls[i].method ==
+			ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON ||
+		    acpi_hp_sysctls[i].method ==
+			ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF ||
+		    acpi_hp_sysctls[i].method ==
+			ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON ||
+		    acpi_hp_sysctls[i].method ==
+			ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF ||
+		    acpi_hp_sysctls[i].method ==
+			ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON ||
+		    acpi_hp_sysctls[i].method ==
+			ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF)) ||
+		    (arg = acpi_hp_sysctl_get(sc,
+		    acpi_hp_sysctls[i].method)) < 0) {
+			continue;
+		}
+		if (acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WLAN_ON_AIR) {
+			sc->was_wlan_on_air = arg;
+		}
+		else if (acpi_hp_sysctls[i].method ==
+			    ACPI_HP_METHOD_BLUETOOTH_ON_AIR) {
+			sc->was_bluetooth_on_air = arg;
+		}
+		else if (acpi_hp_sysctls[i].method ==
+			    ACPI_HP_METHOD_WWAN_ON_AIR) {
+			sc->was_wwan_on_air = arg;
+		}
+
+		SYSCTL_ADD_PROC(sc->sysctl_ctx,
+		SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
+			acpi_hp_sysctls[i].name, acpi_hp_sysctls[i].access,
+			sc, i, acpi_hp_sysctl, "I",
+			acpi_hp_sysctls[i].description);
+	}
+	ACPI_SERIAL_END(hp);
+
+	return (0);
+}
+
+static int
+acpi_hp_detach(device_t dev)
+{
+	int ret;
+	
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
+	struct acpi_hp_softc *sc = device_get_softc(dev);
+	if (sc->has_cmi && sc->hpcmi_open_pid != 0) {
+		ret = EBUSY;
+	}
+	else {
+		if (sc->has_notify) {
+			ACPI_WMI_REMOVE_EVENT_HANDLER(dev,
+			    ACPI_HP_WMI_EVENT_GUID);
+		}
+		if (sc->hpcmi_bufptr != -1) {
+			sbuf_delete(&sc->hpcmi_sbuf);
+			sc->hpcmi_bufptr = -1;
+		}
+		sc->hpcmi_open_pid = 0;
+		destroy_dev(sc->hpcmi_dev_t);
+		ret = 0;
+	}
+
+	return (ret);
+}
+
+static int
+acpi_hp_sysctl(SYSCTL_HANDLER_ARGS)
+{
+	struct acpi_hp_softc *sc;
+	int	arg;
+	int oldarg;
+	int	error = 0;
+	int	function;
+	int	method;
+	
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = (struct acpi_hp_softc *)oidp->oid_arg1;
+	function = oidp->oid_arg2;
+	method = acpi_hp_sysctls[function].method;
+
+	ACPI_SERIAL_BEGIN(hp);
+	arg = acpi_hp_sysctl_get(sc, method);
+	oldarg = arg;
+	error = sysctl_handle_int(oidp, &arg, 0, req);
+	if (!error && req->newptr != NULL) {
+		error = acpi_hp_sysctl_set(sc, method, arg, oldarg);
+	}
+	ACPI_SERIAL_END(hp);
+
+	return (error);
+}
+
+static int
+acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method)
+{
+	int val = 0;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	ACPI_SERIAL_ASSERT(hp);
+
+	switch (method) {
+	case ACPI_HP_METHOD_WLAN_ENABLED:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_WLAN_ENABLED) != 0);
+		break;
+	case ACPI_HP_METHOD_WLAN_RADIO:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_WLAN_RADIO) != 0);
+		break;
+	case ACPI_HP_METHOD_WLAN_ON_AIR:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_WLAN_ON_AIR) != 0);
+		break;
+	case ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON:
+		val = sc->wlan_enable_if_radio_on;
+		break;
+	case ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF:
+		val = sc->wlan_disable_if_radio_off;
+		break;
+	case ACPI_HP_METHOD_BLUETOOTH_ENABLED:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_BLUETOOTH_ENABLED) != 0);
+		break;
+	case ACPI_HP_METHOD_BLUETOOTH_RADIO:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_BLUETOOTH_RADIO) != 0);
+		break;
+	case ACPI_HP_METHOD_BLUETOOTH_ON_AIR:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_BLUETOOTH_ON_AIR) != 0);
+		break;
+	case ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON:
+		val = sc->bluetooth_enable_if_radio_on;
+		break;
+	case ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF:
+		val = sc->bluetooth_disable_if_radio_off;
+		break;
+	case ACPI_HP_METHOD_WWAN_ENABLED:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_WWAN_ENABLED) != 0);
+		break;
+	case ACPI_HP_METHOD_WWAN_RADIO:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_WWAN_RADIO) != 0);
+		break;
+	case ACPI_HP_METHOD_WWAN_ON_AIR:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
+		val = ((val & HP_MASK_WWAN_ON_AIR) != 0);
+		break;
+	case ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON:
+		val = sc->wwan_enable_if_radio_on;
+		break;
+	case ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF:
+		val = sc->wwan_disable_if_radio_off;
+		break;
+	case ACPI_HP_METHOD_ALS:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_ALS_COMMAND, 0, 0);
+		break;
+	case ACPI_HP_METHOD_DISPLAY:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_DISPLAY_COMMAND, 0, 0);
+		break;
+	case ACPI_HP_METHOD_HDDTEMP:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_HDDTEMP_COMMAND, 0, 0);
+		break;
+	case ACPI_HP_METHOD_DOCK:
+		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
+			ACPI_HP_WMI_DOCK_COMMAND, 0, 0);
+		break;
+	case ACPI_HP_METHOD_CMI_DETAIL:
+		val = sc->cmi_detail;
+		break;
+	}
+
+	return (val);
+}
+
+static int
+acpi_hp_sysctl_set(struct acpi_hp_softc *sc, int method, int arg, int oldarg)
+{
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	ACPI_SERIAL_ASSERT(hp);
+
+	if (method != ACPI_HP_METHOD_CMI_DETAIL)
+		arg = arg?1:0;
+
+	if (arg != oldarg) {
+		switch (method) {
+		case ACPI_HP_METHOD_WLAN_ENABLED:
+			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
+				    ACPI_HP_WMI_WIRELESS_COMMAND, 1,
+				    arg?0x101:0x100));
+		case ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON:
+			sc->wlan_enable_if_radio_on = arg;
+			acpi_hp_evaluate_auto_on_off(sc);
+			break;
+		case ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF:
+			sc->wlan_disable_if_radio_off = arg;
+			acpi_hp_evaluate_auto_on_off(sc);
+			break;
+		case ACPI_HP_METHOD_BLUETOOTH_ENABLED:
+			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
+				    ACPI_HP_WMI_WIRELESS_COMMAND, 1,
+				    arg?0x202:0x200));
+		case ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON:
+			sc->bluetooth_enable_if_radio_on = arg;
+			acpi_hp_evaluate_auto_on_off(sc);
+			break;
+		case ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF:
+			sc->bluetooth_disable_if_radio_off = arg?1:0;
+			acpi_hp_evaluate_auto_on_off(sc);
+			break;
+		case ACPI_HP_METHOD_WWAN_ENABLED:
+			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
+				    ACPI_HP_WMI_WIRELESS_COMMAND, 1,
+				    arg?0x404:0x400));
+		case ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON:
+			sc->wwan_enable_if_radio_on = arg?1:0;
+			acpi_hp_evaluate_auto_on_off(sc);
+			break;
+		case ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF:
+			sc->wwan_disable_if_radio_off = arg?1:0;
+			acpi_hp_evaluate_auto_on_off(sc);
+			break;
+		case ACPI_HP_METHOD_ALS:
+			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
+				    ACPI_HP_WMI_ALS_COMMAND, 1,
+				    arg?1:0));
+		case ACPI_HP_METHOD_CMI_DETAIL:
+			sc->cmi_detail = arg;
+			break;
+		}
+	}
+
+	return (0);
+}
+
+static __inline void
+acpi_hp_free_buffer(ACPI_BUFFER* buf) {
+	if (buf && buf->Pointer) {
+		AcpiOsFree(buf->Pointer);
+	}
+}
+
+static void
+acpi_hp_notify(ACPI_HANDLE h, UINT32 notify, void *context)
+{
+	device_t dev = context;
+	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);
+
+	struct acpi_hp_softc *sc = device_get_softc(dev);
+	ACPI_BUFFER response = { ACPI_ALLOCATE_BUFFER, NULL };
+	ACPI_OBJECT *obj;
+	ACPI_WMI_GET_EVENT_DATA(sc->wmi_dev, notify, &response);
+	obj = (ACPI_OBJECT*) response.Pointer;
+	if (obj && obj->Type == ACPI_TYPE_BUFFER && obj->Buffer.Length == 8) {
+		if (*((UINT8 *) obj->Buffer.Pointer) == 0x5) {
+			acpi_hp_evaluate_auto_on_off(sc);
+		}
+	}
+	acpi_hp_free_buffer(&response);
+}
+
+static int
+acpi_hp_exec_wmi_command(device_t wmi_dev, int command, int is_write, int val)
+{
+	UINT32 params[5] = { 0x55434553,
+			     is_write?2:1,
+			     command,
+			     is_write?4:0,
+			     val};
+	UINT32* result;
+	ACPI_OBJECT *obj;
+	ACPI_BUFFER in = { sizeof(params), &params };
+	ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL };
+	int retval;
+	
+	if (ACPI_FAILURE(ACPI_WMI_EVALUATE_CALL(wmi_dev, ACPI_HP_WMI_BIOS_GUID,
+		    0, 0x3, &in, &out))) {
+		acpi_hp_free_buffer(&out);
+		return (-EINVAL);
+	}
+	obj = out.Pointer;
+	if (!obj || obj->Type != ACPI_TYPE_BUFFER) {
+		acpi_hp_free_buffer(&out);
+		return (-EINVAL);
+	}
+	result = (UINT32*) obj->Buffer.Pointer;
+	retval = result[2];
+	if (result[1] > 0) {
+		retval = result[1];
+	}
+	acpi_hp_free_buffer(&out);
+
+	return (retval);
+}
+
+static __inline char*
+acpi_hp_get_string_from_object(ACPI_OBJECT* obj, char* dst, size_t size) {
+	int length;
+	dst[0] = 0;
+	if (obj->Type == ACPI_TYPE_STRING) {
+		length = obj->String.Length+1;
+		if (length > size) {
+			length = size - 1;
+		}
+		strlcpy(dst, obj->String.Pointer, length);
+		acpi_hp_hex_decode(dst);
+	}
+
+	return (dst);
+}
+
+
+/*
+ * Read BIOS Setting block in instance "instance".
+ * The block returned is ACPI_TYPE_PACKAGE which should contain the following
+ * elements:
+ * Index Meaning
+ * 0     Setting Name [string]
+ * 1     Value (comma separated, asterisk marks the current value) [string]
+ * 2     Path within the bios hierarchy [string]
+ * 3     IsReadOnly [int]
+ * 4     DisplayInUI [int]
+ * 5     RequiresPhysicalPresence [int]
+ * 6     Sequence for ordering within the bios settings (absolute) [int]
+ * 7     Length of prerequisites array [int]
+ * 8     Prerequisite1 [string]
+ * 9     Prerequisite2 [string]
+ * 10    Prerequisite3 [string]
+ * 11    Current value (in case of enum) [string] / Array length [int]
+ * 12    Enum length [int] / Array values
+ * 13ff  Enum value at index x [string]
+ */
+static int
+acpi_hp_get_cmi_block(device_t wmi_dev, const char* guid, UINT8 instance,
+    char* outbuf, size_t outsize, UINT32* sequence, int detail)
+{
+	ACPI_OBJECT *obj;
+	ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL };
+	int i;
+	int outlen;
+	int size = 255;
+	int has_enums = 0;
+	char string_buffer[size];
+	int enumbase;
+
+	outlen = 0;
+	outbuf[0] = 0;	
+	if (ACPI_FAILURE(ACPI_WMI_GET_BLOCK(wmi_dev, guid, instance, &out))) {
+		acpi_hp_free_buffer(&out);
+		return (-EINVAL);
+	}
+	obj = out.Pointer;
+	if (!obj && obj->Type != ACPI_TYPE_PACKAGE) {
+		acpi_hp_free_buffer(&out);
+		return (-EINVAL);
+	}
+
+	/* check if this matches our expectations based on limited knowledge */
+	if (obj->Package.Count >= 13 &&
+	    obj->Package.Elements[0].Type == ACPI_TYPE_STRING &&
+	    obj->Package.Elements[1].Type == ACPI_TYPE_STRING &&
+	    obj->Package.Elements[2].Type == ACPI_TYPE_STRING &&
+	    obj->Package.Elements[3].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Elements[4].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Elements[5].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Elements[6].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Elements[7].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Elements[8].Type == ACPI_TYPE_STRING &&
+	    obj->Package.Elements[9].Type == ACPI_TYPE_STRING &&
+	    obj->Package.Elements[10].Type == ACPI_TYPE_STRING &&
+	    ((obj->Package.Elements[11].Type == ACPI_TYPE_STRING &&
+	    obj->Package.Elements[12].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Count >=
+	    	13+obj->Package.Elements[12].Integer.Value) ||
+	    (obj->Package.Elements[11].Type == ACPI_TYPE_INTEGER &&
+	    obj->Package.Count >=
+	    	12+obj->Package.Elements[11].Integer.Value))
+	    ) {
+		enumbase = obj->Package.Elements[11].Type == ACPI_TYPE_STRING?
+				12:11;
+		if (detail & ACPI_HP_CMI_DETAIL_PATHS) {
+			strlcat(outbuf, acpi_hp_get_string_from_object(
+				&obj->Package.Elements[2], string_buffer, size),
+				outsize);
+			outlen += 48;
+			while (strlen(outbuf) < outlen)
+				strlcat(outbuf, " ", outsize);
+		}
+		strlcat(outbuf, acpi_hp_get_string_from_object(
+				&obj->Package.Elements[0], string_buffer, size),
+				outsize);
+		outlen += 43;
+		while (strlen(outbuf) < outlen)
+			strlcat(outbuf, " ", outsize);
+		if (enumbase == 12)
+			strlcat(outbuf, acpi_hp_get_string_from_object(
+					    &obj->Package.Elements[11],
+					    string_buffer, size),
+					outsize);
+		outlen += 21;
+		while (strlen(outbuf) < outlen)
+			strlcat(outbuf, " ", outsize);
+		for (i = 0; i < strlen(outbuf); ++i)
+			if (outbuf[i] == '\\')
+				outbuf[i] = '/';
+		if (detail & ACPI_HP_CMI_DETAIL_ENUMS) {
+			for (i = enumbase+1; i < enumbase + 1 +
+			    obj->Package.Elements[enumbase].Integer.Value;
+			    ++i) {
+				acpi_hp_get_string_from_object(
+				    &obj->Package.Elements[i], string_buffer,
+				    size);
+				if (strlen(string_buffer) > 1 ||
+				    (strlen(string_buffer) == 1 &&
+				    string_buffer[0] != ' ')) {
+					if (has_enums)
+						strlcat(outbuf, "/", outsize);
+					else
+						strlcat(outbuf, " (", outsize);
+					strlcat(outbuf, string_buffer, outsize);
+					has_enums = 1;
+				}
+			}
+		}
+		if (has_enums)
+			strlcat(outbuf, ")", outsize);
+		if (detail & ACPI_HP_CMI_DETAIL_FLAGS) {
+			strlcat(outbuf, obj->Package.Elements[3].Integer.Value?
+			    " [ReadOnly]":"", outsize);
+			strlcat(outbuf, obj->Package.Elements[4].Integer.Value?
+			    "":" [NOUI]", outsize);
+			strlcat(outbuf, obj->Package.Elements[5].Integer.Value?
+			    " [RPP]":"", outsize);
+		}
+		*sequence = (UINT32) obj->Package.Elements[6].Integer.Value;
+	}
+	acpi_hp_free_buffer(&out);
+
+	return (0);
+}
+
+
+
+/*
+ * Convert given two digit hex string (hexin) to an UINT8 referenced
+ * by byteout.
+ * Return != 0 if the was a problem (invalid input)
+ */
+static __inline int acpi_hp_hex_to_int(const UINT8 *hexin, UINT8 *byteout)
+{
+	unsigned int hi;
+	unsigned int lo;
+
+	hi = hexin[0];
+	lo = hexin[1];
+	if ('0' <= hi && hi <= '9')
+		hi -= '0';
+	else if ('A' <= hi && hi <= 'F')
+		hi -= ('A' - 10);
+	else if ('a' <= hi && hi <= 'f')
+		hi -= ('a' - 10);
+	else
+		return (1);
+	if ('0' <= lo && lo <= '9')
+		lo -= '0';
+	else if ('A' <= lo && lo <= 'F')
+		lo -= ('A' - 10);
+	else if ('a' <= lo && lo <= 'f')
+		lo -= ('a' - 10);
+	else
+		return (1);
+	*byteout = (hi << 4) + lo;
+
+	return (0);
+}
+
+
+static void
+acpi_hp_hex_decode(char* buffer)
+{
+	int i;
+	int length = strlen(buffer);
+	UINT8 *uin;
+	UINT8 uout;
+
+	if (((int)length/2)*2 == length || length < 10) return;
+
+	for (i = 0; i<length; ++i) {
+		if (!((i+1)%3)) {
+			if (buffer[i] != ' ')
+				return;
+		}
+		else
+			if (!((buffer[i] >= '0' && buffer[i] <= '9') ||
+		    	    (buffer[i] >= 'A' && buffer[i] <= 'F')))
+				return;			
+	}
+
+	for (i = 0; i<length; i += 3) {
+		uin = &buffer[i];
+		uout = 0;
+		acpi_hp_hex_to_int(uin, &uout);
+		buffer[i/3] = (char) uout;
+	}
+	buffer[(length+1)/3] = 0;
+}
+
+
+/*
+ * open hpcmi device
+ */
+static int
+acpi_hp_hpcmi_open(struct cdev* dev, int flags, int mode, struct thread *td)
+{
+	struct acpi_hp_softc *sc;
+	int ret;
+
+	if (dev == NULL || dev->si_drv1 == NULL)
+		return (EBADF);
+	sc = dev->si_drv1;
+
+	ACPI_SERIAL_BEGIN(hp);
+	if (sc->hpcmi_open_pid != 0) {
+		ret = EBUSY;
+	}
+	else {
+		if (sbuf_new(&sc->hpcmi_sbuf, NULL, 4096, SBUF_AUTOEXTEND)
+		    == NULL) {
+			ret = ENXIO;
+		} else {
+			sc->hpcmi_open_pid = td->td_proc->p_pid;
+			sc->hpcmi_bufptr = 0;
+			ret = 0;
+		}
+	}
+	ACPI_SERIAL_END(hp);
+
+	return (ret);
+}
+
+/*
+ * close hpcmi device
+ */
+static int
+acpi_hp_hpcmi_close(struct cdev* dev, int flags, int mode, struct thread *td)
+{
+	struct acpi_hp_softc *sc;
+	int ret;
+
+	if (dev == NULL || dev->si_drv1 == NULL)
+		return (EBADF);
+	sc = dev->si_drv1;
+
+	ACPI_SERIAL_BEGIN(hp);
+	if (sc->hpcmi_open_pid == 0) {
+		ret = EBADF;
+	}
+	else {
+		if (sc->hpcmi_bufptr != -1) {
+			sbuf_delete(&sc->hpcmi_sbuf);
+			sc->hpcmi_bufptr = -1;
+		}
+		sc->hpcmi_open_pid = 0;
+		ret = 0;
+	}
+	ACPI_SERIAL_END(hp);
+
+	return (ret);
+}
+
+/*
+ * Read from hpcmi bios information
+ */
+static int
+acpi_hp_hpcmi_read(struct cdev *dev, struct uio *buf, int flag)
+{
+	struct acpi_hp_softc *sc;
+	int pos, i, l, ret;
+	UINT8 instance;
+	UINT32 sequence;
+	int linesize = 1025;
+	char line[linesize];
+
+	if (dev == NULL || dev->si_drv1 == NULL)
+		return (EBADF);
+	sc = dev->si_drv1;
+	
+	ACPI_SERIAL_BEGIN(hp);
+	if (sc->hpcmi_open_pid != buf->uio_td->td_proc->p_pid
+	    || sc->hpcmi_bufptr == -1) {
+		ret = EBADF;
+	}
+	else {
+		if (!sbuf_done(&sc->hpcmi_sbuf)) {
+			if (sc->cmi_order_size < 0) {
+				sc->cmi_order_size = 0;
+				for (instance = 0; instance < 128;
+				    ++instance) {
+					if (acpi_hp_get_cmi_block(sc->wmi_dev,
+						ACPI_HP_WMI_CMI_GUID, instance,
+						line, linesize, &sequence,
+						sc->cmi_detail)) {
+						instance = 128;
+					}
+					else {
+						pos = sc->cmi_order_size;
+						for (i=0;
+						  i<sc->cmi_order_size && i<127;
+						     ++i) {
+				if (sc->cmi_order[i].sequence > sequence) {
+								pos = i;
+								break; 							
+							}
+						}
+						for (i=sc->cmi_order_size;
+						    i>pos;
+						    --i) {
+						sc->cmi_order[i].sequence =
+						    sc->cmi_order[i-1].sequence;
+						sc->cmi_order[i].instance =
+						    sc->cmi_order[i-1].instance;
+						}
+						sc->cmi_order[pos].sequence =
+						    sequence;
+						sc->cmi_order[pos].instance =
+						    instance;
+						sc->cmi_order_size++;
+					}
+				}
+			}
+			for (i=0; i<sc->cmi_order_size; ++i) {
+				if (!acpi_hp_get_cmi_block(sc->wmi_dev,
+				    ACPI_HP_WMI_CMI_GUID,
+				    sc->cmi_order[i].instance, line, linesize,
+				    &sequence, sc->cmi_detail)) {
+					sbuf_printf(&sc->hpcmi_sbuf, "%s\n", line);
+				}
+			}
+			sbuf_finish(&sc->hpcmi_sbuf);
+		}
+		if (sbuf_len(&sc->hpcmi_sbuf) <= 0) {
+			sbuf_delete(&sc->hpcmi_sbuf);
+			sc->hpcmi_bufptr = -1;
+			sc->hpcmi_open_pid = 0;
+			ret = ENOMEM;
+		} else {
+			l = min(buf->uio_resid, sbuf_len(&sc->hpcmi_sbuf) -
+			    sc->hpcmi_bufptr);
+			ret = (l > 0)?uiomove(sbuf_data(&sc->hpcmi_sbuf) +
+			    sc->hpcmi_bufptr, l, buf) : 0;
+			sc->hpcmi_bufptr += l;
+		}
+	}
+	ACPI_SERIAL_END(hp);
+
+	return (ret);
+}
--- share/man/man4/man4.i386/acpi_wmi.4~	2009-06-21 12:09:58.000000000 +0200
+++ share/man/man4/man4.i386/acpi_wmi.4	2009-06-21 12:10:57.178932778 +0200
@@ -0,0 +1,97 @@
+.\" Copyright (c) 2009 Michael Gmelin
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd June 21, 2009
+.Dt ACPI_WMI 4 i386
+.Os
+.Sh NAME
+.Nm acpi_wmi
+.Nd "ACPI to WMI mapping driver"
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device acpi_wmi"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+acpi_wmi_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides an interface for vendor specific WMI implementations 
+(e.g. HP and Acer laptops). It creates /dev/wmistat, which can be read to get
+information about GUIDs found in the system.
+.Sh FILES
+.Bl -tag -width /dev/wmistat -compact
+.It Pa /dev/wmistat
+WMI status device.
+.El
+.Sh EXAMPLES
+.Bd Literal
+root# cat /dev/wmistat
+
+GUID                                  INST EXPE METH STR EVENT OID
+{5FB7F034-2C63-45E9-BE91-3D44E2C707E4}   1 NO   WMAA NO  NO    AA
+{95F24279-4D7B-4334-9387-ACCDC67EF61C}   1 NO   NO   NO  0x80+ -
+{2B814318-4BE8-4707-9D84-A190A859B5D0}   1 NO   NO   NO  0xA0  -
+{05901221-D566-11D1-B2F0-00A0C9062910}   1 NO   NO   NO  NO    AB
+{1F4C91EB-DC5C-460B-951D-C7CB9B4B8D5E}   1 NO   WMBA NO  NO    BA
+{2D114B49-2DFB-4130-B8FE-4A3C09E75133}  57 NO   NO   NO  NO    BC
+{988D08E3-68F4-4C35-AF3E-6A1B8106F83C}  20 NO   NO   NO  NO    BD
+{14EA9746-CE1F-4098-A0E0-7045CB4DA745}   1 NO   NO   NO  NO    BE
+{322F2028-0F84-4901-988E-015176049E2D}   2 NO   NO   NO  NO    BF
+{8232DE3D-663D-4327-A8F4-E293ADB9BF05}   0 NO   NO   NO  NO    BG
+{8F1F6436-9F42-42C8-BADC-0E9424F20C9A}   0 NO   NO   NO  NO    BH
+{8F1F6435-9F42-42C8-BADC-0E9424F20C9A}   0 NO   NO   NO  NO    BI
+.Ed
+
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Fx CURRENT .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Michael Gmelin Aq freebsd@grem.de
+.Pp
+Work has been inspired by the Linux acpi-wmi driver written by Carlos Corbacho
+.Pp
+See http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx for
+the specification of ACPI-WMI.
+.Pp
+This manual page was written by
+.An Michael Gmelin Aq freebsd@grem.de
--- sys/dev/acpi_support/acpi_wmi.c~	2009-06-21 18:42:25.739784505 +0200
+++ sys/dev/acpi_support/acpi_wmi.c	2009-06-21 18:50:54.166723000 +0200
@@ -0,0 +1,1005 @@
+/*-
+ * Copyright (c) 2009 Michael Gmelin <freebsd@grem.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Driver for acpi-wmi mapping, provides an interface for vendor specific
+ * implementations (e.g. HP and Acer laptops).
+ * Inspired by the ACPI-WMI mapping driver (c) 2008-2008 Carlos Corbacho which
+ * implements this functionality for Linux.
+ *
+ * WMI and ACPI: http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
+ * acpi-wmi for Linux: http://www.kernel.org
+ */
+
+#include "opt_acpi.h"
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/proc.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/sbuf.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+
+#include <contrib/dev/acpica/acpi.h>
+#include <dev/acpica/acpivar.h>
+#include "acpi_wmi_if.h"
+
+MALLOC_DEFINE(M_ACPIWMI, "acpiwmi", "ACPI-WMI mapping");
+
+#define _COMPONENT	ACPI_OEM
+ACPI_MODULE_NAME("ACPI_WMI");
+
+#define ACPI_WMI_REGFLAG_EXPENSIVE	0x1 /* GUID flag: Expensive operation */
+#define ACPI_WMI_REGFLAG_METHOD		0x2	/* GUID flag: Method call */
+#define ACPI_WMI_REGFLAG_STRING		0x4	/* GUID flag: String */
+#define ACPI_WMI_REGFLAG_EVENT		0x8	/* GUID flag: Event */
+
+/*
+ * acpi_wmi driver private structure
+ */
+struct acpi_wmi_softc {
+	device_t	wmi_dev;	/* wmi device id */
+	ACPI_HANDLE	wmi_handle;	/* handle of the PNP0C14 node */
+	device_t	ec_dev;		/* acpi_ec0 */
+	struct cdev	*wmistat_dev_t;	/* wmistat device handle */
+	struct sbuf	wmistat_sbuf;	/* sbuf for /dev/wmistat output */
+	pid_t		wmistat_open_pid; /* pid operating on /dev/wmistat */
+	int		wmistat_bufptr;	/* /dev/wmistat ptr to buffer position */
+};
+
+/*
+ * Struct that holds information about
+ * about a single GUID entry in _WDG
+ */
+struct guid_info {
+	char	guid[16];	/* 16 byte non human readable GUID */
+	char	oid[2];		/* object id or event notify id (first byte) */
+	UINT8	max_instance;	/* highest instance known for this GUID */
+	UINT8	flags;		/* ACPI_WMI_REGFLAG_%s */
+};
+
+/* WExx event generation state (on/off) */
+enum event_generation_state {
+	EVENT_GENERATION_ON = 1,
+	EVENT_GENERATION_OFF = 0
+};
+
+
+/*
+ * Information about one entry in _WDG.
+ * List of those is used to lookup information by GUID.
+ */
+struct wmi_info {
+	TAILQ_ENTRY(wmi_info)	wmi_list;
+	struct guid_info	ginfo;		/* information on guid */
+	ACPI_NOTIFY_HANDLER	event_handler;/* client provided event handler */
+	void			*event_handler_user_data; /* ev handler cookie  */
+};
+
+TAILQ_HEAD(wmi_info_list_head, wmi_info)
+    wmi_info_list = TAILQ_HEAD_INITIALIZER(wmi_info_list);
+
+ACPI_SERIAL_DECL(acpi_wmi, "ACPI-WMI Mapping");
+
+/* public interface - declaration */
+/* standard device interface*/
+static int		acpi_wmi_probe(device_t dev);
+static int		acpi_wmi_attach(device_t dev);
+static int		acpi_wmi_detach(device_t dev);
+/* see acpi_wmi_if.m */
+static int		acpi_wmi_provides_guid_string_method(device_t dev,
+			    const char *guid_string);
+static ACPI_STATUS	acpi_wmi_evaluate_call_method(device_t dev,
+			    const char *guid_string, UINT8 instance,
+			    UINT32 method_id, const ACPI_BUFFER *in,
+			    ACPI_BUFFER *out);
+static ACPI_STATUS	acpi_wmi_install_event_handler_method(device_t dev,
+			    const char *guid_string, ACPI_NOTIFY_HANDLER handler,
+			    void *data);
+static ACPI_STATUS	acpi_wmi_remove_event_handler_method(device_t dev,
+			    const char *guid_string);
+static ACPI_STATUS	acpi_wmi_get_event_data_method(device_t dev,
+			    UINT32 event_id, ACPI_BUFFER *out);
+static ACPI_STATUS	acpi_wmi_get_block_method(device_t dev,
+			    const char *guid_string,
+			    UINT8 instance, ACPI_BUFFER *out);
+static ACPI_STATUS	acpi_wmi_set_block_method(device_t dev,
+			    const char *guid_string,
+			    UINT8 instance, const ACPI_BUFFER *in);
+/* private interface - declaration */
+/* callbacks */
+static void		acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify,
+			    void *context);
+static ACPI_STATUS	acpi_wmi_ec_handler(UINT32 function,
+			    ACPI_PHYSICAL_ADDRESS address, UINT32 width,
+			    ACPI_INTEGER *value, void *context,
+			    void *region_context);
+/* helpers */
+static ACPI_STATUS	acpi_wmi_read_wdg_blocks(ACPI_HANDLE h);
+static ACPI_STATUS	acpi_wmi_toggle_we_event_generation(device_t dev,
+			    struct wmi_info *winfo,
+			    enum event_generation_state state);
+static int		acpi_wmi_guid_string_to_guid(const UINT8 *guid_string,
+			    UINT8 *guid);
+static struct wmi_info* acpi_wmi_lookup_wmi_info_by_guid_string(
+			    const char *guid_string);
+
+static d_open_t acpi_wmi_wmistat_open;
+static d_close_t acpi_wmi_wmistat_close;
+static d_read_t acpi_wmi_wmistat_read;
+
+/* handler /dev/wmistat device */
+static struct cdevsw wmistat_cdevsw = {
+	.d_version = D_VERSION,
+	.d_open = acpi_wmi_wmistat_open,
+	.d_close = acpi_wmi_wmistat_close,
+	.d_read = acpi_wmi_wmistat_read,
+	.d_name = "wmistat",
+};
+
+
+static device_method_t acpi_wmi_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,	acpi_wmi_probe),
+	DEVMETHOD(device_attach, acpi_wmi_attach),
+	DEVMETHOD(device_detach, acpi_wmi_detach),
+
+	/* acpi_wmi interface */
+	DEVMETHOD(acpi_wmi_provides_guid_string,
+		    acpi_wmi_provides_guid_string_method),
+	DEVMETHOD(acpi_wmi_evaluate_call, acpi_wmi_evaluate_call_method),
+	DEVMETHOD(acpi_wmi_install_event_handler,
+		    acpi_wmi_install_event_handler_method),
+	DEVMETHOD(acpi_wmi_remove_event_handler,
+		    acpi_wmi_remove_event_handler_method),
+	DEVMETHOD(acpi_wmi_get_event_data, acpi_wmi_get_event_data_method),
+	DEVMETHOD(acpi_wmi_get_block, acpi_wmi_get_block_method),
+	DEVMETHOD(acpi_wmi_set_block, acpi_wmi_set_block_method),
+
+	{0, 0}
+};
+
+static driver_t acpi_wmi_driver = {
+	"acpi_wmi",
+	acpi_wmi_methods,
+	sizeof(struct acpi_wmi_softc),
+};
+
+static devclass_t acpi_wmi_devclass;
+DRIVER_MODULE(acpi_wmi, acpi, acpi_wmi_driver, acpi_wmi_devclass, 0, 0);
+MODULE_VERSION(acpi_wmi, 1);
+MODULE_DEPEND(acpi_wmi, acpi, 1, 1, 1);
+static char *wmi_ids[] = {"PNP0C14", "PNP0c14", NULL};
+
+/*
+ * Probe for the PNP0C14 ACPI node
+ */
+static int
+acpi_wmi_probe(device_t dev)
+{
+	if (acpi_disabled("wmi") ||
+	    ACPI_ID_PROBE(device_get_parent(dev), dev, wmi_ids) == NULL)
+		return (ENXIO);
+	device_set_desc(dev, "ACPI-WMI mapping");
+
+	return (0);
+}
+
+/*
+ * Attach the device by:
+ * - Looking for the first ACPI EC device
+ * - Install the notify handler
+ * - Install the EC address space handler
+ * - Look for the _WDG node and read GUID information blocks
+ */
+static int
+acpi_wmi_attach(device_t dev)
+{
+	struct acpi_wmi_softc *sc;
+	int ret;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	sc = device_get_softc(dev);
+	ret = ENXIO;
+
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	sc->wmi_dev = dev;
+	sc->wmi_handle = acpi_get_handle(dev);
+	TAILQ_INIT(&wmi_info_list);
+	/* XXX Only works with one EC, but nearly all systems only have one. */
+	if ((sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0))
+	    == NULL)
+		device_printf(dev, "cannot find EC device\n");
+	else if (ACPI_FAILURE((status = AcpiInstallNotifyHandler(sc->wmi_handle,
+		    ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler, sc))))
+		device_printf(sc->wmi_dev, "couldn't install notify handler - %s\n",
+		    AcpiFormatException(status));
+	else if (ACPI_FAILURE((status = AcpiInstallAddressSpaceHandler(
+		    sc->wmi_handle, ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler,
+		    NULL, sc)))) {
+		device_printf(sc->wmi_dev, "couldn't install EC handler - %s\n",
+		    AcpiFormatException(status));
+		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
+		    acpi_wmi_notify_handler);
+	} else if (ACPI_FAILURE((status = acpi_wmi_read_wdg_blocks(
+		    sc->wmi_handle)))) {
+		device_printf(sc->wmi_dev, "couldn't parse _WDG - %s\n",
+		    AcpiFormatException(status));
+		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
+		    acpi_wmi_notify_handler);
+		AcpiRemoveAddressSpaceHandler(sc->wmi_handle, ACPI_ADR_SPACE_EC,
+		    acpi_wmi_ec_handler);
+	} else {
+		sc->wmistat_dev_t = make_dev(&wmistat_cdevsw, 0, UID_ROOT,
+		    GID_WHEEL, 0644, "wmistat");
+		sc->wmistat_dev_t->si_drv1 = sc;
+		sc->wmistat_open_pid = 0;
+		sc->wmistat_bufptr = -1;
+		ret = 0;
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (ret);
+}
+
+/*
+ * Detach the driver by:
+ * - Removing notification handler
+ * - Removing address space handler
+ * - Turning off event generation for all WExx event activated by
+ *   child drivers
+ */
+static int
+acpi_wmi_detach(device_t dev)
+{
+	struct wmi_info *winfo, *tmp;
+	struct acpi_wmi_softc *sc;
+	int ret;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	sc = device_get_softc(dev);
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+
+	if (sc->wmistat_open_pid != 0) {
+		ret = EBUSY;
+	} else {
+		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
+		    acpi_wmi_notify_handler);
+		AcpiRemoveAddressSpaceHandler(sc->wmi_handle,
+		    ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler);
+		TAILQ_FOREACH_SAFE(winfo, &wmi_info_list, wmi_list, tmp) {
+			if (winfo->event_handler)
+				acpi_wmi_toggle_we_event_generation(dev,
+				    winfo, EVENT_GENERATION_OFF);
+			TAILQ_REMOVE(&wmi_info_list, winfo, wmi_list);
+			free(winfo, M_ACPIWMI);
+		}
+		if (sc->wmistat_bufptr != -1) {
+			sbuf_delete(&sc->wmistat_sbuf);
+			sc->wmistat_bufptr = -1;
+		}
+		sc->wmistat_open_pid = 0;
+		destroy_dev(sc->wmistat_dev_t);
+		ret = 0;
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (ret);
+}
+
+
+/*
+ * Check if the given GUID string (human readable format
+ * AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP)
+ * exists within _WDG
+ */
+static int
+acpi_wmi_provides_guid_string_method(device_t dev, const char *guid_string)
+{
+	int ret;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	ret = (acpi_wmi_lookup_wmi_info_by_guid_string(guid_string) == NULL)?0:1;
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (ret);
+}
+
+/*
+ * Call a method "method_id" on the given GUID block
+ * write result into user provided output buffer
+ */
+static ACPI_STATUS
+acpi_wmi_evaluate_call_method(device_t dev, const char *guid_string,
+    UINT8 instance, UINT32 method_id, const ACPI_BUFFER *in, ACPI_BUFFER *out)
+{
+	ACPI_OBJECT params[3];
+	ACPI_OBJECT_LIST input;
+	char method[5] = "WMxx";
+	struct wmi_info *winfo;
+	struct acpi_wmi_softc *sc;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = device_get_softc(dev);
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
+		    == NULL)
+		status = AE_NOT_FOUND;
+	else if (!(winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
+		status = AE_BAD_DATA;
+	else if (instance > winfo->ginfo.max_instance)
+		status = AE_BAD_PARAMETER;
+	else {
+		params[0].Type = ACPI_TYPE_INTEGER;
+		params[0].Integer.Value = instance;
+		params[1].Type = ACPI_TYPE_INTEGER;
+		params[1].Integer.Value = method_id;
+		input.Pointer = params;
+		input.Count = 2;
+		if (in) {
+			params[2].Type =
+			    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING)
+			    ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER;
+			params[2].Buffer.Length = in->Length;
+			params[2].Buffer.Pointer = in->Pointer;
+			input.Count = 3;
+		}
+		method[2] = winfo->ginfo.oid[0];
+		method[3] = winfo->ginfo.oid[1];
+		status = AcpiEvaluateObject(sc->wmi_handle, method,
+			    &input, out);
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (status);
+}
+
+/*
+ * Install a user provided event_handler on the given GUID
+ * provided *data will be passed on callback
+ * If there is already an existing event handler registered it will be silently
+ * discarded
+ */
+static ACPI_STATUS
+acpi_wmi_install_event_handler_method(device_t dev, const char *guid_string,
+    ACPI_NOTIFY_HANDLER event_handler, void *data)
+{
+	struct wmi_info *winfo;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	status = AE_OK;
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (guid_string == NULL || event_handler == NULL)
+		status = AE_BAD_PARAMETER;
+	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
+		    == NULL)
+		status = AE_NOT_EXIST;
+	else if (winfo->event_handler != NULL ||
+		(status = acpi_wmi_toggle_we_event_generation(dev, winfo,
+		    EVENT_GENERATION_ON)) == AE_OK) {
+		winfo->event_handler = event_handler;
+		winfo->event_handler_user_data = data;
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (status);
+}
+
+/*
+ * Remove a previously installed event handler from the given GUID
+ * If there was none installed, this call is silently discarded and
+ * reported as AE_OK
+ */
+static ACPI_STATUS
+acpi_wmi_remove_event_handler_method(device_t dev, const char *guid_string)
+{
+	struct wmi_info *winfo;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	status = AE_OK;
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (guid_string &&
+	    (winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
+	    != NULL && winfo->event_handler) {
+		status = acpi_wmi_toggle_we_event_generation(dev, winfo,
+			    EVENT_GENERATION_OFF);
+		winfo->event_handler = NULL;
+		winfo->event_handler_user_data = NULL;
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (status);
+}
+
+/*
+ * Get details on an event received through a callback registered
+ * through ACPI_WMI_REMOVE_EVENT_HANDLER into a user provided output buffer.
+ * (event_id equals "notify" passed in the callback)
+ */
+static ACPI_STATUS
+acpi_wmi_get_event_data_method(device_t dev, UINT32 event_id, ACPI_BUFFER *out)
+{
+	ACPI_OBJECT_LIST input;
+	ACPI_OBJECT params[1];
+	struct acpi_wmi_softc *sc;
+	struct wmi_info *winfo;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = device_get_softc(dev);
+	status = AE_NOT_FOUND;	
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	params[0].Type = ACPI_TYPE_INTEGER;
+	params[0].Integer.Value = event_id;
+	input.Pointer = params;
+	input.Count = 1;
+	TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
+		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) &&
+		    ((UINT8) winfo->ginfo.oid[0] == event_id)) {
+			status = AcpiEvaluateObject(sc->wmi_handle, "_WED",
+				    &input, out);
+			break;
+		}
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (status);
+}
+
+/*
+ * Read a block of data from the given GUID (using WQxx (query))
+ * Will be returned in a user provided buffer (out).
+ * If the method is marked as expensive (ACPI_WMI_REGFLAG_EXPENSIVE)
+ * we will first call the WCxx control method to lock the node to
+ * lock the node for data collection and release it afterwards.
+ * (Failed WCxx calls are ignored to "support" broken implementations)
+ */
+static ACPI_STATUS
+acpi_wmi_get_block_method(device_t dev, const char *guid_string, UINT8 instance,
+	ACPI_BUFFER *out)
+{
+	char wc_method[5] = "WCxx";
+	char wq_method[5] = "WQxx";
+	ACPI_OBJECT_LIST wc_input;
+	ACPI_OBJECT_LIST wq_input;
+	ACPI_OBJECT wc_params[1];
+	ACPI_OBJECT wq_params[1];
+	ACPI_HANDLE wc_handle;
+	struct acpi_wmi_softc *sc;
+	struct wmi_info *winfo;
+	ACPI_STATUS status;
+	ACPI_STATUS wc_status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = device_get_softc(dev);
+	wc_status = AE_ERROR;
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (guid_string == NULL || out == NULL)
+		status = AE_BAD_PARAMETER;
+	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
+		    == NULL)
+		status = AE_ERROR;
+	else if (instance > winfo->ginfo.max_instance)
+		status = AE_BAD_PARAMETER;
+	else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) ||
+	    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
+		status = AE_ERROR;
+	else {
+		wq_params[0].Type = ACPI_TYPE_INTEGER;
+		wq_params[0].Integer.Value = instance;
+		wq_input.Pointer = wq_params;
+		wq_input.Count = 1;
+		if (winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE) {
+			wc_params[0].Type = ACPI_TYPE_INTEGER;
+			wc_params[0].Integer.Value = 1;
+			wc_input.Pointer = wc_params;
+			wc_input.Count = 1;
+			wc_method[2] = winfo->ginfo.oid[0];
+			wc_method[3] = winfo->ginfo.oid[1];
+			wc_status = AcpiGetHandle(sc->wmi_handle, wc_method,
+				    &wc_handle);
+			if (ACPI_SUCCESS(wc_status))
+				wc_status = AcpiEvaluateObject(wc_handle,
+						wc_method, &wc_input, NULL);
+		}
+		wq_method[2] = winfo->ginfo.oid[0];
+		wq_method[3] = winfo->ginfo.oid[1];
+		status = AcpiEvaluateObject(sc->wmi_handle, wq_method,
+			    &wq_input, out);
+		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE)
+		    && ACPI_SUCCESS(wc_status)) {
+			wc_params[0].Integer.Value = 0;
+			status = AcpiEvaluateObject(wc_handle, wc_method,
+				    &wc_input, NULL);  /* XXX this might be
+				    			 the wrong status to
+				    			 return? */
+		}
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (status);
+}
+
+/*
+ * Write a block of data to the given GUID (using WSxx)
+ */
+static ACPI_STATUS
+acpi_wmi_set_block_method(device_t dev, const char *guid_string, UINT8 instance,
+	const ACPI_BUFFER *in)
+{
+	char method[5] = "WSxx";
+	ACPI_OBJECT_LIST input;
+	ACPI_OBJECT params[2];
+	struct wmi_info *winfo;
+	struct acpi_wmi_softc *sc;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = device_get_softc(dev);
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (guid_string == NULL || in == NULL)
+		status = AE_BAD_DATA;
+	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
+		    == NULL)
+		status = AE_ERROR;
+	else if (instance > winfo->ginfo.max_instance)
+		status = AE_BAD_PARAMETER;
+	else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) ||
+		    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
+		status = AE_ERROR;
+	else {
+		params[0].Type = ACPI_TYPE_INTEGER;
+		params[0].Integer.Value = instance;
+		input.Pointer = params;
+		input.Count = 2;
+		params[1].Type = (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING)
+		    ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER;
+		params[1].Buffer.Length = in->Length;
+		params[1].Buffer.Pointer = in->Pointer;
+		method[2] = winfo->ginfo.oid[0];
+		method[3] = winfo->ginfo.oid[1];
+		status = AcpiEvaluateObject(sc->wmi_handle, method,
+			    &input, NULL);
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (status);
+}
+
+/*
+ * Handle events received and dispatch them to
+ * stakeholders that registered through ACPI_WMI_INSTALL_EVENT_HANDLER
+ */
+static void
+acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
+{
+	ACPI_NOTIFY_HANDLER handler;
+	void *handler_data;
+	struct wmi_info *winfo;
+
+	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);
+
+	handler = NULL;
+	handler_data = NULL;
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
+		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) &&
+				((UINT8) winfo->ginfo.oid[0] == notify)) {
+			if (winfo->event_handler) {
+				handler = winfo->event_handler;
+				handler_data = winfo->event_handler_user_data;
+				break;
+			}
+		}
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+	if (handler) {
+		handler(h, notify, handler_data);
+	}
+}
+
+/*
+ * Handle EC address space notifications reveived on the WDG node
+ * (this mimics EcAddressSpaceHandler in acpi_ec.c)
+ */
+static ACPI_STATUS
+acpi_wmi_ec_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address,
+    UINT32 width, ACPI_INTEGER *value, void *context,
+    void *region_context)
+{
+	struct acpi_wmi_softc *sc;
+	int i;
+	ACPI_INTEGER ec_data;
+	UINT8 ec_addr;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)Address);
+
+	sc = (struct acpi_wmi_softc *)context;
+	if (width % 8 != 0 || value == NULL || context == NULL)
+		return (AE_BAD_PARAMETER);
+	if (address + (width / 8) - 1 > 0xFF)
+		return (AE_BAD_ADDRESS);
+	if (function == ACPI_READ)
+		*value = 0;
+	ec_addr = address;
+	status = AE_ERROR;
+
+	for (i = 0; i < width; i += 8, ++ec_addr) {
+		switch (function) {
+		case ACPI_READ:
+			status = ACPI_EC_READ(sc->ec_dev, ec_addr, &ec_data, 1);
+			if (ACPI_SUCCESS(status))
+				*value |= ((ACPI_INTEGER)ec_data) << i;
+		break;
+		case ACPI_WRITE:
+			ec_data = (UINT8)((*value) >> i);
+			status = ACPI_EC_WRITE(sc->ec_dev, ec_addr, ec_data, 1);
+			break;
+		default:
+			device_printf(sc->wmi_dev,
+			    "invalid acpi_wmi_ec_handler function %d\n",
+			    function);
+			status = AE_BAD_PARAMETER;
+			break;
+		}
+		if (ACPI_FAILURE(status))
+			break;
+	}
+
+	return (status);
+}
+
+/*
+ * Read GUID blocks from the _WDG node
+ * into wmi_info_list.
+ */
+static ACPI_STATUS
+acpi_wmi_read_wdg_blocks(ACPI_HANDLE h)
+{
+	ACPI_BUFFER out = {ACPI_ALLOCATE_BUFFER, NULL};
+	struct guid_info *ginfo;
+	ACPI_OBJECT *obj;
+	struct wmi_info *winfo;
+	UINT32 i;
+	UINT32 wdg_block_count;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	ACPI_SERIAL_ASSERT(acpi_wmi);
+	if (ACPI_FAILURE(status = AcpiEvaluateObject(h, "_WDG", NULL, &out)))
+		return (status);
+	obj = (ACPI_OBJECT*) out.Pointer;
+	wdg_block_count = obj->Buffer.Length / sizeof(struct guid_info);
+	if ((ginfo = malloc(obj->Buffer.Length, M_ACPIWMI, M_NOWAIT))
+		    == NULL) {
+		AcpiOsFree(out.Pointer);
+		return (AE_NO_MEMORY);
+	}
+	memcpy(ginfo, obj->Buffer.Pointer, obj->Buffer.Length);
+	for (i = 0; i < wdg_block_count; ++i) {
+		if ((winfo = malloc(sizeof(struct wmi_info), M_ACPIWMI,
+			    M_NOWAIT | M_ZERO)) == NULL) {
+			AcpiOsFree(out.Pointer);
+			free(ginfo, M_ACPIWMI);
+			return (AE_NO_MEMORY);
+		}
+		winfo->ginfo = ginfo[i];
+		TAILQ_INSERT_TAIL(&wmi_info_list, winfo, wmi_list);
+	}
+	AcpiOsFree(out.Pointer);
+	free(ginfo, M_ACPIWMI);
+
+	return (status);
+}
+
+/*
+ * Toggle event generation in for the given GUID (passed by winfo)
+ * Turn on to get notified (through acpi_wmi_notify_handler) if events happen
+ * on the given GUID.
+ */
+static ACPI_STATUS
+acpi_wmi_toggle_we_event_generation(device_t dev, struct wmi_info *winfo,
+    enum event_generation_state state)
+{
+	char method[5] = "WExx";
+	ACPI_OBJECT_LIST input;
+	ACPI_OBJECT params[1];
+	struct acpi_wmi_softc *sc;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = device_get_softc(dev);
+	ACPI_SERIAL_ASSERT(acpi_wmi);
+	params[0].Type = ACPI_TYPE_INTEGER;
+	params[0].Integer.Value = state==EVENT_GENERATION_ON?1:0;
+	input.Pointer = params;
+	input.Count = 1;
+	
+	UINT8 hi = ((UINT8) winfo->ginfo.oid[0]) >> 4;
+	UINT8 lo = ((UINT8) winfo->ginfo.oid[0]) & 0xf;
+	method[2] = (hi > 9 ? hi + 55: hi + 48);
+	method[3] = (lo > 9 ? lo + 55: lo + 48);
+	status = AcpiEvaluateObject(sc->wmi_handle, method, &input, NULL);
+	if (status == AE_NOT_FOUND) status = AE_OK;
+
+	return (status);
+}
+
+/*
+ * Convert given two digit hex string (hexin) to an UINT8 referenced
+ * by byteout.
+ * Return != 0 if the was a problem (invalid input)
+ */
+static __inline int acpi_wmi_hex_to_int(const UINT8 *hexin, UINT8 *byteout)
+{
+	unsigned int hi;
+	unsigned int lo;
+
+	hi = hexin[0];
+	lo = hexin[1];
+	if ('0' <= hi && hi <= '9')
+		hi -= '0';
+	else if ('A' <= hi && hi <= 'F')
+		hi -= ('A' - 10);
+	else if ('a' <= hi && hi <= 'f')
+		hi -= ('a' - 10);
+	else
+		return (1);
+	if ('0' <= lo && lo <= '9')
+		lo -= '0';
+	else if ('A' <= lo && lo <= 'F')
+		lo -= ('A' - 10);
+	else if ('a' <= lo && lo <= 'f')
+		lo -= ('a' - 10);
+	else
+		return (1);
+	*byteout = (hi << 4) + lo;
+
+	return (0);
+}
+
+/*
+ * Convert a human readable 36 character GUID into a 16byte
+ * machine readable one.
+ * The basic algorithm looks as follows:
+ * Input:  AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP
+ * Output: DCBAFEHGIJKLMNOP
+ * (AA BB CC etc. represent two digit hex numbers == bytes)
+ * Return != 0 if passed guid string is invalid
+ */
+static int
+acpi_wmi_guid_string_to_guid(const UINT8 *guid_string, UINT8 *guid)
+{
+	static const int mapping[20] = {3, 2, 1, 0, -1, 5, 4, -1, 7, 6, -1,
+	    8, 9, -1, 10, 11, 12, 13, 14, 15};
+	int i;
+
+	for (i = 0; i < 20; ++i, ++guid_string) {
+		if (mapping[i] >= 0) {
+			if (acpi_wmi_hex_to_int(guid_string,
+			    &guid[mapping[i]]))
+				return (-1);
+			++guid_string;
+		} else if (*guid_string != '-')
+			return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Lookup a wmi_info structure in wmi_list based on a
+ * human readable GUID
+ * Return NULL if the GUID is unknown in the _WDG
+ */
+static struct wmi_info*
+acpi_wmi_lookup_wmi_info_by_guid_string(const char *guid_string)
+{
+	char guid[16];
+	struct wmi_info *winfo;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	ACPI_SERIAL_ASSERT(acpi_wmi);
+
+	if (!acpi_wmi_guid_string_to_guid(guid_string, guid)) {
+		TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
+			if (!memcmp(winfo->ginfo.guid, guid, 16)) {
+				return (winfo);
+			}
+		}
+	}
+
+	return (NULL);
+}
+
+/*
+ * open wmistat device
+ */
+static int
+acpi_wmi_wmistat_open(struct cdev* dev, int flags, int mode, struct thread *td)
+{
+	struct acpi_wmi_softc *sc;
+	int ret;
+
+	if (dev == NULL || dev->si_drv1 == NULL)
+		return (EBADF);
+	sc = dev->si_drv1;
+
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (sc->wmistat_open_pid != 0) {
+		ret = EBUSY;
+	}
+	else {
+		if (sbuf_new(&sc->wmistat_sbuf, NULL, 4096, SBUF_AUTOEXTEND)
+			    == NULL) {
+			ret = ENXIO;
+		} else {
+			sc->wmistat_open_pid = td->td_proc->p_pid;
+			sc->wmistat_bufptr = 0;
+			ret = 0;
+		}
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (ret);
+}
+
+/*
+ * close wmistat device
+ */
+static int
+acpi_wmi_wmistat_close(struct cdev* dev, int flags, int mode,
+    struct thread *td)
+{
+	struct acpi_wmi_softc *sc;
+	int ret;
+
+	if (dev == NULL || dev->si_drv1 == NULL)
+		return (EBADF);
+	sc = dev->si_drv1;
+
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (sc->wmistat_open_pid == 0) {
+		ret = EBADF;
+	}
+	else {
+		if (sc->wmistat_bufptr != -1) {
+			sbuf_delete(&sc->wmistat_sbuf);
+			sc->wmistat_bufptr = -1;
+		}
+		sc->wmistat_open_pid = 0;
+		ret = 0;
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (ret);
+}
+
+/*
+ * Read from wmistat guid information
+ */
+static int
+acpi_wmi_wmistat_read(struct cdev *dev, struct uio *buf, int flag)
+{
+	struct acpi_wmi_softc *sc;
+	struct wmi_info *winfo;
+	int l;
+	int ret;
+	UINT8* guid;
+
+	if (dev == NULL || dev->si_drv1 == NULL)
+		return (EBADF);
+	sc = dev->si_drv1;
+	
+	ACPI_SERIAL_BEGIN(acpi_wmi);
+	if (sc->wmistat_open_pid != buf->uio_td->td_proc->p_pid ||
+			sc->wmistat_bufptr == -1) {
+		ret = EBADF;
+	}
+	else {
+		if (!sbuf_done(&sc->wmistat_sbuf)) {
+			sbuf_printf(&sc->wmistat_sbuf, "GUID                 "
+				    "                 INST EXPE METH STR "
+				    "EVENT OID\n");
+			TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
+				guid = (UINT8*)winfo->ginfo.guid;
+				sbuf_printf(&sc->wmistat_sbuf,
+					    "{%02X%02X%02X%02X-%02X%02X-"
+					    "%02X%02X-%02X%02X-%02X%02X"
+					    "%02X%02X%02X%02X} %3d %-5s",
+					guid[3], guid[2], guid[1], guid[0],
+					guid[5], guid[4],
+					guid[7], guid[6],
+					guid[8], guid[9],
+					guid[10], guid[11], guid[12],
+					guid[13], guid[14], guid[15],
+					winfo->ginfo.max_instance,
+					(winfo->ginfo.flags&
+						ACPI_WMI_REGFLAG_EXPENSIVE)?
+						"YES":"NO"
+					);
+				if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_METHOD)
+					sbuf_printf(&sc->wmistat_sbuf,
+						    "WM%c%c ",
+						    winfo->ginfo.oid[0],
+						    winfo->ginfo.oid[1]);
+				else
+					sbuf_printf(&sc->wmistat_sbuf, "NO   ");
+				sbuf_printf(&sc->wmistat_sbuf, "%-4s",
+					    (winfo->ginfo.flags&
+					    ACPI_WMI_REGFLAG_STRING)?"YES":"NO"
+					);
+				if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_EVENT)
+					sbuf_printf(&sc->wmistat_sbuf,
+						    "0x%02X%s -\n",
+						    (UINT8)winfo->ginfo.oid[0],
+						    winfo->event_handler==NULL?
+						    " ":"+");
+				else
+					sbuf_printf(&sc->wmistat_sbuf,
+						    "NO    %c%c\n",
+						    winfo->ginfo.oid[0],
+						    winfo->ginfo.oid[1]);
+			}
+			sbuf_finish(&sc->wmistat_sbuf);
+		}
+		if (sbuf_len(&sc->wmistat_sbuf) <= 0) {
+			sbuf_delete(&sc->wmistat_sbuf);
+			sc->wmistat_bufptr = -1;
+			sc->wmistat_open_pid = 0;
+			ret = ENOMEM;
+		} else {
+			l = min(buf->uio_resid, sbuf_len(&sc->wmistat_sbuf) -
+				    sc->wmistat_bufptr);
+			ret = (l > 0)?uiomove(sbuf_data(&sc->wmistat_sbuf) +
+				    sc->wmistat_bufptr, l, buf) : 0;
+			sc->wmistat_bufptr += l;
+		}
+	}
+	ACPI_SERIAL_END(acpi_wmi);
+
+	return (ret);
+}
--- sys/dev/acpi_support/acpi_wmi_if.m~	2009-06-21 12:09:58.000000000 +0200
+++ sys/dev/acpi_support/acpi_wmi_if.m	2009-06-21 12:13:23.546375847 +0200
@@ -0,0 +1,144 @@
+#-
+# Copyright (c) 2009 Michael Gmelin
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD: src/sys/dev/acpic_support/acpi_wmi_if.m,v 1.0 2008/11/23 16:33:00 rmg Exp $
+#
+
+#include <sys/bus.h>
+#include <sys/types.h>
+#include <contrib/dev/acpica/acpi.h>
+
+INTERFACE acpi_wmi;
+
+#
+# Default implementation for acpi_wmi_generic_provides_guid_string().
+#
+CODE {
+	static int
+	acpi_wmi_generic_provides_guid_string(device_t dev, const char* guid_string)
+	{
+		return 0;
+	}
+};
+
+
+#
+# Check if given GUID exists in WMI
+#
+# device_t dev:	Device to probe
+# const char* guid_string: String form of the GUID
+#
+METHOD int provides_guid_string {
+	device_t	dev;
+	const char*	guid_string;
+} DEFAULT acpi_wmi_generic_provides_guid_string;
+
+#
+# Evaluate a WMI method call
+#
+# device_t dev:  Device to use
+# const char* guid_string:  String form of the GUID
+# UINT8 instance: instance id
+# UINT32 method_id: method to call
+# const ACPI_BUFFER* in: input data
+# ACPI_BUFFER* out: output buffer
+#
+METHOD ACPI_STATUS evaluate_call {
+	device_t	dev;
+	const char	*guid_string;
+	UINT8		instance;
+	UINT32		method_id;
+	const ACPI_BUFFER *in;
+	ACPI_BUFFER	*out;
+};
+
+#
+# Get content of a WMI block
+#
+# device_t dev:  Device to use
+# const char* guid_string:  String form of the GUID
+# UINT8 instance: instance id
+# ACPI_BUFFER* out: output buffer
+#
+METHOD ACPI_STATUS get_block {
+	device_t	dev;
+	const char	*guid_string;
+	UINT8		instance;
+	ACPI_BUFFER	*out;
+};
+#
+# Write to a WMI data block
+#
+# device_t dev:  Device to use
+# const char* guid_string:  String form of the GUID
+# UINT8 instance: instance id
+# const ACPI_BUFFER* in: input data
+#
+METHOD ACPI_STATUS set_block {
+	device_t	dev;
+	const char	*guid_string;
+	UINT8		instance;
+	const ACPI_BUFFER *in;
+};
+
+#
+# Install wmi event handler
+#
+# device_t dev:  Device to use
+# const char* guid_string:  String form of the GUID
+# ACPI_NOTIFY_HANDLER handler: Handler
+# void* data: Payload
+#
+METHOD ACPI_STATUS install_event_handler {
+	device_t	dev;
+	const char	*guid_string;
+	ACPI_NOTIFY_HANDLER handler;
+	void		*data;
+};
+
+#
+# Remove wmi event handler
+#
+# device_t dev:  Device to use
+# const char* guid_string:  String form of the GUID
+#
+METHOD ACPI_STATUS remove_event_handler {
+	device_t	dev;
+	const char	*guid_string;
+};
+
+
+#
+# Get event data associated to an event
+#
+# device_t dev:  Device to use
+# UINT32 event_id: event id
+# ACPI_BUFFER* out: output buffer
+#
+METHOD ACPI_STATUS get_event_data {
+	device_t	dev;
+	UINT32		event_id;
+	ACPI_BUFFER	*out;
+};
--- sys/modules/acpi/acpi_hp/Makefile~	2009-06-21 12:09:58.000000000 +0200
+++ sys/modules/acpi/acpi_hp/Makefile	2009-06-21 12:10:33.000000000 +0100
@@ -0,0 +1,9 @@
+# $FreeBSD: src/sys/modules/acpi/acpi_hp/Makefile,v 1.3 2004/12/13 04:59:35 imp Exp $
+
+.PATH:		${.CURDIR}/../../../dev/acpi_support
+KMOD=		acpi_hp
+CFLAGS+=	-I${.CURDIR}/../../../contrib/dev/acpica
+SRCS=		acpi_hp.c opt_acpi.h device_if.h bus_if.h acpi_if.h acpi_wmi_if.h
+SRCS+=		opt_ddb.h
+
+.include <bsd.kmod.mk>
--- sys/modules/acpi/acpi_wmi/Makefile~	2009-06-21 12:09:58.000000000 +0200
+++ sys/modules/acpi/acpi_wmi/Makefile	2009-06-21 12:10:39.000000000 +0100
@@ -0,0 +1,10 @@
+#	$FreeBSD: src/sys/modules/acpi/acpi_wmi/Makefile,v 1.7 2007/06/24 20:36:51 njl Exp $
+
+.PATH:	${.CURDIR}/../../../dev/acpi_support
+
+KMOD=	acpi_wmi
+CFLAGS+= -I${.CURDIR}/../../../contrib/dev/acpica
+SRCS=	acpi_wmi.c
+SRCS+=	opt_acpi.h acpi_if.h bus_if.h device_if.h acpi_wmi_if.h
+
+.include <bsd.kmod.mk>
--- sys/conf/files.i386~	2008-12-29 04:46:14.000000000 +0100
+++ sys/conf/files.i386	2009-06-21 12:10:14.000000000 +0100
@@ -226,6 +226,7 @@
 dev/syscons/scvtb.c		optional sc
 dev/uart/uart_cpu_i386.c	optional uart
 dev/acpica/acpi_if.m		standard
+dev/acpi_support/acpi_wmi_if.m  standard
 dev/wpi/if_wpi.c		optional wpi
 i386/acpica/OsdEnvironment.c	optional acpi
 i386/acpica/acpi_machdep.c	optional acpi
--- sys/conf/files~	2008-12-29 04:45:44.000000000 +0100
+++ sys/conf/files	2009-06-21 12:10:44.000000000 +0100
@@ -375,9 +375,11 @@
 dev/aac/aac_disk.c		optional aac
 dev/aac/aac_linux.c		optional aac compat_linux
 dev/aac/aac_pci.c		optional aac pci
+dev/acpi_support/acpi_wmi.c     optional acpi_wmi acpi
 dev/acpi_support/acpi_aiboost.c	optional acpi_aiboost acpi
 dev/acpi_support/acpi_asus.c	optional acpi_asus acpi
 dev/acpi_support/acpi_fujitsu.c	optional acpi_fujitsu acpi
+dev/acpi_support/acpi_hp.c      optional acpi_hp acpi
 dev/acpi_support/acpi_ibm.c	optional acpi_ibm acpi
 dev/acpi_support/acpi_panasonic.c optional acpi_panasonic acpi
 dev/acpi_support/acpi_sony.c	optional acpi_sony acpi
--- sys/conf/kmod.mk~	2008-12-29 04:44:41.000000000 +0100
+++ sys/conf/kmod.mk	2009-06-21 12:10:41.000000000 +0100
@@ -321,7 +321,7 @@
 .endfor
 .endif
 
-MFILES?= dev/acpica/acpi_if.m dev/ata/ata_if.m dev/eisa/eisa_if.m \
+MFILES?= dev/acpica/acpi_if.m dev/acpi_support/acpi_wmi_if.m dev/agp/agp_if.m dev/ata/ata_if.m dev/eisa/eisa_if.m \
 	dev/iicbus/iicbb_if.m dev/iicbus/iicbus_if.m \
 	dev/mmc/mmcbr_if.m dev/mmc/mmcbus_if.m \
 	dev/mii/miibus_if.m dev/ofw/ofw_bus_if.m \
--- share/man/man4/man4.i386/Makefile~	2009-06-21 12:36:50.000000000 +0200
+++ share/man/man4/man4.i386/Makefile	2009-06-21 12:36:50.000000000 +0200
@@ -3,10 +3,12 @@
 MAN=	acpi_aiboost.4 \
 	acpi_asus.4 \
 	acpi_fujitsu.4 \
+	acpi_hp.4 \
 	acpi_ibm.4 \
 	acpi_panasonic.4 \
 	acpi_sony.4 \
 	acpi_toshiba.4 \
+	acpi_wmi.4 \
 	aic.4 \
 	alpm.4 \
 	amdpm.4 \

--------------000302080806090206000000
Content-Type: text/plain;
 name="acpi_wmi.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="acpi_wmi.c"

/*-
 * Copyright (c) 2009 Michael Gmelin <freebsd@grem.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/*
 * Driver for acpi-wmi mapping, provides an interface for vendor specific
 * implementations (e.g. HP and Acer laptops).
 * Inspired by the ACPI-WMI mapping driver (c) 2008-2008 Carlos Corbacho which
 * implements this functionality for Linux.
 *
 * WMI and ACPI: http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
 * acpi-wmi for Linux: http://www.kernel.org
 */

#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sbuf.h>
#include <sys/module.h>
#include <sys/bus.h>

#include <contrib/dev/acpica/acpi.h>
#include <dev/acpica/acpivar.h>
#include "acpi_wmi_if.h"

MALLOC_DEFINE(M_ACPIWMI, "acpiwmi", "ACPI-WMI mapping");

#define _COMPONENT	ACPI_OEM
ACPI_MODULE_NAME("ACPI_WMI");

#define ACPI_WMI_REGFLAG_EXPENSIVE	0x1 /* GUID flag: Expensive operation */
#define ACPI_WMI_REGFLAG_METHOD		0x2	/* GUID flag: Method call */
#define ACPI_WMI_REGFLAG_STRING		0x4	/* GUID flag: String */
#define ACPI_WMI_REGFLAG_EVENT		0x8	/* GUID flag: Event */

/*
 * acpi_wmi driver private structure
 */
struct acpi_wmi_softc {
	device_t	wmi_dev;	/* wmi device id */
	ACPI_HANDLE	wmi_handle;	/* handle of the PNP0C14 node */
	device_t	ec_dev;		/* acpi_ec0 */
	struct cdev	*wmistat_dev_t;	/* wmistat device handle */
	struct sbuf	wmistat_sbuf;	/* sbuf for /dev/wmistat output */
	pid_t		wmistat_open_pid; /* pid operating on /dev/wmistat */
	int		wmistat_bufptr;	/* /dev/wmistat ptr to buffer position */
};

/*
 * Struct that holds information about
 * about a single GUID entry in _WDG
 */
struct guid_info {
	char	guid[16];	/* 16 byte non human readable GUID */
	char	oid[2];		/* object id or event notify id (first byte) */
	UINT8	max_instance;	/* highest instance known for this GUID */
	UINT8	flags;		/* ACPI_WMI_REGFLAG_%s */
};

/* WExx event generation state (on/off) */
enum event_generation_state {
	EVENT_GENERATION_ON = 1,
	EVENT_GENERATION_OFF = 0
};


/*
 * Information about one entry in _WDG.
 * List of those is used to lookup information by GUID.
 */
struct wmi_info {
	TAILQ_ENTRY(wmi_info)	wmi_list;
	struct guid_info	ginfo;		/* information on guid */
	ACPI_NOTIFY_HANDLER	event_handler;/* client provided event handler */
	void			*event_handler_user_data; /* ev handler cookie  */
};

TAILQ_HEAD(wmi_info_list_head, wmi_info)
    wmi_info_list = TAILQ_HEAD_INITIALIZER(wmi_info_list);

ACPI_SERIAL_DECL(acpi_wmi, "ACPI-WMI Mapping");

/* public interface - declaration */
/* standard device interface*/
static int		acpi_wmi_probe(device_t dev);
static int		acpi_wmi_attach(device_t dev);
static int		acpi_wmi_detach(device_t dev);
/* see acpi_wmi_if.m */
static int		acpi_wmi_provides_guid_string_method(device_t dev,
			    const char *guid_string);
static ACPI_STATUS	acpi_wmi_evaluate_call_method(device_t dev,
			    const char *guid_string, UINT8 instance,
			    UINT32 method_id, const ACPI_BUFFER *in,
			    ACPI_BUFFER *out);
static ACPI_STATUS	acpi_wmi_install_event_handler_method(device_t dev,
			    const char *guid_string, ACPI_NOTIFY_HANDLER handler,
			    void *data);
static ACPI_STATUS	acpi_wmi_remove_event_handler_method(device_t dev,
			    const char *guid_string);
static ACPI_STATUS	acpi_wmi_get_event_data_method(device_t dev,
			    UINT32 event_id, ACPI_BUFFER *out);
static ACPI_STATUS	acpi_wmi_get_block_method(device_t dev,
			    const char *guid_string,
			    UINT8 instance, ACPI_BUFFER *out);
static ACPI_STATUS	acpi_wmi_set_block_method(device_t dev,
			    const char *guid_string,
			    UINT8 instance, const ACPI_BUFFER *in);
/* private interface - declaration */
/* callbacks */
static void		acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify,
			    void *context);
static ACPI_STATUS	acpi_wmi_ec_handler(UINT32 function,
			    ACPI_PHYSICAL_ADDRESS address, UINT32 width,
			    ACPI_INTEGER *value, void *context,
			    void *region_context);
/* helpers */
static ACPI_STATUS	acpi_wmi_read_wdg_blocks(ACPI_HANDLE h);
static ACPI_STATUS	acpi_wmi_toggle_we_event_generation(device_t dev,
			    struct wmi_info *winfo,
			    enum event_generation_state state);
static int		acpi_wmi_guid_string_to_guid(const UINT8 *guid_string,
			    UINT8 *guid);
static struct wmi_info* acpi_wmi_lookup_wmi_info_by_guid_string(
			    const char *guid_string);

static d_open_t acpi_wmi_wmistat_open;
static d_close_t acpi_wmi_wmistat_close;
static d_read_t acpi_wmi_wmistat_read;

/* handler /dev/wmistat device */
static struct cdevsw wmistat_cdevsw = {
	.d_version = D_VERSION,
	.d_open = acpi_wmi_wmistat_open,
	.d_close = acpi_wmi_wmistat_close,
	.d_read = acpi_wmi_wmistat_read,
	.d_name = "wmistat",
};


static device_method_t acpi_wmi_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,	acpi_wmi_probe),
	DEVMETHOD(device_attach, acpi_wmi_attach),
	DEVMETHOD(device_detach, acpi_wmi_detach),

	/* acpi_wmi interface */
	DEVMETHOD(acpi_wmi_provides_guid_string,
		    acpi_wmi_provides_guid_string_method),
	DEVMETHOD(acpi_wmi_evaluate_call, acpi_wmi_evaluate_call_method),
	DEVMETHOD(acpi_wmi_install_event_handler,
		    acpi_wmi_install_event_handler_method),
	DEVMETHOD(acpi_wmi_remove_event_handler,
		    acpi_wmi_remove_event_handler_method),
	DEVMETHOD(acpi_wmi_get_event_data, acpi_wmi_get_event_data_method),
	DEVMETHOD(acpi_wmi_get_block, acpi_wmi_get_block_method),
	DEVMETHOD(acpi_wmi_set_block, acpi_wmi_set_block_method),

	{0, 0}
};

static driver_t acpi_wmi_driver = {
	"acpi_wmi",
	acpi_wmi_methods,
	sizeof(struct acpi_wmi_softc),
};

static devclass_t acpi_wmi_devclass;
DRIVER_MODULE(acpi_wmi, acpi, acpi_wmi_driver, acpi_wmi_devclass, 0, 0);
MODULE_VERSION(acpi_wmi, 1);
MODULE_DEPEND(acpi_wmi, acpi, 1, 1, 1);
static char *wmi_ids[] = {"PNP0C14", "PNP0c14", NULL};

/*
 * Probe for the PNP0C14 ACPI node
 */
static int
acpi_wmi_probe(device_t dev)
{
	if (acpi_disabled("wmi") ||
	    ACPI_ID_PROBE(device_get_parent(dev), dev, wmi_ids) == NULL)
		return (ENXIO);
	device_set_desc(dev, "ACPI-WMI mapping");

	return (0);
}

/*
 * Attach the device by:
 * - Looking for the first ACPI EC device
 * - Install the notify handler
 * - Install the EC address space handler
 * - Look for the _WDG node and read GUID information blocks
 */
static int
acpi_wmi_attach(device_t dev)
{
	struct acpi_wmi_softc *sc;
	int ret;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
	sc = device_get_softc(dev);
	ret = ENXIO;

	ACPI_SERIAL_BEGIN(acpi_wmi);
	sc->wmi_dev = dev;
	sc->wmi_handle = acpi_get_handle(dev);
	TAILQ_INIT(&wmi_info_list);
	/* XXX Only works with one EC, but nearly all systems only have one. */
	if ((sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0))
	    == NULL)
		device_printf(dev, "cannot find EC device\n");
	else if (ACPI_FAILURE((status = AcpiInstallNotifyHandler(sc->wmi_handle,
		    ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler, sc))))
		device_printf(sc->wmi_dev, "couldn't install notify handler - %s\n",
		    AcpiFormatException(status));
	else if (ACPI_FAILURE((status = AcpiInstallAddressSpaceHandler(
		    sc->wmi_handle, ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler,
		    NULL, sc)))) {
		device_printf(sc->wmi_dev, "couldn't install EC handler - %s\n",
		    AcpiFormatException(status));
		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
		    acpi_wmi_notify_handler);
	} else if (ACPI_FAILURE((status = acpi_wmi_read_wdg_blocks(
		    sc->wmi_handle)))) {
		device_printf(sc->wmi_dev, "couldn't parse _WDG - %s\n",
		    AcpiFormatException(status));
		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
		    acpi_wmi_notify_handler);
		AcpiRemoveAddressSpaceHandler(sc->wmi_handle, ACPI_ADR_SPACE_EC,
		    acpi_wmi_ec_handler);
	} else {
		sc->wmistat_dev_t = make_dev(&wmistat_cdevsw, 0, UID_ROOT,
		    GID_WHEEL, 0644, "wmistat");
		sc->wmistat_dev_t->si_drv1 = sc;
		sc->wmistat_open_pid = 0;
		sc->wmistat_bufptr = -1;
		ret = 0;
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (ret);
}

/*
 * Detach the driver by:
 * - Removing notification handler
 * - Removing address space handler
 * - Turning off event generation for all WExx event activated by
 *   child drivers
 */
static int
acpi_wmi_detach(device_t dev)
{
	struct wmi_info *winfo, *tmp;
	struct acpi_wmi_softc *sc;
	int ret;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
	sc = device_get_softc(dev);
	ACPI_SERIAL_BEGIN(acpi_wmi);

	if (sc->wmistat_open_pid != 0) {
		ret = EBUSY;
	} else {
		AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY,
		    acpi_wmi_notify_handler);
		AcpiRemoveAddressSpaceHandler(sc->wmi_handle,
		    ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler);
		TAILQ_FOREACH_SAFE(winfo, &wmi_info_list, wmi_list, tmp) {
			if (winfo->event_handler)
				acpi_wmi_toggle_we_event_generation(dev,
				    winfo, EVENT_GENERATION_OFF);
			TAILQ_REMOVE(&wmi_info_list, winfo, wmi_list);
			free(winfo, M_ACPIWMI);
		}
		if (sc->wmistat_bufptr != -1) {
			sbuf_delete(&sc->wmistat_sbuf);
			sc->wmistat_bufptr = -1;
		}
		sc->wmistat_open_pid = 0;
		destroy_dev(sc->wmistat_dev_t);
		ret = 0;
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (ret);
}


/*
 * Check if the given GUID string (human readable format
 * AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP)
 * exists within _WDG
 */
static int
acpi_wmi_provides_guid_string_method(device_t dev, const char *guid_string)
{
	int ret;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
	ACPI_SERIAL_BEGIN(acpi_wmi);
	ret = (acpi_wmi_lookup_wmi_info_by_guid_string(guid_string) == NULL)?0:1;
	ACPI_SERIAL_END(acpi_wmi);

	return (ret);
}

/*
 * Call a method "method_id" on the given GUID block
 * write result into user provided output buffer
 */
static ACPI_STATUS
acpi_wmi_evaluate_call_method(device_t dev, const char *guid_string,
    UINT8 instance, UINT32 method_id, const ACPI_BUFFER *in, ACPI_BUFFER *out)
{
	ACPI_OBJECT params[3];
	ACPI_OBJECT_LIST input;
	char method[5] = "WMxx";
	struct wmi_info *winfo;
	struct acpi_wmi_softc *sc;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = device_get_softc(dev);
	ACPI_SERIAL_BEGIN(acpi_wmi);
	if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
		    == NULL)
		status = AE_NOT_FOUND;
	else if (!(winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
		status = AE_BAD_DATA;
	else if (instance > winfo->ginfo.max_instance)
		status = AE_BAD_PARAMETER;
	else {
		params[0].Type = ACPI_TYPE_INTEGER;
		params[0].Integer.Value = instance;
		params[1].Type = ACPI_TYPE_INTEGER;
		params[1].Integer.Value = method_id;
		input.Pointer = params;
		input.Count = 2;
		if (in) {
			params[2].Type =
			    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING)
			    ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER;
			params[2].Buffer.Length = in->Length;
			params[2].Buffer.Pointer = in->Pointer;
			input.Count = 3;
		}
		method[2] = winfo->ginfo.oid[0];
		method[3] = winfo->ginfo.oid[1];
		status = AcpiEvaluateObject(sc->wmi_handle, method,
			    &input, out);
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (status);
}

/*
 * Install a user provided event_handler on the given GUID
 * provided *data will be passed on callback
 * If there is already an existing event handler registered it will be silently
 * discarded
 */
static ACPI_STATUS
acpi_wmi_install_event_handler_method(device_t dev, const char *guid_string,
    ACPI_NOTIFY_HANDLER event_handler, void *data)
{
	struct wmi_info *winfo;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	status = AE_OK;
	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (guid_string == NULL || event_handler == NULL)
		status = AE_BAD_PARAMETER;
	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
		    == NULL)
		status = AE_NOT_EXIST;
	else if (winfo->event_handler != NULL ||
		(status = acpi_wmi_toggle_we_event_generation(dev, winfo,
		    EVENT_GENERATION_ON)) == AE_OK) {
		winfo->event_handler = event_handler;
		winfo->event_handler_user_data = data;
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (status);
}

/*
 * Remove a previously installed event handler from the given GUID
 * If there was none installed, this call is silently discarded and
 * reported as AE_OK
 */
static ACPI_STATUS
acpi_wmi_remove_event_handler_method(device_t dev, const char *guid_string)
{
	struct wmi_info *winfo;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	status = AE_OK;
	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (guid_string &&
	    (winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
	    != NULL && winfo->event_handler) {
		status = acpi_wmi_toggle_we_event_generation(dev, winfo,
			    EVENT_GENERATION_OFF);
		winfo->event_handler = NULL;
		winfo->event_handler_user_data = NULL;
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (status);
}

/*
 * Get details on an event received through a callback registered
 * through ACPI_WMI_REMOVE_EVENT_HANDLER into a user provided output buffer.
 * (event_id equals "notify" passed in the callback)
 */
static ACPI_STATUS
acpi_wmi_get_event_data_method(device_t dev, UINT32 event_id, ACPI_BUFFER *out)
{
	ACPI_OBJECT_LIST input;
	ACPI_OBJECT params[1];
	struct acpi_wmi_softc *sc;
	struct wmi_info *winfo;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = device_get_softc(dev);
	status = AE_NOT_FOUND;	
	ACPI_SERIAL_BEGIN(acpi_wmi);
	params[0].Type = ACPI_TYPE_INTEGER;
	params[0].Integer.Value = event_id;
	input.Pointer = params;
	input.Count = 1;
	TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) &&
		    ((UINT8) winfo->ginfo.oid[0] == event_id)) {
			status = AcpiEvaluateObject(sc->wmi_handle, "_WED",
				    &input, out);
			break;
		}
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (status);
}

/*
 * Read a block of data from the given GUID (using WQxx (query))
 * Will be returned in a user provided buffer (out).
 * If the method is marked as expensive (ACPI_WMI_REGFLAG_EXPENSIVE)
 * we will first call the WCxx control method to lock the node to
 * lock the node for data collection and release it afterwards.
 * (Failed WCxx calls are ignored to "support" broken implementations)
 */
static ACPI_STATUS
acpi_wmi_get_block_method(device_t dev, const char *guid_string, UINT8 instance,
	ACPI_BUFFER *out)
{
	char wc_method[5] = "WCxx";
	char wq_method[5] = "WQxx";
	ACPI_OBJECT_LIST wc_input;
	ACPI_OBJECT_LIST wq_input;
	ACPI_OBJECT wc_params[1];
	ACPI_OBJECT wq_params[1];
	ACPI_HANDLE wc_handle;
	struct acpi_wmi_softc *sc;
	struct wmi_info *winfo;
	ACPI_STATUS status;
	ACPI_STATUS wc_status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = device_get_softc(dev);
	wc_status = AE_ERROR;
	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (guid_string == NULL || out == NULL)
		status = AE_BAD_PARAMETER;
	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
		    == NULL)
		status = AE_ERROR;
	else if (instance > winfo->ginfo.max_instance)
		status = AE_BAD_PARAMETER;
	else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) ||
	    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
		status = AE_ERROR;
	else {
		wq_params[0].Type = ACPI_TYPE_INTEGER;
		wq_params[0].Integer.Value = instance;
		wq_input.Pointer = wq_params;
		wq_input.Count = 1;
		if (winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE) {
			wc_params[0].Type = ACPI_TYPE_INTEGER;
			wc_params[0].Integer.Value = 1;
			wc_input.Pointer = wc_params;
			wc_input.Count = 1;
			wc_method[2] = winfo->ginfo.oid[0];
			wc_method[3] = winfo->ginfo.oid[1];
			wc_status = AcpiGetHandle(sc->wmi_handle, wc_method,
				    &wc_handle);
			if (ACPI_SUCCESS(wc_status))
				wc_status = AcpiEvaluateObject(wc_handle,
						wc_method, &wc_input, NULL);
		}
		wq_method[2] = winfo->ginfo.oid[0];
		wq_method[3] = winfo->ginfo.oid[1];
		status = AcpiEvaluateObject(sc->wmi_handle, wq_method,
			    &wq_input, out);
		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE)
		    && ACPI_SUCCESS(wc_status)) {
			wc_params[0].Integer.Value = 0;
			status = AcpiEvaluateObject(wc_handle, wc_method,
				    &wc_input, NULL);  /* XXX this might be
				    			 the wrong status to
				    			 return? */
		}
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (status);
}

/*
 * Write a block of data to the given GUID (using WSxx)
 */
static ACPI_STATUS
acpi_wmi_set_block_method(device_t dev, const char *guid_string, UINT8 instance,
	const ACPI_BUFFER *in)
{
	char method[5] = "WSxx";
	ACPI_OBJECT_LIST input;
	ACPI_OBJECT params[2];
	struct wmi_info *winfo;
	struct acpi_wmi_softc *sc;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = device_get_softc(dev);
	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (guid_string == NULL || in == NULL)
		status = AE_BAD_DATA;
	else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(guid_string))
		    == NULL)
		status = AE_ERROR;
	else if (instance > winfo->ginfo.max_instance)
		status = AE_BAD_PARAMETER;
	else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) ||
		    (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD))
		status = AE_ERROR;
	else {
		params[0].Type = ACPI_TYPE_INTEGER;
		params[0].Integer.Value = instance;
		input.Pointer = params;
		input.Count = 2;
		params[1].Type = (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING)
		    ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER;
		params[1].Buffer.Length = in->Length;
		params[1].Buffer.Pointer = in->Pointer;
		method[2] = winfo->ginfo.oid[0];
		method[3] = winfo->ginfo.oid[1];
		status = AcpiEvaluateObject(sc->wmi_handle, method,
			    &input, NULL);
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (status);
}

/*
 * Handle events received and dispatch them to
 * stakeholders that registered through ACPI_WMI_INSTALL_EVENT_HANDLER
 */
static void
acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context)
{
	ACPI_NOTIFY_HANDLER handler;
	void *handler_data;
	struct wmi_info *winfo;

	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);

	handler = NULL;
	handler_data = NULL;
	ACPI_SERIAL_BEGIN(acpi_wmi);
	TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
		if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) &&
				((UINT8) winfo->ginfo.oid[0] == notify)) {
			if (winfo->event_handler) {
				handler = winfo->event_handler;
				handler_data = winfo->event_handler_user_data;
				break;
			}
		}
	}
	ACPI_SERIAL_END(acpi_wmi);
	if (handler) {
		handler(h, notify, handler_data);
	}
}

/*
 * Handle EC address space notifications reveived on the WDG node
 * (this mimics EcAddressSpaceHandler in acpi_ec.c)
 */
static ACPI_STATUS
acpi_wmi_ec_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address,
    UINT32 width, ACPI_INTEGER *value, void *context,
    void *region_context)
{
	struct acpi_wmi_softc *sc;
	int i;
	ACPI_INTEGER ec_data;
	UINT8 ec_addr;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)Address);

	sc = (struct acpi_wmi_softc *)context;
	if (width % 8 != 0 || value == NULL || context == NULL)
		return (AE_BAD_PARAMETER);
	if (address + (width / 8) - 1 > 0xFF)
		return (AE_BAD_ADDRESS);
	if (function == ACPI_READ)
		*value = 0;
	ec_addr = address;
	status = AE_ERROR;

	for (i = 0; i < width; i += 8, ++ec_addr) {
		switch (function) {
		case ACPI_READ:
			status = ACPI_EC_READ(sc->ec_dev, ec_addr, &ec_data, 1);
			if (ACPI_SUCCESS(status))
				*value |= ((ACPI_INTEGER)ec_data) << i;
		break;
		case ACPI_WRITE:
			ec_data = (UINT8)((*value) >> i);
			status = ACPI_EC_WRITE(sc->ec_dev, ec_addr, ec_data, 1);
			break;
		default:
			device_printf(sc->wmi_dev,
			    "invalid acpi_wmi_ec_handler function %d\n",
			    function);
			status = AE_BAD_PARAMETER;
			break;
		}
		if (ACPI_FAILURE(status))
			break;
	}

	return (status);
}

/*
 * Read GUID blocks from the _WDG node
 * into wmi_info_list.
 */
static ACPI_STATUS
acpi_wmi_read_wdg_blocks(ACPI_HANDLE h)
{
	ACPI_BUFFER out = {ACPI_ALLOCATE_BUFFER, NULL};
	struct guid_info *ginfo;
	ACPI_OBJECT *obj;
	struct wmi_info *winfo;
	UINT32 i;
	UINT32 wdg_block_count;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	ACPI_SERIAL_ASSERT(acpi_wmi);
	if (ACPI_FAILURE(status = AcpiEvaluateObject(h, "_WDG", NULL, &out)))
		return (status);
	obj = (ACPI_OBJECT*) out.Pointer;
	wdg_block_count = obj->Buffer.Length / sizeof(struct guid_info);
	if ((ginfo = malloc(obj->Buffer.Length, M_ACPIWMI, M_NOWAIT))
		    == NULL) {
		AcpiOsFree(out.Pointer);
		return (AE_NO_MEMORY);
	}
	memcpy(ginfo, obj->Buffer.Pointer, obj->Buffer.Length);
	for (i = 0; i < wdg_block_count; ++i) {
		if ((winfo = malloc(sizeof(struct wmi_info), M_ACPIWMI,
			    M_NOWAIT | M_ZERO)) == NULL) {
			AcpiOsFree(out.Pointer);
			free(ginfo, M_ACPIWMI);
			return (AE_NO_MEMORY);
		}
		winfo->ginfo = ginfo[i];
		TAILQ_INSERT_TAIL(&wmi_info_list, winfo, wmi_list);
	}
	AcpiOsFree(out.Pointer);
	free(ginfo, M_ACPIWMI);

	return (status);
}

/*
 * Toggle event generation in for the given GUID (passed by winfo)
 * Turn on to get notified (through acpi_wmi_notify_handler) if events happen
 * on the given GUID.
 */
static ACPI_STATUS
acpi_wmi_toggle_we_event_generation(device_t dev, struct wmi_info *winfo,
    enum event_generation_state state)
{
	char method[5] = "WExx";
	ACPI_OBJECT_LIST input;
	ACPI_OBJECT params[1];
	struct acpi_wmi_softc *sc;
	ACPI_STATUS status;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = device_get_softc(dev);
	ACPI_SERIAL_ASSERT(acpi_wmi);
	params[0].Type = ACPI_TYPE_INTEGER;
	params[0].Integer.Value = state==EVENT_GENERATION_ON?1:0;
	input.Pointer = params;
	input.Count = 1;
	
	UINT8 hi = ((UINT8) winfo->ginfo.oid[0]) >> 4;
	UINT8 lo = ((UINT8) winfo->ginfo.oid[0]) & 0xf;
	method[2] = (hi > 9 ? hi + 55: hi + 48);
	method[3] = (lo > 9 ? lo + 55: lo + 48);
	status = AcpiEvaluateObject(sc->wmi_handle, method, &input, NULL);
	if (status == AE_NOT_FOUND) status = AE_OK;

	return (status);
}

/*
 * Convert given two digit hex string (hexin) to an UINT8 referenced
 * by byteout.
 * Return != 0 if the was a problem (invalid input)
 */
static __inline int acpi_wmi_hex_to_int(const UINT8 *hexin, UINT8 *byteout)
{
	unsigned int hi;
	unsigned int lo;

	hi = hexin[0];
	lo = hexin[1];
	if ('0' <= hi && hi <= '9')
		hi -= '0';
	else if ('A' <= hi && hi <= 'F')
		hi -= ('A' - 10);
	else if ('a' <= hi && hi <= 'f')
		hi -= ('a' - 10);
	else
		return (1);
	if ('0' <= lo && lo <= '9')
		lo -= '0';
	else if ('A' <= lo && lo <= 'F')
		lo -= ('A' - 10);
	else if ('a' <= lo && lo <= 'f')
		lo -= ('a' - 10);
	else
		return (1);
	*byteout = (hi << 4) + lo;

	return (0);
}

/*
 * Convert a human readable 36 character GUID into a 16byte
 * machine readable one.
 * The basic algorithm looks as follows:
 * Input:  AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP
 * Output: DCBAFEHGIJKLMNOP
 * (AA BB CC etc. represent two digit hex numbers == bytes)
 * Return != 0 if passed guid string is invalid
 */
static int
acpi_wmi_guid_string_to_guid(const UINT8 *guid_string, UINT8 *guid)
{
	static const int mapping[20] = {3, 2, 1, 0, -1, 5, 4, -1, 7, 6, -1,
	    8, 9, -1, 10, 11, 12, 13, 14, 15};
	int i;

	for (i = 0; i < 20; ++i, ++guid_string) {
		if (mapping[i] >= 0) {
			if (acpi_wmi_hex_to_int(guid_string,
			    &guid[mapping[i]]))
				return (-1);
			++guid_string;
		} else if (*guid_string != '-')
			return (-1);
	}

	return (0);
}

/*
 * Lookup a wmi_info structure in wmi_list based on a
 * human readable GUID
 * Return NULL if the GUID is unknown in the _WDG
 */
static struct wmi_info*
acpi_wmi_lookup_wmi_info_by_guid_string(const char *guid_string)
{
	char guid[16];
	struct wmi_info *winfo;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	ACPI_SERIAL_ASSERT(acpi_wmi);

	if (!acpi_wmi_guid_string_to_guid(guid_string, guid)) {
		TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
			if (!memcmp(winfo->ginfo.guid, guid, 16)) {
				return (winfo);
			}
		}
	}

	return (NULL);
}

/*
 * open wmistat device
 */
static int
acpi_wmi_wmistat_open(struct cdev* dev, int flags, int mode, struct thread *td)
{
	struct acpi_wmi_softc *sc;
	int ret;

	if (dev == NULL || dev->si_drv1 == NULL)
		return (EBADF);
	sc = dev->si_drv1;

	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (sc->wmistat_open_pid != 0) {
		ret = EBUSY;
	}
	else {
		if (sbuf_new(&sc->wmistat_sbuf, NULL, 4096, SBUF_AUTOEXTEND)
			    == NULL) {
			ret = ENXIO;
		} else {
			sc->wmistat_open_pid = td->td_proc->p_pid;
			sc->wmistat_bufptr = 0;
			ret = 0;
		}
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (ret);
}

/*
 * close wmistat device
 */
static int
acpi_wmi_wmistat_close(struct cdev* dev, int flags, int mode,
    struct thread *td)
{
	struct acpi_wmi_softc *sc;
	int ret;

	if (dev == NULL || dev->si_drv1 == NULL)
		return (EBADF);
	sc = dev->si_drv1;

	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (sc->wmistat_open_pid == 0) {
		ret = EBADF;
	}
	else {
		if (sc->wmistat_bufptr != -1) {
			sbuf_delete(&sc->wmistat_sbuf);
			sc->wmistat_bufptr = -1;
		}
		sc->wmistat_open_pid = 0;
		ret = 0;
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (ret);
}

/*
 * Read from wmistat guid information
 */
static int
acpi_wmi_wmistat_read(struct cdev *dev, struct uio *buf, int flag)
{
	struct acpi_wmi_softc *sc;
	struct wmi_info *winfo;
	int l;
	int ret;
	UINT8* guid;

	if (dev == NULL || dev->si_drv1 == NULL)
		return (EBADF);
	sc = dev->si_drv1;
	
	ACPI_SERIAL_BEGIN(acpi_wmi);
	if (sc->wmistat_open_pid != buf->uio_td->td_proc->p_pid ||
			sc->wmistat_bufptr == -1) {
		ret = EBADF;
	}
	else {
		if (!sbuf_done(&sc->wmistat_sbuf)) {
			sbuf_printf(&sc->wmistat_sbuf, "GUID                 "
				    "                 INST EXPE METH STR "
				    "EVENT OID\n");
			TAILQ_FOREACH(winfo, &wmi_info_list, wmi_list) {
				guid = (UINT8*)winfo->ginfo.guid;
				sbuf_printf(&sc->wmistat_sbuf,
					    "{%02X%02X%02X%02X-%02X%02X-"
					    "%02X%02X-%02X%02X-%02X%02X"
					    "%02X%02X%02X%02X} %3d %-5s",
					guid[3], guid[2], guid[1], guid[0],
					guid[5], guid[4],
					guid[7], guid[6],
					guid[8], guid[9],
					guid[10], guid[11], guid[12],
					guid[13], guid[14], guid[15],
					winfo->ginfo.max_instance,
					(winfo->ginfo.flags&
						ACPI_WMI_REGFLAG_EXPENSIVE)?
						"YES":"NO"
					);
				if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_METHOD)
					sbuf_printf(&sc->wmistat_sbuf,
						    "WM%c%c ",
						    winfo->ginfo.oid[0],
						    winfo->ginfo.oid[1]);
				else
					sbuf_printf(&sc->wmistat_sbuf, "NO   ");
				sbuf_printf(&sc->wmistat_sbuf, "%-4s",
					    (winfo->ginfo.flags&
					    ACPI_WMI_REGFLAG_STRING)?"YES":"NO"
					);
				if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_EVENT)
					sbuf_printf(&sc->wmistat_sbuf,
						    "0x%02X%s -\n",
						    (UINT8)winfo->ginfo.oid[0],
						    winfo->event_handler==NULL?
						    " ":"+");
				else
					sbuf_printf(&sc->wmistat_sbuf,
						    "NO    %c%c\n",
						    winfo->ginfo.oid[0],
						    winfo->ginfo.oid[1]);
			}
			sbuf_finish(&sc->wmistat_sbuf);
		}
		if (sbuf_len(&sc->wmistat_sbuf) <= 0) {
			sbuf_delete(&sc->wmistat_sbuf);
			sc->wmistat_bufptr = -1;
			sc->wmistat_open_pid = 0;
			ret = ENOMEM;
		} else {
			l = min(buf->uio_resid, sbuf_len(&sc->wmistat_sbuf) -
				    sc->wmistat_bufptr);
			ret = (l > 0)?uiomove(sbuf_data(&sc->wmistat_sbuf) +
				    sc->wmistat_bufptr, l, buf) : 0;
			sc->wmistat_bufptr += l;
		}
	}
	ACPI_SERIAL_END(acpi_wmi);

	return (ret);
}

--------------000302080806090206000000
Content-Type: text/plain;
 name="acpi_hp.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="acpi_hp.c"

/*-
 * Copyright (c) 2009 Michael Gmelin <freebsd@grem.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/*
 * Driver for extra ACPI-controlled features found on HP laptops
 * that use a WMI enabled BIOS (e.g. HP Compaq 8510p and 6510p).
 * Allows to control and read status of integrated hardware and read
 * BIOS settings through CMI.
 * Inspired by the hp-wmi driver, which implements a subset of these
 * features (hotkeys) on Linux.
 *
 * HP CMI whitepaper:
 *     http://h20331.www2.hp.com/Hpsub/downloads/cmi_whitepaper.pdf
 * wmi-hp for Linux:
 *     http://www.kernel.org
 * WMI and ACPI:
 *     http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx
 */

#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/sbuf.h>
#include <sys/module.h>
#include <sys/sysctl.h>

#include <contrib/dev/acpica/acpi.h>
#include <dev/acpica/acpivar.h>
#include "acpi_wmi_if.h"

#define _COMPONENT	ACPI_OEM
ACPI_MODULE_NAME("HP")

#define ACPI_HP_WMI_EVENT_GUID		"95F24279-4D7B-4334-9387-ACCDC67EF61C"
#define ACPI_HP_WMI_BIOS_GUID		"5FB7F034-2C63-45E9-BE91-3D44E2C707E4"
#define ACPI_HP_WMI_CMI_GUID		"2D114B49-2DFB-4130-B8FE-4A3C09E75133"

#define ACPI_HP_WMI_DISPLAY_COMMAND	0x1
#define ACPI_HP_WMI_HDDTEMP_COMMAND	0x2
#define ACPI_HP_WMI_ALS_COMMAND		0x3
#define ACPI_HP_WMI_DOCK_COMMAND	0x4
#define ACPI_HP_WMI_WIRELESS_COMMAND	0x5

#define ACPI_HP_METHOD_WLAN_ENABLED			1
#define ACPI_HP_METHOD_WLAN_RADIO			2
#define ACPI_HP_METHOD_WLAN_ON_AIR			3
#define ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON		4
#define ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF	5
#define ACPI_HP_METHOD_BLUETOOTH_ENABLED		6
#define ACPI_HP_METHOD_BLUETOOTH_RADIO			7
#define ACPI_HP_METHOD_BLUETOOTH_ON_AIR			8
#define ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON	9
#define ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF	10
#define ACPI_HP_METHOD_WWAN_ENABLED			11
#define ACPI_HP_METHOD_WWAN_RADIO			12
#define ACPI_HP_METHOD_WWAN_ON_AIR			13
#define ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON		14
#define ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF	15
#define ACPI_HP_METHOD_ALS				16
#define ACPI_HP_METHOD_DISPLAY				17
#define ACPI_HP_METHOD_HDDTEMP				18
#define ACPI_HP_METHOD_DOCK				19
#define ACPI_HP_METHOD_CMI_DETAIL			20

#define HP_MASK_WWAN_ON_AIR			0x1000000
#define HP_MASK_BLUETOOTH_ON_AIR		0x10000
#define HP_MASK_WLAN_ON_AIR			0x100
#define HP_MASK_WWAN_RADIO			0x8000000
#define HP_MASK_BLUETOOTH_RADIO			0x80000
#define HP_MASK_WLAN_RADIO			0x800
#define HP_MASK_WWAN_ENABLED			0x2000000
#define HP_MASK_BLUETOOTH_ENABLED		0x20000
#define HP_MASK_WLAN_ENABLED			0x200

#define ACPI_HP_CMI_DETAIL_PATHS		0x01
#define ACPI_HP_CMI_DETAIL_ENUMS		0x02
#define ACPI_HP_CMI_DETAIL_FLAGS		0x04

struct acpi_hp_inst_seq_pair {
	UINT32	sequence;	/* sequence number as suggested by cmi bios */
	UINT8	instance;	/* object instance on guid */
};

struct acpi_hp_softc {
	device_t	dev;
	ACPI_HANDLE	handle;
	device_t	wmi_dev;
	int		has_notify;		/* notification GUID found */
	int		has_cmi;		/* CMI GUID found */
	int		cmi_detail;		/* CMI detail level
						   (set by sysctl) */
	int		wlan_enable_if_radio_on;	/* set by sysctl */
	int		wlan_disable_if_radio_off;	/* set by sysctl */
	int		bluetooth_enable_if_radio_on;	/* set by sysctl */
	int		bluetooth_disable_if_radio_off;	/* set by sysctl */
	int		wwan_enable_if_radio_on;	/* set by sysctl */
	int		wwan_disable_if_radio_off;	/* set by sysctl */
	int		was_wlan_on_air;		/* last known WLAN
							   on air status */
	int		was_bluetooth_on_air;		/* last known BT
							   on air status */
	int		was_wwan_on_air;		/* last known WWAN
							   on air status */
	struct sysctl_ctx_list	*sysctl_ctx;
	struct sysctl_oid	*sysctl_tree;
	struct cdev	*hpcmi_dev_t;		/* hpcmi device handle */
	struct sbuf	hpcmi_sbuf;		/* /dev/hpcmi output sbuf */
	pid_t		hpcmi_open_pid;		/* pid operating on
						   /dev/hpcmi */
	int		hpcmi_bufptr;		/* current pointer position
						   in /dev/hpcmi output buffer
						 */
	int		cmi_order_size;		/* size of cmi_order list */
	struct acpi_hp_inst_seq_pair cmi_order[128];	/* list of CMI
			     instances ordered by BIOS suggested sequence */
};

static struct {
	char	*name;
	int	method;
	char	*description;
	int	access;
} acpi_hp_sysctls[] = {
	{
		.name		= "wlan_enabled",
		.method		= ACPI_HP_METHOD_WLAN_ENABLED,
		.description	= "Enable/Disable WLAN (WiFi)",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "wlan_radio",
		.method		= ACPI_HP_METHOD_WLAN_RADIO,
		.description	= "WLAN radio status",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "wlan_on_air",
		.method		= ACPI_HP_METHOD_WLAN_ON_AIR,
		.description	= "WLAN radio ready to use (enabled and radio)",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "wlan_enable_if_radio_on",
		.method		= ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON,
		.description	= "Enable WLAN if radio is turned on",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "wlan_disable_if_radio_off",
		.method		= ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF,
		.description	= "Disable WLAN if radio is turned off",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "bt_enabled",
		.method		= ACPI_HP_METHOD_BLUETOOTH_ENABLED,
		.description	= "Enable/Disable Bluetooth",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "bt_radio",
		.method		= ACPI_HP_METHOD_BLUETOOTH_RADIO,
		.description	= "Bluetooth radio status",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "bt_on_air",
		.method		= ACPI_HP_METHOD_BLUETOOTH_ON_AIR,
		.description	= "Bluetooth radio ready to use"
				    " (enabled and radio)",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "bt_enable_if_radio_on",
		.method		= ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON,
		.description	= "Enable bluetooth if radio is turned on",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "bt_disable_if_radio_off",
		.method		= ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF,
		.description	= "Disable bluetooth if radio is turned off",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "wwan_enabled",
		.method		= ACPI_HP_METHOD_WWAN_ENABLED,
		.description	= "Enable/Disable WWAN (UMTS)",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "wwan_radio",
		.method		= ACPI_HP_METHOD_WWAN_RADIO,
		.description	= "WWAN radio status",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "wwan_on_air",
		.method		= ACPI_HP_METHOD_WWAN_ON_AIR,
		.description	= "WWAN radio ready to use (enabled and radio)",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "wwan_enable_if_radio_on",
		.method		= ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON,
		.description	= "Enable WWAN if radio is turned on",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "wwan_disable_if_radio_off",
		.method		= ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF,
		.description	= "Disable WWAN if radio is turned off",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "als_enabled",
		.method		= ACPI_HP_METHOD_ALS,
		.description	= "Enable/Disable ALS (Ambient light sensor)",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},
	{
		.name		= "display",
		.method		= ACPI_HP_METHOD_DISPLAY,
		.description	= "Display status",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "hdd_temperature",
		.method		= ACPI_HP_METHOD_HDDTEMP,
		.description	= "HDD temperature",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "is_docked",
		.method		= ACPI_HP_METHOD_DOCK,
		.description	= "Docking station status",
		.access		= CTLTYPE_INT | CTLFLAG_RD
	},
	{
		.name		= "cmi_detail",
		.method		= ACPI_HP_METHOD_CMI_DETAIL,
		.description	= "Details shown in CMI output "
				    "(cat /dev/hpcmi)",
		.access		= CTLTYPE_INT | CTLFLAG_RW
	},

	{ NULL, 0, NULL, 0 }
};

ACPI_SERIAL_DECL(hp, "HP ACPI-WMI Mapping");

static int	acpi_hp_probe(device_t dev);
static int	acpi_hp_attach(device_t dev);
static int	acpi_hp_detach(device_t dev);

static void	acpi_hp_evaluate_auto_on_off(struct acpi_hp_softc* sc);
static int	acpi_hp_sysctl(SYSCTL_HANDLER_ARGS);
static int	acpi_hp_sysctl_set(struct acpi_hp_softc *sc, int method,
		    int arg, int oldarg);
static int	acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method);
static int	acpi_hp_exec_wmi_command(device_t wmi_dev, int command,
		    int is_write, int val);
static void	acpi_hp_notify(ACPI_HANDLE h, UINT32 notify, void *context);
static int	acpi_hp_get_cmi_block(device_t wmi_dev, const char* guid,
		    UINT8 instance, char* outbuf, size_t outsize,
		    UINT32* sequence, int detail);
static void	acpi_hp_hex_decode(char* buffer);

static d_open_t	acpi_hp_hpcmi_open;
static d_close_t acpi_hp_hpcmi_close;
static d_read_t	acpi_hp_hpcmi_read;

/* handler /dev/hpcmi device */
static struct cdevsw hpcmi_cdevsw = {
	.d_version = D_VERSION,
	.d_open = acpi_hp_hpcmi_open,
	.d_close = acpi_hp_hpcmi_close,
	.d_read = acpi_hp_hpcmi_read,
	.d_name = "hpcmi",
};

static device_method_t acpi_hp_methods[] = {
	DEVMETHOD(device_probe, acpi_hp_probe),
	DEVMETHOD(device_attach, acpi_hp_attach),
	DEVMETHOD(device_detach, acpi_hp_detach),
	{0, 0}
};

static driver_t	acpi_hp_driver = {
	"acpi_hp",
	acpi_hp_methods,
	sizeof(struct acpi_hp_softc),
};

static devclass_t acpi_hp_devclass;

DRIVER_MODULE(acpi_hp, acpi, acpi_hp_driver, acpi_hp_devclass,
		0, 0);
MODULE_DEPEND(acpi_hp, acpi_wmi, 1, 1, 1);
MODULE_DEPEND(acpi_hp, acpi, 1, 1, 1);

static void	
acpi_hp_evaluate_auto_on_off(struct acpi_hp_softc *sc)
{
	int wireless;
	int new_wlan_status;
	int new_bluetooth_status;
	int new_wwan_status;

	wireless = acpi_hp_exec_wmi_command(sc->wmi_dev,
		    ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
	new_wlan_status = -1;
	new_bluetooth_status = -1;
	new_wwan_status = -1;

	device_printf(sc->wmi_dev, "Wireless status is %x\n", wireless);
	if (sc->wlan_disable_if_radio_off && !(wireless & HP_MASK_WLAN_RADIO)
	    &&  (wireless & HP_MASK_WLAN_ENABLED)) {
		acpi_hp_exec_wmi_command(sc->wmi_dev,
		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x100);
		new_wlan_status = 0;
	}
	else if (sc->wlan_enable_if_radio_on && (wireless & HP_MASK_WLAN_RADIO)
		&&  !(wireless & HP_MASK_WLAN_ENABLED)) {
		acpi_hp_exec_wmi_command(sc->wmi_dev,
		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x101);
		new_wlan_status = 1;
	}
	if (sc->bluetooth_disable_if_radio_off &&
	    !(wireless & HP_MASK_BLUETOOTH_RADIO) &&
	    (wireless & HP_MASK_BLUETOOTH_ENABLED)) {
		acpi_hp_exec_wmi_command(sc->wmi_dev,
		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x200);
		new_bluetooth_status = 0;
	}
	else if (sc->bluetooth_enable_if_radio_on &&
		(wireless & HP_MASK_BLUETOOTH_RADIO) &&
		!(wireless & HP_MASK_BLUETOOTH_ENABLED)) {
		acpi_hp_exec_wmi_command(sc->wmi_dev,
		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x202);
		new_bluetooth_status = 1;
	}
	if (sc->wwan_disable_if_radio_off &&
	    !(wireless & HP_MASK_WWAN_RADIO) &&
	    (wireless & HP_MASK_WWAN_ENABLED)) {
		acpi_hp_exec_wmi_command(sc->wmi_dev,
		ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x400);
		new_wwan_status = 0;
	}
	else if (sc->wwan_enable_if_radio_on &&
		(wireless & HP_MASK_WWAN_RADIO) &&
		!(wireless & HP_MASK_WWAN_ENABLED)) {
		acpi_hp_exec_wmi_command(sc->wmi_dev,
		    ACPI_HP_WMI_WIRELESS_COMMAND, 1, 0x404);
		new_wwan_status = 1;
	}

	if (new_wlan_status == -1) {
		new_wlan_status = (wireless & HP_MASK_WLAN_ON_AIR);
		if ((new_wlan_status?1:0) != sc->was_wlan_on_air) {
			sc->was_wlan_on_air = sc->was_wlan_on_air?0:1;
			device_printf(sc->wmi_dev,
			    "WLAN on air changed to %i "
			    "(new_wlan_status is %i)\n",
			    sc->was_wlan_on_air, new_wlan_status);
			acpi_UserNotify("HP", sc->handle,
			    0xc0+sc->was_wlan_on_air);
		}
	}
	if (new_bluetooth_status == -1) {
		new_bluetooth_status = (wireless & HP_MASK_BLUETOOTH_ON_AIR);
		if ((new_bluetooth_status?1:0) != sc->was_bluetooth_on_air) {
			sc->was_bluetooth_on_air = sc->was_bluetooth_on_air?
			    0:1;
			device_printf(sc->wmi_dev, "BLUETOOTH on air changed"
			    " to %i (new_bluetooth_status is %i)\n",
			    sc->was_bluetooth_on_air, new_bluetooth_status);
			acpi_UserNotify("HP", sc->handle,
			    0xd0+sc->was_bluetooth_on_air);
		}
	}
	if (new_wwan_status == -1) {
		new_wwan_status = (wireless & HP_MASK_WWAN_ON_AIR);
		if ((new_wwan_status?1:0) != sc->was_wwan_on_air) {
			sc->was_wwan_on_air = sc->was_wwan_on_air?0:1;
			device_printf(sc->wmi_dev, "WWAN on air changed to %i"
			    " (new_wwan_status is %i)\n",
			    sc->was_wwan_on_air, new_wwan_status);
			acpi_UserNotify("HP", sc->handle,
			    0xe0+sc->was_wwan_on_air);
		}
	}
}

static int
acpi_hp_probe(device_t dev)
{
	if (acpi_disabled("hp") || device_get_unit(dev) != 0)
		return (ENXIO);
	device_set_desc(dev, "HP ACPI-WMI Mapping");

	return (0);
}

static int
acpi_hp_attach(device_t dev)
{
	struct acpi_hp_softc	*sc;
	struct acpi_softc	*acpi_sc;
	devclass_t		wmi_devclass;
	int arg;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);

	sc = device_get_softc(dev);
	sc->dev = dev;
	sc->handle = acpi_get_handle(dev);
	sc->has_notify = 0;
	sc->has_cmi = 0;
	sc->bluetooth_enable_if_radio_on = 0;
	sc->bluetooth_disable_if_radio_off = 0;
	sc->wlan_enable_if_radio_on = 0;
	sc->wlan_disable_if_radio_off = 0;
	sc->wlan_enable_if_radio_on = 0;
	sc->wlan_disable_if_radio_off = 0;
	sc->was_wlan_on_air = 0;
	sc->was_bluetooth_on_air = 0;
	sc->was_wwan_on_air = 0;
	sc->cmi_detail = 0;
	sc->cmi_order_size = -1;
	memset(sc->cmi_order, 0, sizeof(sc->cmi_order));
	acpi_sc = acpi_device_get_parent_softc(dev);

	if (!(wmi_devclass = devclass_find ("acpi_wmi"))) {
		device_printf(dev, "Couldn't find acpi_wmi devclass\n");
		return (EINVAL);
	}
	if (!(sc->wmi_dev = devclass_get_device(wmi_devclass, 0))) {
		device_printf(dev, "Couldn't find acpi_wmi device\n");
		return (EINVAL);
	}
	if (!ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev,
	    ACPI_HP_WMI_BIOS_GUID)) {
		device_printf(dev,
		    "WMI device does not provide the HP BIOS GUID\n");
		return (EINVAL);
	}
	if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev,
	    ACPI_HP_WMI_EVENT_GUID)) {
		device_printf(dev,
		    "HP event GUID detected, installing event handler\n");
		if (ACPI_WMI_INSTALL_EVENT_HANDLER(sc->wmi_dev,
		    ACPI_HP_WMI_EVENT_GUID, acpi_hp_notify, dev)) {
			device_printf(dev,
			    "Could not install notification handler!\n");
		}
		else {
			sc->has_notify = 1;
		}
	}
	if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_HP_WMI_CMI_GUID)) {
		device_printf(dev, "HP CMI GUID detected\n");
		sc->has_cmi = 1;
	}

	if (sc->has_cmi) {
		sc->hpcmi_dev_t = make_dev(&hpcmi_cdevsw, 0, UID_ROOT,
			    GID_WHEEL, 0644, "hpcmi");
		sc->hpcmi_dev_t->si_drv1 = sc;
		sc->hpcmi_open_pid = 0;
		sc->hpcmi_bufptr = -1;
	}

	ACPI_SERIAL_BEGIN(hp);

	sc->sysctl_ctx = device_get_sysctl_ctx(dev);
	sc->sysctl_tree = device_get_sysctl_tree(dev);
	for (int i = 0; acpi_hp_sysctls[i].name != NULL; ++i) {
		arg = 0;
		if ((!sc->has_notify &&
		    (acpi_hp_sysctls[i].method ==
			ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON ||
		    acpi_hp_sysctls[i].method ==
			ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF ||
		    acpi_hp_sysctls[i].method ==
			ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON ||
		    acpi_hp_sysctls[i].method ==
			ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF ||
		    acpi_hp_sysctls[i].method ==
			ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON ||
		    acpi_hp_sysctls[i].method ==
			ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF)) ||
		    (arg = acpi_hp_sysctl_get(sc,
		    acpi_hp_sysctls[i].method)) < 0) {
			continue;
		}
		if (acpi_hp_sysctls[i].method == ACPI_HP_METHOD_WLAN_ON_AIR) {
			sc->was_wlan_on_air = arg;
		}
		else if (acpi_hp_sysctls[i].method ==
			    ACPI_HP_METHOD_BLUETOOTH_ON_AIR) {
			sc->was_bluetooth_on_air = arg;
		}
		else if (acpi_hp_sysctls[i].method ==
			    ACPI_HP_METHOD_WWAN_ON_AIR) {
			sc->was_wwan_on_air = arg;
		}

		SYSCTL_ADD_PROC(sc->sysctl_ctx,
		SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO,
			acpi_hp_sysctls[i].name, acpi_hp_sysctls[i].access,
			sc, i, acpi_hp_sysctl, "I",
			acpi_hp_sysctls[i].description);
	}
	ACPI_SERIAL_END(hp);

	return (0);
}

static int
acpi_hp_detach(device_t dev)
{
	int ret;
	
	ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__);
	struct acpi_hp_softc *sc = device_get_softc(dev);
	if (sc->has_cmi && sc->hpcmi_open_pid != 0) {
		ret = EBUSY;
	}
	else {
		if (sc->has_notify) {
			ACPI_WMI_REMOVE_EVENT_HANDLER(dev,
			    ACPI_HP_WMI_EVENT_GUID);
		}
		if (sc->hpcmi_bufptr != -1) {
			sbuf_delete(&sc->hpcmi_sbuf);
			sc->hpcmi_bufptr = -1;
		}
		sc->hpcmi_open_pid = 0;
		destroy_dev(sc->hpcmi_dev_t);
		ret = 0;
	}

	return (ret);
}

static int
acpi_hp_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct acpi_hp_softc *sc;
	int	arg;
	int oldarg;
	int	error = 0;
	int	function;
	int	method;
	
	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);

	sc = (struct acpi_hp_softc *)oidp->oid_arg1;
	function = oidp->oid_arg2;
	method = acpi_hp_sysctls[function].method;

	ACPI_SERIAL_BEGIN(hp);
	arg = acpi_hp_sysctl_get(sc, method);
	oldarg = arg;
	error = sysctl_handle_int(oidp, &arg, 0, req);
	if (!error && req->newptr != NULL) {
		error = acpi_hp_sysctl_set(sc, method, arg, oldarg);
	}
	ACPI_SERIAL_END(hp);

	return (error);
}

static int
acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method)
{
	int val = 0;

	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
	ACPI_SERIAL_ASSERT(hp);

	switch (method) {
	case ACPI_HP_METHOD_WLAN_ENABLED:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_WLAN_ENABLED) != 0);
		break;
	case ACPI_HP_METHOD_WLAN_RADIO:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_WLAN_RADIO) != 0);
		break;
	case ACPI_HP_METHOD_WLAN_ON_AIR:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_WLAN_ON_AIR) != 0);
		break;
	case ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON:
		val = sc->wlan_enable_if_radio_on;
		break;
	case ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF:
		val = sc->wlan_disable_if_radio_off;
		break;
	case ACPI_HP_METHOD_BLUETOOTH_ENABLED:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_BLUETOOTH_ENABLED) != 0);
		break;
	case ACPI_HP_METHOD_BLUETOOTH_RADIO:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_BLUETOOTH_RADIO) != 0);
		break;
	case ACPI_HP_METHOD_BLUETOOTH_ON_AIR:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_BLUETOOTH_ON_AIR) != 0);
		break;
	case ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON:
		val = sc->bluetooth_enable_if_radio_on;
		break;
	case ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF:
		val = sc->bluetooth_disable_if_radio_off;
		break;
	case ACPI_HP_METHOD_WWAN_ENABLED:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_WWAN_ENABLED) != 0);
		break;
	case ACPI_HP_METHOD_WWAN_RADIO:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_WWAN_RADIO) != 0);
		break;
	case ACPI_HP_METHOD_WWAN_ON_AIR:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_WIRELESS_COMMAND, 0, 0);
		val = ((val & HP_MASK_WWAN_ON_AIR) != 0);
		break;
	case ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON:
		val = sc->wwan_enable_if_radio_on;
		break;
	case ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF:
		val = sc->wwan_disable_if_radio_off;
		break;
	case ACPI_HP_METHOD_ALS:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_ALS_COMMAND, 0, 0);
		break;
	case ACPI_HP_METHOD_DISPLAY:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_DISPLAY_COMMAND, 0, 0);
		break;
	case ACPI_HP_METHOD_HDDTEMP:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_HDDTEMP_COMMAND, 0, 0);
		break;
	case ACPI_HP_METHOD_DOCK:
		val = acpi_hp_exec_wmi_command(sc->wmi_dev,
			ACPI_HP_WMI_DOCK_COMMAND, 0, 0);
		break;
	case ACPI_HP_METHOD_CMI_DETAIL:
		val = sc->cmi_detail;
		break;
	}

	return (val);
}

static int
acpi_hp_sysctl_set(struct acpi_hp_softc *sc, int method, int arg, int oldarg)
{
	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
	ACPI_SERIAL_ASSERT(hp);

	if (method != ACPI_HP_METHOD_CMI_DETAIL)
		arg = arg?1:0;

	if (arg != oldarg) {
		switch (method) {
		case ACPI_HP_METHOD_WLAN_ENABLED:
			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
				    ACPI_HP_WMI_WIRELESS_COMMAND, 1,
				    arg?0x101:0x100));
		case ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON:
			sc->wlan_enable_if_radio_on = arg;
			acpi_hp_evaluate_auto_on_off(sc);
			break;
		case ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF:
			sc->wlan_disable_if_radio_off = arg;
			acpi_hp_evaluate_auto_on_off(sc);
			break;
		case ACPI_HP_METHOD_BLUETOOTH_ENABLED:
			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
				    ACPI_HP_WMI_WIRELESS_COMMAND, 1,
				    arg?0x202:0x200));
		case ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON:
			sc->bluetooth_enable_if_radio_on = arg;
			acpi_hp_evaluate_auto_on_off(sc);
			break;
		case ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF:
			sc->bluetooth_disable_if_radio_off = arg?1:0;
			acpi_hp_evaluate_auto_on_off(sc);
			break;
		case ACPI_HP_METHOD_WWAN_ENABLED:
			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
				    ACPI_HP_WMI_WIRELESS_COMMAND, 1,
				    arg?0x404:0x400));
		case ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON:
			sc->wwan_enable_if_radio_on = arg?1:0;
			acpi_hp_evaluate_auto_on_off(sc);
			break;
		case ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF:
			sc->wwan_disable_if_radio_off = arg?1:0;
			acpi_hp_evaluate_auto_on_off(sc);
			break;
		case ACPI_HP_METHOD_ALS:
			return (acpi_hp_exec_wmi_command(sc->wmi_dev,
				    ACPI_HP_WMI_ALS_COMMAND, 1,
				    arg?1:0));
		case ACPI_HP_METHOD_CMI_DETAIL:
			sc->cmi_detail = arg;
			break;
		}
	}

	return (0);
}

static __inline void
acpi_hp_free_buffer(ACPI_BUFFER* buf) {
	if (buf && buf->Pointer) {
		AcpiOsFree(buf->Pointer);
	}
}

static void
acpi_hp_notify(ACPI_HANDLE h, UINT32 notify, void *context)
{
	device_t dev = context;
	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);

	struct acpi_hp_softc *sc = device_get_softc(dev);
	ACPI_BUFFER response = { ACPI_ALLOCATE_BUFFER, NULL };
	ACPI_OBJECT *obj;
	ACPI_WMI_GET_EVENT_DATA(sc->wmi_dev, notify, &response);
	obj = (ACPI_OBJECT*) response.Pointer;
	if (obj && obj->Type == ACPI_TYPE_BUFFER && obj->Buffer.Length == 8) {
		if (*((UINT8 *) obj->Buffer.Pointer) == 0x5) {
			acpi_hp_evaluate_auto_on_off(sc);
		}
	}
	acpi_hp_free_buffer(&response);
}

static int
acpi_hp_exec_wmi_command(device_t wmi_dev, int command, int is_write, int val)
{
	UINT32 params[5] = { 0x55434553,
			     is_write?2:1,
			     command,
			     is_write?4:0,
			     val};
	UINT32* result;
	ACPI_OBJECT *obj;
	ACPI_BUFFER in = { sizeof(params), &params };
	ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL };
	int retval;
	
	if (ACPI_FAILURE(ACPI_WMI_EVALUATE_CALL(wmi_dev, ACPI_HP_WMI_BIOS_GUID,
		    0, 0x3, &in, &out))) {
		acpi_hp_free_buffer(&out);
		return (-EINVAL);
	}
	obj = out.Pointer;
	if (!obj || obj->Type != ACPI_TYPE_BUFFER) {
		acpi_hp_free_buffer(&out);
		return (-EINVAL);
	}
	result = (UINT32*) obj->Buffer.Pointer;
	retval = result[2];
	if (result[1] > 0) {
		retval = result[1];
	}
	acpi_hp_free_buffer(&out);

	return (retval);
}

static __inline char*
acpi_hp_get_string_from_object(ACPI_OBJECT* obj, char* dst, size_t size) {
	int length;
	dst[0] = 0;
	if (obj->Type == ACPI_TYPE_STRING) {
		length = obj->String.Length+1;
		if (length > size) {
			length = size - 1;
		}
		strlcpy(dst, obj->String.Pointer, length);
		acpi_hp_hex_decode(dst);
	}

	return (dst);
}


/*
 * Read BIOS Setting block in instance "instance".
 * The block returned is ACPI_TYPE_PACKAGE which should contain the following
 * elements:
 * Index Meaning
 * 0     Setting Name [string]
 * 1     Value (comma separated, asterisk marks the current value) [string]
 * 2     Path within the bios hierarchy [string]
 * 3     IsReadOnly [int]
 * 4     DisplayInUI [int]
 * 5     RequiresPhysicalPresence [int]
 * 6     Sequence for ordering within the bios settings (absolute) [int]
 * 7     Length of prerequisites array [int]
 * 8     Prerequisite1 [string]
 * 9     Prerequisite2 [string]
 * 10    Prerequisite3 [string]
 * 11    Current value (in case of enum) [string] / Array length [int]
 * 12    Enum length [int] / Array values
 * 13ff  Enum value at index x [string]
 */
static int
acpi_hp_get_cmi_block(device_t wmi_dev, const char* guid, UINT8 instance,
    char* outbuf, size_t outsize, UINT32* sequence, int detail)
{
	ACPI_OBJECT *obj;
	ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL };
	int i;
	int outlen;
	int size = 255;
	int has_enums = 0;
	char string_buffer[size];
	int enumbase;

	outlen = 0;
	outbuf[0] = 0;	
	if (ACPI_FAILURE(ACPI_WMI_GET_BLOCK(wmi_dev, guid, instance, &out))) {
		acpi_hp_free_buffer(&out);
		return (-EINVAL);
	}
	obj = out.Pointer;
	if (!obj && obj->Type != ACPI_TYPE_PACKAGE) {
		acpi_hp_free_buffer(&out);
		return (-EINVAL);
	}

	/* check if this matches our expectations based on limited knowledge */
	if (obj->Package.Count >= 13 &&
	    obj->Package.Elements[0].Type == ACPI_TYPE_STRING &&
	    obj->Package.Elements[1].Type == ACPI_TYPE_STRING &&
	    obj->Package.Elements[2].Type == ACPI_TYPE_STRING &&
	    obj->Package.Elements[3].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Elements[4].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Elements[5].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Elements[6].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Elements[7].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Elements[8].Type == ACPI_TYPE_STRING &&
	    obj->Package.Elements[9].Type == ACPI_TYPE_STRING &&
	    obj->Package.Elements[10].Type == ACPI_TYPE_STRING &&
	    ((obj->Package.Elements[11].Type == ACPI_TYPE_STRING &&
	    obj->Package.Elements[12].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Count >=
	    	13+obj->Package.Elements[12].Integer.Value) ||
	    (obj->Package.Elements[11].Type == ACPI_TYPE_INTEGER &&
	    obj->Package.Count >=
	    	12+obj->Package.Elements[11].Integer.Value))
	    ) {
		enumbase = obj->Package.Elements[11].Type == ACPI_TYPE_STRING?
				12:11;
		if (detail & ACPI_HP_CMI_DETAIL_PATHS) {
			strlcat(outbuf, acpi_hp_get_string_from_object(
				&obj->Package.Elements[2], string_buffer, size),
				outsize);
			outlen += 48;
			while (strlen(outbuf) < outlen)
				strlcat(outbuf, " ", outsize);
		}
		strlcat(outbuf, acpi_hp_get_string_from_object(
				&obj->Package.Elements[0], string_buffer, size),
				outsize);
		outlen += 43;
		while (strlen(outbuf) < outlen)
			strlcat(outbuf, " ", outsize);
		if (enumbase == 12)
			strlcat(outbuf, acpi_hp_get_string_from_object(
					    &obj->Package.Elements[11],
					    string_buffer, size),
					outsize);
		outlen += 21;
		while (strlen(outbuf) < outlen)
			strlcat(outbuf, " ", outsize);
		for (i = 0; i < strlen(outbuf); ++i)
			if (outbuf[i] == '\\')
				outbuf[i] = '/';
		if (detail & ACPI_HP_CMI_DETAIL_ENUMS) {
			for (i = enumbase+1; i < enumbase + 1 +
			    obj->Package.Elements[enumbase].Integer.Value;
			    ++i) {
				acpi_hp_get_string_from_object(
				    &obj->Package.Elements[i], string_buffer,
				    size);
				if (strlen(string_buffer) > 1 ||
				    (strlen(string_buffer) == 1 &&
				    string_buffer[0] != ' ')) {
					if (has_enums)
						strlcat(outbuf, "/", outsize);
					else
						strlcat(outbuf, " (", outsize);
					strlcat(outbuf, string_buffer, outsize);
					has_enums = 1;
				}
			}
		}
		if (has_enums)
			strlcat(outbuf, ")", outsize);
		if (detail & ACPI_HP_CMI_DETAIL_FLAGS) {
			strlcat(outbuf, obj->Package.Elements[3].Integer.Value?
			    " [ReadOnly]":"", outsize);
			strlcat(outbuf, obj->Package.Elements[4].Integer.Value?
			    "":" [NOUI]", outsize);
			strlcat(outbuf, obj->Package.Elements[5].Integer.Value?
			    " [RPP]":"", outsize);
		}
		*sequence = (UINT32) obj->Package.Elements[6].Integer.Value;
	}
	acpi_hp_free_buffer(&out);

	return (0);
}



/*
 * Convert given two digit hex string (hexin) to an UINT8 referenced
 * by byteout.
 * Return != 0 if the was a problem (invalid input)
 */
static __inline int acpi_hp_hex_to_int(const UINT8 *hexin, UINT8 *byteout)
{
	unsigned int hi;
	unsigned int lo;

	hi = hexin[0];
	lo = hexin[1];
	if ('0' <= hi && hi <= '9')
		hi -= '0';
	else if ('A' <= hi && hi <= 'F')
		hi -= ('A' - 10);
	else if ('a' <= hi && hi <= 'f')
		hi -= ('a' - 10);
	else
		return (1);
	if ('0' <= lo && lo <= '9')
		lo -= '0';
	else if ('A' <= lo && lo <= 'F')
		lo -= ('A' - 10);
	else if ('a' <= lo && lo <= 'f')
		lo -= ('a' - 10);
	else
		return (1);
	*byteout = (hi << 4) + lo;

	return (0);
}


static void
acpi_hp_hex_decode(char* buffer)
{
	int i;
	int length = strlen(buffer);
	UINT8 *uin;
	UINT8 uout;

	if (((int)length/2)*2 == length || length < 10) return;

	for (i = 0; i<length; ++i) {
		if (!((i+1)%3)) {
			if (buffer[i] != ' ')
				return;
		}
		else
			if (!((buffer[i] >= '0' && buffer[i] <= '9') ||
		    	    (buffer[i] >= 'A' && buffer[i] <= 'F')))
				return;			
	}

	for (i = 0; i<length; i += 3) {
		uin = &buffer[i];
		uout = 0;
		acpi_hp_hex_to_int(uin, &uout);
		buffer[i/3] = (char) uout;
	}
	buffer[(length+1)/3] = 0;
}


/*
 * open hpcmi device
 */
static int
acpi_hp_hpcmi_open(struct cdev* dev, int flags, int mode, struct thread *td)
{
	struct acpi_hp_softc *sc;
	int ret;

	if (dev == NULL || dev->si_drv1 == NULL)
		return (EBADF);
	sc = dev->si_drv1;

	ACPI_SERIAL_BEGIN(hp);
	if (sc->hpcmi_open_pid != 0) {
		ret = EBUSY;
	}
	else {
		if (sbuf_new(&sc->hpcmi_sbuf, NULL, 4096, SBUF_AUTOEXTEND)
		    == NULL) {
			ret = ENXIO;
		} else {
			sc->hpcmi_open_pid = td->td_proc->p_pid;
			sc->hpcmi_bufptr = 0;
			ret = 0;
		}
	}
	ACPI_SERIAL_END(hp);

	return (ret);
}

/*
 * close hpcmi device
 */
static int
acpi_hp_hpcmi_close(struct cdev* dev, int flags, int mode, struct thread *td)
{
	struct acpi_hp_softc *sc;
	int ret;

	if (dev == NULL || dev->si_drv1 == NULL)
		return (EBADF);
	sc = dev->si_drv1;

	ACPI_SERIAL_BEGIN(hp);
	if (sc->hpcmi_open_pid == 0) {
		ret = EBADF;
	}
	else {
		if (sc->hpcmi_bufptr != -1) {
			sbuf_delete(&sc->hpcmi_sbuf);
			sc->hpcmi_bufptr = -1;
		}
		sc->hpcmi_open_pid = 0;
		ret = 0;
	}
	ACPI_SERIAL_END(hp);

	return (ret);
}

/*
 * Read from hpcmi bios information
 */
static int
acpi_hp_hpcmi_read(struct cdev *dev, struct uio *buf, int flag)
{
	struct acpi_hp_softc *sc;
	int pos, i, l, ret;
	UINT8 instance;
	UINT32 sequence;
	int linesize = 1025;
	char line[linesize];

	if (dev == NULL || dev->si_drv1 == NULL)
		return (EBADF);
	sc = dev->si_drv1;
	
	ACPI_SERIAL_BEGIN(hp);
	if (sc->hpcmi_open_pid != buf->uio_td->td_proc->p_pid
	    || sc->hpcmi_bufptr == -1) {
		ret = EBADF;
	}
	else {
		if (!sbuf_done(&sc->hpcmi_sbuf)) {
			if (sc->cmi_order_size < 0) {
				sc->cmi_order_size = 0;
				for (instance = 0; instance < 128;
				    ++instance) {
					if (acpi_hp_get_cmi_block(sc->wmi_dev,
						ACPI_HP_WMI_CMI_GUID, instance,
						line, linesize, &sequence,
						sc->cmi_detail)) {
						instance = 128;
					}
					else {
						pos = sc->cmi_order_size;
						for (i=0;
						  i<sc->cmi_order_size && i<127;
						     ++i) {
				if (sc->cmi_order[i].sequence > sequence) {
								pos = i;
								break; 							
							}
						}
						for (i=sc->cmi_order_size;
						    i>pos;
						    --i) {
						sc->cmi_order[i].sequence =
						    sc->cmi_order[i-1].sequence;
						sc->cmi_order[i].instance =
						    sc->cmi_order[i-1].instance;
						}
						sc->cmi_order[pos].sequence =
						    sequence;
						sc->cmi_order[pos].instance =
						    instance;
						sc->cmi_order_size++;
					}
				}
			}
			for (i=0; i<sc->cmi_order_size; ++i) {
				if (!acpi_hp_get_cmi_block(sc->wmi_dev,
				    ACPI_HP_WMI_CMI_GUID,
				    sc->cmi_order[i].instance, line, linesize,
				    &sequence, sc->cmi_detail)) {
					sbuf_printf(&sc->hpcmi_sbuf, "%s\n", line);
				}
			}
			sbuf_finish(&sc->hpcmi_sbuf);
		}
		if (sbuf_len(&sc->hpcmi_sbuf) <= 0) {
			sbuf_delete(&sc->hpcmi_sbuf);
			sc->hpcmi_bufptr = -1;
			sc->hpcmi_open_pid = 0;
			ret = ENOMEM;
		} else {
			l = min(buf->uio_resid, sbuf_len(&sc->hpcmi_sbuf) -
			    sc->hpcmi_bufptr);
			ret = (l > 0)?uiomove(sbuf_data(&sc->hpcmi_sbuf) +
			    sc->hpcmi_bufptr, l, buf) : 0;
			sc->hpcmi_bufptr += l;
		}
	}
	ACPI_SERIAL_END(hp);

	return (ret);
}

--------------000302080806090206000000--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?4A3E6563.7040400>