Date: Sun, 21 Jun 2009 13:20:36 +0200 From: Michael <freebsdusb@bindone.de> To: freebsd-acpi@freebsd.org Cc: c.r.n.a@wanadoo.fr, "Paul B. Mahol" <onemda@gmail.com> Subject: Two new acpi modules, acpi_wmi and acpi_hp Message-ID: <4A3E1784.2050406@bindone.de>
next in thread | raw e-mail | index | archive | help
[-- Attachment #1 --] 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. This is the first time I'm writing kernel code for FreeBSD, so please excuse me if I failed to apply to all style conventions (I tried to follow style(9) as closely as possible). acpi_wmi is an ACPI to WMI mapping driver (this is used by HP and Acer notebooks and potentially others), so this could also be used to implement additional drivers on (see for more details on WMI and ACPI: http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx). It provides /dev/wmistat, example output: 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 acpi_hp is a driver that uses acpi_wmi to provide HP specific features. These include: - Activate/deactivate WLAN - Activate/deactivate WWAN - Activate/deactivate Bluetooth - Auto activate/deactiavte based on hw radio status - Devd notifications - On air status for different - Controls ambient light sensor - Get docking status - Read BIOS settings through /dev/hpcmi, example output: Flash Media Reader Disable USB Ports including Express Card slot Enable 1394 Port Enable Cardbus Slot Disable Express Card Slot Disable F9, F10 and F12 Delay(Sec) 0 USB Device Detection Delay (Sec) 0 Multiboot Enable Express Boot Popup Delay(Sec) 0 CD-ROM Boot Enable Floppy Boot Disable Internal Network Adapter Boot Disable Internal Network Adapter Boot Mode PXE Swap Fn/Ctrl Key Disable USB Legacy Support Disable Parallel Port Mode ECP ... The man pages included in the patch (man acpi_hp / man acpi_wmi) give you detailed information about all sysctls and devices. The patch attached (acpi_wmi_and_acpi_hp.patch) has been tested against 7.2-RELEASE and everything compiles cleanly, although my tests have been on 8-CURRENT (but the snapshot is older than 7.2), so I expect this to work ok on 7.2. The patch might fail against CURRENT though, this is why I also attached patches.tgz, which contains individual patches per file. I only tested this on i386 and the patch only changes i386 specific things (I have no idea if this could possibly work on amd64, especially because all the other acpi_support modules seem to be i386 only). Also note, that I could only test this on my own machine (HP Compaq 8510p), which features WLAN/BT/WLAN, so I don't know if the readings will be sane if your machine doesn't feature a WWAN chip. In theory this should work for many different HP models. Installation instructions (replace MYKERNEL with your kernel name): mkdir /usr/src/sys/modules/acpi/acpi_wmi mkdir /usr/src/sys/modules/acpi/acpi_hp patch -d /usr/src < acpi_wmi_and_acpi_hp.patch cd /usr/src/share/man/man4/man4.i386 make all && make install cd /usr/src/sys/i386/conf config MYKERNEL cd ../compile/MYKERNEL make cleandepend && make depend make all make install reboot . . . kldload acpi_hp cat /dev/wmistat sysctl -a | grep acpi_hp.0 sysctl dev.acpi_hp.0.als = 0 cat /dev/hpcmi sysctl dev.acpi_hp.0.cmi_detail=7 cat /dev/hpcmi It would be nice to get some feedback on this and maybe someone else can implement vendor specific ACPI modules for other WMI based laptops now. cheers Michael [-- Attachment #2 --] --- 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 12:09:58.000000000 +0200 +++ sys/dev/acpi_support/acpi_hp.c 2009-06-21 12:10:55.256712496 +0200 @@ -0,0 +1,1042 @@ +/*- + * 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 (controlled by sysctl) */ + int wlan_enable_if_radio_on; /* controlled by sysctl */ + int wlan_disable_if_radio_off; /* controlled by sysctl */ + int bluetooth_enable_if_radio_on; /* controlled by sysctl */ + int bluetooth_disable_if_radio_off; /* controlled by sysctl */ + int wwan_enable_if_radio_on; /* controlled by sysctl */ + int wwan_disable_if_radio_off; /* controlled by sysctl */ + int was_wlan_on_air; /* last known WLAN on air status */ + int was_bluetooth_on_air; /* last known Bluetooth in 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), ¶ms }; + 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 our 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 12:09:58.000000000 +0200 +++ sys/dev/acpi_support/acpi_wmi.c 2009-06-21 12:10:26.901562330 +0200 @@ -0,0 +1,911 @@ +/*- + * 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 currently operating in /dev/wmistat */ + int wmistat_bufptr; /* /dev/wmistat ptr to current 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; /* cookie to send when calling event_handler */ +}; +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 eventy 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 \ [-- Attachment #3 --] >J {90Oqv0\|dKc/xiCh~gs>[U76Μ<PKTJYwrSc|>W*1yG~|AcVK=mZ #Y8sc,sbMsQlh>דfsZŃݾ==CD!?jlKG#m/KètTa,{eq\GZ>/li_4~_~btn%*fW/oW?Z~Z9j-~0]6٩1Z6Y_Vz`~&s?zck+ pypXt)3ɱW{47ni] bXWjArPM[sg! du1CSy65&/®pFtB"éҪpf-Ϣ[Wc&ȗ>8%V|y?eM^x$ۃtvv%AvT> ² 1>O̳sQ7o9sb9s29e[@>2S%JK`kN}Scb8к2R ̕Q\>^ٝ>RQ+3swlL_Hs8xT>Md+a9BA } 9B>La<!6u3d?;CcsdMxh(fcξr3jj%3&Ϭ s"L&5a|@O{1pYV@(fSs6ss3g͇ i4oQ9My`F`ɱ9?⿴\ A=@а#dDp0L9(y- "*f1j#3_F*UF", .dn!܅ѱ!nj9 VDC:F|?o>6zU?KU.`oYZgvzm;ҁo+A}g9'E'M ەnSdXvrQo0jb'fu[ e;۵Rm4F{6Iv^iwJ_[Wovj'^1@*gG:*''jc_[:`Y`TĪ^bo5;@$:z_thM)# w^@&x+ށ]Sz{^tuժw80?6kz瘝:q=t+^ziR5Ϻz}qmRԇ%t\u_h02mZ HzP3lWi2;ߟ4g5߶e`u_V>^P7~B]lJc 2UtZ sQ AoLک] Z0&쁕Zb!3dgSgl{w>;Mڮ c<ŒsQʚm_eӑ!M@>^Y\[7g$"Xvf 97Y4ZV FZkl@: "\-A6#XkVfՑuzI︘BN$J%z6J$tNٜF,ug͵i b7t ч ߌ91PقInnriZ~J`r7/`0hlj,wPI]C/f}q,}ށ`#v27 ~Akgfnc0c: a>40('\_ -drÇ4V1lhמpaG=5avdsyx+J1Ť p&Ԕ{@'$dMPVG*ܸa[k qY\AvX6H9?`'x!>7ɇR14&om51Ʀu91Z1`:H_f9qШS\CP'q(&2DEHvB_~hJ_T%Ú!D0@̳$&ާԢڪK *tGmbk/ZS[UW# ;h"v;v+^̛Pw!pK#_gl[i*tswo`i&|s>|4T㥁 1>hX,'T=aAPMB!u_`9gx:2Q5GHGatt-I*`(%on/@Y[V~f,{+ړ}JP*CT?{[lJvZV=8G #I؊vd] v^Z۳ytG/Cu֭#P[gVP,@q@aR04RZ"w3s5֣ E1 I4!NigzI:1?~5?Fym5kn555ZF+(#'D*B ="GcN,؞4.eredž%p0Z.Ih}יm$!=ŏ $(KġjU2A> PQz|$"!m\flu-Z*R% : 'kH+|IpO # ɒm93/~ݼ6[cJvPMBݗ;7`IGtTN!GNJc3[i{6 W]XT"\UsT M͙ɧ!R5Ȁ}+PJ'Ƹ>f݇8Jϐ9>9TY]e zka8S }W8o'I5O!bC$Q_C|(tO o,U7W`ʓoiS{p]@bր!:# \G:Xΐ`ȝ`f>EJJ 3qX VhZqzh+|/A 1q>. 436b~CQ4ܷj?u>Kn_݊/TT}Nr[3PNMv_;Vhq DH.h2aى ڑ5p*{!U` 0/73s@ @ÙZ3O%p 4\8>89_{'Ě,\geǧMlnN P݀hwwXhǷ9h1.et;H^tp͡B;)ah9hgBA6W0c?zN].rvv,TnLTM.1Y QOѣQx=yhuD-qEu$2\!/-sUZ{#d a٭vwQGA]xٟvqؤ#WI=iIJ9Q;1=VWs%oZQPI#닉 /͖Pl ւrXCߨ#1?U/ʁRq%<2'cdND$`9W(k^8G˗ J? {G墣D,D(" {r Lӱq8 JTRf" &M M E M `37?lgMW 5O9!^Qԛ"o.Ju/{7:¹}먾T3^|9n@4_f#(>!`vJ vAYn vӌuXy}{$m{:veGG$6_\-TjU%=B}Ei]Rmj'g]S;n@^uz:w̵S0ki~q0 />ϔZL`{$z/D"}XnJl_͖Rxjjрppʍ~#bTUP륒^R ~Lij0[7 VzT)~Y+Z'SDN^RssVN:^.VUUVv r䑚՜S6z~h]7[-)jYlC50K$"ڥi42Փqqo̲ku{6Q,Z>.dX/aՖ:ڒXm^ƍ?0oJxJGRT?8(Kp!JgH^t5Ћ<&/9IfLrw dח+Z.rW:51|)1|{5MeHwϚ8cћ*.^yP,$ maNqbwl_ngqscʈB2^YC A8H dCOP~5>9~ID'zb<VsҲ7Y_< Ta0B;'""lӷzRJ4eQeEBb)|%WKYW(aDny\0Zal cٗ ,"d_kz/`ZF $b6;^q(m sb`~MΘ\1_4ks| WQ*/y5ἢ2Ԝ ER R$LSbB!ZtLK!&=LN;ь-V8L(}~$sxTMntl*O^3Q32>> mSH`^y[9Hej@vzgUP@`|1~WK^Z +%@st{tY/!۷L4B4HCbV_kƕU}=2JJoyr0S~ zbD`]GDn&Z#樆>%^P*C:ЕSs9P#q*]룸dBq|YC2;~ļ瘝1qty>Kx,itQy\.*,tn+D(`mQyl2ΐZq y$TW^93 KV"<6Ux `\*{BJʁ-#NHKeegjFT䖕y~V!# shƢ%0!$}%! `X;IC&ŚE=ɍ̾2јύpث9ݼ_џ!3'Ș=c1G括+GþS!7|Sp+{`#b;N? ݘ/h ߗYtziAy)\xDðNnU`{R 3!*vgcFȥ3"v~Cb^ɁI0 Z\gȃp'~ylB>y94 =Kry#cy [Jv.JU"D|[E~?[&?'7}5g2·;M|x{ 9eTN0QGPL%hӶe<sUJ!6a>D|GS?ѧ`eEt6Gz[-$$V;y":?2G#D&H,!JsDN lRY 9L+wv`j+SYg'EHvt:;c-m L]L"{`(ӷurGhR6Aǻva*9/9c_nXWMaGؼMxQzdjBD1&i9kclCi`|/<_jqWbG1ʐq-1ujfF6zE<_K[X=~^1S/KzLvufB9bCSٹ@'<cYw:lQ>~x˨%uoQ[",sZX.qspQY)Fer7օcθ! א3thY:2g[`,"r&9*A( F']o\Wc=i=`:%r}S-8nWΤؙtd&ϤștЙt|&.Itd*36z&}<}>9$v`~D8' v?Dbbͱ4u{>5Lt~py%ˁXKa H"GY# F/&[BI{-!C\'9Ӹ8ޜѓ`;N`cXw{=nQ)G$H,2A_)/xNi%i /R}Qri, NHJ^E0hȢ'9&VQA/R40+$250zO)bv('NdԢÚ &oJJH`8'quc$*?\QE䆦<ǡYB&(jb$oguFw{2.)~amWF1cgd'f?זbc$w20]{ta<t+''N~_5s%HRǎ=@ ,{$C-<垟"vQ3倵)"cz<&_^c-6(]~u\G+2ґ;sFJP <OyC<]͓bo%SWAMnk@ >DFn4%B6@i}eARp혥ӲA $ ~%0`wU@-j-=x (R)bDz_?_ CfMǤ\!C.l~ Uv+Um֙R~EJyRog ?IeXd˃1"r~F48ns;Zj*emg E{+/ yiM2K& !Ub8<z2)獟%Krqy[?m}2?+CrA #^VLFz\U,@ }OB= ]QJϥ$$^"HfH >`l6ua}cVS5f`E,1BnqʲPKuREz$xoy\K(<fk : nfNx)7}71oU"RxqQ/e=~C `<~ՐFÇ}_ɏNZfYI{c%d3g"ǭCr&Hfx؊_(mXT0_Z|UAnɕUV>sW^_;,eh?җ|j\A+(`'|'89_/ɣ]AF}=p}+t?j/Sl`xRC֮[QxR݁cowvF (TF^?-+ѨKv_k>zW@S#}S7XRwPsRtZn \q(.*Q!_8!cRUZg5zh\զ(=ϰ#dj- ˢ[VLgGOz!C$ ±+^Ϛ-ĴI6@|zhm <kL˾;'Q8-OM !-%keꯇ/]IꗾbaΈ^{h=DAj.r[U^n{+xڱd6qտ"P\rb\3ÀG]~jLRFfyN|9RN2AmjnL ,W֚Xn39 #l˴SZT.Kr1pe2pѠ{vd F5((E SHIO,rbbh h^rrPj / A&I}h%}8aC oS棄J[$s˭^gH>w/xK<ee|[ʟ?'QQl}! @<?YD1$=waPL%͗N\)JYg>Ixd|#6BAeyo6]y˄#28Aݓm~8sqxtyk.wŅ"M]`ǏġƄa<%Q;ð7Qt{sLd"f8xՂGݔ)?;%F,9̙1%T鸷?_bxCsrTޕ]ca\9;V ^=|qF6Q(vFE,qأB'HBl:3gXo*'<ᇡ4t5u80j*4}R43}lw·n}'{W\1KCĤ%@)_.2c-p4܍_y ˹X,.˲.Ш.V zҙף.}O./94^{gO<ur1a҅fd-<Oqܧ+Ȼ@<v'/.80I.N֭*=hEQE/-[zB=<@kT2S dERJ:*^|jG@P(!XB"Z $:gS^unpj<) euG` I)\\B߲O}sdRX`.I a$v 7OnL~(m)R:MY=K>m@Bl\A`x?'P$a}nxԠ7o֢y:_Ҽg7KA%0 Ɗ>Qx+/_zb" QOe47썻*#Fu78 @[%#JsZ̮*b_/7_78T|[F9tCUr|6]ZCR,b. y;'*5gCJy=j9oٝl`X`1ȩ&)mL)73߾`&BUb n Ơ7eOt]]t6sR 9ia,8 yez#;ϗ4e6DE` c&hM )t7¯,t}4#4$3@9նR5 *ïx}j]˭XNlOAIđ+Oqy֮9KrZXp;v#/.AR; " 'l>i*oNZi-cѕ#b ΰ]#ʽk}"MЄӋkRR@)?͵iSZk*a) rɿuQwИPQsC`:DP=p۹"psמGƍ#3Nܙs"v* EQA9U!ԉW+<W9wvw =n{nA@Rp/:Ջm:gA<7C%}MDA|x)]nH~"wu2z;H^#A?ކ)jlZ`É>A+/&7JAV)eesIʚ0ڝ9=~L)\+.A%Nq-_(pL]\xP V/0(>$hO cQ=ѧ$H.M/P. ;\$uUG5<]oI [O$SZ//i%3%%"@)~ ~WAT6czZF`c{)<_nlEf,!\?~8 Gݟp8"RspP\+?4M8'V = )<?:tTښX0Zy1'+l7!EWkauc^$2t Ά8pJ(9jA"!G$V52zȣrD|aӴ_K,Jc@&,`^ cNy|ۡi7mzY%hccJ'gv=D!?Q|ˇto: ,-Ti/, d-s~e+6]:a(戽yh(ar9m,?^-oC&̱35reM=F1 ?{Ngljcd0y oOt%`ɱ9?⿴\ A#uf{bc Zeq^`h_+ *f1j#3_F*UF",x[]X@!pö ;̙eoh$i58Ϊ*6Yum7V* w鿝NAI v;<\ԛg3ళV4O]meB*\To>Jy~"V@$ym.N*mv~>ou<l\٩Tz= Q8|54f[uQ xas/o:;?/ {harEDmq\T;fVÁA i?6kz瘝:q=0*P 8kW/:M?h/q#@к+PN:C_ڟ2 F]~!]KW5ZSBг]L|t|BP͎kv0C}Y$ZzA݀ 1ghtY*MlTi j58EuvwiT;˴c11YAƤ91eĐCb9\g*H4gclI5`XԘI+n%;Z&j)jRlb&(<F"|=ѭ4ϊںY8Ȃ7xp־vL^\ P'Qg}B>?ӭteg6ld tqj+ c1+`WKpfDudA(Dn;.ЯuSk7̡kK8%3 6DdWs2gjQY)_DAt[餙Ṝ+}/ژʱňXPDOUFD2+)k+P,ᚎy^HAޙo^y#'B̍ YNV[&NCVfƫ>[+^PJ+<ep?r [M.FXj{lf-K%Po FC`l*FX ˍB-RZ^{ZM, TahAZ!T ˇyPв^VZZh|%_;HU 5JCMfr-[WeתR^}=QU{=QkZZ: VzT)~Y+Dy?j ;ࠞ?ЋٽF)[Jg*tR~qP,Aj%r_t-KZT}QgPhlPbz6|P/D!? B]/ֳ{{xMJz',bA4^=l RvVl^?,J`D xYeNd 2G|O/ߵ5ih=r5mp5 y@e²ۙ/&{o+/73bSHK{Á'[dj!FWNE}n]fFvmcl8Ovwooosc?ћe{;w93ݝN1Ŋrcgz`+M\WΜAGGD]]o=Y7#{6/}\W@~G+\~|?i+Ltj枻 r@aOa/ҪW(~M#NVp\1t_ل+x ItG@y7m tl\/'M6͚t !b M Ƽ? kCI۠!;~(|d+ʚG3?!gk'75zvC2lRZ=tSɬ[ PAςא:.Ir?A5";aC Pqc#X=M"Av[*weMxضGІѤ3 h4lO#/,=.F'7YċD5Ww5+x3wk궭)piGF90* & 0-X?W͋Cܖ@it=:͏z"Vr/a"ǠSDƏJ B )?@,!R ?Rayi?X (S`-DS c87EbEzj8<X C ryqk;bxpЮ\Q^~8.n |M+j3{J̾K6f?(x*'ѻ(R%TIȒo.|NPUޛ5WQQq1&C04C=Q/+FCmX5႖KTёѨPvhF@_ 7D:P(mO 1Ccߵkm@Np1 ~h<.æ6S9>qĐpN*HJrgMq w=7ɦ3pFd@QФ+n7` ƀc=ٵ9zgy{<#vA?:.LDj40Gp3460qq~"Ch9h e1H|졜̉! bOI"E cofHe:s)Tf㓼!PܟG?p \wD';=hhڊ9!&^.SAb+u;vRy{ͳ&j@Rz$)y*O}jă"~֞"|ˢevԁE\y5b˸Q7sMoMu1[i=2ꃬvv zEǫ=" A3|wsUD6;C=e7^e؊kz~$Q/y( NU8L |J*Rщ-zghb%)x(? VS/:?9u[N\53.TFs?|4kmG`0s}hA[']ЉK3%14=_Lcлau}sԴޭX~ (7,wnO_DP<ǵ+ Kx|qtQc%mH/Ԝ4h1] b1w!nP疹5oI z`9~z.oh#Ɖ,|[Dz!2Q|HDLYɷl[*{iJϢI?ϒIT&*+;2AE$:3W<2AM3s\8åGpV8PQHH5@LM%K|DV`E eP 2Q6(gY~d/'*W 'uk?8#0MުaIe7+yM X-0!h(4m]?Ǎz49&l`[xyۇ_w|8y(Cq{3clqq'.$mzۤ:gNgNgpJEoд-EpFq/[sF zB,qzuW7XXe_q? ^Sv9C֦ ϊP35\L֭= m>{*eHY6pQdxˬmN(:Tv@?Z凎Gn| hDr+)nm} oP[OkD_5gӘ[[Z?:'f;6q+ǔ3"`썤';v;,?\i\57&xK:T5W;e3)H2<^a+߬U؟@)<3ov %b/!b۠ M;W4%VTL\͕z9",'yƃhn5ރ[LI_X>~7~B)[ՙvGztv/ 㭳RI2X$[not2UV#`v/fWcK,'QpNt,]* E9mJa=5kclg>*^UbYry8%(To -jڇ^ГbJ]7I_5Rk0Z;9d7^\(`Hl;xOQ|MqJRBޱQܦ8tnH@+WYRjzV>d#''gg92 wG[6$BjTt5۟GZH2oC,mw)hCI: l}%|fz"0GJ1!{VpEa=[U?f<!B~-ݶ"kڠD m1ZU^@~S3*HN8$EȳaÁNjNweA Ү@MzGL\P{X{c6aoD6m=hQN0S^߲±Yxj+N0xp+~EGod:^(Ľk("B̪\Elgѓbp!@HWǺ`a-~tPyž&`_*[zɌ njx9u39\@)1C.H(d,x_nk$"[.h47f~*ᰁhxXEP uMO=8?ql7=3,JGEY6F#<'8ӂEbߓq@.Ym2: ^8{ ѩ;sbӡةCAu{k(e3ϻiF/76g LDj=ڗ9HMyy!z}(wfO!2y/P̟얠Y.<`5)msWʶb&<LXZ %vW]m-5Is]}䵞Z8-گ\jʘyrw)緯\8@雊aQɆ3W{z};F]3$Igb$@DK.pG K0g)w$4Cn**f_36]:[|//k$m^G p6z"ө6꾻yfLtiHĴ#SX%`+slLgNwͰxᝉacT;QjxO6N/||\w"JV:7Z}Ps\ vv~5B3OA<K\o4.rOr.PiC?O,9:eBby3rU3Y<x9MHx|iHɷsi@e v!.!nAV;V^' ]zاyy<l ?zA!#uDE`1O`D˖Dgnl`\ImBŜ&`:wwki)1DoQVЉ F0_V#%8`D1,ۋ4JvBK_yZWۊP 5 |"RB#o`9tb212`* `eryem%F+''~W[*§5N?EoEPzcm4]8gF`(8h֯$`ӥHr&xؗIvr7(㹀_W~5$aƥ/9"OhC+VbSn c~Dx$yM͟W8X F؋$ ~lVQy_sU݉|D$SdX_Q"r3LPap)SSZV k4ta1w/Q;_UAt/*V֠[[u݄ iLL)B "<tQ: -蕲"# 8*qMRx_+9%fWXL\v|J&9{Y2T2ݘP[<浱yb}o`wH(GA/r3VmwCr9{>.Ic>A[owfrE_vOZrURvⅎp7}F`E(͛TXnX.Fm:meN7 9J'sz O]#x#E~$\2DƯgJ ^ኍn9x aRA XiU?hd|m02 ! }ˮY,ZIu[v/m899SI@>r3ReO^<.gM] a1&N%V߯9|E7GLr%ݨ1qtjN%SX0XߗA+c>uۈذ<דC!ˌ8YJ=Wx 17 ޱC3VfR:H)YP _xV"n%B^ FjZj9)7lhyI;UpLx&
