linux/drivers/leds/trigger/ledtrig-tty.c
<<
>>
Prefs
   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");
 181