3131#include <linux/pci.h>
3232#include <linux/pci_hotplug.h>
3333#include <linux/platform_data/x86/asus-wmi.h>
34- #include <linux/platform_data/x86/asus-wmi-leds-ids.h>
3534#include <linux/platform_device.h>
3635#include <linux/platform_profile.h>
3736#include <linux/power_supply.h>
3837#include <linux/rfkill.h>
3938#include <linux/seq_file.h>
4039#include <linux/slab.h>
40+ #include <linux/spinlock.h>
4141#include <linux/types.h>
4242#include <linux/units.h>
4343
@@ -256,6 +256,9 @@ struct asus_wmi {
256256 int tpd_led_wk ;
257257 struct led_classdev kbd_led ;
258258 int kbd_led_wk ;
259+ bool kbd_led_notify ;
260+ bool kbd_led_avail ;
261+ bool kbd_led_registered ;
259262 struct led_classdev lightbar_led ;
260263 int lightbar_led_wk ;
261264 struct led_classdev micmute_led ;
@@ -264,6 +267,7 @@ struct asus_wmi {
264267 struct work_struct tpd_led_work ;
265268 struct work_struct wlan_led_work ;
266269 struct work_struct lightbar_led_work ;
270+ struct work_struct kbd_led_work ;
267271
268272 struct asus_rfkill wlan ;
269273 struct asus_rfkill bluetooth ;
@@ -1615,6 +1619,106 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
16151619
16161620/* LEDs ***********************************************************************/
16171621
1622+ struct asus_hid_ref {
1623+ struct list_head listeners ;
1624+ struct asus_wmi * asus ;
1625+ /* Protects concurrent access from hid-asus and asus-wmi to leds */
1626+ spinlock_t lock ;
1627+ };
1628+
1629+ static struct asus_hid_ref asus_ref = {
1630+ .listeners = LIST_HEAD_INIT (asus_ref .listeners ),
1631+ .asus = NULL ,
1632+ /*
1633+ * Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
1634+ * asus variables are read-only after .asus is set.
1635+ *
1636+ * The led cdev device is not protected because it calls backlight_get
1637+ * during initialization, which would result in a nested lock attempt.
1638+ *
1639+ * The led cdev is safe to access without a lock because if
1640+ * kbd_led_avail is true it is initialized before .asus is set and never
1641+ * changed until .asus is dropped. If kbd_led_avail is false, the led
1642+ * cdev is registered by the workqueue, which is single-threaded and
1643+ * cancelled before asus-wmi would access the led cdev to unregister it.
1644+ *
1645+ * A spinlock is used, because the protected variables can be accessed
1646+ * from an IRQ context from asus-hid.
1647+ */
1648+ .lock = __SPIN_LOCK_UNLOCKED (asus_ref .lock ),
1649+ };
1650+
1651+ /*
1652+ * Allows registering hid-asus listeners that want to be notified of
1653+ * keyboard backlight changes.
1654+ */
1655+ int asus_hid_register_listener (struct asus_hid_listener * bdev )
1656+ {
1657+ struct asus_wmi * asus ;
1658+
1659+ guard (spinlock_irqsave )(& asus_ref .lock );
1660+ list_add_tail (& bdev -> list , & asus_ref .listeners );
1661+ asus = asus_ref .asus ;
1662+ if (asus )
1663+ queue_work (asus -> led_workqueue , & asus -> kbd_led_work );
1664+ return 0 ;
1665+ }
1666+ EXPORT_SYMBOL_GPL (asus_hid_register_listener );
1667+
1668+ /*
1669+ * Allows unregistering hid-asus listeners that were added with
1670+ * asus_hid_register_listener().
1671+ */
1672+ void asus_hid_unregister_listener (struct asus_hid_listener * bdev )
1673+ {
1674+ guard (spinlock_irqsave )(& asus_ref .lock );
1675+ list_del (& bdev -> list );
1676+ }
1677+ EXPORT_SYMBOL_GPL (asus_hid_unregister_listener );
1678+
1679+ static void do_kbd_led_set (struct led_classdev * led_cdev , int value );
1680+
1681+ static void kbd_led_update_all (struct work_struct * work )
1682+ {
1683+ struct asus_wmi * asus ;
1684+ bool registered , notify ;
1685+ int ret , value ;
1686+
1687+ asus = container_of (work , struct asus_wmi , kbd_led_work );
1688+
1689+ scoped_guard (spinlock_irqsave , & asus_ref .lock ) {
1690+ registered = asus -> kbd_led_registered ;
1691+ value = asus -> kbd_led_wk ;
1692+ notify = asus -> kbd_led_notify ;
1693+ }
1694+
1695+ if (!registered ) {
1696+ /*
1697+ * This workqueue runs under asus-wmi, which means probe has
1698+ * completed and asus-wmi will keep running until it finishes.
1699+ * Therefore, we can safely register the LED without holding
1700+ * a spinlock.
1701+ */
1702+ ret = devm_led_classdev_register (& asus -> platform_device -> dev ,
1703+ & asus -> kbd_led );
1704+ if (!ret ) {
1705+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
1706+ asus -> kbd_led_registered = true;
1707+ } else {
1708+ pr_warn ("Failed to register keyboard backlight LED: %d\n" , ret );
1709+ return ;
1710+ }
1711+ }
1712+
1713+ if (value >= 0 )
1714+ do_kbd_led_set (& asus -> kbd_led , value );
1715+ if (notify ) {
1716+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
1717+ asus -> kbd_led_notify = false;
1718+ led_classdev_notify_brightness_hw_changed (& asus -> kbd_led , value );
1719+ }
1720+ }
1721+
16181722/*
16191723 * These functions actually update the LED's, and are called from a
16201724 * workqueue. By doing this as separate work rather than when the LED
@@ -1661,7 +1765,8 @@ static void kbd_led_update(struct asus_wmi *asus)
16611765{
16621766 int ctrl_param = 0 ;
16631767
1664- ctrl_param = 0x80 | (asus -> kbd_led_wk & 0x7F );
1768+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
1769+ ctrl_param = 0x80 | (asus -> kbd_led_wk & 0x7F );
16651770 asus_wmi_set_devstate (ASUS_WMI_DEVID_KBD_BACKLIGHT , ctrl_param , NULL );
16661771}
16671772
@@ -1694,14 +1799,23 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
16941799
16951800static void do_kbd_led_set (struct led_classdev * led_cdev , int value )
16961801{
1802+ struct asus_hid_listener * listener ;
16971803 struct asus_wmi * asus ;
16981804 int max_level ;
16991805
17001806 asus = container_of (led_cdev , struct asus_wmi , kbd_led );
17011807 max_level = asus -> kbd_led .max_brightness ;
17021808
1703- asus -> kbd_led_wk = clamp_val (value , 0 , max_level );
1704- kbd_led_update (asus );
1809+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
1810+ asus -> kbd_led_wk = clamp_val (value , 0 , max_level );
1811+
1812+ if (asus -> kbd_led_avail )
1813+ kbd_led_update (asus );
1814+
1815+ scoped_guard (spinlock_irqsave , & asus_ref .lock ) {
1816+ list_for_each_entry (listener , & asus_ref .listeners , list )
1817+ listener -> brightness_set (listener , asus -> kbd_led_wk );
1818+ }
17051819}
17061820
17071821static int kbd_led_set (struct led_classdev * led_cdev , enum led_brightness value )
@@ -1716,10 +1830,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
17161830
17171831static void kbd_led_set_by_kbd (struct asus_wmi * asus , enum led_brightness value )
17181832{
1719- struct led_classdev * led_cdev = & asus -> kbd_led ;
1720-
1721- do_kbd_led_set (led_cdev , value );
1722- led_classdev_notify_brightness_hw_changed (led_cdev , asus -> kbd_led_wk );
1833+ scoped_guard (spinlock_irqsave , & asus_ref .lock ) {
1834+ asus -> kbd_led_wk = value ;
1835+ asus -> kbd_led_notify = true;
1836+ }
1837+ queue_work (asus -> led_workqueue , & asus -> kbd_led_work );
17231838}
17241839
17251840static enum led_brightness kbd_led_get (struct led_classdev * led_cdev )
@@ -1729,10 +1844,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
17291844
17301845 asus = container_of (led_cdev , struct asus_wmi , kbd_led );
17311846
1847+ scoped_guard (spinlock_irqsave , & asus_ref .lock ) {
1848+ if (!asus -> kbd_led_avail )
1849+ return asus -> kbd_led_wk ;
1850+ }
1851+
17321852 retval = kbd_led_read (asus , & value , NULL );
17331853 if (retval < 0 )
17341854 return retval ;
17351855
1856+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
1857+ asus -> kbd_led_wk = value ;
1858+
17361859 return value ;
17371860}
17381861
@@ -1844,7 +1967,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
18441967
18451968static void asus_wmi_led_exit (struct asus_wmi * asus )
18461969{
1847- led_classdev_unregister (& asus -> kbd_led );
1970+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
1971+ asus_ref .asus = NULL ;
1972+
18481973 led_classdev_unregister (& asus -> tpd_led );
18491974 led_classdev_unregister (& asus -> wlan_led );
18501975 led_classdev_unregister (& asus -> lightbar_led );
@@ -1882,22 +2007,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
18822007 goto error ;
18832008 }
18842009
1885- if (!kbd_led_read (asus , & led_val , NULL ) && !dmi_check_system (asus_use_hid_led_dmi_ids )) {
1886- pr_info ("using asus-wmi for asus::kbd_backlight\n" );
1887- asus -> kbd_led_wk = led_val ;
1888- asus -> kbd_led .name = "asus::kbd_backlight" ;
1889- asus -> kbd_led .flags = LED_BRIGHT_HW_CHANGED ;
1890- asus -> kbd_led .brightness_set_blocking = kbd_led_set ;
1891- asus -> kbd_led .brightness_get = kbd_led_get ;
1892- asus -> kbd_led .max_brightness = 3 ;
2010+ asus -> kbd_led .name = "asus::kbd_backlight" ;
2011+ asus -> kbd_led .flags = LED_BRIGHT_HW_CHANGED ;
2012+ asus -> kbd_led .brightness_set_blocking = kbd_led_set ;
2013+ asus -> kbd_led .brightness_get = kbd_led_get ;
2014+ asus -> kbd_led .max_brightness = 3 ;
2015+ asus -> kbd_led_avail = !kbd_led_read (asus , & led_val , NULL );
2016+ INIT_WORK (& asus -> kbd_led_work , kbd_led_update_all );
18932017
2018+ if (asus -> kbd_led_avail ) {
2019+ asus -> kbd_led_wk = led_val ;
18942020 if (num_rgb_groups != 0 )
18952021 asus -> kbd_led .groups = kbd_rgb_mode_groups ;
2022+ } else {
2023+ asus -> kbd_led_wk = -1 ;
2024+ }
18962025
1897- rv = led_classdev_register ( & asus -> platform_device -> dev ,
1898- & asus -> kbd_led ) ;
1899- if (rv )
1900- goto error ;
2026+ scoped_guard ( spinlock_irqsave , & asus_ref . lock ) {
2027+ asus_ref . asus = asus ;
2028+ if (asus -> kbd_led_avail || ! list_empty ( & asus_ref . listeners ) )
2029+ queue_work ( asus -> led_workqueue , & asus -> kbd_led_work ) ;
19012030 }
19022031
19032032 if (asus_wmi_dev_is_present (asus , ASUS_WMI_DEVID_WIRELESS_LED )
@@ -4372,6 +4501,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
43724501
43734502static void asus_wmi_handle_event_code (int code , struct asus_wmi * asus )
43744503{
4504+ enum led_brightness led_value ;
43754505 unsigned int key_value = 1 ;
43764506 bool autorelease = 1 ;
43774507
@@ -4388,19 +4518,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
43884518 return ;
43894519 }
43904520
4521+ scoped_guard (spinlock_irqsave , & asus_ref .lock )
4522+ led_value = asus -> kbd_led_wk ;
4523+
43914524 if (code == NOTIFY_KBD_BRTUP ) {
4392- kbd_led_set_by_kbd (asus , asus -> kbd_led_wk + 1 );
4525+ kbd_led_set_by_kbd (asus , led_value + 1 );
43934526 return ;
43944527 }
43954528 if (code == NOTIFY_KBD_BRTDWN ) {
4396- kbd_led_set_by_kbd (asus , asus -> kbd_led_wk - 1 );
4529+ kbd_led_set_by_kbd (asus , led_value - 1 );
43974530 return ;
43984531 }
43994532 if (code == NOTIFY_KBD_BRTTOGGLE ) {
4400- if (asus -> kbd_led_wk = = asus -> kbd_led .max_brightness )
4533+ if (led_value > = asus -> kbd_led .max_brightness )
44014534 kbd_led_set_by_kbd (asus , 0 );
44024535 else
4403- kbd_led_set_by_kbd (asus , asus -> kbd_led_wk + 1 );
4536+ kbd_led_set_by_kbd (asus , led_value + 1 );
44044537 return ;
44054538 }
44064539
0 commit comments