xref: /openbmc/linux/drivers/tty/tty_ldisc.c (revision b0e95858)
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/init.h>
1596fd7ce5SGreg Kroah-Hartman #include <linux/module.h>
1696fd7ce5SGreg Kroah-Hartman #include <linux/device.h>
1796fd7ce5SGreg Kroah-Hartman #include <linux/wait.h>
1896fd7ce5SGreg Kroah-Hartman #include <linux/bitops.h>
1996fd7ce5SGreg Kroah-Hartman #include <linux/seq_file.h>
2096fd7ce5SGreg Kroah-Hartman #include <linux/uaccess.h>
210c73c08eSJiri Slaby #include <linux/ratelimit.h>
2296fd7ce5SGreg Kroah-Hartman 
23fc575ee6SPeter Hurley #undef LDISC_DEBUG_HANGUP
24fc575ee6SPeter Hurley 
25fc575ee6SPeter Hurley #ifdef LDISC_DEBUG_HANGUP
26fc575ee6SPeter Hurley #define tty_ldisc_debug(tty, f, args...) ({				       \
27fc575ee6SPeter Hurley 	char __b[64];							       \
28fc575ee6SPeter Hurley 	printk(KERN_DEBUG "%s: %s: " f, __func__, tty_name(tty, __b), ##args); \
29fc575ee6SPeter Hurley })
30fc575ee6SPeter Hurley #else
31fc575ee6SPeter Hurley #define tty_ldisc_debug(tty, f, args...)
32fc575ee6SPeter Hurley #endif
33fc575ee6SPeter Hurley 
34d2c43890SPeter Hurley /* lockdep nested classes for tty->ldisc_sem */
35d2c43890SPeter Hurley enum {
36d2c43890SPeter Hurley 	LDISC_SEM_NORMAL,
37d2c43890SPeter Hurley 	LDISC_SEM_OTHER,
38d2c43890SPeter Hurley };
39d2c43890SPeter Hurley 
40d2c43890SPeter Hurley 
4196fd7ce5SGreg Kroah-Hartman /*
4296fd7ce5SGreg Kroah-Hartman  *	This guards the refcounted line discipline lists. The lock
4396fd7ce5SGreg Kroah-Hartman  *	must be taken with irqs off because there are hangup path
4496fd7ce5SGreg Kroah-Hartman  *	callers who will do ldisc lookups and cannot sleep.
4596fd7ce5SGreg Kroah-Hartman  */
4696fd7ce5SGreg Kroah-Hartman 
47137084bbSPeter Hurley static DEFINE_RAW_SPINLOCK(tty_ldiscs_lock);
4896fd7ce5SGreg Kroah-Hartman /* Line disc dispatch table */
4996fd7ce5SGreg Kroah-Hartman static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
5096fd7ce5SGreg Kroah-Hartman 
5196fd7ce5SGreg Kroah-Hartman /**
5296fd7ce5SGreg Kroah-Hartman  *	tty_register_ldisc	-	install a line discipline
5396fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
5496fd7ce5SGreg Kroah-Hartman  *	@new_ldisc: pointer to the ldisc object
5596fd7ce5SGreg Kroah-Hartman  *
5696fd7ce5SGreg Kroah-Hartman  *	Installs a new line discipline into the kernel. The discipline
5796fd7ce5SGreg Kroah-Hartman  *	is set up as unreferenced and then made available to the kernel
5896fd7ce5SGreg Kroah-Hartman  *	from this point onwards.
5996fd7ce5SGreg Kroah-Hartman  *
6096fd7ce5SGreg Kroah-Hartman  *	Locking:
61137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
6296fd7ce5SGreg Kroah-Hartman  */
6396fd7ce5SGreg Kroah-Hartman 
6496fd7ce5SGreg Kroah-Hartman int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
6596fd7ce5SGreg Kroah-Hartman {
6696fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
6796fd7ce5SGreg Kroah-Hartman 	int ret = 0;
6896fd7ce5SGreg Kroah-Hartman 
6996fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
7096fd7ce5SGreg Kroah-Hartman 		return -EINVAL;
7196fd7ce5SGreg Kroah-Hartman 
72137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
7396fd7ce5SGreg Kroah-Hartman 	tty_ldiscs[disc] = new_ldisc;
7496fd7ce5SGreg Kroah-Hartman 	new_ldisc->num = disc;
7596fd7ce5SGreg Kroah-Hartman 	new_ldisc->refcount = 0;
76137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
7796fd7ce5SGreg Kroah-Hartman 
7896fd7ce5SGreg Kroah-Hartman 	return ret;
7996fd7ce5SGreg Kroah-Hartman }
8096fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL(tty_register_ldisc);
8196fd7ce5SGreg Kroah-Hartman 
8296fd7ce5SGreg Kroah-Hartman /**
8396fd7ce5SGreg Kroah-Hartman  *	tty_unregister_ldisc	-	unload a line discipline
8496fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
8596fd7ce5SGreg Kroah-Hartman  *	@new_ldisc: pointer to the ldisc object
8696fd7ce5SGreg Kroah-Hartman  *
8796fd7ce5SGreg Kroah-Hartman  *	Remove a line discipline from the kernel providing it is not
8896fd7ce5SGreg Kroah-Hartman  *	currently in use.
8996fd7ce5SGreg Kroah-Hartman  *
9096fd7ce5SGreg Kroah-Hartman  *	Locking:
91137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
9296fd7ce5SGreg Kroah-Hartman  */
9396fd7ce5SGreg Kroah-Hartman 
9496fd7ce5SGreg Kroah-Hartman int tty_unregister_ldisc(int disc)
9596fd7ce5SGreg Kroah-Hartman {
9696fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
9796fd7ce5SGreg Kroah-Hartman 	int ret = 0;
9896fd7ce5SGreg Kroah-Hartman 
9996fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
10096fd7ce5SGreg Kroah-Hartman 		return -EINVAL;
10196fd7ce5SGreg Kroah-Hartman 
102137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
10396fd7ce5SGreg Kroah-Hartman 	if (tty_ldiscs[disc]->refcount)
10496fd7ce5SGreg Kroah-Hartman 		ret = -EBUSY;
10596fd7ce5SGreg Kroah-Hartman 	else
10696fd7ce5SGreg Kroah-Hartman 		tty_ldiscs[disc] = NULL;
107137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
10896fd7ce5SGreg Kroah-Hartman 
10996fd7ce5SGreg Kroah-Hartman 	return ret;
11096fd7ce5SGreg Kroah-Hartman }
11196fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL(tty_unregister_ldisc);
11296fd7ce5SGreg Kroah-Hartman 
11396fd7ce5SGreg Kroah-Hartman static struct tty_ldisc_ops *get_ldops(int disc)
11496fd7ce5SGreg Kroah-Hartman {
11596fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
11696fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops, *ret;
11796fd7ce5SGreg Kroah-Hartman 
118137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
11996fd7ce5SGreg Kroah-Hartman 	ret = ERR_PTR(-EINVAL);
12096fd7ce5SGreg Kroah-Hartman 	ldops = tty_ldiscs[disc];
12196fd7ce5SGreg Kroah-Hartman 	if (ldops) {
12296fd7ce5SGreg Kroah-Hartman 		ret = ERR_PTR(-EAGAIN);
12396fd7ce5SGreg Kroah-Hartman 		if (try_module_get(ldops->owner)) {
12496fd7ce5SGreg Kroah-Hartman 			ldops->refcount++;
12596fd7ce5SGreg Kroah-Hartman 			ret = ldops;
12696fd7ce5SGreg Kroah-Hartman 		}
12796fd7ce5SGreg Kroah-Hartman 	}
128137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
12996fd7ce5SGreg Kroah-Hartman 	return ret;
13096fd7ce5SGreg Kroah-Hartman }
13196fd7ce5SGreg Kroah-Hartman 
13296fd7ce5SGreg Kroah-Hartman static void put_ldops(struct tty_ldisc_ops *ldops)
13396fd7ce5SGreg Kroah-Hartman {
13496fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
13596fd7ce5SGreg Kroah-Hartman 
136137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
13796fd7ce5SGreg Kroah-Hartman 	ldops->refcount--;
13896fd7ce5SGreg Kroah-Hartman 	module_put(ldops->owner);
139137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
14096fd7ce5SGreg Kroah-Hartman }
14196fd7ce5SGreg Kroah-Hartman 
14296fd7ce5SGreg Kroah-Hartman /**
14396fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_get		-	take a reference to an ldisc
14496fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
14596fd7ce5SGreg Kroah-Hartman  *
14696fd7ce5SGreg Kroah-Hartman  *	Takes a reference to a line discipline. Deals with refcounts and
14796fd7ce5SGreg Kroah-Hartman  *	module locking counts. Returns NULL if the discipline is not available.
14896fd7ce5SGreg Kroah-Hartman  *	Returns a pointer to the discipline and bumps the ref count if it is
14996fd7ce5SGreg Kroah-Hartman  *	available
15096fd7ce5SGreg Kroah-Hartman  *
15196fd7ce5SGreg Kroah-Hartman  *	Locking:
152137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
15396fd7ce5SGreg Kroah-Hartman  */
15496fd7ce5SGreg Kroah-Hartman 
15536697529SPeter Hurley static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
15696fd7ce5SGreg Kroah-Hartman {
15796fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld;
15896fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops;
15996fd7ce5SGreg Kroah-Hartman 
16096fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
16196fd7ce5SGreg Kroah-Hartman 		return ERR_PTR(-EINVAL);
16296fd7ce5SGreg Kroah-Hartman 
16396fd7ce5SGreg Kroah-Hartman 	/*
16496fd7ce5SGreg Kroah-Hartman 	 * Get the ldisc ops - we may need to request them to be loaded
16596fd7ce5SGreg Kroah-Hartman 	 * dynamically and try again.
16696fd7ce5SGreg Kroah-Hartman 	 */
16796fd7ce5SGreg Kroah-Hartman 	ldops = get_ldops(disc);
16896fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ldops)) {
16996fd7ce5SGreg Kroah-Hartman 		request_module("tty-ldisc-%d", disc);
17096fd7ce5SGreg Kroah-Hartman 		ldops = get_ldops(disc);
17196fd7ce5SGreg Kroah-Hartman 		if (IS_ERR(ldops))
17296fd7ce5SGreg Kroah-Hartman 			return ERR_CAST(ldops);
17396fd7ce5SGreg Kroah-Hartman 	}
17496fd7ce5SGreg Kroah-Hartman 
17596fd7ce5SGreg Kroah-Hartman 	ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
17696fd7ce5SGreg Kroah-Hartman 	if (ld == NULL) {
17796fd7ce5SGreg Kroah-Hartman 		put_ldops(ldops);
17896fd7ce5SGreg Kroah-Hartman 		return ERR_PTR(-ENOMEM);
17996fd7ce5SGreg Kroah-Hartman 	}
18096fd7ce5SGreg Kroah-Hartman 
18196fd7ce5SGreg Kroah-Hartman 	ld->ops = ldops;
18236697529SPeter Hurley 	ld->tty = tty;
1831541f845SIvo Sieben 
18496fd7ce5SGreg Kroah-Hartman 	return ld;
18596fd7ce5SGreg Kroah-Hartman }
18696fd7ce5SGreg Kroah-Hartman 
187734de249SPeter Hurley /**
188734de249SPeter Hurley  *	tty_ldisc_put		-	release the ldisc
189734de249SPeter Hurley  *
190734de249SPeter Hurley  *	Complement of tty_ldisc_get().
191734de249SPeter Hurley  */
192734de249SPeter Hurley static inline void tty_ldisc_put(struct tty_ldisc *ld)
193734de249SPeter Hurley {
194734de249SPeter Hurley 	if (WARN_ON_ONCE(!ld))
195734de249SPeter Hurley 		return;
196734de249SPeter Hurley 
19736697529SPeter Hurley 	put_ldops(ld->ops);
198734de249SPeter Hurley 	kfree(ld);
199734de249SPeter Hurley }
200734de249SPeter Hurley 
20196fd7ce5SGreg Kroah-Hartman static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
20296fd7ce5SGreg Kroah-Hartman {
20396fd7ce5SGreg Kroah-Hartman 	return (*pos < NR_LDISCS) ? pos : NULL;
20496fd7ce5SGreg Kroah-Hartman }
20596fd7ce5SGreg Kroah-Hartman 
20696fd7ce5SGreg Kroah-Hartman static void *tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos)
20796fd7ce5SGreg Kroah-Hartman {
20896fd7ce5SGreg Kroah-Hartman 	(*pos)++;
20996fd7ce5SGreg Kroah-Hartman 	return (*pos < NR_LDISCS) ? pos : NULL;
21096fd7ce5SGreg Kroah-Hartman }
21196fd7ce5SGreg Kroah-Hartman 
21296fd7ce5SGreg Kroah-Hartman static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
21396fd7ce5SGreg Kroah-Hartman {
21496fd7ce5SGreg Kroah-Hartman }
21596fd7ce5SGreg Kroah-Hartman 
21696fd7ce5SGreg Kroah-Hartman static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
21796fd7ce5SGreg Kroah-Hartman {
21896fd7ce5SGreg Kroah-Hartman 	int i = *(loff_t *)v;
21996fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops;
22096fd7ce5SGreg Kroah-Hartman 
22196fd7ce5SGreg Kroah-Hartman 	ldops = get_ldops(i);
22296fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ldops))
22396fd7ce5SGreg Kroah-Hartman 		return 0;
22496fd7ce5SGreg Kroah-Hartman 	seq_printf(m, "%-10s %2d\n", ldops->name ? ldops->name : "???", i);
22596fd7ce5SGreg Kroah-Hartman 	put_ldops(ldops);
22696fd7ce5SGreg Kroah-Hartman 	return 0;
22796fd7ce5SGreg Kroah-Hartman }
22896fd7ce5SGreg Kroah-Hartman 
22996fd7ce5SGreg Kroah-Hartman static const struct seq_operations tty_ldiscs_seq_ops = {
23096fd7ce5SGreg Kroah-Hartman 	.start	= tty_ldiscs_seq_start,
23196fd7ce5SGreg Kroah-Hartman 	.next	= tty_ldiscs_seq_next,
23296fd7ce5SGreg Kroah-Hartman 	.stop	= tty_ldiscs_seq_stop,
23396fd7ce5SGreg Kroah-Hartman 	.show	= tty_ldiscs_seq_show,
23496fd7ce5SGreg Kroah-Hartman };
23596fd7ce5SGreg Kroah-Hartman 
23696fd7ce5SGreg Kroah-Hartman static int proc_tty_ldiscs_open(struct inode *inode, struct file *file)
23796fd7ce5SGreg Kroah-Hartman {
23896fd7ce5SGreg Kroah-Hartman 	return seq_open(file, &tty_ldiscs_seq_ops);
23996fd7ce5SGreg Kroah-Hartman }
24096fd7ce5SGreg Kroah-Hartman 
24196fd7ce5SGreg Kroah-Hartman const struct file_operations tty_ldiscs_proc_fops = {
24296fd7ce5SGreg Kroah-Hartman 	.owner		= THIS_MODULE,
24396fd7ce5SGreg Kroah-Hartman 	.open		= proc_tty_ldiscs_open,
24496fd7ce5SGreg Kroah-Hartman 	.read		= seq_read,
24596fd7ce5SGreg Kroah-Hartman 	.llseek		= seq_lseek,
24696fd7ce5SGreg Kroah-Hartman 	.release	= seq_release,
24796fd7ce5SGreg Kroah-Hartman };
24896fd7ce5SGreg Kroah-Hartman 
24996fd7ce5SGreg Kroah-Hartman /**
25096fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_ref_wait	-	wait for the tty ldisc
25196fd7ce5SGreg Kroah-Hartman  *	@tty: tty device
25296fd7ce5SGreg Kroah-Hartman  *
25396fd7ce5SGreg Kroah-Hartman  *	Dereference the line discipline for the terminal and take a
25496fd7ce5SGreg Kroah-Hartman  *	reference to it. If the line discipline is in flux then
25596fd7ce5SGreg Kroah-Hartman  *	wait patiently until it changes.
25696fd7ce5SGreg Kroah-Hartman  *
25796fd7ce5SGreg Kroah-Hartman  *	Note: Must not be called from an IRQ/timer context. The caller
25896fd7ce5SGreg Kroah-Hartman  *	must also be careful not to hold other locks that will deadlock
25996fd7ce5SGreg Kroah-Hartman  *	against a discipline change, such as an existing ldisc reference
26096fd7ce5SGreg Kroah-Hartman  *	(which we check for)
26196fd7ce5SGreg Kroah-Hartman  *
26236697529SPeter Hurley  *	Note: only callable from a file_operations routine (which
26336697529SPeter Hurley  *	guarantees tty->ldisc != NULL when the lock is acquired).
26496fd7ce5SGreg Kroah-Hartman  */
26596fd7ce5SGreg Kroah-Hartman 
26696fd7ce5SGreg Kroah-Hartman struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
26796fd7ce5SGreg Kroah-Hartman {
26836697529SPeter Hurley 	ldsem_down_read(&tty->ldisc_sem, MAX_SCHEDULE_TIMEOUT);
26936697529SPeter Hurley 	WARN_ON(!tty->ldisc);
27036697529SPeter Hurley 	return tty->ldisc;
27196fd7ce5SGreg Kroah-Hartman }
27296fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
27396fd7ce5SGreg Kroah-Hartman 
27496fd7ce5SGreg Kroah-Hartman /**
27596fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_ref		-	get the tty ldisc
27696fd7ce5SGreg Kroah-Hartman  *	@tty: tty device
27796fd7ce5SGreg Kroah-Hartman  *
27896fd7ce5SGreg Kroah-Hartman  *	Dereference the line discipline for the terminal and take a
27996fd7ce5SGreg Kroah-Hartman  *	reference to it. If the line discipline is in flux then
28096fd7ce5SGreg Kroah-Hartman  *	return NULL. Can be called from IRQ and timer functions.
28196fd7ce5SGreg Kroah-Hartman  */
28296fd7ce5SGreg Kroah-Hartman 
28396fd7ce5SGreg Kroah-Hartman struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
28496fd7ce5SGreg Kroah-Hartman {
28536697529SPeter Hurley 	struct tty_ldisc *ld = NULL;
28636697529SPeter Hurley 
28736697529SPeter Hurley 	if (ldsem_down_read_trylock(&tty->ldisc_sem)) {
28836697529SPeter Hurley 		ld = tty->ldisc;
28936697529SPeter Hurley 		if (!ld)
29036697529SPeter Hurley 			ldsem_up_read(&tty->ldisc_sem);
29136697529SPeter Hurley 	}
29236697529SPeter Hurley 	return ld;
29396fd7ce5SGreg Kroah-Hartman }
29496fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_ref);
29596fd7ce5SGreg Kroah-Hartman 
29696fd7ce5SGreg Kroah-Hartman /**
29796fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_deref		-	free a tty ldisc reference
29896fd7ce5SGreg Kroah-Hartman  *	@ld: reference to free up
29996fd7ce5SGreg Kroah-Hartman  *
30096fd7ce5SGreg Kroah-Hartman  *	Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
30196fd7ce5SGreg Kroah-Hartman  *	be called in IRQ context.
30296fd7ce5SGreg Kroah-Hartman  */
30396fd7ce5SGreg Kroah-Hartman 
30496fd7ce5SGreg Kroah-Hartman void tty_ldisc_deref(struct tty_ldisc *ld)
30596fd7ce5SGreg Kroah-Hartman {
30636697529SPeter Hurley 	ldsem_up_read(&ld->tty->ldisc_sem);
30796fd7ce5SGreg Kroah-Hartman }
30896fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_deref);
30996fd7ce5SGreg Kroah-Hartman 
310d2c43890SPeter Hurley 
311d2c43890SPeter Hurley static inline int __lockfunc
312d2c43890SPeter Hurley tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
313d2c43890SPeter Hurley {
314d2c43890SPeter Hurley 	return ldsem_down_write(&tty->ldisc_sem, timeout);
315d2c43890SPeter Hurley }
316d2c43890SPeter Hurley 
317d2c43890SPeter Hurley static inline int __lockfunc
318d2c43890SPeter Hurley tty_ldisc_lock_nested(struct tty_struct *tty, unsigned long timeout)
319d2c43890SPeter Hurley {
320d2c43890SPeter Hurley 	return ldsem_down_write_nested(&tty->ldisc_sem,
321d2c43890SPeter Hurley 				       LDISC_SEM_OTHER, timeout);
322d2c43890SPeter Hurley }
323d2c43890SPeter Hurley 
324d2c43890SPeter Hurley static inline void tty_ldisc_unlock(struct tty_struct *tty)
325d2c43890SPeter Hurley {
326d2c43890SPeter Hurley 	return ldsem_up_write(&tty->ldisc_sem);
327d2c43890SPeter Hurley }
328d2c43890SPeter Hurley 
329d2c43890SPeter Hurley static int __lockfunc
330d2c43890SPeter Hurley tty_ldisc_lock_pair_timeout(struct tty_struct *tty, struct tty_struct *tty2,
331d2c43890SPeter Hurley 			    unsigned long timeout)
332d2c43890SPeter Hurley {
333d2c43890SPeter Hurley 	int ret;
334d2c43890SPeter Hurley 
335d2c43890SPeter Hurley 	if (tty < tty2) {
336d2c43890SPeter Hurley 		ret = tty_ldisc_lock(tty, timeout);
337d2c43890SPeter Hurley 		if (ret) {
338d2c43890SPeter Hurley 			ret = tty_ldisc_lock_nested(tty2, timeout);
339d2c43890SPeter Hurley 			if (!ret)
340d2c43890SPeter Hurley 				tty_ldisc_unlock(tty);
341d2c43890SPeter Hurley 		}
342d2c43890SPeter Hurley 	} else {
343d2c43890SPeter Hurley 		/* if this is possible, it has lots of implications */
344d2c43890SPeter Hurley 		WARN_ON_ONCE(tty == tty2);
345d2c43890SPeter Hurley 		if (tty2 && tty != tty2) {
346d2c43890SPeter Hurley 			ret = tty_ldisc_lock(tty2, timeout);
347d2c43890SPeter Hurley 			if (ret) {
348d2c43890SPeter Hurley 				ret = tty_ldisc_lock_nested(tty, timeout);
349d2c43890SPeter Hurley 				if (!ret)
350d2c43890SPeter Hurley 					tty_ldisc_unlock(tty2);
351d2c43890SPeter Hurley 			}
352d2c43890SPeter Hurley 		} else
353d2c43890SPeter Hurley 			ret = tty_ldisc_lock(tty, timeout);
354d2c43890SPeter Hurley 	}
355d2c43890SPeter Hurley 
356d2c43890SPeter Hurley 	if (!ret)
357d2c43890SPeter Hurley 		return -EBUSY;
358d2c43890SPeter Hurley 
359d2c43890SPeter Hurley 	set_bit(TTY_LDISC_HALTED, &tty->flags);
360d2c43890SPeter Hurley 	if (tty2)
361d2c43890SPeter Hurley 		set_bit(TTY_LDISC_HALTED, &tty2->flags);
362d2c43890SPeter Hurley 	return 0;
363d2c43890SPeter Hurley }
364d2c43890SPeter Hurley 
365d2c43890SPeter Hurley static void __lockfunc
366d2c43890SPeter Hurley tty_ldisc_lock_pair(struct tty_struct *tty, struct tty_struct *tty2)
367d2c43890SPeter Hurley {
368d2c43890SPeter Hurley 	tty_ldisc_lock_pair_timeout(tty, tty2, MAX_SCHEDULE_TIMEOUT);
369d2c43890SPeter Hurley }
370d2c43890SPeter Hurley 
371d2c43890SPeter Hurley static void __lockfunc tty_ldisc_unlock_pair(struct tty_struct *tty,
372d2c43890SPeter Hurley 					     struct tty_struct *tty2)
373d2c43890SPeter Hurley {
374d2c43890SPeter Hurley 	tty_ldisc_unlock(tty);
375d2c43890SPeter Hurley 	if (tty2)
376d2c43890SPeter Hurley 		tty_ldisc_unlock(tty2);
377d2c43890SPeter Hurley }
378d2c43890SPeter Hurley 
379d2c43890SPeter Hurley static void __lockfunc tty_ldisc_enable_pair(struct tty_struct *tty,
380d2c43890SPeter Hurley 					     struct tty_struct *tty2)
381d2c43890SPeter Hurley {
382d2c43890SPeter Hurley 	clear_bit(TTY_LDISC_HALTED, &tty->flags);
383d2c43890SPeter Hurley 	if (tty2)
384d2c43890SPeter Hurley 		clear_bit(TTY_LDISC_HALTED, &tty2->flags);
385d2c43890SPeter Hurley 
386d2c43890SPeter Hurley 	tty_ldisc_unlock_pair(tty, tty2);
387d2c43890SPeter Hurley }
388d2c43890SPeter Hurley 
38996fd7ce5SGreg Kroah-Hartman /**
39096fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_flush	-	flush line discipline queue
39196fd7ce5SGreg Kroah-Hartman  *	@tty: tty
39296fd7ce5SGreg Kroah-Hartman  *
39396fd7ce5SGreg Kroah-Hartman  *	Flush the line discipline queue (if any) for this tty. If there
39496fd7ce5SGreg Kroah-Hartman  *	is no line discipline active this is a no-op.
39596fd7ce5SGreg Kroah-Hartman  */
39696fd7ce5SGreg Kroah-Hartman 
39796fd7ce5SGreg Kroah-Hartman void tty_ldisc_flush(struct tty_struct *tty)
39896fd7ce5SGreg Kroah-Hartman {
39996fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld = tty_ldisc_ref(tty);
40096fd7ce5SGreg Kroah-Hartman 	if (ld) {
40196fd7ce5SGreg Kroah-Hartman 		if (ld->ops->flush_buffer)
40296fd7ce5SGreg Kroah-Hartman 			ld->ops->flush_buffer(tty);
40396fd7ce5SGreg Kroah-Hartman 		tty_ldisc_deref(ld);
40496fd7ce5SGreg Kroah-Hartman 	}
40596fd7ce5SGreg Kroah-Hartman 	tty_buffer_flush(tty);
40696fd7ce5SGreg Kroah-Hartman }
40796fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_flush);
40896fd7ce5SGreg Kroah-Hartman 
40996fd7ce5SGreg Kroah-Hartman /**
41096fd7ce5SGreg Kroah-Hartman  *	tty_set_termios_ldisc		-	set ldisc field
41196fd7ce5SGreg Kroah-Hartman  *	@tty: tty structure
41296fd7ce5SGreg Kroah-Hartman  *	@num: line discipline number
41396fd7ce5SGreg Kroah-Hartman  *
41496fd7ce5SGreg Kroah-Hartman  *	This is probably overkill for real world processors but
41596fd7ce5SGreg Kroah-Hartman  *	they are not on hot paths so a little discipline won't do
41696fd7ce5SGreg Kroah-Hartman  *	any harm.
41796fd7ce5SGreg Kroah-Hartman  *
41896fd7ce5SGreg Kroah-Hartman  *	Locking: takes termios_mutex
41996fd7ce5SGreg Kroah-Hartman  */
42096fd7ce5SGreg Kroah-Hartman 
42196fd7ce5SGreg Kroah-Hartman static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
42296fd7ce5SGreg Kroah-Hartman {
42396fd7ce5SGreg Kroah-Hartman 	mutex_lock(&tty->termios_mutex);
424adc8d746SAlan Cox 	tty->termios.c_line = num;
42596fd7ce5SGreg Kroah-Hartman 	mutex_unlock(&tty->termios_mutex);
42696fd7ce5SGreg Kroah-Hartman }
42796fd7ce5SGreg Kroah-Hartman 
42896fd7ce5SGreg Kroah-Hartman /**
42996fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_open		-	open a line discipline
43096fd7ce5SGreg Kroah-Hartman  *	@tty: tty we are opening the ldisc on
43196fd7ce5SGreg Kroah-Hartman  *	@ld: discipline to open
43296fd7ce5SGreg Kroah-Hartman  *
43396fd7ce5SGreg Kroah-Hartman  *	A helper opening method. Also a convenient debugging and check
43496fd7ce5SGreg Kroah-Hartman  *	point.
43596fd7ce5SGreg Kroah-Hartman  *
43696fd7ce5SGreg Kroah-Hartman  *	Locking: always called with BTM already held.
43796fd7ce5SGreg Kroah-Hartman  */
43896fd7ce5SGreg Kroah-Hartman 
43996fd7ce5SGreg Kroah-Hartman static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
44096fd7ce5SGreg Kroah-Hartman {
44196fd7ce5SGreg Kroah-Hartman 	WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
44296fd7ce5SGreg Kroah-Hartman 	if (ld->ops->open) {
44396fd7ce5SGreg Kroah-Hartman 		int ret;
44496fd7ce5SGreg Kroah-Hartman                 /* BTM here locks versus a hangup event */
44596fd7ce5SGreg Kroah-Hartman 		ret = ld->ops->open(tty);
4467f90cfc5SJiri Slaby 		if (ret)
4477f90cfc5SJiri Slaby 			clear_bit(TTY_LDISC_OPEN, &tty->flags);
44896fd7ce5SGreg Kroah-Hartman 		return ret;
44996fd7ce5SGreg Kroah-Hartman 	}
45096fd7ce5SGreg Kroah-Hartman 	return 0;
45196fd7ce5SGreg Kroah-Hartman }
45296fd7ce5SGreg Kroah-Hartman 
45396fd7ce5SGreg Kroah-Hartman /**
45496fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_close		-	close a line discipline
45596fd7ce5SGreg Kroah-Hartman  *	@tty: tty we are opening the ldisc on
45696fd7ce5SGreg Kroah-Hartman  *	@ld: discipline to close
45796fd7ce5SGreg Kroah-Hartman  *
45896fd7ce5SGreg Kroah-Hartman  *	A helper close method. Also a convenient debugging and check
45996fd7ce5SGreg Kroah-Hartman  *	point.
46096fd7ce5SGreg Kroah-Hartman  */
46196fd7ce5SGreg Kroah-Hartman 
46296fd7ce5SGreg Kroah-Hartman static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
46396fd7ce5SGreg Kroah-Hartman {
46496fd7ce5SGreg Kroah-Hartman 	WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
46596fd7ce5SGreg Kroah-Hartman 	clear_bit(TTY_LDISC_OPEN, &tty->flags);
46696fd7ce5SGreg Kroah-Hartman 	if (ld->ops->close)
46796fd7ce5SGreg Kroah-Hartman 		ld->ops->close(tty);
46896fd7ce5SGreg Kroah-Hartman }
46996fd7ce5SGreg Kroah-Hartman 
47096fd7ce5SGreg Kroah-Hartman /**
47196fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_restore	-	helper for tty ldisc change
47296fd7ce5SGreg Kroah-Hartman  *	@tty: tty to recover
47396fd7ce5SGreg Kroah-Hartman  *	@old: previous ldisc
47496fd7ce5SGreg Kroah-Hartman  *
47596fd7ce5SGreg Kroah-Hartman  *	Restore the previous line discipline or N_TTY when a line discipline
47696fd7ce5SGreg Kroah-Hartman  *	change fails due to an open error
47796fd7ce5SGreg Kroah-Hartman  */
47896fd7ce5SGreg Kroah-Hartman 
47996fd7ce5SGreg Kroah-Hartman static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
48096fd7ce5SGreg Kroah-Hartman {
48196fd7ce5SGreg Kroah-Hartman 	char buf[64];
48296fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *new_ldisc;
48396fd7ce5SGreg Kroah-Hartman 	int r;
48496fd7ce5SGreg Kroah-Hartman 
48596fd7ce5SGreg Kroah-Hartman 	/* There is an outstanding reference here so this is safe */
48636697529SPeter Hurley 	old = tty_ldisc_get(tty, old->ops->num);
48796fd7ce5SGreg Kroah-Hartman 	WARN_ON(IS_ERR(old));
488f4807045SPeter Hurley 	tty->ldisc = old;
48996fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, old->ops->num);
49096fd7ce5SGreg Kroah-Hartman 	if (tty_ldisc_open(tty, old) < 0) {
49196fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(old);
49296fd7ce5SGreg Kroah-Hartman 		/* This driver is always present */
49336697529SPeter Hurley 		new_ldisc = tty_ldisc_get(tty, N_TTY);
49496fd7ce5SGreg Kroah-Hartman 		if (IS_ERR(new_ldisc))
49596fd7ce5SGreg Kroah-Hartman 			panic("n_tty: get");
496f4807045SPeter Hurley 		tty->ldisc = new_ldisc;
49796fd7ce5SGreg Kroah-Hartman 		tty_set_termios_ldisc(tty, N_TTY);
49896fd7ce5SGreg Kroah-Hartman 		r = tty_ldisc_open(tty, new_ldisc);
49996fd7ce5SGreg Kroah-Hartman 		if (r < 0)
50096fd7ce5SGreg Kroah-Hartman 			panic("Couldn't open N_TTY ldisc for "
50196fd7ce5SGreg Kroah-Hartman 			      "%s --- error %d.",
50296fd7ce5SGreg Kroah-Hartman 			      tty_name(tty, buf), r);
50396fd7ce5SGreg Kroah-Hartman 	}
50496fd7ce5SGreg Kroah-Hartman }
50596fd7ce5SGreg Kroah-Hartman 
50696fd7ce5SGreg Kroah-Hartman /**
50796fd7ce5SGreg Kroah-Hartman  *	tty_set_ldisc		-	set line discipline
50896fd7ce5SGreg Kroah-Hartman  *	@tty: the terminal to set
50996fd7ce5SGreg Kroah-Hartman  *	@ldisc: the line discipline
51096fd7ce5SGreg Kroah-Hartman  *
51196fd7ce5SGreg Kroah-Hartman  *	Set the discipline of a tty line. Must be called from a process
51296fd7ce5SGreg Kroah-Hartman  *	context. The ldisc change logic has to protect itself against any
51396fd7ce5SGreg Kroah-Hartman  *	overlapping ldisc change (including on the other end of pty pairs),
51496fd7ce5SGreg Kroah-Hartman  *	the close of one side of a tty/pty pair, and eventually hangup.
51596fd7ce5SGreg Kroah-Hartman  */
51696fd7ce5SGreg Kroah-Hartman 
51796fd7ce5SGreg Kroah-Hartman int tty_set_ldisc(struct tty_struct *tty, int ldisc)
51896fd7ce5SGreg Kroah-Hartman {
51996fd7ce5SGreg Kroah-Hartman 	int retval;
5209fbfa34cSPeter Hurley 	struct tty_ldisc *old_ldisc, *new_ldisc;
52136697529SPeter Hurley 	struct tty_struct *o_tty = tty->link;
52296fd7ce5SGreg Kroah-Hartman 
52336697529SPeter Hurley 	new_ldisc = tty_ldisc_get(tty, ldisc);
52496fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(new_ldisc))
52596fd7ce5SGreg Kroah-Hartman 		return PTR_ERR(new_ldisc);
52696fd7ce5SGreg Kroah-Hartman 
52736697529SPeter Hurley 	retval = tty_ldisc_lock_pair_timeout(tty, o_tty, 5 * HZ);
52836697529SPeter Hurley 	if (retval) {
52936697529SPeter Hurley 		tty_ldisc_put(new_ldisc);
53036697529SPeter Hurley 		return retval;
53136697529SPeter Hurley 	}
53296fd7ce5SGreg Kroah-Hartman 
53396fd7ce5SGreg Kroah-Hartman 	/*
53496fd7ce5SGreg Kroah-Hartman 	 *	Check the no-op case
53596fd7ce5SGreg Kroah-Hartman 	 */
53696fd7ce5SGreg Kroah-Hartman 
53796fd7ce5SGreg Kroah-Hartman 	if (tty->ldisc->ops->num == ldisc) {
53836697529SPeter Hurley 		tty_ldisc_enable_pair(tty, o_tty);
53996fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(new_ldisc);
54096fd7ce5SGreg Kroah-Hartman 		return 0;
54196fd7ce5SGreg Kroah-Hartman 	}
54296fd7ce5SGreg Kroah-Hartman 
54336697529SPeter Hurley 	/* FIXME: why 'shutoff' input if the ldisc is locked? */
54496fd7ce5SGreg Kroah-Hartman 	tty->receive_room = 0;
54596fd7ce5SGreg Kroah-Hartman 
5469fbfa34cSPeter Hurley 	old_ldisc = tty->ldisc;
54789c8d91eSAlan Cox 	tty_lock(tty);
548100eeae2SJiri Slaby 
549e97733caSPeter Hurley 	if (test_bit(TTY_HUPPING, &tty->flags) ||
550e97733caSPeter Hurley 	    test_bit(TTY_HUPPED, &tty->flags)) {
55196fd7ce5SGreg Kroah-Hartman 		/* We were raced by the hangup method. It will have stomped
55296fd7ce5SGreg Kroah-Hartman 		   the ldisc data and closed the ldisc down */
55336697529SPeter Hurley 		tty_ldisc_enable_pair(tty, o_tty);
55496fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(new_ldisc);
55589c8d91eSAlan Cox 		tty_unlock(tty);
55696fd7ce5SGreg Kroah-Hartman 		return -EIO;
55796fd7ce5SGreg Kroah-Hartman 	}
55896fd7ce5SGreg Kroah-Hartman 
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 
5739fbfa34cSPeter Hurley 	if (tty->ldisc->ops->num != old_ldisc->ops->num && tty->ops->set_ldisc)
57496fd7ce5SGreg Kroah-Hartman 		tty->ops->set_ldisc(tty);
57596fd7ce5SGreg Kroah-Hartman 
576b0e95858SPeter Hurley 	/* At this point we hold a reference to the new ldisc and a
577b0e95858SPeter Hurley 	   reference to the old ldisc, or we hold two references to
578b0e95858SPeter Hurley 	   the old ldisc (if it was restored as part of error cleanup
579b0e95858SPeter Hurley 	   above). In either case, releasing a single reference from
580b0e95858SPeter Hurley 	   the old ldisc is correct. */
581b0e95858SPeter Hurley 
5829fbfa34cSPeter Hurley 	tty_ldisc_put(old_ldisc);
58396fd7ce5SGreg Kroah-Hartman 
58496fd7ce5SGreg Kroah-Hartman 	/*
58596fd7ce5SGreg Kroah-Hartman 	 *	Allow ldisc referencing to occur again
58696fd7ce5SGreg Kroah-Hartman 	 */
58736697529SPeter Hurley 	tty_ldisc_enable_pair(tty, o_tty);
58896fd7ce5SGreg Kroah-Hartman 
58996fd7ce5SGreg Kroah-Hartman 	/* Restart the work queue in case no characters kick it off. Safe if
59096fd7ce5SGreg Kroah-Hartman 	   already running */
591ecbbfd44SJiri Slaby 	schedule_work(&tty->port->buf.work);
5924f98d467SPeter Hurley 	if (o_tty)
593ecbbfd44SJiri Slaby 		schedule_work(&o_tty->port->buf.work);
5944f98d467SPeter Hurley 
59589c8d91eSAlan Cox 	tty_unlock(tty);
59696fd7ce5SGreg Kroah-Hartman 	return retval;
59796fd7ce5SGreg Kroah-Hartman }
59896fd7ce5SGreg Kroah-Hartman 
59996fd7ce5SGreg Kroah-Hartman /**
60096fd7ce5SGreg Kroah-Hartman  *	tty_reset_termios	-	reset terminal state
60196fd7ce5SGreg Kroah-Hartman  *	@tty: tty to reset
60296fd7ce5SGreg Kroah-Hartman  *
60396fd7ce5SGreg Kroah-Hartman  *	Restore a terminal to the driver default state.
60496fd7ce5SGreg Kroah-Hartman  */
60596fd7ce5SGreg Kroah-Hartman 
60696fd7ce5SGreg Kroah-Hartman static void tty_reset_termios(struct tty_struct *tty)
60796fd7ce5SGreg Kroah-Hartman {
60896fd7ce5SGreg Kroah-Hartman 	mutex_lock(&tty->termios_mutex);
609adc8d746SAlan Cox 	tty->termios = tty->driver->init_termios;
610adc8d746SAlan Cox 	tty->termios.c_ispeed = tty_termios_input_baud_rate(&tty->termios);
611adc8d746SAlan Cox 	tty->termios.c_ospeed = tty_termios_baud_rate(&tty->termios);
61296fd7ce5SGreg Kroah-Hartman 	mutex_unlock(&tty->termios_mutex);
61396fd7ce5SGreg Kroah-Hartman }
61496fd7ce5SGreg Kroah-Hartman 
61596fd7ce5SGreg Kroah-Hartman 
61696fd7ce5SGreg Kroah-Hartman /**
61796fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_reinit	-	reinitialise the tty ldisc
61896fd7ce5SGreg Kroah-Hartman  *	@tty: tty to reinit
61996fd7ce5SGreg Kroah-Hartman  *	@ldisc: line discipline to reinitialize
62096fd7ce5SGreg Kroah-Hartman  *
62196fd7ce5SGreg Kroah-Hartman  *	Switch the tty to a line discipline and leave the ldisc
62296fd7ce5SGreg Kroah-Hartman  *	state closed
62396fd7ce5SGreg Kroah-Hartman  */
62496fd7ce5SGreg Kroah-Hartman 
6251c95ba1eSPhilippe Rétornaz static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
62696fd7ce5SGreg Kroah-Hartman {
62736697529SPeter Hurley 	struct tty_ldisc *ld = tty_ldisc_get(tty, ldisc);
6281c95ba1eSPhilippe Rétornaz 
6291c95ba1eSPhilippe Rétornaz 	if (IS_ERR(ld))
6301c95ba1eSPhilippe Rétornaz 		return -1;
63196fd7ce5SGreg Kroah-Hartman 
63296fd7ce5SGreg Kroah-Hartman 	tty_ldisc_close(tty, tty->ldisc);
63396fd7ce5SGreg Kroah-Hartman 	tty_ldisc_put(tty->ldisc);
63496fd7ce5SGreg Kroah-Hartman 	/*
63596fd7ce5SGreg Kroah-Hartman 	 *	Switch the line discipline back
63696fd7ce5SGreg Kroah-Hartman 	 */
637f4807045SPeter Hurley 	tty->ldisc = ld;
63896fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, ldisc);
6391c95ba1eSPhilippe Rétornaz 
6401c95ba1eSPhilippe Rétornaz 	return 0;
64196fd7ce5SGreg Kroah-Hartman }
64296fd7ce5SGreg Kroah-Hartman 
64396fd7ce5SGreg Kroah-Hartman /**
64496fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_hangup		-	hangup ldisc reset
64596fd7ce5SGreg Kroah-Hartman  *	@tty: tty being hung up
64696fd7ce5SGreg Kroah-Hartman  *
64796fd7ce5SGreg Kroah-Hartman  *	Some tty devices reset their termios when they receive a hangup
64896fd7ce5SGreg Kroah-Hartman  *	event. In that situation we must also switch back to N_TTY properly
64996fd7ce5SGreg Kroah-Hartman  *	before we reset the termios data.
65096fd7ce5SGreg Kroah-Hartman  *
65196fd7ce5SGreg Kroah-Hartman  *	Locking: We can take the ldisc mutex as the rest of the code is
65296fd7ce5SGreg Kroah-Hartman  *	careful to allow for this.
65396fd7ce5SGreg Kroah-Hartman  *
65496fd7ce5SGreg Kroah-Hartman  *	In the pty pair case this occurs in the close() path of the
65596fd7ce5SGreg Kroah-Hartman  *	tty itself so we must be careful about locking rules.
65696fd7ce5SGreg Kroah-Hartman  */
65796fd7ce5SGreg Kroah-Hartman 
65896fd7ce5SGreg Kroah-Hartman void tty_ldisc_hangup(struct tty_struct *tty)
65996fd7ce5SGreg Kroah-Hartman {
66096fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld;
66196fd7ce5SGreg Kroah-Hartman 	int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
66296fd7ce5SGreg Kroah-Hartman 	int err = 0;
66396fd7ce5SGreg Kroah-Hartman 
664fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc);
665fc575ee6SPeter Hurley 
66696fd7ce5SGreg Kroah-Hartman 	ld = tty_ldisc_ref(tty);
66796fd7ce5SGreg Kroah-Hartman 	if (ld != NULL) {
66896fd7ce5SGreg Kroah-Hartman 		if (ld->ops->flush_buffer)
66996fd7ce5SGreg Kroah-Hartman 			ld->ops->flush_buffer(tty);
67096fd7ce5SGreg Kroah-Hartman 		tty_driver_flush_buffer(tty);
67196fd7ce5SGreg Kroah-Hartman 		if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
67296fd7ce5SGreg Kroah-Hartman 		    ld->ops->write_wakeup)
67396fd7ce5SGreg Kroah-Hartman 			ld->ops->write_wakeup(tty);
67496fd7ce5SGreg Kroah-Hartman 		if (ld->ops->hangup)
67596fd7ce5SGreg Kroah-Hartman 			ld->ops->hangup(tty);
67696fd7ce5SGreg Kroah-Hartman 		tty_ldisc_deref(ld);
67796fd7ce5SGreg Kroah-Hartman 	}
67836697529SPeter Hurley 
67996fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
68096fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible_poll(&tty->read_wait, POLLIN);
68136697529SPeter Hurley 
68236697529SPeter Hurley 	tty_unlock(tty);
68336697529SPeter Hurley 
68496fd7ce5SGreg Kroah-Hartman 	/*
68596fd7ce5SGreg Kroah-Hartman 	 * Shutdown the current line discipline, and reset it to
68696fd7ce5SGreg Kroah-Hartman 	 * N_TTY if need be.
68796fd7ce5SGreg Kroah-Hartman 	 *
68896fd7ce5SGreg Kroah-Hartman 	 * Avoid racing set_ldisc or tty_ldisc_release
68996fd7ce5SGreg Kroah-Hartman 	 */
69036697529SPeter Hurley 	tty_ldisc_lock_pair(tty, tty->link);
69136697529SPeter Hurley 	tty_lock(tty);
69296fd7ce5SGreg Kroah-Hartman 
69336697529SPeter Hurley 	if (tty->ldisc) {
694c8785241SPeter Hurley 
695c8785241SPeter Hurley 		/* At this point we have a halted ldisc; we want to close it and
696c8785241SPeter Hurley 		   reopen a new ldisc. We could defer the reopen to the next
697c8785241SPeter Hurley 		   open but it means auditing a lot of other paths so this is
698c8785241SPeter Hurley 		   a FIXME */
69996fd7ce5SGreg Kroah-Hartman 		if (reset == 0) {
7001c95ba1eSPhilippe Rétornaz 
701adc8d746SAlan Cox 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))
70296fd7ce5SGreg Kroah-Hartman 				err = tty_ldisc_open(tty, tty->ldisc);
7031c95ba1eSPhilippe Rétornaz 			else
7041c95ba1eSPhilippe Rétornaz 				err = 1;
70596fd7ce5SGreg Kroah-Hartman 		}
70696fd7ce5SGreg Kroah-Hartman 		/* If the re-open fails or we reset then go to N_TTY. The
70796fd7ce5SGreg Kroah-Hartman 		   N_TTY open cannot fail */
70896fd7ce5SGreg Kroah-Hartman 		if (reset || err) {
7091c95ba1eSPhilippe Rétornaz 			BUG_ON(tty_ldisc_reinit(tty, N_TTY));
71096fd7ce5SGreg Kroah-Hartman 			WARN_ON(tty_ldisc_open(tty, tty->ldisc));
71196fd7ce5SGreg Kroah-Hartman 		}
71296fd7ce5SGreg Kroah-Hartman 	}
71336697529SPeter Hurley 	tty_ldisc_enable_pair(tty, tty->link);
71496fd7ce5SGreg Kroah-Hartman 	if (reset)
71596fd7ce5SGreg Kroah-Hartman 		tty_reset_termios(tty);
716fc575ee6SPeter Hurley 
717fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "re-opened ldisc: %p\n", tty->ldisc);
71896fd7ce5SGreg Kroah-Hartman }
71996fd7ce5SGreg Kroah-Hartman 
72096fd7ce5SGreg Kroah-Hartman /**
72196fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_setup			-	open line discipline
72296fd7ce5SGreg Kroah-Hartman  *	@tty: tty being shut down
72396fd7ce5SGreg Kroah-Hartman  *	@o_tty: pair tty for pty/tty pairs
72496fd7ce5SGreg Kroah-Hartman  *
72596fd7ce5SGreg Kroah-Hartman  *	Called during the initial open of a tty/pty pair in order to set up the
72696fd7ce5SGreg Kroah-Hartman  *	line disciplines and bind them to the tty. This has no locking issues
72796fd7ce5SGreg Kroah-Hartman  *	as the device isn't yet active.
72896fd7ce5SGreg Kroah-Hartman  */
72996fd7ce5SGreg Kroah-Hartman 
73096fd7ce5SGreg Kroah-Hartman int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
73196fd7ce5SGreg Kroah-Hartman {
73296fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld = tty->ldisc;
73396fd7ce5SGreg Kroah-Hartman 	int retval;
73496fd7ce5SGreg Kroah-Hartman 
73596fd7ce5SGreg Kroah-Hartman 	retval = tty_ldisc_open(tty, ld);
73696fd7ce5SGreg Kroah-Hartman 	if (retval)
73796fd7ce5SGreg Kroah-Hartman 		return retval;
73896fd7ce5SGreg Kroah-Hartman 
73996fd7ce5SGreg Kroah-Hartman 	if (o_tty) {
74096fd7ce5SGreg Kroah-Hartman 		retval = tty_ldisc_open(o_tty, o_tty->ldisc);
74196fd7ce5SGreg Kroah-Hartman 		if (retval) {
74296fd7ce5SGreg Kroah-Hartman 			tty_ldisc_close(tty, ld);
74396fd7ce5SGreg Kroah-Hartman 			return retval;
74496fd7ce5SGreg Kroah-Hartman 		}
74596fd7ce5SGreg Kroah-Hartman 	}
74696fd7ce5SGreg Kroah-Hartman 	return 0;
74796fd7ce5SGreg Kroah-Hartman }
74889c8d91eSAlan Cox 
74989c8d91eSAlan Cox static void tty_ldisc_kill(struct tty_struct *tty)
75089c8d91eSAlan Cox {
75189c8d91eSAlan Cox 	/*
75289c8d91eSAlan Cox 	 * Now kill off the ldisc
75389c8d91eSAlan Cox 	 */
75489c8d91eSAlan Cox 	tty_ldisc_close(tty, tty->ldisc);
75589c8d91eSAlan Cox 	tty_ldisc_put(tty->ldisc);
75689c8d91eSAlan Cox 	/* Force an oops if we mess this up */
75789c8d91eSAlan Cox 	tty->ldisc = NULL;
75889c8d91eSAlan Cox 
75989c8d91eSAlan Cox 	/* Ensure the next open requests the N_TTY ldisc */
76089c8d91eSAlan Cox 	tty_set_termios_ldisc(tty, N_TTY);
76189c8d91eSAlan Cox }
76289c8d91eSAlan Cox 
76396fd7ce5SGreg Kroah-Hartman /**
76496fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_release		-	release line discipline
76596fd7ce5SGreg Kroah-Hartman  *	@tty: tty being shut down
76696fd7ce5SGreg Kroah-Hartman  *	@o_tty: pair tty for pty/tty pairs
76796fd7ce5SGreg Kroah-Hartman  *
76896fd7ce5SGreg Kroah-Hartman  *	Called during the final close of a tty/pty pair in order to shut down
76996fd7ce5SGreg Kroah-Hartman  *	the line discpline layer. On exit the ldisc assigned is N_TTY and the
77096fd7ce5SGreg Kroah-Hartman  *	ldisc has not been opened.
77196fd7ce5SGreg Kroah-Hartman  */
77296fd7ce5SGreg Kroah-Hartman 
77396fd7ce5SGreg Kroah-Hartman void tty_ldisc_release(struct tty_struct *tty, struct tty_struct *o_tty)
77496fd7ce5SGreg Kroah-Hartman {
77596fd7ce5SGreg Kroah-Hartman 	/*
776a2965b7bSPeter Hurley 	 * Shutdown this line discipline. As this is the final close,
777a2965b7bSPeter Hurley 	 * it does not race with the set_ldisc code path.
77896fd7ce5SGreg Kroah-Hartman 	 */
77996fd7ce5SGreg Kroah-Hartman 
780fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc);
781fc575ee6SPeter Hurley 
78236697529SPeter Hurley 	tty_ldisc_lock_pair(tty, o_tty);
783852e4a81SSebastian Andrzej Siewior 	tty_lock_pair(tty, o_tty);
78436697529SPeter Hurley 
78589c8d91eSAlan Cox 	tty_ldisc_kill(tty);
78689c8d91eSAlan Cox 	if (o_tty)
78789c8d91eSAlan Cox 		tty_ldisc_kill(o_tty);
78889c8d91eSAlan Cox 
78989c8d91eSAlan Cox 	tty_unlock_pair(tty, o_tty);
79036697529SPeter Hurley 	tty_ldisc_unlock_pair(tty, o_tty);
79136697529SPeter Hurley 
79296fd7ce5SGreg Kroah-Hartman 	/* And the memory resources remaining (buffers, termios) will be
79396fd7ce5SGreg Kroah-Hartman 	   disposed of when the kref hits zero */
794fc575ee6SPeter Hurley 
795fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "ldisc closed\n");
79696fd7ce5SGreg Kroah-Hartman }
79796fd7ce5SGreg Kroah-Hartman 
79896fd7ce5SGreg Kroah-Hartman /**
79996fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_init		-	ldisc setup for new tty
80096fd7ce5SGreg Kroah-Hartman  *	@tty: tty being allocated
80196fd7ce5SGreg Kroah-Hartman  *
80296fd7ce5SGreg Kroah-Hartman  *	Set up the line discipline objects for a newly allocated tty. Note that
80396fd7ce5SGreg Kroah-Hartman  *	the tty structure is not completely set up when this call is made.
80496fd7ce5SGreg Kroah-Hartman  */
80596fd7ce5SGreg Kroah-Hartman 
80696fd7ce5SGreg Kroah-Hartman void tty_ldisc_init(struct tty_struct *tty)
80796fd7ce5SGreg Kroah-Hartman {
80836697529SPeter Hurley 	struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
80996fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ld))
81096fd7ce5SGreg Kroah-Hartman 		panic("n_tty: init_tty");
811f4807045SPeter Hurley 	tty->ldisc = ld;
81296fd7ce5SGreg Kroah-Hartman }
81396fd7ce5SGreg Kroah-Hartman 
8146716671dSJiri Slaby /**
8156716671dSJiri Slaby  *	tty_ldisc_init		-	ldisc cleanup for new tty
8166716671dSJiri Slaby  *	@tty: tty that was allocated recently
8176716671dSJiri Slaby  *
8186716671dSJiri Slaby  *	The tty structure must not becompletely set up (tty_ldisc_setup) when
8196716671dSJiri Slaby  *      this call is made.
8206716671dSJiri Slaby  */
8216716671dSJiri Slaby void tty_ldisc_deinit(struct tty_struct *tty)
8226716671dSJiri Slaby {
823ebc9baedSPeter Hurley 	tty_ldisc_put(tty->ldisc);
824f4807045SPeter Hurley 	tty->ldisc = NULL;
8256716671dSJiri Slaby }
8266716671dSJiri Slaby 
82796fd7ce5SGreg Kroah-Hartman void tty_ldisc_begin(void)
82896fd7ce5SGreg Kroah-Hartman {
82996fd7ce5SGreg Kroah-Hartman 	/* Setup the default TTY line discipline. */
83096fd7ce5SGreg Kroah-Hartman 	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
83196fd7ce5SGreg Kroah-Hartman }
832