2.3.4 函数库tyLib

tyLib函数在ttyDrv库与底层硬件操作之间建立了一个收发缓冲区,当上层函数需要接收数据时并不是直接读取硬件的接收寄存器,而是调用函数tyRead读取tyLib的缓冲区,同样在发送数据时也是通过调用tyWrite函数将数据发送给tyLib的缓冲区中。

tyLib负责对缓冲区进行管理,包括在轮询和中断模式下自动从串口控制器中读写数据,当接收缓冲区占用了一定的空间之后,调用上层回调函数将接收缓冲区的数据取走等等,另外在不同的模式下,还要完成对接收缓冲区的数据进行重新组织等任务。

当前tyLib库的接收缓冲区管理主要实现了line模式和raw模式的缓冲区管理。所谓line模式就想当我们的中断输入,只有在按下回车键以后,计算机才进行相应的动作,否则只是在屏幕上进行显示,如果在动作前按下了退格、CTRL-X、CTRL-C等等按键,那么还要重新对新输入的line进行修改等等。这在利用串口连接终端的情况下是十分有用的。

在raw模式下,计算机每次指示读取相应的字符,并不对其中的含义进行解释,读取完毕后就根据缓冲区的情况决定是否调用回调函数将接收缓冲区的数据取走。


1. STATUS tyDevInit下面分析tyLib库中的各个函数。

    (

    FAST TY_DEV_ID pTyDev,

    int rdBufSize,

    int wrtBufSize,

    FUNCPTR txStartup

    )

用指定的参数初始化tty设备描述符。tty设备描述符是一个数据结构TY_DEV的变量而不是硬件设备,该结构变量在函数调用前已经创建,因此这里只需要进行初始化就行了,而不用重新创建。

注意区分互斥变量和二进制信号量的区别。互斥变量的目的是为了保证在一定的时间段内一个任务对某个资源独占,即只有当前任务可以访问某一资源,在这期间其他任务无法获取到该资源,互斥变量的获取和释放都是由同一任务完成,它主要强调资源的独占性;而二进制信号量则是某一任务为了获取某一资源,但是该资源却是由其他任务来提供的,它的主要目的是为了确保不同任务的同步,即只有在资源有效的情况下,才能有任务实用该资源,二进制信号量的获取和提供往往是由不同的任务来完成的,通常是任务A提供了某一资源,任务B等待到任务A提供了资源后才能有效获取。二者的区别主要在于二进制信号量的资源是消耗性的,它分别由不同的任务生产和消耗;而互斥变量保护的资源则是独占性,不存在生产和消耗之说,而仅仅是确保只有一个任务可以访问该资源。

这里的二进制信号量rdSyncSem,这个信号量确保底层串口驱动tyIRd在输入环状缓冲区保存了一定的数据之后,tyRead上层函数才能有效获取该信号量并开始读取这些数据。

二进制信号量wrtSyncSem则是确保在输出环状缓冲区中有足够的空间资源,当有了足够的空间资源则函数tyITx释放掉一定的空间时候信号量有效,而tyWrite只有在获取了该信号量后才可以向输出环状缓冲区中写入数据。

互斥变量mutexSem的作用是为了确保对TY_DEV结构变量的访问保护。


2. STATUS tyDevRemove

    (

    TY_DEV_ID pTyDev

    )

tyDevInit函数的逆过程。注意这里flushingRdBuf和flushingWrtBuf要在rdBuf和wrtBuf内存被释放之前置为True是为了确保中断处理程序tyITx和tyIRd不会在释放了之后访问这段内存。


3. LOCAL void tyFlush

    (

    FAST TY_DEV_ID pTyDev

    )

这个函数比较简单,直接调用函数tyFlushRd和函数tyFlushWrt对各自的buffer进行flush。


4. LOCAL void tyFlushRd

    (

FAST TY_DEV_ID pTyDev

)

该函数的作用是对输入环状缓冲区进行flush。注意flush前需要将flushingRdBuf置位是为了确保不被中断处理程序tyIRd搞乱。注意flush结束之后要发送一个XON信号提示对方串口设备接收准备好,并将变量flushingRdBuf复位为False。


5. LOCAL void tyFlushWrt

    (

    FAST TY_DEV_ID pTyDev

    )

该函数的作用是对输出环状缓冲区进行flush。注意flush前需要将flushingWrtBuf置位是为了确保不被中断处理程序tyITx搞乱。注意flush结束之后free空间很大,因此要给出一个wrtSyncSem信号量,并将变量flushingWrtBuf复位为False。最后还要利用_func_selWakeupAll通知各个函数输出环状缓冲区已经空出来了。


6. void tyAbortFuncSet

    (

    FUNCPTR func

    )

设置abort函数,当收到abort字符时执行func函数。默认为null。


7. void tyAbortSet

    (

    char ch

    )

设置abort字符,默认该字符为CTRL-C。


8. void tyBackspaceSet

    (

    char ch

    )

设定backspace字符,默认字符为CTRL-H。


9. void tyDeleteLineSet

    (

    char ch

    )

设置行删除命令字符,默认为CTRL-U。


10. void tyEOFSet

    (

    char ch

    )

设置end-of-file字符,默认该字符为CTRL-D。


11. void tyMonitorTrapSet

    (

    char ch

    )

设置TRAP-TO-MONITOR字符,默认该字符为CTRL-X。当OPT_MON_TRAP选项有效时,输入TRAP-TO-MONITOR字符,将会终止通常的多任务系统并进入TRAP-TO-MONITOR程序。


12. STATUS tyIoctl

    (

    FAST TY_DEV_ID pTyDev,

    int request,

    int arg

    )

处理tty设备的ioctl请求。

现有的bug:在line模式下,以参数FIONREAD调用返回的是输入缓冲区中的字符数,如果在buffer中有5个空行,每个空行字符占用一字节,此外还有一个行字符数占用一字节,那么其返回值为10。

FIONREAD:返回接收环形缓冲区中的有效字符数(不包含正在输入的未完成的行)。

FIONWRITE:返回发送环形缓冲区中的有效字符数。

FIOFLUSH:flush输入发送环形缓冲区。

FIOWFLUSH:flush接收环形缓冲区。

FIORFLUSH:flush发送环形缓冲区。

FIOGETOPTIONS:读取设备选项。

FIOSETOPTIONS:设置设备选项。

FIOCANCEL:取消设备的读写。

FIOISATTY:返回TRUE。

FIOPROTOHOOK:设定函数pTyDev->protoHook。

FIOPROTOARG:设定函数pTyDev->protoHook的参数pTyDev->protoArg。

FIORBUFSET:重新设定发送环形缓冲区。。

FIOWBUFSET:重新设定发送环形缓冲区。

FIOSELECT:增加selWakeupList。

FIOUNSELECT:删掉selWakeupList中指定的item。


13. int tyWrite

    (

    FAST TY_DEV_ID pTyDev,

    char *buffer,

    FAST int nbytes

    )

向TY_DEV的发送环形缓冲区中写入数据。并启动tyTxStartup开始向device发送。

注意在写入前take二进制信号量wrtSyncSem确保有足够的空间,写完后检查是否有空间,如果有空间则需要give二进制信号量wrtSyncSem,方便其他任务写入。


14. int tyRead

    (

    FAST TY_DEV_ID pTyDev,

    char *buffer,

    int maxbytes

    )

从接收环形缓冲区中读取数据。pTyDev->lnBytesLeft变量记录了上次读取数据时,读取行中剩余的字节数,如果为0说明已经完整地读取了一整行。否则说明上次读取的行中还有一定的字节没有读取,这时候可以直接读取。读取完毕之后要检查接收环形缓冲区中空闲的空间否足够,如果足够的话且OPT_TANDEM选项有效且当前为XOFF,这时候需要向device发送一个XON信号表明接收准备好。


15. STATUS tyITx

    (

    FAST TY_DEV_ID pTyDev,

    char *pChar

    )

中断级处理程序,取出下一个要发送的字符(注意并不是所有要发送的字符都是要先从发送环形缓冲区中取出来的,如XON/XOFF字符),准备将该字符发送到串口控制芯片(并未发送)。根据设定的规则,取出该字符需要根据如下准则:

l 首先检查接收端口是否需要发送XON/XOFF信号(需要发送的话rdState.pending为True),如果需要发送,则下一个发送的字符为XON/XOFF;

l 如果wrtState.xoff为true,说明发送处于关闭状态,此时是不能够发送数据的,因此直接退出并置pTyDev->wrtState.busy为FALSE;否则如果wrtState.flushingWrtBuf为true,则说明正在清空发送buffer,此时直接置pTyDev->wrtState.busy为FALSE;

l 如果wrtState.cr为true,说明下一个要发送的字符为'\n';

l 检查发送环形缓冲区中是否有要发送的字符。这一步要进行判断,如果取出的是一个'\n'字符且在OPT_CRMOD模式下,说明该字符要分拆成两个字符连续发送,首先发送'\r'并置wrtState.cr为TRUE,说明下次会发送字符'\n'。发送完毕后要检查发送环形缓冲区是否有足够的空余空间,如果有的话要调用函数_func_selWakeupAll通知相应的程序,可以向buffer中写入数据了。

注意:wrtState.xoff、rdState.xoff、rdState.pending以及wrtState.busy三个变量的区别如下:

l wrtState.xoff表明对方是否准备好接收,如果准备好则向本机发送XON,未准备好发送XOFF,收到的XON/XOFF数值记入wrtState.xoff。

l rdState.xoff表明接收环形缓冲区的状态,true表明buffer已经没有足够的空间来接收新的数据了;flase则表明还有足够的空间来接收新数据。只有rdState.pending状态发生了变化时才向对方发送状态数据XON/XOFF信号。

l 而rdState.pending为true表明rdState.xoff的状态刚刚发生了变化,此时需要在发送端口发送rdState.xoff的状态;

l wrtState.busy则是说明有程序正在将发送环形缓冲区的字符写到串口的发送端发送。


16. STATUS tyIRd

    (

    FAST TY_DEV_ID pTyDev,

    FAST char inchar

    )

中断处理程序调用,主要接收串口芯片接收数据寄存器的数据,并根据情况进行处理或者将字符转移到接收环形缓冲区中。

接收到字符后有一些细节的处理,这里不做太多的描述。只需要了解,接收完毕后要检查buffer中的free空间是否小于门限值,以及当前行是否输入结束,如果输入结束,需要调用函数_func_selWakeupAll准备接受数据。


17. LOCAL void tyRdXoff

    (

    FAST TY_DEV_ID pTyDev,      /* pointer to device structure */

    FAST BOOL xoff

    )

该函数有两个作用,一是根据参数修改rdState.xoff的数值,二是通过调用底层函数pTyDev->txStartup准备向串口控制器中发送XON/XOFF信号。注意,当接收环形缓冲区没有足够空间的时候或者腾出了足够空间时调用,而且仅在rdState.xoff发生变化的时候才会发送一次。发送前需要将rdState.pending置为true。


18. LOCAL void tyWrtXoff

    (

    FAST TY_DEV_ID pTyDev,

    BOOL xoff

    )

该函数有两个作用,一是根据参数修改wrtState.xoff的数值,二是通过调用底层函数pTyDev->txStartup准备向串口控制器中发送XON/XOFF信号。

注意:这里存在一个bug,加入通信端A调用函数发送了一个XON/XOFF信号,而通信端B不能区分是通信端A是因为发送缓冲区满了调用tyRdXoff还是因为其他原因调用函数tyWrtXoff发送的,也就无法进行正确的处理。


19. LOCAL void tyTxStartup

    (

    FAST TY_DEV_ID pTyDev

    )

这个函数的作用是直接准备发送数据。与函数pTyDev->txStartup的区别在于tyTxStartup在调用pTyDev->txStartup之前需要检查发送端口是否正在发送数据,如果正在发送则不再调用pTyDev->txStartup。

函数库tyLib主要使用了一个底层函数pTyDev->txStartup,该底层函数用于向串口控制芯片发送一个字节的数据。除此以外,其核心还是对输入发送环形缓冲区的访问。因此在最初还函数库的初始化函数tyDevInit中就首先对函数指针进行赋值pTyDev->txStartup。