Linux 设备驱动之 UIO 机制(基本概念)

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

1. 存取设备的内存

2. 处理设备产生的中断

对于第一个任务。UIO 核心实现了mmap()能够处理物理内存(physical memory),逻辑内存(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 框架以及内核内部函数的关系。

 struct uio_portio {
    struct kobject kobj;
    struct uio_port *port;
};

/**
 * struct uio_port - description of a UIO port region
 * @name:       name of the port region for identification
 * @start:      start of port region
 * @size:       size of port region
 * @porttype:       type of port (see UIO_PORT_* below)
 * @portio:     for use by the UIO core only.
 */
struct uio_port {
    const char      *name;
    unsigned long       start;
    unsigned long       size;
    int         porttype;
    struct uio_portio   *portio;
};

/* defines for uio_port->porttype */
#define UIO_PORT_NONE   0
#define UIO_PORT_X86    1
#define UIO_PORT_GPIO   2
#define UIO_PORT_OTHER  3

  /*
  * struct uio_mem - description of a UIO memory region
 * @name:       name of the memory region for identification
 * @addr:       address of the device‘s memory
 * @size:       size of IO
 * @memtype:        type of memory addr points to
 * @internal_addr:  ioremap-ped version of addr, for driver internal use
 * @map:        for use by the UIO core only.
 */
struct uio_mem {
    const char      *name;// 内存映射的名字
    unsigned long       addr; // 内存块的地址
    unsigned long       size; //addr所指向的内存块的大小
    int         memtype; //UIO_MEM_PHYS,UIO_MEM_LOGICAL(kmalloc()),UIO_MEM_VIRTUAL( virtual memory)
    void __iomem        *internal_addr; // If you have to access this memory region from within your kernel module,
                                                               // you will want to map it internally by using something like ioremap().

    struct uio_map      *map;
};

 struct uio_map {
    struct kobject kobj;
    struct uio_mem *mem;
};

 static const struct vm_operations_struct uio_vm_ops = {
    .open = uio_vma_open,
    .close = uio_vma_close,
    .fault = uio_vma_fault,
};
 static struct device_attribute uio_class_attributes[] = {
    __ATTR(name, S_IRUGO, show_name, NULL),
    __ATTR(version, S_IRUGO, show_version, NULL),
    __ATTR(event, S_IRUGO, show_event, NULL),
    {}
};
 /* UIO class infrastructure */
static struct class uio_class = {
    .name = "uio",// /sys/class/uio
    .dev_attrs = uio_class_attributes,
};

static const struct file_operations uio_fops = {
    .owner      = THIS_MODULE,
    .open       = uio_open,
    .release    = uio_release,
    .read       = uio_read,
    .write      = uio_write,
    .mmap       = uio_mmap,
    .poll       = uio_poll,
    .fasync     = uio_fasync,
    .llseek     = noop_llseek,
};

/* Protect idr accesses */
static DEFINE_MUTEX(minor_lock);
static DEFINE_IDR(uio_idr);
//关于idr机制。參见 http://blog.csdn.net/ganggexiongqi/article/details/6737389

struct uio_device {
    struct module       *owner;
    struct device       *dev; //在__uio_register_device中初始化
    int         minor; // 次设备id号,uio_get_minor
    atomic_t        event; //中断事件计数
    struct fasync_struct    *async_queue;//该设备上的异步等待队列//
                                                               // 关于 “异步通知“ //參见LDD3第六章
    wait_queue_head_t   wait; //该设备上的等待队列,在注冊设备时(__uio_register_device)初始化
    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:        the UIO device this info belongs to
 * @name:       device name
 * @version:        device driver version
 * @mem:        list of mappable memory regions, size==0 for end of list
 * @port:       list of port regions, size==0 for end of list
 * @irq:        interrupt number or UIO_IRQ_CUSTOM
 * @irq_flags:      flags for request_irq()
 * @priv:       optional private data
 * @handler:        the device‘s irq handler
 * @mmap:       mmap operation for this uio device
 * @open:       open operation 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方法中被调用,运行用户驱动的
                                                                                       //特定操作。
};
  • 1、 函数: static int __init uio_init(void)

    功能:申请字符设备号。设备。并注冊到系统中,注冊uio_class到系统中

    调用模块:init_uio_class()

    运行流程:

    申请字符设备号。设备,并注冊到系统中,注冊uio_class到系统中 //init_uio_class

    //创建”/sys/class/uio”

  • 2、函数:uio_exit

    功能:注销uio_class,注销字符设备编号,删除设备

    调用模块:release_uio_class

    运行流程:

    注销uio_class,注销字符设备编号。删除设备 //release_uio_class

  • 3、函数:static void release_uio_class(void)

    功能:注销uio_class,注销字符设备编号,删除设备

    运行流程:

    注销uio_class//class_unregister

    注销字符设备编号。删除设备 //uio_major_cleanup

  • 4、函数:static int init_uio_class(void)

    功能:申请字符设备号。设备,并注冊到系统中。注冊uio_class到系统中

    调用模块: uio_major_init()

    class_register()

    运行流程:

    申请字符设备编号,设备,并初始化//uio_major_init

    注冊class 类型全局变量uio_class到系统//class_register

    //ls -l /sys/class 查看

  • 5、函数: static int uio_major_init(void)

    功能:申请字符设备编号,设备,并初始化

    调用模块:

    alloc_chrdev_region()

    cdev_alloc()

    kobject_set_name()

    cdev_add()

    运行流程:

    申请字符设备编号(多个)//alloc_chrdev_region

    //2^UIO_MAX_DEVICES个从设备

    //设备的名字为”uio”

    分配一个表示字符设备的cdev结构//cdev_alloc

    初始化cdev结构的file_operations类型字段//控制cdev设备的各种操作。

    // 如 open, close, read, write…

    设置cdev结构的kobj字段的name为uio //kobject_set_name

    加入字符设备到系统中 //cdev_add。调用成功后,我们的设备就“活了”

    // cat /proc/devices ,能够查看到分配到主设备号

    保存主设备号到全局变量uio_major

    保存设备指针到全局变量uio_cdev

    返回

  • 6、函数:static void uio_major_cleanup(void)

    功能:注销字符设备编号。删除设备

    调用模块:unregister_chrdev_region

    运行流程:

    注销字符设备编号//unregister_chrdev_region

    删除设备uio_cdev //cdev_del

    file_operations

  • 7、 函数:static int uio_open(struct inode *inode, struct file *filep)

    參数:inode:

    filep:

    功能:获得和次设备号关联的uio_device指针,创建一个辅助变量listener, 并调用info指向的uio_info结构中的open方法

    运行流程:

    获得保护uio_idr的锁 //mutex_lock

    从inode 结构中获取次编号 //iminor

    获得和次编号关联的uio_device指针 //idr_find 在那里进行地设置呢???

    // 在 uio_get_minor 中分配的次设备编号并设置的关联

    放弃锁 //mutex_unlock

    添加uio_device类型指针指向的模块的引用计数 //try_module_get

    分配一个uio_listener类型的listener //kmalloc

    关联listener和 uio_device 指针

    获得uio_device 指向设备的事件计数值。并存入listener //atomic_read

    把listener指针保存到filep->private_data字段

    调用uio_device的info字段指向的uio_info中的open方法//*

  • 8、函数:static int uio_release(struct inode *inode, struct file *filep)

    功能:从而调用uio_device的字段info指向的uio_info中的release方法

    释放辅助结构体listener

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针。

    利用listener指针找到指向uio_device类型结构指针

    从而调用uio_device的字段info指向的uio_info中的release方法。

    降低uio_device类型指针指向的模块的引用计数//module_put

    释放listener结构体 //kfree

  • 9、 函数:static int uio_fasync(int fd, struct file *filep, int on)

    參数:

    fd

    filep

    on : 0, 删除;非零,加入

    功能: 管理uio_device的async_queue

    调用模块:fasync_helper()

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针。

    利用listener指针找到指向uio_device类型结构指针

    设置uio_device的async_queue//fasync_helper

  • 10、函数:static unsigned int uio_poll(struct file *filep, poll_table *wait)

    功能: 使进程在传递到该系统调用的所有文件描写叙述符相应的等待队列上等待。并返回一个能否够马上无堵塞运行的位掩码

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针。

    利用listener指针找到指向uio_device类型结构指针

    推断用uio_device类型指针的info字段(uio_info类型)的irq成员不为0,则继续。

    否则,返回IO错误

    向poll_table类型的wait表中加入uio_device类型指针指向结构的wait等待队列//poll_wait

    //!!!! 注意poll_wait并不堵塞

    假设listener中的事件计数值event_count和uio_device的

    事件计数值count不一致时// uio_interrupt调用了uio_event_notify对

    //中断事件计数器增一

    返回“通常”的数据可读的位掩码

  • 11、函数:static ssize_t uio_read(struct file *filep, char __user *buf,

    size_t count, loff_t *ppos)

    功能:复制uio设备中断事件计数器的值到用户空间

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针

    利用listener指针找到指向uio_device类型结构指针

    创建一个等待队列的项 //DECLARE_WAITQUEUE

    检查确认uio设备的设备info的中断号(0)不为零

    加入本进程到uio设备的等待队列wait上 // add_wait_queue

    //由uio_interrupt调用uio_event_notify唤醒

    REP: 设置当前进程的 “可中断标志”

    检查是否有中断事件发生。

    假设有(listener中的中断事件计数值event_count)和uio设备中的中断事件

    计数器值不一致),则将设备中断计数器的值拷贝到用户空间

    并将listener中的中断事件计数值更新为设备的中断事件计数值

    把当前进程设置为TASK_RUNNING状态,

    并将当前进程从uio设备的等待队列wait上删除

    假设文件读时设置了O_NONBLOCK标志,

    那么,把当前进程设置为TASK_RUNNING状态。

    并将当前进程从uio设备的等待队列wait上删除

    返回 -EAGAIN

    检查当前进程是否有信号处理 //signal_pending

    //

    uid=20746501&do=blog&cuid=1820175">http://blog.chinaunix.net/space.php?

    uid=20746501&do=blog&cuid=1820175

    如有,把当前进程设置为TASK_RUNNING状态,

    并将当前进程从uio设备的等待队列wait上删除

    并返回 -ERESTARTSYS

    运行调度 //schedule

    JMP REP

  • 12、uio_register_device

    功能: 调用uio_info中注冊的handler中断处理函数,对设备的中断事件计数器增一并通知各读进程。有数据可读

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针

    调用 uio_device类型指针的info字段(uio_info类型)的handler

    假设属于本设备的中断,而且在handler中已经处理过

    那么对设备的中断事件计数器增一,

    并通知各读进程,有数据可读 //uio_event_notify

  • 13、函数:void uio_event_notify(struct uio_info *info)

    功能:“触发“ 一个中断事件。对设备的中断事件计数器增一,并通知各读进程,有数据可读

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针

    对中断事件计数器增一

    唤醒堵塞在设备等待队列wait上的读进程 //wake_up_interruptible

    // 该队列上的进程在uio_read中加入

    向异步等待队列async_queue发出可读信号 //kill_fasync

  • 14、 函数:static ssize_t uio_write(struct file *filep, const char __user

    *buf,size_t count, loff_t *ppos)

    功能: 读取用户空间的值,并调用uio_device注冊的irqcontrol函数

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针

    调用 uio_device类型指针的info字段(uio_info类型)的handler

    检验info字段(uio_info类型)的中断号irq

    读取从用户空间传过来的32位的值//copy_from_user

    调用info字段(uio_info类型)的irqcontrol函数。将用户空间传递过来的32位值作为參数传入。

  • 15、函数:static int uio_mmap(struct file *filep, struct vm_area_struct

    *vma)

    运行流程:

    从filep->private_data中获得uio_open中保存的listener指针

    调用 uio_device类型指针的info字段(uio_info类型)的handler

    保存uio_device类型指针到 vma 的vm_private_data

    返回映射区域的索引(比方 mapX,的X) //uio_find_mem_index

    计算实际的页数和请求的页数

    假设实际的页数小于请求的页数那么。返回-EINVAL

    假设uio设备注冊有mmap函数。那么就调用它

    当内存区域的类型为UIO_MEM_PHYS时,

    //uio_mmap_physical

    当内存区域的类型为UIO_MEM_LOGICAL、UIO_MEM_VIRTUAL时。

    为虚拟内存区域设置操作,和告诉内存不要将

    该区域交换出去。訪问计数器增一//uio_mmap_logical

时间: 2024-10-10 07:19:14

Linux 设备驱动之 UIO 机制(基本概念)的相关文章

Linux 设备驱动之 UIO 机制(二)

一个设备驱动的主要任务有两个: 1. 存取设备的内存 2. 处理设备产生的中断 对于第一个任务,UIO 核心实现了mmap()可以处理物理内存(physical memory),逻辑内存(logical memory), 虚拟内存(virtual memory).UIO驱动的编写是就不需要再考虑这些繁琐的细节. 第二个任务,对于设备中断的应答必须在内核空间进行.所以在内核空间有一小部分代码 用来应答中断和禁止中断,但是其余的工作全部留给用户空间处理. 如果用户空间要等待一个设备中断,它只需要简单

Linux 设备驱动之 UIO 机制(一)

[摘要]什么是UIO?UIO是运行在用户空间的I/O,那为什么要把I/O放在用户空间呢? 1.UIO出现的原因 第一,硬件设备可以根据功能分为网络设备,块设备,字符设备,或者根据与CPU相连的方式分为PCI设备,USB设备等.它们被不同的内核子系统支持.这些标准的设备的驱动编写较为容易而且容易维护.很容易加入主内核源码树.但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA.通常这些非标准设备的驱动被实现为字符驱动.这些驱动使用了很多内核内部函数和宏.而这些内部

Linux 设备驱动之 UIO 机制

一个设备驱动的主要任务有两个: 1. 存取设备的内存 2. 处理设备产生的中断 对于第一个任务.UIO 核心实现了mmap()能够处理物理内存(physical memory),逻辑内存(logical memory), 虚拟内存(virtual memory).UIO驱动的编写是就不须要再考虑这些繁琐的细节. 第二个任务,对于设备中断的应答必须在内核空间进行.所以在内核空间有一小部分代码 用来应答中断和禁止中断,可是其余的工作所有留给用户空间处理. 假设用户空间要等待一个设备中断,它仅仅须要简

Linux 设备驱动之 UIO 用户态驱动优缺点分析

[摘要]linux用户态的设备驱动开发:并不是所有的设备驱动程序都要在内核编写,有些情况下,在用户空间编写驱动程序能够更好地解决遇到的问题.本文对用户态驱动优缺点进行分析. 1.用户空间驱动程序的优点 1.可以和整个C库链接. 2.在驱动中可以使用浮点数,在某些特殊的硬件中,可能需要使用浮点数,而linux内核并不提供浮点数的支持.如果能在用户态实现驱动,就可以轻松解决这一问题. 3.驱动问题不会导致整个系统挂起.内核态驱动的一些错误常常导致整个系统挂起. 4.用户态的驱动调试方便. 5.可以给

linux设备驱动编写_tasklet机制(转)

在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成. 为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们需要把一些在中断处理中不是非常紧急的任务放在后面执行,而让中断处理程序尽快返回.在老版本的 linux 中通常将中断处理分为 top half handler . bottom half handler .利用 top half handler 处理中断必须处理的任务,而 bottom hal

【转】Linux设备驱动--块设备(一)之概念和框架

原文地址:Linux设备驱动--块设备(一)之概念和框架 基本概念   块设备(blockdevice) --- 是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区. 字符设备(Character device) ---是一个顺序的数据流设备,对这种设备的读写是按字符进行的,而且这些字符是连续地形成一个数据流.他不具备缓冲区,所以对这种设备的读写是实时的. 扇区(Sectors):任何块设备硬

linux设备驱动归纳总结(一)内核的相关基础概念【转】

本文转载自:http://blog.chinaunix.net/uid-25014876-id-59413.html linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 一.linux设备驱动的作用 内核:用于管理软硬件资源,并提供运行环境.如分配4G虚拟空间等. linux设备驱动:是连接硬件和内核之间的桥梁. linu

Linux设备驱动中的阻塞和非阻塞I/O

[基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足. 2.非阻塞 非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止. 当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的

linux设备驱动中的并发控制

并发指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源的访问则很容易导致竞态 linux内核中主要竞态1.多对称处理器的多个CPU  2.单CPU内进程与抢占它的进程 3.中断(硬中断.软中断.Tasklet.下半部)与进程之间访问共享内存资源的代码区称为“临界区”,临界区需要被以某种互斥机制加以保护,中断屏蔽.原子操作.自旋锁和信号量等是linux设备驱动中可采用的互斥途径. 这几个互斥的介绍: 1.中断屏蔽,这个主要用于单CPU,中断屏蔽将使得中断和进程之间的并发不再发生.使用方