xref: /linux/drivers/input/serio/serport.c (revision 429b474990cb4e5e8cfe2352daf649d0599cccb6)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * Input device TTY line discipline
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * Copyright (c) 1999-2002 Vojtech Pavlik
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  * This is a module that converts a tty line into a much simpler
71da177e4SLinus Torvalds  * 'serial io port' abstraction that the input device drivers use.
81da177e4SLinus Torvalds  */
91da177e4SLinus Torvalds 
101da177e4SLinus Torvalds /*
111da177e4SLinus Torvalds  * This program is free software; you can redistribute it and/or modify it
121da177e4SLinus Torvalds  * under the terms of the GNU General Public License version 2 as published by
131da177e4SLinus Torvalds  * the Free Software Foundation.
141da177e4SLinus Torvalds  */
151da177e4SLinus Torvalds 
161da177e4SLinus Torvalds #include <asm/uaccess.h>
171da177e4SLinus Torvalds #include <linux/kernel.h>
18d43c36dcSAlexey Dobriyan #include <linux/sched.h>
191da177e4SLinus Torvalds #include <linux/slab.h>
201da177e4SLinus Torvalds #include <linux/module.h>
211da177e4SLinus Torvalds #include <linux/init.h>
221da177e4SLinus Torvalds #include <linux/serio.h>
231da177e4SLinus Torvalds #include <linux/tty.h>
24a80d8b02SJohn Sung #include <linux/compat.h>
251da177e4SLinus Torvalds 
261da177e4SLinus Torvalds MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
271da177e4SLinus Torvalds MODULE_DESCRIPTION("Input device TTY line discipline");
281da177e4SLinus Torvalds MODULE_LICENSE("GPL");
291da177e4SLinus Torvalds MODULE_ALIAS_LDISC(N_MOUSE);
301da177e4SLinus Torvalds 
311da177e4SLinus Torvalds #define SERPORT_BUSY	1
321ff2c873SDmitry Torokhov #define SERPORT_ACTIVE	2
331ff2c873SDmitry Torokhov #define SERPORT_DEAD	3
341da177e4SLinus Torvalds 
351da177e4SLinus Torvalds struct serport {
361da177e4SLinus Torvalds 	struct tty_struct *tty;
371da177e4SLinus Torvalds 	wait_queue_head_t wait;
381da177e4SLinus Torvalds 	struct serio *serio;
391ff2c873SDmitry Torokhov 	struct serio_device_id id;
401ff2c873SDmitry Torokhov 	spinlock_t lock;
411da177e4SLinus Torvalds 	unsigned long flags;
421da177e4SLinus Torvalds };
431da177e4SLinus Torvalds 
441da177e4SLinus Torvalds /*
451da177e4SLinus Torvalds  * Callback functions from the serio code.
461da177e4SLinus Torvalds  */
471da177e4SLinus Torvalds 
481da177e4SLinus Torvalds static int serport_serio_write(struct serio *serio, unsigned char data)
491da177e4SLinus Torvalds {
501da177e4SLinus Torvalds 	struct serport *serport = serio->port_data;
51f34d7a5bSAlan Cox 	return -(serport->tty->ops->write(serport->tty, &data, 1) != 1);
521da177e4SLinus Torvalds }
531da177e4SLinus Torvalds 
541ff2c873SDmitry Torokhov static int serport_serio_open(struct serio *serio)
551ff2c873SDmitry Torokhov {
561ff2c873SDmitry Torokhov 	struct serport *serport = serio->port_data;
571ff2c873SDmitry Torokhov 	unsigned long flags;
581ff2c873SDmitry Torokhov 
591ff2c873SDmitry Torokhov 	spin_lock_irqsave(&serport->lock, flags);
601ff2c873SDmitry Torokhov 	set_bit(SERPORT_ACTIVE, &serport->flags);
611ff2c873SDmitry Torokhov 	spin_unlock_irqrestore(&serport->lock, flags);
621ff2c873SDmitry Torokhov 
631ff2c873SDmitry Torokhov 	return 0;
641ff2c873SDmitry Torokhov }
651ff2c873SDmitry Torokhov 
661ff2c873SDmitry Torokhov 
671da177e4SLinus Torvalds static void serport_serio_close(struct serio *serio)
681da177e4SLinus Torvalds {
691da177e4SLinus Torvalds 	struct serport *serport = serio->port_data;
701ff2c873SDmitry Torokhov 	unsigned long flags;
711da177e4SLinus Torvalds 
721ff2c873SDmitry Torokhov 	spin_lock_irqsave(&serport->lock, flags);
731ff2c873SDmitry Torokhov 	clear_bit(SERPORT_ACTIVE, &serport->flags);
741ff2c873SDmitry Torokhov 	set_bit(SERPORT_DEAD, &serport->flags);
751ff2c873SDmitry Torokhov 	spin_unlock_irqrestore(&serport->lock, flags);
761ff2c873SDmitry Torokhov 
771da177e4SLinus Torvalds 	wake_up_interruptible(&serport->wait);
781da177e4SLinus Torvalds }
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds /*
811da177e4SLinus Torvalds  * serport_ldisc_open() is the routine that is called upon setting our line
821da177e4SLinus Torvalds  * discipline on a tty. It prepares the serio struct.
831da177e4SLinus Torvalds  */
841da177e4SLinus Torvalds 
851da177e4SLinus Torvalds static int serport_ldisc_open(struct tty_struct *tty)
861da177e4SLinus Torvalds {
871da177e4SLinus Torvalds 	struct serport *serport;
881da177e4SLinus Torvalds 
891da177e4SLinus Torvalds 	if (!capable(CAP_SYS_ADMIN))
901da177e4SLinus Torvalds 		return -EPERM;
911da177e4SLinus Torvalds 
92a97e148aSPekka Enberg 	serport = kzalloc(sizeof(struct serport), GFP_KERNEL);
931ff2c873SDmitry Torokhov 	if (!serport)
941da177e4SLinus Torvalds 		return -ENOMEM;
951da177e4SLinus Torvalds 
961da177e4SLinus Torvalds 	serport->tty = tty;
971ff2c873SDmitry Torokhov 	spin_lock_init(&serport->lock);
981da177e4SLinus Torvalds 	init_waitqueue_head(&serport->wait);
991da177e4SLinus Torvalds 
1001ff2c873SDmitry Torokhov 	tty->disc_data = serport;
10133f0f88fSAlan Cox 	tty->receive_room = 256;
1021ff2c873SDmitry Torokhov 	set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
1031ff2c873SDmitry Torokhov 
1041da177e4SLinus Torvalds 	return 0;
1051da177e4SLinus Torvalds }
1061da177e4SLinus Torvalds 
1071da177e4SLinus Torvalds /*
1081da177e4SLinus Torvalds  * serport_ldisc_close() is the opposite of serport_ldisc_open()
1091da177e4SLinus Torvalds  */
1101da177e4SLinus Torvalds 
1111da177e4SLinus Torvalds static void serport_ldisc_close(struct tty_struct *tty)
1121da177e4SLinus Torvalds {
1131da177e4SLinus Torvalds 	struct serport *serport = (struct serport *) tty->disc_data;
1141ff2c873SDmitry Torokhov 
1151da177e4SLinus Torvalds 	kfree(serport);
1161da177e4SLinus Torvalds }
1171da177e4SLinus Torvalds 
1181da177e4SLinus Torvalds /*
1191da177e4SLinus Torvalds  * serport_ldisc_receive() is called by the low level tty driver when characters
12048c27016SDavid Engraf  * are ready for us. We forward the characters and flags, one by one to the
12148c27016SDavid Engraf  * 'interrupt' routine.
1221da177e4SLinus Torvalds  */
1231da177e4SLinus Torvalds 
12455db4c64SLinus Torvalds static void serport_ldisc_receive(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
1251da177e4SLinus Torvalds {
1261da177e4SLinus Torvalds 	struct serport *serport = (struct serport*) tty->disc_data;
1271ff2c873SDmitry Torokhov 	unsigned long flags;
12882f91fe0SPeter Hurley 	unsigned int ch_flags = 0;
1291da177e4SLinus Torvalds 	int i;
1301ff2c873SDmitry Torokhov 
1311ff2c873SDmitry Torokhov 	spin_lock_irqsave(&serport->lock, flags);
1321ff2c873SDmitry Torokhov 
13355db4c64SLinus Torvalds 	if (!test_bit(SERPORT_ACTIVE, &serport->flags))
1341ff2c873SDmitry Torokhov 		goto out;
1351ff2c873SDmitry Torokhov 
13648c27016SDavid Engraf 	for (i = 0; i < count; i++) {
13782f91fe0SPeter Hurley 		if (fp) {
13848c27016SDavid Engraf 			switch (fp[i]) {
13948c27016SDavid Engraf 			case TTY_FRAME:
14048c27016SDavid Engraf 				ch_flags = SERIO_FRAME;
14148c27016SDavid Engraf 				break;
14248c27016SDavid Engraf 
14348c27016SDavid Engraf 			case TTY_PARITY:
14448c27016SDavid Engraf 				ch_flags = SERIO_PARITY;
14548c27016SDavid Engraf 				break;
14648c27016SDavid Engraf 
14748c27016SDavid Engraf 			default:
14848c27016SDavid Engraf 				ch_flags = 0;
14948c27016SDavid Engraf 				break;
15048c27016SDavid Engraf 			}
15182f91fe0SPeter Hurley 		}
15248c27016SDavid Engraf 
15348c27016SDavid Engraf 		serio_interrupt(serport->serio, cp[i], ch_flags);
15448c27016SDavid Engraf 	}
1551ff2c873SDmitry Torokhov 
1561ff2c873SDmitry Torokhov out:
1571ff2c873SDmitry Torokhov 	spin_unlock_irqrestore(&serport->lock, flags);
1581da177e4SLinus Torvalds }
1591da177e4SLinus Torvalds 
1601da177e4SLinus Torvalds /*
1611da177e4SLinus Torvalds  * serport_ldisc_read() just waits indefinitely if everything goes well.
1621da177e4SLinus Torvalds  * However, when the serio driver closes the serio port, it finishes,
1631da177e4SLinus Torvalds  * returning 0 characters.
1641da177e4SLinus Torvalds  */
1651da177e4SLinus Torvalds 
1661da177e4SLinus Torvalds static ssize_t serport_ldisc_read(struct tty_struct * tty, struct file * file, unsigned char __user * buf, size_t nr)
1671da177e4SLinus Torvalds {
1681da177e4SLinus Torvalds 	struct serport *serport = (struct serport*) tty->disc_data;
1691ff2c873SDmitry Torokhov 	struct serio *serio;
1701da177e4SLinus Torvalds 
1711da177e4SLinus Torvalds 	if (test_and_set_bit(SERPORT_BUSY, &serport->flags))
1721da177e4SLinus Torvalds 		return -EBUSY;
1731da177e4SLinus Torvalds 
174a97e148aSPekka Enberg 	serport->serio = serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
1751ff2c873SDmitry Torokhov 	if (!serio)
1761ff2c873SDmitry Torokhov 		return -ENOMEM;
1771ff2c873SDmitry Torokhov 
1781ff2c873SDmitry Torokhov 	strlcpy(serio->name, "Serial port", sizeof(serio->name));
179*429b4749SRasmus Villemoes 	snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", tty_name(tty));
1801ff2c873SDmitry Torokhov 	serio->id = serport->id;
1811ff2c873SDmitry Torokhov 	serio->id.type = SERIO_RS232;
1821ff2c873SDmitry Torokhov 	serio->write = serport_serio_write;
1831ff2c873SDmitry Torokhov 	serio->open = serport_serio_open;
1841ff2c873SDmitry Torokhov 	serio->close = serport_serio_close;
1851ff2c873SDmitry Torokhov 	serio->port_data = serport;
186de838a93SDmitry Eremin-Solenikov 	serio->dev.parent = tty->dev;
1871ff2c873SDmitry Torokhov 
1881da177e4SLinus Torvalds 	serio_register_port(serport->serio);
189*429b4749SRasmus Villemoes 	printk(KERN_INFO "serio: Serial port %s\n", tty_name(tty));
1901da177e4SLinus Torvalds 
1911ff2c873SDmitry Torokhov 	wait_event_interruptible(serport->wait, test_bit(SERPORT_DEAD, &serport->flags));
1921ff2c873SDmitry Torokhov 	serio_unregister_port(serport->serio);
1931ff2c873SDmitry Torokhov 	serport->serio = NULL;
1941ff2c873SDmitry Torokhov 
1951ff2c873SDmitry Torokhov 	clear_bit(SERPORT_DEAD, &serport->flags);
1961da177e4SLinus Torvalds 	clear_bit(SERPORT_BUSY, &serport->flags);
1971da177e4SLinus Torvalds 
1981da177e4SLinus Torvalds 	return 0;
1991da177e4SLinus Torvalds }
2001da177e4SLinus Torvalds 
201a80d8b02SJohn Sung static void serport_set_type(struct tty_struct *tty, unsigned long type)
2021da177e4SLinus Torvalds {
203a80d8b02SJohn Sung 	struct serport *serport = tty->disc_data;
2041da177e4SLinus Torvalds 
2051ff2c873SDmitry Torokhov 	serport->id.proto = type & 0x000000ff;
2061ff2c873SDmitry Torokhov 	serport->id.id    = (type & 0x0000ff00) >> 8;
2071ff2c873SDmitry Torokhov 	serport->id.extra = (type & 0x00ff0000) >> 16;
208a80d8b02SJohn Sung }
2091da177e4SLinus Torvalds 
210a80d8b02SJohn Sung /*
211a80d8b02SJohn Sung  * serport_ldisc_ioctl() allows to set the port protocol, and device ID
212a80d8b02SJohn Sung  */
213a80d8b02SJohn Sung 
214a80d8b02SJohn Sung static int serport_ldisc_ioctl(struct tty_struct *tty, struct file *file,
215a80d8b02SJohn Sung 			       unsigned int cmd, unsigned long arg)
216a80d8b02SJohn Sung {
217a80d8b02SJohn Sung 	if (cmd == SPIOCSTYPE) {
218a80d8b02SJohn Sung 		unsigned long type;
219a80d8b02SJohn Sung 
220a80d8b02SJohn Sung 		if (get_user(type, (unsigned long __user *) arg))
221a80d8b02SJohn Sung 			return -EFAULT;
222a80d8b02SJohn Sung 
223a80d8b02SJohn Sung 		serport_set_type(tty, type);
2241da177e4SLinus Torvalds 		return 0;
2251da177e4SLinus Torvalds 	}
2261da177e4SLinus Torvalds 
2271da177e4SLinus Torvalds 	return -EINVAL;
2281da177e4SLinus Torvalds }
2291da177e4SLinus Torvalds 
230a80d8b02SJohn Sung #ifdef CONFIG_COMPAT
231a80d8b02SJohn Sung #define COMPAT_SPIOCSTYPE	_IOW('q', 0x01, compat_ulong_t)
232a80d8b02SJohn Sung static long serport_ldisc_compat_ioctl(struct tty_struct *tty,
233a80d8b02SJohn Sung 				       struct file *file,
234a80d8b02SJohn Sung 				       unsigned int cmd, unsigned long arg)
235a80d8b02SJohn Sung {
236a80d8b02SJohn Sung 	if (cmd == COMPAT_SPIOCSTYPE) {
237a80d8b02SJohn Sung 		void __user *uarg = compat_ptr(arg);
238a80d8b02SJohn Sung 		compat_ulong_t compat_type;
239a80d8b02SJohn Sung 
240a80d8b02SJohn Sung 		if (get_user(compat_type, (compat_ulong_t __user *)uarg))
241a80d8b02SJohn Sung 			return -EFAULT;
242a80d8b02SJohn Sung 
243a80d8b02SJohn Sung 		serport_set_type(tty, compat_type);
244a80d8b02SJohn Sung 		return 0;
245a80d8b02SJohn Sung 	}
246a80d8b02SJohn Sung 
247a80d8b02SJohn Sung 	return -EINVAL;
248a80d8b02SJohn Sung }
249a80d8b02SJohn Sung #endif
250a80d8b02SJohn Sung 
2511da177e4SLinus Torvalds static void serport_ldisc_write_wakeup(struct tty_struct * tty)
2521da177e4SLinus Torvalds {
2531ff2c873SDmitry Torokhov 	struct serport *serport = (struct serport *) tty->disc_data;
2541ff2c873SDmitry Torokhov 	unsigned long flags;
2551da177e4SLinus Torvalds 
2561ff2c873SDmitry Torokhov 	spin_lock_irqsave(&serport->lock, flags);
2571ff2c873SDmitry Torokhov 	if (test_bit(SERPORT_ACTIVE, &serport->flags))
2581ff2c873SDmitry Torokhov 		serio_drv_write_wakeup(serport->serio);
2591ff2c873SDmitry Torokhov 	spin_unlock_irqrestore(&serport->lock, flags);
2601da177e4SLinus Torvalds }
2611da177e4SLinus Torvalds 
2621da177e4SLinus Torvalds /*
2631da177e4SLinus Torvalds  * The line discipline structure.
2641da177e4SLinus Torvalds  */
2651da177e4SLinus Torvalds 
266a352def2SAlan Cox static struct tty_ldisc_ops serport_ldisc = {
2671da177e4SLinus Torvalds 	.owner =	THIS_MODULE,
2681da177e4SLinus Torvalds 	.name =		"input",
2691da177e4SLinus Torvalds 	.open =		serport_ldisc_open,
2701da177e4SLinus Torvalds 	.close =	serport_ldisc_close,
2711da177e4SLinus Torvalds 	.read =		serport_ldisc_read,
2721da177e4SLinus Torvalds 	.ioctl =	serport_ldisc_ioctl,
273a80d8b02SJohn Sung #ifdef CONFIG_COMPAT
274a80d8b02SJohn Sung 	.compat_ioctl =	serport_ldisc_compat_ioctl,
275a80d8b02SJohn Sung #endif
2761da177e4SLinus Torvalds 	.receive_buf =	serport_ldisc_receive,
2771da177e4SLinus Torvalds 	.write_wakeup =	serport_ldisc_write_wakeup
2781da177e4SLinus Torvalds };
2791da177e4SLinus Torvalds 
2801da177e4SLinus Torvalds /*
2811da177e4SLinus Torvalds  * The functions for insering/removing us as a module.
2821da177e4SLinus Torvalds  */
2831da177e4SLinus Torvalds 
2841da177e4SLinus Torvalds static int __init serport_init(void)
2851da177e4SLinus Torvalds {
2861da177e4SLinus Torvalds 	int retval;
2871da177e4SLinus Torvalds 	retval = tty_register_ldisc(N_MOUSE, &serport_ldisc);
2881da177e4SLinus Torvalds 	if (retval)
2891da177e4SLinus Torvalds 		printk(KERN_ERR "serport.c: Error registering line discipline.\n");
2901da177e4SLinus Torvalds 
2911da177e4SLinus Torvalds 	return  retval;
2921da177e4SLinus Torvalds }
2931da177e4SLinus Torvalds 
2941da177e4SLinus Torvalds static void __exit serport_exit(void)
2951da177e4SLinus Torvalds {
29664ccd715SAlexey Dobriyan 	tty_unregister_ldisc(N_MOUSE);
2971da177e4SLinus Torvalds }
2981da177e4SLinus Torvalds 
2991da177e4SLinus Torvalds module_init(serport_init);
3001da177e4SLinus Torvalds module_exit(serport_exit);
301