• <div id="0yoao"><tr id="0yoao"></tr></div>
    <dl id="0yoao"></dl>
  • <sup id="0yoao"></sup>
    <div id="0yoao"><tr id="0yoao"></tr></div>
  • <div id="0yoao"><tr id="0yoao"></tr></div>
  • ?

    X-019-KERNEL-串口驱动开发之数据收发

    作者:wowo 发布于:2016-11-29 21:55 分类:X Project

    1. 前言

    本文是“X Project”串口驱动开发的第四篇,在第二篇“uart driver框架”的基础上,实现基本的、可收发数据的uart驱动,并借助这个过程,学习如下知识:

    中断的申请和使用;

    利用中断发送和接收数据;

    uart_ops中常用函数(.startup, .start_tx, etc.)的使用。

    2. 中断的申请和使用

    在linux kernel中,使用中断进行数据传输是最基本的要求(更进阶的是DMA,我们会在后续的文章中介绍),因为强悍的CPU无法忍受乌龟般的外设速度,忙等待只会自断生路。下面将会以Bubblegum-96平台的UART driver为例,介绍中断的使用。

    2.1 准备工作

    关于中断,在使用之前,我们至少需要先理清如下内容(以Bubblegum-96平台的UART5为例进行说明):

    1)该外设和中断控制器之间通过哪些中断线(IRQ line,也就是我们常说的中断号)进行连接。

    2)每个中断线的触发方式为何,电平?边沿?

    由[2]可知,Bubblegum-96平台的UART5的中断线为SPI 35,触发方式为高电平触发(外部GPIO中断比较关心触发方式,其它的外设中断,一般都是电平触发)。

    3)在设备内部(例如这里的UART控制器),哪些行为可以产生中断?产生中断的条件为何?如何控制中断的使能?如何清除中断的pending状态?

    对Bubblegum-96平台的UART5控制器来说,由[1]可知:

    有TX和RX两种中断;

    TX的FIFO大小为32bytes,只要空闲空间(empty)大于等于16bytes,就会触发中断;

    RX的FIFO大小也为32bytes,当接收的数据大于等于16bytes时,就会触发中断;

    TX/RX中断使能与否,可由UARTx_CTL(offset=0x0000)的bit19、bit18控制;

    UART5中断产生时,可通过UARTx_STAT(offset=0x000c)的bit1(TX IRQ Pending)、bit0(RX IRQ Pending)获取具体的中断原因(TX还是RX),这两个bit均写1清除。

    注1,这里存在一个问题,需要大家思考(这个问题是串口驱动最常见的):基于上面的描述,只有RX接收到大于等于16bytes的数据时,才会产生中断,那么接收少于16bytes的数据时,怎么办?后面实?#23454;?#35797;的时候,再细说。

    4)中断发生时,要做哪些事情?这些事情是否比较耗时?是否可以在线?#35752;写?#29702;?

    以UART控制器为例:

    TX中断产生时,表明可以向TX FIFO写入数据;

    RX中断产生时,表明RX FIFO中有数据需要读取;

    在Linux serial framework下,上面两个动作都不是耗时的动作,因此不需要在线?#35752;?#36827;行处理。

    2.2 中断申请

    对uart driver来说,中断的申请,包括3个步骤:

    1)在DTS中,通过interrupts字段,指定该设备需要使用的中断号

            serial5: [email protected] {
                    compatible = "actions,s900-serial";
                    reg = <0 0="" 0xe012a000="" 0x2000="">;  /* UART5_BASE */
    +               interrupts = ;
            };

    2)在platform driver的probe接口中,通过platform_get_irq接口,将DTS中指定的中断号取出并保存下来

    @@ -253,6 +268,12 @@ static int owl_serial_probe(struct platform_device *pdev)
            }
            port->iotype = UPIO_MEM32;

    +       port->irq = platform_get_irq(pdev, 0);
    +       if (port->irq < 0) {
    +               dev_err(&pdev->dev, "Failed to get irq\n");
    +               return port->irq;
    +       }
    +
            port->line = of_alias_get_id(pdev->dev.of_node, "serial");

     

    platform_get_irq第二个参数是dts中interrupts字段指定的中断编号,我们只使用一个,因此这里为0即可。

    3)在uart port的.startup接口中,调用devm_request_irq,申请并使能该中断线

    注2:这里的使能,是指UART控制器和GIC之间的使能。

    +static irqreturn_t owl_serial_irq_handle(int irq, void *data)
    +{
    +       struct uart_port *port = data;
    +
    +       return IRQ_HANDLED;
    +}
    +
    static int owl_serial_startup(struct uart_port *port)
    {
            int ret = 0;

            dev_dbg(port->dev, "%s\n", __func__);

    +       ret = devm_request_irq(port->dev, port->irq, owl_serial_irq_handle,
    +                              0, "owl_serial", port);

    +       if (ret < 0) {
    +               dev_err(port->dev, "request irq(%d) failed(%d)\n",
    +                       port->irq, ret);
    +               return ret;
    +       }
    +
            return ret;
    }

    devm_request_irq有很多参数,其中“owl_serial_irq_handle”是中断的handler,0是flags(这里没有特殊的flag需要传递),"owl_serial"是名字(无关紧要),port是传入的?#25509;?#25968;据,kernel irq core会在调用我们的handler的时候再传给我们(具体请参考上面owl_serial_irq_handle函数)。

    2.3 RX和TX中断的使能

    串口在启动(.startup被调用)之后,就要保证可以正常接收数据,因此RX中断需要在uart port的.startup中使能:

    @@ -111,6 +120,9 @@ static int owl_serial_startup(struct uart_port *port)
                    return ret;
            }

    +       /* RX irq enable */
    +       __PORT_SET_BIT(port, UART_CTL, UART_CTL_RXIE);
    +
            return ret;
    }

    而TX中断可以放到uart port的.start_tx中再使能,如下:

    static void owl_serial_start_tx(struct uart_port *port)
    {
            dev_dbg(port->dev, "%s\n", __func__);
    +
    +       /* TX irq enable */
    +       __PORT_SET_BIT(port, UART_CTL, UART_CTL_TXIE);
    }

    2.4 中断处理

    中断处理的逻辑比较简单:

    1)读取UARTx_STAT寄存器,判?#29616;?#26029;类型。

    2)如果是RX中断,从UARTx_RXDAT中读取数据,并调用tty_insert_flip_char将读取的数据保存在uart port的RX buffer中。具体可参考第4章的介绍。

    3)如果是TX中断,则检查uart port的TX buffer,是否还有未发送的数据,如果有,则将数据写入到UARTx_TXDAT,由UART控制器发送出去。具体可参考第3章的介绍。

    3. 收据收发前的其它操作

    在前面的章节提到过,uart driver需要实现由struct uart_ops变量所代表的各?#21482;?#35843;函数,这里列举一些和数据收发直接相关的函数,如下:

    1).startup,除去上面2.3中申请终端、使能RX中?#31995;?#25805;作,我们还需要在startup的时候,使能串口控制器,如下:

    @@ -123,6 +123,9 @@ static int owl_serial_startup(struct uart_port *port)
            /* RX irq enable */
            __PORT_SET_BIT(port, UART_CTL, UART_CTL_RXIE);

    +       /* enable serial port */
    +       __PORT_SET_BIT(port, UART_CTL, UART_CTL_EN);
    +
            return ret;
    }

    2).shutdown,.startup的反动作,disable串口控制器、disable RX中断、注销中?#31995;齲?#19981;再贴代码了。

    3).stop_tx,disable TX中断,以及其它和数据发送有关的内容(具体可参考第4章的描述)。

    4).stop_rx,disable RX中断,以及其它和数据接收有关的内容(具体可参考第5章的描述)。

    5).tx_empty,判断TX FIFO是否为空,如下(如果为空,需要返回TIOCSER_TEMT,否则返回0即可):

    @@ -153,7 +167,10 @@ static unsigned int owl_serial_tx_empty(struct uart_port *port)
    {
            dev_dbg(port->dev, "%s\n", __func__);

    -       return 0;
    +       if (__PORT_TEST_BIT(port, UART_STAT, UART_STAT_TFES))
    +               return TIOCSER_TEMT;
    +       else
    +               return 0;

    }

    3. 数据发送

    上层软件需要通过uart port发送的数据时,会将数据暂存在一个struct circ_buf类型的环形缓冲区中(port->state->xmit),然后调用driver的.start_tx接口,我们可以在这个接口中从环形缓冲区读取数据要发送的数据,写入到UART控制器的TX FIFO。?#27604;唬琓X FIFO可能很小,需要多次传输才能完成,因此我们需要在传输完成的中?#29616;校?#20877;次从环形缓冲区读取数据,写入TX FIFO,直到所有数据发送完成为止。上述的发送流程图如下:

    serial_tx_flow

    图片1 数据发送流程

    该流程比较简单(具体的代码实现可参考本文档对应的patch文件),不过有一些地方需要说明一下:

    1).start_tx实在关中断的情况下调用的,因此不用考虑同步问题。

    2)由于TX FIFO的限制,大多数情况下,数据发送是由TX中断推动的。但存在一种特殊情况:TX buffer的数据已经发送完成了,此时串口控制器没有数据在发送,TX中断不会产生,此时需要在.start_tx中重新写FIFO。上面图片1的流程,可以覆盖这个场景。

    4. 数据接收

    数据接收的流程更简单,流程图如下:

    serial_rx_flow

    图片2 数据接收流程

     

    5. 串口数据发送和console之间的同步

    由于console的write,和串口数据的发送,都是操作同一个TX FIFO,因此存在数据交叉的问题。要解决这个问题,一般遵守一个原则:保证一次console write的完整性,这样可以避免kernel输出日志被打乱。为了做到这一点,我们需要按照如下步骤改造console的write接口:

    1)备份当前TX中断的状态,然后禁止TX中断。

    2)按照原来的方式,调用uart_console_write将字符串通过串口发送出去。

    3)?#25351;碩X的中断状态。

    代码如下:

    static void owl_console_write(struct console *con, const char *s, unsigned n)
    {
    +       bool tx_irq_enabled;
    +
            struct uart_driver *driver = con->data;
            struct uart_port *port = driver->state[con->index].uart_port;

    +       /* save TX IRQ status */
    +       tx_irq_enabled = __PORT_TEST_BIT(port, UART_CTL, UART_CTL_TXIE);
    +
    +       /* TX irq disable */
    +       __PORT_CLEAR_BIT(port, UART_CTL, UART_CTL_TXIE);
    +
            uart_console_write(port, s, n, owl_console_putchar);
    +
    +       /* restore TX IRQ status */
    +       if (tx_irq_enabled)
    +               __PORT_SET_BIT(port, UART_CTL, UART_CTL_TXIE);
    }

    6. 参考文档

    [1] bubblegum-96/SoC_bubblegum96.pdf

    [2] https://github.com/96boards-bubblegum/linux/blob/bubblegum96-3.10/arch/arm64/boot/dts/s900.dtsi

    [3] Documentation/serial/driver

    [4] patch文件,https://github.com/wowotechX/linux/commit/9328d8aa82dcf2c17eec03c1beccf9c5c0567a6a

     

    原创文章,转发请注明出处。蜗窝科技,www.71402172.com

    标签: Linux driver irq serial tx rx transmit

    评论:

    秋暮离
    2017-05-21 23:54
    wowo好,
    如果FIFO阈值可以修改,出于传输效率考虑,以下想法是否正确?
    1、假设CPUa快于CPUb,则a的TX FIFO阈值要设的小于b,a的RX FIFO阈值要设的大于b(uart发送速率一样的前提下)?
    2、是不是因为实际CPU速度相对外设来说都很快,所以TX FIFO阈值都要尽?#21487;?#30340;低些,RX FIFO阈值尽?#21487;?#22791;高些?
    wowo
    2017-05-22 11:39
    @秋暮离:我觉得,uart永远都比cpu慢:因此从uart的?#23884;?#32771;虑(代价是浪费CPU,?#26723;?#21527;?),fifo越小越好(cpu就可以及时的处理数据);但从cpu的?#23884;?#32771;虑(尽量节省cpu),fifo越大越好,这样cpu就可以去做其它事情,偶尔过来处理一下uart数据即可(?#27604;唬?#20195;价也是一目了然的)。
    秋暮离
    2017-05-22 17:55
    @wowo:@wowo:?#34892;?#22238;复,您说的很清楚。不过?#24067;﨔IFO中的数据不是自动发送出去或者获取进来的吗?比如RX FIFO只要不满就可以自己不断的接收外部数据,那是不是要这样考虑:尽量不要让慢的uart停下来,尽量在RX FIFO接收到更多数据时(阈值尽?#21487;?#39640;)或TX FIFO快没数据时(阈值尽?#21487;?#20302;)才产生一次中断(以节省CPU)
    wowo
    2017-05-22 18:45
    @秋暮离:是的。fifo(无论是哪个?#24067;?#27169;块的fifo)的设计初衷均是如此。
    jakeooo
    2019-02-18 20:32
    @秋暮离:RX FIFO出发阈值设置较高,如果对方发送到数据达不到触发阈值,就不会触发中断。如果cpu设计得好,会在RX FIFO有数据但未到阈值时检测几个时钟的超时,超时也触发中断。
    Sky
    2017-03-07 11:34
    版主:上面有错别字“克制”应为“可知”
    wowo
    2017-03-07 14:52
    @Sky:多谢提醒,已修改:-)

    发表评论:

    Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog
    连码三全中是什么
  • <div id="0yoao"><tr id="0yoao"></tr></div>
    <dl id="0yoao"></dl>
  • <sup id="0yoao"></sup>
    <div id="0yoao"><tr id="0yoao"></tr></div>
  • <div id="0yoao"><tr id="0yoao"></tr></div>
  • <div id="0yoao"><tr id="0yoao"></tr></div>
    <dl id="0yoao"></dl>
  • <sup id="0yoao"></sup>
    <div id="0yoao"><tr id="0yoao"></tr></div>
  • <div id="0yoao"><tr id="0yoao"></tr></div>
  • 淘宝快三快3在线合买 山东11选5特别号 新疆时时彩开奖号码 百人牛牛 手机市场 六合图库管家婆 幸运赛车开奖历史号码表 排列3定胆秘籍 甘肃快3几点封盘 湖北十一选五前三直遗漏号 辽宁11选5杀码好方法 快乐时时彩开奖号码 博彩网导航 法甲联赛赛果 江西多乐彩开奖视频 云南快乐十分走势图电视横屏版