Merge tag 'exfat-for-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkin...
[linux-2.6-microblaze.git] / drivers / leds / trigger / ledtrig-tty.c
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(&trigger_data->sysfs,
50                                           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(&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, "%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(buf, size, GFP_KERNEL);
89                 if (!ttyname)
90                         return -ENOMEM;
91         } else {
92                 ttyname = NULL;
93         }
94
95         reinit_completion(&trigger_data->sysfs);
96         completion = ledtrig_tty_wait_for_completion(dev);
97         if (completion < 0)
98                 return completion;
99
100         kfree(trigger_data->ttyname);
101         tty_kref_put(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, "%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(buf, &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(trigger_data->ttyname, &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(devno);
223                 if (IS_ERR(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(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(trigger_data->tty, &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(trigger_data->led_cdev, &interval,
281                                 &interval, invert);
282                 break;
283         case TTY_LED_ENABLE:
284                 led_set_brightness(trigger_data->led_cdev,
285                                 trigger_data->led_cdev->blink_brightness);
286                 break;
287         case TTY_LED_DISABLE:
288                 fallthrough;
289         default:
290                 led_set_brightness(trigger_data->led_cdev, LED_OFF);
291                 break;
292         }
293
294         complete_all(&trigger_data->sysfs);
295         schedule_delayed_work(&trigger_data->dwork,
296                               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(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(&trigger_data->sysfs);
328
329         schedule_delayed_work(&trigger_data->dwork, 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(&trigger_data->dwork);
339
340         kfree(trigger_data->ttyname);
341         tty_kref_put(trigger_data->tty);
342         trigger_data->tty = NULL;
343
344         kfree(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");