这几天因为一个项目用回了zynq7020平台,需要用到PL端的中断,所以考虑使用Linux下的uio驱动。
虽然以前也用过UIO,但时间久了还是有点忘记了,所以记录一下使用的过程,方便以后回顾和方便各位看官。
UIO(Userspace I/O framework)其实就是Linux提供给用户空间进行底层I/O操作的方案,反正我是这么理解的。
在Linux中,用户空间一般不允许直接访问底层的I/O, 也就是不能直接访问物理地址的。想要访问物理地址,就需要将物理地址转换为虚拟地址。
最常用的方法就是mmap(), 将想要访问的地址使用mmap()这个系统调用映射为程序空间虚拟地址。所以,UIO的主要一个工作就是,将驱动程序指定的I/O地址使用mmap()进行地址映射。
至于中断,我们知道,在用户空间也是不能直接接收内核的中断的,所以,UIO也是做了一半的工作,UIO将驱动程序指定的中断进行注册和回调。当中断来临的时候,UIO将响应中断进行回调函数。但是,在回调函数中,UIO只是做了将中断屏蔽掉,剩下的处理和重新打开中断交给了用户空间。
核心部分可以参考uio_core.c。看完就应该大概明白个所以了,中断注册和响应写得非常巧妙,我就不做分析了,看看怎么用吧。
今天的主角:uio_pdrv_genirq
1、首先是配置内核支持UIO驱动。我使用的是petalinux,用和不用petalinux没啥关系,原理大概一样。
petalinux-config -c kernel
Device Drivers --->
<*> Userspace I/O drivers --->
Userspace I/O platform driver with generic IRQ handling
2、修改设备树,增加uio设备。vi project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
/ {
};
&amba_pl{
uio@0 {
compatible = "generic-uio";
status = "okay";
interrupt-controller;
interrupt-parent = <&intc>;
interrupts = <0 29 1>;
};
};
这里我使用的是61号中断,在zynq7020平台对应的PL的中断0,这里的29就是(61-32=29),我也不知道为啥xilinx的中断要写成中断号减去32,我没有去研究。后面的1表示上升沿触发,前面的0是中断控制器编号。
3、修改完就可以编译了。petalinux-build
4、使用新的内核镜像启动,在我的板子上,默认情况下,uio_pdrv_genirq这个模块是没有自动加载的。如果有自动加载,可以先使用modprobe -r uio_pdrv_genirq 卸载掉。
5、加载uio_pdrv_genirq驱动。在uio_pdrv_genirq驱动源码中可以看到,这个模块加载是要加参数的。
#ifdef CONFIG_OF
static struct of_device_id uio_of_genirq_match[] = {
{ /* This is filled with module_parm */ },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);
MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");
#endif
加载:modprobe uio_pdrv_genirq of_id=generic-uio
这里的of_id一定是要和设备树中的compatible属性保持一致。当然,也可以将uio_pdrv_genirq.of_id=generic-uio直接写在bootargs中,启动默认加载这个参数就可以了。
6、顺利的话,可以在/dev/下看到对应的UIO设备了。
对应的中断也可以看到了。
7、使用。由于涉及公司的代码,我就不放源码了,简化版来个截图大概说一下。
使用open打开/dev/uio0设备,得到一个文件描述符,对这个文件描述符进行写操作,是一个使能中断的信号。对这个文件描述符进行poll或者read操作,会进入阻塞状态,直到中断信号发生。中断信号发生后,select或者read会由原来的阻塞状态变为非阻塞状态,可以在这个时候处理中断想要做的事情。处理完之后,记得使用write接口重新打开中断,否则下一次中断不会响应。
总的来说,使用起来还是挺简单的,具体的原理如果有时间我下次再分析。