uart 驱动之用户发送与中断发送的互斥
原创
©著作权归作者所有:来自51CTO博客作者proware的原创作品,请联系作者获取转载授权,否则将追究法律责任
linux 内核中 发送环在判空等操作时,如何确保 原子性。用户在调用写接口写数据时,head的值会变动,而此时在中断里面判空,如何保证准确性。
1) 例如 驱动代码,中断发送的代码 serial-tegra.c drivers\tty\serial
static void tegra_uart_fill_tx_fifo(struct tegra_uart_port *tup, int max_bytes)
{
struct circ_buf *xmit = &tup->uport.state->xmit;
int i;
for (i = 0; i < max_bytes; i++) {
BUG_ON(uart_circ_empty(xmit)); //这里判空,也没有保护,如果head此时增加了?
if (tup->cdata->tx_fifo_full_status) {
unsigned long lsr = tegra_uart_read(tup, UART_LSR);
if ((lsr & TEGRA_UART_LSR_TXFIFO_FULL))
break;
}
tegra_uart_write(tup, xmit->buf[xmit->tail], UART_TX);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); //驱动取数据,从tail获取
tup->uport.icount.tx++;
}
}
2) uart写缓存的代码, (serial_core.c drivers\tty\serial ) ,这里对整个写入过程都加了锁。
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 circ_buf *circ;
unsigned long flags;
int c, ret = 0;
/*
* This means you called this function _after_ the port was
* closed. No cookie for you.
*/
if (!state) {
WARN_ON(1);
return -EL3HLT;
}
port = uart_port_lock(state, flags); //这里加锁了
circ = &state->xmit;
if (!circ->buf) {
uart_port_unlock(port, flags);
return 0;
}
while (port) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0)
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
__uart_start(tty);
uart_port_unlock(port, flags); //到这里才释放锁
return ret;
}
#define uart_port_lock(state, flags) \
({ \
struct uart_port *__uport = uart_port_ref(state); \ //此处为锁的代码也是uart_port中的lock
if (__uport) \
spin_lock_irqsave(&__uport->lock, flags); \
__uport; \
})
3) 回到中断的话题,在中断处理的入口代码
static irqreturn_t tegra_uart_isr(int irq, void *data)
{
struct tegra_uart_port *tup = data;
struct uart_port *u = &tup->uport;
unsigned long iir;
unsigned long ier;
bool is_rx_start = false;
bool is_rx_int = false;
unsigned long flags;
spin_lock_irqsave(&u->lock, flags); //这里加锁了,采用的uart_port的lock
while (1) {
iir = tegra_uart_read(tup, UART_IIR);
if (iir & UART_IIR_NO_INT) {
if (!tup->use_rx_pio && is_rx_int) {
tegra_uart_handle_rx_dma(tup);
if (tup->rx_in_progress) {
ier = tup->ier_shadow;
ier |= (UART_IER_RLSI | UART_IER_RTOIE |
TEGRA_UART_IER_EORD | UART_IER_RDI);
tup->ier_shadow = ier;
tegra_uart_write(tup, ier, UART_IER);
}
} else if (is_rx_start) {
tegra_uart_start_rx_dma(tup);
}
spin_unlock_irqrestore(&u->lock, flags); 这里释放锁了,采用的uart_port的lock
return IRQ_HANDLED;
}
case 1: /* Transmit interrupt only triggered when using PIO */
tup->ier_shadow &= ~UART_IER_THRI;
tegra_uart_write(tup, tup->ier_shadow, UART_IER);
tegra_uart_handle_tx_pio(tup); //这里受此锁保护,此处为第一段代码的入口
break;
综上所述,在用户层发送接口 和中断驱动的发送接口中,都是通过 uart_port结构体中lock 锁代码进行互斥访问的。获取锁的位置:1) 中断处理的入口 2) 写函数的入口。
第二个问题:
在有些操作系统中,即使采用spin_lock_irq,号称禁止中断,但是tx 发送中断在用户进行数据发送时依然会产生,这就导致:
用户进程A在通过串口写数据时,已经获得spin_lock_irq 自旋锁,并进行数据发送。由于默认发送中断打开,导致数据发送后,进入中断发送的流程中,而中断发送抢占了用户进程A,且中断处理也想获得spin_lock_irq自旋锁,此时就死锁了。
在串口发送的过程中,在通用的接口start_tx中,也即uart_core层的代码:
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
if (port && !uart_tx_stopped(port))
port->ops->start_tx(port);
}
通常在底层串口驱动层会使能发送中断,例如:
static void ar933x_uart_start_tx(struct uart_port *port)
{
struct ar933x_uart_port *up =
container_of(port, struct ar933x_uart_port, port);
ar933x_uart_start_tx_interrupt(up);
}
那么这里有个问题,即stop_tx的调用时机是在哪里?
理论上讲,在发送数据前调用,禁止发送中断,以保证用户发送。具体要看tty相关代码了
static const struct tty_operations uart_ops = {
.install = uart_install,
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.stop = uart_stop,