aarch32 linux4.19
基本处理流程如下
cts的状态改变的中断使能,发生cts assert,uart中断更新uart port的状态位hw_stoppped 且停止发送,cts状态转变位deassert时状态恢复重新开始发送
cts assert 底层停止发送的方式是关闭TXfifo empty中断(the TxFIFO Empty interrupt to alert the software to fill-up the TxFIFO again),uart port 停止发送,serial core就不会再调用停止状态的tty port发送数据,详情可见port中的stop_tx 和start_tx的具体实现
/**
* uart_handle_cts_change - handle a change of clear-to-send state
* @uport: uart_port structure for the open port
* @status: new clear to send status, nonzero if active
*
* Caller must hold uport->lock
*/
void uart_handle_cts_change(struct uart_port *uport, unsigned int status)
{
lockdep_assert_held_once(&uport->lock);
uport->icount.cts++;
if (uart_softcts_mode(uport)) {
if (uport->hw_stopped) {
if (status) {
uport->hw_stopped = 0;
uport->ops->start_tx(uport);
uart_write_wakeup(uport);
}
} else {
if (!status) {
uport->hw_stopped = 1;
uport->ops->stop_tx(uport);
}
}
}
}
//以pl010举例,更新cts状态来控制流控的时间点是在uart中断中
static void pl010_modem_status(struct uart_amba_port *uap)
{
unsigned int status, delta;
writel(0, uap->port.membase + UART010_ICR);
status = readb(uap->port.membase + UART01x_FR) & UART01x_FR_MODEM_ANY;
delta = status ^ uap->old_status;
uap->old_status = status;
if (!delta)
return;
if (delta & UART01x_FR_CTS)
uart_handle_cts_change(&uap->port, status & UART01x_FR_CTS);
wake_up_interruptible(&uap->port.state->port.delta_msr_wait);
}
static irqreturn_t pl010_int(int irq, void *dev_id)
{
spin_lock(&uap->port.lock);
status = readb(uap->port.membase + UART010_IIR);
if (status) {
if (status & UART010_IIR_MIS)
pl010_modem_status(uap);
******
}
}
tty write的流程ty_write() -> do_tty_write() -> n_tty_write() -> uart_write() -> serial8250_start_tx() -> serial_out()
uart_tx 被cts停止后,底层发送数据会在serial_core.c还没到tty port时检查tty port的状态是否是stopped,如果是stopped就停止发送
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 uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
unsigned long flags;
port = uart_port_lock(state, flags);
__uart_start(tty);
uart_port_unlock(port, flags);
}
这样看来这些上层的数据就直接丢了?cts deasser重新发送的write_wakeup 又是唤醒的谁呢?
以slcan为例,slcan线路规程的write_wakeup的函数是会唤醒tx_worker, tx_worker 唤醒net_dev的tx_queue的qdisc 队列规程,从这个例子看user space的数据并没有丢而是缓存在了某处等待底层uart port write wakeup。所以cts停止发送的关键操作就是不再调用write_wakeup
static struct tty_ldisc_ops slc_ldisc = {
.owner = THIS_MODULE,
.magic = TTY_LDISC_MAGIC,
.name = "slcan",
.open = slcan_open,
.close = slcan_close,
.hangup = slcan_hangup,
.ioctl = slcan_ioctl,
.receive_buf = slcan_receive_buf,
.write_wakeup = slcan_write_wakeup,
};
/*
* Called by the driver when there's room for more data.
* Schedule the transmit.
*/
static void slcan_write_wakeup(struct tty_struct *tty)
{
struct slcan *sl = tty->disc_data;
schedule_work(&sl->tx_work);
}
/* Write out any remaining transmit buffer. Scheduled when tty is writable */
static void slcan_transmit(struct work_struct *work)
{
struct slcan *sl = container_of(work, struct slcan, tx_work);
if (sl->xleft <= 0) {
/* Now serial buffer is almost free & we can start
* transmission of another packet */
sl->dev->stats.tx_packets++;
clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags);
spin_unlock_bh(&sl->lock);
netif_wake_queue(sl->dev);
return;
}
}
user space的数据到底缓存在了哪?还是以slcan为例,已知cansend 的底层调用位ndo_start_xmit
net_device_ops->ndo_start_xmit
[<c0656a5c>] (dev_hard_start_xmit) from [<c067b19c>] (sch_direct_xmit+0x98/0x17c)
[<c067b19c>] (sch_direct_xmit) from [<c0656f60>] (__dev_queue_xmit+0x2e4/0x588)
[<c0656f60>] (__dev_queue_xmit) from [<c0749b24>] (can_send+0x160/0x1e8)
[<c0749b24>] (can_send) from [<c074d634>] (raw_sendmsg+0x140/0x1b4)
[<c074d634>] (raw_sendmsg) from [<c063a630>] (sock_sendmsg+0x14/0x24)
[<c063a630>] (sock_sendmsg) from [<c063a6c4>] (sock_write_iter+0x84/0xac)
[<c063a6c4>] (sock_write_iter) from [<c022ee20>] (__vfs_write+0xf0/0x11c)
[<c022ee20>] (__vfs_write) from [<c022efc0>] (vfs_write+0xb8/0x144)
[<c022efc0>] (vfs_write) from [<c022f130>] (SyS_write+0x3c/0x74)
[<c022f130>] (SyS_write) from [<c01070a0>] (ret_fast_syscall+0x0/0x48)
slcan 发送数据 最终调用sl->tty->ops->write
static const struct net_device_ops slc_netdev_ops = {
.ndo_open = slc_open,
.ndo_stop = slc_close,
.ndo_start_xmit = slc_xmit,
.ndo_change_mtu = slcan_change_mtu,
};
/* Send a can_frame to a TTY queue. */
static netdev_tx_t slc_xmit(struct sk_buff *skb, struct net_device *dev)
{
spin_lock(&sl->lock);
netif_stop_queue(sl->dev);
slc_encaps(sl, (struct can_frame *) skb->data); /* encaps & send */
spin_unlock(&sl->lock);
out:
kfree_skb(skb);
return NETDEV_TX_OK;
}
/* Encapsulate one can_frame and stuff into a TTY queue. */
static void slc_encaps(struct slcan *sl, struct can_frame *cf)
{
canid_t id = cf->can_id;
sl->xbuff[0] = START_MAGIC;
sl->xbuff[1] = PACK_MTU;
sl->xbuff[2] = 0;
set_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags);
actual = sl->tty->ops->write(sl->tty, sl->xbuff, SLC_MTU);
sl->xleft = SLC_MTU - actual;
sl->xhead = sl->xbuff + actual;
sl->dev->stats.tx_bytes += cf->can_dlc;
}
还有一个delta_msr_wait,在处理了cts事件后wake_up_interruptible(&uap->port.state->port.delta_msr_wait)
都有谁会谁在这个等待队列上呢?从代码上看是通过ioctl给user space提供获取模式控制事件的接口,具体应用暂未查到
static int
uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case TIOCMIWAIT:
ret = uart_wait_modem_status(state, arg);
break;
}
}
/*
* Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
* - mask passed in arg for lines of interest
* (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
* Caller should use TIOCGICOUNT to see which one it was
*
* 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)
{
DECLARE_WAITQUEUE(wait, current);
uart_enable_ms(uport);
//使能 模式控制 流控
add_wait_queue(&port->delta_msr_wait, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
ret = 0;
break;
}
schedule();
/* see if a signal did it */
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&port->delta_msr_wait, &wait);
uart_port_deref(uport);
return ret;
}