Index | Thread | Search

From:
"Ted Unangst" <tedu@tedunangst.com>
Subject:
acpi wmi asus driver
To:
tech@openbsd.org
Date:
Sat, 10 May 2025 21:57:07 -0400

Download raw body.

Thread
My newish ASUS laptop needs WMI to handle hotkeys like backlight toggle.

More importantly, for me, it's needed to handle the Fn-F hotkey to switch
fan/performance profiles. The system is far more pleasant to use in whisper
mode. I also notice a substantial improvement in battery life, without much
performance difference. It affects the power limits, but more long term I
think.

Tested with both an AMD zenbook and Intel vivobook.

Anyway, there's tons of other devices that use WMI as well. They will need
their own WMI handlers, but I think this is a start. It's a little chatty,
and maybe not fully knf, but perhaps it can be beaten into submission?

I include just acpiwmi.c for reading.


/*	$OpenBSD$ */
/*
 * Copyright (c) 2025 Ted Unangst <tedu@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>

#include <machine/intr.h>
#include <machine/bus.h>

#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpidev.h>
#include <dev/acpi/amltypes.h>
#include <dev/acpi/dsdt.h>

struct acpiwmi_softc {
	struct device		sc_dev;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;

	struct acpi_softc	*sc_acpi;
	struct aml_node		*sc_node;

	SLIST_HEAD(, wmihandler) sc_handlers;
};

struct wmihandler {
	SLIST_ENTRY(wmihandler) w_next;
	struct acpiwmi_softc *w_sc;
	int (*w_event)(struct wmihandler *, int);
	char w_method[5];
};

int  acpiwmi_match(struct device *, void *, void *);
void acpiwmi_attach(struct device *, struct device *, void *);
int  acpiwmi_activate(struct device *, int);
int  acpiwmi_notify(struct aml_node *, int, void *);

const struct cfattach acpiwmi_ca = {
	sizeof (struct acpiwmi_softc), acpiwmi_match, acpiwmi_attach,
	NULL, acpiwmi_activate
};

struct cfdriver acpiwmi_cd = {
	NULL, "acpiwmi", DV_DULL
};

const char *acpiwmi_hids[] = {
	"PNP0C14",
	NULL
};

int
acpiwmi_match(struct device *parent, void *match, void *aux)
{
	struct acpi_attach_args *aaa = aux;
	struct cfdata *cf = match;

	return acpi_matchhids(aaa, acpiwmi_hids, cf->cf_driver->cd_name);
}

struct guidinfo {
	uint8_t guid[16];
	uint8_t oid[2];
	uint8_t maxinstance;
	uint8_t flags;
};

#define WMI_EXPENSIVE	1
#define WMI_METHOD		2
#define WMI_STRING		4
#define WMI_EVENT		8

static char *
guid_string(const uint8_t *guid, char *buf)
{
	size_t space = 64; /* xxx */
	char *p = buf;
	int i = 0;

	/* 3210-54-76-89-012345 */
	for (i = 0; i < 4; i++) { // 3210
		snprintf(p, space, "%02X", guid[0 + 3 - i]);
		p += 2;
	}
	*p++ = '-';
	for (i = 0; i < 2; i++) { // 54
		snprintf(p, space, "%02X", guid[4 + 1 - i]);
		p += 2;
	}
	*p++ = '-';
	for (i = 0; i < 2; i++) { // 76
		snprintf(p, space, "%02X", guid[6 + 1 - i]);
		p += 2;
	}
	*p++ = '-';
	for (i = 0; i < 2; i++) { // 89
		snprintf(p, space, "%02X", guid[8 + i]);
		p += 2;
	}
	*p++ = '-';
	for (i = 0; i < 6; i++) { // 012345
		snprintf(p, space, "%02X", guid[10 + i]);
		p += 2;
	}
	return buf;
}

static int wmi_asus_init(struct acpiwmi_softc *, struct guidinfo *);
static int wmi_asus_event(struct wmihandler *, int);

struct wmi_target {
	uint8_t guid[16];
	int (*init)(struct acpiwmi_softc *, struct guidinfo *);
};

static struct wmi_target targets[] = {
	{
		/* "97845ED0-4E6D-11DE-8A39-0800200C9A66" */
		{ 0xd0, 0x5e, 0x84, 0x97, 0x6d, 0x4e, 0xde, 0x11,
		  0x8a, 0x39, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 },
		wmi_asus_init,
	},
};

void
acpiwmi_attach(struct device *parent, struct device *self, void *aux)
{
	struct acpiwmi_softc *sc = (struct acpiwmi_softc *)self;
	struct acpi_attach_args *aaa = aux;
	struct aml_value res;

	sc->sc_acpi = (struct acpi_softc *)parent;
	sc->sc_node = aaa->aaa_node;
	SLIST_INIT(&sc->sc_handlers);

	printf(": %s", aaa->aaa_node->name);

	if (aml_evalname(sc->sc_acpi, sc->sc_node, "_WDG", 0, NULL, &res) == -1) {
        printf(": error\n");
		return;
	}
	printf("\n");

	int num = res.length / sizeof(struct guidinfo);
	struct guidinfo *guids = (struct guidinfo *)res.v_buffer;
	char buf[64];
	for (int i = 0; i < num; i++) {
		printf("method: %s\n", guid_string(guids[i].guid, buf));
		for (int j = 0; j < 1; j++) {
			if (memcmp(guids[i].guid, targets[j].guid, 16) == 0) {
				printf("found it! flags %d [%d, %d]\n", guids[i].flags,
					guids[i].oid[0], guids[i].oid[1]);
				wmi_asus_init(sc, &guids[i]);
			}
		}
	}
	aml_freevalue(&res);

	aml_register_notify(sc->sc_node, aaa->aaa_dev,
		acpiwmi_notify, sc, ACPIDEV_NOPOLL);
}

int
acpiwmi_notify(struct aml_node *node, int note, void *arg)
{
	struct aml_value input, res;
	struct acpiwmi_softc *sc = arg;
	struct wmihandler *wh;

	memset(&input, 0, sizeof(input));
	input.type = AML_OBJTYPE_INTEGER;
	input.v_integer = note; /* ??? */
	if (aml_evalname(sc->sc_acpi, sc->sc_node, "_WED", 1, &input, &res) == -1) {
		printf("trouble eval wed\n");
		return 0;
	}
	int code = res.v_integer;
	code &= 0xffff;
	aml_freevalue(&res);
	SLIST_FOREACH(wh, &sc->sc_handlers, w_next)
		wh->w_event(wh, code);
	return 0;
}

int
acpiwmi_activate(struct device *self, int act)
{

	return 0;
}

static int
wmi_eval_method(struct wmihandler *wh, int32_t instance, uint32_t methodid,
	struct aml_value *input, struct aml_value *res)
{
	struct aml_value params[3];
	struct acpiwmi_softc *sc = wh->w_sc;

	memset(&params, 0, sizeof(params));
	params[0].type = AML_OBJTYPE_INTEGER;
	params[0].v_integer = instance;
	params[1].type = AML_OBJTYPE_INTEGER;
	params[1].v_integer = methodid;
	params[2] = *input;
	if (aml_evalname(sc->sc_acpi, sc->sc_node, wh->w_method, 3, params, res) == -1) {
		printf("can't eval method\n");
		return -1;
	}
	return 0;
}

struct wmiasus {
	struct wmihandler	w_handler;
	int					w_backlight;
	int					w_fnlock;
	int					w_perf;
	int					w_perfid;
};

#define ASUS_METHOD_INIT	0x54494E49
#define ASUS_METHOD_DSTS	0x53545344
#define ASUS_METHOD_DEVS	0x53564544

#define ASUS_DEV_RSOC		0x00120057
#define ASUS_DEV_ALS		0x00050001
#define ASUS_DEV_PERF		0x00120075
#define ASUS_DEV_PERF_2		0x00110019
#define ASUS_DEV_BACKLIGHT	0x00050021
#define ASUS_DEV_FNLOCK		0x00100023

#define ASUS_EVENT_AC_OFF		0x57
#define ASUS_EVENT_AC_ON		0x58
#define ASUS_EVENT_BACKLIGHT	0xc7
#define ASUS_EVENT_FNLOCK		0x4e
#define ASUS_EVENT_PERF			0x9d

static int
asus_eval_method(struct wmiasus *wh, uint32_t methodid,
	uint32_t arg0, uint32_t arg1, uint32_t *retval)
{
	int args[6] = { arg0, arg1, 0 };
	struct aml_value input, res;
	
	memset(&input, 0, sizeof(input));
	input.type = AML_OBJTYPE_BUFFER;
	input.length = sizeof(args);
	input.v_buffer = (void *)args;
	if (wmi_eval_method(&wh->w_handler, 0, methodid, &input, &res) == -1)
		return -1;
	if (retval)
		*retval = res.v_integer;
	aml_freevalue(&res);
	return 0;
}
static int
asus_dev_get(struct wmiasus *wh, uint32_t devid, uint32_t *retval)
{
	return asus_eval_method(wh, ASUS_METHOD_DSTS, devid, 0, retval);
}

static int
asus_dev_set(struct wmiasus *wh, uint32_t devid, uint32_t val)
{
	uint32_t retval;
	if (asus_eval_method(wh, ASUS_METHOD_DEVS, devid, val, &retval) == -1)
		return -1;
	return retval;
}
static void
asus_toggle(struct wmiasus *wh, int devid, int *val)
{
	int maxval = 1, mask = 0;

	switch (devid) {
	case ASUS_DEV_BACKLIGHT:
		mask = 0x80;
		break;
	case ASUS_DEV_PERF:
	case ASUS_DEV_PERF_2:
		maxval = 2;
		break;
	}
	*val += 1;
	if (*val > maxval)
		*val = 0;
	asus_dev_set(wh, devid, *val | mask);
}

static int
wmi_asus_init(struct acpiwmi_softc *sc, struct guidinfo *ginfo)
{
	uint32_t retval;
	struct wmiasus *wh;

	wh = malloc(sizeof(*wh), M_DEVBUF, M_NOWAIT | M_ZERO);
	if (!wh)
		return -1;
	wh->w_handler.w_sc = sc;
	snprintf(wh->w_handler.w_method, sizeof(wh->w_handler.w_method), "WM%c%c",
		ginfo->oid[0], ginfo->oid[1]);

	if (asus_eval_method(wh, ASUS_METHOD_INIT, 0, 0, &retval) == -1 ||
	    retval != 1) {
		printf("cant init\n");
		return -1;
	}
	wh->w_handler.w_event = wmi_asus_event;
	SLIST_INSERT_HEAD(&sc->sc_handlers, &wh->w_handler, w_next);

	struct aml_value on;
	memset(&on, 0, sizeof(on));
	on.type = AML_OBJTYPE_INTEGER;
	on.v_integer = 1;
	char method[5];
	snprintf(method, sizeof(method), "WC%c%c", ginfo->oid[0], ginfo->oid[1]);
	aml_evalname(sc->sc_acpi, sc->sc_node, method, 1, &on, NULL);
	uint32_t res = 0;
	asus_dev_get(wh, ASUS_DEV_BACKLIGHT, &res);
	if (res == -2)
		printf("no led\n");
	else
		printf("led: %d\n", res & 0xffff);
	asus_dev_get(wh, ASUS_DEV_PERF, &res);
	if (res == -2)
		printf("no perf pol\n");
	else {
		printf("perf pol: %d\n", res & 0xffff);
		wh->w_perfid = ASUS_DEV_PERF;
	}
	asus_dev_get(wh, ASUS_DEV_PERF_2, &res);
	if (res == -2)
		printf("no perf pol\n");
	else {
		printf("perf pol: %d\n", res & 0xffff);
		wh->w_perfid = ASUS_DEV_PERF_2;
	}
	wh->w_backlight = 1; // defaults on
	asus_toggle(wh, ASUS_DEV_FNLOCK, &wh->w_fnlock); // turn on
	return 0;
}

int
wmi_asus_event(struct wmihandler *wmi, int code)
{
	struct wmiasus *wh = (struct wmiasus *)wmi;

	switch (code) {
	case ASUS_EVENT_AC_OFF:
	case ASUS_EVENT_AC_ON:
		break;
	case ASUS_EVENT_BACKLIGHT:
		asus_toggle(wh, ASUS_DEV_BACKLIGHT, &wh->w_backlight);
		printf("toggle backlight %d\n", wh->w_backlight);
		break;
	case ASUS_EVENT_FNLOCK:
		asus_toggle(wh, ASUS_DEV_FNLOCK, &wh->w_fnlock);
		printf("toggle fnlock %d\n", wh->w_fnlock);
		break;
	case ASUS_EVENT_PERF:
		asus_toggle(wh, wh->w_perfid, &wh->w_perf);
		printf("toggle perf %d\n", wh->w_perf);
		break;
	default:
		printf("asus button %d\n", code);
		break;
	}
	return 0;
}