diff options
author | Peter Hurley <peter@hurleysoftware.com> | 2014-09-10 15:06:31 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-09-24 00:19:35 -0400 |
commit | f9e053dcfc02b0ad29daec8524fb1afe09774976 (patch) | |
tree | 2d930c2d89b011b40c03eeb13ee6f431cb602d3f | |
parent | d7a855bd6ab25d10d5e3b6aeb53d9c57fa17b808 (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.c | 39 | ||||
-rw-r--r-- | include/linux/tty.h | 5 | ||||
-rw-r--r-- | include/linux/tty_driver.h | 4 |
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 | ||
925 | void stop_tty(struct tty_struct *tty) | 926 | void __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 | ||
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 | EXPORT_SYMBOL(stop_tty); | 952 | EXPORT_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 | ||
959 | void start_tty(struct tty_struct *tty) | 968 | void __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 | ||
988 | void 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 | } | ||
980 | EXPORT_SYMBOL(start_tty); | 996 | EXPORT_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, | |||
400 | extern char *tty_name(struct tty_struct *tty, char *buf); | 401 | extern char *tty_name(struct tty_struct *tty, char *buf); |
401 | extern void tty_wait_until_sent(struct tty_struct *tty, long timeout); | 402 | extern void tty_wait_until_sent(struct tty_struct *tty, long timeout); |
402 | extern int tty_check_change(struct tty_struct *tty); | 403 | extern int tty_check_change(struct tty_struct *tty); |
404 | extern void __stop_tty(struct tty_struct *tty); | ||
403 | extern void stop_tty(struct tty_struct *tty); | 405 | extern void stop_tty(struct tty_struct *tty); |
406 | extern void __start_tty(struct tty_struct *tty); | ||
404 | extern void start_tty(struct tty_struct *tty); | 407 | extern void start_tty(struct tty_struct *tty); |
405 | extern int tty_register_driver(struct tty_driver *driver); | 408 | extern int tty_register_driver(struct tty_driver *driver); |
406 | extern int tty_unregister_driver(struct tty_driver *driver); | 409 | extern 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. |