嵌入式Linux驅動程序開發node
1.設備驅動程序的概念... 2linux
3.1. file_operations結構體... 5dom
3.2.inode{}和file{}結構體... 5async
系統調用是操做系統內核和應用程序之間的接口,設備驅動程序是操做系統內核和機器硬件之間的接口。設備驅動程序爲應用程序屏蔽了硬件的細節,這樣在應用程序看來,硬件設備只是一個設備文件,應用程序能夠象操做普通文件同樣對硬件設備進行操做。設備驅動程序是內核的一部分。
Linux將設備主要分紅兩大類:一類是塊設備,相似磁盤以記錄塊或扇區爲單位,成塊進行輸入/輸出的設備;另外一類是字符設備,相似鍵盤以字符爲單位,逐個進行輸入/輸出的設備。網路設備是介於塊設備和字符設備之間的一種特殊設備。
塊設備接口僅支持面向塊的I/O操做,全部I/O操做都經過在內核地址空間中的I/O緩衝區進行,它能夠支持隨機存取的功能。文件系統一般都創建在塊設備上。
字符設備接口支持面向字符的I/O操做,因爲它們不通過系統的快速緩存,因此它們負責管理本身的緩衝區結構。字符設備接口只支持順序存取的功能,通常不能進行任意長度的I/O請求,而是限制I/O請求的長度必須是設備要求的基本塊長的倍數。
設備驅動程序實際是處理和操做硬件控制器的軟件,從本質上講,是內核中具備最高特權級的、駐留內存的、可共享的底層硬件處理例程。驅動程序是內核的一部分,是操做系統內核與硬件設備的直接接口,驅動程序屏蔽了硬件的細節,完成如下功能:
— 對設備初始化和釋放;
— 對設備進行管理,包括實時參數設置,以及提供對設備的操做接口;
— 讀取應用程序傳送給設備文件的數據或者回送應用程序請求的數據;
— 檢測和處理設備出現的錯誤。
Linux操做系統將全部的設備所有當作文件,並經過文件的操做界面進行操做。對用戶程序而言,設備驅動程序隱藏了設備的具體細節,對各類不一樣設備提供了一致的接口,通常來講,是把設備映射爲一個特殊的設備文件,用戶程序能夠像對其餘文件同樣對此設備文件進行操做。這意味着:
— 因爲每個設備至少由文件系統的一個文件表明,於是都有一個「文件名」。
— 應用程序一般能夠經過系統調用open()打開設備文件,創建起與目標設備的鏈接。
— 打開了表明着目標設備的文件,即創建起與設備的鏈接後,能夠經過read()、write()、ioctl()等常規的文件操做對目標設備進行操做。
設備文件的屬性由三部分信息組成:第一部分是文件的類型,第二部分是一個主設備號,第三部分是一個次設備號。其中類型和主設備號結合在一塊兒唯一地肯定了設備文件驅動程序及其界面,而次設備號則說明目標設備是同類設備中的第幾個。
因爲Linux 中將設備當作文件處理,因此對設備進行操做的調用格式與對文件的操做相似,主要包括open()、read()、write()、ioctl()、close()等。應用程序發出系統調用命令後,會從用戶態轉到核心態,經過內核將open()這樣的系統調用轉換成對物理設備的操做。
處理器與外設之間傳輸數據的控制方式一般有3種:查詢方式、中斷方式和直接內存存取(DMA)方式。
設備驅動程序經過設備的I/O端口空間,以及存儲器空間完成數據的交換。例如,網卡通常將本身的內部寄存器映射爲設備的I/O端口,而顯示卡則利用大量的存儲器空間做爲視頻信息的存儲空間。利用這些地址空間,驅動程序能夠向外設發送指定的操做指令。一般來說,因爲外設的操做耗時較長,所以,當處理器實際執行了操做指令以後,驅動程序可採用查詢方式等待外設完成操做。
查詢方式白白浪費了大量的處理器時間,而中斷方式纔是多任務操做系統中最有效利用處理器的方式。當CPU進行主程序操做時,外設的數據已存入端口的數據輸入寄存器,或端口的數據輸出寄存器已空,此時由外設經過接口電路向CPU發出中斷請求信號。CPU在知足必定條件下,暫停執行當前正在執行的主程序,轉入執行相應可以進行輸入/輸出操做的子程序,待輸入/輸出操做執行完畢以後,CPU再返回並繼續執行原來被中斷的主程序。這樣,CPU就避免了把大量時間耗費在等待、查詢外設狀態的操做上,使其工做效率得以大大提升。中斷方式的原理示意圖如圖6.1所示。
利用中斷,系統和設備之間能夠經過設備驅動程序傳送數據,可是,當傳送的數據量很大時,由於中斷處理上的延遲,利用中斷方式的效率會大大下降。而直接內存訪問(DMA)能夠解決這一問題。DMA可容許設備和系統內存間在沒有處理器參與的狀況下傳輸大量數據。設備驅動程序在利用DMA以前,須要選擇DMA通道並定義相關寄存器,以及數據的傳輸方向,即讀取或寫入,而後將設備設定爲利用該DMA通道傳輸數據。設備完成設置以後,能夠當即利用該DMA通道在設備和系統的內存之間傳輸數據,傳輸完畢後產生中斷以便通知驅動程序進行後續處理。在利用DMA進行數據傳輸的同時,處理器仍然能夠繼續執行指令。
設備驅動程序流程圖
在系統內部,I/O設備的存取經過一組固定的入口點來進行,入口點也能夠理解爲設備的句柄,就是對設備進行操做的基本函數。字符型設備驅動程序提供以下幾個入口點:
— open入口點。打開設備準備I/O操做。對字符設備文件進行打開操做,都會調用設備的open入口點。open子程序必須對將要進行的I/O操做作好必要的準備工做,如清除緩衝區等。若是設備是獨佔的,即同一時刻只能有一個程序訪問此設備,則open子程序必須設置一些標誌以表示設備處於忙狀態。
— close入口點。關閉一個設備。當最後一次使用設備完成後,調用close子程序。獨佔設備必須標記設備方可再次使用。
— read入口點。從設備上讀數據。對於有緩衝區的I/O操做,通常是從緩衝區裏讀數據。對字符設備文件進行讀操做將調用read子程序。
— write入口點。往設備上寫數據。對於有緩衝區的I/O操做,通常是把數據寫入緩衝區裏。對字符設備文件進行寫操做將調用write子程序。
— ioctl入口點。執行讀、寫以外的操做。
— select入口點。檢查設備,看數據是否可讀或設備是否可用於寫數據。select系統調用在檢查與設備文件相關的文件描述符時使用select入口點。
struct file_operations {
structmodule *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char *, size_t, loff_t *);
ssize_t(*write) (struct file *, const char *, size_t, loff_t *);
int(*readdir) (struct file *, void *, filldir_t);
unsignedint (*poll) (struct file *, struct poll_table_struct *);
int(*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap)(struct file *, struct vm_area_struct *);
int (*open)(struct inode *, struct file *);
int(*flush) (struct file *);
int(*release) (struct inode *, struct file *);
int(*fsync) (struct file *, struct dentry *, int datasync);
int(*fasync) (int, struct file *, int);
int (*lock)(struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsignedlong (*get_unmapped_area)(
struct file*,
unsignedlong,
unsignedlong,
unsignedlong,
unsignedlong
);
};
— lseek,移動文件指針的位置,只能用於能夠隨機存取的設備。
— read,進行讀操做,buf爲存放讀取結果的緩衝區,count爲所要讀取的數據長度。
— write,進行寫操做,與read相似。
— select,進行選擇操做。
— ioctl,進行讀、寫之外的其餘操做。
— mmap,用於把設備的內容映射到地址空間,通常只有塊設備驅動程序使用。
— open,打開設備進行I/O操做。返回0表示成功,返回負數表示失敗。
— release,即close操做
inode數據結構體提供關於特別設備文件的信息。file結構體主要是與文件系統對應的設備驅動程序使用。
struct file主要用於與文件系統相關的設備驅動程序,可提供關於被打開的文件的信息,定義以下:
struct file {
struct list_head f_list;
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
unsigned long f_reada,f_ramax, f_raend, f_ralen, f_rawin;
struct fown_struct f_owner;
unsigned int f_uid,f_gid;
int f_error;
unsigned long f_version;
/* needed for tty driver, and maybe others */
void *private_data;
/* preallocated helper kiobuf to speedup O_DIRECT */
struct kiobuf *f_iobuf;
long f_iobuf_lock;
};
在用戶本身的驅動程序中,首先要根據驅動程序的功能,完成file_operations結構中函數的實現。不須要的函數接口能夠直接在file_operations結構中初始化爲NULL。file_operations中的變量會在驅動程序初始化時,註冊到系統內部。每一個進程對設備的操做,都會根據主次設備號,轉換成對file_operations結構的訪問。
設備的驅動程序在加載的時候首先須要調用入口函數init_module(),該函數最重要的一個工做就是向內核註冊該設備,對於字符設備調用register_chrdev()完成註冊。register_chrdev 的定義爲:int register_chrdev(unsignedint major, const char *name, struct file_ operations *fops);
其中,major是爲設備驅動程序向系統申請的主設備號,若是爲0,則系統爲此驅動程序動態分配一個主設備號。name是設備名,fops是對各個調用的入口點說明。此函數返回0時表示成功;返回-EINVAL,表示申請的主設備號非法,主要緣由是主設備號大於系統所容許的最大設備號;返回-EBUSY,表示所申請的主設備號正在被其餘設備程序使用。若是動態分配主設備號成功,此函數將返回所分配的主設備號。若是register_chrdev()操做成功,設備名就會出如今/proc/dvices文件中。
Linux在/dev目錄中爲每一個設備創建一個文件,用ls –l命令列出函數返回值,若小於0,則表示註冊失敗;返回0或者大於0的值表示註冊成功。註冊之後,Linux將設備名與主、次設備號聯繫起來。當有對此設備名的訪問時,Linux經過請求訪問的設備名獲得主、次設備號,而後把此訪問分發到對應的設備驅動,設備驅動再根據次設備號調用不一樣的函數。
當設備驅動模塊從Linux內核中卸載,對應的主設備號必須被釋放。字符設備在cleanup_module()函數中調用unregister_chrdev()來完成設備的註銷。unregister_chrdev()的定義爲:int unregister_chrdev(unsignedint major, const char *name);
此函數的參數爲主設備號major和設備名name。Linux內核把name和major在內核註冊的名稱對比,若是不相等,卸載失敗,並返回-EINVAL;若是major大於最大的設備號,也返回-EINVAL。
包括設備註冊在內,設備驅動的初始化函數主要完成的功能是有如下5項。
(1)對驅動程序管理的硬件進行必要的初始化。
對硬件寄存器進行設置。好比,設置中斷掩碼,設置串口的工做方式、並口的數據方向等。
(2)初始化設備驅動相關的參數。
通常說來,每一個設備都要定義一個設備變量,用以保存設備相關的參數。在這一步驟裏對設備變量中的項進行初始化。
(3)在內核註冊設備。
調用register_chrdev()函數來註冊設備。
(4)註冊中斷。
若是設備須要IRQ支持,則要使用request_irq()函數註冊中斷。
(5)其餘初始化工做。
初始化部分通常還負責給設備驅動程序申請包括內存、時鐘、I/O端口等在內的系統資源,這些資源也能夠在open子程序或者其餘地方申請。這些資源不用時,應該釋放,以利於資源的共享。
若驅動程序是內核的一部分,初始化函數則要按以下方式聲明:
int __init chr_driver_init(void);
其中__init是必不可少的,在系統啓動時會由內核調用chr_driver_init,完成驅動程序的初始化。
當驅動程序是以模塊的形式編寫時,則要按照以下方式聲明:
int init_module(void)
當運行後面介紹的insmod命令插入模塊時,會調用init_module函數完成初始化工做。
設備驅動程序經過調用request_irq函數來申請中斷,經過free_irq來釋放中斷。它們在linux/sched.h中的定義以下:
int request_irq(
unsigned int irq,
void (*handler)(int irq,void dev_id,structpt_regs *regs),
unsigned long flags,
const char *device,
void *dev_id
);
void free_irq(unsigned int irq, void*dev_id);
一般從request_irq函數返回的值爲0時,表示申請成功;負值表示出現錯誤。
— irq表示所要申請的硬件中斷號。
— handler爲向系統登記的中斷處理子程序,中斷產生時由系統來調用,調用時所帶參數irq爲中斷號,dev_id爲申請時告訴系統的設備標識,regs爲中斷髮生時寄存器內容。
— device爲設備名,將會出如今/proc/interrupts文件裏。
— flag是申請時的選項,它決定中斷處理程序的一些特性,其中最重要的是決定中斷處理程序是快速處理程序(flag裏設置了SA_INTERRUPT)仍是慢速處理程序(不設置SA_INTERRUPT)。
下面的代碼將在SBC-2410X的Linux中註冊外部中斷2。
eint_irq = IRQ_EINT2;
set_external_irq (eint_irq, EXT_FALLING_EDGE,GPIO_PULLUP_DIS);
ret_val =request_irq(eint_irq,eint2_handler, 「S3C2410Xeint2」,0);
if(ret_val < 0){
return ret_val;
}
用來打開和關閉中斷的函數以下:
#define cli() _asm_ _volatile_("cli"::)
#define sli() _asm_ _volatile_("sli"::) 。
因爲嵌入式設備因爲硬件種類很是豐富,在默認的內核發佈版中不必定包括全部驅動程序。因此進行嵌入式Linux系統的開發,很大的工做量是爲各類設備編寫驅動程序。除非系統不使用操做系統,程序直接操縱硬件。嵌入式Linux系統驅動程序開發與普通Linux開發沒有區別。能夠在硬件生產廠家或者Internet上尋找驅動程序,也能夠根據相近的硬件驅動程序來改寫,這樣能夠加快開發速度。實現一個嵌入式Linux設備驅動的大體流程以下。
(1)查看原理圖,理解設備的工做原理。通常嵌入式處理器的生產商提供參考電路,也能夠根據須要自行設計。
(2)定義設備號。設備由一個主設備號和一個次設備號來標識。主設備號唯一標識了設備類型,即設備驅動程序類型,它是塊設備表或字符設備表中設備表項的索引。次設備號僅由設備驅動程序解釋,區分被一個設備驅動控制下的某個獨立的設備。
(3)實現初始化函數。在驅動程序中實現驅動的註冊和卸載。
(4)設計所要實現的文件操做,定義file_operations結構。
(5)實現所需的文件操做調用,如read、write等。
(6)實現中斷服務,並用request_irq向內核註冊,中斷並非每一個設備驅動所必需的。
(7)編譯該驅動程序到內核中,或者用insmod命令加載模塊。
(8)測試該設備,編寫應用程序,對驅動程序進行測試。
不管驅動程序多麼複雜,歸根結底,無非仍是向某個端口或者某個寄存器位賦值,這個值只能是0或1。接收值的就是I/O口。與中斷和內存不一樣,使用一個沒有申請的I/O端口不會使處理器產生異常,也就不會致使諸如「segmentationfault」一類的錯誤發生。因爲任何進程均可以訪問任何一個I/O端口,此時系統沒法保證對I/O端口的操做不會發生衝突,甚至所以而使系統崩潰。所以,在使用I/O端口前,也應該檢查此I/O端口是否已有別的程序在使用,若沒有,再把此端口標記爲正在使用,在使用完之後釋放它。
這樣須要用到以下幾個函數:
int check_region(unsigned int from,unsigned int extent);
void request_region(unsigned int from,unsigned int extent,const char *name);
void release_region(unsigned int from, unsignedint extent);
調用這些函數時的參數爲:
— from表示所申請的I/O端口的起始地址;
— extent爲所要申請的從from開始的端口數;
— name爲設備名,將會出如今/proc/ioports文件裏;
— check_region返回0表示I/O端口空閒,不然爲正在被使用。
在申請了I/O端口以後,能夠藉助asm/io.h中的以下幾個函數來訪問I/O端口:
inline unsigned int inb(unsigned shortport);
inline unsigned int inb_p(unsigned shortport);
inline void outb(char value, unsigned shortport);
inline void outb_p(char value,unsigned short port);
其中inb_p和outb_p插入了必定的延時以適應某些低速的I/O端口。
在設備驅動程序中,通常都須要用到計時機制。在Linux系統中,時鐘是由系統接管的,設備驅動程序能夠向系統申請時鐘。與時鐘有關的系統調用有:
#include <asm/param.h>
#include <linux/timer.h>
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
inline void init_timer(struct timer_list *timer);
struct timer_list的定義爲:
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long d);
};
其中,expires是要執行function的時間。系統核心有一個全局變量jiffies表示當前時間,通常在調用add_timer時jiffies=JIFFIES+num,表示在num個系統最小時間間隔後執行function函數。系統最小時間間隔與所用的硬件平臺有關,在覈內心定義了常數HZ表示一秒內最小時間間隔的數目,則num*HZ表示num秒。系統計時到預約時間就調用function,並把此子程序從定時隊列裏刪除,可見,若是想要每隔必定時間間隔執行一次的話,就必須在function裏再一次調用add_timer。function的參數d即爲timer裏面的data項。
做爲系統核心的一部分,設備驅動程序在申請和釋放內存時不是調用malloc和free,而代之以調用kmalloc和kfree,它們在linux/kernel.h中被定義爲:
void * kmalloc(unsigned int len, intpriority);
void kfree(void * obj);
參數len爲但願申請的字節數,obj爲要釋放的內存指針。priority爲分配內存操做的優先級,即在沒有足夠空閒內存時如何操做,通常由取值GFP_KERNEL解決便可。
在用戶程序調用read、write時,由於進程的運行狀態由用戶態變爲核心態,地址空間也變爲核心地址空間。因爲read、write中參數buf是指向用戶程序的私有地址空間的,因此不能直接訪問,必須經過下面兩個系統函數來訪問用戶程序的私有地址空間。
#include <asm/segment.h>
void memcpy_fromfs(void * to,const void *from,unsigned long n);
void memcpy_tofs(void * to,const void *from,unsigned long n);
memcpy_fromfs由用戶程序地址空間往核心地址空間複製,memcpy_tofs則反之。參數to爲複製的目的指針,from爲源指針,n爲要複製的字節數。
在設備驅動程序裏,能夠調用printk來打印一些調試信息,printk的用法與printf相似。printk打印的信息不只出如今屏幕上,同時還記錄在文件syslog裏。
雖然模塊做爲內核的一部分,但並未被編譯到內核中,它們被分別編譯和連接成目標文件。Linux中模塊能夠用C語言編寫,用gcc命令編譯成模塊*.o,在命令行里加上-c的參數和「-D__KERNEL__-DMODULE」參數。而後用depmod -a 使此模塊成爲可加載模塊。模塊用insmod命令加載,用rmmod命令來卸載,這兩個命令分別調用init_module()和cleanup_ module()函數,還能夠用lsmod命令來查看全部已加載的模塊的狀態。
insmod命令可將編譯好的模塊調入內存。內核模塊與系統中其餘程序同樣是已連接的目標文件,但不一樣的是它們被連接成可重定位映像。insmod將執行一個特權級系統調用get_kernel_sysms()函數以找到內核的輸出內容,insmod修改模塊對內核符號的引用後,將再次使用特權級系統調用create_module()函數來申請足夠的物理內存空間,以保存新的模塊。內核將爲其分配一個新的module結構,以及足夠的內核內存,並將新模塊添加在內核模塊鏈表的尾部,而後將新模塊標記爲uninitialized。
利用rmmod命令能夠卸載模塊。若是內核中還在使用此模塊,這個模塊就不能被卸載。緣由是若是設備文件正被一個進程打開就卸載還在使用的內核模塊,並致使對內核模塊的讀/寫函數所在內存區域的調用。若是幸運,沒有其餘代碼被加載到那個內存區域,將獲得一個錯誤提示;不然,另外一個內核模塊被加載到同一區域,這就意味着程序跳到內核中另外一個函數的中間,結果是不可預見的。
/****************************** * LED_Driver 2007/09/20 *****************************/ #include<linux/config.h> #include<linux/kernel.h> #include<linux/sched.h> #include<linux/timer.h> #include<linux/init.h> #include<linux/module.h> #include<asm/hardware.h> #defineGPIO_LED_MAJOR 97 #defineARM_GPIO_LED_DEBUG #defineARM_GPIO_LED (GPIO96) #defineLED_ON 0 #defineLED_OFF 1 #definectl_GPIO_LED1 1 #defineVERSION "ARM_GPIO_LED_2007/09/20" voidshowversion(void) { printk("*********************************************\n"); printk("\t %s \t\n",VERSION); printk("********************************************\n\n"); } //------------------- READ ------------------------ ssize_tGPIO_LED_read (struct file * file ,char * buf, size_t count, loff_t * f_ops) { #ifdef ARM_GPIO_LED_DEBUG printk ("GPIO_LED_read [--kernel--]\n"); #endif return count; } //------------------- WRITE ----------------------- ssize_tGPIO_LED_write (struct file * file ,const char * buf, size_t count, loff_t *f_ops) { #ifdef ARM_GPIO_LED_DEBUG printk("GPIO_LED_write [ --kernel--]\n"); #endif return count; } //------------------- IOCTL ----------------------- ssize_tGPIO_LED_ioctl (struct inode * inode ,struct file * file, unsigned int cmd,long data) { #ifdef ARM_GPIO_LED_DEBUG printk("GPIO_LED_ioctl [ --kernel--]\n"); #endif switch (cmd) { case LED_ON : {GPCR3 |= 0x1;break;} case LED_OFF: {GPSR3 |= 0x1;break;} default : {printk ("lcdcontrol : no cmd run [ --kernel--]\n"); return (-EINVAL);} } return 0; } //------------------- OPEN ------------------------ ssize_tGPIO_LED_open (struct inode * inode ,struct file * file) { #ifdef ARM_GPIO_LED_DEBUG printk("GPIO_LED_open [ --kernel--]\n"); #endif MOD_INC_USE_COUNT; return 0; } //------------------- RELEASE/CLOSE --------------- ssize_tGPIO_LED_release (struct inode * inode,struct file * file) { #ifdef ARM_GPIO_LED_DEBUG printk ("GPIO_LED_release [ --kernel--]\n"); #endif MOD_DEC_USE_COUNT; return 0; } //------------------------------------------------- structfile_operations GPIO_LED_ctl_ops ={ open: GPIO_LED_open, read: GPIO_LED_read, write: GPIO_LED_write, ioctl: GPIO_LED_ioctl, release: GPIO_LED_release, }; //------------------- INIT ------------------------ staticint __init HW_GPIO_LED_CTL_init(void) { int ret = -ENODEV; printk("Driver Loding.....................\n\n"); showversion(); // init GPIO GPDR3 |= 0x00000001; // SET GPIO96 OUTPUTMODE GPSR3 |= 0x00000001; // OFF THE LED #ifdef ARM_GPIO_LED_DEBUG printk (" GPLR3=%x\n",GPLR3); printk (" GPDR3=%x \n",GPDR3); #endif ret = devfs_register_chrdev(GPIO_LED_MAJOR,"led_drv", &GPIO_LED_ctl_ops); if( ret < 0 ) { printk (" ARM: init_module failedwith %d\n [ --kernel--]", ret); return ret; } else { printk(" ARM gpio_led_driverregister success!!! [ --kernel--]\n"); } return ret; } staticint __init ARM_GPIO_LED_CTL_init(void) { int ret = -ENODEV; #ifdef ARM_GPIO_LED_DEBUG printk("ARM_GPIO_LED_CTL_init [ --kernel--]\n"); #endif ret = HW_GPIO_LED_CTL_init(); if (ret) return ret; return 0; } staticvoid __exit cleanup_GPIO_LED_ctl(void) { #ifdef ARM_GPIO_LED_DEBUG printk("cleanup_GPIO_LED_ctl [ --kernel--]\n"); #endif devfs_unregister_chrdev (GPIO_LED_MAJOR,"gpio_led_ctl" ); } MODULE_DESCRIPTION("GPIO_led driver module"); MODULE_AUTHOR("zsm"); MODULE_LICENSE("GPL");//GPL協議證書信息 module_init(ARM_GPIO_LED_CTL_init); module_exit(cleanup_GPIO_LED_ctl);
在編寫用戶應用程序過程當中,考慮經過接口open()函數打開設備,再經過接口ioctl()函數來實現對LED的控制功能。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> // open() close() #include <unistd.h> // read() write() #define DEVICE_NAME "/dev/led_drv" #define LED_ON 0 #define LED_OFF 1 int main(void) { intfd; int ret; char *i; printf("\nstart GPIO_led_driver test\n\n"); fd= open(DEVICE_NAME, O_RDWR); printf("fd= %d\n",fd); if(fd == -1) { printf("open device %s error\n",DEVICE_NAME); } else { while(1) { ioctl(fd,LED_OFF); sleep(1); ioctl(fd,LED_ON); sleep(1); } ret =close(fd); printf("ret=%d\n",ret); printf("close led_driver test\n"); } return 0; }
(1)Create:
[root@OURSELEC usb]# mknod /dev/led_drv c 97 0
(2)view:
[root@OURSELEC usb]# ls -l /dev/led_drv
crw-r--r-- 1 root root 97, 0 Jan 1 01:29 /dev/led_drv
(3)insmod:
[root@OURSELEC usb]# insmod led_drv.o
Using led_drv.o
ARM_GPIO_LED_CTL_init [ --kernel--]
Driver Loding .....................
*********************************************
ARM_GPIO_LED_2007/09/20
*********************************************
GPLR3=73e7fd
GPDR3=1efffc3
ARMgpio_led_driver register success!!! [ --kernel--]
(4)./test_led:
[root@OURSELEC usb]# ./test_led
startGPIO_led_driver test
GPIO_LED_open[ --kernel--]
fd = 3
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_ioctl [ --kernel--]
GPIO_LED_release [ --kernel--]
(5) remove led_drv mode:
[root@OURSELEC usb]# rmmod led_drv
cleanup_GPIO_LED_ctl [ --kernel--]
/************************************* NAME:gt2440_leds.c COPYRIGHT:www.e-online.cc *************************************/ #include <linux/miscdevice.h> #include <linux/delay.h> #include <asm/irq.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/delay.h> #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/string.h> #include <linux/list.h> #include <linux/pci.h> #include <asm/uaccess.h> #include <asm/atomic.h> #include <asm/unistd.h> #define DEVICE_NAME "leds" /* 應用程序執行ioctl(fd, cmd, arg)時的第2個參數 */ #define IOCTL_LED_ON 1 #define IOCTL_LED_OFF 0 /* 用來指定LED所用的GPIO引腳 */ static unsigned long led_table [] = { S3C2410_GPB5, S3C2410_GPB6, S3C2410_GPB7, S3C2410_GPB8, }; /* 用來指定GPIO引腳的功能:輸出 */ static unsigned int led_cfg_table [] = { S3C2410_GPB5_OUTP, S3C2410_GPB6_OUTP, S3C2410_GPB7_OUTP, S3C2410_GPB8_OUTP, }; static int gt2440_leds_ioctl( structinode *inode, structfile *file, unsignedint cmd, unsignedlong arg) { if(arg > 4) { return-EINVAL; } switch(cmd) { caseIOCTL_LED_ON: //設置指定引腳的輸出電平爲0 s3c2410_gpio_setpin(led_table[arg], 0); return0; caseIOCTL_LED_OFF: //設置指定引腳的輸出電平爲1 s3c2410_gpio_setpin(led_table[arg], 1); return0; default: return-EINVAL; } } static struct file_operations dev_fops = { .owner = THIS_MODULE, .ioctl = gt2440_leds_ioctl, }; static struct miscdevice misc = { .minor= MISC_DYNAMIC_MINOR, .name= DEVICE_NAME, .fops= &dev_fops, }; static int __init dev_init(void) { intret; inti; for(i = 0; i < 4; i++) { s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]); s3c2410_gpio_setpin(led_table[i], 0); } ret= misc_register(&misc); printk(DEVICE_NAME" initialized\n"); returnret; } static void __exit dev_exit(void) { misc_deregister(&misc); } module_init(dev_init); module_exit(dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("www.e-online.cc"); MODULE_DESCRIPTION("LEDS control forGT2440 Board");
塊設備文件一般指一些須要以塊(如512字節)的方式寫入的設備,如IDE硬盤、SCSI硬盤、光驅等。它的驅動程序的編寫過程與字符型設備驅動程序的編寫有很大的區別。
爲了把各類塊設備的操做請求隊列有效地組織起來,內核中設置了一個結構數組blk_dev,該數組中的元素類型是blk_dev_struct結構。這個結構由三個成分組成,其主體是執行操做的請求隊列request_queue,還有一個函數指針queue。當這個指針不爲0時,就調用這個函數來找到具體設備的請求隊列。塊設備驅動程序描述符是一個包含在<linux/blkdev.h>中的blk_dev_struct類型的數據結構,其定義以下所示:
struct blk_dev_struct {
request_queue_t request_queue;
queue_proc *queue;
void *date;
};
在這個結構中,請求隊列request_queue是主體,包含了初始化以後的I/O 請求隊列。
全部塊設備的描述符都存放在blk_dev表struct blk_dev_structblk_dev[MAX_BLKDEV]中;每一個塊設備都對應着數組中的一項,可使用主設備號進行檢索。每當用戶進程對一個塊設備發出一個讀寫請求時,首先調用塊設備所公用的函數generic_file_read(),generic_file_write()。若是數據存在在緩衝區中或緩衝區還能夠存放數據,那麼就同緩衝區進行數據交換。不然,系統會將相應的請求隊列結構添加到其對應項的blk_dev_struct中,以下圖所示:
塊設備驅動程序的編寫流程同字符設備驅動程序的編寫流程很相似,也包括了註冊和使
用兩部分。但與字符驅動設備所不一樣的是,塊設備驅動程序包括一個request請求隊列。它是
當內核安排一次數據傳輸時在列表中的一個請求隊列,用以最大化系統性能爲原則進行排序。
塊設備驅動程序流程圖
Linux系統中有一個名爲blkdevs的結構數組,它描述了一系列在系統中登記的塊設備。數組blkdevs也使用設備的主設備號做爲索引,其元素類型是device_struct結構。該結構中包括指向已登記的設備驅動程序名的指針和指向block_device_operations結構的指針。在block_device_operations結構中包含指向有關操做的函數指針。因此,該結構就是鏈接抽象的塊設備操做與具體塊設備類型的操做之間的樞紐。
一個bio結構體是在通用塊層或更底層對塊設備i/o操做的的表示單位。一般1個bio對應1個I/O請求.
struct bio { sector_t bi_sector; /* device address in 512 byte sectors */ structbio *bi_next; /*request queue link */ structblock_device *bi_bdev; unsignedlong bi_flags; /* status, command, etc */ unsignedlong bi_rw; /* bottom bits READ/WRITE, * top bits priority */ unsignedshort bi_vcnt; /*how many bio_vec's */ unsignedshort bi_idx; /* current index into bvl_vec */ /* Numberofsegments in this BIO after *physical address coalescing is performed. */ unsignedint bi_phys_segments; unsignedint bi_size; /*residual I/O count */ /* * Tokeep track of the max segment size, weaccount for the *sizes of the first and last mergeablesegments in this bio. */ unsignedint bi_seg_front_size; unsignedint bi_seg_back_size; unsignedint bi_max_vecs; /* max bvl_vecs we can hold */ unsignedint bi_comp_cpu; /* completion CPU */ atomic_t bi_cnt; /*pin count*/ structbio_vec *bi_io_vec; /* the actual vec list */ bio_end_io_t *bi_end_io; void *bi_private; #if defined(CONFIG_BLK_DEV_INTEGRITY) structbio_integrity_payload *bi_integrity; /*data integrity */ #endif bio_destructor_t *bi_destructor; /* destructor */ /* * Wecan inline a number of vecs at the end ofthe bio, to avoid *double allocations for a small number ofbio_vecs. This member * MUSTobviously be kept at the very end ofthe bio. */ structbio_vec bi_inline_vecs[0]; };
struct gendisk { //表示一個獨立的磁盤設備或分區 intmajor; /* major number of driver */ intfirst_minor; /*starting minor number*/ intminors; /* maximumnumber ofminors, =1 for *disks that can't be partitioned. 每個分區都有一個minor號*/ chardisk_name[DISK_NAME_LEN]; /* name ofmajor driver */ structdisk_part_tbl *part_tbl; structhd_struct part0; structblock_device_operations *fops; structrequest_queue*queue; void*private_data; int flags; struct device*driverfs_dev; // FIXME: remove struct kobject*slave_dir; structtimer_rand_state *random; atomic_tsync_io; /* RAID */ structwork_struct async_notify; #ifdef CONFIG_BLK_DEV_INTEGRITY structblk_integrity *integrity; #endif int node_id; }; struct device_struct { const char *name; struct file_operations *chops; }; static struct device_structblkdevs[MAX_BLKDEV]; struct sbull_dev { void **data; int quantum;// thecurrent quantum size int qset;// the current array size unsigned long size; unsigned int access_key;// used by sbulluid and sbullpriv unsigned int usage;// lock the device while using it unsigned int new_msg; struct sbull_dev *next;// next listitem }; 與字符設備驅動程序同樣,塊設備驅動程序也包含一個file_operation結構,其結構定義通常以下所示: struct file_operation blk_fops = { NULL,//seek block_read,//內核函數 block_write,//內核函數 NULL,//readdir NULL,//poll sbull_ioctl,// ioctl NULL,//mmap sbull_open,//open NULL,//flush sbull_release,//release block_fsync,//內核函數 NULL,//fasync sbull_check_media_change,//check media change NULL,//revalidate NULL,//lock };
全部的塊驅動程序都調用內核函數block_read()、block_write(),block_fsync()函數,因此在塊設備驅動程序入口中不包含這些函數,只需包括ioctl()、open()
和release()函數便可。
塊設備的初始化過程要比字符設備複雜,它既須要像字符設備同樣在引導內核時完成必定的
工做,還須要在內核編譯時增長一些內容。塊設備驅動程序初始化時,由驅動程序的init()完成。
塊設備驅動程序初始化的工做主要包括:
· 檢查硬件是否存在;
· 登記主設備號;
· 將fops結構的指針傳遞給內核;
· 利用register_blkdev()函數對設備進行註冊:
if(register_blkdev(sbull_MAJOR,「sbull」,&sbull_fops)) {
printk(「Registering block device major:%d failed\n」,sbull_MAJOR);
return-EIO;
};
· 將request()函數的地址傳遞給內核:
blk_dev[sbull_MAJOR].request_fn= DEVICE_REQUEST;
· 將塊設備驅動程序的數據容量傳遞給緩衝區:
#define sbull_HARDS_SIZE 512
#define sbull_BLOCK_SIZE 1024
static int sbull_hard = sbull_HARDS_SIZE;
static int sbull_soft = sbull_BLOCK_SIZE;
hardsect_size[sbull_MAJOR] = &sbull_hard;
blksize_size[sbull_MAJOR] = &sbull_soft;
在塊設備驅動程序內核編譯時,應把下列宏加到blk.h文件中:
#define MAJOR_NR sbull_MAJOR
#define DEVICE_NAME 「sbull」
#define DEVICE_REQUEST sbull_request
#define DEVICE_NR(device) (MINOR(device))
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
Request操做涉及一個重要的數據結構以下。
struct request {
kdev_t rq_dev;
int cmd; // 讀或寫
int errors;
unsigned long sector;
char *buffer;
struct request *next;
};
對於具體的塊設備,函數指針request_fn固然是不一樣的。塊設備的讀寫操做都是由request()函數完成。全部的讀寫請求都存儲在request結構的鏈表中。request()函數利用CURRENT宏
檢查當前的請求。request()函數從INIT_REQUEST宏命令開始(它也在blk.h中定義),它對請求隊列進行檢查,保證請求隊列中至少有一個請求在等待處理。若是沒有請求(即CURRENT = 0),則INIT_REQUEST宏命令將使request()函數返回,任務結束。
假定隊列中至少有一個請求,request()函數如今應處理隊列中的第一個請求,當處理完
請求後,request()函數將調用end_request()函數。若是成功地完成了讀寫操做,那麼應該用參數值1 調用end_request()函數;若是讀寫操做不成功,那麼以參數值0 調用end_request()函數。若是隊列中還有其餘請求,那麼將CURRENT 指針設爲指向下一個請求。執行end_request()函數後,request()函數回到循環的起點,對下一個請求重複上面的處理過程。
不少Linux 的驅動都是經過中斷的方式來進行內核和硬件的交互。
這是驅動程序申請中斷和釋放中斷的調用。在include/linux/sched.h裏聲明。
request_irq()調用的定義:
int request_irq(unsigned int irq,
void (*handler)(int irq, void*dev_id, struct pt_regs *regs),
unsigned long irqflags,const char* devname,oid *dev_id);
irq 是要申請的硬件中斷號。在Intel平臺,範圍是0~15。handler 是向系統登記的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用這個函數,傳入的參數包括硬件中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)仍是慢速處理程序(不設置SA_INTERRUPT)。快速處理程序
被調用時屏蔽全部中斷。慢速處理程序不屏蔽。還有一個SA_SHIRQ 屬性,設置了之後運行多個設備共享中斷。dev_id在中斷共享時會用到。通常設置爲這個設備的device結構自己或者NULL。中斷處理程序能夠用dev_id找到相應的控制這個中斷的設備,或者用irq2dev_map
找到中斷對應的設備。void free_irq(unsigned int irq,void *dev_id);
經過寫一個創建在內存中的塊設備驅動,來學習linux內核和相關設備驅動知識
#defineSIMP_BLKDEV_DISKNAME "simp_blkdev"
#defineSIMP_BLKDEV_BYTES (16*1024*1024)// 使用宏定義了塊設備的大小,定爲16M
#defineSIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
structblock_device_operations simp_blkdev_fops = {
.owner = THIS_MODULE,
};// gendisk結構須要設置fops指針,雖然咱們用不到,但該設仍是要設的
static structgendisk *simp_blkdev_disk;
static structrequest_queue *simp_blkdev_queue;// 指向塊設備須要的請求隊列
unsigned charsimp_blkdev_data[SIMP_BLKDEV_BYTES];
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
module_init(simp_blkdev_init);//而後申明模塊的入口和出口
module_exit(simp_blkdev_exit);
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
static int __initsimp_blkdev_init(void) //在入口處添加這個設備、出口處刪除這個設備
{
simp_blkdev_disk = alloc_disk(1); //在添加設備以前咱們須要申請這個設備的資源,這用到了alloc_disk()函數
strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME); //設備有關的屬性也是須要設置
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops =&simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
add_disk(simp_blkdev_disk);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
simp_blkdev_queue =blk_init_queue(simp_blkdev_do_request, NULL);//初始化請求隊列
if (!simp_blkdev_queue) {
ret = -ENOMEM;
goto err_init_queue;
}//在加載模塊時用simp_blkdev_do_request()函數的地址做參數
調用blk_init_queue()初始化一個請求隊列
//用來從一個請求隊列中拿出一條請求(其實嚴格來講,拿出的多是請求中的一段)。
隨後的處理請求本質上是根據rq_data_dir(req)返回的該請求的方向(讀/寫),把塊設備中的數據裝入req->buffer、或是把req->buffer中的數據寫入塊設備。
static voidsimp_blkdev_do_request(struct request_queue *q) //請求隊列的處理函數。 { struct request *req; while ((req = elv_next_request(q)) != NULL) { if ((req->sector +req->current_nr_sectors) << 9 > SIMP_BLKDEV_BYTES) { printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": bad request: block=%llu,count=%u\n", (unsigned long long)req->sector, req->current_nr_sectors); end_request(req, 0); continue; } switch (rq_data_dir(req)){ case READ: memcpy(req->buffer, simp_blkdev_data + (req->sector <<9), req->current_nr_sectors << 9); end_request(req, 1); break; case WRITE: memcpy(simp_blkdev_data + (req->sector << 9), req->buffer, req->current_nr_sectors<< 9); end_request(req, 1); break; default: /* No default because rq_data_dir(req) is 1 bit */ break; } } } return 0; err_alloc_disk: blk_cleanup_queue(simp_blkdev_queue); err_init_queue: return ret;} ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ static void __exit simp_blkdev_exit(void) { del_gendisk(simp_blkdev_disk); put_disk(simp_blkdev_disk); blk_cleanup_queue(simp_blkdev_queue); }