Commit cf54a5af authored by Alan Cox's avatar Alan Cox Committed by Linus Torvalds

[PATCH] Update termios to use per tty semaphore

This makes the agreed change of termios locking to be semaphore based
sleep locking. This is needed for USB in particular as it has to use
messaging to issue terminal mode changes.

This code passes Torvalds test grades 0, 1 and 2 (it looks ok, it
compiles and it booted). It does mean that a driver cannot take an
atomic peek at termios data during an interrupt. Nobody seems to be
doing this although some of the driver receive paths for line
disciplines will eventually want to (n_tty currently doesn't do this
locked on the receive path). Since the ldisc is given a chance to copy
any essential bits on the ->set_termios path this seems not to be a
problem.
parent d5978a21
...@@ -60,8 +60,8 @@ chars_in_buffer() - Report the number of bytes in the buffer. ...@@ -60,8 +60,8 @@ chars_in_buffer() - Report the number of bytes in the buffer.
set_termios() - Called on termios structure changes. The caller set_termios() - Called on termios structure changes. The caller
passes the old termios data and the current data passes the old termios data and the current data
is in the tty. Called under the termios lock so is in the tty. Called under the termios semaphore so
may not sleep. Serialized against itself only. allowed to sleep. Serialized against itself only.
read() - Move data from the line discipline to the user. read() - Move data from the line discipline to the user.
Multiple read calls may occur in parallel and the Multiple read calls may occur in parallel and the
...@@ -158,8 +158,8 @@ write_room() - Return the number of characters tht can be stuffed ...@@ -158,8 +158,8 @@ write_room() - Return the number of characters tht can be stuffed
ioctl() - Called when an ioctl may be for the driver ioctl() - Called when an ioctl may be for the driver
set_termios() - Called on termios change, may get parallel calls, set_termios() - Called on termios change, serialized against
may block for now (may change that) itself by a semaphore. May sleep.
set_ldisc() - Notifier for discipline change. At the point this set_ldisc() - Notifier for discipline change. At the point this
is done the discipline is not yet usable. Can now is done the discipline is not yet usable. Can now
......
...@@ -130,8 +130,6 @@ LIST_HEAD(tty_drivers); /* linked list of tty drivers */ ...@@ -130,8 +130,6 @@ LIST_HEAD(tty_drivers); /* linked list of tty drivers */
/* Semaphore to protect creating and releasing a tty. This is shared with /* Semaphore to protect creating and releasing a tty. This is shared with
vt.c for deeply disgusting hack reasons */ vt.c for deeply disgusting hack reasons */
DECLARE_MUTEX(tty_sem); DECLARE_MUTEX(tty_sem);
/* Lock for tty_termios changes - private to tty_io/tty_ioctl */
spinlock_t tty_termios_lock = SPIN_LOCK_UNLOCKED;
#ifdef CONFIG_UNIX98_PTYS #ifdef CONFIG_UNIX98_PTYS
extern struct tty_driver *ptm_driver; /* Unix98 pty masters; for /dev/ptmx */ extern struct tty_driver *ptm_driver; /* Unix98 pty masters; for /dev/ptmx */
...@@ -239,10 +237,9 @@ static int check_tty_count(struct tty_struct *tty, const char *routine) ...@@ -239,10 +237,9 @@ static int check_tty_count(struct tty_struct *tty, const char *routine)
static void tty_set_termios_ldisc(struct tty_struct *tty, int num) static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
{ {
unsigned long flags; down(&tty->termios_sem);
spin_lock_irqsave(&tty_termios_lock, flags);
tty->termios->c_line = num; tty->termios->c_line = num;
spin_unlock_irqrestore(&tty_termios_lock, flags); up(&tty->termios_sem);
} }
/* /*
...@@ -811,10 +808,9 @@ void do_tty_hangup(void *data) ...@@ -811,10 +808,9 @@ void do_tty_hangup(void *data)
*/ */
if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
{ {
unsigned long flags; down(&tty->termios_sem);
spin_lock_irqsave(&tty_termios_lock, flags);
*tty->termios = tty->driver->init_termios; *tty->termios = tty->driver->init_termios;
spin_unlock_irqrestore(&tty_termios_lock, flags); up(&tty->termios_sem);
} }
/* Defer ldisc switch */ /* Defer ldisc switch */
...@@ -2606,6 +2602,7 @@ static void initialize_tty_struct(struct tty_struct *tty) ...@@ -2606,6 +2602,7 @@ static void initialize_tty_struct(struct tty_struct *tty)
tty->flip.flag_buf_ptr = tty->flip.flag_buf; tty->flip.flag_buf_ptr = tty->flip.flag_buf;
INIT_WORK(&tty->flip.work, flush_to_ldisc, tty); INIT_WORK(&tty->flip.work, flush_to_ldisc, tty);
init_MUTEX(&tty->flip.pty_sem); init_MUTEX(&tty->flip.pty_sem);
init_MUTEX(&tty->termios_sem);
init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait); init_waitqueue_head(&tty->read_wait);
INIT_WORK(&tty->hangup_work, do_tty_hangup, tty); INIT_WORK(&tty->hangup_work, do_tty_hangup, tty);
......
...@@ -29,8 +29,6 @@ ...@@ -29,8 +29,6 @@
#undef DEBUG #undef DEBUG
extern spinlock_t tty_termios_lock;
/* /*
* Internal flag options for termios setting behavior * Internal flag options for termios setting behavior
*/ */
...@@ -101,7 +99,6 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios ...@@ -101,7 +99,6 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios
int canon_change; int canon_change;
struct termios old_termios = *tty->termios; struct termios old_termios = *tty->termios;
struct tty_ldisc *ld; struct tty_ldisc *ld;
unsigned long flags;
/* /*
* Perform the actual termios internal changes under lock. * Perform the actual termios internal changes under lock.
...@@ -110,7 +107,7 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios ...@@ -110,7 +107,7 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios
/* FIXME: we need to decide on some locking/ordering semantics /* FIXME: we need to decide on some locking/ordering semantics
for the set_termios notification eventually */ for the set_termios notification eventually */
spin_lock_irqsave(&tty_termios_lock, flags); down(&tty->termios_sem);
*tty->termios = *new_termios; *tty->termios = *new_termios;
unset_locked_termios(tty->termios, &old_termios, tty->termios_locked); unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
...@@ -145,13 +142,6 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios ...@@ -145,13 +142,6 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios
wake_up_interruptible(&tty->link->read_wait); wake_up_interruptible(&tty->link->read_wait);
} }
} }
/*
* Fixme! We should really try to protect the driver and ldisc
* termios usage too. But they need to be able to sleep, so
* the global termios spinlock is not the right thing.
*/
spin_unlock_irqrestore(&tty_termios_lock, flags);
if (tty->driver->set_termios) if (tty->driver->set_termios)
(*tty->driver->set_termios)(tty, &old_termios); (*tty->driver->set_termios)(tty, &old_termios);
...@@ -162,6 +152,7 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios ...@@ -162,6 +152,7 @@ static void change_termios(struct tty_struct * tty, struct termios * new_termios
(ld->set_termios)(tty, &old_termios); (ld->set_termios)(tty, &old_termios);
tty_ldisc_deref(ld); tty_ldisc_deref(ld);
} }
up(&tty->termios_sem);
} }
static int set_termios(struct tty_struct * tty, void __user *arg, int opt) static int set_termios(struct tty_struct * tty, void __user *arg, int opt)
...@@ -255,15 +246,14 @@ static int get_sgflags(struct tty_struct * tty) ...@@ -255,15 +246,14 @@ static int get_sgflags(struct tty_struct * tty)
static int get_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb) static int get_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
{ {
struct sgttyb tmp; struct sgttyb tmp;
unsigned long flags;
spin_lock_irqsave(&tty_termios_lock, flags); down(&tty->termios_sem);
tmp.sg_ispeed = 0; tmp.sg_ispeed = 0;
tmp.sg_ospeed = 0; tmp.sg_ospeed = 0;
tmp.sg_erase = tty->termios->c_cc[VERASE]; tmp.sg_erase = tty->termios->c_cc[VERASE];
tmp.sg_kill = tty->termios->c_cc[VKILL]; tmp.sg_kill = tty->termios->c_cc[VKILL];
tmp.sg_flags = get_sgflags(tty); tmp.sg_flags = get_sgflags(tty);
spin_unlock_irqrestore(&tty_termios_lock, flags); up(&tty->termios_sem);
return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0; return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0;
} }
...@@ -299,7 +289,6 @@ static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb) ...@@ -299,7 +289,6 @@ static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
int retval; int retval;
struct sgttyb tmp; struct sgttyb tmp;
struct termios termios; struct termios termios;
unsigned long flags;
retval = tty_check_change(tty); retval = tty_check_change(tty);
if (retval) if (retval)
...@@ -307,13 +296,13 @@ static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb) ...@@ -307,13 +296,13 @@ static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
if (copy_from_user(&tmp, sgttyb, sizeof(tmp))) if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
return -EFAULT; return -EFAULT;
spin_lock_irqsave(&tty_termios_lock, flags); down(&tty->termios_sem);
termios = *tty->termios; termios = *tty->termios;
termios.c_cc[VERASE] = tmp.sg_erase; termios.c_cc[VERASE] = tmp.sg_erase;
termios.c_cc[VKILL] = tmp.sg_kill; termios.c_cc[VKILL] = tmp.sg_kill;
set_sgflags(&termios, tmp.sg_flags); set_sgflags(&termios, tmp.sg_flags);
spin_unlock_irqrestore(&tty_termios_lock, flags); up(&tty->termios_sem);
change_termios(tty, &termios); change_termios(tty, &termios);
return 0; return 0;
} }
...@@ -549,11 +538,11 @@ int n_tty_ioctl(struct tty_struct * tty, struct file * file, ...@@ -549,11 +538,11 @@ int n_tty_ioctl(struct tty_struct * tty, struct file * file,
case TIOCSSOFTCAR: case TIOCSSOFTCAR:
if (get_user(arg, (unsigned int __user *) arg)) if (get_user(arg, (unsigned int __user *) arg))
return -EFAULT; return -EFAULT;
spin_lock_irqsave(&tty_termios_lock, flags); down(&tty->termios_sem);
tty->termios->c_cflag = tty->termios->c_cflag =
((tty->termios->c_cflag & ~CLOCAL) | ((tty->termios->c_cflag & ~CLOCAL) |
(arg ? CLOCAL : 0)); (arg ? CLOCAL : 0));
spin_unlock_irqrestore(&tty_termios_lock, flags); up(&tty->termios_sem);
return 0; return 0;
default: default:
return -ENOIOCTLCMD; return -ENOIOCTLCMD;
......
...@@ -244,6 +244,7 @@ struct tty_struct { ...@@ -244,6 +244,7 @@ struct tty_struct {
struct tty_driver *driver; struct tty_driver *driver;
int index; int index;
struct tty_ldisc ldisc; struct tty_ldisc ldisc;
struct semaphore termios_sem;
struct termios *termios, *termios_locked; struct termios *termios, *termios_locked;
char name[64]; char name[64];
int pgrp; int pgrp;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment