Commit 64325a3b authored by Ilya Zykov's avatar Ilya Zykov Committed by Greg Kroah-Hartman

tty: Correct tty buffer flush.

  The root of problem is carelessly zeroing pointer(in function __tty_buffer_flush()),
when another thread can use it. It can be cause of "NULL pointer dereference".
  Main idea of the patch, this is never free last (struct tty_buffer) in the active buffer.
Only flush the data for ldisc(buf->head->read = buf->head->commit).
At that moment driver can collect(write) data in buffer without conflict.
It is repeat behavior of flush_to_ldisc(), only without feeding data to ldisc.

Also revert:
  commit c56a00a1
  tty: hold lock across tty buffer finding and buffer filling
In order to delete the unneeded locks any more.
Signed-off-by: default avatarIlya Zykov <ilya@ilyx.ru>
CC: Alan Cox <alan@lxorguk.ukuu.org.uk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f8a2b220
...@@ -119,11 +119,14 @@ static void __tty_buffer_flush(struct tty_port *port) ...@@ -119,11 +119,14 @@ static void __tty_buffer_flush(struct tty_port *port)
struct tty_bufhead *buf = &port->buf; struct tty_bufhead *buf = &port->buf;
struct tty_buffer *thead; struct tty_buffer *thead;
while ((thead = buf->head) != NULL) { if (unlikely(buf->head == NULL))
buf->head = thead->next; return;
tty_buffer_free(port, thead); while ((thead = buf->head->next) != NULL) {
tty_buffer_free(port, buf->head);
buf->head = thead;
} }
buf->tail = NULL; WARN_ON(buf->head != buf->tail);
buf->head->read = buf->head->commit;
} }
/** /**
...@@ -194,19 +197,22 @@ static struct tty_buffer *tty_buffer_find(struct tty_port *port, size_t size) ...@@ -194,19 +197,22 @@ static struct tty_buffer *tty_buffer_find(struct tty_port *port, size_t size)
have queued and recycle that ? */ have queued and recycle that ? */
} }
/** /**
* __tty_buffer_request_room - grow tty buffer if needed * tty_buffer_request_room - grow tty buffer if needed
* @tty: tty structure * @tty: tty structure
* @size: size desired * @size: size desired
* *
* Make at least size bytes of linear space available for the tty * Make at least size bytes of linear space available for the tty
* buffer. If we fail return the size we managed to find. * buffer. If we fail return the size we managed to find.
* Locking: Caller must hold port->buf.lock *
* Locking: Takes port->buf.lock
*/ */
static int __tty_buffer_request_room(struct tty_port *port, size_t size) int tty_buffer_request_room(struct tty_port *port, size_t size)
{ {
struct tty_bufhead *buf = &port->buf; struct tty_bufhead *buf = &port->buf;
struct tty_buffer *b, *n; struct tty_buffer *b, *n;
int left; int left;
unsigned long flags;
spin_lock_irqsave(&buf->lock, flags);
/* OPTIMISATION: We could keep a per tty "zero" sized buffer to /* OPTIMISATION: We could keep a per tty "zero" sized buffer to
remove this conditional if its worth it. This would be invisible remove this conditional if its worth it. This would be invisible
to the callers */ to the callers */
...@@ -228,31 +234,9 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size) ...@@ -228,31 +234,9 @@ static int __tty_buffer_request_room(struct tty_port *port, size_t size)
} else } else
size = left; size = left;
} }
spin_unlock_irqrestore(&buf->lock, flags);
return size; return size;
} }
/**
* tty_buffer_request_room - grow tty buffer if needed
* @port: tty port structure
* @size: size desired
*
* Make at least size bytes of linear space available for the tty
* buffer. If we fail return the size we managed to find.
*
* Locking: Takes port->buf.lock
*/
int tty_buffer_request_room(struct tty_port *port, size_t size)
{
unsigned long flags;
int length;
spin_lock_irqsave(&port->buf.lock, flags);
length = __tty_buffer_request_room(port, size);
spin_unlock_irqrestore(&port->buf.lock, flags);
return length;
}
EXPORT_SYMBOL_GPL(tty_buffer_request_room); EXPORT_SYMBOL_GPL(tty_buffer_request_room);
/** /**
...@@ -271,26 +255,18 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room); ...@@ -271,26 +255,18 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room);
int tty_insert_flip_string_fixed_flag(struct tty_port *port, int tty_insert_flip_string_fixed_flag(struct tty_port *port,
const unsigned char *chars, char flag, size_t size) const unsigned char *chars, char flag, size_t size)
{ {
struct tty_bufhead *buf = &port->buf;
int copied = 0; int copied = 0;
do { do {
int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
int space; int space = tty_buffer_request_room(port, goal);
unsigned long flags; struct tty_buffer *tb = port->buf.tail;
struct tty_buffer *tb;
spin_lock_irqsave(&buf->lock, flags);
space = __tty_buffer_request_room(port, goal);
tb = buf->tail;
/* If there is no space then tb may be NULL */ /* If there is no space then tb may be NULL */
if (unlikely(space == 0)) { if (unlikely(space == 0)) {
spin_unlock_irqrestore(&buf->lock, flags);
break; break;
} }
memcpy(tb->char_buf_ptr + tb->used, chars, space); memcpy(tb->char_buf_ptr + tb->used, chars, space);
memset(tb->flag_buf_ptr + tb->used, flag, space); memset(tb->flag_buf_ptr + tb->used, flag, space);
tb->used += space; tb->used += space;
spin_unlock_irqrestore(&buf->lock, flags);
copied += space; copied += space;
chars += space; chars += space;
/* There is a small chance that we need to split the data over /* There is a small chance that we need to split the data over
...@@ -317,26 +293,18 @@ EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag); ...@@ -317,26 +293,18 @@ EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag);
int tty_insert_flip_string_flags(struct tty_port *port, int tty_insert_flip_string_flags(struct tty_port *port,
const unsigned char *chars, const char *flags, size_t size) const unsigned char *chars, const char *flags, size_t size)
{ {
struct tty_bufhead *buf = &port->buf;
int copied = 0; int copied = 0;
do { do {
int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
int space; int space = tty_buffer_request_room(port, goal);
unsigned long __flags; struct tty_buffer *tb = port->buf.tail;
struct tty_buffer *tb;
spin_lock_irqsave(&buf->lock, __flags);
space = __tty_buffer_request_room(port, goal);
tb = buf->tail;
/* If there is no space then tb may be NULL */ /* If there is no space then tb may be NULL */
if (unlikely(space == 0)) { if (unlikely(space == 0)) {
spin_unlock_irqrestore(&buf->lock, __flags);
break; break;
} }
memcpy(tb->char_buf_ptr + tb->used, chars, space); memcpy(tb->char_buf_ptr + tb->used, chars, space);
memcpy(tb->flag_buf_ptr + tb->used, flags, space); memcpy(tb->flag_buf_ptr + tb->used, flags, space);
tb->used += space; tb->used += space;
spin_unlock_irqrestore(&buf->lock, __flags);
copied += space; copied += space;
chars += space; chars += space;
flags += space; flags += space;
...@@ -392,21 +360,13 @@ EXPORT_SYMBOL(tty_schedule_flip); ...@@ -392,21 +360,13 @@ EXPORT_SYMBOL(tty_schedule_flip);
int tty_prepare_flip_string(struct tty_port *port, unsigned char **chars, int tty_prepare_flip_string(struct tty_port *port, unsigned char **chars,
size_t size) size_t size)
{ {
struct tty_bufhead *buf = &port->buf; int space = tty_buffer_request_room(port, size);
int space;
unsigned long flags;
struct tty_buffer *tb;
spin_lock_irqsave(&buf->lock, flags);
space = __tty_buffer_request_room(port, size);
tb = buf->tail;
if (likely(space)) { if (likely(space)) {
struct tty_buffer *tb = port->buf.tail;
*chars = tb->char_buf_ptr + tb->used; *chars = tb->char_buf_ptr + tb->used;
memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space); memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space);
tb->used += space; tb->used += space;
} }
spin_unlock_irqrestore(&buf->lock, flags);
return space; return space;
} }
EXPORT_SYMBOL_GPL(tty_prepare_flip_string); EXPORT_SYMBOL_GPL(tty_prepare_flip_string);
...@@ -430,21 +390,13 @@ EXPORT_SYMBOL_GPL(tty_prepare_flip_string); ...@@ -430,21 +390,13 @@ EXPORT_SYMBOL_GPL(tty_prepare_flip_string);
int tty_prepare_flip_string_flags(struct tty_port *port, int tty_prepare_flip_string_flags(struct tty_port *port,
unsigned char **chars, char **flags, size_t size) unsigned char **chars, char **flags, size_t size)
{ {
struct tty_bufhead *buf = &port->buf; int space = tty_buffer_request_room(port, size);
int space;
unsigned long __flags;
struct tty_buffer *tb;
spin_lock_irqsave(&buf->lock, __flags);
space = __tty_buffer_request_room(port, size);
tb = buf->tail;
if (likely(space)) { if (likely(space)) {
struct tty_buffer *tb = port->buf.tail;
*chars = tb->char_buf_ptr + tb->used; *chars = tb->char_buf_ptr + tb->used;
*flags = tb->flag_buf_ptr + tb->used; *flags = tb->flag_buf_ptr + tb->used;
tb->used += space; tb->used += space;
} }
spin_unlock_irqrestore(&buf->lock, __flags);
return space; return space;
} }
EXPORT_SYMBOL_GPL(tty_prepare_flip_string_flags); EXPORT_SYMBOL_GPL(tty_prepare_flip_string_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