Message ID | 20240926174405.110748-2-wse@tuxedocomputers.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 | expand |
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
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
Hi Werner, kernel test robot noticed the following build warnings: [auto build test WARNING on drm-misc/drm-misc-next] [also build test WARNING on drm-tip/drm-tip lee-leds/for-leds-next linus/master v6.11 next-20240927] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Werner-Sembach/platform-x86-tuxedo-Add-virtual-LampArray-for-TUXEDO-NB04-devices/20240927-014628 base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next patch link: https://lore.kernel.org/r/20240926174405.110748-2-wse%40tuxedocomputers.com patch subject: [PATCH 1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices config: i386-buildonly-randconfig-001-20240927 (https://download.01.org/0day-ci/archive/20240927/202409271601.cdUpq1Zd-lkp@intel.com/config) compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240927/202409271601.cdUpq1Zd-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202409271601.cdUpq1Zd-lkp@intel.com/ All warnings (new ones prefixed by >>): >> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:601:11: warning: variable 'lamp_count' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized] 601 | else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:604:28: note: uninitialized use occurs here 604 | if (report->lamp_id_end > lamp_count - 1) { | ^~~~~~~~~~ drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:601:7: note: remove the 'if' if its condition is always true 601 | else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 602 | lamp_count = sizeof(sirius_16_ansii_kbl_mapping); drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:587:20: note: initialize the variable 'lamp_count' to silence this warning 587 | uint8_t lamp_count; | ^ | = '\0' >> drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:670:24: warning: format specifies type 'unsigned long' but the argument has type 'size_t' (aka 'unsigned int') [-Wformat] 669 | pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype, | ~~~ | %zu 670 | reqtype, reportnum, len); | ^~~ include/linux/printk.h:595:38: note: expanded from macro 'pr_debug' 595 | no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) | ~~~ ^~~~~~~~~~~ include/linux/printk.h:133:18: note: expanded from macro 'no_printk' 133 | _printk(fmt, ##__VA_ARGS__); \ | ~~~ ^~~~~~~~~~~ 2 warnings generated. vim +601 drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c 581 582 static int handle_lamp_range_update_report(struct hid_device *hdev, 583 struct lamp_range_update_report_t *report) 584 { 585 int ret; 586 struct driver_data_t *driver_data = hdev->driver_data; 587 uint8_t lamp_count; 588 struct lamp_multi_update_report_t lamp_multi_update_report = { 589 .report_id = LAMP_MULTI_UPDATE_REPORT_ID 590 }; 591 592 // Catching missformated lamp_range_update_report and fail silently according to 593 // "HID Usage Tables v1.5" 594 if (report->lamp_id_start > report->lamp_id_end) { 595 pr_debug("lamp_id_start > lamp_id_end in lamp_range_update_report. Skippng whole report!\n"); 596 return sizeof(struct lamp_range_update_report_t); 597 } 598 599 if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) 600 lamp_count = sizeof(sirius_16_ansii_kbl_mapping); > 601 else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) 602 lamp_count = sizeof(sirius_16_ansii_kbl_mapping); 603 604 if (report->lamp_id_end > lamp_count - 1) { 605 pr_debug("Out of bounds lamp_id_* in lamp_range_update_report. Skippng whole report!\n"); 606 return sizeof(struct lamp_range_update_report_t); 607 } 608 609 // Break handle_lamp_range_update_report call down to multiple 610 // handle_lamp_multi_update_report calls to easily ensure that mixing 611 // handle_lamp_range_update_report and handle_lamp_multi_update_report 612 // does not break things. 613 for (int i = report->lamp_id_start; i < report->lamp_id_end + 1; i = i + 8) { 614 lamp_multi_update_report.lamp_count = MIN(report->lamp_id_end + 1 - i, 8); 615 if (i + lamp_multi_update_report.lamp_count == report->lamp_id_end + 1) 616 lamp_multi_update_report.lamp_update_flags |= 617 LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE; 618 619 for (int j = 0; j < lamp_multi_update_report.lamp_count; ++j) { 620 lamp_multi_update_report.lamp_id[j] = i + j; 621 lamp_multi_update_report.update_channels[j].red = 622 report->red_update_channel; 623 lamp_multi_update_report.update_channels[j].green = 624 report->green_update_channel; 625 lamp_multi_update_report.update_channels[j].blue = 626 report->blue_update_channel; 627 lamp_multi_update_report.update_channels[j].intensity = 628 report->intensity_update_channel; 629 } 630 631 ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report); 632 if (ret < 0) 633 return ret; 634 else if (ret != sizeof(struct lamp_multi_update_report_t)) 635 return -EIO; 636 } 637 638 return sizeof(struct lamp_range_update_report_t); 639 } 640 641 642 struct __packed lamp_array_control_report_t { 643 const uint8_t report_id; 644 uint8_t autonomous_mode; 645 }; 646 647 static int handle_lamp_array_control_report(struct hid_device __always_unused *hdev, 648 struct lamp_array_control_report_t __always_unused *rep) 649 { 650 // The keyboard firmware doesn't have any built in effects or controls 651 // so this is a NOOP. 652 // According to the HID Documentation (HID Usage Tables v1.5) this 653 // function is optional and can be removed from the HID Report 654 // Descriptor, but it should first be confirmed that userspace respects 655 // this possibility too. The Microsoft MacroPad reference implementation 656 // (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad) 657 // already deviates from the spec at another point, see 658 // handle_lamp_*_update_report. 659 660 return sizeof(struct lamp_array_control_report_t); 661 } 662 663 664 static int ll_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, 665 unsigned char rtype, int reqtype) 666 { 667 int ret; 668 669 pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype, > 670 reqtype, reportnum, len); 671 print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len); 672 673 ret = -EINVAL; 674 if (rtype == HID_FEATURE_REPORT) { 675 if (reqtype == HID_REQ_GET_REPORT) { 676 if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID 677 && len == sizeof(struct lamp_array_attributes_report_t)) 678 ret = handle_lamp_array_attributes_report( 679 hdev, (struct lamp_array_attributes_report_t *)buf); 680 else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID 681 && len == sizeof(struct lamp_attributes_response_report_t)) 682 ret = handle_lamp_attributes_response_report( 683 hdev, (struct lamp_attributes_response_report_t *)buf); 684 } else if (reqtype == HID_REQ_SET_REPORT) { 685 if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID 686 && len == sizeof(struct lamp_attributes_request_report_t)) 687 ret = handle_lamp_attributes_request_report( 688 hdev, (struct lamp_attributes_request_report_t *)buf); 689 else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID 690 && len == sizeof(struct lamp_multi_update_report_t)) 691 ret = handle_lamp_multi_update_report( 692 hdev, (struct lamp_multi_update_report_t *)buf); 693 else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID 694 && len == sizeof(struct lamp_range_update_report_t)) 695 ret = handle_lamp_range_update_report( 696 hdev, (struct lamp_range_update_report_t *)buf); 697 else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID 698 && len == sizeof(struct lamp_array_control_report_t)) 699 ret = handle_lamp_array_control_report( 700 hdev, (struct lamp_array_control_report_t *)buf); 701 } 702 } 703 704 return ret; 705 } 706
Hi Werner,
kernel test robot noticed the following build warnings:
[auto build test WARNING on drm-misc/drm-misc-next]
[also build test WARNING on drm-tip/drm-tip lee-leds/for-leds-next linus/master v6.11 next-20240927]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Werner-Sembach/platform-x86-tuxedo-Add-virtual-LampArray-for-TUXEDO-NB04-devices/20240927-014628
base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next
patch link: https://lore.kernel.org/r/20240926174405.110748-2-wse%40tuxedocomputers.com
patch subject: [PATCH 1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices
config: i386-buildonly-randconfig-002-20240927 (https://download.01.org/0day-ci/archive/20240927/202409271653.rAiw37rN-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240927/202409271653.rAiw37rN-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202409271653.rAiw37rN-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c: In function 'll_raw_request':
>> <command-line>: warning: format '%lu' expects argument of type 'long unsigned int', but argument 6 has type 'size_t' {aka 'unsigned int'} [-Wformat=]
<command-line>: note: in definition of macro 'KBUILD_MODNAME'
include/linux/dynamic_debug.h:224:29: note: in expansion of macro 'pr_fmt'
224 | func(&id, ##__VA_ARGS__); \
| ^~~~~~~~~~~
include/linux/dynamic_debug.h:248:9: note: in expansion of macro '__dynamic_func_call_cls'
248 | __dynamic_func_call_cls(__UNIQUE_ID(ddebug), cls, fmt, func, ##__VA_ARGS__)
| ^~~~~~~~~~~~~~~~~~~~~~~
include/linux/dynamic_debug.h:250:9: note: in expansion of macro '_dynamic_func_call_cls'
250 | _dynamic_func_call_cls(_DPRINTK_CLASS_DFLT, fmt, func, ##__VA_ARGS__)
| ^~~~~~~~~~~~~~~~~~~~~~
include/linux/dynamic_debug.h:269:9: note: in expansion of macro '_dynamic_func_call'
269 | _dynamic_func_call(fmt, __dynamic_pr_debug, \
| ^~~~~~~~~~~~~~~~~~
include/linux/printk.h:589:9: note: in expansion of macro 'dynamic_pr_debug'
589 | dynamic_pr_debug(fmt, ##__VA_ARGS__)
| ^~~~~~~~~~~~~~~~
drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c:669:9: note: in expansion of macro 'pr_debug'
669 | pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype,
| ^~~~~~~~
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
Hi Werner, kernel test robot noticed the following build errors: [auto build test ERROR on drm-misc/drm-misc-next] [also build test ERROR on drm-tip/drm-tip linus/master v6.11 next-20240927] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Werner-Sembach/platform-x86-tuxedo-Add-virtual-LampArray-for-TUXEDO-NB04-devices/20240927-014628 base: git://anongit.freedesktop.org/drm/drm-misc drm-misc-next patch link: https://lore.kernel.org/r/20240926174405.110748-2-wse%40tuxedocomputers.com patch subject: [PATCH 1/1] platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices config: i386-alldefconfig (https://download.01.org/0day-ci/archive/20240927/202409271950.jUMzgCG8-lkp@intel.com/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240927/202409271950.jUMzgCG8-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202409271950.jUMzgCG8-lkp@intel.com/ All errors (new ones prefixed by >>): ld: drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.o: in function `tuxedo_nb04_wmi_ab_driver_init': >> tuxedo_nb04_wmi_ab_init.c:(.init.text+0xb): undefined reference to `__wmi_driver_register' ld: drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.o: in function `tuxedo_nb04_wmi_ab_driver_exit': >> tuxedo_nb04_wmi_ab_init.c:(.exit.text+0x9): undefined reference to `wmi_driver_unregister' ld: drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.o: in function `__wmi_method_buffer_out.constprop.0': >> tuxedo_nb04_wmi_util.c:(.text+0x52): undefined reference to `wmidev_evaluate_method'
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
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 > >
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 > +}; > +
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 >> +}; >> +
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 > > > +}; > > > +
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
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 >> >>
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 >> +}; >> +
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 >>> +}; >>> +
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 >>>> +}; >>>> +
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
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)
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
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
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
Hi, Am 30.09.24 um 19:06 schrieb Benjamin Tissoires: > On Sep 30 2024, Werner Sembach wrote: >> [...] >> Thinking about it, maybe it's not to bad that it only changes once udev is >> ready, like this udev could decide if leds should be used or if it should >> directly be passed to OpenRGB for example, giving at least some consistency >> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds >> setting->OpenRGB setting. > That would work if OpenRGB gets to ship the LampArray bpf object (not > saying that it should). Because if OpenRGB is not installed, you'll get > a led class device, and if/when OpenRGB is installed, full LampArray > would be presented. The idea in my head is still that there is some kind of sysfs switch to enable/disable leds. My idea is then that a udev rule shipped with OpenRGB sets this switch to disable before loading the BPF driver so leds never get initialized for the final LampArray device. > > But anyway, BPF allows to dynamically change the behaviour of the > device, so that's IMO one bonus point of it. > >>> FWIW, the use of BPF only allows you to not corner yourself. If you >>> failed at your LampArray implementation, you'll have to deal with it >>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step >>> where you develop both userspace and kernel space and then convert back >>> the BPF into a proper HID driver. >> I don't really see this point: The LampArray API is defined by the HID Usage >> Table and the report descriptor, so there is not API to mess up and >> everything else has to be parsed dynamically by userspace anyway, so it can >> easily be changed and userspace just adopts automatically. >> >> And for this case the proper HID driver is already ready. > Yeah, except we don't have the fallback LED class. If you are confident > enough with your implementation, then maybe yes we can include it as a > driver from day one, but that looks like looking for troubles from my > point of view. To be on the safe side that we don't talk about different things: My current plan is that the leds subsystem builds on top of the LampArray implementation. Like this the leds part has to be only implemented once for all LampArray devices be it emulated via a driver or native via firmware in the device itself. And I feel confident that the UAPI should be that the userspace gets a hidraw device with a LampArray HID descriptor, and every thing else is, by the HID spec, dynamic anyway so I can still change my mind in implementation specifics there, can't I? > > After a second look at the LampArray code here... Aren't you forgetting > the to/from CPU conversions in case you are on a little endian system? Since this driver is for built in keyboards of x86 notebooks it isn't required or is it? > >> So the only point for me currently is: Is it ok to have key position/usage >> description tables in the kernel driver or not? > good question :) > > I would say, probably not in the WMI driver itself. I would rather have > a hid-tuxedo.c HID driver that does that. But even there, we already had > Linus complaining once regarding the report descriptors we sometimes > insert in drivers, which are looking like opaque blobs. So it might not be > the best either. Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like hid-tuxedo.c? or should it be a separate file with just the arrays? > > Sorry I don't have a clear yes/no answer. Hm... Well if it's no problem I would keep the current implementation with minor adjustments because, like i described above, I don't see a benefit now that this already works to rewrite it in BPF again. If it is a problem then i don't see another way then to rewrite it in BPF. Note: For future devices there might be more keyboard layouts added, basically every time the chassis form factor changes. > > Cheers, > Benjamin To sum up the architechture (not mutally exclusive technically) /- leds WMI <- WMI to LampArray Kernel driver <-switch-| Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \- OpenRGB /- leds WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF driver<-switch-| \- OpenRGB With the "switch" and "leds" implemented in hid core, automatically initialized every time a LampArray device pops up (regardless if it is from native firmware, a bpf driver or a kernel driver) Writing this down I think it was never decided how the switch should look like: It should not be a sysfs attribute of the leds device as the leds device should disappear when the switch is set away from it, but should it be a sysfs variable of the hid device? This would mean that hid core needs to add that switch variable to every hid device having a LampArray section in the descriptor. > >>> Being able to develop a kernel driver without having to reboot and >>> being sure you won't crash your kernel is a game changer ;) >>> >>> Cheers, >>> Benjamin Best regards and sorry for the many questions, Werner Sembach PS: on a side node: How does hid core handle HID devices with a broken HID implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid device get reinitialized by hid core once the bpf driver got loaded? If yes, is there a way to avoid side effects by this double initialization or is there a way to avoid this double initialization, like marking the device id as broken so that hid core- does not initialize it unless it's fixed by bpf?
(sorry resend because thunderbird made it a html mail) Hi, Am 30.09.24 um 19:06 schrieb Benjamin Tissoires: > On Sep 30 2024, Werner Sembach wrote: >> [...] >> Thinking about it, maybe it's not to bad that it only changes once udev is >> ready, like this udev could decide if leds should be used or if it should >> directly be passed to OpenRGB for example, giving at least some consistency >> only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds >> setting->OpenRGB setting. > That would work if OpenRGB gets to ship the LampArray bpf object (not > saying that it should). Because if OpenRGB is not installed, you'll get > a led class device, and if/when OpenRGB is installed, full LampArray > would be presented. The idea in my head is still that there is some kind of sysfs switch to enable/disable leds. My idea is then that a udev rule shipped with OpenRGB sets this switch to disable before loading the BPF driver so leds never get initialized for the final LampArray device. > But anyway, BPF allows to dynamically change the behaviour of the > device, so that's IMO one bonus point of it. > >>> FWIW, the use of BPF only allows you to not corner yourself. If you >>> failed at your LampArray implementation, you'll have to deal with it >>> forever-ish. So it's perfectly sensible to use BPF as an intermediate step >>> where you develop both userspace and kernel space and then convert back >>> the BPF into a proper HID driver. >> I don't really see this point: The LampArray API is defined by the HID Usage >> Table and the report descriptor, so there is not API to mess up and >> everything else has to be parsed dynamically by userspace anyway, so it can >> easily be changed and userspace just adopts automatically. >> >> And for this case the proper HID driver is already ready. > Yeah, except we don't have the fallback LED class. If you are confident > enough with your implementation, then maybe yes we can include it as a > driver from day one, but that looks like looking for troubles from my > point of view. To be on the safe side that we don't talk about different things: My current plan is that the leds subsystem builds on top of the LampArray implementation. Like this the leds part has to be only implemented once for all LampArray devices be it emulated via a driver or native via firmware in the device itself. And I feel confident that the UAPI should be that the userspace gets a hidraw device with a LampArray HID descriptor, and every thing else is, by the HID spec, dynamic anyway so I can still change my mind in implementation specifics there, can't I? > After a second look at the LampArray code here... Aren't you forgetting > the to/from CPU conversions in case you are on a little endian system? Since this driver is for built in keyboards of x86 notebooks it isn't required or is it? >> So the only point for me currently is: Is it ok to have key position/usage >> description tables in the kernel driver or not? > good question :) > > I would say, probably not in the WMI driver itself. I would rather have > a hid-tuxedo.c HID driver that does that. But even there, we already had > Linus complaining once regarding the report descriptors we sometimes > insert in drivers, which are looking like opaque blobs. So it might not be > the best either. Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like hid-tuxedo.c? or should it be a separate file with just the arrays? > Sorry I don't have a clear yes/no answer. Hm... Well if it's no problem I would keep the current implementation with minor adjustments because, like i described above, I don't see a benefit now that this already works to rewrite it in BPF again. If it is a problem then i don't see another way then to rewrite it in BPF. Note: For future devices there might be more keyboard layouts added, basically every time the chassis form factor changes. > Cheers, > Benjamin To sum up the architechture (not mutally exclusive technically) /- leds WMI <- WMI to LampArray Kernel driver <-switch-| Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \- OpenRGB /- leds WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF driver<-switch-| \- OpenRGB With the "switch" and "leds" implemented in hid core, automatically initialized every time a LampArray device pops up (regardless if it is from native firmware, a bpf driver or a kernel driver) Writing this down I think it was never decided how the switch should look like: It should not be a sysfs attribute of the leds device as the leds device should disappear when the switch is set away from it, but should it be a sysfs variable of the hid device? This would mean that hid core needs to add that switch variable to every hid device having a LampArray section in the descriptor. >>> Being able to develop a kernel driver without having to reboot and >>> being sure you won't crash your kernel is a game changer ;) >>> >>> Cheers, >>> Benjamin Best regards and sorry for the many questions, Werner Sembach PS: on a side node: How does hid core handle HID devices with a broken HID implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid device get reinitialized by hid core once the bpf driver got loaded? If yes, is there a way to avoid side effects by this double initialization or is there a way to avoid this double initialization, like marking the device id as broken so that hid core- does not initialize it unless it's fixed by bpf?
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? >
On Oct 01 2024, Werner Sembach wrote: > (sorry resend because thunderbird made it a html mail) > > Hi, > > Am 30.09.24 um 19:06 schrieb Benjamin Tissoires: > > On Sep 30 2024, Werner Sembach wrote: > > > [...] > > > Thinking about it, maybe it's not to bad that it only changes once udev is > > > ready, like this udev could decide if leds should be used or if it should > > > directly be passed to OpenRGB for example, giving at least some consistency > > > only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds > > > setting->OpenRGB setting. > > That would work if OpenRGB gets to ship the LampArray bpf object (not > > saying that it should). Because if OpenRGB is not installed, you'll get > > a led class device, and if/when OpenRGB is installed, full LampArray > > would be presented. > > The idea in my head is still that there is some kind of sysfs switch to > enable/disable leds. FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed without possibility to change them... > > My idea is then that a udev rule shipped with OpenRGB sets this switch to > disable before loading the BPF driver so leds never get initialized for the > final LampArray device. FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So basically we can have a udev property set (or not) by openrgb which makes the BPF program decide whether to present the keyboard as LampArray or not. > > > But anyway, BPF allows to dynamically change the behaviour of the > > device, so that's IMO one bonus point of it. > > > > > > FWIW, the use of BPF only allows you to not corner yourself. If you > > > > failed at your LampArray implementation, you'll have to deal with it > > > > forever-ish. So it's perfectly sensible to use BPF as an intermediate step > > > > where you develop both userspace and kernel space and then convert back > > > > the BPF into a proper HID driver. > > > I don't really see this point: The LampArray API is defined by the HID Usage > > > Table and the report descriptor, so there is not API to mess up and > > > everything else has to be parsed dynamically by userspace anyway, so it can > > > easily be changed and userspace just adopts automatically. > > > > > > And for this case the proper HID driver is already ready. > > Yeah, except we don't have the fallback LED class. If you are confident > > enough with your implementation, then maybe yes we can include it as a > > driver from day one, but that looks like looking for troubles from my > > point of view. > > To be on the safe side that we don't talk about different things: My current > plan is that the leds subsystem builds on top of the LampArray > implementation. I would say that the HID subsystem knows how to translate LampArray into a subset of LEDs. But I think that's what you are saying. > > Like this the leds part has to be only implemented once for all LampArray > devices be it emulated via a driver or native via firmware in the device > itself. yep, that's the plan. However, not sure how to fit LampArray into LED. > > And I feel confident that the UAPI should be that the userspace gets a > hidraw device with a LampArray HID descriptor, and every thing else is, by > the HID spec, dynamic anyway so I can still change my mind in implementation > specifics there, can't I? Yeah... I think? From my point of view we are just bikeshedding on to where put that "firmware" extension, in WMI, in HID (kernel with a subdriver), or externally in BPF. > > > After a second look at the LampArray code here... Aren't you forgetting > > the to/from CPU conversions in case you are on a little endian system? > Since this driver is for built in keyboards of x86 notebooks it isn't > required or is it? Good point. Let's just hope you don't start shipping a LE laptop with the same keyboard hardware :) > > > So the only point for me currently is: Is it ok to have key position/usage > > > description tables in the kernel driver or not? > > good question :) > > > > I would say, probably not in the WMI driver itself. I would rather have > > a hid-tuxedo.c HID driver that does that. But even there, we already had > > Linus complaining once regarding the report descriptors we sometimes > > insert in drivers, which are looking like opaque blobs. So it might not be > > the best either. > Isn't tuxedo_nb04_wmi_ab_virtual_lamp_array.c not something like > hid-tuxedo.c? or should it be a separate file with just the arrays? It is, in a way. I think it's more a question for Hans and the other platform maintainers, whether they would accept this. > > Sorry I don't have a clear yes/no answer. > > Hm... Well if it's no problem I would keep the current implementation with > minor adjustments because, like i described above, I don't see a benefit now > that this already works to rewrite it in BPF again. > > If it is a problem then i don't see another way then to rewrite it in BPF. > > Note: For future devices there might be more keyboard layouts added, > basically every time the chassis form factor changes. If the WMI part doesn't change, then maybe having BPF would be easier for you in the future. Adding a HID-BPF file would cost basically nothing, and it'll be out of band with the kernel, meaning you can ship it in already running kernels (assuming the same WMI driver doesn't need any updates). > > > Cheers, > > Benjamin > To sum up the architechture (not mutally exclusive technically) > > /- leds > WMI <- WMI to LampArray Kernel driver <-switch-| > Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \- OpenRGB > > /- leds > WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF > driver<-switch-| > \- OpenRGB > > With the "switch" and "leds" implemented in hid core, automatically > initialized every time a LampArray device pops up (regardless if it is from > native firmware, a bpf driver or a kernel driver) > > Writing this down I think it was never decided how the switch should look like: > > It should not be a sysfs attribute of the leds device as the leds device > should disappear when the switch is set away from it, but should it be a > sysfs variable of the hid device? This would mean that hid core needs to add > that switch variable to every hid device having a LampArray section in the > descriptor. Again, not a big fan of the sysfs, because it's UAPI and need root to trigger it (though the udev rule sort this one out). BPF allows already to re-enumerate the device with a different look and feel, so it seems more appropriate to me. Also, having a sysfs that depends on the report descriptor basically means that we will lose it whenever we re-enumerate it (kind of what the LED problem you mentioned above). So we would need to have a sysfs on *every* HID devices??? Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos HW): - a device presents a report descriptor with LampArray (wherever it comes from) - hid-led.c takes over it (assuming we extend it for LampArray), and creates a few LEDs based on the Input usage (one global rgb color for regular keys, another one for the few other LEDs known to userspace) - when openRGB is present (special udev property), a BPF program is inserted that only contains a report descriptor fixup that prevent the use of hid-led.c - the device gets re-enumerated, cleaning the in-kernel leds, and only present the LampArray through hidraw, waiting for OpenRGB to take over. - at any time we can remove the BPF and restore the LEDs functionality of hid-led.c > > > > > Being able to develop a kernel driver without having to reboot and > > > > being sure you won't crash your kernel is a game changer ;) > > > > > > > > Cheers, > > > > Benjamin > > Best regards and sorry for the many questions, > > Werner Sembach > > PS: on a side node: How does hid core handle HID devices with a broken HID > implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid > device get reinitialized by hid core once the bpf driver got loaded? If yes, > is there a way to avoid side effects by this double initialization or is > there a way to avoid this double initialization, like marking the device id > as broken so that hid core- does not initialize it unless it's fixed by bpf? - broken HID device: it depends on what you call "broken" HID device. If the report descriptor is boggus, hid-core will reject the device and will not present it to user space (by returning -EINVAL). If the device is sensible in terms of HID protocol, it will present it to userspace, but the fact that it creates an input node or LED or whatever just depends on what is inside the report descriptor. - HID-BPF: HID-BPF is inserted between the device itself and the rest of the in-kernel HID stack. Whenever you load and attach (or detach) a BPF program which has a report descriptor fixup, HID-core will reconnect the device, re-enumerate it (calling ->probe()), and will re-present it to userspace as if it were a new device (you get all uevents). - double initialization: nowadays hid-generic doesn't do anything on the device itself except calling the powerup/powerdown, by calling ->start and ->stop on the HID transport driver. It's not a problem on 99% of the devices AFAICT. technically, if the report descriptor is bogus, start/stop won't be called, but you'll get an error in the dmesg. So if you really want to rely on that "broken" scenario we can always add a specific quirk in HID to not spew that error. Cheers, Benjamin PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits all of the requirements here: - need to be dynamic - still unsure of the userspace implementation, meaning that userspace might do something wrong, which might require kernel changes - possibility to extend later the kernel API - lots of fun :)
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 :)
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
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 :)
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 > > > > +}; > > > > +
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
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
On Oct 01 2024, Werner Sembach wrote: > Hi Benjamin, > > Am 01.10.24 um 15:41 schrieb Benjamin Tissoires: > > On Oct 01 2024, Werner Sembach wrote: > > > (sorry resend because thunderbird made it a html mail) > > > > > > Hi, > > > > > > Am 30.09.24 um 19:06 schrieb Benjamin Tissoires: > > > > On Sep 30 2024, Werner Sembach wrote: > > > > > [...] > > > > > Thinking about it, maybe it's not to bad that it only changes once udev is > > > > > ready, like this udev could decide if leds should be used or if it should > > > > > directly be passed to OpenRGB for example, giving at least some consistency > > > > > only changing once: i.e. firmware -> OpenRGB setting and not firmware->leds > > > > > setting->OpenRGB setting. > > > > That would work if OpenRGB gets to ship the LampArray bpf object (not > > > > saying that it should). Because if OpenRGB is not installed, you'll get > > > > a led class device, and if/when OpenRGB is installed, full LampArray > > > > would be presented. > > > The idea in my head is still that there is some kind of sysfs switch to > > > enable/disable leds. > > FWIW, I'm never a big fan of sysfs. They become UAPI and we are screwed > > without possibility to change them... > > > > > My idea is then that a udev rule shipped with OpenRGB sets this switch to > > > disable before loading the BPF driver so leds never get initialized for the > > > final LampArray device. > > FWIW, udev-hid-bpf can inject a udev property into a HID-BPF. So > > basically we can have a udev property set (or not) by openrgb which > > makes the BPF program decide whether to present the keyboard as > > LampArray or not. > > > > > > But anyway, BPF allows to dynamically change the behaviour of the > > > > device, so that's IMO one bonus point of it. > > > > > > > > > > FWIW, the use of BPF only allows you to not corner yourself. If you > > > > > > failed at your LampArray implementation, you'll have to deal with it > > > > > > forever-ish. So it's perfectly sensible to use BPF as an intermediate step > > > > > > where you develop both userspace and kernel space and then convert back > > > > > > the BPF into a proper HID driver. > > > > > I don't really see this point: The LampArray API is defined by the HID Usage > > > > > Table and the report descriptor, so there is not API to mess up and > > > > > everything else has to be parsed dynamically by userspace anyway, so it can > > > > > easily be changed and userspace just adopts automatically. > > > > > > > > > > And for this case the proper HID driver is already ready. > > > > Yeah, except we don't have the fallback LED class. If you are confident > > > > enough with your implementation, then maybe yes we can include it as a > > > > driver from day one, but that looks like looking for troubles from my > > > > point of view. > > > To be on the safe side that we don't talk about different things: My current > > > plan is that the leds subsystem builds on top of the LampArray > > > implementation. > > I would say that the HID subsystem knows how to translate LampArray into > > a subset of LEDs. But I think that's what you are saying. > > > > > Like this the leds part has to be only implemented once for all LampArray > > > devices be it emulated via a driver or native via firmware in the device > > > itself. > > yep, that's the plan. However, not sure how to fit LampArray into LED. > > My idea was that all leds just get treated as a singular led only allowing > to set a singular color and brightness, but I just looked it up again: > LampArray allows different color and brightness ranges per key, so the > grouping might not be possible in a sensible way ... maybe we can use that information to group per "color and brightness"? > > Maybe the leds integration is a bad idea after all and we should just nudge > the DEs and/or UPower to implement LampArray directly? But that's just > kicking the complexity down the road, at least as long as there is not > universal easy to use library (haven't looked into the library build of > OpenRGB yet). I think that LED integration is not a bad thing if we can approximate the keyboard backlight. We might not be able to provide RGB, but at least some "light up, light down" from the system would certainly make sense. FWIW, I know people using libratbag as a CLI to just turn off the LEDs on their mouse. So we have already users for that exact use case. [...snipped... (because I don't disagree on the WMI discussion)] > > > To sum up the architechture (not mutally exclusive technically) > > > > > > /- leds > > > WMI <- WMI to LampArray Kernel driver <-switch-| > > > Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â \- OpenRGB > > > > > > /- leds > > > WMI <- WMI to Custom HID Kernel driver <- Custom HID to LampArray BPF > > > driver<-switch-| > > > \- OpenRGB > > > > > > With the "switch" and "leds" implemented in hid core, automatically > > > initialized every time a LampArray device pops up (regardless if it is from > > > native firmware, a bpf driver or a kernel driver) > > > > > > Writing this down I think it was never decided how the switch should look like: > > > > > > It should not be a sysfs attribute of the leds device as the leds device > > > should disappear when the switch is set away from it, but should it be a > > > sysfs variable of the hid device? This would mean that hid core needs to add > > > that switch variable to every hid device having a LampArray section in the > > > descriptor. > > Again, not a big fan of the sysfs, because it's UAPI and need root to > > trigger it (though the udev rule sort this one out). > > BPF allows already to re-enumerate the device with a different look and > > feel, so it seems more appropriate to me. > > > > Also, having a sysfs that depends on the report descriptor basically > > means that we will lose it whenever we re-enumerate it (kind of what the > > LED problem you mentioned above). So we would need to have a sysfs on > > *every* HID devices??? > > > > Actually, what would work is (ignoring the BPF bikeshedding for Tuxedos > > HW): > > - a device presents a report descriptor with LampArray (wherever it > > comes from) > > - hid-led.c takes over it (assuming we extend it for LampArray), and > > creates a few LEDs based on the Input usage (one global rgb color for > > regular keys, another one for the few other LEDs known to userspace) > > - when openRGB is present (special udev property), a BPF program is > > inserted that only contains a report descriptor fixup that prevent the > > use of hid-led.c > > How would that look like? just a custom bit in a "Vendor defined" usage page? not a vendor (the code will be in the hid selftests once my v3 series is merged[0]): ``` SEC("?struct_ops/hid_rdesc_fixup") int BPF_PROG(hid_test_driver_probe, struct hid_bpf_ctx *hid_ctx) { hid_ctx->hid->quirks |= HID_QUIRK_IGNORE_SPECIAL_DRIVER; return 0; } SEC(".struct_ops.link") struct hid_bpf_ops test_driver_probe = { .hid_rdesc_fixup = (void *)hid_test_driver_probe, }; ``` This has basically no overhead, because it's a one time operation, no change in the event processing. That code will probably need to be enhanced by comparing a UDEV_PROP_* static char that udev-hid-bpf fills in when loading the device. We can also have a dynamic quirk on the boot cmd line that sets that quirk, but it just doesn't scale when multiple devices are supported. > > But this is still UAPI just hidden inside a BFP program instead of sysfs. > But it would avoid the re-enumeration problem. Yep, completely agree, but that UAPI is not set in stone. It's controlled by userspace, and if there are no users, we can drop it much more easily than having a sysfs. The other advantage of using BPF is you have free re-enumeration of the device. Because the quirks sysfs API for HID doesn't support it (maybe we can fix that as well, but I don't see the point given how simple BPF is). > > > - the device gets re-enumerated, cleaning the in-kernel leds, and only > > present the LampArray through hidraw, waiting for OpenRGB to take > > over. > > - at any time we can remove the BPF and restore the LEDs functionality > > of hid-led.c > > > > > > > > Being able to develop a kernel driver without having to reboot and > > > > > > being sure you won't crash your kernel is a game changer ;) > > > > > > > > > > > > Cheers, > > > > > > Benjamin > > > Best regards and sorry for the many questions, > > > > > > Werner Sembach > > > > > > PS: on a side node: How does hid core handle HID devices with a broken HID > > > implementation fixed by bpf, if bpf is loaded after hid-core? Does the hid > > > device get reinitialized by hid core once the bpf driver got loaded? If yes, > > > is there a way to avoid side effects by this double initialization or is > > > there a way to avoid this double initialization, like marking the device id > > > as broken so that hid core- does not initialize it unless it's fixed by bpf? > > - broken HID device: > > it depends on what you call "broken" HID device. If the report > > descriptor is boggus, hid-core will reject the device and will not > > present it to user space (by returning -EINVAL). > > If the device is sensible in terms of HID protocol, it will present it > > to userspace, but the fact that it creates an input node or LED or > > whatever just depends on what is inside the report descriptor. > > > > - HID-BPF: > > HID-BPF is inserted between the device itself and the rest of the > > in-kernel HID stack. > > Whenever you load and attach (or detach) a BPF program which has a > > report descriptor fixup, HID-core will reconnect the device, > > re-enumerate it (calling ->probe()), and will re-present it to > > userspace as if it were a new device (you get all uevents). > > > > - double initialization: > > nowadays hid-generic doesn't do anything on the device itself except > > calling the powerup/powerdown, by calling ->start and ->stop on the > > HID transport driver. It's not a problem on 99% of the devices AFAICT. > > technically, if the report descriptor is bogus, start/stop won't be > > called, but you'll get an error in the dmesg. So if you really want to > > rely on that "broken" scenario we can always add a specific quirk in > > HID to not spew that error. > > > > Cheers, > > Benjamin > > > > PPS: sorry for pushing that hard on HID-BPF, but I can see that it fits > > all of the requirements here: > > - need to be dynamic > > - still unsure of the userspace implementation, meaning that userspace > > might do something wrong, which might require kernel changes > > Well the reference implementetion for the arduiono macropad from microsoft > ignores the intensity (brightness) channel on rgb leds contrary to the HID > spec, soo yeah you have a point here ... Heh :) > > > - possibility to extend later the kernel API > > - lots of fun :) > > You advertise it good ;). More work for me now but maybe less work for me > later, I will look into it. Again, I'm pushing this because I see the benefits and because I can probably reuse the same code on my Corsair and Logitech keyboards. But also, keep in mind that it's not mandatory because you can actually attach the BPF code on top of your existing driver to change the way it behaves. It'll be slightly more complex if you don't let a couple of vendor passthrough reports that we can use to directly talk to the device without any tampering, but that's doable. But if you want to keep the current implementation and have a different layout, this can easily be done in BPF on top. Cheers, Benjamin [0] https://lore.kernel.org/linux-input/20241001-hid-bpf-hid-generic-v3-0-2ef1019468df@kernel.org/T/#t
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
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
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
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
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
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
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
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
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). 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. > > 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. Cheers, Benjamin
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 >
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 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
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 >
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 > >
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*]
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*]
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*] >
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
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
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
Hi Armin, On 21-Oct-24 10:26 PM, Armin Wolf wrote: > 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? 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. 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 LampArray gives us a well designed API + a safe way to give direct access to e.g. games to control the lighting. I really don't see the advantage of inventing our own API here only to then also have to design + code some way to safely give access to sandboxed apps. Note that giving access to sandboxed apps is a lot of work, it is not just kernel API it also requires designing a portal interface + implementing that portal for at least GNOME, KDE and wlroots. 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. Regards, Hans
On Oct 22 2024, Hans de Goede wrote: > Hi Armin, > > On 21-Oct-24 10:26 PM, Armin Wolf wrote: > > 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? > > 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. > > 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 LampArray gives us a well designed API + a safe way to give direct access > to e.g. games to control the lighting. I really don't see the advantage of > inventing our own API here only to then also have to design + code some way to > safely give access to sandboxed apps. > > Note that giving access to sandboxed apps is a lot of work, it is not just > kernel API it also requires designing a portal interface + implementing > that portal for at least GNOME, KDE and wlroots. > > 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 applying and it makes a lot of sense: We can devise a lot about this new API, but if we don't have users for it, it's energy wasted. When I mean users, I'm not talking about an example in the kernel tree or some quick prototype. I mean talking to the existing folks already doing that and getting their stamp of approval and have an actual integrated prototype. We know that OpenRGB and probably others will implement LampArray, if not for Linux, at least for Mac and Windows. So we will have users for this protocol. A new Linux specific protocol should be discussed with them, but I doubt that they'll be happy writing an entirely new abstraction layer because of Linux. Cheers, Benjamin
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
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
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
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
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
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
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
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
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 >
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
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
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 >
diff --git a/MAINTAINERS b/MAINTAINERS index cc40a9d9b8cd1..3385ad51af194 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23358,6 +23358,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux.git turbostat F: tools/power/x86/turbostat/ F: tools/testing/selftests/turbostat/ +TUXEDO DRIVERS +M: Werner Sembach <wse@tuxedocomputers.com> +L: platform-driver-x86@vger.kernel.org +S: Supported +F: drivers/platform/x86/tuxedo/ + TW5864 VIDEO4LINUX DRIVER M: Bluecherry Maintainers <maintainers@bluecherrydvr.com> M: Andrey Utkin <andrey.utkin@corp.bluecherry.net> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ddfccc226751f..c7cffb222adac 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1196,3 +1196,5 @@ config P2SB The main purpose of this library is to unhide P2SB device in case firmware kept it hidden on some platforms in order to access devices behind it. + +source "drivers/platform/x86/tuxedo/Kconfig" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e1b1429470674..1562dcd7ad9a5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -153,3 +153,6 @@ obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o # SEL obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o + +# TUXEDO +obj-y += tuxedo/ diff --git a/drivers/platform/x86/tuxedo/Kbuild b/drivers/platform/x86/tuxedo/Kbuild new file mode 100644 index 0000000000000..5a3506ab98131 --- /dev/null +++ b/drivers/platform/x86/tuxedo/Kbuild @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# TUXEDO X86 Platform Specific Drivers +# + +tuxedo_nb04_wmi_ab-y := tuxedo_nb04_wmi_ab_init.o +tuxedo_nb04_wmi_ab-y += tuxedo_nb04_wmi_util.o +tuxedo_nb04_wmi_ab-y += tuxedo_nb04_wmi_ab_virtual_lamp_array.o +obj-$(CONFIG_TUXEDO_NB04_WMI_AB) += tuxedo_nb04_wmi_ab.o diff --git a/drivers/platform/x86/tuxedo/Kconfig b/drivers/platform/x86/tuxedo/Kconfig new file mode 100644 index 0000000000000..b1f7c6ceeaae4 --- /dev/null +++ b/drivers/platform/x86/tuxedo/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# TUXEDO X86 Platform Specific Drivers +# + +menuconfig TUXEDO_NB04_WMI_AB + tristate "TUXEDO NB04 WMI AB Platform Driver" + default m + help + This driver implements the WMI AB device found on TUXEDO Notebooks + with board vendor NB04. For the time being only the keyboard backlight + control is implemented. + + When compiled as a module it will be called tuxedo_nb04_wmi_ab. diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c new file mode 100644 index 0000000000000..6e4446b0e3dd8 --- /dev/null +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This driver implements the WMI AB device found on TUXEDO Notebooks with board + * vendor NB04. + * + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/wmi.h> +#include <linux/dmi.h> + +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h" + +#include "tuxedo_nb04_wmi_ab_init.h" + +// We don't know if the WMI API is stable and how unique the GUID is for this ODM. To be on the safe +// side we therefore only run this driver on tested devices defined by this list. +static const struct dmi_system_id tested_devices_dmi_table[] = { + { + // TUXEDO Sirius 16 Gen1 + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "APX958"), + }, + }, + { + // TUXEDO Sirius 16 Gen2 + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "TUXEDO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "AHP958"), + }, + }, + { } +}; + +static int probe(struct wmi_device *wdev, const void __always_unused *context) +{ + struct tuxedo_nb04_wmi_driver_data_t *driver_data; + + if (dmi_check_system(tested_devices_dmi_table)) + return -ENODEV; + + driver_data = devm_kzalloc(&wdev->dev, sizeof(struct tuxedo_nb04_wmi_driver_data_t), + GFP_KERNEL); + if (!driver_data) + return -ENOMEM; + + mutex_init(&driver_data->wmi_access_mutex); + + dev_set_drvdata(&wdev->dev, driver_data); + + tuxedo_nb04_virtual_lamp_array_add_device(wdev, &driver_data->virtual_lamp_array_hdev); + + return 0; +} + +static void remove(struct wmi_device *wdev) +{ + struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data; + + hid_destroy_device(driver_data->virtual_lamp_array_hdev); +} + +static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = { + { .guid_string = "80C9BAA6-AC48-4538-9234-9F81A55E7C85" }, + { } +}; +MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids); + +static struct wmi_driver tuxedo_nb04_wmi_ab_driver = { + .driver = { + .name = "tuxedo_nb04_wmi_ab", + .owner = THIS_MODULE + }, + .id_table = tuxedo_nb04_wmi_ab_device_ids, + .probe = probe, + .remove = remove +}; +module_wmi_driver(tuxedo_nb04_wmi_ab_driver); + +MODULE_DESCRIPTION("Virtual HID LampArray interface for TUXEDO NB04 devices"); +MODULE_AUTHOR("Werner Sembach <wse@tuxedocomputers.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h new file mode 100644 index 0000000000000..aebfd465c9b61 --- /dev/null +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_init.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This driver implements the WMI AB device found on TUXEDO Notebooks with board + * vendor NB04. + * + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com + */ + +#ifndef TUXEDO_NB04_WMI_AB_INIT_H +#define TUXEDO_NB04_WMI_AB_INIT_H + +#include <linux/mutex.h> +#include <linux/hid.h> + +struct tuxedo_nb04_wmi_driver_data_t { + struct mutex wmi_access_mutex; + struct hid_device *virtual_lamp_array_hdev; +}; + +#endif diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c new file mode 100644 index 0000000000000..04af19aa6ad5f --- /dev/null +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.c @@ -0,0 +1,741 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This code gives the built in RGB lighting of the TUXEDO NB04 devices a + * standardised interface, namely HID LampArray. + * + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "tuxedo_nb04_wmi_util.h" + +#include "tuxedo_nb04_wmi_ab_virtual_lamp_array.h" + +#define dev_to_wdev(__dev) container_of(__dev, struct wmi_device, dev) + +enum report_ids { + LAMP_ARRAY_ATTRIBUTES_REPORT_ID = 0x01, + LAMP_ATTRIBUTES_REQUEST_REPORT_ID = 0x02, + LAMP_ATTRIBUTES_RESPONSE_REPORT_ID = 0x03, + LAMP_MULTI_UPDATE_REPORT_ID = 0x04, + LAMP_RANGE_UPDATE_REPORT_ID = 0x05, + LAMP_ARRAY_CONTROL_REPORT_ID = 0x06, +}; + +static const uint8_t sirius_16_ansii_kbl_mapping[] = { + 0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, + 0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c, 0x4a, 0x4d, 0x4b, 0x4e, + 0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + 0x27, 0x2d, 0x2e, 0x2a, 0x53, 0x55, 0x54, 0x56, + 0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12, + 0x13, 0x2f, 0x30, 0x31, 0x5f, 0x60, 0x61, + 0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, + 0x33, 0x34, 0x28, 0x5c, 0x5d, 0x5e, 0x57, + 0xe1, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, 0x37, + 0x38, 0xe5, 0x52, 0x59, 0x5a, 0x5b, + 0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51, + 0x4f, 0x62, 0x63, 0x58 +}; + +static const uint32_t sirius_16_ansii_kbl_mapping_pos_x[] = { + 25000, 41700, 58400, 75100, 91800, 108500, 125200, 141900, 158600, 175300, + 192000, 208700, 225400, 242100, 258800, 275500, 294500, 311200, 327900, 344600, + 24500, 42500, 61000, 79500, 98000, 116500, 135000, 153500, 172000, 190500, + 209000, 227500, 246000, 269500, 294500, 311200, 327900, 344600, + 31000, 51500, 70000, 88500, 107000, 125500, 144000, 162500, 181000, 199500, + 218000, 236500, 255000, 273500, 294500, 311200, 327900, + 33000, 57000, 75500, 94000, 112500, 131000, 149500, 168000, 186500, 205000, + 223500, 242000, 267500, 294500, 311200, 327900, 344600, + 37000, 66000, 84500, 103000, 121500, 140000, 158500, 177000, 195500, 214000, + 232500, 251500, 273500, 294500, 311200, 327900, + 28000, 47500, 66000, 84500, 140000, 195500, 214000, 234000, 255000, 273500, + 292000, 311200, 327900, 344600 +}; + +static const uint32_t sirius_16_ansii_kbl_mapping_pos_y[] = { + 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, + 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, + 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, + 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, + 85500, 85500, 85500, 85500, 85500, 85500, 85500, 85500, 85500, 85500, + 85500, 85500, 85500, 85500, 85500, 85500, 85500, + 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, + 103500, 103500, 103500, 103500, 103500, 103500, 94500, + 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, + 121500, 121500, 129000, 121500, 121500, 121500, + 139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000, + 147000, 139500, 139500, 130500 +}; + +static const uint32_t sirius_16_ansii_kbl_mapping_pos_z[] = { + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, + 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, + 5500, 5500, 5500, 5500, 5500, 5500, 5500, 5500, 5500, 5500, + 5500, 5500, 5500, 5500, 5500, 5500, 5500, + 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5750, + 5750, 5750, 5750, 5750, 5750, 5750, 5625, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6125, 6000, 6000, 6000, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6375, 6375, + 6375, 6250, 6250, 6125 +}; + +static const uint8_t sirius_16_iso_kbl_mapping[] = { + 0x29, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, + 0x43, 0x44, 0x45, 0xf1, 0x46, 0x4c, 0x4a, 0x4d, 0x4b, 0x4e, + 0x35, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + 0x27, 0x2d, 0x2e, 0x2a, 0x53, 0x55, 0x54, 0x56, + 0x2b, 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, 0x12, + 0x13, 0x2f, 0x30, 0x5f, 0x60, 0x61, + 0x39, 0x04, 0x16, 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, + 0x33, 0x34, 0x32, 0x28, 0x5c, 0x5d, 0x5e, 0x57, + 0xe1, 0x64, 0x1d, 0x1b, 0x06, 0x19, 0x05, 0x11, 0x10, 0x36, + 0x37, 0x38, 0xe5, 0x52, 0x59, 0x5a, 0x5b, + 0xe0, 0xfe, 0xe3, 0xe2, 0x2c, 0xe6, 0x65, 0xe4, 0x50, 0x51, + 0x4f, 0x62, 0x63, 0x58 +}; + +static const uint32_t sirius_16_iso_kbl_mapping_pos_x[] = { + 25000, 41700, 58400, 75100, 91800, 108500, 125200, 141900, 158600, 175300, + 192000, 208700, 225400, 242100, 258800, 275500, 294500, 311200, 327900, 344600, + 24500, 42500, 61000, 79500, 98000, 116500, 135000, 153500, 172000, 190500, + 209000, 227500, 246000, 269500, 294500, 311200, 327900, 344600, + 31000, 51500, 70000, 88500, 107000, 125500, 144000, 162500, 181000, 199500, + 218000, 234500, 251000, 294500, 311200, 327900, + 33000, 57000, 75500, 94000, 112500, 131000, 149500, 168000, 186500, 205000, + 223500, 240000, 256500, 271500, 294500, 311200, 327900, 344600, + 28000, 47500, 66000, 84500, 103000, 121500, 140000, 158500, 177000, 195500, + 214000, 232500, 251500, 273500, 294500, 311200, 327900, + 28000, 47500, 66000, 84500, 140000, 195500, 214000, 234000, 255000, 273500, + 292000, 311200, 327900, 344600 +}; + +static const uint32_t sirius_16_iso_kbl_mapping_pos_y[] = { + 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, + 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, 53000, + 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, + 67500, 67500, 67500, 67500, 67500, 67500, 67500, 67500, + 85500, 85500, 85500, 85500, 85500, 85500, 85500, 85500, 85500, 85500, + 85500, 85500, 85500, 85500, 85500, 85500, + 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, 103500, + 103500, 103500, 103500, 94500, 103500, 103500, 103500, 94500, + 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, 121500, + 121500, 121500, 121500, 129000, 121500, 121500, 121500, + 139500, 139500, 139500, 139500, 139500, 139500, 139500, 139500, 147000, 147000, + 147000, 139500, 139500, 130500 +}; + +static const uint32_t sirius_16_iso_kbl_mapping_pos_z[] = { + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000, + 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, + 5250, 5250, 5250, 5250, 5250, 5250, 5250, 5250, + 5500, 5500, 5500, 5500, 5500, 5500, 5500, 5500, 5500, 5500, + 5500, 5500, 5500, 5500, 5500, 5500, + 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5750, + 5750, 5750, 5750, 5750, 5750, 5750, 5750, 5625, + 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, + 6000, 6000, 6000, 6125, 6000, 6000, 6000, + 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6250, 6375, 6375, + 6375, 6250, 6250, 6125 +}; + +struct driver_data_t { + uint8_t keyboard_type; + uint8_t lamp_count; + uint8_t next_lamp_id; + union tuxedo_nb04_wmi_496_b_in_80_b_out_input next_kbl_set_multiple_keys_input; +}; + + +static int ll_start(struct hid_device *hdev) +{ + int ret; + struct driver_data_t *driver_data; + struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent); + union tuxedo_nb04_wmi_8_b_in_80_b_out_input input; + union tuxedo_nb04_wmi_8_b_in_80_b_out_output output; + + driver_data = devm_kzalloc(&hdev->dev, sizeof(struct driver_data_t), GFP_KERNEL); + if (!driver_data) + return -ENOMEM; + + input.get_device_status_input.device_type = WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD; + ret = tuxedo_nb04_wmi_8_b_in_80_b_out(wdev, WMI_AB_GET_DEVICE_STATUS, &input, &output); + if (ret) + return ret; + + driver_data->keyboard_type = output.get_device_status_output.keyboard_physical_layout; + driver_data->lamp_count = sizeof(sirius_16_ansii_kbl_mapping); + driver_data->next_lamp_id = 0; + + hdev->driver_data = driver_data; + + return ret; +} + + +static void ll_stop(struct hid_device __always_unused *hdev) +{ +} + + +static int ll_open(struct hid_device __always_unused *hdev) +{ + return 0; +} + + +static void ll_close(struct hid_device __always_unused *hdev) +{ +} + + +static uint8_t report_descriptor[327] = { + 0x05, 0x59, // Usage Page (Lighting and Illumination) + 0x09, 0x01, // Usage (Lamp Array) + 0xa1, 0x01, // Collection (Application) + 0x85, LAMP_ARRAY_ATTRIBUTES_REPORT_ID, // Report ID (1) + 0x09, 0x02, // Usage (Lamp Array Attributes Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x03, // Usage (Lamp Count) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x03, // Feature (Cnst,Var,Abs) + 0x09, 0x04, // Usage (Bounding Box Width In Micrometers) + 0x09, 0x05, // Usage (Bounding Box Height In Micrometers) + 0x09, 0x06, // Usage (Bounding Box Depth In Micrometers) + 0x09, 0x07, // Usage (Lamp Array Kind) + 0x09, 0x08, // Usage (Min Update Interval In Microseconds) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) + 0x75, 0x20, // Report Size (32) + 0x95, 0x05, // Report Count (5) + 0xb1, 0x03, // Feature (Cnst,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_ATTRIBUTES_REQUEST_REPORT_ID, // Report ID (2) + 0x09, 0x20, // Usage (Lamp Attributes Request Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x21, // Usage (Lamp Id) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_ATTRIBUTES_RESPONSE_REPORT_ID, // Report ID (3) + 0x09, 0x22, // Usage (Lamp Attributes Response Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x21, // Usage (Lamp Id) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x23, // Usage (Position X In Micrometers) + 0x09, 0x24, // Usage (Position Y In Micrometers) + 0x09, 0x25, // Usage (Position Z In Micrometers) + 0x09, 0x27, // Usage (Update Latency In Microseconds) + 0x09, 0x26, // Usage (Lamp Purposes) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) + 0x75, 0x20, // Report Size (32) + 0x95, 0x05, // Report Count (5) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x28, // Usage (Red Level Count) + 0x09, 0x29, // Usage (Green Level Count) + 0x09, 0x2a, // Usage (Blue Level Count) + 0x09, 0x2b, // Usage (Intensity Level Count) + 0x09, 0x2c, // Usage (Is Programmable) + 0x09, 0x2d, // Usage (Input Binding) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x06, // Report Count (6) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_MULTI_UPDATE_REPORT_ID, // Report ID (4) + 0x09, 0x50, // Usage (Lamp Multi Update Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x03, // Usage (Lamp Count) + 0x09, 0x55, // Usage (Lamp Update Flags) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x08, // Logical Maximum (8) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x21, // Usage (Lamp Id) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x08, // Report Count (8) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x20, // Report Count (32) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_RANGE_UPDATE_REPORT_ID, // Report ID (5) + 0x09, 0x60, // Usage (Lamp Range Update Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x55, // Usage (Lamp Update Flags) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x08, // Logical Maximum (8) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x61, // Usage (Lamp Id Start) + 0x09, 0x62, // Usage (Lamp Id End) + 0x15, 0x00, // Logical Minimum (0) + 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) + 0x75, 0x10, // Report Size (16) + 0x95, 0x02, // Report Count (2) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0x09, 0x51, // Usage (Red Update Channel) + 0x09, 0x52, // Usage (Green Update Channel) + 0x09, 0x53, // Usage (Blue Update Channel) + 0x09, 0x54, // Usage (Intensity Update Channel) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xff, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0x85, LAMP_ARRAY_CONTROL_REPORT_ID, // Report ID (6) + 0x09, 0x70, // Usage (Lamp Array Control Report) + 0xa1, 0x02, // Collection (Logical) + 0x09, 0x71, // Usage (Autonomous Mode) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0xb1, 0x02, // Feature (Data,Var,Abs) + 0xc0, // End Collection + 0xc0 // End Collection +}; + +static int ll_parse(struct hid_device *hdev) +{ + return hid_parse_report(hdev, report_descriptor, sizeof(report_descriptor)); +} + + +struct __packed lamp_array_attributes_report_t { + const uint8_t report_id; + uint16_t lamp_count; + uint32_t bounding_box_width_in_micrometers; + uint32_t bounding_box_height_in_micrometers; + uint32_t bounding_box_depth_in_micrometers; + uint32_t lamp_array_kind; + uint32_t min_update_interval_in_microseconds; +}; + +static int handle_lamp_array_attributes_report(struct hid_device *hdev, + struct lamp_array_attributes_report_t *rep) +{ + struct driver_data_t *driver_data = hdev->driver_data; + + rep->lamp_count = driver_data->lamp_count; + rep->bounding_box_width_in_micrometers = 368000; + rep->bounding_box_height_in_micrometers = 266000; + rep->bounding_box_depth_in_micrometers = 30000; + // LampArrayKindKeyboard, see "26.2.1 LampArrayKind Values" of "HID Usage Tables v1.5" + rep->lamp_array_kind = 1; + // Some guessed value for interval microseconds + rep->min_update_interval_in_microseconds = 500; + + return sizeof(struct lamp_array_attributes_report_t); +} + + +struct __packed lamp_attributes_request_report_t { + const uint8_t report_id; + uint16_t lamp_id; +}; + +static int handle_lamp_attributes_request_report(struct hid_device *hdev, + struct lamp_attributes_request_report_t *rep) +{ + struct driver_data_t *driver_data = hdev->driver_data; + + if (rep->lamp_id < driver_data->lamp_count) + driver_data->next_lamp_id = rep->lamp_id; + else + driver_data->next_lamp_id = 0; + + return sizeof(struct lamp_attributes_request_report_t); +} + + +struct __packed lamp_attributes_response_report_t { + const uint8_t report_id; + uint16_t lamp_id; + uint32_t position_x_in_micrometers; + uint32_t position_y_in_micrometers; + uint32_t position_z_in_micrometers; + uint32_t update_latency_in_microseconds; + uint32_t lamp_purpose; + uint8_t red_level_count; + uint8_t green_level_count; + uint8_t blue_level_count; + uint8_t intensity_level_count; + uint8_t is_programmable; + uint8_t input_binding; +}; + +static int handle_lamp_attributes_response_report(struct hid_device *hdev, + struct lamp_attributes_response_report_t *rep) +{ + struct driver_data_t *driver_data = hdev->driver_data; + uint16_t lamp_id = driver_data->next_lamp_id; + const uint8_t *kbl_mapping; + const uint32_t *kbl_mapping_pos_x, *kbl_mapping_pos_y, *kbl_mapping_pos_z; + + rep->lamp_id = lamp_id; + // Some guessed value for latency microseconds + rep->update_latency_in_microseconds = 100; + // LampPurposeControl, see "26.3.1 LampPurposes Flags" of "HID Usage Tables v1.5" + rep->lamp_purpose = 1; + rep->red_level_count = 0xff; + rep->green_level_count = 0xff; + rep->blue_level_count = 0xff; + rep->intensity_level_count = 0xff; + rep->is_programmable = 1; + + if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) { + kbl_mapping = &sirius_16_ansii_kbl_mapping[0]; + kbl_mapping_pos_x = &sirius_16_ansii_kbl_mapping_pos_x[0]; + kbl_mapping_pos_y = &sirius_16_ansii_kbl_mapping_pos_y[0]; + kbl_mapping_pos_z = &sirius_16_ansii_kbl_mapping_pos_z[0]; + } else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) { + kbl_mapping = &sirius_16_iso_kbl_mapping[0]; + kbl_mapping_pos_x = &sirius_16_iso_kbl_mapping_pos_x[0]; + kbl_mapping_pos_y = &sirius_16_iso_kbl_mapping_pos_y[0]; + kbl_mapping_pos_z = &sirius_16_iso_kbl_mapping_pos_z[0]; + } else + return -EINVAL; + + if (kbl_mapping[lamp_id] <= 0xe8) + rep->input_binding = kbl_mapping[lamp_id]; + else + // Everything bigger is reserved/undefined, see "10 Keyboard/Keypad Page (0x07)" of + // "HID Usage Tables v1.5" and should return 0, see "26.8.3 Lamp Attributes" of the + // same document. + rep->input_binding = 0; + rep->position_x_in_micrometers = kbl_mapping_pos_x[lamp_id]; + rep->position_y_in_micrometers = kbl_mapping_pos_y[lamp_id]; + rep->position_z_in_micrometers = kbl_mapping_pos_z[lamp_id]; + + driver_data->next_lamp_id = (driver_data->next_lamp_id + 1) % driver_data->lamp_count; + + return sizeof(struct lamp_attributes_response_report_t); +} + + +#define LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE BIT(0) + +struct __packed lamp_multi_update_report_t { + const uint8_t report_id; + uint8_t lamp_count; + uint8_t lamp_update_flags; + uint16_t lamp_id[8]; + struct { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t intensity; + } update_channels[8]; +}; + +static int handle_lamp_multi_update_report(struct hid_device *hdev, + struct lamp_multi_update_report_t *rep) +{ + int ret; + struct driver_data_t *driver_data = hdev->driver_data; + struct wmi_device *wdev = dev_to_wdev(hdev->dev.parent); + uint8_t lamp_count, key_id, key_id_j; + union tuxedo_nb04_wmi_496_b_in_80_b_out_input *next = + &driver_data->next_kbl_set_multiple_keys_input; + union tuxedo_nb04_wmi_496_b_in_80_b_out_output output; + + // Catching missformated lamp_multi_update_report and fail silently according to + // "HID Usage Tables v1.5" + for (int i = 0; i < rep->lamp_count; ++i) { + if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) + lamp_count = sizeof(sirius_16_ansii_kbl_mapping); + else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) + lamp_count = sizeof(sirius_16_ansii_kbl_mapping); + + if (rep->lamp_id[i] > lamp_count) { + pr_debug("Out of bounds lamp_id in lamp_multi_update_report. Skippng whole report!\n"); + return sizeof(struct lamp_multi_update_report_t); + } + + for (int j = i + 1; j < rep->lamp_count; ++j) { + if (rep->lamp_id[i] == rep->lamp_id[j]) { + pr_debug("Duplicate lamp_id in lamp_multi_update_report. Skippng whole report!\n"); + return sizeof(struct lamp_multi_update_report_t); + } + } + } + + for (int i = 0; i < rep->lamp_count; ++i) { + if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) + key_id = sirius_16_ansii_kbl_mapping[rep->lamp_id[i]]; + else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) + key_id = sirius_16_iso_kbl_mapping[rep->lamp_id[i]]; + + for (int j = 0; j < WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX; ++j) { + key_id_j = next->kbl_set_multiple_keys_input.lighting_settings[j].key_id; + if (key_id_j == 0x00 || key_id_j == key_id) { + if (key_id_j == 0x00) + next->kbl_set_multiple_keys_input.lighting_setting_count = + j + 1; + next->kbl_set_multiple_keys_input.lighting_settings[j].key_id = + key_id; + // While this driver respects + // intensity_update_channel according to "HID + // Usage Tables v1.5" also on RGB leds, the + // Microsoft MacroPad reference implementation + // (https://github.com/microsoft/RP2040MacropadHidSample + // 1d6c3ad) does not and ignores it. If it turns + // out that Windows writes intensity = 0 for RGB + // leds instead of intensity = 255, this driver + // should also irgnore the + // intensity_update_channel. + next->kbl_set_multiple_keys_input.lighting_settings[j].red = + rep->update_channels[i].red + * rep->update_channels[i].intensity / 0xff; + next->kbl_set_multiple_keys_input.lighting_settings[j].green = + rep->update_channels[i].green + * rep->update_channels[i].intensity / 0xff; + next->kbl_set_multiple_keys_input.lighting_settings[j].blue = + rep->update_channels[i].blue + * rep->update_channels[i].intensity / 0xff; + + break; + } + } + } + + if (rep->lamp_update_flags & LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE) { + ret = tuxedo_nb04_wmi_496_b_in_80_b_out(wdev, WMI_AB_KBL_SET_MULTIPLE_KEYS, next, + &output); + memset(next, 0, sizeof(union tuxedo_nb04_wmi_496_b_in_80_b_out_input)); + if (ret) + return ret; + } + + return sizeof(struct lamp_multi_update_report_t); +} + + +struct __packed lamp_range_update_report_t { + const uint8_t report_id; + uint8_t lamp_update_flags; + uint16_t lamp_id_start; + uint16_t lamp_id_end; + uint8_t red_update_channel; + uint8_t green_update_channel; + uint8_t blue_update_channel; + uint8_t intensity_update_channel; +}; + +static int handle_lamp_range_update_report(struct hid_device *hdev, + struct lamp_range_update_report_t *report) +{ + int ret; + struct driver_data_t *driver_data = hdev->driver_data; + uint8_t lamp_count; + struct lamp_multi_update_report_t lamp_multi_update_report = { + .report_id = LAMP_MULTI_UPDATE_REPORT_ID + }; + + // Catching missformated lamp_range_update_report and fail silently according to + // "HID Usage Tables v1.5" + if (report->lamp_id_start > report->lamp_id_end) { + pr_debug("lamp_id_start > lamp_id_end in lamp_range_update_report. Skippng whole report!\n"); + return sizeof(struct lamp_range_update_report_t); + } + + if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII) + lamp_count = sizeof(sirius_16_ansii_kbl_mapping); + else if (driver_data->keyboard_type == WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO) + lamp_count = sizeof(sirius_16_ansii_kbl_mapping); + + if (report->lamp_id_end > lamp_count - 1) { + pr_debug("Out of bounds lamp_id_* in lamp_range_update_report. Skippng whole report!\n"); + return sizeof(struct lamp_range_update_report_t); + } + + // Break handle_lamp_range_update_report call down to multiple + // handle_lamp_multi_update_report calls to easily ensure that mixing + // handle_lamp_range_update_report and handle_lamp_multi_update_report + // does not break things. + for (int i = report->lamp_id_start; i < report->lamp_id_end + 1; i = i + 8) { + lamp_multi_update_report.lamp_count = MIN(report->lamp_id_end + 1 - i, 8); + if (i + lamp_multi_update_report.lamp_count == report->lamp_id_end + 1) + lamp_multi_update_report.lamp_update_flags |= + LAMP_UPDATE_FLAGS_LAMP_UPDATE_COMPLETE; + + for (int j = 0; j < lamp_multi_update_report.lamp_count; ++j) { + lamp_multi_update_report.lamp_id[j] = i + j; + lamp_multi_update_report.update_channels[j].red = + report->red_update_channel; + lamp_multi_update_report.update_channels[j].green = + report->green_update_channel; + lamp_multi_update_report.update_channels[j].blue = + report->blue_update_channel; + lamp_multi_update_report.update_channels[j].intensity = + report->intensity_update_channel; + } + + ret = handle_lamp_multi_update_report(hdev, &lamp_multi_update_report); + if (ret < 0) + return ret; + else if (ret != sizeof(struct lamp_multi_update_report_t)) + return -EIO; + } + + return sizeof(struct lamp_range_update_report_t); +} + + +struct __packed lamp_array_control_report_t { + const uint8_t report_id; + uint8_t autonomous_mode; +}; + +static int handle_lamp_array_control_report(struct hid_device __always_unused *hdev, + struct lamp_array_control_report_t __always_unused *rep) +{ + // The keyboard firmware doesn't have any built in effects or controls + // so this is a NOOP. + // According to the HID Documentation (HID Usage Tables v1.5) this + // function is optional and can be removed from the HID Report + // Descriptor, but it should first be confirmed that userspace respects + // this possibility too. The Microsoft MacroPad reference implementation + // (https://github.com/microsoft/RP2040MacropadHidSample 1d6c3ad) + // already deviates from the spec at another point, see + // handle_lamp_*_update_report. + + return sizeof(struct lamp_array_control_report_t); +} + + +static int ll_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, + unsigned char rtype, int reqtype) +{ + int ret; + + pr_debug("Recived report: rtype: %u, reqtype: %u, reportnum: %u, len: %lu buf:\n", rtype, + reqtype, reportnum, len); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, len); + + ret = -EINVAL; + if (rtype == HID_FEATURE_REPORT) { + if (reqtype == HID_REQ_GET_REPORT) { + if (reportnum == LAMP_ARRAY_ATTRIBUTES_REPORT_ID + && len == sizeof(struct lamp_array_attributes_report_t)) + ret = handle_lamp_array_attributes_report( + hdev, (struct lamp_array_attributes_report_t *)buf); + else if (reportnum == LAMP_ATTRIBUTES_RESPONSE_REPORT_ID + && len == sizeof(struct lamp_attributes_response_report_t)) + ret = handle_lamp_attributes_response_report( + hdev, (struct lamp_attributes_response_report_t *)buf); + } else if (reqtype == HID_REQ_SET_REPORT) { + if (reportnum == LAMP_ATTRIBUTES_REQUEST_REPORT_ID + && len == sizeof(struct lamp_attributes_request_report_t)) + ret = handle_lamp_attributes_request_report( + hdev, (struct lamp_attributes_request_report_t *)buf); + else if (reportnum == LAMP_MULTI_UPDATE_REPORT_ID + && len == sizeof(struct lamp_multi_update_report_t)) + ret = handle_lamp_multi_update_report( + hdev, (struct lamp_multi_update_report_t *)buf); + else if (reportnum == LAMP_RANGE_UPDATE_REPORT_ID + && len == sizeof(struct lamp_range_update_report_t)) + ret = handle_lamp_range_update_report( + hdev, (struct lamp_range_update_report_t *)buf); + else if (reportnum == LAMP_ARRAY_CONTROL_REPORT_ID + && len == sizeof(struct lamp_array_control_report_t)) + ret = handle_lamp_array_control_report( + hdev, (struct lamp_array_control_report_t *)buf); + } + } + + return ret; +} + +static const struct hid_ll_driver ll_driver = { + .start = &ll_start, + .stop = &ll_stop, + .open = &ll_open, + .close = &ll_close, + .parse = &ll_parse, + .raw_request = &ll_raw_request, +}; + +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev, struct hid_device **hdev_out) +{ + int ret; + struct hid_device *hdev; + + pr_debug("Adding TUXEDO NB04 Virtual LampArray device.\n"); + + hdev = hid_allocate_device(); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + *hdev_out = hdev; + + strscpy(hdev->name, "TUXEDO NB04 RGB Lighting", sizeof(hdev->name)); + + hdev->ll_driver = &ll_driver; + hdev->bus = BUS_VIRTUAL; + hdev->vendor = 0x21ba; + hdev->product = 0x0400; + hdev->dev.parent = &wdev->dev; + + ret = hid_add_device(hdev); + if (ret) + hid_destroy_device(hdev); + return ret; +} +EXPORT_SYMBOL(tuxedo_nb04_virtual_lamp_array_add_device); diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h new file mode 100644 index 0000000000000..fdc2a01d95c24 --- /dev/null +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_ab_virtual_lamp_array.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This code gives the built in RGB lighting of the TUXEDO NB04 devices a + * standardised interface, namely HID LampArray. + * + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com + */ + +#ifndef TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H +#define TUXEDO_NB04_WMI_AB_VIRTUAL_LAMP_ARRAY_H + +#include <linux/wmi.h> +#include <linux/hid.h> + +int tuxedo_nb04_virtual_lamp_array_add_device(struct wmi_device *wdev, + struct hid_device **hdev_out); + +#endif diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c new file mode 100644 index 0000000000000..dbabdb9dd60c7 --- /dev/null +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This code gives functions to avoid code duplication while interacting with + * the TUXEDO NB04 wmi interfaces. + * + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "tuxedo_nb04_wmi_ab_init.h" + +#include "tuxedo_nb04_wmi_util.h" + +static int __wmi_method_acpi_object_out(struct wmi_device *wdev, uint32_t wmi_method_id, + uint8_t *in, acpi_size in_len, union acpi_object **out) +{ + struct tuxedo_nb04_wmi_driver_data_t *driver_data = wdev->dev.driver_data; + struct acpi_buffer acpi_buffer_in = { in_len, in }; + struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL }; + + pr_debug("Evaluate WMI method: %u in:\n", wmi_method_id); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len); + + mutex_lock(&driver_data->wmi_access_mutex); + acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id, &acpi_buffer_in, + &acpi_buffer_out); + mutex_unlock(&driver_data->wmi_access_mutex); + if (ACPI_FAILURE(status)) { + pr_err("Failed to evaluate WMI method.\n"); + return -EIO; + } + if (!acpi_buffer_out.pointer) { + pr_err("Unexpected empty out buffer.\n"); + return -ENODATA; + } + + *out = acpi_buffer_out.pointer; + + return 0; +} + +static int __wmi_method_buffer_out(struct wmi_device *wdev, uint32_t wmi_method_id, uint8_t *in, + acpi_size in_len, uint8_t *out, acpi_size out_len) +{ + int ret; + union acpi_object *acpi_object_out = NULL; + + ret = __wmi_method_acpi_object_out(wdev, wmi_method_id, in, in_len, &acpi_object_out); + if (ret) + return ret; + + if (acpi_object_out->type != ACPI_TYPE_BUFFER) { + pr_err("Unexpected out buffer type. Expected: %u Got: %u\n", ACPI_TYPE_BUFFER, + acpi_object_out->type); + kfree(acpi_object_out); + return -EIO; + } + if (acpi_object_out->buffer.length != out_len) { + pr_err("Unexpected out buffer length.\n"); + kfree(acpi_object_out); + return -EIO; + } + + memcpy(out, acpi_object_out->buffer.pointer, out_len); + kfree(acpi_object_out); + + return ret; +} + +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev, + enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method, + union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input, + union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output) +{ + return __wmi_method_buffer_out(wdev, method, input->raw, 8, output->raw, 80); +} + +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev, + enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method, + union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input, + union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output) +{ + return __wmi_method_buffer_out(wdev, method, input->raw, 496, output->raw, 80); +} diff --git a/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h new file mode 100644 index 0000000000000..2765cbe9fcfef --- /dev/null +++ b/drivers/platform/x86/tuxedo/tuxedo_nb04_wmi_util.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This code gives functions to avoid code duplication while interacting with + * the TUXEDO NB04 wmi interfaces. + * + * Copyright (C) 2024 Werner Sembach wse@tuxedocomputers.com + */ + +#ifndef TUXEDO_NB04_WMI_UTIL_H +#define TUXEDO_NB04_WMI_UTIL_H + +#include <linux/wmi.h> + +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_TOUCHPAD 1 +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_KEYBOARD 2 +#define WMI_AB_GET_DEVICE_STATUS_DEVICE_ID_APP_PAGES 3 + +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_NONE 0 +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_PER_KEY 1 +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_FOUR_ZONE 2 +#define WMI_AB_GET_DEVICE_STATUS_KBL_TYPE_WHITE_ONLY 3 + +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ANSII 0 +#define WMI_AB_GET_DEVICE_STATUS_KEYBOARD_LAYOUT_ISO 1 + +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_RED 1 +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_GREEN 2 +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_YELLOW 3 +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_BLUE 4 +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_PURPLE 5 +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_INDIGO 6 +#define WMI_AB_GET_DEVICE_STATUS_COLOR_ID_WHITE 7 + +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_DASHBOARD BIT(0) +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_SYSTEMINFOS BIT(1) +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_KBL BIT(2) +#define WMI_AB_GET_DEVICE_STATUS_APP_PAGES_HOTKEYS BIT(3) + + +union tuxedo_nb04_wmi_8_b_in_80_b_out_input { + uint8_t raw[8]; + struct __packed { + uint8_t device_type; + uint8_t reserved_0[7]; + } get_device_status_input; +}; + +union tuxedo_nb04_wmi_8_b_in_80_b_out_output { + uint8_t raw[80]; + struct __packed { + uint16_t return_status; + uint8_t device_enabled; + uint8_t kbl_type; + uint8_t kbl_side_bar_supported; + uint8_t keyboard_physical_layout; + uint8_t app_pages; + uint8_t per_key_kbl_default_color; + uint8_t four_zone_kbl_default_color_1; + uint8_t four_zone_kbl_default_color_2; + uint8_t four_zone_kbl_default_color_3; + uint8_t four_zone_kbl_default_color_4; + uint8_t light_bar_kbl_default_color; + uint8_t reserved_0[1]; + uint16_t dedicated_gpu_id; + uint8_t reserved_1[64]; + } get_device_status_output; +}; + +enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods { + WMI_AB_GET_DEVICE_STATUS = 2, +}; + + +#define WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX 120 + +union tuxedo_nb04_wmi_496_b_in_80_b_out_input { + uint8_t raw[496]; + struct __packed { + uint8_t reserved_0[15]; + uint8_t lighting_setting_count; + struct { + uint8_t key_id; + uint8_t red; + uint8_t green; + uint8_t blue; + } lighting_settings[WMI_AB_KBL_SET_MULTIPLE_KEYS_LIGHTING_SETTINGS_COUNT_MAX]; + } kbl_set_multiple_keys_input; +}; + +union tuxedo_nb04_wmi_496_b_in_80_b_out_output { + uint8_t raw[80]; + struct __packed { + uint8_t return_value; + uint8_t reserved_0[79]; + } kbl_set_multiple_keys_output; +}; + +enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods { + WMI_AB_KBL_SET_MULTIPLE_KEYS = 6, +}; + + +int tuxedo_nb04_wmi_8_b_in_80_b_out(struct wmi_device *wdev, + enum tuxedo_nb04_wmi_8_b_in_80_b_out_methods method, + union tuxedo_nb04_wmi_8_b_in_80_b_out_input *input, + union tuxedo_nb04_wmi_8_b_in_80_b_out_output *output); +int tuxedo_nb04_wmi_496_b_in_80_b_out(struct wmi_device *wdev, + enum tuxedo_nb04_wmi_496_b_in_80_b_out_methods method, + union tuxedo_nb04_wmi_496_b_in_80_b_out_input *input, + union tuxedo_nb04_wmi_496_b_in_80_b_out_output *output); + +#endif