2013年12月14日 星期六

[嵌入式系統]Ch7 Linux驅動程式開發詳述

本文閱讀http://www.jollen.org/LinuxDeviceDriver/並整理過

一 Linux下的裝置有三種基本型別:
1.字元驅動程式(character device driver):初學者學Linux驅動程式 可由此開始,Linux device file檔案屬性的第一個位元顯示為 “c”
2.區塊型驅動程式(block device driver):Linux device file檔案屬性的第一個位元顯示為 “b”
3.網路介面(network device driver):

二 在Linux中,Device file 的 major number 代表一個特定的裝置,如 major number 為 1 為 null 虛擬裝置,major number 定義於 kernel 文件目錄 Documentation/devices.txt 。Minor number 代表裝置上的子裝置,例如同一個硬碟上的分割區就用不同的 minor number 來代表,但其 major number 相同。
使用 OS API 的最大優點是使得驅動程式的設計抽象化 (abstraction),我們可以不需要太深入硬體層次。


=====================================
三 驅動程式本身可分成 2 個層面來討論:
1.Virtual device driver(觀念討論):Virtual device driver往上是為了連結Linux kernel的VFS層,並藉此實作 system calls。使用者可透過 system call interface 與 device driver 溝通。D重要性遠在physical device driver之上,因為能不能寫出好的驅動程式,關鍵是在virtual device driver的部份。因一個「機制」良好的kernel mode驅動程式,virtual device driver也必須考慮與user application的互動。實作上,則是需要善用kernel所提供的介面(interface),即kernel APIs。

2.Virtual device driver再分為3階段的觀念實作:

a.定義 file_operations: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 所傳遞進來的參數取得驅動程式資訊。

========================================
b.實作 system calls:文章中提到:user application要呼叫system call必須透過呼叫 GNU C 所提供的"wrapper function",每個 system call 都會對應到 driver 內的一個 task,此 task 即是 file_operation 函數指標所指的函數,我將其概念用簡易的圖形呈現如下:

user application=文字圖縮寫為UA
System call = SC (ex.open/release/read/write/ioctl)
Linux device driver = 文字圖縮寫為Driver
VFS(Virtual File System):Linux 驅動程式必須透過 VFS層來實作 system call.而VFS就存在linux的/dev底下,所以每一個/dev底下的文件就是一個system call
文字圖:
                   (介面) (第二層-VFS層)
UA(GNU C's wrapper function) >>>>>system call1 >kernel's VFS> Driver1(file_operation->task1)
>>>>>system call2 >kernel's VFS> Driver1(file_operation->task2)
>>>>>system call3 >kernel's VFS> Driver1(file_operation->task3)
>>>>>system call4 >kernel's VFS> Driver1(file_operation->task4)
>>>>>system call5 >kernel's VFS> Driver1(file_operation->task5)



GNU LIBC(GLIBC) 的幾個function
=============================
五個System call
opefops->open實作原則如下:
1.將usage count加一(increment)
2.檢查inode->i_rdev
3.檢查裝置是否錯誤
4.初始化裝置
5.將驅動程式自己的資料結構放到filp->private_datan()

close()(release):

當user application 呼叫close() 函數後,便執行fops->release。有些驅動程式會將release method函數名稱命名為 XXX_close(),但建議以XXX_release()名稱為主,以避免混淆。
fops->release實作原則如下:
1.將 usage count 減一。

read():fops->read
write():fops->write
ioctl():fops->ioctl:
=============================
由網路上範例程式碼可看到
int main(int argc, char *argv[])
{
int devfd;

devfd = open("/dev/debug", O_RDONLY);

if (devfd == -1) {
printf("Can't open /dev/debug\n");
return -1;
}

ioctl(devfd, IOCTL_WRITE, num);

close(devfd);

return 0;
}

===================================
c.註冊 driver (VFS):註冊driver的動作呼叫register_chrdev()函數完成,此函數接受3個參數如下:
major:要註冊的裝置 major number
name:device 名稱
fops:driver 的 file operation

「註冊」這個動作觀念上是將fops加到kernel的VFS層,因此user application必須透過「device file」才能呼叫到driver method。註冊這個動作的另一層涵意則是將driver method與不同的system call做「正確的對應」,當user application呼叫system call時,才能執行正確的driver method。
======================================

四 Physical device driver(語法討論):physical device drvier往下是為了存取實體硬體,並使用 Linux kernel 所提供的 device interface 來隨時查閱晶片(chipsets)的 data sheet,並透過晶片的
 control register 來控制裝置。(實作控制硬體的程式碼)。Physical device driver 則是討論「如何透過 I/O port 或 I/O memory」來控制裝置,也就是與晶片組的溝通。這個部份需要實作晶片組的 data sheet,本書會以市面上最容易取得的 BT878 視訊擷取晶片為例做說明。
此外,physical device driver是一成不變的程式寫法

===========

1理論上,我們可以將晶片的暫存器分成3大類:

a.Data registers:Data register是晶片裡用來存放資料的暫存器,control register則是用來控制晶片行為的暫存器,status register則保存目前晶片的狀態。設計控制硬體周邊的驅動程式時,需要了解硬體使用的晶片組,晶片組則需要參考IC設計廠商所提供的「datasheet」才能了解晶片組的暫存器名稱與用途,通常不同的暫存器會對應到一個「相對」的偏移位址(offset)。

b.control registers:驅動程式則是要透過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以符合此階段的實作。

c.status registers


========================

physical device 的 I/O 存取

1. I/O port
˙ unsigned inb(unsigned port);
˙ unsigned inw(unsigned port);
˙ unsigned inl(unsigned port);
˙ void outb(unsigned char byte, unsigned port);
˙ void outw(unsigned short word, unsigned port);
˙ void outl(unsigned long word, unsigned port);
2. I/O memory
˙ unsigned readb(unsigned port);
˙ unsigned readw(unsigned port);
˙ unsigned readl(unsigned port);
˙ void writeb(unsigned char byte, unsigned port);
˙ void writew(unsigned short word, unsigned port);
˙ void writel(unsigned long word, unsigned port);

此外,對於 I/O memory 的操作,Linux 也提供 memory copy 系列函數如下:

˙ memset_io(address, value, count);
˙ memcpy_fromio(dest, source, num);
˙ memcpy_toio(dest, source, num);

3. PCI configuration space:Linux 讀寫 PCI configuration space(PCI BIOS)的函數:

˙ int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
˙ int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
˙ int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
˙ int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
˙ int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
˙ int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

4. ioremap:重要API,因Linux device driver 「不能直接存取 physical address」。所以,「使用以上的 I/O 相關函數時,只能傳 virtual address,不能傳入 physical address」,ioremap() 就是用來將 physical address 對應到 virtual address 的 API。


I/O 裝置分成以下 2 種(from hardware view):
資料傳輸的方式來區分
Polling:I/O裝置不具備中斷。
Interrupt:I/O裝置以中斷觸發方式進行I/O。


第一步device至kernel(註冊Driver)
1.將driver自己「註冊」到kernel的VFS層,註冊時所要呼叫的函數根據裝置類型的不同而不同。
2.註冊的動作是寫在init_module()裡,因此當使用者執行insmod載入驅動程式時,register_chrdev()便會執行。由此可知,註冊驅動程式的時機為insmod時。

3.將驅動程式「註冊」(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) {
˙ 第1個參數:為device file的major number。該device file應在Linux系統底下以root身份手動建立。
˙ 第2個參數:
˙ 第3個參數:為驅動程式的fops。 MSG("Couldn't register a device.");
return -1;
}

return 0;
}


二 解除註冊:在rmmod時,必須執行解除註冊的動作,此動作必須實作在cleanup_module()函數裡。
˙ 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");
}




環境說明&建置


方法一:
jason@jason-NB:/usr/local/arm$ sudo tar -jxf cross-4.2.2-eabi.tar.bz2
jason@jason-NB:/usr/local/arm$ ls
4.2.2-eabi  cross-4.2.2-eabi.tar.bz2
jason@jason-NB:/usr/local/arm$ vim /etc/profile
jason@jason-NB:/usr/local/arm$ vim /etc/profile
jason@jason-NB:/usr/local/arm$ sudo vim /etc/profile

jason@jason-NB:/usr/local/arm$ sudo vim /etc/profile

PATH="$PATH:/usr/local/arm/4.2.2-eabi/usr/bin"
export PATH

jason@jason-NB:/usr/local/arm$ source PATH
bash: PATH: 沒有此一檔案或目錄
jason@jason-NB:/usr/local/arm$ source /etc/profile


鍵入arm-linux-gcc -v 以檢查配置是否成功
Using built-in specs.
Target: arm-unknown-linux-gnueabi
Configured with: /home/scsuh/workplace/coffee/buildroot-20071011/toolchain_build_arm/gcc-4.2.2/configure --prefix=/usr --build=i386-pc-linux-gnu --host=i386-pc-linux-gnu --target=arm-unknown-linux-gnueabi --enable-languages=c,c++ --with-sysroot=/usr/local/arm/4.2.2-eabi/ --with-build-time-tools=/usr/local/arm/4.2.2-eabi//usr/arm-unknown-linux-gnueabi/bin --disable-__cxa_atexit --enable-target-optspace --with-gnu-ld --enable-shared --with-gmp=/usr/local/arm/4.2.2-eabi//gmp --with-mpfr=/usr/local/arm/4.2.2-eabi//mpfr --disable-nls --enable-threads --disable-multilib --disable-largefile --with-arch=armv4t --with-float=soft --enable-cxx-flags=-msoft-float
Thread model: posix
gcc version 4.2.2

方法二:
有自build嵌入式系統 可以直接用build bsp之環境

沒有留言:

張貼留言