summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Hurley <peter@hurleysoftware.com>2014-09-10 15:06:31 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-09-24 00:19:35 -0400
commitf9e053dcfc02b0ad29daec8524fb1afe09774976 (patch)
tree2d930c2d89b011b40c03eeb13ee6f431cb602d3f
parentd7a855bd6ab25d10d5e3b6aeb53d9c57fa17b808 (diff)
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>
-rw-r--r--drivers/tty/tty_io.c39
-rw-r--r--include/linux/tty.h5
-rw-r--r--include/linux/tty_driver.h4
3 files changed, 36 insertions, 12 deletions
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index d4eb2a8b7047..b8ddfef6b5d8 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -919,18 +919,18 @@ void no_tty(void)
919 * but not always. 919 * but not always.
920 * 920 *
921 * Locking: 921 * Locking:
922 * Uses the tty control lock internally 922 * ctrl_lock
923 * flow_lock
923 */ 924 */
924 925
925void stop_tty(struct tty_struct *tty) 926void __stop_tty(struct tty_struct *tty)
926{ 927{
927 unsigned long flags; 928 unsigned long flags;
928 spin_lock_irqsave(&tty->ctrl_lock, flags); 929
929 if (tty->stopped) { 930 if (tty->stopped)
930 spin_unlock_irqrestore(&tty->ctrl_lock, flags);
931 return; 931 return;
932 }
933 tty->stopped = 1; 932 tty->stopped = 1;
933 spin_lock_irqsave(&tty->ctrl_lock, flags);
934 if (tty->link && tty->link->packet) { 934 if (tty->link && tty->link->packet) {
935 tty->ctrl_status &= ~TIOCPKT_START; 935 tty->ctrl_status &= ~TIOCPKT_START;
936 tty->ctrl_status |= TIOCPKT_STOP; 936 tty->ctrl_status |= TIOCPKT_STOP;
@@ -941,6 +941,14 @@ void stop_tty(struct tty_struct *tty)
941 (tty->ops->stop)(tty); 941 (tty->ops->stop)(tty);
942} 942}
943 943
944void 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}
944EXPORT_SYMBOL(stop_tty); 952EXPORT_SYMBOL(stop_tty);
945 953
946/** 954/**
@@ -954,17 +962,17 @@ EXPORT_SYMBOL(stop_tty);
954 * 962 *
955 * Locking: 963 * Locking:
956 * ctrl_lock 964 * ctrl_lock
965 * flow_lock
957 */ 966 */
958 967
959void start_tty(struct tty_struct *tty) 968void __start_tty(struct tty_struct *tty)
960{ 969{
961 unsigned long flags; 970 unsigned long flags;
962 spin_lock_irqsave(&tty->ctrl_lock, flags); 971
963 if (!tty->stopped || tty->flow_stopped) { 972 if (!tty->stopped || tty->flow_stopped)
964 spin_unlock_irqrestore(&tty->ctrl_lock, flags);
965 return; 973 return;
966 }
967 tty->stopped = 0; 974 tty->stopped = 0;
975 spin_lock_irqsave(&tty->ctrl_lock, flags);
968 if (tty->link && tty->link->packet) { 976 if (tty->link && tty->link->packet) {
969 tty->ctrl_status &= ~TIOCPKT_STOP; 977 tty->ctrl_status &= ~TIOCPKT_STOP;
970 tty->ctrl_status |= TIOCPKT_START; 978 tty->ctrl_status |= TIOCPKT_START;
@@ -977,6 +985,14 @@ void start_tty(struct tty_struct *tty)
977 tty_wakeup(tty); 985 tty_wakeup(tty);
978} 986}
979 987
988void start_tty(struct tty_struct *tty)
989{
990 unsigned long flags;
991
992 spin_lock_irqsave(&tty->flow_lock, flags);
993 __start_tty(tty);
994 spin_unlock_irqrestore(&tty->flow_lock, flags);
995}
980EXPORT_SYMBOL(start_tty); 996EXPORT_SYMBOL(start_tty);
981 997
982/* We limit tty time update visibility to every 8 seconds or so. */ 998/* We limit tty time update visibility to every 8 seconds or so. */
@@ -3019,6 +3035,7 @@ struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
3019 INIT_WORK(&tty->hangup_work, do_tty_hangup); 3035 INIT_WORK(&tty->hangup_work, do_tty_hangup);
3020 mutex_init(&tty->atomic_write_lock); 3036 mutex_init(&tty->atomic_write_lock);
3021 spin_lock_init(&tty->ctrl_lock); 3037 spin_lock_init(&tty->ctrl_lock);
3038 spin_lock_init(&tty->flow_lock);
3022 INIT_LIST_HEAD(&tty->tty_files); 3039 INIT_LIST_HEAD(&tty->tty_files);
3023 INIT_WORK(&tty->SAK_work, do_SAK_work); 3040 INIT_WORK(&tty->SAK_work, do_SAK_work);
3024 3041
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 4cfd4a82fc31..fd4148d3a261 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -252,6 +252,7 @@ struct tty_struct {
252 struct rw_semaphore termios_rwsem; 252 struct rw_semaphore termios_rwsem;
253 struct mutex winsize_mutex; 253 struct mutex winsize_mutex;
254 spinlock_t ctrl_lock; 254 spinlock_t ctrl_lock;
255 spinlock_t flow_lock;
255 /* Termios values are protected by the termios rwsem */ 256 /* Termios values are protected by the termios rwsem */
256 struct ktermios termios, termios_locked; 257 struct ktermios termios, termios_locked;
257 struct termiox *termiox; /* May be NULL for unsupported */ 258 struct termiox *termiox; /* May be NULL for unsupported */
@@ -261,7 +262,7 @@ struct tty_struct {
261 unsigned long flags; 262 unsigned long flags;
262 int count; 263 int count;
263 struct winsize winsize; /* winsize_mutex */ 264 struct winsize winsize; /* winsize_mutex */
264 int stopped; 265 int stopped; /* flow_lock */
265 int flow_stopped; 266 int flow_stopped;
266 int hw_stopped; 267 int hw_stopped;
267 int packet; 268 int packet;
@@ -400,7 +401,9 @@ extern int tty_paranoia_check(struct tty_struct *tty, struct inode *inode,
400extern char *tty_name(struct tty_struct *tty, char *buf); 401extern char *tty_name(struct tty_struct *tty, char *buf);
401extern void tty_wait_until_sent(struct tty_struct *tty, long timeout); 402extern void tty_wait_until_sent(struct tty_struct *tty, long timeout);
402extern int tty_check_change(struct tty_struct *tty); 403extern int tty_check_change(struct tty_struct *tty);
404extern void __stop_tty(struct tty_struct *tty);
403extern void stop_tty(struct tty_struct *tty); 405extern void stop_tty(struct tty_struct *tty);
406extern void __start_tty(struct tty_struct *tty);
404extern void start_tty(struct tty_struct *tty); 407extern void start_tty(struct tty_struct *tty);
405extern int tty_register_driver(struct tty_driver *driver); 408extern int tty_register_driver(struct tty_driver *driver);
406extern int tty_unregister_driver(struct tty_driver *driver); 409extern int tty_unregister_driver(struct tty_driver *driver);
diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h
index e48c608a8fa8..92e337c18839 100644
--- a/include/linux/tty_driver.h
+++ b/include/linux/tty_driver.h
@@ -152,6 +152,8 @@
152 * This routine notifies the tty driver that it should stop 152 * This routine notifies the tty driver that it should stop
153 * outputting characters to the tty device. 153 * outputting characters to the tty device.
154 * 154 *
155 * Called with ->flow_lock held. Serialized with start() method.
156 *
155 * Optional: 157 * Optional:
156 * 158 *
157 * Note: Call stop_tty not this method. 159 * Note: Call stop_tty not this method.
@@ -161,6 +163,8 @@
161 * This routine notifies the tty driver that it resume sending 163 * This routine notifies the tty driver that it resume sending
162 * characters to the tty device. 164 * characters to the tty device.
163 * 165 *
166 * Called with ->flow_lock held. Serialized with stop() method.
167 *
164 * Optional: 168 * Optional:
165 * 169 *
166 * Note: Call start_tty not this method. 170 * Note: Call start_tty not this method.