Loading drivers/hid/hid-asus.c +101 −113 Original line number Diff line number Diff line Loading @@ -27,7 +27,6 @@ #include <linux/hid.h> #include <linux/module.h> #include <linux/platform_data/x86/asus-wmi.h> #include <linux/platform_data/x86/asus-wmi-leds-ids.h> #include <linux/input/mt.h> #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */ #include <linux/power_supply.h> Loading @@ -48,8 +47,9 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define T100CHI_MOUSE_REPORT_ID 0x06 #define FEATURE_REPORT_ID 0x0d #define INPUT_REPORT_ID 0x5d #define HID_USAGE_PAGE_VENDOR 0xff310000 #define FEATURE_KBD_REPORT_ID 0x5a #define FEATURE_KBD_REPORT_SIZE 16 #define FEATURE_KBD_REPORT_SIZE 64 #define FEATURE_KBD_LED_REPORT_ID1 0x5d #define FEATURE_KBD_LED_REPORT_ID2 0x5e Loading Loading @@ -90,6 +90,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) #define QUIRK_ROG_ALLY_XPAD BIT(13) #define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(14) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ Loading @@ -101,7 +102,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define TRKID_SGN ((TRKID_MAX + 1) >> 1) struct asus_kbd_leds { struct led_classdev cdev; struct asus_hid_listener listener; struct hid_device *hdev; struct work_struct work; unsigned int brightness; Loading @@ -126,7 +127,6 @@ struct asus_drvdata { struct input_dev *tp_kbd_input; struct asus_kbd_leds *kbd_backlight; const struct asus_touchpad_info *tp; bool enable_backlight; struct power_supply *battery; struct power_supply_desc battery_desc; int battery_capacity; Loading Loading @@ -317,13 +317,24 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size) static int asus_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 && if ((usage->hid & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR && (usage->hid & HID_USAGE) != 0x00 && (usage->hid & HID_USAGE) != 0xff && !usage->type) { hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n", usage->hid & HID_USAGE); } if (usage->type == EV_KEY && value) { switch (usage->code) { case KEY_KBDILLUMUP: return !asus_hid_event(ASUS_EV_BRTUP); case KEY_KBDILLUMDOWN: return !asus_hid_event(ASUS_EV_BRTDOWN); case KEY_KBDILLUMTOGGLE: return !asus_hid_event(ASUS_EV_BRTTOGGLE); } } return 0; } Loading Loading @@ -394,17 +405,43 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu static int asus_kbd_init(struct hid_device *hdev, u8 report_id) { /* * The handshake is first sent as a set_report, then retrieved * from a get_report. They should be equal. */ const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; int ret; ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); if (ret < 0) hid_err(hdev, "Asus failed to send init command: %d\n", ret); if (ret < 0) { hid_err(hdev, "Asus handshake %02x failed to send: %d\n", report_id, ret); return ret; } u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL); if (!readbuf) return -ENOMEM; ret = hid_hw_raw_request(hdev, report_id, readbuf, FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) { hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n", report_id, ret); } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) { hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n", report_id, FEATURE_KBD_REPORT_SIZE, readbuf); } /* * Do not return error if handshake is wrong until this is * verified to work for all devices. */ return 0; } static int asus_kbd_get_functions(struct hid_device *hdev, unsigned char *kbd_func, u8 report_id) Loading @@ -423,7 +460,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev, if (!readbuf) return -ENOMEM; ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf, ret = hid_hw_raw_request(hdev, report_id, readbuf, FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) { Loading Loading @@ -468,11 +505,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led) spin_unlock_irqrestore(&led->lock, flags); } static void asus_kbd_backlight_set(struct led_classdev *led_cdev, enum led_brightness brightness) static void asus_kbd_backlight_set(struct asus_hid_listener *listener, int brightness) { struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, cdev); struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds, listener); unsigned long flags; spin_lock_irqsave(&led->lock, flags); Loading @@ -482,20 +519,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev, asus_schedule_work(led); } static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev) { struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, cdev); enum led_brightness brightness; unsigned long flags; spin_lock_irqsave(&led->lock, flags); brightness = led->brightness; spin_unlock_irqrestore(&led->lock, flags); return brightness; } static void asus_kbd_backlight_work(struct work_struct *work) { struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); Loading @@ -512,34 +535,6 @@ static void asus_kbd_backlight_work(struct work_struct *work) hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); } /* WMI-based keyboard backlight LED control (via asus-wmi driver) takes * precedence. We only activate HID-based backlight control when the * WMI control is not available. */ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); u32 value; int ret; if (!IS_ENABLED(CONFIG_ASUS_WMI)) return false; if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD && dmi_check_system(asus_use_hid_led_dmi_ids)) { hid_info(hdev, "using HID for asus::kbd_backlight\n"); return false; } ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value); hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value); if (ret) return false; return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT); } /* * We don't care about any other part of the string except the version section. * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 Loading Loading @@ -639,20 +634,23 @@ static int asus_kbd_register_leds(struct hid_device *hdev) unsigned char kbd_func; int ret; if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) { /* Initialize keyboard */ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; /* The LED endpoint is initialised in two HID */ ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); /* Get keyboard functions */ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); if (ret < 0) return ret; /* Check for backlight support */ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) return -ENODEV; if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) { asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); } if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { ret = asus_kbd_disable_oobe(hdev); Loading @@ -667,22 +665,6 @@ static int asus_kbd_register_leds(struct hid_device *hdev) le16_to_cpu(udev->descriptor.idProduct)); } } else { /* Initialize keyboard */ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; /* Get keyboard functions */ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; /* Check for backlight support */ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) return -ENODEV; } drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, sizeof(struct asus_kbd_leds), GFP_KERNEL); Loading @@ -692,14 +674,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev) drvdata->kbd_backlight->removed = false; drvdata->kbd_backlight->brightness = 0; drvdata->kbd_backlight->hdev = hdev; drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight"; drvdata->kbd_backlight->cdev.max_brightness = 3; drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set; drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get; drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set; INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); spin_lock_init(&drvdata->kbd_backlight->lock); ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev); ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener); if (ret < 0) { /* No need to have this still around */ devm_kfree(&hdev->dev, drvdata->kbd_backlight); Loading Loading @@ -924,11 +903,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) drvdata->input = input; if (drvdata->enable_backlight && !asus_kbd_wmi_led_control_present(hdev) && asus_kbd_register_leds(hdev)) hid_warn(hdev, "Failed to initialize backlight.\n"); return 0; } Loading Loading @@ -1001,15 +975,6 @@ static int asus_input_mapping(struct hid_device *hdev, return -1; } /* * Check and enable backlight only on devices with UsagePage == * 0xff31 to avoid initializing the keyboard firmware multiple * times on devices with multiple HID descriptors but same * PID/VID. */ if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) drvdata->enable_backlight = true; set_bit(EV_REP, hi->input->evbit); return 1; } Loading Loading @@ -1102,7 +1067,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) { if (drvdata->kbd_backlight) { const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, drvdata->kbd_backlight->cdev.brightness }; drvdata->kbd_backlight->brightness }; ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); if (ret < 0) { hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); Loading @@ -1126,8 +1091,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev) static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; struct hid_report_enum *rep_enum; struct asus_drvdata *drvdata; struct hid_report *rep; bool is_vendor = false; int ret; drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); if (drvdata == NULL) { Loading Loading @@ -1211,12 +1179,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } /* Check for vendor for RGB init and handle generic devices properly. */ rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; list_for_each_entry(rep, &rep_enum->report_list, list) { if ((rep->application & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR) is_vendor = true; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "Asus hw start failed: %d\n", ret); return ret; } if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) && asus_kbd_register_leds(hdev)) hid_warn(hdev, "Failed to initialize backlight.\n"); /* * For ROG keyboards, skip rename for consistency and ->input check as * some devices do not have inputs. */ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) return 0; /* * Check that input registration succeeded. Checking that * HID_CLAIMED_INPUT is set prevents a UAF when all input devices Loading Loading @@ -1253,6 +1239,8 @@ static void asus_remove(struct hid_device *hdev) unsigned long flags; if (drvdata->kbd_backlight) { asus_hid_unregister_listener(&drvdata->kbd_backlight->listener); spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags); drvdata->kbd_backlight->removed = true; spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags); Loading Loading @@ -1384,10 +1372,10 @@ static const struct hid_device_id asus_devices[] = { QUIRK_USE_KBD_BACKLIGHT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, Loading drivers/platform/x86/asus-wmi.c +196 −27 Original line number Diff line number Diff line Loading @@ -31,13 +31,13 @@ #include <linux/pci.h> #include <linux/pci_hotplug.h> #include <linux/platform_data/x86/asus-wmi.h> #include <linux/platform_data/x86/asus-wmi-leds-ids.h> #include <linux/platform_device.h> #include <linux/platform_profile.h> #include <linux/power_supply.h> #include <linux/rfkill.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/types.h> #include <linux/units.h> Loading Loading @@ -256,6 +256,9 @@ struct asus_wmi { int tpd_led_wk; struct led_classdev kbd_led; int kbd_led_wk; bool kbd_led_notify; bool kbd_led_avail; bool kbd_led_registered; struct led_classdev lightbar_led; int lightbar_led_wk; struct led_classdev micmute_led; Loading @@ -264,6 +267,7 @@ struct asus_wmi { struct work_struct tpd_led_work; struct work_struct wlan_led_work; struct work_struct lightbar_led_work; struct work_struct kbd_led_work; struct asus_rfkill wlan; struct asus_rfkill bluetooth; Loading Loading @@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus) /* LEDs ***********************************************************************/ struct asus_hid_ref { struct list_head listeners; struct asus_wmi *asus; /* Protects concurrent access from hid-asus and asus-wmi to leds */ spinlock_t lock; }; static struct asus_hid_ref asus_ref = { .listeners = LIST_HEAD_INIT(asus_ref.listeners), .asus = NULL, /* * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other * asus variables are read-only after .asus is set. * * The led cdev device is not protected because it calls backlight_get * during initialization, which would result in a nested lock attempt. * * The led cdev is safe to access without a lock because if * kbd_led_avail is true it is initialized before .asus is set and never * changed until .asus is dropped. If kbd_led_avail is false, the led * cdev is registered by the workqueue, which is single-threaded and * cancelled before asus-wmi would access the led cdev to unregister it. * * A spinlock is used, because the protected variables can be accessed * from an IRQ context from asus-hid. */ .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock), }; /* * Allows registering hid-asus listeners that want to be notified of * keyboard backlight changes. */ int asus_hid_register_listener(struct asus_hid_listener *bdev) { struct asus_wmi *asus; guard(spinlock_irqsave)(&asus_ref.lock); list_add_tail(&bdev->list, &asus_ref.listeners); asus = asus_ref.asus; if (asus) queue_work(asus->led_workqueue, &asus->kbd_led_work); return 0; } EXPORT_SYMBOL_GPL(asus_hid_register_listener); /* * Allows unregistering hid-asus listeners that were added with * asus_hid_register_listener(). */ void asus_hid_unregister_listener(struct asus_hid_listener *bdev) { guard(spinlock_irqsave)(&asus_ref.lock); list_del(&bdev->list); } EXPORT_SYMBOL_GPL(asus_hid_unregister_listener); static void do_kbd_led_set(struct led_classdev *led_cdev, int value); static void kbd_led_update_all(struct work_struct *work) { struct asus_wmi *asus; bool registered, notify; int ret, value; asus = container_of(work, struct asus_wmi, kbd_led_work); scoped_guard(spinlock_irqsave, &asus_ref.lock) { registered = asus->kbd_led_registered; value = asus->kbd_led_wk; notify = asus->kbd_led_notify; } if (!registered) { /* * This workqueue runs under asus-wmi, which means probe has * completed and asus-wmi will keep running until it finishes. * Therefore, we can safely register the LED without holding * a spinlock. */ ret = devm_led_classdev_register(&asus->platform_device->dev, &asus->kbd_led); if (!ret) { scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_registered = true; } else { pr_warn("Failed to register keyboard backlight LED: %d\n", ret); return; } } if (value >= 0) do_kbd_led_set(&asus->kbd_led, value); if (notify) { scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_notify = false; led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value); } } /* * This function is called from hid-asus to inform asus-wmi of brightness * changes initiated by the keyboard backlight keys. */ int asus_hid_event(enum asus_hid_event event) { struct asus_wmi *asus; int brightness; guard(spinlock_irqsave)(&asus_ref.lock); asus = asus_ref.asus; if (!asus || !asus->kbd_led_registered) return -EBUSY; brightness = asus->kbd_led_wk; switch (event) { case ASUS_EV_BRTUP: brightness += 1; break; case ASUS_EV_BRTDOWN: brightness -= 1; break; case ASUS_EV_BRTTOGGLE: if (brightness >= ASUS_EV_MAX_BRIGHTNESS) brightness = 0; else brightness += 1; break; } asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS); asus->kbd_led_notify = true; queue_work(asus->led_workqueue, &asus->kbd_led_work); return 0; } EXPORT_SYMBOL_GPL(asus_hid_event); /* * These functions actually update the LED's, and are called from a * workqueue. By doing this as separate work rather than when the LED Loading Loading @@ -1661,6 +1803,7 @@ static void kbd_led_update(struct asus_wmi *asus) { int ctrl_param = 0; scoped_guard(spinlock_irqsave, &asus_ref.lock) ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL); } Loading Loading @@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) static void do_kbd_led_set(struct led_classdev *led_cdev, int value) { struct asus_hid_listener *listener; struct asus_wmi *asus; int max_level; asus = container_of(led_cdev, struct asus_wmi, kbd_led); max_level = asus->kbd_led.max_brightness; asus->kbd_led_wk = clamp_val(value, 0, max_level); scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS); if (asus->kbd_led_avail) kbd_led_update(asus); scoped_guard(spinlock_irqsave, &asus_ref.lock) { list_for_each_entry(listener, &asus_ref.listeners, list) listener->brightness_set(listener, asus->kbd_led_wk); } } static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) Loading @@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value) { struct led_classdev *led_cdev = &asus->kbd_led; do_kbd_led_set(led_cdev, value); led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk); scoped_guard(spinlock_irqsave, &asus_ref.lock) { asus->kbd_led_wk = value; asus->kbd_led_notify = true; } queue_work(asus->led_workqueue, &asus->kbd_led_work); } static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) Loading @@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) asus = container_of(led_cdev, struct asus_wmi, kbd_led); scoped_guard(spinlock_irqsave, &asus_ref.lock) { if (!asus->kbd_led_avail) return asus->kbd_led_wk; } retval = kbd_led_read(asus, &value, NULL); if (retval < 0) return retval; scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_wk = value; return value; } Loading Loading @@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev, static void asus_wmi_led_exit(struct asus_wmi *asus) { led_classdev_unregister(&asus->kbd_led); scoped_guard(spinlock_irqsave, &asus_ref.lock) asus_ref.asus = NULL; led_classdev_unregister(&asus->tpd_led); led_classdev_unregister(&asus->wlan_led); led_classdev_unregister(&asus->lightbar_led); Loading Loading @@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) { pr_info("using asus-wmi for asus::kbd_backlight\n"); asus->kbd_led_wk = led_val; asus->kbd_led.name = "asus::kbd_backlight"; asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; asus->kbd_led.brightness_set_blocking = kbd_led_set; asus->kbd_led.brightness_get = kbd_led_get; asus->kbd_led.max_brightness = 3; asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS; asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL); INIT_WORK(&asus->kbd_led_work, kbd_led_update_all); if (asus->kbd_led_avail) { asus->kbd_led_wk = led_val; if (num_rgb_groups != 0) asus->kbd_led.groups = kbd_rgb_mode_groups; } else { asus->kbd_led_wk = -1; } rv = led_classdev_register(&asus->platform_device->dev, &asus->kbd_led); if (rv) goto error; scoped_guard(spinlock_irqsave, &asus_ref.lock) { asus_ref.asus = asus; if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners)) queue_work(asus->led_workqueue, &asus->kbd_led_work); } if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED) Loading Loading @@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj) static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) { enum led_brightness led_value; unsigned int key_value = 1; bool autorelease = 1; Loading @@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) return; } scoped_guard(spinlock_irqsave, &asus_ref.lock) led_value = asus->kbd_led_wk; if (code == NOTIFY_KBD_BRTUP) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); kbd_led_set_by_kbd(asus, led_value + 1); return; } if (code == NOTIFY_KBD_BRTDWN) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1); kbd_led_set_by_kbd(asus, led_value - 1); return; } if (code == NOTIFY_KBD_BRTTOGGLE) { if (asus->kbd_led_wk == asus->kbd_led.max_brightness) if (led_value >= ASUS_EV_MAX_BRIGHTNESS) kbd_led_set_by_kbd(asus, 0); else kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); kbd_led_set_by_kbd(asus, led_value + 1); return; } Loading include/linux/platform_data/x86/asus-wmi-leds-ids.hdeleted 100644 → 0 +0 −50 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0 */ #ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H #define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H #include <linux/dmi.h> #include <linux/types.h> /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */ #if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS) static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = { { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"), }, }, { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"), }, }, { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"), }, }, { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"), }, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA403U"), }, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GU605M"), }, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "RC71L"), }, }, { }, }; #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */ include/linux/platform_data/x86/asus-wmi.h +28 −0 Original line number Diff line number Diff line Loading @@ -172,12 +172,29 @@ enum asus_ally_mcu_hack { ASUS_WMI_ALLY_MCU_HACK_DISABLED, }; /* Used to notify hid-asus when asus-wmi changes keyboard backlight */ struct asus_hid_listener { struct list_head list; void (*brightness_set)(struct asus_hid_listener *listener, int brightness); }; enum asus_hid_event { ASUS_EV_BRTUP, ASUS_EV_BRTDOWN, ASUS_EV_BRTTOGGLE, }; #define ASUS_EV_MAX_BRIGHTNESS 3 #if IS_REACHABLE(CONFIG_ASUS_WMI) void set_ally_mcu_hack(enum asus_ally_mcu_hack status); void set_ally_mcu_powersave(bool enabled); int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); int asus_hid_register_listener(struct asus_hid_listener *cdev); void asus_hid_unregister_listener(struct asus_hid_listener *cdev); int asus_hid_event(enum asus_hid_event event); #else static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) { Loading @@ -198,6 +215,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, { return -ENODEV; } static inline int asus_hid_register_listener(struct asus_hid_listener *bdev) { return -ENODEV; } static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev) { } static inline int asus_hid_event(enum asus_hid_event event) { return -ENODEV; } #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */ Loading
drivers/hid/hid-asus.c +101 −113 Original line number Diff line number Diff line Loading @@ -27,7 +27,6 @@ #include <linux/hid.h> #include <linux/module.h> #include <linux/platform_data/x86/asus-wmi.h> #include <linux/platform_data/x86/asus-wmi-leds-ids.h> #include <linux/input/mt.h> #include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */ #include <linux/power_supply.h> Loading @@ -48,8 +47,9 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define T100CHI_MOUSE_REPORT_ID 0x06 #define FEATURE_REPORT_ID 0x0d #define INPUT_REPORT_ID 0x5d #define HID_USAGE_PAGE_VENDOR 0xff310000 #define FEATURE_KBD_REPORT_ID 0x5a #define FEATURE_KBD_REPORT_SIZE 16 #define FEATURE_KBD_REPORT_SIZE 64 #define FEATURE_KBD_LED_REPORT_ID1 0x5d #define FEATURE_KBD_LED_REPORT_ID2 0x5e Loading Loading @@ -90,6 +90,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define QUIRK_ROG_NKEY_KEYBOARD BIT(11) #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12) #define QUIRK_ROG_ALLY_XPAD BIT(13) #define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(14) #define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \ QUIRK_NO_INIT_REPORTS | \ Loading @@ -101,7 +102,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); #define TRKID_SGN ((TRKID_MAX + 1) >> 1) struct asus_kbd_leds { struct led_classdev cdev; struct asus_hid_listener listener; struct hid_device *hdev; struct work_struct work; unsigned int brightness; Loading @@ -126,7 +127,6 @@ struct asus_drvdata { struct input_dev *tp_kbd_input; struct asus_kbd_leds *kbd_backlight; const struct asus_touchpad_info *tp; bool enable_backlight; struct power_supply *battery; struct power_supply_desc battery_desc; int battery_capacity; Loading Loading @@ -317,13 +317,24 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size) static int asus_event(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 && if ((usage->hid & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR && (usage->hid & HID_USAGE) != 0x00 && (usage->hid & HID_USAGE) != 0xff && !usage->type) { hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n", usage->hid & HID_USAGE); } if (usage->type == EV_KEY && value) { switch (usage->code) { case KEY_KBDILLUMUP: return !asus_hid_event(ASUS_EV_BRTUP); case KEY_KBDILLUMDOWN: return !asus_hid_event(ASUS_EV_BRTDOWN); case KEY_KBDILLUMTOGGLE: return !asus_hid_event(ASUS_EV_BRTTOGGLE); } } return 0; } Loading Loading @@ -394,17 +405,43 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu static int asus_kbd_init(struct hid_device *hdev, u8 report_id) { /* * The handshake is first sent as a set_report, then retrieved * from a get_report. They should be equal. */ const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 }; int ret; ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); if (ret < 0) hid_err(hdev, "Asus failed to send init command: %d\n", ret); if (ret < 0) { hid_err(hdev, "Asus handshake %02x failed to send: %d\n", report_id, ret); return ret; } u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL); if (!readbuf) return -ENOMEM; ret = hid_hw_raw_request(hdev, report_id, readbuf, FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) { hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n", report_id, ret); } else if (memcmp(readbuf, buf, sizeof(buf)) != 0) { hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n", report_id, FEATURE_KBD_REPORT_SIZE, readbuf); } /* * Do not return error if handshake is wrong until this is * verified to work for all devices. */ return 0; } static int asus_kbd_get_functions(struct hid_device *hdev, unsigned char *kbd_func, u8 report_id) Loading @@ -423,7 +460,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev, if (!readbuf) return -ENOMEM; ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf, ret = hid_hw_raw_request(hdev, report_id, readbuf, FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) { Loading Loading @@ -468,11 +505,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led) spin_unlock_irqrestore(&led->lock, flags); } static void asus_kbd_backlight_set(struct led_classdev *led_cdev, enum led_brightness brightness) static void asus_kbd_backlight_set(struct asus_hid_listener *listener, int brightness) { struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, cdev); struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds, listener); unsigned long flags; spin_lock_irqsave(&led->lock, flags); Loading @@ -482,20 +519,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev, asus_schedule_work(led); } static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev) { struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, cdev); enum led_brightness brightness; unsigned long flags; spin_lock_irqsave(&led->lock, flags); brightness = led->brightness; spin_unlock_irqrestore(&led->lock, flags); return brightness; } static void asus_kbd_backlight_work(struct work_struct *work) { struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); Loading @@ -512,34 +535,6 @@ static void asus_kbd_backlight_work(struct work_struct *work) hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret); } /* WMI-based keyboard backlight LED control (via asus-wmi driver) takes * precedence. We only activate HID-based backlight control when the * WMI control is not available. */ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); u32 value; int ret; if (!IS_ENABLED(CONFIG_ASUS_WMI)) return false; if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD && dmi_check_system(asus_use_hid_led_dmi_ids)) { hid_info(hdev, "using HID for asus::kbd_backlight\n"); return false; } ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value); hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value); if (ret) return false; return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT); } /* * We don't care about any other part of the string except the version section. * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01 Loading Loading @@ -639,20 +634,23 @@ static int asus_kbd_register_leds(struct hid_device *hdev) unsigned char kbd_func; int ret; if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) { /* Initialize keyboard */ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; /* The LED endpoint is initialised in two HID */ ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); /* Get keyboard functions */ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); if (ret < 0) return ret; /* Check for backlight support */ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) return -ENODEV; if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) { asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1); asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2); } if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) { ret = asus_kbd_disable_oobe(hdev); Loading @@ -667,22 +665,6 @@ static int asus_kbd_register_leds(struct hid_device *hdev) le16_to_cpu(udev->descriptor.idProduct)); } } else { /* Initialize keyboard */ ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; /* Get keyboard functions */ ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID); if (ret < 0) return ret; /* Check for backlight support */ if (!(kbd_func & SUPPORT_KBD_BACKLIGHT)) return -ENODEV; } drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, sizeof(struct asus_kbd_leds), GFP_KERNEL); Loading @@ -692,14 +674,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev) drvdata->kbd_backlight->removed = false; drvdata->kbd_backlight->brightness = 0; drvdata->kbd_backlight->hdev = hdev; drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight"; drvdata->kbd_backlight->cdev.max_brightness = 3; drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set; drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get; drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set; INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); spin_lock_init(&drvdata->kbd_backlight->lock); ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev); ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener); if (ret < 0) { /* No need to have this still around */ devm_kfree(&hdev->dev, drvdata->kbd_backlight); Loading Loading @@ -924,11 +903,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi) drvdata->input = input; if (drvdata->enable_backlight && !asus_kbd_wmi_led_control_present(hdev) && asus_kbd_register_leds(hdev)) hid_warn(hdev, "Failed to initialize backlight.\n"); return 0; } Loading Loading @@ -1001,15 +975,6 @@ static int asus_input_mapping(struct hid_device *hdev, return -1; } /* * Check and enable backlight only on devices with UsagePage == * 0xff31 to avoid initializing the keyboard firmware multiple * times on devices with multiple HID descriptors but same * PID/VID. */ if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) drvdata->enable_backlight = true; set_bit(EV_REP, hi->input->evbit); return 1; } Loading Loading @@ -1102,7 +1067,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) { if (drvdata->kbd_backlight) { const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, drvdata->kbd_backlight->cdev.brightness }; drvdata->kbd_backlight->brightness }; ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); if (ret < 0) { hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); Loading @@ -1126,8 +1091,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev) static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) { int ret; struct hid_report_enum *rep_enum; struct asus_drvdata *drvdata; struct hid_report *rep; bool is_vendor = false; int ret; drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); if (drvdata == NULL) { Loading Loading @@ -1211,12 +1179,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } /* Check for vendor for RGB init and handle generic devices properly. */ rep_enum = &hdev->report_enum[HID_INPUT_REPORT]; list_for_each_entry(rep, &rep_enum->report_list, list) { if ((rep->application & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR) is_vendor = true; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "Asus hw start failed: %d\n", ret); return ret; } if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) && asus_kbd_register_leds(hdev)) hid_warn(hdev, "Failed to initialize backlight.\n"); /* * For ROG keyboards, skip rename for consistency and ->input check as * some devices do not have inputs. */ if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) return 0; /* * Check that input registration succeeded. Checking that * HID_CLAIMED_INPUT is set prevents a UAF when all input devices Loading Loading @@ -1253,6 +1239,8 @@ static void asus_remove(struct hid_device *hdev) unsigned long flags; if (drvdata->kbd_backlight) { asus_hid_unregister_listener(&drvdata->kbd_backlight->listener); spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags); drvdata->kbd_backlight->removed = true; spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags); Loading Loading @@ -1384,10 +1372,10 @@ static const struct hid_device_id asus_devices[] = { QUIRK_USE_KBD_BACKLIGHT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR), QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, Loading
drivers/platform/x86/asus-wmi.c +196 −27 Original line number Diff line number Diff line Loading @@ -31,13 +31,13 @@ #include <linux/pci.h> #include <linux/pci_hotplug.h> #include <linux/platform_data/x86/asus-wmi.h> #include <linux/platform_data/x86/asus-wmi-leds-ids.h> #include <linux/platform_device.h> #include <linux/platform_profile.h> #include <linux/power_supply.h> #include <linux/rfkill.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/types.h> #include <linux/units.h> Loading Loading @@ -256,6 +256,9 @@ struct asus_wmi { int tpd_led_wk; struct led_classdev kbd_led; int kbd_led_wk; bool kbd_led_notify; bool kbd_led_avail; bool kbd_led_registered; struct led_classdev lightbar_led; int lightbar_led_wk; struct led_classdev micmute_led; Loading @@ -264,6 +267,7 @@ struct asus_wmi { struct work_struct tpd_led_work; struct work_struct wlan_led_work; struct work_struct lightbar_led_work; struct work_struct kbd_led_work; struct asus_rfkill wlan; struct asus_rfkill bluetooth; Loading Loading @@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus) /* LEDs ***********************************************************************/ struct asus_hid_ref { struct list_head listeners; struct asus_wmi *asus; /* Protects concurrent access from hid-asus and asus-wmi to leds */ spinlock_t lock; }; static struct asus_hid_ref asus_ref = { .listeners = LIST_HEAD_INIT(asus_ref.listeners), .asus = NULL, /* * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other * asus variables are read-only after .asus is set. * * The led cdev device is not protected because it calls backlight_get * during initialization, which would result in a nested lock attempt. * * The led cdev is safe to access without a lock because if * kbd_led_avail is true it is initialized before .asus is set and never * changed until .asus is dropped. If kbd_led_avail is false, the led * cdev is registered by the workqueue, which is single-threaded and * cancelled before asus-wmi would access the led cdev to unregister it. * * A spinlock is used, because the protected variables can be accessed * from an IRQ context from asus-hid. */ .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock), }; /* * Allows registering hid-asus listeners that want to be notified of * keyboard backlight changes. */ int asus_hid_register_listener(struct asus_hid_listener *bdev) { struct asus_wmi *asus; guard(spinlock_irqsave)(&asus_ref.lock); list_add_tail(&bdev->list, &asus_ref.listeners); asus = asus_ref.asus; if (asus) queue_work(asus->led_workqueue, &asus->kbd_led_work); return 0; } EXPORT_SYMBOL_GPL(asus_hid_register_listener); /* * Allows unregistering hid-asus listeners that were added with * asus_hid_register_listener(). */ void asus_hid_unregister_listener(struct asus_hid_listener *bdev) { guard(spinlock_irqsave)(&asus_ref.lock); list_del(&bdev->list); } EXPORT_SYMBOL_GPL(asus_hid_unregister_listener); static void do_kbd_led_set(struct led_classdev *led_cdev, int value); static void kbd_led_update_all(struct work_struct *work) { struct asus_wmi *asus; bool registered, notify; int ret, value; asus = container_of(work, struct asus_wmi, kbd_led_work); scoped_guard(spinlock_irqsave, &asus_ref.lock) { registered = asus->kbd_led_registered; value = asus->kbd_led_wk; notify = asus->kbd_led_notify; } if (!registered) { /* * This workqueue runs under asus-wmi, which means probe has * completed and asus-wmi will keep running until it finishes. * Therefore, we can safely register the LED without holding * a spinlock. */ ret = devm_led_classdev_register(&asus->platform_device->dev, &asus->kbd_led); if (!ret) { scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_registered = true; } else { pr_warn("Failed to register keyboard backlight LED: %d\n", ret); return; } } if (value >= 0) do_kbd_led_set(&asus->kbd_led, value); if (notify) { scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_notify = false; led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value); } } /* * This function is called from hid-asus to inform asus-wmi of brightness * changes initiated by the keyboard backlight keys. */ int asus_hid_event(enum asus_hid_event event) { struct asus_wmi *asus; int brightness; guard(spinlock_irqsave)(&asus_ref.lock); asus = asus_ref.asus; if (!asus || !asus->kbd_led_registered) return -EBUSY; brightness = asus->kbd_led_wk; switch (event) { case ASUS_EV_BRTUP: brightness += 1; break; case ASUS_EV_BRTDOWN: brightness -= 1; break; case ASUS_EV_BRTTOGGLE: if (brightness >= ASUS_EV_MAX_BRIGHTNESS) brightness = 0; else brightness += 1; break; } asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS); asus->kbd_led_notify = true; queue_work(asus->led_workqueue, &asus->kbd_led_work); return 0; } EXPORT_SYMBOL_GPL(asus_hid_event); /* * These functions actually update the LED's, and are called from a * workqueue. By doing this as separate work rather than when the LED Loading Loading @@ -1661,6 +1803,7 @@ static void kbd_led_update(struct asus_wmi *asus) { int ctrl_param = 0; scoped_guard(spinlock_irqsave, &asus_ref.lock) ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL); } Loading Loading @@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) static void do_kbd_led_set(struct led_classdev *led_cdev, int value) { struct asus_hid_listener *listener; struct asus_wmi *asus; int max_level; asus = container_of(led_cdev, struct asus_wmi, kbd_led); max_level = asus->kbd_led.max_brightness; asus->kbd_led_wk = clamp_val(value, 0, max_level); scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS); if (asus->kbd_led_avail) kbd_led_update(asus); scoped_guard(spinlock_irqsave, &asus_ref.lock) { list_for_each_entry(listener, &asus_ref.listeners, list) listener->brightness_set(listener, asus->kbd_led_wk); } } static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) Loading @@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value) { struct led_classdev *led_cdev = &asus->kbd_led; do_kbd_led_set(led_cdev, value); led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk); scoped_guard(spinlock_irqsave, &asus_ref.lock) { asus->kbd_led_wk = value; asus->kbd_led_notify = true; } queue_work(asus->led_workqueue, &asus->kbd_led_work); } static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) Loading @@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) asus = container_of(led_cdev, struct asus_wmi, kbd_led); scoped_guard(spinlock_irqsave, &asus_ref.lock) { if (!asus->kbd_led_avail) return asus->kbd_led_wk; } retval = kbd_led_read(asus, &value, NULL); if (retval < 0) return retval; scoped_guard(spinlock_irqsave, &asus_ref.lock) asus->kbd_led_wk = value; return value; } Loading Loading @@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev, static void asus_wmi_led_exit(struct asus_wmi *asus) { led_classdev_unregister(&asus->kbd_led); scoped_guard(spinlock_irqsave, &asus_ref.lock) asus_ref.asus = NULL; led_classdev_unregister(&asus->tpd_led); led_classdev_unregister(&asus->wlan_led); led_classdev_unregister(&asus->lightbar_led); Loading Loading @@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) { pr_info("using asus-wmi for asus::kbd_backlight\n"); asus->kbd_led_wk = led_val; asus->kbd_led.name = "asus::kbd_backlight"; asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; asus->kbd_led.brightness_set_blocking = kbd_led_set; asus->kbd_led.brightness_get = kbd_led_get; asus->kbd_led.max_brightness = 3; asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS; asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL); INIT_WORK(&asus->kbd_led_work, kbd_led_update_all); if (asus->kbd_led_avail) { asus->kbd_led_wk = led_val; if (num_rgb_groups != 0) asus->kbd_led.groups = kbd_rgb_mode_groups; } else { asus->kbd_led_wk = -1; } rv = led_classdev_register(&asus->platform_device->dev, &asus->kbd_led); if (rv) goto error; scoped_guard(spinlock_irqsave, &asus_ref.lock) { asus_ref.asus = asus; if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners)) queue_work(asus->led_workqueue, &asus->kbd_led_work); } if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED) Loading Loading @@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj) static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) { enum led_brightness led_value; unsigned int key_value = 1; bool autorelease = 1; Loading @@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) return; } scoped_guard(spinlock_irqsave, &asus_ref.lock) led_value = asus->kbd_led_wk; if (code == NOTIFY_KBD_BRTUP) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); kbd_led_set_by_kbd(asus, led_value + 1); return; } if (code == NOTIFY_KBD_BRTDWN) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1); kbd_led_set_by_kbd(asus, led_value - 1); return; } if (code == NOTIFY_KBD_BRTTOGGLE) { if (asus->kbd_led_wk == asus->kbd_led.max_brightness) if (led_value >= ASUS_EV_MAX_BRIGHTNESS) kbd_led_set_by_kbd(asus, 0); else kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); kbd_led_set_by_kbd(asus, led_value + 1); return; } Loading
include/linux/platform_data/x86/asus-wmi-leds-ids.hdeleted 100644 → 0 +0 −50 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0 */ #ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H #define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H #include <linux/dmi.h> #include <linux/types.h> /* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */ #if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS) static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = { { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"), }, }, { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"), }, }, { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"), }, }, { .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"), }, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GA403U"), }, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "GU605M"), }, }, { .matches = { DMI_MATCH(DMI_BOARD_NAME, "RC71L"), }, }, { }, }; #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */
include/linux/platform_data/x86/asus-wmi.h +28 −0 Original line number Diff line number Diff line Loading @@ -172,12 +172,29 @@ enum asus_ally_mcu_hack { ASUS_WMI_ALLY_MCU_HACK_DISABLED, }; /* Used to notify hid-asus when asus-wmi changes keyboard backlight */ struct asus_hid_listener { struct list_head list; void (*brightness_set)(struct asus_hid_listener *listener, int brightness); }; enum asus_hid_event { ASUS_EV_BRTUP, ASUS_EV_BRTDOWN, ASUS_EV_BRTTOGGLE, }; #define ASUS_EV_MAX_BRIGHTNESS 3 #if IS_REACHABLE(CONFIG_ASUS_WMI) void set_ally_mcu_hack(enum asus_ally_mcu_hack status); void set_ally_mcu_powersave(bool enabled); int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); int asus_hid_register_listener(struct asus_hid_listener *cdev); void asus_hid_unregister_listener(struct asus_hid_listener *cdev); int asus_hid_event(enum asus_hid_event event); #else static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) { Loading @@ -198,6 +215,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, { return -ENODEV; } static inline int asus_hid_register_listener(struct asus_hid_listener *bdev) { return -ENODEV; } static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev) { } static inline int asus_hid_event(enum asus_hid_event event) { return -ENODEV; } #endif #endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */