linux uart hw flowcontrl

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;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux中的UART是一种串口通信接口,用于在计算机和外部设备之间进行数据传输。在Linux中,可以通过打开串口设备文件(如/dev/ttyS0)来进行串口通信。可以使用open函数打开串口设备文件,并使用相应的参数(如O_RDWR、O_NOCTTY、O_NDELAY)来配置串口的工作方式。其中,O_NOCTTY表示程序不会成为串口的控制终端,O_NDELAY表示程序不关心DCD信号线的状态。接下来,可以使用相应的寄存器(如ULCONn、UCONx、UBRDIVn)来设置串口的传输格式、时钟源和波特率等参数。ULCONn寄存器用于设置传输格式,UCONx寄存器用于选择UART时钟源和设置中断方式,UBRDIVn寄存器用于设置比特率。通过配置这些寄存器,可以实现对串口的控制和数据传输。 #### 引用[.reference_title] - *1* *3* [linux之UART](https://blog.csdn.net/pengliang528/article/details/79847817)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Linux串口UART编程--C语言](https://blog.csdn.net/leumber/article/details/80105295)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenhuxi_yu

感谢投币,继续输出

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值