引 言
編寫 Linux 設備驅動程序無疑是一項復雜的工作。本文將集中介紹非標准硬件的設備驅動程序編寫,探討硬件應用編程接口,並借用 Cirrus Logic EP9312 片上系統嵌入式平台添加設備驅動程序這一案例來進行分析。
如果有些編程內容未能在本文中涉及,那麼讀者亦可以查閱相似的設備驅動程序編碼,以做參考。還有一種方法,就是檢索歷史檔案或者向 Linux 內核問訊中心去函問訊。
Linux 概述
Linux 是 UNIX 操作系統的翻版,1991 年由 Linus Torvalds 最先開發出來,並通過開放源代碼開發模式不斷得到開放源代碼組織的改進。任何使用 Linux 的個人和團體都無需支付任何版權費用。
只有內核還不夠,通常Linux 與一些在內核上運行的視窗環境、視窗管理器和應用捆綁在一起。然而,由於具備了嵌入式平台,視窗環境並非必不可少。與微軟的視窗操作系統不同的是,Linux 並不需要一套固定的、必須采用的應用軟件或實用程序,因此能夠十分符合嵌入式市場終端解決方案的客制化要求。
操作系統最基本的組成部分包括 1個資源管理器、1個調度程序、1個介於硬件和應用軟件之間的接口、1個網絡管理器和 1 個文檔系統管理器。Linux操作系統也包括這些組成部分,當然還有其他部分。本文主要闡述介於硬件和應用軟件之間的接口--設備驅動程序。
設備驅動程序類型
設備驅動程序可分為2大類:硬件設備驅動程序和軟件設備驅動程序。硬件設備驅動程序和物理硬件設備相連接,如UART設備或IDE設備,而軟件設備驅動程序則作為低級數據結構間的接口,或硬件設備驅動程序和高級數據結構間的接口。圖形控制台驅動程序就是一個軟件設備驅動程序。其中,1個LCD控制器驅動程序裝載並管理該顯示器,同時圖形控制台對即將顯示的字符進行著色,並獲取從鍵盤輸入的信息。軟件設備驅動程序的另一個例子是文檔系統執行--文檔系統驅動程序采用1個硬盤驅動程序存儲數據,而該硬盤驅動程序直接與物理硬盤相連接。
設備驅動程序的分類
Linux 設備驅動程序有幾類:字符、區塊、網絡和其他。通常,驅動程序根據設備的訪問方式分類。然而,也有些設備無法按照此類方式得到區分,因此被歸到"其他類型"。字符設備包括那些使數據成為數據流的設備,可通過1個文檔系統的特殊文件獲得(文檔系統的特殊文件將在後文中加以討論)。鑒於字符設備的特性,該設備只能根據順序訪問數據,即無法往前或往後搜索數據。串行端口和音頻設備都是這種類型。圖2是Cirrus Logic的EP9312 片上系統結構圖,其中Linux字符設備以綠色標出。
區塊設備能夠照管1個文檔系統。該類設備和字符設備一樣,也是通過文檔系統特殊文件訪問。但是,區塊設備與文檔設備的差異在於其可被隨機訪問。這意味著,應用軟件可查找在該設備中的隨機位置。硬盤驅動器和CD驅動器都是區塊設備,它們內部的文件指針可以指向設備內部的任何位置,惟一的限制來自設備本身。當區塊設備通過文檔系統特殊文件訪問時,該應用接口即同字符設備一樣,只是與內核的接口有所差別而已。圖2中的紅色部分即為Cirrus Logic EP9312 片上系統結構中Linux區塊設備。
網絡接口設備既可以是硬件設備,也可以是軟件設備。硬件設備如以太網卡,軟件設備如低端網絡協議堆棧(本文將此類接口視為軟件設備)。中間件和協議堆棧有時會被看作是軟件設備。網絡接口設備是信息包數據的通信設備,一般擁有惟一名稱,並且無法通過文檔系統特殊文件訪問。相反,它們只對內核網絡堆棧開放。通常,用戶級應用軟件可訪問內核網絡堆棧,而不能訪問網絡接口設備。圖2中的藍色部分即為Cirrus Logic EP9312 片上系統結構中的Linux網絡接口設備。
其他的設備驅動程序還包括數據總線驅動程序(USB, I2C, AMBA等)、 /proc 接口和視頻驅動程序。這些類型的設備無法被歸入以上的3個類型中,但仍然是與Linux內核接口的設備驅動程序。
文檔系統特殊文件
文檔系統特殊文件提供了從文檔系統訪問硬件設備的可行性。這些訪問點使用mknod 命令在文檔系統/dev 目錄中生成。命令如下:mknod 。
其中, 是給予硬件設備的名稱,如 /dev/hda1 是給予硬盤驅動器的通用名稱。 是設備驅動程序的類型--字符(char)、區塊等。 代表設備類別和與之相配的驅動程序。 表示設備類別中的一個實例,並僅對設備驅動程序適用。例如,某個系統中同時采用2個硬盤驅動器,它們都具有同樣的主要編號,使用同樣的設備驅動軟件,但是該設備驅動程序軟件卻會在內部根據次要編號區分這2個硬盤驅動器。
值得注意的是,並非所有的設備都執行特殊文件接口。如同本文前面已經提及的,網絡設備驅動程序就不采用這種接口訪問設備。
這種情況下,在設備文檔系統裡,就會使用 devfs來獲得文檔設備特殊文件。devfs 目前廣受歡迎,但仍然還不是內核的默認功能。如果采用devfs 文檔系統,那麼就無需mknod 來生成特殊文件了。相反,設備驅動程序軟件會使用直接的devfs 文檔系統接口在空閒時刻或者設備剛被初始化時生成特殊文件。
編程實例概述
為便於示范非標准嵌入式平台的Linux設備驅動程序,本文將說明EP9312的設備驅動程序實現情況。其中,EP9312 IDE設備驅動程序是區塊設備, EP9312觸摸屏為字符設備,代碼中的高級API/硬件接口、初始化序列和應用軟件編碼均將予以說明。
字符設備驅動程序實例:觸摸屏設備驅動程序
EP9312觸摸屏控制器因其數據只能按順序獲取而被列為Linux字符設備。觸摸屏字符驅動程序的執行是相當簡單的--設備向操作系統注冊,並通過文檔系統特殊文件進行訪問。有關硬件代碼包含在文檔操作表的一套函數中。我們將從內核初始化開始,解釋該驅動程序的執行情況。
初始化EP9312觸摸屏的函數是:
int __init ep93xx_ts_init(void)
該函數處理2項工作:當設備被中斷驅動時獲取設備IRQ和在操作系統內注冊觸摸屏設備。
函數request_irq() 在請求IRQ時被調用,並注冊中斷處理器函數以在設備發生系統中斷時處理所需的任務。
而函數 register_chrdev() 則是用來注冊字符設備的。該函數表現形式如下:
int register_chrdev(unsigned int major,
const char * name,
strUCt file_operations *fops)
該函數安裝了字符設備硬件的內核接口。主要編號用於把驅動程序映射到 /dev 目錄中的文檔系統特殊文件。設備被賦予一個名稱,以便內核識辨。此外,file_operations 結構具有對函數指針表的一個指針,該表指向硬件的相應函數。
然而,仍然有一些字符設備不符合預先確定的字符設備范疇。這些設備就用主編號10一起歸於"其他類型",注冊設備用以下函數:
int misc_register(struct miscdevice * misc)
misc_register()用主編號10調用 register_chrdev(),設備名稱和函數表指針通過miscdevice數據結構獲得。同樣,miscdevice 數據結構還保存設備驅動程序所使用的次要號碼。
以下是在設備驅動程序代碼內注冊 EP9312 觸摸屏采用的函數調用:
misc_register(&ep93xx_ts_miscdev)
數據結構 ep93xx_ts_miscdev 是對觸摸屏硬件的內核訪問,定義如下:
static struct miscdevice ep93xx_ts_miscdev =
{
EP93XX_TS_MINOR, /* device minor number */
"ep93xx_ts", /* name of the device */
&ep93xx_ts_fops /* device file operations */
/* table pointer */
}
其他類型設備驅動程序采用次要號碼區分設備。
硬件接口函數在設備驅動器內即被靜態定義,當設備注冊時,由內核通過傳遞給操作系統的文檔操作函數指針獲得。指針列表定義如下:
static struct file_operations ep93xx_ts_fops =
{
owner: THIS_MODULE,
read: ep93xx_ts_read,
write: ep93xx_ts_write,
poll: ep93xx_ts_poll,
open: ep93xx_ts_open,
release: ep93xx_ts_release,
fasync: ep93xx_ts_fasync,
}
初始化觸摸屏設備後,即需創建文檔系統特殊文件,以便協助應用程序代碼訪問設備。創建 EP9312 觸摸屏特殊文件的 mknod 命令如下:
mknod /dev/misc/ep93xx_ts c 10 240
該步驟即可在根目錄系統下的初始化文檔初始化 Linux 時得到執行,也可在命令提示裡實現手動操作。
以下是用戶級應用代碼的一個實例,通過文檔系統特殊文件訪問觸摸屏設備:
#define TS_DEV "/dev/misc/ep93xx_ts"
int read_ts()
{
int fd, nbytes;
short data[3];
fd = open("/dev/misc/ep93xx_ts", O_NONBLOCK);
if ( fd < 0 )
{
printf("Unable to open touch screen device %s!\n", TS_DEV);
exit(1);
}
nbytes = read(pd_fd, data, sizeof(data));
close(fd);
if (nbytes != sizeof(data))
return 0;
return 1;
}
區塊設備驅動器實例:IDE 設備驅動
與 EP9312 IDE 控制器接口的 IDE 設備被劃分為 Linux 區塊設備,其中包括硬盤驅動器和 CDROM 驅動器。這些設備上的數據可以隨機讀取是將其劃分為區塊設備的主要原因。
與簡單的觸摸屏接口執行相比,IDE 區塊設備驅動器是相當復雜的。該設備驅動器被分成幾部分,包括 IDE 區塊設備內核接口、為 IDE 控制器設置的內部驅動器硬件接口(附加的獨立 IDE 設備多達 4 個)、針對硬盤、軟盤等 IDE 設備類型的模塊,以及結構特別接口。通過允許硬件或結構特殊函數的調用,IDE 設備類型數據結構內的函數指針可以實現非標准結構的靈活性和可延展性。圖3為IDE區塊設備驅動程序結構示意圖。下面從設備驅動程序初始化開始說明該驅動程序。
高級IDE驅動程序在Linux內核初始化或模塊安裝(如果驅動程序被設置為模塊)時得到初始化。本文不詳述高級IDE 驅動程序初始化或安裝細節,而是著重討論為初始化定制並與硬件接口的驅動程序各片斷。在高級IDE驅動程序初始化過程中,以下函數被用於設置IDE控制器:
static __inline__ void ide_init_default_hwifs(void)
該函數在文件中被定義為:include/asm/mach/ide.h,為非標准IDE控制器配置硬件接口數據結構,注冊高級IDE驅動程序EP9312 IDE接口,並為接口設置IRQ。
在結構特殊初始化代碼內完成的IRQ設置僅僅在硬件接口數據結構內設置IDE接口所需的平台IRQ號碼。調用request_irq() 由高級IDE驅動程序負責。
IDE硬件接口數據結構通過調用以下函數得到配置,並同時在include/asm/mach/ide.h內得到定義:
static __inline__ void
ide_init_hwif_ports(hw_regs_t *hw,
int data_port,
int ctrl_port,
int *irq)
該函數通過設置硬件接口數據結構內的命令和控制注冊地址配置了非標准EP9312 IDE 接口,並設置和實現EP9312上的接口。
在ide_init_default_hwifs(void) 函數設置IDE控制器並由高級IDE驅動程序注冊硬件接口後,結構特殊接口通過以下函數調用得到進一步初始化:
void ep93xx_ide_init(unsigned int * pointer)
該函數在文檔驅動器/ide/ide-ep93xx.c 內被定義,並同時執行幾個任務--把結構特殊函數映射到硬件接口數據結構內的函數指針函數,如果平台設有DMA則設置DMA接口。
IDE硬件接口數據結構的結構特殊函數指針如下所示:
typedef struct hwif_s
{
…
ide_rw_proc_t *rwproc;
ide_ideproc_t *ideproc;
ide_dmaproc_t *dmaproc; …
} ide_hwif_t;
ideproc 處理PIO模式轉換,並被映射到結構特殊函數 ep93xx_ideproc()。rwproc 和dmaproc 都處理DMA模式轉換。rwproc 向ep93xx_rwproc()映射,dmaproc向ep93xx_dmaproc()映射。高級IDE驅動程序檢測這些指針是否無效。如果確為無效,則放棄結構特殊函數而采用默認函數。ideproc()和dmaproc()均系基於IOCTL的函數,可執行一系列高級IDE驅動程序定義的ioctls命令。rwproc()函數為特殊轉換速度和方向設置IDE控制器。這些EP9312結構特殊函數都在文件驅動程序/ide/ide-ep93xx.c內得到定義。函數原型示意如下:
static void ep93xx_ideproc(ide_ide_action_t action, ide_drive_t * drive, void * buffer, unsigned int count)
static void ep93xx_dmaproc(ide_dma_action_t action, ide_drive_t *drive)
static void ep93xx_rwproc(ide_drive_t * drive, ide_dma_action_t action)
此外,一部分結構特殊執行命令也是幾個IDE普通宏命令的再定義。它們是直接讀寫IDE設備的宏命令。文件 /include/asm/mach/ide.h 下的宏映射到EP9312 定義。
#define OUT_BYTE(b, p) ep93xx_ide_outb((b), (p))
#define OUT_Word(w, p) ep93xx_ide_outw((w), (p))
#define IN_BYTE(p) ep93xx_ide_inb((p))
#define IN_WORD(p) ep93xx_ide_inw((p))
硬件接口(EP9312 IDE控制器接口)被初始化並與高級IDE驅動程序一起注冊後,高級IDE驅動程序通過探測相連的IDE設備硬件接口繼續初始化。如果設備被探測到,則與操作系統一起注冊。設備與操作系統一起注冊後,向能在設備上執行的操作表上映射。這樣,操作系統也獲得了設備的額外信息,並需要對設備進行資源管理。這些額外信息包括大小和分區數量等。以下是注冊IDE硬盤的函數調用:
register_disk(struct gendisk *gd, int drive,
unsigned minors,
struct block_device_operations *ops,
long size)
高級IDE驅動程序用探測設備時獲得的的函數參數值調用這個函數。第一個參數是gd,它是描述盤片布局的數據結構。第二個參數--drive,是設備編號。對於EP9312而言,設備編號或為0,或為1,因為硬件只支持的兩台設備。第三個參數--minors,是設備被探測時發現的盤片分區。第四個參數--block_device_operations,是函數指針列表,系IDE驅動程序硬盤執行所定義。被映射到該列表中的函數采用結構特殊函數執行不同任務。最後一個參數--size,是指設備的扇區數,它同樣也是從設備中直接獲得。
設備指針列表包括以下區塊設備操作:
• open - 設備和驅動程序實例初始化
• release - 關閉設備或清除驅動程序實例
• ioctl - 填補空白,是通過內核向設備驅動程序傳遞的一種信息的一種方式
• check media change - 處理支持可移動媒體的設備
• revalidate - 處理支持可移動媒體的設備(通常為設備指定)
區塊設備的設備操作列表不包括任何輸入輸出操作。對於區塊設備而言,request方法用於處理設備輸入輸出,並與等待的輸入輸出操作隊列相關,因此進一步與字符設備有所區分。Request方法和隊列均由高級IDE設備驅動器定義,與操作系統一起注冊並與設備主要編號相連。
除了將設備和操作系統一起注冊,高級IDE設備驅動程序還通過數據結構在本地管理該設備,數據結構包括映射到IDE設備特別函數的函數指針。下面是映射到針對IDE硬盤函數的該數據結構的一部分:
static ide_driver_t idedisk_driver = {
…
cleanup: idedisk_cleanup,
standby: do_idedisk_standby,
flushcache: do_idedisk_flushcache,
do_request: do_rw_disk,
end_request: NULL,
ioctl: NULL,
open: idedisk_open,
release: idedisk_release,
media_change: idedisk_media_change,
revalidate: idedisk_revalidate,
pre_reset: idedisk_pre_reset,
capacity: idedisk_capacity,
special: idedisk_special,
proc: idedisk_proc,
reinit: idedisk_reinit,
…
};
值得注意的是,一些函數指針直接向與操作系統一起注冊的文件操作列表函數指針映射,而此時IDE設備驅動器內部使用其他函數指針。例如,高級設備驅動程序內部使用函數指針do_request 和 end_request處理要求方法輸入輸出。
這就涵蓋了IDE設備驅動器的結構特殊API。下一步是創建文檔系統特殊文件,從而幫助用戶級應用進入該設備。使用以下命令生成IDE硬盤驅動特殊文件:mknod /dev/hda1 b 3 1
正如在觸摸屏特殊文件創建中談及,可在系統初始化階段安排自動執行該步驟,或者用戶可以在系統啟動運行顯示操作提示時手工操作該命令。
用戶級應用較少直接調用區塊設備。一般而言,區塊設備直接通過內核級文檔系統執行接入。用戶級應用通常獲取具有操作系統實用程序的區塊設備,以執行文檔系統創建、安裝訪問文檔系統的設備等文檔系統操作。命令行工具涵蓋分割、格式化、安裝和驗證區塊設備。例如,以下是用mnknod命令創建的設備的一個安裝設備命令:
mount -t ext3 -o rw /dev/hda1 /mnt/drive
-t ext3 指出設備由一個Extended 3文檔系統配置;-o rw 則說明設備應該具備讀寫函數;/dev/hda1是被安裝設備的文檔系統特殊文件;/mnt/drive 則是用戶獲取設備所存文檔系統內容的安裝位置。
添加Linux內核的新設備驅動程序支持
Linux內核用以下三個命令建立:
make menuconfig (config, xconfig, oldconfig, etc.)
make dep
make
首先,Linux內核針對目標運行環境進行配置。用戶還可選擇添加支持各種設備、支持各種文檔系統和配置引導參數等。當一個新的設備驅動程序在Linux內核中得到執行時,必須增加對該新設備的配置支持,所以要先更新驅動程序目錄中合適設備類型子目錄下的Makefile。在Makefile中,必須增加新選項建立設備驅動程序二進制文件,並且直接與Linux內核相連或創建一個模塊。第二步需要更新驅動程序目錄設備類型子目錄下的Config. in。此新設備的配置選項必須加入Config.in。
小結
本文無意闡述Linux設備驅動程序的各個環節,因為包括Linux源代碼在內的各種資源都已對此做出了解釋。相反,本文旨在探索針對嵌入式非標准設備、用以執行設備驅動程序的硬件API。對於幾個不同類型的設備驅動程序,本文以EP9312片上系統平台為例,詳解了這些為硬件接口定制的API。了解如何設計並執行這些API是為新設備編寫驅動程序的第一步。
值得注意的是,一些函數指針直接向與操作系統一起注冊的文件操作列表函數指針映射,而此時IDE設備驅動器內部使用其他函數指針。例如,高級設備驅動程序內部使用函數指針do_request 和 end_request處理要求方法輸入輸出。
這就涵蓋了IDE設備驅動器的結構特殊API。下一步是創建文檔系統特殊文件,從而幫助用戶級應用進入該設備。使用以下命令生成IDE硬盤驅動特殊文件:mknod /dev/hda1 b 3 1
正如在觸摸屏特殊文件創建中談及,可在系統初始化階段安排自動執行該步驟,或者用戶可以在系統啟動運行顯示操作提示時手工操作該命令。
用戶級應用較少直接調用區塊設備。一般而言,區塊設備直接通過內核級文檔系統執行接入。用戶級應用通常獲取具有操作系統實用程序的區塊設備,以執行文檔系統創建、安裝訪問文檔系統的設備等文檔系統操作。命令行工具涵蓋分割、格式化、安裝和驗證區塊設備。例如,以下是用mnknod命令創建的設備的一個安裝設備命令:
mount -t ext3 -o rw /dev/hda1 /mnt/drive
-t ext3 指出設備由一個Extended 3文檔系統配置;-o rw 則說明設備應該具備讀寫函數;/dev/hda1是被安裝設備的文檔系統特殊文件;/mnt/drive 則是用戶獲取設備所存文檔系統內容的安裝位置。
添加Linux內核的新設備驅動程序支持
Linux內核用以下三個命令建立:
make menuconfig (config, xconfig, oldconfig, etc.)
make dep
make
首先,Linux內核針對目標運行環境進行配置。用戶還可選擇添加支持各種設備、支持各種文檔系統和配置引導參數等。當一個新的設備驅動程序在Linux內核中得到執行時,必須增加對該新設備的配置支持,所以要先更新驅動程序目錄中合適設備類型子目錄下的Makefile。在Makefile中,必須增加新選項建立設備驅動程序二進制文件,並且直接與Linux內核相連或創建一個模塊。第二步需要更新驅動程序目錄設備類型子目錄下的Config. in。此新設備的配置選項必須加入Config.in。
小結
本文無意闡述Linux設備驅動程序的各個環節,因為包括Linux源代碼在內的各種資源都已對此做出了解釋。相反,本文旨在探索針對嵌入式非標准設備、用以執行設備驅動程序的硬件API。對於幾個不同類型的設備驅動程序,本文以EP9312片上系統平台為例,詳解了這些為硬件接口定制的API。了解如何設計並執行這些API是為新設備編寫驅動程序的第一步。