Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: asus-wmi: Add support for multiple kbd led handlers

Some devices, such as the Z13 have multiple Aura devices connected
to them by USB. In addition, they might have a WMI interface for
RGB. In Windows, Armoury Crate exposes a unified brightness slider
for all of them, with 3 brightness levels.

Therefore, to be synergistic in Linux, and support existing tooling
such as UPower, allow adding listeners to the RGB device of the WMI
interface. If WMI does not exist, lazy initialize the interface.

Since hid-asus and asus-wmi can both interact with the led objects
including from an atomic context, protect the brightness access with a
spinlock and update the values from a workqueue. Use this workqueue to
also process WMI keyboard events, so they are handled asynchronously.

Acked-by: Benjamin Tissoires <bentiss@kernel.org>
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
Reviewed-by: Denis Benato <benato.denis96@gmail.com>
Link: https://patch.msgid.link/20260122075044.5070-8-lkml@antheas.dev
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Antheas Kapenekakis and committed by
Ilpo Järvinen
fac55d29 4ac74ea6

+173 -25
+158 -25
drivers/platform/x86/asus-wmi.c
··· 31 31 #include <linux/pci.h> 32 32 #include <linux/pci_hotplug.h> 33 33 #include <linux/platform_data/x86/asus-wmi.h> 34 - #include <linux/platform_data/x86/asus-wmi-leds-ids.h> 35 34 #include <linux/platform_device.h> 36 35 #include <linux/platform_profile.h> 37 36 #include <linux/power_supply.h> 38 37 #include <linux/rfkill.h> 39 38 #include <linux/seq_file.h> 40 39 #include <linux/slab.h> 40 + #include <linux/spinlock.h> 41 41 #include <linux/types.h> 42 42 #include <linux/units.h> 43 43 ··· 256 256 int tpd_led_wk; 257 257 struct led_classdev kbd_led; 258 258 int kbd_led_wk; 259 + bool kbd_led_notify; 260 + bool kbd_led_avail; 261 + bool kbd_led_registered; 259 262 struct led_classdev lightbar_led; 260 263 int lightbar_led_wk; 261 264 struct led_classdev micmute_led; ··· 267 264 struct work_struct tpd_led_work; 268 265 struct work_struct wlan_led_work; 269 266 struct work_struct lightbar_led_work; 267 + struct work_struct kbd_led_work; 270 268 271 269 struct asus_rfkill wlan; 272 270 struct asus_rfkill bluetooth; ··· 1619 1615 1620 1616 /* LEDs ***********************************************************************/ 1621 1617 1618 + struct asus_hid_ref { 1619 + struct list_head listeners; 1620 + struct asus_wmi *asus; 1621 + /* Protects concurrent access from hid-asus and asus-wmi to leds */ 1622 + spinlock_t lock; 1623 + }; 1624 + 1625 + static struct asus_hid_ref asus_ref = { 1626 + .listeners = LIST_HEAD_INIT(asus_ref.listeners), 1627 + .asus = NULL, 1628 + /* 1629 + * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other 1630 + * asus variables are read-only after .asus is set. 1631 + * 1632 + * The led cdev device is not protected because it calls backlight_get 1633 + * during initialization, which would result in a nested lock attempt. 1634 + * 1635 + * The led cdev is safe to access without a lock because if 1636 + * kbd_led_avail is true it is initialized before .asus is set and never 1637 + * changed until .asus is dropped. If kbd_led_avail is false, the led 1638 + * cdev is registered by the workqueue, which is single-threaded and 1639 + * cancelled before asus-wmi would access the led cdev to unregister it. 1640 + * 1641 + * A spinlock is used, because the protected variables can be accessed 1642 + * from an IRQ context from asus-hid. 1643 + */ 1644 + .lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock), 1645 + }; 1646 + 1647 + /* 1648 + * Allows registering hid-asus listeners that want to be notified of 1649 + * keyboard backlight changes. 1650 + */ 1651 + int asus_hid_register_listener(struct asus_hid_listener *bdev) 1652 + { 1653 + struct asus_wmi *asus; 1654 + 1655 + guard(spinlock_irqsave)(&asus_ref.lock); 1656 + list_add_tail(&bdev->list, &asus_ref.listeners); 1657 + asus = asus_ref.asus; 1658 + if (asus) 1659 + queue_work(asus->led_workqueue, &asus->kbd_led_work); 1660 + return 0; 1661 + } 1662 + EXPORT_SYMBOL_GPL(asus_hid_register_listener); 1663 + 1664 + /* 1665 + * Allows unregistering hid-asus listeners that were added with 1666 + * asus_hid_register_listener(). 1667 + */ 1668 + void asus_hid_unregister_listener(struct asus_hid_listener *bdev) 1669 + { 1670 + guard(spinlock_irqsave)(&asus_ref.lock); 1671 + list_del(&bdev->list); 1672 + } 1673 + EXPORT_SYMBOL_GPL(asus_hid_unregister_listener); 1674 + 1675 + static void do_kbd_led_set(struct led_classdev *led_cdev, int value); 1676 + 1677 + static void kbd_led_update_all(struct work_struct *work) 1678 + { 1679 + struct asus_wmi *asus; 1680 + bool registered, notify; 1681 + int ret, value; 1682 + 1683 + asus = container_of(work, struct asus_wmi, kbd_led_work); 1684 + 1685 + scoped_guard(spinlock_irqsave, &asus_ref.lock) { 1686 + registered = asus->kbd_led_registered; 1687 + value = asus->kbd_led_wk; 1688 + notify = asus->kbd_led_notify; 1689 + } 1690 + 1691 + if (!registered) { 1692 + /* 1693 + * This workqueue runs under asus-wmi, which means probe has 1694 + * completed and asus-wmi will keep running until it finishes. 1695 + * Therefore, we can safely register the LED without holding 1696 + * a spinlock. 1697 + */ 1698 + ret = devm_led_classdev_register(&asus->platform_device->dev, 1699 + &asus->kbd_led); 1700 + if (!ret) { 1701 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 1702 + asus->kbd_led_registered = true; 1703 + } else { 1704 + pr_warn("Failed to register keyboard backlight LED: %d\n", ret); 1705 + return; 1706 + } 1707 + } 1708 + 1709 + if (value >= 0) 1710 + do_kbd_led_set(&asus->kbd_led, value); 1711 + if (notify) { 1712 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 1713 + asus->kbd_led_notify = false; 1714 + led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value); 1715 + } 1716 + } 1717 + 1622 1718 /* 1623 1719 * These functions actually update the LED's, and are called from a 1624 1720 * workqueue. By doing this as separate work rather than when the LED ··· 1765 1661 { 1766 1662 int ctrl_param = 0; 1767 1663 1768 - ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); 1664 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 1665 + ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F); 1769 1666 asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL); 1770 1667 } 1771 1668 ··· 1799 1694 1800 1695 static void do_kbd_led_set(struct led_classdev *led_cdev, int value) 1801 1696 { 1697 + struct asus_hid_listener *listener; 1802 1698 struct asus_wmi *asus; 1803 1699 int max_level; 1804 1700 1805 1701 asus = container_of(led_cdev, struct asus_wmi, kbd_led); 1806 1702 max_level = asus->kbd_led.max_brightness; 1807 1703 1808 - asus->kbd_led_wk = clamp_val(value, 0, max_level); 1809 - kbd_led_update(asus); 1704 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 1705 + asus->kbd_led_wk = clamp_val(value, 0, max_level); 1706 + 1707 + if (asus->kbd_led_avail) 1708 + kbd_led_update(asus); 1709 + 1710 + scoped_guard(spinlock_irqsave, &asus_ref.lock) { 1711 + list_for_each_entry(listener, &asus_ref.listeners, list) 1712 + listener->brightness_set(listener, asus->kbd_led_wk); 1713 + } 1810 1714 } 1811 1715 1812 1716 static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) ··· 1830 1716 1831 1717 static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value) 1832 1718 { 1833 - struct led_classdev *led_cdev = &asus->kbd_led; 1834 - 1835 - do_kbd_led_set(led_cdev, value); 1836 - led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk); 1719 + scoped_guard(spinlock_irqsave, &asus_ref.lock) { 1720 + asus->kbd_led_wk = value; 1721 + asus->kbd_led_notify = true; 1722 + } 1723 + queue_work(asus->led_workqueue, &asus->kbd_led_work); 1837 1724 } 1838 1725 1839 1726 static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) ··· 1844 1729 1845 1730 asus = container_of(led_cdev, struct asus_wmi, kbd_led); 1846 1731 1732 + scoped_guard(spinlock_irqsave, &asus_ref.lock) { 1733 + if (!asus->kbd_led_avail) 1734 + return asus->kbd_led_wk; 1735 + } 1736 + 1847 1737 retval = kbd_led_read(asus, &value, NULL); 1848 1738 if (retval < 0) 1849 1739 return retval; 1740 + 1741 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 1742 + asus->kbd_led_wk = value; 1850 1743 1851 1744 return value; 1852 1745 } ··· 1967 1844 1968 1845 static void asus_wmi_led_exit(struct asus_wmi *asus) 1969 1846 { 1970 - led_classdev_unregister(&asus->kbd_led); 1847 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 1848 + asus_ref.asus = NULL; 1849 + 1971 1850 led_classdev_unregister(&asus->tpd_led); 1972 1851 led_classdev_unregister(&asus->wlan_led); 1973 1852 led_classdev_unregister(&asus->lightbar_led); ··· 2007 1882 goto error; 2008 1883 } 2009 1884 2010 - if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) { 2011 - pr_info("using asus-wmi for asus::kbd_backlight\n"); 2012 - asus->kbd_led_wk = led_val; 2013 - asus->kbd_led.name = "asus::kbd_backlight"; 2014 - asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; 2015 - asus->kbd_led.brightness_set_blocking = kbd_led_set; 2016 - asus->kbd_led.brightness_get = kbd_led_get; 2017 - asus->kbd_led.max_brightness = 3; 1885 + asus->kbd_led.name = "asus::kbd_backlight"; 1886 + asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; 1887 + asus->kbd_led.brightness_set_blocking = kbd_led_set; 1888 + asus->kbd_led.brightness_get = kbd_led_get; 1889 + asus->kbd_led.max_brightness = 3; 1890 + asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL); 1891 + INIT_WORK(&asus->kbd_led_work, kbd_led_update_all); 2018 1892 1893 + if (asus->kbd_led_avail) { 1894 + asus->kbd_led_wk = led_val; 2019 1895 if (num_rgb_groups != 0) 2020 1896 asus->kbd_led.groups = kbd_rgb_mode_groups; 1897 + } else { 1898 + asus->kbd_led_wk = -1; 1899 + } 2021 1900 2022 - rv = led_classdev_register(&asus->platform_device->dev, 2023 - &asus->kbd_led); 2024 - if (rv) 2025 - goto error; 1901 + scoped_guard(spinlock_irqsave, &asus_ref.lock) { 1902 + asus_ref.asus = asus; 1903 + if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners)) 1904 + queue_work(asus->led_workqueue, &asus->kbd_led_work); 2026 1905 } 2027 1906 2028 1907 if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED) ··· 4501 4372 4502 4373 static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) 4503 4374 { 4375 + enum led_brightness led_value; 4504 4376 unsigned int key_value = 1; 4505 4377 bool autorelease = 1; 4506 4378 ··· 4518 4388 return; 4519 4389 } 4520 4390 4391 + scoped_guard(spinlock_irqsave, &asus_ref.lock) 4392 + led_value = asus->kbd_led_wk; 4393 + 4521 4394 if (code == NOTIFY_KBD_BRTUP) { 4522 - kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); 4395 + kbd_led_set_by_kbd(asus, led_value + 1); 4523 4396 return; 4524 4397 } 4525 4398 if (code == NOTIFY_KBD_BRTDWN) { 4526 - kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1); 4399 + kbd_led_set_by_kbd(asus, led_value - 1); 4527 4400 return; 4528 4401 } 4529 4402 if (code == NOTIFY_KBD_BRTTOGGLE) { 4530 - if (asus->kbd_led_wk == asus->kbd_led.max_brightness) 4403 + if (led_value >= asus->kbd_led.max_brightness) 4531 4404 kbd_led_set_by_kbd(asus, 0); 4532 4405 else 4533 - kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); 4406 + kbd_led_set_by_kbd(asus, led_value + 1); 4534 4407 return; 4535 4408 } 4536 4409
+15
include/linux/platform_data/x86/asus-wmi.h
··· 172 172 ASUS_WMI_ALLY_MCU_HACK_DISABLED, 173 173 }; 174 174 175 + /* Used to notify hid-asus when asus-wmi changes keyboard backlight */ 176 + struct asus_hid_listener { 177 + struct list_head list; 178 + void (*brightness_set)(struct asus_hid_listener *listener, int brightness); 179 + }; 180 + 175 181 #if IS_REACHABLE(CONFIG_ASUS_WMI) 176 182 void set_ally_mcu_hack(enum asus_ally_mcu_hack status); 177 183 void set_ally_mcu_powersave(bool enabled); 178 184 int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval); 179 185 int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval); 180 186 int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval); 187 + int asus_hid_register_listener(struct asus_hid_listener *cdev); 188 + void asus_hid_unregister_listener(struct asus_hid_listener *cdev); 181 189 #else 182 190 static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status) 183 191 { ··· 205 197 u32 *retval) 206 198 { 207 199 return -ENODEV; 200 + } 201 + static inline int asus_hid_register_listener(struct asus_hid_listener *bdev) 202 + { 203 + return -ENODEV; 204 + } 205 + static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev) 206 + { 208 207 } 209 208 #endif 210 209