[PATCH 8/8] serial: core: Prevent unsafe uart port access, part 3

From: Peter Hurley
Date: Sat Dec 12 2015 - 19:38:04 EST


For serial core operations without sleeping locks and other waits
(and not already excluded by holding port->mutex), use RCU to
protect dereferencing the state->uart_port.

Introduce helper functions, uart_port_ref() and uart_port_deref(), to
wrap uart_port access in RCU read sections, and helper macros,
uart_port_lock_rcu() and uart_port_unlock_rcu(), to wrap combination
RCU read/uart port lock sections.

For functions only reading the tx circular buffer indexes (where the
uart port lock is claimed to prevent concurrent users), a NULL
uart port is simply ignored and the operation completes.

For functions changing the tx circular buffer indexes (where the
uart port lock is claimed to prevent concurrent users), the operation
is aborted if the uart port is NULL (ie, has been detached).

For functions already holding the port->mutex (and thus guaranteed
safe uart port dereference), introduce helper uart_port_check_rcu(),
which asserts port->mutex is held (if CONFIG_PROVE_RCU).

Signed-off-by: Peter Hurley <peter@xxxxxxxxxxxxxxxxxx>
---
drivers/tty/serial/8250/8250_port.c | 6 +-
drivers/tty/serial/serial_core.c | 312 +++++++++++++++++++++++-------------
2 files changed, 205 insertions(+), 113 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index ea9d1ce..ced0674 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2570,7 +2570,8 @@ static int bytes_to_fcr_rxtrig(struct uart_8250_port *up, unsigned char bytes)
static int do_get_rxtrig(struct tty_port *port)
{
struct uart_state *state = container_of(port, struct uart_state, port);
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = rcu_dereference_protected(state->uart_port,
+ lockdep_is_held(&port->mutex));
struct uart_8250_port *up =
container_of(uport, struct uart_8250_port, port);

@@ -2607,7 +2608,8 @@ static ssize_t serial8250_get_attr_rx_trig_bytes(struct device *dev,
static int do_set_rxtrig(struct tty_port *port, unsigned char bytes)
{
struct uart_state *state = container_of(port, struct uart_state, port);
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = rcu_dereference_protected(state->uart_port,
+ lockdep_is_held(&port->mutex));
struct uart_8250_port *up =
container_of(uport, struct uart_8250_port, port);
int rxtrig;
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 47a3dc9..e98b16f 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -64,6 +64,46 @@ static int uart_dcd_enabled(struct uart_port *uport)
return !!(uport->status & UPSTAT_DCD_ENABLE);
}

+static inline struct uart_port *uart_port_ref(struct uart_state *state)
+{
+ struct uart_port *uport;
+
+ rcu_read_lock();
+ uport = rcu_dereference(state->uart_port);
+ if (!uport)
+ rcu_read_unlock();
+ return uport;
+}
+
+static inline void uart_port_deref(struct uart_port *uport)
+{
+ if (uport)
+ rcu_read_unlock();
+}
+
+static inline struct uart_port *uart_port_check_rcu(struct uart_state *state)
+{
+ return rcu_dereference_check(state->uart_port,
+ lockdep_is_held(&state->port.mutex));
+}
+
+#define uart_port_lock_rcu(state, flags) \
+ ({ \
+ struct uart_port *__uport = uart_port_ref(state); \
+ if (__uport) \
+ spin_lock_irqsave(&__uport->lock, flags); \
+ __uport; \
+ })
+
+#define uart_port_unlock_rcu(uport, flags) \
+ ({ \
+ struct uart_port *__uport = uport; \
+ if (__uport) \
+ spin_unlock_irqrestore(&__uport->lock, flags); \
+ uart_port_deref(__uport); \
+ })
+
+
/*
* This routine is used by the interrupt handler to schedule processing in
* the software interrupt portion of the driver.
@@ -82,32 +122,33 @@ void uart_write_wakeup(struct uart_port *port)
static void uart_stop(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port = state->uart_port;
+ struct uart_port *uport;
unsigned long flags;

- spin_lock_irqsave(&port->lock, flags);
- port->ops->stop_tx(port);
- spin_unlock_irqrestore(&port->lock, flags);
+ uport = uart_port_lock_rcu(state, flags);
+ if (uport)
+ uport->ops->stop_tx(uport);
+ uart_port_unlock_rcu(uport, flags);
}

static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);

- if (!uart_tx_stopped(port))
- port->ops->start_tx(port);
+ if (uport && !uart_tx_stopped(uport))
+ uport->ops->start_tx(uport);
}

static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port = state->uart_port;
+ struct uart_port *uport;
unsigned long flags;

- spin_lock_irqsave(&port->lock, flags);
+ uport = uart_port_lock_rcu(state, flags);
__uart_start(tty);
- spin_unlock_irqrestore(&port->lock, flags);
+ uart_port_unlock_rcu(uport, flags);
}

static inline void
@@ -134,7 +175,7 @@ uart_update_mctrl(struct uart_port *port, unsigned int set, unsigned int clear)
static int uart_port_startup(struct tty_struct *tty, struct uart_state *state,
int init_hw)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);
unsigned long page;
int retval = 0;

@@ -226,7 +267,7 @@ static int uart_startup(struct tty_struct *tty, struct uart_state *state,
*/
static void uart_shutdown(struct tty_struct *tty, struct uart_state *state)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);
struct tty_port *port = &state->port;

/*
@@ -445,7 +486,7 @@ EXPORT_SYMBOL(uart_get_divisor);
static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
struct ktermios *old_termios)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);
struct ktermios *termios;
int hw_stopped;

@@ -490,7 +531,7 @@ static void uart_change_speed(struct tty_struct *tty, struct uart_state *state,
static int uart_put_char(struct tty_struct *tty, unsigned char c)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port = state->uart_port;
+ struct uart_port *uport;
struct circ_buf *circ;
unsigned long flags;
int ret = 0;
@@ -499,13 +540,13 @@ static int uart_put_char(struct tty_struct *tty, unsigned char c)
if (!circ->buf)
return 0;

- spin_lock_irqsave(&port->lock, flags);
- if (uart_circ_chars_free(circ) != 0) {
+ uport = uart_port_lock_rcu(state, flags);
+ if (uport && uart_circ_chars_free(circ) != 0) {
circ->buf[circ->head] = c;
circ->head = (circ->head + 1) & (UART_XMIT_SIZE - 1);
ret = 1;
}
- spin_unlock_irqrestore(&port->lock, flags);
+ uart_port_unlock_rcu(uport, flags);
return ret;
}

@@ -518,7 +559,7 @@ static int uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port;
+ struct uart_port *uport;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
@@ -532,14 +573,12 @@ static int uart_write(struct tty_struct *tty,
return -EL3HLT;
}

- port = state->uart_port;
circ = &state->xmit;
-
if (!circ->buf)
return 0;

- spin_lock_irqsave(&port->lock, flags);
- while (1) {
+ uport = uart_port_lock_rcu(state, flags);
+ while (uport) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
@@ -553,39 +592,40 @@ static int uart_write(struct tty_struct *tty,
}

__uart_start(tty);
- spin_unlock_irqrestore(&port->lock, flags);
-
+ uart_port_unlock_rcu(uport, flags);
return ret;
}

static int uart_write_room(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
+ struct uart_port *uport;
unsigned long flags;
int ret;

- spin_lock_irqsave(&state->uart_port->lock, flags);
+ uport = uart_port_lock_rcu(state, flags);
ret = uart_circ_chars_free(&state->xmit);
- spin_unlock_irqrestore(&state->uart_port->lock, flags);
+ uart_port_unlock_rcu(uport, flags);
return ret;
}

static int uart_chars_in_buffer(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
+ struct uart_port *uport;
unsigned long flags;
int ret;

- spin_lock_irqsave(&state->uart_port->lock, flags);
+ uport = uart_port_lock_rcu(state, flags);
ret = uart_circ_chars_pending(&state->xmit);
- spin_unlock_irqrestore(&state->uart_port->lock, flags);
+ uart_port_unlock_rcu(uport, flags);
return ret;
}

static void uart_flush_buffer(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port;
+ struct uart_port *uport;
unsigned long flags;

/*
@@ -597,14 +637,15 @@ static void uart_flush_buffer(struct tty_struct *tty)
return;
}

- port = state->uart_port;
pr_debug("uart_flush_buffer(%d) called\n", tty->index);

- spin_lock_irqsave(&port->lock, flags);
+ uport = uart_port_lock_rcu(state, flags);
+ if (!uport)
+ return;
uart_circ_clear(&state->xmit);
- if (port->ops->flush_buffer)
- port->ops->flush_buffer(port);
- spin_unlock_irqrestore(&port->lock, flags);
+ if (uport->ops->flush_buffer)
+ uport->ops->flush_buffer(uport);
+ uart_port_unlock_rcu(uport, flags);
tty_wakeup(tty);
}

@@ -622,7 +663,7 @@ static void __uart_send_xchar(struct uart_port *uport, char ch)
spin_lock_irqsave(&uport->lock, flags);
uport->x_char = ch;
if (ch)
- uport->ops->start_tx(port);
+ uport->ops->start_tx(uport);
spin_unlock_irqrestore(&uport->lock, flags);
}
}
@@ -630,57 +671,71 @@ static void __uart_send_xchar(struct uart_port *uport, char ch)
static void uart_send_xchar(struct tty_struct *tty, char ch)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport;

mutex_lock(&state->port.mutex);
- __uart_send_xchar(uport, ch);
+ uport = uart_port_check_rcu(state);
+ if (uport)
+ __uart_send_xchar(uport, ch);
mutex_unlock(&state->port.mutex);
}

static void uart_throttle(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port = state->uart_port;
+ struct uart_port *uport;
upstat_t mask = 0;

+ uport = uart_port_ref(state);
+ if (!uport)
+ return;
+
if (I_IXOFF(tty))
mask |= UPSTAT_AUTOXOFF;
if (tty->termios.c_cflag & CRTSCTS)
mask |= UPSTAT_AUTORTS;

- if (port->status & mask) {
- port->ops->throttle(port);
- mask &= ~port->status;
+ if (uport->status & mask) {
+ uport->ops->throttle(uport);
+ mask &= ~uport->status;
}

if (mask & UPSTAT_AUTOXOFF)
- __uart_send_xchar(port, STOP_CHAR(tty));
+ __uart_send_xchar(uport, STOP_CHAR(tty));

if (mask & UPSTAT_AUTORTS)
- uart_clear_mctrl(port, TIOCM_RTS);
+ uart_clear_mctrl(uport, TIOCM_RTS);
+
+ uart_port_deref(uport);
}

static void uart_unthrottle(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *port = state->uart_port;
+ struct uart_port *uport;
upstat_t mask = 0;

+ uport = uart_port_ref(state);
+ if (!uport)
+ return;
+
if (I_IXOFF(tty))
mask |= UPSTAT_AUTOXOFF;
if (tty->termios.c_cflag & CRTSCTS)
mask |= UPSTAT_AUTORTS;

- if (port->status & mask) {
- port->ops->unthrottle(port);
- mask &= ~port->status;
+ if (uport->status & mask) {
+ uport->ops->unthrottle(uport);
+ mask &= ~uport->status;
}

if (mask & UPSTAT_AUTOXOFF)
- __uart_send_xchar(port, START_CHAR(tty));
+ __uart_send_xchar(uport, START_CHAR(tty));

if (mask & UPSTAT_AUTORTS)
- uart_set_mctrl(port, TIOCM_RTS);
+ uart_set_mctrl(uport, TIOCM_RTS);
+
+ uart_port_deref(uport);
}

static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
@@ -696,7 +751,7 @@ static int uart_get_info(struct tty_port *port, struct serial_struct *retinfo)
* occur as we go
*/
mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
goto out;

@@ -742,7 +797,7 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port,
struct uart_state *state,
struct serial_struct *new_info)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);
unsigned long new_port;
unsigned int change_irq, change_port, closing_wait;
unsigned int old_custom_divisor, close_delay;
@@ -965,7 +1020,7 @@ static int uart_set_info_user(struct tty_struct *tty, struct uart_state *state,
static int uart_get_lsr_info(struct tty_struct *tty,
struct uart_state *state, unsigned int __user *value)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);
unsigned int result;

result = uport->ops->tx_empty(uport);
@@ -992,7 +1047,7 @@ static int uart_tiocmget(struct tty_struct *tty)
int result = -EIO;

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
goto out;

@@ -1016,7 +1071,7 @@ uart_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear)
int ret = -EIO;

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
goto out;

@@ -1037,7 +1092,7 @@ static int uart_break_ctl(struct tty_struct *tty, int break_state)
int ret = -EIO;

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
goto out;

@@ -1066,7 +1121,7 @@ static int uart_do_autoconfig(struct tty_struct *tty,struct uart_state *state)
if (mutex_lock_interruptible(&port->mutex))
return -ERESTARTSYS;

- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport) {
ret = -EIO;
goto out;
@@ -1118,28 +1173,32 @@ static void uart_enable_ms(struct uart_port *uport)
* FIXME: This wants extracting into a common all driver implementation
* of TIOCMWAIT using tty_port.
*/
-static int
-uart_wait_modem_status(struct uart_state *state, unsigned long arg)
+static int uart_wait_modem_status(struct uart_state *state, unsigned long arg)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport;
struct tty_port *port = &state->port;
DECLARE_WAITQUEUE(wait, current);
struct uart_icount cprev, cnow;
- int ret;
+ unsigned long flags;
+ int ret = -EIO;

/*
* note the counters on entry
*/
- spin_lock_irq(&uport->lock);
- memcpy(&cprev, &uport->icount, sizeof(struct uart_icount));
+ uport = uart_port_lock_rcu(state, flags);
+ if (!uport)
+ return -EIO;
+ memcpy(&cprev, &uport->icount, sizeof(cprev));
uart_enable_ms(uport);
- spin_unlock_irq(&uport->lock);
+ uart_port_unlock_rcu(uport, flags);

add_wait_queue(&port->delta_msr_wait, &wait);
for (;;) {
- spin_lock_irq(&uport->lock);
- memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
- spin_unlock_irq(&uport->lock);
+ uport = uart_port_lock_rcu(state, flags);
+ if (!uport)
+ break;
+ memcpy(&cnow, &uport->icount, sizeof(cnow));
+ uart_port_unlock_rcu(uport, flags);

set_current_state(TASK_INTERRUPTIBLE);

@@ -1178,11 +1237,14 @@ static int uart_get_icount(struct tty_struct *tty,
{
struct uart_state *state = tty->driver_data;
struct uart_icount cnow;
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport;
+ unsigned long flags;

- spin_lock_irq(&uport->lock);
+ uport = uart_port_lock_rcu(state, flags);
+ if (!uport)
+ return -EIO;
memcpy(&cnow, &uport->icount, sizeof(struct uart_icount));
- spin_unlock_irq(&uport->lock);
+ uart_port_unlock_rcu(uport, flags);

icount->cts = cnow.cts;
icount->dsr = cnow.dsr;
@@ -1300,7 +1362,7 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
goto out;

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);

if (!uport || test_bit(TTY_IO_ERROR, &tty->flags)) {
ret = -EIO;
@@ -1341,7 +1403,7 @@ static void uart_set_ldisc(struct tty_struct *tty)
struct uart_port *uport;

mutex_lock(&state->port.mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (uport && uport->ops->set_ldisc)
uport->ops->set_ldisc(uport, &tty->termios);
mutex_unlock(&state->port.mutex);
@@ -1357,7 +1419,7 @@ static void uart_set_termios(struct tty_struct *tty,
bool sw_changed = false;

mutex_lock(&state->port.mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
goto out;

@@ -1433,7 +1495,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
return;

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);

/*
* At this point, we stop accepting input. To do this, we
@@ -1450,7 +1512,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
* important if there is a transmit FIFO!
*/
__uart_wait_until_sent(tty, uport->timeout);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
}

uart_shutdown(tty, state);
@@ -1482,7 +1544,7 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct uart_state *state = tty->driver_data;
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);
unsigned long char_time, expire;

if (uport->type == PORT_UNKNOWN || uport->fifosize == 0)
@@ -1529,7 +1591,7 @@ static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
mutex_unlock(&state->port.mutex);
msleep_interruptible(jiffies_to_msecs(char_time));
mutex_lock(&state->port.mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
break;
if (signal_pending(current))
@@ -1542,9 +1604,11 @@ static void __uart_wait_until_sent(struct tty_struct *tty, int timeout)
static void uart_wait_until_sent(struct tty_struct *tty, int timeout)
{
struct uart_state *state = tty->driver_data;
+ struct uart_port *uport;

mutex_lock(&state->port.mutex);
- if (state->uart_port)
+ uport = uart_port_check_rcu(state);
+ if (uport)
__uart_wait_until_sent(tty, timeout);
mutex_unlock(&state->port.mutex);
}
@@ -1564,7 +1628,7 @@ static void uart_hangup(struct tty_struct *tty)
pr_debug("uart_hangup(%d)\n", tty->index);

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
WARN(!uport, "hangup of detached port!\n");

if (port->flags & ASYNC_NORMAL_ACTIVE) {
@@ -1587,7 +1651,7 @@ static void uart_hangup(struct tty_struct *tty)
static void uart_port_shutdown(struct tty_port *port)
{
struct uart_state *state = container_of(port, struct uart_state, port);
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);

/*
* clear delta_msr_wait queue to avoid mem leaks: we may free
@@ -1614,26 +1678,40 @@ static void uart_port_shutdown(struct tty_port *port)
static int uart_carrier_raised(struct tty_port *port)
{
struct uart_state *state = container_of(port, struct uart_state, port);
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport;
+ unsigned long flags;
int mctrl;
- spin_lock_irq(&uport->lock);
+
+ uport = uart_port_lock_rcu(state, flags);
+ /*
+ * Should never observe uport == NULL since checks for hangup should
+ * abort the tty_port_block_til_ready() loop before checking for carrier
+ * raised -- but report carrier raised if it does to cause open to
+ * continue and not sleep
+ */
+ if (WARN_ON(!uport))
+ return 1;
uart_enable_ms(uport);
mctrl = uport->ops->get_mctrl(uport);
- spin_unlock_irq(&uport->lock);
- if (mctrl & TIOCM_CAR)
- return 1;
- return 0;
+ uart_port_unlock_rcu(uport, flags);
+ return !!(mctrl & TIOCM_CAR);
}

static void uart_dtr_rts(struct tty_port *port, int onoff)
{
struct uart_state *state = container_of(port, struct uart_state, port);
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport;
+
+ uport = uart_port_ref(state);
+ if (!uport)
+ return;

if (onoff)
uart_set_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
else
uart_clear_mctrl(uport, TIOCM_DTR | TIOCM_RTS);
+
+ uart_port_deref(uport);
}

/*
@@ -1652,6 +1730,7 @@ static int uart_open(struct tty_struct *tty, struct file *filp)
int retval, line = tty->index;
struct uart_state *state = drv->state + line;
struct tty_port *port = &state->port;
+ struct uart_port *uport;

pr_debug("uart_open(%d) called\n", line);

@@ -1671,15 +1750,15 @@ static int uart_open(struct tty_struct *tty, struct file *filp)
goto end;
}

- if (!state->uart_port || state->uart_port->flags & UPF_DEAD) {
+ uport = uart_port_check_rcu(state);
+ if (!uport || (uport->flags & UPF_DEAD)) {
retval = -ENXIO;
goto err_unlock;
}

tty->driver_data = state;
- state->uart_port->state = state;
- state->port.low_latency =
- (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
+ uport->state = state;
+ port->low_latency = !!(uport->flags & UPF_LOW_LATENCY);
tty_port_tty_set(port, tty);

/*
@@ -1727,7 +1806,7 @@ static void uart_line_info(struct seq_file *m, struct uart_driver *drv, int i)
int mmio;

mutex_lock(&port->mutex);
- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!uport)
goto out;

@@ -2060,7 +2139,7 @@ EXPORT_SYMBOL_GPL(uart_set_options);
static void uart_change_pm(struct uart_state *state,
enum uart_pm_state pm_state)
{
- struct uart_port *uport = state->uart_port;
+ struct uart_port *uport = uart_port_check_rcu(state);

if (state->pm_state != pm_state) {
if (uport && uport->ops->pm)
@@ -2347,7 +2426,7 @@ static int uart_poll_init(struct tty_driver *driver, int line, char *options)
port = &state->port;
mutex_lock(&port->mutex);

- uport = state->uart_port;
+ uport = uart_port_check_rcu(state);
if (!(uport->ops->poll_get_char && uport->ops->poll_put_char))
goto out;

@@ -2374,29 +2453,35 @@ static int uart_poll_get_char(struct tty_driver *driver, int line)
{
struct uart_driver *drv = driver->driver_state;
struct uart_state *state = drv->state + line;
- struct uart_port *port;
+ struct uart_port *uport;
+ int ret = -1;

- if (!state || !state->uart_port)
+ if (!state)
return -1;

- port = state->uart_port;
- return port->ops->poll_get_char(port);
+ uport = uart_port_ref(state);
+ if (uport)
+ ret = uport->ops->poll_get_char(uport);
+ uart_port_deref(uport);
+ return ret;
}

static void uart_poll_put_char(struct tty_driver *driver, int line, char ch)
{
struct uart_driver *drv = driver->driver_state;
struct uart_state *state = drv->state + line;
- struct uart_port *port;
+ struct uart_port *uport;

- if (!state || !state->uart_port)
+ if (!state)
return;

- port = state->uart_port;
-
+ uport = uart_port_ref(state);
+ if (!uport)
+ return;
if (ch == '\n')
- port->ops->poll_put_char(port, '\r');
- port->ops->poll_put_char(port, ch);
+ uport->ops->poll_put_char(uport, '\r');
+ uport->ops->poll_put_char(uport, ch);
+ uart_port_deref(uport);
}
#endif

@@ -2737,13 +2822,13 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)

mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
- if (state->uart_port) {
+ if (uart_port_check_rcu(state)) {
ret = -EINVAL;
goto out;
}

/* Link the port to the driver state table and vice versa */
- state->uart_port = uport;
+ rcu_assign_pointer(state->uart_port, uport);
uport->state = state;

state->pm_state = UART_PM_STATE_UNDEFINED;
@@ -2815,15 +2900,12 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state = drv->state + uport->line;
struct tty_port *port = &state->port;
+ struct uart_port *uart_port;
struct tty_struct *tty;
int ret = 0;

BUG_ON(in_interrupt());

- if (state->uart_port != uport)
- dev_alert(uport->dev, "Removing wrong port: %p != %p\n",
- state->uart_port, uport);
-
mutex_lock(&port_mutex);

/*
@@ -2831,7 +2913,12 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
* succeeding while we shut down the port.
*/
mutex_lock(&port->mutex);
- if (!state->uart_port) {
+ uart_port = uart_port_check_rcu(state);
+ if (uart_port != uport)
+ dev_alert(uport->dev, "Removing wrong port: %p != %p\n",
+ uart_port, uport);
+
+ if (!uart_port) {
mutex_unlock(&port->mutex);
ret = -EINVAL;
goto out;
@@ -2869,8 +2956,11 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
uport->type = PORT_UNKNOWN;

mutex_lock(&port->mutex);
- state->uart_port = NULL;
+ rcu_assign_pointer(state->uart_port, NULL);
mutex_unlock(&port->mutex);
+
+ /* wait for existing uart port RCU read sections to complete */
+ synchronize_rcu();
out:
mutex_unlock(&port_mutex);

--
2.6.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/