Skip to content

Commit fd4a641

Browse files
Uwe Kleine-Königgregkh
Uwe Kleine-König
authored andcommitted
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 <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 63e34e7 commit fd4a641

File tree

4 files changed

+199
-0
lines changed

4 files changed

+199
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
What: /sys/class/leds/<led>/ttyname
2+
Date: Dec 2020
3+
KernelVersion: 5.10
4+
5+
Description:
6+
Specifies the tty device name of the triggering tty

drivers/leds/trigger/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,13 @@ config LEDS_TRIGGER_AUDIO
144144
the audio mute and mic-mute changes.
145145
If unsure, say N
146146

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+
147156
endif # LEDS_TRIGGERS

drivers/leds/trigger/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o
1515
obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o
1616
obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o
1717
obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o
18+
obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o

drivers/leds/trigger/ledtrig-tty.c

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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 <[email protected]>");
182+
MODULE_DESCRIPTION("UART LED trigger");
183+
MODULE_LICENSE("GPL v2");

0 commit comments

Comments
 (0)