xref: /openbmc/linux/drivers/tty/tty_ldisc.c (revision 429b4749)
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
25fc575ee6SPeter Hurley #define tty_ldisc_debug(tty, f, args...) ({				  \
26429b4749SRasmus Villemoes 	printk(KERN_DEBUG "%s: %s: " f, __func__, tty_name(tty), ##args); \
27fc575ee6SPeter Hurley })
28fc575ee6SPeter Hurley #else
29fc575ee6SPeter Hurley #define tty_ldisc_debug(tty, f, args...)
30fc575ee6SPeter Hurley #endif
31fc575ee6SPeter Hurley 
32d2c43890SPeter Hurley /* lockdep nested classes for tty->ldisc_sem */
33d2c43890SPeter Hurley enum {
34d2c43890SPeter Hurley 	LDISC_SEM_NORMAL,
35d2c43890SPeter Hurley 	LDISC_SEM_OTHER,
36d2c43890SPeter Hurley };
37d2c43890SPeter Hurley 
38d2c43890SPeter Hurley 
3996fd7ce5SGreg Kroah-Hartman /*
4096fd7ce5SGreg Kroah-Hartman  *	This guards the refcounted line discipline lists. The lock
4196fd7ce5SGreg Kroah-Hartman  *	must be taken with irqs off because there are hangup path
4296fd7ce5SGreg Kroah-Hartman  *	callers who will do ldisc lookups and cannot sleep.
4396fd7ce5SGreg Kroah-Hartman  */
4496fd7ce5SGreg Kroah-Hartman 
45137084bbSPeter Hurley static DEFINE_RAW_SPINLOCK(tty_ldiscs_lock);
4696fd7ce5SGreg Kroah-Hartman /* Line disc dispatch table */
4796fd7ce5SGreg Kroah-Hartman static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
4896fd7ce5SGreg Kroah-Hartman 
4996fd7ce5SGreg Kroah-Hartman /**
5096fd7ce5SGreg Kroah-Hartman  *	tty_register_ldisc	-	install a line discipline
5196fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
5296fd7ce5SGreg Kroah-Hartman  *	@new_ldisc: pointer to the ldisc object
5396fd7ce5SGreg Kroah-Hartman  *
5496fd7ce5SGreg Kroah-Hartman  *	Installs a new line discipline into the kernel. The discipline
5596fd7ce5SGreg Kroah-Hartman  *	is set up as unreferenced and then made available to the kernel
5696fd7ce5SGreg Kroah-Hartman  *	from this point onwards.
5796fd7ce5SGreg Kroah-Hartman  *
5896fd7ce5SGreg Kroah-Hartman  *	Locking:
59137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
6096fd7ce5SGreg Kroah-Hartman  */
6196fd7ce5SGreg Kroah-Hartman 
6296fd7ce5SGreg Kroah-Hartman int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
6396fd7ce5SGreg Kroah-Hartman {
6496fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
6596fd7ce5SGreg Kroah-Hartman 	int ret = 0;
6696fd7ce5SGreg Kroah-Hartman 
6796fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
6896fd7ce5SGreg Kroah-Hartman 		return -EINVAL;
6996fd7ce5SGreg Kroah-Hartman 
70137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
7196fd7ce5SGreg Kroah-Hartman 	tty_ldiscs[disc] = new_ldisc;
7296fd7ce5SGreg Kroah-Hartman 	new_ldisc->num = disc;
7396fd7ce5SGreg Kroah-Hartman 	new_ldisc->refcount = 0;
74137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
7596fd7ce5SGreg Kroah-Hartman 
7696fd7ce5SGreg Kroah-Hartman 	return ret;
7796fd7ce5SGreg Kroah-Hartman }
7896fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL(tty_register_ldisc);
7996fd7ce5SGreg Kroah-Hartman 
8096fd7ce5SGreg Kroah-Hartman /**
8196fd7ce5SGreg Kroah-Hartman  *	tty_unregister_ldisc	-	unload a line discipline
8296fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
8396fd7ce5SGreg Kroah-Hartman  *	@new_ldisc: pointer to the ldisc object
8496fd7ce5SGreg Kroah-Hartman  *
8596fd7ce5SGreg Kroah-Hartman  *	Remove a line discipline from the kernel providing it is not
8696fd7ce5SGreg Kroah-Hartman  *	currently in use.
8796fd7ce5SGreg Kroah-Hartman  *
8896fd7ce5SGreg Kroah-Hartman  *	Locking:
89137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
9096fd7ce5SGreg Kroah-Hartman  */
9196fd7ce5SGreg Kroah-Hartman 
9296fd7ce5SGreg Kroah-Hartman int tty_unregister_ldisc(int disc)
9396fd7ce5SGreg Kroah-Hartman {
9496fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
9596fd7ce5SGreg Kroah-Hartman 	int ret = 0;
9696fd7ce5SGreg Kroah-Hartman 
9796fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
9896fd7ce5SGreg Kroah-Hartman 		return -EINVAL;
9996fd7ce5SGreg Kroah-Hartman 
100137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
10196fd7ce5SGreg Kroah-Hartman 	if (tty_ldiscs[disc]->refcount)
10296fd7ce5SGreg Kroah-Hartman 		ret = -EBUSY;
10396fd7ce5SGreg Kroah-Hartman 	else
10496fd7ce5SGreg Kroah-Hartman 		tty_ldiscs[disc] = NULL;
105137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
10696fd7ce5SGreg Kroah-Hartman 
10796fd7ce5SGreg Kroah-Hartman 	return ret;
10896fd7ce5SGreg Kroah-Hartman }
10996fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL(tty_unregister_ldisc);
11096fd7ce5SGreg Kroah-Hartman 
11196fd7ce5SGreg Kroah-Hartman static struct tty_ldisc_ops *get_ldops(int disc)
11296fd7ce5SGreg Kroah-Hartman {
11396fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
11496fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops, *ret;
11596fd7ce5SGreg Kroah-Hartman 
116137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
11796fd7ce5SGreg Kroah-Hartman 	ret = ERR_PTR(-EINVAL);
11896fd7ce5SGreg Kroah-Hartman 	ldops = tty_ldiscs[disc];
11996fd7ce5SGreg Kroah-Hartman 	if (ldops) {
12096fd7ce5SGreg Kroah-Hartman 		ret = ERR_PTR(-EAGAIN);
12196fd7ce5SGreg Kroah-Hartman 		if (try_module_get(ldops->owner)) {
12296fd7ce5SGreg Kroah-Hartman 			ldops->refcount++;
12396fd7ce5SGreg Kroah-Hartman 			ret = ldops;
12496fd7ce5SGreg Kroah-Hartman 		}
12596fd7ce5SGreg Kroah-Hartman 	}
126137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
12796fd7ce5SGreg Kroah-Hartman 	return ret;
12896fd7ce5SGreg Kroah-Hartman }
12996fd7ce5SGreg Kroah-Hartman 
13096fd7ce5SGreg Kroah-Hartman static void put_ldops(struct tty_ldisc_ops *ldops)
13196fd7ce5SGreg Kroah-Hartman {
13296fd7ce5SGreg Kroah-Hartman 	unsigned long flags;
13396fd7ce5SGreg Kroah-Hartman 
134137084bbSPeter Hurley 	raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
13596fd7ce5SGreg Kroah-Hartman 	ldops->refcount--;
13696fd7ce5SGreg Kroah-Hartman 	module_put(ldops->owner);
137137084bbSPeter Hurley 	raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);
13896fd7ce5SGreg Kroah-Hartman }
13996fd7ce5SGreg Kroah-Hartman 
14096fd7ce5SGreg Kroah-Hartman /**
14196fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_get		-	take a reference to an ldisc
14296fd7ce5SGreg Kroah-Hartman  *	@disc: ldisc number
14396fd7ce5SGreg Kroah-Hartman  *
14496fd7ce5SGreg Kroah-Hartman  *	Takes a reference to a line discipline. Deals with refcounts and
14596fd7ce5SGreg Kroah-Hartman  *	module locking counts. Returns NULL if the discipline is not available.
14696fd7ce5SGreg Kroah-Hartman  *	Returns a pointer to the discipline and bumps the ref count if it is
14796fd7ce5SGreg Kroah-Hartman  *	available
14896fd7ce5SGreg Kroah-Hartman  *
14996fd7ce5SGreg Kroah-Hartman  *	Locking:
150137084bbSPeter Hurley  *		takes tty_ldiscs_lock to guard against ldisc races
15196fd7ce5SGreg Kroah-Hartman  */
15296fd7ce5SGreg Kroah-Hartman 
15336697529SPeter Hurley static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
15496fd7ce5SGreg Kroah-Hartman {
15596fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld;
15696fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops;
15796fd7ce5SGreg Kroah-Hartman 
15896fd7ce5SGreg Kroah-Hartman 	if (disc < N_TTY || disc >= NR_LDISCS)
15996fd7ce5SGreg Kroah-Hartman 		return ERR_PTR(-EINVAL);
16096fd7ce5SGreg Kroah-Hartman 
16196fd7ce5SGreg Kroah-Hartman 	/*
16296fd7ce5SGreg Kroah-Hartman 	 * Get the ldisc ops - we may need to request them to be loaded
16396fd7ce5SGreg Kroah-Hartman 	 * dynamically and try again.
16496fd7ce5SGreg Kroah-Hartman 	 */
16596fd7ce5SGreg Kroah-Hartman 	ldops = get_ldops(disc);
16696fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ldops)) {
16796fd7ce5SGreg Kroah-Hartman 		request_module("tty-ldisc-%d", disc);
16896fd7ce5SGreg Kroah-Hartman 		ldops = get_ldops(disc);
16996fd7ce5SGreg Kroah-Hartman 		if (IS_ERR(ldops))
17096fd7ce5SGreg Kroah-Hartman 			return ERR_CAST(ldops);
17196fd7ce5SGreg Kroah-Hartman 	}
17296fd7ce5SGreg Kroah-Hartman 
17396fd7ce5SGreg Kroah-Hartman 	ld = kmalloc(sizeof(struct tty_ldisc), GFP_KERNEL);
17496fd7ce5SGreg Kroah-Hartman 	if (ld == NULL) {
17596fd7ce5SGreg Kroah-Hartman 		put_ldops(ldops);
17696fd7ce5SGreg Kroah-Hartman 		return ERR_PTR(-ENOMEM);
17796fd7ce5SGreg Kroah-Hartman 	}
17896fd7ce5SGreg Kroah-Hartman 
17996fd7ce5SGreg Kroah-Hartman 	ld->ops = ldops;
18036697529SPeter Hurley 	ld->tty = tty;
1811541f845SIvo Sieben 
18296fd7ce5SGreg Kroah-Hartman 	return ld;
18396fd7ce5SGreg Kroah-Hartman }
18496fd7ce5SGreg Kroah-Hartman 
185734de249SPeter Hurley /**
186734de249SPeter Hurley  *	tty_ldisc_put		-	release the ldisc
187734de249SPeter Hurley  *
188734de249SPeter Hurley  *	Complement of tty_ldisc_get().
189734de249SPeter Hurley  */
190734de249SPeter Hurley static inline void tty_ldisc_put(struct tty_ldisc *ld)
191734de249SPeter Hurley {
192734de249SPeter Hurley 	if (WARN_ON_ONCE(!ld))
193734de249SPeter Hurley 		return;
194734de249SPeter Hurley 
19536697529SPeter Hurley 	put_ldops(ld->ops);
196734de249SPeter Hurley 	kfree(ld);
197734de249SPeter Hurley }
198734de249SPeter Hurley 
19996fd7ce5SGreg Kroah-Hartman static void *tty_ldiscs_seq_start(struct seq_file *m, loff_t *pos)
20096fd7ce5SGreg Kroah-Hartman {
20196fd7ce5SGreg Kroah-Hartman 	return (*pos < NR_LDISCS) ? pos : NULL;
20296fd7ce5SGreg Kroah-Hartman }
20396fd7ce5SGreg Kroah-Hartman 
20496fd7ce5SGreg Kroah-Hartman static void *tty_ldiscs_seq_next(struct seq_file *m, void *v, loff_t *pos)
20596fd7ce5SGreg Kroah-Hartman {
20696fd7ce5SGreg Kroah-Hartman 	(*pos)++;
20796fd7ce5SGreg Kroah-Hartman 	return (*pos < NR_LDISCS) ? pos : NULL;
20896fd7ce5SGreg Kroah-Hartman }
20996fd7ce5SGreg Kroah-Hartman 
21096fd7ce5SGreg Kroah-Hartman static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
21196fd7ce5SGreg Kroah-Hartman {
21296fd7ce5SGreg Kroah-Hartman }
21396fd7ce5SGreg Kroah-Hartman 
21496fd7ce5SGreg Kroah-Hartman static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
21596fd7ce5SGreg Kroah-Hartman {
21696fd7ce5SGreg Kroah-Hartman 	int i = *(loff_t *)v;
21796fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc_ops *ldops;
21896fd7ce5SGreg Kroah-Hartman 
21996fd7ce5SGreg Kroah-Hartman 	ldops = get_ldops(i);
22096fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ldops))
22196fd7ce5SGreg Kroah-Hartman 		return 0;
22296fd7ce5SGreg Kroah-Hartman 	seq_printf(m, "%-10s %2d\n", ldops->name ? ldops->name : "???", i);
22396fd7ce5SGreg Kroah-Hartman 	put_ldops(ldops);
22496fd7ce5SGreg Kroah-Hartman 	return 0;
22596fd7ce5SGreg Kroah-Hartman }
22696fd7ce5SGreg Kroah-Hartman 
22796fd7ce5SGreg Kroah-Hartman static const struct seq_operations tty_ldiscs_seq_ops = {
22896fd7ce5SGreg Kroah-Hartman 	.start	= tty_ldiscs_seq_start,
22996fd7ce5SGreg Kroah-Hartman 	.next	= tty_ldiscs_seq_next,
23096fd7ce5SGreg Kroah-Hartman 	.stop	= tty_ldiscs_seq_stop,
23196fd7ce5SGreg Kroah-Hartman 	.show	= tty_ldiscs_seq_show,
23296fd7ce5SGreg Kroah-Hartman };
23396fd7ce5SGreg Kroah-Hartman 
23496fd7ce5SGreg Kroah-Hartman static int proc_tty_ldiscs_open(struct inode *inode, struct file *file)
23596fd7ce5SGreg Kroah-Hartman {
23696fd7ce5SGreg Kroah-Hartman 	return seq_open(file, &tty_ldiscs_seq_ops);
23796fd7ce5SGreg Kroah-Hartman }
23896fd7ce5SGreg Kroah-Hartman 
23996fd7ce5SGreg Kroah-Hartman const struct file_operations tty_ldiscs_proc_fops = {
24096fd7ce5SGreg Kroah-Hartman 	.owner		= THIS_MODULE,
24196fd7ce5SGreg Kroah-Hartman 	.open		= proc_tty_ldiscs_open,
24296fd7ce5SGreg Kroah-Hartman 	.read		= seq_read,
24396fd7ce5SGreg Kroah-Hartman 	.llseek		= seq_lseek,
24496fd7ce5SGreg Kroah-Hartman 	.release	= seq_release,
24596fd7ce5SGreg Kroah-Hartman };
24696fd7ce5SGreg Kroah-Hartman 
24796fd7ce5SGreg Kroah-Hartman /**
24896fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_ref_wait	-	wait for the tty ldisc
24996fd7ce5SGreg Kroah-Hartman  *	@tty: tty device
25096fd7ce5SGreg Kroah-Hartman  *
25196fd7ce5SGreg Kroah-Hartman  *	Dereference the line discipline for the terminal and take a
25296fd7ce5SGreg Kroah-Hartman  *	reference to it. If the line discipline is in flux then
25396fd7ce5SGreg Kroah-Hartman  *	wait patiently until it changes.
25496fd7ce5SGreg Kroah-Hartman  *
25596fd7ce5SGreg Kroah-Hartman  *	Note: Must not be called from an IRQ/timer context. The caller
25696fd7ce5SGreg Kroah-Hartman  *	must also be careful not to hold other locks that will deadlock
25796fd7ce5SGreg Kroah-Hartman  *	against a discipline change, such as an existing ldisc reference
25896fd7ce5SGreg Kroah-Hartman  *	(which we check for)
25996fd7ce5SGreg Kroah-Hartman  *
26036697529SPeter Hurley  *	Note: only callable from a file_operations routine (which
26136697529SPeter Hurley  *	guarantees tty->ldisc != NULL when the lock is acquired).
26296fd7ce5SGreg Kroah-Hartman  */
26396fd7ce5SGreg Kroah-Hartman 
26496fd7ce5SGreg Kroah-Hartman struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
26596fd7ce5SGreg Kroah-Hartman {
26636697529SPeter Hurley 	ldsem_down_read(&tty->ldisc_sem, MAX_SCHEDULE_TIMEOUT);
26736697529SPeter Hurley 	WARN_ON(!tty->ldisc);
26836697529SPeter Hurley 	return tty->ldisc;
26996fd7ce5SGreg Kroah-Hartman }
27096fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
27196fd7ce5SGreg Kroah-Hartman 
27296fd7ce5SGreg Kroah-Hartman /**
27396fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_ref		-	get the tty ldisc
27496fd7ce5SGreg Kroah-Hartman  *	@tty: tty device
27596fd7ce5SGreg Kroah-Hartman  *
27696fd7ce5SGreg Kroah-Hartman  *	Dereference the line discipline for the terminal and take a
27796fd7ce5SGreg Kroah-Hartman  *	reference to it. If the line discipline is in flux then
27896fd7ce5SGreg Kroah-Hartman  *	return NULL. Can be called from IRQ and timer functions.
27996fd7ce5SGreg Kroah-Hartman  */
28096fd7ce5SGreg Kroah-Hartman 
28196fd7ce5SGreg Kroah-Hartman struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
28296fd7ce5SGreg Kroah-Hartman {
28336697529SPeter Hurley 	struct tty_ldisc *ld = NULL;
28436697529SPeter Hurley 
28536697529SPeter Hurley 	if (ldsem_down_read_trylock(&tty->ldisc_sem)) {
28636697529SPeter Hurley 		ld = tty->ldisc;
28736697529SPeter Hurley 		if (!ld)
28836697529SPeter Hurley 			ldsem_up_read(&tty->ldisc_sem);
28936697529SPeter Hurley 	}
29036697529SPeter Hurley 	return ld;
29196fd7ce5SGreg Kroah-Hartman }
29296fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_ref);
29396fd7ce5SGreg Kroah-Hartman 
29496fd7ce5SGreg Kroah-Hartman /**
29596fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_deref		-	free a tty ldisc reference
29696fd7ce5SGreg Kroah-Hartman  *	@ld: reference to free up
29796fd7ce5SGreg Kroah-Hartman  *
29896fd7ce5SGreg Kroah-Hartman  *	Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
29996fd7ce5SGreg Kroah-Hartman  *	be called in IRQ context.
30096fd7ce5SGreg Kroah-Hartman  */
30196fd7ce5SGreg Kroah-Hartman 
30296fd7ce5SGreg Kroah-Hartman void tty_ldisc_deref(struct tty_ldisc *ld)
30396fd7ce5SGreg Kroah-Hartman {
30436697529SPeter Hurley 	ldsem_up_read(&ld->tty->ldisc_sem);
30596fd7ce5SGreg Kroah-Hartman }
30696fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_deref);
30796fd7ce5SGreg Kroah-Hartman 
308d2c43890SPeter Hurley 
309d2c43890SPeter Hurley static inline int __lockfunc
310e80a10eeSPeter Hurley __tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
311d2c43890SPeter Hurley {
312d2c43890SPeter Hurley 	return ldsem_down_write(&tty->ldisc_sem, timeout);
313d2c43890SPeter Hurley }
314d2c43890SPeter Hurley 
315d2c43890SPeter Hurley static inline int __lockfunc
316e80a10eeSPeter Hurley __tty_ldisc_lock_nested(struct tty_struct *tty, unsigned long timeout)
317d2c43890SPeter Hurley {
318d2c43890SPeter Hurley 	return ldsem_down_write_nested(&tty->ldisc_sem,
319d2c43890SPeter Hurley 				       LDISC_SEM_OTHER, timeout);
320d2c43890SPeter Hurley }
321d2c43890SPeter Hurley 
322e80a10eeSPeter Hurley static inline void __tty_ldisc_unlock(struct tty_struct *tty)
323d2c43890SPeter Hurley {
324d2c43890SPeter Hurley 	return ldsem_up_write(&tty->ldisc_sem);
325d2c43890SPeter Hurley }
326d2c43890SPeter Hurley 
327d2c43890SPeter Hurley static int __lockfunc
328fae76e9aSPeter Hurley tty_ldisc_lock(struct tty_struct *tty, unsigned long timeout)
329fae76e9aSPeter Hurley {
330fae76e9aSPeter Hurley 	int ret;
331fae76e9aSPeter Hurley 
332fae76e9aSPeter Hurley 	ret = __tty_ldisc_lock(tty, timeout);
333fae76e9aSPeter Hurley 	if (!ret)
334fae76e9aSPeter Hurley 		return -EBUSY;
335fae76e9aSPeter Hurley 	set_bit(TTY_LDISC_HALTED, &tty->flags);
336fae76e9aSPeter Hurley 	return 0;
337fae76e9aSPeter Hurley }
338fae76e9aSPeter Hurley 
339fae76e9aSPeter Hurley static void tty_ldisc_unlock(struct tty_struct *tty)
340fae76e9aSPeter Hurley {
341fae76e9aSPeter Hurley 	clear_bit(TTY_LDISC_HALTED, &tty->flags);
342fae76e9aSPeter Hurley 	__tty_ldisc_unlock(tty);
343fae76e9aSPeter Hurley }
344fae76e9aSPeter Hurley 
345fae76e9aSPeter Hurley static int __lockfunc
346d2c43890SPeter Hurley tty_ldisc_lock_pair_timeout(struct tty_struct *tty, struct tty_struct *tty2,
347d2c43890SPeter Hurley 			    unsigned long timeout)
348d2c43890SPeter Hurley {
349d2c43890SPeter Hurley 	int ret;
350d2c43890SPeter Hurley 
351d2c43890SPeter Hurley 	if (tty < tty2) {
352e80a10eeSPeter Hurley 		ret = __tty_ldisc_lock(tty, timeout);
353d2c43890SPeter Hurley 		if (ret) {
354e80a10eeSPeter Hurley 			ret = __tty_ldisc_lock_nested(tty2, timeout);
355d2c43890SPeter Hurley 			if (!ret)
356e80a10eeSPeter Hurley 				__tty_ldisc_unlock(tty);
357d2c43890SPeter Hurley 		}
358d2c43890SPeter Hurley 	} else {
359d2c43890SPeter Hurley 		/* if this is possible, it has lots of implications */
360d2c43890SPeter Hurley 		WARN_ON_ONCE(tty == tty2);
361d2c43890SPeter Hurley 		if (tty2 && tty != tty2) {
362e80a10eeSPeter Hurley 			ret = __tty_ldisc_lock(tty2, timeout);
363d2c43890SPeter Hurley 			if (ret) {
364e80a10eeSPeter Hurley 				ret = __tty_ldisc_lock_nested(tty, timeout);
365d2c43890SPeter Hurley 				if (!ret)
366e80a10eeSPeter Hurley 					__tty_ldisc_unlock(tty2);
367d2c43890SPeter Hurley 			}
368d2c43890SPeter Hurley 		} else
369e80a10eeSPeter Hurley 			ret = __tty_ldisc_lock(tty, timeout);
370d2c43890SPeter Hurley 	}
371d2c43890SPeter Hurley 
372d2c43890SPeter Hurley 	if (!ret)
373d2c43890SPeter Hurley 		return -EBUSY;
374d2c43890SPeter Hurley 
375d2c43890SPeter Hurley 	set_bit(TTY_LDISC_HALTED, &tty->flags);
376d2c43890SPeter Hurley 	if (tty2)
377d2c43890SPeter Hurley 		set_bit(TTY_LDISC_HALTED, &tty2->flags);
378d2c43890SPeter Hurley 	return 0;
379d2c43890SPeter Hurley }
380d2c43890SPeter Hurley 
381d2c43890SPeter Hurley static void __lockfunc
382d2c43890SPeter Hurley tty_ldisc_lock_pair(struct tty_struct *tty, struct tty_struct *tty2)
383d2c43890SPeter Hurley {
384d2c43890SPeter Hurley 	tty_ldisc_lock_pair_timeout(tty, tty2, MAX_SCHEDULE_TIMEOUT);
385d2c43890SPeter Hurley }
386d2c43890SPeter Hurley 
387d2c43890SPeter Hurley static void __lockfunc tty_ldisc_unlock_pair(struct tty_struct *tty,
388d2c43890SPeter Hurley 					     struct tty_struct *tty2)
389d2c43890SPeter Hurley {
390e80a10eeSPeter Hurley 	__tty_ldisc_unlock(tty);
391d2c43890SPeter Hurley 	if (tty2)
392e80a10eeSPeter Hurley 		__tty_ldisc_unlock(tty2);
393d2c43890SPeter Hurley }
394d2c43890SPeter Hurley 
39596fd7ce5SGreg Kroah-Hartman /**
39696fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_flush	-	flush line discipline queue
39796fd7ce5SGreg Kroah-Hartman  *	@tty: tty
39896fd7ce5SGreg Kroah-Hartman  *
39986c80a8eSPeter Hurley  *	Flush the line discipline queue (if any) and the tty flip buffers
40086c80a8eSPeter Hurley  *	for this tty.
40196fd7ce5SGreg Kroah-Hartman  */
40296fd7ce5SGreg Kroah-Hartman 
40396fd7ce5SGreg Kroah-Hartman void tty_ldisc_flush(struct tty_struct *tty)
40496fd7ce5SGreg Kroah-Hartman {
40596fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld = tty_ldisc_ref(tty);
40686c80a8eSPeter Hurley 
40786c80a8eSPeter Hurley 	tty_buffer_flush(tty, ld);
40886c80a8eSPeter Hurley 	if (ld)
40996fd7ce5SGreg Kroah-Hartman 		tty_ldisc_deref(ld);
41096fd7ce5SGreg Kroah-Hartman }
41196fd7ce5SGreg Kroah-Hartman EXPORT_SYMBOL_GPL(tty_ldisc_flush);
41296fd7ce5SGreg Kroah-Hartman 
41396fd7ce5SGreg Kroah-Hartman /**
41496fd7ce5SGreg Kroah-Hartman  *	tty_set_termios_ldisc		-	set ldisc field
41596fd7ce5SGreg Kroah-Hartman  *	@tty: tty structure
41696fd7ce5SGreg Kroah-Hartman  *	@num: line discipline number
41796fd7ce5SGreg Kroah-Hartman  *
41896fd7ce5SGreg Kroah-Hartman  *	This is probably overkill for real world processors but
41996fd7ce5SGreg Kroah-Hartman  *	they are not on hot paths so a little discipline won't do
42096fd7ce5SGreg Kroah-Hartman  *	any harm.
42196fd7ce5SGreg Kroah-Hartman  *
4226a1c0680SPeter Hurley  *	Locking: takes termios_rwsem
42396fd7ce5SGreg Kroah-Hartman  */
42496fd7ce5SGreg Kroah-Hartman 
42596fd7ce5SGreg Kroah-Hartman static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
42696fd7ce5SGreg Kroah-Hartman {
4276a1c0680SPeter Hurley 	down_write(&tty->termios_rwsem);
428adc8d746SAlan Cox 	tty->termios.c_line = num;
4296a1c0680SPeter Hurley 	up_write(&tty->termios_rwsem);
43096fd7ce5SGreg Kroah-Hartman }
43196fd7ce5SGreg Kroah-Hartman 
43296fd7ce5SGreg Kroah-Hartman /**
43396fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_open		-	open a line discipline
43496fd7ce5SGreg Kroah-Hartman  *	@tty: tty we are opening the ldisc on
43596fd7ce5SGreg Kroah-Hartman  *	@ld: discipline to open
43696fd7ce5SGreg Kroah-Hartman  *
43796fd7ce5SGreg Kroah-Hartman  *	A helper opening method. Also a convenient debugging and check
43896fd7ce5SGreg Kroah-Hartman  *	point.
43996fd7ce5SGreg Kroah-Hartman  *
44096fd7ce5SGreg Kroah-Hartman  *	Locking: always called with BTM already held.
44196fd7ce5SGreg Kroah-Hartman  */
44296fd7ce5SGreg Kroah-Hartman 
44396fd7ce5SGreg Kroah-Hartman static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)
44496fd7ce5SGreg Kroah-Hartman {
44596fd7ce5SGreg Kroah-Hartman 	WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));
44696fd7ce5SGreg Kroah-Hartman 	if (ld->ops->open) {
44796fd7ce5SGreg Kroah-Hartman 		int ret;
44896fd7ce5SGreg Kroah-Hartman                 /* BTM here locks versus a hangup event */
44996fd7ce5SGreg Kroah-Hartman 		ret = ld->ops->open(tty);
4507f90cfc5SJiri Slaby 		if (ret)
4517f90cfc5SJiri Slaby 			clear_bit(TTY_LDISC_OPEN, &tty->flags);
45296fd7ce5SGreg Kroah-Hartman 		return ret;
45396fd7ce5SGreg Kroah-Hartman 	}
45496fd7ce5SGreg Kroah-Hartman 	return 0;
45596fd7ce5SGreg Kroah-Hartman }
45696fd7ce5SGreg Kroah-Hartman 
45796fd7ce5SGreg Kroah-Hartman /**
45896fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_close		-	close a line discipline
45996fd7ce5SGreg Kroah-Hartman  *	@tty: tty we are opening the ldisc on
46096fd7ce5SGreg Kroah-Hartman  *	@ld: discipline to close
46196fd7ce5SGreg Kroah-Hartman  *
46296fd7ce5SGreg Kroah-Hartman  *	A helper close method. Also a convenient debugging and check
46396fd7ce5SGreg Kroah-Hartman  *	point.
46496fd7ce5SGreg Kroah-Hartman  */
46596fd7ce5SGreg Kroah-Hartman 
46696fd7ce5SGreg Kroah-Hartman static void tty_ldisc_close(struct tty_struct *tty, struct tty_ldisc *ld)
46796fd7ce5SGreg Kroah-Hartman {
46896fd7ce5SGreg Kroah-Hartman 	WARN_ON(!test_bit(TTY_LDISC_OPEN, &tty->flags));
46996fd7ce5SGreg Kroah-Hartman 	clear_bit(TTY_LDISC_OPEN, &tty->flags);
47096fd7ce5SGreg Kroah-Hartman 	if (ld->ops->close)
47196fd7ce5SGreg Kroah-Hartman 		ld->ops->close(tty);
47296fd7ce5SGreg Kroah-Hartman }
47396fd7ce5SGreg Kroah-Hartman 
47496fd7ce5SGreg Kroah-Hartman /**
47596fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_restore	-	helper for tty ldisc change
47696fd7ce5SGreg Kroah-Hartman  *	@tty: tty to recover
47796fd7ce5SGreg Kroah-Hartman  *	@old: previous ldisc
47896fd7ce5SGreg Kroah-Hartman  *
47996fd7ce5SGreg Kroah-Hartman  *	Restore the previous line discipline or N_TTY when a line discipline
48096fd7ce5SGreg Kroah-Hartman  *	change fails due to an open error
48196fd7ce5SGreg Kroah-Hartman  */
48296fd7ce5SGreg Kroah-Hartman 
48396fd7ce5SGreg Kroah-Hartman static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
48496fd7ce5SGreg Kroah-Hartman {
48596fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *new_ldisc;
48696fd7ce5SGreg Kroah-Hartman 	int r;
48796fd7ce5SGreg Kroah-Hartman 
48896fd7ce5SGreg Kroah-Hartman 	/* There is an outstanding reference here so this is safe */
48936697529SPeter Hurley 	old = tty_ldisc_get(tty, old->ops->num);
49096fd7ce5SGreg Kroah-Hartman 	WARN_ON(IS_ERR(old));
491f4807045SPeter Hurley 	tty->ldisc = old;
49296fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, old->ops->num);
49396fd7ce5SGreg Kroah-Hartman 	if (tty_ldisc_open(tty, old) < 0) {
49496fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(old);
49596fd7ce5SGreg Kroah-Hartman 		/* This driver is always present */
49636697529SPeter Hurley 		new_ldisc = tty_ldisc_get(tty, N_TTY);
49796fd7ce5SGreg Kroah-Hartman 		if (IS_ERR(new_ldisc))
49896fd7ce5SGreg Kroah-Hartman 			panic("n_tty: get");
499f4807045SPeter Hurley 		tty->ldisc = new_ldisc;
50096fd7ce5SGreg Kroah-Hartman 		tty_set_termios_ldisc(tty, N_TTY);
50196fd7ce5SGreg Kroah-Hartman 		r = tty_ldisc_open(tty, new_ldisc);
50296fd7ce5SGreg Kroah-Hartman 		if (r < 0)
50396fd7ce5SGreg Kroah-Hartman 			panic("Couldn't open N_TTY ldisc for "
50496fd7ce5SGreg Kroah-Hartman 			      "%s --- error %d.",
505429b4749SRasmus Villemoes 			      tty_name(tty), r);
50696fd7ce5SGreg Kroah-Hartman 	}
50796fd7ce5SGreg Kroah-Hartman }
50896fd7ce5SGreg Kroah-Hartman 
50996fd7ce5SGreg Kroah-Hartman /**
51096fd7ce5SGreg Kroah-Hartman  *	tty_set_ldisc		-	set line discipline
51196fd7ce5SGreg Kroah-Hartman  *	@tty: the terminal to set
51296fd7ce5SGreg Kroah-Hartman  *	@ldisc: the line discipline
51396fd7ce5SGreg Kroah-Hartman  *
51496fd7ce5SGreg Kroah-Hartman  *	Set the discipline of a tty line. Must be called from a process
51596fd7ce5SGreg Kroah-Hartman  *	context. The ldisc change logic has to protect itself against any
51696fd7ce5SGreg Kroah-Hartman  *	overlapping ldisc change (including on the other end of pty pairs),
51796fd7ce5SGreg Kroah-Hartman  *	the close of one side of a tty/pty pair, and eventually hangup.
51896fd7ce5SGreg Kroah-Hartman  */
51996fd7ce5SGreg Kroah-Hartman 
52096fd7ce5SGreg Kroah-Hartman int tty_set_ldisc(struct tty_struct *tty, int ldisc)
52196fd7ce5SGreg Kroah-Hartman {
52296fd7ce5SGreg Kroah-Hartman 	int retval;
5239fbfa34cSPeter Hurley 	struct tty_ldisc *old_ldisc, *new_ldisc;
52496fd7ce5SGreg Kroah-Hartman 
52536697529SPeter Hurley 	new_ldisc = tty_ldisc_get(tty, ldisc);
52696fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(new_ldisc))
52796fd7ce5SGreg Kroah-Hartman 		return PTR_ERR(new_ldisc);
52896fd7ce5SGreg Kroah-Hartman 
529c8483bc9SPeter Hurley 	tty_lock(tty);
530276a661aSPeter Hurley 	retval = tty_ldisc_lock(tty, 5 * HZ);
53136697529SPeter Hurley 	if (retval) {
53236697529SPeter Hurley 		tty_ldisc_put(new_ldisc);
533c8483bc9SPeter Hurley 		tty_unlock(tty);
53436697529SPeter Hurley 		return retval;
53536697529SPeter Hurley 	}
53696fd7ce5SGreg Kroah-Hartman 
53796fd7ce5SGreg Kroah-Hartman 	/*
53896fd7ce5SGreg Kroah-Hartman 	 *	Check the no-op case
53996fd7ce5SGreg Kroah-Hartman 	 */
54096fd7ce5SGreg Kroah-Hartman 
54196fd7ce5SGreg Kroah-Hartman 	if (tty->ldisc->ops->num == ldisc) {
542276a661aSPeter Hurley 		tty_ldisc_unlock(tty);
54396fd7ce5SGreg Kroah-Hartman 		tty_ldisc_put(new_ldisc);
544c8483bc9SPeter Hurley 		tty_unlock(tty);
54596fd7ce5SGreg Kroah-Hartman 		return 0;
54696fd7ce5SGreg Kroah-Hartman 	}
54796fd7ce5SGreg Kroah-Hartman 
5489fbfa34cSPeter Hurley 	old_ldisc = tty->ldisc;
549100eeae2SJiri Slaby 
5503ff51a19SPeter Hurley 	if (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 */
553276a661aSPeter Hurley 		tty_ldisc_unlock(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 
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. */
584b0e95858SPeter Hurley 
5859fbfa34cSPeter Hurley 	tty_ldisc_put(old_ldisc);
58696fd7ce5SGreg Kroah-Hartman 
58796fd7ce5SGreg Kroah-Hartman 	/*
58896fd7ce5SGreg Kroah-Hartman 	 *	Allow ldisc referencing to occur again
58996fd7ce5SGreg Kroah-Hartman 	 */
590276a661aSPeter Hurley 	tty_ldisc_unlock(tty);
59196fd7ce5SGreg Kroah-Hartman 
59296fd7ce5SGreg Kroah-Hartman 	/* Restart the work queue in case no characters kick it off. Safe if
59396fd7ce5SGreg Kroah-Hartman 	   already running */
594ecbbfd44SJiri Slaby 	schedule_work(&tty->port->buf.work);
5954f98d467SPeter Hurley 
59689c8d91eSAlan Cox 	tty_unlock(tty);
59796fd7ce5SGreg Kroah-Hartman 	return retval;
59896fd7ce5SGreg Kroah-Hartman }
59996fd7ce5SGreg Kroah-Hartman 
60096fd7ce5SGreg Kroah-Hartman /**
60196fd7ce5SGreg Kroah-Hartman  *	tty_reset_termios	-	reset terminal state
60296fd7ce5SGreg Kroah-Hartman  *	@tty: tty to reset
60396fd7ce5SGreg Kroah-Hartman  *
60496fd7ce5SGreg Kroah-Hartman  *	Restore a terminal to the driver default state.
60596fd7ce5SGreg Kroah-Hartman  */
60696fd7ce5SGreg Kroah-Hartman 
60796fd7ce5SGreg Kroah-Hartman static void tty_reset_termios(struct tty_struct *tty)
60896fd7ce5SGreg Kroah-Hartman {
6096a1c0680SPeter Hurley 	down_write(&tty->termios_rwsem);
610adc8d746SAlan Cox 	tty->termios = tty->driver->init_termios;
611adc8d746SAlan Cox 	tty->termios.c_ispeed = tty_termios_input_baud_rate(&tty->termios);
612adc8d746SAlan Cox 	tty->termios.c_ospeed = tty_termios_baud_rate(&tty->termios);
6136a1c0680SPeter Hurley 	up_write(&tty->termios_rwsem);
61496fd7ce5SGreg Kroah-Hartman }
61596fd7ce5SGreg Kroah-Hartman 
61696fd7ce5SGreg Kroah-Hartman 
61796fd7ce5SGreg Kroah-Hartman /**
61896fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_reinit	-	reinitialise the tty ldisc
61996fd7ce5SGreg Kroah-Hartman  *	@tty: tty to reinit
62096fd7ce5SGreg Kroah-Hartman  *	@ldisc: line discipline to reinitialize
62196fd7ce5SGreg Kroah-Hartman  *
62296fd7ce5SGreg Kroah-Hartman  *	Switch the tty to a line discipline and leave the ldisc
62396fd7ce5SGreg Kroah-Hartman  *	state closed
62496fd7ce5SGreg Kroah-Hartman  */
62596fd7ce5SGreg Kroah-Hartman 
6261c95ba1eSPhilippe Rétornaz static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
62796fd7ce5SGreg Kroah-Hartman {
62836697529SPeter Hurley 	struct tty_ldisc *ld = tty_ldisc_get(tty, ldisc);
6291c95ba1eSPhilippe Rétornaz 
6301c95ba1eSPhilippe Rétornaz 	if (IS_ERR(ld))
6311c95ba1eSPhilippe Rétornaz 		return -1;
63296fd7ce5SGreg Kroah-Hartman 
63396fd7ce5SGreg Kroah-Hartman 	tty_ldisc_close(tty, tty->ldisc);
63496fd7ce5SGreg Kroah-Hartman 	tty_ldisc_put(tty->ldisc);
63596fd7ce5SGreg Kroah-Hartman 	/*
63696fd7ce5SGreg Kroah-Hartman 	 *	Switch the line discipline back
63796fd7ce5SGreg Kroah-Hartman 	 */
638f4807045SPeter Hurley 	tty->ldisc = ld;
63996fd7ce5SGreg Kroah-Hartman 	tty_set_termios_ldisc(tty, ldisc);
6401c95ba1eSPhilippe Rétornaz 
6411c95ba1eSPhilippe Rétornaz 	return 0;
64296fd7ce5SGreg Kroah-Hartman }
64396fd7ce5SGreg Kroah-Hartman 
64496fd7ce5SGreg Kroah-Hartman /**
64596fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_hangup		-	hangup ldisc reset
64696fd7ce5SGreg Kroah-Hartman  *	@tty: tty being hung up
64796fd7ce5SGreg Kroah-Hartman  *
64896fd7ce5SGreg Kroah-Hartman  *	Some tty devices reset their termios when they receive a hangup
64996fd7ce5SGreg Kroah-Hartman  *	event. In that situation we must also switch back to N_TTY properly
65096fd7ce5SGreg Kroah-Hartman  *	before we reset the termios data.
65196fd7ce5SGreg Kroah-Hartman  *
65296fd7ce5SGreg Kroah-Hartman  *	Locking: We can take the ldisc mutex as the rest of the code is
65396fd7ce5SGreg Kroah-Hartman  *	careful to allow for this.
65496fd7ce5SGreg Kroah-Hartman  *
65596fd7ce5SGreg Kroah-Hartman  *	In the pty pair case this occurs in the close() path of the
65696fd7ce5SGreg Kroah-Hartman  *	tty itself so we must be careful about locking rules.
65796fd7ce5SGreg Kroah-Hartman  */
65896fd7ce5SGreg Kroah-Hartman 
65996fd7ce5SGreg Kroah-Hartman void tty_ldisc_hangup(struct tty_struct *tty)
66096fd7ce5SGreg Kroah-Hartman {
66196fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld;
66296fd7ce5SGreg Kroah-Hartman 	int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
66396fd7ce5SGreg Kroah-Hartman 	int err = 0;
66496fd7ce5SGreg Kroah-Hartman 
665fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc);
666fc575ee6SPeter Hurley 
66796fd7ce5SGreg Kroah-Hartman 	ld = tty_ldisc_ref(tty);
66896fd7ce5SGreg Kroah-Hartman 	if (ld != NULL) {
66996fd7ce5SGreg Kroah-Hartman 		if (ld->ops->flush_buffer)
67096fd7ce5SGreg Kroah-Hartman 			ld->ops->flush_buffer(tty);
67196fd7ce5SGreg Kroah-Hartman 		tty_driver_flush_buffer(tty);
67296fd7ce5SGreg Kroah-Hartman 		if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
67396fd7ce5SGreg Kroah-Hartman 		    ld->ops->write_wakeup)
67496fd7ce5SGreg Kroah-Hartman 			ld->ops->write_wakeup(tty);
67596fd7ce5SGreg Kroah-Hartman 		if (ld->ops->hangup)
67696fd7ce5SGreg Kroah-Hartman 			ld->ops->hangup(tty);
67796fd7ce5SGreg Kroah-Hartman 		tty_ldisc_deref(ld);
67896fd7ce5SGreg Kroah-Hartman 	}
67936697529SPeter Hurley 
68096fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible_poll(&tty->write_wait, POLLOUT);
68196fd7ce5SGreg Kroah-Hartman 	wake_up_interruptible_poll(&tty->read_wait, POLLIN);
68236697529SPeter Hurley 
68396fd7ce5SGreg Kroah-Hartman 	/*
68496fd7ce5SGreg Kroah-Hartman 	 * Shutdown the current line discipline, and reset it to
68596fd7ce5SGreg Kroah-Hartman 	 * N_TTY if need be.
68696fd7ce5SGreg Kroah-Hartman 	 *
68796fd7ce5SGreg Kroah-Hartman 	 * Avoid racing set_ldisc or tty_ldisc_release
68896fd7ce5SGreg Kroah-Hartman 	 */
689fae76e9aSPeter Hurley 	tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
69096fd7ce5SGreg Kroah-Hartman 
69136697529SPeter Hurley 	if (tty->ldisc) {
692c8785241SPeter Hurley 
693c8785241SPeter Hurley 		/* At this point we have a halted ldisc; we want to close it and
694c8785241SPeter Hurley 		   reopen a new ldisc. We could defer the reopen to the next
695c8785241SPeter Hurley 		   open but it means auditing a lot of other paths so this is
696c8785241SPeter Hurley 		   a FIXME */
69796fd7ce5SGreg Kroah-Hartman 		if (reset == 0) {
6981c95ba1eSPhilippe Rétornaz 
699adc8d746SAlan Cox 			if (!tty_ldisc_reinit(tty, tty->termios.c_line))
70096fd7ce5SGreg Kroah-Hartman 				err = tty_ldisc_open(tty, tty->ldisc);
7011c95ba1eSPhilippe Rétornaz 			else
7021c95ba1eSPhilippe Rétornaz 				err = 1;
70396fd7ce5SGreg Kroah-Hartman 		}
70496fd7ce5SGreg Kroah-Hartman 		/* If the re-open fails or we reset then go to N_TTY. The
70596fd7ce5SGreg Kroah-Hartman 		   N_TTY open cannot fail */
70696fd7ce5SGreg Kroah-Hartman 		if (reset || err) {
7071c95ba1eSPhilippe Rétornaz 			BUG_ON(tty_ldisc_reinit(tty, N_TTY));
70896fd7ce5SGreg Kroah-Hartman 			WARN_ON(tty_ldisc_open(tty, tty->ldisc));
70996fd7ce5SGreg Kroah-Hartman 		}
71096fd7ce5SGreg Kroah-Hartman 	}
711fae76e9aSPeter Hurley 	tty_ldisc_unlock(tty);
71296fd7ce5SGreg Kroah-Hartman 	if (reset)
71396fd7ce5SGreg Kroah-Hartman 		tty_reset_termios(tty);
714fc575ee6SPeter Hurley 
715fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "re-opened ldisc: %p\n", tty->ldisc);
71696fd7ce5SGreg Kroah-Hartman }
71796fd7ce5SGreg Kroah-Hartman 
71896fd7ce5SGreg Kroah-Hartman /**
71996fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_setup			-	open line discipline
72096fd7ce5SGreg Kroah-Hartman  *	@tty: tty being shut down
72196fd7ce5SGreg Kroah-Hartman  *	@o_tty: pair tty for pty/tty pairs
72296fd7ce5SGreg Kroah-Hartman  *
72396fd7ce5SGreg Kroah-Hartman  *	Called during the initial open of a tty/pty pair in order to set up the
72496fd7ce5SGreg Kroah-Hartman  *	line disciplines and bind them to the tty. This has no locking issues
72596fd7ce5SGreg Kroah-Hartman  *	as the device isn't yet active.
72696fd7ce5SGreg Kroah-Hartman  */
72796fd7ce5SGreg Kroah-Hartman 
72896fd7ce5SGreg Kroah-Hartman int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)
72996fd7ce5SGreg Kroah-Hartman {
73096fd7ce5SGreg Kroah-Hartman 	struct tty_ldisc *ld = tty->ldisc;
73196fd7ce5SGreg Kroah-Hartman 	int retval;
73296fd7ce5SGreg Kroah-Hartman 
73396fd7ce5SGreg Kroah-Hartman 	retval = tty_ldisc_open(tty, ld);
73496fd7ce5SGreg Kroah-Hartman 	if (retval)
73596fd7ce5SGreg Kroah-Hartman 		return retval;
73696fd7ce5SGreg Kroah-Hartman 
73796fd7ce5SGreg Kroah-Hartman 	if (o_tty) {
73896fd7ce5SGreg Kroah-Hartman 		retval = tty_ldisc_open(o_tty, o_tty->ldisc);
73996fd7ce5SGreg Kroah-Hartman 		if (retval) {
74096fd7ce5SGreg Kroah-Hartman 			tty_ldisc_close(tty, ld);
74196fd7ce5SGreg Kroah-Hartman 			return retval;
74296fd7ce5SGreg Kroah-Hartman 		}
74396fd7ce5SGreg Kroah-Hartman 	}
74496fd7ce5SGreg Kroah-Hartman 	return 0;
74596fd7ce5SGreg Kroah-Hartman }
74689c8d91eSAlan Cox 
74789c8d91eSAlan Cox static void tty_ldisc_kill(struct tty_struct *tty)
74889c8d91eSAlan Cox {
74989c8d91eSAlan Cox 	/*
75089c8d91eSAlan Cox 	 * Now kill off the ldisc
75189c8d91eSAlan Cox 	 */
75289c8d91eSAlan Cox 	tty_ldisc_close(tty, tty->ldisc);
75389c8d91eSAlan Cox 	tty_ldisc_put(tty->ldisc);
75489c8d91eSAlan Cox 	/* Force an oops if we mess this up */
75589c8d91eSAlan Cox 	tty->ldisc = NULL;
75689c8d91eSAlan Cox 
75789c8d91eSAlan Cox 	/* Ensure the next open requests the N_TTY ldisc */
75889c8d91eSAlan Cox 	tty_set_termios_ldisc(tty, N_TTY);
75989c8d91eSAlan Cox }
76089c8d91eSAlan Cox 
76196fd7ce5SGreg Kroah-Hartman /**
76296fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_release		-	release line discipline
76362462aefSPeter Hurley  *	@tty: tty being shut down (or one end of pty pair)
76496fd7ce5SGreg Kroah-Hartman  *
76562462aefSPeter Hurley  *	Called during the final close of a tty or a pty pair in order to shut
76662462aefSPeter Hurley  *	down the line discpline layer. On exit, each ldisc assigned is N_TTY and
76762462aefSPeter Hurley  *	each ldisc has not been opened.
76896fd7ce5SGreg Kroah-Hartman  */
76996fd7ce5SGreg Kroah-Hartman 
77062462aefSPeter Hurley void tty_ldisc_release(struct tty_struct *tty)
77196fd7ce5SGreg Kroah-Hartman {
77262462aefSPeter Hurley 	struct tty_struct *o_tty = tty->link;
77362462aefSPeter Hurley 
77496fd7ce5SGreg Kroah-Hartman 	/*
775a2965b7bSPeter Hurley 	 * Shutdown this line discipline. As this is the final close,
776a2965b7bSPeter Hurley 	 * it does not race with the set_ldisc code path.
77796fd7ce5SGreg Kroah-Hartman 	 */
77896fd7ce5SGreg Kroah-Hartman 
779fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "closing ldisc: %p\n", tty->ldisc);
780fc575ee6SPeter Hurley 
78136697529SPeter Hurley 	tty_ldisc_lock_pair(tty, o_tty);
78289c8d91eSAlan Cox 	tty_ldisc_kill(tty);
78389c8d91eSAlan Cox 	if (o_tty)
78489c8d91eSAlan Cox 		tty_ldisc_kill(o_tty);
78536697529SPeter Hurley 	tty_ldisc_unlock_pair(tty, o_tty);
78636697529SPeter Hurley 
78796fd7ce5SGreg Kroah-Hartman 	/* And the memory resources remaining (buffers, termios) will be
78896fd7ce5SGreg Kroah-Hartman 	   disposed of when the kref hits zero */
789fc575ee6SPeter Hurley 
790fc575ee6SPeter Hurley 	tty_ldisc_debug(tty, "ldisc closed\n");
79196fd7ce5SGreg Kroah-Hartman }
79296fd7ce5SGreg Kroah-Hartman 
79396fd7ce5SGreg Kroah-Hartman /**
79496fd7ce5SGreg Kroah-Hartman  *	tty_ldisc_init		-	ldisc setup for new tty
79596fd7ce5SGreg Kroah-Hartman  *	@tty: tty being allocated
79696fd7ce5SGreg Kroah-Hartman  *
79796fd7ce5SGreg Kroah-Hartman  *	Set up the line discipline objects for a newly allocated tty. Note that
79896fd7ce5SGreg Kroah-Hartman  *	the tty structure is not completely set up when this call is made.
79996fd7ce5SGreg Kroah-Hartman  */
80096fd7ce5SGreg Kroah-Hartman 
80196fd7ce5SGreg Kroah-Hartman void tty_ldisc_init(struct tty_struct *tty)
80296fd7ce5SGreg Kroah-Hartman {
80336697529SPeter Hurley 	struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY);
80496fd7ce5SGreg Kroah-Hartman 	if (IS_ERR(ld))
80596fd7ce5SGreg Kroah-Hartman 		panic("n_tty: init_tty");
806f4807045SPeter Hurley 	tty->ldisc = ld;
80796fd7ce5SGreg Kroah-Hartman }
80896fd7ce5SGreg Kroah-Hartman 
8096716671dSJiri Slaby /**
8106716671dSJiri Slaby  *	tty_ldisc_init		-	ldisc cleanup for new tty
8116716671dSJiri Slaby  *	@tty: tty that was allocated recently
8126716671dSJiri Slaby  *
8136716671dSJiri Slaby  *	The tty structure must not becompletely set up (tty_ldisc_setup) when
8146716671dSJiri Slaby  *      this call is made.
8156716671dSJiri Slaby  */
8166716671dSJiri Slaby void tty_ldisc_deinit(struct tty_struct *tty)
8176716671dSJiri Slaby {
818ebc9baedSPeter Hurley 	tty_ldisc_put(tty->ldisc);
819f4807045SPeter Hurley 	tty->ldisc = NULL;
8206716671dSJiri Slaby }
8216716671dSJiri Slaby 
82296fd7ce5SGreg Kroah-Hartman void tty_ldisc_begin(void)
82396fd7ce5SGreg Kroah-Hartman {
82496fd7ce5SGreg Kroah-Hartman 	/* Setup the default TTY line discipline. */
82596fd7ce5SGreg Kroah-Hartman 	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
82696fd7ce5SGreg Kroah-Hartman }
827