UIO
概念
对于很多类型的设备,为它创建一个linux驱动有点过头。其实真正需要的只是得到处理其中断的一种方式还有访问其设备内存空间的权限。而真正控制设备的逻辑其实并没有必要搞到内核里面去,因为这种设备并不需要用到内核提供的其它资源。类似这种设备的就比如 industrial I/O cards。
UIO就用来解决这个问题,全称:userspace I/O system。对于典型的industrial I/O cards,驱动只需要很少的一点内核模块,而大部分都在用户空间执行。这能简化开发,而且减少了内核模块发生严重bug的可能。
那哪些设备适合用uio来驱动呢?
- 能通过写它的内存空间完全控制这个设备
- 设备能产生中断
- 不适合已有的标准内核子系统(既然有标准的一套可以支持,就不需要uio了)
uio的机制
uio这套框架抽象出一个uio设备,在/dev/uioX,X表示标号,如/dev/uio0。另外在sysfs中有一些属性用来访问uio设备的信息,处于/sys/class/uio/uioX。
/dev/uioX
/dev/uioX 用来访问设备的地址空间 —— 使用mmap操作
当通过read函数来读它的时候,会阻塞,直到中断来临才返回,所以可以通过读它来处理中断。当然,也可以通过select操作来等待中断。read函数返回后,读到的值代表到目前为止所有的中断数 —— 这可用来检查是否遗漏了中断。
对于某些硬件设备来说,内部可能有好几个中断源,但同时却没有分开的中断mask和状态寄存器,这时如果中断处理函数是通过写硬件设备的寄存器来disable这个中断,那么用户空间就不知道到底来的是哪个中断。这样,kernel就必须彻底disable掉这个中断,这样就可以不操作这个硬件设备。如此一来,用户空间就能辨认出中断是谁产生的,可是这样就不能再次enable这个中断了。另外,还有一个边缘场景:硬件设备的中断状态和确认寄存器是一起的(combined),而且re-enable中断是个读-修改-写入的操作,如果与此同时一个新的中断又发生了就会造成竞争。为了解决这个问题,UIO框架实现了对/dev/uioX的write 操作。如果不存在上述问题,那么这个操作可以被忽略(不用使用);否则,write操作用来enable或disable这个设备的中断。它内部会调用到irqcontrol(),如果没有实现irqcontrol(), write操作会返回错误码:-ENOSYS。
为了合理地处理中断,你自己定制的kernel模块可以提供它自己的中断handler,这个会被自动调用。
对于那些不能产生中断而是通过polling寄存器方式的硬件设备,可以通过设置一个接受可配置时间间隔的timer来触发中断。通过在timer的event handler中调用uio_event_notify()来模拟这个中断。
/sys/class/uio/uioX
这个目录下面会有哪些文件呢?
-
name
设备名,建议用模块的名字 -
version
驱动版本 -
event
自上次这个节点被访问以来这个驱动处理的中断的次数 -
maps/mapX
内存映射属性目录
对于采用内存映射方式嵌入系统的设备,是通过mmap来完成映射访问,就会有这个属性。而且创建了多少组的内存映射,就会有多少组mapX, X=0,1,…,N-1。每组mapX都会有如下4个只读属性- name
名字 - size
大小 - addr
基地址 - offset
偏移量
size、addr、offset的单位都是字节
- name
-
portio/portX
某些体系架构支持非内存映射IO, 例如x86架构,那么当设备使用的是端口映射IO的方式嵌入系统的时候,就用的是portio。而且有多少组port就会有多少组portX, X=0,1,…N-1。每个portX下就会有如下只读属性- name
名字 - start
起始port - size
port的个数 - porttype
port的类型
- name
用户态编程注意事项
用户态首先需要得到/sys/class/uio/uioX/maps/mapN/size的大小
然后根据需要打开/dev/uioX(c::open())。
再使用mmap来做映射:
mmap的原型:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start填NULL, length应该填sysfs中uioX对应的mapN的size, offset是重点,其值应该是N*getpagesize(),对应mapN。
然后就可以直接读写mmap返回的指针指向的空间了。