diff options
Diffstat (limited to 'drivers/leds/trigger')
| -rw-r--r-- | drivers/leds/trigger/Kconfig | 30 | ||||
| -rw-r--r-- | drivers/leds/trigger/Makefile | 2 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-activity.c | 8 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-audio.c | 44 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-backlight.c | 48 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-cpu.c | 18 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-default-on.c | 1 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-disk.c | 13 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-gpio.c | 155 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-heartbeat.c | 4 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-input-events.c | 165 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-mtd.c | 8 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-netdev.c | 551 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-panic.c | 28 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-pattern.c | 130 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-timer.c | 5 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-transient.c | 6 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-tty.c | 253 |
18 files changed, 1039 insertions, 430 deletions
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index b77a01bd27f4..c11282a74b5a 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -34,7 +34,7 @@ config LEDS_TRIGGER_ONESHOT config LEDS_TRIGGER_DISK bool "LED Disk Trigger" - depends on IDE_GD_ATA || ATA + depends on ATA help This allows LEDs to be controlled by disk activity. If unsure, say Y. @@ -64,6 +64,7 @@ config LEDS_TRIGGER_BACKLIGHT config LEDS_TRIGGER_CPU bool "LED CPU Trigger" + depends on !PREEMPT_RT help This allows LEDs to be controlled by active CPUs. This shows the active CPUs across an array of LEDs so you can see which @@ -85,9 +86,7 @@ config LEDS_TRIGGER_GPIO help This allows LEDs to be controlled by gpio events. It's good when using gpios as switches and triggering the needed LEDs - from there. One use case is n810's keypad LEDs that could - be triggered by this trigger when user slides up to show - keypad. + from there. Triggers are defined as device properties. If unsure, say N. @@ -137,13 +136,6 @@ config LEDS_TRIGGER_PATTERN which is a series of tuples, of brightness and duration (ms). If unsure, say N -config LEDS_TRIGGER_AUDIO - tristate "Audio Mute LED Trigger" - help - This allows LEDs to be controlled by audio drivers for following - the audio mute and mic-mute changes. - If unsure, say N - config LEDS_TRIGGER_TTY tristate "LED Trigger for TTY devices" depends on TTY @@ -153,4 +145,20 @@ config LEDS_TRIGGER_TTY When build as a module this driver will be called ledtrig-tty. +config LEDS_TRIGGER_INPUT_EVENTS + tristate "LED Input events trigger" + depends on INPUT + help + Turn LEDs on when there is input (/dev/input/event*) activity and turn + them back off again after there has been no activity for 5 seconds. + + This is primarily intended to control LEDs which are a backlight for + capacitive touch-buttons, such as e.g. the menu / home / back buttons + found on the bottom bezel of many older smartphones and tablets. + + This can also be used to turn on the keyboard backlight LED on + input events and turn the keyboard backlight off again when idle. + + When build as a module this driver will be called ledtrig-input-events. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 25c4db97cdd4..3b3628889f68 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -14,5 +14,5 @@ obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o -obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o +obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS) += ledtrig-input-events.o diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c index 30bc9df03636..c973246a57f9 100644 --- a/drivers/leds/trigger/ledtrig-activity.c +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -32,8 +32,8 @@ struct activity_data { static void led_activity_function(struct timer_list *t) { - struct activity_data *activity_data = from_timer(activity_data, t, - timer); + struct activity_data *activity_data = timer_container_of(activity_data, + t, timer); struct led_classdev *led_cdev = activity_data->led_cdev; unsigned int target; unsigned int usage; @@ -156,7 +156,7 @@ static ssize_t led_invert_show(struct device *dev, { struct activity_data *activity_data = led_trigger_get_drvdata(dev); - return sprintf(buf, "%u\n", activity_data->invert); + return sprintf(buf, "%d\n", activity_data->invert); } static ssize_t led_invert_store(struct device *dev, @@ -208,7 +208,7 @@ static void activity_deactivate(struct led_classdev *led_cdev) { struct activity_data *activity_data = led_get_trigger_data(led_cdev); - del_timer_sync(&activity_data->timer); + timer_shutdown_sync(&activity_data->timer); kfree(activity_data); clear_bit(LED_BLINK_SW, &led_cdev->work_flags); } diff --git a/drivers/leds/trigger/ledtrig-audio.c b/drivers/leds/trigger/ledtrig-audio.c deleted file mode 100644 index f76621e88482..000000000000 --- a/drivers/leds/trigger/ledtrig-audio.c +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// -// Audio Mute LED trigger -// - -#include <linux/kernel.h> -#include <linux/leds.h> -#include <linux/module.h> - -static struct led_trigger *ledtrig_audio[NUM_AUDIO_LEDS]; -static enum led_brightness audio_state[NUM_AUDIO_LEDS]; - -enum led_brightness ledtrig_audio_get(enum led_audio type) -{ - return audio_state[type]; -} -EXPORT_SYMBOL_GPL(ledtrig_audio_get); - -void ledtrig_audio_set(enum led_audio type, enum led_brightness state) -{ - audio_state[type] = state; - led_trigger_event(ledtrig_audio[type], state); -} -EXPORT_SYMBOL_GPL(ledtrig_audio_set); - -static int __init ledtrig_audio_init(void) -{ - led_trigger_register_simple("audio-mute", - &ledtrig_audio[LED_AUDIO_MUTE]); - led_trigger_register_simple("audio-micmute", - &ledtrig_audio[LED_AUDIO_MICMUTE]); - return 0; -} -module_init(ledtrig_audio_init); - -static void __exit ledtrig_audio_exit(void) -{ - led_trigger_unregister_simple(ledtrig_audio[LED_AUDIO_MUTE]); - led_trigger_unregister_simple(ledtrig_audio[LED_AUDIO_MICMUTE]); -} -module_exit(ledtrig_audio_exit); - -MODULE_DESCRIPTION("LED trigger for audio mute control"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c index 487577d22cfc..c1f0f5becaee 100644 --- a/drivers/leds/trigger/ledtrig-backlight.c +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -10,7 +10,6 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/init.h> -#include <linux/fb.h> #include <linux/leds.h> #include "../leds.h" @@ -21,29 +20,20 @@ struct bl_trig_notifier { struct led_classdev *led; int brightness; int old_status; - struct notifier_block notifier; unsigned invert; + + struct list_head entry; }; -static int fb_notifier_callback(struct notifier_block *p, - unsigned long event, void *data) +static DEFINE_MUTEX(ledtrig_backlight_list_mutex); +static LIST_HEAD(ledtrig_backlight_list); + +static void ledtrig_backlight_notify_blank(struct bl_trig_notifier *n, int new_status) { - struct bl_trig_notifier *n = container_of(p, - struct bl_trig_notifier, notifier); struct led_classdev *led = n->led; - struct fb_event *fb_event = data; - int *blank; - int new_status; - - /* If we aren't interested in this event, skip it immediately ... */ - if (event != FB_EVENT_BLANK) - return 0; - - blank = fb_event->data; - new_status = *blank ? BLANK : UNBLANK; if (new_status == n->old_status) - return 0; + return; if ((n->old_status == UNBLANK) ^ n->invert) { n->brightness = led->brightness; @@ -53,9 +43,19 @@ static int fb_notifier_callback(struct notifier_block *p, } n->old_status = new_status; +} - return 0; +void ledtrig_backlight_blank(bool blank) +{ + struct bl_trig_notifier *n; + int new_status = blank ? BLANK : UNBLANK; + + guard(mutex)(&ledtrig_backlight_list_mutex); + + list_for_each_entry(n, &ledtrig_backlight_list, entry) + ledtrig_backlight_notify_blank(n, new_status); } +EXPORT_SYMBOL(ledtrig_backlight_blank); static ssize_t bl_trig_invert_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -100,8 +100,6 @@ ATTRIBUTE_GROUPS(bl_trig); static int bl_trig_activate(struct led_classdev *led) { - int ret; - struct bl_trig_notifier *n; n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL); @@ -112,11 +110,9 @@ static int bl_trig_activate(struct led_classdev *led) n->led = led; n->brightness = led->brightness; n->old_status = UNBLANK; - n->notifier.notifier_call = fb_notifier_callback; - ret = fb_register_client(&n->notifier); - if (ret) - dev_err(led->dev, "unable to register backlight trigger\n"); + guard(mutex)(&ledtrig_backlight_list_mutex); + list_add(&n->entry, &ledtrig_backlight_list); return 0; } @@ -125,7 +121,9 @@ static void bl_trig_deactivate(struct led_classdev *led) { struct bl_trig_notifier *n = led_get_trigger_data(led); - fb_unregister_client(&n->notifier); + guard(mutex)(&ledtrig_backlight_list_mutex); + list_del(&n->entry); + kfree(n); } diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c index 8af4f9bb9cde..679323c2ccda 100644 --- a/drivers/leds/trigger/ledtrig-cpu.c +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -94,28 +94,32 @@ void ledtrig_cpu(enum cpu_led_event ledevt) } EXPORT_SYMBOL(ledtrig_cpu); -static int ledtrig_cpu_syscore_suspend(void) +static int ledtrig_cpu_syscore_suspend(void *data) { ledtrig_cpu(CPU_LED_STOP); return 0; } -static void ledtrig_cpu_syscore_resume(void) +static void ledtrig_cpu_syscore_resume(void *data) { ledtrig_cpu(CPU_LED_START); } -static void ledtrig_cpu_syscore_shutdown(void) +static void ledtrig_cpu_syscore_shutdown(void *data) { ledtrig_cpu(CPU_LED_HALTED); } -static struct syscore_ops ledtrig_cpu_syscore_ops = { +static const struct syscore_ops ledtrig_cpu_syscore_ops = { .shutdown = ledtrig_cpu_syscore_shutdown, .suspend = ledtrig_cpu_syscore_suspend, .resume = ledtrig_cpu_syscore_resume, }; +static struct syscore ledtrig_cpu_syscore = { + .ops = &ledtrig_cpu_syscore_ops, +}; + static int ledtrig_online_cpu(unsigned int cpu) { ledtrig_cpu(CPU_LED_START); @@ -130,7 +134,7 @@ static int ledtrig_prepare_down_cpu(unsigned int cpu) static int __init ledtrig_cpu_init(void) { - int cpu; + unsigned int cpu; int ret; /* Supports up to 9999 cpu cores */ @@ -152,12 +156,12 @@ static int __init ledtrig_cpu_init(void) if (cpu >= 8) continue; - snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); + snprintf(trig->name, MAX_NAME_LEN, "cpu%u", cpu); led_trigger_register_simple(trig->name, &trig->_trig); } - register_syscore_ops(&ledtrig_cpu_syscore_ops); + register_syscore(&ledtrig_cpu_syscore); ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "leds/trigger:starting", ledtrig_online_cpu, ledtrig_prepare_down_cpu); diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c index 8207f85eceb1..8678e64a5c33 100644 --- a/drivers/leds/trigger/ledtrig-default-on.c +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -28,3 +28,4 @@ module_led_trigger(defon_led_trigger); MODULE_AUTHOR("Nick Forbes <nick.forbes@incepta.com>"); MODULE_DESCRIPTION("Default-ON LED trigger"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ledtrig:default-on"); diff --git a/drivers/leds/trigger/ledtrig-disk.c b/drivers/leds/trigger/ledtrig-disk.c index 0741910785bb..e9b87ee944f2 100644 --- a/drivers/leds/trigger/ledtrig-disk.c +++ b/drivers/leds/trigger/ledtrig-disk.c @@ -16,22 +16,16 @@ DEFINE_LED_TRIGGER(ledtrig_disk); DEFINE_LED_TRIGGER(ledtrig_disk_read); DEFINE_LED_TRIGGER(ledtrig_disk_write); -DEFINE_LED_TRIGGER(ledtrig_ide); void ledtrig_disk_activity(bool write) { - unsigned long blink_delay = BLINK_DELAY; - - led_trigger_blink_oneshot(ledtrig_disk, - &blink_delay, &blink_delay, 0); - led_trigger_blink_oneshot(ledtrig_ide, - &blink_delay, &blink_delay, 0); + led_trigger_blink_oneshot(ledtrig_disk, BLINK_DELAY, BLINK_DELAY, 0); if (write) led_trigger_blink_oneshot(ledtrig_disk_write, - &blink_delay, &blink_delay, 0); + BLINK_DELAY, BLINK_DELAY, 0); else led_trigger_blink_oneshot(ledtrig_disk_read, - &blink_delay, &blink_delay, 0); + BLINK_DELAY, BLINK_DELAY, 0); } EXPORT_SYMBOL(ledtrig_disk_activity); @@ -40,7 +34,6 @@ static int __init ledtrig_disk_init(void) led_trigger_register_simple("disk-activity", &ledtrig_disk); led_trigger_register_simple("disk-read", &ledtrig_disk_read); led_trigger_register_simple("disk-write", &ledtrig_disk_write); - led_trigger_register_simple("ide-disk", &ledtrig_ide); return 0; } diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index 0120faa3dafa..7f6a2352b0ac 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -3,12 +3,13 @@ * ledtrig-gio.c - LED Trigger Based on GPIO events * * Copyright 2009 Felipe Balbi <me@felipebalbi.com> + * Copyright 2023 Linus Walleij <linus.walleij@linaro.org> */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/leds.h> #include <linux/slab.h> @@ -16,10 +17,8 @@ struct gpio_trig_data { struct led_classdev *led; - unsigned desired_brightness; /* desired brightness when led is on */ - unsigned inverted; /* true when gpio is inverted */ - unsigned gpio; /* gpio that triggers the leds */ + struct gpio_desc *gpiod; /* gpio that triggers the led */ }; static irqreturn_t gpio_trig_irq(int irq, void *_led) @@ -28,10 +27,7 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led) struct gpio_trig_data *gpio_data = led_get_trigger_data(led); int tmp; - tmp = gpio_get_value_cansleep(gpio_data->gpio); - if (gpio_data->inverted) - tmp = !tmp; - + tmp = gpiod_get_value_cansleep(gpio_data->gpiod); if (tmp) { if (gpio_data->desired_brightness) led_set_brightness_nosleep(gpio_data->led, @@ -45,121 +41,33 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led) return IRQ_HANDLED; } -static ssize_t gpio_trig_brightness_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - - return sprintf(buf, "%u\n", gpio_data->desired_brightness); -} - -static ssize_t gpio_trig_brightness_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned desired_brightness; - int ret; - - ret = sscanf(buf, "%u", &desired_brightness); - if (ret < 1 || desired_brightness > 255) { - dev_err(dev, "invalid value\n"); - return -EINVAL; - } - - gpio_data->desired_brightness = desired_brightness; - - return n; -} -static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, - gpio_trig_brightness_store); - -static ssize_t gpio_trig_inverted_show(struct device *dev, +static ssize_t desired_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - return sprintf(buf, "%u\n", gpio_data->inverted); + return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness); } -static ssize_t gpio_trig_inverted_store(struct device *dev, +static ssize_t desired_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { - struct led_classdev *led = led_trigger_get_led(dev); struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned long inverted; + u8 desired_brightness; int ret; - ret = kstrtoul(buf, 10, &inverted); - if (ret < 0) + ret = kstrtou8(buf, 10, &desired_brightness); + if (ret) return ret; - if (inverted > 1) - return -EINVAL; - - gpio_data->inverted = inverted; - - /* After inverting, we need to update the LED. */ - if (gpio_is_valid(gpio_data->gpio)) - gpio_trig_irq(0, led); + gpio_data->desired_brightness = desired_brightness; return n; } -static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show, - gpio_trig_inverted_store); - -static ssize_t gpio_trig_gpio_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - - return sprintf(buf, "%u\n", gpio_data->gpio); -} - -static ssize_t gpio_trig_gpio_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct led_classdev *led = led_trigger_get_led(dev); - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned gpio; - int ret; - - ret = sscanf(buf, "%u", &gpio); - if (ret < 1) { - dev_err(dev, "couldn't read gpio number\n"); - return -EINVAL; - } - - if (gpio_data->gpio == gpio) - return n; - - if (!gpio_is_valid(gpio)) { - if (gpio_is_valid(gpio_data->gpio)) - free_irq(gpio_to_irq(gpio_data->gpio), led); - gpio_data->gpio = gpio; - return n; - } - - ret = request_threaded_irq(gpio_to_irq(gpio), NULL, gpio_trig_irq, - IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING - | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); - if (ret) { - dev_err(dev, "request_irq failed with error %d\n", ret); - } else { - if (gpio_is_valid(gpio_data->gpio)) - free_irq(gpio_to_irq(gpio_data->gpio), led); - gpio_data->gpio = gpio; - /* After changing the GPIO, we need to update the LED. */ - gpio_trig_irq(0, led); - } - - return ret ? ret : n; -} -static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store); +static DEVICE_ATTR_RW(desired_brightness); static struct attribute *gpio_trig_attrs[] = { &dev_attr_desired_brightness.attr, - &dev_attr_inverted.attr, - &dev_attr_gpio.attr, NULL }; ATTRIBUTE_GROUPS(gpio_trig); @@ -167,16 +75,47 @@ ATTRIBUTE_GROUPS(gpio_trig); static int gpio_trig_activate(struct led_classdev *led) { struct gpio_trig_data *gpio_data; + struct device *dev = led->dev; + int ret; gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); if (!gpio_data) return -ENOMEM; - gpio_data->led = led; - gpio_data->gpio = -ENOENT; + /* + * The generic property "trigger-sources" is followed, + * and we hope that this is a GPIO. + */ + gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN); + if (IS_ERR(gpio_data->gpiod)) { + ret = PTR_ERR(gpio_data->gpiod); + kfree(gpio_data); + return ret; + } + if (!gpio_data->gpiod) { + dev_err(dev, "no valid GPIO for the trigger\n"); + kfree(gpio_data); + return -EINVAL; + } + gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger"); + + gpio_data->led = led; led_set_trigger_data(led, gpio_data); + ret = request_threaded_irq(gpiod_to_irq(gpio_data->gpiod), NULL, gpio_trig_irq, + IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); + if (ret) { + dev_err(dev, "request_irq failed with error %d\n", ret); + gpiod_put(gpio_data->gpiod); + kfree(gpio_data); + return ret; + } + + /* Finally update the LED to initial status */ + gpio_trig_irq(0, led); + return 0; } @@ -184,8 +123,8 @@ static void gpio_trig_deactivate(struct led_classdev *led) { struct gpio_trig_data *gpio_data = led_get_trigger_data(led); - if (gpio_is_valid(gpio_data->gpio)) - free_irq(gpio_to_irq(gpio_data->gpio), led); + free_irq(gpiod_to_irq(gpio_data->gpiod), led); + gpiod_put(gpio_data->gpiod); kfree(gpio_data); } diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c index 7fe0a05574d2..40eb61b6d54e 100644 --- a/drivers/leds/trigger/ledtrig-heartbeat.c +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -33,7 +33,7 @@ struct heartbeat_trig_data { static void led_heartbeat_function(struct timer_list *t) { struct heartbeat_trig_data *heartbeat_data = - from_timer(heartbeat_data, t, timer); + timer_container_of(heartbeat_data, t, timer); struct led_classdev *led_cdev; unsigned long brightness = LED_OFF; unsigned long delay = 0; @@ -151,7 +151,7 @@ static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) struct heartbeat_trig_data *heartbeat_data = led_get_trigger_data(led_cdev); - del_timer_sync(&heartbeat_data->timer); + timer_shutdown_sync(&heartbeat_data->timer); kfree(heartbeat_data); clear_bit(LED_BLINK_SW, &led_cdev->work_flags); } diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c new file mode 100644 index 000000000000..3c6414259c27 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-input-events.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Input Events LED trigger + * + * Copyright (C) 2024 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/input.h> +#include <linux/jiffies.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include "../leds.h" + +static unsigned long led_off_delay_ms = 5000; +module_param(led_off_delay_ms, ulong, 0644); +MODULE_PARM_DESC(led_off_delay_ms, + "Specify delay in ms for turning LEDs off after last input event"); + +static struct input_events_data { + struct delayed_work work; + spinlock_t lock; + /* To avoid repeatedly setting the brightness while there are events */ + bool led_on; + unsigned long led_off_time; +} input_events_data; + +static struct led_trigger *input_events_led_trigger; + +static void led_input_events_work(struct work_struct *work) +{ + struct input_events_data *data = + container_of(work, struct input_events_data, work.work); + + spin_lock_irq(&data->lock); + + /* + * This time_after_eq() check avoids a race where this work starts + * running before a new event pushed led_off_time back. + */ + if (time_after_eq(jiffies, data->led_off_time)) { + led_trigger_event(input_events_led_trigger, LED_OFF); + data->led_on = false; + } + + spin_unlock_irq(&data->lock); +} + +static void input_events_event(struct input_handle *handle, unsigned int type, + unsigned int code, int val) +{ + struct input_events_data *data = &input_events_data; + unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + + if (!data->led_on) { + led_trigger_event(input_events_led_trigger, LED_FULL); + data->led_on = true; + } + data->led_off_time = jiffies + led_off_delay; + + spin_unlock_irqrestore(&data->lock, flags); + + mod_delayed_work(system_percpu_wq, &data->work, led_off_delay); +} + +static int input_events_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = KBUILD_MODNAME; + + ret = input_register_handle(handle); + if (ret) + goto err_free_handle; + + ret = input_open_device(handle); + if (ret) + goto err_unregister_handle; + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return ret; +} + +static void input_events_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id input_events_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_REL) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_ABS) }, + }, + { } +}; + +static struct input_handler input_events_handler = { + .name = KBUILD_MODNAME, + .event = input_events_event, + .connect = input_events_connect, + .disconnect = input_events_disconnect, + .id_table = input_events_ids, +}; + +static int __init input_events_init(void) +{ + int ret; + + INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work); + spin_lock_init(&input_events_data.lock); + + led_trigger_register_simple("input-events", &input_events_led_trigger); + + ret = input_register_handler(&input_events_handler); + if (ret) { + led_trigger_unregister_simple(input_events_led_trigger); + return ret; + } + + return 0; +} + +static void __exit input_events_exit(void) +{ + input_unregister_handler(&input_events_handler); + cancel_delayed_work_sync(&input_events_data.work); + led_trigger_unregister_simple(input_events_led_trigger); +} + +module_init(input_events_init); +module_exit(input_events_exit); + +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("Input Events LED trigger"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ledtrig:input-events"); diff --git a/drivers/leds/trigger/ledtrig-mtd.c b/drivers/leds/trigger/ledtrig-mtd.c index 8fa763c2269b..bbe6876a249d 100644 --- a/drivers/leds/trigger/ledtrig-mtd.c +++ b/drivers/leds/trigger/ledtrig-mtd.c @@ -22,12 +22,8 @@ DEFINE_LED_TRIGGER(ledtrig_nand); void ledtrig_mtd_activity(void) { - unsigned long blink_delay = BLINK_DELAY; - - led_trigger_blink_oneshot(ledtrig_mtd, - &blink_delay, &blink_delay, 0); - led_trigger_blink_oneshot(ledtrig_nand, - &blink_delay, &blink_delay, 0); + led_trigger_blink_oneshot(ledtrig_mtd, BLINK_DELAY, BLINK_DELAY, 0); + led_trigger_blink_oneshot(ledtrig_nand, BLINK_DELAY, BLINK_DELAY, 0); } EXPORT_SYMBOL(ledtrig_mtd_activity); diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index d5e774d83021..c15efe3e5078 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -13,17 +13,23 @@ #include <linux/atomic.h> #include <linux/ctype.h> #include <linux/device.h> +#include <linux/ethtool.h> #include <linux/init.h> #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/leds.h> +#include <linux/linkmode.h> #include <linux/list.h> #include <linux/module.h> #include <linux/netdevice.h> -#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/phy.h> +#include <linux/rtnetlink.h> #include <linux/timer.h> #include "../leds.h" +#define NETDEV_LED_DEFAULT_INTERVAL 50 + /* * Configurable sysfs attributes: * @@ -33,11 +39,23 @@ * (has carrier) or not * tx - LED blinks on transmitted data * rx - LED blinks on receive data + * tx_err - LED blinks on transmit error + * rx_err - LED blinks on receive error * + * Note: If the user selects a mode that is not supported by hw, default + * behavior is to fall back to software control of the LED. However not every + * hw supports software control. LED callbacks brightness_set() and + * brightness_set_blocking() are NULL in this case. hw_control_is_supported() + * should use available means supported by hw to inform the user that selected + * mode isn't supported by hw. This could be switching off the LED or any + * hw blink mode. If software control fallback isn't possible, we return + * -EOPNOTSUPP to the user, but still store the selected mode. This is needed + * in case an intermediate unsupported mode is necessary to switch from one + * supported mode to another. */ struct led_netdev_data { - spinlock_t lock; + struct mutex lock; struct delayed_work work; struct notifier_block notifier; @@ -50,33 +68,75 @@ struct led_netdev_data { unsigned int last_activity; unsigned long mode; -#define NETDEV_LED_LINK 0 -#define NETDEV_LED_TX 1 -#define NETDEV_LED_RX 2 -#define NETDEV_LED_MODE_LINKUP 3 -}; + int link_speed; + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes); + u8 duplex; -enum netdev_led_attr { - NETDEV_ATTR_LINK, - NETDEV_ATTR_TX, - NETDEV_ATTR_RX + bool carrier_link_up; + bool hw_control; }; +static const struct attribute_group netdev_trig_link_speed_attrs_group; + static void set_baseline_state(struct led_netdev_data *trigger_data) { int current_brightness; struct led_classdev *led_cdev = trigger_data->led_cdev; + /* Already validated, hw control is possible with the requested mode */ + if (trigger_data->hw_control) { + led_cdev->hw_control_set(led_cdev, trigger_data->mode); + + return; + } + current_brightness = led_cdev->brightness; if (current_brightness) led_cdev->blink_brightness = current_brightness; if (!led_cdev->blink_brightness) led_cdev->blink_brightness = led_cdev->max_brightness; - if (!test_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode)) + if (!trigger_data->carrier_link_up) { led_set_brightness(led_cdev, LED_OFF); - else { - if (test_bit(NETDEV_LED_LINK, &trigger_data->mode)) + } else { + bool blink_on = false; + + if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode)) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) && + trigger_data->link_speed == SPEED_100) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_1000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) && + trigger_data->link_speed == SPEED_2500) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_5000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && + trigger_data->duplex == DUPLEX_HALF) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) && + trigger_data->duplex == DUPLEX_FULL) + blink_on = true; + + if (blink_on) led_set_brightness(led_cdev, led_cdev->blink_brightness); else @@ -85,44 +145,138 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) /* If we are looking for RX/TX start periodically * checking stats */ - if (test_bit(NETDEV_LED_TX, &trigger_data->mode) || - test_bit(NETDEV_LED_RX, &trigger_data->mode)) + if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode)) schedule_delayed_work(&trigger_data->work, 0); } } +static bool supports_hw_control(struct led_classdev *led_cdev) +{ + if (!led_cdev->hw_control_get || !led_cdev->hw_control_set || + !led_cdev->hw_control_is_supported) + return false; + + return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name); +} + +/* + * Validate the configured netdev is the same as the one associated with + * the LED driver in hw control. + */ +static bool validate_net_dev(struct led_classdev *led_cdev, + struct net_device *net_dev) +{ + struct device *dev = led_cdev->hw_control_get_device(led_cdev); + struct net_device *ndev; + + if (!dev) + return false; + + ndev = to_net_dev(dev); + + return ndev == net_dev; +} + +static bool can_hw_control(struct led_netdev_data *trigger_data) +{ + unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL); + unsigned int interval = atomic_read(&trigger_data->interval); + struct led_classdev *led_cdev = trigger_data->led_cdev; + int ret; + + if (!supports_hw_control(led_cdev)) + return false; + + /* + * Interval must be set to the default + * value. Any different value is rejected if in hw + * control. + */ + if (interval != default_interval) + return false; + + /* + * net_dev must be set with hw control, otherwise no + * blinking can be happening and there is nothing to + * offloaded. Additionally, for hw control to be + * valid, the configured netdev must be the same as + * netdev associated to the LED. + */ + if (!validate_net_dev(led_cdev, trigger_data->net_dev)) + return false; + + /* Check if the requested mode is supported */ + ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode); + /* Fall back to software blinking if not supported */ + if (ret == -EOPNOTSUPP) + return false; + if (ret) { + dev_warn(led_cdev->dev, + "Current mode check failed with error %d\n", ret); + return false; + } + + return true; +} + +static void get_device_state(struct led_netdev_data *trigger_data) +{ + struct ethtool_link_ksettings cmd; + + trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev); + + if (__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) + return; + + if (trigger_data->carrier_link_up) { + trigger_data->link_speed = cmd.base.speed; + trigger_data->duplex = cmd.base.duplex; + } + + /* + * Have a local copy of the link speed supported to avoid rtnl lock every time + * modes are refreshed on any change event + */ + linkmode_copy(trigger_data->supported_link_modes, cmd.link_modes.supported); +} + static ssize_t device_name_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); ssize_t len; - spin_lock_bh(&trigger_data->lock); + mutex_lock(&trigger_data->lock); len = sprintf(buf, "%s\n", trigger_data->device_name); - spin_unlock_bh(&trigger_data->lock); + mutex_unlock(&trigger_data->lock); return len; } -static ssize_t device_name_store(struct device *dev, - struct device_attribute *attr, const char *buf, - size_t size) +static int set_device_name(struct led_netdev_data *trigger_data, + const char *name, size_t size) { - struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); - if (size >= IFNAMSIZ) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); - spin_lock_bh(&trigger_data->lock); + /* + * Take RTNL lock before trigger_data lock to prevent potential + * deadlock with netdev notifier registration. + */ + rtnl_lock(); + mutex_lock(&trigger_data->lock); if (trigger_data->net_dev) { dev_put(trigger_data->net_dev); trigger_data->net_dev = NULL; } - memcpy(trigger_data->device_name, buf, size); + memcpy(trigger_data->device_name, name, size); trigger_data->device_name[size] = 0; if (size > 0 && trigger_data->device_name[size - 1] == '\n') trigger_data->device_name[size - 1] = 0; @@ -131,15 +285,38 @@ static ssize_t device_name_store(struct device *dev, trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); - clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); - if (trigger_data->net_dev != NULL) - if (netif_carrier_ok(trigger_data->net_dev)) - set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + trigger_data->carrier_link_up = false; + trigger_data->link_speed = SPEED_UNKNOWN; + trigger_data->duplex = DUPLEX_UNKNOWN; + if (trigger_data->net_dev) + get_device_state(trigger_data); trigger_data->last_activity = 0; - set_baseline_state(trigger_data); - spin_unlock_bh(&trigger_data->lock); + /* Skip if we're called from netdev_trig_activate() and hw_control is true */ + if (!trigger_data->hw_control || led_get_trigger_data(trigger_data->led_cdev)) + set_baseline_state(trigger_data); + + mutex_unlock(&trigger_data->lock); + rtnl_unlock(); + + return 0; +} + +static ssize_t device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t size) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + int ret; + + ret = set_device_name(trigger_data, buf, size); + + if (ret < 0) + return ret; + + /* Refresh link_speed visibility */ + sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group); return size; } @@ -147,20 +324,26 @@ static ssize_t device_name_store(struct device *dev, static DEVICE_ATTR_RW(device_name); static ssize_t netdev_led_attr_show(struct device *dev, char *buf, - enum netdev_led_attr attr) + enum led_trigger_netdev_modes attr) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); int bit; switch (attr) { - case NETDEV_ATTR_LINK: - bit = NETDEV_LED_LINK; - break; - case NETDEV_ATTR_TX: - bit = NETDEV_LED_TX; - break; - case NETDEV_ATTR_RX: - bit = NETDEV_LED_RX; + case TRIGGER_NETDEV_LINK: + case TRIGGER_NETDEV_LINK_10: + case TRIGGER_NETDEV_LINK_100: + case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_LINK_2500: + case TRIGGER_NETDEV_LINK_5000: + case TRIGGER_NETDEV_LINK_10000: + case TRIGGER_NETDEV_HALF_DUPLEX: + case TRIGGER_NETDEV_FULL_DUPLEX: + case TRIGGER_NETDEV_TX: + case TRIGGER_NETDEV_RX: + case TRIGGER_NETDEV_TX_ERR: + case TRIGGER_NETDEV_RX_ERR: + bit = attr; break; default: return -EINVAL; @@ -170,10 +353,11 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf, } static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, - size_t size, enum netdev_led_attr attr) + size_t size, enum led_trigger_netdev_modes attr) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); - unsigned long state; + struct led_classdev *led_cdev = trigger_data->led_cdev; + unsigned long state, mode = trigger_data->mode; int ret; int bit; @@ -182,72 +366,79 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, return ret; switch (attr) { - case NETDEV_ATTR_LINK: - bit = NETDEV_LED_LINK; - break; - case NETDEV_ATTR_TX: - bit = NETDEV_LED_TX; - break; - case NETDEV_ATTR_RX: - bit = NETDEV_LED_RX; + case TRIGGER_NETDEV_LINK: + case TRIGGER_NETDEV_LINK_10: + case TRIGGER_NETDEV_LINK_100: + case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_LINK_2500: + case TRIGGER_NETDEV_LINK_5000: + case TRIGGER_NETDEV_LINK_10000: + case TRIGGER_NETDEV_HALF_DUPLEX: + case TRIGGER_NETDEV_FULL_DUPLEX: + case TRIGGER_NETDEV_TX: + case TRIGGER_NETDEV_RX: + case TRIGGER_NETDEV_TX_ERR: + case TRIGGER_NETDEV_RX_ERR: + bit = attr; break; default: return -EINVAL; } - cancel_delayed_work_sync(&trigger_data->work); - if (state) - set_bit(bit, &trigger_data->mode); + set_bit(bit, &mode); else - clear_bit(bit, &trigger_data->mode); - - set_baseline_state(trigger_data); - - return size; -} - -static ssize_t link_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return netdev_led_attr_show(dev, buf, NETDEV_ATTR_LINK); -} - -static ssize_t link_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_LINK); -} - -static DEVICE_ATTR_RW(link); + clear_bit(bit, &mode); + + if (test_bit(TRIGGER_NETDEV_LINK, &mode) && + (test_bit(TRIGGER_NETDEV_LINK_10, &mode) || + test_bit(TRIGGER_NETDEV_LINK_100, &mode) || + test_bit(TRIGGER_NETDEV_LINK_1000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_2500, &mode) || + test_bit(TRIGGER_NETDEV_LINK_5000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_10000, &mode))) + return -EINVAL; -static ssize_t tx_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return netdev_led_attr_show(dev, buf, NETDEV_ATTR_TX); -} + cancel_delayed_work_sync(&trigger_data->work); -static ssize_t tx_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_TX); -} + trigger_data->mode = mode; + trigger_data->hw_control = can_hw_control(trigger_data); -static DEVICE_ATTR_RW(tx); + if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking && + !trigger_data->hw_control) + return -EOPNOTSUPP; -static ssize_t rx_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return netdev_led_attr_show(dev, buf, NETDEV_ATTR_RX); -} + set_baseline_state(trigger_data); -static ssize_t rx_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_RX); + return size; } -static DEVICE_ATTR_RW(rx); +#define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \ + static ssize_t trigger_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return netdev_led_attr_show(dev, buf, trigger); \ + } \ + static ssize_t trigger_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t size) \ + { \ + return netdev_led_attr_store(dev, buf, size, trigger); \ + } \ + static DEVICE_ATTR_RW(trigger_name) + +DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK); +DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10); +DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100); +DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); +DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500); +DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000); +DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000); +DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); +DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); +DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); +DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX); +DEFINE_NETDEV_TRIGGER(tx_err, TRIGGER_NETDEV_TX_ERR); +DEFINE_NETDEV_TRIGGER(rx_err, TRIGGER_NETDEV_RX_ERR); static ssize_t interval_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -266,6 +457,9 @@ static ssize_t interval_store(struct device *dev, unsigned long value; int ret; + if (trigger_data->hw_control) + return -EINVAL; + ret = kstrtoul(buf, 0, &value); if (ret) return ret; @@ -283,15 +477,93 @@ static ssize_t interval_store(struct device *dev, static DEVICE_ATTR_RW(interval); +static ssize_t offloaded_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%d\n", trigger_data->hw_control); +} + +static DEVICE_ATTR_RO(offloaded); + +#define CHECK_LINK_MODE_ATTR(link_speed) \ + do { \ + if (attr == &dev_attr_link_##link_speed.attr && \ + link_ksettings.base.speed == SPEED_##link_speed) \ + return attr->mode; \ + } while (0) + +static umode_t netdev_trig_link_speed_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct led_netdev_data *trigger_data; + unsigned long *supported_link_modes; + u32 mode; + + trigger_data = led_trigger_get_drvdata(dev); + supported_link_modes = trigger_data->supported_link_modes; + + /* + * Search in the supported link mode mask a matching supported mode. + * Stop at the first matching entry as we care only to check if a particular + * speed is supported and not the kind. + */ + for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) { + struct ethtool_link_ksettings link_ksettings; + + ethtool_params_from_link_mode(&link_ksettings, mode); + + CHECK_LINK_MODE_ATTR(10); + CHECK_LINK_MODE_ATTR(100); + CHECK_LINK_MODE_ATTR(1000); + CHECK_LINK_MODE_ATTR(2500); + CHECK_LINK_MODE_ATTR(5000); + CHECK_LINK_MODE_ATTR(10000); + } + + return 0; +} + +static struct attribute *netdev_trig_link_speed_attrs[] = { + &dev_attr_link_10.attr, + &dev_attr_link_100.attr, + &dev_attr_link_1000.attr, + &dev_attr_link_2500.attr, + &dev_attr_link_5000.attr, + &dev_attr_link_10000.attr, + NULL +}; + +static const struct attribute_group netdev_trig_link_speed_attrs_group = { + .attrs = netdev_trig_link_speed_attrs, + .is_visible = netdev_trig_link_speed_visible, +}; + static struct attribute *netdev_trig_attrs[] = { &dev_attr_device_name.attr, &dev_attr_link.attr, + &dev_attr_full_duplex.attr, + &dev_attr_half_duplex.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, + &dev_attr_rx_err.attr, + &dev_attr_tx_err.attr, &dev_attr_interval.attr, + &dev_attr_offloaded.attr, NULL }; -ATTRIBUTE_GROUPS(netdev_trig); + +static const struct attribute_group netdev_trig_attrs_group = { + .attrs = netdev_trig_attrs, +}; + +static const struct attribute_group *netdev_trig_groups[] = { + &netdev_trig_attrs_group, + &netdev_trig_link_speed_attrs_group, + NULL, +}; static int netdev_trig_notify(struct notifier_block *nb, unsigned long evt, void *dv) @@ -300,6 +572,7 @@ static int netdev_trig_notify(struct notifier_block *nb, netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv); struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); + struct led_classdev *led_cdev = trigger_data->led_cdev; if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER @@ -313,31 +586,39 @@ static int netdev_trig_notify(struct notifier_block *nb, cancel_delayed_work_sync(&trigger_data->work); - spin_lock_bh(&trigger_data->lock); + mutex_lock(&trigger_data->lock); - clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + trigger_data->carrier_link_up = false; + trigger_data->link_speed = SPEED_UNKNOWN; + trigger_data->duplex = DUPLEX_UNKNOWN; switch (evt) { case NETDEV_CHANGENAME: case NETDEV_REGISTER: - if (trigger_data->net_dev) - dev_put(trigger_data->net_dev); + dev_put(trigger_data->net_dev); dev_hold(dev); trigger_data->net_dev = dev; + if (evt == NETDEV_CHANGENAME) + get_device_state(trigger_data); break; case NETDEV_UNREGISTER: dev_put(trigger_data->net_dev); trigger_data->net_dev = NULL; break; case NETDEV_UP: + trigger_data->hw_control = can_hw_control(trigger_data); + fallthrough; case NETDEV_CHANGE: - if (netif_carrier_ok(dev)) - set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode); + get_device_state(trigger_data); + /* Refresh link_speed visibility */ + if (evt == NETDEV_CHANGE) + sysfs_update_group(&led_cdev->dev->kobj, + &netdev_trig_link_speed_attrs_group); break; } set_baseline_state(trigger_data); - spin_unlock_bh(&trigger_data->lock); + mutex_unlock(&trigger_data->lock); return NOTIFY_DONE; } @@ -360,21 +641,35 @@ static void netdev_trig_work(struct work_struct *work) } /* If we are not looking for RX/TX then return */ - if (!test_bit(NETDEV_LED_TX, &trigger_data->mode) && - !test_bit(NETDEV_LED_RX, &trigger_data->mode)) + if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) && + !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) && + !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) && + !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode)) return; dev_stats = dev_get_stats(trigger_data->net_dev, &temp); new_activity = - (test_bit(NETDEV_LED_TX, &trigger_data->mode) ? + (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ? dev_stats->tx_packets : 0) + - (test_bit(NETDEV_LED_RX, &trigger_data->mode) ? - dev_stats->rx_packets : 0); + (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ? + dev_stats->rx_packets : 0) + + (test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ? + dev_stats->tx_errors : 0) + + (test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode) ? + dev_stats->rx_errors : 0); if (trigger_data->last_activity != new_activity) { led_stop_software_blink(trigger_data->led_cdev); - invert = test_bit(NETDEV_LED_LINK, &trigger_data->mode); + invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); interval = jiffies_to_msecs( atomic_read(&trigger_data->interval)); /* base state is ON (link present) */ @@ -392,13 +687,15 @@ static void netdev_trig_work(struct work_struct *work) static int netdev_trig_activate(struct led_classdev *led_cdev) { struct led_netdev_data *trigger_data; + unsigned long mode = 0; + struct device *dev; int rc; trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); if (!trigger_data) return -ENOMEM; - spin_lock_init(&trigger_data->lock); + mutex_init(&trigger_data->lock); trigger_data->notifier.notifier_call = netdev_trig_notify; trigger_data->notifier.priority = 10; @@ -410,9 +707,26 @@ static int netdev_trig_activate(struct led_classdev *led_cdev) trigger_data->device_name[0] = 0; trigger_data->mode = 0; - atomic_set(&trigger_data->interval, msecs_to_jiffies(50)); + atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL)); trigger_data->last_activity = 0; + /* Check if hw control is active by default on the LED. + * Init already enabled mode in hw control. + */ + if (supports_hw_control(led_cdev)) { + dev = led_cdev->hw_control_get_device(led_cdev); + if (dev) { + const char *name = dev_name(dev); + + trigger_data->hw_control = true; + set_device_name(trigger_data, name, strlen(name)); + + rc = led_cdev->hw_control_get(led_cdev, &mode); + if (!rc) + trigger_data->mode = mode; + } + } + led_set_trigger_data(led_cdev, trigger_data); rc = register_netdevice_notifier(&trigger_data->notifier); @@ -430,8 +744,7 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev) cancel_delayed_work_sync(&trigger_data->work); - if (trigger_data->net_dev) - dev_put(trigger_data->net_dev); + dev_put(trigger_data->net_dev); kfree(trigger_data); } @@ -443,20 +756,10 @@ static struct led_trigger netdev_led_trigger = { .groups = netdev_trig_groups, }; -static int __init netdev_trig_init(void) -{ - return led_trigger_register(&netdev_led_trigger); -} - -static void __exit netdev_trig_exit(void) -{ - led_trigger_unregister(&netdev_led_trigger); -} - -module_init(netdev_trig_init); -module_exit(netdev_trig_exit); +module_led_trigger(netdev_led_trigger); MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>"); MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>"); MODULE_DESCRIPTION("Netdev LED trigger"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ledtrig:netdev"); diff --git a/drivers/leds/trigger/ledtrig-panic.c b/drivers/leds/trigger/ledtrig-panic.c index 64abf2e91608..1d49c1078091 100644 --- a/drivers/leds/trigger/ledtrig-panic.c +++ b/drivers/leds/trigger/ledtrig-panic.c @@ -21,24 +21,15 @@ static struct led_trigger *trigger; */ static void led_trigger_set_panic(struct led_classdev *led_cdev) { - struct led_trigger *trig; + if (led_cdev->trigger) + list_del(&led_cdev->trig_list); + list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); - list_for_each_entry(trig, &trigger_list, next_trig) { - if (strcmp("panic", trig->name)) - continue; - if (led_cdev->trigger) - list_del(&led_cdev->trig_list); - list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); + /* Avoid the delayed blink path */ + led_cdev->blink_delay_on = 0; + led_cdev->blink_delay_off = 0; - /* Avoid the delayed blink path */ - led_cdev->blink_delay_on = 0; - led_cdev->blink_delay_off = 0; - - led_cdev->trigger = trig; - if (trig->activate) - trig->activate(led_cdev); - break; - } + led_cdev->trigger = trigger; } static int led_trigger_panic_notifier(struct notifier_block *nb, @@ -64,10 +55,13 @@ static long led_panic_blink(int state) static int __init ledtrig_panic_init(void) { + led_trigger_register_simple("panic", &trigger); + if (!trigger) + return -ENOMEM; + atomic_notifier_chain_register(&panic_notifier_list, &led_trigger_panic_nb); - led_trigger_register_simple("panic", &trigger); panic_blink = led_panic_blink; return 0; } diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c index 43a265dc4696..9af3c18f14f4 100644 --- a/drivers/leds/trigger/ledtrig-pattern.c +++ b/drivers/leds/trigger/ledtrig-pattern.c @@ -13,6 +13,7 @@ #include <linux/mutex.h> #include <linux/slab.h> #include <linux/timer.h> +#include <linux/hrtimer.h> #define MAX_PATTERNS 1024 /* @@ -21,6 +22,12 @@ */ #define UPDATE_INTERVAL 50 +enum pattern_type { + PATTERN_TYPE_SW, /* Use standard timer for software pattern */ + PATTERN_TYPE_HR, /* Use hrtimer for software pattern */ + PATTERN_TYPE_HW, /* Hardware pattern */ +}; + struct pattern_trig_data { struct led_classdev *led_cdev; struct led_pattern patterns[MAX_PATTERNS]; @@ -32,8 +39,9 @@ struct pattern_trig_data { int last_repeat; int delta_t; bool is_indefinite; - bool is_hw_pattern; + enum pattern_type type; struct timer_list timer; + struct hrtimer hrtimer; }; static void pattern_trig_update_patterns(struct pattern_trig_data *data) @@ -71,10 +79,35 @@ static int pattern_trig_compute_brightness(struct pattern_trig_data *data) return data->curr->brightness - step_brightness; } -static void pattern_trig_timer_function(struct timer_list *t) +static void pattern_trig_timer_start(struct pattern_trig_data *data) +{ + if (data->type == PATTERN_TYPE_HR) { + hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL); + } else { + data->timer.expires = jiffies; + add_timer(&data->timer); + } +} + +static void pattern_trig_timer_cancel(struct pattern_trig_data *data) +{ + if (data->type == PATTERN_TYPE_HR) + hrtimer_cancel(&data->hrtimer); + else + timer_delete_sync(&data->timer); +} + +static void pattern_trig_timer_restart(struct pattern_trig_data *data, + unsigned long interval) { - struct pattern_trig_data *data = from_timer(data, t, timer); + if (data->type == PATTERN_TYPE_HR) + hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval)); + else + mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval)); +} +static void pattern_trig_timer_common_function(struct pattern_trig_data *data) +{ for (;;) { if (!data->is_indefinite && !data->repeat) break; @@ -83,8 +116,7 @@ static void pattern_trig_timer_function(struct timer_list *t) /* Step change of brightness */ led_set_brightness(data->led_cdev, data->curr->brightness); - mod_timer(&data->timer, - jiffies + msecs_to_jiffies(data->curr->delta_t)); + pattern_trig_timer_restart(data, data->curr->delta_t); if (!data->next->delta_t) { /* Skip the tuple with zero duration */ pattern_trig_update_patterns(data); @@ -106,8 +138,7 @@ static void pattern_trig_timer_function(struct timer_list *t) led_set_brightness(data->led_cdev, pattern_trig_compute_brightness(data)); - mod_timer(&data->timer, - jiffies + msecs_to_jiffies(UPDATE_INTERVAL)); + pattern_trig_timer_restart(data, UPDATE_INTERVAL); /* Accumulate the gradual dimming time */ data->delta_t += UPDATE_INTERVAL; @@ -117,6 +148,25 @@ static void pattern_trig_timer_function(struct timer_list *t) } } +static void pattern_trig_timer_function(struct timer_list *t) +{ + struct pattern_trig_data *data = timer_container_of(data, t, timer); + + return pattern_trig_timer_common_function(data); +} + +static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t) +{ + struct pattern_trig_data *data = + container_of(t, struct pattern_trig_data, hrtimer); + + pattern_trig_timer_common_function(data); + if (!data->is_indefinite && !data->repeat) + return HRTIMER_NORESTART; + + return HRTIMER_RESTART; +} + static int pattern_trig_start_pattern(struct led_classdev *led_cdev) { struct pattern_trig_data *data = led_cdev->trigger_data; @@ -124,7 +174,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev) if (!data->npatterns) return 0; - if (data->is_hw_pattern) { + if (data->type == PATTERN_TYPE_HW) { return led_cdev->pattern_set(led_cdev, data->patterns, data->npatterns, data->repeat); } @@ -136,8 +186,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev) data->delta_t = 0; data->curr = data->patterns; data->next = data->patterns + 1; - data->timer.expires = jiffies; - add_timer(&data->timer); + pattern_trig_timer_start(data); return 0; } @@ -155,7 +204,7 @@ static ssize_t repeat_show(struct device *dev, struct device_attribute *attr, mutex_unlock(&data->lock); - return scnprintf(buf, PAGE_SIZE, "%d\n", repeat); + return sysfs_emit(buf, "%d\n", repeat); } static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, @@ -175,9 +224,9 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, mutex_lock(&data->lock); - del_timer_sync(&data->timer); + pattern_trig_timer_cancel(data); - if (data->is_hw_pattern) + if (data->type == PATTERN_TYPE_HW) led_cdev->pattern_clear(led_cdev); data->last_repeat = data->repeat = res; @@ -196,14 +245,14 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(repeat); static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, - char *buf, bool hw_pattern) + char *buf, enum pattern_type type) { ssize_t count = 0; int i; mutex_lock(&data->lock); - if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern)) + if (!data->npatterns || data->type != type) goto out; for (i = 0; i < data->npatterns; i++) { @@ -260,19 +309,19 @@ static int pattern_trig_store_patterns_int(struct pattern_trig_data *data, static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, const char *buf, const u32 *buf_int, - size_t count, bool hw_pattern) + size_t count, enum pattern_type type) { struct pattern_trig_data *data = led_cdev->trigger_data; int err = 0; mutex_lock(&data->lock); - del_timer_sync(&data->timer); + pattern_trig_timer_cancel(data); - if (data->is_hw_pattern) + if (data->type == PATTERN_TYPE_HW) led_cdev->pattern_clear(led_cdev); - data->is_hw_pattern = hw_pattern; + data->type = type; data->npatterns = 0; if (buf) @@ -297,7 +346,7 @@ static ssize_t pattern_show(struct device *dev, struct device_attribute *attr, struct led_classdev *led_cdev = dev_get_drvdata(dev); struct pattern_trig_data *data = led_cdev->trigger_data; - return pattern_trig_show_patterns(data, buf, false); + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW); } static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, @@ -305,7 +354,8 @@ static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, { struct led_classdev *led_cdev = dev_get_drvdata(dev); - return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false); + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, + PATTERN_TYPE_SW); } static DEVICE_ATTR_RW(pattern); @@ -316,7 +366,7 @@ static ssize_t hw_pattern_show(struct device *dev, struct led_classdev *led_cdev = dev_get_drvdata(dev); struct pattern_trig_data *data = led_cdev->trigger_data; - return pattern_trig_show_patterns(data, buf, true); + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW); } static ssize_t hw_pattern_store(struct device *dev, @@ -325,11 +375,33 @@ static ssize_t hw_pattern_store(struct device *dev, { struct led_classdev *led_cdev = dev_get_drvdata(dev); - return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true); + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, + PATTERN_TYPE_HW); } static DEVICE_ATTR_RW(hw_pattern); +static ssize_t hr_pattern_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct pattern_trig_data *data = led_cdev->trigger_data; + + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR); +} + +static ssize_t hr_pattern_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, + PATTERN_TYPE_HR); +} + +static DEVICE_ATTR_RW(hr_pattern); + static umode_t pattern_trig_attrs_mode(struct kobject *kobj, struct attribute *attr, int index) { @@ -338,6 +410,8 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj, if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr) return attr->mode; + else if (attr == &dev_attr_hr_pattern.attr) + return attr->mode; else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set) return attr->mode; @@ -347,6 +421,7 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj, static struct attribute *pattern_trig_attrs[] = { &dev_attr_pattern.attr, &dev_attr_hw_pattern.attr, + &dev_attr_hr_pattern.attr, &dev_attr_repeat.attr, NULL }; @@ -376,7 +451,8 @@ static void pattern_init(struct led_classdev *led_cdev) goto out; } - err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false); + err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, + PATTERN_TYPE_SW); if (err < 0) dev_warn(led_cdev->dev, "Pattern initialization failed with error %d\n", err); @@ -400,12 +476,15 @@ static int pattern_trig_activate(struct led_classdev *led_cdev) led_cdev->pattern_clear = NULL; } + data->type = PATTERN_TYPE_SW; data->is_indefinite = true; data->last_repeat = -1; mutex_init(&data->lock); data->led_cdev = led_cdev; led_set_trigger_data(led_cdev, data); timer_setup(&data->timer, pattern_trig_timer_function, 0); + hrtimer_setup(&data->hrtimer, pattern_trig_hrtimer_function, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); led_cdev->activated = true; if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { @@ -430,7 +509,8 @@ static void pattern_trig_deactivate(struct led_classdev *led_cdev) if (led_cdev->pattern_clear) led_cdev->pattern_clear(led_cdev); - del_timer_sync(&data->timer); + timer_shutdown_sync(&data->timer); + hrtimer_cancel(&data->hrtimer); led_set_brightness(led_cdev, LED_OFF); kfree(data); diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c index b4688d1d9d2b..1d213c999d40 100644 --- a/drivers/leds/trigger/ledtrig-timer.c +++ b/drivers/leds/trigger/ledtrig-timer.c @@ -110,11 +110,6 @@ static int timer_trig_activate(struct led_classdev *led_cdev) led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; } - /* - * If "set brightness to 0" is pending in workqueue, we don't - * want that to be reordered after blink_set() - */ - flush_work(&led_cdev->set_brightness_work); led_blink_set(led_cdev, &led_cdev->blink_delay_on, &led_cdev->blink_delay_off); diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c index 80635183fac8..20f1351464b1 100644 --- a/drivers/leds/trigger/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -32,7 +32,7 @@ struct transient_trig_data { static void transient_timer_function(struct timer_list *t) { struct transient_trig_data *transient_data = - from_timer(transient_data, t, timer); + timer_container_of(transient_data, t, timer); struct led_classdev *led_cdev = transient_data->led_cdev; transient_data->activate = 0; @@ -66,7 +66,7 @@ static ssize_t transient_activate_store(struct device *dev, /* cancel the running timer */ if (state == 0 && transient_data->activate == 1) { - del_timer(&transient_data->timer); + timer_delete(&transient_data->timer); transient_data->activate = state; led_set_brightness_nosleep(led_cdev, transient_data->restore_state); @@ -180,7 +180,7 @@ static void transient_trig_deactivate(struct led_classdev *led_cdev) { struct transient_trig_data *transient_data = led_get_trigger_data(led_cdev); - del_timer_sync(&transient_data->timer); + timer_shutdown_sync(&transient_data->timer); led_set_brightness_nosleep(led_cdev, transient_data->restore_state); kfree(transient_data); } diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c index f62db7e520b5..8cf1485e8165 100644 --- a/drivers/leds/trigger/ledtrig-tty.c +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include <linux/completion.h> #include <linux/delay.h> #include <linux/leds.h> #include <linux/module.h> @@ -7,18 +8,50 @@ #include <linux/tty.h> #include <uapi/linux/serial.h> +#define LEDTRIG_TTY_INTERVAL 50 + struct ledtrig_tty_data { struct led_classdev *led_cdev; struct delayed_work dwork; - struct mutex mutex; + struct completion sysfs; const char *ttyname; struct tty_struct *tty; int rx, tx; + bool mode_rx; + bool mode_tx; + bool mode_cts; + bool mode_dsr; + bool mode_dcd; + bool mode_rng; +}; + +/* Indicates which state the LED should now display */ +enum led_trigger_tty_state { + TTY_LED_BLINK, + TTY_LED_ENABLE, + TTY_LED_DISABLE, }; -static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data) +enum led_trigger_tty_modes { + TRIGGER_TTY_RX = 0, + TRIGGER_TTY_TX, + TRIGGER_TTY_CTS, + TRIGGER_TTY_DSR, + TRIGGER_TTY_DCD, + TRIGGER_TTY_RNG, +}; + +static int ledtrig_tty_wait_for_completion(struct device *dev) { - schedule_delayed_work(&trigger_data->dwork, 0); + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + int ret; + + ret = wait_for_completion_timeout(&trigger_data->sysfs, + msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20)); + if (ret == 0) + return -ETIMEDOUT; + + return ret; } static ssize_t ttyname_show(struct device *dev, @@ -26,14 +59,16 @@ static ssize_t ttyname_show(struct device *dev, { struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); ssize_t len = 0; + int completion; - mutex_lock(&trigger_data->mutex); + reinit_completion(&trigger_data->sysfs); + completion = ledtrig_tty_wait_for_completion(dev); + if (completion < 0) + return completion; if (trigger_data->ttyname) len = sprintf(buf, "%s\n", trigger_data->ttyname); - mutex_unlock(&trigger_data->mutex); - return len; } @@ -44,7 +79,7 @@ static ssize_t ttyname_store(struct device *dev, struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); char *ttyname; ssize_t ret = size; - bool running; + int completion; if (size > 0 && buf[size - 1] == '\n') size -= 1; @@ -57,9 +92,10 @@ static ssize_t ttyname_store(struct device *dev, ttyname = NULL; } - mutex_lock(&trigger_data->mutex); - - running = trigger_data->ttyname != NULL; + reinit_completion(&trigger_data->sysfs); + completion = ledtrig_tty_wait_for_completion(dev); + if (completion < 0) + return completion; kfree(trigger_data->ttyname); tty_kref_put(trigger_data->tty); @@ -67,29 +103,107 @@ static ssize_t ttyname_store(struct device *dev, trigger_data->ttyname = ttyname; - mutex_unlock(&trigger_data->mutex); - - if (ttyname && !running) - ledtrig_tty_restart(trigger_data); - return ret; } static DEVICE_ATTR_RW(ttyname); +static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf, + enum led_trigger_tty_modes attr) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + bool state; + + switch (attr) { + case TRIGGER_TTY_RX: + state = trigger_data->mode_rx; + break; + case TRIGGER_TTY_TX: + state = trigger_data->mode_tx; + break; + case TRIGGER_TTY_CTS: + state = trigger_data->mode_cts; + break; + case TRIGGER_TTY_DSR: + state = trigger_data->mode_dsr; + break; + case TRIGGER_TTY_DCD: + state = trigger_data->mode_dcd; + break; + case TRIGGER_TTY_RNG: + state = trigger_data->mode_rng; + break; + } + + return sysfs_emit(buf, "%u\n", state); +} + +static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf, + size_t size, enum led_trigger_tty_modes attr) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + switch (attr) { + case TRIGGER_TTY_RX: + trigger_data->mode_rx = state; + break; + case TRIGGER_TTY_TX: + trigger_data->mode_tx = state; + break; + case TRIGGER_TTY_CTS: + trigger_data->mode_cts = state; + break; + case TRIGGER_TTY_DSR: + trigger_data->mode_dsr = state; + break; + case TRIGGER_TTY_DCD: + trigger_data->mode_dcd = state; + break; + case TRIGGER_TTY_RNG: + trigger_data->mode_rng = state; + break; + } + + return size; +} + +#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \ + static ssize_t trigger_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return ledtrig_tty_attr_show(dev, buf, trigger); \ + } \ + static ssize_t trigger_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t size) \ + { \ + return ledtrig_tty_attr_store(dev, buf, size, trigger); \ + } \ + static DEVICE_ATTR_RW(trigger_name) + +DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX); +DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX); +DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS); +DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR); +DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD); +DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG); + static void ledtrig_tty_work(struct work_struct *work) { struct ledtrig_tty_data *trigger_data = container_of(work, struct ledtrig_tty_data, dwork.work); - struct serial_icounter_struct icount; + enum led_trigger_tty_state state = TTY_LED_DISABLE; + unsigned long interval = LEDTRIG_TTY_INTERVAL; + bool invert = false; + int status; int ret; - mutex_lock(&trigger_data->mutex); - - if (!trigger_data->ttyname) { - /* exit without rescheduling */ - mutex_unlock(&trigger_data->mutex); - return; - } + if (!trigger_data->ttyname) + goto out; /* try to get the tty corresponding to $ttyname */ if (!trigger_data->tty) { @@ -113,30 +227,83 @@ static void ledtrig_tty_work(struct work_struct *work) trigger_data->tty = tty; } - ret = tty_get_icount(trigger_data->tty, &icount); - if (ret) { - dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n"); - mutex_unlock(&trigger_data->mutex); - return; + status = tty_get_tiocm(trigger_data->tty); + if (status > 0) { + if (trigger_data->mode_cts) { + if (status & TIOCM_CTS) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_dsr) { + if (status & TIOCM_DSR) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_dcd) { + if (status & TIOCM_CAR) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_rng) { + if (status & TIOCM_RNG) + state = TTY_LED_ENABLE; + } } - if (icount.rx != trigger_data->rx || - icount.tx != trigger_data->tx) { - led_set_brightness_sync(trigger_data->led_cdev, LED_ON); + /* + * The evaluation of rx/tx must be done after the evaluation + * of TIOCM_*, because rx/tx has priority. + */ + if (trigger_data->mode_rx || trigger_data->mode_tx) { + struct serial_icounter_struct icount; - trigger_data->rx = icount.rx; - trigger_data->tx = icount.tx; - } else { - led_set_brightness_sync(trigger_data->led_cdev, LED_OFF); + ret = tty_get_icount(trigger_data->tty, &icount); + if (ret) + goto out; + + if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) { + trigger_data->tx = icount.tx; + invert = state == TTY_LED_ENABLE; + state = TTY_LED_BLINK; + } + + if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) { + trigger_data->rx = icount.rx; + invert = state == TTY_LED_ENABLE; + state = TTY_LED_BLINK; + } } out: - mutex_unlock(&trigger_data->mutex); - schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100)); + switch (state) { + case TTY_LED_BLINK: + led_blink_set_oneshot(trigger_data->led_cdev, &interval, + &interval, invert); + break; + case TTY_LED_ENABLE: + led_set_brightness(trigger_data->led_cdev, + trigger_data->led_cdev->blink_brightness); + break; + case TTY_LED_DISABLE: + fallthrough; + default: + led_set_brightness(trigger_data->led_cdev, LED_OFF); + break; + } + + complete_all(&trigger_data->sysfs); + schedule_delayed_work(&trigger_data->dwork, + msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2)); } static struct attribute *ledtrig_tty_attrs[] = { &dev_attr_ttyname.attr, + &dev_attr_rx.attr, + &dev_attr_tx.attr, + &dev_attr_cts.attr, + &dev_attr_dsr.attr, + &dev_attr_dcd.attr, + &dev_attr_rng.attr, NULL }; ATTRIBUTE_GROUPS(ledtrig_tty); @@ -149,11 +316,17 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev) if (!trigger_data) return -ENOMEM; + /* Enable default rx/tx mode */ + trigger_data->mode_rx = true; + trigger_data->mode_tx = true; + led_set_trigger_data(led_cdev, trigger_data); INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); trigger_data->led_cdev = led_cdev; - mutex_init(&trigger_data->mutex); + init_completion(&trigger_data->sysfs); + + schedule_delayed_work(&trigger_data->dwork, 0); return 0; } @@ -164,6 +337,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev) cancel_delayed_work_sync(&trigger_data->dwork); + kfree(trigger_data->ttyname); + tty_kref_put(trigger_data->tty); + trigger_data->tty = NULL; + kfree(trigger_data); } |
