leds: ledtrig-tty: Free allocated ttyname buffer on deactivate
[platform/kernel/linux-rpi.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 #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
21 static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
22 {
23         schedule_delayed_work(&trigger_data->dwork, 0);
24 }
25
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
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
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
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
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");