1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/completion.h> |
4 | #include <linux/delay.h> |
5 | #include <linux/leds.h> |
6 | #include <linux/module.h> |
7 | #include <linux/slab.h> |
8 | #include <linux/tty.h> |
9 | #include <uapi/linux/serial.h> |
10 | |
11 | #define LEDTRIG_TTY_INTERVAL 50 |
12 | |
13 | struct ledtrig_tty_data { |
14 | struct led_classdev *led_cdev; |
15 | struct delayed_work dwork; |
16 | struct completion sysfs; |
17 | const char *ttyname; |
18 | struct tty_struct *tty; |
19 | int rx, tx; |
20 | bool mode_rx; |
21 | bool mode_tx; |
22 | bool mode_cts; |
23 | bool mode_dsr; |
24 | bool mode_dcd; |
25 | bool mode_rng; |
26 | }; |
27 | |
28 | /* Indicates which state the LED should now display */ |
29 | enum led_trigger_tty_state { |
30 | TTY_LED_BLINK, |
31 | TTY_LED_ENABLE, |
32 | TTY_LED_DISABLE, |
33 | }; |
34 | |
35 | enum led_trigger_tty_modes { |
36 | TRIGGER_TTY_RX = 0, |
37 | TRIGGER_TTY_TX, |
38 | TRIGGER_TTY_CTS, |
39 | TRIGGER_TTY_DSR, |
40 | TRIGGER_TTY_DCD, |
41 | TRIGGER_TTY_RNG, |
42 | }; |
43 | |
44 | static int ledtrig_tty_wait_for_completion(struct device *dev) |
45 | { |
46 | struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); |
47 | int ret; |
48 | |
49 | ret = wait_for_completion_timeout(x: &trigger_data->sysfs, |
50 | timeout: msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20)); |
51 | if (ret == 0) |
52 | return -ETIMEDOUT; |
53 | |
54 | return ret; |
55 | } |
56 | |
57 | static ssize_t ttyname_show(struct device *dev, |
58 | struct device_attribute *attr, char *buf) |
59 | { |
60 | struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); |
61 | ssize_t len = 0; |
62 | int completion; |
63 | |
64 | reinit_completion(x: &trigger_data->sysfs); |
65 | completion = ledtrig_tty_wait_for_completion(dev); |
66 | if (completion < 0) |
67 | return completion; |
68 | |
69 | if (trigger_data->ttyname) |
70 | len = sprintf(buf, fmt: "%s\n" , trigger_data->ttyname); |
71 | |
72 | return len; |
73 | } |
74 | |
75 | static ssize_t ttyname_store(struct device *dev, |
76 | struct device_attribute *attr, const char *buf, |
77 | size_t size) |
78 | { |
79 | struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); |
80 | char *ttyname; |
81 | ssize_t ret = size; |
82 | int completion; |
83 | |
84 | if (size > 0 && buf[size - 1] == '\n') |
85 | size -= 1; |
86 | |
87 | if (size) { |
88 | ttyname = kmemdup_nul(s: buf, len: size, GFP_KERNEL); |
89 | if (!ttyname) |
90 | return -ENOMEM; |
91 | } else { |
92 | ttyname = NULL; |
93 | } |
94 | |
95 | reinit_completion(x: &trigger_data->sysfs); |
96 | completion = ledtrig_tty_wait_for_completion(dev); |
97 | if (completion < 0) |
98 | return completion; |
99 | |
100 | kfree(objp: trigger_data->ttyname); |
101 | tty_kref_put(tty: trigger_data->tty); |
102 | trigger_data->tty = NULL; |
103 | |
104 | trigger_data->ttyname = ttyname; |
105 | |
106 | return ret; |
107 | } |
108 | static DEVICE_ATTR_RW(ttyname); |
109 | |
110 | static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf, |
111 | enum led_trigger_tty_modes attr) |
112 | { |
113 | struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); |
114 | bool state; |
115 | |
116 | switch (attr) { |
117 | case TRIGGER_TTY_RX: |
118 | state = trigger_data->mode_rx; |
119 | break; |
120 | case TRIGGER_TTY_TX: |
121 | state = trigger_data->mode_tx; |
122 | break; |
123 | case TRIGGER_TTY_CTS: |
124 | state = trigger_data->mode_cts; |
125 | break; |
126 | case TRIGGER_TTY_DSR: |
127 | state = trigger_data->mode_dsr; |
128 | break; |
129 | case TRIGGER_TTY_DCD: |
130 | state = trigger_data->mode_dcd; |
131 | break; |
132 | case TRIGGER_TTY_RNG: |
133 | state = trigger_data->mode_rng; |
134 | break; |
135 | } |
136 | |
137 | return sysfs_emit(buf, fmt: "%u\n" , state); |
138 | } |
139 | |
140 | static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf, |
141 | size_t size, enum led_trigger_tty_modes attr) |
142 | { |
143 | struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); |
144 | bool state; |
145 | int ret; |
146 | |
147 | ret = kstrtobool(s: buf, res: &state); |
148 | if (ret) |
149 | return ret; |
150 | |
151 | switch (attr) { |
152 | case TRIGGER_TTY_RX: |
153 | trigger_data->mode_rx = state; |
154 | break; |
155 | case TRIGGER_TTY_TX: |
156 | trigger_data->mode_tx = state; |
157 | break; |
158 | case TRIGGER_TTY_CTS: |
159 | trigger_data->mode_cts = state; |
160 | break; |
161 | case TRIGGER_TTY_DSR: |
162 | trigger_data->mode_dsr = state; |
163 | break; |
164 | case TRIGGER_TTY_DCD: |
165 | trigger_data->mode_dcd = state; |
166 | break; |
167 | case TRIGGER_TTY_RNG: |
168 | trigger_data->mode_rng = state; |
169 | break; |
170 | } |
171 | |
172 | return size; |
173 | } |
174 | |
175 | #define DEFINE_TTY_TRIGGER(trigger_name, trigger) \ |
176 | static ssize_t trigger_name##_show(struct device *dev, \ |
177 | struct device_attribute *attr, char *buf) \ |
178 | { \ |
179 | return ledtrig_tty_attr_show(dev, buf, trigger); \ |
180 | } \ |
181 | static ssize_t trigger_name##_store(struct device *dev, \ |
182 | struct device_attribute *attr, const char *buf, size_t size) \ |
183 | { \ |
184 | return ledtrig_tty_attr_store(dev, buf, size, trigger); \ |
185 | } \ |
186 | static DEVICE_ATTR_RW(trigger_name) |
187 | |
188 | DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX); |
189 | DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX); |
190 | DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS); |
191 | DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR); |
192 | DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD); |
193 | DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG); |
194 | |
195 | static void ledtrig_tty_work(struct work_struct *work) |
196 | { |
197 | struct ledtrig_tty_data *trigger_data = |
198 | container_of(work, struct ledtrig_tty_data, dwork.work); |
199 | enum led_trigger_tty_state state = TTY_LED_DISABLE; |
200 | unsigned long interval = LEDTRIG_TTY_INTERVAL; |
201 | bool invert = false; |
202 | int status; |
203 | int ret; |
204 | |
205 | if (!trigger_data->ttyname) |
206 | goto out; |
207 | |
208 | /* try to get the tty corresponding to $ttyname */ |
209 | if (!trigger_data->tty) { |
210 | dev_t devno; |
211 | struct tty_struct *tty; |
212 | int ret; |
213 | |
214 | ret = tty_dev_name_to_number(name: trigger_data->ttyname, number: &devno); |
215 | if (ret < 0) |
216 | /* |
217 | * A device with this name might appear later, so keep |
218 | * retrying. |
219 | */ |
220 | goto out; |
221 | |
222 | tty = tty_kopen_shared(device: devno); |
223 | if (IS_ERR(ptr: tty) || !tty) |
224 | /* What to do? retry or abort */ |
225 | goto out; |
226 | |
227 | trigger_data->tty = tty; |
228 | } |
229 | |
230 | status = tty_get_tiocm(tty: trigger_data->tty); |
231 | if (status > 0) { |
232 | if (trigger_data->mode_cts) { |
233 | if (status & TIOCM_CTS) |
234 | state = TTY_LED_ENABLE; |
235 | } |
236 | |
237 | if (trigger_data->mode_dsr) { |
238 | if (status & TIOCM_DSR) |
239 | state = TTY_LED_ENABLE; |
240 | } |
241 | |
242 | if (trigger_data->mode_dcd) { |
243 | if (status & TIOCM_CAR) |
244 | state = TTY_LED_ENABLE; |
245 | } |
246 | |
247 | if (trigger_data->mode_rng) { |
248 | if (status & TIOCM_RNG) |
249 | state = TTY_LED_ENABLE; |
250 | } |
251 | } |
252 | |
253 | /* |
254 | * The evaluation of rx/tx must be done after the evaluation |
255 | * of TIOCM_*, because rx/tx has priority. |
256 | */ |
257 | if (trigger_data->mode_rx || trigger_data->mode_tx) { |
258 | struct serial_icounter_struct icount; |
259 | |
260 | ret = tty_get_icount(tty: trigger_data->tty, icount: &icount); |
261 | if (ret) |
262 | goto out; |
263 | |
264 | if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) { |
265 | trigger_data->tx = icount.tx; |
266 | invert = state == TTY_LED_ENABLE; |
267 | state = TTY_LED_BLINK; |
268 | } |
269 | |
270 | if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) { |
271 | trigger_data->rx = icount.rx; |
272 | invert = state == TTY_LED_ENABLE; |
273 | state = TTY_LED_BLINK; |
274 | } |
275 | } |
276 | |
277 | out: |
278 | switch (state) { |
279 | case TTY_LED_BLINK: |
280 | led_blink_set_oneshot(led_cdev: trigger_data->led_cdev, delay_on: &interval, |
281 | delay_off: &interval, invert); |
282 | break; |
283 | case TTY_LED_ENABLE: |
284 | led_set_brightness(led_cdev: trigger_data->led_cdev, |
285 | brightness: trigger_data->led_cdev->blink_brightness); |
286 | break; |
287 | case TTY_LED_DISABLE: |
288 | fallthrough; |
289 | default: |
290 | led_set_brightness(led_cdev: trigger_data->led_cdev, brightness: LED_OFF); |
291 | break; |
292 | } |
293 | |
294 | complete_all(&trigger_data->sysfs); |
295 | schedule_delayed_work(dwork: &trigger_data->dwork, |
296 | delay: msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2)); |
297 | } |
298 | |
299 | static struct attribute *ledtrig_tty_attrs[] = { |
300 | &dev_attr_ttyname.attr, |
301 | &dev_attr_rx.attr, |
302 | &dev_attr_tx.attr, |
303 | &dev_attr_cts.attr, |
304 | &dev_attr_dsr.attr, |
305 | &dev_attr_dcd.attr, |
306 | &dev_attr_rng.attr, |
307 | NULL |
308 | }; |
309 | ATTRIBUTE_GROUPS(ledtrig_tty); |
310 | |
311 | static int ledtrig_tty_activate(struct led_classdev *led_cdev) |
312 | { |
313 | struct ledtrig_tty_data *trigger_data; |
314 | |
315 | trigger_data = kzalloc(size: sizeof(*trigger_data), GFP_KERNEL); |
316 | if (!trigger_data) |
317 | return -ENOMEM; |
318 | |
319 | /* Enable default rx/tx mode */ |
320 | trigger_data->mode_rx = true; |
321 | trigger_data->mode_tx = true; |
322 | |
323 | led_set_trigger_data(led_cdev, trigger_data); |
324 | |
325 | INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); |
326 | trigger_data->led_cdev = led_cdev; |
327 | init_completion(x: &trigger_data->sysfs); |
328 | |
329 | schedule_delayed_work(dwork: &trigger_data->dwork, delay: 0); |
330 | |
331 | return 0; |
332 | } |
333 | |
334 | static void ledtrig_tty_deactivate(struct led_classdev *led_cdev) |
335 | { |
336 | struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev); |
337 | |
338 | cancel_delayed_work_sync(dwork: &trigger_data->dwork); |
339 | |
340 | kfree(objp: trigger_data->ttyname); |
341 | tty_kref_put(tty: trigger_data->tty); |
342 | trigger_data->tty = NULL; |
343 | |
344 | kfree(objp: trigger_data); |
345 | } |
346 | |
347 | static struct led_trigger ledtrig_tty = { |
348 | .name = "tty" , |
349 | .activate = ledtrig_tty_activate, |
350 | .deactivate = ledtrig_tty_deactivate, |
351 | .groups = ledtrig_tty_groups, |
352 | }; |
353 | module_led_trigger(ledtrig_tty); |
354 | |
355 | MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>" ); |
356 | MODULE_DESCRIPTION("UART LED trigger" ); |
357 | MODULE_LICENSE("GPL v2" ); |
358 | |