Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1177007
  • 博文数量: 573
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 66
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-28 16:21
文章分类

全部博文(573)

文章存档

2018年(3)

2016年(48)

2015年(522)

分类: LINUX

2015-12-07 14:15:14

/*
UIO(Userspace I/O)是运行在用户空间的I/O技术。Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可,
而UIO则是将驱动的很少一部分运行在内核空间,而在用户空间实现驱动的绝大多数功能!
使用UIO可以避免设备的驱动程序需要随着内核的更新而更新的问题!




怎么编写uio驱动详解
======================
为了用最简单的例子说明问题,我们在我们uio驱动的内核部分只映射了一块1024字节的
 逻辑内存。没有申请中断。
 这样加载上我们的驱动后,将会在/sys/class/uio/uio0/maps/map0中看到
 addr name offset size。他们分别是映射内存的起始地址, 映射内存的名字,起始地址的页内偏移, 映射内存 的大小。 
  在uio驱动的用户空间部分,我们将打开addr, size以便使用映射好的内存。
*/


/**  
* 编译驱动:    
*   Save this file name it simple.c  
*   # echo "obj-m := simple.o" > Makefile  
*   # make -Wall -C /lib/modules/`uname -r`/build M=`pwd` modules  
* 加载模块:  
*   #modprobe uio  
*   #insmod simple.ko  
*/


/*uio驱动的内核部分*/
#include  
#include  
#include  
#include /* kmalloc, kfree */


struct uio_info kpart_info =
{
.name = "kpart",
.version = "0.1",
.irq = UIO_IRQ_NONE, //没有使用中断,所以初始为UIO_IRQ_NONE=0
/*当你没有实际的硬件设备,但是,还想产生中断的话,就可以把irq设置为UIO_IRQ_CUSTOM
如果有实际的硬件设备,那么irq应该是你的硬件设备实际使用的中断号。
*/
//初始化uio_info的handler字段,那么在产生中断时,你注册的中断处理函数将会被调用。
};


static int drv_kpart_probe(struct device *dev);
static int drv_kpart_remove(struct device *dev);


/*
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};


int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;


return driver_register(&drv->driver);
}


下面的 struct device_driver uio_dummy_driver 等价于:
struct platform_driver uio_dummy_drv{
.probe = drv_kpart_probe,
.remove = drv_kpart_remove,
.driver = {
.name = "kpart",
}
};
对应的注册函数driver_register(&uio_dummy_driver);,必须使用如下注册函数替换:
platform_driver_register(&uio_dummy_drv);
*/


static struct device_driver uio_dummy_driver =
{
.name = "kpart", //这个名字必须和 platform_device 结构体中的名称一致,注册后,才会回调 probe函数。
.bus = &platform_bus_type, //内核定义的全局变量
.probe = drv_kpart_probe, //注册回调函数
.remove = drv_kpart_remove, //卸载函数
};


//回调函数
static int drv_kpart_probe(struct device *dev)
{
printk("drv_kpart_probe( %p)\n", dev);

//内存映射必须要设置的
kpart_info.mem[0].addr = (unsigned long)kmalloc(1024,GFP_KERNEL);  //该区域的地址(物理地址)


if(kpart_info.mem[0].addr == 0)
return -ENOMEM;
kpart_info.mem[0].memtype = UIO_MEM_LOGICAL;  //描述映射区的属性,逻辑内存
/*UIO_MEM_PHYS : 被映射到用户空间的是设备的物理内存
 UIO_MEM_LOGICAL : 被映射到用户空间的是逻辑内存
  UIO_MEM_VIRTUAL : 那么被映射到用户空间的是虚拟内存
*/
kpart_info.mem[0].size = 1024; //映射区的大小


/*
一个uio驱动的注册过程简单点说有两个步骤:
1. 初始化设备相关的 uio_info结构。
2. 调用uio_register_device 分配并注册一个uio设备。
*/
if(uio_register_device(dev, &kpart_info))  //uio设备向uio core的注册。
return -ENODEV;
return 0;
}


static int drv_kpart_remove(struct device *dev)
{
uio_unregister_device(&kpart_info);
return 0;
}


static struct platform_device * uio_dummy_device;


static int __init uio_kpart_init(void)
{
/*
下面的platform_device_register_simple包含2步:
1,填充一个platform_device 结构体
2,用platform_device_register(一次注册一个)或platform_add_devices(一次可以注册多个platform设备)将platform_device注册到内核.
*/
uio_dummy_device = platform_device_register_simple("kpart", -1,  NULL, 0); //建立并注册 设备 platform_device


return driver_register(&uio_dummy_driver); //注册 驱动,触发回调probe函数。
}


static void __exit uio_kpart_exit(void)
{
platform_device_unregister(uio_dummy_device);
driver_unregister(&uio_dummy_driver);
}


module_init(uio_kpart_init);
module_exit(uio_kpart_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benedikt Spranger");
MODULE_DESCRIPTION("UIO dummy driver");


/*
这个文件是我们uio驱动的内核部分。下面做下简要解释。
 一个uio驱动的注册过程简单点说有两个步骤:
   1. 初始化设备相关的 uio_info结构。
   2. 调用uio_register_device 分配并注册一个uio设备。
    
  uio驱动必须要提供并初始化的结构 uio_info, 它包含了您要注册的uio_device的重要特性。
 struct uio_info kpart_info = { 
         .name = "kpart",
         .version = "0.1",
         .irq = UIO_IRQ_NONE,//我们没有使用中断,所以初始为UIO_IRQ_NONE
 };
当你没有实际的硬件设备,但是,还想产生中断的话,就可以把irq设置为UIO_IRQ_CUSTOM,
 并初始化uio_info的handler字段,那么在产生中断时,你注册的中断处理函数将会被调用。
 如果有实际的硬件设备,那么irq应该是您的硬件设备实际使用的中断号。


 然后,在drv_kpart_probe函数(先不要管为什么在这个函数中进行这些操作)中,完成了
kpart_info.mem[0].addr, kpart_info.mem[0].memtype,
  kpart_info.mem[0].size字段的初始化。这是内存映射必须要设置的。
  其中,  kpart_info.mem[0].memtype可以是 UIO_MEM_PHYS,那么被映射到用户空间的是你
  设备的物理内存。也可以是UIO_MEM_LOGICAL,那么被映射到用户空间的是逻辑内存
  (比如使用 kmalloc分配的内存)。还可以是UIO_MEM_VIRTUAL,那么被映射到用户空间的是
  虚拟内存(比如用vmalloc分配的内存).这样就完成了uio_info结构的初始化。
  下一步,还是在drv_kpart_probe中,调用uio_register_device完成了uio设备向uio core的注册。


   下面讲一下,为什么我们的设备跟platform之类的东东扯上了关系?
   我们的驱动不存在真正的物理设备与之对应。而 Platform  驱动“自动探测“,这个特性是
  我们在没有实际硬件的情况下需要的。


   先从uio_kpart_init开始分析。
  platform_device_register_simple的调用创建了一个简单的platform设备。
  注册device_driver类型的uio_dummy_driver变量到bus。
  这里需要注意一个问题,就是 device_driver结构中的name为“kpart", 我们创建的platform设备
  名称也是"kpart"。而且先创建platform设备,后注册驱动。这是因为,创建好设备后,注册驱动时,
  驱动依靠name来匹配设备。
  之后 drv_kpart_probe就完成了uio_info的初始化和uio设备的注册。
*/












/*
这个是uio驱动的用户空间部分。
*/
#include
#include
#include
#include
#include
#include


#define UIO_DEV "/dev/uio0"  //uio_register_device成功之后,会自动生成这个文件


/*
 对设备的控制还可以通过/sys/class/uio下的各个文件的读写来完成。你注册的uio设备将会出现在该目录下。
 1,如果你的uio设备是uio0,那么映射的设备内存文件出现在 /sys/class/uio/uio0/maps/mapX,对该文件的读写就是对设备内存的读写。
*/
#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];
static char uio_size_buf[16];


int main(void)
{
int uio_fd;
int addr_fd; 
int size_fd;


int uio_size;
void * uio_addr,
void *access_address;

//打开uio设备文件
uio_fd = open(UIO_DEV, /*O_RDONLY*/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)); //读取uio设备内存映射区的起始地址
read(size_fd, uio_size_buf, sizeof(uio_size_buf)); //读取uio设备内存映射区的长度
uio_addr = (void*)strtoul(uio_addr_buf, NULL, 0);
uio_size = (int)strtol(uio_size_buf, NULL, 0);


//将uio_fd文件,映射进内存。通过mmap返回的地址addr跟驱动的内核部分进行交互
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", uio_addr, uio_size);
printf("can be accessed over\n");
printf("logical address %p\n", access_address);


//读写操作
/*
access_address = (void*)mremap(access_address, getpagesize(), uio_size + getpagesize() + 11111, MAP_SHARED);  
 
if ( access_address == (void*) -1) {  
     fprintf(stderr, "mremmap: %s\n", strerror(errno));  
     exit(-1);  
  }   
printf(">>>AFTER REMAP:"  
        "logical address %p\n", access_address);  
*/  
return 0;  
}


/*
加载uio模块
#modprobe uio


加载simple.ko
 #insmod simple.ko
 # ls /dev/ | grep uio0
    uio0
 # ls /sys/class/uio/uio0 
    ...
如果相应的文件都存在,那么加载用户空间驱动部分。
#./user_part
 The device address 0xee244400 (lenth 1024)
 can be accessed over
 logical address 0xb78d4000
*/
















阅读(2222) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~