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"); 184