mbox series

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

Message ID 20240926174405.110748-1-wse@tuxedocomputers.com
Headers show
Series platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 | expand

Message

Werner Sembach Sept. 26, 2024, 5:44 p.m. UTC
Hi,
took some time but now a first working draft of the suggested new way of
handling per-key RGB keyboard backlights is finished. See:
https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
First time for me sending a whole new driver to the LKML, so please excuse
mistakes I might have made.

Known bugs:
- The device has a lightbar which is currently not implemented and
  therefore stuck to blue once the first backlight control command is send.

What is still missing:
- The leds fallback
- Lightbar control

Some general noob questions:

Initially I though it would be nice to have 2 modules, one jsut being the
wmi initialization and utility stuff and one just being the backlight logic
stuff, being loaded automatically via module_alias, but that would still
require me to create the virtual hid device during the wmi_ab probe, and
that already needs the ll_driver, so i guess I have to do it statically
like i did now?
Or in other words: I would have liked to have a module dependency graph
like this:
    tuxedo_nb04_lamp_array depends on tuxedo_nb04_platform (combining *_wmi_init and *_wmi_utility)
but if i currently split it into modules i would get this:
    tuxedo_nb04_wmi_ab_init dpends on tuxedo_nb04_wmi_ab_lamp_array depends on tuxedo_nb04_wmi_utility

Currently after creating the virtual hdev in the wmi init probe function I
have to keep track of it and manually destroy it during the wmi init
remove. Can this be automated devm_kzalloc-style?

Kind regards,
Werner Sembach

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
Werner Sembach Sept. 27, 2024, 11:24 a.m. UTC | #3
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
Benjamin Tissoires Sept. 27, 2024, 4:08 p.m. UTC | #4
On Sep 26 2024, Werner Sembach wrote:
> Hi,
> took some time but now a first working draft of the suggested new way of
> handling per-key RGB keyboard backlights is finished. See:
> https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
> First time for me sending a whole new driver to the LKML, so please excuse
> mistakes I might have made.
> 
> Known bugs:
> - The device has a lightbar which is currently not implemented and
>   therefore stuck to blue once the first backlight control command is send.
> 
> What is still missing:
> - The leds fallback
> - Lightbar control
> 
> Some general noob questions:
> 
> Initially I though it would be nice to have 2 modules, one jsut being the
> wmi initialization and utility stuff and one just being the backlight logic
> stuff, being loaded automatically via module_alias, but that would still
> require me to create the virtual hid device during the wmi_ab probe, and
> that already needs the ll_driver, so i guess I have to do it statically
> like i did now?
> Or in other words: I would have liked to have a module dependency graph
> like this:
>     tuxedo_nb04_lamp_array depends on tuxedo_nb04_platform (combining *_wmi_init and *_wmi_utility)
> but if i currently split it into modules i would get this:
>     tuxedo_nb04_wmi_ab_init dpends on tuxedo_nb04_wmi_ab_lamp_array depends on tuxedo_nb04_wmi_utility

On more general question to you: how much confident are you about your
LampArray implementation?

If you still need to add/fix stuff in it, I would advise you to have a
simple HID device, with bare minimum functionality, and then add the
LampArray functionality on top through HID-BPF. This way you can fix
LampArray out of band with the kernel, while having a more stable kernel
module. This should be possible with v6.11+.

Another solution is to still have your wmi-to-hid module, and then a
HID kernel module in drivers/hid that supports LampArray.

But I would strongly suggest while you are figuring out the userspace
part to stick to HID-BPF, and then once you are happy we can move to a
full kernel module.

Cheers,
Benjamin

> 
> Currently after creating the virtual hdev in the wmi init probe function I
> have to keep track of it and manually destroy it during the wmi init
> remove. Can this be automated devm_kzalloc-style?
> 
> Kind regards,
> Werner Sembach
> 
>
Armin Wolf Sept. 27, 2024, 5:15 p.m. UTC | #5
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 | #6
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 | #7
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
> +};
> +
Pavel Machek Sept. 27, 2024, 9:03 p.m. UTC | #8
On Fri 2024-09-27 18:08:52, Benjamin Tissoires wrote:
> On Sep 26 2024, Werner Sembach wrote:
> > Hi,
> > took some time but now a first working draft of the suggested new way of
> > handling per-key RGB keyboard backlights is finished. See:
> > https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
> > First time for me sending a whole new driver to the LKML, so please excuse
> > mistakes I might have made.
> > 
> > Known bugs:
> > - The device has a lightbar which is currently not implemented and
> >   therefore stuck to blue once the first backlight control command is send.
> > 
> > What is still missing:
> > - The leds fallback
> > - Lightbar control
> > 
> > Some general noob questions:
> > 
> > Initially I though it would be nice to have 2 modules, one jsut being the
> > wmi initialization and utility stuff and one just being the backlight logic
> > stuff, being loaded automatically via module_alias, but that would still
> > require me to create the virtual hid device during the wmi_ab probe, and
> > that already needs the ll_driver, so i guess I have to do it statically
> > like i did now?
> > Or in other words: I would have liked to have a module dependency graph
> > like this:
> >     tuxedo_nb04_lamp_array depends on tuxedo_nb04_platform (combining *_wmi_init and *_wmi_utility)
> > but if i currently split it into modules i would get this:
> >     tuxedo_nb04_wmi_ab_init dpends on tuxedo_nb04_wmi_ab_lamp_array depends on tuxedo_nb04_wmi_utility
> 
> On more general question to you: how much confident are you about your
> LampArray implementation?
> 
> If you still need to add/fix stuff in it, I would advise you to have a
> simple HID device, with bare minimum functionality, and then add the
> LampArray functionality on top through HID-BPF. This way you can fix
> LampArray out of band with the kernel, while having a more stable kernel
> module. This should be possible with v6.11+.
> 
> Another solution is to still have your wmi-to-hid module, and then a
> HID kernel module in drivers/hid that supports LampArray.
> 
> But I would strongly suggest while you are figuring out the userspace
> part to stick to HID-BPF, and then once you are happy we can move to a
> full kernel module.

What about creating real kernel driver with real interface to
userland, instead? My preference would be treating this as a display,
but nearly anything is better than _this_.

Best regards,
								Pavel
Armin Wolf Sept. 27, 2024, 10:21 p.m. UTC | #9
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 | #10
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:31 a.m. UTC | #11
Hi Benjamin,

Am 27.09.24 um 18:08 schrieb Benjamin Tissoires:
> On Sep 26 2024, Werner Sembach wrote:
>> Hi,
>> took some time but now a first working draft of the suggested new way of
>> handling per-key RGB keyboard backlights is finished. See:
>> https://lore.kernel.org/all/1fb08a74-62c7-4d0c-ba5d-648e23082dcb@tuxedocomputers.com/
>> First time for me sending a whole new driver to the LKML, so please excuse
>> mistakes I might have made.
>>
>> Known bugs:
>> - The device has a lightbar which is currently not implemented and
>>    therefore stuck to blue once the first backlight control command is send.
>>
>> What is still missing:
>> - The leds fallback
>> - Lightbar control
>>
>> Some general noob questions:
>>
>> Initially I though it would be nice to have 2 modules, one jsut being the
>> wmi initialization and utility stuff and one just being the backlight logic
>> stuff, being loaded automatically via module_alias, but that would still
>> require me to create the virtual hid device during the wmi_ab probe, and
>> that already needs the ll_driver, so i guess I have to do it statically
>> like i did now?
>> Or in other words: I would have liked to have a module dependency graph
>> like this:
>>      tuxedo_nb04_lamp_array depends on tuxedo_nb04_platform (combining *_wmi_init and *_wmi_utility)
>> but if i currently split it into modules i would get this:
>>      tuxedo_nb04_wmi_ab_init dpends on tuxedo_nb04_wmi_ab_lamp_array depends on tuxedo_nb04_wmi_utility
> On more general question to you: how much confident are you about your
> LampArray implementation?
>
> If you still need to add/fix stuff in it, I would advise you to have a
> simple HID device, with bare minimum functionality, and then add the
> LampArray functionality on top through HID-BPF. This way you can fix
> LampArray out of band with the kernel, while having a more stable kernel
> module. This should be possible with v6.11+.
>
> Another solution is to still have your wmi-to-hid module, and then a
> HID kernel module in drivers/hid that supports LampArray.
>
> But I would strongly suggest while you are figuring out the userspace
> part to stick to HID-BPF, and then once you are happy we can move to a
> full kernel module.

I don't expect this patch to get merged right away, but like i wrote, 
wanted to collect some feedback on it to already start refining it.

With this driver now functional I have something to build and test 
userspace against while waiting on the feedback and the undoubtly 
following discussion of details to get it right ^^.

Until now I only tested with a very simple, self built command line 
binary, looping some patterns. My next step is to try the work in 
progress implementetion for LampArray in OpenRGB: 
https://gitlab.com/CalcProgrammer1/OpenRGB/-/merge_requests/2348

Regards,

Werner

>
> Cheers,
> Benjamin
>
>> Currently after creating the virtual hdev in the wmi init probe function I
>> have to keep track of it and manually destroy it during the wmi init
>> remove. Can this be automated devm_kzalloc-style?
>>
>> Kind regards,
>> Werner Sembach
>>
>>
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:28 p.m. UTC | #22
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?
>
Armin Wolf Oct. 1, 2024, 4:45 p.m. UTC | #23
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 | #24
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 | #25
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 | #26
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 | #27
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 | #28
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:42 a.m. UTC | #29
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 | #30
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 | #31
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 | #32
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 | #33
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 | #34
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 | #35
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
Werner Sembach Oct. 7, 2024, 5:57 p.m. UTC | #36
Hi,

Am 02.10.24 um 10:31 schrieb Benjamin Tissoires:
> On Oct 01 2024, Werner Sembach wrote:
>> Hi Benjamin,
>>
>> Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
>>> [...]
>>> 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

Thinking about the minimal WMI to HID today, but found a problem: a HID feature 
report is either strictly input or output afaik, but the WMI interface has both 
in some functions.

How would I map that?

If I split everything in input and output the new interface wouldn't actually be 
much smaller.

Also what would I write for the usage for the reserved padding in the report 
descriptor. Usage: 0x00?

best regards,

Werner
Werner Sembach Oct. 8, 2024, 10:45 a.m. UTC | #37
Am 08.10.24 um 11:53 schrieb Benjamin Tissoires:
> On Oct 07 2024, Werner Sembach wrote:
>> Hi,
>>
>> Am 02.10.24 um 10:31 schrieb Benjamin Tissoires:
>>> On Oct 01 2024, Werner Sembach wrote:
>>>> Hi Benjamin,
>>>>
>>>> Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
>>>>> [...]
>>>>> 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
>> Thinking about the minimal WMI to HID today, but found a problem: a HID
>> feature report is either strictly input or output afaik, but the WMI
>> interface has both in some functions.
> Not sure you are talking about feature reports, because they are
> read/write. It's just that they are synchronous over the USB control
> endpoint (on USB).

I'm confused about the split between get and send feature reports 
https://www.kernel.org/doc/html/latest/hid/hidraw.html

I guess then a get feature report can also carry input data and the difference 
is that a send feature report doesn't wait for a reply? but then what is it's 
reason of existence in contrast to an output report?

>
> An input report is strictly directed from the device, and an output
> report is from the host to the device.
>
> But a feature report is bidirectional.
>
>> How would I map that?
> Depending on the WMI interface, if you want this to be synchronous,
> defining a Feature report is correct, otherwise (if you don't need
> feedback from WMI), you can declare the commands to WMI as Output
> reports.
Thanks for reminding me that output reports exist xD.
>
>> If I split everything in input and output the new interface wouldn't
>> actually be much smaller.
> The HID report descriptor doesn't need to be smaller. The fact that by
> default it exposes only one or two LEDs so we don't have the micrometers
> arrays is the only purpose.
>
> But if we also implement a not-full HID implementation of LampArray, we
> should be able to strip out the parts that we don't care in the LED
> class implementation, like the exact positioning, or the multiupdate.
>
>> Also what would I write for the usage for the reserved padding in the report
>> descriptor. Usage: 0x00?
> padding are ignored by HID. So whatever current usage you have is fine.
>
> However, if you are talking about the custom WMI vendor access, I'd go
> with a vendor collection (usage page 0xff00, usage 0x08 for the 8 bytes
> long WMI command for instance, 0x10 for the 16 bytes long one).
>
> Side note: in drivers/hid/bpf/progs/hid_report_helpers.h we have some
> autogenerated macros to help writing report descriptors (see
> drivers/hid/bpf/progs/Huion__Dial-2.bpf.c for an example of usage). It's
> in the hid-bpf tree but I think we might be able to include this in
> other drivers (or do a minimal rewrite/move into include).
> I'm not asking you to use it on your code right now, but this has the
> advantage of becoming less "binary blob" in your code, and prevent
> mistakes where you edit the comments but not the values.

I will look into it.

Since the interface is fixed I don't need to flesh out the whole descriptor 
(which i thought i must do) and usage page (0xff42, because NB04 and the wmi has 
2 other ec controlling wmi interfaces besides the AB one), report usage 
(matching the wmi comand id's) and report size should be enough.

Regards,

Werner

>
> Cheers,
> Benjamin
>
Werner Sembach Oct. 8, 2024, 2:51 p.m. UTC | #38
Am 08.10.24 um 14:18 schrieb Benjamin Tissoires:
> On Oct 08 2024, Werner Sembach wrote:
>> Am 08.10.24 um 11:53 schrieb Benjamin Tissoires:
>>> On Oct 07 2024, Werner Sembach wrote:
>>>> Hi,
>>>>
>>>> Am 02.10.24 um 10:31 schrieb Benjamin Tissoires:
>>>>> On Oct 01 2024, Werner Sembach wrote:
>>>>>> Hi Benjamin,
>>>>>>
>>>>>> Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
>>>>>>> [...]
>>>>>>> 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
>>>> Thinking about the minimal WMI to HID today, but found a problem: a HID
>>>> feature report is either strictly input or output afaik, but the WMI
>>>> interface has both in some functions.
>>> Not sure you are talking about feature reports, because they are
>>> read/write. It's just that they are synchronous over the USB control
>>> endpoint (on USB).
>> I'm confused about the split between get and send feature reports
>> https://www.kernel.org/doc/html/latest/hid/hidraw.html
>>
>> I guess then a get feature report can also carry input data and the
>> difference is that a send feature report doesn't wait for a reply? but then
>> what is it's reason of existence in contrast to an output report?
> I'm under the impression you are mixing the 3 types of reports (just
> re-stating that here in case I wasn't clear).
>
> - Input reports:
>    `Input()` in the report descriptor
>    -> data emitted by the device to the host, and notified through an IRQ
>    mechanism
>    -> obtained in hidraw through a blocking read() operation
> - Output reports:
>    `Output()` in the report descriptor
>    -> data sent asynchronously by the host to the device.
>    -> sent from hidraw by calling write() on the dev node (no feedback
>    except how many bytes were sent)
> - Feature reports:
>    `Feature()` in the report descriptor
>    -> way to synchronously configure the device. Think of it like a
>    register on the device: you can read it, write it, but you never get
>    an interrupt when there is a change
>    -> read/written by using an ioctl on the hidraw node

 From userspace there are the HIDIOCSFEATURE and HIDIOCGFEATURE ioctls.

 From the hid 1.11 spec:

"

7.2.2 Set_Report Request

[...]

The meaning of the request fields for the Set_Report request is the same as for
the Get_Report request, however the data direction is reversed and the Report
Data is sent from host to device.

"

and from the hut 1.5, some of the LampArray feature reports are meant to be used 
with set report and some with get report

(well as far as I can tell the hut doesn't actual specify, if they need to be 
feature reports, or am I missing something?)

and there is the pair with LampAttributesRequestReport and 
LampAttributesResponseReport.

Sorry for my confusion over the hid spec.

>
> And BTW, it's perfectly fine to have a dedicated report ID which has
> Input, Output and Feature attached to it :)
>
>>> An input report is strictly directed from the device, and an output
>>> report is from the host to the device.
>>>
>>> But a feature report is bidirectional.
>>>
>>>> How would I map that?
>>> Depending on the WMI interface, if you want this to be synchronous,
>>> defining a Feature report is correct, otherwise (if you don't need
>>> feedback from WMI), you can declare the commands to WMI as Output
>>> reports.
>> Thanks for reminding me that output reports exist xD.
> hehe
>
>>>> If I split everything in input and output the new interface wouldn't
>>>> actually be much smaller.
>>> The HID report descriptor doesn't need to be smaller. The fact that by
>>> default it exposes only one or two LEDs so we don't have the micrometers
>>> arrays is the only purpose.
>>>
>>> But if we also implement a not-full HID implementation of LampArray, we
>>> should be able to strip out the parts that we don't care in the LED
>>> class implementation, like the exact positioning, or the multiupdate.
>>>
>>>> Also what would I write for the usage for the reserved padding in the report
>>>> descriptor. Usage: 0x00?
>>> padding are ignored by HID. So whatever current usage you have is fine.
>>>
>>> However, if you are talking about the custom WMI vendor access, I'd go
>>> with a vendor collection (usage page 0xff00, usage 0x08 for the 8 bytes
>>> long WMI command for instance, 0x10 for the 16 bytes long one).
>>>
>>> Side note: in drivers/hid/bpf/progs/hid_report_helpers.h we have some
>>> autogenerated macros to help writing report descriptors (see
>>> drivers/hid/bpf/progs/Huion__Dial-2.bpf.c for an example of usage). It's
>>> in the hid-bpf tree but I think we might be able to include this in
>>> other drivers (or do a minimal rewrite/move into include).
>>> I'm not asking you to use it on your code right now, but this has the
>>> advantage of becoming less "binary blob" in your code, and prevent
>>> mistakes where you edit the comments but not the values.
>> I will look into it.
>>
>> Since the interface is fixed I don't need to flesh out the whole descriptor
>> (which i thought i must do) and usage page (0xff42, because NB04 and the wmi
>> has 2 other ec controlling wmi interfaces besides the AB one), report usage
>> (matching the wmi comand id's) and report size should be enough.
> I'm a little confused by that last sentence. But yeah, I would expect
> some minimal sanity check before handing over the HID report to the WMI
> interface :)
>
> Cheers,
> Benjamin
>
Benjamin Tissoires Oct. 8, 2024, 3:21 p.m. UTC | #39
On Oct 08 2024, Werner Sembach wrote:
> 
> Am 08.10.24 um 14:18 schrieb Benjamin Tissoires:
> > On Oct 08 2024, Werner Sembach wrote:
> > > Am 08.10.24 um 11:53 schrieb Benjamin Tissoires:
> > > > On Oct 07 2024, Werner Sembach wrote:
> > > > > Hi,
> > > > > 
> > > > > Am 02.10.24 um 10:31 schrieb Benjamin Tissoires:
> > > > > > On Oct 01 2024, Werner Sembach wrote:
> > > > > > > Hi Benjamin,
> > > > > > > 
> > > > > > > Am 01.10.24 um 15:41 schrieb Benjamin Tissoires:
> > > > > > > > [...]
> > > > > > > > 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
> > > > > Thinking about the minimal WMI to HID today, but found a problem: a HID
> > > > > feature report is either strictly input or output afaik, but the WMI
> > > > > interface has both in some functions.
> > > > Not sure you are talking about feature reports, because they are
> > > > read/write. It's just that they are synchronous over the USB control
> > > > endpoint (on USB).
> > > I'm confused about the split between get and send feature reports
> > > https://www.kernel.org/doc/html/latest/hid/hidraw.html
> > > 
> > > I guess then a get feature report can also carry input data and the
> > > difference is that a send feature report doesn't wait for a reply? but then
> > > what is it's reason of existence in contrast to an output report?
> > I'm under the impression you are mixing the 3 types of reports (just
> > re-stating that here in case I wasn't clear).
> > 
> > - Input reports:
> >    `Input()` in the report descriptor
> >    -> data emitted by the device to the host, and notified through an IRQ
> >    mechanism
> >    -> obtained in hidraw through a blocking read() operation
> > - Output reports:
> >    `Output()` in the report descriptor
> >    -> data sent asynchronously by the host to the device.
> >    -> sent from hidraw by calling write() on the dev node (no feedback
> >    except how many bytes were sent)
> > - Feature reports:
> >    `Feature()` in the report descriptor
> >    -> way to synchronously configure the device. Think of it like a
> >    register on the device: you can read it, write it, but you never get
> >    an interrupt when there is a change
> >    -> read/written by using an ioctl on the hidraw node
> 
> From userspace there are the HIDIOCSFEATURE and HIDIOCGFEATURE ioctls.
> 
> From the hid 1.11 spec:
> 
> "
> 
> 7.2.2 Set_Report Request
> 
> [...]
> 
> The meaning of the request fields for the Set_Report request is the same as for
> the Get_Report request, however the data direction is reversed and the Report
> Data is sent from host to device.
> 
> "
> 
> and from the hut 1.5, some of the LampArray feature reports are meant to be
> used with set report and some with get report

Yeah, it just means that you can query or send the data. You can also
use HIDIOCGINPUT() and HIDIOCSOUTPUT() to get a current input report and
set an output report through the hidraw ioctl...

Internally, HIDIOCGINPUT() uses the same code path than
HIDIOCGFEATURE(), but with the report type being an Input instead of a
Feature. Same for HIDIOCSOUTPUT() and HIDIOCSFEATURE().

> 
> (well as far as I can tell the hut doesn't actual specify, if they need to
> be feature reports, or am I missing something?)

They can be both actually. The HUT is missing what's expected here :(.

However, looking at the HUT RR 84:
https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf

There is an example of a report descriptor, and they are using Features.
Not Input+Output.

And looking even further (above), in 3.5 Usage Definitions:
3.5.2, 3.5.3 and 3.5.5 all of them are meant to be a feature, like:
LampArrayAttributesReport CL – Feature -
LampAttributesRequestReport CL – Feature –
LampAttributesResponseReport CL – Feature –
LampArrayControlReport CL – Feature –

3.5.4: can be either feature or output, like:
LampMultiUpdateReport CL – Feature/Output –
LampRangeUpdateReport CL – Feature/ Output –

So I guess the MS implementation can handle Feature only for all but the
update commands.

> 
> and there is the pair with LampAttributesRequestReport and
> LampAttributesResponseReport.

Yeah, not a big deal. The bold IN and OUT are just to say that calling a
setReport on a LampAttributesResponseReport is just ignored AFAIU.

> 
> Sorry for my confusion over the hid spec.

No worries. It is definitely confusing :)

Cheers,
Benjamin

> 
> > 
> > And BTW, it's perfectly fine to have a dedicated report ID which has
> > Input, Output and Feature attached to it :)
> > 
> > > > An input report is strictly directed from the device, and an output
> > > > report is from the host to the device.
> > > > 
> > > > But a feature report is bidirectional.
> > > > 
> > > > > How would I map that?
> > > > Depending on the WMI interface, if you want this to be synchronous,
> > > > defining a Feature report is correct, otherwise (if you don't need
> > > > feedback from WMI), you can declare the commands to WMI as Output
> > > > reports.
> > > Thanks for reminding me that output reports exist xD.
> > hehe
> > 
> > > > > If I split everything in input and output the new interface wouldn't
> > > > > actually be much smaller.
> > > > The HID report descriptor doesn't need to be smaller. The fact that by
> > > > default it exposes only one or two LEDs so we don't have the micrometers
> > > > arrays is the only purpose.
> > > > 
> > > > But if we also implement a not-full HID implementation of LampArray, we
> > > > should be able to strip out the parts that we don't care in the LED
> > > > class implementation, like the exact positioning, or the multiupdate.
> > > > 
> > > > > Also what would I write for the usage for the reserved padding in the report
> > > > > descriptor. Usage: 0x00?
> > > > padding are ignored by HID. So whatever current usage you have is fine.
> > > > 
> > > > However, if you are talking about the custom WMI vendor access, I'd go
> > > > with a vendor collection (usage page 0xff00, usage 0x08 for the 8 bytes
> > > > long WMI command for instance, 0x10 for the 16 bytes long one).
> > > > 
> > > > Side note: in drivers/hid/bpf/progs/hid_report_helpers.h we have some
> > > > autogenerated macros to help writing report descriptors (see
> > > > drivers/hid/bpf/progs/Huion__Dial-2.bpf.c for an example of usage). It's
> > > > in the hid-bpf tree but I think we might be able to include this in
> > > > other drivers (or do a minimal rewrite/move into include).
> > > > I'm not asking you to use it on your code right now, but this has the
> > > > advantage of becoming less "binary blob" in your code, and prevent
> > > > mistakes where you edit the comments but not the values.
> > > I will look into it.
> > > 
> > > Since the interface is fixed I don't need to flesh out the whole descriptor
> > > (which i thought i must do) and usage page (0xff42, because NB04 and the wmi
> > > has 2 other ec controlling wmi interfaces besides the AB one), report usage
> > > (matching the wmi comand id's) and report size should be enough.
> > I'm a little confused by that last sentence. But yeah, I would expect
> > some minimal sanity check before handing over the HID report to the WMI
> > interface :)
> > 
> > Cheers,
> > Benjamin
> >
Werner Sembach Oct. 9, 2024, 9:55 a.m. UTC | #40
Resend because HTML mail ..., but I think I now know when Thunderbird does it: 
Every time I include a link it gets converted.

Hi

Am 08.10.24 um 17:21 schrieb Benjamin Tissoires:
> On Oct 08 2024, Werner Sembach wrote:
>> [...]
> Yeah, it just means that you can query or send the data. You can also
> use HIDIOCGINPUT() and HIDIOCSOUTPUT() to get a current input report and
> set an output report through the hidraw ioctl...
>
> Internally, HIDIOCGINPUT() uses the same code path than
> HIDIOCGFEATURE(), but with the report type being an Input instead of a
> Feature. Same for HIDIOCSOUTPUT() and HIDIOCSFEATURE().

Ok so just a difference in definition not in implementation.

Then I use a get feature report for the device status function and use it as 
input and output at the same time, and use a set output report for the led 
update function (which technically has a return value but i think it's always 0 
anyway).

I scoured the old thread about exposing WMI calls to userspace, because I 
remembered that something here came up already.

1. https://lore.kernel.org/all/6b32fb73-0544-4a68-95ba-e82406a4b188@gmx.de/ -> 
Should be no problem? Because this is not generally exposing wmi calls, just 
mapping two explicitly with sanitized input (whitelisting basically).

2. https://lore.kernel.org/all/b6d79727-ae94-44b1-aa88-069416435c14@redhat.com/ 
-> Do this concerns this apply here? The actual API to be used is LampArray and 
the HID mapped WMI calls are just an "internal" interface for the BPF driver, 
but technically UAPI.

Also at Armin and Hans: Do you have comments on this approach?

>> (well as far as I can tell the hut doesn't actual specify, if they need to
>> be feature reports, or am I missing something?)
> They can be both actually. The HUT is missing what's expected here :(.
>
> However, looking at the HUT RR 84:
> https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf
>
> There is an example of a report descriptor, and they are using Features.
> Not Input+Output.
>
> And looking even further (above), in 3.5 Usage Definitions:
> 3.5.2, 3.5.3 and 3.5.5 all of them are meant to be a feature, like:
> LampArrayAttributesReport CL – Feature -
> LampAttributesRequestReport CL – Feature –
> LampAttributesResponseReport CL – Feature –
> LampArrayControlReport CL – Feature –
>
> 3.5.4: can be either feature or output, like:
> LampMultiUpdateReport CL – Feature/Output –
> LampRangeUpdateReport CL – Feature/ Output –
>
> So I guess the MS implementation can handle Feature only for all but the
> update commands.
Thanks for the link, I guess for the BPF driver I will stick to feature reports 
for the LampArray part until there is actually a hid descriptor spotted in the 
wild defining LampMultiUpdateReport and LampRangeUpdateReport as Output and not 
feature.
>> and there is the pair with LampAttributesRequestReport and
>> LampAttributesResponseReport.
> Yeah, not a big deal. The bold IN and OUT are just to say that calling a
> setReport on a LampAttributesResponseReport is just ignored AFAIU.
>
>> Sorry for my confusion over the hid spec.
> No worries. It is definitely confusing :)

On this note as I fathom:

Input Report (usually always get report): Interrupts (the ioctl just there to 
repeat the last one?)

Output Report (usually always set report): Async write, no return value (Buffer 
should stay untouched)

Feature report set: Sync write, no return value (Buffer should stay untouched)

Feature report get: Sync read/write (intended only for read, but not limited to 
it, uses singular buffer for both input and output)

I kind of don't get why feature report set exists, but well it's the specs ^^.

Regards,

Werner

[*snip*]
Armin Wolf Oct. 11, 2024, 12:14 p.m. UTC | #41
Am 09.10.24 um 11:55 schrieb Werner Sembach:

> Resend because HTML mail ..., but I think I now know when Thunderbird
> does it: Every time I include a link it gets converted.
>
> Hi
>
> Am 08.10.24 um 17:21 schrieb Benjamin Tissoires:
>> On Oct 08 2024, Werner Sembach wrote:
>>> [...]
>> Yeah, it just means that you can query or send the data. You can also
>> use HIDIOCGINPUT() and HIDIOCSOUTPUT() to get a current input report and
>> set an output report through the hidraw ioctl...
>>
>> Internally, HIDIOCGINPUT() uses the same code path than
>> HIDIOCGFEATURE(), but with the report type being an Input instead of a
>> Feature. Same for HIDIOCSOUTPUT() and HIDIOCSFEATURE().
>
> Ok so just a difference in definition not in implementation.
>
> Then I use a get feature report for the device status function and use
> it as input and output at the same time, and use a set output report
> for the led update function (which technically has a return value but
> i think it's always 0 anyway).
>
> I scoured the old thread about exposing WMI calls to userspace,
> because I remembered that something here came up already.
>
> 1.
> https://lore.kernel.org/all/6b32fb73-0544-4a68-95ba-e82406a4b188@gmx.de/
> -> Should be no problem? Because this is not generally exposing wmi
> calls, just mapping two explicitly with sanitized input (whitelisting
> basically).

It would be OK to expose a selected set of WMI calls to userspace and sanitizing the input of protect potentially buggy firmware from userspace.

>
> 2.
> https://lore.kernel.org/all/b6d79727-ae94-44b1-aa88-069416435c14@redhat.com/
> -> Do this concerns this apply here? The actual API to be used is
> LampArray and the HID mapped WMI calls are just an "internal"
> interface for the BPF driver, but technically UAPI.
>
I see no benefit of using BPF for creating the whole HID reports. Otherwise the HID interface exported by the driver to userspace would be a HID-mapped IOCTL interface
with no real benefit.

I think it would make more sense for the driver to export a generic HID LampArray interface, which contains placeholder values for the dimensions. Those values can then
be supplied by a HID-BPF snipped for each individual machine model. This would indeed be a suitable use of HID-BPF, as this would allow us to omit having a large quirk
table inside the kernel driver.

Regarding the basic idea of having a virtual HID interface: i would prefer to create a illumination subsystem instead, but i have to agree that we should be doing this
only after enough drivers are inside the kernel, so we can design a suitable interface for them. For now, creating a virtual HID interface seems to be good enough.

Thanks,
Armin Wolf

> Also at Armin and Hans: Do you have comments on this approach?
>
>>> (well as far as I can tell the hut doesn't actual specify, if they
>>> need to
>>> be feature reports, or am I missing something?)
>> They can be both actually. The HUT is missing what's expected here :(.
>>
>> However, looking at the HUT RR 84:
>> https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf
>>
>>
>> There is an example of a report descriptor, and they are using Features.
>> Not Input+Output.
>>
>> And looking even further (above), in 3.5 Usage Definitions:
>> 3.5.2, 3.5.3 and 3.5.5 all of them are meant to be a feature, like:
>> LampArrayAttributesReport CL – Feature -
>> LampAttributesRequestReport CL – Feature –
>> LampAttributesResponseReport CL – Feature –
>> LampArrayControlReport CL – Feature –
>>
>> 3.5.4: can be either feature or output, like:
>> LampMultiUpdateReport CL – Feature/Output –
>> LampRangeUpdateReport CL – Feature/ Output –
>>
>> So I guess the MS implementation can handle Feature only for all but the
>> update commands.
> Thanks for the link, I guess for the BPF driver I will stick to
> feature reports for the LampArray part until there is actually a hid
> descriptor spotted in the wild defining LampMultiUpdateReport and
> LampRangeUpdateReport as Output and not feature.
>>> and there is the pair with LampAttributesRequestReport and
>>> LampAttributesResponseReport.
>> Yeah, not a big deal. The bold IN and OUT are just to say that calling a
>> setReport on a LampAttributesResponseReport is just ignored AFAIU.
>>
>>> Sorry for my confusion over the hid spec.
>> No worries. It is definitely confusing :)
>
> On this note as I fathom:
>
> Input Report (usually always get report): Interrupts (the ioctl just
> there to repeat the last one?)
>
> Output Report (usually always set report): Async write, no return
> value (Buffer should stay untouched)
>
> Feature report set: Sync write, no return value (Buffer should stay
> untouched)
>
> Feature report get: Sync read/write (intended only for read, but not
> limited to it, uses singular buffer for both input and output)
>
> I kind of don't get why feature report set exists, but well it's the
> specs ^^.
>
> Regards,
>
> Werner
>
> [*snip*]
>
Pavel Machek Oct. 11, 2024, 3:23 p.m. UTC | #42
Hi!

> > > 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.

But the patch being proposed does not match the this description,
right?

And for hardware I seen, "minimum driver" you describe would be
already 90% of the full driver. (We can just use fbdev interface...)

Anyway, lets do it. I have rgb keyboard, you have few, and we have
Tuxedocomputers with machines where driver can't live in userspace.
If you have working driver, lets see it. I have posted my copy, but I
hae problem where keyboard functionality stops working when its
loaded. Can you help?

Then we can see how much of the driver is required for basic
functionality. I suspect it will be fairly easy to turn it into "full"
driver at that point.

> > 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.

Good.

> 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).

I have yet to see HID LampArray device.

Best regards,
									Pavel
Pavel Machek Oct. 11, 2024, 3:26 p.m. UTC | #43
Hi!

> > 1.
> > https://lore.kernel.org/all/6b32fb73-0544-4a68-95ba-e82406a4b188@gmx.de/
> > -> Should be no problem? Because this is not generally exposing wmi
> > calls, just mapping two explicitly with sanitized input (whitelisting
> > basically).
> 
> It would be OK to expose a selected set of WMI calls to userspace and sanitizing the input of protect potentially buggy firmware from userspace.
>

I don't believe this is good idea. Passthrough interfaces where
userland talks directly to hardware are very tricky.

> 
> Regarding the basic idea of having a virtual HID interface: i would prefer to create a illumination subsystem instead, but i have to agree that we should be doing this
> only after enough drivers are inside the kernel, so we can design a
> suitable interface for them. For now, creating a virtual HID
> interface seems to be good enough.

I have an RGB keyboard, and would like to get it supported. I already
have kernel driver for LEDs (which breaks input functionality). I'd
like to cooperate on "illumination" subsystem.

Best regards,
								Pavel
Armin Wolf Oct. 21, 2024, 8:26 p.m. UTC | #44
Am 11.10.24 um 17:26 schrieb Pavel Machek:

> Hi!
>
>>> 1.
>>> https://lore.kernel.org/all/6b32fb73-0544-4a68-95ba-e82406a4b188@gmx.de/
>>> -> Should be no problem? Because this is not generally exposing wmi
>>> calls, just mapping two explicitly with sanitized input (whitelisting
>>> basically).
>> It would be OK to expose a selected set of WMI calls to userspace and sanitizing the input of protect potentially buggy firmware from userspace.
>>
> I don't believe this is good idea. Passthrough interfaces where
> userland talks directly to hardware are very tricky.
>
>> Regarding the basic idea of having a virtual HID interface: i would prefer to create a illumination subsystem instead, but i have to agree that we should be doing this
>> only after enough drivers are inside the kernel, so we can design a
>> suitable interface for them. For now, creating a virtual HID
>> interface seems to be good enough.
> I have an RGB keyboard, and would like to get it supported. I already
> have kernel driver for LEDs (which breaks input functionality). I'd
> like to cooperate on "illumination" subsystem.
>
> Best regards,
> 								Pavel

Sorry for taking a bit long to respond.

This "illumination" subsystem would (from my perspective) act like some sort of LED subsystem
for devices with a high count of LEDs, like some RGB keyboards.

This would allow us too:
- provide an abstract interface for userspace applications like OpenRGB
- provide an generic LED subsystem emulation on top of the illumination device (optional)
- support future RGB controllers in a generic way

Advanced features like RGB effects, etc can be added later should the need arise.

I would suggest that we model it after the HID LampArray interface:

- interface for querying:
  - number of LEDs
  - supported colors, etc of those LEDs
  - position of those LEDs if available
  - kind (keyboard, ...)
  - latency, etc
- interface for setting multiple LEDs at once
- interface for setting a range of LEDs at once
- interface for getting the current LED colors

Since sysfs has a "one value per file" rule, i suggest that we use a chardev interface
for querying per-LED data and for setting/getting LED colors.

I do not know if mixing sysfs (for controller attributes like number of LEDs, etc) and IOCTL
(for setting/getting LED colors) is a good idea, any thoughts?

Thanks,
Armin Wolf
Benjamin Tissoires Oct. 22, 2024, 9:05 a.m. UTC | #45
Sorry I should have answered earlier...

On Oct 09 2024, Werner Sembach wrote:
> Resend because HTML mail ..., but I think I now know when Thunderbird does
> it: Every time I include a link it gets converted.
> 
> Hi
> 
> Am 08.10.24 um 17:21 schrieb Benjamin Tissoires:
> > On Oct 08 2024, Werner Sembach wrote:
> > > [...]
> > Yeah, it just means that you can query or send the data. You can also
> > use HIDIOCGINPUT() and HIDIOCSOUTPUT() to get a current input report and
> > set an output report through the hidraw ioctl...
> > 
> > Internally, HIDIOCGINPUT() uses the same code path than
> > HIDIOCGFEATURE(), but with the report type being an Input instead of a
> > Feature. Same for HIDIOCSOUTPUT() and HIDIOCSFEATURE().
> 
> Ok so just a difference in definition not in implementation.
> 
> Then I use a get feature report for the device status function and use it as
> input and output at the same time, and use a set output report for the led
> update function (which technically has a return value but i think it's
> always 0 anyway).

not quite. You can not use a get feature to set something on the device.

The semantic is:
Set -> "write" something on the device (from host to device)
Get -> "read" something from the device (from device to host)

Features can be set/get.
Input can only be get.
Output can only be set.

The implementation in the kernel should enforce that.

> 
> I scoured the old thread about exposing WMI calls to userspace, because I
> remembered that something here came up already.
> 
> 1. https://lore.kernel.org/all/6b32fb73-0544-4a68-95ba-e82406a4b188@gmx.de/
> -> Should be no problem? Because this is not generally exposing wmi calls,
> just mapping two explicitly with sanitized input (whitelisting basically).
> 
> 2.
> https://lore.kernel.org/all/b6d79727-ae94-44b1-aa88-069416435c14@redhat.com/
> -> Do this concerns this apply here? The actual API to be used is LampArray
> and the HID mapped WMI calls are just an "internal" interface for the BPF
> driver, but technically UAPI.
> 
> Also at Armin and Hans: Do you have comments on this approach?
> 
> > > (well as far as I can tell the hut doesn't actual specify, if they need to
> > > be feature reports, or am I missing something?)
> > They can be both actually. The HUT is missing what's expected here :(.
> > 
> > However, looking at the HUT RR 84:
> > https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf
> > 
> > There is an example of a report descriptor, and they are using Features.
> > Not Input+Output.
> > 
> > And looking even further (above), in 3.5 Usage Definitions:
> > 3.5.2, 3.5.3 and 3.5.5 all of them are meant to be a feature, like:
> > LampArrayAttributesReport CL – Feature -
> > LampAttributesRequestReport CL – Feature –
> > LampAttributesResponseReport CL – Feature –
> > LampArrayControlReport CL – Feature –
> > 
> > 3.5.4: can be either feature or output, like:
> > LampMultiUpdateReport CL – Feature/Output –
> > LampRangeUpdateReport CL – Feature/ Output –
> > 
> > So I guess the MS implementation can handle Feature only for all but the
> > update commands.
> Thanks for the link, I guess for the BPF driver I will stick to feature
> reports for the LampArray part until there is actually a hid descriptor
> spotted in the wild defining LampMultiUpdateReport and LampRangeUpdateReport
> as Output and not feature.
> > > and there is the pair with LampAttributesRequestReport and
> > > LampAttributesResponseReport.
> > Yeah, not a big deal. The bold IN and OUT are just to say that calling a
> > setReport on a LampAttributesResponseReport is just ignored AFAIU.
> > 
> > > Sorry for my confusion over the hid spec.
> > No worries. It is definitely confusing :)
> 
> On this note as I fathom:
> 
> Input Report (usually always get report): Interrupts (the ioctl just there
> to repeat the last one?)

yeah, but from hidraw the kernel calls the device directly to query the
report, so some device don't like that and just hang.

Rule of thumbs: never use get_report on an input report, unless the
specification explicitely says that the device is supposed to support
it for the given usage.

> 
> Output Report (usually always set report): Async write, no return value
> (Buffer should stay untouched)

yep

> 
> Feature report set: Sync write, no return value (Buffer should stay untouched)

yep

> 
> Feature report get: Sync read/write (intended only for read, but not limited
> to it, uses singular buffer for both input and output)

sync read only, no write. The existing values in the incoming buffer are
just overwritten.

> 
> I kind of don't get why feature report set exists, but well it's the specs ^^.

if "feature report set" doesn't exist, you can not write a vlaue to a
feature on a device (because get doesn't allow you to write).

Anyway, it's a USB implementation detail: input/output are using URB, so
direct USB read/write, when Features are using the control endpoint,
which allows for a slightly different approach.

And this transfered as output being async, when features are
synchronous.

Cheers,
Benjamin
Pavel Machek Oct. 22, 2024, 9:37 a.m. UTC | #46
Hi!

> > Personally I really like the idea to just emulate a HID LampArray device
> > for this instead or rolling our own API.  I believe there need to be
> > strong arguments to go with some alternative NIH API and I have not
> > heard such arguments yet.
> 
> Agreed on everything Hans said.
> 
> I'll personnaly fight against any new "illumination" API as long as we
> don't have committed users. This is the same policy the DRM folks
> > are

Well, and I'll personally fight against user<->kernel protocol as
crazy as HID LampArray is.

OpenRGB is not suitable hardware driver.
								Pavel
Pavel Machek Oct. 22, 2024, 9:47 a.m. UTC | #47
Hi!

> > Sorry for taking a bit long to respond.
> > 
> > This "illumination" subsystem would (from my perspective) act like some sort of LED subsystem
> > for devices with a high count of LEDs, like some RGB keyboards.
> > 
> > This would allow us too:
> > - provide an abstract interface for userspace applications like OpenRGB
> > - provide an generic LED subsystem emulation on top of the illumination device (optional)
> > - support future RGB controllers in a generic way
> > 
> > Advanced features like RGB effects, etc can be added later should the need arise.
> > 
> > I would suggest that we model it after the HID LampArray interface:
> > 
> > - interface for querying:
> >  - number of LEDs
> >  - supported colors, etc of those LEDs
> >  - position of those LEDs if available
> >  - kind (keyboard, ...)
> >  - latency, etc
> > - interface for setting multiple LEDs at once
> > - interface for setting a range of LEDs at once

How are LEDs ordered? I don't believe range makes much sense.

> > I do not know if mixing sysfs (for controller attributes like number of LEDs, etc) and IOCTL
> > (for setting/getting LED colors) is a good idea, any thoughts?
> 
> I wonder what the advantage of this approach is over simply using HID LampArray
> (emulation), openRGB is already going to support HID LampArray and since Microsoft
> is pushing this we will likely see it getting used more and more.

There's nothing simple about "HID LampArray". Specification is long
ang ugly... and we don't want to be stuck with with OpenRGB (links to QT!).

> Using HID LampArray also has the advantage that work has landed and is landing
> to allow safely handing over raw HID access to userspace programs or even
> individual graphical apps with the option to revoke that access when it is
> no longer desired for the app to have access.

HID raw is not suitable kernel interface. 

> Personally I really like the idea to just emulate a HID LampArray device
> for this instead or rolling our own API.  I believe there need to be
> strong arguments to go with some alternative NIH API and I have not
> heard such arguments yet.

If you don't want "some alternative API", we already have perfectly
working API for 2D arrays of LEDs. I believe I mentioned it before
:-). Senzrohssre.

								Pavel
Armin Wolf Oct. 22, 2024, 3:02 p.m. UTC | #48
Am 22.10.24 um 11:37 schrieb Pavel Machek:

> Hi!
>
>>> Personally I really like the idea to just emulate a HID LampArray device
>>> for this instead or rolling our own API.  I believe there need to be
>>> strong arguments to go with some alternative NIH API and I have not
>>> heard such arguments yet.

Using a virtual HID LampArray already creates two issues:

1. We have to supply device size data (length, width, height), but the driver
cannot know this.

2. It is very difficult to extend the HID LampArray interface, for example
there is no way to read the current LED color from the hardware or switch
between different modes.

A sysfs- and/or ioctl-based interface would allow us to:

1. Threat some data as optional.

2. Extend the interface later should the need arise.

Looking at the tuxedo-drivers code, it seems that the WMI interface also reports:

- preset color
- device type (touchpad, keyboard, ...)
- keyboard type (US/UK)

Making this information available through the HID LampArray protocol would be
difficult (except for the device type information).

>> Agreed on everything Hans said.
>>
>> I'll personnaly fight against any new "illumination" API as long as we
>> don't have committed users. This is the same policy the DRM folks
>>> are
> Well, and I'll personally fight against user<->kernel protocol as
> crazy as HID LampArray is.
>
> OpenRGB is not suitable hardware driver.
> 								Pavel

I agree.

The point is that we need to design a userspace API since we cannot just allow
userspace to access the raw device like with HID devices.

And since we are already forced to come up with a userspace API, then maybe it would
make sense to build a extendable userspace API or else we might end up in the exact
same situation later should another similar driver appear.

Since the HID LampArray is a hardware interface standard, we AFAIK cannot easily extend it.

Also i like to point out that OpenRGB seems to be willing to use this new "illumination" API
as long as the underlying hardware interface is properly documented so that they can implement
support for it under Windows.

I would even volunteer to write the necessary OpenRGB backend since i already contributed to
the project in the past.

Thanks,
Armin Wolf
Armin Wolf Oct. 22, 2024, 3:18 p.m. UTC | #49
Am 22.10.24 um 11:47 schrieb Pavel Machek:

> Hi!
>
>>> Sorry for taking a bit long to respond.
>>>
>>> This "illumination" subsystem would (from my perspective) act like some sort of LED subsystem
>>> for devices with a high count of LEDs, like some RGB keyboards.
>>>
>>> This would allow us too:
>>> - provide an abstract interface for userspace applications like OpenRGB
>>> - provide an generic LED subsystem emulation on top of the illumination device (optional)
>>> - support future RGB controllers in a generic way
>>>
>>> Advanced features like RGB effects, etc can be added later should the need arise.
>>>
>>> I would suggest that we model it after the HID LampArray interface:
>>>
>>> - interface for querying:
>>>   - number of LEDs
>>>   - supported colors, etc of those LEDs
>>>   - position of those LEDs if available
>>>   - kind (keyboard, ...)
>>>   - latency, etc
>>> - interface for setting multiple LEDs at once
>>> - interface for setting a range of LEDs at once
> How are LEDs ordered? I don't believe range makes much sense.

Range would allow for efficiently changing the color of all LEDs. But i agree
that this can be considered optional and can be added later.

Should we ever prototype such an interface, then providing a method for setting
multiple LEDs at once would be enough.

>>> I do not know if mixing sysfs (for controller attributes like number of LEDs, etc) and IOCTL
>>> (for setting/getting LED colors) is a good idea, any thoughts?
>> I wonder what the advantage of this approach is over simply using HID LampArray
>> (emulation), openRGB is already going to support HID LampArray and since Microsoft
>> is pushing this we will likely see it getting used more and more.
> There's nothing simple about "HID LampArray". Specification is long
> ang ugly... and we don't want to be stuck with with OpenRGB (links to QT!).

And HID LampArray its not easily extendable.

>
>> Using HID LampArray also has the advantage that work has landed and is landing
>> to allow safely handing over raw HID access to userspace programs or even
>> individual graphical apps with the option to revoke that access when it is
>> no longer desired for the app to have access.
> HID raw is not suitable kernel interface.

I agree, using HID raw in this case would be like amdgpu emulating a i915 GPU to
support applications working with a i915 GPU.

>> Personally I really like the idea to just emulate a HID LampArray device
>> for this instead or rolling our own API.  I believe there need to be
>> strong arguments to go with some alternative NIH API and I have not
>> heard such arguments yet.
> If you don't want "some alternative API", we already have perfectly
> working API for 2D arrays of LEDs. I believe I mentioned it before
> :-). Senzrohssre.
>
> 								Pavel

We may have to support 3D arrays of LEDs, so using a simple framebuffer
would likely cause trouble.

I think of something like this:

illumination class:

sysfs attrs:

  - lamp_count
  - kind (optional)
  - width, height, length (all optional)
  - latency (optional)
  - driver-defined attributes like firmware_version, ... (optional)

ioctl interface:

  - get LED info (id, supported colors, position (optional), key code (optional), ...)
  - get current color of LEDs
  - set multiple LEDs (by ID)

This interface is similar the the HID LampArray interface except that:

  - we can read the current color
  - we can omit optional information
  - we can extend the interface later (animations, etc)

Thanks,
Armin Wolf
Pavel Machek Oct. 22, 2024, 7:15 p.m. UTC | #50
Hi!

> > > > - interface for setting multiple LEDs at once
> > > > - interface for setting a range of LEDs at once
> > How are LEDs ordered? I don't believe range makes much sense.
> 
> Range would allow for efficiently changing the color of all LEDs. But i agree
> that this can be considered optional and can be added later.

Yep, setting all of them makes sense. We should probably provide
backward-compatible interface for keyboards with single backlight, so
this would likely be LED class.

> > > Personally I really like the idea to just emulate a HID LampArray device
> > > for this instead or rolling our own API.  I believe there need to be
> > > strong arguments to go with some alternative NIH API and I have not
> > > heard such arguments yet.
> > If you don't want "some alternative API", we already have perfectly
> > working API for 2D arrays of LEDs. I believe I mentioned it before
> > :-). Senzrohssre.
> 
> We may have to support 3D arrays of LEDs, so using a simple framebuffer
> would likely cause trouble.

Do you have pointer for device that is 3D?

OpenRGB manages to map keyboard into plane... so what I'd propose is
this:

Framebuffer
Information for each pixel:
	    present ? (displays with missing pixels are pretty common)
	    list of keys related to this pixel
	    width, height, length (if we know them)

Pixels map to keys M:N.

Yes, we'll have some number of non-present pixels, but again, I
believe that's not uncommon due to round screens, etc.

(But I'm fine with other interfaces, as long as they are "normal") 

Best regards,
								Pavel
Armin Wolf Oct. 23, 2024, 7:03 a.m. UTC | #51
Am 22.10.24 um 21:15 schrieb Pavel Machek:

> Hi!
>
>>>>> - interface for setting multiple LEDs at once
>>>>> - interface for setting a range of LEDs at once
>>> How are LEDs ordered? I don't believe range makes much sense.
>> Range would allow for efficiently changing the color of all LEDs. But i agree
>> that this can be considered optional and can be added later.
> Yep, setting all of them makes sense. We should probably provide
> backward-compatible interface for keyboards with single backlight, so
> this would likely be LED class.
>
Good idea, the LED device could also be provided by the illumination subsystem code.

>>>> Personally I really like the idea to just emulate a HID LampArray device
>>>> for this instead or rolling our own API.  I believe there need to be
>>>> strong arguments to go with some alternative NIH API and I have not
>>>> heard such arguments yet.
>>> If you don't want "some alternative API", we already have perfectly
>>> working API for 2D arrays of LEDs. I believe I mentioned it before
>>> :-). Senzrohssre.
>> We may have to support 3D arrays of LEDs, so using a simple framebuffer
>> would likely cause trouble.
> Do you have pointer for device that is 3D?

Maybe a PC case with LEDs on each corner.

>
> OpenRGB manages to map keyboard into plane... so what I'd propose is
> this:
>
> Framebuffer
> Information for each pixel:
> 	    present ? (displays with missing pixels are pretty common)
> 	    list of keys related to this pixel
> 	    width, height, length (if we know them)
>
> Pixels map to keys M:N.
>
> Yes, we'll have some number of non-present pixels, but again, I
> believe that's not uncommon due to round screens, etc.
>
> (But I'm fine with other interfaces, as long as they are "normal")
>
> Best regards,
> 								Pavel

Using an ID-based interface would allow for more flexibility and allow
us to support 3D-arrays.

Thanks,
Armin Wolf
Werner Sembach Oct. 23, 2024, 4:38 p.m. UTC | #52
Hi

Am 22.10.24 um 11:47 schrieb Pavel Machek:
> Hi!
>
>>> Sorry for taking a bit long to respond.
>>>
>>> This "illumination" subsystem would (from my perspective) act like some sort of LED subsystem
>>> for devices with a high count of LEDs, like some RGB keyboards.
>>>
>>> This would allow us too:
>>> - provide an abstract interface for userspace applications like OpenRGB
>>> - provide an generic LED subsystem emulation on top of the illumination device (optional)
>>> - support future RGB controllers in a generic way
>>>
>>> Advanced features like RGB effects, etc can be added later should the need arise.
>>>
>>> I would suggest that we model it after the HID LampArray interface:
>>>
>>> - interface for querying:
>>>   - number of LEDs
>>>   - supported colors, etc of those LEDs
>>>   - position of those LEDs if available
>>>   - kind (keyboard, ...)
>>>   - latency, etc
>>> - interface for setting multiple LEDs at once
>>> - interface for setting a range of LEDs at once
> How are LEDs ordered? I don't believe range makes much sense.
For LampArray the spec suggests (but not requires) "row wise" starting in the 
upper left, however the spec does not specify how to handle with double row keys 
like iso-enter or half-key-downward offset arrow keys like they exist on some 
notebooks.
>
>>> I do not know if mixing sysfs (for controller attributes like number of LEDs, etc) and IOCTL
>>> (for setting/getting LED colors) is a good idea, any thoughts?
>> I wonder what the advantage of this approach is over simply using HID LampArray
>> (emulation), openRGB is already going to support HID LampArray and since Microsoft
>> is pushing this we will likely see it getting used more and more.
> There's nothing simple about "HID LampArray". Specification is long
> ang ugly... and we don't want to be stuck with with OpenRGB (links to QT!).
It is the only vendor agnostic approach to complex userspace lighting control 
atm. And what's the problem with QT?
>
>> Using HID LampArray also has the advantage that work has landed and is landing
>> to allow safely handing over raw HID access to userspace programs or even
>> individual graphical apps with the option to revoke that access when it is
>> no longer desired for the app to have access.
> HID raw is not suitable kernel interface.
>
>> Personally I really like the idea to just emulate a HID LampArray device
>> for this instead or rolling our own API.  I believe there need to be
>> strong arguments to go with some alternative NIH API and I have not
>> heard such arguments yet.
> If you don't want "some alternative API", we already have perfectly
> working API for 2D arrays of LEDs. I believe I mentioned it before
> :-). Senzrohssre.
>
> 								Pavel
Werner Sembach Oct. 23, 2024, 5:14 p.m. UTC | #53
Hi

Am 22.10.24 um 21:15 schrieb Pavel Machek:
> Hi!
>
>>>>> - interface for setting multiple LEDs at once
>>>>> - interface for setting a range of LEDs at once
>>> How are LEDs ordered? I don't believe range makes much sense.
>> Range would allow for efficiently changing the color of all LEDs. But i agree
>> that this can be considered optional and can be added later.
> Yep, setting all of them makes sense. We should probably provide
> backward-compatible interface for keyboards with single backlight, so
> this would likely be LED class.
>
>>>> Personally I really like the idea to just emulate a HID LampArray device
>>>> for this instead or rolling our own API.  I believe there need to be
>>>> strong arguments to go with some alternative NIH API and I have not
>>>> heard such arguments yet.
>>> If you don't want "some alternative API", we already have perfectly
>>> working API for 2D arrays of LEDs. I believe I mentioned it before
>>> :-). Senzrohssre.
>> We may have to support 3D arrays of LEDs, so using a simple framebuffer
>> would likely cause trouble.
> Do you have pointer for device that is 3D?

The example from the spec is a keyboard with lightbars on the side, the we 
actually sell notebooks with similar led configurations (mostly on the front and 
not on the side). Example is the Sirius I implemented which has a not yet 
implemented lightbar on the front.

Another usecase is probably ergonomic keyboards, but I cannot tell you a real 
world example atm.

>
> OpenRGB manages to map keyboard into plane... so what I'd propose is
> this:
>
> Framebuffer
> Information for each pixel:
> 	    present ? (displays with missing pixels are pretty common)
> 	    list of keys related to this pixel
> 	    width, height, length (if we know them)
>
> Pixels map to keys M:N.

How would iso-enter be mapped here?

How would the q-key be mapped relative the the 1-key? (they are exactly halve a 
key offset)

would it be:
~,1,2
tab,q,w

or:

~,1,2
tab,missing pixel,q

Regards

Werner

>
> Yes, we'll have some number of non-present pixels, but again, I
> believe that's not uncommon due to round screens, etc.
>
> (But I'm fine with other interfaces, as long as they are "normal")
>
> Best regards,
> 								Pavel
>
Werner Sembach Oct. 23, 2024, 5:23 p.m. UTC | #54
Am 22.10.24 um 11:05 schrieb Benjamin Tissoires:
> Sorry I should have answered earlier...
>
> On Oct 09 2024, Werner Sembach wrote:
>> Resend because HTML mail ..., but I think I now know when Thunderbird does
>> it: Every time I include a link it gets converted.
>>
>> Hi
>>
>> Am 08.10.24 um 17:21 schrieb Benjamin Tissoires:
>>> On Oct 08 2024, Werner Sembach wrote:
>>>> [...]
>>> Yeah, it just means that you can query or send the data. You can also
>>> use HIDIOCGINPUT() and HIDIOCSOUTPUT() to get a current input report and
>>> set an output report through the hidraw ioctl...
>>>
>>> Internally, HIDIOCGINPUT() uses the same code path than
>>> HIDIOCGFEATURE(), but with the report type being an Input instead of a
>>> Feature. Same for HIDIOCSOUTPUT() and HIDIOCSFEATURE().
>> Ok so just a difference in definition not in implementation.
>>
>> Then I use a get feature report for the device status function and use it as
>> input and output at the same time, and use a set output report for the led
>> update function (which technically has a return value but i think it's
>> always 0 anyway).
> not quite. You can not use a get feature to set something on the device.
>
> The semantic is:
> Set -> "write" something on the device (from host to device)
> Get -> "read" something from the device (from device to host)
>
> Features can be set/get.
> Input can only be get.
> Output can only be set.
>
> The implementation in the kernel should enforce that.
>
>> I scoured the old thread about exposing WMI calls to userspace, because I
>> remembered that something here came up already.
>>
>> 1. https://lore.kernel.org/all/6b32fb73-0544-4a68-95ba-e82406a4b188@gmx.de/
>> -> Should be no problem? Because this is not generally exposing wmi calls,
>> just mapping two explicitly with sanitized input (whitelisting basically).
>>
>> 2.
>> https://lore.kernel.org/all/b6d79727-ae94-44b1-aa88-069416435c14@redhat.com/
>> -> Do this concerns this apply here? The actual API to be used is LampArray
>> and the HID mapped WMI calls are just an "internal" interface for the BPF
>> driver, but technically UAPI.
>>
>> Also at Armin and Hans: Do you have comments on this approach?
>>
>>>> (well as far as I can tell the hut doesn't actual specify, if they need to
>>>> be feature reports, or am I missing something?)
>>> They can be both actually. The HUT is missing what's expected here :(.
>>>
>>> However, looking at the HUT RR 84:
>>> https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf
>>>
>>> There is an example of a report descriptor, and they are using Features.
>>> Not Input+Output.
>>>
>>> And looking even further (above), in 3.5 Usage Definitions:
>>> 3.5.2, 3.5.3 and 3.5.5 all of them are meant to be a feature, like:
>>> LampArrayAttributesReport CL – Feature -
>>> LampAttributesRequestReport CL – Feature –
>>> LampAttributesResponseReport CL – Feature –
>>> LampArrayControlReport CL – Feature –
>>>
>>> 3.5.4: can be either feature or output, like:
>>> LampMultiUpdateReport CL – Feature/Output –
>>> LampRangeUpdateReport CL – Feature/ Output –
>>>
>>> So I guess the MS implementation can handle Feature only for all but the
>>> update commands.
>> Thanks for the link, I guess for the BPF driver I will stick to feature
>> reports for the LampArray part until there is actually a hid descriptor
>> spotted in the wild defining LampMultiUpdateReport and LampRangeUpdateReport
>> as Output and not feature.
>>>> and there is the pair with LampAttributesRequestReport and
>>>> LampAttributesResponseReport.
>>> Yeah, not a big deal. The bold IN and OUT are just to say that calling a
>>> setReport on a LampAttributesResponseReport is just ignored AFAIU.
>>>
>>>> Sorry for my confusion over the hid spec.
>>> No worries. It is definitely confusing :)
>> On this note as I fathom:
>>
>> Input Report (usually always get report): Interrupts (the ioctl just there
>> to repeat the last one?)
> yeah, but from hidraw the kernel calls the device directly to query the
> report, so some device don't like that and just hang.
>
> Rule of thumbs: never use get_report on an input report, unless the
> specification explicitely says that the device is supposed to support
> it for the given usage.
>
>> Output Report (usually always set report): Async write, no return value
>> (Buffer should stay untouched)
> yep
>
>> Feature report set: Sync write, no return value (Buffer should stay untouched)
> yep
>
>> Feature report get: Sync read/write (intended only for read, but not limited
>> to it, uses singular buffer for both input and output)
> sync read only, no write. The existing values in the incoming buffer are
> just overwritten.
Sorry I'm still confused: You said i could do input and output in a singular 
feature report, but now you say i can't do input or i can't do output, so i 
still need to use 2?
>
>> I kind of don't get why feature report set exists, but well it's the specs ^^.
> if "feature report set" doesn't exist, you can not write a vlaue to a
> feature on a device (because get doesn't allow you to write).
>
> Anyway, it's a USB implementation detail: input/output are using URB, so
> direct USB read/write, when Features are using the control endpoint,
> which allows for a slightly different approach.
>
> And this transfered as output being async, when features are
> synchronous.
>
> Cheers,
> Benjamin
Pavel Machek Oct. 23, 2024, 5:47 p.m. UTC | #55
Hi!

> > > > > Personally I really like the idea to just emulate a HID LampArray device
> > > > > for this instead or rolling our own API.  I believe there need to be
> > > > > strong arguments to go with some alternative NIH API and I have not
> > > > > heard such arguments yet.
> > > > If you don't want "some alternative API", we already have perfectly
> > > > working API for 2D arrays of LEDs. I believe I mentioned it before
> > > > :-). Senzrohssre.
> > > We may have to support 3D arrays of LEDs, so using a simple framebuffer
> > > would likely cause trouble.
> > Do you have pointer for device that is 3D?
> 
> The example from the spec is a keyboard with lightbars on the side, the we
> actually sell notebooks with similar led configurations (mostly on the front
> and not on the side). Example is the Sirius I implemented which has a not
> yet implemented lightbar on the front.

I also have lightbar on the keyboard. Put it is still close-enough to
2D. As would be bars on side or bar in front.

> > OpenRGB manages to map keyboard into plane... so what I'd propose is
> > this:
> > 
> > Framebuffer
> > Information for each pixel:
> > 	    present ? (displays with missing pixels are pretty common)
> > 	    list of keys related to this pixel
> > 	    width, height, length (if we know them)
> > 
> > Pixels map to keys M:N.
> 
> How would iso-enter be mapped here?

I guess it depends on number of LEDs under the enter. I have one LED
under it, so it would be one pixel.

> How would the q-key be mapped relative the the 1-key? (they are exactly
> halve a key offset)

That would have to be decided. I remember this from openrgb:

https://www.gamingonlinux.com/2022/01/openrgb-gets-greately-expanded-hardware-support-in-the-07-release/

and that's one option.

> ~,1,2
> tab,missing pixel,q

I'd go with this one. OpenRGB does it on one screenshot, but there are
other screenshots. Advantage is that if someone does TAB with two
LEDs, we'll have place for it.

Best regards,
							Pavel
Werner Sembach Oct. 23, 2024, 5:54 p.m. UTC | #56
Hi,

Am 22.10.24 um 17:02 schrieb Armin Wolf:
> Am 22.10.24 um 11:37 schrieb Pavel Machek:
>
>> Hi!
>>
>>>> Personally I really like the idea to just emulate a HID LampArray device
>>>> for this instead or rolling our own API.  I believe there need to be
>>>> strong arguments to go with some alternative NIH API and I have not
>>>> heard such arguments yet.
>
> Using a virtual HID LampArray already creates two issues:
>
> 1. We have to supply device size data (length, width, height), but the driver
> cannot know this.
>
> 2. It is very difficult to extend the HID LampArray interface, for example
> there is no way to read the current LED color from the hardware or switch
> between different modes.
>
> A sysfs- and/or ioctl-based interface would allow us to:
>
> 1. Threat some data as optional.
>
> 2. Extend the interface later should the need arise.
>
> Looking at the tuxedo-drivers code, it seems that the WMI interface also reports:
>
> - preset color
> - device type (touchpad, keyboard, ...)
> - keyboard type (US/UK)
>
> Making this information available through the HID LampArray protocol would be
> difficult (except for the device type information).
>
>>> Agreed on everything Hans said.
>>>
>>> I'll personnaly fight against any new "illumination" API as long as we
>>> don't have committed users. This is the same policy the DRM folks
>>>> are
>> Well, and I'll personally fight against user<->kernel protocol as
>> crazy as HID LampArray is.
>>
>> OpenRGB is not suitable hardware driver.
>>                                 Pavel
>
> I agree.
>
> The point is that we need to design a userspace API since we cannot just allow
> userspace to access the raw device like with HID devices.
>
> And since we are already forced to come up with a userspace API, then maybe it 
> would
> make sense to build a extendable userspace API or else we might end up in the 
> exact
> same situation later should another similar driver appear.
>
> Since the HID LampArray is a hardware interface standard, we AFAIK cannot 
> easily extend it.
>
> Also i like to point out that OpenRGB seems to be willing to use this new 
> "illumination" API
> as long as the underlying hardware interface is properly documented so that 
> they can implement
> support for it under Windows.
>
> I would even volunteer to write the necessary OpenRGB backend since i already 
> contributed to
> the project in the past.

Just wanting to leave my 2 cents here: I'm in theory fine with both approaches 
(hidraw LampArray or wrapping it in some kind of new UAPI which at least has the 
LampArray feature set).

I also don't think that OpenRGB has a problem with a new Linux exclusive API as 
long as someone is doing the implementation work. After all the reason why 
OpenRGB was started is to unify all the different vendor APIs under one UI. So 
one more or less doesn't matter.

BUT: I already did work for the hidraw LampArray approach and OpenRGB already 
did work for that as well (albeit I didn't yet managed to get the draft running) 
and we already had a lengthy discussion about this in the last thread. (This one 
https://lore.kernel.org/all/20231011190017.1230898-1-wse@tuxedocomputers.com/) 
with all the same arguments.

e.g. Expansion of the API: How should that look like? It would have to be 
basically an own extension for every keyboard manufacturer because every one 
supports different built in modes with different values to tweak.

So I'm siding with Hans and Benjamin on this one.

My only plan for the current patch besides some more code beautification: Move 
the device-sku specific values (key map, and key positions) to a bpf driver.

The question in my mind currently is: Is the patch merge ready with just that? 
Or must the OpenRGB implemenation also be finished before the merge?

Best regards,

Werner

>
> Thanks,
> Armin Wolf
>