brintos

brintos / linux-shallow public Read only

0
0
Text · 8.0 KiB · 8cf1485 Raw
358 lines · c
1// SPDX-License-Identifier: GPL-2.02 3#include <linux/completion.h>4#include <linux/delay.h>5#include <linux/leds.h>6#include <linux/module.h>7#include <linux/slab.h>8#include <linux/tty.h>9#include <uapi/linux/serial.h>10 11#define LEDTRIG_TTY_INTERVAL	5012 13struct ledtrig_tty_data {14	struct led_classdev *led_cdev;15	struct delayed_work dwork;16	struct completion sysfs;17	const char *ttyname;18	struct tty_struct *tty;19	int rx, tx;20	bool mode_rx;21	bool mode_tx;22	bool mode_cts;23	bool mode_dsr;24	bool mode_dcd;25	bool mode_rng;26};27 28/* Indicates which state the LED should now display */29enum led_trigger_tty_state {30	TTY_LED_BLINK,31	TTY_LED_ENABLE,32	TTY_LED_DISABLE,33};34 35enum led_trigger_tty_modes {36	TRIGGER_TTY_RX = 0,37	TRIGGER_TTY_TX,38	TRIGGER_TTY_CTS,39	TRIGGER_TTY_DSR,40	TRIGGER_TTY_DCD,41	TRIGGER_TTY_RNG,42};43 44static int ledtrig_tty_wait_for_completion(struct device *dev)45{46	struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);47	int ret;48 49	ret = wait_for_completion_timeout(&trigger_data->sysfs,50					  msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20));51	if (ret == 0)52		return -ETIMEDOUT;53 54	return ret;55}56 57static ssize_t ttyname_show(struct device *dev,58			    struct device_attribute *attr, char *buf)59{60	struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);61	ssize_t len = 0;62	int completion;63 64	reinit_completion(&trigger_data->sysfs);65	completion = ledtrig_tty_wait_for_completion(dev);66	if (completion < 0)67		return completion;68 69	if (trigger_data->ttyname)70		len = sprintf(buf, "%s\n", trigger_data->ttyname);71 72	return len;73}74 75static ssize_t ttyname_store(struct device *dev,76			     struct device_attribute *attr, const char *buf,77			     size_t size)78{79	struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);80	char *ttyname;81	ssize_t ret = size;82	int completion;83 84	if (size > 0 && buf[size - 1] == '\n')85		size -= 1;86 87	if (size) {88		ttyname = kmemdup_nul(buf, size, GFP_KERNEL);89		if (!ttyname)90			return -ENOMEM;91	} else {92		ttyname = NULL;93	}94 95	reinit_completion(&trigger_data->sysfs);96	completion = ledtrig_tty_wait_for_completion(dev);97	if (completion < 0)98		return completion;99 100	kfree(trigger_data->ttyname);101	tty_kref_put(trigger_data->tty);102	trigger_data->tty = NULL;103 104	trigger_data->ttyname = ttyname;105 106	return ret;107}108static DEVICE_ATTR_RW(ttyname);109 110static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,111				     enum led_trigger_tty_modes attr)112{113	struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);114	bool state;115 116	switch (attr) {117	case TRIGGER_TTY_RX:118		state = trigger_data->mode_rx;119		break;120	case TRIGGER_TTY_TX:121		state = trigger_data->mode_tx;122		break;123	case TRIGGER_TTY_CTS:124		state = trigger_data->mode_cts;125		break;126	case TRIGGER_TTY_DSR:127		state = trigger_data->mode_dsr;128		break;129	case TRIGGER_TTY_DCD:130		state = trigger_data->mode_dcd;131		break;132	case TRIGGER_TTY_RNG:133		state = trigger_data->mode_rng;134		break;135	}136 137	return sysfs_emit(buf, "%u\n", state);138}139 140static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,141				      size_t size, enum led_trigger_tty_modes attr)142{143	struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);144	bool state;145	int ret;146 147	ret = kstrtobool(buf, &state);148	if (ret)149		return ret;150 151	switch (attr) {152	case TRIGGER_TTY_RX:153		trigger_data->mode_rx = state;154		break;155	case TRIGGER_TTY_TX:156		trigger_data->mode_tx = state;157		break;158	case TRIGGER_TTY_CTS:159		trigger_data->mode_cts = state;160		break;161	case TRIGGER_TTY_DSR:162		trigger_data->mode_dsr = state;163		break;164	case TRIGGER_TTY_DCD:165		trigger_data->mode_dcd = state;166		break;167	case TRIGGER_TTY_RNG:168		trigger_data->mode_rng = state;169		break;170	}171 172	return size;173}174 175#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \176	static ssize_t trigger_name##_show(struct device *dev, \177		struct device_attribute *attr, char *buf) \178	{ \179		return ledtrig_tty_attr_show(dev, buf, trigger); \180	} \181	static ssize_t trigger_name##_store(struct device *dev, \182		struct device_attribute *attr, const char *buf, size_t size) \183	{ \184		return ledtrig_tty_attr_store(dev, buf, size, trigger); \185	} \186	static DEVICE_ATTR_RW(trigger_name)187 188DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);189DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);190DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);191DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);192DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);193DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);194 195static void ledtrig_tty_work(struct work_struct *work)196{197	struct ledtrig_tty_data *trigger_data =198		container_of(work, struct ledtrig_tty_data, dwork.work);199	enum led_trigger_tty_state state = TTY_LED_DISABLE;200	unsigned long interval = LEDTRIG_TTY_INTERVAL;201	bool invert = false;202	int status;203	int ret;204 205	if (!trigger_data->ttyname)206		goto out;207 208	/* try to get the tty corresponding to $ttyname */209	if (!trigger_data->tty) {210		dev_t devno;211		struct tty_struct *tty;212		int ret;213 214		ret = tty_dev_name_to_number(trigger_data->ttyname, &devno);215		if (ret < 0)216			/*217			 * A device with this name might appear later, so keep218			 * retrying.219			 */220			goto out;221 222		tty = tty_kopen_shared(devno);223		if (IS_ERR(tty) || !tty)224			/* What to do? retry or abort */225			goto out;226 227		trigger_data->tty = tty;228	}229 230	status = tty_get_tiocm(trigger_data->tty);231	if (status > 0) {232		if (trigger_data->mode_cts) {233			if (status & TIOCM_CTS)234				state = TTY_LED_ENABLE;235		}236 237		if (trigger_data->mode_dsr) {238			if (status & TIOCM_DSR)239				state = TTY_LED_ENABLE;240		}241 242		if (trigger_data->mode_dcd) {243			if (status & TIOCM_CAR)244				state = TTY_LED_ENABLE;245		}246 247		if (trigger_data->mode_rng) {248			if (status & TIOCM_RNG)249				state = TTY_LED_ENABLE;250		}251	}252 253	/*254	 * The evaluation of rx/tx must be done after the evaluation255	 * of TIOCM_*, because rx/tx has priority.256	 */257	if (trigger_data->mode_rx || trigger_data->mode_tx) {258		struct serial_icounter_struct icount;259 260		ret = tty_get_icount(trigger_data->tty, &icount);261		if (ret)262			goto out;263 264		if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {265			trigger_data->tx = icount.tx;266			invert = state == TTY_LED_ENABLE;267			state = TTY_LED_BLINK;268		}269 270		if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {271			trigger_data->rx = icount.rx;272			invert = state == TTY_LED_ENABLE;273			state = TTY_LED_BLINK;274		}275	}276 277out:278	switch (state) {279	case TTY_LED_BLINK:280		led_blink_set_oneshot(trigger_data->led_cdev, &interval,281				&interval, invert);282		break;283	case TTY_LED_ENABLE:284		led_set_brightness(trigger_data->led_cdev,285				trigger_data->led_cdev->blink_brightness);286		break;287	case TTY_LED_DISABLE:288		fallthrough;289	default:290		led_set_brightness(trigger_data->led_cdev, LED_OFF);291		break;292	}293 294	complete_all(&trigger_data->sysfs);295	schedule_delayed_work(&trigger_data->dwork,296			      msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));297}298 299static struct attribute *ledtrig_tty_attrs[] = {300	&dev_attr_ttyname.attr,301	&dev_attr_rx.attr,302	&dev_attr_tx.attr,303	&dev_attr_cts.attr,304	&dev_attr_dsr.attr,305	&dev_attr_dcd.attr,306	&dev_attr_rng.attr,307	NULL308};309ATTRIBUTE_GROUPS(ledtrig_tty);310 311static int ledtrig_tty_activate(struct led_classdev *led_cdev)312{313	struct ledtrig_tty_data *trigger_data;314 315	trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);316	if (!trigger_data)317		return -ENOMEM;318 319	/* Enable default rx/tx mode */320	trigger_data->mode_rx = true;321	trigger_data->mode_tx = true;322 323	led_set_trigger_data(led_cdev, trigger_data);324 325	INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);326	trigger_data->led_cdev = led_cdev;327	init_completion(&trigger_data->sysfs);328 329	schedule_delayed_work(&trigger_data->dwork, 0);330 331	return 0;332}333 334static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)335{336	struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev);337 338	cancel_delayed_work_sync(&trigger_data->dwork);339 340	kfree(trigger_data->ttyname);341	tty_kref_put(trigger_data->tty);342	trigger_data->tty = NULL;343 344	kfree(trigger_data);345}346 347static struct led_trigger ledtrig_tty = {348	.name = "tty",349	.activate = ledtrig_tty_activate,350	.deactivate = ledtrig_tty_deactivate,351	.groups = ledtrig_tty_groups,352};353module_led_trigger(ledtrig_tty);354 355MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>");356MODULE_DESCRIPTION("UART LED trigger");357MODULE_LICENSE("GPL v2");358