字元設備驅動含有open、read、write、ioctl等函數,用於用戶層和內核之間的通信,所以當用戶要獲得內核驅動的一些數據或者發送一些控制命令,就需要使用設備驅動了。對於一些中斷類型的驅動,比如輸入子系統,用戶層不需要對其進行操作,可以不使用設備驅動。 ...
字元設備驅動含有open、read、write、ioctl等函數,用於用戶層和內核之間的通信,所以當用戶要獲得內核驅動的一些數據或者發送一些控制命令,就需要使用設備驅動了。對於一些中斷類型的驅動,比如輸入子系統,用戶層不需要對其進行操作,可以不使用設備驅動。
1.register_chrdev
說起字元設備驅動程式,肯定少不了register_chrdev函數,它在記憶體中分配了一塊區域用於字元設備驅動的熱拔插。這個區域裡面的驅動擁有相同的主設備號,不同的次設備號,但它們都指向同一個file_operations結構體。就好比USB介面,一臺電腦上有好幾個USB介面,它們的屬性都是一樣的,滑鼠接上去後左鍵右鍵功能都是相同的,這是因為它們有相同的usb_device結構體。
對於linux2.x的內核一般都是採用這種方式註冊字元設備驅動,但這種方式的缺點是同一結構體下分配了過多的字元設備驅動的介面。所以linux3.x的內核對其進行了改進,register_chrdev函數可以拆分成3給獨立的函數來對字元設備驅動進行註冊。register_chrdev = register_chrdev_region/alloc_chrdev_region + cdev_init + cdev_add。
如果主設備號已經指定了,就是定義了major = ?,就使用register_chrdev_region來開闢驅動的介面;如果讓內核自動分配major的值,則使用alloc_chrdev_region。具體的函數形式如下:
if(major){
devid = MKDEV(major,0); //次設備號
error = register_chrdev_region(devid,MAXPIN,"ledkey");
if(error<0){
printk("register_chrdev_region error!\n");
return -1;
}
}else{
error = alloc_chrdev_region(&devid,0,MAXPIN,"ledkey");
if(error<0){
printk("alloc_chrdev_region error!\n");
return -1;
}
major = MAJOR(devid);
}
cdev = cdev_alloc();
cdev_init(cdev,&key_fops);
cdev_add(cdev,devid,MAXPIN);
這裡要說一下cdev_alloc()這個函數,如果是static struct cdev *cdev這麼定義的,則使用到cdev_alloc,如果定義的是結構體而不是指針(static struct cdev cdev),就不需要使用cdev_alloc這個函數了。巨集定義MAXPIN表示對該file_operations結構體可以支持的設備驅動個數。
2. class_create
這個函數用來創建類,在類下創建具體的設備。創建設備正如英文名,是device_create(這是linux3.x的內核,對於2.x的內核使用的是class_device_create)。為了方便理解,具體的函數形式如下:
key_class = class_create(THIS_MODULE,"keyled"); //"keyled"類的名字
device_create(key_class,NULL,MKDEV(major,0),NULL,"key0");// 創建了設備/dev/key0
3. request_irq
具體形式如下:
error = request_irq(IRQ_EINT0,key_handler,IRQ_TYPE_EDGE_BOTH,"key1",NULL);
這裡要註意的是第一個參數是中斷的類型,像上面直接寫的IRQ_EINT0,這樣就啟動了外部中斷0,在Linux內核裡面就不需要再像之前的裸板程式一樣配置相關引腳為中斷模式了,這裡內核已經幫我們做好了;然後第二個參數是中斷服務函數,發生中斷之後進入這個函數。第三給參數很重要,它是中斷的觸發類型,這裡我用的是雙邊沿觸發。如果只有一個中斷,最後一個參數寫NULL就可以了,如果有多個中斷,最好是分配一個結構體,裡面記錄每個中斷的信息,這些中斷可以使用相同的中斷服務函數,根據中斷信息就可以知道是哪個中斷發生了。
比如: /*中斷*/
struct input_inode {
char *name;
unsigned int irq_type;
unsigned int pin;
int key_val;
};
static struct input_inode key[] = {
[0] = {
"L",
IRQ_EINT0,
S3C2410_GPF(0),
KEY_L},
[1] = {
"S",
IRQ_EINT2,
S3C2410_GPF(2),
KEY_S},
[2] = {
"ENTER",
IRQ_EINT11,
S3C2410_GPG(3),
KEY_ENTER},
};
for(i = 0; i < 3; i++){
error = request_irq(key[i].irq_type,key_irq,IRQ_TYPE_EDGE_BOTH,key[i].name,&key[i]);
if(error){
printk("couldn't get irq!\n");
return -1;
}
}
而發生中斷後,進入static irqreturn_t key_irq(int irq, void *dev_id),它的dev_id正是request_irq的最後一個參數&key[i],通過(&key[i])->pin我們就能知道到底是GPF0,還是GPF2或者是GPG3發生了中斷,也就是外部中斷0,2或者11處發生了中斷。
4 .時間函數
首先定義一個timer_list結構體static struct timer_list key_time。
再初始化函數裡加上 init_timer(&key_time);
key_time.function = key_time_function;
add_timer(&key_time);
然後中斷服務函數裡面加上 mod_timer(&key_time,jiffies + HZ/100); //延遲10ms,延遲過程中斷仍然可以被觸發 原先在中斷服務函數裡面實現的代碼放到時間函數key_time_function裡面。
對於 mod_timer,+HZ表示延時1秒,/100就是10ms了。
時間函數可以用於中斷,去按鍵抖動。中斷里啟動時間函數後中斷函數就結束了,它不管時間函數是否執行完成。這樣在延時10ms的過程中中斷又可以被觸發,這就去了按鍵抖動,這是它與傳統的delay函數的區別。它只要告訴內核多長時間後啟動時間函數,mod_timer這個函數就執行完了,中斷服務函數就執行完了,剩下的就等過完這段時間後內核啟動時間函數key_time_function,然後執行裡面的內容就可以了。