了解重要的架構觀念面後,接著就是 Linux 驅動程式本身了!Linux 驅動程式的設計雖然沒有一定的標準流程,但是由「觀念」層面可以歸納出一個一般化的流程。
作者/陳俊宏
www.jollen.org
一般化設計流程
我們提過,依照驅動程式本身的實作,可以將 Linux 驅動程式分為 2 大部份:virtual device driver與physical device driver。
此流程為一個觀念流程,實際撰寫驅動程式時,並不會完全以這個流程來設計。但初學Linux驅動程式時,則應以此流程為主循序學習,才能理解基本的Linux驅動程式實作。
對Linux驅動程式而言,virtual device driver的重要性遠在physical device driver之上,乍聽之下這或許不太能理解,因為沒有physical device driver是無法真正驅動硬體的。但實作上,physical device driver是一成不變的程式寫法,能不能寫出好的驅動程式,關鍵是在virtual device driver的部份。
struct file_operations
struct file_operations 是 kernel 提供的一個重要資料結構,這是學習 Linux 驅動程式第一個會認識的對象,也是最重要的一個主題。
Linux 驅動程式建構在 file_operations 之上。file_operations定義驅動程式的system call與實作system call的函數,我們把file_operations任何一個部份拿出來討論的話,都能切成virtual device driver與physical device driver二個部份。
流程解說
Virtual device driver往上是為了連結Linux kernel的VFS層,physical device drvier往下是為了存取實體硬體。
Virtual Device Driver
Virtual device driver 的目的在於設計一個「機制」良好的kernel mode驅動程式,virtual device driver也必須考慮與user application的互動。實作上,則是需要善用kernel所提供的介面(interface),即kernel APIs。
Virtual device driver再分為3階段的觀念實作:
fops 是指向 file_operations 結構的指標,驅動程式呼叫 register_chrdev() 將fops註冊到 kernel 裡後,fops 便成為該 device driver 所實作的system call進入點。實作system call的函數便是透過file_operations結構來定義,我們稱實作system call的函數為driver method。
kernel 會在需要時回呼 (callback) 我們所註冊的driver method。因此,當 driver 裡的 method 被呼叫時,kernel便將傳遞參數(parameters)給 driver method,driver method可由 kernel 所傳遞進來的參數取得驅動程式資訊。
註冊driver的動作呼叫register_chrdev()函數完成,此函數接受3個參數如下:
「註冊」這個動作觀念上是將fops加到kernel的VFS層,因此user application必須透過「device file」才能呼叫到driver method。註冊這個動作的另一層涵意則是將driver method與不同的system call做「正確的對應」,當user application呼叫system call時,才能執行正確的driver method。
Physical Device Driver
Physical device driver的目的在於實作控制硬體的程式碼。Physical device driver 的設計必須隨時查閱晶片(chipsets)的 data sheet,並透過晶片的 control register 來控制裝置。
理論上,我們可以將晶片的暫存器分成3大類:
Data register是晶片裡用來存放資料的暫存器,control register則是用來控制晶片行為的暫存器,status register則保存目前晶片的狀態。設計控制硬體周邊的驅動程式時,需要了解硬體使用的晶片組,晶片組則需要參考IC設計廠商所提供的「datasheet」才能了解晶片組的暫存器名稱與用途,通常不同的暫存器會對應到一個「相對」的偏移位址(offset)。
驅動程式則是要透過control register才能控制晶片,因此需要隨時查閱晶片的datasheet,並了解每一個暫存器的用途。通常暫存器的每個位元(bit)也都是有特定用途的,因此設計驅動程式時,必須要很熟悉C語言的位元運算用法。
實作上,首先會將晶片的 datasheet 寫成C語言的標頭檔,通常這個檔案都可以從 vendor 取得。
接著再定義一組操作暫存器的I/O函數,我們稱這組函數為I/O wrapper function。I/O wrapper functions通常是重新定義Linux kernel所提供的readb()、writeb()或inb()、outb()系列函數所寫成的。
最後,利用I/O wrapper function實作一系列的控制函數,以控制實際硬體,我們稱此函數為chipset control functions。Chipset control functions是由實作system calls的函數(driver method)所呼叫,因此在設計chipset control functions時也會回頭改寫driver method以符合此階段的實作。
Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue
您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw