Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  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
 10struct 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
 19static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
 20{
 21	schedule_delayed_work(&trigger_data->dwork, 0);
 22}
 23
 24static 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
 40static 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			return -ENOMEM;
 56	} else {
 57		ttyname = NULL;
 58	}
 59
 60	mutex_lock(&trigger_data->mutex);
 61
 62	running = trigger_data->ttyname != NULL;
 63
 64	kfree(trigger_data->ttyname);
 65	tty_kref_put(trigger_data->tty);
 66	trigger_data->tty = NULL;
 67
 68	trigger_data->ttyname = ttyname;
 69
 70	mutex_unlock(&trigger_data->mutex);
 71
 72	if (ttyname && !running)
 73		ledtrig_tty_restart(trigger_data);
 74
 75	return ret;
 76}
 77static DEVICE_ATTR_RW(ttyname);
 78
 79static void ledtrig_tty_work(struct work_struct *work)
 80{
 81	struct ledtrig_tty_data *trigger_data =
 82		container_of(work, struct ledtrig_tty_data, dwork.work);
 83	struct serial_icounter_struct icount;
 84	int ret;
 85
 86	mutex_lock(&trigger_data->mutex);
 87
 88	if (!trigger_data->ttyname) {
 89		/* exit without rescheduling */
 90		mutex_unlock(&trigger_data->mutex);
 91		return;
 92	}
 93
 94	/* try to get the tty corresponding to $ttyname */
 95	if (!trigger_data->tty) {
 96		dev_t devno;
 97		struct tty_struct *tty;
 98		int ret;
 99
100		ret = tty_dev_name_to_number(trigger_data->ttyname, &devno);
101		if (ret < 0)
102			/*
103			 * A device with this name might appear later, so keep
104			 * retrying.
105			 */
106			goto out;
107
108		tty = tty_kopen_shared(devno);
109		if (IS_ERR(tty) || !tty)
110			/* What to do? retry or abort */
111			goto out;
112
113		trigger_data->tty = tty;
114	}
115
116	ret = tty_get_icount(trigger_data->tty, &icount);
117	if (ret) {
118		dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
119		mutex_unlock(&trigger_data->mutex);
120		return;
121	}
122
123	if (icount.rx != trigger_data->rx ||
124	    icount.tx != trigger_data->tx) {
125		led_set_brightness_sync(trigger_data->led_cdev, LED_ON);
126
127		trigger_data->rx = icount.rx;
128		trigger_data->tx = icount.tx;
129	} else {
130		led_set_brightness_sync(trigger_data->led_cdev, LED_OFF);
131	}
132
133out:
134	mutex_unlock(&trigger_data->mutex);
135	schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100));
136}
137
138static struct attribute *ledtrig_tty_attrs[] = {
139	&dev_attr_ttyname.attr,
140	NULL
141};
142ATTRIBUTE_GROUPS(ledtrig_tty);
143
144static int ledtrig_tty_activate(struct led_classdev *led_cdev)
145{
146	struct ledtrig_tty_data *trigger_data;
147
148	trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
149	if (!trigger_data)
150		return -ENOMEM;
151
152	led_set_trigger_data(led_cdev, trigger_data);
153
154	INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
155	trigger_data->led_cdev = led_cdev;
156	mutex_init(&trigger_data->mutex);
157
158	return 0;
159}
160
161static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
162{
163	struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev);
164
165	cancel_delayed_work_sync(&trigger_data->dwork);
166
167	kfree(trigger_data);
168}
169
170static struct led_trigger ledtrig_tty = {
171	.name = "tty",
172	.activate = ledtrig_tty_activate,
173	.deactivate = ledtrig_tty_deactivate,
174	.groups = ledtrig_tty_groups,
175};
176module_led_trigger(ledtrig_tty);
177
178MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>");
179MODULE_DESCRIPTION("UART LED trigger");
180MODULE_LICENSE("GPL v2");