热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

一个设备驱动的主要任务有两个:1.存取设备的内存2.处理设备产生的中断对于第一个任务,UIO核心实现了mmap()可以处理物理内存(physicalmemory),逻辑内存(l

一个设备驱动的主要任务有两个:
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”


    1. 函数:uio_exit
      参数:
      返回值:
      功能:注销uio_class,注销字符设备编号,删除设备
      调用模块:release_uio_class
      执行流程:
      注销uio_class,注销字符设备编号,删除设备 //release_uio_class

    2. 函数:static void release_uio_class(void)
      参数:
      返回值:
      功能:注销uio_class,注销字符设备编号,删除设备
      调用模块:
      执行流程:
      注销uio_class//class_unregister
      注销字符设备编号,删除设备 //uio_major_cleanup
  2. 函数: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 查看

  3. 函数: 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

    返回

  4. 函数: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方法//*

  1. 函数:static int uio_release(struct inode *inode, struct file *filep)
    参数:inode
    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

  2. 函数: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

    1. 函数:static unsigned int uio_poll(struct file *filep, poll_table *wait)
      参数: filep
      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对
      //中断事件计数器增一
      返回“通常”的数据可读的位掩码

    2. 函数:static ssize_t uio_read(struct file *filep, char __user *buf,
      size_t count, loff_t *ppos)
      参数:
      filep
      buf
      count
      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
      //http://blog.chinaunix.net/space.php?uid=20746501&do=blog&cuid=1820175
      如有,把当前进程设置为TASK_RUNNING状态,
      并将当前进程从uio设备的等待队列wait上删除
      并返回 -ERESTARTSYS
      执行调度 //schedule
      JMP REP

12.
函数:static irqreturn_t uio_interrupt(int irq, void *dev_id) 被谁调用呢??? __uio_register_device中设定
参数: irq
dev_id
返回值:
功能: 调用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位值
作为参数传入。


  1. 函数: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

版权声明:本文为博主原创文章,未经博主允许不得转载。

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


推荐阅读
  • 安全3AAuthentication:认证Authorzation:授权Accouting|Audition:审计用户管理用户:UID:0,不一定是root,root的uid非0时 ... [详细]
  • packagetest;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOE ... [详细]
  • 抓取百万知乎用户设计之实体设计
    一.实体的关系实体是根据返回的Json数据来设计的教育经历方面用户可以有很多教育经理,USER和education是一对多的关系,一个education对应一个education一 ... [详细]
  • Xib九宫格应用管理使用xib封装一个自定义view的步骤1新建一个继承UIView的自定义view,假设类名叫做(AppView)2新建一个AppView.xib文件来描述 ... [详细]
  • 看这里,教你如何快速将pdf文件翻译成中文
    因为网上下载的PDF资料,往往掺杂着一些英文,所以中英文翻译是一件很平常的事,毕竟不是每个人的英文都那么好,轻轻松松的就能够看完一篇英文的文件,那么,我们就要寻找翻译工具来帮助我们 ... [详细]
  • 作业迁移
    背景:数据库服务器更换,1、数据库迁移(BACKUPRESTORE);2、数据库登录名用户迁移(注意孤立用户);3、作业迁移数据库迁移,备份数据库、拷贝备份文件到新服务器,还原数据 ... [详细]
  • Linux     系统安装
    Linux系统安装linux系统安装准备工作电脑、u盘、光盘、网络、硬盘主要使用光盘、网络虚拟化软件vmwarevi ... [详细]
  • rbac 4表 常规设计
    rbac4表常规设计设计模型:1、管理员表(users)Schema::create('users',function(Blueprint$table){$tabl ... [详细]
  • iOS之富文本
    之前做项目时遇到一个问题:使用UITextView显示一段电影的简介,由于字数比较多,所以字体设置的很小,行间距和段间距也很小,一大段文字挤在一起看起来很别扭,想要把行间距调大,结 ... [详细]
  • 【自制小工具】代码生成器
    【自制小工具】代码生成器陆陆续续接触过好几款代码生成工具,发现确实好用,但都会有那么点不完善的地方,所以索性就自己做一个吧。界面非常简单,反正是自己用的,简单点用起来也方便上图:左 ... [详细]
  • 论文阅读及复现 | Improved Semantic Representations From TreeStructured Long ShortTerm Memory Networks
    两种形式的LSTM变体Child-SumTree-LSTMsN-aryTree-LSTMshttps:paperswithcode.compaperimproved-semanti ... [详细]
  • 读书这件事
    没事晒下自己的借书清单。60多本了,不知道自己一本子能读多少本?可惜的就是没写读书笔记,都是一些泛读。图书馆要 ... [详细]
  • phpstorm使用和配置技巧
    1.使用phpstorm的过程中,有时光标不小心变成了方块状,怎么修复回来呢?见下图,去掉“Useblockcare ... [详细]
  • vector:在vc6中,如果要镶嵌使用vector,如vector,后面的两个应该用,空格隔开,否则被编译器认为是移位符string::npos的值为 ... [详细]
  • Spark 贝叶斯分类算法
    一、贝叶斯定理数学基础我们都知道条件概率的数学公式形式为即B发生的条件下A发生的概率等于A和B同时发生的概率除以B发生的概率。根据此公式变换,得到贝叶斯公式:即贝叶斯定律是关于随机 ... [详细]
author-avatar
对方尽快
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有