转自:https://blog.csdn.net/u013982161/article/details/51584900

1 uio理论部分

 

1.1为什么出现了UIO?

硬件设备可以根据功能分为网络设备,块设备,字符设备,或者根据与CPU相连的方式分为PCI设备,USB设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写较为容易而且容易维护。很容易加入主内核源码树。但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA。通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护这些代码。而且这些驱动进不了主内核源码。于是就出现了用户空间I/O框架(Userspace I/O framework)。

1.2 UIO 是怎么工作的?

一个设备驱动的主要任务有两个:

1. 存取设备的内存

2. 处理设备产生的中断

对于第一个任务,UIO核心实现了mmap()可以处理物理内存(physicalmemory),逻辑内存(logical memory),虚拟内存(virtual memory)。UIO驱动的编写是就不需要再考虑这些繁琐的细节。

第二个任务,对于设备中断的应答必须在内核空间进行。所以在内核空间有一小部分代码用来应答中断和禁止中断,但是其余的工作全部留给用户空间处理。

如果用户空间要等待一个设备中断,它只需要简单的阻塞在对 /dev/uioX的read()操作上。当设备产生中断时,read()操作立即返回。UIO 也实现了poll()系统调用,你可以使用 select()来等待中断的发生。select()有一个超时参数可以用来实现有限时间内等待中断。

对设备的控制还可以通过/sys/class/uio下的各个文件的读写来完成。你注册的uio设备将会出现在该目录下。假如你的uio设备是uio0那么映射的设备内存文件出现在 /sys/class/uio/uio0/maps/mapX,对该文件的读写就是 对设备内存的读写。

如下的图描述了uio驱动的内核部分,用户空间部分,和uio 框架以及内核内部函数的关系。

详细的UIO驱动的编写可以参考 drivers/uio/下的例子,以及Documentation/DocBook/uio-howto.tmp//tmpl格式的文件可以借助 docbook-utils (debian下)工具转化为pdf或者html等格式。

1.3 UIO核心的实现 和 UIO驱动的内核部分的关系

重要的结构:

struct uio_device {

struct module      *owner;

struct device        *dev; //在__uio_register_device中初始化

int             minor; // 次设备id号,uio_get_minor

atomic_t       event; //中断事件计数
   //该设备上的异步等待队列,关于 “异步通知“参见LDD3第六章

struct fasync_struct   *async_queue;

//该设备上的等待队列,在注册设备时(__uio_register_device)初始化

wait_queue_head_t   wait;

int        vma_count;

struct uio_info     *info;// 指向用户注册的uio_info,在__uio_register_device中被赋值的

struct kobject     *map_dir;

struct kobject     *portio_dir;

};

/*

* struct uio_info - UIO device capabilities
 * @uio_dev:        theUIO device this info belongs to
 * @name:       device name
 * @version:       device driver version
 * @mem:        list ofmappable memory regions, size==0 for end of list
 * @port:       list of portregions, size==0 for end of list
 * @irq:       interrupt number or UIO_IRQ_CUSTOM
 * @irq_flags:      flagsfor request_irq()
 * @priv:       optionalprivate data
 * @handler:        thedevice's irq handler
 * @mmap:       mmapoperation for this uio device
 * @open:       openoperation for this uio device
 * @release:       release operation for this uio device
 * @irqcontrol:    disable/enable irqs when 0/1 is written to /dev/uioX
 */

struct uio_info { 
    struct uio_device   *uio_dev;// 在__uio_register_device中初始化
    const char       *name; // 调用__uio_register_device之前必须初始化
    const char       *version;//调用__uio_register_device之前必须初始化
    struct uio_mem  mem[MAX_UIO_MAPS];
    struct uio_port   port[MAX_UIO_PORT_REGIONS];
    long             irq; //分配给uio设备的中断号,调用__uio_register_device之前必须初始化
    unsigned long      irq_flags;// 调用__uio_register_device之前必须初始化
    void           *priv; //
    irqreturn_t (*handler)(int irq,struct uio_info *dev_info); //uio_interrupt中调用,用于中断处理
                                                                                        //调用__uio_register_device之前必须初始化
    int (*mmap)(struct uio_info *info,struct vm_area_struct *vma); //在uio_mmap中被调用,
                                                                                                     //执行设备打开特定操作
    int (*open)(struct uio_info *info,struct inode *inode);//在uio_open中被调用,执行设备打开特定操作
    int (*release)(struct uio_info*info, struct inode *inode);//在uio_device中被调用,执行设备关闭特定操作
    int (*irqcontrol)(struct uio_info*info, s32 irq_on);//在uio_write方法中被调用,执行用户驱动的/特定操作。

};

先看一个uio 核心和 uio设备之间关系的图,有个整体印象:

uio核心部分是一个名为"uio"的字符设备(下文称为“uio核心字符设备“)。用户驱动的内核部分使用uio_register_device向uio核心部分注册uio设备。uio核心的任务就是管理好这些注册的uio设备。这些uio设备使用的数据结构是  uio_device。而这些设备属性,比如name, open(), release()等操作都放在了uio_info结构中,用户使用 uio_register_device注册这些驱动之前要设置好uio_info。

uio核心字符设备注册的 uio_open,uio_fasync, uio_release, uio_poll, uio_read, uio_write中除了完成相关的维护工作外,还调用了注册在uio_info中的相关方法。比如,在 uio_open中调用了uio_info中注册的open方法。

那么这里有一个问题,uio核心字符设备怎么找到相关设备的uio_device结构的呢?

这就涉及到了内核的idr机制,关于该机制可以参考:

http://blog.csdn.net/ganggexiongqi/article/details/6737389

在uio.c中,有如下的定义:

staticDEFINE_IDR(uio_idr);
 /* Protect idr accesses */
static DEFINE_MUTEX(minor_lock);

在你调用uio_register_device(内部调用了__uio_register_device)注册你的uio设备时,在__uio_register_device中调用了uio_get_minor函数,在uio_get_minor函数中,利用idr机制(idr_get_new)建立了次设备号和uio_device类型指针之间的联系。而uio_device指针指向了代表你注册的uio设备的内核结构。在uio核心字符设备的打开方法,uio_open中先取得了设备的次设备号(iminor(inode)),再次利用idr机制提供的方法(idr_find)取得了对应的uio_device类型的指针。并且把该指针保存在了uio_listener结构中,以方便以后使用。

1.4 关于设备中断的处理

在__uio_register_device中,为uio设备注册了统一的中断处理函数uio_interrupt,在该函数中,调用了uio设备自己提供的中断处理函数handler(uio_info结构中)。并调用了uio_event_notify函数对uio设备的中断事件计数器增一,通知各个读进程“有数据可读”。每个uio设备的中断处理函数都是单独注册的。

关于中断计数: uio_listener

struct uio_listener {

struct uio_device *dev; //  保存uio设备的指针,便于访问
               s32 event_count; //跟踪uio设备的中断事件计数器

};

对于每一个注册的uio设备(uio_device),都关联一个这样的结构。它的作用就是跟踪每个uio设备(uio_device)的中断事件计数器值。在用户空间进行文件打开操作(open)时,与uio设备关联的uio_listener结构就被分配,指向它的指针被保存在filep指针的private_data字段以供其他操作使用。

在用户空间执行文件关闭操作时,和uio设备关联的uio_listener结构就被销毁。在uio设备注册时,uiocore会为设备注册一个通用的中断处理函数(uio_interrupt),在该函数中,会调用uio设备自身的中断处理函数(handler).中断发生时,uio_event_notify将被调用,用来对设备的中断事件计数器()增一,并通知各读进程,有数据可读。uio_poll操作判断是否有数据可读的依据就是 listener中的中断事件计数值(event_count)和uio设备中的中断事件计数器值不一致(前者小于后者)。因为listener的值除了在执行文件打开操作时被置为被赋值外,只在uio_read操作中被更新为uio设备的中断事件计数器值。

疑问1:

对于中断事件计数器,uio_device中定义为 atomic_t类型,又有
            typedef struct {
               int counter;
            }atomic_t;

需不需要考虑溢出问题?
   同样的问题存在在uio_listener的event_count字段。

关于uio_device的event字段 uio_howto中:

event: The total number of interrupts handledby the driver since the last time the device node was read.

【如果中断事件产生的频率是100MHZ的话,(2^32)/(10^8) = 42秒】counter计数器就会溢出。所以,依赖于counter的操作可能会出现问题。//补充:中断发生的频率最多为kHz不会是 Mhz,所以[]中的假设是不合理的,但是溢出会发生,而且,依赖counter值的应用可能会出现问题!!

我们可以添加一个timer,在timer处理函数中,调用uio_event_notify增加counter的值,很快会观察到溢出。<<<<<<<例子,还没有写 (^_^)

//其实,可以在我们注册的函数中,得到uio_device的指针,可以直接修改event的值。

===========关于 sysfs文件创建
   sysfs下uio相关的文件结构如下

sys  
 ├───uio  
       ├───uio0  
       │    ├───maps  
       │         ├───mapX  
       ├───uio1  
            ├───maps                          
             │   ├───mapX        
            ├───portio  
                 ├───portX

其中的uio是uio模块加载时,uio_init调用init_uio_class调用class_register注册到内核空间的。关于这个类的方法有个疑问,就是比如在show_event方法中,struct uio_device *idev = dev_get_drvdata(dev);//具体的uio设备相关的信息这个uio_device相关的信息是怎么跟 uio class联系上的?

在调用__uio_register_device注册uio设备时,通过

idev->dev =device_create(&uio_class, parent,
                 MKDEV(uio_major, idev->minor), idev,
                 "uio%d", idev->minor);
     其中,idev就是 uio_device类型的指针,它作为drvdata被传入,device_create调用了device_create调用了device_create_vargs调用了dev_set_drvdata。

这样在uio class的 show_event方法中,就可以使用struct uio_device *idev = dev_get_drvdata(dev);得到了uio设备的结构体的指针。

device_create调用完毕后在 /sys/class/uio/下就会出现代表uio设备的uioX文件夹,其中X为uio设备的次设备号。

2 UIO编写实例

 

怎么编写uio驱动详解

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

[plain] viewplaincopy
/**  
*  This is a simple demon of uio driver.  
*  Last modified by   
        09-05-2011  Joseph Yang(Yang Honggang)<ganggexiongqi@gmail.com>  
*  
* Compile:    
*   Save this file name it simple.c  
*   # echo "obj-m := simple.o" >Makefile  
*   # make -Wall -C /lib/modules/`uname-r`/build M=`pwd` modules  
* Load the module:  
*   #modprobe uio  
*   #insmod simple.ko  
*/  
  
#include <linux/module.h>  
#include <linux/platform_device.h>  
#include <linux/uio_driver.h>  
#include <linux/slab.h> /* kmalloc, kfree */ 
struct uio_info kpart_info = {  
        .name ="kpart",  
        .version ="0.1",  
        .irq = UIO_IRQ_NONE,  
};  
  
static int drv_kpart_probe(struct device *dev);  
static int drv_kpart_remove(struct device *dev);  
static struct device_driver uio_dummy_driver = {  
        .name ="kpart",  
        .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;  
        kpart_info.mem[0].size= 1024;  
  
        if(uio_register_device(dev, &kpart_info))  
               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)  
{  
        uio_dummy_device =platform_device_register_simple("kpart", -1,  
                       NULL, 0);  
  
        return driver_register(&uio_dummy_driver); 
}  
  
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的重要特性。

structuio_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设备向uiocore的注册。下面讲一下,为什么我们的设备跟platform之类的东东扯上了关系?

我们的驱动不存在真正的物理设备与之对应。而 Platform  驱动“自动探测“,这个特性是我们在没有实际硬件的情况下需要的。

先从uio_kpart_init开始分析。

1platform_device_register_simple的调用创建了一个简单的platform设备。
2注册device_driver类型的uio_dummy_driver变量到bus。

这里需要注意一个问题,就是 device_driver结构中的name为“kpart",我们创建的platform设备名称也是"kpart"。而且先创建platform设备,后注册驱动。这是因为,创建好设备后,注册驱动时,驱动依靠name来匹配设备。之后drv_kpart_probe就完成了uio_info的初始化和uio设备的注册。

---------------user_part.c ----------------------

这个是uio驱动的用户空间部分。

[plain] view plaincopy
#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(void)  
{  
  int uio_fd, addr_fd, size_fd;  
  int uio_size;  
  void* uio_addr, *access_address;  
   
  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));  
  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 beaccessed over\n"  
         "logicaladdress %p\n", uio_addr, uio_size, 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:" 
         "logicaladdress %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

http://blog.csdn.net/wenwuge_topsec/article/details/9628409

用户态驱动--UIO机制的实现【转】的更多相关文章

  1. 聊聊Linux用户态驱动设计

    序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都有各自的缺点.内核态驱动的问题是: ...

  2. Linux用户态驱动设计

    聊聊Linux用户态驱动设计   序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都 ...

  3. linuxok6410的I2C驱动分析---用户态驱动

    3  i2c-dev 3.1 概述 之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动.不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件 ...

  4. [国嵌攻略][155][I2C用户态驱动设计]

    用户态驱动模型 用户态驱动模型首先是一个应用程序,其次是在这个用户程序中通过内核调用来驱动设备. IIC通用驱动代码 IIC通用驱动程序的代码在/drivers/i2c/i2c-dev.c中.一次读操 ...

  5. Linux I2C驱动--用户态驱动简单示例

    1. Linux内核支持I2C通用设备驱动(用户态驱动:由应用层实现对硬件的控制可以称之为用户态驱动),实现文件位于drivers/i2c/i2c-dev.c,设备文件为/dev/i2c-0 2. I ...

  6. I2C用户态驱动设计

    一.用户态驱动模型 1.1 I2C通用驱动代码 i2c_dev_init: static int __init i2c_dev_init(void) { int res; printk(KERN_IN ...

  7. linux 字符驱动框架(用户态的read,write,poll是怎么操作驱动的)

    前言 这篇文章是通过对一个简单字符设备驱动的操作来解释,用户态的读写操作是怎么映射到具体设备的. 因为针对不同版本的linux内核,驱动的接口函数一直有变化,这贴出我测试的系统信息: root@ubu ...

  8. [中英对照]User-Space Device Drivers in Linux: A First Look | 初识Linux用户态设备驱动程序

    如对Linux用户态驱动程序开发有兴趣,请阅读本文,否则请飘过. User-Space Device Drivers in Linux: A First Look | 初识Linux用户态设备驱动程序 ...

  9. [windows驱动]内核态驱动架构

    1.windows驱动简介: 1.1 windows组件简介: 1.2 windows驱动类型: windows驱动分为两种基本类型: 用户态驱动在用户态下执行.它们一般提供一套win32应用程序和内 ...

随机推荐

  1. 《玩转Django2.0》读书笔记-Django配置信息

    <玩转Django2.0>读书笔记-Django配置信息 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 项目配置是根据实际开发需求从而对整个Web框架编写相应配置信息. ...

  2. Java Web之下载文件

    下载的文件,不能随便的被访问,放在外面的文件夹肯定不行,url一敲就能访问了,所以我们要放在WEB-INF文件夹里面,WEB-INF文件夹只有Servlet才能访问,我们新建一个文件夹,叫downlo ...

  3. ruby数组操作方法汇总

    1.数组定义 arr1 = [] arr2 = Array.new arr3 = ['1','2','3'] 2.输出 print arr3,"\n" #123 puts arr3 ...

  4. Jmeter测试报告生成

    Jmeter测试报告生成 本文使用的 Jmeter 版本为 apache-jmeter-3.2 1. 命令行模式将 jtl 文件转成测试图表 注意: 这种方式只适用于jmeter3.0以后的版本 1. ...

  5. luogu 2827 蚯蚓 单调队列/优先队列

    易知可利用优先队列选取最大值: 但是通过分析可知,先取出的蚯蚓分开后仍然要比后分的长,所以可直接利用单调队列找队头即可,分三个单调队列,分别找未切割,切割,切割2三种情况 #include<bi ...

  6. C#基础_MD5

    MD5加密 1创建Md5 2.开始加密,需要将字符转换为字节数组 3.返回一个加密好的字节数组 4.将字节数组中每个元素按照指定的编码格式解析成字符串 1 static void Main(strin ...

  7. Nginx下配置网站ssl实现https访问

    第一步:服务器环境,lnmp即Linux+Nginx+PHP+MySQL,本文中以我的博客为例,使用的是阿里云最低档的vps+免费的Linux服务器管理系统WDCP快速搭建的lnmp环境(同类产品还有 ...

  8. python 的基础 学习 12天,函数

    1,   *args   动态 参数,万能参数 *args就是接受实参对应的剩余的位置参数,并将其放在元组中.在定义函数时,*args代表的是聚合. def func(*args): print(ar ...

  9. JavaScript中Float类型保留两位小数

    JavaScript中Float类型保留两位小数 核心方法: num:要操作的数字     size:要保留的位数 parseFloat(num).toFixed(size); 实现代码如下:var  ...

  10. Luogu P4859「已经没有什么好害怕的了」

    以前开过一遍这题,以为很难没刚下去 今天$ review$一遍分析了一下感觉也还好 luogu 4859 题意:给定长度为$ n \leq 2000$的数组$ A,B$求完全匹配使得$A>B$的 ...