UIO(Userspace I/O)是运行在用户空间的I/O技术。UIO适合在编写大型驱动程序的时候使用,它免去了频繁的内核模块的切换与重启。另外某些情况下性能也比内核驱动好,比如典型的应用例子就是dpdk。除了实现用户态驱动以外UIO也适合在虚拟化的时候做设备透传,相较于VFIO也是一种不错的选择。
1.UIO的工作方式
设备驱动的编写无非是两件事情:
1.设备内存的读写
2.中断的响应
UIO实现了mmap,可以实现映射物理内存到虚拟内存供用户层读写。
中断的响应必须在内核进行,UIO在内核实现了很少一部分中断的处理,之后通知到用户空间。用户层只需要简单的阻塞在对 /dev/uioX的read()操作上。当设备产生中断时,read()操作立即返回。UIO 也实现了poll()系统调用,你可以使用 select()来等待中断的发生。select()有一个超时参数可以用来实现有限时间内等待中断。
对设备的控制还可以通过/sys/class/uio下的各个文件的读写来完成。你注册的uio设备将会出现在该目录下。 假如你的uio设备是uio0那么映射的设备内存文件出现在 /sys/class/uio/uio0/maps/mapX,对该文件的读写就是对设备内存的读写。
想要运行一个 UIO的程序需要以下几个步骤:
1.编写内核UIO驱动
2.卸载真实设备驱动
3.绑定设备到UIO驱动
UIO的使用需要先编写一个内核的驱动,之后通过sys下的接口将设备卸载后再挂载到UIO驱动上。下面先看一下一个简单的内核驱动的实现。
2.UIO内核驱动
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/slab.h> /* kmalloc, kfree */
struct uio_info uio_virtual_device_info = {
.name = "myuio",
.version = "1.0",
.irq = UIO_IRQ_NONE,
// .irq = UIO_IRQ_CUSTOM,
};
static int uio_virtual_device_drv_probe(struct platform_device *pdev)
{
printk("uio_virtual_device_probe( %p)\n", pdev);
uio_virtual_device_info.mem[0].addr = (unsigned long)kmalloc(1024,GFP_KERNEL);
if(uio_virtual_device_info.mem[0].addr == 0)
return -ENOMEM;
uio_virtual_device_info.mem[0].memtype = UIO_MEM_LOGICAL;
uio_virtual_device_info.mem[0].size = 1024;
printk("[%s,%d] uio_virtual_device_info.mem[0].addr:0x%x,.size :%lu\n",\
__func__,__LINE__,uio_virtual_device_info.mem[0].addr,\
uio_virtual_device_info.mem[0].size);
if(uio_register_device(&pdev->dev, &uio_virtual_device_info))
return -ENODEV;
return 0;
}
static int uio_virtual_device_drv_remove(struct platform_device *pdev)
{
uio_unregister_device(&uio_virtual_device_info);
return 0;
}
static struct platform_driver virtual_device_drv = {
.probe = uio_virtual_device_drv_probe,
// .remove = __devexit_p(uio_virtual_device_drv_remove),
.remove = uio_virtual_device_drv_remove,
.driver = {
.name = "VIRTUAL_DEVICE",
.owner = THIS_MODULE,
}
};
static void virtual_device_remove(struct device *dev)
{
}
static struct platform_device virtual_device = {
.name = "VIRTUAL_DEVICE",
.id = -1,
.dev = {
.release = virtual_device_remove,
},
};
static int __init uio_virtual_device_init(void)
{
printk("virtual_device init ok!\n");
platform_device_register(&virtual_device);
printk("virtual_device_drv init ok!\n");
return platform_driver_register(&virtual_device_drv);
}
static void __exit uio_virtual_device_exit(void)
{
printk("Virtual_device remove ok!\n");
platform_device_unregister(&virtual_device);
printk("virtual_device_drv remove ok!\n");
platform_driver_unregister(&virtual_device_drv);
}
module_init(uio_virtual_device_init);
module_exit(uio_virtual_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZP1015");
MODULE_DESCRIPTION("Demon of UIO");
这里使用了一个虚拟的设备代替真实设备,所以模块一插入就能初始化好UIO驱动,用户太就能直接操作。
3.UIO 内核驱动的绑定
这里顺带提一下sys下pci设备的各种ID目录
/sys/bus/pci/devices/地址/vendor
/sys/bus/pci/devices/地址/device
/sys/bus/pci/devices/地址/subsystem_vendor
/sys/bus/pci/devices/地址/subsystem_device
根据id可以匹配自己想匹配的设备,这里就暂时不用真实设备了,设备的解绑和绑定都不需要。
4.UIO user驱动
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#define UIO_DEV "/dev/uio0"
#define UIO_ADDR "/sys/class/uio/uio0/maps/map0/addr"
#define UIO_SIZE "/sys/class/uio/uio0/maps/map0/size"
static char uio_addr_buf[16], uio_size_buf[16];
int main()
{
int uio_fd, addr_fd, size_fd;
int uio_size;
void* uio_addr, *access_address;
uio_fd = open(UIO_DEV, O_RDWR);
addr_fd = open(UIO_ADDR, O_RDONLY);
size_fd = open(UIO_SIZE, O_RDONLY);
if( addr_fd < 0 || size_fd < 0 || uio_fd < 0) {
fprintf(stderr, "mmap: %s\n", strerror(errno));
exit(-1);
}
read(addr_fd, uio_addr_buf, sizeof(uio_addr_buf));
read(size_fd, uio_size_buf, sizeof(uio_size_buf));
uio_addr = (void *)strtoul(uio_addr_buf, NULL, 0);
uio_size = (int)strtol(uio_size_buf, NULL, 0);
access_address = mmap(NULL, uio_size, PROT_READ | PROT_WRITE,
MAP_SHARED, uio_fd, 0);
if (access_address == (void*) -1) {
fprintf(stderr, "mmap: %s\n", strerror(errno));
exit(-1);
}
printf("The device address %p (lenth %d)\n"
"can be accessed over\n"
"logical address %p\n", uio_addr, uio_size, access_address);
return 0;
}