Index | Thread | Search

From:
Robert Nagy <robert@openbsd.org>
Subject:
Re: acpi wmi asus driver
To:
Ted Unangst <tedu@tedunangst.com>
Cc:
tech@openbsd.org
Date:
Mon, 19 May 2025 02:21:07 +0200

Download raw body.

Thread
Hey,

I've been working on a similar driver for my Asus laptop which you can
find on cvs in my home (asuswmi.c), however I've just merged some changes
into your code instead so that we can get that in as it is more generic
than mine.
I've also did some cleanup. I think it is safe to get this in so that we
can continue working on it in tree.


/*
 * 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>

/* #define ACPIWMI_DEBUG */

#ifdef ACPIWMI_DEBUG
#define DPRINTF(x)	printf x
#else
#define DPRINTF(x)
#endif

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_notify(struct aml_node *, int, void *);

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

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);
}

#define GUID_SIZE 16

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

#define WMI_EVENT_MASK	0xffff

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

#ifdef ACPIWMI_DEBUG
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;
}
#endif

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

struct wmi_target {
	uint8_t guid[GUID_SIZE];
	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);

	if (aml_evalname(sc->sc_acpi, sc->sc_node, "_WDG", 0, NULL, &res))
		return;

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

	int num = res.length / sizeof(struct guidinfo);
	struct guidinfo *guids = (struct guidinfo *)res.v_buffer;
#ifdef ACPIWMI_DEBUG
	char buf[64];
#endif
	for (int i = 0; i < num; i++) {
		DPRINTF(("method: %s\n", guid_string(guids[i].guid, buf)));
		for (int j = 0; j < 1; j++) {
			if (memcmp(guids[i].guid, targets[j].guid, GUID_SIZE) == 0) {
				DPRINTF(("found it! flags %d [%d, %d]\n", guids[i].flags,
					guids[i].oid[0], guids[i].oid[1]));
				targets[j].init(sc, &guids[i]);
				aml_register_notify(sc->sc_node, aaa->aaa_dev,
					acpiwmi_notify, sc, ACPIDEV_NOPOLL);
			}
		}
	}
	aml_freevalue(&res);
}

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("%s: failed to evaulate _WED\n", DEVNAME(sc));
		return 0;
	}

	if (res.type != AML_OBJTYPE_INTEGER) {
		aml_freevalue(&res);
		return 0;
	}

	int code = res.v_integer & WMI_EVENT_MASK;

	aml_freevalue(&res);

	SLIST_FOREACH(wh, &sc->sc_handlers, w_next)
		wh->w_event(wh, code);

	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;
}

/* ASUS related WMI code */
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_UP		0xc4
#define ASUS_EVENT_BACKLIGHT_DOWN	0xc5
#define ASUS_EVENT_BACKLIGHT_TOGGLE	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:
		maxval = 3;
		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)
		DPRINTF(("no led\n"));
	else
		DPRINTF(("led: %d\n", res & WMI_EVENT_MASK));

	asus_dev_get(wh, ASUS_DEV_PERF, &res);
	if (res == -2)
		DPRINTF(("no perf pol\n"));
	else {
		DPRINTF(("perf pol: %d\n", res & WMI_EVENT_MASK));
		wh->w_perfid = ASUS_DEV_PERF;
	}

	asus_dev_get(wh, ASUS_DEV_PERF_2, &res);
	if (res == -2)
		DPRINTF(("no perf pol\n"));
	else {
		DPRINTF(("perf pol: %d\n", res & WMI_EVENT_MASK));
		wh->w_perfid = ASUS_DEV_PERF_2;
	}

	/* set initial keyboard brightness */
	wh->w_backlight = 1;

	/* set default Fn lock status (on) */
	asus_toggle(wh, ASUS_DEV_FNLOCK, &wh->w_fnlock);

	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_UP:
	case ASUS_EVENT_BACKLIGHT_DOWN:
	case ASUS_EVENT_BACKLIGHT_TOGGLE:
		asus_toggle(wh, ASUS_DEV_BACKLIGHT, &wh->w_backlight);
		DPRINTF(("toggle backlight %d\n", wh->w_backlight));
		break;
	case ASUS_EVENT_FNLOCK:
		asus_toggle(wh, ASUS_DEV_FNLOCK, &wh->w_fnlock);
		DPRINTF(("toggle fnlock %d\n", wh->w_fnlock));
		break;
	case ASUS_EVENT_PERF:
		asus_toggle(wh, wh->w_perfid, &wh->w_perf);
		DPRINTF(("toggle perf %d\n", wh->w_perf));
		break;
	default:
		DPRINTF(("asus button %d\n", code));
		break;
	}
	return 0;
}