1.記 憶 體 配 置 、 釋 放 和 轉 移
void *kmalloc(unsigned int size, int priority)
void *kfree_s(void * obj, int size)
kfree(char *)
memcpy_fromfs(dest, src, size)
memcpy_tofs(dest, src, size)
put_fs_byte(src, dest)
byte get_fs_byte(char *s)
因為原本預設的 kernel config 並不接受動態掛載 module ,我們要新增對 loadable module 的支援。
首先找到在實驗二中所使用的 linux kernel 原始碼,接著在的 menuconfig 中 [3]
,找到 「 Loadable Module Support 」,並將它裡面的 「 Enable loadable module support
」、「 Module unloading 」以及「 Forced module unloading 」勾選為 built-in
,再存檔離開即可。
接著請重新編譯 kernel ,即可產生支援動態掛載 module 的 kernel image 了。
| [3] | 編譯 linux kernel 的相關步驟可參考實驗二 |
編譯 module 的方法和一般的程式有一些不同,請先到 opencsl 網站下載 Makefile :
wget http://opencsl.openfoundry.org/src/Makefile
將它和 demo.c 放到同一層目錄,並在上面鍵入 [4]
make -C <linux> M=$(pwd) modules ARCH=arm CROSS_COMPILE=arm-linux-uclibc-
即可產生 demo.ko ,這是我們之後要拿來掛載的 module 。
| [4] | <linux> 為 linux source 的根目錄路徑 |
在 linux 中和 module 有關的指令有三:
在用 QEMU 載入新 kernel image 後,可以在 demo.ko 的目錄下鍵入
insmod demo.ko
即可將 demo 載入 kernel 中。此時可用 lsmod 來確定 demo 是否有被成功掛載。
在掛載 demo 的同時,我們也可以發現 linux 有印出我們在 demo.c 裡定義的訊息。
若要將 demo 移除或重新掛載,可以鍵入
rmmod demo.ko
就可以將 demo 移除。
引入標頭檔
在撰寫 driver 前必須先 include 一些標頭檔:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
撰寫控制元件的函式
struct file_operations 即為定義各個 function pointer 的 structure。
static ssize_t drv_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
printk("device read\n");
return count;
}
static ssize_t drv_write(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
printk("device write\n");
return count;
}
static int drv_open(struct inode *inode, struct file *filp)
{
printk("device open\n");
return 0;
}
int drv_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("device ioctl\n");
return 0;
}
static int drv_release(struct inode *inode, struct file *filp)
{
printk("device close\n");
return 0;
}
struct file_operations 即為定義各個 function pointer 的 structure。
struct file_operations drv_fops =
{
read: drv_read,
write: drv_write,
ioctl: drv_ioctl,
open: drv_open,
release: drv_release,
};
撰寫初始化、結束元件的函式
#define MAJOR_NUM 60
#define MODULE_NAME "DEMO"
static int demo_init(void) {
if (register_chrdev(MAJOR_NUM, "demo", &drv_fops) < 0)
{
printk("<1>%s: can't get major %d\n", MODULE_NAME, MAJOR_NUM);
return (-EBUSY);
}
printk("<1>%s: started\n", MODULE_NAME);
return 0;
}
static void demo_exit(void) {
unregister_chrdev(MAJOR_NUM, "demo");
printk("<1>%s: removed\n", MODULE_NAME);
}
module_init(demo_init);
module_exit(demo_exit);
其中 MAJOR_NUM 即為 driver 所對應的 device 的 major number
根據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以符合此階段的實作。