Commit c8e33141 authored by Linus Torvalds's avatar Linus Torvalds

tty: Make flush_to_ldisc() locking more robust

The locking logic in this function is extremely subtle, and it broke
when we started doing potentially concurrent 'flush_to_ldisc()' calls in
commit e043e42b ("pty: avoid forcing
'low_latency' tty flag").

The code in flush_to_ldisc() used to set 'tty->buf.head' to NULL, with
the intention that this would then cause any other concurrent calls to
not do anything (locking note: we have to drop the buf.lock over the
call to ->receive_buf that can block, which is why we can have
concurrency here at all in the first place).

It also used to set the TTY_FLUSHING bit, which would then cause any
concurrent 'tty_buffer_flush()' to not free all the tty buffers and
clear 'tty->buf.tail'.  And with 'buf.head' being NULL, and 'buf.tail'
being non-NULL, new data would never touch 'buf.head'.

Does that sound a bit too subtle? It was.  If another concurrent call to
'flush_to_ldisc()' were to come in, the NULL buf.head would indeed cause
it to not process the buffer list, but it would still clear TTY_FLUSHING
afterwards, making the buffer protection against 'tty_buffer_flush()' no
longer work.

So this clears it all up.  We depend purely on TTY_FLUSHING for handling
re-entrancy, and stop playing games with the buffer list entirely.  In
fact, the buffer list handling is now robust enough that we could
probably stop doing the whole "protect against 'tty_buffer_flush()'"
thing entirely.

However, Alan also points out that we would probably be better off
simplifying the locking even further, and just take the tty ldisc_mutex
around all the buffer flushing calls.  That seems like a good idea, but
in the meantime this is a conceptually minimal fix (with the patch
itself being bigger than required just to clean the code up and make it

This fixes keyboard trouble under X: default avatarFrédéric Meunier <>
Reported-and-tested-by: default avatarBoyan <>
Cc: Alan Cox <>
Cc: Paul Fulghum <>
Signed-off-by: default avatarLinus Torvalds <>
parent 80f50691
......@@ -402,28 +402,26 @@ static void flush_to_ldisc(struct work_struct *work)
container_of(work, struct tty_struct,;
unsigned long flags;
struct tty_ldisc *disc;
struct tty_buffer *tbuf, *head;
char *char_buf;
unsigned char *flag_buf;
disc = tty_ldisc_ref(tty);
if (disc == NULL) /* !TTY_LDISC */
spin_lock_irqsave(&tty->buf.lock, flags);
/* So we know a flush is running */
set_bit(TTY_FLUSHING, &tty->flags);
head = tty->buf.head;
if (head != NULL) {
tty->buf.head = NULL;
for (;;) {
int count = head->commit - head->read;
if (!test_and_set_bit(TTY_FLUSHING, &tty->flags)) {
struct tty_buffer *head;
while ((head = tty->buf.head) != NULL) {
int count;
char *char_buf;
unsigned char *flag_buf;
count = head->commit - head->read;
if (!count) {
if (head->next == NULL)
tbuf = head;
head = head->next;
tty_buffer_free(tty, tbuf);
tty->buf.head = head->next;
tty_buffer_free(tty, head);
/* Ldisc or user is trying to flush the buffers
......@@ -445,9 +443,9 @@ static void flush_to_ldisc(struct work_struct *work)
flag_buf, count);
spin_lock_irqsave(&tty->buf.lock, flags);
/* Restore the queue head */
tty->buf.head = head;
clear_bit(TTY_FLUSHING, &tty->flags);
/* We may have a deferred request to flush the input buffer,
if so pull the chain under the lock and empty the queue */
if (test_bit(TTY_FLUSHPENDING, &tty->flags)) {
......@@ -455,7 +453,6 @@ static void flush_to_ldisc(struct work_struct *work)
clear_bit(TTY_FLUSHPENDING, &tty->flags);
clear_bit(TTY_FLUSHING, &tty->flags);
spin_unlock_irqrestore(&tty->buf.lock, flags);
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