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
12struct 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
21static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
22{
23	schedule_delayed_work(&trigger_data->dwork, 0);
24}
25
26static 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
42static 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}
79static DEVICE_ATTR_RW(ttyname);
80
81static 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
136out:
137	mutex_unlock(&trigger_data->mutex);
138	schedule_delayed_work(&trigger_data->dwork,
139			      msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
140}
141
142static struct attribute *ledtrig_tty_attrs[] = {
143	&dev_attr_ttyname.attr,
144	NULL
145};
146ATTRIBUTE_GROUPS(ledtrig_tty);
147
148static 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
165static 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
178static struct led_trigger ledtrig_tty = {
179	.name = "tty",
180	.activate = ledtrig_tty_activate,
181	.deactivate = ledtrig_tty_deactivate,
182	.groups = ledtrig_tty_groups,
183};
184module_led_trigger(ledtrig_tty);
185
186MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>");
187MODULE_DESCRIPTION("UART LED trigger");
188MODULE_LICENSE("GPL v2");
189