diff mbox series

[1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices

Message ID 20240926174405.110748-2-wse@tuxedocomputers.com (mailing list archive)
State Superseded, archived
Headers show
Series [1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices | expand

Commit Message

Werner Sembach Sept. 26, 2024, 5:44 p.m. UTC
The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
controllable RGB keyboard backlight. The firmware API for it is implemented
via WMI.

To make the backlight userspace configurable this driver emulates a
LampArray HID device and translates the input from hidraw to the
corresponding WMI calls. This is a new approach as the leds subsystem lacks
a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
needs to be established.

Co-developed-by: Christoffer Sandberg <cs@tuxedo.de>
Signed-off-by: Christoffer Sandberg <cs@tuxedo.de>
Signed-off-by: Werner Sembach <wse@tuxedocomputers.com>
Link: https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
---
 MAINTAINERS                                   |   6 +
 drivers/platform/x86/Kconfig                  |   2 +
 drivers/platform/x86/Makefile                 |   3 +
 drivers/platform/x86/tuxedo/Kbuild            |   9 +
 drivers/platform/x86/tuxedo/Kconfig           |  14 +
 .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.c      |  86 ++
 .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.h      |  20 +
 .../tuxedo_nb04_wmi_ab_virtual_lamp_array.c   | 741 ++++++++++++++++++
 .../tuxedo_nb04_wmi_ab_virtual_lamp_array.h   |  18 +
 .../x86/tuxedo/tuxedo_nb04_wmi_util.c         |  85 ++
 .../x86/tuxedo/tuxedo_nb04_wmi_util.h         | 112 +++
 11 files changed, 1096 insertions(+)
 create mode 100644 drivers/platform/x86/tuxedo/Kbuild
 create mode 100644 drivers/platform/x86/tuxedo/Kconfig
 create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
 create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
 create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
 create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
 create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
 create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h

Comments

Armin Wolf Sept. 26, 2024, 6:39 p.m. UTC | #1
Am 26.09.24 um 19:44 schrieb Werner Sembach:

> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
> controllable RGB keyboard backlight. The firmware API for it is implemented
> via WMI.
>
> To make the backlight userspace configurable this driver emulates a
> LampArray HID device and translates the input from hidraw to the
> corresponding WMI calls. This is a new approach as the leds subsystem lacks
> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
> needs to be established.
>
> Co-developed-by: Christoffer Sandberg <cs@tuxedo.de>
> Signed-off-by: Christoffer Sandberg <cs@tuxedo.de>
> Signed-off-by: Werner Sembach <wse@tuxedocomputers.com>
> Link: https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
> ---
>   MAINTAINERS                                   |   6 +
>   drivers/platform/x86/Kconfig                  |   2 +
>   drivers/platform/x86/Makefile                 |   3 +
>   drivers/platform/x86/tuxedo/Kbuild            |   9 +
>   drivers/platform/x86/tuxedo/Kconfig           |  14 +
>   .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.c      |  86 ++
>   .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.h      |  20 +
>   .../tuxedo_nb04_wmi_ab_virtual_lamp_array.c   | 741 ++++++++++++++++++
>   .../tuxedo_nb04_wmi_ab_virtual_lamp_array.h   |  18 +
>   .../x86/tuxedo/tuxedo_nb04_wmi_util.c         |  85 ++
>   .../x86/tuxedo/tuxedo_nb04_wmi_util.h         | 112 +++
>   11 files changed, 1096 insertions(+)
>   create mode 100644 drivers/platform/x86/tuxedo/Kbuild
>   create mode 100644 drivers/platform/x86/tuxedo/Kconfig
>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cc40a9d9b8cd1..3385ad51af194 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23358,6 +23358,12 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat
>   F:	tools/power/x86/turbostat/
>   F:	tools/testing/selftests/turbostat/
>
> +TUXEDO DRIVERS
> +M:	Werner Sembach <wse@tuxedocomputers.com>
> +L:	platform-driver-x86@vger.kernel.org
> +S:	Supported
> +F:	drivers/platform/x86/tuxedo/
> +
>   TW5864 VIDEO4LINUX DRIVER
>   M:	Bluecherry Maintainers <maintainers@bluecherrydvr.com>
>   M:	Andrey Utkin <andrey.utkin@corp.bluecherry.net>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index ddfccc226751f..c7cffb222adac 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -1196,3 +1196,5 @@ config P2SB
>   	  The main purpose of this library is to unhide P2SB device in case
>   	  firmware kept it hidden on some platforms in order to access devices
>   	  behind it.
> +
> +source "drivers/platform/x86/tuxedo/Kconfig"
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index e1b1429470674..1562dcd7ad9a5 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -153,3 +153,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS)		+= winmate-fm07-keys.o
>
>   # SEL
>   obj-$(CONFIG_SEL3350_PLATFORM)		+= sel3350-platform.o
> +
> +# TUXEDO
> +obj-y					+= tuxedo/
> diff --git a/drivers/platform/x86/tuxedo/Kbuild b/drivers/platform/x86/tuxedo/Kbuild
> new file mode 100644
> index 0000000000000..5a3506ab98131
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/Kbuild
> @@ -0,0 +1,9 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# TUXEDO X86 Platform Specific Drivers
> +#
> +
> +tuxedo_nb04_wmi_ab-y			:= tuxedo_nb04_wmi_ab_init.o
> +tuxedo_nb04_wmi_ab-y			+= tuxedo_nb04_wmi_util.o
> +tuxedo_nb04_wmi_ab-y			+= tuxedo_nb04_wmi_ab_virtual_lamp_array.o
> +obj-$(CONFIG_TUXEDO_NB04_WMI_AB)	+= tuxedo_nb04_wmi_ab.o
> diff --git a/drivers/platform/x86/tuxedo/Kconfig b/drivers/platform/x86/tuxedo/Kconfig
> new file mode 100644
> index 0000000000000..b1f7c6ceeaae4
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/Kconfig
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# TUXEDO X86 Platform Specific Drivers
> +#
> +
> +menuconfig TUXEDO_NB04_WMI_AB
> +	tristate "TUXEDO NB04 WMI AB Platform Driver"
> +	default m
> +	help
> +	  This driver implements the WMI AB device found on TUXEDO Notebooks
> +	  with board vendor NB04. For the time being only the keyboard backlight
> +	  control is implemented.
> +
> +	  When compiled as a module it will be called tuxedo_nb04_wmi_ab.
> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
> new file mode 100644
> index 0000000000000..6e4446b0e3dd8
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
> @@ -0,0 +1,86 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This driver implements the WMI AB device found on TUXEDO Notebooks with board
> + * vendor NB04.
> + *
> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/module.h>
> +#include <linux/wmi.h>
> +#include <linux/dmi.h>
> +
> +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
> +
> +#include "tuxedo_nb04_wmi_ab_init.h"
> +
> +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
> +// side we therefore only run this driver on tested devices defined by this list.
> +static const struct dmi_system_id tested_devices_dmi_table[] = {
> +	{
> +		// TUXEDO Sirius 16 Gen1
> +		.matches = {
> +			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
> +			DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
> +		},
> +	},
> +	{
> +		// TUXEDO Sirius 16 Gen2
> +		.matches = {
> +			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
> +			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
> +		},
> +	},
> +	{ }
> +};
> +
> +static int probe(struct wmi_device *wdev, const void __always_unused *context)
> +{
> +	struct tuxedo_nb04_wmi_driver_data_t *driver_data;
> +
> +	if (dmi_check_system(tested_devices_dmi_table))
> +		return -ENODEV;

Hi,

please do this DMI check during module initialization. This avoids having an useless WMI driver
on unsupported machines and allows for marking tested_devices_dmi_table as __initconst.

Besides that, maybe a "force" module parameter for overriding the DMI checking could be
useful?

> +
> +	driver_data = devm_kzalloc(&wdev->dev, sizeof(struct tuxedo_nb04_wmi_driver_data_t),
> +				   GFP_KERNEL);

Please use sizeof(*driver_data).

> +	if (!driver_data)
> +		return -ENOMEM;
> +
> +	mutex_init(&driver_data->wmi_access_mutex);

Please use devm_mutex_init(), so the mutex is properly destroyed when unbinding.

> +
> +	dev_set_drvdata(&wdev->dev, driver_data);
> +
> +	tuxedo_nb04_virtual_lamp_array_add_device(wdev, &driver_data->virtual_lamp_array_hdev);

Error handling missing.

> +
> +	return 0;
> +}
> +
> +static void remove(struct wmi_device *wdev)
> +{
> +	struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data;
> +
> +	hid_destroy_device(driver_data->virtual_lamp_array_hdev);
> +}
> +
> +static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = {
> +	{ .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids);
> +
> +static struct wmi_driver tuxedo_nb04_wmi_ab_driver = {
> +	.driver = {
> +		.name = "tuxedo_nb04_wmi_ab",
> +		.owner = THIS_MODULE
> +	},
> +	.id_table = tuxedo_nb04_wmi_ab_device_ids,
> +	.probe = probe,
> +	.remove = remove

I recommend setting probe_type = PROBE_PREFER_ASYNCHRONOUS, see Documentation/wmi/driver-development-guide.rst.
Also please set no_singleton = true.

> +};
> +module_wmi_driver(tuxedo_nb04_wmi_ab_driver);
> +
> +MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices");
> +MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
> new file mode 100644
> index 0000000000000..aebfd465c9b61
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
> @@ -0,0 +1,20 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * This driver implements the WMI AB device found on TUXEDO Notebooks with board
> + * vendor NB04.
> + *
> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
> + */
> +
> +#ifndef TUXEDO_NB04_WMI_AB_INIT_H
> +#define TUXEDO_NB04_WMI_AB_INIT_H
> +
> +#include <linux/mutex.h>
> +#include <linux/hid.h>
> +
> +struct tuxedo_nb04_wmi_driver_data_t {
> +	struct mutex wmi_access_mutex;
> +	struct hid_device *virtual_lamp_array_hdev;
> +};
> +
> +#endif
> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
> new file mode 100644
> index 0000000000000..04af19aa6ad5f
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
> @@ -0,0 +1,741 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This code gives the built in RGB lighting of the TUXEDO NB04 devices a
> + * standardised interface, namely HID LampArray.
> + *
> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include "tuxedo_nb04_wmi_util.h"
> +
> +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
> +
> +#define dev_to_wdev(__dev)	container_of(__dev, struct wmi_device, dev)

Please use to_wmi_device() instead.

> +
> +enum report_ids {
> +	LAMP_ARRAY_ATTRIBUTES_REPORT_ID		= 0x01,
> +	LAMP_ATTRIBUTES_REQUEST_REPORT_ID	= 0x02,
> +	LAMP_ATTRIBUTES_RESPONSE_REPORT_ID	= 0x03,
> +	LAMP_MULTI_UPDATE_REPORT_ID		= 0x04,
> +	LAMP_RANGE_UPDATE_REPORT_ID		= 0x05,
> +	LAMP_ARRAY_CONTROL_REPORT_ID		= 0x06,
> +};
> +
> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> +	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> +	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
> +	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
> +	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> +	0x4f,                                 0x62, 0x63, 0x58
> +};
> +
> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
> +	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
> +	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
> +	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
> +	232500, 251500, 273500,                           294500, 311200, 327900,
> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
> +	292000,                                           311200, 327900, 344600
> +};
> +
> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
> +	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
> +	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
> +	121500, 121500, 129000,                           121500, 121500, 121500,
> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
> +	147000,                                           139500, 139500, 130500
> +};
> +
> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
> +	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
> +	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
> +	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
> +	  6000,   6000,   6125,                             6000,   6000,   6000,
> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
> +	  6375,                                             6250,   6250,   6125
> +};
> +
> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> +	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> +	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
> +	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
> +	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> +	0x4f,                                 0x62, 0x63, 0x58
> +};
> +
> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
> +	218000, 234500, 251000,                           294500, 311200, 327900,
> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
> +	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
> +	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
> +	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
> +	292000,                                           311200, 327900, 344600
> +};
> +
> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
> +	 85500,  85500,  85500,                            85500,  85500,  85500,
> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
> +	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
> +	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
> +	147000,                                           139500, 139500, 130500
> +};
> +
> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
> +	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
> +	  5500,   5500,   5500,                             5500,   5500,   5500,
> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
> +	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
> +	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
> +	  6375,                                             6250,   6250,   6125
> +};
> +
> +struct driver_data_t {
> +	uint8_t keyboard_type;
> +	uint8_t lamp_count;
> +	uint8_t next_lamp_id;
> +	union tuxedo_nb04_wmi_496_b_in_80_b_out_input next_kbl_set_multiple_keys_input;
> +};
> +
> +
> +static int ll_start(struct hid_device *hdev)
> +{
> +	int ret;
> +	struct driver_data_t *driver_data;
> +	struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
> +	union tuxedo_nb04_wmi_8_b_in_80_b_out_input input;
> +	union tuxedo_nb04_wmi_8_b_in_80_b_out_output output;
> +
> +	driver_data = devm_kzalloc(&hdev->dev, sizeof(struct driver_data_t), GFP_KERNEL);
> +	if (!driver_data)
> +		return -ENOMEM;

Please use sizeof(*driver_data).

> +
> +	input.get_device_status_input.device_type = WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD;
> +	ret = tuxedo_nb04_wmi_8_b_in_80_b_out(wdev, WMI_AB_GET_DEVICE_STATUS, &input, &output);
> +	if (ret)
> +		return ret;
> +
> +	driver_data->keyboard_type = output.get_device_status_output.keyboard_physical_layout;
> +	driver_data->lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
> +	driver_data->next_lamp_id = 0;
> +
> +	hdev->driver_data = driver_data;
> +
> +	return ret;
> +}
> +
> +
> +static void ll_stop(struct hid_device __always_unused *hdev)
> +{
> +}
> +
> +
> +static int ll_open(struct hid_device __always_unused *hdev)
> +{
> +	return 0;
> +}
> +
> +
> +static void ll_close(struct hid_device __always_unused *hdev)
> +{
> +}

I have no experience with the HID subsystem, but this looks suspicious.

> +
> +
> +static uint8_t report_descriptor[327] = {
> +	0x05, 0x59,			// Usage Page (Lighting and Illumination)
> +	0x09, 0x01,			// Usage (Lamp Array)
> +	0xa1, 0x01,			// Collection (Application)
> +	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
> +	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x03,			//   Usage (Lamp Count)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
> +	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
> +	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
> +	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
> +	0x09, 0x07,			//   Usage (Lamp Array Kind)
> +	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
> +	0x75, 0x20,			//   Report Size (32)
> +	0x95, 0x05,			//   Report Count (5)
> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
> +	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x21,			//   Usage (Lamp Id)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
> +	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x21,			//   Usage (Lamp Id)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x23,			//   Usage (Position X In Micrometers)
> +	0x09, 0x24,			//   Usage (Position Y In Micrometers)
> +	0x09, 0x25,			//   Usage (Position Z In Micrometers)
> +	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
> +	0x09, 0x26,			//   Usage (Lamp Purposes)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
> +	0x75, 0x20,			//   Report Size (32)
> +	0x95, 0x05,			//   Report Count (5)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x28,			//   Usage (Red Level Count)
> +	0x09, 0x29,			//   Usage (Green Level Count)
> +	0x09, 0x2a,			//   Usage (Blue Level Count)
> +	0x09, 0x2b,			//   Usage (Intensity Level Count)
> +	0x09, 0x2c,			//   Usage (Is Programmable)
> +	0x09, 0x2d,			//   Usage (Input Binding)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x06,			//   Report Count (6)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
> +	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x03,			//   Usage (Lamp Count)
> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x25, 0x08,			//   Logical Maximum (8)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x02,			//   Report Count (2)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x21,			//   Usage (Lamp Id)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x08,			//   Report Count (8)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x20,			//   Report Count (32)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
> +	0x09, 0x60,			//  Usage (Lamp Range Update Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x25, 0x08,			//   Logical Maximum (8)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x61,			//   Usage (Lamp Id Start)
> +	0x09, 0x62,			//   Usage (Lamp Id End)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x02,			//   Report Count (2)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x04,			//   Report Count (4)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
> +	0x09, 0x70,			//  Usage (Lamp Array Control Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x71,			//   Usage (Autonomous Mode)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x25, 0x01,			//   Logical Maximum (1)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0xc0				// End Collection
> +};
> +
> +static int ll_parse(struct hid_device *hdev)
> +{
> +	return hid_parse_report(hdev, report_descriptor, sizeof(report_descriptor));
> +}
> +
> +
> +struct __packed lamp_array_attributes_report_t {
> +	const uint8_t report_id;
> +	uint16_t lamp_count;
> +	uint32_t bounding_box_width_in_micrometers;
> +	uint32_t bounding_box_height_in_micrometers;
> +	uint32_t bounding_box_depth_in_micrometers;
> +	uint32_t lamp_array_kind;
> +	uint32_t min_update_interval_in_microseconds;
> +};
> +
> +static int handle_lamp_array_attributes_report(struct hid_device *hdev,
> +					       struct lamp_array_attributes_report_t *rep)
> +{
> +	struct driver_data_t *driver_data = hdev->driver_data;
> +
> +	rep->lamp_count = driver_data->lamp_count;
> +	rep->bounding_box_width_in_micrometers = 368000;
> +	rep->bounding_box_height_in_micrometers = 266000;
> +	rep->bounding_box_depth_in_micrometers = 30000;
> +	// LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of "HID Usage Tables v1.5"
> +	rep->lamp_array_kind = 1;
> +	// Some guessed value for interval microseconds
> +	rep->min_update_interval_in_microseconds = 500;
> +
> +	return sizeof(struct lamp_array_attributes_report_t);
> +}
> +
> +
> +struct __packed lamp_attributes_request_report_t {
> +	const uint8_t report_id;
> +	uint16_t lamp_id;
> +};
> +
> +static int handle_lamp_attributes_request_report(struct hid_device *hdev,
> +						 struct lamp_attributes_request_report_t *rep)
> +{
> +	struct driver_data_t *driver_data = hdev->driver_data;
> +
> +	if (rep->lamp_id < driver_data->lamp_count)
> +		driver_data->next_lamp_id = rep->lamp_id;
> +	else
> +		driver_data->next_lamp_id = 0;
> +
> +	return sizeof(struct lamp_attributes_request_report_t);
> +}
> +
> +
> +struct __packed lamp_attributes_response_report_t {
> +	const uint8_t report_id;
> +	uint16_t lamp_id;
> +	uint32_t position_x_in_micrometers;
> +	uint32_t position_y_in_micrometers;
> +	uint32_t position_z_in_micrometers;
> +	uint32_t update_latency_in_microseconds;
> +	uint32_t lamp_purpose;
> +	uint8_t red_level_count;
> +	uint8_t green_level_count;
> +	uint8_t blue_level_count;
> +	uint8_t intensity_level_count;
> +	uint8_t is_programmable;
> +	uint8_t input_binding;
> +};
> +
> +static int handle_lamp_attributes_response_report(struct hid_device *hdev,
> +						  struct lamp_attributes_response_report_t *rep)
> +{
> +	struct driver_data_t *driver_data = hdev->driver_data;
> +	uint16_t lamp_id = driver_data->next_lamp_id;
> +	const uint8_t *kbl_mapping;
> +	const uint32_t *kbl_mapping_pos_x, *kbl_mapping_pos_y, *kbl_mapping_pos_z;
> +
> +	rep->lamp_id = lamp_id;
> +	// Some guessed value for latency microseconds
> +	rep->update_latency_in_microseconds = 100;
> +	 // LampPurposeControl, see "26.3.1 LampPurposes Flags" of "HID Usage Tables v1.5"
> +	rep->lamp_purpose = 1;
> +	rep->red_level_count = 0xff;
> +	rep->green_level_count = 0xff;
> +	rep->blue_level_count = 0xff;
> +	rep->intensity_level_count = 0xff;
> +	rep->is_programmable = 1;
> +
> +	if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) {
> +		kbl_mapping = &sirius_16_ansii_kbl_mapping[0];
> +		kbl_mapping_pos_x = &sirius_16_ansii_kbl_mapping_pos_x[0];
> +		kbl_mapping_pos_y = &sirius_16_ansii_kbl_mapping_pos_y[0];
> +		kbl_mapping_pos_z = &sirius_16_ansii_kbl_mapping_pos_z[0];
> +	} else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) {
> +		kbl_mapping = &sirius_16_iso_kbl_mapping[0];
> +		kbl_mapping_pos_x = &sirius_16_iso_kbl_mapping_pos_x[0];
> +		kbl_mapping_pos_y = &sirius_16_iso_kbl_mapping_pos_y[0];
> +		kbl_mapping_pos_z = &sirius_16_iso_kbl_mapping_pos_z[0];
> +	} else
> +		return -EINVAL;
> +
> +	if (kbl_mapping[lamp_id] <= 0xe8)
> +		rep->input_binding = kbl_mapping[lamp_id];
> +	else
> +		// Everything bigger is reserved/undefined, see "10 Keyboard/Keypad Page (0x07)" of
> +		// "HID Usage Tables v1.5" and should return 0, see "26.8.3 Lamp Attributes" of the
> +		// same document.
> +		rep->input_binding = 0;
> +	rep->position_x_in_micrometers = kbl_mapping_pos_x[lamp_id];
> +	rep->position_y_in_micrometers = kbl_mapping_pos_y[lamp_id];
> +	rep->position_z_in_micrometers = kbl_mapping_pos_z[lamp_id];
> +
> +	driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % driver_data->lamp_count;
> +
> +	return sizeof(struct lamp_attributes_response_report_t);
> +}
> +
> +
> +#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE	BIT(0)
> +
> +struct __packed lamp_multi_update_report_t {
> +	const uint8_t report_id;
> +	uint8_t lamp_count;
> +	uint8_t lamp_update_flags;
> +	uint16_t lamp_id[8];
> +	struct {
> +		uint8_t red;
> +		uint8_t green;
> +		uint8_t blue;
> +		uint8_t intensity;
> +	} update_channels[8];
> +};
> +
> +static int handle_lamp_multi_update_report(struct hid_device *hdev,
> +					   struct lamp_multi_update_report_t *rep)
> +{
> +	int ret;
> +	struct driver_data_t *driver_data = hdev->driver_data;
> +	struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
> +	uint8_t lamp_count, key_id, key_id_j;
> +	union tuxedo_nb04_wmi_496_b_in_80_b_out_input *next =
> +		&driver_data->next_kbl_set_multiple_keys_input;
> +	union tuxedo_nb04_wmi_496_b_in_80_b_out_output output;
> +
> +	// Catching missformated lamp_multi_update_report and fail silently according to
> +	// "HID Usage Tables v1.5"
> +	for (int i = 0; i < rep->lamp_count; ++i) {
> +		if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
> +			lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
> +		else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
> +			lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
> +
> +		if (rep->lamp_id[i] > lamp_count) {
> +			pr_debug("Out of bounds lamp_id in lamp_multi_update_report. Skippng whole report!\n");
> +			return sizeof(struct lamp_multi_update_report_t);
> +		}
> +
> +		for (int j = i + 1; j < rep->lamp_count; ++j) {
> +			if (rep->lamp_id[i] == rep->lamp_id[j]) {
> +				pr_debug("Duplicate lamp_id in lamp_multi_update_report. Skippng whole report!\n");
> +				return sizeof(struct lamp_multi_update_report_t);
> +			}
> +		}
> +	}
> +
> +	for (int i = 0; i < rep->lamp_count; ++i) {
> +		if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
> +			key_id = sirius_16_ansii_kbl_mapping[rep->lamp_id[i]];
> +		else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
> +			key_id = sirius_16_iso_kbl_mapping[rep->lamp_id[i]];
> +
> +		for (int j = 0; j < WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; ++j) {
> +			key_id_j = next->kbl_set_multiple_keys_input.lighting_settings[j].key_id;
> +			if (key_id_j == 0x00 || key_id_j == key_id) {
> +				if (key_id_j == 0x00)
> +					next->kbl_set_multiple_keys_input.lighting_setting_count =
> +						j + 1;
> +				next->kbl_set_multiple_keys_input.lighting_settings[j].key_id =
> +					key_id;
> +				// While this driver respects
> +				// intensity_update_channel according to "HID
> +				// Usage Tables v1.5" also on RGB leds, the
> +				// Microsoft MacroPad reference implementation
> +				// (https://github.com/microsoft/RP2040MacropadHidSample
> +				// 1d6c3ad) does not and ignores it. If it turns
> +				// out that Windows writes intensity = 0 for RGB
> +				// leds instead of intensity = 255, this driver
> +				// should also irgnore the
> +				// intensity_update_channel.
> +				next->kbl_set_multiple_keys_input.lighting_settings[j].red =
> +					rep->update_channels[i].red
> +						* rep->update_channels[i].intensity / 0xff;
> +				next->kbl_set_multiple_keys_input.lighting_settings[j].green =
> +					rep->update_channels[i].green
> +						* rep->update_channels[i].intensity / 0xff;
> +				next->kbl_set_multiple_keys_input.lighting_settings[j].blue =
> +					rep->update_channels[i].blue
> +						* rep->update_channels[i].intensity / 0xff;
> +
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) {
> +		ret = tuxedo_nb04_wmi_496_b_in_80_b_out(wdev, WMI_AB_KBL_SET_MULTIPLE_KEYS, next,
> +							&output);
> +		memset(next, 0, sizeof(union tuxedo_nb04_wmi_496_b_in_80_b_out_input));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return sizeof(struct lamp_multi_update_report_t);
> +}
> +
> +
> +struct __packed lamp_range_update_report_t {
> +	const uint8_t report_id;
> +	uint8_t lamp_update_flags;
> +	uint16_t lamp_id_start;
> +	uint16_t lamp_id_end;
> +	uint8_t red_update_channel;
> +	uint8_t green_update_channel;
> +	uint8_t blue_update_channel;
> +	uint8_t intensity_update_channel;
> +};
> +
> +static int handle_lamp_range_update_report(struct hid_device *hdev,
> +					   struct lamp_range_update_report_t *report)
> +{
> +	int ret;
> +	struct driver_data_t *driver_data = hdev->driver_data;
> +	uint8_t lamp_count;
> +	struct lamp_multi_update_report_t lamp_multi_update_report = {
> +		.report_id = LAMP_MULTI_UPDATE_REPORT_ID
> +	};
> +
> +	// Catching missformated lamp_range_update_report and fail silently according to
> +	// "HID Usage Tables v1.5"
> +	if (report->lamp_id_start > report->lamp_id_end) {
> +		pr_debug("lamp_id_start > lamp_id_end in lamp_range_update_report. Skippng whole report!\n");
> +		return sizeof(struct lamp_range_update_report_t);
> +	}
> +
> +	if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
> +		lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
> +	else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
> +		lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
> +
> +	if (report->lamp_id_end > lamp_count - 1) {
> +		pr_debug("Out of bounds lamp_id_* in lamp_range_update_report. Skippng whole report!\n");
> +		return sizeof(struct lamp_range_update_report_t);
> +	}
> +
> +	// Break handle_lamp_range_update_report call down to multiple
> +	// handle_lamp_multi_update_report calls to easily ensure that mixing
> +	// handle_lamp_range_update_report and handle_lamp_multi_update_report
> +	// does not break things.
> +	for (int i = report->lamp_id_start; i < report->lamp_id_end + 1; i = i + 8) {
> +		lamp_multi_update_report.lamp_count = MIN(report->lamp_id_end + 1 - i, 8);
> +		if (i + lamp_multi_update_report.lamp_count == report->lamp_id_end + 1)
> +			lamp_multi_update_report.lamp_update_flags |=
> +				LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE;
> +
> +		for (int j = 0; j < lamp_multi_update_report.lamp_count; ++j) {
> +			lamp_multi_update_report.lamp_id[j] = i + j;
> +			lamp_multi_update_report.update_channels[j].red =
> +				report->red_update_channel;
> +			lamp_multi_update_report.update_channels[j].green =
> +				report->green_update_channel;
> +			lamp_multi_update_report.update_channels[j].blue =
> +				report->blue_update_channel;
> +			lamp_multi_update_report.update_channels[j].intensity =
> +				report->intensity_update_channel;
> +		}
> +
> +		ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report);
> +		if (ret < 0)
> +			return ret;
> +		else if (ret != sizeof(struct lamp_multi_update_report_t))
> +			return -EIO;
> +	}
> +
> +	return sizeof(struct lamp_range_update_report_t);
> +}
> +
> +
> +struct __packed lamp_array_control_report_t {
> +	const uint8_t report_id;
> +	uint8_t autonomous_mode;
> +};
> +
> +static int handle_lamp_array_control_report(struct hid_device __always_unused *hdev,
> +					    struct lamp_array_control_report_t __always_unused *rep)
> +{
> +	// The keyboard firmware doesn't have any built in effects or controls
> +	// so this is a NOOP.
> +	// According to the HID Documentation (HID Usage Tables v1.5) this
> +	// function is optional and can be removed from the HID Report
> +	// Descriptor, but it should first be confirmed that userspace respects
> +	// this possibility too. The Microsoft MacroPad reference implementation
> +	// (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
> +	// already deviates from the spec at another point, see
> +	// handle_lamp_*_update_report.
> +
> +	return sizeof(struct lamp_array_control_report_t);
> +}
> +
> +
> +static int ll_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len,
> +			   unsigned char rtype, int reqtype)
> +{
> +	int ret;
> +
> +	pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype,
> +		 reqtype, reportnum, len);
> +	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len);
> +
> +	ret = -EINVAL;
> +	if (rtype == HID_FEATURE_REPORT) {
> +		if (reqtype == HID_REQ_GET_REPORT) {
> +			if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID
> +			    && len == sizeof(struct lamp_array_attributes_report_t))
> +				ret = handle_lamp_array_attributes_report(
> +					hdev, (struct lamp_array_attributes_report_t *)buf);
> +			else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID
> +			    && len == sizeof(struct lamp_attributes_response_report_t))
> +				ret = handle_lamp_attributes_response_report(
> +					hdev, (struct lamp_attributes_response_report_t *)buf);
> +		} else if (reqtype == HID_REQ_SET_REPORT) {
> +			if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID
> +			    && len == sizeof(struct lamp_attributes_request_report_t))
> +				ret = handle_lamp_attributes_request_report(
> +					hdev, (struct lamp_attributes_request_report_t *)buf);
> +			else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID
> +			    && len == sizeof(struct lamp_multi_update_report_t))
> +				ret = handle_lamp_multi_update_report(
> +					hdev, (struct lamp_multi_update_report_t *)buf);
> +			else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID
> +			    && len == sizeof(struct lamp_range_update_report_t))
> +				ret = handle_lamp_range_update_report(
> +					hdev, (struct lamp_range_update_report_t *)buf);
> +			else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID
> +			    && len == sizeof(struct lamp_array_control_report_t))
> +				ret = handle_lamp_array_control_report(
> +					hdev, (struct lamp_array_control_report_t *)buf);
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct hid_ll_driver ll_driver = {
> +	.start = &ll_start,
> +	.stop = &ll_stop,
> +	.open = &ll_open,
> +	.close = &ll_close,
> +	.parse = &ll_parse,
> +	.raw_request = &ll_raw_request,
> +};
> +
> +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev, struct hid_device **hdev_out)
> +{
> +	int ret;
> +	struct hid_device *hdev;
> +
> +	pr_debug("Adding TUXEDO NB04 Virtual LampArray device.\n");
> +
> +	hdev = hid_allocate_device();
> +	if (IS_ERR(hdev))
> +		return PTR_ERR(hdev);
> +	*hdev_out = hdev;
> +
> +	strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name));
> +
> +	hdev->ll_driver = &ll_driver;
> +	hdev->bus = BUS_VIRTUAL;
> +	hdev->vendor = 0x21ba;
> +	hdev->product = 0x0400;
> +	hdev->dev.parent = &wdev->dev;
> +
> +	ret = hid_add_device(hdev);
> +	if (ret)
> +		hid_destroy_device(hdev);
> +	return ret;
> +}
> +EXPORT_SYMBOL(tuxedo_nb04_virtual_lamp_array_add_device);
> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
> new file mode 100644
> index 0000000000000..fdc2a01d95c24
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * This code gives the built in RGB lighting of the TUXEDO NB04 devices a
> + * standardised interface, namely HID LampArray.
> + *
> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
> + */
> +
> +#ifndef TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
> +#define TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
> +
> +#include <linux/wmi.h>
> +#include <linux/hid.h>
> +
> +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev,
> +					      struct hid_device **hdev_out);
> +
> +#endif
> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
> new file mode 100644
> index 0000000000000..dbabdb9dd60c7
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
> @@ -0,0 +1,85 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This code gives functions to avoid code duplication while interacting with
> + * the TUXEDO NB04 wmi interfaces.
> + *
> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include "tuxedo_nb04_wmi_ab_init.h"
> +
> +#include "tuxedo_nb04_wmi_util.h"
> +
> +static int __wmi_method_acpi_object_out(struct wmi_device *wdev, uint32_t wmi_method_id,
> +					uint8_t *in, acpi_size in_len, union acpi_object **out)

Please use size_t instead of acpi_size.

> +{
> +	struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data;

Please use dev_get_drvdata().

> +	struct acpi_buffer acpi_buffer_in = { in_len, in };
> +	struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL };
> +
> +	pr_debug("Evaluate WMI method: %u in:\n", wmi_method_id);
> +	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len);

I do not think this is useful, please remove.

> +
> +	mutex_lock(&driver_data->wmi_access_mutex);

Does the underlying ACPI method really require external locking? If not, then please remove this mutex.

> +	acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id, &acpi_buffer_in,
> +						    &acpi_buffer_out);
> +	mutex_unlock(&driver_data->wmi_access_mutex);
> +	if (ACPI_FAILURE(status)) {
> +		pr_err("Failed to evaluate WMI method.\n");
> +		return -EIO;
> +	}
> +	if (!acpi_buffer_out.pointer) {
> +		pr_err("Unexpected empty out buffer.\n");
> +		return -ENODATA;
> +	}

I believe that printing error messages should be done by the callers of this method.

> +
> +	*out = acpi_buffer_out.pointer;
> +
> +	return 0;
> +}
> +
> +static int __wmi_method_buffer_out(struct wmi_device *wdev, uint32_t wmi_method_id, uint8_t *in,
> +				   acpi_size in_len, uint8_t *out, acpi_size out_len)

Please use size_t instead of acpi_size.

> +{
> +	int ret;
> +	union acpi_object *acpi_object_out = NULL;

union acpi_object *obj;
int ret;

> +
> +	ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, in, in_len, &acpi_object_out);
> +	if (ret)
> +		return ret;
> +
> +	if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
> +		pr_err("Unexpected out buffer type. Expected: %u Got: %u\n", ACPI_TYPE_BUFFER,
> +		       acpi_object_out->type);
> +		kfree(acpi_object_out);
> +		return -EIO;
> +	}
> +	if (acpi_object_out->buffer.length != out_len) {

The Windows ACPI-WMI mappers accepts oversized buffers and ignores any additional data,
so please change this code to also accept oversized buffers.

> +		pr_err("Unexpected out buffer length.\n");
> +		kfree(acpi_object_out);
> +		return -EIO;
> +	}
> +
> +	memcpy(out, acpi_object_out->buffer.pointer, out_len);
> +	kfree(acpi_object_out);
> +
> +	return ret;
> +}
> +
> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
> +				    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method,
> +				    union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input,
> +				    union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output)
> +{
> +	return __wmi_method_buffer_out(wdev, method, input->raw, 8, output->raw, 80);
> +}
> +
> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
> +				      enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
> +				      union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
> +				      union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output)
> +{
> +	return __wmi_method_buffer_out(wdev, method, input->raw, 496, output->raw, 80);
> +}

Those two functions seem useless to me, please use wmi_method_buffer_out() directly by passing
a pointer to the underlying struct as data and the output of sizeof() as length.

> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
> new file mode 100644
> index 0000000000000..2765cbe9fcfef
> --- /dev/null
> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
> @@ -0,0 +1,112 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * This code gives functions to avoid code duplication while interacting with
> + * the TUXEDO NB04 wmi interfaces.
> + *
> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
> + */
> +
> +#ifndef TUXEDO_NB04_WMI_UTIL_H
> +#define TUXEDO_NB04_WMI_UTIL_H
> +
> +#include <linux/wmi.h>
> +
> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD	1
> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD	2
> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES	3
> +
> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_NONE		0
> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY	1
> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE	2
> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY	3
> +
> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII	0
> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO	1
> +
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_RED		1
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_GREEN		2
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_YELLOW	3
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_BLUE		4
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_PURPLE	5
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_INDIGO	6
> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_WHITE		7
> +
> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD	BIT(0)
> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS	BIT(1)
> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_KBL		BIT(2)
> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS	BIT(3)
> +
> +
> +union tuxedo_nb04_wmi_8_b_in_80_b_out_input {
> +	uint8_t raw[8];
> +	struct __packed {
> +		uint8_t device_type;
> +		uint8_t reserved_0[7];
> +	} get_device_status_input;
> +};
> +
> +union tuxedo_nb04_wmi_8_b_in_80_b_out_output {
> +	uint8_t raw[80];
> +	struct __packed {
> +		uint16_t return_status;
> +		uint8_t device_enabled;
> +		uint8_t kbl_type;
> +		uint8_t kbl_side_bar_supported;
> +		uint8_t keyboard_physical_layout;
> +		uint8_t app_pages;
> +		uint8_t per_key_kbl_default_color;
> +		uint8_t four_zone_kbl_default_color_1;
> +		uint8_t four_zone_kbl_default_color_2;
> +		uint8_t four_zone_kbl_default_color_3;
> +		uint8_t four_zone_kbl_default_color_4;
> +		uint8_t light_bar_kbl_default_color;
> +		uint8_t reserved_0[1];
> +		uint16_t dedicated_gpu_id;
> +		uint8_t reserved_1[64];
> +	} get_device_status_output;
> +};
> +
> +enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods {
> +	WMI_AB_GET_DEVICE_STATUS	= 2,
> +};
> +
> +
> +#define WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX	120
> +
> +union tuxedo_nb04_wmi_496_b_in_80_b_out_input {
> +	uint8_t raw[496];
> +	struct __packed {
> +		uint8_t reserved_0[15];
> +		uint8_t lighting_setting_count;
> +		struct {
> +			uint8_t key_id;
> +			uint8_t red;
> +			uint8_t green;
> +			uint8_t blue;
> +		} lighting_settings[WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX];
> +	}  kbl_set_multiple_keys_input;
> +};
> +
> +union tuxedo_nb04_wmi_496_b_in_80_b_out_output {
> +	uint8_t raw[80];
> +	struct __packed {
> +		uint8_t return_value;
> +		uint8_t reserved_0[79];
> +	} kbl_set_multiple_keys_output;
> +};
> +
> +enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods {
> +	WMI_AB_KBL_SET_MULTIPLE_KEYS	= 6,
> +};
> +
> +
> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
> +				    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method,
> +				    union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input,
> +				    union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output);
> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
> +				      enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
> +				      union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
> +				      union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output);
> +
> +#endif
Werner Sembach Sept. 27, 2024, 6:59 a.m. UTC | #2
Hi,

Am 26.09.24 um 20:39 schrieb Armin Wolf:
> Am 26.09.24 um 19:44 schrieb Werner Sembach:
>
>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
>> controllable RGB keyboard backlight. The firmware API for it is implemented
>> via WMI.
>>
>> To make the backlight userspace configurable this driver emulates a
>> LampArray HID device and translates the input from hidraw to the
>> corresponding WMI calls. This is a new approach as the leds subsystem lacks
>> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
>> needs to be established.
>>
>> Co-developed-by: Christoffer Sandberg <cs@tuxedo.de>
>> Signed-off-by: Christoffer Sandberg <cs@tuxedo.de>
>> Signed-off-by: Werner Sembach <wse@tuxedocomputers.com>
>> Link: 
>> https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
>> ---
>>   MAINTAINERS                                   |   6 +
>>   drivers/platform/x86/Kconfig                  |   2 +
>>   drivers/platform/x86/Makefile                 |   3 +
>>   drivers/platform/x86/tuxedo/Kbuild            |   9 +
>>   drivers/platform/x86/tuxedo/Kconfig           |  14 +
>>   .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.c      |  86 ++
>>   .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.h      |  20 +
>>   .../tuxedo_nb04_wmi_ab_virtual_lamp_array.c   | 741 ++++++++++++++++++
>>   .../tuxedo_nb04_wmi_ab_virtual_lamp_array.h   |  18 +
>>   .../x86/tuxedo/tuxedo_nb04_wmi_util.c         |  85 ++
>>   .../x86/tuxedo/tuxedo_nb04_wmi_util.h         | 112 +++
>>   11 files changed, 1096 insertions(+)
>>   create mode 100644 drivers/platform/x86/tuxedo/Kbuild
>>   create mode 100644 drivers/platform/x86/tuxedo/Kconfig
>>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>>   create mode 100644 
>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>>   create mode 100644 
>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index cc40a9d9b8cd1..3385ad51af194 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -23358,6 +23358,12 @@ T:    git 
>> git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat
>>   F:    tools/power/x86/turbostat/
>>   F:    tools/testing/selftests/turbostat/
>>
>> +TUXEDO DRIVERS
>> +M:    Werner Sembach <wse@tuxedocomputers.com>
>> +L:    platform-driver-x86@vger.kernel.org
>> +S:    Supported
>> +F:    drivers/platform/x86/tuxedo/
>> +
>>   TW5864 VIDEO4LINUX DRIVER
>>   M:    Bluecherry Maintainers <maintainers@bluecherrydvr.com>
>>   M:    Andrey Utkin <andrey.utkin@corp.bluecherry.net>
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> index ddfccc226751f..c7cffb222adac 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -1196,3 +1196,5 @@ config P2SB
>>         The main purpose of this library is to unhide P2SB device in case
>>         firmware kept it hidden on some platforms in order to access devices
>>         behind it.
>> +
>> +source "drivers/platform/x86/tuxedo/Kconfig"
>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>> index e1b1429470674..1562dcd7ad9a5 100644
>> --- a/drivers/platform/x86/Makefile
>> +++ b/drivers/platform/x86/Makefile
>> @@ -153,3 +153,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS)        += 
>> winmate-fm07-keys.o
>>
>>   # SEL
>>   obj-$(CONFIG_SEL3350_PLATFORM)        += sel3350-platform.o
>> +
>> +# TUXEDO
>> +obj-y                    += tuxedo/
>> diff --git a/drivers/platform/x86/tuxedo/Kbuild 
>> b/drivers/platform/x86/tuxedo/Kbuild
>> new file mode 100644
>> index 0000000000000..5a3506ab98131
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/Kbuild
>> @@ -0,0 +1,9 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +#
>> +# TUXEDO X86 Platform Specific Drivers
>> +#
>> +
>> +tuxedo_nb04_wmi_ab-y            := tuxedo_nb04_wmi_ab_init.o
>> +tuxedo_nb04_wmi_ab-y            += tuxedo_nb04_wmi_util.o
>> +tuxedo_nb04_wmi_ab-y            += tuxedo_nb04_wmi_ab_virtual_lamp_array.o
>> +obj-$(CONFIG_TUXEDO_NB04_WMI_AB)    += tuxedo_nb04_wmi_ab.o
>> diff --git a/drivers/platform/x86/tuxedo/Kconfig 
>> b/drivers/platform/x86/tuxedo/Kconfig
>> new file mode 100644
>> index 0000000000000..b1f7c6ceeaae4
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/Kconfig
>> @@ -0,0 +1,14 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +#
>> +# TUXEDO X86 Platform Specific Drivers
>> +#
>> +
>> +menuconfig TUXEDO_NB04_WMI_AB
>> +    tristate "TUXEDO NB04 WMI AB Platform Driver"
>> +    default m
>> +    help
>> +      This driver implements the WMI AB device found on TUXEDO Notebooks
>> +      with board vendor NB04. For the time being only the keyboard backlight
>> +      control is implemented.
>> +
>> +      When compiled as a module it will be called tuxedo_nb04_wmi_ab.
>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c 
>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>> new file mode 100644
>> index 0000000000000..6e4446b0e3dd8
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>> @@ -0,0 +1,86 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * This driver implements the WMI AB device found on TUXEDO Notebooks with 
>> board
>> + * vendor NB04.
>> + *
>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>> + */
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> +
>> +#include <linux/module.h>
>> +#include <linux/wmi.h>
>> +#include <linux/dmi.h>
>> +
>> +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
>> +
>> +#include "tuxedo_nb04_wmi_ab_init.h"
>> +
>> +// We don't know if the WMI API is stable and how unique the GUID is for 
>> this ODM. To be on the safe
>> +// side we therefore only run this driver on tested devices defined by this 
>> list.
>> +static const struct dmi_system_id tested_devices_dmi_table[] = {
>> +    {
>> +        // TUXEDO Sirius 16 Gen1
>> +        .matches = {
>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
>> +        },
>> +    },
>> +    {
>> +        // TUXEDO Sirius 16 Gen2
>> +        .matches = {
>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
>> +        },
>> +    },
>> +    { }
>> +};
>> +
>> +static int probe(struct wmi_device *wdev, const void __always_unused *context)
>> +{
>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data;
>> +
>> +    if (dmi_check_system(tested_devices_dmi_table))
>> +        return -ENODEV;
>
> Hi,
>
> please do this DMI check during module initialization. This avoids having an 
> useless WMI driver
> on unsupported machines and allows for marking tested_devices_dmi_table as 
> __initconst.
>
> Besides that, maybe a "force" module parameter for overriding the DMI checking 
> could be
> useful?
>
>> +
>> +    driver_data = devm_kzalloc(&wdev->dev, sizeof(struct 
>> tuxedo_nb04_wmi_driver_data_t),
>> +                   GFP_KERNEL);
>
> Please use sizeof(*driver_data).
>
>> +    if (!driver_data)
>> +        return -ENOMEM;
>> +
>> +    mutex_init(&driver_data->wmi_access_mutex);
>
> Please use devm_mutex_init(), so the mutex is properly destroyed when unbinding.
>
>> +
>> +    dev_set_drvdata(&wdev->dev, driver_data);
>> +
>> +    tuxedo_nb04_virtual_lamp_array_add_device(wdev, 
>> &driver_data->virtual_lamp_array_hdev);
>
> Error handling missing.
>
>> +
>> +    return 0;
>> +}
>> +
>> +static void remove(struct wmi_device *wdev)
>> +{
>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data;
>> +
>> + hid_destroy_device(driver_data->virtual_lamp_array_hdev);
>> +}
>> +
>> +static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = {
>> +    { .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" },
>> +    { }
>> +};
>> +MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids);
>> +
>> +static struct wmi_driver tuxedo_nb04_wmi_ab_driver = {
>> +    .driver = {
>> +        .name = "tuxedo_nb04_wmi_ab",
>> +        .owner = THIS_MODULE
>> +    },
>> +    .id_table = tuxedo_nb04_wmi_ab_device_ids,
>> +    .probe = probe,
>> +    .remove = remove
>
> I recommend setting probe_type = PROBE_PREFER_ASYNCHRONOUS, see 
> Documentation/wmi/driver-development-guide.rst.
> Also please set no_singleton = true.
>
>> +};
>> +module_wmi_driver(tuxedo_nb04_wmi_ab_driver);
>> +
>> +MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices");
>> +MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h 
>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>> new file mode 100644
>> index 0000000000000..aebfd465c9b61
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>> @@ -0,0 +1,20 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * This driver implements the WMI AB device found on TUXEDO Notebooks with 
>> board
>> + * vendor NB04.
>> + *
>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>> + */
>> +
>> +#ifndef TUXEDO_NB04_WMI_AB_INIT_H
>> +#define TUXEDO_NB04_WMI_AB_INIT_H
>> +
>> +#include <linux/mutex.h>
>> +#include <linux/hid.h>
>> +
>> +struct tuxedo_nb04_wmi_driver_data_t {
>> +    struct mutex wmi_access_mutex;
>> +    struct hid_device *virtual_lamp_array_hdev;
>> +};
>> +
>> +#endif
>> diff --git 
>> a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c 
>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>> new file mode 100644
>> index 0000000000000..04af19aa6ad5f
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>> @@ -0,0 +1,741 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * This code gives the built in RGB lighting of the TUXEDO NB04 devices a
>> + * standardised interface, namely HID LampArray.
>> + *
>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>> + */
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> +
>> +#include "tuxedo_nb04_wmi_util.h"
>> +
>> +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
>> +
>> +#define dev_to_wdev(__dev)    container_of(__dev, struct wmi_device, dev)
>
> Please use to_wmi_device() instead.
>
>> +
>> +enum report_ids {
>> +    LAMP_ARRAY_ATTRIBUTES_REPORT_ID        = 0x01,
>> +    LAMP_ATTRIBUTES_REQUEST_REPORT_ID    = 0x02,
>> +    LAMP_ATTRIBUTES_RESPONSE_REPORT_ID    = 0x03,
>> +    LAMP_MULTI_UPDATE_REPORT_ID        = 0x04,
>> +    LAMP_RANGE_UPDATE_REPORT_ID        = 0x05,
>> +    LAMP_ARRAY_CONTROL_REPORT_ID        = 0x06,
>> +};
>> +
>> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
>> +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>> +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>> +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>> +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>> +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>> +    0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
>> +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>> +    0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
>> +    0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
>> +    0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
>> +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>> +    0x4f,                                 0x62, 0x63, 0x58
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
>> +     25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 
>> 175300,
>> +    192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 
>> 327900, 344600,
>> +     24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 
>> 190500,
>> +    209000, 227500, 246000, 269500,                   294500, 311200, 
>> 327900, 344600,
>> +     31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 
>> 199500,
>> +    218000, 236500, 255000, 273500,                   294500, 311200, 327900,
>> +     33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 
>> 205000,
>> +    223500, 242000, 267500,                           294500, 311200, 
>> 327900, 344600,
>> +     37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 
>> 214000,
>> +    232500, 251500, 273500,                           294500, 311200, 327900,
>> +     28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 
>> 273500,
>> +    292000,                                           311200, 327900, 344600
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
>> +     53000,  53000,  53000,  53000,  53000,  53000,  53000, 53000,  53000,  
>> 53000,
>> +     53000,  53000,  53000,  53000,  53000,  53000,    53000, 53000,  
>> 53000,  53000,
>> +     67500,  67500,  67500,  67500,  67500,  67500,  67500, 67500,  67500,  
>> 67500,
>> +     67500,  67500,  67500,  67500,                    67500, 67500,  
>> 67500,  67500,
>> +     85500,  85500,  85500,  85500,  85500,  85500,  85500, 85500,  85500,  
>> 85500,
>> +     85500,  85500,  85500,  85500,                    85500, 85500,  85500,
>> +    103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 
>> 103500,
>> +    103500, 103500, 103500,                           103500, 103500, 
>> 103500,  94500,
>> +    121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 
>> 121500,
>> +    121500, 121500, 129000,                           121500, 121500, 121500,
>> +    139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 
>> 147000,
>> +    147000,                                           139500, 139500, 130500
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
>> +      5000,   5000,   5000,   5000,   5000,   5000,   5000, 5000,   5000,   
>> 5000,
>> +      5000,   5000,   5000,   5000,   5000,   5000,     5000, 5000,   
>> 5000,   5000,
>> +      5250,   5250,   5250,   5250,   5250,   5250,   5250, 5250,   5250,   
>> 5250,
>> +      5250,   5250,   5250,   5250,                     5250, 5250,   
>> 5250,   5250,
>> +      5500,   5500,   5500,   5500,   5500,   5500,   5500, 5500,   5500,   
>> 5500,
>> +      5500,   5500,   5500,   5500,                     5500, 5500,   5500,
>> +      5750,   5750,   5750,   5750,   5750,   5750,   5750, 5750,   5750,   
>> 5750,
>> +      5750,   5750,   5750,                             5750, 5750,   
>> 5750,   5625,
>> +      6000,   6000,   6000,   6000,   6000,   6000,   6000, 6000,   6000,   
>> 6000,
>> +      6000,   6000,   6125,                             6000, 6000,   6000,
>> +      6250,   6250,   6250,   6250,   6250,   6250,   6250, 6250,   6375,   
>> 6375,
>> +      6375,                                             6250, 6250,   6125
>> +};
>> +
>> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
>> +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>> +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>> +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>> +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>> +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>> +    0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
>> +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>> +    0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
>> +    0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
>> +    0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
>> +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>> +    0x4f,                                 0x62, 0x63, 0x58
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
>> +     25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 
>> 175300,
>> +    192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 
>> 327900, 344600,
>> +     24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 
>> 190500,
>> +    209000, 227500, 246000, 269500,                   294500, 311200, 
>> 327900, 344600,
>> +     31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 
>> 199500,
>> +    218000, 234500, 251000,                           294500, 311200, 327900,
>> +     33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 
>> 205000,
>> +    223500, 240000, 256500, 271500,                   294500, 311200, 
>> 327900, 344600,
>> +     28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 
>> 195500,
>> +    214000, 232500, 251500, 273500,                   294500, 311200, 327900,
>> +     28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 
>> 273500,
>> +    292000,                                           311200, 327900, 344600
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
>> +     53000,  53000,  53000,  53000,  53000,  53000,  53000, 53000,  53000,  
>> 53000,
>> +     53000,  53000,  53000,  53000,  53000,  53000,    53000, 53000,  
>> 53000,  53000,
>> +     67500,  67500,  67500,  67500,  67500,  67500,  67500, 67500,  67500,  
>> 67500,
>> +     67500,  67500,  67500,  67500,                    67500, 67500,  
>> 67500,  67500,
>> +     85500,  85500,  85500,  85500,  85500,  85500,  85500, 85500,  85500,  
>> 85500,
>> +     85500,  85500,  85500,                            85500, 85500,  85500,
>> +    103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 
>> 103500,
>> +    103500, 103500, 103500,  94500,                   103500, 103500, 
>> 103500,  94500,
>> +    121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 
>> 121500,
>> +    121500, 121500, 121500, 129000,                   121500, 121500, 121500,
>> +    139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 
>> 147000,
>> +    147000,                                           139500, 139500, 130500
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
>> +      5000,   5000,   5000,   5000,   5000,   5000,   5000, 5000,   5000,   
>> 5000,
>> +      5000,   5000,   5000,   5000, 5000, 5000,         5000, 5000,   
>> 5000,   5000,
>> +      5250,   5250,   5250,   5250,   5250,   5250,   5250, 5250,   5250,   
>> 5250,
>> +      5250,   5250,   5250,   5250,                     5250, 5250,   
>> 5250,   5250,
>> +      5500,   5500,   5500,   5500,   5500,   5500,   5500, 5500,   5500,   
>> 5500,
>> +      5500,   5500,   5500,                             5500, 5500,   5500,
>> +      5750,   5750,   5750,   5750,   5750,   5750,   5750, 5750,   5750,   
>> 5750,
>> +      5750,   5750,   5750,   5750,                     5750, 5750,   
>> 5750,   5625,
>> +      6000,   6000,   6000,   6000,   6000,   6000,   6000, 6000,   6000,   
>> 6000,
>> +      6000,   6000,   6000,   6125,                     6000, 6000,   6000,
>> +      6250,   6250,   6250,   6250,   6250,   6250,   6250, 6250,   6375,   
>> 6375,
>> +      6375,                                             6250, 6250,   6125
>> +};
>> +
>> +struct driver_data_t {
>> +    uint8_t keyboard_type;
>> +    uint8_t lamp_count;
>> +    uint8_t next_lamp_id;
>> +    union tuxedo_nb04_wmi_496_b_in_80_b_out_input 
>> next_kbl_set_multiple_keys_input;
>> +};
>> +
>> +
>> +static int ll_start(struct hid_device *hdev)
>> +{
>> +    int ret;
>> +    struct driver_data_t *driver_data;
>> +    struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
>> +    union tuxedo_nb04_wmi_8_b_in_80_b_out_input input;
>> +    union tuxedo_nb04_wmi_8_b_in_80_b_out_output output;
>> +
>> +    driver_data = devm_kzalloc(&hdev->dev, sizeof(struct driver_data_t), 
>> GFP_KERNEL);
>> +    if (!driver_data)
>> +        return -ENOMEM;
>
> Please use sizeof(*driver_data).
All of the above: Ack, will be in v2.
>
>> +
>> +    input.get_device_status_input.device_type = 
>> WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD;
>> +    ret = tuxedo_nb04_wmi_8_b_in_80_b_out(wdev, WMI_AB_GET_DEVICE_STATUS, 
>> &input, &output);
>> +    if (ret)
>> +        return ret;
>> +
>> +    driver_data->keyboard_type = 
>> output.get_device_status_output.keyboard_physical_layout;
>> +    driver_data->lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>> +    driver_data->next_lamp_id = 0;
>> +
>> +    hdev->driver_data = driver_data;
>> +
>> +    return ret;
>> +}
>> +
>> +
>> +static void ll_stop(struct hid_device __always_unused *hdev)
>> +{
>> +}
>> +
>> +
>> +static int ll_open(struct hid_device __always_unused *hdev)
>> +{
>> +    return 0;
>> +}
>> +
>> +
>> +static void ll_close(struct hid_device __always_unused *hdev)
>> +{
>> +}
>
> I have no experience with the HID subsystem, but this looks suspicious.

stop() is to cleanup stuff from start(), but the only thing i alloc in start() 
is with devm_kzalloc, so it gets cleaned up automatically.

open and close is called whenever the hidraw file descriptor gets opened/closed 
in userspace, nothing to do here for this driver.

leaving these functions pointer as NULL in the ll_driver struct crashes the 
kernel, so i placed noop functions there. Don't know if there is a more elegant way.

>
>> +
>> +
>> +static uint8_t report_descriptor[327] = {
>> +    0x05, 0x59,            // Usage Page (Lighting and Illumination)
>> +    0x09, 0x01,            // Usage (Lamp Array)
>> +    0xa1, 0x01,            // Collection (Application)
>> +    0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
>> +    0x09, 0x02,            //  Usage (Lamp Array Attributes Report)
>> +    0xa1, 0x02,            //  Collection (Logical)
>> +    0x09, 0x03,            //   Usage (Lamp Count)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>> +    0x75, 0x10,            //   Report Size (16)
>> +    0x95, 0x01,            //   Report Count (1)
>> +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
>> +    0x09, 0x04,            //   Usage (Bounding Box Width In Micrometers)
>> +    0x09, 0x05,            //   Usage (Bounding Box Height In Micrometers)
>> +    0x09, 0x06,            //   Usage (Bounding Box Depth In Micrometers)
>> +    0x09, 0x07,            //   Usage (Lamp Array Kind)
>> +    0x09, 0x08,            //   Usage (Min Update Interval In Microseconds)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
>> +    0x75, 0x20,            //   Report Size (32)
>> +    0x95, 0x05,            //   Report Count (5)
>> +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
>> +    0xc0,                //  End Collection
>> +    0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
>> +    0x09, 0x20,            //  Usage (Lamp Attributes Request Report)
>> +    0xa1, 0x02,            //  Collection (Logical)
>> +    0x09, 0x21,            //   Usage (Lamp Id)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>> +    0x75, 0x10,            //   Report Size (16)
>> +    0x95, 0x01,            //   Report Count (1)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0xc0,                //  End Collection
>> +    0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
>> +    0x09, 0x22,            //  Usage (Lamp Attributes Response Report)
>> +    0xa1, 0x02,            //  Collection (Logical)
>> +    0x09, 0x21,            //   Usage (Lamp Id)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>> +    0x75, 0x10,            //   Report Size (16)
>> +    0x95, 0x01,            //   Report Count (1)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0x09, 0x23,            //   Usage (Position X In Micrometers)
>> +    0x09, 0x24,            //   Usage (Position Y In Micrometers)
>> +    0x09, 0x25,            //   Usage (Position Z In Micrometers)
>> +    0x09, 0x27,            //   Usage (Update Latency In Microseconds)
>> +    0x09, 0x26,            //   Usage (Lamp Purposes)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
>> +    0x75, 0x20,            //   Report Size (32)
>> +    0x95, 0x05,            //   Report Count (5)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0x09, 0x28,            //   Usage (Red Level Count)
>> +    0x09, 0x29,            //   Usage (Green Level Count)
>> +    0x09, 0x2a,            //   Usage (Blue Level Count)
>> +    0x09, 0x2b,            //   Usage (Intensity Level Count)
>> +    0x09, 0x2c,            //   Usage (Is Programmable)
>> +    0x09, 0x2d,            //   Usage (Input Binding)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>> +    0x75, 0x08,            //   Report Size (8)
>> +    0x95, 0x06,            //   Report Count (6)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0xc0,                //  End Collection
>> +    0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
>> +    0x09, 0x50,            //  Usage (Lamp Multi Update Report)
>> +    0xa1, 0x02,            //  Collection (Logical)
>> +    0x09, 0x03,            //   Usage (Lamp Count)
>> +    0x09, 0x55,            //   Usage (Lamp Update Flags)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x25, 0x08,            //   Logical Maximum (8)
>> +    0x75, 0x08,            //   Report Size (8)
>> +    0x95, 0x02,            //   Report Count (2)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0x09, 0x21,            //   Usage (Lamp Id)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>> +    0x75, 0x10,            //   Report Size (16)
>> +    0x95, 0x08,            //   Report Count (8)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>> +    0x75, 0x08,            //   Report Size (8)
>> +    0x95, 0x20,            //   Report Count (32)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0xc0,                //  End Collection
>> +    0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
>> +    0x09, 0x60,            //  Usage (Lamp Range Update Report)
>> +    0xa1, 0x02,            //  Collection (Logical)
>> +    0x09, 0x55,            //   Usage (Lamp Update Flags)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x25, 0x08,            //   Logical Maximum (8)
>> +    0x75, 0x08,            //   Report Size (8)
>> +    0x95, 0x01,            //   Report Count (1)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0x09, 0x61,            //   Usage (Lamp Id Start)
>> +    0x09, 0x62,            //   Usage (Lamp Id End)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>> +    0x75, 0x10,            //   Report Size (16)
>> +    0x95, 0x02,            //   Report Count (2)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>> +    0x75, 0x08,            //   Report Size (8)
>> +    0x95, 0x04,            //   Report Count (4)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0xc0,                //  End Collection
>> +    0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
>> +    0x09, 0x70,            //  Usage (Lamp Array Control Report)
>> +    0xa1, 0x02,            //  Collection (Logical)
>> +    0x09, 0x71,            //   Usage (Autonomous Mode)
>> +    0x15, 0x00,            //   Logical Minimum (0)
>> +    0x25, 0x01,            //   Logical Maximum (1)
>> +    0x75, 0x08,            //   Report Size (8)
>> +    0x95, 0x01,            //   Report Count (1)
>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>> +    0xc0,                //  End Collection
>> +    0xc0                // End Collection
>> +};
>> +
>> +static int ll_parse(struct hid_device *hdev)
>> +{
>> +    return hid_parse_report(hdev, report_descriptor, 
>> sizeof(report_descriptor));
>> +}
>> +
>> +
>> +struct __packed lamp_array_attributes_report_t {
>> +    const uint8_t report_id;
>> +    uint16_t lamp_count;
>> +    uint32_t bounding_box_width_in_micrometers;
>> +    uint32_t bounding_box_height_in_micrometers;
>> +    uint32_t bounding_box_depth_in_micrometers;
>> +    uint32_t lamp_array_kind;
>> +    uint32_t min_update_interval_in_microseconds;
>> +};
>> +
>> +static int handle_lamp_array_attributes_report(struct hid_device *hdev,
>> +                           struct lamp_array_attributes_report_t *rep)
>> +{
>> +    struct driver_data_t *driver_data = hdev->driver_data;
>> +
>> +    rep->lamp_count = driver_data->lamp_count;
>> +    rep->bounding_box_width_in_micrometers = 368000;
>> +    rep->bounding_box_height_in_micrometers = 266000;
>> +    rep->bounding_box_depth_in_micrometers = 30000;
>> +    // LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of "HID 
>> Usage Tables v1.5"
>> +    rep->lamp_array_kind = 1;
>> +    // Some guessed value for interval microseconds
>> +    rep->min_update_interval_in_microseconds = 500;
>> +
>> +    return sizeof(struct lamp_array_attributes_report_t);
>> +}
>> +
>> +
>> +struct __packed lamp_attributes_request_report_t {
>> +    const uint8_t report_id;
>> +    uint16_t lamp_id;
>> +};
>> +
>> +static int handle_lamp_attributes_request_report(struct hid_device *hdev,
>> +                         struct lamp_attributes_request_report_t *rep)
>> +{
>> +    struct driver_data_t *driver_data = hdev->driver_data;
>> +
>> +    if (rep->lamp_id < driver_data->lamp_count)
>> +        driver_data->next_lamp_id = rep->lamp_id;
>> +    else
>> +        driver_data->next_lamp_id = 0;
>> +
>> +    return sizeof(struct lamp_attributes_request_report_t);
>> +}
>> +
>> +
>> +struct __packed lamp_attributes_response_report_t {
>> +    const uint8_t report_id;
>> +    uint16_t lamp_id;
>> +    uint32_t position_x_in_micrometers;
>> +    uint32_t position_y_in_micrometers;
>> +    uint32_t position_z_in_micrometers;
>> +    uint32_t update_latency_in_microseconds;
>> +    uint32_t lamp_purpose;
>> +    uint8_t red_level_count;
>> +    uint8_t green_level_count;
>> +    uint8_t blue_level_count;
>> +    uint8_t intensity_level_count;
>> +    uint8_t is_programmable;
>> +    uint8_t input_binding;
>> +};
>> +
>> +static int handle_lamp_attributes_response_report(struct hid_device *hdev,
>> +                          struct lamp_attributes_response_report_t *rep)
>> +{
>> +    struct driver_data_t *driver_data = hdev->driver_data;
>> +    uint16_t lamp_id = driver_data->next_lamp_id;
>> +    const uint8_t *kbl_mapping;
>> +    const uint32_t *kbl_mapping_pos_x, *kbl_mapping_pos_y, *kbl_mapping_pos_z;
>> +
>> +    rep->lamp_id = lamp_id;
>> +    // Some guessed value for latency microseconds
>> +    rep->update_latency_in_microseconds = 100;
>> +     // LampPurposeControl, see "26.3.1 LampPurposes Flags" of "HID Usage 
>> Tables v1.5"
>> +    rep->lamp_purpose = 1;
>> +    rep->red_level_count = 0xff;
>> +    rep->green_level_count = 0xff;
>> +    rep->blue_level_count = 0xff;
>> +    rep->intensity_level_count = 0xff;
>> +    rep->is_programmable = 1;
>> +
>> +    if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) {
>> +        kbl_mapping = &sirius_16_ansii_kbl_mapping[0];
>> +        kbl_mapping_pos_x = &sirius_16_ansii_kbl_mapping_pos_x[0];
>> +        kbl_mapping_pos_y = &sirius_16_ansii_kbl_mapping_pos_y[0];
>> +        kbl_mapping_pos_z = &sirius_16_ansii_kbl_mapping_pos_z[0];
>> +    } else if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) {
>> +        kbl_mapping = &sirius_16_iso_kbl_mapping[0];
>> +        kbl_mapping_pos_x = &sirius_16_iso_kbl_mapping_pos_x[0];
>> +        kbl_mapping_pos_y = &sirius_16_iso_kbl_mapping_pos_y[0];
>> +        kbl_mapping_pos_z = &sirius_16_iso_kbl_mapping_pos_z[0];
>> +    } else
>> +        return -EINVAL;
>> +
>> +    if (kbl_mapping[lamp_id] <= 0xe8)
>> +        rep->input_binding = kbl_mapping[lamp_id];
>> +    else
>> +        // Everything bigger is reserved/undefined, see "10 Keyboard/Keypad 
>> Page (0x07)" of
>> +        // "HID Usage Tables v1.5" and should return 0, see "26.8.3 Lamp 
>> Attributes" of the
>> +        // same document.
>> +        rep->input_binding = 0;
>> +    rep->position_x_in_micrometers = kbl_mapping_pos_x[lamp_id];
>> +    rep->position_y_in_micrometers = kbl_mapping_pos_y[lamp_id];
>> +    rep->position_z_in_micrometers = kbl_mapping_pos_z[lamp_id];
>> +
>> +    driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % 
>> driver_data->lamp_count;
>> +
>> +    return sizeof(struct lamp_attributes_response_report_t);
>> +}
>> +
>> +
>> +#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE    BIT(0)
>> +
>> +struct __packed lamp_multi_update_report_t {
>> +    const uint8_t report_id;
>> +    uint8_t lamp_count;
>> +    uint8_t lamp_update_flags;
>> +    uint16_t lamp_id[8];
>> +    struct {
>> +        uint8_t red;
>> +        uint8_t green;
>> +        uint8_t blue;
>> +        uint8_t intensity;
>> +    } update_channels[8];
>> +};
>> +
>> +static int handle_lamp_multi_update_report(struct hid_device *hdev,
>> +                       struct lamp_multi_update_report_t *rep)
>> +{
>> +    int ret;
>> +    struct driver_data_t *driver_data = hdev->driver_data;
>> +    struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
>> +    uint8_t lamp_count, key_id, key_id_j;
>> +    union tuxedo_nb04_wmi_496_b_in_80_b_out_input *next =
>> +        &driver_data->next_kbl_set_multiple_keys_input;
>> +    union tuxedo_nb04_wmi_496_b_in_80_b_out_output output;
>> +
>> +    // Catching missformated lamp_multi_update_report and fail silently 
>> according to
>> +    // "HID Usage Tables v1.5"
>> +    for (int i = 0; i < rep->lamp_count; ++i) {
>> +        if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
>> +            lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>> +        else if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
>> +            lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>> +
>> +        if (rep->lamp_id[i] > lamp_count) {
>> +            pr_debug("Out of bounds lamp_id in lamp_multi_update_report. 
>> Skippng whole report!\n");
>> +            return sizeof(struct lamp_multi_update_report_t);
>> +        }
>> +
>> +        for (int j = i + 1; j < rep->lamp_count; ++j) {
>> +            if (rep->lamp_id[i] == rep->lamp_id[j]) {
>> +                pr_debug("Duplicate lamp_id in lamp_multi_update_report. 
>> Skippng whole report!\n");
>> +                return sizeof(struct lamp_multi_update_report_t);
>> +            }
>> +        }
>> +    }
>> +
>> +    for (int i = 0; i < rep->lamp_count; ++i) {
>> +        if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
>> +            key_id = sirius_16_ansii_kbl_mapping[rep->lamp_id[i]];
>> +        else if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
>> +            key_id = sirius_16_iso_kbl_mapping[rep->lamp_id[i]];
>> +
>> +        for (int j = 0; j < 
>> WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; ++j) {
>> +            key_id_j = 
>> next->kbl_set_multiple_keys_input.lighting_settings[j].key_id;
>> +            if (key_id_j == 0x00 || key_id_j == key_id) {
>> +                if (key_id_j == 0x00)
>> + next->kbl_set_multiple_keys_input.lighting_setting_count =
>> +                        j + 1;
>> + next->kbl_set_multiple_keys_input.lighting_settings[j].key_id =
>> +                    key_id;
>> +                // While this driver respects
>> +                // intensity_update_channel according to "HID
>> +                // Usage Tables v1.5" also on RGB leds, the
>> +                // Microsoft MacroPad reference implementation
>> +                // (https://github.com/microsoft/RP2040MacropadHidSample
>> +                // 1d6c3ad) does not and ignores it. If it turns
>> +                // out that Windows writes intensity = 0 for RGB
>> +                // leds instead of intensity = 255, this driver
>> +                // should also irgnore the
>> +                // intensity_update_channel.
>> + next->kbl_set_multiple_keys_input.lighting_settings[j].red =
>> +                    rep->update_channels[i].red
>> +                        * rep->update_channels[i].intensity / 0xff;
>> + next->kbl_set_multiple_keys_input.lighting_settings[j].green =
>> +                    rep->update_channels[i].green
>> +                        * rep->update_channels[i].intensity / 0xff;
>> + next->kbl_set_multiple_keys_input.lighting_settings[j].blue =
>> +                    rep->update_channels[i].blue
>> +                        * rep->update_channels[i].intensity / 0xff;
>> +
>> +                break;
>> +            }
>> +        }
>> +    }
>> +
>> +    if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) {
>> +        ret = tuxedo_nb04_wmi_496_b_in_80_b_out(wdev, 
>> WMI_AB_KBL_SET_MULTIPLE_KEYS, next,
>> +                            &output);
>> +        memset(next, 0, sizeof(union tuxedo_nb04_wmi_496_b_in_80_b_out_input));
>> +        if (ret)
>> +            return ret;
>> +    }
>> +
>> +    return sizeof(struct lamp_multi_update_report_t);
>> +}
>> +
>> +
>> +struct __packed lamp_range_update_report_t {
>> +    const uint8_t report_id;
>> +    uint8_t lamp_update_flags;
>> +    uint16_t lamp_id_start;
>> +    uint16_t lamp_id_end;
>> +    uint8_t red_update_channel;
>> +    uint8_t green_update_channel;
>> +    uint8_t blue_update_channel;
>> +    uint8_t intensity_update_channel;
>> +};
>> +
>> +static int handle_lamp_range_update_report(struct hid_device *hdev,
>> +                       struct lamp_range_update_report_t *report)
>> +{
>> +    int ret;
>> +    struct driver_data_t *driver_data = hdev->driver_data;
>> +    uint8_t lamp_count;
>> +    struct lamp_multi_update_report_t lamp_multi_update_report = {
>> +        .report_id = LAMP_MULTI_UPDATE_REPORT_ID
>> +    };
>> +
>> +    // Catching missformated lamp_range_update_report and fail silently 
>> according to
>> +    // "HID Usage Tables v1.5"
>> +    if (report->lamp_id_start > report->lamp_id_end) {
>> +        pr_debug("lamp_id_start > lamp_id_end in lamp_range_update_report. 
>> Skippng whole report!\n");
>> +        return sizeof(struct lamp_range_update_report_t);
>> +    }
>> +
>> +    if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
>> +        lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>> +    else if (driver_data->keyboard_type == 
>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
>> +        lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>> +
>> +    if (report->lamp_id_end > lamp_count - 1) {
>> +        pr_debug("Out of bounds lamp_id_* in lamp_range_update_report. 
>> Skippng whole report!\n");
>> +        return sizeof(struct lamp_range_update_report_t);
>> +    }
>> +
>> +    // Break handle_lamp_range_update_report call down to multiple
>> +    // handle_lamp_multi_update_report calls to easily ensure that mixing
>> +    // handle_lamp_range_update_report and handle_lamp_multi_update_report
>> +    // does not break things.
>> +    for (int i = report->lamp_id_start; i < report->lamp_id_end + 1; i = i + 
>> 8) {
>> +        lamp_multi_update_report.lamp_count = MIN(report->lamp_id_end + 1 - 
>> i, 8);
>> +        if (i + lamp_multi_update_report.lamp_count == report->lamp_id_end + 1)
>> +            lamp_multi_update_report.lamp_update_flags |=
>> +                LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE;
>> +
>> +        for (int j = 0; j < lamp_multi_update_report.lamp_count; ++j) {
>> +            lamp_multi_update_report.lamp_id[j] = i + j;
>> +            lamp_multi_update_report.update_channels[j].red =
>> +                report->red_update_channel;
>> +            lamp_multi_update_report.update_channels[j].green =
>> +                report->green_update_channel;
>> +            lamp_multi_update_report.update_channels[j].blue =
>> +                report->blue_update_channel;
>> + lamp_multi_update_report.update_channels[j].intensity =
>> +                report->intensity_update_channel;
>> +        }
>> +
>> +        ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report);
>> +        if (ret < 0)
>> +            return ret;
>> +        else if (ret != sizeof(struct lamp_multi_update_report_t))
>> +            return -EIO;
>> +    }
>> +
>> +    return sizeof(struct lamp_range_update_report_t);
>> +}
>> +
>> +
>> +struct __packed lamp_array_control_report_t {
>> +    const uint8_t report_id;
>> +    uint8_t autonomous_mode;
>> +};
>> +
>> +static int handle_lamp_array_control_report(struct hid_device 
>> __always_unused *hdev,
>> +                        struct lamp_array_control_report_t __always_unused 
>> *rep)
>> +{
>> +    // The keyboard firmware doesn't have any built in effects or controls
>> +    // so this is a NOOP.
>> +    // According to the HID Documentation (HID Usage Tables v1.5) this
>> +    // function is optional and can be removed from the HID Report
>> +    // Descriptor, but it should first be confirmed that userspace respects
>> +    // this possibility too. The Microsoft MacroPad reference implementation
>> +    // (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
>> +    // already deviates from the spec at another point, see
>> +    // handle_lamp_*_update_report.
>> +
>> +    return sizeof(struct lamp_array_control_report_t);
>> +}
>> +
>> +
>> +static int ll_raw_request(struct hid_device *hdev, unsigned char reportnum, 
>> __u8 *buf, size_t len,
>> +               unsigned char rtype, int reqtype)
>> +{
>> +    int ret;
>> +
>> +    pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: 
>> %lu buf:\n", rtype,
>> +         reqtype, reportnum, len);
>> +    print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len);
>> +
>> +    ret = -EINVAL;
>> +    if (rtype == HID_FEATURE_REPORT) {
>> +        if (reqtype == HID_REQ_GET_REPORT) {
>> +            if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID
>> +                && len == sizeof(struct lamp_array_attributes_report_t))
>> +                ret = handle_lamp_array_attributes_report(
>> +                    hdev, (struct lamp_array_attributes_report_t *)buf);
>> +            else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID
>> +                && len == sizeof(struct lamp_attributes_response_report_t))
>> +                ret = handle_lamp_attributes_response_report(
>> +                    hdev, (struct lamp_attributes_response_report_t *)buf);
>> +        } else if (reqtype == HID_REQ_SET_REPORT) {
>> +            if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID
>> +                && len == sizeof(struct lamp_attributes_request_report_t))
>> +                ret = handle_lamp_attributes_request_report(
>> +                    hdev, (struct lamp_attributes_request_report_t *)buf);
>> +            else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID
>> +                && len == sizeof(struct lamp_multi_update_report_t))
>> +                ret = handle_lamp_multi_update_report(
>> +                    hdev, (struct lamp_multi_update_report_t *)buf);
>> +            else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID
>> +                && len == sizeof(struct lamp_range_update_report_t))
>> +                ret = handle_lamp_range_update_report(
>> +                    hdev, (struct lamp_range_update_report_t *)buf);
>> +            else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID
>> +                && len == sizeof(struct lamp_array_control_report_t))
>> +                ret = handle_lamp_array_control_report(
>> +                    hdev, (struct lamp_array_control_report_t *)buf);
>> +        }
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static const struct hid_ll_driver ll_driver = {
>> +    .start = &ll_start,
>> +    .stop = &ll_stop,
>> +    .open = &ll_open,
>> +    .close = &ll_close,
>> +    .parse = &ll_parse,
>> +    .raw_request = &ll_raw_request,
>> +};
>> +
>> +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev, 
>> struct hid_device **hdev_out)
>> +{
>> +    int ret;
>> +    struct hid_device *hdev;
>> +
>> +    pr_debug("Adding TUXEDO NB04 Virtual LampArray device.\n");
>> +
>> +    hdev = hid_allocate_device();
>> +    if (IS_ERR(hdev))
>> +        return PTR_ERR(hdev);
>> +    *hdev_out = hdev;
>> +
>> +    strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name));
>> +
>> +    hdev->ll_driver = &ll_driver;
>> +    hdev->bus = BUS_VIRTUAL;
>> +    hdev->vendor = 0x21ba;
>> +    hdev->product = 0x0400;
>> +    hdev->dev.parent = &wdev->dev;
>> +
>> +    ret = hid_add_device(hdev);
>> +    if (ret)
>> +        hid_destroy_device(hdev);
>> +    return ret;
>> +}
>> +EXPORT_SYMBOL(tuxedo_nb04_virtual_lamp_array_add_device);
>> diff --git 
>> a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h 
>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>> new file mode 100644
>> index 0000000000000..fdc2a01d95c24
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>> @@ -0,0 +1,18 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * This code gives the built in RGB lighting of the TUXEDO NB04 devices a
>> + * standardised interface, namely HID LampArray.
>> + *
>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>> + */
>> +
>> +#ifndef TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
>> +#define TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
>> +
>> +#include <linux/wmi.h>
>> +#include <linux/hid.h>
>> +
>> +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev,
>> +                          struct hid_device **hdev_out);
>> +
>> +#endif
>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c 
>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>> new file mode 100644
>> index 0000000000000..dbabdb9dd60c7
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>> @@ -0,0 +1,85 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * This code gives functions to avoid code duplication while interacting with
>> + * the TUXEDO NB04 wmi interfaces.
>> + *
>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>> + */
>> +
>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>> +
>> +#include "tuxedo_nb04_wmi_ab_init.h"
>> +
>> +#include "tuxedo_nb04_wmi_util.h"
>> +
>> +static int __wmi_method_acpi_object_out(struct wmi_device *wdev, uint32_t 
>> wmi_method_id,
>> +                    uint8_t *in, acpi_size in_len, union acpi_object **out)
>
> Please use size_t instead of acpi_size.
>
>> +{
>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data;
>
> Please use dev_get_drvdata().
Ack and Ack -> v2
>
>> +    struct acpi_buffer acpi_buffer_in = { in_len, in };
>> +    struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL };
>> +
>> +    pr_debug("Evaluate WMI method: %u in:\n", wmi_method_id);
>> +    print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len);
>
> I do not think this is useful, please remove.
Will do in the final release, currently it's helping me writing the userspace part.
>
>> +
>> +    mutex_lock(&driver_data->wmi_access_mutex);
>
> Does the underlying ACPI method really require external locking? If not, then 
> please remove this mutex.
Taken from the out of tree driver written by Christoffer, I will ask him about this.
>
>> +    acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 
>> &acpi_buffer_in,
>> +                            &acpi_buffer_out);
>> +    mutex_unlock(&driver_data->wmi_access_mutex);
>> +    if (ACPI_FAILURE(status)) {
>> +        pr_err("Failed to evaluate WMI method.\n");
>> +        return -EIO;
>> +    }
>> +    if (!acpi_buffer_out.pointer) {
>> +        pr_err("Unexpected empty out buffer.\n");
>> +        return -ENODATA;
>> +    }
>
> I believe that printing error messages should be done by the callers of this 
> method.
>
>> +
>> +    *out = acpi_buffer_out.pointer;
>> +
>> +    return 0;
>> +}
>> +
>> +static int __wmi_method_buffer_out(struct wmi_device *wdev, uint32_t 
>> wmi_method_id, uint8_t *in,
>> +                   acpi_size in_len, uint8_t *out, acpi_size out_len)
>
> Please use size_t instead of acpi_size.
>
>> +{
>> +    int ret;
>> +    union acpi_object *acpi_object_out = NULL;
>
> union acpi_object *obj;
> int ret;
ack ack ack
>
>> +
>> +    ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, in, in_len, 
>> &acpi_object_out);
>> +    if (ret)
>> +        return ret;
>> +
>> +    if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
>> +        pr_err("Unexpected out buffer type. Expected: %u Got: %u\n", 
>> ACPI_TYPE_BUFFER,
>> +               acpi_object_out->type);
>> +        kfree(acpi_object_out);
>> +        return -EIO;
>> +    }
>> +    if (acpi_object_out->buffer.length != out_len) {
>
> The Windows ACPI-WMI mappers accepts oversized buffers and ignores any 
> additional data,
> so please change this code to also accept oversized buffers.
Only for input or also for output?
>
>> +        pr_err("Unexpected out buffer length.\n");
>> +        kfree(acpi_object_out);
>> +        return -EIO;
>> +    }
>> +
>> +    memcpy(out, acpi_object_out->buffer.pointer, out_len);
>> +    kfree(acpi_object_out);
>> +
>> +    return ret;
>> +}
>> +
>> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
>> +                    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method,
>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input,
>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output)
>> +{
>> +    return __wmi_method_buffer_out(wdev, method, input->raw, 8, output->raw, 
>> 80);
>> +}
>> +
>> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
>> +                      enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
>> +                      union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
>> +                      union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output)
>> +{
>> +    return __wmi_method_buffer_out(wdev, method, input->raw, 496, 
>> output->raw, 80);
>> +}
>
> Those two functions seem useless to me, please use wmi_method_buffer_out() 
> directly by passing
> a pointer to the underlying struct as data and the output of sizeof() as length.
They are thought of bringing some type safety into the mix so that for any 
method id the input/output size is correct.
>
>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h 
>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>> new file mode 100644
>> index 0000000000000..2765cbe9fcfef
>> --- /dev/null
>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>> @@ -0,0 +1,112 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * This code gives functions to avoid code duplication while interacting with
>> + * the TUXEDO NB04 wmi interfaces.
>> + *
>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>> + */
>> +
>> +#ifndef TUXEDO_NB04_WMI_UTIL_H
>> +#define TUXEDO_NB04_WMI_UTIL_H
>> +
>> +#include <linux/wmi.h>
>> +
>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD    1
>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD    2
>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES    3
>> +
>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_NONE        0
>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY    1
>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE    2
>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY    3
>> +
>> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII    0
>> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO    1
>> +
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_RED        1
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_GREEN        2
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_YELLOW    3
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_BLUE        4
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_PURPLE    5
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_INDIGO    6
>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_WHITE        7
>> +
>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD    BIT(0)
>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1)
>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_KBL        BIT(2)
>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS    BIT(3)
>> +
>> +
>> +union tuxedo_nb04_wmi_8_b_in_80_b_out_input {
>> +    uint8_t raw[8];
>> +    struct __packed {
>> +        uint8_t device_type;
>> +        uint8_t reserved_0[7];
>> +    } get_device_status_input;
>> +};
>> +
>> +union tuxedo_nb04_wmi_8_b_in_80_b_out_output {
>> +    uint8_t raw[80];
>> +    struct __packed {
>> +        uint16_t return_status;
>> +        uint8_t device_enabled;
>> +        uint8_t kbl_type;
>> +        uint8_t kbl_side_bar_supported;
>> +        uint8_t keyboard_physical_layout;
>> +        uint8_t app_pages;
>> +        uint8_t per_key_kbl_default_color;
>> +        uint8_t four_zone_kbl_default_color_1;
>> +        uint8_t four_zone_kbl_default_color_2;
>> +        uint8_t four_zone_kbl_default_color_3;
>> +        uint8_t four_zone_kbl_default_color_4;
>> +        uint8_t light_bar_kbl_default_color;
>> +        uint8_t reserved_0[1];
>> +        uint16_t dedicated_gpu_id;
>> +        uint8_t reserved_1[64];
>> +    } get_device_status_output;
>> +};
>> +
>> +enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods {
>> +    WMI_AB_GET_DEVICE_STATUS    = 2,
>> +};
>> +
>> +
>> +#define WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX    120
>> +
>> +union tuxedo_nb04_wmi_496_b_in_80_b_out_input {
>> +    uint8_t raw[496];
>> +    struct __packed {
>> +        uint8_t reserved_0[15];
>> +        uint8_t lighting_setting_count;
>> +        struct {
>> +            uint8_t key_id;
>> +            uint8_t red;
>> +            uint8_t green;
>> +            uint8_t blue;
>> +        } 
>> lighting_settings[WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX];
>> +    }  kbl_set_multiple_keys_input;
>> +};
>> +
>> +union tuxedo_nb04_wmi_496_b_in_80_b_out_output {
>> +    uint8_t raw[80];
>> +    struct __packed {
>> +        uint8_t return_value;
>> +        uint8_t reserved_0[79];
>> +    } kbl_set_multiple_keys_output;
>> +};
>> +
>> +enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods {
>> +    WMI_AB_KBL_SET_MULTIPLE_KEYS    = 6,
>> +};
>> +
>> +
>> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
>> +                    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method,
>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input,
>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output);
>> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
>> +                      enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
>> +                      union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
>> +                      union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output);
>> +
>> +#endif
kernel test robot Sept. 27, 2024, 8:59 a.m. UTC | #3
Hi Werner,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on drm-tip/drm-tip lee-leds/for-leds-next linus/master v6.11 next-20240927]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Werner-Sembach/platform-x86-tuxedo-Add-virtual-LampArray-for-TUXEDO-NB04-devices/20240927-014628
base:   git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link:    https://lore.kernel.org/r/20240926174405.110748-2-wse%40tuxedocomputers.com
patch subject: [PATCH 1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices
config: i386-buildonly-randconfig-001-20240927 (https://download.01.org/0day-ci/archive/20240927/202409271601.cdUpq1Zd-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240927/202409271601.cdUpq1Zd-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202409271601.cdUpq1Zd-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:601:11: warning: variable 'lamp_count' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized]
     601 |         else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
         |                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:604:28: note: uninitialized use occurs here
     604 |         if (report->lamp_id_end > lamp_count - 1) {
         |                                   ^~~~~~~~~~
   drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:601:7: note: remove the 'if' if its condition is always true
     601 |         else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
         |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     602 |                 lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
   drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:587:20: note: initialize the variable 'lamp_count' to silence this warning
     587 |         uint8_t lamp_count;
         |                           ^
         |                            = '\0'
>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:670:24: warning: format specifies type 'unsigned long' but the argument has type 'size_t' (aka 'unsigned int') [-Wformat]
     669 |         pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype,
         |                                                                               ~~~
         |                                                                               %zu
     670 |                  reqtype, reportnum, len);
         |                                      ^~~
   include/linux/printk.h:595:38: note: expanded from macro 'pr_debug'
     595 |         no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
         |                                     ~~~     ^~~~~~~~~~~
   include/linux/printk.h:133:18: note: expanded from macro 'no_printk'
     133 |                 _printk(fmt, ##__VA_ARGS__);            \
         |                         ~~~    ^~~~~~~~~~~
   2 warnings generated.


vim +601 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c

   581	
   582	static int handle_lamp_range_update_report(struct hid_device *hdev,
   583						   struct lamp_range_update_report_t *report)
   584	{
   585		int ret;
   586		struct driver_data_t *driver_data = hdev->driver_data;
   587		uint8_t lamp_count;
   588		struct lamp_multi_update_report_t lamp_multi_update_report = {
   589			.report_id = LAMP_MULTI_UPDATE_REPORT_ID
   590		};
   591	
   592		// Catching missformated lamp_range_update_report and fail silently according to
   593		// "HID Usage Tables v1.5"
   594		if (report->lamp_id_start > report->lamp_id_end) {
   595			pr_debug("lamp_id_start > lamp_id_end in lamp_range_update_report. Skippng whole report!\n");
   596			return sizeof(struct lamp_range_update_report_t);
   597		}
   598	
   599		if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
   600			lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
 > 601		else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
   602			lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
   603	
   604		if (report->lamp_id_end > lamp_count - 1) {
   605			pr_debug("Out of bounds lamp_id_* in lamp_range_update_report. Skippng whole report!\n");
   606			return sizeof(struct lamp_range_update_report_t);
   607		}
   608	
   609		// Break handle_lamp_range_update_report call down to multiple
   610		// handle_lamp_multi_update_report calls to easily ensure that mixing
   611		// handle_lamp_range_update_report and handle_lamp_multi_update_report
   612		// does not break things.
   613		for (int i = report->lamp_id_start; i < report->lamp_id_end + 1; i = i + 8) {
   614			lamp_multi_update_report.lamp_count = MIN(report->lamp_id_end + 1 - i, 8);
   615			if (i + lamp_multi_update_report.lamp_count == report->lamp_id_end + 1)
   616				lamp_multi_update_report.lamp_update_flags |=
   617					LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE;
   618	
   619			for (int j = 0; j < lamp_multi_update_report.lamp_count; ++j) {
   620				lamp_multi_update_report.lamp_id[j] = i + j;
   621				lamp_multi_update_report.update_channels[j].red =
   622					report->red_update_channel;
   623				lamp_multi_update_report.update_channels[j].green =
   624					report->green_update_channel;
   625				lamp_multi_update_report.update_channels[j].blue =
   626					report->blue_update_channel;
   627				lamp_multi_update_report.update_channels[j].intensity =
   628					report->intensity_update_channel;
   629			}
   630	
   631			ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report);
   632			if (ret < 0)
   633				return ret;
   634			else if (ret != sizeof(struct lamp_multi_update_report_t))
   635				return -EIO;
   636		}
   637	
   638		return sizeof(struct lamp_range_update_report_t);
   639	}
   640	
   641	
   642	struct __packed lamp_array_control_report_t {
   643		const uint8_t report_id;
   644		uint8_t autonomous_mode;
   645	};
   646	
   647	static int handle_lamp_array_control_report(struct hid_device __always_unused *hdev,
   648						    struct lamp_array_control_report_t __always_unused *rep)
   649	{
   650		// The keyboard firmware doesn't have any built in effects or controls
   651		// so this is a NOOP.
   652		// According to the HID Documentation (HID Usage Tables v1.5) this
   653		// function is optional and can be removed from the HID Report
   654		// Descriptor, but it should first be confirmed that userspace respects
   655		// this possibility too. The Microsoft MacroPad reference implementation
   656		// (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
   657		// already deviates from the spec at another point, see
   658		// handle_lamp_*_update_report.
   659	
   660		return sizeof(struct lamp_array_control_report_t);
   661	}
   662	
   663	
   664	static int ll_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len,
   665				   unsigned char rtype, int reqtype)
   666	{
   667		int ret;
   668	
   669		pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype,
 > 670			 reqtype, reportnum, len);
   671		print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len);
   672	
   673		ret = -EINVAL;
   674		if (rtype == HID_FEATURE_REPORT) {
   675			if (reqtype == HID_REQ_GET_REPORT) {
   676				if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID
   677				    && len == sizeof(struct lamp_array_attributes_report_t))
   678					ret = handle_lamp_array_attributes_report(
   679						hdev, (struct lamp_array_attributes_report_t *)buf);
   680				else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID
   681				    && len == sizeof(struct lamp_attributes_response_report_t))
   682					ret = handle_lamp_attributes_response_report(
   683						hdev, (struct lamp_attributes_response_report_t *)buf);
   684			} else if (reqtype == HID_REQ_SET_REPORT) {
   685				if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID
   686				    && len == sizeof(struct lamp_attributes_request_report_t))
   687					ret = handle_lamp_attributes_request_report(
   688						hdev, (struct lamp_attributes_request_report_t *)buf);
   689				else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID
   690				    && len == sizeof(struct lamp_multi_update_report_t))
   691					ret = handle_lamp_multi_update_report(
   692						hdev, (struct lamp_multi_update_report_t *)buf);
   693				else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID
   694				    && len == sizeof(struct lamp_range_update_report_t))
   695					ret = handle_lamp_range_update_report(
   696						hdev, (struct lamp_range_update_report_t *)buf);
   697				else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID
   698				    && len == sizeof(struct lamp_array_control_report_t))
   699					ret = handle_lamp_array_control_report(
   700						hdev, (struct lamp_array_control_report_t *)buf);
   701			}
   702		}
   703	
   704		return ret;
   705	}
   706
kernel test robot Sept. 27, 2024, 9:20 a.m. UTC | #4
Hi Werner,

kernel test robot noticed the following build warnings:

[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on drm-tip/drm-tip lee-leds/for-leds-next linus/master v6.11 next-20240927]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Werner-Sembach/platform-x86-tuxedo-Add-virtual-LampArray-for-TUXEDO-NB04-devices/20240927-014628
base:   git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link:    https://lore.kernel.org/r/20240926174405.110748-2-wse%40tuxedocomputers.com
patch subject: [PATCH 1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices
config: i386-buildonly-randconfig-002-20240927 (https://download.01.org/0day-ci/archive/20240927/202409271653.rAiw37rN-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240927/202409271653.rAiw37rN-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202409271653.rAiw37rN-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c: In function 'll_raw_request':
>> <command-line>: warning: format '%lu' expects argument of type 'long unsigned int', but argument 6 has type 'size_t' {aka 'unsigned int'} [-Wformat=]
   <command-line>: note: in definition of macro 'KBUILD_MODNAME'
   include/linux/dynamic_debug.h:224:29: note: in expansion of macro 'pr_fmt'
     224 |                 func(&id, ##__VA_ARGS__);                       \
         |                             ^~~~~~~~~~~
   include/linux/dynamic_debug.h:248:9: note: in expansion of macro '__dynamic_func_call_cls'
     248 |         __dynamic_func_call_cls(__UNIQUE_ID(ddebug), cls, fmt, func, ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~~~~~~~~~
   include/linux/dynamic_debug.h:250:9: note: in expansion of macro '_dynamic_func_call_cls'
     250 |         _dynamic_func_call_cls(_DPRINTK_CLASS_DFLT, fmt, func, ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~~~~~~~~
   include/linux/dynamic_debug.h:269:9: note: in expansion of macro '_dynamic_func_call'
     269 |         _dynamic_func_call(fmt, __dynamic_pr_debug,             \
         |         ^~~~~~~~~~~~~~~~~~
   include/linux/printk.h:589:9: note: in expansion of macro 'dynamic_pr_debug'
     589 |         dynamic_pr_debug(fmt, ##__VA_ARGS__)
         |         ^~~~~~~~~~~~~~~~
   drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:669:9: note: in expansion of macro 'pr_debug'
     669 |         pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype,
         |         ^~~~~~~~
Werner Sembach Sept. 27, 2024, 11:24 a.m. UTC | #5
Hi,

an additional question below

Am 27.09.24 um 08:59 schrieb Werner Sembach:
> Hi,
>
> Am 26.09.24 um 20:39 schrieb Armin Wolf:
>> Am 26.09.24 um 19:44 schrieb Werner Sembach:
>>
>>> [...]
>>> +// We don't know if the WMI API is stable and how unique the GUID is for 
>>> this ODM. To be on the safe
>>> +// side we therefore only run this driver on tested devices defined by this 
>>> list.
>>> +static const struct dmi_system_id tested_devices_dmi_table[] = {
>>> +    {
>>> +        // TUXEDO Sirius 16 Gen1
>>> +        .matches = {
>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
>>> +        },
>>> +    },
>>> +    {
>>> +        // TUXEDO Sirius 16 Gen2
>>> +        .matches = {
>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
>>> +        },
>>> +    },
>>> +    { }
>>> +};
>>> +
>>> +static int probe(struct wmi_device *wdev, const void __always_unused *context)
>>> +{
>>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data;
>>> +
>>> +    if (dmi_check_system(tested_devices_dmi_table))
>>> +        return -ENODEV;
>>
>> Hi,
>>
>> please do this DMI check during module initialization. This avoids having an 
>> useless WMI driver
>> on unsupported machines and allows for marking tested_devices_dmi_table as 
>> __initconst.
I wonder how to do it since I don't use module_init manually but 
module_wmi_driver to register the module.
>>
>> Besides that, maybe a "force" module parameter for overriding the DMI 
>> checking could be
>> useful?

Considering the bricking potential i somewhat want for people to look in the 
source first, so i would not implementen a force module parameter.

Kind regards,

Werner
kernel test robot Sept. 27, 2024, 12:18 p.m. UTC | #6
Hi Werner,

kernel test robot noticed the following build errors:

[auto build test ERROR on drm-misc/drm-misc-next]
[also build test ERROR on drm-tip/drm-tip linus/master v6.11 next-20240927]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Werner-Sembach/platform-x86-tuxedo-Add-virtual-LampArray-for-TUXEDO-NB04-devices/20240927-014628
base:   git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link:    https://lore.kernel.org/r/20240926174405.110748-2-wse%40tuxedocomputers.com
patch subject: [PATCH 1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices
config: i386-alldefconfig (https://download.01.org/0day-ci/archive/20240927/202409271950.jUMzgCG8-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240927/202409271950.jUMzgCG8-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202409271950.jUMzgCG8-lkp@intel.com/

All errors (new ones prefixed by >>):

   ld: drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.o: in function `tuxedo_nb04_wmi_ab_driver_init':
>> tuxedo_nb04_wmi_ab_init.c:(.init.text+0xb): undefined reference to `__wmi_driver_register'
   ld: drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.o: in function `tuxedo_nb04_wmi_ab_driver_exit':
>> tuxedo_nb04_wmi_ab_init.c:(.exit.text+0x9): undefined reference to `wmi_driver_unregister'
   ld: drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.o: in function `__wmi_method_buffer_out.constprop.0':
>> tuxedo_nb04_wmi_util.c:(.text+0x52): undefined reference to `wmidev_evaluate_method'
Armin Wolf Sept. 27, 2024, 5:15 p.m. UTC | #7
Am 27.09.24 um 08:59 schrieb Werner Sembach:

> Hi,
>
> Am 26.09.24 um 20:39 schrieb Armin Wolf:
>> Am 26.09.24 um 19:44 schrieb Werner Sembach:
>>
>>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a 
>>> per-key
>>> controllable RGB keyboard backlight. The firmware API for it is 
>>> implemented
>>> via WMI.
>>>
>>> To make the backlight userspace configurable this driver emulates a
>>> LampArray HID device and translates the input from hidraw to the
>>> corresponding WMI calls. This is a new approach as the leds 
>>> subsystem lacks
>>> a suitable UAPI for per-key keyboard backlights, and like this no 
>>> new UAPI
>>> needs to be established.
>>>
>>> Co-developed-by: Christoffer Sandberg <cs@tuxedo.de>
>>> Signed-off-by: Christoffer Sandberg <cs@tuxedo.de>
>>> Signed-off-by: Werner Sembach <wse@tuxedocomputers.com>
>>> Link: 
>>> https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
>>> ---
>>>   MAINTAINERS                                   |   6 +
>>>   drivers/platform/x86/Kconfig                  |   2 +
>>>   drivers/platform/x86/Makefile                 |   3 +
>>>   drivers/platform/x86/tuxedo/Kbuild            |   9 +
>>>   drivers/platform/x86/tuxedo/Kconfig           |  14 +
>>>   .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.c      |  86 ++
>>>   .../x86/tuxedo/tuxedo_nb04_wmi_ab_init.h      |  20 +
>>>   .../tuxedo_nb04_wmi_ab_virtual_lamp_array.c   | 741 
>>> ++++++++++++++++++
>>>   .../tuxedo_nb04_wmi_ab_virtual_lamp_array.h   |  18 +
>>>   .../x86/tuxedo/tuxedo_nb04_wmi_util.c         |  85 ++
>>>   .../x86/tuxedo/tuxedo_nb04_wmi_util.h         | 112 +++
>>>   11 files changed, 1096 insertions(+)
>>>   create mode 100644 drivers/platform/x86/tuxedo/Kbuild
>>>   create mode 100644 drivers/platform/x86/tuxedo/Kconfig
>>>   create mode 100644 
>>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>>>   create mode 100644 
>>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>>>   create mode 100644 
>>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>>>   create mode 100644 
>>> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>>>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>>>   create mode 100644 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index cc40a9d9b8cd1..3385ad51af194 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -23358,6 +23358,12 @@ T:    git 
>>> git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat
>>>   F:    tools/power/x86/turbostat/
>>>   F:    tools/testing/selftests/turbostat/
>>>
>>> +TUXEDO DRIVERS
>>> +M:    Werner Sembach <wse@tuxedocomputers.com>
>>> +L:    platform-driver-x86@vger.kernel.org
>>> +S:    Supported
>>> +F:    drivers/platform/x86/tuxedo/
>>> +
>>>   TW5864 VIDEO4LINUX DRIVER
>>>   M:    Bluecherry Maintainers <maintainers@bluecherrydvr.com>
>>>   M:    Andrey Utkin <andrey.utkin@corp.bluecherry.net>
>>> diff --git a/drivers/platform/x86/Kconfig 
>>> b/drivers/platform/x86/Kconfig
>>> index ddfccc226751f..c7cffb222adac 100644
>>> --- a/drivers/platform/x86/Kconfig
>>> +++ b/drivers/platform/x86/Kconfig
>>> @@ -1196,3 +1196,5 @@ config P2SB
>>>         The main purpose of this library is to unhide P2SB device in 
>>> case
>>>         firmware kept it hidden on some platforms in order to access 
>>> devices
>>>         behind it.
>>> +
>>> +source "drivers/platform/x86/tuxedo/Kconfig"
>>> diff --git a/drivers/platform/x86/Makefile 
>>> b/drivers/platform/x86/Makefile
>>> index e1b1429470674..1562dcd7ad9a5 100644
>>> --- a/drivers/platform/x86/Makefile
>>> +++ b/drivers/platform/x86/Makefile
>>> @@ -153,3 +153,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS)        += 
>>> winmate-fm07-keys.o
>>>
>>>   # SEL
>>>   obj-$(CONFIG_SEL3350_PLATFORM)        += sel3350-platform.o
>>> +
>>> +# TUXEDO
>>> +obj-y                    += tuxedo/
>>> diff --git a/drivers/platform/x86/tuxedo/Kbuild 
>>> b/drivers/platform/x86/tuxedo/Kbuild
>>> new file mode 100644
>>> index 0000000000000..5a3506ab98131
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/tuxedo/Kbuild
>>> @@ -0,0 +1,9 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +#
>>> +# TUXEDO X86 Platform Specific Drivers
>>> +#
>>> +
>>> +tuxedo_nb04_wmi_ab-y            := tuxedo_nb04_wmi_ab_init.o
>>> +tuxedo_nb04_wmi_ab-y            += tuxedo_nb04_wmi_util.o
>>> +tuxedo_nb04_wmi_ab-y            += 
>>> tuxedo_nb04_wmi_ab_virtual_lamp_array.o
>>> +obj-$(CONFIG_TUXEDO_NB04_WMI_AB)    += tuxedo_nb04_wmi_ab.o
>>> diff --git a/drivers/platform/x86/tuxedo/Kconfig 
>>> b/drivers/platform/x86/tuxedo/Kconfig
>>> new file mode 100644
>>> index 0000000000000..b1f7c6ceeaae4
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/tuxedo/Kconfig
>>> @@ -0,0 +1,14 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +#
>>> +# TUXEDO X86 Platform Specific Drivers
>>> +#
>>> +
>>> +menuconfig TUXEDO_NB04_WMI_AB
>>> +    tristate "TUXEDO NB04 WMI AB Platform Driver"
>>> +    default m
>>> +    help
>>> +      This driver implements the WMI AB device found on TUXEDO 
>>> Notebooks
>>> +      with board vendor NB04. For the time being only the keyboard 
>>> backlight
>>> +      control is implemented.
>>> +
>>> +      When compiled as a module it will be called tuxedo_nb04_wmi_ab.
>>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>>> new file mode 100644
>>> index 0000000000000..6e4446b0e3dd8
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
>>> @@ -0,0 +1,86 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * This driver implements the WMI AB device found on TUXEDO 
>>> Notebooks with board
>>> + * vendor NB04.
>>> + *
>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>> + */
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/wmi.h>
>>> +#include <linux/dmi.h>
>>> +
>>> +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
>>> +
>>> +#include "tuxedo_nb04_wmi_ab_init.h"
>>> +
>>> +// We don't know if the WMI API is stable and how unique the GUID 
>>> is for this ODM. To be on the safe
>>> +// side we therefore only run this driver on tested devices defined 
>>> by this list.
>>> +static const struct dmi_system_id tested_devices_dmi_table[] = {
>>> +    {
>>> +        // TUXEDO Sirius 16 Gen1
>>> +        .matches = {
>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
>>> +        },
>>> +    },
>>> +    {
>>> +        // TUXEDO Sirius 16 Gen2
>>> +        .matches = {
>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
>>> +        },
>>> +    },
>>> +    { }
>>> +};
>>> +
>>> +static int probe(struct wmi_device *wdev, const void 
>>> __always_unused *context)
>>> +{
>>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data;
>>> +
>>> +    if (dmi_check_system(tested_devices_dmi_table))
>>> +        return -ENODEV;
>>
>> Hi,
>>
>> please do this DMI check during module initialization. This avoids 
>> having an useless WMI driver
>> on unsupported machines and allows for marking 
>> tested_devices_dmi_table as __initconst.
>>
>> Besides that, maybe a "force" module parameter for overriding the DMI 
>> checking could be
>> useful?
>>
>>> +
>>> +    driver_data = devm_kzalloc(&wdev->dev, sizeof(struct 
>>> tuxedo_nb04_wmi_driver_data_t),
>>> +                   GFP_KERNEL);
>>
>> Please use sizeof(*driver_data).
>>
>>> +    if (!driver_data)
>>> +        return -ENOMEM;
>>> +
>>> +    mutex_init(&driver_data->wmi_access_mutex);
>>
>> Please use devm_mutex_init(), so the mutex is properly destroyed when 
>> unbinding.
>>
>>> +
>>> +    dev_set_drvdata(&wdev->dev, driver_data);
>>> +
>>> +    tuxedo_nb04_virtual_lamp_array_add_device(wdev, 
>>> &driver_data->virtual_lamp_array_hdev);
>>
>> Error handling missing.
>>
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static void remove(struct wmi_device *wdev)
>>> +{
>>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data = 
>>> wdev->dev.driver_data;
>>> +
>>> + hid_destroy_device(driver_data->virtual_lamp_array_hdev);
>>> +}
>>> +
>>> +static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = {
>>> +    { .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" },
>>> +    { }
>>> +};
>>> +MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids);
>>> +
>>> +static struct wmi_driver tuxedo_nb04_wmi_ab_driver = {
>>> +    .driver = {
>>> +        .name = "tuxedo_nb04_wmi_ab",
>>> +        .owner = THIS_MODULE
>>> +    },
>>> +    .id_table = tuxedo_nb04_wmi_ab_device_ids,
>>> +    .probe = probe,
>>> +    .remove = remove
>>
>> I recommend setting probe_type = PROBE_PREFER_ASYNCHRONOUS, see 
>> Documentation/wmi/driver-development-guide.rst.
>> Also please set no_singleton = true.
>>
>>> +};
>>> +module_wmi_driver(tuxedo_nb04_wmi_ab_driver);
>>> +
>>> +MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 
>>> devices");
>>> +MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>");
>>> +MODULE_LICENSE("GPL");
>>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>>> new file mode 100644
>>> index 0000000000000..aebfd465c9b61
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
>>> @@ -0,0 +1,20 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * This driver implements the WMI AB device found on TUXEDO 
>>> Notebooks with board
>>> + * vendor NB04.
>>> + *
>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>> + */
>>> +
>>> +#ifndef TUXEDO_NB04_WMI_AB_INIT_H
>>> +#define TUXEDO_NB04_WMI_AB_INIT_H
>>> +
>>> +#include <linux/mutex.h>
>>> +#include <linux/hid.h>
>>> +
>>> +struct tuxedo_nb04_wmi_driver_data_t {
>>> +    struct mutex wmi_access_mutex;
>>> +    struct hid_device *virtual_lamp_array_hdev;
>>> +};
>>> +
>>> +#endif
>>> diff --git 
>>> a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>>> new file mode 100644
>>> index 0000000000000..04af19aa6ad5f
>>> --- /dev/null
>>> +++ 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
>>> @@ -0,0 +1,741 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * This code gives the built in RGB lighting of the TUXEDO NB04 
>>> devices a
>>> + * standardised interface, namely HID LampArray.
>>> + *
>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>> + */
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#include "tuxedo_nb04_wmi_util.h"
>>> +
>>> +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
>>> +
>>> +#define dev_to_wdev(__dev)    container_of(__dev, struct 
>>> wmi_device, dev)
>>
>> Please use to_wmi_device() instead.
>>
>>> +
>>> +enum report_ids {
>>> +    LAMP_ARRAY_ATTRIBUTES_REPORT_ID        = 0x01,
>>> +    LAMP_ATTRIBUTES_REQUEST_REPORT_ID    = 0x02,
>>> +    LAMP_ATTRIBUTES_RESPONSE_REPORT_ID    = 0x03,
>>> +    LAMP_MULTI_UPDATE_REPORT_ID        = 0x04,
>>> +    LAMP_RANGE_UPDATE_REPORT_ID        = 0x05,
>>> +    LAMP_ARRAY_CONTROL_REPORT_ID        = 0x06,
>>> +};
>>> +
>>> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
>>> +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>>> +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>>> +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>>> +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>>> +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>>> +    0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
>>> +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>>> +    0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
>>> +    0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
>>> +    0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
>>> +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>>> +    0x4f,                                 0x62, 0x63, 0x58
>>> +};
>>> +
>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
>>> +     25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 
>>> 158600, 175300,
>>> +    192000, 208700, 225400, 242100, 258800, 275500,   294500, 
>>> 311200, 327900, 344600,
>>> +     24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 
>>> 172000, 190500,
>>> +    209000, 227500, 246000, 269500,                   294500, 
>>> 311200, 327900, 344600,
>>> +     31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 
>>> 181000, 199500,
>>> +    218000, 236500, 255000, 273500,                   294500, 
>>> 311200, 327900,
>>> +     33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 
>>> 186500, 205000,
>>> +    223500, 242000, 267500,                           294500, 
>>> 311200, 327900, 344600,
>>> +     37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 
>>> 195500, 214000,
>>> +    232500, 251500, 273500,                           294500, 
>>> 311200, 327900,
>>> +     28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 
>>> 255000, 273500,
>>> +    292000,                                           311200, 
>>> 327900, 344600
>>> +};
>>> +
>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
>>> +     53000,  53000,  53000,  53000,  53000,  53000,  53000, 53000,  
>>> 53000,  53000,
>>> +     53000,  53000,  53000,  53000,  53000,  53000,    53000, 
>>> 53000,  53000,  53000,
>>> +     67500,  67500,  67500,  67500,  67500,  67500,  67500, 67500,  
>>> 67500,  67500,
>>> +     67500,  67500,  67500,  67500,                    67500, 
>>> 67500,  67500,  67500,
>>> +     85500,  85500,  85500,  85500,  85500,  85500,  85500, 85500,  
>>> 85500,  85500,
>>> +     85500,  85500,  85500,  85500,                    85500, 
>>> 85500,  85500,
>>> +    103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 
>>> 103500, 103500,
>>> +    103500, 103500, 103500,                           103500, 
>>> 103500, 103500,  94500,
>>> +    121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 
>>> 121500, 121500,
>>> +    121500, 121500, 129000,                           121500, 
>>> 121500, 121500,
>>> +    139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 
>>> 147000, 147000,
>>> +    147000,                                           139500, 
>>> 139500, 130500
>>> +};
>>> +
>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
>>> +      5000,   5000,   5000,   5000,   5000,   5000,   5000, 5000,   
>>> 5000,   5000,
>>> +      5000,   5000,   5000,   5000,   5000,   5000,     5000, 
>>> 5000,   5000,   5000,
>>> +      5250,   5250,   5250,   5250,   5250,   5250,   5250, 5250,   
>>> 5250,   5250,
>>> +      5250,   5250,   5250,   5250,                     5250, 
>>> 5250,   5250,   5250,
>>> +      5500,   5500,   5500,   5500,   5500,   5500,   5500, 5500,   
>>> 5500,   5500,
>>> +      5500,   5500,   5500,   5500,                     5500, 
>>> 5500,   5500,
>>> +      5750,   5750,   5750,   5750,   5750,   5750,   5750, 5750,   
>>> 5750,   5750,
>>> +      5750,   5750,   5750,                             5750, 
>>> 5750,   5750,   5625,
>>> +      6000,   6000,   6000,   6000,   6000,   6000,   6000, 6000,   
>>> 6000,   6000,
>>> +      6000,   6000,   6125,                             6000, 
>>> 6000,   6000,
>>> +      6250,   6250,   6250,   6250,   6250,   6250,   6250, 6250,   
>>> 6375,   6375,
>>> +      6375,                                             6250, 
>>> 6250,   6125
>>> +};
>>> +
>>> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
>>> +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>>> +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>>> +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>>> +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>>> +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>>> +    0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
>>> +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>>> +    0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
>>> +    0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
>>> +    0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
>>> +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>>> +    0x4f,                                 0x62, 0x63, 0x58
>>> +};
>>> +
>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
>>> +     25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 
>>> 158600, 175300,
>>> +    192000, 208700, 225400, 242100, 258800, 275500,   294500, 
>>> 311200, 327900, 344600,
>>> +     24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 
>>> 172000, 190500,
>>> +    209000, 227500, 246000, 269500,                   294500, 
>>> 311200, 327900, 344600,
>>> +     31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 
>>> 181000, 199500,
>>> +    218000, 234500, 251000,                           294500, 
>>> 311200, 327900,
>>> +     33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 
>>> 186500, 205000,
>>> +    223500, 240000, 256500, 271500,                   294500, 
>>> 311200, 327900, 344600,
>>> +     28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 
>>> 177000, 195500,
>>> +    214000, 232500, 251500, 273500,                   294500, 
>>> 311200, 327900,
>>> +     28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 
>>> 255000, 273500,
>>> +    292000,                                           311200, 
>>> 327900, 344600
>>> +};
>>> +
>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
>>> +     53000,  53000,  53000,  53000,  53000,  53000,  53000, 53000,  
>>> 53000,  53000,
>>> +     53000,  53000,  53000,  53000,  53000,  53000,    53000, 
>>> 53000,  53000,  53000,
>>> +     67500,  67500,  67500,  67500,  67500,  67500,  67500, 67500,  
>>> 67500,  67500,
>>> +     67500,  67500,  67500,  67500,                    67500, 
>>> 67500,  67500,  67500,
>>> +     85500,  85500,  85500,  85500,  85500,  85500,  85500, 85500,  
>>> 85500,  85500,
>>> +     85500,  85500,  85500,                            85500, 
>>> 85500,  85500,
>>> +    103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 
>>> 103500, 103500,
>>> +    103500, 103500, 103500,  94500,                   103500, 
>>> 103500, 103500,  94500,
>>> +    121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 
>>> 121500, 121500,
>>> +    121500, 121500, 121500, 129000,                   121500, 
>>> 121500, 121500,
>>> +    139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 
>>> 147000, 147000,
>>> +    147000,                                           139500, 
>>> 139500, 130500
>>> +};
>>> +
>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
>>> +      5000,   5000,   5000,   5000,   5000,   5000,   5000, 5000,   
>>> 5000,   5000,
>>> +      5000,   5000,   5000,   5000, 5000, 5000,         5000, 
>>> 5000,   5000,   5000,
>>> +      5250,   5250,   5250,   5250,   5250,   5250,   5250, 5250,   
>>> 5250,   5250,
>>> +      5250,   5250,   5250,   5250,                     5250, 
>>> 5250,   5250,   5250,
>>> +      5500,   5500,   5500,   5500,   5500,   5500,   5500, 5500,   
>>> 5500,   5500,
>>> +      5500,   5500,   5500,                             5500, 
>>> 5500,   5500,
>>> +      5750,   5750,   5750,   5750,   5750,   5750,   5750, 5750,   
>>> 5750,   5750,
>>> +      5750,   5750,   5750,   5750,                     5750, 
>>> 5750,   5750,   5625,
>>> +      6000,   6000,   6000,   6000,   6000,   6000,   6000, 6000,   
>>> 6000,   6000,
>>> +      6000,   6000,   6000,   6125,                     6000, 
>>> 6000,   6000,
>>> +      6250,   6250,   6250,   6250,   6250,   6250,   6250, 6250,   
>>> 6375,   6375,
>>> +      6375,                                             6250, 
>>> 6250,   6125
>>> +};
>>> +
>>> +struct driver_data_t {
>>> +    uint8_t keyboard_type;
>>> +    uint8_t lamp_count;
>>> +    uint8_t next_lamp_id;
>>> +    union tuxedo_nb04_wmi_496_b_in_80_b_out_input 
>>> next_kbl_set_multiple_keys_input;
>>> +};
>>> +
>>> +
>>> +static int ll_start(struct hid_device *hdev)
>>> +{
>>> +    int ret;
>>> +    struct driver_data_t *driver_data;
>>> +    struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
>>> +    union tuxedo_nb04_wmi_8_b_in_80_b_out_input input;
>>> +    union tuxedo_nb04_wmi_8_b_in_80_b_out_output output;
>>> +
>>> +    driver_data = devm_kzalloc(&hdev->dev, sizeof(struct 
>>> driver_data_t), GFP_KERNEL);
>>> +    if (!driver_data)
>>> +        return -ENOMEM;
>>
>> Please use sizeof(*driver_data).
> All of the above: Ack, will be in v2.
>>
>>> +
>>> +    input.get_device_status_input.device_type = 
>>> WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD;
>>> +    ret = tuxedo_nb04_wmi_8_b_in_80_b_out(wdev, 
>>> WMI_AB_GET_DEVICE_STATUS, &input, &output);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>> +    driver_data->keyboard_type = 
>>> output.get_device_status_output.keyboard_physical_layout;
>>> +    driver_data->lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>>> +    driver_data->next_lamp_id = 0;
>>> +
>>> +    hdev->driver_data = driver_data;
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +
>>> +static void ll_stop(struct hid_device __always_unused *hdev)
>>> +{
>>> +}
>>> +
>>> +
>>> +static int ll_open(struct hid_device __always_unused *hdev)
>>> +{
>>> +    return 0;
>>> +}
>>> +
>>> +
>>> +static void ll_close(struct hid_device __always_unused *hdev)
>>> +{
>>> +}
>>
>> I have no experience with the HID subsystem, but this looks suspicious.
>
> stop() is to cleanup stuff from start(), but the only thing i alloc in 
> start() is with devm_kzalloc, so it gets cleaned up automatically.
>
> open and close is called whenever the hidraw file descriptor gets 
> opened/closed in userspace, nothing to do here for this driver.
>
> leaving these functions pointer as NULL in the ll_driver struct 
> crashes the kernel, so i placed noop functions there. Don't know if 
> there is a more elegant way.
>
>>
>>> +
>>> +
>>> +static uint8_t report_descriptor[327] = {
>>> +    0x05, 0x59,            // Usage Page (Lighting and Illumination)
>>> +    0x09, 0x01,            // Usage (Lamp Array)
>>> +    0xa1, 0x01,            // Collection (Application)
>>> +    0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
>>> +    0x09, 0x02,            //  Usage (Lamp Array Attributes Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x03,            //   Usage (Lamp Count)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
>>> +    0x09, 0x04,            //   Usage (Bounding Box Width In 
>>> Micrometers)
>>> +    0x09, 0x05,            //   Usage (Bounding Box Height In 
>>> Micrometers)
>>> +    0x09, 0x06,            //   Usage (Bounding Box Depth In 
>>> Micrometers)
>>> +    0x09, 0x07,            //   Usage (Lamp Array Kind)
>>> +    0x09, 0x08,            //   Usage (Min Update Interval In 
>>> Microseconds)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
>>> +    0x75, 0x20,            //   Report Size (32)
>>> +    0x95, 0x05,            //   Report Count (5)
>>> +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
>>> +    0x09, 0x20,            //  Usage (Lamp Attributes Request Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x21,            //   Usage (Lamp Id)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
>>> +    0x09, 0x22,            //  Usage (Lamp Attributes Response Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x21,            //   Usage (Lamp Id)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x23,            //   Usage (Position X In Micrometers)
>>> +    0x09, 0x24,            //   Usage (Position Y In Micrometers)
>>> +    0x09, 0x25,            //   Usage (Position Z In Micrometers)
>>> +    0x09, 0x27,            //   Usage (Update Latency In Microseconds)
>>> +    0x09, 0x26,            //   Usage (Lamp Purposes)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
>>> +    0x75, 0x20,            //   Report Size (32)
>>> +    0x95, 0x05,            //   Report Count (5)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x28,            //   Usage (Red Level Count)
>>> +    0x09, 0x29,            //   Usage (Green Level Count)
>>> +    0x09, 0x2a,            //   Usage (Blue Level Count)
>>> +    0x09, 0x2b,            //   Usage (Intensity Level Count)
>>> +    0x09, 0x2c,            //   Usage (Is Programmable)
>>> +    0x09, 0x2d,            //   Usage (Input Binding)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x06,            //   Report Count (6)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
>>> +    0x09, 0x50,            //  Usage (Lamp Multi Update Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x03,            //   Usage (Lamp Count)
>>> +    0x09, 0x55,            //   Usage (Lamp Update Flags)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x25, 0x08,            //   Logical Maximum (8)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x02,            //   Report Count (2)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x21,            //   Usage (Lamp Id)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x08,            //   Report Count (8)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x20,            //   Report Count (32)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
>>> +    0x09, 0x60,            //  Usage (Lamp Range Update Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x55,            //   Usage (Lamp Update Flags)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x25, 0x08,            //   Logical Maximum (8)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x61,            //   Usage (Lamp Id Start)
>>> +    0x09, 0x62,            //   Usage (Lamp Id End)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x02,            //   Report Count (2)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x04,            //   Report Count (4)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
>>> +    0x09, 0x70,            //  Usage (Lamp Array Control Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x71,            //   Usage (Autonomous Mode)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x25, 0x01,            //   Logical Maximum (1)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0xc0                // End Collection
>>> +};
>>> +
>>> +static int ll_parse(struct hid_device *hdev)
>>> +{
>>> +    return hid_parse_report(hdev, report_descriptor, 
>>> sizeof(report_descriptor));
>>> +}
>>> +
>>> +
>>> +struct __packed lamp_array_attributes_report_t {
>>> +    const uint8_t report_id;
>>> +    uint16_t lamp_count;
>>> +    uint32_t bounding_box_width_in_micrometers;
>>> +    uint32_t bounding_box_height_in_micrometers;
>>> +    uint32_t bounding_box_depth_in_micrometers;
>>> +    uint32_t lamp_array_kind;
>>> +    uint32_t min_update_interval_in_microseconds;
>>> +};
>>> +
>>> +static int handle_lamp_array_attributes_report(struct hid_device 
>>> *hdev,
>>> +                           struct lamp_array_attributes_report_t *rep)
>>> +{
>>> +    struct driver_data_t *driver_data = hdev->driver_data;
>>> +
>>> +    rep->lamp_count = driver_data->lamp_count;
>>> +    rep->bounding_box_width_in_micrometers = 368000;
>>> +    rep->bounding_box_height_in_micrometers = 266000;
>>> +    rep->bounding_box_depth_in_micrometers = 30000;
>>> +    // LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of 
>>> "HID Usage Tables v1.5"
>>> +    rep->lamp_array_kind = 1;
>>> +    // Some guessed value for interval microseconds
>>> +    rep->min_update_interval_in_microseconds = 500;
>>> +
>>> +    return sizeof(struct lamp_array_attributes_report_t);
>>> +}
>>> +
>>> +
>>> +struct __packed lamp_attributes_request_report_t {
>>> +    const uint8_t report_id;
>>> +    uint16_t lamp_id;
>>> +};
>>> +
>>> +static int handle_lamp_attributes_request_report(struct hid_device 
>>> *hdev,
>>> +                         struct lamp_attributes_request_report_t *rep)
>>> +{
>>> +    struct driver_data_t *driver_data = hdev->driver_data;
>>> +
>>> +    if (rep->lamp_id < driver_data->lamp_count)
>>> +        driver_data->next_lamp_id = rep->lamp_id;
>>> +    else
>>> +        driver_data->next_lamp_id = 0;
>>> +
>>> +    return sizeof(struct lamp_attributes_request_report_t);
>>> +}
>>> +
>>> +
>>> +struct __packed lamp_attributes_response_report_t {
>>> +    const uint8_t report_id;
>>> +    uint16_t lamp_id;
>>> +    uint32_t position_x_in_micrometers;
>>> +    uint32_t position_y_in_micrometers;
>>> +    uint32_t position_z_in_micrometers;
>>> +    uint32_t update_latency_in_microseconds;
>>> +    uint32_t lamp_purpose;
>>> +    uint8_t red_level_count;
>>> +    uint8_t green_level_count;
>>> +    uint8_t blue_level_count;
>>> +    uint8_t intensity_level_count;
>>> +    uint8_t is_programmable;
>>> +    uint8_t input_binding;
>>> +};
>>> +
>>> +static int handle_lamp_attributes_response_report(struct hid_device 
>>> *hdev,
>>> +                          struct lamp_attributes_response_report_t 
>>> *rep)
>>> +{
>>> +    struct driver_data_t *driver_data = hdev->driver_data;
>>> +    uint16_t lamp_id = driver_data->next_lamp_id;
>>> +    const uint8_t *kbl_mapping;
>>> +    const uint32_t *kbl_mapping_pos_x, *kbl_mapping_pos_y, 
>>> *kbl_mapping_pos_z;
>>> +
>>> +    rep->lamp_id = lamp_id;
>>> +    // Some guessed value for latency microseconds
>>> +    rep->update_latency_in_microseconds = 100;
>>> +     // LampPurposeControl, see "26.3.1 LampPurposes Flags" of "HID 
>>> Usage Tables v1.5"
>>> +    rep->lamp_purpose = 1;
>>> +    rep->red_level_count = 0xff;
>>> +    rep->green_level_count = 0xff;
>>> +    rep->blue_level_count = 0xff;
>>> +    rep->intensity_level_count = 0xff;
>>> +    rep->is_programmable = 1;
>>> +
>>> +    if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) {
>>> +        kbl_mapping = &sirius_16_ansii_kbl_mapping[0];
>>> +        kbl_mapping_pos_x = &sirius_16_ansii_kbl_mapping_pos_x[0];
>>> +        kbl_mapping_pos_y = &sirius_16_ansii_kbl_mapping_pos_y[0];
>>> +        kbl_mapping_pos_z = &sirius_16_ansii_kbl_mapping_pos_z[0];
>>> +    } else if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) {
>>> +        kbl_mapping = &sirius_16_iso_kbl_mapping[0];
>>> +        kbl_mapping_pos_x = &sirius_16_iso_kbl_mapping_pos_x[0];
>>> +        kbl_mapping_pos_y = &sirius_16_iso_kbl_mapping_pos_y[0];
>>> +        kbl_mapping_pos_z = &sirius_16_iso_kbl_mapping_pos_z[0];
>>> +    } else
>>> +        return -EINVAL;
>>> +
>>> +    if (kbl_mapping[lamp_id] <= 0xe8)
>>> +        rep->input_binding = kbl_mapping[lamp_id];
>>> +    else
>>> +        // Everything bigger is reserved/undefined, see "10 
>>> Keyboard/Keypad Page (0x07)" of
>>> +        // "HID Usage Tables v1.5" and should return 0, see "26.8.3 
>>> Lamp Attributes" of the
>>> +        // same document.
>>> +        rep->input_binding = 0;
>>> +    rep->position_x_in_micrometers = kbl_mapping_pos_x[lamp_id];
>>> +    rep->position_y_in_micrometers = kbl_mapping_pos_y[lamp_id];
>>> +    rep->position_z_in_micrometers = kbl_mapping_pos_z[lamp_id];
>>> +
>>> +    driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % 
>>> driver_data->lamp_count;
>>> +
>>> +    return sizeof(struct lamp_attributes_response_report_t);
>>> +}
>>> +
>>> +
>>> +#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE    BIT(0)
>>> +
>>> +struct __packed lamp_multi_update_report_t {
>>> +    const uint8_t report_id;
>>> +    uint8_t lamp_count;
>>> +    uint8_t lamp_update_flags;
>>> +    uint16_t lamp_id[8];
>>> +    struct {
>>> +        uint8_t red;
>>> +        uint8_t green;
>>> +        uint8_t blue;
>>> +        uint8_t intensity;
>>> +    } update_channels[8];
>>> +};
>>> +
>>> +static int handle_lamp_multi_update_report(struct hid_device *hdev,
>>> +                       struct lamp_multi_update_report_t *rep)
>>> +{
>>> +    int ret;
>>> +    struct driver_data_t *driver_data = hdev->driver_data;
>>> +    struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
>>> +    uint8_t lamp_count, key_id, key_id_j;
>>> +    union tuxedo_nb04_wmi_496_b_in_80_b_out_input *next =
>>> + &driver_data->next_kbl_set_multiple_keys_input;
>>> +    union tuxedo_nb04_wmi_496_b_in_80_b_out_output output;
>>> +
>>> +    // Catching missformated lamp_multi_update_report and fail 
>>> silently according to
>>> +    // "HID Usage Tables v1.5"
>>> +    for (int i = 0; i < rep->lamp_count; ++i) {
>>> +        if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
>>> +            lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>>> +        else if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
>>> +            lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>>> +
>>> +        if (rep->lamp_id[i] > lamp_count) {
>>> +            pr_debug("Out of bounds lamp_id in 
>>> lamp_multi_update_report. Skippng whole report!\n");
>>> +            return sizeof(struct lamp_multi_update_report_t);
>>> +        }
>>> +
>>> +        for (int j = i + 1; j < rep->lamp_count; ++j) {
>>> +            if (rep->lamp_id[i] == rep->lamp_id[j]) {
>>> +                pr_debug("Duplicate lamp_id in 
>>> lamp_multi_update_report. Skippng whole report!\n");
>>> +                return sizeof(struct lamp_multi_update_report_t);
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    for (int i = 0; i < rep->lamp_count; ++i) {
>>> +        if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
>>> +            key_id = sirius_16_ansii_kbl_mapping[rep->lamp_id[i]];
>>> +        else if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
>>> +            key_id = sirius_16_iso_kbl_mapping[rep->lamp_id[i]];
>>> +
>>> +        for (int j = 0; j < 
>>> WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; ++j) {
>>> +            key_id_j = 
>>> next->kbl_set_multiple_keys_input.lighting_settings[j].key_id;
>>> +            if (key_id_j == 0x00 || key_id_j == key_id) {
>>> +                if (key_id_j == 0x00)
>>> + next->kbl_set_multiple_keys_input.lighting_setting_count =
>>> +                        j + 1;
>>> + next->kbl_set_multiple_keys_input.lighting_settings[j].key_id =
>>> +                    key_id;
>>> +                // While this driver respects
>>> +                // intensity_update_channel according to "HID
>>> +                // Usage Tables v1.5" also on RGB leds, the
>>> +                // Microsoft MacroPad reference implementation
>>> +                // 
>>> (https://github.com/microsoft/RP2040MacropadHidSample
>>> +                // 1d6c3ad) does not and ignores it. If it turns
>>> +                // out that Windows writes intensity = 0 for RGB
>>> +                // leds instead of intensity = 255, this driver
>>> +                // should also irgnore the
>>> +                // intensity_update_channel.
>>> + next->kbl_set_multiple_keys_input.lighting_settings[j].red =
>>> +                    rep->update_channels[i].red
>>> +                        * rep->update_channels[i].intensity / 0xff;
>>> + next->kbl_set_multiple_keys_input.lighting_settings[j].green =
>>> +                    rep->update_channels[i].green
>>> +                        * rep->update_channels[i].intensity / 0xff;
>>> + next->kbl_set_multiple_keys_input.lighting_settings[j].blue =
>>> +                    rep->update_channels[i].blue
>>> +                        * rep->update_channels[i].intensity / 0xff;
>>> +
>>> +                break;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    if (rep->lamp_update_flags & 
>>> LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) {
>>> +        ret = tuxedo_nb04_wmi_496_b_in_80_b_out(wdev, 
>>> WMI_AB_KBL_SET_MULTIPLE_KEYS, next,
>>> +                            &output);
>>> +        memset(next, 0, sizeof(union 
>>> tuxedo_nb04_wmi_496_b_in_80_b_out_input));
>>> +        if (ret)
>>> +            return ret;
>>> +    }
>>> +
>>> +    return sizeof(struct lamp_multi_update_report_t);
>>> +}
>>> +
>>> +
>>> +struct __packed lamp_range_update_report_t {
>>> +    const uint8_t report_id;
>>> +    uint8_t lamp_update_flags;
>>> +    uint16_t lamp_id_start;
>>> +    uint16_t lamp_id_end;
>>> +    uint8_t red_update_channel;
>>> +    uint8_t green_update_channel;
>>> +    uint8_t blue_update_channel;
>>> +    uint8_t intensity_update_channel;
>>> +};
>>> +
>>> +static int handle_lamp_range_update_report(struct hid_device *hdev,
>>> +                       struct lamp_range_update_report_t *report)
>>> +{
>>> +    int ret;
>>> +    struct driver_data_t *driver_data = hdev->driver_data;
>>> +    uint8_t lamp_count;
>>> +    struct lamp_multi_update_report_t lamp_multi_update_report = {
>>> +        .report_id = LAMP_MULTI_UPDATE_REPORT_ID
>>> +    };
>>> +
>>> +    // Catching missformated lamp_range_update_report and fail 
>>> silently according to
>>> +    // "HID Usage Tables v1.5"
>>> +    if (report->lamp_id_start > report->lamp_id_end) {
>>> +        pr_debug("lamp_id_start > lamp_id_end in 
>>> lamp_range_update_report. Skippng whole report!\n");
>>> +        return sizeof(struct lamp_range_update_report_t);
>>> +    }
>>> +
>>> +    if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
>>> +        lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>>> +    else if (driver_data->keyboard_type == 
>>> WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
>>> +        lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
>>> +
>>> +    if (report->lamp_id_end > lamp_count - 1) {
>>> +        pr_debug("Out of bounds lamp_id_* in 
>>> lamp_range_update_report. Skippng whole report!\n");
>>> +        return sizeof(struct lamp_range_update_report_t);
>>> +    }
>>> +
>>> +    // Break handle_lamp_range_update_report call down to multiple
>>> +    // handle_lamp_multi_update_report calls to easily ensure that 
>>> mixing
>>> +    // handle_lamp_range_update_report and 
>>> handle_lamp_multi_update_report
>>> +    // does not break things.
>>> +    for (int i = report->lamp_id_start; i < report->lamp_id_end + 
>>> 1; i = i + 8) {
>>> +        lamp_multi_update_report.lamp_count = 
>>> MIN(report->lamp_id_end + 1 - i, 8);
>>> +        if (i + lamp_multi_update_report.lamp_count == 
>>> report->lamp_id_end + 1)
>>> +            lamp_multi_update_report.lamp_update_flags |=
>>> +                LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE;
>>> +
>>> +        for (int j = 0; j < lamp_multi_update_report.lamp_count; 
>>> ++j) {
>>> +            lamp_multi_update_report.lamp_id[j] = i + j;
>>> +            lamp_multi_update_report.update_channels[j].red =
>>> +                report->red_update_channel;
>>> +            lamp_multi_update_report.update_channels[j].green =
>>> +                report->green_update_channel;
>>> +            lamp_multi_update_report.update_channels[j].blue =
>>> +                report->blue_update_channel;
>>> + lamp_multi_update_report.update_channels[j].intensity =
>>> +                report->intensity_update_channel;
>>> +        }
>>> +
>>> +        ret = handle_lamp_multi_update_report(hdev, 
>>> &lamp_multi_update_report);
>>> +        if (ret < 0)
>>> +            return ret;
>>> +        else if (ret != sizeof(struct lamp_multi_update_report_t))
>>> +            return -EIO;
>>> +    }
>>> +
>>> +    return sizeof(struct lamp_range_update_report_t);
>>> +}
>>> +
>>> +
>>> +struct __packed lamp_array_control_report_t {
>>> +    const uint8_t report_id;
>>> +    uint8_t autonomous_mode;
>>> +};
>>> +
>>> +static int handle_lamp_array_control_report(struct hid_device 
>>> __always_unused *hdev,
>>> +                        struct lamp_array_control_report_t 
>>> __always_unused *rep)
>>> +{
>>> +    // The keyboard firmware doesn't have any built in effects or 
>>> controls
>>> +    // so this is a NOOP.
>>> +    // According to the HID Documentation (HID Usage Tables v1.5) this
>>> +    // function is optional and can be removed from the HID Report
>>> +    // Descriptor, but it should first be confirmed that userspace 
>>> respects
>>> +    // this possibility too. The Microsoft MacroPad reference 
>>> implementation
>>> +    // (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
>>> +    // already deviates from the spec at another point, see
>>> +    // handle_lamp_*_update_report.
>>> +
>>> +    return sizeof(struct lamp_array_control_report_t);
>>> +}
>>> +
>>> +
>>> +static int ll_raw_request(struct hid_device *hdev, unsigned char 
>>> reportnum, __u8 *buf, size_t len,
>>> +               unsigned char rtype, int reqtype)
>>> +{
>>> +    int ret;
>>> +
>>> +    pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: 
>>> %u, len: %lu buf:\n", rtype,
>>> +         reqtype, reportnum, len);
>>> +    print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len);
>>> +
>>> +    ret = -EINVAL;
>>> +    if (rtype == HID_FEATURE_REPORT) {
>>> +        if (reqtype == HID_REQ_GET_REPORT) {
>>> +            if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID
>>> +                && len == sizeof(struct 
>>> lamp_array_attributes_report_t))
>>> +                ret = handle_lamp_array_attributes_report(
>>> +                    hdev, (struct lamp_array_attributes_report_t 
>>> *)buf);
>>> +            else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID
>>> +                && len == sizeof(struct 
>>> lamp_attributes_response_report_t))
>>> +                ret = handle_lamp_attributes_response_report(
>>> +                    hdev, (struct lamp_attributes_response_report_t 
>>> *)buf);
>>> +        } else if (reqtype == HID_REQ_SET_REPORT) {
>>> +            if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID
>>> +                && len == sizeof(struct 
>>> lamp_attributes_request_report_t))
>>> +                ret = handle_lamp_attributes_request_report(
>>> +                    hdev, (struct lamp_attributes_request_report_t 
>>> *)buf);
>>> +            else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID
>>> +                && len == sizeof(struct lamp_multi_update_report_t))
>>> +                ret = handle_lamp_multi_update_report(
>>> +                    hdev, (struct lamp_multi_update_report_t *)buf);
>>> +            else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID
>>> +                && len == sizeof(struct lamp_range_update_report_t))
>>> +                ret = handle_lamp_range_update_report(
>>> +                    hdev, (struct lamp_range_update_report_t *)buf);
>>> +            else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID
>>> +                && len == sizeof(struct lamp_array_control_report_t))
>>> +                ret = handle_lamp_array_control_report(
>>> +                    hdev, (struct lamp_array_control_report_t *)buf);
>>> +        }
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static const struct hid_ll_driver ll_driver = {
>>> +    .start = &ll_start,
>>> +    .stop = &ll_stop,
>>> +    .open = &ll_open,
>>> +    .close = &ll_close,
>>> +    .parse = &ll_parse,
>>> +    .raw_request = &ll_raw_request,
>>> +};
>>> +
>>> +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device 
>>> *wdev, struct hid_device **hdev_out)
>>> +{
>>> +    int ret;
>>> +    struct hid_device *hdev;
>>> +
>>> +    pr_debug("Adding TUXEDO NB04 Virtual LampArray device.\n");
>>> +
>>> +    hdev = hid_allocate_device();
>>> +    if (IS_ERR(hdev))
>>> +        return PTR_ERR(hdev);
>>> +    *hdev_out = hdev;
>>> +
>>> +    strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", 
>>> sizeof(hdev->name));
>>> +
>>> +    hdev->ll_driver = &ll_driver;
>>> +    hdev->bus = BUS_VIRTUAL;
>>> +    hdev->vendor = 0x21ba;
>>> +    hdev->product = 0x0400;
>>> +    hdev->dev.parent = &wdev->dev;
>>> +
>>> +    ret = hid_add_device(hdev);
>>> +    if (ret)
>>> +        hid_destroy_device(hdev);
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL(tuxedo_nb04_virtual_lamp_array_add_device);
>>> diff --git 
>>> a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>>> new file mode 100644
>>> index 0000000000000..fdc2a01d95c24
>>> --- /dev/null
>>> +++ 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
>>> @@ -0,0 +1,18 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * This code gives the built in RGB lighting of the TUXEDO NB04 
>>> devices a
>>> + * standardised interface, namely HID LampArray.
>>> + *
>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>> + */
>>> +
>>> +#ifndef TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
>>> +#define TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
>>> +
>>> +#include <linux/wmi.h>
>>> +#include <linux/hid.h>
>>> +
>>> +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev,
>>> +                          struct hid_device **hdev_out);
>>> +
>>> +#endif
>>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>>> new file mode 100644
>>> index 0000000000000..dbabdb9dd60c7
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
>>> @@ -0,0 +1,85 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * This code gives functions to avoid code duplication while 
>>> interacting with
>>> + * the TUXEDO NB04 wmi interfaces.
>>> + *
>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>> + */
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#include "tuxedo_nb04_wmi_ab_init.h"
>>> +
>>> +#include "tuxedo_nb04_wmi_util.h"
>>> +
>>> +static int __wmi_method_acpi_object_out(struct wmi_device *wdev, 
>>> uint32_t wmi_method_id,
>>> +                    uint8_t *in, acpi_size in_len, union 
>>> acpi_object **out)
>>
>> Please use size_t instead of acpi_size.
>>
>>> +{
>>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data = 
>>> wdev->dev.driver_data;
>>
>> Please use dev_get_drvdata().
> Ack and Ack -> v2
>>
>>> +    struct acpi_buffer acpi_buffer_in = { in_len, in };
>>> +    struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, 
>>> NULL };
>>> +
>>> +    pr_debug("Evaluate WMI method: %u in:\n", wmi_method_id);
>>> +    print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len);
>>
>> I do not think this is useful, please remove.
> Will do in the final release, currently it's helping me writing the 
> userspace part.

If so, please mark your patches as "RFC" if they are not considered as a potentially "final" release.
Otherwise they might get accepted with the debug printing still inside.

>>
>>> +
>>> +    mutex_lock(&driver_data->wmi_access_mutex);
>>
>> Does the underlying ACPI method really require external locking? If 
>> not, then please remove this mutex.
> Taken from the out of tree driver written by Christoffer, I will ask 
> him about this.
>>
>>> +    acpi_status status = wmidev_evaluate_method(wdev, 0, 
>>> wmi_method_id, &acpi_buffer_in,
>>> +                            &acpi_buffer_out);
>>> +    mutex_unlock(&driver_data->wmi_access_mutex);
>>> +    if (ACPI_FAILURE(status)) {
>>> +        pr_err("Failed to evaluate WMI method.\n");
>>> +        return -EIO;
>>> +    }
>>> +    if (!acpi_buffer_out.pointer) {
>>> +        pr_err("Unexpected empty out buffer.\n");
>>> +        return -ENODATA;
>>> +    }
>>
>> I believe that printing error messages should be done by the callers 
>> of this method.
>>
>>> +
>>> +    *out = acpi_buffer_out.pointer;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>> +static int __wmi_method_buffer_out(struct wmi_device *wdev, 
>>> uint32_t wmi_method_id, uint8_t *in,
>>> +                   acpi_size in_len, uint8_t *out, acpi_size out_len)
>>
>> Please use size_t instead of acpi_size.
>>
>>> +{
>>> +    int ret;
>>> +    union acpi_object *acpi_object_out = NULL;
>>
>> union acpi_object *obj;
>> int ret;
> ack ack ack
>>
>>> +
>>> +    ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, in, 
>>> in_len, &acpi_object_out);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>> +    if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
>>> +        pr_err("Unexpected out buffer type. Expected: %u Got: 
>>> %u\n", ACPI_TYPE_BUFFER,
>>> +               acpi_object_out->type);
>>> +        kfree(acpi_object_out);
>>> +        return -EIO;
>>> +    }
>>> +    if (acpi_object_out->buffer.length != out_len) {
>>
>> The Windows ACPI-WMI mappers accepts oversized buffers and ignores 
>> any additional data,
>> so please change this code to also accept oversized buffers.
> Only for input or also for output?

Only forbuffers coming from the ACPI firmware.

>>
>>> +        pr_err("Unexpected out buffer length.\n");
>>> +        kfree(acpi_object_out);
>>> +        return -EIO;
>>> +    }
>>> +
>>> +    memcpy(out, acpi_object_out->buffer.pointer, out_len);
>>> +    kfree(acpi_object_out);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
>>> +                    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods 
>>> method,
>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_input 
>>> *input,
>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_output 
>>> *output)
>>> +{
>>> +    return __wmi_method_buffer_out(wdev, method, input->raw, 8, 
>>> output->raw, 80);
>>> +}
>>> +
>>> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
>>> +                      enum 
>>> tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
>>> +                      union tuxedo_nb04_wmi_496_b_in_80_b_out_input 
>>> *input,
>>> +                      union 
>>> tuxedo_nb04_wmi_496_b_in_80_b_out_output *output)
>>> +{
>>> +    return __wmi_method_buffer_out(wdev, method, input->raw, 496, 
>>> output->raw, 80);
>>> +}
>>
>> Those two functions seem useless to me, please use 
>> wmi_method_buffer_out() directly by passing
>> a pointer to the underlying struct as data and the output of sizeof() 
>> as length.
> They are thought of bringing some type safety into the mix so that for 
> any method id the input/output size is correct.

I do not think that this brings any real benefits when it comes to type safety. Using predefined structs and sizeof()
already takes care that the buffer size is correct, and choosing the correct method id already needs to be done by
the driver itself.

>>
>>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h 
>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>>> new file mode 100644
>>> index 0000000000000..2765cbe9fcfef
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>>> @@ -0,0 +1,112 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + * This code gives functions to avoid code duplication while 
>>> interacting with
>>> + * the TUXEDO NB04 wmi interfaces.
>>> + *
>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>> + */
>>> +
>>> +#ifndef TUXEDO_NB04_WMI_UTIL_H
>>> +#define TUXEDO_NB04_WMI_UTIL_H
>>> +
>>> +#include <linux/wmi.h>
>>> +
>>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD    1
>>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD    2
>>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES    3
>>> +
>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_NONE        0
>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY    1
>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE    2
>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY    3
>>> +
>>> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII    0
>>> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO    1
>>> +
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_RED        1
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_GREEN        2
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_YELLOW    3
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_BLUE        4
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_PURPLE    5
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_INDIGO    6
>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_WHITE        7
>>> +
>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD BIT(0)
>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1)
>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_KBL        BIT(2)
>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS    BIT(3)
>>> +
>>> +
>>> +union tuxedo_nb04_wmi_8_b_in_80_b_out_input {
>>> +    uint8_t raw[8];
>>> +    struct __packed {
>>> +        uint8_t device_type;
>>> +        uint8_t reserved_0[7];
>>> +    } get_device_status_input;
>>> +};
>>> +
>>> +union tuxedo_nb04_wmi_8_b_in_80_b_out_output {
>>> +    uint8_t raw[80];
>>> +    struct __packed {
>>> +        uint16_t return_status;
>>> +        uint8_t device_enabled;
>>> +        uint8_t kbl_type;
>>> +        uint8_t kbl_side_bar_supported;
>>> +        uint8_t keyboard_physical_layout;
>>> +        uint8_t app_pages;
>>> +        uint8_t per_key_kbl_default_color;
>>> +        uint8_t four_zone_kbl_default_color_1;
>>> +        uint8_t four_zone_kbl_default_color_2;
>>> +        uint8_t four_zone_kbl_default_color_3;
>>> +        uint8_t four_zone_kbl_default_color_4;
>>> +        uint8_t light_bar_kbl_default_color;
>>> +        uint8_t reserved_0[1];
>>> +        uint16_t dedicated_gpu_id;
>>> +        uint8_t reserved_1[64];
>>> +    } get_device_status_output;
>>> +};
>>> +
>>> +enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods {
>>> +    WMI_AB_GET_DEVICE_STATUS    = 2,
>>> +};
>>> +
>>> +
>>> +#define WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX 120
>>> +
>>> +union tuxedo_nb04_wmi_496_b_in_80_b_out_input {
>>> +    uint8_t raw[496];
>>> +    struct __packed {
>>> +        uint8_t reserved_0[15];
>>> +        uint8_t lighting_setting_count;
>>> +        struct {
>>> +            uint8_t key_id;
>>> +            uint8_t red;
>>> +            uint8_t green;
>>> +            uint8_t blue;
>>> +        } 
>>> lighting_settings[WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX];
>>> +    }  kbl_set_multiple_keys_input;
>>> +};
>>> +
>>> +union tuxedo_nb04_wmi_496_b_in_80_b_out_output {
>>> +    uint8_t raw[80];
>>> +    struct __packed {
>>> +        uint8_t return_value;
>>> +        uint8_t reserved_0[79];
>>> +    } kbl_set_multiple_keys_output;
>>> +};
>>> +
>>> +enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods {
>>> +    WMI_AB_KBL_SET_MULTIPLE_KEYS    = 6,
>>> +};
>>> +
>>> +
>>> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
>>> +                    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods 
>>> method,
>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_input 
>>> *input,
>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_output 
>>> *output);
>>> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
>>> +                      enum 
>>> tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
>>> +                      union tuxedo_nb04_wmi_496_b_in_80_b_out_input 
>>> *input,
>>> +                      union 
>>> tuxedo_nb04_wmi_496_b_in_80_b_out_output *output);
>>> +
>>> +#endif
Armin Wolf Sept. 27, 2024, 5:18 p.m. UTC | #8
Am 27.09.24 um 13:24 schrieb Werner Sembach:

> Hi,
>
> an additional question below
>
> Am 27.09.24 um 08:59 schrieb Werner Sembach:
>> Hi,
>>
>> Am 26.09.24 um 20:39 schrieb Armin Wolf:
>>> Am 26.09.24 um 19:44 schrieb Werner Sembach:
>>>
>>>> [...]
>>>> +// We don't know if the WMI API is stable and how unique the GUID
>>>> is for this ODM. To be on the safe
>>>> +// side we therefore only run this driver on tested devices
>>>> defined by this list.
>>>> +static const struct dmi_system_id tested_devices_dmi_table[] = {
>>>> +    {
>>>> +        // TUXEDO Sirius 16 Gen1
>>>> +        .matches = {
>>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
>>>> +        },
>>>> +    },
>>>> +    {
>>>> +        // TUXEDO Sirius 16 Gen2
>>>> +        .matches = {
>>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
>>>> +        },
>>>> +    },
>>>> +    { }
>>>> +};
>>>> +
>>>> +static int probe(struct wmi_device *wdev, const void
>>>> __always_unused *context)
>>>> +{
>>>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data;
>>>> +
>>>> +    if (dmi_check_system(tested_devices_dmi_table))
>>>> +        return -ENODEV;
>>>
>>> Hi,
>>>
>>> please do this DMI check during module initialization. This avoids
>>> having an useless WMI driver
>>> on unsupported machines and allows for marking
>>> tested_devices_dmi_table as __initconst.
> I wonder how to do it since I don't use module_init manually but
> module_wmi_driver to register the module.

In this case you cannot use module_wmi_driver. You have to manually call wmi_driver_register()/wmi_driver_unregister()
in module_init()/module_exit().

>>>
>>> Besides that, maybe a "force" module parameter for overriding the
>>> DMI checking could be
>>> useful?
>
> Considering the bricking potential i somewhat want for people to look
> in the source first, so i would not implementen a force module parameter.
>
Ok.

> Kind regards,
>
> Werner
>
>
Pavel Machek Sept. 27, 2024, 9:01 p.m. UTC | #9
Hi!

> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
> controllable RGB keyboard backlight. The firmware API for it is implemented
> via WMI.

Ok.

> To make the backlight userspace configurable this driver emulates a
> LampArray HID device and translates the input from hidraw to the
> corresponding WMI calls. This is a new approach as the leds subsystem lacks
> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
> needs to be established.

Please don't.

a) I don't believe emulating crazy HID interface si right thing to
do. (Ton of magic constants. IIRC it stores key positions with
micrometer accuracy or something that crazy. How is userland going to
use this? Will we update micrometers for every single machine?)

Even if it is,

b) The emulation should go to generic layer, it is not specific to
your hardware.


> +
> +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
> +// side we therefore only run this driver on tested devices defined by this list.

80 columns, /* */ is usual comment style.

To illustrate my point... this is crazy:

(and would require equally crazy par in openrgb to parse).

Best regards,
								Pavel

> +
> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> +	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> +	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
> +	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
> +	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> +	0x4f,                                 0x62, 0x63, 0x58
> +};
> +
> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
> +	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
> +	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
> +	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
> +	232500, 251500, 273500,                           294500, 311200, 327900,
> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
> +	292000,                                           311200, 327900, 344600
> +};
> +
> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
> +	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
> +	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
> +	121500, 121500, 129000,                           121500, 121500, 121500,
> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
> +	147000,                                           139500, 139500, 130500
> +};
> +
> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
> +	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
> +	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
> +	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
> +	  6000,   6000,   6125,                             6000,   6000,   6000,
> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
> +	  6375,                                             6250,   6250,   6125
> +};
> +
> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> +	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> +	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
> +	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
> +	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> +	0x4f,                                 0x62, 0x63, 0x58
> +};
> +
> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
> +	218000, 234500, 251000,                           294500, 311200, 327900,
> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
> +	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
> +	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
> +	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
> +	292000,                                           311200, 327900, 344600
> +};
> +
> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
> +	 85500,  85500,  85500,                            85500,  85500,  85500,
> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
> +	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
> +	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
> +	147000,                                           139500, 139500, 130500
> +};
> +
> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
> +	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
> +	  5500,   5500,   5500,                             5500,   5500,   5500,
> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
> +	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
> +	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
> +	  6375,                                             6250,   6250,   6125
> +};

...
> +
> +static uint8_t report_descriptor[327] = {
> +	0x05, 0x59,			// Usage Page (Lighting and Illumination)
> +	0x09, 0x01,			// Usage (Lamp Array)
> +	0xa1, 0x01,			// Collection (Application)
> +	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
> +	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x03,			//   Usage (Lamp Count)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
> +	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
> +	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
> +	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
> +	0x09, 0x07,			//   Usage (Lamp Array Kind)
> +	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
> +	0x75, 0x20,			//   Report Size (32)
> +	0x95, 0x05,			//   Report Count (5)
> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
> +	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x21,			//   Usage (Lamp Id)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
> +	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x21,			//   Usage (Lamp Id)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x23,			//   Usage (Position X In Micrometers)
> +	0x09, 0x24,			//   Usage (Position Y In Micrometers)
> +	0x09, 0x25,			//   Usage (Position Z In Micrometers)
> +	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
> +	0x09, 0x26,			//   Usage (Lamp Purposes)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
> +	0x75, 0x20,			//   Report Size (32)
> +	0x95, 0x05,			//   Report Count (5)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x28,			//   Usage (Red Level Count)
> +	0x09, 0x29,			//   Usage (Green Level Count)
> +	0x09, 0x2a,			//   Usage (Blue Level Count)
> +	0x09, 0x2b,			//   Usage (Intensity Level Count)
> +	0x09, 0x2c,			//   Usage (Is Programmable)
> +	0x09, 0x2d,			//   Usage (Input Binding)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x06,			//   Report Count (6)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
> +	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x03,			//   Usage (Lamp Count)
> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x25, 0x08,			//   Logical Maximum (8)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x02,			//   Report Count (2)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x21,			//   Usage (Lamp Id)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x08,			//   Report Count (8)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x20,			//   Report Count (32)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
> +	0x09, 0x60,			//  Usage (Lamp Range Update Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x25, 0x08,			//   Logical Maximum (8)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x61,			//   Usage (Lamp Id Start)
> +	0x09, 0x62,			//   Usage (Lamp Id End)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> +	0x75, 0x10,			//   Report Size (16)
> +	0x95, 0x02,			//   Report Count (2)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0x09, 0x51,			//   Usage (Red Update Channel)
> +	0x09, 0x52,			//   Usage (Green Update Channel)
> +	0x09, 0x53,			//   Usage (Blue Update Channel)
> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x04,			//   Report Count (4)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
> +	0x09, 0x70,			//  Usage (Lamp Array Control Report)
> +	0xa1, 0x02,			//  Collection (Logical)
> +	0x09, 0x71,			//   Usage (Autonomous Mode)
> +	0x15, 0x00,			//   Logical Minimum (0)
> +	0x25, 0x01,			//   Logical Maximum (1)
> +	0x75, 0x08,			//   Report Size (8)
> +	0x95, 0x01,			//   Report Count (1)
> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> +	0xc0,				//  End Collection
> +	0xc0				// End Collection
> +};
> +
Armin Wolf Sept. 27, 2024, 10:21 p.m. UTC | #10
Am 27.09.24 um 23:01 schrieb Pavel Machek:

> Hi!
>
>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
>> controllable RGB keyboard backlight. The firmware API for it is implemented
>> via WMI.
> Ok.
>
>> To make the backlight userspace configurable this driver emulates a
>> LampArray HID device and translates the input from hidraw to the
>> corresponding WMI calls. This is a new approach as the leds subsystem lacks
>> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
>> needs to be established.
> Please don't.
>
> a) I don't believe emulating crazy HID interface si right thing to
> do. (Ton of magic constants. IIRC it stores key positions with
> micrometer accuracy or something that crazy. How is userland going to
> use this? Will we update micrometers for every single machine?)
>
> Even if it is,
>
> b) The emulation should go to generic layer, it is not specific to
> your hardware.
>
Maybe introducing a misc-device which provides an ioctl-based API similar
to the HID LampArray would be a solution?

Basically we would need:
- ioctl for querying the supported LEDs and their properties
- ioctl for enabling/disabling autonomous mode
- ioctl for updating a range of LEDs
- ioctl for updating multiple LEDs at once

If we implement this as a separate subsystem ("illumination subsystem"), then different
drivers could use this. This would also allow us to add additional ioctl calls later
for more features.

Thanks,
Armin Wolf

>> +
>> +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
>> +// side we therefore only run this driver on tested devices defined by this list.
> 80 columns, /* */ is usual comment style.
>
> To illustrate my point... this is crazy:
>
> (and would require equally crazy par in openrgb to parse).
>
> Best regards,
> 								Pavel
>
>> +
>> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
>> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>> +	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
>> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>> +	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
>> +	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
>> +	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
>> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>> +	0x4f,                                 0x62, 0x63, 0x58
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
>> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
>> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
>> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
>> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
>> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
>> +	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
>> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
>> +	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
>> +	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
>> +	232500, 251500, 273500,                           294500, 311200, 327900,
>> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
>> +	292000,                                           311200, 327900, 344600
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
>> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
>> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
>> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
>> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
>> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
>> +	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
>> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
>> +	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
>> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
>> +	121500, 121500, 129000,                           121500, 121500, 121500,
>> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
>> +	147000,                                           139500, 139500, 130500
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
>> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
>> +	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
>> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
>> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
>> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
>> +	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
>> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
>> +	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
>> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
>> +	  6000,   6000,   6125,                             6000,   6000,   6000,
>> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
>> +	  6375,                                             6250,   6250,   6125
>> +};
>> +
>> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
>> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>> +	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
>> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>> +	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
>> +	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
>> +	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
>> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>> +	0x4f,                                 0x62, 0x63, 0x58
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
>> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
>> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
>> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
>> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
>> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
>> +	218000, 234500, 251000,                           294500, 311200, 327900,
>> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
>> +	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
>> +	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
>> +	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
>> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
>> +	292000,                                           311200, 327900, 344600
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
>> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
>> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
>> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
>> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
>> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
>> +	 85500,  85500,  85500,                            85500,  85500,  85500,
>> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
>> +	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
>> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
>> +	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
>> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
>> +	147000,                                           139500, 139500, 130500
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
>> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
>> +	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
>> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
>> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
>> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
>> +	  5500,   5500,   5500,                             5500,   5500,   5500,
>> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
>> +	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
>> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
>> +	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
>> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
>> +	  6375,                                             6250,   6250,   6125
>> +};
> ...
>> +
>> +static uint8_t report_descriptor[327] = {
>> +	0x05, 0x59,			// Usage Page (Lighting and Illumination)
>> +	0x09, 0x01,			// Usage (Lamp Array)
>> +	0xa1, 0x01,			// Collection (Application)
>> +	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
>> +	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x03,			//   Usage (Lamp Count)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
>> +	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
>> +	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
>> +	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
>> +	0x09, 0x07,			//   Usage (Lamp Array Kind)
>> +	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
>> +	0x75, 0x20,			//   Report Size (32)
>> +	0x95, 0x05,			//   Report Count (5)
>> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
>> +	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x21,			//   Usage (Lamp Id)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
>> +	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x21,			//   Usage (Lamp Id)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x23,			//   Usage (Position X In Micrometers)
>> +	0x09, 0x24,			//   Usage (Position Y In Micrometers)
>> +	0x09, 0x25,			//   Usage (Position Z In Micrometers)
>> +	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
>> +	0x09, 0x26,			//   Usage (Lamp Purposes)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
>> +	0x75, 0x20,			//   Report Size (32)
>> +	0x95, 0x05,			//   Report Count (5)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x28,			//   Usage (Red Level Count)
>> +	0x09, 0x29,			//   Usage (Green Level Count)
>> +	0x09, 0x2a,			//   Usage (Blue Level Count)
>> +	0x09, 0x2b,			//   Usage (Intensity Level Count)
>> +	0x09, 0x2c,			//   Usage (Is Programmable)
>> +	0x09, 0x2d,			//   Usage (Input Binding)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x06,			//   Report Count (6)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
>> +	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x03,			//   Usage (Lamp Count)
>> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x25, 0x08,			//   Logical Maximum (8)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x02,			//   Report Count (2)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x21,			//   Usage (Lamp Id)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x08,			//   Report Count (8)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x20,			//   Report Count (32)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
>> +	0x09, 0x60,			//  Usage (Lamp Range Update Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x25, 0x08,			//   Logical Maximum (8)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x61,			//   Usage (Lamp Id Start)
>> +	0x09, 0x62,			//   Usage (Lamp Id End)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x02,			//   Report Count (2)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x04,			//   Report Count (4)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
>> +	0x09, 0x70,			//  Usage (Lamp Array Control Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x71,			//   Usage (Autonomous Mode)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x25, 0x01,			//   Logical Maximum (1)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0xc0				// End Collection
>> +};
>> +
Benjamin Tissoires Sept. 28, 2024, 7:27 a.m. UTC | #11
On Sep 28 2024, Armin Wolf wrote:
> Am 27.09.24 um 23:01 schrieb Pavel Machek:
> 
> > Hi!
> > 
> > > The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
> > > controllable RGB keyboard backlight. The firmware API for it is implemented
> > > via WMI.
> > Ok.
> > 
> > > To make the backlight userspace configurable this driver emulates a
> > > LampArray HID device and translates the input from hidraw to the
> > > corresponding WMI calls. This is a new approach as the leds subsystem lacks
> > > a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
> > > needs to be established.
> > Please don't.
> > 
> > a) I don't believe emulating crazy HID interface si right thing to
> > do. (Ton of magic constants. IIRC it stores key positions with
> > micrometer accuracy or something that crazy. How is userland going to
> > use this? Will we update micrometers for every single machine?)

This is exactly why I suggest to make use of HID-BPF. The machine
specifics is going to be controlled by userspace, leaving out the crazy
bits out of the kernel.

> > 
> > Even if it is,
> > 
> > b) The emulation should go to generic layer, it is not specific to
> > your hardware.

Well, there is not so much about an emulation here. It's a different way
of presenting the information.
But given that HID LampArray is a HID standard, userspace is able to
implement it once for all the operating systems, which is why this is so
appealing for them. For reference, we have the same issue with SDL and
Steam regarding advanced game controller: they very much prefer to
directly use HID(raw) to talk to the device instead of having a Linux
specific interface.

Also, starting with v6.12, systemd (logind) will be able to provide
hidraw node access to non root applications (in the same way you can
request an input evdev node). So HID LampArray makes a lot of sense IMO.

> > 
> Maybe introducing a misc-device which provides an ioctl-based API similar
> to the HID LampArray would be a solution?
> 
> Basically we would need:
> - ioctl for querying the supported LEDs and their properties
> - ioctl for enabling/disabling autonomous mode
> - ioctl for updating a range of LEDs
> - ioctl for updating multiple LEDs at once

You'll definitely get the API wrong at first, then you'll need to adapt
for a new device, extend it, etc... But then, you'll depend on one
userspace application that can talk to your custom ioctls, because cross
platform applications will have to implement LampArray, and they'ĺl
probably skip your custom ioctls. And once that userspace application is
gone, you'll still have to maintain this forever.

Also, the application needs to have root access to that misc device, or
you need to add extra support for it in systemd...

> 
> If we implement this as a separate subsystem ("illumination subsystem"), then different
> drivers could use this. This would also allow us to add additional ioctl calls later
> for more features.

Again, I strongly advise against this.

I'll just reiterate what makes the more sense to me:
- provide a thin wmi-to-hid layer that creates a normal regular HID
  device from your device (could be using vendor collections)
- deal with the LampArray bits in the HID stack, that we can reuse for
  other devices (I was planing on getting there for my Corsair and
  Logitech keyboads).
- Meanwhile, while prototyping the LampArray support in userspace and
  kernelspace, make use of HID-BPF to transform your vendor protocol
  into LampArray. This will allow to fix things without having to
  support them forever. This is why HID-BPF exists: so we can create
  crazy but safe kernel interfaces, without having to support them
  forever.

Cheers,
Benjamin

> 
> Thanks,
> Armin Wolf
> 
> > > +
> > > +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
> > > +// side we therefore only run this driver on tested devices defined by this list.
> > 80 columns, /* */ is usual comment style.
> > 
> > To illustrate my point... this is crazy:
> > 
> > (and would require equally crazy par in openrgb to parse).
> > 
> > Best regards,
> > 								Pavel
> > 
> > > +
> > > +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
> > > +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> > > +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> > > +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > > +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> > > +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> > > +	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
> > > +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> > > +	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
> > > +	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
> > > +	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
> > > +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> > > +	0x4f,                                 0x62, 0x63, 0x58
> > > +};
> > > +
> > > +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
> > > +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
> > > +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
> > > +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
> > > +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
> > > +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
> > > +	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
> > > +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
> > > +	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
> > > +	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
> > > +	232500, 251500, 273500,                           294500, 311200, 327900,
> > > +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
> > > +	292000,                                           311200, 327900, 344600
> > > +};
> > > +
> > > +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
> > > +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
> > > +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
> > > +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
> > > +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
> > > +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
> > > +	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
> > > +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
> > > +	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
> > > +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
> > > +	121500, 121500, 129000,                           121500, 121500, 121500,
> > > +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
> > > +	147000,                                           139500, 139500, 130500
> > > +};
> > > +
> > > +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
> > > +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
> > > +	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
> > > +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
> > > +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
> > > +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
> > > +	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
> > > +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
> > > +	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
> > > +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
> > > +	  6000,   6000,   6125,                             6000,   6000,   6000,
> > > +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
> > > +	  6375,                                             6250,   6250,   6125
> > > +};
> > > +
> > > +static const uint8_t sirius_16_iso_kbl_mapping[] = {
> > > +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> > > +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> > > +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > > +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> > > +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> > > +	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
> > > +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> > > +	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
> > > +	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
> > > +	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
> > > +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> > > +	0x4f,                                 0x62, 0x63, 0x58
> > > +};
> > > +
> > > +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
> > > +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
> > > +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
> > > +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
> > > +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
> > > +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
> > > +	218000, 234500, 251000,                           294500, 311200, 327900,
> > > +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
> > > +	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
> > > +	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
> > > +	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
> > > +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
> > > +	292000,                                           311200, 327900, 344600
> > > +};
> > > +
> > > +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
> > > +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
> > > +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
> > > +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
> > > +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
> > > +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
> > > +	 85500,  85500,  85500,                            85500,  85500,  85500,
> > > +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
> > > +	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
> > > +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
> > > +	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
> > > +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
> > > +	147000,                                           139500, 139500, 130500
> > > +};
> > > +
> > > +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
> > > +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
> > > +	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
> > > +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
> > > +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
> > > +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
> > > +	  5500,   5500,   5500,                             5500,   5500,   5500,
> > > +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
> > > +	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
> > > +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
> > > +	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
> > > +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
> > > +	  6375,                                             6250,   6250,   6125
> > > +};
> > ...
> > > +
> > > +static uint8_t report_descriptor[327] = {
> > > +	0x05, 0x59,			// Usage Page (Lighting and Illumination)
> > > +	0x09, 0x01,			// Usage (Lamp Array)
> > > +	0xa1, 0x01,			// Collection (Application)
> > > +	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
> > > +	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
> > > +	0xa1, 0x02,			//  Collection (Logical)
> > > +	0x09, 0x03,			//   Usage (Lamp Count)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> > > +	0x75, 0x10,			//   Report Size (16)
> > > +	0x95, 0x01,			//   Report Count (1)
> > > +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
> > > +	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
> > > +	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
> > > +	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
> > > +	0x09, 0x07,			//   Usage (Lamp Array Kind)
> > > +	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
> > > +	0x75, 0x20,			//   Report Size (32)
> > > +	0x95, 0x05,			//   Report Count (5)
> > > +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
> > > +	0xc0,				//  End Collection
> > > +	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
> > > +	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
> > > +	0xa1, 0x02,			//  Collection (Logical)
> > > +	0x09, 0x21,			//   Usage (Lamp Id)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> > > +	0x75, 0x10,			//   Report Size (16)
> > > +	0x95, 0x01,			//   Report Count (1)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0xc0,				//  End Collection
> > > +	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
> > > +	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
> > > +	0xa1, 0x02,			//  Collection (Logical)
> > > +	0x09, 0x21,			//   Usage (Lamp Id)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> > > +	0x75, 0x10,			//   Report Size (16)
> > > +	0x95, 0x01,			//   Report Count (1)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0x09, 0x23,			//   Usage (Position X In Micrometers)
> > > +	0x09, 0x24,			//   Usage (Position Y In Micrometers)
> > > +	0x09, 0x25,			//   Usage (Position Z In Micrometers)
> > > +	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
> > > +	0x09, 0x26,			//   Usage (Lamp Purposes)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
> > > +	0x75, 0x20,			//   Report Size (32)
> > > +	0x95, 0x05,			//   Report Count (5)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0x09, 0x28,			//   Usage (Red Level Count)
> > > +	0x09, 0x29,			//   Usage (Green Level Count)
> > > +	0x09, 0x2a,			//   Usage (Blue Level Count)
> > > +	0x09, 0x2b,			//   Usage (Intensity Level Count)
> > > +	0x09, 0x2c,			//   Usage (Is Programmable)
> > > +	0x09, 0x2d,			//   Usage (Input Binding)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> > > +	0x75, 0x08,			//   Report Size (8)
> > > +	0x95, 0x06,			//   Report Count (6)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0xc0,				//  End Collection
> > > +	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
> > > +	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
> > > +	0xa1, 0x02,			//  Collection (Logical)
> > > +	0x09, 0x03,			//   Usage (Lamp Count)
> > > +	0x09, 0x55,			//   Usage (Lamp Update Flags)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x25, 0x08,			//   Logical Maximum (8)
> > > +	0x75, 0x08,			//   Report Size (8)
> > > +	0x95, 0x02,			//   Report Count (2)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0x09, 0x21,			//   Usage (Lamp Id)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> > > +	0x75, 0x10,			//   Report Size (16)
> > > +	0x95, 0x08,			//   Report Count (8)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> > > +	0x75, 0x08,			//   Report Size (8)
> > > +	0x95, 0x20,			//   Report Count (32)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0xc0,				//  End Collection
> > > +	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
> > > +	0x09, 0x60,			//  Usage (Lamp Range Update Report)
> > > +	0xa1, 0x02,			//  Collection (Logical)
> > > +	0x09, 0x55,			//   Usage (Lamp Update Flags)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x25, 0x08,			//   Logical Maximum (8)
> > > +	0x75, 0x08,			//   Report Size (8)
> > > +	0x95, 0x01,			//   Report Count (1)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0x09, 0x61,			//   Usage (Lamp Id Start)
> > > +	0x09, 0x62,			//   Usage (Lamp Id End)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
> > > +	0x75, 0x10,			//   Report Size (16)
> > > +	0x95, 0x02,			//   Report Count (2)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0x09, 0x51,			//   Usage (Red Update Channel)
> > > +	0x09, 0x52,			//   Usage (Green Update Channel)
> > > +	0x09, 0x53,			//   Usage (Blue Update Channel)
> > > +	0x09, 0x54,			//   Usage (Intensity Update Channel)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
> > > +	0x75, 0x08,			//   Report Size (8)
> > > +	0x95, 0x04,			//   Report Count (4)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0xc0,				//  End Collection
> > > +	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
> > > +	0x09, 0x70,			//  Usage (Lamp Array Control Report)
> > > +	0xa1, 0x02,			//  Collection (Logical)
> > > +	0x09, 0x71,			//   Usage (Autonomous Mode)
> > > +	0x15, 0x00,			//   Logical Minimum (0)
> > > +	0x25, 0x01,			//   Logical Maximum (1)
> > > +	0x75, 0x08,			//   Report Size (8)
> > > +	0x95, 0x01,			//   Report Count (1)
> > > +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
> > > +	0xc0,				//  End Collection
> > > +	0xc0				// End Collection
> > > +};
> > > +
Werner Sembach Sept. 28, 2024, 7:36 a.m. UTC | #12
Hi Armin,

Am 27.09.24 um 19:15 schrieb Armin Wolf:
> [...]
> If so, please mark your patches as "RFC" if they are not considered as 
> a potentially "final" release.
> Otherwise they might get accepted with the debug printing still inside.
Talking about noob mistakes ... I'm sorry, will do this with the next patch.
>
>>>
>>>> +
>>>> +    mutex_lock(&driver_data->wmi_access_mutex);
>>>
>>> Does the underlying ACPI method really require external locking? If 
>>> not, then please remove this mutex.
>> Taken from the out of tree driver written by Christoffer, I will ask 
>> him about this.
>>>
>>>> +    acpi_status status = wmidev_evaluate_method(wdev, 0, 
>>>> wmi_method_id, &acpi_buffer_in,
>>>> +                            &acpi_buffer_out);
>>>> +    mutex_unlock(&driver_data->wmi_access_mutex);
>>>> +    if (ACPI_FAILURE(status)) {
>>>> +        pr_err("Failed to evaluate WMI method.\n");
>>>> +        return -EIO;
>>>> +    }
>>>> +    if (!acpi_buffer_out.pointer) {
>>>> +        pr_err("Unexpected empty out buffer.\n");
>>>> +        return -ENODATA;
>>>> +    }
>>>
>>> I believe that printing error messages should be done by the callers 
>>> of this method.
>>>
>>>> +
>>>> +    *out = acpi_buffer_out.pointer;
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +
>>>> +static int __wmi_method_buffer_out(struct wmi_device *wdev, 
>>>> uint32_t wmi_method_id, uint8_t *in,
>>>> +                   acpi_size in_len, uint8_t *out, acpi_size out_len)
>>>
>>> Please use size_t instead of acpi_size.
>>>
>>>> +{
>>>> +    int ret;
>>>> +    union acpi_object *acpi_object_out = NULL;
>>>
>>> union acpi_object *obj;
>>> int ret;
>> ack ack ack
>>>
>>>> +
>>>> +    ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, in, 
>>>> in_len, &acpi_object_out);
>>>> +    if (ret)
>>>> +        return ret;
>>>> +
>>>> +    if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
>>>> +        pr_err("Unexpected out buffer type. Expected: %u Got: 
>>>> %u\n", ACPI_TYPE_BUFFER,
>>>> +               acpi_object_out->type);
>>>> +        kfree(acpi_object_out);
>>>> +        return -EIO;
>>>> +    }
>>>> +    if (acpi_object_out->buffer.length != out_len) {
>>>
>>> The Windows ACPI-WMI mappers accepts oversized buffers and ignores 
>>> any additional data,
>>> so please change this code to also accept oversized buffers.
>> Only for input or also for output?
>
> Only forbuffers coming from the ACPI firmware.
ack
>
>>>
>>>> +        pr_err("Unexpected out buffer length.\n");
>>>> +        kfree(acpi_object_out);
>>>> +        return -EIO;
>>>> +    }
>>>> +
>>>> +    memcpy(out, acpi_object_out->buffer.pointer, out_len);
>>>> +    kfree(acpi_object_out);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
>>>> +                    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods 
>>>> method,
>>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_input 
>>>> *input,
>>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_output 
>>>> *output)
>>>> +{
>>>> +    return __wmi_method_buffer_out(wdev, method, input->raw, 8, 
>>>> output->raw, 80);
>>>> +}
>>>> +
>>>> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
>>>> +                      enum 
>>>> tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
>>>> +                      union 
>>>> tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
>>>> +                      union 
>>>> tuxedo_nb04_wmi_496_b_in_80_b_out_output *output)
>>>> +{
>>>> +    return __wmi_method_buffer_out(wdev, method, input->raw, 496, 
>>>> output->raw, 80);
>>>> +}
>>>
>>> Those two functions seem useless to me, please use 
>>> wmi_method_buffer_out() directly by passing
>>> a pointer to the underlying struct as data and the output of 
>>> sizeof() as length.
>> They are thought of bringing some type safety into the mix so that 
>> for any method id the input/output size is correct.
>
> I do not think that this brings any real benefits when it comes to 
> type safety. Using predefined structs and sizeof()
> already takes care that the buffer size is correct, and choosing the 
> correct method id already needs to be done by
> the driver itself.
ack
>
>>>
>>>> diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h 
>>>> b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>>>> new file mode 100644
>>>> index 0000000000000..2765cbe9fcfef
>>>> --- /dev/null
>>>> +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
>>>> @@ -0,0 +1,112 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +/*
>>>> + * This code gives functions to avoid code duplication while 
>>>> interacting with
>>>> + * the TUXEDO NB04 wmi interfaces.
>>>> + *
>>>> + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
>>>> + */
>>>> +
>>>> +#ifndef TUXEDO_NB04_WMI_UTIL_H
>>>> +#define TUXEDO_NB04_WMI_UTIL_H
>>>> +
>>>> +#include <linux/wmi.h>
>>>> +
>>>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD    1
>>>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD    2
>>>> +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES    3
>>>> +
>>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_NONE        0
>>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY    1
>>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE    2
>>>> +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY    3
>>>> +
>>>> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII    0
>>>> +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO    1
>>>> +
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_RED        1
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_GREEN        2
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_YELLOW    3
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_BLUE        4
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_PURPLE    5
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_INDIGO    6
>>>> +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_WHITE        7
>>>> +
>>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD BIT(0)
>>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1)
>>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_KBL BIT(2)
>>>> +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS BIT(3)
>>>> +
>>>> +
>>>> +union tuxedo_nb04_wmi_8_b_in_80_b_out_input {
>>>> +    uint8_t raw[8];
>>>> +    struct __packed {
>>>> +        uint8_t device_type;
>>>> +        uint8_t reserved_0[7];
>>>> +    } get_device_status_input;
>>>> +};
>>>> +
>>>> +union tuxedo_nb04_wmi_8_b_in_80_b_out_output {
>>>> +    uint8_t raw[80];
>>>> +    struct __packed {
>>>> +        uint16_t return_status;
>>>> +        uint8_t device_enabled;
>>>> +        uint8_t kbl_type;
>>>> +        uint8_t kbl_side_bar_supported;
>>>> +        uint8_t keyboard_physical_layout;
>>>> +        uint8_t app_pages;
>>>> +        uint8_t per_key_kbl_default_color;
>>>> +        uint8_t four_zone_kbl_default_color_1;
>>>> +        uint8_t four_zone_kbl_default_color_2;
>>>> +        uint8_t four_zone_kbl_default_color_3;
>>>> +        uint8_t four_zone_kbl_default_color_4;
>>>> +        uint8_t light_bar_kbl_default_color;
>>>> +        uint8_t reserved_0[1];
>>>> +        uint16_t dedicated_gpu_id;
>>>> +        uint8_t reserved_1[64];
>>>> +    } get_device_status_output;
>>>> +};
>>>> +
>>>> +enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods {
>>>> +    WMI_AB_GET_DEVICE_STATUS    = 2,
>>>> +};
>>>> +
>>>> +
>>>> +#define WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX 120
>>>> +
>>>> +union tuxedo_nb04_wmi_496_b_in_80_b_out_input {
>>>> +    uint8_t raw[496];
>>>> +    struct __packed {
>>>> +        uint8_t reserved_0[15];
>>>> +        uint8_t lighting_setting_count;
>>>> +        struct {
>>>> +            uint8_t key_id;
>>>> +            uint8_t red;
>>>> +            uint8_t green;
>>>> +            uint8_t blue;
>>>> +        } 
>>>> lighting_settings[WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX];
>>>> +    }  kbl_set_multiple_keys_input;
>>>> +};
>>>> +
>>>> +union tuxedo_nb04_wmi_496_b_in_80_b_out_output {
>>>> +    uint8_t raw[80];
>>>> +    struct __packed {
>>>> +        uint8_t return_value;
>>>> +        uint8_t reserved_0[79];
>>>> +    } kbl_set_multiple_keys_output;
>>>> +};
>>>> +
>>>> +enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods {
>>>> +    WMI_AB_KBL_SET_MULTIPLE_KEYS    = 6,
>>>> +};
>>>> +
>>>> +
>>>> +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
>>>> +                    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods 
>>>> method,
>>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_input 
>>>> *input,
>>>> +                    union tuxedo_nb04_wmi_8_b_in_80_b_out_output 
>>>> *output);
>>>> +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
>>>> +                      enum 
>>>> tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
>>>> +                      union 
>>>> tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
>>>> +                      union 
>>>> tuxedo_nb04_wmi_496_b_in_80_b_out_output *output);
>>>> +
>>>> +#endif
Werner Sembach Sept. 28, 2024, 7:40 a.m. UTC | #13
Hi,

Am 27.09.24 um 19:18 schrieb Armin Wolf:
> Am 27.09.24 um 13:24 schrieb Werner Sembach:
>
>> Hi,
>>
>> an additional question below
>>
>> Am 27.09.24 um 08:59 schrieb Werner Sembach:
>>> Hi,
>>>
>>> Am 26.09.24 um 20:39 schrieb Armin Wolf:
>>>> Am 26.09.24 um 19:44 schrieb Werner Sembach:
>>>>
>>>>> [...]
>>>>> +// We don't know if the WMI API is stable and how unique the GUID
>>>>> is for this ODM. To be on the safe
>>>>> +// side we therefore only run this driver on tested devices
>>>>> defined by this list.
>>>>> +static const struct dmi_system_id tested_devices_dmi_table[] = {
>>>>> +    {
>>>>> +        // TUXEDO Sirius 16 Gen1
>>>>> +        .matches = {
>>>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
>>>>> +        },
>>>>> +    },
>>>>> +    {
>>>>> +        // TUXEDO Sirius 16 Gen2
>>>>> +        .matches = {
>>>>> +            DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
>>>>> +            DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
>>>>> +        },
>>>>> +    },
>>>>> +    { }
>>>>> +};
>>>>> +
>>>>> +static int probe(struct wmi_device *wdev, const void
>>>>> __always_unused *context)
>>>>> +{
>>>>> +    struct tuxedo_nb04_wmi_driver_data_t *driver_data;
>>>>> +
>>>>> +    if (dmi_check_system(tested_devices_dmi_table))
>>>>> +        return -ENODEV;
>>>>
>>>> Hi,
>>>>
>>>> please do this DMI check during module initialization. This avoids
>>>> having an useless WMI driver
>>>> on unsupported machines and allows for marking
>>>> tested_devices_dmi_table as __initconst.
>> I wonder how to do it since I don't use module_init manually but
>> module_wmi_driver to register the module.
>
> In this case you cannot use module_wmi_driver. You have to manually 
> call wmi_driver_register()/wmi_driver_unregister()
> in module_init()/module_exit().
ack
>
>>>>
>>>> Besides that, maybe a "force" module parameter for overriding the
>>>> DMI checking could be
>>>> useful?
>>
>> Considering the bricking potential i somewhat want for people to look
>> in the source first, so i would not implementen a force module 
>> parameter.
>>
> Ok.
>
>> Kind regards,
>>
>> Werner
>>
>>
Werner Sembach Sept. 28, 2024, 7:55 a.m. UTC | #14
Hi Pavel,

Am 27.09.24 um 23:01 schrieb Pavel Machek:
> Hi!
>
>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
>> controllable RGB keyboard backlight. The firmware API for it is implemented
>> via WMI.
> Ok.
>
>> To make the backlight userspace configurable this driver emulates a
>> LampArray HID device and translates the input from hidraw to the
>> corresponding WMI calls. This is a new approach as the leds subsystem lacks
>> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
>> needs to be established.
> Please don't.
>
> a) I don't believe emulating crazy HID interface si right thing to
> do. (Ton of magic constants. IIRC it stores key positions with
> micrometer accuracy or something that crazy. How is userland going to
> use this? Will we update micrometers for every single machine?)
While the standard allows to go down to that preccission I don't think 
it's neccessary to actually be that precise, for example I only whent 
down to 0.5mm precission because all i did was using a ruler on my test 
device and I think this is fine.
>
> Even if it is,
>
> b) The emulation should go to generic layer, it is not specific to
> your hardware.

You are right, with time it might show that there are some common 
patterns that might form a new subsystem so that a 
register_virtual_lamp_array() function becomes in handy.

But with this being the first and currently only device doing it this 
way i'm not confident in designing such a system to match every hardware 
needs, that is why it's currently a device specific implementation.

>
>
>> +
>> +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
>> +// side we therefore only run this driver on tested devices defined by this list.
> 80 columns, /* */ is usual comment style.
Checkpatch wasn't complaining, but whill change that in the next version.
>
> To illustrate my point... this is crazy:
>
> (and would require equally crazy par in openrgb to parse).

This is the standard for new RGB hardware going forward. I didn't decide 
it, but some conglomerate from microsoft and gaming gear manufacturers 
probably.

So OpenRGB has to implement it and already started to work on it: 
https://gitlab.com/CalcProgrammer1/OpenRGB/-/merge_requests/2348

Kind regards,

Werner

>
> Best regards,
> 								Pavel
>
>> +
>> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
>> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>> +	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
>> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>> +	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
>> +	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
>> +	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
>> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>> +	0x4f,                                 0x62, 0x63, 0x58
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
>> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
>> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
>> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
>> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
>> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
>> +	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
>> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
>> +	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
>> +	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
>> +	232500, 251500, 273500,                           294500, 311200, 327900,
>> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
>> +	292000,                                           311200, 327900, 344600
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
>> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
>> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
>> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
>> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
>> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
>> +	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
>> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
>> +	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
>> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
>> +	121500, 121500, 129000,                           121500, 121500, 121500,
>> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
>> +	147000,                                           139500, 139500, 130500
>> +};
>> +
>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
>> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
>> +	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
>> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
>> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
>> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
>> +	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
>> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
>> +	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
>> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
>> +	  6000,   6000,   6125,                             6000,   6000,   6000,
>> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
>> +	  6375,                                             6250,   6250,   6125
>> +};
>> +
>> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
>> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>> +	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
>> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>> +	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
>> +	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
>> +	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
>> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>> +	0x4f,                                 0x62, 0x63, 0x58
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
>> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
>> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
>> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
>> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
>> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
>> +	218000, 234500, 251000,                           294500, 311200, 327900,
>> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
>> +	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
>> +	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
>> +	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
>> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
>> +	292000,                                           311200, 327900, 344600
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
>> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
>> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
>> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
>> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
>> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
>> +	 85500,  85500,  85500,                            85500,  85500,  85500,
>> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
>> +	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
>> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
>> +	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
>> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
>> +	147000,                                           139500, 139500, 130500
>> +};
>> +
>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
>> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
>> +	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
>> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
>> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
>> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
>> +	  5500,   5500,   5500,                             5500,   5500,   5500,
>> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
>> +	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
>> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
>> +	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
>> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
>> +	  6375,                                             6250,   6250,   6125
>> +};
> ...
>> +
>> +static uint8_t report_descriptor[327] = {
>> +	0x05, 0x59,			// Usage Page (Lighting and Illumination)
>> +	0x09, 0x01,			// Usage (Lamp Array)
>> +	0xa1, 0x01,			// Collection (Application)
>> +	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
>> +	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x03,			//   Usage (Lamp Count)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
>> +	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
>> +	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
>> +	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
>> +	0x09, 0x07,			//   Usage (Lamp Array Kind)
>> +	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
>> +	0x75, 0x20,			//   Report Size (32)
>> +	0x95, 0x05,			//   Report Count (5)
>> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
>> +	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x21,			//   Usage (Lamp Id)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
>> +	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x21,			//   Usage (Lamp Id)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x23,			//   Usage (Position X In Micrometers)
>> +	0x09, 0x24,			//   Usage (Position Y In Micrometers)
>> +	0x09, 0x25,			//   Usage (Position Z In Micrometers)
>> +	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
>> +	0x09, 0x26,			//   Usage (Lamp Purposes)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
>> +	0x75, 0x20,			//   Report Size (32)
>> +	0x95, 0x05,			//   Report Count (5)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x28,			//   Usage (Red Level Count)
>> +	0x09, 0x29,			//   Usage (Green Level Count)
>> +	0x09, 0x2a,			//   Usage (Blue Level Count)
>> +	0x09, 0x2b,			//   Usage (Intensity Level Count)
>> +	0x09, 0x2c,			//   Usage (Is Programmable)
>> +	0x09, 0x2d,			//   Usage (Input Binding)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x06,			//   Report Count (6)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
>> +	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x03,			//   Usage (Lamp Count)
>> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x25, 0x08,			//   Logical Maximum (8)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x02,			//   Report Count (2)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x21,			//   Usage (Lamp Id)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x08,			//   Report Count (8)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x20,			//   Report Count (32)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
>> +	0x09, 0x60,			//  Usage (Lamp Range Update Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x25, 0x08,			//   Logical Maximum (8)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x61,			//   Usage (Lamp Id Start)
>> +	0x09, 0x62,			//   Usage (Lamp Id End)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>> +	0x75, 0x10,			//   Report Size (16)
>> +	0x95, 0x02,			//   Report Count (2)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x04,			//   Report Count (4)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
>> +	0x09, 0x70,			//  Usage (Lamp Array Control Report)
>> +	0xa1, 0x02,			//  Collection (Logical)
>> +	0x09, 0x71,			//   Usage (Autonomous Mode)
>> +	0x15, 0x00,			//   Logical Minimum (0)
>> +	0x25, 0x01,			//   Logical Maximum (1)
>> +	0x75, 0x08,			//   Report Size (8)
>> +	0x95, 0x01,			//   Report Count (1)
>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>> +	0xc0,				//  End Collection
>> +	0xc0				// End Collection
>> +};
>> +
Werner Sembach Sept. 28, 2024, 8:09 a.m. UTC | #15
Hi,

Am 28.09.24 um 00:21 schrieb Armin Wolf:
> Am 27.09.24 um 23:01 schrieb Pavel Machek:
>
>> Hi!
>>
>>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a 
>>> per-key
>>> controllable RGB keyboard backlight. The firmware API for it is 
>>> implemented
>>> via WMI.
>> Ok.
>>
>>> To make the backlight userspace configurable this driver emulates a
>>> LampArray HID device and translates the input from hidraw to the
>>> corresponding WMI calls. This is a new approach as the leds 
>>> subsystem lacks
>>> a suitable UAPI for per-key keyboard backlights, and like this no 
>>> new UAPI
>>> needs to be established.
>> Please don't.
>>
>> a) I don't believe emulating crazy HID interface si right thing to
>> do. (Ton of magic constants. IIRC it stores key positions with
>> micrometer accuracy or something that crazy. How is userland going to
>> use this? Will we update micrometers for every single machine?)
>>
>> Even if it is,
>>
>> b) The emulation should go to generic layer, it is not specific to
>> your hardware.
>>
> Maybe introducing a misc-device which provides an ioctl-based API similar
> to the HID LampArray would be a solution?
>
> Basically we would need:
> - ioctl for querying the supported LEDs and their properties
> - ioctl for enabling/disabling autonomous mode
> - ioctl for updating a range of LEDs
> - ioctl for updating multiple LEDs at once
>
> If we implement this as a separate subsystem ("illumination 
> subsystem"), then different
> drivers could use this. This would also allow us to add additional 
> ioctl calls later
> for more features.

We went over this in the past discussion, the conclusion was iirc that 
we are just wraping hidraw ioctls in other ioctls with no added benefit.

For reference 
https://lore.kernel.org/all/20231011190017.1230898-1-wse@tuxedocomputers.com/

I don't know the exact message anymore, but if relevant I can dig for it 
(it's a over 5 month long e-mail thread).

And we would need to write code to apply this wrapper to devices 
implementing LampArray in firmware.

Regards,

Werner

>
> Thanks,
> Armin Wolf
>
>>> +
>>> +// We don't know if the WMI API is stable and how unique the GUID 
>>> is for this ODM. To be on the safe
>>> +// side we therefore only run this driver on tested devices defined 
>>> by this list.
>> 80 columns, /* */ is usual comment style.
>>
>> To illustrate my point... this is crazy:
>>
>> (and would require equally crazy par in openrgb to parse).
>>
>> Best regards,
>>                                 Pavel
>>
>>> +
>>> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
>>> +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>>> +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>>> +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>>> +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>>> +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>>> +    0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
>>> +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>>> +    0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
>>> +    0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
>>> +    0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
>>> +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>>> +    0x4f,                                 0x62, 0x63, 0x58
>>> +};
>>> +
>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
>>> +     25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 
>>> 158600, 175300,
>>> +    192000, 208700, 225400, 242100, 258800, 275500,   294500, 
>>> 311200, 327900, 344600,
>>> +     24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 
>>> 172000, 190500,
>>> +    209000, 227500, 246000, 269500,                   294500, 
>>> 311200, 327900, 344600,
>>> +     31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 
>>> 181000, 199500,
>>> +    218000, 236500, 255000, 273500,                   294500, 
>>> 311200, 327900,
>>> +     33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 
>>> 186500, 205000,
>>> +    223500, 242000, 267500,                           294500, 
>>> 311200, 327900, 344600,
>>> +     37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 
>>> 195500, 214000,
>>> +    232500, 251500, 273500,                           294500, 
>>> 311200, 327900,
>>> +     28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 
>>> 255000, 273500,
>>> +    292000,                                           311200, 
>>> 327900, 344600
>>> +};
>>> +
>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
>>> +     53000,  53000,  53000,  53000,  53000,  53000,  53000, 53000,  
>>> 53000,  53000,
>>> +     53000,  53000,  53000,  53000,  53000,  53000, 53000,  53000,  
>>> 53000,  53000,
>>> +     67500,  67500,  67500,  67500,  67500,  67500,  67500, 67500,  
>>> 67500,  67500,
>>> +     67500,  67500,  67500,  67500, 67500,  67500,  67500,  67500,
>>> +     85500,  85500,  85500,  85500,  85500,  85500,  85500, 85500,  
>>> 85500,  85500,
>>> +     85500,  85500,  85500,  85500, 85500,  85500,  85500,
>>> +    103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 
>>> 103500, 103500,
>>> +    103500, 103500, 103500,                           103500, 
>>> 103500, 103500,  94500,
>>> +    121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 
>>> 121500, 121500,
>>> +    121500, 121500, 129000,                           121500, 
>>> 121500, 121500,
>>> +    139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 
>>> 147000, 147000,
>>> +    147000,                                           139500, 
>>> 139500, 130500
>>> +};
>>> +
>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
>>> +      5000,   5000,   5000,   5000,   5000,   5000,   5000, 5000,   
>>> 5000,   5000,
>>> +      5000,   5000,   5000,   5000,   5000,   5000, 5000,   5000,   
>>> 5000,   5000,
>>> +      5250,   5250,   5250,   5250,   5250,   5250,   5250, 5250,   
>>> 5250,   5250,
>>> +      5250,   5250,   5250,   5250, 5250,   5250,   5250,   5250,
>>> +      5500,   5500,   5500,   5500,   5500,   5500,   5500, 5500,   
>>> 5500,   5500,
>>> +      5500,   5500,   5500,   5500, 5500,   5500,   5500,
>>> +      5750,   5750,   5750,   5750,   5750,   5750,   5750, 5750,   
>>> 5750,   5750,
>>> +      5750,   5750,   5750, 5750,   5750,   5750,   5625,
>>> +      6000,   6000,   6000,   6000,   6000,   6000,   6000, 6000,   
>>> 6000,   6000,
>>> +      6000,   6000,   6125, 6000,   6000,   6000,
>>> +      6250,   6250,   6250,   6250,   6250,   6250,   6250, 6250,   
>>> 6375,   6375,
>>> +      6375, 6250,   6250,   6125
>>> +};
>>> +
>>> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
>>> +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>>> +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>>> +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>>> +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>>> +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>>> +    0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
>>> +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>>> +    0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
>>> +    0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
>>> +    0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
>>> +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>>> +    0x4f,                                 0x62, 0x63, 0x58
>>> +};
>>> +
>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
>>> +     25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 
>>> 158600, 175300,
>>> +    192000, 208700, 225400, 242100, 258800, 275500,   294500, 
>>> 311200, 327900, 344600,
>>> +     24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 
>>> 172000, 190500,
>>> +    209000, 227500, 246000, 269500,                   294500, 
>>> 311200, 327900, 344600,
>>> +     31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 
>>> 181000, 199500,
>>> +    218000, 234500, 251000,                           294500, 
>>> 311200, 327900,
>>> +     33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 
>>> 186500, 205000,
>>> +    223500, 240000, 256500, 271500,                   294500, 
>>> 311200, 327900, 344600,
>>> +     28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 
>>> 177000, 195500,
>>> +    214000, 232500, 251500, 273500,                   294500, 
>>> 311200, 327900,
>>> +     28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 
>>> 255000, 273500,
>>> +    292000,                                           311200, 
>>> 327900, 344600
>>> +};
>>> +
>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
>>> +     53000,  53000,  53000,  53000,  53000,  53000,  53000, 53000,  
>>> 53000,  53000,
>>> +     53000,  53000,  53000,  53000,  53000,  53000, 53000,  53000,  
>>> 53000,  53000,
>>> +     67500,  67500,  67500,  67500,  67500,  67500,  67500, 67500,  
>>> 67500,  67500,
>>> +     67500,  67500,  67500,  67500, 67500,  67500,  67500,  67500,
>>> +     85500,  85500,  85500,  85500,  85500,  85500,  85500, 85500,  
>>> 85500,  85500,
>>> +     85500,  85500,  85500, 85500,  85500,  85500,
>>> +    103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 
>>> 103500, 103500,
>>> +    103500, 103500, 103500,  94500,                   103500, 
>>> 103500, 103500,  94500,
>>> +    121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 
>>> 121500, 121500,
>>> +    121500, 121500, 121500, 129000,                   121500, 
>>> 121500, 121500,
>>> +    139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 
>>> 147000, 147000,
>>> +    147000,                                           139500, 
>>> 139500, 130500
>>> +};
>>> +
>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
>>> +      5000,   5000,   5000,   5000,   5000,   5000,   5000, 5000,   
>>> 5000,   5000,
>>> +      5000,   5000,   5000,   5000, 5000, 5000, 5000,   5000,   
>>> 5000,   5000,
>>> +      5250,   5250,   5250,   5250,   5250,   5250,   5250, 5250,   
>>> 5250,   5250,
>>> +      5250,   5250,   5250,   5250, 5250,   5250,   5250,   5250,
>>> +      5500,   5500,   5500,   5500,   5500,   5500,   5500, 5500,   
>>> 5500,   5500,
>>> +      5500,   5500,   5500, 5500,   5500,   5500,
>>> +      5750,   5750,   5750,   5750,   5750,   5750,   5750, 5750,   
>>> 5750,   5750,
>>> +      5750,   5750,   5750,   5750, 5750,   5750,   5750,   5625,
>>> +      6000,   6000,   6000,   6000,   6000,   6000,   6000, 6000,   
>>> 6000,   6000,
>>> +      6000,   6000,   6000,   6125, 6000,   6000,   6000,
>>> +      6250,   6250,   6250,   6250,   6250,   6250,   6250, 6250,   
>>> 6375,   6375,
>>> +      6375, 6250,   6250,   6125
>>> +};
>> ...
>>> +
>>> +static uint8_t report_descriptor[327] = {
>>> +    0x05, 0x59,            // Usage Page (Lighting and Illumination)
>>> +    0x09, 0x01,            // Usage (Lamp Array)
>>> +    0xa1, 0x01,            // Collection (Application)
>>> +    0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
>>> +    0x09, 0x02,            //  Usage (Lamp Array Attributes Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x03,            //   Usage (Lamp Count)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
>>> +    0x09, 0x04,            //   Usage (Bounding Box Width In 
>>> Micrometers)
>>> +    0x09, 0x05,            //   Usage (Bounding Box Height In 
>>> Micrometers)
>>> +    0x09, 0x06,            //   Usage (Bounding Box Depth In 
>>> Micrometers)
>>> +    0x09, 0x07,            //   Usage (Lamp Array Kind)
>>> +    0x09, 0x08,            //   Usage (Min Update Interval In 
>>> Microseconds)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
>>> +    0x75, 0x20,            //   Report Size (32)
>>> +    0x95, 0x05,            //   Report Count (5)
>>> +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
>>> +    0x09, 0x20,            //  Usage (Lamp Attributes Request Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x21,            //   Usage (Lamp Id)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
>>> +    0x09, 0x22,            //  Usage (Lamp Attributes Response Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x21,            //   Usage (Lamp Id)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x23,            //   Usage (Position X In Micrometers)
>>> +    0x09, 0x24,            //   Usage (Position Y In Micrometers)
>>> +    0x09, 0x25,            //   Usage (Position Z In Micrometers)
>>> +    0x09, 0x27,            //   Usage (Update Latency In Microseconds)
>>> +    0x09, 0x26,            //   Usage (Lamp Purposes)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
>>> +    0x75, 0x20,            //   Report Size (32)
>>> +    0x95, 0x05,            //   Report Count (5)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x28,            //   Usage (Red Level Count)
>>> +    0x09, 0x29,            //   Usage (Green Level Count)
>>> +    0x09, 0x2a,            //   Usage (Blue Level Count)
>>> +    0x09, 0x2b,            //   Usage (Intensity Level Count)
>>> +    0x09, 0x2c,            //   Usage (Is Programmable)
>>> +    0x09, 0x2d,            //   Usage (Input Binding)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x06,            //   Report Count (6)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
>>> +    0x09, 0x50,            //  Usage (Lamp Multi Update Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x03,            //   Usage (Lamp Count)
>>> +    0x09, 0x55,            //   Usage (Lamp Update Flags)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x25, 0x08,            //   Logical Maximum (8)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x02,            //   Report Count (2)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x21,            //   Usage (Lamp Id)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x08,            //   Report Count (8)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x20,            //   Report Count (32)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
>>> +    0x09, 0x60,            //  Usage (Lamp Range Update Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x55,            //   Usage (Lamp Update Flags)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x25, 0x08,            //   Logical Maximum (8)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x61,            //   Usage (Lamp Id Start)
>>> +    0x09, 0x62,            //   Usage (Lamp Id End)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
>>> +    0x75, 0x10,            //   Report Size (16)
>>> +    0x95, 0x02,            //   Report Count (2)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0x09, 0x51,            //   Usage (Red Update Channel)
>>> +    0x09, 0x52,            //   Usage (Green Update Channel)
>>> +    0x09, 0x53,            //   Usage (Blue Update Channel)
>>> +    0x09, 0x54,            //   Usage (Intensity Update Channel)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x04,            //   Report Count (4)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
>>> +    0x09, 0x70,            //  Usage (Lamp Array Control Report)
>>> +    0xa1, 0x02,            //  Collection (Logical)
>>> +    0x09, 0x71,            //   Usage (Autonomous Mode)
>>> +    0x15, 0x00,            //   Logical Minimum (0)
>>> +    0x25, 0x01,            //   Logical Maximum (1)
>>> +    0x75, 0x08,            //   Report Size (8)
>>> +    0x95, 0x01,            //   Report Count (1)
>>> +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
>>> +    0xc0,                //  End Collection
>>> +    0xc0                // End Collection
>>> +};
>>> +
Werner Sembach Sept. 28, 2024, 8:23 a.m. UTC | #16
Hi,

Am 28.09.24 um 09:27 schrieb Benjamin Tissoires:
> On Sep 28 2024, Armin Wolf wrote:
>> Am 27.09.24 um 23:01 schrieb Pavel Machek:
>>
>>> Hi!
>>>
>>>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
>>>> controllable RGB keyboard backlight. The firmware API for it is implemented
>>>> via WMI.
>>> Ok.
>>>
>>>> To make the backlight userspace configurable this driver emulates a
>>>> LampArray HID device and translates the input from hidraw to the
>>>> corresponding WMI calls. This is a new approach as the leds subsystem lacks
>>>> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
>>>> needs to be established.
>>> Please don't.
>>>
>>> a) I don't believe emulating crazy HID interface si right thing to
>>> do. (Ton of magic constants. IIRC it stores key positions with
>>> micrometer accuracy or something that crazy. How is userland going to
>>> use this? Will we update micrometers for every single machine?)
> This is exactly why I suggest to make use of HID-BPF. The machine
> specifics is going to be controlled by userspace, leaving out the crazy
> bits out of the kernel.

 From just a quick look at 
https://www.kernel.org/doc/html/latest/hid/hid-bpf.html HID-BPF is some 
kind HID remapping?

But the device in question nativly does not have a hid interface for the 
backlight. It is controlled via WMI calls.

Afaik userspace on linux has no access to WMI? How could HID-BPF 
implement the WMI calls?

>
>>> Even if it is,
>>>
>>> b) The emulation should go to generic layer, it is not specific to
>>> your hardware.
> Well, there is not so much about an emulation here. It's a different way
> of presenting the information.
> But given that HID LampArray is a HID standard, userspace is able to
> implement it once for all the operating systems, which is why this is so
> appealing for them. For reference, we have the same issue with SDL and
> Steam regarding advanced game controller: they very much prefer to
> directly use HID(raw) to talk to the device instead of having a Linux
> specific interface.
>
> Also, starting with v6.12, systemd (logind) will be able to provide
> hidraw node access to non root applications (in the same way you can
> request an input evdev node). So HID LampArray makes a lot of sense IMO.
>
>> Maybe introducing a misc-device which provides an ioctl-based API similar
>> to the HID LampArray would be a solution?
>>
>> Basically we would need:
>> - ioctl for querying the supported LEDs and their properties
>> - ioctl for enabling/disabling autonomous mode
>> - ioctl for updating a range of LEDs
>> - ioctl for updating multiple LEDs at once
> You'll definitely get the API wrong at first, then you'll need to adapt
> for a new device, extend it, etc... But then, you'll depend on one
> userspace application that can talk to your custom ioctls, because cross
> platform applications will have to implement LampArray, and they'ĺl
> probably skip your custom ioctls. And once that userspace application is
> gone, you'll still have to maintain this forever.
>
> Also, the application needs to have root access to that misc device, or
> you need to add extra support for it in systemd...
>
>> If we implement this as a separate subsystem ("illumination subsystem"), then different
>> drivers could use this. This would also allow us to add additional ioctl calls later
>> for more features.
> Again, I strongly advise against this.
>
> I'll just reiterate what makes the more sense to me:
> - provide a thin wmi-to-hid layer that creates a normal regular HID
>    device from your device (could be using vendor collections)
This is what this driver tries to be.
> - deal with the LampArray bits in the HID stack, that we can reuse for
>    other devices (I was planing on getting there for my Corsair and
>    Logitech keyboads).

If a greater efford in the hid stack is planed here i would be all for it.

On my todolist i would try to integrate the leds subsystem with the 
LampArray interface next, just a simple implementation treating the 
whole keyboard as a single led.

> - Meanwhile, while prototyping the LampArray support in userspace and
>    kernelspace, make use of HID-BPF to transform your vendor protocol
>    into LampArray. This will allow to fix things without having to
>    support them forever. This is why HID-BPF exists: so we can create
>    crazy but safe kernel interfaces, without having to support them
>    forever.

I guess i have to do some readup xD.

Regards,

Werner

>
> Cheers,
> Benjamin
>
>> Thanks,
>> Armin Wolf
>>
>>>> +
>>>> +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
>>>> +// side we therefore only run this driver on tested devices defined by this list.
>>> 80 columns, /* */ is usual comment style.
>>>
>>> To illustrate my point... this is crazy:
>>>
>>> (and would require equally crazy par in openrgb to parse).
>>>
>>> Best regards,
>>> 								Pavel
>>>
>>>> +
>>>> +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
>>>> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>>>> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>>>> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>>>> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>>>> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>>>> +	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
>>>> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>>>> +	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
>>>> +	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
>>>> +	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
>>>> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>>>> +	0x4f,                                 0x62, 0x63, 0x58
>>>> +};
>>>> +
>>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
>>>> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
>>>> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
>>>> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
>>>> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
>>>> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
>>>> +	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
>>>> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
>>>> +	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
>>>> +	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
>>>> +	232500, 251500, 273500,                           294500, 311200, 327900,
>>>> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
>>>> +	292000,                                           311200, 327900, 344600
>>>> +};
>>>> +
>>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
>>>> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
>>>> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
>>>> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
>>>> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
>>>> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
>>>> +	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
>>>> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
>>>> +	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
>>>> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
>>>> +	121500, 121500, 129000,                           121500, 121500, 121500,
>>>> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
>>>> +	147000,                                           139500, 139500, 130500
>>>> +};
>>>> +
>>>> +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
>>>> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
>>>> +	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
>>>> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
>>>> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
>>>> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
>>>> +	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
>>>> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
>>>> +	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
>>>> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
>>>> +	  6000,   6000,   6125,                             6000,   6000,   6000,
>>>> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
>>>> +	  6375,                                             6250,   6250,   6125
>>>> +};
>>>> +
>>>> +static const uint8_t sirius_16_iso_kbl_mapping[] = {
>>>> +	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
>>>> +	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
>>>> +	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
>>>> +	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
>>>> +	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
>>>> +	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
>>>> +	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
>>>> +	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
>>>> +	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
>>>> +	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
>>>> +	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
>>>> +	0x4f,                                 0x62, 0x63, 0x58
>>>> +};
>>>> +
>>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
>>>> +	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
>>>> +	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
>>>> +	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
>>>> +	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
>>>> +	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
>>>> +	218000, 234500, 251000,                           294500, 311200, 327900,
>>>> +	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
>>>> +	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
>>>> +	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
>>>> +	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
>>>> +	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
>>>> +	292000,                                           311200, 327900, 344600
>>>> +};
>>>> +
>>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
>>>> +	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
>>>> +	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
>>>> +	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
>>>> +	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
>>>> +	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
>>>> +	 85500,  85500,  85500,                            85500,  85500,  85500,
>>>> +	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
>>>> +	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
>>>> +	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
>>>> +	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
>>>> +	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
>>>> +	147000,                                           139500, 139500, 130500
>>>> +};
>>>> +
>>>> +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
>>>> +	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
>>>> +	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
>>>> +	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
>>>> +	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
>>>> +	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
>>>> +	  5500,   5500,   5500,                             5500,   5500,   5500,
>>>> +	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
>>>> +	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
>>>> +	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
>>>> +	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
>>>> +	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
>>>> +	  6375,                                             6250,   6250,   6125
>>>> +};
>>> ...
>>>> +
>>>> +static uint8_t report_descriptor[327] = {
>>>> +	0x05, 0x59,			// Usage Page (Lighting and Illumination)
>>>> +	0x09, 0x01,			// Usage (Lamp Array)
>>>> +	0xa1, 0x01,			// Collection (Application)
>>>> +	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
>>>> +	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
>>>> +	0xa1, 0x02,			//  Collection (Logical)
>>>> +	0x09, 0x03,			//   Usage (Lamp Count)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>>>> +	0x75, 0x10,			//   Report Size (16)
>>>> +	0x95, 0x01,			//   Report Count (1)
>>>> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
>>>> +	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
>>>> +	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
>>>> +	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
>>>> +	0x09, 0x07,			//   Usage (Lamp Array Kind)
>>>> +	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
>>>> +	0x75, 0x20,			//   Report Size (32)
>>>> +	0x95, 0x05,			//   Report Count (5)
>>>> +	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
>>>> +	0xc0,				//  End Collection
>>>> +	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
>>>> +	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
>>>> +	0xa1, 0x02,			//  Collection (Logical)
>>>> +	0x09, 0x21,			//   Usage (Lamp Id)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>>>> +	0x75, 0x10,			//   Report Size (16)
>>>> +	0x95, 0x01,			//   Report Count (1)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0xc0,				//  End Collection
>>>> +	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
>>>> +	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
>>>> +	0xa1, 0x02,			//  Collection (Logical)
>>>> +	0x09, 0x21,			//   Usage (Lamp Id)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>>>> +	0x75, 0x10,			//   Report Size (16)
>>>> +	0x95, 0x01,			//   Report Count (1)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0x09, 0x23,			//   Usage (Position X In Micrometers)
>>>> +	0x09, 0x24,			//   Usage (Position Y In Micrometers)
>>>> +	0x09, 0x25,			//   Usage (Position Z In Micrometers)
>>>> +	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
>>>> +	0x09, 0x26,			//   Usage (Lamp Purposes)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
>>>> +	0x75, 0x20,			//   Report Size (32)
>>>> +	0x95, 0x05,			//   Report Count (5)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0x09, 0x28,			//   Usage (Red Level Count)
>>>> +	0x09, 0x29,			//   Usage (Green Level Count)
>>>> +	0x09, 0x2a,			//   Usage (Blue Level Count)
>>>> +	0x09, 0x2b,			//   Usage (Intensity Level Count)
>>>> +	0x09, 0x2c,			//   Usage (Is Programmable)
>>>> +	0x09, 0x2d,			//   Usage (Input Binding)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>>>> +	0x75, 0x08,			//   Report Size (8)
>>>> +	0x95, 0x06,			//   Report Count (6)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0xc0,				//  End Collection
>>>> +	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
>>>> +	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
>>>> +	0xa1, 0x02,			//  Collection (Logical)
>>>> +	0x09, 0x03,			//   Usage (Lamp Count)
>>>> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x25, 0x08,			//   Logical Maximum (8)
>>>> +	0x75, 0x08,			//   Report Size (8)
>>>> +	0x95, 0x02,			//   Report Count (2)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0x09, 0x21,			//   Usage (Lamp Id)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>>>> +	0x75, 0x10,			//   Report Size (16)
>>>> +	0x95, 0x08,			//   Report Count (8)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>>>> +	0x75, 0x08,			//   Report Size (8)
>>>> +	0x95, 0x20,			//   Report Count (32)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0xc0,				//  End Collection
>>>> +	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
>>>> +	0x09, 0x60,			//  Usage (Lamp Range Update Report)
>>>> +	0xa1, 0x02,			//  Collection (Logical)
>>>> +	0x09, 0x55,			//   Usage (Lamp Update Flags)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x25, 0x08,			//   Logical Maximum (8)
>>>> +	0x75, 0x08,			//   Report Size (8)
>>>> +	0x95, 0x01,			//   Report Count (1)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0x09, 0x61,			//   Usage (Lamp Id Start)
>>>> +	0x09, 0x62,			//   Usage (Lamp Id End)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
>>>> +	0x75, 0x10,			//   Report Size (16)
>>>> +	0x95, 0x02,			//   Report Count (2)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0x09, 0x51,			//   Usage (Red Update Channel)
>>>> +	0x09, 0x52,			//   Usage (Green Update Channel)
>>>> +	0x09, 0x53,			//   Usage (Blue Update Channel)
>>>> +	0x09, 0x54,			//   Usage (Intensity Update Channel)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x26, 0xff, 0x00,		//   Logical Maximum (255)
>>>> +	0x75, 0x08,			//   Report Size (8)
>>>> +	0x95, 0x04,			//   Report Count (4)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0xc0,				//  End Collection
>>>> +	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
>>>> +	0x09, 0x70,			//  Usage (Lamp Array Control Report)
>>>> +	0xa1, 0x02,			//  Collection (Logical)
>>>> +	0x09, 0x71,			//   Usage (Autonomous Mode)
>>>> +	0x15, 0x00,			//   Logical Minimum (0)
>>>> +	0x25, 0x01,			//   Logical Maximum (1)
>>>> +	0x75, 0x08,			//   Report Size (8)
>>>> +	0x95, 0x01,			//   Report Count (1)
>>>> +	0xb1, 0x02,			//   Feature (Data,Var,Abs)
>>>> +	0xc0,				//  End Collection
>>>> +	0xc0				// End Collection
>>>> +};
>>>> +
Benjamin Tissoires Sept. 28, 2024, 10:05 a.m. UTC | #17
On Sep 28 2024, Werner Sembach wrote:
> Hi,
> 
> Am 28.09.24 um 09:27 schrieb Benjamin Tissoires:
> > On Sep 28 2024, Armin Wolf wrote:
> > > Am 27.09.24 um 23:01 schrieb Pavel Machek:
> > > 
> > > > Hi!
> > > > 
> > > > > The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
> > > > > controllable RGB keyboard backlight. The firmware API for it is implemented
> > > > > via WMI.
> > > > Ok.
> > > > 
> > > > > To make the backlight userspace configurable this driver emulates a
> > > > > LampArray HID device and translates the input from hidraw to the
> > > > > corresponding WMI calls. This is a new approach as the leds subsystem lacks
> > > > > a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
> > > > > needs to be established.
> > > > Please don't.
> > > > 
> > > > a) I don't believe emulating crazy HID interface si right thing to
> > > > do. (Ton of magic constants. IIRC it stores key positions with
> > > > micrometer accuracy or something that crazy. How is userland going to
> > > > use this? Will we update micrometers for every single machine?)
> > This is exactly why I suggest to make use of HID-BPF. The machine
> > specifics is going to be controlled by userspace, leaving out the crazy
> > bits out of the kernel.
> 
> From just a quick look at
> https://www.kernel.org/doc/html/latest/hid/hid-bpf.html HID-BPF is some kind
> HID remapping?

Yes. HID-BPF allows to customize a HID device by changing the report
descriptor and/or the events, and the requests made from hidraw.

It's a HID -> HID conversion, but controlled by userspace.

See [0] for a tutorial.

> 
> But the device in question nativly does not have a hid interface for the
> backlight. It is controlled via WMI calls.
> 
> Afaik userspace on linux has no access to WMI? How could HID-BPF implement
> the WMI calls?

You'll need a thin WMI to HID wrapper, but without LampArray.
Then you load the HID-BPF program from userspace, that program knows
about the specifics of the device, and can do the LampArray transform.

Which means that once the wmi-to-hid driver specific to this device is
built in the kernel, you can adjust your LampArray implementation (the
device specifics micrometers and what not) from usersapce.

> 
> > 
> > > > Even if it is,
> > > > 
> > > > b) The emulation should go to generic layer, it is not specific to
> > > > your hardware.
> > Well, there is not so much about an emulation here. It's a different way
> > of presenting the information.
> > But given that HID LampArray is a HID standard, userspace is able to
> > implement it once for all the operating systems, which is why this is so
> > appealing for them. For reference, we have the same issue with SDL and
> > Steam regarding advanced game controller: they very much prefer to
> > directly use HID(raw) to talk to the device instead of having a Linux
> > specific interface.
> > 
> > Also, starting with v6.12, systemd (logind) will be able to provide
> > hidraw node access to non root applications (in the same way you can
> > request an input evdev node). So HID LampArray makes a lot of sense IMO.
> > 
> > > Maybe introducing a misc-device which provides an ioctl-based API similar
> > > to the HID LampArray would be a solution?
> > > 
> > > Basically we would need:
> > > - ioctl for querying the supported LEDs and their properties
> > > - ioctl for enabling/disabling autonomous mode
> > > - ioctl for updating a range of LEDs
> > > - ioctl for updating multiple LEDs at once
> > You'll definitely get the API wrong at first, then you'll need to adapt
> > for a new device, extend it, etc... But then, you'll depend on one
> > userspace application that can talk to your custom ioctls, because cross
> > platform applications will have to implement LampArray, and they'ĺl
> > probably skip your custom ioctls. And once that userspace application is
> > gone, you'll still have to maintain this forever.
> > 
> > Also, the application needs to have root access to that misc device, or
> > you need to add extra support for it in systemd...
> > 
> > > If we implement this as a separate subsystem ("illumination subsystem"), then different
> > > drivers could use this. This would also allow us to add additional ioctl calls later
> > > for more features.
> > Again, I strongly advise against this.
> > 
> > I'll just reiterate what makes the more sense to me:
> > - provide a thin wmi-to-hid layer that creates a normal regular HID
> >    device from your device (could be using vendor collections)
> This is what this driver tries to be.

Except that your current implementation also does the LampArray
conversion. I think it'll make more sense to provide an almost raw
access to the underlying protocol (think of it like your own Tuxedo
vendor collection in HID), and handle the LampArray weirdeness in bpf:
definition of the device physicals, conversion from HID LampArray
commands into Tuxedo specifics.

> > - deal with the LampArray bits in the HID stack, that we can reuse for
> >    other devices (I was planing on getting there for my Corsair and
> >    Logitech keyboads).
> 
> If a greater efford in the hid stack is planed here i would be all for it.

That's what makes more sense to me at least. Other operating systems
export the HID nodes directly, so userspace prefers to talk to the
device directly. So I'd rather rely on a standard than trying to fit the
current use case in a new interface that will probably fail.

> 
> On my todolist i would try to integrate the leds subsystem with the
> LampArray interface next, just a simple implementation treating the whole
> keyboard as a single led.

That could be done in HID-core as well. Making it part of HID-core also
means that once we get an actual LampArray device, we'll get support for
it from day one.

> 
> > - Meanwhile, while prototyping the LampArray support in userspace and
> >    kernelspace, make use of HID-BPF to transform your vendor protocol
> >    into LampArray. This will allow to fix things without having to
> >    support them forever. This is why HID-BPF exists: so we can create
> >    crazy but safe kernel interfaces, without having to support them
> >    forever.
> 
> I guess i have to do some readup xD.
> 

Please have a look at the tutorial[0]. That tutorial is missing the
couple of new hooks you'll need to change the requests emitted from
hidraw as LampArray into Tuxedo, but I can also give you a help into
making it happening.

Basically, you also need to define a .hid_hw_request callback in your
HID_BPF_OPS and extract all of the code you have here into that bpf
program (which is roughly C code).

Cheers,
Benjamin


[0] https://libevdev.pages.freedesktop.org/udev-hid-bpf/tutorial.html
Werner Sembach Sept. 30, 2024, 3:35 p.m. UTC | #18
Am 28.09.24 um 12:05 schrieb Benjamin Tissoires:
> On Sep 28 2024, Werner Sembach wrote:
>> Hi,
>>
>> Am 28.09.24 um 09:27 schrieb Benjamin Tissoires:
>>> On Sep 28 2024, Armin Wolf wrote:
>>>> Am 27.09.24 um 23:01 schrieb Pavel Machek:
>>>>
>>>>> Hi!
>>>>>
>>>>>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
>>>>>> controllable RGB keyboard backlight. The firmware API for it is implemented
>>>>>> via WMI.
>>>>> Ok.
>>>>>
>>>>>> To make the backlight userspace configurable this driver emulates a
>>>>>> LampArray HID device and translates the input from hidraw to the
>>>>>> corresponding WMI calls. This is a new approach as the leds subsystem lacks
>>>>>> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
>>>>>> needs to be established.
>>>>> Please don't.
>>>>>
>>>>> a) I don't believe emulating crazy HID interface si right thing to
>>>>> do. (Ton of magic constants. IIRC it stores key positions with
>>>>> micrometer accuracy or something that crazy. How is userland going to
>>>>> use this? Will we update micrometers for every single machine?)
>>> This is exactly why I suggest to make use of HID-BPF. The machine
>>> specifics is going to be controlled by userspace, leaving out the crazy
>>> bits out of the kernel.
>>  From just a quick look at
>> https://www.kernel.org/doc/html/latest/hid/hid-bpf.html HID-BPF is some kind
>> HID remapping?
> Yes. HID-BPF allows to customize a HID device by changing the report
> descriptor and/or the events, and the requests made from hidraw.
>
> It's a HID -> HID conversion, but controlled by userspace.
>
> See [0] for a tutorial.
>
>> But the device in question nativly does not have a hid interface for the
>> backlight. It is controlled via WMI calls.
>>
>> Afaik userspace on linux has no access to WMI? How could HID-BPF implement
>> the WMI calls?
> You'll need a thin WMI to HID wrapper, but without LampArray.
> Then you load the HID-BPF program from userspace, that program knows
> about the specifics of the device, and can do the LampArray transform.
>
> Which means that once the wmi-to-hid driver specific to this device is
> built in the kernel, you can adjust your LampArray implementation (the
> device specifics micrometers and what not) from usersapce.
>
>>>>> Even if it is,
>>>>>
>>>>> b) The emulation should go to generic layer, it is not specific to
>>>>> your hardware.
>>> Well, there is not so much about an emulation here. It's a different way
>>> of presenting the information.
>>> But given that HID LampArray is a HID standard, userspace is able to
>>> implement it once for all the operating systems, which is why this is so
>>> appealing for them. For reference, we have the same issue with SDL and
>>> Steam regarding advanced game controller: they very much prefer to
>>> directly use HID(raw) to talk to the device instead of having a Linux
>>> specific interface.
>>>
>>> Also, starting with v6.12, systemd (logind) will be able to provide
>>> hidraw node access to non root applications (in the same way you can
>>> request an input evdev node). So HID LampArray makes a lot of sense IMO.
>>>
>>>> Maybe introducing a misc-device which provides an ioctl-based API similar
>>>> to the HID LampArray would be a solution?
>>>>
>>>> Basically we would need:
>>>> - ioctl for querying the supported LEDs and their properties
>>>> - ioctl for enabling/disabling autonomous mode
>>>> - ioctl for updating a range of LEDs
>>>> - ioctl for updating multiple LEDs at once
>>> You'll definitely get the API wrong at first, then you'll need to adapt
>>> for a new device, extend it, etc... But then, you'll depend on one
>>> userspace application that can talk to your custom ioctls, because cross
>>> platform applications will have to implement LampArray, and they'ĺl
>>> probably skip your custom ioctls. And once that userspace application is
>>> gone, you'll still have to maintain this forever.
>>>
>>> Also, the application needs to have root access to that misc device, or
>>> you need to add extra support for it in systemd...
>>>
>>>> If we implement this as a separate subsystem ("illumination subsystem"), then different
>>>> drivers could use this. This would also allow us to add additional ioctl calls later
>>>> for more features.
>>> Again, I strongly advise against this.
>>>
>>> I'll just reiterate what makes the more sense to me:
>>> - provide a thin wmi-to-hid layer that creates a normal regular HID
>>>     device from your device (could be using vendor collections)
>> This is what this driver tries to be.
> Except that your current implementation also does the LampArray
> conversion. I think it'll make more sense to provide an almost raw
> access to the underlying protocol (think of it like your own Tuxedo
> vendor collection in HID), and handle the LampArray weirdeness in bpf:
> definition of the device physicals, conversion from HID LampArray
> commands into Tuxedo specifics.
>
>>> - deal with the LampArray bits in the HID stack, that we can reuse for
>>>     other devices (I was planing on getting there for my Corsair and
>>>     Logitech keyboads).
>> If a greater efford in the hid stack is planed here i would be all for it.
> That's what makes more sense to me at least. Other operating systems
> export the HID nodes directly, so userspace prefers to talk to the
> device directly. So I'd rather rely on a standard than trying to fit the
> current use case in a new interface that will probably fail.
>
>> On my todolist i would try to integrate the leds subsystem with the
>> LampArray interface next, just a simple implementation treating the whole
>> keyboard as a single led.
> That could be done in HID-core as well. Making it part of HID-core also
> means that once we get an actual LampArray device, we'll get support for
> it from day one.
>
>>> - Meanwhile, while prototyping the LampArray support in userspace and
>>>     kernelspace, make use of HID-BPF to transform your vendor protocol
>>>     into LampArray. This will allow to fix things without having to
>>>     support them forever. This is why HID-BPF exists: so we can create
>>>     crazy but safe kernel interfaces, without having to support them
>>>     forever.
>> I guess i have to do some readup xD.
>>
> Please have a look at the tutorial[0]. That tutorial is missing the
> couple of new hooks you'll need to change the requests emitted from
> hidraw as LampArray into Tuxedo, but I can also give you a help into
> making it happening.
>
> Basically, you also need to define a .hid_hw_request callback in your
> HID_BPF_OPS and extract all of the code you have here into that bpf
> program (which is roughly C code).
>
> Cheers,
> Benjamin
>
>
> [0] https://libevdev.pages.freedesktop.org/udev-hid-bpf/tutorial.html
>
2 question left on my side:

- Does the BPF approach have performance/latency impact?

- Does it work during boot? (e.g. early control via the leds subsystem to stop 
firmware induced rainbow puke)
Benjamin Tissoires Sept. 30, 2024, 4:15 p.m. UTC | #19
On Sep 30 2024, Werner Sembach wrote:
> Am 28.09.24 um 12:05 schrieb Benjamin Tissoires:
> > On Sep 28 2024, Werner Sembach wrote:
> > > Hi,
> > > 
> > > Am 28.09.24 um 09:27 schrieb Benjamin Tissoires:
> > > > On Sep 28 2024, Armin Wolf wrote:
> > > > > Am 27.09.24 um 23:01 schrieb Pavel Machek:
> > > > > 
> > > > > > Hi!
> > > > > > 
> > > > > > > The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
> > > > > > > controllable RGB keyboard backlight. The firmware API for it is implemented
> > > > > > > via WMI.
> > > > > > Ok.
> > > > > > 
> > > > > > > To make the backlight userspace configurable this driver emulates a
> > > > > > > LampArray HID device and translates the input from hidraw to the
> > > > > > > corresponding WMI calls. This is a new approach as the leds subsystem lacks
> > > > > > > a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
> > > > > > > needs to be established.
> > > > > > Please don't.
> > > > > > 
> > > > > > a) I don't believe emulating crazy HID interface si right thing to
> > > > > > do. (Ton of magic constants. IIRC it stores key positions with
> > > > > > micrometer accuracy or something that crazy. How is userland going to
> > > > > > use this? Will we update micrometers for every single machine?)
> > > > This is exactly why I suggest to make use of HID-BPF. The machine
> > > > specifics is going to be controlled by userspace, leaving out the crazy
> > > > bits out of the kernel.
> > >  From just a quick look at
> > > https://www.kernel.org/doc/html/latest/hid/hid-bpf.html HID-BPF is some kind
> > > HID remapping?
> > Yes. HID-BPF allows to customize a HID device by changing the report
> > descriptor and/or the events, and the requests made from hidraw.
> > 
> > It's a HID -> HID conversion, but controlled by userspace.
> > 
> > See [0] for a tutorial.
> > 
> > > But the device in question nativly does not have a hid interface for the
> > > backlight. It is controlled via WMI calls.
> > > 
> > > Afaik userspace on linux has no access to WMI? How could HID-BPF implement
> > > the WMI calls?
> > You'll need a thin WMI to HID wrapper, but without LampArray.
> > Then you load the HID-BPF program from userspace, that program knows
> > about the specifics of the device, and can do the LampArray transform.
> > 
> > Which means that once the wmi-to-hid driver specific to this device is
> > built in the kernel, you can adjust your LampArray implementation (the
> > device specifics micrometers and what not) from usersapce.
> > 
> > > > > > Even if it is,
> > > > > > 
> > > > > > b) The emulation should go to generic layer, it is not specific to
> > > > > > your hardware.
> > > > Well, there is not so much about an emulation here. It's a different way
> > > > of presenting the information.
> > > > But given that HID LampArray is a HID standard, userspace is able to
> > > > implement it once for all the operating systems, which is why this is so
> > > > appealing for them. For reference, we have the same issue with SDL and
> > > > Steam regarding advanced game controller: they very much prefer to
> > > > directly use HID(raw) to talk to the device instead of having a Linux
> > > > specific interface.
> > > > 
> > > > Also, starting with v6.12, systemd (logind) will be able to provide
> > > > hidraw node access to non root applications (in the same way you can
> > > > request an input evdev node). So HID LampArray makes a lot of sense IMO.
> > > > 
> > > > > Maybe introducing a misc-device which provides an ioctl-based API similar
> > > > > to the HID LampArray would be a solution?
> > > > > 
> > > > > Basically we would need:
> > > > > - ioctl for querying the supported LEDs and their properties
> > > > > - ioctl for enabling/disabling autonomous mode
> > > > > - ioctl for updating a range of LEDs
> > > > > - ioctl for updating multiple LEDs at once
> > > > You'll definitely get the API wrong at first, then you'll need to adapt
> > > > for a new device, extend it, etc... But then, you'll depend on one
> > > > userspace application that can talk to your custom ioctls, because cross
> > > > platform applications will have to implement LampArray, and they'ĺl
> > > > probably skip your custom ioctls. And once that userspace application is
> > > > gone, you'll still have to maintain this forever.
> > > > 
> > > > Also, the application needs to have root access to that misc device, or
> > > > you need to add extra support for it in systemd...
> > > > 
> > > > > If we implement this as a separate subsystem ("illumination subsystem"), then different
> > > > > drivers could use this. This would also allow us to add additional ioctl calls later
> > > > > for more features.
> > > > Again, I strongly advise against this.
> > > > 
> > > > I'll just reiterate what makes the more sense to me:
> > > > - provide a thin wmi-to-hid layer that creates a normal regular HID
> > > >     device from your device (could be using vendor collections)
> > > This is what this driver tries to be.
> > Except that your current implementation also does the LampArray
> > conversion. I think it'll make more sense to provide an almost raw
> > access to the underlying protocol (think of it like your own Tuxedo
> > vendor collection in HID), and handle the LampArray weirdeness in bpf:
> > definition of the device physicals, conversion from HID LampArray
> > commands into Tuxedo specifics.
> > 
> > > > - deal with the LampArray bits in the HID stack, that we can reuse for
> > > >     other devices (I was planing on getting there for my Corsair and
> > > >     Logitech keyboads).
> > > If a greater efford in the hid stack is planed here i would be all for it.
> > That's what makes more sense to me at least. Other operating systems
> > export the HID nodes directly, so userspace prefers to talk to the
> > device directly. So I'd rather rely on a standard than trying to fit the
> > current use case in a new interface that will probably fail.
> > 
> > > On my todolist i would try to integrate the leds subsystem with the
> > > LampArray interface next, just a simple implementation treating the whole
> > > keyboard as a single led.
> > That could be done in HID-core as well. Making it part of HID-core also
> > means that once we get an actual LampArray device, we'll get support for
> > it from day one.
> > 
> > > > - Meanwhile, while prototyping the LampArray support in userspace and
> > > >     kernelspace, make use of HID-BPF to transform your vendor protocol
> > > >     into LampArray. This will allow to fix things without having to
> > > >     support them forever. This is why HID-BPF exists: so we can create
> > > >     crazy but safe kernel interfaces, without having to support them
> > > >     forever.
> > > I guess i have to do some readup xD.
> > > 
> > Please have a look at the tutorial[0]. That tutorial is missing the
> > couple of new hooks you'll need to change the requests emitted from
> > hidraw as LampArray into Tuxedo, but I can also give you a help into
> > making it happening.
> > 
> > Basically, you also need to define a .hid_hw_request callback in your
> > HID_BPF_OPS and extract all of the code you have here into that bpf
> > program (which is roughly C code).
> > 
> > Cheers,
> > Benjamin
> > 
> > 
> > [0] https://libevdev.pages.freedesktop.org/udev-hid-bpf/tutorial.html
> > 
> 2 question left on my side:
> 
> - Does the BPF approach have performance/latency impact?

Not anything you'll notice. BPF is used in network on much more
demanding latency purposes. And IIRC, jumping into BPF is almost a no-op
nowadays. From what I can tell from the BPF maintainer in his ALPSS
presentation last week:
"
BPF C code is compiled into BPF ISA with BPF calling convention,
JIT translate BPF ISA into native ISA,
One to one mapping of BPF registers to x86 registers.
"

> 
> - Does it work during boot? (e.g. early control via the leds subsystem to
> stop firmware induced rainbow puke)
> 

Nope. It gets loaded once udev enumerates the device, so unless you
craft a special intird with both the loader and the bpf object it is
not.

However, if that rainbow is bothering you, you can "initialize" the
keyboard to a sane state with your WMI-to-HID driver before exposing the
device to HID.

FWIW, the use of BPF only allows you to not corner yourself. If you
failed at your LampArray implementation, you'll have to deal with it
forever-ish. So it's perfectly sensible to use BPF as an intermediate step
where you develop both userspace and kernel space and then convert back
the BPF into a proper HID driver.

Being able to develop a kernel driver without having to reboot and
being sure you won't crash your kernel is a game changer ;)

Cheers,
Benjamin
Werner Sembach Sept. 30, 2024, 4:35 p.m. UTC | #20
Hi,

Am 30.09.24 um 18:15 schrieb Benjamin Tissoires:
> On Sep 30 2024, Werner Sembach wrote:
>> Am 28.09.24 um 12:05 schrieb Benjamin Tissoires:
>>> On Sep 28 2024, Werner Sembach wrote:
>>>> Hi,
>>>>
>>>> Am 28.09.24 um 09:27 schrieb Benjamin Tissoires:
>>>>> On Sep 28 2024, Armin Wolf wrote:
>>>>>> Am 27.09.24 um 23:01 schrieb Pavel Machek:
>>>>>>
>>>>>>> Hi!
>>>>>>>
>>>>>>>> The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
>>>>>>>> controllable RGB keyboard backlight. The firmware API for it is implemented
>>>>>>>> via WMI.
>>>>>>> Ok.
>>>>>>>
>>>>>>>> To make the backlight userspace configurable this driver emulates a
>>>>>>>> LampArray HID device and translates the input from hidraw to the
>>>>>>>> corresponding WMI calls. This is a new approach as the leds subsystem lacks
>>>>>>>> a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
>>>>>>>> needs to be established.
>>>>>>> Please don't.
>>>>>>>
>>>>>>> a) I don't believe emulating crazy HID interface si right thing to
>>>>>>> do. (Ton of magic constants. IIRC it stores key positions with
>>>>>>> micrometer accuracy or something that crazy. How is userland going to
>>>>>>> use this? Will we update micrometers for every single machine?)
>>>>> This is exactly why I suggest to make use of HID-BPF. The machine
>>>>> specifics is going to be controlled by userspace, leaving out the crazy
>>>>> bits out of the kernel.
>>>>   From just a quick look at
>>>> https://www.kernel.org/doc/html/latest/hid/hid-bpf.html HID-BPF is some kind
>>>> HID remapping?
>>> Yes. HID-BPF allows to customize a HID device by changing the report
>>> descriptor and/or the events, and the requests made from hidraw.
>>>
>>> It's a HID -> HID conversion, but controlled by userspace.
>>>
>>> See [0] for a tutorial.
>>>
>>>> But the device in question nativly does not have a hid interface for the
>>>> backlight. It is controlled via WMI calls.
>>>>
>>>> Afaik userspace on linux has no access to WMI? How could HID-BPF implement
>>>> the WMI calls?
>>> You'll need a thin WMI to HID wrapper, but without LampArray.
>>> Then you load the HID-BPF program from userspace, that program knows
>>> about the specifics of the device, and can do the LampArray transform.
>>>
>>> Which means that once the wmi-to-hid driver specific to this device is
>>> built in the kernel, you can adjust your LampArray implementation (the
>>> device specifics micrometers and what not) from usersapce.
>>>
>>>>>>> Even if it is,
>>>>>>>
>>>>>>> b) The emulation should go to generic layer, it is not specific to
>>>>>>> your hardware.
>>>>> Well, there is not so much about an emulation here. It's a different way
>>>>> of presenting the information.
>>>>> But given that HID LampArray is a HID standard, userspace is able to
>>>>> implement it once for all the operating systems, which is why this is so
>>>>> appealing for them. For reference, we have the same issue with SDL and
>>>>> Steam regarding advanced game controller: they very much prefer to
>>>>> directly use HID(raw) to talk to the device instead of having a Linux
>>>>> specific interface.
>>>>>
>>>>> Also, starting with v6.12, systemd (logind) will be able to provide
>>>>> hidraw node access to non root applications (in the same way you can
>>>>> request an input evdev node). So HID LampArray makes a lot of sense IMO.
>>>>>
>>>>>> Maybe introducing a misc-device which provides an ioctl-based API similar
>>>>>> to the HID LampArray would be a solution?
>>>>>>
>>>>>> Basically we would need:
>>>>>> - ioctl for querying the supported LEDs and their properties
>>>>>> - ioctl for enabling/disabling autonomous mode
>>>>>> - ioctl for updating a range of LEDs
>>>>>> - ioctl for updating multiple LEDs at once
>>>>> You'll definitely get the API wrong at first, then you'll need to adapt
>>>>> for a new device, extend it, etc... But then, you'll depend on one
>>>>> userspace application that can talk to your custom ioctls, because cross
>>>>> platform applications will have to implement LampArray, and they'ĺl
>>>>> probably skip your custom ioctls. And once that userspace application is
>>>>> gone, you'll still have to maintain this forever.
>>>>>
>>>>> Also, the application needs to have root access to that misc device, or
>>>>> you need to add extra support for it in systemd...
>>>>>
>>>>>> If we implement this as a separate subsystem ("illumination subsystem"), then different
>>>>>> drivers could use this. This would also allow us to add additional ioctl calls later
>>>>>> for more features.
>>>>> Again, I strongly advise against this.
>>>>>
>>>>> I'll just reiterate what makes the more sense to me:
>>>>> - provide a thin wmi-to-hid layer that creates a normal regular HID
>>>>>      device from your device (could be using vendor collections)
>>>> This is what this driver tries to be.
>>> Except that your current implementation also does the LampArray
>>> conversion. I think it'll make more sense to provide an almost raw
>>> access to the underlying protocol (think of it like your own Tuxedo
>>> vendor collection in HID), and handle the LampArray weirdeness in bpf:
>>> definition of the device physicals, conversion from HID LampArray
>>> commands into Tuxedo specifics.
>>>
>>>>> - deal with the LampArray bits in the HID stack, that we can reuse for
>>>>>      other devices (I was planing on getting there for my Corsair and
>>>>>      Logitech keyboads).
>>>> If a greater efford in the hid stack is planed here i would be all for it.
>>> That's what makes more sense to me at least. Other operating systems
>>> export the HID nodes directly, so userspace prefers to talk to the
>>> device directly. So I'd rather rely on a standard than trying to fit the
>>> current use case in a new interface that will probably fail.
>>>
>>>> On my todolist i would try to integrate the leds subsystem with the
>>>> LampArray interface next, just a simple implementation treating the whole
>>>> keyboard as a single led.
>>> That could be done in HID-core as well. Making it part of HID-core also
>>> means that once we get an actual LampArray device, we'll get support for
>>> it from day one.
>>>
>>>>> - Meanwhile, while prototyping the LampArray support in userspace and
>>>>>      kernelspace, make use of HID-BPF to transform your vendor protocol
>>>>>      into LampArray. This will allow to fix things without having to
>>>>>      support them forever. This is why HID-BPF exists: so we can create
>>>>>      crazy but safe kernel interfaces, without having to support them
>>>>>      forever.
>>>> I guess i have to do some readup xD.
>>>>
>>> Please have a look at the tutorial[0]. That tutorial is missing the
>>> couple of new hooks you'll need to change the requests emitted from
>>> hidraw as LampArray into Tuxedo, but I can also give you a help into
>>> making it happening.
>>>
>>> Basically, you also need to define a .hid_hw_request callback in your
>>> HID_BPF_OPS and extract all of the code you have here into that bpf
>>> program (which is roughly C code).
>>>
>>> Cheers,
>>> Benjamin
>>>
>>>
>>> [0] https://libevdev.pages.freedesktop.org/udev-hid-bpf/tutorial.html
>>>
>> 2 question left on my side:
>>
>> - Does the BPF approach have performance/latency impact?
> Not anything you'll notice. BPF is used in network on much more
> demanding latency purposes. And IIRC, jumping into BPF is almost a no-op
> nowadays. From what I can tell from the BPF maintainer in his ALPSS
> presentation last week:
> "
> BPF C code is compiled into BPF ISA with BPF calling convention,
> JIT translate BPF ISA into native ISA,
> One to one mapping of BPF registers to x86 registers.
> "
Ok
>
>> - Does it work during boot? (e.g. early control via the leds subsystem to
>> stop firmware induced rainbow puke)
>>
> Nope. It gets loaded once udev enumerates the device, so unless you
> craft a special intird with both the loader and the bpf object it is
> not.
>
> However, if that rainbow is bothering you, you can "initialize" the
> keyboard to a sane state with your WMI-to-HID driver before exposing the
> device to HID.
Thinking about it, maybe it's not to bad that it only changes once udev is 
ready, like this udev could decide if leds should be used or if it should 
directly be passed to OpenRGB for example, giving at least some consistency only 
changing once: i.e. firmware -> OpenRGB setting and not firmware->leds 
setting->OpenRGB setting.
>
> FWIW, the use of BPF only allows you to not corner yourself. If you
> failed at your LampArray implementation, you'll have to deal with it
> forever-ish. So it's perfectly sensible to use BPF as an intermediate step
> where you develop both userspace and kernel space and then convert back
> the BPF into a proper HID driver.

I don't really see this point: The LampArray API is defined by the HID Usage 
Table and the report descriptor, so there is not API to mess up and everything 
else has to be parsed dynamically by userspace anyway, so it can easily be 
changed and userspace just adopts automatically.

And for this case the proper HID driver is already ready.

So the only point for me currently is: Is it ok to have key position/usage 
description tables in the kernel driver or not?

>
> Being able to develop a kernel driver without having to reboot and
> being sure you won't crash your kernel is a game changer ;)
>
> Cheers,
> Benjamin
Benjamin Tissoires Sept. 30, 2024, 5:06 p.m. UTC | #21
On Sep 30 2024, Werner Sembach wrote:
> Hi,
> 
> Am 30.09.24 um 18:15 schrieb Benjamin Tissoires:
> > On Sep 30 2024, Werner Sembach wrote:
> > > Am 28.09.24 um 12:05 schrieb Benjamin Tissoires:
> > > > On Sep 28 2024, Werner Sembach wrote:
> > > > > Hi,
> > > > > 
> > > > > Am 28.09.24 um 09:27 schrieb Benjamin Tissoires:
> > > > > > On Sep 28 2024, Armin Wolf wrote:
> > > > > > > Am 27.09.24 um 23:01 schrieb Pavel Machek:
> > > > > > > 
> > > > > > > > Hi!
> > > > > > > > 
> > > > > > > > > The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
> > > > > > > > > controllable RGB keyboard backlight. The firmware API for it is implemented
> > > > > > > > > via WMI.
> > > > > > > > Ok.
> > > > > > > > 
> > > > > > > > > To make the backlight userspace configurable this driver emulates a
> > > > > > > > > LampArray HID device and translates the input from hidraw to the
> > > > > > > > > corresponding WMI calls. This is a new approach as the leds subsystem lacks
> > > > > > > > > a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
> > > > > > > > > needs to be established.
> > > > > > > > Please don't.
> > > > > > > > 
> > > > > > > > a) I don't believe emulating crazy HID interface si right thing to
> > > > > > > > do. (Ton of magic constants. IIRC it stores key positions with
> > > > > > > > micrometer accuracy or something that crazy. How is userland going to
> > > > > > > > use this? Will we update micrometers for every single machine?)
> > > > > > This is exactly why I suggest to make use of HID-BPF. The machine
> > > > > > specifics is going to be controlled by userspace, leaving out the crazy
> > > > > > bits out of the kernel.
> > > > >   From just a quick look at
> > > > > https://www.kernel.org/doc/html/latest/hid/hid-bpf.html HID-BPF is some kind
> > > > > HID remapping?
> > > > Yes. HID-BPF allows to customize a HID device by changing the report
> > > > descriptor and/or the events, and the requests made from hidraw.
> > > > 
> > > > It's a HID -> HID conversion, but controlled by userspace.
> > > > 
> > > > See [0] for a tutorial.
> > > > 
> > > > > But the device in question nativly does not have a hid interface for the
> > > > > backlight. It is controlled via WMI calls.
> > > > > 
> > > > > Afaik userspace on linux has no access to WMI? How could HID-BPF implement
> > > > > the WMI calls?
> > > > You'll need a thin WMI to HID wrapper, but without LampArray.
> > > > Then you load the HID-BPF program from userspace, that program knows
> > > > about the specifics of the device, and can do the LampArray transform.
> > > > 
> > > > Which means that once the wmi-to-hid driver specific to this device is
> > > > built in the kernel, you can adjust your LampArray implementation (the
> > > > device specifics micrometers and what not) from usersapce.
> > > > 
> > > > > > > > Even if it is,
> > > > > > > > 
> > > > > > > > b) The emulation should go to generic layer, it is not specific to
> > > > > > > > your hardware.
> > > > > > Well, there is not so much about an emulation here. It's a different way
> > > > > > of presenting the information.
> > > > > > But given that HID LampArray is a HID standard, userspace is able to
> > > > > > implement it once for all the operating systems, which is why this is so
> > > > > > appealing for them. For reference, we have the same issue with SDL and
> > > > > > Steam regarding advanced game controller: they very much prefer to
> > > > > > directly use HID(raw) to talk to the device instead of having a Linux
> > > > > > specific interface.
> > > > > > 
> > > > > > Also, starting with v6.12, systemd (logind) will be able to provide
> > > > > > hidraw node access to non root applications (in the same way you can
> > > > > > request an input evdev node). So HID LampArray makes a lot of sense IMO.
> > > > > > 
> > > > > > > Maybe introducing a misc-device which provides an ioctl-based API similar
> > > > > > > to the HID LampArray would be a solution?
> > > > > > > 
> > > > > > > Basically we would need:
> > > > > > > - ioctl for querying the supported LEDs and their properties
> > > > > > > - ioctl for enabling/disabling autonomous mode
> > > > > > > - ioctl for updating a range of LEDs
> > > > > > > - ioctl for updating multiple LEDs at once
> > > > > > You'll definitely get the API wrong at first, then you'll need to adapt
> > > > > > for a new device, extend it, etc... But then, you'll depend on one
> > > > > > userspace application that can talk to your custom ioctls, because cross
> > > > > > platform applications will have to implement LampArray, and they'ĺl
> > > > > > probably skip your custom ioctls. And once that userspace application is
> > > > > > gone, you'll still have to maintain this forever.
> > > > > > 
> > > > > > Also, the application needs to have root access to that misc device, or
> > > > > > you need to add extra support for it in systemd...
> > > > > > 
> > > > > > > If we implement this as a separate subsystem ("illumination subsystem"), then different
> > > > > > > drivers could use this. This would also allow us to add additional ioctl calls later
> > > > > > > for more features.
> > > > > > Again, I strongly advise against this.
> > > > > > 
> > > > > > I'll just reiterate what makes the more sense to me:
> > > > > > - provide a thin wmi-to-hid layer that creates a normal regular HID
> > > > > >      device from your device (could be using vendor collections)
> > > > > This is what this driver tries to be.
> > > > Except that your current implementation also does the LampArray
> > > > conversion. I think it'll make more sense to provide an almost raw
> > > > access to the underlying protocol (think of it like your own Tuxedo
> > > > vendor collection in HID), and handle the LampArray weirdeness in bpf:
> > > > definition of the device physicals, conversion from HID LampArray
> > > > commands into Tuxedo specifics.
> > > > 
> > > > > > - deal with the LampArray bits in the HID stack, that we can reuse for
> > > > > >      other devices (I was planing on getting there for my Corsair and
> > > > > >      Logitech keyboads).
> > > > > If a greater efford in the hid stack is planed here i would be all for it.
> > > > That's what makes more sense to me at least. Other operating systems
> > > > export the HID nodes directly, so userspace prefers to talk to the
> > > > device directly. So I'd rather rely on a standard than trying to fit the
> > > > current use case in a new interface that will probably fail.
> > > > 
> > > > > On my todolist i would try to integrate the leds subsystem with the
> > > > > LampArray interface next, just a simple implementation treating the whole
> > > > > keyboard as a single led.
> > > > That could be done in HID-core as well. Making it part of HID-core also
> > > > means that once we get an actual LampArray device, we'll get support for
> > > > it from day one.
> > > > 
> > > > > > - Meanwhile, while prototyping the LampArray support in userspace and
> > > > > >      kernelspace, make use of HID-BPF to transform your vendor protocol
> > > > > >      into LampArray. This will allow to fix things without having to
> > > > > >      support them forever. This is why HID-BPF exists: so we can create
> > > > > >      crazy but safe kernel interfaces, without having to support them
> > > > > >      forever.
> > > > > I guess i have to do some readup xD.
> > > > > 
> > > > Please have a look at the tutorial[0]. That tutorial is missing the
> > > > couple of new hooks you'll need to change the requests emitted from
> > > > hidraw as LampArray into Tuxedo, but I can also give you a help into
> > > > making it happening.
> > > > 
> > > > Basically, you also need to define a .hid_hw_request callback in your
> > > > HID_BPF_OPS and extract all of the code you have here into that bpf
> > > > program (which is roughly C code).
> > > > 
> > > > Cheers,
> > > > Benjamin
> > > > 
> > > > 
> > > > [0] https://libevdev.pages.freedesktop.org/udev-hid-bpf/tutorial.html
> > > > 
> > > 2 question left on my side:
> > > 
> > > - Does the BPF approach have performance/latency impact?
> > Not anything you'll notice. BPF is used in network on much more
> > demanding latency purposes. And IIRC, jumping into BPF is almost a no-op
> > nowadays. From what I can tell from the BPF maintainer in his ALPSS
> > presentation last week:
> > "
> > BPF C code is compiled into BPF ISA with BPF calling convention,
> > JIT translate BPF ISA into native ISA,
> > One to one mapping of BPF registers to x86 registers.
> > "
> Ok
> > 
> > > - Does it work during boot? (e.g. early control via the leds subsystem to
> > > stop firmware induced rainbow puke)
> > > 
> > Nope. It gets loaded once udev enumerates the device, so unless you
> > craft a special intird with both the loader and the bpf object it is
> > not.
> > 
> > However, if that rainbow is bothering you, you can "initialize" the
> > keyboard to a sane state with your WMI-to-HID driver before exposing the
> > device to HID.
> Thinking about it, maybe it's not to bad that it only changes once udev is
> ready, like this udev could decide if leds should be used or if it should
> directly be passed to OpenRGB for example, giving at least some consistency
> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
> setting->OpenRGB setting.

That would work if OpenRGB gets to ship the LampArray bpf object (not
saying that it should). Because if OpenRGB is not installed, you'll get
a led class device, and if/when OpenRGB is installed, full LampArray
would be presented.

But anyway, BPF allows to dynamically change the behaviour of the
device, so that's IMO one bonus point of it.

> > 
> > FWIW, the use of BPF only allows you to not corner yourself. If you
> > failed at your LampArray implementation, you'll have to deal with it
> > forever-ish. So it's perfectly sensible to use BPF as an intermediate step
> > where you develop both userspace and kernel space and then convert back
> > the BPF into a proper HID driver.
> 
> I don't really see this point: The LampArray API is defined by the HID Usage
> Table and the report descriptor, so there is not API to mess up and
> everything else has to be parsed dynamically by userspace anyway, so it can
> easily be changed and userspace just adopts automatically.
> 
> And for this case the proper HID driver is already ready.

Yeah, except we don't have the fallback LED class. If you are confident
enough with your implementation, then maybe yes we can include it as a
driver from day one, but that looks like looking for troubles from my
point of view.

After a second look at the LampArray code here... Aren't you forgetting
the to/from CPU conversions in case you are on a little endian system?

> 
> So the only point for me currently is: Is it ok to have key position/usage
> description tables in the kernel driver or not?

good question :)

I would say, probably not in the WMI driver itself. I would rather have
a hid-tuxedo.c HID driver that does that. But even there, we already had
Linus complaining once regarding the report descriptors we sometimes
insert in drivers, which are looking like opaque blobs. So it might not be
the best either.

Sorry I don't have a clear yes/no answer.

Cheers,
Benjamin

> 
> > 
> > Being able to develop a kernel driver without having to reboot and
> > being sure you won't crash your kernel is a game changer ;)
> > 
> > Cheers,
> > Benjamin
Werner Sembach Oct. 1, 2024, 12:23 p.m. UTC | #22
(sorry resend because thunderbird made it a html mail)

Hi,

Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
> On Sep 30 2024, Werner Sembach wrote:
>> [...]
>> Thinking about it, maybe it's not to bad that it only changes once udev is
>> ready, like this udev could decide if leds should be used or if it should
>> directly be passed to OpenRGB for example, giving at least some consistency
>> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
>> setting->OpenRGB setting.
> That would work if OpenRGB gets to ship the LampArray bpf object (not
> saying that it should). Because if OpenRGB is not installed, you'll get
> a led class device, and if/when OpenRGB is installed, full LampArray
> would be presented.

The idea in my head is still that there is some kind of sysfs switch to 
enable/disable leds.

My idea is then that a udev rule shipped with OpenRGB sets this switch to 
disable before loading the BPF driver so leds never get initialized for the 
final LampArray device.

> But anyway, BPF allows to dynamically change the behaviour of the
> device, so that's IMO one bonus point of it.
>
>>> FWIW, the use of BPF only allows you to not corner yourself. If you
>>> failed at your LampArray implementation, you'll have to deal with it
>>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step
>>> where you develop both userspace and kernel space and then convert back
>>> the BPF into a proper HID driver.
>> I don't really see this point: The LampArray API is defined by the HID Usage
>> Table and the report descriptor, so there is not API to mess up and
>> everything else has to be parsed dynamically by userspace anyway, so it can
>> easily be changed and userspace just adopts automatically.
>>
>> And for this case the proper HID driver is already ready.
> Yeah, except we don't have the fallback LED class. If you are confident
> enough with your implementation, then maybe yes we can include it as a
> driver from day one, but that looks like looking for troubles from my
> point of view.

To be on the safe side that we don't talk about different things: My current 
plan is that the leds subsystem builds on top of the LampArray implementation.

Like this the leds part has to be only implemented once for all LampArray 
devices be it emulated via a driver or native via firmware in the device itself.

And I feel confident that the UAPI should be that the userspace gets a hidraw 
device with a LampArray HID descriptor, and every thing else is, by the HID 
spec, dynamic anyway so I can still change my mind in implementation specifics 
there, can't I?

> After a second look at the LampArray code here... Aren't you forgetting
> the to/from CPU conversions in case you are on a little endian system?
Since this driver is for built in keyboards of x86 notebooks it isn't required 
or is it?
>> So the only point for me currently is: Is it ok to have key position/usage
>> description tables in the kernel driver or not?
> good question :)
>
> I would say, probably not in the WMI driver itself. I would rather have
> a hid-tuxedo.c HID driver that does that. But even there, we already had
> Linus complaining once regarding the report descriptors we sometimes
> insert in drivers, which are looking like opaque blobs. So it might not be
> the best either.
Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like hid-tuxedo.c? 
or should it be a separate file with just the arrays?
> Sorry I don't have a clear yes/no answer.

Hm... Well if it's no problem I would keep the current implementation with minor 
adjustments because, like i described above, I don't see a benefit now that this 
already works to rewrite it in BPF again.

If it is a problem then i don't see another way then to rewrite it in BPF.

Note: For future devices there might be more keyboard layouts added, basically 
every time the chassis form factor changes.

> Cheers,
> Benjamin
To sum up the architechture (not mutally exclusive technically)

/- leds
WMI <- WMI to LampArray Kernel driver <-switch-|
                                                \- OpenRGB

/- leds
WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF 
driver<-switch-|
\- OpenRGB

With the "switch" and "leds" implemented in hid core, automatically initialized 
every time a LampArray device pops up (regardless if it is from native firmware, 
a bpf driver or a kernel driver)

Writing this down I think it was never decided how the switch should look like:

It should not be a sysfs attribute of the leds device as the leds device should 
disappear when the switch is set away from it, but should it be a sysfs variable 
of the hid device? This would mean that hid core needs to add that switch 
variable to every hid device having a LampArray section in the descriptor.

>>> Being able to develop a kernel driver without having to reboot and
>>> being sure you won't crash your kernel is a game changer ;)
>>>
>>> Cheers,
>>> Benjamin

Best regards and sorry for the many questions,

Werner Sembach

PS: on a side node: How does hid core handle HID devices with a broken HID 
implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid 
device get reinitialized by hid core once the bpf driver got loaded? If yes, is 
there a way to avoid side effects by this double initialization or is there a 
way to avoid this double initialization, like marking the device id as broken so 
that hid core- does not initialize it unless it's fixed by bpf?
Werner Sembach Oct. 1, 2024, 12:28 p.m. UTC | #23
Hi again,

Am 01.10.24 um 14:23 schrieb Werner Sembach:
> (sorry resend because thunderbird made it a html mail)
>
> Hi,
>
> Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
>> On Sep 30 2024, Werner Sembach wrote:
>>> [...]
>>> Thinking about it, maybe it's not to bad that it only changes once udev is
>>> ready, like this udev could decide if leds should be used or if it should
>>> directly be passed to OpenRGB for example, giving at least some consistency
>>> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
>>> setting->OpenRGB setting.
>> That would work if OpenRGB gets to ship the LampArray bpf object (not
>> saying that it should). Because if OpenRGB is not installed, you'll get
>> a led class device, and if/when OpenRGB is installed, full LampArray
>> would be presented.
>
> The idea in my head is still that there is some kind of sysfs switch to 
> enable/disable leds.
>
> My idea is then that a udev rule shipped with OpenRGB sets this switch to 
> disable before loading the BPF driver so leds never get initialized for the 
> final LampArray device.
>
>> But anyway, BPF allows to dynamically change the behaviour of the
>> device, so that's IMO one bonus point of it.
>>
>>>> FWIW, the use of BPF only allows you to not corner yourself. If you
>>>> failed at your LampArray implementation, you'll have to deal with it
>>>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step
>>>> where you develop both userspace and kernel space and then convert back
>>>> the BPF into a proper HID driver.
>>> I don't really see this point: The LampArray API is defined by the HID Usage
>>> Table and the report descriptor, so there is not API to mess up and
>>> everything else has to be parsed dynamically by userspace anyway, so it can
>>> easily be changed and userspace just adopts automatically.
>>>
>>> And for this case the proper HID driver is already ready.
>> Yeah, except we don't have the fallback LED class. If you are confident
>> enough with your implementation, then maybe yes we can include it as a
>> driver from day one, but that looks like looking for troubles from my
>> point of view.
>
> To be on the safe side that we don't talk about different things: My current 
> plan is that the leds subsystem builds on top of the LampArray implementation.
>
> Like this the leds part has to be only implemented once for all LampArray 
> devices be it emulated via a driver or native via firmware in the device itself.
>
> And I feel confident that the UAPI should be that the userspace gets a hidraw 
> device with a LampArray HID descriptor, and every thing else is, by the HID 
> spec, dynamic anyway so I can still change my mind in implementation specifics 
> there, can't I?
>
>> After a second look at the LampArray code here... Aren't you forgetting
>> the to/from CPU conversions in case you are on a little endian system?
> Since this driver is for built in keyboards of x86 notebooks it isn't required 
> or is it?
>>> So the only point for me currently is: Is it ok to have key position/usage
>>> description tables in the kernel driver or not?
>> good question :)
>>
>> I would say, probably not in the WMI driver itself. I would rather have
>> a hid-tuxedo.c HID driver that does that. But even there, we already had
>> Linus complaining once regarding the report descriptors we sometimes
>> insert in drivers, which are looking like opaque blobs. So it might not be
>> the best either.
> Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like hid-tuxedo.c? 
> or should it be a separate file with just the arrays?
>> Sorry I don't have a clear yes/no answer.
>
> Hm... Well if it's no problem I would keep the current implementation with 
> minor adjustments because, like i described above, I don't see a benefit now 
> that this already works to rewrite it in BPF again.
>
> If it is a problem then i don't see another way then to rewrite it in BPF.
>
> Note: For future devices there might be more keyboard layouts added, basically 
> every time the chassis form factor changes.
>
>> Cheers,
>> Benjamin
> To sum up the architechture (not mutally exclusive technically)
>
> /- leds
> WMI <- WMI to LampArray Kernel driver <-switch-|
>                                                \- OpenRGB
>
> /- leds
> WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF 
> driver<-switch-|
> \- OpenRGB

ups my ascii art formatting got botched, the switch decides between "leds" and 
"OpenRGB" was what I wanted to visualize

Regards,

Werner

>
> With the "switch" and "leds" implemented in hid core, automatically 
> initialized every time a LampArray device pops up (regardless if it is from 
> native firmware, a bpf driver or a kernel driver)
>
> Writing this down I think it was never decided how the switch should look like:
>
> It should not be a sysfs attribute of the leds device as the leds device 
> should disappear when the switch is set away from it, but should it be a sysfs 
> variable of the hid device? This would mean that hid core needs to add that 
> switch variable to every hid device having a LampArray section in the descriptor.
>
>>>> Being able to develop a kernel driver without having to reboot and
>>>> being sure you won't crash your kernel is a game changer ;)
>>>>
>>>> Cheers,
>>>> Benjamin
>
> Best regards and sorry for the many questions,
>
> Werner Sembach
>
> PS: on a side node: How does hid core handle HID devices with a broken HID 
> implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid 
> device get reinitialized by hid core once the bpf driver got loaded? If yes, 
> is there a way to avoid side effects by this double initialization or is there 
> a way to avoid this double initialization, like marking the device id as 
> broken so that hid core- does not initialize it unless it's fixed by bpf?
>
Benjamin Tissoires Oct. 1, 2024, 1:41 p.m. UTC | #24
On Oct 01 2024, Werner Sembach wrote:
> (sorry resend because thunderbird made it a html mail)
> 
> Hi,
> 
> Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
> > On Sep 30 2024, Werner Sembach wrote:
> > > [...]
> > > Thinking about it, maybe it's not to bad that it only changes once udev is
> > > ready, like this udev could decide if leds should be used or if it should
> > > directly be passed to OpenRGB for example, giving at least some consistency
> > > only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
> > > setting->OpenRGB setting.
> > That would work if OpenRGB gets to ship the LampArray bpf object (not
> > saying that it should). Because if OpenRGB is not installed, you'll get
> > a led class device, and if/when OpenRGB is installed, full LampArray
> > would be presented.
> 
> The idea in my head is still that there is some kind of sysfs switch to
> enable/disable leds.

FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed
without possibility to change them...

> 
> My idea is then that a udev rule shipped with OpenRGB sets this switch to
> disable before loading the BPF driver so leds never get initialized for the
> final LampArray device.

FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So
basically we can have a udev property set (or not) by openrgb which
makes the BPF program decide whether to present the keyboard as
LampArray or not.

> 
> > But anyway, BPF allows to dynamically change the behaviour of the
> > device, so that's IMO one bonus point of it.
> > 
> > > > FWIW, the use of BPF only allows you to not corner yourself. If you
> > > > failed at your LampArray implementation, you'll have to deal with it
> > > > forever-ish. So it's perfectly sensible to use BPF as an intermediate step
> > > > where you develop both userspace and kernel space and then convert back
> > > > the BPF into a proper HID driver.
> > > I don't really see this point: The LampArray API is defined by the HID Usage
> > > Table and the report descriptor, so there is not API to mess up and
> > > everything else has to be parsed dynamically by userspace anyway, so it can
> > > easily be changed and userspace just adopts automatically.
> > > 
> > > And for this case the proper HID driver is already ready.
> > Yeah, except we don't have the fallback LED class. If you are confident
> > enough with your implementation, then maybe yes we can include it as a
> > driver from day one, but that looks like looking for troubles from my
> > point of view.
> 
> To be on the safe side that we don't talk about different things: My current
> plan is that the leds subsystem builds on top of the LampArray
> implementation.

I would say that the HID subsystem knows how to translate LampArray into
a subset of LEDs. But I think that's what you are saying.

> 
> Like this the leds part has to be only implemented once for all LampArray
> devices be it emulated via a driver or native via firmware in the device
> itself.

yep, that's the plan. However, not sure how to fit LampArray into LED.

> 
> And I feel confident that the UAPI should be that the userspace gets a
> hidraw device with a LampArray HID descriptor, and every thing else is, by
> the HID spec, dynamic anyway so I can still change my mind in implementation
> specifics there, can't I?

Yeah... I think?

From my point of view we are just bikeshedding on to where put that
"firmware" extension, in WMI, in HID (kernel with a subdriver), or
externally in BPF.

> 
> > After a second look at the LampArray code here... Aren't you forgetting
> > the to/from CPU conversions in case you are on a little endian system?
> Since this driver is for built in keyboards of x86 notebooks it isn't
> required or is it?

Good point. Let's just hope you don't start shipping a LE laptop with
the same keyboard hardware :)

> > > So the only point for me currently is: Is it ok to have key position/usage
> > > description tables in the kernel driver or not?
> > good question :)
> > 
> > I would say, probably not in the WMI driver itself. I would rather have
> > a hid-tuxedo.c HID driver that does that. But even there, we already had
> > Linus complaining once regarding the report descriptors we sometimes
> > insert in drivers, which are looking like opaque blobs. So it might not be
> > the best either.
> Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like
> hid-tuxedo.c? or should it be a separate file with just the arrays?

It is, in a way. I think it's more a question for Hans and the other
platform maintainers, whether they would accept this.

> > Sorry I don't have a clear yes/no answer.
> 
> Hm... Well if it's no problem I would keep the current implementation with
> minor adjustments because, like i described above, I don't see a benefit now
> that this already works to rewrite it in BPF again.
> 
> If it is a problem then i don't see another way then to rewrite it in BPF.
> 
> Note: For future devices there might be more keyboard layouts added,
> basically every time the chassis form factor changes.

If the WMI part doesn't change, then maybe having BPF would be easier
for you in the future. Adding a HID-BPF file would cost basically
nothing, and it'll be out of band with the kernel, meaning you can ship
it in already running kernels (assuming the same WMI driver doesn't need
any updates).

> 
> > Cheers,
> > Benjamin
> To sum up the architechture (not mutally exclusive technically)
> 
> /- leds
> WMI <- WMI to LampArray Kernel driver <-switch-|
>                                                \- OpenRGB
> 
> /- leds
> WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF
> driver<-switch-|
> \- OpenRGB
> 
> With the "switch" and "leds" implemented in hid core, automatically
> initialized every time a LampArray device pops up (regardless if it is from
> native firmware, a bpf driver or a kernel driver)
> 
> Writing this down I think it was never decided how the switch should look like:
> 
> It should not be a sysfs attribute of the leds device as the leds device
> should disappear when the switch is set away from it, but should it be a
> sysfs variable of the hid device? This would mean that hid core needs to add
> that switch variable to every hid device having a LampArray section in the
> descriptor.

Again, not a big fan of the sysfs, because it's UAPI and need root to
trigger it (though the udev rule sort this one out).
BPF allows already to re-enumerate the device with a different look and
feel, so it seems more appropriate to me.

Also, having a sysfs that depends on the report descriptor basically
means that we will lose it whenever we re-enumerate it (kind of what the
LED problem you mentioned above). So we would need to have a sysfs on
*every* HID devices???

Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos
HW):
- a device presents a report descriptor with LampArray (wherever it
  comes from)
- hid-led.c takes over it (assuming we extend it for LampArray), and
  creates a few LEDs based on the Input usage (one global rgb color for
  regular keys, another one for the few other LEDs known to userspace)
- when openRGB is present (special udev property), a BPF program is
  inserted that only contains a report descriptor fixup that prevent the
  use of hid-led.c
- the device gets re-enumerated, cleaning the in-kernel leds, and only
  present the LampArray through hidraw, waiting for OpenRGB to take
  over.
- at any time we can remove the BPF and restore the LEDs functionality
  of hid-led.c

> 
> > > > Being able to develop a kernel driver without having to reboot and
> > > > being sure you won't crash your kernel is a game changer ;)
> > > > 
> > > > Cheers,
> > > > Benjamin
> 
> Best regards and sorry for the many questions,
> 
> Werner Sembach
> 
> PS: on a side node: How does hid core handle HID devices with a broken HID
> implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid
> device get reinitialized by hid core once the bpf driver got loaded? If yes,
> is there a way to avoid side effects by this double initialization or is
> there a way to avoid this double initialization, like marking the device id
> as broken so that hid core- does not initialize it unless it's fixed by bpf?

- broken HID device:
  it depends on what you call "broken" HID device. If the report
  descriptor is boggus, hid-core will reject the device and will not
  present it to user space (by returning -EINVAL).
  If the device is sensible in terms of HID protocol, it will present it
  to userspace, but the fact that it creates an input node or LED or
  whatever just depends on what is inside the report descriptor.

- HID-BPF:
  HID-BPF is inserted between the device itself and the rest of the
  in-kernel HID stack.
  Whenever you load and attach (or detach) a BPF program which has a
  report descriptor fixup, HID-core will reconnect the device,
  re-enumerate it (calling ->probe()), and will re-present it to
  userspace as if it were a new device (you get all uevents).

- double initialization:
  nowadays hid-generic doesn't do anything on the device itself except
  calling the powerup/powerdown, by calling ->start and ->stop on the
  HID transport driver. It's not a problem on 99% of the devices AFAICT.
  technically, if the report descriptor is bogus, start/stop won't be
  called, but you'll get an error in the dmesg. So if you really want to
  rely on that "broken" scenario we can always add a specific quirk in
  HID to not spew that error.

Cheers,
Benjamin

PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
all of the requirements here:
- need to be dynamic
- still unsure of the userspace implementation, meaning that userspace
  might do something wrong, which might require kernel changes
- possibility to extend later the kernel API
- lots of fun :)
Armin Wolf Oct. 1, 2024, 4:45 p.m. UTC | #25
Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:

> On Oct 01 2024, Werner Sembach wrote:
>> (sorry resend because thunderbird made it a html mail)
>>
>> Hi,
>>
>> Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
>>> On Sep 30 2024, Werner Sembach wrote:
>>>> [...]
>>>> Thinking about it, maybe it's not to bad that it only changes once udev is
>>>> ready, like this udev could decide if leds should be used or if it should
>>>> directly be passed to OpenRGB for example, giving at least some consistency
>>>> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
>>>> setting->OpenRGB setting.
>>> That would work if OpenRGB gets to ship the LampArray bpf object (not
>>> saying that it should). Because if OpenRGB is not installed, you'll get
>>> a led class device, and if/when OpenRGB is installed, full LampArray
>>> would be presented.
>> The idea in my head is still that there is some kind of sysfs switch to
>> enable/disable leds.
> FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed
> without possibility to change them...

Why not having a simple led driver for HID LampArray devices which exposes the
whole LampArray as a single LED?

If userspace wants to have direct control over the underlying LampArray device,
it just needs to unbind the default driver (maybe udev can be useful here?).

>> My idea is then that a udev rule shipped with OpenRGB sets this switch to
>> disable before loading the BPF driver so leds never get initialized for the
>> final LampArray device.
> FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So
> basically we can have a udev property set (or not) by openrgb which
> makes the BPF program decide whether to present the keyboard as
> LampArray or not.

I do not think that using HID-BPF makes sense here, since the underlying HID device
is already purely virtual.

Using HID-BPF on top of the already virtual HID device would be a bit strange.

>>> But anyway, BPF allows to dynamically change the behaviour of the
>>> device, so that's IMO one bonus point of it.
>>>
>>>>> FWIW, the use of BPF only allows you to not corner yourself. If you
>>>>> failed at your LampArray implementation, you'll have to deal with it
>>>>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step
>>>>> where you develop both userspace and kernel space and then convert back
>>>>> the BPF into a proper HID driver.
>>>> I don't really see this point: The LampArray API is defined by the HID Usage
>>>> Table and the report descriptor, so there is not API to mess up and
>>>> everything else has to be parsed dynamically by userspace anyway, so it can
>>>> easily be changed and userspace just adopts automatically.
>>>>
>>>> And for this case the proper HID driver is already ready.
>>> Yeah, except we don't have the fallback LED class. If you are confident
>>> enough with your implementation, then maybe yes we can include it as a
>>> driver from day one, but that looks like looking for troubles from my
>>> point of view.
>> To be on the safe side that we don't talk about different things: My current
>> plan is that the leds subsystem builds on top of the LampArray
>> implementation.
> I would say that the HID subsystem knows how to translate LampArray into
> a subset of LEDs. But I think that's what you are saying.
>
>> Like this the leds part has to be only implemented once for all LampArray
>> devices be it emulated via a driver or native via firmware in the device
>> itself.
> yep, that's the plan. However, not sure how to fit LampArray into LED.

Maybe the LED driver can present the whole LampArry as a single RGB LED. This
might be enough for basic LED control (on/off, changing color, ...).

For advanced LED control (effects, large number of LEDs, ...) userspace can just
unbind the default LED driver and use hidraw to drive the LampArray themself.

>> And I feel confident that the UAPI should be that the userspace gets a
>> hidraw device with a LampArray HID descriptor, and every thing else is, by
>> the HID spec, dynamic anyway so I can still change my mind in implementation
>> specifics there, can't I?
> Yeah... I think?
>
>  From my point of view we are just bikeshedding on to where put that
> "firmware" extension, in WMI, in HID (kernel with a subdriver), or
> externally in BPF.
>
Just a insane idea: can Tuxedo change the ACPI code supplied by the BIOS?

>>> After a second look at the LampArray code here... Aren't you forgetting
>>> the to/from CPU conversions in case you are on a little endian system?
>> Since this driver is for built in keyboards of x86 notebooks it isn't
>> required or is it?
> Good point. Let's just hope you don't start shipping a LE laptop with
> the same keyboard hardware :)

I believe we should do those CPU conversions regardless, so future driver developers
have good examples to copy from.

>>>> So the only point for me currently is: Is it ok to have key position/usage
>>>> description tables in the kernel driver or not?
>>> good question :)
>>>
>>> I would say, probably not in the WMI driver itself. I would rather have
>>> a hid-tuxedo.c HID driver that does that. But even there, we already had
>>> Linus complaining once regarding the report descriptors we sometimes
>>> insert in drivers, which are looking like opaque blobs. So it might not be
>>> the best either.
>> Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like
>> hid-tuxedo.c? or should it be a separate file with just the arrays?
> It is, in a way. I think it's more a question for Hans and the other
> platform maintainers, whether they would accept this.

Is there a possibility to query the physical keyboard layout using the WMI interface?

>>> Sorry I don't have a clear yes/no answer.
>> Hm... Well if it's no problem I would keep the current implementation with
>> minor adjustments because, like i described above, I don't see a benefit now
>> that this already works to rewrite it in BPF again.
>>
>> If it is a problem then i don't see another way then to rewrite it in BPF.
>>
>> Note: For future devices there might be more keyboard layouts added,
>> basically every time the chassis form factor changes.
> If the WMI part doesn't change, then maybe having BPF would be easier
> for you in the future. Adding a HID-BPF file would cost basically
> nothing, and it'll be out of band with the kernel, meaning you can ship
> it in already running kernels (assuming the same WMI driver doesn't need
> any updates).
>
>>> Cheers,
>>> Benjamin
>> To sum up the architechture (not mutally exclusive technically)
>>
>> /- leds
>> WMI <- WMI to LampArray Kernel driver <-switch-|
>>                                                 \- OpenRGB
>>
>> /- leds
>> WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF
>> driver<-switch-|
>> \- OpenRGB
>>
>> With the "switch" and "leds" implemented in hid core, automatically
>> initialized every time a LampArray device pops up (regardless if it is from
>> native firmware, a bpf driver or a kernel driver)
>>
>> Writing this down I think it was never decided how the switch should look like:
>>
>> It should not be a sysfs attribute of the leds device as the leds device
>> should disappear when the switch is set away from it, but should it be a
>> sysfs variable of the hid device? This would mean that hid core needs to add
>> that switch variable to every hid device having a LampArray section in the
>> descriptor.
> Again, not a big fan of the sysfs, because it's UAPI and need root to
> trigger it (though the udev rule sort this one out).
> BPF allows already to re-enumerate the device with a different look and
> feel, so it seems more appropriate to me.
>
> Also, having a sysfs that depends on the report descriptor basically
> means that we will lose it whenever we re-enumerate it (kind of what the
> LED problem you mentioned above). So we would need to have a sysfs on
> *every* HID devices???
>
> Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos
> HW):
> - a device presents a report descriptor with LampArray (wherever it
>    comes from)
> - hid-led.c takes over it (assuming we extend it for LampArray), and
>    creates a few LEDs based on the Input usage (one global rgb color for
>    regular keys, another one for the few other LEDs known to userspace)
> - when openRGB is present (special udev property), a BPF program is
>    inserted that only contains a report descriptor fixup that prevent the
>    use of hid-led.c

Can we just manually unbind the hid-led driver?

> - the device gets re-enumerated, cleaning the in-kernel leds, and only
>    present the LampArray through hidraw, waiting for OpenRGB to take
>    over.
> - at any time we can remove the BPF and restore the LEDs functionality
>    of hid-led.c
>
>>>>> Being able to develop a kernel driver without having to reboot and
>>>>> being sure you won't crash your kernel is a game changer ;)
>>>>>
>>>>> Cheers,
>>>>> Benjamin
>> Best regards and sorry for the many questions,
>>
>> Werner Sembach
>>
>> PS: on a side node: How does hid core handle HID devices with a broken HID
>> implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid
>> device get reinitialized by hid core once the bpf driver got loaded? If yes,
>> is there a way to avoid side effects by this double initialization or is
>> there a way to avoid this double initialization, like marking the device id
>> as broken so that hid core- does not initialize it unless it's fixed by bpf?
> - broken HID device:
>    it depends on what you call "broken" HID device. If the report
>    descriptor is boggus, hid-core will reject the device and will not
>    present it to user space (by returning -EINVAL).
>    If the device is sensible in terms of HID protocol, it will present it
>    to userspace, but the fact that it creates an input node or LED or
>    whatever just depends on what is inside the report descriptor.
>
> - HID-BPF:
>    HID-BPF is inserted between the device itself and the rest of the
>    in-kernel HID stack.
>    Whenever you load and attach (or detach) a BPF program which has a
>    report descriptor fixup, HID-core will reconnect the device,
>    re-enumerate it (calling ->probe()), and will re-present it to
>    userspace as if it were a new device (you get all uevents).
>
> - double initialization:
>    nowadays hid-generic doesn't do anything on the device itself except
>    calling the powerup/powerdown, by calling ->start and ->stop on the
>    HID transport driver. It's not a problem on 99% of the devices AFAICT.
>    technically, if the report descriptor is bogus, start/stop won't be
>    called, but you'll get an error in the dmesg. So if you really want to
>    rely on that "broken" scenario we can always add a specific quirk in
>    HID to not spew that error.
>
> Cheers,
> Benjamin
>
> PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> all of the requirements here:
> - need to be dynamic
> - still unsure of the userspace implementation, meaning that userspace
>    might do something wrong, which might require kernel changes
> - possibility to extend later the kernel API
> - lots of fun :)
Werner Sembach Oct. 1, 2024, 7:18 p.m. UTC | #26
Hi Benjamin,

Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
> On Oct 01 2024, Werner Sembach wrote:
>> (sorry resend because thunderbird made it a html mail)
>>
>> Hi,
>>
>> Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
>>> On Sep 30 2024, Werner Sembach wrote:
>>>> [...]
>>>> Thinking about it, maybe it's not to bad that it only changes once udev is
>>>> ready, like this udev could decide if leds should be used or if it should
>>>> directly be passed to OpenRGB for example, giving at least some consistency
>>>> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
>>>> setting->OpenRGB setting.
>>> That would work if OpenRGB gets to ship the LampArray bpf object (not
>>> saying that it should). Because if OpenRGB is not installed, you'll get
>>> a led class device, and if/when OpenRGB is installed, full LampArray
>>> would be presented.
>> The idea in my head is still that there is some kind of sysfs switch to
>> enable/disable leds.
> FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed
> without possibility to change them...
>
>> My idea is then that a udev rule shipped with OpenRGB sets this switch to
>> disable before loading the BPF driver so leds never get initialized for the
>> final LampArray device.
> FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So
> basically we can have a udev property set (or not) by openrgb which
> makes the BPF program decide whether to present the keyboard as
> LampArray or not.
>
>>> But anyway, BPF allows to dynamically change the behaviour of the
>>> device, so that's IMO one bonus point of it.
>>>
>>>>> FWIW, the use of BPF only allows you to not corner yourself. If you
>>>>> failed at your LampArray implementation, you'll have to deal with it
>>>>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step
>>>>> where you develop both userspace and kernel space and then convert back
>>>>> the BPF into a proper HID driver.
>>>> I don't really see this point: The LampArray API is defined by the HID Usage
>>>> Table and the report descriptor, so there is not API to mess up and
>>>> everything else has to be parsed dynamically by userspace anyway, so it can
>>>> easily be changed and userspace just adopts automatically.
>>>>
>>>> And for this case the proper HID driver is already ready.
>>> Yeah, except we don't have the fallback LED class. If you are confident
>>> enough with your implementation, then maybe yes we can include it as a
>>> driver from day one, but that looks like looking for troubles from my
>>> point of view.
>> To be on the safe side that we don't talk about different things: My current
>> plan is that the leds subsystem builds on top of the LampArray
>> implementation.
> I would say that the HID subsystem knows how to translate LampArray into
> a subset of LEDs. But I think that's what you are saying.
>
>> Like this the leds part has to be only implemented once for all LampArray
>> devices be it emulated via a driver or native via firmware in the device
>> itself.
> yep, that's the plan. However, not sure how to fit LampArray into LED.

My idea was that all leds just get treated as a singular led only allowing to 
set a singular color and brightness, but I just looked it up again: LampArray 
allows different color and brightness ranges per key, so the grouping might not 
be possible in a sensible way ...

Maybe the leds integration is a bad idea after all and we should just nudge the 
DEs and/or UPower to implement LampArray directly? But that's just kicking the 
complexity down the road, at least as long as there is not universal easy to use 
library (haven't looked into the library build of OpenRGB yet).

>
>> And I feel confident that the UAPI should be that the userspace gets a
>> hidraw device with a LampArray HID descriptor, and every thing else is, by
>> the HID spec, dynamic anyway so I can still change my mind in implementation
>> specifics there, can't I?
> Yeah... I think?
>
>  From my point of view we are just bikeshedding on to where put that
> "firmware" extension, in WMI, in HID (kernel with a subdriver), or
> externally in BPF.
>
>>> After a second look at the LampArray code here... Aren't you forgetting
>>> the to/from CPU conversions in case you are on a little endian system?
>> Since this driver is for built in keyboards of x86 notebooks it isn't
>> required or is it?
> Good point. Let's just hope you don't start shipping a LE laptop with
> the same keyboard hardware :)
Well there is the dmi table in the driver that would prevent issues in this front.
>
>>>> So the only point for me currently is: Is it ok to have key position/usage
>>>> description tables in the kernel driver or not?
>>> good question :)
>>>
>>> I would say, probably not in the WMI driver itself. I would rather have
>>> a hid-tuxedo.c HID driver that does that. But even there, we already had
>>> Linus complaining once regarding the report descriptors we sometimes
>>> insert in drivers, which are looking like opaque blobs. So it might not be
>>> the best either.
>> Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like
>> hid-tuxedo.c? or should it be a separate file with just the arrays?
> It is, in a way. I think it's more a question for Hans and the other
> platform maintainers, whether they would accept this.
>
>>> Sorry I don't have a clear yes/no answer.
>> Hm... Well if it's no problem I would keep the current implementation with
>> minor adjustments because, like i described above, I don't see a benefit now
>> that this already works to rewrite it in BPF again.
>>
>> If it is a problem then i don't see another way then to rewrite it in BPF.
>>
>> Note: For future devices there might be more keyboard layouts added,
>> basically every time the chassis form factor changes.
> If the WMI part doesn't change, then maybe having BPF would be easier
> for you in the future. Adding a HID-BPF file would cost basically
> nothing, and it'll be out of band with the kernel, meaning you can ship
> it in already running kernels (assuming the same WMI driver doesn't need
> any updates).
The WMI part will probably not change, but since we don't write the firmware but 
also just get it as a blob we can't control that. That's why I put the DMI table 
in the driver, so at least an expansion to the DMI table is required every time 
a new device releases.
>
>>> Cheers,
>>> Benjamin
>> To sum up the architechture (not mutally exclusive technically)
>>
>> /- leds
>> WMI <- WMI to LampArray Kernel driver <-switch-|
>>                                                 \- OpenRGB
>>
>> /- leds
>> WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF
>> driver<-switch-|
>> \- OpenRGB
>>
>> With the "switch" and "leds" implemented in hid core, automatically
>> initialized every time a LampArray device pops up (regardless if it is from
>> native firmware, a bpf driver or a kernel driver)
>>
>> Writing this down I think it was never decided how the switch should look like:
>>
>> It should not be a sysfs attribute of the leds device as the leds device
>> should disappear when the switch is set away from it, but should it be a
>> sysfs variable of the hid device? This would mean that hid core needs to add
>> that switch variable to every hid device having a LampArray section in the
>> descriptor.
> Again, not a big fan of the sysfs, because it's UAPI and need root to
> trigger it (though the udev rule sort this one out).
> BPF allows already to re-enumerate the device with a different look and
> feel, so it seems more appropriate to me.
>
> Also, having a sysfs that depends on the report descriptor basically
> means that we will lose it whenever we re-enumerate it (kind of what the
> LED problem you mentioned above). So we would need to have a sysfs on
> *every* HID devices???
>
> Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos
> HW):
> - a device presents a report descriptor with LampArray (wherever it
>    comes from)
> - hid-led.c takes over it (assuming we extend it for LampArray), and
>    creates a few LEDs based on the Input usage (one global rgb color for
>    regular keys, another one for the few other LEDs known to userspace)
> - when openRGB is present (special udev property), a BPF program is
>    inserted that only contains a report descriptor fixup that prevent the
>    use of hid-led.c

How would that look like? just a custom bit in a "Vendor defined" usage page?

But this is still UAPI just hidden inside a BFP program instead of sysfs. But it 
would avoid the re-enumeration problem.

> - the device gets re-enumerated, cleaning the in-kernel leds, and only
>    present the LampArray through hidraw, waiting for OpenRGB to take
>    over.
> - at any time we can remove the BPF and restore the LEDs functionality
>    of hid-led.c
>
>>>>> Being able to develop a kernel driver without having to reboot and
>>>>> being sure you won't crash your kernel is a game changer ;)
>>>>>
>>>>> Cheers,
>>>>> Benjamin
>> Best regards and sorry for the many questions,
>>
>> Werner Sembach
>>
>> PS: on a side node: How does hid core handle HID devices with a broken HID
>> implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid
>> device get reinitialized by hid core once the bpf driver got loaded? If yes,
>> is there a way to avoid side effects by this double initialization or is
>> there a way to avoid this double initialization, like marking the device id
>> as broken so that hid core- does not initialize it unless it's fixed by bpf?
> - broken HID device:
>    it depends on what you call "broken" HID device. If the report
>    descriptor is boggus, hid-core will reject the device and will not
>    present it to user space (by returning -EINVAL).
>    If the device is sensible in terms of HID protocol, it will present it
>    to userspace, but the fact that it creates an input node or LED or
>    whatever just depends on what is inside the report descriptor.
>
> - HID-BPF:
>    HID-BPF is inserted between the device itself and the rest of the
>    in-kernel HID stack.
>    Whenever you load and attach (or detach) a BPF program which has a
>    report descriptor fixup, HID-core will reconnect the device,
>    re-enumerate it (calling ->probe()), and will re-present it to
>    userspace as if it were a new device (you get all uevents).
>
> - double initialization:
>    nowadays hid-generic doesn't do anything on the device itself except
>    calling the powerup/powerdown, by calling ->start and ->stop on the
>    HID transport driver. It's not a problem on 99% of the devices AFAICT.
>    technically, if the report descriptor is bogus, start/stop won't be
>    called, but you'll get an error in the dmesg. So if you really want to
>    rely on that "broken" scenario we can always add a specific quirk in
>    HID to not spew that error.
>
> Cheers,
> Benjamin
>
> PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> all of the requirements here:
> - need to be dynamic
> - still unsure of the userspace implementation, meaning that userspace
>    might do something wrong, which might require kernel changes

Well the reference implementetion for the arduiono macropad from microsoft 
ignores the intensity (brightness) channel on rgb leds contrary to the HID spec, 
soo yeah you have a point here ...

> - possibility to extend later the kernel API
> - lots of fun :)

You advertise it good ;). More work for me now but maybe less work for me later, 
I will look into it.

Best regards,

Werner
Werner Sembach Oct. 1, 2024, 7:32 p.m. UTC | #27
Hi Armin,

Am 01.10.24 um 18:45 schrieb Armin Wolf:
> Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
>
>> On Oct 01 2024, Werner Sembach wrote:
>>> (sorry resend because thunderbird made it a html mail)
>>>
>>> Hi,
>>>
>>> Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
>>>> On Sep 30 2024, Werner Sembach wrote:
>>>>> [...]
>>>>> Thinking about it, maybe it's not to bad that it only changes once udev is
>>>>> ready, like this udev could decide if leds should be used or if it should
>>>>> directly be passed to OpenRGB for example, giving at least some consistency
>>>>> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
>>>>> setting->OpenRGB setting.
>>>> That would work if OpenRGB gets to ship the LampArray bpf object (not
>>>> saying that it should). Because if OpenRGB is not installed, you'll get
>>>> a led class device, and if/when OpenRGB is installed, full LampArray
>>>> would be presented.
>>> The idea in my head is still that there is some kind of sysfs switch to
>>> enable/disable leds.
>> FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed
>> without possibility to change them...
>
> Why not having a simple led driver for HID LampArray devices which exposes the
> whole LampArray as a single LED?
Yes that is my plan, but see my last reply to Benjamin, it might not be trivial 
as different leds in the same LampArray might have different max values for red, 
green, blue, and intensity. And the LampArray spec even allows to mix RGB and 
non-RGB leds.
>
> If userspace wants to have direct control over the underlying LampArray device,
> it just needs to unbind the default driver (maybe udev can be useful here?).
There was something in the last discussion why this might not work, but i can't 
put my finger on it.
>
>>> My idea is then that a udev rule shipped with OpenRGB sets this switch to
>>> disable before loading the BPF driver so leds never get initialized for the
>>> final LampArray device.
>> FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So
>> basically we can have a udev property set (or not) by openrgb which
>> makes the BPF program decide whether to present the keyboard as
>> LampArray or not.
>
> I do not think that using HID-BPF makes sense here, since the underlying HID 
> device
> is already purely virtual.
>
> Using HID-BPF on top of the already virtual HID device would be a bit strange.
>
>>>> But anyway, BPF allows to dynamically change the behaviour of the
>>>> device, so that's IMO one bonus point of it.
>>>>
>>>>>> FWIW, the use of BPF only allows you to not corner yourself. If you
>>>>>> failed at your LampArray implementation, you'll have to deal with it
>>>>>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step
>>>>>> where you develop both userspace and kernel space and then convert back
>>>>>> the BPF into a proper HID driver.
>>>>> I don't really see this point: The LampArray API is defined by the HID Usage
>>>>> Table and the report descriptor, so there is not API to mess up and
>>>>> everything else has to be parsed dynamically by userspace anyway, so it can
>>>>> easily be changed and userspace just adopts automatically.
>>>>>
>>>>> And for this case the proper HID driver is already ready.
>>>> Yeah, except we don't have the fallback LED class. If you are confident
>>>> enough with your implementation, then maybe yes we can include it as a
>>>> driver from day one, but that looks like looking for troubles from my
>>>> point of view.
>>> To be on the safe side that we don't talk about different things: My current
>>> plan is that the leds subsystem builds on top of the LampArray
>>> implementation.
>> I would say that the HID subsystem knows how to translate LampArray into
>> a subset of LEDs. But I think that's what you are saying.
>>
>>> Like this the leds part has to be only implemented once for all LampArray
>>> devices be it emulated via a driver or native via firmware in the device
>>> itself.
>> yep, that's the plan. However, not sure how to fit LampArray into LED.
>
> Maybe the LED driver can present the whole LampArry as a single RGB LED. This
> might be enough for basic LED control (on/off, changing color, ...).
>
> For advanced LED control (effects, large number of LEDs, ...) userspace can just
> unbind the default LED driver and use hidraw to drive the LampArray themself.
>
>>> And I feel confident that the UAPI should be that the userspace gets a
>>> hidraw device with a LampArray HID descriptor, and every thing else is, by
>>> the HID spec, dynamic anyway so I can still change my mind in implementation
>>> specifics there, can't I?
>> Yeah... I think?
>>
>>  From my point of view we are just bikeshedding on to where put that
>> "firmware" extension, in WMI, in HID (kernel with a subdriver), or
>> externally in BPF.
>>
> Just a insane idea: can Tuxedo change the ACPI code supplied by the BIOS?
Sadly not easily, we get the BIOS from the ODMs and don't have the source and 
built tools for it so we need to request each change individually from the ODMs.
>
>>>> After a second look at the LampArray code here... Aren't you forgetting
>>>> the to/from CPU conversions in case you are on a little endian system?
>>> Since this driver is for built in keyboards of x86 notebooks it isn't
>>> required or is it?
>> Good point. Let's just hope you don't start shipping a LE laptop with
>> the same keyboard hardware :)
>
> I believe we should do those CPU conversions regardless, so future driver 
> developers
> have good examples to copy from.
TBH I don't know where to look for an example myself, on how the appropriate 
conversion functions/macros are called xD.
>
>>>>> So the only point for me currently is: Is it ok to have key position/usage
>>>>> description tables in the kernel driver or not?
>>>> good question :)
>>>>
>>>> I would say, probably not in the WMI driver itself. I would rather have
>>>> a hid-tuxedo.c HID driver that does that. But even there, we already had
>>>> Linus complaining once regarding the report descriptors we sometimes
>>>> insert in drivers, which are looking like opaque blobs. So it might not be
>>>> the best either.
>>> Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like
>>> hid-tuxedo.c? or should it be a separate file with just the arrays?
>> It is, in a way. I think it's more a question for Hans and the other
>> platform maintainers, whether they would accept this.
>
> Is there a possibility to query the physical keyboard layout using the WMI 
> interface?

Only if it is ansii or iso layout. The id's for the special keys and the key 
positions are not provided by the firmware.

Best regards,

Werner

>
>>>> Sorry I don't have a clear yes/no answer.
>>> Hm... Well if it's no problem I would keep the current implementation with
>>> minor adjustments because, like i described above, I don't see a benefit now
>>> that this already works to rewrite it in BPF again.
>>>
>>> If it is a problem then i don't see another way then to rewrite it in BPF.
>>>
>>> Note: For future devices there might be more keyboard layouts added,
>>> basically every time the chassis form factor changes.
>> If the WMI part doesn't change, then maybe having BPF would be easier
>> for you in the future. Adding a HID-BPF file would cost basically
>> nothing, and it'll be out of band with the kernel, meaning you can ship
>> it in already running kernels (assuming the same WMI driver doesn't need
>> any updates).
>>
>>>> Cheers,
>>>> Benjamin
>>> To sum up the architechture (not mutally exclusive technically)
>>>
>>> /- leds
>>> WMI <- WMI to LampArray Kernel driver <-switch-|
>>>                                                 \- OpenRGB
>>>
>>> /- leds
>>> WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF
>>> driver<-switch-|
>>> \- OpenRGB
>>>
>>> With the "switch" and "leds" implemented in hid core, automatically
>>> initialized every time a LampArray device pops up (regardless if it is from
>>> native firmware, a bpf driver or a kernel driver)
>>>
>>> Writing this down I think it was never decided how the switch should look like:
>>>
>>> It should not be a sysfs attribute of the leds device as the leds device
>>> should disappear when the switch is set away from it, but should it be a
>>> sysfs variable of the hid device? This would mean that hid core needs to add
>>> that switch variable to every hid device having a LampArray section in the
>>> descriptor.
>> Again, not a big fan of the sysfs, because it's UAPI and need root to
>> trigger it (though the udev rule sort this one out).
>> BPF allows already to re-enumerate the device with a different look and
>> feel, so it seems more appropriate to me.
>>
>> Also, having a sysfs that depends on the report descriptor basically
>> means that we will lose it whenever we re-enumerate it (kind of what the
>> LED problem you mentioned above). So we would need to have a sysfs on
>> *every* HID devices???
>>
>> Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos
>> HW):
>> - a device presents a report descriptor with LampArray (wherever it
>>    comes from)
>> - hid-led.c takes over it (assuming we extend it for LampArray), and
>>    creates a few LEDs based on the Input usage (one global rgb color for
>>    regular keys, another one for the few other LEDs known to userspace)
>> - when openRGB is present (special udev property), a BPF program is
>>    inserted that only contains a report descriptor fixup that prevent the
>>    use of hid-led.c
>
> Can we just manually unbind the hid-led driver?
>
>> - the device gets re-enumerated, cleaning the in-kernel leds, and only
>>    present the LampArray through hidraw, waiting for OpenRGB to take
>>    over.
>> - at any time we can remove the BPF and restore the LEDs functionality
>>    of hid-led.c
>>
>>>>>> Being able to develop a kernel driver without having to reboot and
>>>>>> being sure you won't crash your kernel is a game changer ;)
>>>>>>
>>>>>> Cheers,
>>>>>> Benjamin
>>> Best regards and sorry for the many questions,
>>>
>>> Werner Sembach
>>>
>>> PS: on a side node: How does hid core handle HID devices with a broken HID
>>> implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid
>>> device get reinitialized by hid core once the bpf driver got loaded? If yes,
>>> is there a way to avoid side effects by this double initialization or is
>>> there a way to avoid this double initialization, like marking the device id
>>> as broken so that hid core- does not initialize it unless it's fixed by bpf?
>> - broken HID device:
>>    it depends on what you call "broken" HID device. If the report
>>    descriptor is boggus, hid-core will reject the device and will not
>>    present it to user space (by returning -EINVAL).
>>    If the device is sensible in terms of HID protocol, it will present it
>>    to userspace, but the fact that it creates an input node or LED or
>>    whatever just depends on what is inside the report descriptor.
>>
>> - HID-BPF:
>>    HID-BPF is inserted between the device itself and the rest of the
>>    in-kernel HID stack.
>>    Whenever you load and attach (or detach) a BPF program which has a
>>    report descriptor fixup, HID-core will reconnect the device,
>>    re-enumerate it (calling ->probe()), and will re-present it to
>>    userspace as if it were a new device (you get all uevents).
>>
>> - double initialization:
>>    nowadays hid-generic doesn't do anything on the device itself except
>>    calling the powerup/powerdown, by calling ->start and ->stop on the
>>    HID transport driver. It's not a problem on 99% of the devices AFAICT.
>>    technically, if the report descriptor is bogus, start/stop won't be
>>    called, but you'll get an error in the dmesg. So if you really want to
>>    rely on that "broken" scenario we can always add a specific quirk in
>>    HID to not spew that error.
>>
>> Cheers,
>> Benjamin
>>
>> PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
>> all of the requirements here:
>> - need to be dynamic
>> - still unsure of the userspace implementation, meaning that userspace
>>    might do something wrong, which might require kernel changes
>> - possibility to extend later the kernel API
>> - lots of fun :)
Pavel Machek Oct. 1, 2024, 8:47 p.m. UTC | #28
Hi!

> > > > LampArray HID device and translates the input from hidraw to the
> > > > corresponding WMI calls. This is a new approach as the leds
> > > > subsystem lacks
> > > > a suitable UAPI for per-key keyboard backlights, and like this
> > > > no new UAPI
> > > > needs to be established.
> > > Please don't.
> > > 
> > > a) I don't believe emulating crazy HID interface si right thing to
> > > do. (Ton of magic constants. IIRC it stores key positions with
> > > micrometer accuracy or something that crazy. How is userland going to
> > > use this? Will we update micrometers for every single machine?)
> > > 
> > > Even if it is,
> > > 
> > > b) The emulation should go to generic layer, it is not specific to
> > > your hardware.
> > > 
> > Maybe introducing a misc-device which provides an ioctl-based API similar
> > to the HID LampArray would be a solution?
> > 
> > Basically we would need:
> > - ioctl for querying the supported LEDs and their properties
> > - ioctl for enabling/disabling autonomous mode
> > - ioctl for updating a range of LEDs
> > - ioctl for updating multiple LEDs at once
> > 
> > If we implement this as a separate subsystem ("illumination subsystem"),
> > then different
> > drivers could use this. This would also allow us to add additional ioctl
> > calls later
> > for more features.
> 
> We went over this in the past discussion, the conclusion was iirc that we
> are just wraping hidraw ioctls in other ioctls with no added benefit.

I don't believe that conclusion was widely accepted.

Benefit of doing reasonable interface is ... that kernel would have
reasonable interface. We would get rid of binary tables in the driver,
and long term, we could get something more reasonable than OpenRGB.

> For reference
> https://lore.kernel.org/all/20231011190017.1230898-1-wse@tuxedocomputers.com/
> 
> I don't know the exact message anymore, but if relevant I can dig for it
> (it's a over 5 month long e-mail thread).
> 
> And we would need to write code to apply this wrapper to devices
> implementing LampArray in firmware.

Yes, that would be long term plan.

I bought gaming keyboard to play with rgb leds. I don't really want to
do crazy arrays in the driver as you did below. And I'd really like to
have 100-line application in userland, talking to kernel, not full
OpenRGB which is huge and depends on QT IIRC.

(Work-in-progress version is attached. Note it is smaller than tables
for the fake-HID implementation).

Best regards,
								Pavel


> > > > +
> > > > +static const uint8_t sirius_16_ansii_kbl_mapping[] = {
> > > > +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> > > > +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> > > > +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > > > +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> > > > +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> > > > +    0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
> > > > +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> > > > +    0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
> > > > +    0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
> > > > +    0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
> > > > +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> > > > +    0x4f,                                 0x62, 0x63, 0x58
> > > > +};
> > > > +
> > > > +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
> > > > +     25000,  41700,  58400,  75100,  91800, 108500, 125200,
> > > > 141900, 158600, 175300,
> > > > +    192000, 208700, 225400, 242100, 258800, 275500,   294500,
> > > > 311200, 327900, 344600,
> > > > +     24500,  42500,  61000,  79500,  98000, 116500, 135000,
> > > > 153500, 172000, 190500,
> > > > +    209000, 227500, 246000, 269500,                   294500,
> > > > 311200, 327900, 344600,
> > > > +     31000,  51500,  70000,  88500, 107000, 125500, 144000,
> > > > 162500, 181000, 199500,
> > > > +    218000, 236500, 255000, 273500,                   294500,
> > > > 311200, 327900,
> > > > +     33000,  57000,  75500,  94000, 112500, 131000, 149500,
> > > > 168000, 186500, 205000,
> > > > +    223500, 242000, 267500,                           294500,
> > > > 311200, 327900, 344600,
> > > > +     37000,  66000,  84500, 103000, 121500, 140000, 158500,
> > > > 177000, 195500, 214000,
> > > > +    232500, 251500, 273500,                           294500,
> > > > 311200, 327900,
> > > > +     28000,  47500,  66000,  84500, 140000, 195500, 214000,
> > > > 234000, 255000, 273500,
> > > > +    292000,                                           311200,
> > > > 327900, 344600
> > > > +};
> > > > +
> > > > +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
> > > > +     53000,  53000,  53000,  53000,  53000,  53000,  53000,
> > > > 53000,  53000,  53000,
> > > > +     53000,  53000,  53000,  53000,  53000,  53000, 53000, 
> > > > 53000,  53000,  53000,
> > > > +     67500,  67500,  67500,  67500,  67500,  67500,  67500,
> > > > 67500,  67500,  67500,
> > > > +     67500,  67500,  67500,  67500, 67500,  67500,  67500,  67500,
> > > > +     85500,  85500,  85500,  85500,  85500,  85500,  85500,
> > > > 85500,  85500,  85500,
> > > > +     85500,  85500,  85500,  85500, 85500,  85500,  85500,
> > > > +    103500, 103500, 103500, 103500, 103500, 103500, 103500,
> > > > 103500, 103500, 103500,
> > > > +    103500, 103500, 103500,                           103500,
> > > > 103500, 103500,  94500,
> > > > +    121500, 121500, 121500, 121500, 121500, 121500, 121500,
> > > > 121500, 121500, 121500,
> > > > +    121500, 121500, 129000,                           121500,
> > > > 121500, 121500,
> > > > +    139500, 139500, 139500, 139500, 139500, 139500, 139500,
> > > > 139500, 147000, 147000,
> > > > +    147000,                                           139500,
> > > > 139500, 130500
> > > > +};
> > > > +
> > > > +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
> > > > +      5000,   5000,   5000,   5000,   5000,   5000,   5000,
> > > > 5000,   5000,   5000,
> > > > +      5000,   5000,   5000,   5000,   5000,   5000, 5000,  
> > > > 5000,   5000,   5000,
> > > > +      5250,   5250,   5250,   5250,   5250,   5250,   5250,
> > > > 5250,   5250,   5250,
> > > > +      5250,   5250,   5250,   5250, 5250,   5250,   5250,   5250,
> > > > +      5500,   5500,   5500,   5500,   5500,   5500,   5500,
> > > > 5500,   5500,   5500,
> > > > +      5500,   5500,   5500,   5500, 5500,   5500,   5500,
> > > > +      5750,   5750,   5750,   5750,   5750,   5750,   5750,
> > > > 5750,   5750,   5750,
> > > > +      5750,   5750,   5750, 5750,   5750,   5750,   5625,
> > > > +      6000,   6000,   6000,   6000,   6000,   6000,   6000,
> > > > 6000,   6000,   6000,
> > > > +      6000,   6000,   6125, 6000,   6000,   6000,
> > > > +      6250,   6250,   6250,   6250,   6250,   6250,   6250,
> > > > 6250,   6375,   6375,
> > > > +      6375, 6250,   6250,   6125
> > > > +};
> > > > +
> > > > +static const uint8_t sirius_16_iso_kbl_mapping[] = {
> > > > +    0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
> > > > +    0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
> > > > +    0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
> > > > +    0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
> > > > +    0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
> > > > +    0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
> > > > +    0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
> > > > +    0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
> > > > +    0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
> > > > +    0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
> > > > +    0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
> > > > +    0x4f,                                 0x62, 0x63, 0x58
> > > > +};
> > > > +
> > > > +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
> > > > +     25000,  41700,  58400,  75100,  91800, 108500, 125200,
> > > > 141900, 158600, 175300,
> > > > +    192000, 208700, 225400, 242100, 258800, 275500,   294500,
> > > > 311200, 327900, 344600,
> > > > +     24500,  42500,  61000,  79500,  98000, 116500, 135000,
> > > > 153500, 172000, 190500,
> > > > +    209000, 227500, 246000, 269500,                   294500,
> > > > 311200, 327900, 344600,
> > > > +     31000,  51500,  70000,  88500, 107000, 125500, 144000,
> > > > 162500, 181000, 199500,
> > > > +    218000, 234500, 251000,                           294500,
> > > > 311200, 327900,
> > > > +     33000,  57000,  75500,  94000, 112500, 131000, 149500,
> > > > 168000, 186500, 205000,
> > > > +    223500, 240000, 256500, 271500,                   294500,
> > > > 311200, 327900, 344600,
> > > > +     28000,  47500,  66000,  84500, 103000, 121500, 140000,
> > > > 158500, 177000, 195500,
> > > > +    214000, 232500, 251500, 273500,                   294500,
> > > > 311200, 327900,
> > > > +     28000,  47500,  66000,  84500, 140000, 195500, 214000,
> > > > 234000, 255000, 273500,
> > > > +    292000,                                           311200,
> > > > 327900, 344600
> > > > +};
> > > > +
> > > > +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
> > > > +     53000,  53000,  53000,  53000,  53000,  53000,  53000,
> > > > 53000,  53000,  53000,
> > > > +     53000,  53000,  53000,  53000,  53000,  53000, 53000, 
> > > > 53000,  53000,  53000,
> > > > +     67500,  67500,  67500,  67500,  67500,  67500,  67500,
> > > > 67500,  67500,  67500,
> > > > +     67500,  67500,  67500,  67500, 67500,  67500,  67500,  67500,
> > > > +     85500,  85500,  85500,  85500,  85500,  85500,  85500,
> > > > 85500,  85500,  85500,
> > > > +     85500,  85500,  85500, 85500,  85500,  85500,
> > > > +    103500, 103500, 103500, 103500, 103500, 103500, 103500,
> > > > 103500, 103500, 103500,
> > > > +    103500, 103500, 103500,  94500,                   103500,
> > > > 103500, 103500,  94500,
> > > > +    121500, 121500, 121500, 121500, 121500, 121500, 121500,
> > > > 121500, 121500, 121500,
> > > > +    121500, 121500, 121500, 129000,                   121500,
> > > > 121500, 121500,
> > > > +    139500, 139500, 139500, 139500, 139500, 139500, 139500,
> > > > 139500, 147000, 147000,
> > > > +    147000,                                           139500,
> > > > 139500, 130500
> > > > +};
> > > > +
> > > > +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
> > > > +      5000,   5000,   5000,   5000,   5000,   5000,   5000,
> > > > 5000,   5000,   5000,
> > > > +      5000,   5000,   5000,   5000, 5000, 5000, 5000,   5000,  
> > > > 5000,   5000,
> > > > +      5250,   5250,   5250,   5250,   5250,   5250,   5250,
> > > > 5250,   5250,   5250,
> > > > +      5250,   5250,   5250,   5250, 5250,   5250,   5250,   5250,
> > > > +      5500,   5500,   5500,   5500,   5500,   5500,   5500,
> > > > 5500,   5500,   5500,
> > > > +      5500,   5500,   5500, 5500,   5500,   5500,
> > > > +      5750,   5750,   5750,   5750,   5750,   5750,   5750,
> > > > 5750,   5750,   5750,
> > > > +      5750,   5750,   5750,   5750, 5750,   5750,   5750,   5625,
> > > > +      6000,   6000,   6000,   6000,   6000,   6000,   6000,
> > > > 6000,   6000,   6000,
> > > > +      6000,   6000,   6000,   6125, 6000,   6000,   6000,
> > > > +      6250,   6250,   6250,   6250,   6250,   6250,   6250,
> > > > 6250,   6375,   6375,
> > > > +      6375, 6250,   6250,   6125
> > > > +};
> > > ...
> > > > +
> > > > +static uint8_t report_descriptor[327] = {
> > > > +    0x05, 0x59,            // Usage Page (Lighting and Illumination)
> > > > +    0x09, 0x01,            // Usage (Lamp Array)
> > > > +    0xa1, 0x01,            // Collection (Application)
> > > > +    0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
> > > > +    0x09, 0x02,            //  Usage (Lamp Array Attributes Report)
> > > > +    0xa1, 0x02,            //  Collection (Logical)
> > > > +    0x09, 0x03,            //   Usage (Lamp Count)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
> > > > +    0x75, 0x10,            //   Report Size (16)
> > > > +    0x95, 0x01,            //   Report Count (1)
> > > > +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
> > > > +    0x09, 0x04,            //   Usage (Bounding Box Width In
> > > > Micrometers)
> > > > +    0x09, 0x05,            //   Usage (Bounding Box Height In
> > > > Micrometers)
> > > > +    0x09, 0x06,            //   Usage (Bounding Box Depth In
> > > > Micrometers)
> > > > +    0x09, 0x07,            //   Usage (Lamp Array Kind)
> > > > +    0x09, 0x08,            //   Usage (Min Update Interval In
> > > > Microseconds)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
> > > > +    0x75, 0x20,            //   Report Size (32)
> > > > +    0x95, 0x05,            //   Report Count (5)
> > > > +    0xb1, 0x03,            //   Feature (Cnst,Var,Abs)
> > > > +    0xc0,                //  End Collection
> > > > +    0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
> > > > +    0x09, 0x20,            //  Usage (Lamp Attributes Request Report)
> > > > +    0xa1, 0x02,            //  Collection (Logical)
> > > > +    0x09, 0x21,            //   Usage (Lamp Id)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
> > > > +    0x75, 0x10,            //   Report Size (16)
> > > > +    0x95, 0x01,            //   Report Count (1)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0xc0,                //  End Collection
> > > > +    0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
> > > > +    0x09, 0x22,            //  Usage (Lamp Attributes Response Report)
> > > > +    0xa1, 0x02,            //  Collection (Logical)
> > > > +    0x09, 0x21,            //   Usage (Lamp Id)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
> > > > +    0x75, 0x10,            //   Report Size (16)
> > > > +    0x95, 0x01,            //   Report Count (1)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0x09, 0x23,            //   Usage (Position X In Micrometers)
> > > > +    0x09, 0x24,            //   Usage (Position Y In Micrometers)
> > > > +    0x09, 0x25,            //   Usage (Position Z In Micrometers)
> > > > +    0x09, 0x27,            //   Usage (Update Latency In Microseconds)
> > > > +    0x09, 0x26,            //   Usage (Lamp Purposes)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0xff, 0x7f,    //   Logical Maximum (2147483647)
> > > > +    0x75, 0x20,            //   Report Size (32)
> > > > +    0x95, 0x05,            //   Report Count (5)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0x09, 0x28,            //   Usage (Red Level Count)
> > > > +    0x09, 0x29,            //   Usage (Green Level Count)
> > > > +    0x09, 0x2a,            //   Usage (Blue Level Count)
> > > > +    0x09, 0x2b,            //   Usage (Intensity Level Count)
> > > > +    0x09, 0x2c,            //   Usage (Is Programmable)
> > > > +    0x09, 0x2d,            //   Usage (Input Binding)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
> > > > +    0x75, 0x08,            //   Report Size (8)
> > > > +    0x95, 0x06,            //   Report Count (6)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0xc0,                //  End Collection
> > > > +    0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
> > > > +    0x09, 0x50,            //  Usage (Lamp Multi Update Report)
> > > > +    0xa1, 0x02,            //  Collection (Logical)
> > > > +    0x09, 0x03,            //   Usage (Lamp Count)
> > > > +    0x09, 0x55,            //   Usage (Lamp Update Flags)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x25, 0x08,            //   Logical Maximum (8)
> > > > +    0x75, 0x08,            //   Report Size (8)
> > > > +    0x95, 0x02,            //   Report Count (2)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0x09, 0x21,            //   Usage (Lamp Id)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
> > > > +    0x75, 0x10,            //   Report Size (16)
> > > > +    0x95, 0x08,            //   Report Count (8)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
> > > > +    0x75, 0x08,            //   Report Size (8)
> > > > +    0x95, 0x20,            //   Report Count (32)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0xc0,                //  End Collection
> > > > +    0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
> > > > +    0x09, 0x60,            //  Usage (Lamp Range Update Report)
> > > > +    0xa1, 0x02,            //  Collection (Logical)
> > > > +    0x09, 0x55,            //   Usage (Lamp Update Flags)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x25, 0x08,            //   Logical Maximum (8)
> > > > +    0x75, 0x08,            //   Report Size (8)
> > > > +    0x95, 0x01,            //   Report Count (1)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0x09, 0x61,            //   Usage (Lamp Id Start)
> > > > +    0x09, 0x62,            //   Usage (Lamp Id End)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x27, 0xff, 0xff, 0x00, 0x00,    //   Logical Maximum (65535)
> > > > +    0x75, 0x10,            //   Report Size (16)
> > > > +    0x95, 0x02,            //   Report Count (2)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0x09, 0x51,            //   Usage (Red Update Channel)
> > > > +    0x09, 0x52,            //   Usage (Green Update Channel)
> > > > +    0x09, 0x53,            //   Usage (Blue Update Channel)
> > > > +    0x09, 0x54,            //   Usage (Intensity Update Channel)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x26, 0xff, 0x00,        //   Logical Maximum (255)
> > > > +    0x75, 0x08,            //   Report Size (8)
> > > > +    0x95, 0x04,            //   Report Count (4)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0xc0,                //  End Collection
> > > > +    0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
> > > > +    0x09, 0x70,            //  Usage (Lamp Array Control Report)
> > > > +    0xa1, 0x02,            //  Collection (Logical)
> > > > +    0x09, 0x71,            //   Usage (Autonomous Mode)
> > > > +    0x15, 0x00,            //   Logical Minimum (0)
> > > > +    0x25, 0x01,            //   Logical Maximum (1)
> > > > +    0x75, 0x08,            //   Report Size (8)
> > > > +    0x95, 0x01,            //   Report Count (1)
> > > > +    0xb1, 0x02,            //   Feature (Data,Var,Abs)
> > > > +    0xc0,                //  End Collection
> > > > +    0xc0                // End Collection
> > > > +};
> > > > +
Pavel Machek Oct. 1, 2024, 9:03 p.m. UTC | #29
Hi!

> PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> all of the requirements here:
> - need to be dynamic
> - still unsure of the userspace implementation, meaning that userspace
>   might do something wrong, which might require kernel changes
> - possibility to extend later the kernel API
> - lots of fun :)

Please don't do this.

We have real drivers for framebuffers. We don't make them emulate
USB-display devices.

Yes, this is a small display, and somewhat unusual with weird pixel
positions, but it is common enough that we should have real driver for
that, with real API.

Best regards,
								Pavel
Benjamin Tissoires Oct. 2, 2024, 8:13 a.m. UTC | #30
On Oct 01 2024, Pavel Machek wrote:
> Hi!
> 
> > PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> > all of the requirements here:
> > - need to be dynamic
> > - still unsure of the userspace implementation, meaning that userspace
> >   might do something wrong, which might require kernel changes
> > - possibility to extend later the kernel API
> > - lots of fun :)
> 
> Please don't do this.
> 
> We have real drivers for framebuffers. We don't make them emulate
> USB-display devices.

Except that they are not framebuffer for multiple reasons. I know you
disagree, but the DRM maintainers gave a strong NACK already for a
DRM-like interface, and the auxdisplay would need some sort of tweaking
that is going too far IMO (I'll explain below why I believe this).

> 
> Yes, this is a small display, and somewhat unusual with weird pixel
> positions, but it is common enough that we should have real driver for
> that, with real API.

It's not just weird pixel positions. It's also weird shapes. How are you
going to fit the space bar in a grid, unless you start saying that it
spans accross several pixels. But then users will want to address
individual "grouped" pixels, and you'll end up with a weird API. The
Enter key on non US layouts is also a problem: is it 1 or 2 pixels wide?
Is is 2 pixel in heights?

The positions of the pixels also depend on the physical layout of the
keyboard itself. So with the same vendor ID/Product ID, you might have
different pixel positions if the device is sold in Europe, or in the US.

It's also luminance problem IIRC. Some keys might have a different range
of luminance than others. I remember Bastien Nocera telling me we
already have that issue on some Logitech LEDs (for streaming) where the
maximum brightness value changes depending on the current you can pull
from the USB port. You'll have to find a way to provide that to
userspace as well.

But that's just the "easy" part. We can define a kernel API, for sure,
but then we need users. And there are several problems here:

- first, users of this new kernel API need to be root to address the
  LEDs. They probably won't, so they'll rely on a third party daemon for
  that, or just use uaccess (yay!). But that part is easy
- then, user sees a new kernel interface, and they have to implement it.
  OK, fair enough, but more code to maintain
- but that new kernel API doesn't give enough information: all you have
  is an approximation of the keyboard layout, which will not match the
  reality. So they probably start requiring new things, like offsets on
  each row, pixel width, and so on. Or, and that's the easiest (and what
  we did in libratbag at the time), they'll rely on an external DB of
  SVGs representing the keyboard so they can have a GUI. And yes, people
  who like to configure their keys like to have a GUI.
- then, they somehow manage to have a GUI with the new kernel interface,
  and an approximate representation of the keyboard. Great! But now,
  users want to "react" to key presses, like their proprietary stack do
  on Windows. So they still need to have access to the keys, but not
  necessarily the keymap used in wayland/X, the raw keys. Because if you
  switch your keymap from en-US to dvorak, then your GUI needs to also
  do the reverse mapping.
- but then, even if you make everyones happy, the GUI project is
  actually cross-platform (OpenRGB is, Steam is, SDL is). And what is
  done on Windows is simple: raw access to the HID device. And the raw
  access gives you directly the exact representation of the device, the
  raw keys as they are pressed, and for that, under Linux with a 6.12
  kernel, you'll just need to ask logind (through a portal, with mutter
  in the middle) to give you raw access to HID *as a user* (because
  logind can revoke the access whenever it want).
  So that GUI ends up writing raw HID LampArray support, and starts
  complaining at any new kernel API we do, because it conflicts with
  their access.
- and guess what, native HID LampArray devices are coming to be true, so
  that userspace HID implementation is not for nothing.

I've been through this exact same process with Input and game
controllers, and even for libratbag for configuring gaming devices. In
the end, the kernel developer never wins, but the userspace application
does, and the fact that Windows and Mac gives raw access to a sensible
API that already provide anything the userspace application needs is the
killer feature. With raw access they have much finer control over the
device, and TBH it is not a critical component of the system.

If you want a 100 lines of code program to control your keyboard, with
LampArray, you can, as long as you don't require a GUI and don't require
to be generic. Just write the values directly on the hidraw device, and
done. Or use a BPF program like I currently do for fun on my Corsair
keyboard. It's all in the kernel, no overhead, and my daughters are
impressed because the colors of the keys of my keyboard are changing
dynamically...

Having a global keyboard backlight handled through LED class is
perfectly fine also. But we are talking about crazy things that people
do for basically nothing. Having a dedicated kernel interface when there
is already a published standard is IMO shooting ourself in the foot
because there'll be no users, and we'll have to support it forever.

You might agree with me or not, but this is actually not relevant to the
discussion here IMO: all what Werner is doing (with crazy arrays) is to
take a proprietary protocol and convert into a HID standard. He is
basically supplying over a firmware that should have reported this
information from day one. You are arguing against this, and want to
bypass that by providing a new subsystem, but if you take a step back,
that new subsystem, even if I disagree with it, can come on top of HID
LampArray, and it will already have all the information you want. So
instead of having multiple places where you implement that new
subsystem, you have one canonical implementation, meaning one place to
fix it.

I'm also under the impression that you are scared by BPF. BPF is just a
tool here to "fix" the device with an easy path forward. BPF is safer
than a true kernel driver, but all in all, we can have the exact same
discussion on whether having a dedicated kernel API or not once we get
our hands on the first true HID LampArray supported keyboard.

Cheers,
Benjamin
Benjamin Tissoires Oct. 2, 2024, 8:31 a.m. UTC | #31
On Oct 01 2024, Werner Sembach wrote:
> Hi Benjamin,
> 
> Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
> > On Oct 01 2024, Werner Sembach wrote:
> > > (sorry resend because thunderbird made it a html mail)
> > > 
> > > Hi,
> > > 
> > > Am 30.09.24 um 19:06 schrieb Benjamin Tissoires:
> > > > On Sep 30 2024, Werner Sembach wrote:
> > > > > [...]
> > > > > Thinking about it, maybe it's not to bad that it only changes once udev is
> > > > > ready, like this udev could decide if leds should be used or if it should
> > > > > directly be passed to OpenRGB for example, giving at least some consistency
> > > > > only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds
> > > > > setting->OpenRGB setting.
> > > > That would work if OpenRGB gets to ship the LampArray bpf object (not
> > > > saying that it should). Because if OpenRGB is not installed, you'll get
> > > > a led class device, and if/when OpenRGB is installed, full LampArray
> > > > would be presented.
> > > The idea in my head is still that there is some kind of sysfs switch to
> > > enable/disable leds.
> > FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed
> > without possibility to change them...
> > 
> > > My idea is then that a udev rule shipped with OpenRGB sets this switch to
> > > disable before loading the BPF driver so leds never get initialized for the
> > > final LampArray device.
> > FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So
> > basically we can have a udev property set (or not) by openrgb which
> > makes the BPF program decide whether to present the keyboard as
> > LampArray or not.
> > 
> > > > But anyway, BPF allows to dynamically change the behaviour of the
> > > > device, so that's IMO one bonus point of it.
> > > > 
> > > > > > FWIW, the use of BPF only allows you to not corner yourself. If you
> > > > > > failed at your LampArray implementation, you'll have to deal with it
> > > > > > forever-ish. So it's perfectly sensible to use BPF as an intermediate step
> > > > > > where you develop both userspace and kernel space and then convert back
> > > > > > the BPF into a proper HID driver.
> > > > > I don't really see this point: The LampArray API is defined by the HID Usage
> > > > > Table and the report descriptor, so there is not API to mess up and
> > > > > everything else has to be parsed dynamically by userspace anyway, so it can
> > > > > easily be changed and userspace just adopts automatically.
> > > > > 
> > > > > And for this case the proper HID driver is already ready.
> > > > Yeah, except we don't have the fallback LED class. If you are confident
> > > > enough with your implementation, then maybe yes we can include it as a
> > > > driver from day one, but that looks like looking for troubles from my
> > > > point of view.
> > > To be on the safe side that we don't talk about different things: My current
> > > plan is that the leds subsystem builds on top of the LampArray
> > > implementation.
> > I would say that the HID subsystem knows how to translate LampArray into
> > a subset of LEDs. But I think that's what you are saying.
> > 
> > > Like this the leds part has to be only implemented once for all LampArray
> > > devices be it emulated via a driver or native via firmware in the device
> > > itself.
> > yep, that's the plan. However, not sure how to fit LampArray into LED.
> 
> My idea was that all leds just get treated as a singular led only allowing
> to set a singular color and brightness, but I just looked it up again:
> LampArray allows different color and brightness ranges per key, so the
> grouping might not be possible in a sensible way ...

maybe we can use that information to group per "color and brightness"?

> 
> Maybe the leds integration is a bad idea after all and we should just nudge
> the DEs and/or UPower to implement LampArray directly? But that's just
> kicking the complexity down the road, at least as long as there is not
> universal easy to use library (haven't looked into the library build of
> OpenRGB yet).

I think that LED integration is not a bad thing if we can approximate
the keyboard backlight. We might not be able to provide RGB, but at
least some "light up, light down" from the system would certainly make
sense.

FWIW, I know people using libratbag as a CLI to just turn off the LEDs
on their mouse. So we have already users for that exact use case.


[...snipped... (because I don't disagree on the WMI discussion)]

> > > To sum up the architechture (not mutally exclusive technically)
> > > 
> > > /- leds
> > > WMI <- WMI to LampArray Kernel driver <-switch-|
> > >                                                 \- OpenRGB
> > > 
> > > /- leds
> > > WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF
> > > driver<-switch-|
> > > \- OpenRGB
> > > 
> > > With the "switch" and "leds" implemented in hid core, automatically
> > > initialized every time a LampArray device pops up (regardless if it is from
> > > native firmware, a bpf driver or a kernel driver)
> > > 
> > > Writing this down I think it was never decided how the switch should look like:
> > > 
> > > It should not be a sysfs attribute of the leds device as the leds device
> > > should disappear when the switch is set away from it, but should it be a
> > > sysfs variable of the hid device? This would mean that hid core needs to add
> > > that switch variable to every hid device having a LampArray section in the
> > > descriptor.
> > Again, not a big fan of the sysfs, because it's UAPI and need root to
> > trigger it (though the udev rule sort this one out).
> > BPF allows already to re-enumerate the device with a different look and
> > feel, so it seems more appropriate to me.
> > 
> > Also, having a sysfs that depends on the report descriptor basically
> > means that we will lose it whenever we re-enumerate it (kind of what the
> > LED problem you mentioned above). So we would need to have a sysfs on
> > *every* HID devices???
> > 
> > Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos
> > HW):
> > - a device presents a report descriptor with LampArray (wherever it
> >    comes from)
> > - hid-led.c takes over it (assuming we extend it for LampArray), and
> >    creates a few LEDs based on the Input usage (one global rgb color for
> >    regular keys, another one for the few other LEDs known to userspace)
> > - when openRGB is present (special udev property), a BPF program is
> >    inserted that only contains a report descriptor fixup that prevent the
> >    use of hid-led.c
> 
> How would that look like? just a custom bit in a "Vendor defined" usage page?

not a vendor (the code will be in the hid selftests once my v3 series is merged[0]):

```
SEC("?struct_ops/hid_rdesc_fixup")
int BPF_PROG(hid_test_driver_probe, struct hid_bpf_ctx *hid_ctx)
{
	hid_ctx->hid->quirks |= HID_QUIRK_IGNORE_SPECIAL_DRIVER;
	return 0;
}

SEC(".struct_ops.link")
struct hid_bpf_ops test_driver_probe = {
	.hid_rdesc_fixup = (void *)hid_test_driver_probe,
};
```

This has basically no overhead, because it's a one time operation, no
change in the event processing.

That code will probably need to be enhanced by comparing a
UDEV_PROP_* static char that udev-hid-bpf fills in when loading the
device.

We can also have a dynamic quirk on the boot cmd line that sets that
quirk, but it just doesn't scale when multiple devices are supported.

> 
> But this is still UAPI just hidden inside a BFP program instead of sysfs.
> But it would avoid the re-enumeration problem.

Yep, completely agree, but that UAPI is not set in stone. It's
controlled by userspace, and if there are no users, we can drop it much
more easily than having a sysfs.

The other advantage of using BPF is you have free re-enumeration of the
device. Because the quirks sysfs API for HID doesn't support it (maybe
we can fix that as well, but I don't see the point given how simple BPF
is).

> 
> > - the device gets re-enumerated, cleaning the in-kernel leds, and only
> >    present the LampArray through hidraw, waiting for OpenRGB to take
> >    over.
> > - at any time we can remove the BPF and restore the LEDs functionality
> >    of hid-led.c
> > 
> > > > > > Being able to develop a kernel driver without having to reboot and
> > > > > > being sure you won't crash your kernel is a game changer ;)
> > > > > > 
> > > > > > Cheers,
> > > > > > Benjamin
> > > Best regards and sorry for the many questions,
> > > 
> > > Werner Sembach
> > > 
> > > PS: on a side node: How does hid core handle HID devices with a broken HID
> > > implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid
> > > device get reinitialized by hid core once the bpf driver got loaded? If yes,
> > > is there a way to avoid side effects by this double initialization or is
> > > there a way to avoid this double initialization, like marking the device id
> > > as broken so that hid core- does not initialize it unless it's fixed by bpf?
> > - broken HID device:
> >    it depends on what you call "broken" HID device. If the report
> >    descriptor is boggus, hid-core will reject the device and will not
> >    present it to user space (by returning -EINVAL).
> >    If the device is sensible in terms of HID protocol, it will present it
> >    to userspace, but the fact that it creates an input node or LED or
> >    whatever just depends on what is inside the report descriptor.
> > 
> > - HID-BPF:
> >    HID-BPF is inserted between the device itself and the rest of the
> >    in-kernel HID stack.
> >    Whenever you load and attach (or detach) a BPF program which has a
> >    report descriptor fixup, HID-core will reconnect the device,
> >    re-enumerate it (calling ->probe()), and will re-present it to
> >    userspace as if it were a new device (you get all uevents).
> > 
> > - double initialization:
> >    nowadays hid-generic doesn't do anything on the device itself except
> >    calling the powerup/powerdown, by calling ->start and ->stop on the
> >    HID transport driver. It's not a problem on 99% of the devices AFAICT.
> >    technically, if the report descriptor is bogus, start/stop won't be
> >    called, but you'll get an error in the dmesg. So if you really want to
> >    rely on that "broken" scenario we can always add a specific quirk in
> >    HID to not spew that error.
> > 
> > Cheers,
> > Benjamin
> > 
> > PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> > all of the requirements here:
> > - need to be dynamic
> > - still unsure of the userspace implementation, meaning that userspace
> >    might do something wrong, which might require kernel changes
> 
> Well the reference implementetion for the arduiono macropad from microsoft
> ignores the intensity (brightness) channel on rgb leds contrary to the HID
> spec, soo yeah you have a point here ...

Heh :)

> 
> > - possibility to extend later the kernel API
> > - lots of fun :)
> 
> You advertise it good ;). More work for me now but maybe less work for me
> later, I will look into it.

Again, I'm pushing this because I see the benefits and because I can
probably reuse the same code on my Corsair and Logitech keyboards. But
also, keep in mind that it's not mandatory because you can actually
attach the BPF code on top of your existing driver to change the way it
behaves. It'll be slightly more complex if you don't let a couple of
vendor passthrough reports that we can use to directly talk to the
device without any tampering, but that's doable. But if you want to keep
the current implementation and have a different layout, this can easily
be done in BPF on top.

Cheers,
Benjamin


[0] https://lore.kernel.org/linux-input/20241001-hid-bpf-hid-generic-v3-0-2ef1019468df@kernel.org/T/#t
Benjamin Tissoires Oct. 2, 2024, 8:42 a.m. UTC | #32
On Oct 01 2024, Werner Sembach wrote:
> Hi Armin,
> 
> Am 01.10.24 um 18:45 schrieb Armin Wolf:
[...snipped...]
> > Why not having a simple led driver for HID LampArray devices which exposes the
> > whole LampArray as a single LED?
> Yes that is my plan, but see my last reply to Benjamin, it might not be
> trivial as different leds in the same LampArray might have different max
> values for red, green, blue, and intensity. And the LampArray spec even
> allows to mix RGB and non-RGB leds.
> > 
> > If userspace wants to have direct control over the underlying LampArray device,
> > it just needs to unbind the default driver (maybe udev can be useful here?).
> There was something in the last discussion why this might not work, but i
> can't put my finger on it.

We recently have the exact same problem, so it's still fresh in my
memory. And here are what is happening:
- you can unbind the driver with a sysfs command for sure
- but then the device is not attached to a driver so HID core doesn't
  expose the hidraw node
- you'd think "we can just rebind it to hid-generic", but that doesn't
  work because hid-generic sees that there is already a loaded driver
  that can handle the device and it'll reject itself because it gives
  priority over the other driver
- what works is that you might be able to unload the other driver, but
  if it's already used by something else (like hid-multitouch), you
  don't want to do that. And also if you unload that driver, whenever
  the driver gets re-inserted, hid-generic will unbind itself, so back
  to square one

So unless we find a way to forward the "manual" binding to hid-generic,
and/or we can also quirk the device with
HID_QUIRK_IGNORE_SPECIAL_DRIVER[0] just unbinding the device doesn't
work.

Cheers,
Benjamin

PS: brain fart:
if HID LampArray support (whatever the implementation, through Pavel's
new API or simple LED emulation) is in hid-input, we can also simply add
a new HID quirk to enable this or not, and use that quirk dynamically
(yes, with BPF :-P ) to rebind the device...

[0] https://lore.kernel.org/linux-input/20241001-hid-bpf-hid-generic-v3-0-2ef1019468df@kernel.org/T/#t
Armin Wolf Oct. 2, 2024, 9:27 a.m. UTC | #33
Am 02.10.24 um 10:42 schrieb Benjamin Tissoires:

> On Oct 01 2024, Werner Sembach wrote:
>> Hi Armin,
>>
>> Am 01.10.24 um 18:45 schrieb Armin Wolf:
> [...snipped...]
>>> Why not having a simple led driver for HID LampArray devices which exposes the
>>> whole LampArray as a single LED?
>> Yes that is my plan, but see my last reply to Benjamin, it might not be
>> trivial as different leds in the same LampArray might have different max
>> values for red, green, blue, and intensity. And the LampArray spec even
>> allows to mix RGB and non-RGB leds.
>>> If userspace wants to have direct control over the underlying LampArray device,
>>> it just needs to unbind the default driver (maybe udev can be useful here?).
>> There was something in the last discussion why this might not work, but i
>> can't put my finger on it.
> We recently have the exact same problem, so it's still fresh in my
> memory. And here are what is happening:
> - you can unbind the driver with a sysfs command for sure
> - but then the device is not attached to a driver so HID core doesn't
>    expose the hidraw node
> - you'd think "we can just rebind it to hid-generic", but that doesn't
>    work because hid-generic sees that there is already a loaded driver
>    that can handle the device and it'll reject itself because it gives
>    priority over the other driver
> - what works is that you might be able to unload the other driver, but
>    if it's already used by something else (like hid-multitouch), you
>    don't want to do that. And also if you unload that driver, whenever
>    the driver gets re-inserted, hid-generic will unbind itself, so back
>    to square one
>
> So unless we find a way to forward the "manual" binding to hid-generic,
> and/or we can also quirk the device with
> HID_QUIRK_IGNORE_SPECIAL_DRIVER[0] just unbinding the device doesn't
> work.
>
> Cheers,
> Benjamin

I see, maybe we can add support for the driver_override mechanism to the HID bus?
Basically userspace could use the driver_override mechanism to forcefully bind hid-generic
to a given HID device even if a compatible HID driver already exists.

Thanks,
Armin Wolf

> PS: brain fart:
> if HID LampArray support (whatever the implementation, through Pavel's
> new API or simple LED emulation) is in hid-input, we can also simply add
> a new HID quirk to enable this or not, and use that quirk dynamically
> (yes, with BPF :-P ) to rebind the device...
>
> [0] https://lore.kernel.org/linux-input/20241001-hid-bpf-hid-generic-v3-0-2ef1019468df@kernel.org/T/#t
Pavel Machek Oct. 2, 2024, 9:53 a.m. UTC | #34
On Wed 2024-10-02 10:13:10, Benjamin Tissoires wrote:
> On Oct 01 2024, Pavel Machek wrote:
> > Hi!
> > 
> > > PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> > > all of the requirements here:
> > > - need to be dynamic
> > > - still unsure of the userspace implementation, meaning that userspace
> > >   might do something wrong, which might require kernel changes
> > > - possibility to extend later the kernel API
> > > - lots of fun :)
> > 
> > Please don't do this.
> > 
> > We have real drivers for framebuffers. We don't make them emulate
> > USB-display devices.
> 
> Except that they are not framebuffer for multiple reasons. I know you
> disagree, but the DRM maintainers gave a strong NACK already for a

Person not linking the DRM stuff was not a maintainer.

> DRM-like interface, and the auxdisplay would need some sort of tweaking
> that is going too far IMO (I'll explain below why I believe this).



> > Yes, this is a small display, and somewhat unusual with weird pixel
> > positions, but it is common enough that we should have real driver for
> > that, with real API.
> 
> It's not just weird pixel positions. It's also weird shapes. How are you
> going to fit the space bar in a grid, unless you start saying that it
> spans accross several pixels. But then users will want to address
> individual "grouped" pixels, and you'll end up with a weird API. The
> Enter key on non US layouts is also a problem: is it 1 or 2 pixels wide?
> Is is 2 pixel in heights?

Have you seen one of those keyboards?

(Hint: it is LEDs below regular keyboard.)

> The positions of the pixels also depend on the physical layout of the
> keyboard itself. So with the same vendor ID/Product ID, you might have
> different pixel positions if the device is sold in Europe, or in the
> US.

If vendor sells different hardware with same IDs, well 1) that's a
nono, a 2) that's what kernel parameters are for.

> It's also luminance problem IIRC. Some keys might have a different range
> of luminance than others. I remember Bastien Nocera telling me we

Have you seen one of those keyboards?

> But that's just the "easy" part. We can define a kernel API, for sure,
> but then we need users. And there are several problems here:
> 
> - first, users of this new kernel API need to be root to address the
>   LEDs. They probably won't, so they'll rely on a third party daemon for
>   that, or just use uaccess (yay!). But that part is easy

Eventually, desktop environment should talk the interface. (Plus, how
does HID or BPF craziness help with his?)

> - then, user sees a new kernel interface, and they have to implement it.
>   OK, fair enough, but more code to maintain

Yep. At least there's single interface to talk to.

> - but that new kernel API doesn't give enough information: all you have
>   is an approximation of the keyboard layout, which will not match
>   the

Have you seen OpenRGB? It already aproximates keyboard as a grid. Or
maybe we give them enough information.

Below you were just inventing problems.

> - but then, even if you make everyones happy, the GUI project is
>   actually cross-platform (OpenRGB is, Steam is, SDL is). And what is
>   done on Windows is simple: raw access to the HID device. And the
>   raw

Yes, Windows is a mess. We don't want to emulate them.

> I've been through this exact same process with Input and game
> controllers, and even for libratbag for configuring gaming devices. In
> the end, the kernel developer never wins, but the userspace

Yes, we have been in this exact situation. Userland was directly
accessing mice. It was called "gpm" and we moved away from that for
good reasons.

> If you want a 100 lines of code program to control your keyboard, with
> LampArray, you can, as long as you don't require a GUI and don't require
> to be generic. Just write the values directly on the hidraw device,
> and

Haha, no. Kernel part was 400+ lines, no way you can parse that in 100
lines.

> You might agree with me or not, but this is actually not relevant to the
> discussion here IMO: all what Werner is doing (with crazy arrays) is to
> take a proprietary protocol and convert into a HID standard. He is

Yes, we should never have had input subsystem. We should simply
convert all mice to PS/2 standard protocol. ... And yes, we have that,
that's /dev/mice, yet input layer is very useful.

What is relevant that these crazy arrays are not going to be merged,
and better solution is needed.

> I'm also under the impression that you are scared by BPF. BPF is just a
> tool here to "fix" the device with an easy path forward. BPF is
> safer

I should not need to run just-in-time compiler to get support for my
hardware. If you are not scared by BPF, take a look at modern CPU
design, with emphasis on speculation vulnerabilities such as Spectre
and Meltdown.


Best regards,
								Pavel
Benjamin Tissoires Oct. 2, 2024, 10:21 a.m. UTC | #35
On Oct 02 2024, Pavel Machek wrote:
> On Wed 2024-10-02 10:13:10, Benjamin Tissoires wrote:
> > On Oct 01 2024, Pavel Machek wrote:
> > > Hi!
> > > 
> > > > PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits
> > > > all of the requirements here:
> > > > - need to be dynamic
> > > > - still unsure of the userspace implementation, meaning that userspace
> > > >   might do something wrong, which might require kernel changes
> > > > - possibility to extend later the kernel API
> > > > - lots of fun :)
> > > 
> > > Please don't do this.
> > > 
> > > We have real drivers for framebuffers. We don't make them emulate
> > > USB-display devices.
> > 
> > Except that they are not framebuffer for multiple reasons. I know you
> > disagree, but the DRM maintainers gave a strong NACK already for a
> 
> Person not linking the DRM stuff was not a maintainer.
> 
> > DRM-like interface, and the auxdisplay would need some sort of tweaking
> > that is going too far IMO (I'll explain below why I believe this).
> 
> 
> 
> > > Yes, this is a small display, and somewhat unusual with weird pixel
> > > positions, but it is common enough that we should have real driver for
> > > that, with real API.
> > 
> > It's not just weird pixel positions. It's also weird shapes. How are you
> > going to fit the space bar in a grid, unless you start saying that it
> > spans accross several pixels. But then users will want to address
> > individual "grouped" pixels, and you'll end up with a weird API. The
> > Enter key on non US layouts is also a problem: is it 1 or 2 pixels wide?
> > Is is 2 pixel in heights?
> 
> Have you seen one of those keyboards?

I already refrain from mention this once or twice, but please, try not
being aggressive in suggesting I'm dumb.

> 
> (Hint: it is LEDs below regular keyboard.)

Yes, I know, and if you read this email and the few others, you'll read
that I own a few of them already (for a long time), and I worked on a
cross vendor userspace API to configure them. So I know what I am
talking about.

> 
> > The positions of the pixels also depend on the physical layout of the
> > keyboard itself. So with the same vendor ID/Product ID, you might have
> > different pixel positions if the device is sold in Europe, or in the
> > US.
> 
> If vendor sells different hardware with same IDs, well 1) that's a
> nono, a 2) that's what kernel parameters are for.

This is already the case (hello hid-uclogic), and no, kernel parameters
are not helping. In that case (uclogic), we ask the device a specific
USB string which has the information, but is not part of HID. This is
dumb, but we don't control hardware makers.

> 
> > It's also luminance problem IIRC. Some keys might have a different range
> > of luminance than others. I remember Bastien Nocera telling me we
> 
> Have you seen one of those keyboards?

Again, please refrain any aggressive comments.

> 
> > But that's just the "easy" part. We can define a kernel API, for sure,
> > but then we need users. And there are several problems here:
> > 
> > - first, users of this new kernel API need to be root to address the
> >   LEDs. They probably won't, so they'll rely on a third party daemon for
> >   that, or just use uaccess (yay!). But that part is easy
> 
> Eventually, desktop environment should talk the interface. (Plus, how
> does HID or BPF craziness help with his?)

HID helps because we already have the case with game controllers. Steam
and SDL (both widely use), put rules giving uaccess to hidraw nodes on
those controllers. So we finally made the jump and now provide in v6.12
a new hidraw ioctl to allow logind to revoke the hidraw node. This
should allow us to not give uaccess to those hidraw nodes.

So in the near future, there will be a portal available, that says
"please give me a fd for this hidraw node", the compositor will then ask
logind to open the file for it and then will pass that fd to the final
application. Once there is a vt-switch, logind will revoke the fd,
meaning that the application will not have access to the device.

> 
> > - then, user sees a new kernel interface, and they have to implement it.
> >   OK, fair enough, but more code to maintain
> 
> Yep. At least there's single interface to talk to.
> 
> > - but that new kernel API doesn't give enough information: all you have
> >   is an approximation of the keyboard layout, which will not match
> >   the
> 
> Have you seen OpenRGB? It already aproximates keyboard as a grid. Or
> maybe we give them enough information.
> 
> Below you were just inventing problems.

A simple "IMO" would makes this kind of comments acceptable. But this is
really offensive TBH.

> 
> > - but then, even if you make everyones happy, the GUI project is
> >   actually cross-platform (OpenRGB is, Steam is, SDL is). And what is
> >   done on Windows is simple: raw access to the HID device. And the
> >   raw
> 
> Yes, Windows is a mess. We don't want to emulate them.
> 
> > I've been through this exact same process with Input and game
> > controllers, and even for libratbag for configuring gaming devices. In
> > the end, the kernel developer never wins, but the userspace
> 
> Yes, we have been in this exact situation. Userland was directly
> accessing mice. It was called "gpm" and we moved away from that for
> good reasons.

There is a slight difference between mouse support and LEDs on your
keyboard. The former is actually required to bring up the machine and to
use it, the latter is nice to have.

And if you want to take that mouse comparison, we are already seeing the
limits of the input subsystem, because we are running out of bits for
defining usages. A few years ago we talked about creating evdev2, but we
ended up nowhere. Now we are realizing that HID has way more
inforamtions on the device that the kernel provides, so we also need a
new way to export those information (pending proposal already).

> 
> > If you want a 100 lines of code program to control your keyboard, with
> > LampArray, you can, as long as you don't require a GUI and don't require
> > to be generic. Just write the values directly on the hidraw device,
> > and
> 
> Haha, no. Kernel part was 400+ lines, no way you can parse that in 100
> lines.

I'm not saying "parsing", I mean adapt to your use case. If you know
your device, your simple CLI is just writing a static array of bytes to
the hidraw interface.

> 
> > You might agree with me or not, but this is actually not relevant to the
> > discussion here IMO: all what Werner is doing (with crazy arrays) is to
> > take a proprietary protocol and convert into a HID standard. He is
> 
> Yes, we should never have had input subsystem. We should simply
> convert all mice to PS/2 standard protocol. ... And yes, we have that,
> that's /dev/mice, yet input layer is very useful.

Again, apple and oranges. Input is required for everything. The LEDs
under a keyboard is not a vital component. And there is already a HID
standard to it.

> 
> What is relevant that these crazy arrays are not going to be merged,
> and better solution is needed.

Again, you seemn to miss the point: those crazy arrays should have been
in the firmware from day one. They are not, so the idea is to convert
proprietary protocol into a standard. Then we can start thinking what
comes next.

> 
> > I'm also under the impression that you are scared by BPF. BPF is just a
> > tool here to "fix" the device with an easy path forward. BPF is
> > safer
> 
> I should not need to run just-in-time compiler to get support for my
> hardware. If you are not scared by BPF, take a look at modern CPU
> design, with emphasis on speculation vulnerabilities such as Spectre
> and Meltdown.
> 

Cheers,
Benjamin
Pavel Machek Oct. 3, 2024, 10:59 a.m. UTC | #36
Hi!

> > (Hint: it is LEDs below regular keyboard.)
> 
> Yes, I know, and if you read this email and the few others, you'll read
> that I own a few of them already (for a long time), and I worked on a
> cross vendor userspace API to configure them. So I know what I am
> talking about.

Ok.

> > > The positions of the pixels also depend on the physical layout of the
> > > keyboard itself. So with the same vendor ID/Product ID, you might have
> > > different pixel positions if the device is sold in Europe, or in the
> > > US.
> > 
> > If vendor sells different hardware with same IDs, well 1) that's a
> > nono, a 2) that's what kernel parameters are for.
> 
> This is already the case (hello hid-uclogic), and no, kernel parameters
> are not helping. In that case (uclogic), we ask the device a specific
> USB string which has the information, but is not part of HID. This is
> dumb, but we don't control hardware makers.

Well, good you find other solution. Kernel parameter would have worked
as a fallback.

> > > But that's just the "easy" part. We can define a kernel API, for sure,
> > > but then we need users. And there are several problems here:
> > > 
> > > - first, users of this new kernel API need to be root to address the
> > >   LEDs. They probably won't, so they'll rely on a third party daemon for
> > >   that, or just use uaccess (yay!). But that part is easy
> > 
> > Eventually, desktop environment should talk the interface. (Plus, how
> > does HID or BPF craziness help with his?)
> 
> HID helps because we already have the case with game controllers. Steam
> and SDL (both widely use), put rules giving uaccess to hidraw nodes on
> those controllers. So we finally made the jump and now provide in v6.12
> a new hidraw ioctl to allow logind to revoke the hidraw node. This
> should allow us to not give uaccess to those hidraw nodes.
> 
> So in the near future, there will be a portal available, that says
> "please give me a fd for this hidraw node", the compositor will then ask
> logind to open the file for it and then will pass that fd to the final
> application. Once there is a vt-switch, logind will revoke the fd,
> meaning that the application will not have access to the device.

Yes, you can work around kernel not providing abstractions. But you
should not have to.

> > > - but then, even if you make everyones happy, the GUI project is
> > >   actually cross-platform (OpenRGB is, Steam is, SDL is). And what is
> > >   done on Windows is simple: raw access to the HID device. And the
> > >   raw
> > 
> > Yes, Windows is a mess. We don't want to emulate them.
> > 
> > > I've been through this exact same process with Input and game
> > > controllers, and even for libratbag for configuring gaming devices. In
> > > the end, the kernel developer never wins, but the userspace
> > 
> > Yes, we have been in this exact situation. Userland was directly
> > accessing mice. It was called "gpm" and we moved away from that for
> > good reasons.
> 
> There is a slight difference between mouse support and LEDs on your
> keyboard. The former is actually required to bring up the machine and to
> use it, the latter is nice to have.

But that's not the difference that matters. Linux is not microkernel,
and is trying to provide hardware abstractions. (Except for printers,
I guess that's because printers are often network devices).

Besides, mouse was not required to bring up a machine "back then".

Besides,

1) using those keyboards in dark room without backlight is hard,
because their labels are translucent and not having enough contrast.

2) rainbow effects make people ill.

Note how we have drivers for audio, LEDs, cameras, dunno, iio sensors,
none of that is required to bring system up.

We need driver for the WMI stuff in kernel. And that point it should
be pretty clear proper driver/subsystem should be done.

> > > If you want a 100 lines of code program to control your keyboard, with
> > > LampArray, you can, as long as you don't require a GUI and don't require
> > > to be generic. Just write the values directly on the hidraw device,
> > > and
> > 
> > Haha, no. Kernel part was 400+ lines, no way you can parse that in 100
> > lines.
> 
> I'm not saying "parsing", I mean adapt to your use case. If you know
> your device, your simple CLI is just writing a static array of bytes to
> the hidraw interface.

No. Hardware abstraction is kernel work, my application should work
everywhere.

> > What is relevant that these crazy arrays are not going to be merged,
> > and better solution is needed.
> 
> Again, you seemn to miss the point: those crazy arrays should have been
> in the firmware from day one. They are not, so the idea is to convert
> proprietary protocol into a standard. Then we can start thinking what
> comes next.

Firmware is what it is and we have to deal with that.

(Not to mention that "standard" you are citing is not used by anyone
and is ugly as hell. So not even open hardware such as MNT Reform uses
it).

Best regards,
								Pavel
Benjamin Tissoires Oct. 3, 2024, 12:54 p.m. UTC | #37
On Oct 03 2024, Pavel Machek wrote:
> Hi!
> 
> > > (Hint: it is LEDs below regular keyboard.)
> > 
> > Yes, I know, and if you read this email and the few others, you'll read
> > that I own a few of them already (for a long time), and I worked on a
> > cross vendor userspace API to configure them. So I know what I am
> > talking about.
> 
> Ok.
> 
> > > > The positions of the pixels also depend on the physical layout of the
> > > > keyboard itself. So with the same vendor ID/Product ID, you might have
> > > > different pixel positions if the device is sold in Europe, or in the
> > > > US.
> > > 
> > > If vendor sells different hardware with same IDs, well 1) that's a
> > > nono, a 2) that's what kernel parameters are for.
> > 
> > This is already the case (hello hid-uclogic), and no, kernel parameters
> > are not helping. In that case (uclogic), we ask the device a specific
> > USB string which has the information, but is not part of HID. This is
> > dumb, but we don't control hardware makers.
> 
> Well, good you find other solution. Kernel parameter would have worked
> as a fallback.

This is probably a side topic, but IMO, kernel parameter are most of the
time the worst solution. Basically we are asking people to look for
solutions on random forums and they have to manually add the parameter
in their bootcmd. But that's a different topic.

Of course, I'm not saying kernel parameters are just a bad thing: being
able to enable specific debug or some per user configuration (like
enabling disabling a feature) is a whole different story. It's just
"kernel parameter to fix a device" that I dislike.

> 
> > > > But that's just the "easy" part. We can define a kernel API, for sure,
> > > > but then we need users. And there are several problems here:
> > > > 
> > > > - first, users of this new kernel API need to be root to address the
> > > >   LEDs. They probably won't, so they'll rely on a third party daemon for
> > > >   that, or just use uaccess (yay!). But that part is easy
> > > 
> > > Eventually, desktop environment should talk the interface. (Plus, how
> > > does HID or BPF craziness help with his?)
> > 
> > HID helps because we already have the case with game controllers. Steam
> > and SDL (both widely use), put rules giving uaccess to hidraw nodes on
> > those controllers. So we finally made the jump and now provide in v6.12
> > a new hidraw ioctl to allow logind to revoke the hidraw node. This
> > should allow us to not give uaccess to those hidraw nodes.
> > 
> > So in the near future, there will be a portal available, that says
> > "please give me a fd for this hidraw node", the compositor will then ask
> > logind to open the file for it and then will pass that fd to the final
> > application. Once there is a vt-switch, logind will revoke the fd,
> > meaning that the application will not have access to the device.
> 
> Yes, you can work around kernel not providing abstractions. But you
> should not have to.
> 
> > > > - but then, even if you make everyones happy, the GUI project is
> > > >   actually cross-platform (OpenRGB is, Steam is, SDL is). And what is
> > > >   done on Windows is simple: raw access to the HID device. And the
> > > >   raw
> > > 
> > > Yes, Windows is a mess. We don't want to emulate them.
> > > 
> > > > I've been through this exact same process with Input and game
> > > > controllers, and even for libratbag for configuring gaming devices. In
> > > > the end, the kernel developer never wins, but the userspace
> > > 
> > > Yes, we have been in this exact situation. Userland was directly
> > > accessing mice. It was called "gpm" and we moved away from that for
> > > good reasons.
> > 
> > There is a slight difference between mouse support and LEDs on your
> > keyboard. The former is actually required to bring up the machine and to
> > use it, the latter is nice to have.
> 
> But that's not the difference that matters. Linux is not microkernel,
> and is trying to provide hardware abstractions. (Except for printers,
> I guess that's because printers are often network devices).
> 
> Besides, mouse was not required to bring up a machine "back then".
> 
> Besides,
> 
> 1) using those keyboards in dark room without backlight is hard,
> because their labels are translucent and not having enough contrast.
> 
> 2) rainbow effects make people ill.

And I agree with you here. And that's also why I agree with Werner's
plan: have a minimum support in kernel for that with the already
supported LED class, which is supported by UPower and others, and let
the ones who want the fancy effects be in charge of their mess.

To me, there is no value in designing a new API, gather all the
requirements, try to make it perfect, when the users will just say
"nope, we rather talk to hidraw because we can have the same code on
Linux, Windows and Mac".

This is what happened to us with SDL and Steam. We added support for the
PlayStation controllers, the XBox ones, the Wii, and many others,
through the regular input and FF stacks. But all they want is being able
to disable what the kernel is doing because they are using the device
differently and in the same way on Windows, Mac and Linux.

And if you look at OpenRGB (or any other tool that configures multiple
crazy LEDs devices), they are all doing the same thing, *already*. So if
we come to them with a new fancy interface, they'll just laugh at us.

(and no, it's not just a hidraw problem, they are actually dettaching
the USB device entirely, having a userspace USB library and then on top
of it parse the HID data with a userspace HID library).

> 
> Note how we have drivers for audio, LEDs, cameras, dunno, iio sensors,
> none of that is required to bring system up.
> 
> We need driver for the WMI stuff in kernel. And that point it should
> be pretty clear proper driver/subsystem should be done.

Yes, and again, I never said we need to provide WMI to userspace.

What I want is:
- provide a minimum support on Linux using already existing APIs (LED
  class)
- allow crazy people to do their thing if they want to have a rainbow
  initiated by every key press
- ensure the minimum support of the LED class is not messed up when
  people start using the HID LampArray API.

HID LampArray is a ratified standard by a few hardware makers already[0]
(Acer, Asus, HP, Logitech, Razer, SteelSeries and Twinkly apparently).
They already made the job of knowing their requirements. From the
kernel, we probably don't need all of this. But they have users who
cares. So providing the minimum support in Linux and a way to forward
more advanced usage seems like a good way to me.

> 
> > > > If you want a 100 lines of code program to control your keyboard, with
> > > > LampArray, you can, as long as you don't require a GUI and don't require
> > > > to be generic. Just write the values directly on the hidraw device,
> > > > and
> > > 
> > > Haha, no. Kernel part was 400+ lines, no way you can parse that in 100
> > > lines.
> > 
> > I'm not saying "parsing", I mean adapt to your use case. If you know
> > your device, your simple CLI is just writing a static array of bytes to
> > the hidraw interface.
> 
> No. Hardware abstraction is kernel work, my application should work
> everywhere.

So when you say "Kernel part was 400+ lines" you mean the HID parsing of
the report descriptor? You don't want to use a already existing HID
parsing library?

Because if you want a plain C program without anything outside stdlib,
then yes, 100 LoC is going to be tricky. But if you can cope with a HID
parsing library, setting the color of a keyboard driven by LampArray is
a single write to the hidraw node (see page 345 of HID HUT 1.5[1]):

LampRangeUpdateReport(LampIdStart==0, LampIdEnd==(LampCount-1),
RGBI==color)

where LampCount is found in the report descriptor and color a simple
(r,g,b) value.

> 
> > > What is relevant that these crazy arrays are not going to be merged,
> > > and better solution is needed.
> > 
> > Again, you seemn to miss the point: those crazy arrays should have been
> > in the firmware from day one. They are not, so the idea is to convert
> > proprietary protocol into a standard. Then we can start thinking what
> > comes next.
> 
> Firmware is what it is and we have to deal with that.
> 
> (Not to mention that "standard" you are citing is not used by anyone
> and is ugly as hell. So not even open hardware such as MNT Reform uses
> it).

See Microsoft's pledge[0] and the list of vendors I quoted. And again, I
don't care if it's ugly as long as we have minimal support in the kernel
and can let userspace deal with this, if they want.


Cheers,
Benjamin

[0] https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/dynamic-lighting-devices
[1] https://www.usb.org/sites/default/files/hut1_5.pdf
Benjamin Tissoires Oct. 3, 2024, 4:01 p.m. UTC | #38
On Oct 02 2024, Armin Wolf wrote:
> Am 02.10.24 um 10:42 schrieb Benjamin Tissoires:
> 
> > On Oct 01 2024, Werner Sembach wrote:
> > > Hi Armin,
> > > 
> > > Am 01.10.24 um 18:45 schrieb Armin Wolf:
> > [...snipped...]
> > > > Why not having a simple led driver for HID LampArray devices which exposes the
> > > > whole LampArray as a single LED?
> > > Yes that is my plan, but see my last reply to Benjamin, it might not be
> > > trivial as different leds in the same LampArray might have different max
> > > values for red, green, blue, and intensity. And the LampArray spec even
> > > allows to mix RGB and non-RGB leds.
> > > > If userspace wants to have direct control over the underlying LampArray device,
> > > > it just needs to unbind the default driver (maybe udev can be useful here?).
> > > There was something in the last discussion why this might not work, but i
> > > can't put my finger on it.
> > We recently have the exact same problem, so it's still fresh in my
> > memory. And here are what is happening:
> > - you can unbind the driver with a sysfs command for sure
> > - but then the device is not attached to a driver so HID core doesn't
> >    expose the hidraw node
> > - you'd think "we can just rebind it to hid-generic", but that doesn't
> >    work because hid-generic sees that there is already a loaded driver
> >    that can handle the device and it'll reject itself because it gives
> >    priority over the other driver
> > - what works is that you might be able to unload the other driver, but
> >    if it's already used by something else (like hid-multitouch), you
> >    don't want to do that. And also if you unload that driver, whenever
> >    the driver gets re-inserted, hid-generic will unbind itself, so back
> >    to square one
> > 
> > So unless we find a way to forward the "manual" binding to hid-generic,
> > and/or we can also quirk the device with
> > HID_QUIRK_IGNORE_SPECIAL_DRIVER[0] just unbinding the device doesn't
> > work.
> > 
> > Cheers,
> > Benjamin
> 
> I see, maybe we can add support for the driver_override mechanism to the HID bus?

hmm, we can, but only a couple of drivers would be valid: hid-multitouch
and hid-generic AFAICT. All of the others are device specific, so
allowing anybody to map a device to it might not work (if the driver
requires driver_data).

> Basically userspace could use the driver_override mechanism to forcefully bind hid-generic
> to a given HID device even if a compatible HID driver already exists.
> 

that coud be an option. But in that case, I wonder if the LampArray
implementation should be done in hid-led or in hid-input.c (the generic
part). I don't know if the new devices will export one HID device for
LampArray and one other for the rest, when the rest might need a
specific driver.

Anyway, thanks for the tip :)

Cheers,
Benjamin

> Thanks,
> Armin Wolf
> 
> > PS: brain fart:
> > if HID LampArray support (whatever the implementation, through Pavel's
> > new API or simple LED emulation) is in hid-input, we can also simply add
> > a new HID quirk to enable this or not, and use that quirk dynamically
> > (yes, with BPF :-P ) to rebind the device...
> > 
> > [0] https://lore.kernel.org/linux-input/20241001-hid-bpf-hid-generic-v3-0-2ef1019468df@kernel.org/T/#t
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index cc40a9d9b8cd1..3385ad51af194 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23358,6 +23358,12 @@  T:	git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat
 F:	tools/power/x86/turbostat/
 F:	tools/testing/selftests/turbostat/
 
+TUXEDO DRIVERS
+M:	Werner Sembach <wse@tuxedocomputers.com>
+L:	platform-driver-x86@vger.kernel.org
+S:	Supported
+F:	drivers/platform/x86/tuxedo/
+
 TW5864 VIDEO4LINUX DRIVER
 M:	Bluecherry Maintainers <maintainers@bluecherrydvr.com>
 M:	Andrey Utkin <andrey.utkin@corp.bluecherry.net>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index ddfccc226751f..c7cffb222adac 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -1196,3 +1196,5 @@  config P2SB
 	  The main purpose of this library is to unhide P2SB device in case
 	  firmware kept it hidden on some platforms in order to access devices
 	  behind it.
+
+source "drivers/platform/x86/tuxedo/Kconfig"
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e1b1429470674..1562dcd7ad9a5 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -153,3 +153,6 @@  obj-$(CONFIG_WINMATE_FM07_KEYS)		+= winmate-fm07-keys.o
 
 # SEL
 obj-$(CONFIG_SEL3350_PLATFORM)		+= sel3350-platform.o
+
+# TUXEDO
+obj-y					+= tuxedo/
diff --git a/drivers/platform/x86/tuxedo/Kbuild b/drivers/platform/x86/tuxedo/Kbuild
new file mode 100644
index 0000000000000..5a3506ab98131
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/Kbuild
@@ -0,0 +1,9 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# TUXEDO X86 Platform Specific Drivers
+#
+
+tuxedo_nb04_wmi_ab-y			:= tuxedo_nb04_wmi_ab_init.o
+tuxedo_nb04_wmi_ab-y			+= tuxedo_nb04_wmi_util.o
+tuxedo_nb04_wmi_ab-y			+= tuxedo_nb04_wmi_ab_virtual_lamp_array.o
+obj-$(CONFIG_TUXEDO_NB04_WMI_AB)	+= tuxedo_nb04_wmi_ab.o
diff --git a/drivers/platform/x86/tuxedo/Kconfig b/drivers/platform/x86/tuxedo/Kconfig
new file mode 100644
index 0000000000000..b1f7c6ceeaae4
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/Kconfig
@@ -0,0 +1,14 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# TUXEDO X86 Platform Specific Drivers
+#
+
+menuconfig TUXEDO_NB04_WMI_AB
+	tristate "TUXEDO NB04 WMI AB Platform Driver"
+	default m
+	help
+	  This driver implements the WMI AB device found on TUXEDO Notebooks
+	  with board vendor NB04. For the time being only the keyboard backlight
+	  control is implemented.
+
+	  When compiled as a module it will be called tuxedo_nb04_wmi_ab.
diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
new file mode 100644
index 0000000000000..6e4446b0e3dd8
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c
@@ -0,0 +1,86 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This driver implements the WMI AB device found on TUXEDO Notebooks with board
+ * vendor NB04.
+ *
+ * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/wmi.h>
+#include <linux/dmi.h>
+
+#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
+
+#include "tuxedo_nb04_wmi_ab_init.h"
+
+// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe
+// side we therefore only run this driver on tested devices defined by this list.
+static const struct dmi_system_id tested_devices_dmi_table[] = {
+	{
+		// TUXEDO Sirius 16 Gen1
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"),
+		},
+	},
+	{
+		// TUXEDO Sirius 16 Gen2
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"),
+		},
+	},
+	{ }
+};
+
+static int probe(struct wmi_device *wdev, const void __always_unused *context)
+{
+	struct tuxedo_nb04_wmi_driver_data_t *driver_data;
+
+	if (dmi_check_system(tested_devices_dmi_table))
+		return -ENODEV;
+
+	driver_data = devm_kzalloc(&wdev->dev, sizeof(struct tuxedo_nb04_wmi_driver_data_t),
+				   GFP_KERNEL);
+	if (!driver_data)
+		return -ENOMEM;
+
+	mutex_init(&driver_data->wmi_access_mutex);
+
+	dev_set_drvdata(&wdev->dev, driver_data);
+
+	tuxedo_nb04_virtual_lamp_array_add_device(wdev, &driver_data->virtual_lamp_array_hdev);
+
+	return 0;
+}
+
+static void remove(struct wmi_device *wdev)
+{
+	struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data;
+
+	hid_destroy_device(driver_data->virtual_lamp_array_hdev);
+}
+
+static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = {
+	{ .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" },
+	{ }
+};
+MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids);
+
+static struct wmi_driver tuxedo_nb04_wmi_ab_driver = {
+	.driver = {
+		.name = "tuxedo_nb04_wmi_ab",
+		.owner = THIS_MODULE
+	},
+	.id_table = tuxedo_nb04_wmi_ab_device_ids,
+	.probe = probe,
+	.remove = remove
+};
+module_wmi_driver(tuxedo_nb04_wmi_ab_driver);
+
+MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices");
+MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
new file mode 100644
index 0000000000000..aebfd465c9b61
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h
@@ -0,0 +1,20 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This driver implements the WMI AB device found on TUXEDO Notebooks with board
+ * vendor NB04.
+ *
+ * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
+ */
+
+#ifndef TUXEDO_NB04_WMI_AB_INIT_H
+#define TUXEDO_NB04_WMI_AB_INIT_H
+
+#include <linux/mutex.h>
+#include <linux/hid.h>
+
+struct tuxedo_nb04_wmi_driver_data_t {
+	struct mutex wmi_access_mutex;
+	struct hid_device *virtual_lamp_array_hdev;
+};
+
+#endif
diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
new file mode 100644
index 0000000000000..04af19aa6ad5f
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c
@@ -0,0 +1,741 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This code gives the built in RGB lighting of the TUXEDO NB04 devices a
+ * standardised interface, namely HID LampArray.
+ *
+ * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "tuxedo_nb04_wmi_util.h"
+
+#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h"
+
+#define dev_to_wdev(__dev)	container_of(__dev, struct wmi_device, dev)
+
+enum report_ids {
+	LAMP_ARRAY_ATTRIBUTES_REPORT_ID		= 0x01,
+	LAMP_ATTRIBUTES_REQUEST_REPORT_ID	= 0x02,
+	LAMP_ATTRIBUTES_RESPONSE_REPORT_ID	= 0x03,
+	LAMP_MULTI_UPDATE_REPORT_ID		= 0x04,
+	LAMP_RANGE_UPDATE_REPORT_ID		= 0x05,
+	LAMP_ARRAY_CONTROL_REPORT_ID		= 0x06,
+};
+
+static const uint8_t sirius_16_ansii_kbl_mapping[] = {
+	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
+	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
+	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
+	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
+	0x13, 0x2f, 0x30, 0x31,               0x5f, 0x60, 0x61,
+	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
+	0x33, 0x34, 0x28,                     0x5c, 0x5d, 0x5e, 0x57,
+	0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37,
+	0x38, 0xe5, 0x52,                     0x59, 0x5a, 0x5b,
+	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
+	0x4f,                                 0x62, 0x63, 0x58
+};
+
+static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = {
+	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
+	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
+	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
+	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
+	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
+	218000, 236500, 255000, 273500,                   294500, 311200, 327900,
+	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
+	223500, 242000, 267500,                           294500, 311200, 327900, 344600,
+	 37000,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000,
+	232500, 251500, 273500,                           294500, 311200, 327900,
+	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
+	292000,                                           311200, 327900, 344600
+};
+
+static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = {
+	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
+	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
+	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
+	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
+	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
+	 85500,  85500,  85500,  85500,                    85500,  85500,  85500,
+	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
+	103500, 103500, 103500,                           103500, 103500, 103500,  94500,
+	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
+	121500, 121500, 129000,                           121500, 121500, 121500,
+	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
+	147000,                                           139500, 139500, 130500
+};
+
+static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = {
+	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
+	  5000,   5000,   5000,   5000,   5000,   5000,     5000,   5000,   5000,   5000,
+	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
+	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
+	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
+	  5500,   5500,   5500,   5500,                     5500,   5500,   5500,
+	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
+	  5750,   5750,   5750,                             5750,   5750,   5750,   5625,
+	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
+	  6000,   6000,   6125,                             6000,   6000,   6000,
+	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
+	  6375,                                             6250,   6250,   6125
+};
+
+static const uint8_t sirius_16_iso_kbl_mapping[] = {
+	0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42,
+	0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c,   0x4a, 0x4d, 0x4b, 0x4e,
+	0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+	0x27, 0x2d, 0x2e, 0x2a,               0x53, 0x55, 0x54, 0x56,
+	0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12,
+	0x13, 0x2f, 0x30,                     0x5f, 0x60, 0x61,
+	0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f,
+	0x33, 0x34, 0x32, 0x28,               0x5c, 0x5d, 0x5e, 0x57,
+	0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36,
+	0x37, 0x38, 0xe5, 0x52,               0x59, 0x5a, 0x5b,
+	0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51,
+	0x4f,                                 0x62, 0x63, 0x58
+};
+
+static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = {
+	 25000,  41700,  58400,  75100,  91800, 108500, 125200, 141900, 158600, 175300,
+	192000, 208700, 225400, 242100, 258800, 275500,   294500, 311200, 327900, 344600,
+	 24500,  42500,  61000,  79500,  98000, 116500, 135000, 153500, 172000, 190500,
+	209000, 227500, 246000, 269500,                   294500, 311200, 327900, 344600,
+	 31000,  51500,  70000,  88500, 107000, 125500, 144000, 162500, 181000, 199500,
+	218000, 234500, 251000,                           294500, 311200, 327900,
+	 33000,  57000,  75500,  94000, 112500, 131000, 149500, 168000, 186500, 205000,
+	223500, 240000, 256500, 271500,                   294500, 311200, 327900, 344600,
+	 28000,  47500,  66000,  84500, 103000, 121500, 140000, 158500, 177000, 195500,
+	214000, 232500, 251500, 273500,                   294500, 311200, 327900,
+	 28000,  47500,  66000,  84500, 140000, 195500, 214000, 234000, 255000, 273500,
+	292000,                                           311200, 327900, 344600
+};
+
+static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = {
+	 53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,  53000,
+	 53000,  53000,  53000,  53000,  53000,  53000,    53000,  53000,  53000,  53000,
+	 67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,  67500,
+	 67500,  67500,  67500,  67500,                    67500,  67500,  67500,  67500,
+	 85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,  85500,
+	 85500,  85500,  85500,                            85500,  85500,  85500,
+	103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500,
+	103500, 103500, 103500,  94500,                   103500, 103500, 103500,  94500,
+	121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500,
+	121500, 121500, 121500, 129000,                   121500, 121500, 121500,
+	139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000,
+	147000,                                           139500, 139500, 130500
+};
+
+static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = {
+	  5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,   5000,
+	  5000,   5000,   5000,   5000, 5000, 5000,         5000,   5000,   5000,   5000,
+	  5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,   5250,
+	  5250,   5250,   5250,   5250,                     5250,   5250,   5250,   5250,
+	  5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,   5500,
+	  5500,   5500,   5500,                             5500,   5500,   5500,
+	  5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,   5750,
+	  5750,   5750,   5750,   5750,                     5750,   5750,   5750,   5625,
+	  6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,   6000,
+	  6000,   6000,   6000,   6125,                     6000,   6000,   6000,
+	  6250,   6250,   6250,   6250,   6250,   6250,   6250,   6250,   6375,   6375,
+	  6375,                                             6250,   6250,   6125
+};
+
+struct driver_data_t {
+	uint8_t keyboard_type;
+	uint8_t lamp_count;
+	uint8_t next_lamp_id;
+	union tuxedo_nb04_wmi_496_b_in_80_b_out_input next_kbl_set_multiple_keys_input;
+};
+
+
+static int ll_start(struct hid_device *hdev)
+{
+	int ret;
+	struct driver_data_t *driver_data;
+	struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
+	union tuxedo_nb04_wmi_8_b_in_80_b_out_input input;
+	union tuxedo_nb04_wmi_8_b_in_80_b_out_output output;
+
+	driver_data = devm_kzalloc(&hdev->dev, sizeof(struct driver_data_t), GFP_KERNEL);
+	if (!driver_data)
+		return -ENOMEM;
+
+	input.get_device_status_input.device_type = WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD;
+	ret = tuxedo_nb04_wmi_8_b_in_80_b_out(wdev, WMI_AB_GET_DEVICE_STATUS, &input, &output);
+	if (ret)
+		return ret;
+
+	driver_data->keyboard_type = output.get_device_status_output.keyboard_physical_layout;
+	driver_data->lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
+	driver_data->next_lamp_id = 0;
+
+	hdev->driver_data = driver_data;
+
+	return ret;
+}
+
+
+static void ll_stop(struct hid_device __always_unused *hdev)
+{
+}
+
+
+static int ll_open(struct hid_device __always_unused *hdev)
+{
+	return 0;
+}
+
+
+static void ll_close(struct hid_device __always_unused *hdev)
+{
+}
+
+
+static uint8_t report_descriptor[327] = {
+	0x05, 0x59,			// Usage Page (Lighting and Illumination)
+	0x09, 0x01,			// Usage (Lamp Array)
+	0xa1, 0x01,			// Collection (Application)
+	0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, //  Report ID (1)
+	0x09, 0x02,			//  Usage (Lamp Array Attributes Report)
+	0xa1, 0x02,			//  Collection (Logical)
+	0x09, 0x03,			//   Usage (Lamp Count)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
+	0x75, 0x10,			//   Report Size (16)
+	0x95, 0x01,			//   Report Count (1)
+	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
+	0x09, 0x04,			//   Usage (Bounding Box Width In Micrometers)
+	0x09, 0x05,			//   Usage (Bounding Box Height In Micrometers)
+	0x09, 0x06,			//   Usage (Bounding Box Depth In Micrometers)
+	0x09, 0x07,			//   Usage (Lamp Array Kind)
+	0x09, 0x08,			//   Usage (Min Update Interval In Microseconds)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
+	0x75, 0x20,			//   Report Size (32)
+	0x95, 0x05,			//   Report Count (5)
+	0xb1, 0x03,			//   Feature (Cnst,Var,Abs)
+	0xc0,				//  End Collection
+	0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, //  Report ID (2)
+	0x09, 0x20,			//  Usage (Lamp Attributes Request Report)
+	0xa1, 0x02,			//  Collection (Logical)
+	0x09, 0x21,			//   Usage (Lamp Id)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
+	0x75, 0x10,			//   Report Size (16)
+	0x95, 0x01,			//   Report Count (1)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0xc0,				//  End Collection
+	0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, //  Report ID (3)
+	0x09, 0x22,			//  Usage (Lamp Attributes Response Report)
+	0xa1, 0x02,			//  Collection (Logical)
+	0x09, 0x21,			//   Usage (Lamp Id)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
+	0x75, 0x10,			//   Report Size (16)
+	0x95, 0x01,			//   Report Count (1)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0x09, 0x23,			//   Usage (Position X In Micrometers)
+	0x09, 0x24,			//   Usage (Position Y In Micrometers)
+	0x09, 0x25,			//   Usage (Position Z In Micrometers)
+	0x09, 0x27,			//   Usage (Update Latency In Microseconds)
+	0x09, 0x26,			//   Usage (Lamp Purposes)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0xff, 0x7f,	//   Logical Maximum (2147483647)
+	0x75, 0x20,			//   Report Size (32)
+	0x95, 0x05,			//   Report Count (5)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0x09, 0x28,			//   Usage (Red Level Count)
+	0x09, 0x29,			//   Usage (Green Level Count)
+	0x09, 0x2a,			//   Usage (Blue Level Count)
+	0x09, 0x2b,			//   Usage (Intensity Level Count)
+	0x09, 0x2c,			//   Usage (Is Programmable)
+	0x09, 0x2d,			//   Usage (Input Binding)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x26, 0xff, 0x00,		//   Logical Maximum (255)
+	0x75, 0x08,			//   Report Size (8)
+	0x95, 0x06,			//   Report Count (6)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0xc0,				//  End Collection
+	0x85, LAMP_MULTI_UPDATE_REPORT_ID, //  Report ID (4)
+	0x09, 0x50,			//  Usage (Lamp Multi Update Report)
+	0xa1, 0x02,			//  Collection (Logical)
+	0x09, 0x03,			//   Usage (Lamp Count)
+	0x09, 0x55,			//   Usage (Lamp Update Flags)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x25, 0x08,			//   Logical Maximum (8)
+	0x75, 0x08,			//   Report Size (8)
+	0x95, 0x02,			//   Report Count (2)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0x09, 0x21,			//   Usage (Lamp Id)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
+	0x75, 0x10,			//   Report Size (16)
+	0x95, 0x08,			//   Report Count (8)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x26, 0xff, 0x00,		//   Logical Maximum (255)
+	0x75, 0x08,			//   Report Size (8)
+	0x95, 0x20,			//   Report Count (32)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0xc0,				//  End Collection
+	0x85, LAMP_RANGE_UPDATE_REPORT_ID, //  Report ID (5)
+	0x09, 0x60,			//  Usage (Lamp Range Update Report)
+	0xa1, 0x02,			//  Collection (Logical)
+	0x09, 0x55,			//   Usage (Lamp Update Flags)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x25, 0x08,			//   Logical Maximum (8)
+	0x75, 0x08,			//   Report Size (8)
+	0x95, 0x01,			//   Report Count (1)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0x09, 0x61,			//   Usage (Lamp Id Start)
+	0x09, 0x62,			//   Usage (Lamp Id End)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x27, 0xff, 0xff, 0x00, 0x00,	//   Logical Maximum (65535)
+	0x75, 0x10,			//   Report Size (16)
+	0x95, 0x02,			//   Report Count (2)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0x09, 0x51,			//   Usage (Red Update Channel)
+	0x09, 0x52,			//   Usage (Green Update Channel)
+	0x09, 0x53,			//   Usage (Blue Update Channel)
+	0x09, 0x54,			//   Usage (Intensity Update Channel)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x26, 0xff, 0x00,		//   Logical Maximum (255)
+	0x75, 0x08,			//   Report Size (8)
+	0x95, 0x04,			//   Report Count (4)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0xc0,				//  End Collection
+	0x85, LAMP_ARRAY_CONTROL_REPORT_ID, //  Report ID (6)
+	0x09, 0x70,			//  Usage (Lamp Array Control Report)
+	0xa1, 0x02,			//  Collection (Logical)
+	0x09, 0x71,			//   Usage (Autonomous Mode)
+	0x15, 0x00,			//   Logical Minimum (0)
+	0x25, 0x01,			//   Logical Maximum (1)
+	0x75, 0x08,			//   Report Size (8)
+	0x95, 0x01,			//   Report Count (1)
+	0xb1, 0x02,			//   Feature (Data,Var,Abs)
+	0xc0,				//  End Collection
+	0xc0				// End Collection
+};
+
+static int ll_parse(struct hid_device *hdev)
+{
+	return hid_parse_report(hdev, report_descriptor, sizeof(report_descriptor));
+}
+
+
+struct __packed lamp_array_attributes_report_t {
+	const uint8_t report_id;
+	uint16_t lamp_count;
+	uint32_t bounding_box_width_in_micrometers;
+	uint32_t bounding_box_height_in_micrometers;
+	uint32_t bounding_box_depth_in_micrometers;
+	uint32_t lamp_array_kind;
+	uint32_t min_update_interval_in_microseconds;
+};
+
+static int handle_lamp_array_attributes_report(struct hid_device *hdev,
+					       struct lamp_array_attributes_report_t *rep)
+{
+	struct driver_data_t *driver_data = hdev->driver_data;
+
+	rep->lamp_count = driver_data->lamp_count;
+	rep->bounding_box_width_in_micrometers = 368000;
+	rep->bounding_box_height_in_micrometers = 266000;
+	rep->bounding_box_depth_in_micrometers = 30000;
+	// LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of "HID Usage Tables v1.5"
+	rep->lamp_array_kind = 1;
+	// Some guessed value for interval microseconds
+	rep->min_update_interval_in_microseconds = 500;
+
+	return sizeof(struct lamp_array_attributes_report_t);
+}
+
+
+struct __packed lamp_attributes_request_report_t {
+	const uint8_t report_id;
+	uint16_t lamp_id;
+};
+
+static int handle_lamp_attributes_request_report(struct hid_device *hdev,
+						 struct lamp_attributes_request_report_t *rep)
+{
+	struct driver_data_t *driver_data = hdev->driver_data;
+
+	if (rep->lamp_id < driver_data->lamp_count)
+		driver_data->next_lamp_id = rep->lamp_id;
+	else
+		driver_data->next_lamp_id = 0;
+
+	return sizeof(struct lamp_attributes_request_report_t);
+}
+
+
+struct __packed lamp_attributes_response_report_t {
+	const uint8_t report_id;
+	uint16_t lamp_id;
+	uint32_t position_x_in_micrometers;
+	uint32_t position_y_in_micrometers;
+	uint32_t position_z_in_micrometers;
+	uint32_t update_latency_in_microseconds;
+	uint32_t lamp_purpose;
+	uint8_t red_level_count;
+	uint8_t green_level_count;
+	uint8_t blue_level_count;
+	uint8_t intensity_level_count;
+	uint8_t is_programmable;
+	uint8_t input_binding;
+};
+
+static int handle_lamp_attributes_response_report(struct hid_device *hdev,
+						  struct lamp_attributes_response_report_t *rep)
+{
+	struct driver_data_t *driver_data = hdev->driver_data;
+	uint16_t lamp_id = driver_data->next_lamp_id;
+	const uint8_t *kbl_mapping;
+	const uint32_t *kbl_mapping_pos_x, *kbl_mapping_pos_y, *kbl_mapping_pos_z;
+
+	rep->lamp_id = lamp_id;
+	// Some guessed value for latency microseconds
+	rep->update_latency_in_microseconds = 100;
+	 // LampPurposeControl, see "26.3.1 LampPurposes Flags" of "HID Usage Tables v1.5"
+	rep->lamp_purpose = 1;
+	rep->red_level_count = 0xff;
+	rep->green_level_count = 0xff;
+	rep->blue_level_count = 0xff;
+	rep->intensity_level_count = 0xff;
+	rep->is_programmable = 1;
+
+	if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) {
+		kbl_mapping = &sirius_16_ansii_kbl_mapping[0];
+		kbl_mapping_pos_x = &sirius_16_ansii_kbl_mapping_pos_x[0];
+		kbl_mapping_pos_y = &sirius_16_ansii_kbl_mapping_pos_y[0];
+		kbl_mapping_pos_z = &sirius_16_ansii_kbl_mapping_pos_z[0];
+	} else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) {
+		kbl_mapping = &sirius_16_iso_kbl_mapping[0];
+		kbl_mapping_pos_x = &sirius_16_iso_kbl_mapping_pos_x[0];
+		kbl_mapping_pos_y = &sirius_16_iso_kbl_mapping_pos_y[0];
+		kbl_mapping_pos_z = &sirius_16_iso_kbl_mapping_pos_z[0];
+	} else
+		return -EINVAL;
+
+	if (kbl_mapping[lamp_id] <= 0xe8)
+		rep->input_binding = kbl_mapping[lamp_id];
+	else
+		// Everything bigger is reserved/undefined, see "10 Keyboard/Keypad Page (0x07)" of
+		// "HID Usage Tables v1.5" and should return 0, see "26.8.3 Lamp Attributes" of the
+		// same document.
+		rep->input_binding = 0;
+	rep->position_x_in_micrometers = kbl_mapping_pos_x[lamp_id];
+	rep->position_y_in_micrometers = kbl_mapping_pos_y[lamp_id];
+	rep->position_z_in_micrometers = kbl_mapping_pos_z[lamp_id];
+
+	driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % driver_data->lamp_count;
+
+	return sizeof(struct lamp_attributes_response_report_t);
+}
+
+
+#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE	BIT(0)
+
+struct __packed lamp_multi_update_report_t {
+	const uint8_t report_id;
+	uint8_t lamp_count;
+	uint8_t lamp_update_flags;
+	uint16_t lamp_id[8];
+	struct {
+		uint8_t red;
+		uint8_t green;
+		uint8_t blue;
+		uint8_t intensity;
+	} update_channels[8];
+};
+
+static int handle_lamp_multi_update_report(struct hid_device *hdev,
+					   struct lamp_multi_update_report_t *rep)
+{
+	int ret;
+	struct driver_data_t *driver_data = hdev->driver_data;
+	struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent);
+	uint8_t lamp_count, key_id, key_id_j;
+	union tuxedo_nb04_wmi_496_b_in_80_b_out_input *next =
+		&driver_data->next_kbl_set_multiple_keys_input;
+	union tuxedo_nb04_wmi_496_b_in_80_b_out_output output;
+
+	// Catching missformated lamp_multi_update_report and fail silently according to
+	// "HID Usage Tables v1.5"
+	for (int i = 0; i < rep->lamp_count; ++i) {
+		if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
+			lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
+		else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
+			lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
+
+		if (rep->lamp_id[i] > lamp_count) {
+			pr_debug("Out of bounds lamp_id in lamp_multi_update_report. Skippng whole report!\n");
+			return sizeof(struct lamp_multi_update_report_t);
+		}
+
+		for (int j = i + 1; j < rep->lamp_count; ++j) {
+			if (rep->lamp_id[i] == rep->lamp_id[j]) {
+				pr_debug("Duplicate lamp_id in lamp_multi_update_report. Skippng whole report!\n");
+				return sizeof(struct lamp_multi_update_report_t);
+			}
+		}
+	}
+
+	for (int i = 0; i < rep->lamp_count; ++i) {
+		if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
+			key_id = sirius_16_ansii_kbl_mapping[rep->lamp_id[i]];
+		else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
+			key_id = sirius_16_iso_kbl_mapping[rep->lamp_id[i]];
+
+		for (int j = 0; j < WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; ++j) {
+			key_id_j = next->kbl_set_multiple_keys_input.lighting_settings[j].key_id;
+			if (key_id_j == 0x00 || key_id_j == key_id) {
+				if (key_id_j == 0x00)
+					next->kbl_set_multiple_keys_input.lighting_setting_count =
+						j + 1;
+				next->kbl_set_multiple_keys_input.lighting_settings[j].key_id =
+					key_id;
+				// While this driver respects
+				// intensity_update_channel according to "HID
+				// Usage Tables v1.5" also on RGB leds, the
+				// Microsoft MacroPad reference implementation
+				// (https://github.com/microsoft/RP2040MacropadHidSample
+				// 1d6c3ad) does not and ignores it. If it turns
+				// out that Windows writes intensity = 0 for RGB
+				// leds instead of intensity = 255, this driver
+				// should also irgnore the
+				// intensity_update_channel.
+				next->kbl_set_multiple_keys_input.lighting_settings[j].red =
+					rep->update_channels[i].red
+						* rep->update_channels[i].intensity / 0xff;
+				next->kbl_set_multiple_keys_input.lighting_settings[j].green =
+					rep->update_channels[i].green
+						* rep->update_channels[i].intensity / 0xff;
+				next->kbl_set_multiple_keys_input.lighting_settings[j].blue =
+					rep->update_channels[i].blue
+						* rep->update_channels[i].intensity / 0xff;
+
+				break;
+			}
+		}
+	}
+
+	if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) {
+		ret = tuxedo_nb04_wmi_496_b_in_80_b_out(wdev, WMI_AB_KBL_SET_MULTIPLE_KEYS, next,
+							&output);
+		memset(next, 0, sizeof(union tuxedo_nb04_wmi_496_b_in_80_b_out_input));
+		if (ret)
+			return ret;
+	}
+
+	return sizeof(struct lamp_multi_update_report_t);
+}
+
+
+struct __packed lamp_range_update_report_t {
+	const uint8_t report_id;
+	uint8_t lamp_update_flags;
+	uint16_t lamp_id_start;
+	uint16_t lamp_id_end;
+	uint8_t red_update_channel;
+	uint8_t green_update_channel;
+	uint8_t blue_update_channel;
+	uint8_t intensity_update_channel;
+};
+
+static int handle_lamp_range_update_report(struct hid_device *hdev,
+					   struct lamp_range_update_report_t *report)
+{
+	int ret;
+	struct driver_data_t *driver_data = hdev->driver_data;
+	uint8_t lamp_count;
+	struct lamp_multi_update_report_t lamp_multi_update_report = {
+		.report_id = LAMP_MULTI_UPDATE_REPORT_ID
+	};
+
+	// Catching missformated lamp_range_update_report and fail silently according to
+	// "HID Usage Tables v1.5"
+	if (report->lamp_id_start > report->lamp_id_end) {
+		pr_debug("lamp_id_start > lamp_id_end in lamp_range_update_report. Skippng whole report!\n");
+		return sizeof(struct lamp_range_update_report_t);
+	}
+
+	if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII)
+		lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
+	else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO)
+		lamp_count = sizeof(sirius_16_ansii_kbl_mapping);
+
+	if (report->lamp_id_end > lamp_count - 1) {
+		pr_debug("Out of bounds lamp_id_* in lamp_range_update_report. Skippng whole report!\n");
+		return sizeof(struct lamp_range_update_report_t);
+	}
+
+	// Break handle_lamp_range_update_report call down to multiple
+	// handle_lamp_multi_update_report calls to easily ensure that mixing
+	// handle_lamp_range_update_report and handle_lamp_multi_update_report
+	// does not break things.
+	for (int i = report->lamp_id_start; i < report->lamp_id_end + 1; i = i + 8) {
+		lamp_multi_update_report.lamp_count = MIN(report->lamp_id_end + 1 - i, 8);
+		if (i + lamp_multi_update_report.lamp_count == report->lamp_id_end + 1)
+			lamp_multi_update_report.lamp_update_flags |=
+				LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE;
+
+		for (int j = 0; j < lamp_multi_update_report.lamp_count; ++j) {
+			lamp_multi_update_report.lamp_id[j] = i + j;
+			lamp_multi_update_report.update_channels[j].red =
+				report->red_update_channel;
+			lamp_multi_update_report.update_channels[j].green =
+				report->green_update_channel;
+			lamp_multi_update_report.update_channels[j].blue =
+				report->blue_update_channel;
+			lamp_multi_update_report.update_channels[j].intensity =
+				report->intensity_update_channel;
+		}
+
+		ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report);
+		if (ret < 0)
+			return ret;
+		else if (ret != sizeof(struct lamp_multi_update_report_t))
+			return -EIO;
+	}
+
+	return sizeof(struct lamp_range_update_report_t);
+}
+
+
+struct __packed lamp_array_control_report_t {
+	const uint8_t report_id;
+	uint8_t autonomous_mode;
+};
+
+static int handle_lamp_array_control_report(struct hid_device __always_unused *hdev,
+					    struct lamp_array_control_report_t __always_unused *rep)
+{
+	// The keyboard firmware doesn't have any built in effects or controls
+	// so this is a NOOP.
+	// According to the HID Documentation (HID Usage Tables v1.5) this
+	// function is optional and can be removed from the HID Report
+	// Descriptor, but it should first be confirmed that userspace respects
+	// this possibility too. The Microsoft MacroPad reference implementation
+	// (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad)
+	// already deviates from the spec at another point, see
+	// handle_lamp_*_update_report.
+
+	return sizeof(struct lamp_array_control_report_t);
+}
+
+
+static int ll_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len,
+			   unsigned char rtype, int reqtype)
+{
+	int ret;
+
+	pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype,
+		 reqtype, reportnum, len);
+	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len);
+
+	ret = -EINVAL;
+	if (rtype == HID_FEATURE_REPORT) {
+		if (reqtype == HID_REQ_GET_REPORT) {
+			if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID
+			    && len == sizeof(struct lamp_array_attributes_report_t))
+				ret = handle_lamp_array_attributes_report(
+					hdev, (struct lamp_array_attributes_report_t *)buf);
+			else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID
+			    && len == sizeof(struct lamp_attributes_response_report_t))
+				ret = handle_lamp_attributes_response_report(
+					hdev, (struct lamp_attributes_response_report_t *)buf);
+		} else if (reqtype == HID_REQ_SET_REPORT) {
+			if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID
+			    && len == sizeof(struct lamp_attributes_request_report_t))
+				ret = handle_lamp_attributes_request_report(
+					hdev, (struct lamp_attributes_request_report_t *)buf);
+			else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID
+			    && len == sizeof(struct lamp_multi_update_report_t))
+				ret = handle_lamp_multi_update_report(
+					hdev, (struct lamp_multi_update_report_t *)buf);
+			else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID
+			    && len == sizeof(struct lamp_range_update_report_t))
+				ret = handle_lamp_range_update_report(
+					hdev, (struct lamp_range_update_report_t *)buf);
+			else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID
+			    && len == sizeof(struct lamp_array_control_report_t))
+				ret = handle_lamp_array_control_report(
+					hdev, (struct lamp_array_control_report_t *)buf);
+		}
+	}
+
+	return ret;
+}
+
+static const struct hid_ll_driver ll_driver = {
+	.start = &ll_start,
+	.stop = &ll_stop,
+	.open = &ll_open,
+	.close = &ll_close,
+	.parse = &ll_parse,
+	.raw_request = &ll_raw_request,
+};
+
+int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev, struct hid_device **hdev_out)
+{
+	int ret;
+	struct hid_device *hdev;
+
+	pr_debug("Adding TUXEDO NB04 Virtual LampArray device.\n");
+
+	hdev = hid_allocate_device();
+	if (IS_ERR(hdev))
+		return PTR_ERR(hdev);
+	*hdev_out = hdev;
+
+	strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name));
+
+	hdev->ll_driver = &ll_driver;
+	hdev->bus = BUS_VIRTUAL;
+	hdev->vendor = 0x21ba;
+	hdev->product = 0x0400;
+	hdev->dev.parent = &wdev->dev;
+
+	ret = hid_add_device(hdev);
+	if (ret)
+		hid_destroy_device(hdev);
+	return ret;
+}
+EXPORT_SYMBOL(tuxedo_nb04_virtual_lamp_array_add_device);
diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
new file mode 100644
index 0000000000000..fdc2a01d95c24
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h
@@ -0,0 +1,18 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This code gives the built in RGB lighting of the TUXEDO NB04 devices a
+ * standardised interface, namely HID LampArray.
+ *
+ * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
+ */
+
+#ifndef TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
+#define TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H
+
+#include <linux/wmi.h>
+#include <linux/hid.h>
+
+int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev,
+					      struct hid_device **hdev_out);
+
+#endif
diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
new file mode 100644
index 0000000000000..dbabdb9dd60c7
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c
@@ -0,0 +1,85 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This code gives functions to avoid code duplication while interacting with
+ * the TUXEDO NB04 wmi interfaces.
+ *
+ * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "tuxedo_nb04_wmi_ab_init.h"
+
+#include "tuxedo_nb04_wmi_util.h"
+
+static int __wmi_method_acpi_object_out(struct wmi_device *wdev, uint32_t wmi_method_id,
+					uint8_t *in, acpi_size in_len, union acpi_object **out)
+{
+	struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data;
+	struct acpi_buffer acpi_buffer_in = { in_len, in };
+	struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL };
+
+	pr_debug("Evaluate WMI method: %u in:\n", wmi_method_id);
+	print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len);
+
+	mutex_lock(&driver_data->wmi_access_mutex);
+	acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id, &acpi_buffer_in,
+						    &acpi_buffer_out);
+	mutex_unlock(&driver_data->wmi_access_mutex);
+	if (ACPI_FAILURE(status)) {
+		pr_err("Failed to evaluate WMI method.\n");
+		return -EIO;
+	}
+	if (!acpi_buffer_out.pointer) {
+		pr_err("Unexpected empty out buffer.\n");
+		return -ENODATA;
+	}
+
+	*out = acpi_buffer_out.pointer;
+
+	return 0;
+}
+
+static int __wmi_method_buffer_out(struct wmi_device *wdev, uint32_t wmi_method_id, uint8_t *in,
+				   acpi_size in_len, uint8_t *out, acpi_size out_len)
+{
+	int ret;
+	union acpi_object *acpi_object_out = NULL;
+
+	ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, in, in_len, &acpi_object_out);
+	if (ret)
+		return ret;
+
+	if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
+		pr_err("Unexpected out buffer type. Expected: %u Got: %u\n", ACPI_TYPE_BUFFER,
+		       acpi_object_out->type);
+		kfree(acpi_object_out);
+		return -EIO;
+	}
+	if (acpi_object_out->buffer.length != out_len) {
+		pr_err("Unexpected out buffer length.\n");
+		kfree(acpi_object_out);
+		return -EIO;
+	}
+
+	memcpy(out, acpi_object_out->buffer.pointer, out_len);
+	kfree(acpi_object_out);
+
+	return ret;
+}
+
+int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
+				    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method,
+				    union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input,
+				    union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output)
+{
+	return __wmi_method_buffer_out(wdev, method, input->raw, 8, output->raw, 80);
+}
+
+int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
+				      enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
+				      union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
+				      union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output)
+{
+	return __wmi_method_buffer_out(wdev, method, input->raw, 496, output->raw, 80);
+}
diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
new file mode 100644
index 0000000000000..2765cbe9fcfef
--- /dev/null
+++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h
@@ -0,0 +1,112 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This code gives functions to avoid code duplication while interacting with
+ * the TUXEDO NB04 wmi interfaces.
+ *
+ * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com
+ */
+
+#ifndef TUXEDO_NB04_WMI_UTIL_H
+#define TUXEDO_NB04_WMI_UTIL_H
+
+#include <linux/wmi.h>
+
+#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD	1
+#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD	2
+#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES	3
+
+#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_NONE		0
+#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY	1
+#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE	2
+#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY	3
+
+#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII	0
+#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO	1
+
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_RED		1
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_GREEN		2
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_YELLOW	3
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_BLUE		4
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_PURPLE	5
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_INDIGO	6
+#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_WHITE		7
+
+#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD	BIT(0)
+#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS	BIT(1)
+#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_KBL		BIT(2)
+#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS	BIT(3)
+
+
+union tuxedo_nb04_wmi_8_b_in_80_b_out_input {
+	uint8_t raw[8];
+	struct __packed {
+		uint8_t device_type;
+		uint8_t reserved_0[7];
+	} get_device_status_input;
+};
+
+union tuxedo_nb04_wmi_8_b_in_80_b_out_output {
+	uint8_t raw[80];
+	struct __packed {
+		uint16_t return_status;
+		uint8_t device_enabled;
+		uint8_t kbl_type;
+		uint8_t kbl_side_bar_supported;
+		uint8_t keyboard_physical_layout;
+		uint8_t app_pages;
+		uint8_t per_key_kbl_default_color;
+		uint8_t four_zone_kbl_default_color_1;
+		uint8_t four_zone_kbl_default_color_2;
+		uint8_t four_zone_kbl_default_color_3;
+		uint8_t four_zone_kbl_default_color_4;
+		uint8_t light_bar_kbl_default_color;
+		uint8_t reserved_0[1];
+		uint16_t dedicated_gpu_id;
+		uint8_t reserved_1[64];
+	} get_device_status_output;
+};
+
+enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods {
+	WMI_AB_GET_DEVICE_STATUS	= 2,
+};
+
+
+#define WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX	120
+
+union tuxedo_nb04_wmi_496_b_in_80_b_out_input {
+	uint8_t raw[496];
+	struct __packed {
+		uint8_t reserved_0[15];
+		uint8_t lighting_setting_count;
+		struct {
+			uint8_t key_id;
+			uint8_t red;
+			uint8_t green;
+			uint8_t blue;
+		} lighting_settings[WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX];
+	}  kbl_set_multiple_keys_input;
+};
+
+union tuxedo_nb04_wmi_496_b_in_80_b_out_output {
+	uint8_t raw[80];
+	struct __packed {
+		uint8_t return_value;
+		uint8_t reserved_0[79];
+	} kbl_set_multiple_keys_output;
+};
+
+enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods {
+	WMI_AB_KBL_SET_MULTIPLE_KEYS	= 6,
+};
+
+
+int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev,
+				    enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method,
+				    union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input,
+				    union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output);
+int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev,
+				      enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method,
+				      union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input,
+				      union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output);
+
+#endif