This permits to change which modifier triggers which keyboard LEDs, by adding
modifier triggers, and a series of LEDs for the VT.

This permits to fix #7063 from userland by using a modifier to implement proper
CapsLock behavior and have the keyboard caps lock led show that modifier state.

[[email protected]: Rebased to 3.2-rc1 or so, cleaned up some includes, and 
fixed some constants]
[[email protected]: remove unneeded `extern', fix comment layout]
Signed-off-by: Samuel Thibault <[email protected]>
Signed-off-by: Evan Broder <[email protected]>
Acked-by: Peter Korsgaard <[email protected]>
Signed-off-by: John Crispin <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Tested-by: Pavel Machek <[email protected]>
---
changes since v6: fix initialization of vt_led_work before registering the LEDs

--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -13,6 +13,9 @@ config VT
        bool "Virtual terminal" if EXPERT
        depends on !S390 && !UML
        select INPUT
+       select NEW_LEDS
+       select LEDS_CLASS
+       select LEDS_TRIGGERS
        default y
        ---help---
          If you say Y here, you will get support for terminal devices with
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -33,6 +33,7 @@
 #include <linux/string.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/leds.h>
 
 #include <linux/kbd_kern.h>
 #include <linux/kbd_diacr.h>
@@ -130,6 +131,7 @@ static char rep;                                    /* flag 
telling cha
 static int shift_state = 0;
 
 static unsigned char ledstate = 0xff;                  /* undefined */
+static unsigned char lockstate = 0xff;                 /* undefined */
 static unsigned char ledioctl;
 
 /*
@@ -962,6 +964,146 @@ static void k_brl(struct vc_data *vc, un
 }
 
 /*
+ * Keyboard LEDs are propagated by default like the following example:
+ *
+ * VT keyboard LED state or modifier state, calls kbd_bh()
+ * -> kbd-xxx VT trigger
+ * -> vt::xxx VT LED
+ * -> input EV_DEV events (in vt_led_cb)
+ *
+ * KDSETLED directly triggers vt::xxx LEDs.
+ *
+ * Userland can however choose the trigger for the vt::numl LED, or
+ * independently choose the trigger for any inputx::numl LED.
+ */
+
+/* We route VT keyboard "leds" through triggers */
+static void kbd_ledstate_trigger_activate(struct led_classdev *cdev);
+
+static struct led_trigger ledtrig_ledstate[] = {
+#define DEFINE_LEDSTATE_TRIGGER(kbd_led, nam) \
+       [kbd_led] = { \
+               .name = nam, \
+               .activate = kbd_ledstate_trigger_activate, \
+       }
+       DEFINE_LEDSTATE_TRIGGER(VC_SCROLLOCK, "kbd-scrollock"),
+       DEFINE_LEDSTATE_TRIGGER(VC_NUMLOCK,   "kbd-numlock"),
+       DEFINE_LEDSTATE_TRIGGER(VC_CAPSLOCK,  "kbd-capslock"),
+       DEFINE_LEDSTATE_TRIGGER(VC_KANALOCK,  "kbd-kanalock"),
+#undef DEFINE_LEDSTATE_TRIGGER
+};
+
+static int input_leds[] = {
+       [VC_SCROLLOCK] = LED_SCROLLL,
+       [VC_NUMLOCK] = LED_NUML,
+       [VC_CAPSLOCK] = LED_CAPSL,
+       [VC_KANALOCK] = LED_KANA,
+};
+
+/* Called on trigger connection, to set initial state */
+static void kbd_ledstate_trigger_activate(struct led_classdev *cdev)
+{
+       struct led_trigger *trigger = cdev->trigger;
+       int led = trigger - ledtrig_ledstate;
+
+       tasklet_disable(&keyboard_tasklet);
+       led_trigger_event(trigger, ledstate & (1 << led) ? LED_FULL : LED_OFF);
+       tasklet_enable(&keyboard_tasklet);
+}
+
+/* We route VT keyboard lockstates through triggers */
+static void kbd_lockstate_trigger_activate(struct led_classdev *cdev);
+
+static struct led_trigger ledtrig_lockstate[] = {
+#define DEFINE_LOCKSTATE_TRIGGER(kbd_led, nam) \
+       [kbd_led] = { \
+               .name = nam, \
+               .activate = kbd_lockstate_trigger_activate, \
+       }
+       DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTLOCK,  "kbd-shiftlock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_ALTGRLOCK,  "kbd-altgrlock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_CTRLLOCK,   "kbd-ctrllock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_ALTLOCK,    "kbd-altlock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTLLOCK, "kbd-shiftllock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTRLOCK, "kbd-shiftrlock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_CTRLLLOCK,  "kbd-ctrlllock"),
+       DEFINE_LOCKSTATE_TRIGGER(VC_CTRLRLOCK,  "kbd-ctrlrlock"),
+#undef DEFINE_LOCKSTATE_TRIGGER
+};
+
+/* Called on trigger connection, to set initial state */
+static void kbd_lockstate_trigger_activate(struct led_classdev *cdev)
+{
+       struct led_trigger *trigger = cdev->trigger;
+       int led = trigger - ledtrig_lockstate;
+
+       tasklet_disable(&keyboard_tasklet);
+       led_trigger_event(trigger, lockstate & (1 << led) ? LED_FULL : LED_OFF);
+       tasklet_enable(&keyboard_tasklet);
+}
+
+/* Handler for VT LEDs, scheduling injecting input events in a worker */
+static void vt_led_set(struct led_classdev *cdev,
+                         enum led_brightness brightness);
+static struct led_classdev vt_leds[LED_CNT] = {
+#define DEFINE_INPUT_LED(vt_led, nam, deftrig) \
+       [vt_led] = { \
+               .name = "vt::"nam, \
+               .max_brightness = 1, \
+               .brightness_set = vt_led_set, \
+               .default_trigger = deftrig, \
+       }
+/* Default triggers for the VT LEDs just correspond to the legacy
+ * usage. */
+       DEFINE_INPUT_LED(LED_NUML, "numl", "kbd-numlock"),
+       DEFINE_INPUT_LED(LED_CAPSL, "capsl", "kbd-capslock"),
+       DEFINE_INPUT_LED(LED_SCROLLL, "scrolll", "kbd-scrollock"),
+       DEFINE_INPUT_LED(LED_COMPOSE, "compose", NULL),
+       DEFINE_INPUT_LED(LED_KANA, "kana", "kbd-kanalock"),
+       DEFINE_INPUT_LED(LED_SLEEP, "sleep", NULL),
+       DEFINE_INPUT_LED(LED_SUSPEND, "suspend", NULL),
+       DEFINE_INPUT_LED(LED_MUTE, "mute", NULL),
+       DEFINE_INPUT_LED(LED_MISC, "misc", NULL),
+       DEFINE_INPUT_LED(LED_MAIL, "mail", NULL),
+       DEFINE_INPUT_LED(LED_CHARGING, "charging", NULL),
+};
+
+static struct work_struct vt_led_work[LED_CNT];
+static char vt_led_state[LED_CNT];
+
+/* Emit input events to actually update one LED */
+static int kbd_update_leds_helper(struct input_handle *handle, void *data)
+{
+       int led = *(int *)data;
+
+       if (test_bit(EV_LED, handle->dev->evbit)) {
+               input_inject_event(handle, EV_LED, led, vt_led_state[led]);
+               input_inject_event(handle, EV_SYN, SYN_REPORT, 0);
+       }
+
+       return 0;
+}
+
+/* Emit input events to actually update one LED on all keyboards */
+static void vt_led_cb(struct work_struct *work)
+{
+       int led = work - vt_led_work;
+
+       input_handler_for_each_handle(&kbd_handler, &led,
+                                     kbd_update_leds_helper);
+}
+
+/* VT LED state change, scheduling triggering input events for it */
+static void vt_led_set(struct led_classdev *cdev,
+                         enum led_brightness brightness)
+{
+       int led = cdev - vt_leds;
+
+       vt_led_state[led] = !!brightness;
+       schedule_work(&vt_led_work[led]);
+}
+
+/*
  * The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
  * or (ii) whatever pattern of lights people want to show using KDSETLED,
  * or (iii) specified bits of specified words in kernel memory.
@@ -995,20 +1137,6 @@ static inline unsigned char getleds(void
        return kb->ledflagstate;
 }
 
-static int kbd_update_leds_helper(struct input_handle *handle, void *data)
-{
-       unsigned char leds = *(unsigned char *)data;
-
-       if (test_bit(EV_LED, handle->dev->evbit)) {
-               input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 
0x01));
-               input_inject_event(handle, EV_LED, LED_NUML,    !!(leds & 
0x02));
-               input_inject_event(handle, EV_LED, LED_CAPSL,   !!(leds & 
0x04));
-               input_inject_event(handle, EV_SYN, SYN_REPORT, 0);
-       }
-
-       return 0;
-}
-
 /**
  *     vt_get_leds     -       helper for braille console
  *     @console: console to read
@@ -1085,26 +1213,39 @@ void vt_kbd_con_stop(int console)
 }
 
 /*
- * This is the tasklet that updates LED state on all keyboards
- * attached to the box. The reason we use tasklet is that we
- * need to handle the scenario when keyboard handler is not
- * registered yet but we already getting updates from the VT to
- * update led state.
+ * This is the tasklet that updates LED state of all LED triggers, which will
+ * thus update the VT LED status, and eventually submit input events.
+ * The reason we use tasklet is that we need to handle the scenario when
+ * keyboard handler is not registered yet but we already getting updates
+ * from the VT to update led state.
  */
 static void kbd_bh(unsigned long dummy)
 {
        unsigned char leds;
        unsigned long flags;
-       
+       int i;
+
        spin_lock_irqsave(&led_lock, flags);
        leds = getleds();
        spin_unlock_irqrestore(&led_lock, flags);
 
        if (leds != ledstate) {
-               input_handler_for_each_handle(&kbd_handler, &leds,
-                                             kbd_update_leds_helper);
+               for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++)
+                       if ((leds ^ ledstate) & (1 << i))
+                               led_trigger_event(&ledtrig_ledstate[i],
+                                               leds & (1 << i)
+                                               ? LED_FULL : LED_OFF);
                ledstate = leds;
        }
+
+       if (kbd->lockstate != lockstate) {
+               for (i = 0; i < ARRAY_SIZE(ledtrig_lockstate); i++)
+                       if ((kbd->lockstate ^ lockstate) & (1 << i))
+                               led_trigger_event(&ledtrig_lockstate[i],
+                                               kbd->lockstate & (1 << i)
+                                               ? LED_FULL : LED_OFF);
+               lockstate = kbd->lockstate;
+       }
 }
 
 DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);
@@ -1448,10 +1589,13 @@ static void kbd_disconnect(struct input_
  */
 static void kbd_start(struct input_handle *handle)
 {
+       int i;
+
        tasklet_disable(&keyboard_tasklet);
 
        if (ledstate != 0xff)
-               kbd_update_leds_helper(handle, &ledstate);
+               for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++)
+                       kbd_update_leds_helper(handle, &input_leds[i]);
 
        tasklet_enable(&keyboard_tasklet);
 }
@@ -1501,6 +1645,29 @@ int __init kbd_init(void)
        if (error)
                return error;
 
+       for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++) {
+               error = led_trigger_register(&ledtrig_ledstate[i]);
+               if (error)
+                       pr_err("error %d while registering trigger %s\n",
+                                       error, ledtrig_ledstate[i].name);
+       }
+
+       for (i = 0; i < ARRAY_SIZE(ledtrig_lockstate); i++) {
+               error = led_trigger_register(&ledtrig_lockstate[i]);
+               if (error)
+                       pr_err("error %d while registering trigger %s\n",
+                                       error, ledtrig_lockstate[i].name);
+       }
+
+       for (i = 0; i < LED_CNT; i++)
+               if (vt_leds[i].name) {
+                       INIT_WORK(&vt_led_work[i], vt_led_cb);
+                       error = led_classdev_register(NULL, &vt_leds[i]);
+                       if (error)
+                               pr_err("error %d while registering led %s\n",
+                                               error, vt_leds[i].name);
+               }
+
        tasklet_enable(&keyboard_tasklet);
        tasklet_schedule(&keyboard_tasklet);
 
--- a/Documentation/leds/leds-class.txt
+++ b/Documentation/leds/leds-class.txt
@@ -2,9 +2,6 @@
 LED handling under Linux
 ========================
 
-If you're reading this and thinking about keyboard leds, these are
-handled by the input subsystem and the led class is *not* needed.
-
 In its simplest form, the LED class just allows control of LEDs from
 userspace. LEDs appear in /sys/class/leds/. The maximum brightness of the
 LED is defined in max_brightness file. The brightness file will set the 
brightness
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -11,9 +11,6 @@ menuconfig NEW_LEDS
          Say Y to enable Linux LED support.  This allows control of supported
          LEDs from both userspace and optionally, by kernel events (triggers).
 
-         This is not related to standard keyboard LEDs which are controlled
-         via the input system.
-
 if NEW_LEDS
 
 config LEDS_CLASS
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to