Module Name:    src
Committed By:   bouyer
Date:           Sun Dec  3 17:40:48 UTC 2017

Modified Files:
        src/sys/dev/acpi/wmi: wmi_dell.c

Log Message:
Fix dell WMI mappings:
- query the descriptor to get the interface version, needed to workaround
  a bug in the BIOS/ACPI
- properly decode the event buffer in type/subtype, and handle multiple events
  per handler call
- record some known type/subtype in a table, with associated actions.

Informations mostly from linux. Tested on a Dell 5480 laptop.


To generate a diff of this commit:
cvs rdiff -u -r1.9 -r1.10 src/sys/dev/acpi/wmi/wmi_dell.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/acpi/wmi/wmi_dell.c
diff -u src/sys/dev/acpi/wmi/wmi_dell.c:1.9 src/sys/dev/acpi/wmi/wmi_dell.c:1.10
--- src/sys/dev/acpi/wmi/wmi_dell.c:1.9	Thu Apr 23 23:23:00 2015
+++ src/sys/dev/acpi/wmi/wmi_dell.c	Sun Dec  3 17:40:48 2017
@@ -1,4 +1,4 @@
-/*	$NetBSD: wmi_dell.c,v 1.9 2015/04/23 23:23:00 pgoyette Exp $ */
+/*	$NetBSD: wmi_dell.c,v 1.10 2017/12/03 17:40:48 bouyer Exp $ */
 
 /*-
  * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc.
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: wmi_dell.c,v 1.9 2015/04/23 23:23:00 pgoyette Exp $");
+__KERNEL_RCSID(0, "$NetBSD: wmi_dell.c,v 1.10 2017/12/03 17:40:48 bouyer Exp $");
 
 #include <sys/param.h>
 #include <sys/device.h>
@@ -43,29 +43,69 @@ __KERNEL_RCSID(0, "$NetBSD: wmi_dell.c,v
 
 #include <dev/sysmon/sysmonvar.h>
 
+#ifdef WMI_DEBUG
+#define DPRINTF(x)	printf x
+#else
+#define DPRINTF(x)
+#endif
+
 #define _COMPONENT			ACPI_RESOURCE_COMPONENT
 ACPI_MODULE_NAME			("wmi_dell")
 
-#define WMI_DELL_HOTKEY_BRIGHTNESS_DOWN	0xE005
-#define WMI_DELL_HOTKEY_BRIGHTNESS_UP	0xE006
-#define WMI_DELL_HOTKEY_DISPLAY_CYCLE	0xE00B
-#define WMI_DELL_HOTKEY_VOLUME_MUTE	0xE020
-#define WMI_DELL_HOTKEY_VOLUME_DOWN	0xE02E
-#define WMI_DELL_HOTKEY_VOLUME_UP	0xE030
-/*      WMI_DELL_HOTKEY_UNKNOWN		0xXXXX */
-
 #define WMI_DELL_PSW_DISPLAY_CYCLE	0
 #define WMI_DELL_PSW_COUNT		1
 
 #define WMI_DELL_GUID_EVENT		"9DBB5994-A997-11DA-B012-B622A1EF5492"
+#define WMI_DELL_GUID_DESC		"8D9DDCBC-A997-11DA-B012-B622A1EF5492"
 
 struct wmi_dell_softc {
 	device_t		sc_dev;
 	device_t		sc_parent;
+	int			sc_version;
 	struct sysmon_pswitch	sc_smpsw[WMI_DELL_PSW_COUNT];
 	bool			sc_smpsw_valid;
 };
 
+#define WMI_DELLA_PMF	0x0
+#define WMI_DELLA_PSW	0x1
+#define WMI_DELLA_IGN	0x2
+
+const struct wmi_dell_actions {
+	u_int	wda_action;
+	u_int	wda_type;
+	u_int	wda_subtype;
+	u_int	wda_data;
+} wmi_dell_actions[] = {
+	/* type 0 */
+	/* brightness control */
+	{WMI_DELLA_PMF, 0x0000, 0xe005, PMFE_DISPLAY_BRIGHTNESS_DOWN},
+	{WMI_DELLA_PMF, 0x0000, 0xe006, PMFE_DISPLAY_BRIGHTNESS_UP},
+	{WMI_DELLA_PSW, 0x0000, 0xe00b, WMI_DELL_PSW_DISPLAY_CYCLE},
+
+	{WMI_DELLA_PMF, 0x0000, 0xe008, PMFE_RADIO_TOGGLE},
+	{WMI_DELLA_IGN, 0x0000, 0xe00c, 0}, /* keyboard illumination */
+
+	/* volume control */
+	{WMI_DELLA_PMF, 0x0000, 0xe020, PMFE_AUDIO_VOLUME_TOGGLE},
+	{WMI_DELLA_PMF, 0x0000, 0xe02e, PMFE_AUDIO_VOLUME_DOWN},
+	{WMI_DELLA_PMF, 0x0000, 0xe030, PMFE_AUDIO_VOLUME_UP},
+	{WMI_DELLA_PMF, 0x0000, 0xe0f8, PMFE_AUDIO_VOLUME_DOWN},
+	{WMI_DELLA_PMF, 0x0000, 0xe0f9, PMFE_AUDIO_VOLUME_UP},
+
+
+	/* type 0x10 */
+	{WMI_DELLA_PMF, 0x0010, 0x0057, PMFE_DISPLAY_BRIGHTNESS_DOWN},
+	{WMI_DELLA_PMF, 0x0010, 0x0058, PMFE_DISPLAY_BRIGHTNESS_UP},
+	{WMI_DELLA_IGN, 0x0010, 0x0151, 0}, /* Fn-lock */
+	{WMI_DELLA_IGN, 0x0010, 0x0152, 0}, /* keyboard illumination */
+	{WMI_DELLA_PMF, 0x0010, 0x0153, PMFE_RADIO_TOGGLE},
+	{WMI_DELLA_IGN, 0x0010, 0x0155, 0}, /* Stealth mode toggle */
+	{WMI_DELLA_IGN, 0x0010, 0xE035, 0}, /* Fn-lock */
+
+	/* type 0x11 */
+	{WMI_DELLA_IGN, 0x0011, 0x02eb5, 0}, /* keyboard illumination */
+};
+
 static int	wmi_dell_match(device_t, cfdata_t, void *);
 static void	wmi_dell_attach(device_t, device_t, void *);
 static int	wmi_dell_detach(device_t, int);
@@ -87,6 +127,9 @@ wmi_dell_attach(device_t parent, device_
 {
 	struct wmi_dell_softc *sc = device_private(self);
 	ACPI_STATUS rv;
+	ACPI_BUFFER obuf;
+	ACPI_OBJECT *obj;
+	uint32_t *data;
 	int e;
 
 	sc->sc_dev = self;
@@ -100,8 +143,34 @@ wmi_dell_attach(device_t parent, device_
 		return;
 	}
 
+	memset(&obuf, 0, sizeof(obuf));
+	rv = acpi_wmi_data_query(parent, WMI_DELL_GUID_DESC, 0, &obuf);
+	if (ACPI_FAILURE(rv)) {
+		aprint_error(": failed to query WMI descriptor: %s\n",
+		    AcpiFormatException(rv));
+		return;
+	}
+	obj = obuf.Pointer;
+	if (obj->Type != ACPI_TYPE_BUFFER) {
+		aprint_error(": wrong type %d for WMI descriptor\n", obj->Type);
+		return;
+	}
+	if (obj->Buffer.Length != 128) {
+		aprint_error(": wrong len %d for WMI descriptor",
+		    obj->Buffer.Length);
+		if (obj->Buffer.Length < 16) {
+			aprint_error("\n");
+			return;
+		}
+	}
+	data = (uint32_t *)obj->Buffer.Pointer;
+	if (data[0] != 0x4C4C4544 || data[1] != 0x494D5720) {
+		aprint_error(": wrong WMI descriptor signature 0x%x 0x%x",
+		    data[0], data[1]);
+	}
+	sc->sc_version = data[2];
 	aprint_naive("\n");
-	aprint_normal(": Dell WMI mappings\n");
+	aprint_normal(": Dell WMI mappings version %d\n", sc->sc_version);
 
 	sc->sc_smpsw[WMI_DELL_PSW_DISPLAY_CYCLE].smpsw_name =
 	    PSWITCH_HK_DISPLAY_CYCLE;
@@ -159,6 +228,43 @@ wmi_dell_resume(device_t self, const pmf
 }
 
 static void
+wmi_dell_action(struct wmi_dell_softc *sc, uint16_t *data, int len)
+{
+	int i;
+	for (i = 0; i < __arraycount(wmi_dell_actions); i++) {
+		const struct wmi_dell_actions *wda = &wmi_dell_actions[i];
+		if (wda->wda_type == data[0] &&
+		    wda->wda_subtype == data[1]) {
+			switch(wda->wda_action) {
+			case WMI_DELLA_IGN:
+				DPRINTF((" ignored"));
+				return;
+			case WMI_DELLA_PMF:
+				DPRINTF((" pmf %d",
+				    wda->wda_data));
+				pmf_event_inject(NULL,
+				    wda->wda_data);
+				return;
+			case WMI_DELLA_PSW:
+				DPRINTF((" psw %d",
+				    wda->wda_data));
+				sysmon_pswitch_event(
+				    &sc->sc_smpsw[wda->wda_data],
+				    PSWITCH_EVENT_PRESSED);
+				return;
+			default:
+				printf("unknown dell wmi action %d\n",
+				    wda->wda_action);
+				return;
+			}
+
+		}
+	}
+	aprint_debug_dev(sc->sc_dev, "unkown event 0x%4X 0x%4X\n",
+	    data[0], data[1]);
+}
+
+static void
 wmi_dell_notify_handler(ACPI_HANDLE hdl, uint32_t evt, void *aux)
 {
 	struct wmi_dell_softc *sc;
@@ -166,7 +272,8 @@ wmi_dell_notify_handler(ACPI_HANDLE hdl,
 	ACPI_OBJECT *obj;
 	ACPI_BUFFER buf;
 	ACPI_STATUS rv;
-	uint32_t val;
+	uint16_t *data, *end;
+	int i, len;
 
 	buf.Pointer = NULL;
 
@@ -183,45 +290,40 @@ wmi_dell_notify_handler(ACPI_HANDLE hdl,
 		goto out;
 	}
 
-	val = obj->Buffer.Pointer[1] & 0xFFFF;
-
-	switch (val) {
-
-	case WMI_DELL_HOTKEY_BRIGHTNESS_DOWN:
-		pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_DOWN);
-		break;
-
-	case WMI_DELL_HOTKEY_BRIGHTNESS_UP:
-		pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_UP);
-		break;
+	data = (void *)(&obj->Buffer.Pointer[0]);
+	end = (void *)(&obj->Buffer.Pointer[obj->Buffer.Length]);
 
-	case WMI_DELL_HOTKEY_DISPLAY_CYCLE:
-
-		if (sc->sc_smpsw_valid != true) {
-			rv = AE_ABORT_METHOD;
+	DPRINTF(("wmi_dell_notify_handler buffer len %d\n",
+	    obj->Buffer.Length));
+	while (data < end) {
+		DPRINTF(("wmi_dell_notify_handler len %d", data[0]));
+		if (data[0] == 0) {
+			DPRINTF(("\n"));
 			break;
 		}
+		len = data[0] + 1;
 
-		sysmon_pswitch_event(&sc->sc_smpsw[WMI_DELL_PSW_DISPLAY_CYCLE],
-		    PSWITCH_EVENT_PRESSED);
-		break;
-
-	case WMI_DELL_HOTKEY_VOLUME_MUTE:
-		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_TOGGLE);
-		break;
-
-	case WMI_DELL_HOTKEY_VOLUME_DOWN:
-		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_DOWN);
-		break;
-
-	case WMI_DELL_HOTKEY_VOLUME_UP:
-		pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_UP);
-		break;
-
-	default:
-		aprint_debug_dev(sc->sc_dev,
-		    "unknown key 0x%02X for event 0x%02X\n", val, evt);
-		break;
+		if (&data[len] >= end) {
+			DPRINTF(("\n"));
+			break;
+		}
+		if (len < 2) {
+			DPRINTF(("\n"));
+			continue;
+		}
+		for (i = 1; i < len; i++)
+			DPRINTF((" 0x%04X", data[i]));
+		wmi_dell_action(sc, &data[1], len - 1);
+		DPRINTF(("\n"));
+		data = &data[len];
+		/* 
+		 * WMI interface version 0 don't clear the buffer from previous
+		 * event, so if the current event is smaller than the previous
+		 * one there will be garbage after the current event.
+		 * workaround by processing only the first event
+		 */
+		if (sc->sc_version == 0)
+			break;
 	}
 
 out:

Reply via email to