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

leds: trigger: implement a tty trigger

Usage is as follows:

myled=ledname
tty=ttyS0

echo tty > /sys/class/leds/$myled/trigger
echo $tty > /sys/class/leds/$myled/ttyname

. When this new trigger is active it periodically checks the tty's
statistics and when it changed since the last check the led is flashed
once.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Link: https://lore.kernel.org/r/20210113173018.bq2fkea2o3yp6rf6@pengutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Uwe Kleine-König and committed by
Greg Kroah-Hartman
fd4a641a 63e34e70

+199
+6
Documentation/ABI/testing/sysfs-class-led-trigger-tty
··· 1 + What: /sys/class/leds/<led>/ttyname 2 + Date: Dec 2020 3 + KernelVersion: 5.10 4 + Contact: linux-leds@vger.kernel.org 5 + Description: 6 + Specifies the tty device name of the triggering tty
+9
drivers/leds/trigger/Kconfig
··· 144 144 the audio mute and mic-mute changes. 145 145 If unsure, say N 146 146 147 + config LEDS_TRIGGER_TTY 148 + tristate "LED Trigger for TTY devices" 149 + depends on TTY 150 + help 151 + This allows LEDs to be controlled by activity on ttys which includes 152 + serial devices like /dev/ttyS0. 153 + 154 + When build as a module this driver will be called ledtrig-tty. 155 + 147 156 endif # LEDS_TRIGGERS
+1
drivers/leds/trigger/Makefile
··· 15 15 obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o 16 16 obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o 17 17 obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o 18 + obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o
+183
drivers/leds/trigger/ledtrig-tty.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + #include <linux/delay.h> 4 + #include <linux/leds.h> 5 + #include <linux/module.h> 6 + #include <linux/slab.h> 7 + #include <linux/tty.h> 8 + #include <uapi/linux/serial.h> 9 + 10 + struct ledtrig_tty_data { 11 + struct led_classdev *led_cdev; 12 + struct delayed_work dwork; 13 + struct mutex mutex; 14 + const char *ttyname; 15 + struct tty_struct *tty; 16 + int rx, tx; 17 + }; 18 + 19 + static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data) 20 + { 21 + schedule_delayed_work(&trigger_data->dwork, 0); 22 + } 23 + 24 + static ssize_t ttyname_show(struct device *dev, 25 + struct device_attribute *attr, char *buf) 26 + { 27 + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); 28 + ssize_t len = 0; 29 + 30 + mutex_lock(&trigger_data->mutex); 31 + 32 + if (trigger_data->ttyname) 33 + len = sprintf(buf, "%s\n", trigger_data->ttyname); 34 + 35 + mutex_unlock(&trigger_data->mutex); 36 + 37 + return len; 38 + } 39 + 40 + static ssize_t ttyname_store(struct device *dev, 41 + struct device_attribute *attr, const char *buf, 42 + size_t size) 43 + { 44 + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); 45 + char *ttyname; 46 + ssize_t ret = size; 47 + bool running; 48 + 49 + if (size > 0 && buf[size - 1] == '\n') 50 + size -= 1; 51 + 52 + if (size) { 53 + ttyname = kmemdup_nul(buf, size, GFP_KERNEL); 54 + if (!ttyname) { 55 + ret = -ENOMEM; 56 + goto out_unlock; 57 + } 58 + } else { 59 + ttyname = NULL; 60 + } 61 + 62 + mutex_lock(&trigger_data->mutex); 63 + 64 + running = trigger_data->ttyname != NULL; 65 + 66 + kfree(trigger_data->ttyname); 67 + tty_kref_put(trigger_data->tty); 68 + trigger_data->tty = NULL; 69 + 70 + trigger_data->ttyname = ttyname; 71 + 72 + out_unlock: 73 + mutex_unlock(&trigger_data->mutex); 74 + 75 + if (ttyname && !running) 76 + ledtrig_tty_restart(trigger_data); 77 + 78 + return ret; 79 + } 80 + static DEVICE_ATTR_RW(ttyname); 81 + 82 + static void ledtrig_tty_work(struct work_struct *work) 83 + { 84 + struct ledtrig_tty_data *trigger_data = 85 + container_of(work, struct ledtrig_tty_data, dwork.work); 86 + struct serial_icounter_struct icount; 87 + int ret; 88 + 89 + mutex_lock(&trigger_data->mutex); 90 + 91 + if (!trigger_data->ttyname) { 92 + /* exit without rescheduling */ 93 + mutex_unlock(&trigger_data->mutex); 94 + return; 95 + } 96 + 97 + /* try to get the tty corresponding to $ttyname */ 98 + if (!trigger_data->tty) { 99 + dev_t devno; 100 + struct tty_struct *tty; 101 + int ret; 102 + 103 + ret = tty_dev_name_to_number(trigger_data->ttyname, &devno); 104 + if (ret < 0) 105 + /* 106 + * A device with this name might appear later, so keep 107 + * retrying. 108 + */ 109 + goto out; 110 + 111 + tty = tty_kopen_shared(devno); 112 + if (IS_ERR(tty) || !tty) 113 + /* What to do? retry or abort */ 114 + goto out; 115 + 116 + trigger_data->tty = tty; 117 + } 118 + 119 + ret = tty_get_icount(trigger_data->tty, &icount); 120 + if (ret) { 121 + dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n"); 122 + mutex_unlock(&trigger_data->mutex); 123 + return; 124 + } 125 + 126 + if (icount.rx != trigger_data->rx || 127 + icount.tx != trigger_data->tx) { 128 + led_set_brightness(trigger_data->led_cdev, LED_ON); 129 + 130 + trigger_data->rx = icount.rx; 131 + trigger_data->tx = icount.tx; 132 + } else { 133 + led_set_brightness(trigger_data->led_cdev, LED_OFF); 134 + } 135 + 136 + out: 137 + mutex_unlock(&trigger_data->mutex); 138 + schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100)); 139 + } 140 + 141 + static struct attribute *ledtrig_tty_attrs[] = { 142 + &dev_attr_ttyname.attr, 143 + NULL 144 + }; 145 + ATTRIBUTE_GROUPS(ledtrig_tty); 146 + 147 + static int ledtrig_tty_activate(struct led_classdev *led_cdev) 148 + { 149 + struct ledtrig_tty_data *trigger_data; 150 + 151 + trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL); 152 + if (!trigger_data) 153 + return -ENOMEM; 154 + 155 + led_set_trigger_data(led_cdev, trigger_data); 156 + 157 + INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); 158 + trigger_data->led_cdev = led_cdev; 159 + mutex_init(&trigger_data->mutex); 160 + 161 + return 0; 162 + } 163 + 164 + static void ledtrig_tty_deactivate(struct led_classdev *led_cdev) 165 + { 166 + struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev); 167 + 168 + cancel_delayed_work_sync(&trigger_data->dwork); 169 + 170 + kfree(trigger_data); 171 + } 172 + 173 + static struct led_trigger ledtrig_tty = { 174 + .name = "tty", 175 + .activate = ledtrig_tty_activate, 176 + .deactivate = ledtrig_tty_deactivate, 177 + .groups = ledtrig_tty_groups, 178 + }; 179 + module_led_trigger(ledtrig_tty); 180 + 181 + MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>"); 182 + MODULE_DESCRIPTION("UART LED trigger"); 183 + MODULE_LICENSE("GPL v2");