一個設備驅動的主要任務有兩個:
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”2、函數:uio_exit
功能:注銷uio_class,注銷字符設備編號,刪除設備
調用模塊:release_uio_class
運行流程:
注銷uio_class,注銷字符設備編號。刪除設備 //release_uio_class3、函數:static void release_uio_class(void)
功能:注銷uio_class,注銷字符設備編號,刪除設備
運行流程:
注銷uio_class//class_unregister
注銷字符設備編號。刪除設備 //uio_major_cleanup4、函數: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_delfile_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結構體 //kfree9、 函數: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_helper10、函數: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 REP12、uio_register_device
功能: 調用uio_info中注冊的handler中斷處理函數,對設備的中斷事件計數器增一並通知各讀進程。有數據可讀
運行流程:
從filep->private_data中獲得uio_open中保存的listener指針
調用 uio_device類型指針的info字段(uio_info類型)的handler
假設屬於本設備的中斷,而且在handler中已經處理過
那么對設備的中斷事件計數器增一,
並通知各讀進程,有數據可讀 //uio_event_notify13、函數:void uio_event_notify(struct uio_info *info)
功能:“觸發“ 一個中斷事件。對設備的中斷事件計數器增一,並通知各讀進程,有數據可讀
運行流程:
從filep->private_data中獲得uio_open中保存的listener指針
對中斷事件計數器增一
喚醒堵塞在設備等待隊列wait上的讀進程 //wake_up_interruptible
// 該隊列上的進程在uio_read中加入
向異步等待隊列async_queue發出可讀信號 //kill_fasync14、 函數: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