From: Mark Kettenis Subject: Re: acpi wmi asus driver To: "Ted Unangst" Cc: tech@openbsd.org Date: Sun, 11 May 2025 23:42:46 +0200 > From: "Ted Unangst" > Date: Sat, 10 May 2025 21:57:07 -0400 > > 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? Probably worth cleaning this up. > I include just acpiwmi.c for reading. > > > /* $OpenBSD$ */ > /* > * Copyright (c) 2025 Ted Unangst > * > * 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 > #include > #include > > #include > #include > > #include > #include > #include > #include > #include > > 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(¶ms, 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; > } > >