轉自:http://blog.csdn.net/ganggexiongqi/article/details/6748103
------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