Index | Thread | Search

From:
"Piotr K. Isajew" <pki@ex.com.pl>
Subject:
display brightness on lenovo legion (amdgpu/nvidia dual)
To:
tech@openbsd.org
Date:
Thu, 18 Jul 2024 22:17:55 +0200

Download raw body.

Thread
  • Piotr K. Isajew:

    display brightness on lenovo legion (amdgpu/nvidia dual)

Hi,

I'm using OpenBSD on Lenovo Legion laptop. This machine has an
AMD Ryzen CPU with Radeon graphics and an external NVidia GPU. My
problem so far was that there was no way to control display
brightness on that machine. After some research I hound that this
machine uses windows management instrumentation to control
brightness. Neither acpi backlight nor amdgpu backlight control
do work.

There is a Linux driver available
(https://github.com/torvalds/linux/blob/master/drivers/platform/x86/nvidia-wmi-ec-backlight.c)
which I used as an inspiration for a patch below:

Index: arch/amd64/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/arch/amd64/conf/GENERIC,v
retrieving revision 1.523
diff -u -p -u -r1.523 GENERIC
--- arch/amd64/conf/GENERIC	9 May 2024 17:05:22 -0000	1.523
+++ arch/amd64/conf/GENERIC	18 Jul 2024 19:44:43 -0000
@@ -78,6 +78,7 @@ acpicbkbd*	at acpi?
 acpials*	at acpi?
 abl*		at acpi?	# Apple Backlight
 asmc*		at acpi?	# Apple SMC
+wminvbl* at acpi? # WMI Nvidia backlight
 tpm*		at acpi?
 acpihve*	at acpi?
 acpisurface*	at acpi?
Index: dev/acpi/files.acpi
===================================================================
RCS file: /cvs/src/sys/dev/acpi/files.acpi,v
retrieving revision 1.69
diff -u -p -u -r1.69 files.acpi
--- dev/acpi/files.acpi	23 Apr 2023 00:20:26 -0000	1.69
+++ dev/acpi/files.acpi	18 Jul 2024 19:44:43 -0000
@@ -90,6 +90,11 @@ device	abl
 attach	abl at acpi
 file	dev/acpi/abl.c			abl
 
+# WMI NVIDIA Backlight
+device  wminvbl
+attach  wminvbl at acpi
+file    dev/acpi/wminvbl.c  wminvbl
+
 # Apple System Management Controller (SMC)
 device	asmc
 attach	asmc at acpi
Index: dev/acpi/wminvbl.c
===================================================================
RCS file: dev/acpi/wminvbl.c
diff -N dev/acpi/wminvbl.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ dev/acpi/wminvbl.c	18 Jul 2024 19:44:43 -0000
@@ -0,0 +1,284 @@
+#include <sys/param.h>
+#include <sys/systm.h>
+
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpidev.h>
+#include <dev/acpi/amltypes.h>
+#include <dev/acpi/dsdt.h>
+
+#include <dev/pci/pcidevs.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsdisplayvar.h>
+
+struct wminvbl_softc {
+	struct device sc_dev;
+	struct acpi_softc	*sc_acpi;
+	struct aml_node		*sc_devnode;
+
+	uint8_t wmi_method_id[2];
+	
+	uint8_t sc_brightness;
+	uint8_t sc_brightness_max;
+};
+
+int wminvbl_match(struct device *, void *, void *);
+void wminvbl_attach(struct device *, struct device *, void *);
+
+int wminvbl_get_brightness(struct wminvbl_softc *);
+int wminvbl_get_max_brightness(struct wminvbl_softc *);
+int wminvbl_set_brightness(struct wminvbl_softc *, uint8_t);
+
+int wminvbl_get_param(struct wsdisplay_param *);
+int wminvbl_set_param(struct wsdisplay_param *);
+
+const struct cfattach wminvbl_ca = {
+	sizeof(struct wminvbl_softc), wminvbl_match, wminvbl_attach,
+	NULL, NULL
+};
+
+struct cfdriver wminvbl_cd = {
+	NULL, "wminvbl", DV_DULL
+};
+
+const char *wmi_hids[] = {
+	"PNP0C14", "pnp0c14", NULL
+};
+
+const char *wmi_brightness_uuid =
+	"603E9613-EF25-4338-A3D0-C46177516DB7";
+
+#define WMI_BRIGHTNESS_METHOD_LEVEL 1
+
+#define WMI_BRIGHTNESS_OP_GET 0
+#define WMI_BRIGHTNESS_OP_SET 1
+#define WMI_BRIGHTNESS_OP_GET_MAX 2
+
+struct _wdg_entry {
+	uint8_t guid[16];
+	uint8_t id[2];
+	uint8_t ocnt;
+	uint8_t flags;
+} __attribute__((packed));
+
+struct wmi_brightness_args {
+	uint32_t op;
+	uint32_t val;
+	uint32_t res;
+	uint32_t padding[3];
+	
+};
+
+#define WDG_FLAG_EXPENSIVE 0x1
+#define WDG_FLAG_METHODS	 0x2
+#define WDG_FLAG_STRINGZ	 0x4
+#define WDG_FLAG_EVENT		 0x8
+
+static void
+uuid_to_string(uint8_t uuidbytes[16], char outbuff[37])
+{
+	uint32_t time_low;
+	uint16_t time_mid, time_hi_and_version;
+	uint8_t clock_seq_hi_and_reserved;
+	uint8_t clock_seq_low;
+
+	void *p = uuidbytes;
+	time_low = *(uint32_t*)p; p += 4;
+	time_mid = *(uint16_t*)p; p += 2;
+	time_hi_and_version = *(uint16_t*)p; p += 2;
+	clock_seq_hi_and_reserved = *(uint8_t*)p; ++p;
+	clock_seq_low = *(uint8_t*)p; ++p;
+
+	snprintf(outbuff, 37, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+					 time_low, time_mid, time_hi_and_version,
+					 clock_seq_hi_and_reserved, clock_seq_low,
+					 uuidbytes[10], uuidbytes[11], uuidbytes[12],
+					 uuidbytes[13], uuidbytes[14], uuidbytes[15]);
+}
+
+static int
+_found_wdg(struct aml_node *node, void *aaa)
+{
+	int *found = (int *)aaa;
+
+	if(node->value->type == AML_OBJTYPE_BUFFER) {
+		char uuidbuff[37];
+		uint8_t *bptr = node->value->v_buffer,
+			*eptr = node->value->v_buffer + node->value->length;
+		while(bptr < eptr) {
+			struct _wdg_entry *pe = (struct _wdg_entry *)bptr;
+			bptr += sizeof(struct _wdg_entry);
+			uuid_to_string(pe->guid, uuidbuff);
+			if(strncmp(uuidbuff, wmi_brightness_uuid, 36) == 0) {
+				*found = 1;
+				return 0;
+			}
+		}
+	}
+	return 0;
+}
+
+static int
+_attach_wdg(struct aml_node *node, void *aaa)
+{
+	struct wminvbl_softc *sc = (struct wminvbl_softc *)aaa;
+
+	if(node->value->type == AML_OBJTYPE_BUFFER) {
+		char uuidbuff[37];
+		uint8_t *bptr = node->value->v_buffer,
+			*eptr = node->value->v_buffer + node->value->length;
+		while(bptr < eptr) {
+			struct _wdg_entry *pe = (struct _wdg_entry *)bptr;
+			bptr += sizeof(struct _wdg_entry);
+			uuid_to_string(pe->guid, uuidbuff);
+			if(strncmp(uuidbuff, wmi_brightness_uuid, 36) == 0) {
+				sc->wmi_method_id[0] = pe->id[0];
+				sc->wmi_method_id[1] = pe->id[1];
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+
+int
+wminvbl_match(struct device *parent, void *match, void *aux)
+{
+	struct acpi_attach_args *aa = aux;
+	struct cfdata *cf = match;
+
+	int mr = acpi_matchhids(aa, wmi_hids, cf->cf_driver->cd_name);
+	if(mr) {
+		mr = 0;
+		aml_find_node(aa->aaa_node, "_WDG", _found_wdg, &mr);
+	}
+	return mr;
+}
+
+void
+wminvbl_attach(struct device *parent, struct device *self,
+							 void *aux)
+{
+	struct wminvbl_softc *sc = (struct wminvbl_softc *)self;
+	struct acpi_attach_args *aa = aux;
+
+	sc->sc_acpi = (struct acpi_softc *)parent;
+	sc->sc_devnode = aa->aaa_node;
+
+	aml_find_node(aa->aaa_node, "_WDG", _attach_wdg, sc); 
+	
+	sc->sc_brightness = wminvbl_get_brightness(sc);
+	sc->sc_brightness_max = wminvbl_get_max_brightness(sc);
+
+	ws_get_param = wminvbl_get_param;
+	ws_set_param = wminvbl_set_param;
+	
+	printf(": brightness=%d (max=%d)\n", sc->sc_brightness,
+				 sc->sc_brightness_max);
+}
+
+static int
+_call_wmi(struct wminvbl_softc *sc,
+					int wmi_method_id,
+					struct wmi_brightness_args *args
+				 )
+{
+	char method[5];
+	struct aml_value req[3], resp;
+	memset(&req, 0, sizeof(req));
+	memset(&resp, 0, sizeof(resp));
+	req[0].type = AML_OBJTYPE_INTEGER;
+	req[0].v_integer = 1; // instance
+	req[1].type = AML_OBJTYPE_INTEGER;
+	req[1].v_integer = wmi_method_id;
+	req[2].type = AML_OBJTYPE_BUFFER;
+	req[2].v_buffer = (uint8_t*)args;
+	req[2].length = sizeof(*args);
+	
+	snprintf(method, 5, "WM%c%c", sc->wmi_method_id[0],
+					 sc->wmi_method_id[1]);
+	return aml_evalname(sc->sc_acpi, sc->sc_devnode, method,
+											3, req, &resp);
+}
+ 
+int
+wminvbl_get_brightness(struct wminvbl_softc *sc)
+{
+	struct wmi_brightness_args args;
+	memset(&args, 0, sizeof(args));
+
+	args.op = WMI_BRIGHTNESS_OP_GET;
+	if(_call_wmi(sc, WMI_BRIGHTNESS_METHOD_LEVEL, &args)) {
+		return args.res;
+	}
+	return -1;
+}
+ 
+int
+wminvbl_get_max_brightness(struct wminvbl_softc *sc)
+{
+	struct wmi_brightness_args args;
+	memset(&args, 0, sizeof(args));
+
+	args.op = WMI_BRIGHTNESS_OP_GET_MAX;
+	if(_call_wmi(sc, WMI_BRIGHTNESS_METHOD_LEVEL, &args)) {
+		return args.res;
+	}
+	return -1;
+}
+
+int
+wminvbl_set_brightness(struct wminvbl_softc *sc, uint8_t v)
+{
+	struct wmi_brightness_args args;
+	memset(&args, 0, sizeof(args));
+
+	args.op = WMI_BRIGHTNESS_OP_SET;
+	args.val = v;
+	if(_call_wmi(sc, WMI_BRIGHTNESS_METHOD_LEVEL, &args)) {
+		return args.res;
+	}
+	return 0;
+}
+ 
+int
+wminvbl_get_param(struct wsdisplay_param *dp)
+{
+	struct wminvbl_softc *sc = wminvbl_cd.cd_devs[0];
+
+	if(sc == NULL)
+		return -1;
+
+	switch(dp->param) {
+	case WSDISPLAYIO_PARAM_BRIGHTNESS:
+		dp->min = 0;
+		dp->max = sc->sc_brightness_max;
+		dp->curval = sc->sc_brightness;
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+int
+wminvbl_set_param(struct wsdisplay_param *dp)
+{
+	struct wminvbl_softc *sc = wminvbl_cd.cd_devs[0];
+	
+	if (sc == NULL)
+		return -1;
+	
+	switch(dp->param) {
+	case WSDISPLAYIO_PARAM_BRIGHTNESS:
+		if(dp->curval < 0) dp->curval = 0;
+		if(dp->curval > sc->sc_brightness_max)
+			dp->curval = sc->sc_brightness_max;
+		wminvbl_set_brightness(sc, dp->curval);
+		sc->sc_brightness = dp->curval;
+		return 0;
+	default:
+		return -1;
+	}
+}
Index: dev/pci/drm/amd/amdgpu/amdgpu_drv.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/drm/amd/amdgpu/amdgpu_drv.c,v
retrieving revision 1.44
diff -u -p -u -r1.44 amdgpu_drv.c
--- dev/pci/drm/amd/amdgpu/amdgpu_drv.c	14 May 2024 04:55:42 -0000	1.44
+++ dev/pci/drm/amd/amdgpu/amdgpu_drv.c	18 Jul 2024 19:44:43 -0000
@@ -3348,10 +3348,14 @@ amdgpu_wsioctl(void *v, u_long cmd, cadd
 
 		switch (dp->param) {
 		case WSDISPLAYIO_PARAM_BRIGHTNESS:
-			dp->min = 0;
-			dp->max = bd->props.max_brightness;
-			dp->curval = bd->props.brightness;
-			return (dp->max > dp->min) ? 0 : -1;
+			if(ws_get_param == NULL) { 
+				dp->min = 0;
+				dp->max = bd->props.max_brightness;
+				dp->curval = bd->props.brightness;
+				return (dp->max > dp->min) ? 0 : -1;
+			} else {
+				return ws_get_param(dp);
+			}
 		}
 		break;
 	case WSDISPLAYIO_SETPARAM:
@@ -3360,10 +3364,14 @@ amdgpu_wsioctl(void *v, u_long cmd, cadd
 
 		switch (dp->param) {
 		case WSDISPLAYIO_PARAM_BRIGHTNESS:
-			bd->props.brightness = dp->curval;
-			backlight_update_status(bd);
-			knote_locked(&adev->ddev.note, NOTE_CHANGE);
-			return 0;
+			if(ws_set_param == NULL) {
+				bd->props.brightness = dp->curval;
+				backlight_update_status(bd);
+				knote_locked(&adev->ddev.note, NOTE_CHANGE);
+				return 0;
+			} else {
+				return ws_set_param(dp);
+			}
 		}
 		break;
 	case WSDISPLAYIO_SVIDEO: