AUTHOR: Joseph Yang (杨红刚) <ganggexiongqi@gmail.com>
CONTENT: Introduction of UIO subsystem ( UIO子系统介绍 )
NOTE: linux-3.0
LAST MODIFIED:09-15-2011
-----------------------------------------------------------------------------------------------------------
Distributed and Embedded System Lab (分布式嵌入式系统实验室,兰州大学)
===============================================================
分为PCI设备,USB设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写
较为容易而且容易维护。很容易加入主内核源码树。
但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA。
通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。
而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护
这些代码。而且这些驱动进不了主内核源码。于是就出现了用户空间I/O框架(Userspace I/O framework)。
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驱动的编写可以参考 drivers/uio/下的例子,以及Documentation/DocBook/uio-howto.tmpl
//tmpl格式的文件可以借助 docbook-utils (debian下)工具转化为pdf或者html合格等。
---------3----------- “uio 核心的实现 和 uio驱动的内核部分的关系“详谈
重要的结构:
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方法中被调用,执行用户驱动的
//特定操作。
};
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中,有如下的定义:
static DEFINE_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结构中,以方便以后
使用。
----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设备注册时,uio core会为设备注册一个通用的中断处理函数(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 handled by 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相关的文件结构如下
其中的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设备的次设备号。
往下,就不再啰嗦了。希望有所帮助。
=======================================
参考:
1,2 参考了Userspace I/O drivers in a realtime context Hans J. Koch, Linutronix GmbH
3,4 参考了 uio.c 分析 http://blog.csdn.net/ganggexiongqi/article/details/6737647
CONTENT: Introduction of UIO subsystem ( UIO子系统介绍 )
NOTE: linux-3.0
LAST MODIFIED:09-15-2011
-----------------------------------------------------------------------------------------------------------
Distributed and Embedded System Lab (分布式嵌入式系统实验室,兰州大学)
===============================================================
------1------为什么出现了UIO?
分为PCI设备,USB设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写
较为容易而且容易维护。很容易加入主内核源码树。
但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA。
通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。
而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护
这些代码。而且这些驱动进不了主内核源码。于是就出现了用户空间I/O框架(Userspace I/O framework)。
---------2----------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 框架以及内核内部函数的关系。
详细的UIO驱动的编写可以参考 drivers/uio/下的例子,以及Documentation/DocBook/uio-howto.tmpl
//tmpl格式的文件可以借助 docbook-utils (debian下)工具转化为pdf或者html合格等。
---------3----------- “uio 核心的实现 和 uio驱动的内核部分的关系“详谈
重要的结构:
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方法中被调用,执行用户驱动的
//特定操作。
};
先看一个uio 核心和 uio 设备之间关系的图,有个整体印象:
Figure 2: uio_core_device
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中,有如下的定义:
static DEFINE_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结构中,以方便以后
使用。
----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设备注册时,uio core会为设备注册一个通用的中断处理函数(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 handled by 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设备的次设备号。
往下,就不再啰嗦了。希望有所帮助。
=======================================
参考:
1,2 参考了Userspace I/O drivers in a realtime context Hans J. Koch, Linutronix GmbH
3,4 参考了 uio.c 分析 http://blog.csdn.net/ganggexiongqi/article/details/6737647