Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tty: Serialize tty flow control changes with flow_lock

Without serialization, the flow control state can become inverted
wrt. the actual hardware state. For example,

CPU 0 | CPU 1
stop_tty() |
lock ctrl_lock |
tty->stopped = 1 |
unlock ctrl_lock |
| start_tty()
| lock ctrl_lock
| tty->stopped = 0
| unlock ctrl_lock
| driver->start()
driver->stop() |

In this case, the flow control state now indicates the tty has
been started, but the actual hardware state has actually been stopped.

Introduce tty->flow_lock spinlock to serialize tty flow control changes.
Split out unlocked __start_tty()/__stop_tty() flavors for use by
ioctl(TCXONC) in follow-on patch.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Peter Hurley and committed by
Greg Kroah-Hartman
f9e053dc d7a855bd

+36 -12
+28 -11
drivers/tty/tty_io.c
··· 919 919 * but not always. 920 920 * 921 921 * Locking: 922 - * Uses the tty control lock internally 922 + * ctrl_lock 923 + * flow_lock 923 924 */ 924 925 925 - void stop_tty(struct tty_struct *tty) 926 + void __stop_tty(struct tty_struct *tty) 926 927 { 927 928 unsigned long flags; 928 - spin_lock_irqsave(&tty->ctrl_lock, flags); 929 - if (tty->stopped) { 930 - spin_unlock_irqrestore(&tty->ctrl_lock, flags); 929 + 930 + if (tty->stopped) 931 931 return; 932 - } 933 932 tty->stopped = 1; 933 + spin_lock_irqsave(&tty->ctrl_lock, flags); 934 934 if (tty->link && tty->link->packet) { 935 935 tty->ctrl_status &= ~TIOCPKT_START; 936 936 tty->ctrl_status |= TIOCPKT_STOP; ··· 941 941 (tty->ops->stop)(tty); 942 942 } 943 943 944 + void stop_tty(struct tty_struct *tty) 945 + { 946 + unsigned long flags; 947 + 948 + spin_lock_irqsave(&tty->flow_lock, flags); 949 + __stop_tty(tty); 950 + spin_unlock_irqrestore(&tty->flow_lock, flags); 951 + } 944 952 EXPORT_SYMBOL(stop_tty); 945 953 946 954 /** ··· 962 954 * 963 955 * Locking: 964 956 * ctrl_lock 957 + * flow_lock 965 958 */ 966 959 967 - void start_tty(struct tty_struct *tty) 960 + void __start_tty(struct tty_struct *tty) 968 961 { 969 962 unsigned long flags; 970 - spin_lock_irqsave(&tty->ctrl_lock, flags); 971 - if (!tty->stopped || tty->flow_stopped) { 972 - spin_unlock_irqrestore(&tty->ctrl_lock, flags); 963 + 964 + if (!tty->stopped || tty->flow_stopped) 973 965 return; 974 - } 975 966 tty->stopped = 0; 967 + spin_lock_irqsave(&tty->ctrl_lock, flags); 976 968 if (tty->link && tty->link->packet) { 977 969 tty->ctrl_status &= ~TIOCPKT_STOP; 978 970 tty->ctrl_status |= TIOCPKT_START; ··· 985 977 tty_wakeup(tty); 986 978 } 987 979 980 + void start_tty(struct tty_struct *tty) 981 + { 982 + unsigned long flags; 983 + 984 + spin_lock_irqsave(&tty->flow_lock, flags); 985 + __start_tty(tty); 986 + spin_unlock_irqrestore(&tty->flow_lock, flags); 987 + } 988 988 EXPORT_SYMBOL(start_tty); 989 989 990 990 /* We limit tty time update visibility to every 8 seconds or so. */ ··· 3035 3019 INIT_WORK(&tty->hangup_work, do_tty_hangup); 3036 3020 mutex_init(&tty->atomic_write_lock); 3037 3021 spin_lock_init(&tty->ctrl_lock); 3022 + spin_lock_init(&tty->flow_lock); 3038 3023 INIT_LIST_HEAD(&tty->tty_files); 3039 3024 INIT_WORK(&tty->SAK_work, do_SAK_work); 3040 3025
+4 -1
include/linux/tty.h
··· 252 252 struct rw_semaphore termios_rwsem; 253 253 struct mutex winsize_mutex; 254 254 spinlock_t ctrl_lock; 255 + spinlock_t flow_lock; 255 256 /* Termios values are protected by the termios rwsem */ 256 257 struct ktermios termios, termios_locked; 257 258 struct termiox *termiox; /* May be NULL for unsupported */ ··· 262 261 unsigned long flags; 263 262 int count; 264 263 struct winsize winsize; /* winsize_mutex */ 265 - int stopped; 264 + int stopped; /* flow_lock */ 266 265 int flow_stopped; 267 266 int hw_stopped; 268 267 int packet; ··· 401 400 extern char *tty_name(struct tty_struct *tty, char *buf); 402 401 extern void tty_wait_until_sent(struct tty_struct *tty, long timeout); 403 402 extern int tty_check_change(struct tty_struct *tty); 403 + extern void __stop_tty(struct tty_struct *tty); 404 404 extern void stop_tty(struct tty_struct *tty); 405 + extern void __start_tty(struct tty_struct *tty); 405 406 extern void start_tty(struct tty_struct *tty); 406 407 extern int tty_register_driver(struct tty_driver *driver); 407 408 extern int tty_unregister_driver(struct tty_driver *driver);
+4
include/linux/tty_driver.h
··· 152 152 * This routine notifies the tty driver that it should stop 153 153 * outputting characters to the tty device. 154 154 * 155 + * Called with ->flow_lock held. Serialized with start() method. 156 + * 155 157 * Optional: 156 158 * 157 159 * Note: Call stop_tty not this method. ··· 162 160 * 163 161 * This routine notifies the tty driver that it resume sending 164 162 * characters to the tty device. 163 + * 164 + * Called with ->flow_lock held. Serialized with stop() method. 165 165 * 166 166 * Optional: 167 167 *