ledtrig-tty.c (4099B)
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");