,這些 device 不一定是實際存在的硬體,也有可能是以虛擬的方式掛載上去的。例如,如果想要了解目前系統中總共有多少的 SATA
硬碟以及分割區,可以在終端機下鍵入:
根據Linux驅動程式的一般化設計流程,我們來設計一個真正可以動的驅動程式。
作者/陳俊宏
www.jollen.org
根據流程寫程式
定義 file_operations
struct file_operations card_fops =
{
open: card_open,
write: card_write,
release: card_release,
ioctl: card_ioctl,
};
由此定義可以,我們所計的驅動程式將提供 open/write/close(release)/ioctl 4 個 system call 介面給 user application。
實作 System Call
接著要實作我們所提供的4個 system call。open/close(即 release)/read/write/ ioctl 是初學
Linux 驅動程式最重要的 5 個 system call,了解如何實作不同的 system call,是學好 Linux
驅動程式的重要工作。
本文先介紹 open/close(release)/write 的實作。此部份說明如後。
註冊 Driver
將driver自己「註冊」到kernel的VFS層,註冊時所要呼叫的函數根據裝置類型的不同而不同。
將驅動程式「註冊」(registration)至kernel的動作必須在init_module()函數裡實作。根據裝置類型的不同,所呼叫的函數也不同,以下是幾個基本的裝置註冊函數:
˙ int register_chrdev(unsigned int major, const char * name, struct
file_operations *fops):註冊字元型驅動程式。
˙ int register_blkdev(unsigned int major, const char *name, struct
file_operations *fops):註冊區塊型驅動程式。
˙ int usb_register(struct usb_driver *new_driver):註冊USB驅動程式。
˙ int pci_register_driver(struct pci_driver *):註冊PCI驅動程式。
本文範例註冊驅動程式的程式片斷如下:
#define DEV_MAJOR 121
#define DEV_NAME "debug"
#define MSG(format, arg...) printk(KERN_INFO "DEBUG CARD: " format "\n", ## arg)int init_module(void)
{
MSG("DEBUG CARD v0.1.1");
MSG(" Copyright (C) 2004 www.jollen.org");
p.s. &card_fops就是 struct file_operations card_fops
if (register_chrdev(DEV_MAJOR, DEV_NAME, &card_fops) < 0) {
MSG("Couldn't register a device.");
return -1;
}
return 0;
}
register_chrdev()參數說明如下:
˙ 第1個參數:為device file的major number。該device file應在Linux系統底下以root身份手動建立。
˙ 第2個參數:
˙ 第3個參數:為驅動程式的fops。
註冊的動作是寫在init_module()裡,因此當使用者執行insmod載入驅動程式時,register_chrdev()便會執行。由此
可知,註冊驅動程式的時機為insmod時。相對的,在rmmod時,必須執行解除註冊的動作,此動作必須實作在cleanup_module()函數
裡。
前面所介紹的4個註冊函數,其相對應的解除註冊函數如下:
˙ int unregister_chrdev(unsigned int major, const char * name) :解除註冊字元型驅動程式。
˙ int unregister_blkdev(unsigned int major, const char *name) :解除註冊區塊型驅動程式。
˙ void usb_deregister(struct usb_driver *driver):解除註冊USB驅動程式。
˙ pci_unregister_driver(struct pci_driver *drv) :解除註冊PCI驅動程式。
範例debug card 0.1.0解除註冊的程式片斷如下:
void cleanup_module(void)
{
if (unregister_chrdev(DEV_MAJOR, DEV_NAME))
MSG("failed to unregister driver");
else
MSG("driver un-installed\n");
}
Linux驅動程式的「註冊」是一個非常重要的動作,這個動作代表 Linux 驅動程式是一個嚴謹的分層式架構;換句話說,
Linux驅動程式的分層(layered)關係可透過「註冊」的程序來分析。
定義chipset標頭檔
我們所要設計的 Port 80H 除錯卡驅動程式,不需要定義標頭檔;此部份可參考 kernel 裡的 BTTV 驅動程式。
定義I/O wrapper function
我們所要設計的 Port 80H 除錯卡驅動程式,不需要定義 I/O wrapper function;此部份可參考 kernel 裡的 BTTV
驅動程式。
實作chipset控制函數
在我們所要設計的 Port 80H 除錯卡驅動程式中,我們是直接使用 kernel 的 I/O 介面來控制除錯卡,physiacl device
driver 的部份將在下一篇文章再做說明。
open/release實作
open與release是Linux驅動程式最基本的2個system call。驅動程式應先實作此2個system call。
為了方便說明起見,本文後文將以「fops->open」表示實作open system call的driver function。其它system call亦同。 |
open與release system call的執行時機如下:
1. 當user application執行open()函數時,便呼叫Linux kernel的open system call,即執行fops->open。
2. 當user application執行close()函數時,便呼叫Linux kernel的close system
call,即執行fops->release。
Linux驅動程式註冊至kernel時會指定device file的major number,user application便可以透過此符合此major
number的device file與硬體溝通,即Linux驅動程式是透過VFS架構層與user
application溝通。
file_operation是Linux驅動程式支援VFS的重要結構。學習file_operation的重要目的如下:
1. 了解每一個system call的用途。
2. 了解每一個system call的實作「原則」。
open System Call
以open system call為例,fops->open是在user呼叫open()函數時執行,即當user開啟driver所指定的device file時呼叫fops->open。
fops->open實作原則如下:
以下是本範例的fops->open實作:
int card_open(struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
return 0;
};
release System Call
當user application 呼叫close() 函數後,便執行fops->release。
有些驅動程式會將release method函數名稱命名為 XXX_close(),但建議以XXX_release()名稱為主,以避免混淆。
fops->release實作原則如下:
以下是本範例的fops->release實作:
int card_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
kfree(filp->private_data); //reentrant code 觀念 (本文尚未說明)return 0;
};
--jollen
一般化設計流程
依照驅動程式本身的實作,可以將 Linux 驅動程式分為 2 大部份:
virtual device driver與physical device driver。
struct file_operations
struct file_operations 是 kernel 提供的一個重要資料結構
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大類:
設計控制硬體周邊的驅動程式時,需要了解硬體使用的晶片組
晶片組 要看IC設計廠商所提供的「datasheet」才能了解晶片組的暫存器名稱與用途
p.s.通常不同的暫存器會對應到一個「相對」的偏移位址(offset)。
驅動程式則是要透過control register才能控制晶片
實作上,首先會將晶片的 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以符合此階段的實作。