【摘要】什么是UIO?UIO是运行在用户空间的I/O,那为什么要把I/O放在用户空间呢?
第一,硬件设备可以根据功能分为网络设备,块设备,字符设备,或者根据与CPU相连的方式分为PCI设备,USB设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写较为容易而且容易维护。很容易加入主内核源码树。但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA。通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护这些代码。像这种设备如果把驱动放入Linux内核,不但增大了内核的负担而且还很少使用,更没有人愿意免费的花费大量精力去维护这样的驱动,使用UIO使驱动的开发可以利用所有的用户空间的应用程序开发工具和库,而且当系统内核发生变化时,只要更改UIO框架与其他内核程序交互的接口即可,不需要更改UIO框架下面的driver。
第二,内核驱动的开发必须使用C语言加上少量汇编代码。uio可以使用C++,Java …等高级语言极大的方便了开发。我们可以发现,有很多的卡的驱动在内核空间已经有实现,这样,我们可以参考已经存在的代码,极大的提高的开发的速度,和降低了开发成本。而且内核驱动的调试会比用户空间程序调试复杂很多。我们经常遇到死机,涉及到多个子系统,棘手。放在用户空间的话如果驱动程序死了,并不影响系统的正常运行并且方便了我们的开发。
通过UIO的运行原理图可以看出,用户空间下的驱动程序比运行在内核空间的驱动要多得多,UIO框架下运行在内核空间的驱动程序所做的工作很简单,常做的只有两个:分配和记录设备需要的资源和注册uio设备和必须在内核空间实现的小部分中断应答函数,经过实践表明后面的工作也是可以省略的!我们认为uio内核空间的程序所做的越少越好,在用户空间能完成的我们就不需要放在内核空间做(比如说响应中断),这样假如内核有变化,uio框架中的驱动维护也是比较简单的!对于用户空间的驱动程序,我们还可以集成到某款应用软件中,这样也是可行的,上面已经说过了,因为使用uio的设备一般比较少见,所以可以作出这类的驱动也可以针对某款或者一类设备作出应用程序集成了驱动即可!
UIO的少量运行在内核空间的驱动所做的工作有哪些呢?
1.分配和记录设备需要的资源和注册uio设备
在设备的探测函数中:
1. - 使能设备
2. - 申请资源
3. - 读取并记录配置信息
4. - 注册uio设备// uio_register_device()
2.必须*在内核空间实现的小部分中断应答函数
用户空间的关键操作
5. 判断是否产生了硬件中断
6. 响应硬件中断
int32_t irq_count;
int fd = open("/dev/uio0", O_RDWR);
/* Map the register regions to proccess's virtual memspace */
void * access = mmap(NULL, 4096,
// 寄存器的读写操作,可用过普通内存读写的方式完成
PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);// [1]
while (read(fd, &irq_count, 4) == 4) {[2]
printf("Interrupt number %d\n", irq_count);
}
首先需要内核支持uio机制,make menuconfig,添加uio驱动机制,如下图所示:
/** * This is a simple demon of uio driver. * @Author: ZP1015 * * @Copyright:SCUT. * * @Function:Demon of uio driver. * * @Creat:2015-12-30 * * @Modify: **/
#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,
};
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),
.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");
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.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;
}