From patchwork Sun Mar 7 20:02:50 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Samuel Thibault X-Patchwork-Id: 83976 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.3/8.14.3) with ESMTP id o27K3JuS001714 for ; Sun, 7 Mar 2010 20:03:19 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754730Ab0CGUC7 (ORCPT ); Sun, 7 Mar 2010 15:02:59 -0500 Received: from solo.fdn.fr ([80.67.169.19]:51579 "EHLO solo.fdn.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754643Ab0CGUC6 (ORCPT ); Sun, 7 Mar 2010 15:02:58 -0500 Received: from const.ipv6 (youpi.is-a-geek.org [80.67.176.89]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by solo.fdn.fr (Postfix) with ESMTP id 687D044621; Sun, 7 Mar 2010 21:02:51 +0100 (CET) Received: from samy by const.ipv6 with local (Exim 4.71) (envelope-from ) id 1NoMgc-0002Uo-4z; Sun, 07 Mar 2010 21:02:50 +0100 Date: Sun, 7 Mar 2010 21:02:50 +0100 From: Samuel Thibault To: Pavel Machek , Dmitry Torokhov , "H. Peter Anvin" , Alexey Dobriyan , akpm@linux-foundation.org, linux-kernel@vger.kernel.org, alan@lxorguk.ukuu.org.uk, mgarski@post.pl, linux-input@vger.kernel.org, Vojtech Pavlik , Richard Purdie Subject: [PATCH] Route kbd LEDs through the generic LEDs layer (4th version) Message-ID: <20100307200250.GA9340@const.famille.thibault.fr> Mail-Followup-To: Samuel Thibault , Pavel Machek , Dmitry Torokhov , "H. Peter Anvin" , Alexey Dobriyan , akpm@linux-foundation.org, linux-kernel@vger.kernel.org, alan@lxorguk.ukuu.org.uk, mgarski@post.pl, linux-input@vger.kernel.org, Vojtech Pavlik , Richard Purdie MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.12-2006-07-14 Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter.kernel.org [140.211.167.41]); Sun, 07 Mar 2010 20:03:20 +0000 (UTC) diff -ur linux-2.6.33-orig/Documentation/leds-class.txt linux-2.6.33-perso/Documentation/leds-class.txt --- linux-2.6.33-orig/Documentation/leds-class.txt 2009-12-03 13:41:42.000000000 +0100 +++ linux-2.6.33-perso/Documentation/leds-class.txt 2010-02-25 01:45:28.000000000 +0100 @@ -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 diff -ur linux-2.6.33-orig/drivers/char/keyboard.c linux-2.6.33-perso/drivers/char/keyboard.c --- linux-2.6.33-orig/drivers/char/keyboard.c 2010-02-25 01:41:19.000000000 +0100 +++ linux-2.6.33-perso/drivers/char/keyboard.c 2010-03-07 20:03:37.000000000 +0100 @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -139,6 +140,9 @@ static char rep; /* flag telling character repeat */ static unsigned char ledstate = 0xff; /* undefined */ +#ifdef CONFIG_LEDS_INPUT +static unsigned char lockstate = 0xff; /* undefined */ +#endif static unsigned char ledioctl; static struct ledptr { @@ -971,6 +975,35 @@ } } +#ifdef CONFIG_LEDS_INPUT +/* When input-based leds are enabled, we route 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, "scrollock"), + DEFINE_LEDSTATE_TRIGGER(VC_NUMLOCK, "numlock"), + DEFINE_LEDSTATE_TRIGGER(VC_CAPSLOCK, "capslock"), + DEFINE_LEDSTATE_TRIGGER(VC_KANALOCK, "kanalock"), +#undef DEFINE_LEDSTATE_TRIGGER +}; +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, "shiftlock"), + DEFINE_LOCKSTATE_TRIGGER(VC_ALTGRLOCK, "altgrlock"), + DEFINE_LOCKSTATE_TRIGGER(VC_CTRLLOCK, "ctrllock"), + DEFINE_LOCKSTATE_TRIGGER(VC_ALTLOCK, "altlock"), + DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTLLOCK, "shiftllock"), + DEFINE_LOCKSTATE_TRIGGER(VC_SHIFTRLOCK, "shiftrlock"), + DEFINE_LOCKSTATE_TRIGGER(VC_CTRLLLOCK, "ctrlllock"), + DEFINE_LOCKSTATE_TRIGGER(VC_CTRLRLOCK, "ctrlrlock"), +#undef DEFINE_LOCKSTATE_TRIGGER +}; +#endif + /* * The leds display either (i) the status of NumLock, CapsLock, ScrollLock, * or (ii) whatever pattern of lights people want to show using KDSETLED, @@ -1014,19 +1047,44 @@ return leds; } +#ifdef CONFIG_LEDS_INPUT +/* 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) ? INT_MAX : LED_OFF); + tasklet_enable(&keyboard_tasklet); +} +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) ? INT_MAX : LED_OFF); + tasklet_enable(&keyboard_tasklet); +} +#else 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_LED, LED_SCROLLL, + !!(leds & VC_SCROLLOCK)); + input_inject_event(handle, EV_LED, LED_NUML, + !!(leds & VC_NUMLOCK)); + input_inject_event(handle, EV_LED, LED_CAPSL, + !!(leds & VC_CAPSLOCK)); input_inject_event(handle, EV_SYN, SYN_REPORT, 0); } return 0; } +#endif /* * This is the tasklet that updates LED state on all keyboards @@ -1040,10 +1098,31 @@ unsigned char leds = getleds(); if (leds != ledstate) { +#ifdef CONFIG_LEDS_INPUT + int i; + for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++) + if ((leds ^ ledstate) & (1 << i)) + led_trigger_event(&ledtrig_ledstate[i], + leds & (1 << i) + ? INT_MAX : LED_OFF); +#else input_handler_for_each_handle(&kbd_handler, &leds, kbd_update_leds_helper); +#endif ledstate = leds; } + +#ifdef CONFIG_LEDS_INPUT + if (kbd->lockstate != lockstate) { + int i; + 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) + ? INT_MAX : LED_OFF); + lockstate = kbd->lockstate; + } +#endif } DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0); @@ -1380,6 +1459,7 @@ kfree(handle); } +#ifndef CONFIG_LEDS_INPUT /* * Start keyboard handler on the new keyboard by refreshing LED state to * match the rest of the system. @@ -1393,6 +1473,7 @@ tasklet_enable(&keyboard_tasklet); } +#endif static const struct input_device_id kbd_ids[] = { { @@ -1414,7 +1495,9 @@ .event = kbd_event, .connect = kbd_connect, .disconnect = kbd_disconnect, +#ifndef CONFIG_LEDS_INPUT .start = kbd_start, +#endif .name = "kbd", .id_table = kbd_ids, }; @@ -1441,5 +1524,12 @@ tasklet_enable(&keyboard_tasklet); tasklet_schedule(&keyboard_tasklet); +#ifdef CONFIG_LEDS_INPUT + for (i = 0; i < ARRAY_SIZE(ledtrig_ledstate); i++) + led_trigger_register(&ledtrig_ledstate[i]); + for (i = 0; i < ARRAY_SIZE(ledtrig_lockstate); i++) + led_trigger_register(&ledtrig_lockstate[i]); +#endif + return 0; } diff -ur linux-2.6.33-orig/drivers/leds/Kconfig linux-2.6.33-perso/drivers/leds/Kconfig --- linux-2.6.33-orig/drivers/leds/Kconfig 2010-02-25 01:41:27.000000000 +0100 +++ linux-2.6.33-perso/drivers/leds/Kconfig 2010-03-07 15:38:11.000000000 +0100 @@ -4,9 +4,6 @@ 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 @@ -17,6 +14,14 @@ comment "LED drivers" +config LEDS_INPUT + tristate "LED Support using input keyboards" + depends on LEDS_CLASS + select LEDS_TRIGGERS + help + This option enables support for the LEDs on keyboard managed + by the input layer. + config LEDS_ATMEL_PWM tristate "LED Support using Atmel PWM outputs" depends on LEDS_CLASS && ATMEL_PWM diff -ur linux-2.6.33-orig/drivers/leds/leds-input.c linux-2.6.33-perso/drivers/leds/leds-input.c --- linux-2.6.33-orig/drivers/leds/leds-input.c 2010-02-21 04:13:41.000000000 +0100 +++ linux-2.6.33-perso/drivers/leds/leds-input.c 2010-03-07 20:16:38.000000000 +0100 @@ -0,0 +1,295 @@ +/* + * LED support for the input layer + * + * Copyright 2010 Samuel Thibault + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +/* + * Keyboard LEDs are propagated by default like the following example: + * + * keyboard numlock trigger + * -> input::numl global input LED + * -> input-numl global input trigger + * -> per-device -input::numl LED + * + * Userland can however choose the trigger for the input::numl LED, or + * independently choose the trigger for any input-::numl LED. + */ + +/* Global LED classes and triggers are registered on-demand according to + * existing devices */ + +/* Handler for global input LEDs, just triggers the corresponding global input + * trigger. */ +static void input_led_set(struct led_classdev *cdev, + enum led_brightness brightness); +static struct led_classdev input_leds[LED_CNT] = { +#define DEFINE_INPUT_LED(input_led, nam, deftrig) \ + [input_led] = { \ + .name = "input::"nam, \ + .max_brightness = 1, \ + .brightness_set = input_led_set, \ + .default_trigger = deftrig, \ + } +/* Default triggers for the global input LEDs just correspond to the legacy + * usage. */ + DEFINE_INPUT_LED(LED_NUML, "numl", "numlock"), + DEFINE_INPUT_LED(LED_CAPSL, "capsl", "capslock"), + DEFINE_INPUT_LED(LED_SCROLLL, "scrolll", "scrollock"), + DEFINE_INPUT_LED(LED_COMPOSE, "compose", NULL), + DEFINE_INPUT_LED(LED_KANA, "kana", "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 const char *const input_led_names[LED_CNT] = { + [LED_NUML] = "numl", + [LED_CAPSL] = "capsl", + [LED_SCROLLL] = "scrolll", + [LED_COMPOSE] = "compose", + [LED_KANA] = "kana", + [LED_SLEEP] = "sleep", + [LED_SUSPEND] = "suspend", + [LED_MUTE] = "mute", + [LED_MISC] = "misc", + [LED_MAIL] = "mail", + [LED_CHARGING] = "charging", +}; +/* Handler for hotplug initialization */ +static void input_led_trigger_activate(struct led_classdev *cdev); +/* Global input triggers */ +static struct led_trigger input_led_triggers[LED_CNT] = { +#define DEFINE_INPUT_LED_TRIGGER(input_led, nam) \ + [input_led] = { \ + .name = "input-"nam, \ + .activate = input_led_trigger_activate, \ + } + DEFINE_INPUT_LED_TRIGGER(LED_NUML, "numl"), + DEFINE_INPUT_LED_TRIGGER(LED_CAPSL, "capsl"), + DEFINE_INPUT_LED_TRIGGER(LED_SCROLLL, "scrolll"), + DEFINE_INPUT_LED_TRIGGER(LED_COMPOSE, "compose"), + DEFINE_INPUT_LED_TRIGGER(LED_KANA, "kana"), + DEFINE_INPUT_LED_TRIGGER(LED_SLEEP, "sleep"), + DEFINE_INPUT_LED_TRIGGER(LED_SUSPEND, "suspend"), + DEFINE_INPUT_LED_TRIGGER(LED_MUTE, "mute"), + DEFINE_INPUT_LED_TRIGGER(LED_MISC, "misc"), + DEFINE_INPUT_LED_TRIGGER(LED_MAIL, "mail"), + DEFINE_INPUT_LED_TRIGGER(LED_CHARGING, "charging"), +}; +/* Lock for registration coherency */ +static DEFINE_SPINLOCK(input_led_registered_lock); +/* Which global LED classes and triggers are registered */ +static unsigned long input_led_registered[BITS_TO_LONGS(LED_CNT)]; + +/* Our input handler to catch connect/disconnect */ +static struct input_handler input_led_handler; + +/* Global input LED state change, tell the global input trigger. */ +static void input_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + int led = cdev - input_leds; + + led_trigger_event(&input_led_triggers[led], !!brightness); +} + +/* LED state change for some keyboard, notify that keyboard. */ +static void perdevice_input_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct input_handle *handle = cdev->private; + struct led_classdev *leds = handle->private; + int led = cdev - leds; + + input_inject_event(handle, EV_LED, led, !!brightness); + input_inject_event(handle, EV_SYN, SYN_REPORT, 0); +} + +/* Keyboard hotplug, initialize its LED status */ +static void input_led_trigger_activate(struct led_classdev *cdev) +{ + struct input_handle *handle = cdev->private; + struct led_classdev *leds = handle->private; + int led = cdev - leds; + + perdevice_input_led_set(cdev, input_leds[led].brightness); +} + +/* Free an input handle, used at abortion and disconnection. */ +static void input_led_delete_handle(struct input_handle *handle) +{ + if (handle) { + struct led_classdev *leds = handle->private; + if (leds) { + int i; + for (i = 0; i < LED_CNT; i++) + kfree(leds[i].name); + kfree(leds); + } + kfree(handle); + } +} + +/* A new input device with potential LEDs to connect. */ +static int input_led_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int i, error = 0; + unsigned long flags; + struct led_classdev *leds; + + if (!test_bit(EV_LED, dev->keybit)) + return -ENODEV; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) { + error = -ENOMEM; + goto err; + } + + handle->private = leds = kzalloc(sizeof(*leds) * LED_CNT, + GFP_KERNEL); + if (!handle->private) { + error = -ENOMEM; + goto err; + } + + handle->dev = dev; + handle->handler = handler; + handle->name = "input leds"; + + error = input_register_handle(handle); + if (error) + goto err; + + /* lazily register missing global input LEDs */ + spin_lock_irqsave(&input_led_registered_lock, flags); + for (i = 0; i < LED_CNT; i++) + if (input_leds[i].name + && !test_bit(i, input_led_registered) + && test_bit(i, dev->ledbit)) { + led_trigger_register(&input_led_triggers[i]); + /* This keyboard has led i, try to register it */ + if (!led_classdev_register(NULL, &input_leds[i])) + set_bit(i, input_led_registered); + else + led_trigger_unregister(&input_led_triggers[i]); + } + spin_unlock_irqrestore(&input_led_registered_lock, flags); + + /* and register this device's LEDs */ + for (i = 0; i < LED_CNT; i++) + if (input_leds[i].name && test_bit(i, dev->ledbit)) { + leds[i].name = kasprintf(GFP_KERNEL, "%s::%s", + dev_name(&dev->dev), + input_led_names[i]); + if (!leds[i].name) { + error = -ENOMEM; + goto err_handler; + } + leds[i].max_brightness = 1; + leds[i].brightness_set = perdevice_input_led_set; + leds[i].default_trigger = input_led_triggers[i].name; + leds[i].private = handle; + } + + /* No issue so far, we can register for real. */ + for (i = 0; i < LED_CNT; i++) + if (leds[i].name) + led_classdev_register(input_leds[i].dev, &leds[i]); + + return 0; + +err_handler: + input_unregister_handler(&input_led_handler); +err: + input_led_delete_handle(handle); + return error; +} + +/* Disconnected input device. Clean it, and deregister now-useless global LEDs + * and triggers. */ +static void input_led_disconnect(struct input_handle *handle) +{ + int unregister, i; + unsigned long flags; + struct led_classdev *leds = handle->private; + + for (i = 0; i < LED_CNT; i++) + if (leds[i].name) + led_classdev_unregister(&leds[i]); + + input_unregister_handle(handle); + input_led_delete_handle(handle); + + spin_lock_irqsave(&input_led_registered_lock, flags); + for (i = 0; i < LED_CNT; i++) { + if (!test_bit(i, input_led_registered)) + continue; + + unregister = 1; + list_for_each_entry(handle, &input_led_handler.h_list, h_node) { + if (test_bit(i, handle->dev->ledbit)) { + unregister = 0; + break; + } + } + if (!unregister) + continue; + + led_classdev_unregister(&input_leds[i]); + led_trigger_unregister(&input_led_triggers[i]); + clear_bit(i, input_led_registered); + } + spin_unlock_irqrestore(&input_led_registered_lock, flags); +} + +/* Only handle input devices which have LEDs */ +static const struct input_device_id input_led_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_LED) }, + }, + + { }, /* Terminating entry */ +}; + +static struct input_handler input_led_handler = { + .connect = input_led_connect, + .disconnect = input_led_disconnect, + .name = "input leds", + .id_table = input_led_ids, +}; + +static int __init input_led_init(void) +{ + return input_register_handler(&input_led_handler); +} + +static void __exit input_led_exit(void) +{ + /* This also disconnects all devices and thus unregisters LEDs and + * triggers */ + input_unregister_handler(&input_led_handler); +} + +module_init(input_led_init); +module_exit(input_led_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("User LED support for input layer"); +MODULE_AUTHOR("Samuel Thibault "); diff -ur linux-2.6.33-orig/drivers/leds/Makefile linux-2.6.33-perso/drivers/leds/Makefile --- linux-2.6.33-orig/drivers/leds/Makefile 2010-02-25 01:41:27.000000000 +0100 +++ linux-2.6.33-perso/drivers/leds/Makefile 2010-02-25 01:45:28.000000000 +0100 @@ -5,6 +5,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers +obj-$(CONFIG_LEDS_INPUT) += leds-input.o obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o diff -ur linux-2.6.33-orig/include/linux/leds.h linux-2.6.33-perso/include/linux/leds.h --- linux-2.6.33-orig/include/linux/leds.h 2009-09-10 01:26:46.000000000 +0200 +++ linux-2.6.33-perso/include/linux/leds.h 2010-03-07 15:34:32.000000000 +0100 @@ -65,6 +65,7 @@ struct list_head trig_list; void *trigger_data; #endif + void *private; }; extern int led_classdev_register(struct device *parent,