一、背景:
该设备连接4个串口芯片,每个串口芯片连接一个带有串口接口的智能电表,远程主机通过连接到设备实现读取/控制4个智能电表。
普然CPU没有4个串口接口,所以需要额外的方式实现连接,使用的方式是通过本地localbus的方式,通过连接到一片CPLD上,该CPLD连接4个智能电表的串口,CPU通过访问CPLD实现间接访问4个串口,如下图:
二、CPU访问机制
前面已提到,普然CPU没有4个串口接口,所以通过本地localbus的方式连接到CPLD,在CPLD上实现4个串口的控制器的功能,通过读写映射到CPLD的相关内存(寄存器),实现控制4个串口设备;串口
本文不对CPLD逻辑实现做描述,仅描述驱动代码对CPLD的访问。
驱动代码首先使能片选CPLD并设置localbus所连接物理地址的映射,实现映射CPLD上的串口控制器的内存,进而再映射这片区域到虚拟内存,介绍如下:
使用CPU的CS2片选CPLD,并设置CS2物理基地址(0x17B00000),这样实现就确定了CPU访问CPLD的物理内存地址,然后ioremap该物理地址为基址、长度为1页的内存区域到内核,至此驱动代码可访问CPLD的这部分物理内存。
这部分内存包括了4个串口控制器的IO内存,驱动代码要依次的记录下来,这样以后就能区分出不同的串口控制器,如下图:
上面代码片段在一个for循环中运行,uap是描述每个串口控制器的结构体,每个uap的uap->membase记录每个串口控制器IO内存的基址,uap->line记录每个串口控制器id,irq记录中断号,可见4个串口控制器共享同一个中断,当接收到数据时,中断处理函数必须依次查看每个串口控制器的中断状态寄存器来判断该串口控制器是否发出中断并进行处理。
三、数据接收:
3.1、接收中断上半部处理:
数据接收采用中断方式,4个串口控制器共用一个中断线,中断处理函数必须查询每个串口控制器的中断状态寄存器的状态,并依次处理。正常情况下,会只发现一个需要处理的串口控制器,因为在CPLD逻辑中,只有当前发出中断的串口控制器的中断状态寄存器被CPU清除后,才会允许另一个串口控制器发出中断,当然由此也可见,CPU必须在接收到中断后尽快处理完中断并清除中断,否则其他串口控制器的中断迟迟无法发出(事实上这样情况也并不多见,因为CPU的处理很快),这部分任务就是中断上半部的任务,即读取该串口控制器接收到的所有数据,然后清除中断,最后触发中断下半部(work queue);
接收中断上半部处理流程如下图:
3.2、接收中断下半部:
接收中断下半部将会把暂存buffer的数据复制到给用户进程访问提供的user buffer当前未读取位置处,然后清空暂存buffer;用户进程接收数据时,以接收到10个字节为单位,即user buffer满10个字节后读取并返回,否则将睡眠等待,当满足10个字节后,它将从接收buffer中复制数据,复制后更新当前未读取位置指针,这需要一个同步,以避免复制拷贝错乱,如下图:
可见这是为什么还需要中断下半部的根本原因,在用户进程被唤醒后,它将读取10字节的数据并更新user buffer的当前未读取位置指针,每一次中断下半部在收到数据后会把数据放在这个位置,这是为了防止一旦用户进程出问题时没有及时读取数据,在user buffer里可以缓存所有用户进程尚未接收的数据,避免数据丢失,user buffer是一个相对较大的动态buffer(1000个字节)。
所以在用户进程从user buffer复制数据时,要避免userbuffer被更新,所以用一全局变量设置是否可操作user buffer,当该变量不满足时,中断下半部的工作队列必须推迟执行(schedule_delayed_work),等到用户进程拷贝完毕设置该变量满足时,才能重新操作user buffer;如下图:
中断下半部的work queue代码在发现当前状态不满足时,说明用户进程正在操作user buffer,必须推迟再执行work queue代码,直到用户空间操作完成并设置状态满足。
用户进程只要发现user buffer当前没有10个字节,就会定时睡眠,无论是被唤醒还是定时时间到唤醒,都会首先设置user buffer当前不可操作,然后再检查当前是否足够10个字节,再根据唤醒时剩余的定时值判断是应该超时退出还是复制数据更新未读取位置并返回,如下图:
每次睡眠之前都会设置当前userbuffer可操作(clear_bit),被唤醒后首先设置当前user buffer不可操作(set_bit),然后判断是什么原因被唤醒,如下图:
如果是因为定时时间到被唤醒,则退出
否则是被中断下半部代码唤醒,说明userbuffer已满10字节可以复制了,复制数据并更新user buffer的当前未读取位置指针,最后设置user buffer可操作:
3.3、数据接收的poll方法:
因为是4个串口,所以需要给用户进程提供多路访问机制,即在驱动代码中提供接收数据的poll方法,道理和上面是一样的,获取当前4个串口描述符中哪一个的user buffer超过10字节即可,如下图:
图中函数第二参数amt设为10,即user buffer超过10字节库存即可置位POLIN。
四、数据发送:
相当于数据接收流程,数据发送流程相对简单,数据发送在驱动代码中是一字节一字节的发送,每发送一字节后陷入睡眠,等待被唤醒,当产生发送中断后,发送中断处理函数会唤醒用户的发送流程,进入下一字节的发送;对于同一串口控制器,必须只能有一个用户的write系统调用正在被处理,这是用信号量实现的,如下图:
显而易见,数据发送的poll方法只需检查各个串口控制器的发送信号量是否可用,可用即说明当前没有进程向该串口控制器正在发送。