From owner-freebsd-acpi@FreeBSD.ORG Sat Jun 27 11:25:06 2009 Return-Path: Delivered-To: freebsd-acpi@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 1C1AF1065670 for ; Sat, 27 Jun 2009 11:25:06 +0000 (UTC) (envelope-from freebsdusb@bindone.de) Received: from mail.bindone.de (mail.bindone.de [80.190.134.51]) by mx1.freebsd.org (Postfix) with SMTP id 3618E8FC20 for ; Sat, 27 Jun 2009 11:25:03 +0000 (UTC) (envelope-from freebsdusb@bindone.de) Received: (qmail 47870 invoked by uid 89); 27 Jun 2009 11:25:02 -0000 Received: from unknown (HELO ufo.bindone.de) (mg@bindone.de@87.152.167.4) by mail.bindone.de with ESMTPA; 27 Jun 2009 11:25:02 -0000 Message-ID: <4A46018B.90709@bindone.de> Date: Sat, 27 Jun 2009 13:24:59 +0200 From: Michael Gmelin User-Agent: Thunderbird 2.0.0.17pre (X11/20090202) MIME-Version: 1.0 To: Rui Paulo , freebsd-acpi@freebsd.org Content-Type: multipart/mixed; boundary="------------090600020803080001000309" Cc: "Paul B. Mahol" Subject: Patches to acpi_hp X-BeenThere: freebsd-acpi@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: ACPI and power management development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 27 Jun 2009 11:25:06 -0000 This is a multi-part message in MIME format. --------------090600020803080001000309 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Hello, please find attached a patch to acpi_hp.c and acpi_hp.4 (and the complete files as well). This patch brings: - sysctl dev.acpi_hp.0.verbose to toggle debug output - A modification so this can deal with different array lengths when reading the CMI BIOS - now it works ok on HP Compaq nx7300 as well. - Document sysctl in man page - Add a section to manpage about hardware that has been reported to work ok Installation instructions (against latest CURRENT): patch -d /usr/src < /path/to/acpi_hp.patch cd /usr/src/sys/modules/acpi/acpi_hp make all && make install cd /usr/src/share/man/man4 make all && make install cheers Michael --------------090600020803080001000309 Content-Type: text/plain; name="acpi_hp.4" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="acpi_hp.4" .\" 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 .It Va dev.acpi_hp.0.verbose (read-only) Set verbosity level .El .Pp Defaults for these sysctls can be set in .Xr sysctl.conf 5 . .Sh HARDWARE The .Nm driver has been reported to support the following hardware: .Pp .Bl -bullet -compact .It HP Compaq 8510p .It HP Compaq nx7300 .El .Pp It should work on most HP laptops that feature a WMI enabled BIOS. .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. --------------090600020803080001000309 Content-Type: text/plain; name="acpi_hp.c" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="acpi_hp.c" /*- * 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. */ #include __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 #include #include #include #include #include #include #include #include #include #include #include #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 ACPI_HP_METHOD_VERBOSE 21 #define HP_MASK_WWAN_ON_AIR 0x1000000 #define HP_MASK_BLUETOOTH_ON_AIR 0x10000 #define HP_MASK_WLAN_ON_AIR 0x100 #define HP_MASK_WWAN_RADIO 0x8000000 #define HP_MASK_BLUETOOTH_RADIO 0x80000 #define HP_MASK_WLAN_RADIO 0x800 #define HP_MASK_WWAN_ENABLED 0x2000000 #define HP_MASK_BLUETOOTH_ENABLED 0x20000 #define HP_MASK_WLAN_ENABLED 0x200 #define ACPI_HP_CMI_DETAIL_PATHS 0x01 #define ACPI_HP_CMI_DETAIL_ENUMS 0x02 #define ACPI_HP_CMI_DETAIL_FLAGS 0x04 struct acpi_hp_inst_seq_pair { UINT32 sequence; /* sequence number as suggested by cmi bios */ UINT8 instance; /* object instance on guid */ }; struct acpi_hp_softc { device_t dev; ACPI_HANDLE handle; device_t wmi_dev; int has_notify; /* notification GUID found */ int has_cmi; /* CMI GUID found */ int cmi_detail; /* CMI detail level (set by sysctl) */ int verbose; /* add debug output */ int wlan_enable_if_radio_on; /* set by sysctl */ int wlan_disable_if_radio_off; /* set by sysctl */ int bluetooth_enable_if_radio_on; /* set by sysctl */ int bluetooth_disable_if_radio_off; /* set by sysctl */ int wwan_enable_if_radio_on; /* set by sysctl */ int wwan_disable_if_radio_off; /* set by sysctl */ int was_wlan_on_air; /* last known WLAN on air status */ int was_bluetooth_on_air; /* last known BT on air status */ int was_wwan_on_air; /* last known WWAN on air status */ struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; struct cdev *hpcmi_dev_t; /* hpcmi device handle */ struct sbuf hpcmi_sbuf; /* /dev/hpcmi output sbuf */ pid_t hpcmi_open_pid; /* pid operating on /dev/hpcmi */ int hpcmi_bufptr; /* current pointer position in /dev/hpcmi output buffer */ int cmi_order_size; /* size of cmi_order list */ struct acpi_hp_inst_seq_pair cmi_order[128]; /* list of CMI instances ordered by BIOS suggested sequence */ }; static struct { char *name; int method; char *description; int access; } acpi_hp_sysctls[] = { { .name = "wlan_enabled", .method = ACPI_HP_METHOD_WLAN_ENABLED, .description = "Enable/Disable WLAN (WiFi)", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "wlan_radio", .method = ACPI_HP_METHOD_WLAN_RADIO, .description = "WLAN radio status", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "wlan_on_air", .method = ACPI_HP_METHOD_WLAN_ON_AIR, .description = "WLAN radio ready to use (enabled and radio)", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "wlan_enable_if_radio_on", .method = ACPI_HP_METHOD_WLAN_ENABLE_IF_RADIO_ON, .description = "Enable WLAN if radio is turned on", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "wlan_disable_if_radio_off", .method = ACPI_HP_METHOD_WLAN_DISABLE_IF_RADIO_OFF, .description = "Disable WLAN if radio is turned off", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "bt_enabled", .method = ACPI_HP_METHOD_BLUETOOTH_ENABLED, .description = "Enable/Disable Bluetooth", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "bt_radio", .method = ACPI_HP_METHOD_BLUETOOTH_RADIO, .description = "Bluetooth radio status", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "bt_on_air", .method = ACPI_HP_METHOD_BLUETOOTH_ON_AIR, .description = "Bluetooth radio ready to use" " (enabled and radio)", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "bt_enable_if_radio_on", .method = ACPI_HP_METHOD_BLUETOOTH_ENABLE_IF_RADIO_ON, .description = "Enable bluetooth if radio is turned on", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "bt_disable_if_radio_off", .method = ACPI_HP_METHOD_BLUETOOTH_DISABLE_IF_RADIO_OFF, .description = "Disable bluetooth if radio is turned off", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "wwan_enabled", .method = ACPI_HP_METHOD_WWAN_ENABLED, .description = "Enable/Disable WWAN (UMTS)", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "wwan_radio", .method = ACPI_HP_METHOD_WWAN_RADIO, .description = "WWAN radio status", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "wwan_on_air", .method = ACPI_HP_METHOD_WWAN_ON_AIR, .description = "WWAN radio ready to use (enabled and radio)", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "wwan_enable_if_radio_on", .method = ACPI_HP_METHOD_WWAN_ENABLE_IF_RADIO_ON, .description = "Enable WWAN if radio is turned on", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "wwan_disable_if_radio_off", .method = ACPI_HP_METHOD_WWAN_DISABLE_IF_RADIO_OFF, .description = "Disable WWAN if radio is turned off", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "als_enabled", .method = ACPI_HP_METHOD_ALS, .description = "Enable/Disable ALS (Ambient light sensor)", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "display", .method = ACPI_HP_METHOD_DISPLAY, .description = "Display status", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "hdd_temperature", .method = ACPI_HP_METHOD_HDDTEMP, .description = "HDD temperature", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "is_docked", .method = ACPI_HP_METHOD_DOCK, .description = "Docking station status", .access = CTLTYPE_INT | CTLFLAG_RD }, { .name = "cmi_detail", .method = ACPI_HP_METHOD_CMI_DETAIL, .description = "Details shown in CMI output " "(cat /dev/hpcmi)", .access = CTLTYPE_INT | CTLFLAG_RW }, { .name = "verbose", .method = ACPI_HP_METHOD_VERBOSE, .description = "Verbosity level", .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; if (sc->verbose) 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; if (sc->verbose) 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; if (sc->verbose) 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; if (sc->verbose) 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; sc->verbose = 0; 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; case ACPI_HP_METHOD_VERBOSE: val = sc->verbose; 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 && method != ACPI_HP_METHOD_VERBOSE) 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; case ACPI_HP_METHOD_VERBOSE: sc->verbose = 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..8+[7] PrerequisiteN [string] * 9+[7] Current value (in case of enum) [string] / Array length [int] * 10+[7] Enum length [int] / Array values * 11+[7]ff 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; int valuebase = 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); } if (obj->Package.Count >= 8 && obj->Package.Elements[7].Type == ACPI_TYPE_INTEGER) { valuebase = 8 + obj->Package.Elements[7].Integer.Value; } /* check if this matches our expectations based on limited knowledge */ if (valuebase > 7 && obj->Package.Count > valuebase + 1 && 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[valuebase].Type == ACPI_TYPE_STRING && obj->Package.Elements[valuebase+1].Type == ACPI_TYPE_INTEGER && obj->Package.Count > valuebase + obj->Package.Elements[valuebase+1].Integer.Value ) { enumbase = valuebase + 1; 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); strlcat(outbuf, acpi_hp_get_string_from_object( &obj->Package.Elements[valuebase], 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= '0' && buffer[i] <= '9') || (buffer[i] >= 'A' && buffer[i] <= 'F'))) return; } for (i = 0; isi_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; icmi_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; icmi_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); } --------------090600020803080001000309 Content-Type: text/plain; name="acpi_hp.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="acpi_hp.patch" --- share/man/man4/acpi_hp.4.orig 2009-06-26 12:50:30.005708266 +0200 +++ share/man/man4/acpi_hp.4 2009-06-26 13:03:16.331066657 +0200 @@ -166,10 +166,26 @@ .It Li 0x04 Show additional flags of BIOS setting (ReadOnly etc.) .El +.It Va dev.acpi_hp.0.verbose +(read-only) +Set verbosity level .El .Pp Defaults for these sysctls can be set in .Xr sysctl.conf 5 . +.Sh HARDWARE +The +.Nm +driver has been reported to support the following hardware: +.Pp +.Bl -bullet -compact +.It +HP Compaq 8510p +.It +HP Compaq nx7300 +.El +.Pp +It should work on most HP laptops that feature a WMI enabled BIOS. .Sh FILES .Bl -tag -width ".Pa /dev/hpcmi" .It Pa /dev/hpcmi --- sys/dev/acpi_support/acpi_hp.c.orig 2009-06-26 12:43:35.676585431 +0200 +++ sys/dev/acpi_support/acpi_hp.c 2009-06-26 12:54:46.509994426 +0200 @@ -92,6 +92,7 @@ #define ACPI_HP_METHOD_HDDTEMP 18 #define ACPI_HP_METHOD_DOCK 19 #define ACPI_HP_METHOD_CMI_DETAIL 20 +#define ACPI_HP_METHOD_VERBOSE 21 #define HP_MASK_WWAN_ON_AIR 0x1000000 #define HP_MASK_BLUETOOTH_ON_AIR 0x10000 @@ -120,6 +121,7 @@ int has_cmi; /* CMI GUID found */ int cmi_detail; /* CMI detail level (set by sysctl) */ + int verbose; /* add debug output */ int wlan_enable_if_radio_on; /* set by sysctl */ int wlan_disable_if_radio_off; /* set by sysctl */ int bluetooth_enable_if_radio_on; /* set by sysctl */ @@ -274,6 +276,12 @@ "(cat /dev/hpcmi)", .access = CTLTYPE_INT | CTLFLAG_RW }, + { + .name = "verbose", + .method = ACPI_HP_METHOD_VERBOSE, + .description = "Verbosity level", + .access = CTLTYPE_INT | CTLFLAG_RW + }, { NULL, 0, NULL, 0 } }; @@ -333,10 +341,10 @@ 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; + 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); @@ -344,7 +352,8 @@ new_bluetooth_status = -1; new_wwan_status = -1; - device_printf(sc->wmi_dev, "Wireless status is %x\n", wireless); + if (sc->verbose) + 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, @@ -390,10 +399,11 @@ 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); + if (sc->verbose) + 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); } @@ -403,9 +413,12 @@ 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); + if (sc->verbose) + 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); } @@ -414,9 +427,11 @@ 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); + if (sc->verbose) + 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); } @@ -439,7 +454,7 @@ struct acpi_hp_softc *sc; struct acpi_softc *acpi_sc; devclass_t wmi_devclass; - int arg; + int arg; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); @@ -459,6 +474,7 @@ sc->was_wwan_on_air = 0; sc->cmi_detail = 0; sc->cmi_order_size = -1; + sc->verbose = 0; memset(sc->cmi_order, 0, sizeof(sc->cmi_order)); acpi_sc = acpi_device_get_parent_softc(dev); @@ -551,7 +567,7 @@ static int acpi_hp_detach(device_t dev) { - int ret; + int ret; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); struct acpi_hp_softc *sc = device_get_softc(dev); @@ -578,12 +594,12 @@ static int acpi_hp_sysctl(SYSCTL_HANDLER_ARGS) { - struct acpi_hp_softc *sc; - int arg; - int oldarg; - int error = 0; - int function; - int method; + struct acpi_hp_softc *sc; + int arg; + int oldarg; + int error = 0; + int function; + int method; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); @@ -606,7 +622,7 @@ static int acpi_hp_sysctl_get(struct acpi_hp_softc *sc, int method) { - int val = 0; + int val = 0; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(hp); @@ -694,6 +710,9 @@ case ACPI_HP_METHOD_CMI_DETAIL: val = sc->cmi_detail; break; + case ACPI_HP_METHOD_VERBOSE: + val = sc->verbose; + break; } return (val); @@ -705,7 +724,8 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(hp); - if (method != ACPI_HP_METHOD_CMI_DETAIL) + if (method != ACPI_HP_METHOD_CMI_DETAIL && + method != ACPI_HP_METHOD_VERBOSE) arg = arg?1:0; if (arg != oldarg) { @@ -753,6 +773,9 @@ case ACPI_HP_METHOD_CMI_DETAIL: sc->cmi_detail = arg; break; + case ACPI_HP_METHOD_VERBOSE: + sc->verbose = arg; + break; } } @@ -788,15 +811,15 @@ 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 }; + 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, @@ -821,7 +844,8 @@ static __inline char* acpi_hp_get_string_from_object(ACPI_OBJECT* obj, char* dst, size_t size) { - int length; + int length; + dst[0] = 0; if (obj->Type == ACPI_TYPE_STRING) { length = obj->String.Length+1; @@ -841,33 +865,32 @@ * 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] + * 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..8+[7] PrerequisiteN [string] + * 9+[7] Current value (in case of enum) [string] / Array length [int] + * 10+[7] Enum length [int] / Array values + * 11+[7]ff 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; + ACPI_OBJECT *obj; + ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL }; + int i; + int outlen; + int size = 255; + int has_enums = 0; + int valuebase = 0; + char string_buffer[size]; + int enumbase; outlen = 0; outbuf[0] = 0; @@ -881,8 +904,13 @@ return (-EINVAL); } + if (obj->Package.Count >= 8 && + obj->Package.Elements[7].Type == ACPI_TYPE_INTEGER) { + valuebase = 8 + obj->Package.Elements[7].Integer.Value; + } + /* check if this matches our expectations based on limited knowledge */ - if (obj->Package.Count >= 13 && + if (valuebase > 7 && obj->Package.Count > valuebase + 1 && obj->Package.Elements[0].Type == ACPI_TYPE_STRING && obj->Package.Elements[1].Type == ACPI_TYPE_STRING && obj->Package.Elements[2].Type == ACPI_TYPE_STRING && @@ -890,20 +918,12 @@ 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; + obj->Package.Elements[valuebase].Type == ACPI_TYPE_STRING && + obj->Package.Elements[valuebase+1].Type == ACPI_TYPE_INTEGER && + obj->Package.Count > valuebase + + obj->Package.Elements[valuebase+1].Integer.Value + ) { + enumbase = valuebase + 1; if (detail & ACPI_HP_CMI_DETAIL_PATHS) { strlcat(outbuf, acpi_hp_get_string_from_object( &obj->Package.Elements[2], string_buffer, size), @@ -918,11 +938,10 @@ 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); + strlcat(outbuf, acpi_hp_get_string_from_object( + &obj->Package.Elements[valuebase], string_buffer, + size), + outsize); outlen += 21; while (strlen(outbuf) < outlen) strlcat(outbuf, " ", outsize); @@ -930,7 +949,7 @@ if (outbuf[i] == '\\') outbuf[i] = '/'; if (detail & ACPI_HP_CMI_DETAIL_ENUMS) { - for (i = enumbase+1; i < enumbase + 1 + + for (i = enumbase + 1; i < enumbase + 1 + obj->Package.Elements[enumbase].Integer.Value; ++i) { acpi_hp_get_string_from_object( @@ -974,8 +993,8 @@ */ static __inline int acpi_hp_hex_to_int(const UINT8 *hexin, UINT8 *byteout) { - unsigned int hi; - unsigned int lo; + unsigned int hi; + unsigned int lo; hi = hexin[0]; lo = hexin[1]; @@ -1004,10 +1023,10 @@ static void acpi_hp_hex_decode(char* buffer) { - int i; - int length = strlen(buffer); - UINT8 *uin; - UINT8 uout; + int i; + int length = strlen(buffer); + UINT8 *uin; + UINT8 uout; if (((int)length/2)*2 == length || length < 10) return; @@ -1038,8 +1057,8 @@ static int acpi_hp_hpcmi_open(struct cdev* dev, int flags, int mode, struct thread *td) { - struct acpi_hp_softc *sc; - int ret; + struct acpi_hp_softc *sc; + int ret; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); @@ -1070,8 +1089,8 @@ static int acpi_hp_hpcmi_close(struct cdev* dev, int flags, int mode, struct thread *td) { - struct acpi_hp_softc *sc; - int ret; + struct acpi_hp_softc *sc; + int ret; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); @@ -1100,12 +1119,12 @@ 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]; + 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); --------------090600020803080001000309 Content-Type: application/octet-stream; name="patch.tgz" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="patch.tgz" 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 8+Pz7/n5P7EUM4QA8AAA --------------090600020803080001000309--