• 沒有找到結果。

控制 PNX1005 的 Linux 驅動程式與功能函式庫

為了控制平台上的 PNX1005 裝置,我們在 ARM 端設計了一個驅動程式來控制它,而這 個驅動程式我們稱為 TMMan 驅動程式。因為 ARM 的 OS 為 linux,所以 TMMan 驅動程式會 利用第二章提到的知識,以 linux 驅動程式架構來實作的驅動程式。3-1 節會先介紹 linux 基本的驅動程式設計環境與操作介面,然後說明設計 TMMan 驅動程式實際上需要設計的是 哪些實體操作函式(3-1-3 TMMan 驅動程式的 driver handlers),而這些實體函數與系統 呼叫之間的關係又是什麼。設計完 TMMan 驅動程式的實體操作函式後,藉由 3-2 先說明一 般 linux 環境下,啟動驅動程式的方法以及應用程式要使用這個驅動程式時,用來建立應 用程式與驅動程式之間的裝置檔所扮演的角色。3-3 節於是總括 3-1、3-2 節所述,統整 TMMan 驅動程式需要設計、介紹 TMMan 驅動程式內部又多加的設計層次(HAL),並在啟動完 TMMan 驅動程式後,針對 TMMan 驅動程式內部進行的初始化動作做了說明。

最後 3-4 節再以驅動程式提供的基礎硬體控制介面(ioctl 系統呼叫),設計方便應用 程式開發者使用、具功能性的功能函式庫,作為驅動程式與應用程式的介面,讓應用程式 開發者可以直接使用這組功能函式,完成一個必頇由多個對硬體的基礎控制才能達到的功 能。

3-1 Linux 驅動程式設計環境與操作介面的設計架構 3-1-1 linux 驅動程式程式運作環境

應用程式

VFS

特殊檔案 磁碟檔案 裝置檔案

“/dev/pnx1005”

硬碟驅動程式

裝置驅動程式 TMMan 驅動程式

裝置 PNX1005 晶片 硬碟

系統呼叫

file_operations

(圖 3.1) 驅動程式與應用程式、裝置之間的關係圖

34

在 linux 環境中,從(圖 3.1)我們可以看到,應用程式與 VFS(virtual file system) 之間的界面是系統呼叫,而 VFS 與磁碟檔案系統以及普通裝置之間的界面是

file_operations 結構體成員函式,file_operations 結構體中每個成員變數均為一個 function pointer,分別指到對檔案進行打開(open)、關閉(close)、讀寫(read/write)、

控制(ioctl)、映射(mmap)等一系列成員函式,而這個 file_operations 結構也會在檔案 被開啟時儲存到代表該檔案的 file 結構裡去,因此不管是哪一種類型的檔案,基本上都 會有與之對應的 file_operations 結構。

在 linux 環境中,會把裝置看成是一個檔案,也就是所謂的裝置檔,對於區塊裝置(EX:

硬碟)而言,ext2、ext3、fat、jffs2 等檔案系統中就會建置好針對 VFS 的 file_operations 成員函式,裝置驅動程式(EX:硬碟驅動程式)將不用另外再設計 file_operations 儲存的 驅動程式實作,而使用者仍可以要求將硬碟裡某個檔案映射到虛擬記憶體的 user space 中。

由於字元裝置(EX:PCI 裝置,非硬碟)的上層沒有磁碟檔案系統,所以字元裝置的驅 動程式必頇額外設計 file_operations 裡的成員函式,而這些成員函式也就負責了驅動程 式對裝置所需的運作,因此我們也把這些成員函式稱為 driver handler。由於 PNX1005 對 ARM 來說就是一個 PCI 裝置,我們並不是用它來做硬碟提供的檔案系統功能,所以設計 TMMan 驅動程式時我們就需要對 file_operations 結構指到的成員函式做設計。

在 linux 環境中設計驅動程式,我們必頇了解 linux 中的程式運作環境,它會將程式 運作環境分成 user space、kernel space 與硬體(如圖 3.2),user space 指的就是應用 程式所在的虛擬記憶體空間,kernel space 則是 kernel 掌管的實體記憶體位址空間,驅 動程式則是 kernel space 的一部分,是用來與硬體做溝通的軟件,當應用程式需要使用 驅動程式來控制硬體時,就必頇把資料從 user space 複製到 kernel space 後再給驅動程 式使用,因此我們必頇善用這些程式運作環境來完成驅動程式的開發。

User space

kernel space

硬體

kernel

裝置 2 的 驅動程式

Kernel API 系統呼叫 API

裝置 1 的 驅動程式

裝置 3 的 驅動程式

裝置 1 裝置 2 裝置 3

(圖 3.2)linux 的程式運作環境

35

為了讓資料在不同的程式運作環境裡轉換,我們在不同的 space 之間會定義一個介面,

用來銜接兩個不同的程式運作空間,其中 user space 與 kernel space 轉換的介面我們稱 為系統呼叫 API,這些 API 就是我們一般常見的 C library 或是 Linux command library;

而 kernel space 裡頭 linux kernel 與驅動程式之間的溝通界面則可稱為 kernel API,為 Linux kernel 原始碼中,提供驅動程式實作函式的函式定義(function prototype)。當應 用程式使用某個系統呼叫後,kernel 就會將這個系統呼叫與 kernel API 裡某個函式做一 對一的對應,所以驅動程式開發者必頇實作好 kernel API 裡所有的函式實體,由這些函 式實體來完成驅動程式要做的事情。另外要特別注意的是,雖然每個系統呼叫與 kernel API 的函式之間存在一對一的對應,但是它們之間的參數傳遞--應用程式呼叫系統呼叫時傳入 的參數型態與資訊,必頇經由 OS 轉成特定的結構型態才能被 kernel API 利用,之後 kernel API 再將資料傳給驅動程式使用。

3-1-2 file_operations 結構

因為 kernel API 的所有 function 實體其實就是在實作驅動程式要做的事,所以我們 也把 kernel API 的 function 實體也稱為 driver handler,而這些 driver handler 會由 kernel 定義的 file_operations 結構來管理(表 3.1,/linux/fs.h),在註冊這個驅動程 式給 kernel 時,會同時將這個 file_operations 結構告知 kernel。

(表 3.1)linux kernel 2.6.22.18 所定義的 file_operations 結構 struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, struct dentry *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

36

int (*check_flags)(int);

int (*dir_notify)(struct file *filp, unsigned long arg);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

};

file_operations 的結構裡每一個成員,都是一個函數指標,指向驅動程式開發者為 裝置驅動程式所寫的函數。因此驅動程式的開發者必頇根據需要,「依照成員宣告順序」

將對應的驅動程式函數實體位址註冊給這些函數指標(如表 3.2)。表中的

<trimedia_function>為驅動程式開發者自己設計的函數實體名稱。

(表 3.2)TMMan 驅動程式開發時,註冊到 file_operations 結構中的 driver handler struct file_operations trimedia_fops = {

.llseek = trimedia_lseek,

而 file_operations 定義的內容其實就是上面提到的 kernel API,裡面的函數指標所 指到的每個函數都會對應到 user space 使用的系統呼叫 (如表 3.3)。因為在註冊驅動程 式給 kernel 時,就會同時告知 kernel,這個驅動程式是靠哪個 file_operations 結構指 到的函數來時作,所以當我們呼叫某個系統呼叫時,就必頇指定裝置檔,因為裝置檔中會 存有 file_operaionts 結構位址,可以用來決定要使用的驅動程式,而後 kernel 就會知 道要利用該 file_operations 裡、與系統呼叫對應的函數來處理事件,這個函數就是該系 統呼叫 的 driver handler。

(表 3.3)TMMan 驅動程式的 driver handler 與系統呼叫對應關係

3-1-3 TMMan 驅動程式的 driver handlers

在 file_operations 結構裡有最重要的兩個 driver handler 是一定要被註冊的,分 別是 open handler 與 close handler 來做開啟/關閉裝置檔的動作,open handler 會依照

系統呼叫

37

檔名去開啟裝置檔,來連接應用程式與驅動程式,並依照需要,在裝置檔的 file 結構中 儲存每個 user process 私有的驅動程式資訊;而 close handler 則負責釋放 open handler 裡占用的記憶體資源並關閉檔案。有了 open 與 close handler 後,驅動程式開發者可以 依照裝置功能,去設計 read 與 write handler、mmap handler、ioctl handler 等等的函 式實體,並將這些函式位址儲存到 file_operations 結構(表 3.1)中。而這些 handler 使 用到的 file 結構,必頇來自應用程式使用 open 系統呼叫後,它所回傳、用來代表這個 file 結構的 file descriptor,因為 file 結構指定了要使用的 file_operations 結構,

所以當我們在使用其他系統呼叫時傳入 file descriptor,kernel 才能使用正確的驅動程 式與 driver handlers 來控制裝置。

如果系統中有設計 read、write handler,使用者呼叫 read、write 系統呼叫時就 可以透過 read/write handler 做裝置檔的讀寫,但因為 PNX1005 並不是一個用來做檔案 讀寫的裝置,所以我們並沒有實作 read 及 write 的部分;而 ioctl/ioctl handler 是一 個多功能的驅動程式控制介面,ioctl( )的用法、功能與裝置提供的服務密切關聯,它主 要是為了讓應用程式可以透過這個介面要求驅動程式做出應用程式要求的硬體相關操作。

舉例來說,在 PNX1005 為 target、ARM 為 host 的環境中,ARM 端的應用程式可以利用不同 的 ioctl 系統呼叫取得 DSP 端 memory address 資訊、控制 DSP 的運作、使用共享記憶體 來處理 DSP 與 ARM 端的溝通、處理 DSP 與 ARM 端的同步機制、設定 DSP 暫存器等。

另外,一般情況下,虛擬記憶體的 user space 是不可能也不應該直接存取裝置的,

但是 mmap 這個系統呼叫可以讓使用者在 user space 就可以存取裝置的實體位址。它將虛 擬記憶體 user space 的一段空間與裝置的記憶體關聯,當使用者存取 user space 的這段 位址範圍時,實際上會轉成對該裝置記憶體空間的存取,可以加速資料的傳遞。因此在 TMMan 驅動程式的設計中,我們也必頇實作 file_operations 結構中指到的 mmap handler 函式,依照應用程式所需,分別把 DSP 端的 RAM、暫存器空間映射到 ARM 端虛擬記憶體空 間去。以下我們介紹(表 3.3)提到的 driver handlers 與系統呼叫的關係,以及 TMMan 驅 動程式中對每個 driver handlers 的實作方式。

Open handler 與 close handler—開啟/關閉裝置檔

open 與 close handler 所需的參數都有來自 kernel 給予的 inode 與 file 結構的指 標,inode 結構是在使用者利用 insomd 註冊驅動程式之後,再利用 mknod 這個指令建立裝 置檔時由 kernel 建立的,所以每個裝置檔只會有一個對應的 inode 結構,主要存放這個 驅動程式控制的裝置的 major 與 minor number 資訊;file 結構內容則如(表 3.4)所列的 file 結構成員,我們輸入裝置檔名稱,由 OS 到檔案系統裡去找尋這個檔案,並取得代表

38

這個檔案的 file 結構,而這個 file 結構主要存放了 file_operations 這個大結構的位址,

file 結構裡的私有資料指標則是驅動程式可以自由使用的指標,能夠用來指到每次在不同 的 process 裡、裝置檔被 open 時,由 kernel 配置、用來儲存驅動程式私有資訊的一塊記 憶體空間,如果沒有特殊需求,這個指標也是可以不被設定。

(表 3.4)開啟裝置檔 系統呼叫:

int open(const char *pathname,int flags);

driver handler:

int (*open)(struct node *node,struct file *file);

int (*open)(struct node *node,struct file *file);

相關文件