xref: /openbmc/linux/drivers/tty/tty_ldisc.c (revision 5b6e6832)
196fd7ce5SGreg Kroah-Hartman #include <linux/types.h>
296fd7ce5SGreg Kroah-Hartman #include <linux/errno.h>
38b3ffa17SJiri Slaby #include <linux/kmod.h>
496fd7ce5SGreg Kroah-Hartman #include <linux/sched.h>
596fd7ce5SGreg Kroah-Hartman #include <linux/interrupt.h>
696fd7ce5SGreg Kroah-Hartman #include <linux/tty.h>
796fd7ce5SGreg Kroah-Hartman #include <linux/tty_driver.h>
896fd7ce5SGreg Kroah-Hartman #include <linux/file.h>
996fd7ce5SGreg Kroah-Hartman #include <linux/mm.h>
1096fd7ce5SGreg Kroah-Hartman #include <linux/string.h>
1196fd7ce5SGreg Kroah-Hartman #include <linux/slab.h>
1296fd7ce5SGreg Kroah-Hartman #include <linux/poll.h>
1396fd7ce5SGreg Kroah-Hartman #include <linux/proc_fs.h>
1496fd7ce5SGreg Kroah-Hartman #include <linux/module.h>
1596fd7ce5SGreg Kroah-Hartman #include <linux/device.h>
1696fd7ce5SGreg Kroah-Hartman #include <linux/wait.h>
1796fd7ce5SGreg Kroah-Hartman #include <linux/bitops.h>
1896fd7ce5SGreg Kroah-Hartman #include <linux/seq_file.h>
1996fd7ce5SGreg Kroah-Hartman #include <linux/uaccess.h>
200c73c08eSJiri Slaby #include <linux/ratelimit.h>
2196fd7ce5SGreg Kroah-Hartman 
22fc575ee6SPeter Hurley #undef LDISC_DEBUG_HANGUP
23fc575ee6SPeter Hurley 
24fc575ee6SPeter Hurley #ifdef LDISC_DEBUG_HANGUP
250a6adc13SPeter Hurley #define tty_ldisc_debug(tty, f, args...)	tty_debug(tty, f, ##args)
26fc575ee6SPeter Hurley #else
27fc575ee6SPeter Hurley #define tty_ldisc_debug(tty, f, args...)
28fc575ee6SPeter Hurley #endif
29fc575ee6SPeter Hurley 
30d2c43890SPeter Hurley /* lockdep nested classes for tty->ldisc_sem */
31d2c43890SPeter Hurley enum {
32d2c43890SPeter Hurley 	LDISC_SEM_NORMAL,
33d2c43890SPeter Hurley 	LDISC_SEM_OTHER,
34d2c43890SPeter Hurley };
35d2c43890SPeter Hurley 
36d2c43890SPeter Hurley 
3796fd7ce5SGreg Kroah-Hartman /*
3896fd7ce5SGreg Kroah-Hartman  *	This guards the refcounted line discipline lists. The lock
3996fd7ce5SGreg Kroah-Hartman  *	must be taken with irqs off because there are hangup path
4096fd7ce5SGreg Kroah-Hartman  *	callers who will do ldisc lookups and cannot sleep.
4196fd7ce5SGreg Kroah-Hartman  */
4296fd7ce5SGreg Kroah-Hartman 
43137084bbSPeter Hurley static DEFINE_RAW_SPINLOCK(tty_ldiscs_lock);
4496fd7ce5SGreg Kroah-Hartman /* Line disc dispatch table */
4596fd7ce5SGreg Kroah-Hartman static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
4696fd7ce5SGreg Kroah-Hartman 
4796fd7ce5SGreg Kroah-Hartman /**
4896fd7ce5SGreg Kroah-Hartman  *	tty_register_ldisc	-	install a line discipline
4996fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
5096fd7ce5SGreg Kroah-Hartman  *	@new_ldisc: pointer to the ldisc object
5196fd7ce5SGreg Kroah-Hartman  *
5296fd7ce5SGreg Kroah-Hartman  *	Installs a new line discipline into the kernel. The discipline
5396fd7ce5SGreg Kroah-Hartman  *	is set up as unreferenced and then made available to the kernel
5496fd7ce5SGreg Kroah-Hartman  *	from this point onwards.
5596fd7ce5SGreg Kroah-Hartman  *
5696fd7ce5SGreg Kroah-Hartman  *	Locking:
57137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
5896fd7ce5SGreg Kroah-Hartman  */
5996fd7ce5SGreg Kroah-Hartman 
6096fd7ce5SGreg Kroah-Hartman int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
6196fd7ce5SGreg Kroah-Hartman {
6296fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
6396fd7ce5SGreg Kroah-Hartman 	int ret = 0;
6496fd7ce5SGreg Kroah-Hartman 
6596fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
6696fd7ce5SGreg Kroah-Hartman 		return -EINVAL;
6796fd7ce5SGreg Kroah-Hartman 
68137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
6996fd7ce5SGreg Kroah-Hartman 	tty_ldiscs[disc] = new_ldisc;
7096fd7ce5SGreg Kroah-Hartman 	new_ldisc->num = disc;
7196fd7ce5SGreg Kroah-Hartman 	new_ldisc->refcount = 0;
72137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
7396fd7ce5SGreg Kroah-Hartman 
7496fd7ce5SGreg Kroah-Hartman 	return ret;
7596fd7ce5SGreg Kroah-Hartman }
7696fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL(tty_register_ldisc);
7796fd7ce5SGreg Kroah-Hartman 
7896fd7ce5SGreg Kroah-Hartman /**
7996fd7ce5SGreg Kroah-Hartman  *	tty_unregister_ldisc	-	unload a line discipline
8096fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
8196fd7ce5SGreg Kroah-Hartman  *	@new_ldisc: pointer to the ldisc object
8296fd7ce5SGreg Kroah-Hartman  *
8396fd7ce5SGreg Kroah-Hartman  *	Remove a line discipline from the kernel providing it is not
8496fd7ce5SGreg Kroah-Hartman  *	currently in use.
8596fd7ce5SGreg Kroah-Hartman  *
8696fd7ce5SGreg Kroah-Hartman  *	Locking:
87137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
8896fd7ce5SGreg Kroah-Hartman  */
8996fd7ce5SGreg Kroah-Hartman 
9096fd7ce5SGreg Kroah-Hartman int tty_unregister_ldisc(int disc)
9196fd7ce5SGreg Kroah-Hartman {
9296fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
9396fd7ce5SGreg Kroah-Hartman 	int ret = 0;
9496fd7ce5SGreg Kroah-Hartman 
9596fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
9696fd7ce5SGreg Kroah-Hartman 		return -EINVAL;
9796fd7ce5SGreg Kroah-Hartman 
98137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
9996fd7ce5SGreg Kroah-Hartman 	if (tty_ldiscs[disc]->refcount)
10096fd7ce5SGreg Kroah-Hartman 		ret = -EBUSY;
10196fd7ce5SGreg Kroah-Hartman 	else
10296fd7ce5SGreg Kroah-Hartman 		tty_ldiscs[disc] = NULL;
103137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
10496fd7ce5SGreg Kroah-Hartman 
10596fd7ce5SGreg Kroah-Hartman 	return ret;
10696fd7ce5SGreg Kroah-Hartman }
10796fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL(tty_unregister_ldisc);
10896fd7ce5SGreg Kroah-Hartman 
10996fd7ce5SGreg Kroah-Hartman static struct tty_ldisc_ops *get_ldops(int disc)
11096fd7ce5SGreg Kroah-Hartman {
11196fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
11296fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops, *ret;
11396fd7ce5SGreg Kroah-Hartman 
114137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
11596fd7ce5SGreg Kroah-Hartman 	ret = ERR_PTR(-EINVAL);
11696fd7ce5SGreg Kroah-Hartman 	ldops = tty_ldiscs[disc];
11796fd7ce5SGreg Kroah-Hartman 	if (ldops) {
11896fd7ce5SGreg Kroah-Hartman 		ret = ERR_PTR(-EAGAIN);
11996fd7ce5SGreg Kroah-Hartman 		if (try_module_get(ldops->owner)) {
12096fd7ce5SGreg Kroah-Hartman 			ldops->refcount++;
12196fd7ce5SGreg Kroah-Hartman 			ret = ldops;
12296fd7ce5SGreg Kroah-Hartman 		}
12396fd7ce5SGreg Kroah-Hartman 	}
124137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
12596fd7ce5SGreg Kroah-Hartman 	return ret;
12696fd7ce5SGreg Kroah-Hartman }
12796fd7ce5SGreg Kroah-Hartman 
12896fd7ce5SGreg Kroah-Hartman static void put_ldops(struct tty_ldisc_ops *ldops)
12996fd7ce5SGreg Kroah-Hartman {
13096fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
13196fd7ce5SGreg Kroah-Hartman 
132137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
13396fd7ce5SGreg Kroah-Hartman 	ldops->refcount--;
13496fd7ce5SGreg Kroah-Hartman 	module_put(ldops->owner);
135137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
13696fd7ce5SGreg Kroah-Hartman }
13796fd7ce5SGreg Kroah-Hartman 
13896fd7ce5SGreg Kroah-Hartman /**
13996fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_get		-	take a reference to an ldisc
14096fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
14196fd7ce5SGreg Kroah-Hartman  *
14296fd7ce5SGreg Kroah-Hartman  *	Takes a reference to a line discipline. Deals with refcounts and
143c0cc1c5dSPeter Hurley  *	module locking counts.
144c0cc1c5dSPeter Hurley  *
145c0cc1c5dSPeter Hurley  *	Returns: -EINVAL if the discipline index is not [N_TTY..NR_LDISCS] or
146c0cc1c5dSPeter Hurley  *			 if the discipline is not registered
147c0cc1c5dSPeter Hurley  *		 -EAGAIN if request_module() failed to load or register the
148c0cc1c5dSPeter Hurley  *			 the discipline
149c0cc1c5dSPeter Hurley  *		 -ENOMEM if allocation failure
150c0cc1c5dSPeter Hurley  *
151c0cc1c5dSPeter Hurley  *		 Otherwise, returns a pointer to the discipline and bumps the
152c0cc1c5dSPeter Hurley  *		 ref count
15396fd7ce5SGreg Kroah-Hartman  *
15496fd7ce5SGreg Kroah-Hartman  *	Locking:
155137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
15696fd7ce5SGreg Kroah-Hartman  */
15796fd7ce5SGreg Kroah-Hartman 
15836697529SPeter Hurley static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
15996fd7ce5SGreg Kroah-Hartman {
16096fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld;
16196fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops;
16296fd7ce5SGreg Kroah-Hartman 
16396fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
16496fd7ce5SGreg Kroah-Hartman 		return ERR_PTR(-EINVAL);
16596fd7ce5SGreg Kroah-Hartman 
16696fd7ce5SGreg Kroah-Hartman 	/*
16796fd7ce5SGreg Kroah-Hartman 	 * Get the ldisc ops - we may need to request them to be loaded
16896fd7ce5SGreg Kroah-Hartman 	 * dynamically and try again.
16996fd7ce5SGreg Kroah-Hartman 	 */
17096fd7ce5SGreg Kroah-Hartman 	ldops = get_ldops(disc);
17196fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ldops)) {
17296fd7ce5SGreg Kroah-Hartman 		request_module("tty-ldisc-%d", disc);
17396fd7ce5SGreg Kroah-Hartman 		ldops = get_ldops(disc);
17496fd7ce5SGreg Kroah-Hartman 		if (IS_ERR(ldops))
17596fd7ce5SGreg Kroah-Hartman 			return ERR_CAST(ldops);
17696fd7ce5SGreg Kroah-Hartman 	}
17796fd7ce5SGreg Kroah-Hartman 
17896fd7ce5SGreg Kroah-Hartman 	ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
17996fd7ce5SGreg Kroah-Hartman 	if (ld == NULL) {
18096fd7ce5SGreg Kroah-Hartman 		put_ldops(ldops);
18196fd7ce5SGreg Kroah-Hartman 		return ERR_PTR(-ENOMEM);
18296fd7ce5SGreg Kroah-Hartman 	}
18396fd7ce5SGreg Kroah-Hartman 
18496fd7ce5SGreg Kroah-Hartman 	ld->ops = ldops;
18536697529SPeter Hurley 	ld->tty = tty;
1861541f845SIvo Sieben 
18796fd7ce5SGreg Kroah-Hartman 	return ld;
18896fd7ce5SGreg Kroah-Hartman }
18996fd7ce5SGreg Kroah-Hartman 
190734de249SPeter Hurley /**
191734de249SPeter Hurley  *	tty_ldisc_put		-	release the ldisc
192734de249SPeter Hurley  *
193734de249SPeter Hurley  *	Complement of tty_ldisc_get().
194734de249SPeter Hurley  */
195cb128f69SDenys Vlasenko static void tty_ldisc_put(struct tty_ldisc *ld)
196734de249SPeter Hurley {
197734de249SPeter Hurley 	if (WARN_ON_ONCE(!ld))
198734de249SPeter Hurley 		return;
199734de249SPeter Hurley 
20036697529SPeter Hurley 	put_ldops(ld->ops);
201734de249SPeter Hurley 	kfree(ld);
202734de249SPeter Hurley }
203734de249SPeter Hurley 
20496fd7ce5SGreg Kroah-Hartman static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
20596fd7ce5SGreg Kroah-Hartman {
20696fd7ce5SGreg Kroah-Hartman 	return (*pos < NR_LDISCS) ? pos : NULL;
20796fd7ce5SGreg Kroah-Hartman }
20896fd7ce5SGreg Kroah-Hartman 
20996fd7ce5SGreg Kroah-Hartman static void *tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos)
21096fd7ce5SGreg Kroah-Hartman {
21196fd7ce5SGreg Kroah-Hartman 	(*pos)++;
21296fd7ce5SGreg Kroah-Hartman 	return (*pos < NR_LDISCS) ? pos : NULL;
21396fd7ce5SGreg Kroah-Hartman }
21496fd7ce5SGreg Kroah-Hartman 
21596fd7ce5SGreg Kroah-Hartman static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
21696fd7ce5SGreg Kroah-Hartman {
21796fd7ce5SGreg Kroah-Hartman }
21896fd7ce5SGreg Kroah-Hartman 
21996fd7ce5SGreg Kroah-Hartman static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
22096fd7ce5SGreg Kroah-Hartman {
22196fd7ce5SGreg Kroah-Hartman 	int i = *(loff_t *)v;
22296fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops;
22396fd7ce5SGreg Kroah-Hartman 
22496fd7ce5SGreg Kroah-Hartman 	ldops = get_ldops(i);
22596fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ldops))
22696fd7ce5SGreg Kroah-Hartman 		return 0;
22796fd7ce5SGreg Kroah-Hartman 	seq_printf(m, "%-10s %2d\n", ldops->name ? ldops->name : "???", i);
22896fd7ce5SGreg Kroah-Hartman 	put_ldops(ldops);
22996fd7ce5SGreg Kroah-Hartman 	return 0;
23096fd7ce5SGreg Kroah-Hartman }
23196fd7ce5SGreg Kroah-Hartman 
23296fd7ce5SGreg Kroah-Hartman static const struct seq_operations tty_ldiscs_seq_ops = {
23396fd7ce5SGreg Kroah-Hartman 	.start	= tty_ldiscs_seq_start,
23496fd7ce5SGreg Kroah-Hartman 	.next	= tty_ldiscs_seq_next,
23596fd7ce5SGreg Kroah-Hartman 	.stop	= tty_ldiscs_seq_stop,
23696fd7ce5SGreg Kroah-Hartman 	.show	= tty_ldiscs_seq_show,
23796fd7ce5SGreg Kroah-Hartman };
23896fd7ce5SGreg Kroah-Hartman 
23996fd7ce5SGreg Kroah-Hartman static int proc_tty_ldiscs_open(struct inode *inode, struct file *file)
24096fd7ce5SGreg Kroah-Hartman {
24196fd7ce5SGreg Kroah-Hartman 	return seq_open(file, &tty_ldiscs_seq_ops);
24296fd7ce5SGreg Kroah-Hartman }
24396fd7ce5SGreg Kroah-Hartman 
24496fd7ce5SGreg Kroah-Hartman const struct file_operations tty_ldiscs_proc_fops = {
24596fd7ce5SGreg Kroah-Hartman 	.owner		= THIS_MODULE,
24696fd7ce5SGreg Kroah-Hartman 	.open		= proc_tty_ldiscs_open,
24796fd7ce5SGreg Kroah-Hartman 	.read		= seq_read,
24896fd7ce5SGreg Kroah-Hartman 	.llseek		= seq_lseek,
24996fd7ce5SGreg Kroah-Hartman 	.release	= seq_release,
25096fd7ce5SGreg Kroah-Hartman };
25196fd7ce5SGreg Kroah-Hartman 
25296fd7ce5SGreg Kroah-Hartman /**
25396fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_ref_wait	-	wait for the tty ldisc
25496fd7ce5SGreg Kroah-Hartman  *	@tty: tty device
25596fd7ce5SGreg Kroah-Hartman  *
25696fd7ce5SGreg Kroah-Hartman  *	Dereference the line discipline for the terminal and take a
25796fd7ce5SGreg Kroah-Hartman  *	reference to it. If the line discipline is in flux then
25896fd7ce5SGreg Kroah-Hartman  *	wait patiently until it changes.
25996fd7ce5SGreg Kroah-Hartman  *
26096fd7ce5SGreg Kroah-Hartman  *	Note: Must not be called from an IRQ/timer context. The caller
26196fd7ce5SGreg Kroah-Hartman  *	must also be careful not to hold other locks that will deadlock
26296fd7ce5SGreg Kroah-Hartman  *	against a discipline change, such as an existing ldisc reference
26396fd7ce5SGreg Kroah-Hartman  *	(which we check for)
26496fd7ce5SGreg Kroah-Hartman  *
26536697529SPeter Hurley  *	Note: only callable from a file_operations routine (which
26636697529SPeter Hurley  *	guarantees tty->ldisc != NULL when the lock is acquired).
26796fd7ce5SGreg Kroah-Hartman  */
26896fd7ce5SGreg Kroah-Hartman 
26996fd7ce5SGreg Kroah-Hartman struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
27096fd7ce5SGreg Kroah-Hartman {
27136697529SPeter Hurley 	ldsem_down_read(&tty->ldisc_sem, MAX_SCHEDULE_TIMEOUT);
27236697529SPeter Hurley 	WARN_ON(!tty->ldisc);
27336697529SPeter Hurley 	return tty->ldisc;
27496fd7ce5SGreg Kroah-Hartman }
27596fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
27696fd7ce5SGreg Kroah-Hartman 
27796fd7ce5SGreg Kroah-Hartman /**
27896fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_ref		-	get the tty ldisc
27996fd7ce5SGreg Kroah-Hartman  *	@tty: tty device
28096fd7ce5SGreg Kroah-Hartman  *
28196fd7ce5SGreg Kroah-Hartman  *	Dereference the line discipline for the terminal and take a
28296fd7ce5SGreg Kroah-Hartman  *	reference to it. If the line discipline is in flux then
28396fd7ce5SGreg Kroah-Hartman  *	return NULL. Can be called from IRQ and timer functions.
28496fd7ce5SGreg Kroah-Hartman  */
28596fd7ce5SGreg Kroah-Hartman 
28696fd7ce5SGreg Kroah-Hartman struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
28796fd7ce5SGreg Kroah-Hartman {
28836697529SPeter Hurley 	struct tty_ldisc *ld = NULL;
28936697529SPeter Hurley 
29036697529SPeter Hurley 	if (ldsem_down_read_trylock(&tty->ldisc_sem)) {
29136697529SPeter Hurley 		ld = tty->ldisc;
29236697529SPeter Hurley 		if (!ld)
29336697529SPeter Hurley 			ldsem_up_read(&tty->ldisc_sem);
29436697529SPeter Hurley 	}
29536697529SPeter Hurley 	return ld;
29696fd7ce5SGreg Kroah-Hartman }
29796fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_ref);
29896fd7ce5SGreg Kroah-Hartman 
29996fd7ce5SGreg Kroah-Hartman /**
30096fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_deref		-	free a tty ldisc reference
30196fd7ce5SGreg Kroah-Hartman  *	@ld: reference to free up
30296fd7ce5SGreg Kroah-Hartman  *
30396fd7ce5SGreg Kroah-Hartman  *	Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
30496fd7ce5SGreg Kroah-Hartman  *	be called in IRQ context.
30596fd7ce5SGreg Kroah-Hartman  */
30696fd7ce5SGreg Kroah-Hartman 
30796fd7ce5SGreg Kroah-Hartman void tty_ldisc_deref(struct tty_ldisc *ld)
30896fd7ce5SGreg Kroah-Hartman {
30936697529SPeter Hurley 	ldsem_up_read(&ld->tty->ldisc_sem);
31096fd7ce5SGreg Kroah-Hartman }
31196fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_deref);
31296fd7ce5SGreg Kroah-Hartman 
313d2c43890SPeter Hurley 
314c2bb524bSPeter Hurley static inline int
315e80a10eeSPeter Hurley __tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
316d2c43890SPeter Hurley {
317d2c43890SPeter Hurley 	return ldsem_down_write(&tty->ldisc_sem, timeout);
318d2c43890SPeter Hurley }
319d2c43890SPeter Hurley 
320c2bb524bSPeter Hurley static inline int
321e80a10eeSPeter Hurley __tty_ldisc_lock_nested(struct tty_struct *tty, unsigned long timeout)
322d2c43890SPeter Hurley {
323d2c43890SPeter Hurley 	return ldsem_down_write_nested(&tty->ldisc_sem,
324d2c43890SPeter Hurley 				       LDISC_SEM_OTHER, timeout);
325d2c43890SPeter Hurley }
326d2c43890SPeter Hurley 
327e80a10eeSPeter Hurley static inline void __tty_ldisc_unlock(struct tty_struct *tty)
328d2c43890SPeter Hurley {
32952772ea6SGuillaume Gomez 	ldsem_up_write(&tty->ldisc_sem);
330d2c43890SPeter Hurley }
331d2c43890SPeter Hurley 
332c2bb524bSPeter Hurley static int tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
333fae76e9aSPeter Hurley {
334fae76e9aSPeter Hurley 	int ret;
335fae76e9aSPeter Hurley 
336fae76e9aSPeter Hurley 	ret = __tty_ldisc_lock(tty, timeout);
337fae76e9aSPeter Hurley 	if (!ret)
338fae76e9aSPeter Hurley 		return -EBUSY;
339fae76e9aSPeter Hurley 	set_bit(TTY_LDISC_HALTED, &tty->flags);
340fae76e9aSPeter Hurley 	return 0;
341fae76e9aSPeter Hurley }
342fae76e9aSPeter Hurley 
343fae76e9aSPeter Hurley static void tty_ldisc_unlock(struct tty_struct *tty)
344fae76e9aSPeter Hurley {
345fae76e9aSPeter Hurley 	clear_bit(TTY_LDISC_HALTED, &tty->flags);
346fae76e9aSPeter Hurley 	__tty_ldisc_unlock(tty);
347fae76e9aSPeter Hurley }
348fae76e9aSPeter Hurley 
349c2bb524bSPeter Hurley static int
350d2c43890SPeter Hurley tty_ldisc_lock_pair_timeout(struct tty_struct *tty, struct tty_struct *tty2,
351d2c43890SPeter Hurley 			    unsigned long timeout)
352d2c43890SPeter Hurley {
353d2c43890SPeter Hurley 	int ret;
354d2c43890SPeter Hurley 
355d2c43890SPeter Hurley 	if (tty < tty2) {
356e80a10eeSPeter Hurley 		ret = __tty_ldisc_lock(tty, timeout);
357d2c43890SPeter Hurley 		if (ret) {
358e80a10eeSPeter Hurley 			ret = __tty_ldisc_lock_nested(tty2, timeout);
359d2c43890SPeter Hurley 			if (!ret)
360e80a10eeSPeter Hurley 				__tty_ldisc_unlock(tty);
361d2c43890SPeter Hurley 		}
362d2c43890SPeter Hurley 	} else {
363d2c43890SPeter Hurley 		/* if this is possible, it has lots of implications */
364d2c43890SPeter Hurley 		WARN_ON_ONCE(tty == tty2);
365d2c43890SPeter Hurley 		if (tty2 && tty != tty2) {
366e80a10eeSPeter Hurley 			ret = __tty_ldisc_lock(tty2, timeout);
367d2c43890SPeter Hurley 			if (ret) {
368e80a10eeSPeter Hurley 				ret = __tty_ldisc_lock_nested(tty, timeout);
369d2c43890SPeter Hurley 				if (!ret)
370e80a10eeSPeter Hurley 					__tty_ldisc_unlock(tty2);
371d2c43890SPeter Hurley 			}
372d2c43890SPeter Hurley 		} else
373e80a10eeSPeter Hurley 			ret = __tty_ldisc_lock(tty, timeout);
374d2c43890SPeter Hurley 	}
375d2c43890SPeter Hurley 
376d2c43890SPeter Hurley 	if (!ret)
377d2c43890SPeter Hurley 		return -EBUSY;
378d2c43890SPeter Hurley 
379d2c43890SPeter Hurley 	set_bit(TTY_LDISC_HALTED, &tty->flags);
380d2c43890SPeter Hurley 	if (tty2)
381d2c43890SPeter Hurley 		set_bit(TTY_LDISC_HALTED, &tty2->flags);
382d2c43890SPeter Hurley 	return 0;
383d2c43890SPeter Hurley }
384d2c43890SPeter Hurley 
385c2bb524bSPeter Hurley static void tty_ldisc_lock_pair(struct tty_struct *tty, struct tty_struct *tty2)
386d2c43890SPeter Hurley {
387d2c43890SPeter Hurley 	tty_ldisc_lock_pair_timeout(tty, tty2, MAX_SCHEDULE_TIMEOUT);
388d2c43890SPeter Hurley }
389d2c43890SPeter Hurley 
390c2bb524bSPeter Hurley static void tty_ldisc_unlock_pair(struct tty_struct *tty,
391d2c43890SPeter Hurley 				  struct tty_struct *tty2)
392d2c43890SPeter Hurley {
393e80a10eeSPeter Hurley 	__tty_ldisc_unlock(tty);
394d2c43890SPeter Hurley 	if (tty2)
395e80a10eeSPeter Hurley 		__tty_ldisc_unlock(tty2);
396d2c43890SPeter Hurley }
397d2c43890SPeter Hurley 
39896fd7ce5SGreg Kroah-Hartman /**
39996fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_flush	-	flush line discipline queue
40096fd7ce5SGreg Kroah-Hartman  *	@tty: tty
40196fd7ce5SGreg Kroah-Hartman  *
40286c80a8eSPeter Hurley  *	Flush the line discipline queue (if any) and the tty flip buffers
40386c80a8eSPeter Hurley  *	for this tty.
40496fd7ce5SGreg Kroah-Hartman  */
40596fd7ce5SGreg Kroah-Hartman 
40696fd7ce5SGreg Kroah-Hartman void tty_ldisc_flush(struct tty_struct *tty)
40796fd7ce5SGreg Kroah-Hartman {
40896fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld = tty_ldisc_ref(tty);
40986c80a8eSPeter Hurley 
41086c80a8eSPeter Hurley 	tty_buffer_flush(tty, ld);
41186c80a8eSPeter Hurley 	if (ld)
41296fd7ce5SGreg Kroah-Hartman 		tty_ldisc_deref(ld);
41396fd7ce5SGreg Kroah-Hartman }
41496fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_flush);
41596fd7ce5SGreg Kroah-Hartman 
41696fd7ce5SGreg Kroah-Hartman /**
41796fd7ce5SGreg Kroah-Hartman  *	tty_set_termios_ldisc		-	set ldisc field
41896fd7ce5SGreg Kroah-Hartman  *	@tty: tty structure
41996fd7ce5SGreg Kroah-Hartman  *	@num: line discipline number
42096fd7ce5SGreg Kroah-Hartman  *
42196fd7ce5SGreg Kroah-Hartman  *	This is probably overkill for real world processors but
42296fd7ce5SGreg Kroah-Hartman  *	they are not on hot paths so a little discipline won't do
42396fd7ce5SGreg Kroah-Hartman  *	any harm.
42496fd7ce5SGreg Kroah-Hartman  *
425dd42bf11SPeter Hurley  *	The line discipline-related tty_struct fields are reset to
426dd42bf11SPeter Hurley  *	prevent the ldisc driver from re-using stale information for
427dd42bf11SPeter Hurley  *	the new ldisc instance.
428dd42bf11SPeter Hurley  *
4296a1c0680SPeter Hurley  *	Locking: takes termios_rwsem
43096fd7ce5SGreg Kroah-Hartman  */
43196fd7ce5SGreg Kroah-Hartman 
43296fd7ce5SGreg Kroah-Hartman static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
43396fd7ce5SGreg Kroah-Hartman {
4346a1c0680SPeter Hurley 	down_write(&tty->termios_rwsem);
435adc8d746SAlan Cox 	tty->termios.c_line = num;
4366a1c0680SPeter Hurley 	up_write(&tty->termios_rwsem);
437dd42bf11SPeter Hurley 
438dd42bf11SPeter Hurley 	tty->disc_data = NULL;
439dd42bf11SPeter Hurley 	tty->receive_room = 0;
44096fd7ce5SGreg Kroah-Hartman }
44196fd7ce5SGreg Kroah-Hartman 
44296fd7ce5SGreg Kroah-Hartman /**
44396fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_open		-	open a line discipline
44496fd7ce5SGreg Kroah-Hartman  *	@tty: tty we are opening the ldisc on
44596fd7ce5SGreg Kroah-Hartman  *	@ld: discipline to open
44696fd7ce5SGreg Kroah-Hartman  *
44796fd7ce5SGreg Kroah-Hartman  *	A helper opening method. Also a convenient debugging and check
44896fd7ce5SGreg Kroah-Hartman  *	point.
44996fd7ce5SGreg Kroah-Hartman  *
45096fd7ce5SGreg Kroah-Hartman  *	Locking: always called with BTM already held.
45196fd7ce5SGreg Kroah-Hartman  */
45296fd7ce5SGreg Kroah-Hartman 
45396fd7ce5SGreg Kroah-Hartman static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
45496fd7ce5SGreg Kroah-Hartman {
45596fd7ce5SGreg Kroah-Hartman 	WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
45696fd7ce5SGreg Kroah-Hartman 	if (ld->ops->open) {
45796fd7ce5SGreg Kroah-Hartman 		int ret;
45896fd7ce5SGreg Kroah-Hartman                 /* BTM here locks versus a hangup event */
45996fd7ce5SGreg Kroah-Hartman 		ret = ld->ops->open(tty);
4607f90cfc5SJiri Slaby 		if (ret)
4617f90cfc5SJiri Slaby 			clear_bit(TTY_LDISC_OPEN, &tty->flags);
462fb6edc91SPeter Hurley 
463fb6edc91SPeter Hurley 		tty_ldisc_debug(tty, "%p: opened\n", tty->ldisc);
46496fd7ce5SGreg Kroah-Hartman 		return ret;
46596fd7ce5SGreg Kroah-Hartman 	}
46696fd7ce5SGreg Kroah-Hartman 	return 0;
46796fd7ce5SGreg Kroah-Hartman }
46896fd7ce5SGreg Kroah-Hartman 
46996fd7ce5SGreg Kroah-Hartman /**
47096fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_close		-	close a line discipline
47196fd7ce5SGreg Kroah-Hartman  *	@tty: tty we are opening the ldisc on
47296fd7ce5SGreg Kroah-Hartman  *	@ld: discipline to close
47396fd7ce5SGreg Kroah-Hartman  *
47496fd7ce5SGreg Kroah-Hartman  *	A helper close method. Also a convenient debugging and check
47596fd7ce5SGreg Kroah-Hartman  *	point.
47696fd7ce5SGreg Kroah-Hartman  */
47796fd7ce5SGreg Kroah-Hartman 
47896fd7ce5SGreg Kroah-Hartman static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
47996fd7ce5SGreg Kroah-Hartman {
48096fd7ce5SGreg Kroah-Hartman 	WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
48196fd7ce5SGreg Kroah-Hartman 	clear_bit(TTY_LDISC_OPEN, &tty->flags);
48296fd7ce5SGreg Kroah-Hartman 	if (ld->ops->close)
48396fd7ce5SGreg Kroah-Hartman 		ld->ops->close(tty);
484fb6edc91SPeter Hurley 	tty_ldisc_debug(tty, "%p: closed\n", tty->ldisc);
48596fd7ce5SGreg Kroah-Hartman }
48696fd7ce5SGreg Kroah-Hartman 
48796fd7ce5SGreg Kroah-Hartman /**
48896fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_restore	-	helper for tty ldisc change
48996fd7ce5SGreg Kroah-Hartman  *	@tty: tty to recover
49096fd7ce5SGreg Kroah-Hartman  *	@old: previous ldisc
49196fd7ce5SGreg Kroah-Hartman  *
49296fd7ce5SGreg Kroah-Hartman  *	Restore the previous line discipline or N_TTY when a line discipline
49396fd7ce5SGreg Kroah-Hartman  *	change fails due to an open error
49496fd7ce5SGreg Kroah-Hartman  */
49596fd7ce5SGreg Kroah-Hartman 
49696fd7ce5SGreg Kroah-Hartman static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
49796fd7ce5SGreg Kroah-Hartman {
49896fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *new_ldisc;
49996fd7ce5SGreg Kroah-Hartman 	int r;
50096fd7ce5SGreg Kroah-Hartman 
50196fd7ce5SGreg Kroah-Hartman 	/* There is an outstanding reference here so this is safe */
50236697529SPeter Hurley 	old = tty_ldisc_get(tty, old->ops->num);
50396fd7ce5SGreg Kroah-Hartman 	WARN_ON(IS_ERR(old));
504f4807045SPeter Hurley 	tty->ldisc = old;
50596fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, old->ops->num);
50696fd7ce5SGreg Kroah-Hartman 	if (tty_ldisc_open(tty, old) < 0) {
50796fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(old);
50896fd7ce5SGreg Kroah-Hartman 		/* This driver is always present */
50936697529SPeter Hurley 		new_ldisc = tty_ldisc_get(tty, N_TTY);
51096fd7ce5SGreg Kroah-Hartman 		if (IS_ERR(new_ldisc))
51196fd7ce5SGreg Kroah-Hartman 			panic("n_tty: get");
512f4807045SPeter Hurley 		tty->ldisc = new_ldisc;
51396fd7ce5SGreg Kroah-Hartman 		tty_set_termios_ldisc(tty, N_TTY);
51496fd7ce5SGreg Kroah-Hartman 		r = tty_ldisc_open(tty, new_ldisc);
51596fd7ce5SGreg Kroah-Hartman 		if (r < 0)
51696fd7ce5SGreg Kroah-Hartman 			panic("Couldn't open N_TTY ldisc for "
51796fd7ce5SGreg Kroah-Hartman 			      "%s --- error %d.",
518429b4749SRasmus Villemoes 			      tty_name(tty), r);
51996fd7ce5SGreg Kroah-Hartman 	}
52096fd7ce5SGreg Kroah-Hartman }
52196fd7ce5SGreg Kroah-Hartman 
52296fd7ce5SGreg Kroah-Hartman /**
52396fd7ce5SGreg Kroah-Hartman  *	tty_set_ldisc		-	set line discipline
52496fd7ce5SGreg Kroah-Hartman  *	@tty: the terminal to set
52596fd7ce5SGreg Kroah-Hartman  *	@ldisc: the line discipline
52696fd7ce5SGreg Kroah-Hartman  *
52796fd7ce5SGreg Kroah-Hartman  *	Set the discipline of a tty line. Must be called from a process
52896fd7ce5SGreg Kroah-Hartman  *	context. The ldisc change logic has to protect itself against any
52996fd7ce5SGreg Kroah-Hartman  *	overlapping ldisc change (including on the other end of pty pairs),
53096fd7ce5SGreg Kroah-Hartman  *	the close of one side of a tty/pty pair, and eventually hangup.
53196fd7ce5SGreg Kroah-Hartman  */
53296fd7ce5SGreg Kroah-Hartman 
53396fd7ce5SGreg Kroah-Hartman int tty_set_ldisc(struct tty_struct *tty, int ldisc)
53496fd7ce5SGreg Kroah-Hartman {
53596fd7ce5SGreg Kroah-Hartman 	int retval;
5369fbfa34cSPeter Hurley 	struct tty_ldisc *old_ldisc, *new_ldisc;
53796fd7ce5SGreg Kroah-Hartman 
53836697529SPeter Hurley 	new_ldisc = tty_ldisc_get(tty, ldisc);
53996fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(new_ldisc))
54096fd7ce5SGreg Kroah-Hartman 		return PTR_ERR(new_ldisc);
54196fd7ce5SGreg Kroah-Hartman 
542c8483bc9SPeter Hurley 	tty_lock(tty);
543276a661aSPeter Hurley 	retval = tty_ldisc_lock(tty, 5 * HZ);
54463d8cb3fSPeter Hurley 	if (retval)
54563d8cb3fSPeter Hurley 		goto err;
54696fd7ce5SGreg Kroah-Hartman 
54763d8cb3fSPeter Hurley 	/* Check the no-op case */
54863d8cb3fSPeter Hurley 	if (tty->ldisc->ops->num == ldisc)
54963d8cb3fSPeter Hurley 		goto out;
55096fd7ce5SGreg Kroah-Hartman 
55163d8cb3fSPeter Hurley 	if (test_bit(TTY_HUPPED, &tty->flags)) {
55263d8cb3fSPeter Hurley 		/* We were raced by hangup */
55363d8cb3fSPeter Hurley 		retval = -EIO;
55463d8cb3fSPeter Hurley 		goto out;
55596fd7ce5SGreg Kroah-Hartman 	}
55696fd7ce5SGreg Kroah-Hartman 
5579fbfa34cSPeter Hurley 	old_ldisc = tty->ldisc;
558100eeae2SJiri Slaby 
5599fbfa34cSPeter Hurley 	/* Shutdown the old discipline. */
5609fbfa34cSPeter Hurley 	tty_ldisc_close(tty, old_ldisc);
56196fd7ce5SGreg Kroah-Hartman 
56296fd7ce5SGreg Kroah-Hartman 	/* Now set up the new line discipline. */
563f4807045SPeter Hurley 	tty->ldisc = new_ldisc;
56496fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, ldisc);
56596fd7ce5SGreg Kroah-Hartman 
56696fd7ce5SGreg Kroah-Hartman 	retval = tty_ldisc_open(tty, new_ldisc);
56796fd7ce5SGreg Kroah-Hartman 	if (retval < 0) {
56896fd7ce5SGreg Kroah-Hartman 		/* Back to the old one or N_TTY if we can't */
56996fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(new_ldisc);
5709fbfa34cSPeter Hurley 		tty_ldisc_restore(tty, old_ldisc);
57196fd7ce5SGreg Kroah-Hartman 	}
57296fd7ce5SGreg Kroah-Hartman 
5739191aaaaSPeter Hurley 	if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc) {
5749191aaaaSPeter Hurley 		down_read(&tty->termios_rwsem);
57596fd7ce5SGreg Kroah-Hartman 		tty->ops->set_ldisc(tty);
5769191aaaaSPeter Hurley 		up_read(&tty->termios_rwsem);
5779191aaaaSPeter Hurley 	}
57896fd7ce5SGreg Kroah-Hartman 
579b0e95858SPeter Hurley 	/* At this point we hold a reference to the new ldisc and a
580b0e95858SPeter Hurley 	   reference to the old ldisc, or we hold two references to
581b0e95858SPeter Hurley 	   the old ldisc (if it was restored as part of error cleanup
582b0e95858SPeter Hurley 	   above). In either case, releasing a single reference from
583b0e95858SPeter Hurley 	   the old ldisc is correct. */
58463d8cb3fSPeter Hurley 	new_ldisc = old_ldisc;
58563d8cb3fSPeter Hurley out:
586276a661aSPeter Hurley 	tty_ldisc_unlock(tty);
58796fd7ce5SGreg Kroah-Hartman 
58896fd7ce5SGreg Kroah-Hartman 	/* Restart the work queue in case no characters kick it off. Safe if
58996fd7ce5SGreg Kroah-Hartman 	   already running */
59017a69219SPeter Hurley 	tty_buffer_restart_work(tty->port);
59163d8cb3fSPeter Hurley err:
59263d8cb3fSPeter Hurley 	tty_ldisc_put(new_ldisc);	/* drop the extra reference */
59389c8d91eSAlan Cox 	tty_unlock(tty);
59496fd7ce5SGreg Kroah-Hartman 	return retval;
59596fd7ce5SGreg Kroah-Hartman }
59696fd7ce5SGreg Kroah-Hartman 
59796fd7ce5SGreg Kroah-Hartman /**
59896fd7ce5SGreg Kroah-Hartman  *	tty_reset_termios	-	reset terminal state
59996fd7ce5SGreg Kroah-Hartman  *	@tty: tty to reset
60096fd7ce5SGreg Kroah-Hartman  *
60196fd7ce5SGreg Kroah-Hartman  *	Restore a terminal to the driver default state.
60296fd7ce5SGreg Kroah-Hartman  */
60396fd7ce5SGreg Kroah-Hartman 
60496fd7ce5SGreg Kroah-Hartman static void tty_reset_termios(struct tty_struct *tty)
60596fd7ce5SGreg Kroah-Hartman {
6066a1c0680SPeter Hurley 	down_write(&tty->termios_rwsem);
607adc8d746SAlan Cox 	tty->termios = tty->driver->init_termios;
608adc8d746SAlan Cox 	tty->termios.c_ispeed = tty_termios_input_baud_rate(&tty->termios);
609adc8d746SAlan Cox 	tty->termios.c_ospeed = tty_termios_baud_rate(&tty->termios);
6106a1c0680SPeter Hurley 	up_write(&tty->termios_rwsem);
61196fd7ce5SGreg Kroah-Hartman }
61296fd7ce5SGreg Kroah-Hartman 
61396fd7ce5SGreg Kroah-Hartman 
61496fd7ce5SGreg Kroah-Hartman /**
61596fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_reinit	-	reinitialise the tty ldisc
61696fd7ce5SGreg Kroah-Hartman  *	@tty: tty to reinit
61796fd7ce5SGreg Kroah-Hartman  *	@ldisc: line discipline to reinitialize
61896fd7ce5SGreg Kroah-Hartman  *
61996fd7ce5SGreg Kroah-Hartman  *	Switch the tty to a line discipline and leave the ldisc
62096fd7ce5SGreg Kroah-Hartman  *	state closed
62196fd7ce5SGreg Kroah-Hartman  */
62296fd7ce5SGreg Kroah-Hartman 
6231c95ba1eSPhilippe Rétornaz static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
62496fd7ce5SGreg Kroah-Hartman {
62536697529SPeter Hurley 	struct tty_ldisc *ld = tty_ldisc_get(tty, ldisc);
6261c95ba1eSPhilippe Rétornaz 
6271c95ba1eSPhilippe Rétornaz 	if (IS_ERR(ld))
6281c95ba1eSPhilippe Rétornaz 		return -1;
62996fd7ce5SGreg Kroah-Hartman 
63096fd7ce5SGreg Kroah-Hartman 	tty_ldisc_close(tty, tty->ldisc);
63196fd7ce5SGreg Kroah-Hartman 	tty_ldisc_put(tty->ldisc);
63296fd7ce5SGreg Kroah-Hartman 	/*
63396fd7ce5SGreg Kroah-Hartman 	 *	Switch the line discipline back
63496fd7ce5SGreg Kroah-Hartman 	 */
635f4807045SPeter Hurley 	tty->ldisc = ld;
63696fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, ldisc);
6371c95ba1eSPhilippe Rétornaz 
6381c95ba1eSPhilippe Rétornaz 	return 0;
63996fd7ce5SGreg Kroah-Hartman }
64096fd7ce5SGreg Kroah-Hartman 
64196fd7ce5SGreg Kroah-Hartman /**
64296fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_hangup		-	hangup ldisc reset
64396fd7ce5SGreg Kroah-Hartman  *	@tty: tty being hung up
64496fd7ce5SGreg Kroah-Hartman  *
64596fd7ce5SGreg Kroah-Hartman  *	Some tty devices reset their termios when they receive a hangup
64696fd7ce5SGreg Kroah-Hartman  *	event. In that situation we must also switch back to N_TTY properly
64796fd7ce5SGreg Kroah-Hartman  *	before we reset the termios data.
64896fd7ce5SGreg Kroah-Hartman  *
64996fd7ce5SGreg Kroah-Hartman  *	Locking: We can take the ldisc mutex as the rest of the code is
65096fd7ce5SGreg Kroah-Hartman  *	careful to allow for this.
65196fd7ce5SGreg Kroah-Hartman  *
65296fd7ce5SGreg Kroah-Hartman  *	In the pty pair case this occurs in the close() path of the
65396fd7ce5SGreg Kroah-Hartman  *	tty itself so we must be careful about locking rules.
65496fd7ce5SGreg Kroah-Hartman  */
65596fd7ce5SGreg Kroah-Hartman 
65696fd7ce5SGreg Kroah-Hartman void tty_ldisc_hangup(struct tty_struct *tty)
65796fd7ce5SGreg Kroah-Hartman {
65896fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld;
65996fd7ce5SGreg Kroah-Hartman 	int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
66096fd7ce5SGreg Kroah-Hartman 	int err = 0;
66196fd7ce5SGreg Kroah-Hartman 
662fb6edc91SPeter Hurley 	tty_ldisc_debug(tty, "%p: closing\n", tty->ldisc);
663fc575ee6SPeter Hurley 
66496fd7ce5SGreg Kroah-Hartman 	ld = tty_ldisc_ref(tty);
66596fd7ce5SGreg Kroah-Hartman 	if (ld != NULL) {
66696fd7ce5SGreg Kroah-Hartman 		if (ld->ops->flush_buffer)
66796fd7ce5SGreg Kroah-Hartman 			ld->ops->flush_buffer(tty);
66896fd7ce5SGreg Kroah-Hartman 		tty_driver_flush_buffer(tty);
66996fd7ce5SGreg Kroah-Hartman 		if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
67096fd7ce5SGreg Kroah-Hartman 		    ld->ops->write_wakeup)
67196fd7ce5SGreg Kroah-Hartman 			ld->ops->write_wakeup(tty);
67296fd7ce5SGreg Kroah-Hartman 		if (ld->ops->hangup)
67396fd7ce5SGreg Kroah-Hartman 			ld->ops->hangup(tty);
67496fd7ce5SGreg Kroah-Hartman 		tty_ldisc_deref(ld);
67596fd7ce5SGreg Kroah-Hartman 	}
67636697529SPeter Hurley 
67796fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
67896fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible_poll(&tty->read_wait, POLLIN);
67936697529SPeter Hurley 
68096fd7ce5SGreg Kroah-Hartman 	/*
68196fd7ce5SGreg Kroah-Hartman 	 * Shutdown the current line discipline, and reset it to
68296fd7ce5SGreg Kroah-Hartman 	 * N_TTY if need be.
68396fd7ce5SGreg Kroah-Hartman 	 *
68496fd7ce5SGreg Kroah-Hartman 	 * Avoid racing set_ldisc or tty_ldisc_release
68596fd7ce5SGreg Kroah-Hartman 	 */
686fae76e9aSPeter Hurley 	tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
68796fd7ce5SGreg Kroah-Hartman 
68836697529SPeter Hurley 	if (tty->ldisc) {
689c8785241SPeter Hurley 
690c8785241SPeter Hurley 		/* At this point we have a halted ldisc; we want to close it and
691c8785241SPeter Hurley 		   reopen a new ldisc. We could defer the reopen to the next
692c8785241SPeter Hurley 		   open but it means auditing a lot of other paths so this is
693c8785241SPeter Hurley 		   a FIXME */
69496fd7ce5SGreg Kroah-Hartman 		if (reset == 0) {
6951c95ba1eSPhilippe Rétornaz 
696adc8d746SAlan Cox 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))
69796fd7ce5SGreg Kroah-Hartman 				err = tty_ldisc_open(tty, tty->ldisc);
6981c95ba1eSPhilippe Rétornaz 			else
6991c95ba1eSPhilippe Rétornaz 				err = 1;
70096fd7ce5SGreg Kroah-Hartman 		}
70196fd7ce5SGreg Kroah-Hartman 		/* If the re-open fails or we reset then go to N_TTY. The
70296fd7ce5SGreg Kroah-Hartman 		   N_TTY open cannot fail */
70396fd7ce5SGreg Kroah-Hartman 		if (reset || err) {
7041c95ba1eSPhilippe Rétornaz 			BUG_ON(tty_ldisc_reinit(tty, N_TTY));
70596fd7ce5SGreg Kroah-Hartman 			WARN_ON(tty_ldisc_open(tty, tty->ldisc));
70696fd7ce5SGreg Kroah-Hartman 		}
70796fd7ce5SGreg Kroah-Hartman 	}
708fae76e9aSPeter Hurley 	tty_ldisc_unlock(tty);
70996fd7ce5SGreg Kroah-Hartman 	if (reset)
71096fd7ce5SGreg Kroah-Hartman 		tty_reset_termios(tty);
711fc575ee6SPeter Hurley 
712fb6edc91SPeter Hurley 	tty_ldisc_debug(tty, "%p: re-opened\n", tty->ldisc);
71396fd7ce5SGreg Kroah-Hartman }
71496fd7ce5SGreg Kroah-Hartman 
71596fd7ce5SGreg Kroah-Hartman /**
71696fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_setup			-	open line discipline
71796fd7ce5SGreg Kroah-Hartman  *	@tty: tty being shut down
71896fd7ce5SGreg Kroah-Hartman  *	@o_tty: pair tty for pty/tty pairs
71996fd7ce5SGreg Kroah-Hartman  *
72096fd7ce5SGreg Kroah-Hartman  *	Called during the initial open of a tty/pty pair in order to set up the
72196fd7ce5SGreg Kroah-Hartman  *	line disciplines and bind them to the tty. This has no locking issues
72296fd7ce5SGreg Kroah-Hartman  *	as the device isn't yet active.
72396fd7ce5SGreg Kroah-Hartman  */
72496fd7ce5SGreg Kroah-Hartman 
72596fd7ce5SGreg Kroah-Hartman int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
72696fd7ce5SGreg Kroah-Hartman {
72796fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld = tty->ldisc;
72896fd7ce5SGreg Kroah-Hartman 	int retval;
72996fd7ce5SGreg Kroah-Hartman 
73096fd7ce5SGreg Kroah-Hartman 	retval = tty_ldisc_open(tty, ld);
73196fd7ce5SGreg Kroah-Hartman 	if (retval)
73296fd7ce5SGreg Kroah-Hartman 		return retval;
73396fd7ce5SGreg Kroah-Hartman 
73496fd7ce5SGreg Kroah-Hartman 	if (o_tty) {
73596fd7ce5SGreg Kroah-Hartman 		retval = tty_ldisc_open(o_tty, o_tty->ldisc);
73696fd7ce5SGreg Kroah-Hartman 		if (retval) {
73796fd7ce5SGreg Kroah-Hartman 			tty_ldisc_close(tty, ld);
73896fd7ce5SGreg Kroah-Hartman 			return retval;
73996fd7ce5SGreg Kroah-Hartman 		}
74096fd7ce5SGreg Kroah-Hartman 	}
74196fd7ce5SGreg Kroah-Hartman 	return 0;
74296fd7ce5SGreg Kroah-Hartman }
74389c8d91eSAlan Cox 
74489c8d91eSAlan Cox static void tty_ldisc_kill(struct tty_struct *tty)
74589c8d91eSAlan Cox {
74689c8d91eSAlan Cox 	/*
74789c8d91eSAlan Cox 	 * Now kill off the ldisc
74889c8d91eSAlan Cox 	 */
74989c8d91eSAlan Cox 	tty_ldisc_close(tty, tty->ldisc);
75089c8d91eSAlan Cox 	tty_ldisc_put(tty->ldisc);
75189c8d91eSAlan Cox 	/* Force an oops if we mess this up */
75289c8d91eSAlan Cox 	tty->ldisc = NULL;
75389c8d91eSAlan Cox }
75489c8d91eSAlan Cox 
75596fd7ce5SGreg Kroah-Hartman /**
75696fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_release		-	release line discipline
75762462aefSPeter Hurley  *	@tty: tty being shut down (or one end of pty pair)
75896fd7ce5SGreg Kroah-Hartman  *
75962462aefSPeter Hurley  *	Called during the final close of a tty or a pty pair in order to shut
7605b6e6832SPeter Hurley  *	down the line discpline layer. On exit, each tty's ldisc is NULL.
76196fd7ce5SGreg Kroah-Hartman  */
76296fd7ce5SGreg Kroah-Hartman 
76362462aefSPeter Hurley void tty_ldisc_release(struct tty_struct *tty)
76496fd7ce5SGreg Kroah-Hartman {
76562462aefSPeter Hurley 	struct tty_struct *o_tty = tty->link;
76662462aefSPeter Hurley 
76796fd7ce5SGreg Kroah-Hartman 	/*
768a2965b7bSPeter Hurley 	 * Shutdown this line discipline. As this is the final close,
769a2965b7bSPeter Hurley 	 * it does not race with the set_ldisc code path.
77096fd7ce5SGreg Kroah-Hartman 	 */
77196fd7ce5SGreg Kroah-Hartman 
77236697529SPeter Hurley 	tty_ldisc_lock_pair(tty, o_tty);
77389c8d91eSAlan Cox 	tty_ldisc_kill(tty);
77489c8d91eSAlan Cox 	if (o_tty)
77589c8d91eSAlan Cox 		tty_ldisc_kill(o_tty);
77636697529SPeter Hurley 	tty_ldisc_unlock_pair(tty, o_tty);
77736697529SPeter Hurley 
77896fd7ce5SGreg Kroah-Hartman 	/* And the memory resources remaining (buffers, termios) will be
77996fd7ce5SGreg Kroah-Hartman 	   disposed of when the kref hits zero */
780fc575ee6SPeter Hurley 
781fb6edc91SPeter Hurley 	tty_ldisc_debug(tty, "released\n");
78296fd7ce5SGreg Kroah-Hartman }
78396fd7ce5SGreg Kroah-Hartman 
78496fd7ce5SGreg Kroah-Hartman /**
78596fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_init		-	ldisc setup for new tty
78696fd7ce5SGreg Kroah-Hartman  *	@tty: tty being allocated
78796fd7ce5SGreg Kroah-Hartman  *
78896fd7ce5SGreg Kroah-Hartman  *	Set up the line discipline objects for a newly allocated tty. Note that
78996fd7ce5SGreg Kroah-Hartman  *	the tty structure is not completely set up when this call is made.
79096fd7ce5SGreg Kroah-Hartman  */
79196fd7ce5SGreg Kroah-Hartman 
79296fd7ce5SGreg Kroah-Hartman void tty_ldisc_init(struct tty_struct *tty)
79396fd7ce5SGreg Kroah-Hartman {
79436697529SPeter Hurley 	struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
79596fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ld))
79696fd7ce5SGreg Kroah-Hartman 		panic("n_tty: init_tty");
797f4807045SPeter Hurley 	tty->ldisc = ld;
79896fd7ce5SGreg Kroah-Hartman }
79996fd7ce5SGreg Kroah-Hartman 
8006716671dSJiri Slaby /**
801c8b710b3SPeter Hurley  *	tty_ldisc_deinit	-	ldisc cleanup for new tty
8026716671dSJiri Slaby  *	@tty: tty that was allocated recently
8036716671dSJiri Slaby  *
8046716671dSJiri Slaby  *	The tty structure must not becompletely set up (tty_ldisc_setup) when
8056716671dSJiri Slaby  *      this call is made.
8066716671dSJiri Slaby  */
8076716671dSJiri Slaby void tty_ldisc_deinit(struct tty_struct *tty)
8086716671dSJiri Slaby {
809c8b710b3SPeter Hurley 	if (tty->ldisc)
810ebc9baedSPeter Hurley 		tty_ldisc_put(tty->ldisc);
811f4807045SPeter Hurley 	tty->ldisc = NULL;
8126716671dSJiri Slaby }
8136716671dSJiri Slaby 
81496fd7ce5SGreg Kroah-Hartman void tty_ldisc_begin(void)
81596fd7ce5SGreg Kroah-Hartman {
81696fd7ce5SGreg Kroah-Hartman 	/* Setup the default TTY line discipline. */
81796fd7ce5SGreg Kroah-Hartman 	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
81896fd7ce5SGreg Kroah-Hartman }
819