Skip to content

Commit

Permalink
leds: trigger: implement a tty trigger
Browse files Browse the repository at this point in the history
Usage is as follows:

	myled=ledname
	tty=ttyS0

	echo tty > /sys/class/leds/$myled/trigger
	echo $tty > /sys/class/leds/$myled/ttyname

. When this new trigger is active it periodically checks the tty's
statistics and when it changed since the last check the led is flashed
once.

Signed-off-by: Uwe Kleine-König <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
Uwe Kleine-König authored and gregkh committed Jan 15, 2021
1 parent 63e34e7 commit fd4a641
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Documentation/ABI/testing/sysfs-class-led-trigger-tty
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
What: /sys/class/leds/<led>/ttyname
Date: Dec 2020
KernelVersion: 5.10
Contact: [email protected]
Description:
Specifies the tty device name of the triggering tty
9 changes: 9 additions & 0 deletions drivers/leds/trigger/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,13 @@ config LEDS_TRIGGER_AUDIO
the audio mute and mic-mute changes.
If unsure, say N

config LEDS_TRIGGER_TTY
tristate "LED Trigger for TTY devices"
depends on TTY
help
This allows LEDs to be controlled by activity on ttys which includes
serial devices like /dev/ttyS0.

When build as a module this driver will be called ledtrig-tty.

endif # LEDS_TRIGGERS
1 change: 1 addition & 0 deletions drivers/leds/trigger/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o
obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o
obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o
obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o
obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o
183 changes: 183 additions & 0 deletions drivers/leds/trigger/ledtrig-tty.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <uapi/linux/serial.h>

struct ledtrig_tty_data {
struct led_classdev *led_cdev;
struct delayed_work dwork;
struct mutex mutex;
const char *ttyname;
struct tty_struct *tty;
int rx, tx;
};

static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data)
{
schedule_delayed_work(&trigger_data->dwork, 0);
}

static ssize_t ttyname_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
ssize_t len = 0;

mutex_lock(&trigger_data->mutex);

if (trigger_data->ttyname)
len = sprintf(buf, "%s\n", trigger_data->ttyname);

mutex_unlock(&trigger_data->mutex);

return len;
}

static ssize_t ttyname_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t size)
{
struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
char *ttyname;
ssize_t ret = size;
bool running;

if (size > 0 && buf[size - 1] == '\n')
size -= 1;

if (size) {
ttyname = kmemdup_nul(buf, size, GFP_KERNEL);
if (!ttyname) {
ret = -ENOMEM;
goto out_unlock;
}
} else {
ttyname = NULL;
}

mutex_lock(&trigger_data->mutex);

running = trigger_data->ttyname != NULL;

kfree(trigger_data->ttyname);
tty_kref_put(trigger_data->tty);
trigger_data->tty = NULL;

trigger_data->ttyname = ttyname;

out_unlock:
mutex_unlock(&trigger_data->mutex);

if (ttyname && !running)
ledtrig_tty_restart(trigger_data);

return ret;
}
static DEVICE_ATTR_RW(ttyname);

static void ledtrig_tty_work(struct work_struct *work)
{
struct ledtrig_tty_data *trigger_data =
container_of(work, struct ledtrig_tty_data, dwork.work);
struct serial_icounter_struct icount;
int ret;

mutex_lock(&trigger_data->mutex);

if (!trigger_data->ttyname) {
/* exit without rescheduling */
mutex_unlock(&trigger_data->mutex);
return;
}

/* try to get the tty corresponding to $ttyname */
if (!trigger_data->tty) {
dev_t devno;
struct tty_struct *tty;
int ret;

ret = tty_dev_name_to_number(trigger_data->ttyname, &devno);
if (ret < 0)
/*
* A device with this name might appear later, so keep
* retrying.
*/
goto out;

tty = tty_kopen_shared(devno);
if (IS_ERR(tty) || !tty)
/* What to do? retry or abort */
goto out;

trigger_data->tty = tty;
}

ret = tty_get_icount(trigger_data->tty, &icount);
if (ret) {
dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n");
mutex_unlock(&trigger_data->mutex);
return;
}

if (icount.rx != trigger_data->rx ||
icount.tx != trigger_data->tx) {
led_set_brightness(trigger_data->led_cdev, LED_ON);

trigger_data->rx = icount.rx;
trigger_data->tx = icount.tx;
} else {
led_set_brightness(trigger_data->led_cdev, LED_OFF);
}

out:
mutex_unlock(&trigger_data->mutex);
schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100));
}

static struct attribute *ledtrig_tty_attrs[] = {
&dev_attr_ttyname.attr,
NULL
};
ATTRIBUTE_GROUPS(ledtrig_tty);

static int ledtrig_tty_activate(struct led_classdev *led_cdev)
{
struct ledtrig_tty_data *trigger_data;

trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
if (!trigger_data)
return -ENOMEM;

led_set_trigger_data(led_cdev, trigger_data);

INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
trigger_data->led_cdev = led_cdev;
mutex_init(&trigger_data->mutex);

return 0;
}

static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
{
struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev);

cancel_delayed_work_sync(&trigger_data->dwork);

kfree(trigger_data);
}

static struct led_trigger ledtrig_tty = {
.name = "tty",
.activate = ledtrig_tty_activate,
.deactivate = ledtrig_tty_deactivate,
.groups = ledtrig_tty_groups,
};
module_led_trigger(ledtrig_tty);

MODULE_AUTHOR("Uwe Kleine-König <[email protected]>");
MODULE_DESCRIPTION("UART LED trigger");
MODULE_LICENSE("GPL v2");

0 comments on commit fd4a641

Please sign in to comment.