分類:
Linux 內(nèi)核
Linux 基礎(chǔ)學習
Linux 驅(qū)動
移植
實時操作系統(tǒng)
雜項
2011-09-04 22:40
259人閱讀
評論(0)
收藏
舉報
AUTHOR: Joseph Yang (楊紅剛) <ganggexiongqi@gmail.com>
CONTENT: Introduction of UIO subsystem ( UIO子系統(tǒng)介紹 ) NOTE: linux-3.0 LAST MODIFIED:09-15-2011 ----------------------------------------------------------------------------------------------------------- Distributed and Embedded System Lab (分布式嵌入式系統(tǒng)實驗室,蘭州大學) =============================================================== ------1------為什么出現(xiàn)了UIO?
分為PCI設(shè)備,USB設(shè)備等。它們被不同的內(nèi)核子系統(tǒng)支持。這些標準的設(shè)備的驅(qū)動編寫 較為容易而且容易維護。很容易加入主內(nèi)核源碼樹。 但是,又有很多設(shè)備難以劃分到這些子系統(tǒng)中,比如I/O卡,現(xiàn)場總線接口或者定制的FPGA。 通常這些非標準設(shè)備的驅(qū)動被實現(xiàn)為字符驅(qū)動。這些驅(qū)動使用了很多內(nèi)核內(nèi)部函數(shù)和宏。 而這些內(nèi)部函數(shù)和宏是變化的。這樣驅(qū)動的編寫者必須編寫一個完全的內(nèi)核驅(qū)動,而且一直維護 這些代碼。而且這些驅(qū)動進不了主內(nèi)核源碼。于是就出現(xiàn)了用戶空間I/O框架(Userspace I/O framework)。 ---------2----------UIO 是怎么工作的?
1. 存取設(shè)備的內(nèi)存 2. 處理設(shè)備產(chǎn)生的中斷 對于第一個任務,UIO 核心實現(xiàn)了mmap()可以處理物理內(nèi)存(physical memory),邏輯內(nèi)存(logical memory), 虛擬內(nèi)存(virtual memory)。UIO驅(qū)動的編寫是就不需要再考慮這些繁瑣的細節(jié)。 第二個任務,對于設(shè)備中斷的應答必須在內(nèi)核空間進行。所以在內(nèi)核空間有一小部分代碼 用來應答中斷和禁止中斷,但是其余的工作全部留給用戶空間處理。 如果用戶空間要等待一個設(shè)備中斷,它只需要簡單的阻塞在對 /dev/uioX的read()操作上。 當設(shè)備產(chǎn)生中斷時,read()操作立即返回。UIO 也實現(xiàn)了poll()系統(tǒng)調(diào)用,你可以使用 select()來等待中斷的發(fā)生。select()有一個超時參數(shù)可以用來實現(xiàn)有限時間內(nèi)等待中斷。 對設(shè)備的控制還可以通過/sys/class/uio下的各個文件的讀寫來完成。你注冊的uio設(shè)備將會出現(xiàn)在該目錄下。 假如你的uio設(shè)備是uio0那么映射的設(shè)備內(nèi)存文件出現(xiàn)在 /sys/class/uio/uio0/maps/mapX,對該文件的讀寫就是 對設(shè)備內(nèi)存的讀寫。 如下的圖描述了uio驅(qū)動的內(nèi)核部分,用戶空間部分,和uio 框架以及內(nèi)核內(nèi)部函數(shù)的關(guān)系。
詳細的UIO驅(qū)動的編寫可以參考 drivers/uio/下的例子,以及Documentation/DocBook/uio-howto.tmpl //tmpl格式的文件可以借助 docbook-utils (debian下)工具轉(zhuǎn)化為pdf或者html合格等。 ---------3----------- “uio 核心的實現(xiàn) 和 uio驅(qū)動的內(nèi)核部分的關(guān)系“詳談 重要的結(jié)構(gòu): struct uio_device { struct module *owner; struct device *dev; //在__uio_register_device中初始化 int minor; // 次設(shè)備id號,uio_get_minor atomic_t event; //中斷事件計數(shù) struct fasync_struct *async_queue;//該設(shè)備上的異步等待隊列// // 關(guān)于 “異步通知“ //參見LDD3第六章 wait_queue_head_t wait; //該設(shè)備上的等待隊列,在注冊設(shè)備時(__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; // 調(diào)用__uio_register_device之前必須初始化 const char *version; //調(diào)用__uio_register_device之前必須初始化 struct uio_mem mem[MAX_UIO_MAPS]; struct uio_port port[MAX_UIO_PORT_REGIONS]; long irq; //分配給uio設(shè)備的中斷號,調(diào)用__uio_register_device之前必須初始化 unsigned long irq_flags;// 調(diào)用__uio_register_device之前必須初始化 void *priv; // irqreturn_t (*handler)(int irq, struct uio_info *dev_info); //uio_interrupt中調(diào)用,用于中斷處理 // 調(diào)用__uio_register_device之前必須初始化 int (*mmap)(struct uio_info *info, struct vm_area_struct *vma); //在uio_mmap中被調(diào)用, // 執(zhí)行設(shè)備打開特定操作 int (*open)(struct uio_info *info, struct inode *inode);//在uio_open中被調(diào)用,執(zhí)行設(shè)備打開特定操作 int (*release)(struct uio_info *info, struct inode *inode);//在uio_device中被調(diào)用,執(zhí)行設(shè)備打開特定操作 int (*irqcontrol)(struct uio_info *info, s32 irq_on);//在uio_write方法中被調(diào)用,執(zhí)行用戶驅(qū)動的 //特定操作。 }; 先看一個uio 核心和 uio 設(shè)備之間關(guān)系的圖,有個整體印象:
Figure 2: uio_core_device
uio核心部分是一個名為"uio"的字符設(shè)備(下文稱為“uio核心字符設(shè)備“)。用戶驅(qū)動的內(nèi)核部分 使用uio_register_device向uio核心部分 注冊uio設(shè)備。uio 核心的任務就是管理好這些注冊的uio 設(shè)備。這些uio設(shè)備使用的數(shù)據(jù)結(jié)構(gòu)是 uio_device。而這些設(shè)備屬性,比如name, open(), release()等操作都放在了uio_info結(jié)構(gòu)中,用戶使用 uio_register_device注冊這些驅(qū)動之前 要設(shè)置好uio_info。 uio核心字符設(shè)備注冊的 uio_open uio_fasync uio_release uio_poll uio_read uio_write 中除了完成相關(guān)的維護工作外,還調(diào)用了注冊在uio_info中的相關(guān)方法。比如,在 uio_open中調(diào)用了uio_info中注冊的open方法。 那么這里有一個問題,uio核心字符設(shè)備怎么找到相關(guān)設(shè)備的uio_device結(jié)構(gòu)的呢? 這就涉及到了內(nèi)核的idr機制,關(guān)于該機制可以參考: http://blog.csdn.net/ganggexiongqi/article/details/6737389 在uio.c中,有如下的定義: static DEFINE_IDR(uio_idr); /* Protect idr accesses */ static DEFINE_MUTEX(minor_lock); 在你調(diào)用uio_register_device(內(nèi)部調(diào)用了__uio_register_device)注冊你的uio 設(shè)備時, 在__uio_register_device中調(diào)用了uio_get_minor函數(shù),在uio_get_minor函數(shù)中,利用 idr機制(idr_get_new)建立了次設(shè)備號和uio_device類型指針之間的聯(lián)系。而uio_device指針 指向了代表你注冊的uio設(shè)備的內(nèi)核結(jié)構(gòu)。在uio核心字符設(shè)備的打開方法,uio_open中 先取得了設(shè)備的次設(shè)備號(iminor(inode)),再次利用idr機制提供的方法(idr_find)取得了 對應的uio_device類型的指針。并且把該指針保存在了uio_listener結(jié)構(gòu)中,以方便以后 使用。 ----4---關(guān)于設(shè)備中斷的處理 在__uio_register_device中,為uio設(shè)備注冊了統(tǒng)一的中斷處理函數(shù)uio_interrupt, 在該函數(shù)中,調(diào)用了uio設(shè)備自己提供的中斷處理函數(shù)handler(uio_info結(jié)構(gòu)中)。 并調(diào)用了uio_event_notify函數(shù)對uio設(shè)備的中斷事件計數(shù)器增一, 通知各個讀進程 “有數(shù)據(jù)可讀”。每個uio設(shè)備的中斷處理函數(shù)都是單獨注冊的。 關(guān)于中斷計數(shù): uio_listener struct uio_listener { struct uio_device *dev; // 保存uio設(shè)備的指針,便于訪問 s32 event_count; //跟蹤uio設(shè)備的中斷事件計數(shù)器 }; 對于每一個注冊的uio 設(shè)備(uio_device), 都關(guān)聯(lián)一個這樣的結(jié)構(gòu)。 它的作用就是跟蹤每個uio設(shè)備(uio_device)的中斷事件計數(shù)器值。 在用戶空間進行文件打開操作(open)時,與uio設(shè)備關(guān)聯(lián)的uio_listener結(jié)構(gòu)就被分配, 指向它的指針被保存在filep指針的private_data字段以供其他操作使用。 在用戶空間執(zhí)行文件關(guān)閉操作時,和uio設(shè)備關(guān)聯(lián)的uio_listener結(jié)構(gòu)就被銷毀。 在uio設(shè)備注冊時,uio core會為設(shè)備注冊一個通用的中斷處理函數(shù)(uio_interrupt), 在該函數(shù)中,會調(diào)用uio設(shè)備自身的中斷處理函數(shù)(handler). 中斷發(fā)生時, uio_event_notify將被調(diào)用,用來對設(shè)備的中斷事件計數(shù)器()增一,并通知各讀進程, 有數(shù)據(jù)可讀。 uio_poll 操作判斷是否有數(shù)據(jù)可讀的依據(jù)就是 listener中的中斷事件計數(shù)值 (event_count)和uio設(shè)備中的中斷事件計數(shù)器值不一致(前者小于后者)。因為 listener的值除了在執(zhí)行文件打開操作時被置為被賦值外,只在uio_read操作中 被更新為uio設(shè)備的中斷事件計數(shù)器值。 疑問1: 對于中斷事件計數(shù)器,uio_device中定義為 atomic_t 類型,又有 typedef struct { int counter; } atomic_t; 需不需要考慮溢出問題? 同樣的問題存在在uio_listener的event_count字段。 關(guān)于uio_device的event字段 uio_howto中: event: The total number of interrupts handled by the driver since the last time the device node was read. 【如果中斷事件產(chǎn)生的頻率是100MHZ的話,(2^32)/(10^8) = 42 秒 】counter計數(shù)器就會 溢出。所以,依賴于counter的操作可能會出現(xiàn)問題。//補充:中斷發(fā)生的頻率最多為kHz不會是 Mhz,所以[]中的假設(shè)是不合理的,但是溢出會發(fā)生,而且,依賴counter值的應用可能會出現(xiàn)問題??! 我們可以添加一個timer,在timer 處理函數(shù)中,調(diào)用uio_event_notify增加counter的值, 很快會觀察到溢出。<<<<<<< 例子,還沒有寫 (^_^) //其實,可以在我們注冊的函數(shù)中,得到uio_device的指針,可以直接修改event的值。 ===========關(guān)于 sysfs文件創(chuàng)建 sysfs下uio相關(guān)的文件結(jié)構(gòu)如下
其中的uio是uio模塊加載時,uio_init調(diào)用init_uio_class調(diào)用class_register注冊到內(nèi)核空間的。 關(guān)于這個類的方法有個疑問,就是比如在show_event方法中, struct uio_device *idev = dev_get_drvdata(dev);//具體的uio設(shè)備相關(guān)的信息 這個uio_device相關(guān)的信息是怎么跟 uio class聯(lián)系上的? 在調(diào)用__uio_register_device注冊uio設(shè)備時,通過 idev->dev = device_create(&uio_class, parent, MKDEV(uio_major, idev->minor), idev, "uio%d", idev->minor); 其中,idev就是 uio_device類型的指針,它作為drvdata被傳入, device_create調(diào)用了device_create調(diào)用了device_create_vargs調(diào)用了dev_set_drvdata。 這樣在uio class的 show_event方法中,就可以使用 struct uio_device *idev = dev_get_drvdata(dev); 得到了uio設(shè)備的結(jié)構(gòu)體的指針。 device_create調(diào)用完畢后在 /sys/class/uio/下就會出現(xiàn) 代表uio設(shè)備的uioX文件夾, 其中X為uio設(shè)備的次設(shè)備號。 往下,就不再啰嗦了。希望有所幫助。 ======================================= 參考: 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 |
|