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 #define LEDTRIG_TTY_INTERVAL 50
11
12 struct ledtrig_tty_data {
13 struct led_classdev *led_cdev;
14 struct delayed_work dwork;
15 struct mutex mutex;
16 const char *ttyname;
17 struct tty_struct *tty;
18 int rx, tx;
19 };
20
ledtrig_tty_restart(struct ledtrig_tty_data * trigger_data)21 static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
22 {
23 schedule_delayed_work(&trigger_data->dwork, 0);
24 }
25
ttyname_show(struct device * dev,struct device_attribute * attr,char * buf)26 static ssize_t ttyname_show(struct device *dev,
27 struct device_attribute *attr, char *buf)
28 {
29 struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
30 ssize_t len = 0;
31
32 mutex_lock(&trigger_data->mutex);
33
34 if (trigger_data->ttyname)
35 len = sprintf(buf, "%s\n", trigger_data->ttyname);
36
37 mutex_unlock(&trigger_data->mutex);
38
39 return len;
40 }
41
ttyname_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)42 static ssize_t ttyname_store(struct device *dev,
43 struct device_attribute *attr, const char *buf,
44 size_t size)
45 {
46 struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
47 char *ttyname;
48 ssize_t ret = size;
49 bool running;
50
51 if (size > 0 && buf[size - 1] == '\n')
52 size -= 1;
53
54 if (size) {
55 ttyname = kmemdup_nul(buf, size, GFP_KERNEL);
56 if (!ttyname)
57 return -ENOMEM;
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 mutex_unlock(&trigger_data->mutex);
73
74 if (ttyname && !running)
75 ledtrig_tty_restart(trigger_data);
76
77 return ret;
78 }
79 static DEVICE_ATTR_RW(ttyname);
80
ledtrig_tty_work(struct work_struct * work)81 static void ledtrig_tty_work(struct work_struct *work)
82 {
83 struct ledtrig_tty_data *trigger_data =
84 container_of(work, struct ledtrig_tty_data, dwork.work);
85 struct serial_icounter_struct icount;
86 int ret;
87
88 mutex_lock(&trigger_data->mutex);
89
90 if (!trigger_data->ttyname) {
91 /* exit without rescheduling */
92 mutex_unlock(&trigger_data->mutex);
93 return;
94 }
95
96 /* try to get the tty corresponding to $ttyname */
97 if (!trigger_data->tty) {
98 dev_t devno;
99 struct tty_struct *tty;
100 int ret;
101
102 ret = tty_dev_name_to_number(trigger_data->ttyname, &devno);
103 if (ret < 0)
104 /*
105 * A device with this name might appear later, so keep
106 * retrying.
107 */
108 goto out;
109
110 tty = tty_kopen_shared(devno);
111 if (IS_ERR(tty) || !tty)
112 /* What to do? retry or abort */
113 goto out;
114
115 trigger_data->tty = tty;
116 }
117
118 ret = tty_get_icount(trigger_data->tty, &icount);
119 if (ret) {
120 dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
121 mutex_unlock(&trigger_data->mutex);
122 return;
123 }
124
125 if (icount.rx != trigger_data->rx ||
126 icount.tx != trigger_data->tx) {
127 unsigned long interval = LEDTRIG_TTY_INTERVAL;
128
129 led_blink_set_oneshot(trigger_data->led_cdev, &interval,
130 &interval, 0);
131
132 trigger_data->rx = icount.rx;
133 trigger_data->tx = icount.tx;
134 }
135
136 out:
137 mutex_unlock(&trigger_data->mutex);
138 schedule_delayed_work(&trigger_data->dwork,
139 msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
140 }
141
142 static struct attribute *ledtrig_tty_attrs[] = {
143 &dev_attr_ttyname.attr,
144 NULL
145 };
146 ATTRIBUTE_GROUPS(ledtrig_tty);
147
ledtrig_tty_activate(struct led_classdev * led_cdev)148 static int ledtrig_tty_activate(struct led_classdev *led_cdev)
149 {
150 struct ledtrig_tty_data *trigger_data;
151
152 trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
153 if (!trigger_data)
154 return -ENOMEM;
155
156 led_set_trigger_data(led_cdev, trigger_data);
157
158 INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
159 trigger_data->led_cdev = led_cdev;
160 mutex_init(&trigger_data->mutex);
161
162 return 0;
163 }
164
ledtrig_tty_deactivate(struct led_classdev * led_cdev)165 static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
166 {
167 struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev);
168
169 cancel_delayed_work_sync(&trigger_data->dwork);
170
171 kfree(trigger_data->ttyname);
172 tty_kref_put(trigger_data->tty);
173 trigger_data->tty = NULL;
174
175 kfree(trigger_data);
176 }
177
178 static struct led_trigger ledtrig_tty = {
179 .name = "tty",
180 .activate = ledtrig_tty_activate,
181 .deactivate = ledtrig_tty_deactivate,
182 .groups = ledtrig_tty_groups,
183 };
184 module_led_trigger(ledtrig_tty);
185
186 MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>");
187 MODULE_DESCRIPTION("UART LED trigger");
188 MODULE_LICENSE("GPL v2");
189