您的位置:首页 > 运维架构 > Linux

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

2016-12-20 15:19 495 查看
一个设备驱动的主要任务有两个: 

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

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 

//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




2
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: