Merge branch '40GbE' of git://git.kernel.org/pub/scm/linux/kernel/git/tnguy/net-queue
[linux-2.6-microblaze.git] / 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");