@@ -201,8 +201,8 @@ static void n_tty_kick_worker(struct tty_struct *tty)
struct n_tty_data *ldata = tty->disc_data;
/* Did the input worker stop? Restart it */
- if (unlikely(ldata->no_room)) {
- ldata->no_room = 0;
+ if (unlikely(READ_ONCE(ldata->no_room))) {
+ WRITE_ONCE(ldata->no_room, 0);
WARN_RATELIMIT(tty->port->itty == NULL,
"scheduling with invalid itty\n");
@@ -1632,7 +1632,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
if (overflow && room < 0)
ldata->read_head--;
room = overflow;
- ldata->no_room = flow && !room;
+ WRITE_ONCE(ldata->no_room, flow && !room);
} else
overflow = 0;
@@ -1663,6 +1663,24 @@ n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
} else
n_tty_check_throttle(tty);
+ if (unlikely(ldata->no_room)) {
+ /*
+ * Barrier here is to ensure to read the latest read_tail in
+ * chars_in_buffer() and to make sure that read_tail is not loaded
+ * before ldata->no_room is set, otherwise, following race may occur:
+ * n_tty_receive_buf_common() |n_tty_read()
+ * chars_in_buffer() > 0 |
+ * |copy_from_read_buf()->chars_in_buffer()==0
+ * |if (ldata->no_room)
+ * ldata->no_room = 1 |
+ * Then both kworker and reader will fail to kick n_tty_kick_worker(),
+ * smp_mb is paired with smp_mb() in n_tty_read().
+ */
+ smp_mb();
+ if (!chars_in_buffer(tty))
+ n_tty_kick_worker(tty);
+ }
+
up_read(&tty->termios_rwsem);
return rcvd;
@@ -2180,8 +2198,23 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
if (time)
timeout = time;
}
- if (tail != ldata->read_tail)
+ if (tail != ldata->read_tail) {
+ /*
+ * Make sure no_room is not read before setting read_tail,
+ * otherwise, following race may occur:
+ * n_tty_read() |n_tty_receive_buf_common()
+ * if(ldata->no_room)->false |
+ * |ldata->no_room = 1
+ * |char_in_buffer() > 0
+ * ldata->read_tail = ldata->commit_head|
+ * Then copy_from_read_buf() in reader consumes all the data
+ * in read buffer, both reader and kworker will fail to kick
+ * tty_buffer_restart_work().
+ * smp_mb is paired with smp_mb() in n_tty_receive_buf_common().
+ */
+ smp_mb();
n_tty_kick_worker(tty);
+ }
up_read(&tty->termios_rwsem);
remove_wait_queue(&tty->read_wait, &wait);