Re: [PATCH] platform/x86: acer-wmi: Add support for Acer Helios 300 RGB keyboard backlight

From: Hans de Goede
Date: Fri May 14 2021 - 15:49:30 EST


Hi Jafar,

On 5/14/21 2:06 PM, Jafar Akhondali wrote:
> From e65b0ddbf559aa3ca8a7998404e7e67e64b705e9 Mon Sep 17 00:00:00 2001
> From: JafarAkhondali <jafar.akhoondali@xxxxxxxxx>
> Date: Fri, 14 May 2021 08:26:47 +0430
> Subject: [PATCH] platform/x86: acer-wmi: Add support for Acer Helios
> 300 rgb keyboard backlight
>
> The Acer helios 300 provides gaming functions WMI that is available in
> Windows, however this was not implemented in Linux. The process of finding
> the related method was done by decompiling PredatorSense(official Acer
> gaming functions software for Predator series) and decompiling WQ
> buffers. This patch provides a gaming interface which will then expose a
> character device named "acer-gkbbl". This character device accepts 16
> bytes long config, which is specific for the backlight method. The
> meaning of each bytes ordered by bit position is as follows:
>
> Bit 0 -> Backlight modes:
> 1: Breath
> 2: Neon
> 3: Wave
> 4: Shifting
> 5: Zoom
> Bit 1 -> Animation Speed: from 1 to 9 ( 1 is slowest, 9 is fastest)
> Bit 2 -> Brightness from 0 to 100 ( 0 is no backlight, 100 is brightest)
> Bit 3 -> Unknown. Wave effect uses 8, other modes must use 0
> Bit 4 -> Animation Direction:
> 1: Right-to-Left
> 2: Left-to-Right
> Bit 5 -> Red Color Selection
> Bit 6 -> Green Color Selection
> Bit 7 -> Blue Color Selection
> Bit 8 -> Currently unknown, or not used in known model
> Bit 9 -> Currently unknown, or not used in known model
> Bit 10 -> Currently unknown, or not used in known model
> Bit 11 -> Currently unknown, or not used in known model
> Bit 12 -> Currently unknown, or not used in known model
> Bit 13 -> Currently unknown, or not used in known model
> Bit 14 -> Currently unknown, or not used in known model
> Bit 15 -> Currently unknown, or not used in known model
>
> Filling this config is out of scope for the kernel module, and this module
> only acts as an interface.
>
> Currently, I'm not sure with the method for communicating with user-space,
> but since leds.h subsystem wouldn't fit for complex actions such as this
> complex config, I couldn't find any better method than char dev.

Thank you for your patch, given that there is no existing kernel
interface which is a good match for the features exported by this
keyboard I'm fine with just having a raw interface where userspace
writes GAMING_KBBL_CONFIG_LEN bytes as you suggest.

But lets not use a classdev + chardev for this please, you can
just add a binary write-only sysfs-atrribute under the wmi-dev for
this with a name like (for example) gaming_kbd_backlight_config
and then userspace can write to that without needing a class + char
dev just for this single write.

Regards,

Hans


>
> Signed-off-by: JafarAkhondali <jafar.akhoondali@xxxxxxxxx>
> ---
> drivers/platform/x86/acer-wmi.c | 228 ++++++++++++++++++++++++++++++--
> 1 file changed, 216 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
> index 85db9403cc14..a2cb0d4bbec8 100644
> --- a/drivers/platform/x86/acer-wmi.c
> +++ b/drivers/platform/x86/acer-wmi.c
> @@ -27,13 +27,16 @@
> #include <linux/debugfs.h>
> #include <linux/slab.h>
> #include <linux/input.h>
> +#include <linux/cdev.h>
> #include <linux/input/sparse-keymap.h>
> #include <acpi/video.h>
>
> +
> MODULE_AUTHOR("Carlos Corbacho");
> MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
> MODULE_LICENSE("GPL");
>
> +
> /*
> * Magic Number
> * Meaning is unknown - this number is required for writing to ACPI for AMW0
> @@ -59,6 +62,8 @@ MODULE_LICENSE("GPL");
> #define ACER_WMID_SET_BRIGHTNESS_METHODID 6
> #define ACER_WMID_GET_THREEG_METHODID 10
> #define ACER_WMID_SET_THREEG_METHODID 11
> +#define ACER_WMID_SET_GAMINGKBBL_METHODID 20
> +#define ACER_WMID_GET_GAMINGKBBL_METHODID 21
>
> /*
> * Acer ACPI method GUIDs
> @@ -68,6 +73,7 @@ MODULE_LICENSE("GPL");
> #define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3"
> #define WMID_GUID2 "95764E09-FB56-4E83-B31A-37761F60994A"
> #define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531"
> +#define WMID_GUID4 "7A4DDFE7-5B5D-40B4-8595-4408E0CC7F56"
>
> /*
> * Acer ACPI event GUIDs
> @@ -144,6 +150,14 @@ struct event_return_value {
>
> #define ACER_WMID3_GDS_TOUCHPAD (1<<1) /* Touchpad */
>
> +/*
> + * Gaming functions user-space communication
> + * A character drive will be exposed in /dev/acer-gkbbl as char block
> for keyboard backlight config
> + * Config length is 16 bytes
> + */
> +#define GAMING_KBBL_CHR "acer-gkbbl"
> +#define GAMING_KBBL_CONFIG_LEN 16
> +
> /* Hotkey Customized Setting and Acer Application Status.
> * Set Device Default Value and Report Acer Application Status.
> * When Acer Application starts, it will run this method to inform
> @@ -215,6 +229,7 @@ struct hotkey_function_type_aa {
> #define ACER_CAP_THREEG BIT(4)
> #define ACER_CAP_SET_FUNCTION_MODE BIT(5)
> #define ACER_CAP_KBD_DOCK BIT(6)
> +#define ACER_CAP_GAMINGKB BIT(7)
>
> /*
> * Interface type flags
> @@ -224,6 +239,7 @@ enum interface_flags {
> ACER_AMW0_V2,
> ACER_WMID,
> ACER_WMID_v2,
> + ACER_WMID_GAMING,
> };
>
> #define ACER_DEFAULT_WIRELESS 0
> @@ -290,6 +306,20 @@ struct wmi_interface {
> /* The static interface pointer, points to the currently detected interface */
> static struct wmi_interface *interface;
>
> +/* The static gaming interface pointer, points to the currently
> detected gaming interface */
> +static struct wmi_interface *gaming_interface;
> +
> +/*
> + * Character device registration
> + * GAMING_KBBL_MINOR -> used to configure gaming rgb keyboard
> backlights from user-space
> + */
> +
> +#define GAMING_KBBL_MINOR 0
> +
> +
> +static int dev_major; // Global variable to store major number of driver
> +
> +
> /*
> * Embedded Controller quirks
> * Some laptops require us to directly access the EC to either enable or query
> @@ -903,7 +933,7 @@ static acpi_status __init AMW0_set_capabilities(void)
> */
> if (wmi_has_guid(AMW0_GUID2)) {
> if ((quirks != &quirk_unknown) ||
> - !AMW0_set_cap_acpi_check_device())
> + !AMW0_set_cap_acpi_check_device())
> interface->capability |= ACER_CAP_WIRELESS;
> return AE_OK;
> }
> @@ -1344,6 +1374,64 @@ static struct wmi_interface wmid_v2_interface = {
> .type = ACER_WMID_v2,
> };
>
> +
> +/*
> + * WMID Gaming interface
> + */
> +
> +static struct wmi_interface wmid_gaming_interface = {
> + .type = ACER_WMID_GAMING
> +};
> +
> +static acpi_status
> +WMI_gaming_execute_u8_array(u32 method_id, u8 array[], size_t
> array_size, u32 *out)
> +{
> + struct acpi_buffer input = { (acpi_size) array_size, (void *)(array) };
> + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *obj;
> + u32 tmp = 0;
> + acpi_status status;
> +
> + status = wmi_evaluate_method(WMID_GUID4, 0, method_id, &input, &result);
> +
> + if (ACPI_FAILURE(status))
> + return status;
> +
> + obj = (union acpi_object *) result.pointer;
> + if (obj) {
> + if (obj->type == ACPI_TYPE_BUFFER &&
> + (obj->buffer.length == sizeof(u32) ||
> + obj->buffer.length == sizeof(u64))) {
> + tmp = *((u32 *) obj->buffer.pointer);
> + } else if (obj->type == ACPI_TYPE_INTEGER) {
> + tmp = (u32) obj->integer.value;
> + }
> + }
> +
> + if (out)
> + *out = tmp;
> +
> + kfree(result.pointer);
> +
> + return status;
> +}
> +
> +static acpi_status WMID_gaming_set_u8_array(u8 array[], size_t
> array_size, u32 cap)
> +{
> + u32 method_id = 0;
> +
> + switch (cap) {
> + case ACER_CAP_GAMINGKB:
> + if (array_size != GAMING_KBBL_CONFIG_LEN)
> + return AE_BAD_PARAMETER;
> + method_id = ACER_WMID_SET_GAMINGKBBL_METHODID;
> + break;
> + default:
> + return AE_ERROR;
> + }
> + return WMI_gaming_execute_u8_array(method_id, array, array_size, NULL);
> +}
> +
> /*
> * Generic Device (interface-independent)
> */
> @@ -1422,6 +1510,29 @@ static acpi_status set_u32(u32 value, u32 cap)
> return AE_BAD_PARAMETER;
> }
>
> +static acpi_status set_u8_array(u8 array[], size_t array_size, u32 cap)
> +{
> + acpi_status status;
> +
> + if (interface->capability & cap) {
> + switch (interface->type) {
> + default:
> + return AE_BAD_PARAMETER;
> + }
> + } else if (gaming_interface->capability & cap) {
> + switch (gaming_interface->type) {
> + case ACER_WMID_GAMING:
> + status = WMID_gaming_set_u8_array(array, array_size, cap);
> + if (ACPI_FAILURE(status))
> + return status;
> + fallthrough;
> + default:
> + return AE_BAD_PARAMETER;
> + }
> + }
> + return AE_BAD_PARAMETER;
> +}
> +
> static void __init acer_commandline_init(void)
> {
> /*
> @@ -1461,6 +1572,88 @@ static void acer_led_exit(void)
> led_classdev_unregister(&mail_led);
> }
>
> +/*
> + * Keyboard RGB backlight character device handler.
> + * On systems supporting Acer gaming functions, a char device
> + * will be exposed to communicate with user space
> + * for keyboard RGB backlight configurations.
> + */
> +
> +static ssize_t gkbbl_drv_write(struct file *file,
> + const char __user *buf, size_t count, loff_t *offset)
> +{
> + u8 config_buf[GAMING_KBBL_CONFIG_LEN];
> + unsigned long err;
> +
> + if (count != GAMING_KBBL_CONFIG_LEN) {
> + pr_err("Invalid data given to gaming keyboard backlight");
> + return 0;
> + }
> + err = copy_from_user(config_buf, buf, GAMING_KBBL_CONFIG_LEN);
> + if (err < 0)
> + pr_err("Copying data from userspace failed with code: %lu\n", err);
> +
> + set_u8_array(config_buf, GAMING_KBBL_CONFIG_LEN, ACER_CAP_GAMINGKB);
> + return count;
> +}
> +
> +
> +static const struct file_operations gkbbl_dev_fops = {
> + .owner = THIS_MODULE,
> + .write = gkbbl_drv_write
> +};
> +
> +struct gkbbl_device_data {
> + struct cdev cdev;
> +};
> +
> +static struct class *gkbbl_dev_class;
> +static struct gkbbl_device_data gkbbl_dev_data;
> +
> +static int gkbbl_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
> +{
> + add_uevent_var(env, "DEVMODE=%#o", 0666);
> + return 0;
> +}
> +
> +static int __init gaming_kbbl_cdev_init(void)
> +{
> + dev_t dev;
> + int err;
> +
> + err = alloc_chrdev_region(&dev, 0, 1, GAMING_KBBL_CHR);
> + if (err < 0) {
> + pr_err("Char drive registering for gaming keyboard backlight failed:
> %d\n", err);
> + return err;
> + }
> +
> + dev_major = MAJOR(dev);
> +
> + gkbbl_dev_class = class_create(THIS_MODULE, GAMING_KBBL_CHR);
> + gkbbl_dev_class->dev_uevent = gkbbl_dev_uevent;
> +
> + cdev_init(&gkbbl_dev_data.cdev, &gkbbl_dev_fops);
> + gkbbl_dev_data.cdev.owner = THIS_MODULE;
> +
> + cdev_add(&gkbbl_dev_data.cdev, MKDEV(dev_major, GAMING_KBBL_MINOR), 1);
> +
> + device_create(gkbbl_dev_class, NULL, MKDEV(dev_major,
> GAMING_KBBL_MINOR), NULL, "%s-%d",
> + GAMING_KBBL_CHR,
> + GAMING_KBBL_MINOR);
> +
> + return 0;
> +}
> +
> +static void __exit gaming_kbbl_cdev_exit(void)
> +{
> + device_destroy(gkbbl_dev_class, MKDEV(dev_major, GAMING_KBBL_MINOR));
> +
> + class_unregister(gkbbl_dev_class);
> + class_destroy(gkbbl_dev_class);
> +
> + unregister_chrdev_region(MKDEV(dev_major, GAMING_KBBL_MINOR), MINORMASK);
> +}
> +
> /*
> * Backlight device
> */
> @@ -1501,7 +1694,7 @@ static int acer_backlight_init(struct device *dev)
> props.type = BACKLIGHT_PLATFORM;
> props.max_brightness = max_brightness;
> bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops,
> - &props);
> + &props);
> if (IS_ERR(bd)) {
> pr_err("Could not register Acer backlight device\n");
> acer_backlight_device = NULL;
> @@ -1605,7 +1798,7 @@ static void acer_kbd_dock_get_initial_state(void)
> status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input_buf, &output_buf);
> if (ACPI_FAILURE(status)) {
> pr_err("Error getting keyboard-dock initial status: %s\n",
> - acpi_format_exception(status));
> + acpi_format_exception(status));
> return;
> }
>
> @@ -1618,7 +1811,7 @@ static void acer_kbd_dock_get_initial_state(void)
> output = obj->buffer.pointer;
> if (output[0] != 0x00 || (output[3] != 0x05 && output[3] != 0x45)) {
> pr_err("Unexpected output [0]=0x%02x [3]=0x%02x getting
> keyboard-dock initial status\n",
> - output[0], output[3]);
> + output[0], output[3]);
> goto out_free_obj;
> }
>
> @@ -1759,7 +1952,7 @@ static int acer_rfkill_init(struct device *dev)
> rfkill_inited = true;
>
> if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) &&
> - has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
> + has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
> schedule_delayed_work(&acer_rfkill_work,
> round_jiffies_relative(HZ));
>
> @@ -1782,7 +1975,7 @@ static int acer_rfkill_init(struct device *dev)
> static void acer_rfkill_exit(void)
> {
> if ((ec_raw_mode || !wmi_has_guid(ACERWMID_EVENT_GUID)) &&
> - has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
> + has_cap(ACER_CAP_WIRELESS | ACER_CAP_BLUETOOTH | ACER_CAP_THREEG))
> cancel_delayed_work_sync(&acer_rfkill_work);
>
> if (has_cap(ACER_CAP_WIRELESS)) {
> @@ -2251,8 +2444,8 @@ static int __init acer_wmi_init(void)
> * in the past quirk list.
> */
> if (wmi_has_guid(AMW0_GUID1) &&
> - !dmi_check_system(amw0_whitelist) &&
> - quirks == &quirk_unknown) {
> + !dmi_check_system(amw0_whitelist) &&
> + quirks == &quirk_unknown) {
> pr_debug("Unsupported machine has AMW0_GUID1, unable to load\n");
> return -ENODEV;
> }
> @@ -2266,9 +2459,12 @@ static int __init acer_wmi_init(void)
> if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
> interface = &wmid_interface;
>
> - if (wmi_has_guid(WMID_GUID3))
> + if (wmi_has_guid(WMID_GUID3)) {
> interface = &wmid_v2_interface;
> -
> + if (wmi_has_guid(WMID_GUID4))
> + gaming_interface = &wmid_gaming_interface;
> + }
> +
> if (interface)
> dmi_walk(type_aa_dmi_decode, NULL);
>
> @@ -2309,14 +2505,19 @@ static int __init acer_wmi_init(void)
> if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
> interface->capability &= ~ACER_CAP_BRIGHTNESS;
>
> - if (wmi_has_guid(WMID_GUID3))
> + if (wmi_has_guid(WMID_GUID3)) {
> interface->capability |= ACER_CAP_SET_FUNCTION_MODE;
> + if (wmi_has_guid(WMID_GUID4)) {
> + gaming_interface->capability |= ACER_CAP_GAMINGKB;
> + gaming_kbbl_cdev_init();
> + }
> + }
>
> if (force_caps != -1)
> interface->capability = force_caps;
>
> if (wmi_has_guid(WMID_GUID3) &&
> - (interface->capability & ACER_CAP_SET_FUNCTION_MODE)) {
> + (interface->capability & ACER_CAP_SET_FUNCTION_MODE)) {
> if (ACPI_FAILURE(acer_wmi_enable_rf_button()))
> pr_warn("Cannot enable RF Button Driver\n");
>
> @@ -2389,6 +2590,9 @@ static void __exit acer_wmi_exit(void)
> if (acer_wmi_accel_dev)
> input_unregister_device(acer_wmi_accel_dev);
>
> + if (wmi_has_guid(WMID_GUID4))
> + gaming_kbbl_cdev_exit();
> +
> remove_debugfs();
> platform_device_unregister(acer_platform_device);
> platform_driver_unregister(&acer_platform_driver);
>