根據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");
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的實作「原則」。
System call的實作原則即driver function所要負責處理的基本工作。學習Linux device driver的重要工作之一,便是一一了解fops裡每一個system call的實作原則,並依照實際需求來實作不同的 system call。
open System Call
以open system call為例,fops->open是在user呼叫open()函數時執行,即當user開啟driver所指定的device file時呼叫fops->open。
fops->open實作原則如下:
- 將usage count加一(increment)
- 檢查inode->i_rdev
- 檢查裝置是否錯誤
- 初始化裝置
- 將驅動程式自己的資料結構放到filp->private_data
以下是本範例的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;
};