摘 要:基于FPGA 嵌入式系統(tǒng),在PowerPC 架構的Linux2.6 操作系統(tǒng)環(huán)境下,對通用輸入輸出接口(GPIO)控制器的驅動,采用平臺設備機制進行中斷控制管理。通過該管理機制,將GPIO 設備本身的資源注冊進內核,由內核統(tǒng)一管理。在參照Linux2.6 內核源碼有關平臺設備驅動的基礎上,編寫和測試了GPIO 設備的驅動程序。該驅動程序已在Xilinx 公司FPGA 開發(fā)板ML403 上驗證,并且穩(wěn)定運行。
從Linux 2.6 起引入了平臺設備機制,即platform device driver 機制,Linux 中大部分設備驅動都可以使用這套機制[1]。和傳統(tǒng)的device driver 機制(通過driverregister 函數(shù)進行注冊)相比,十分明顯的優(yōu)勢在于platform 機制將設備本身的資源注冊進內核,由內核統(tǒng)一管理,在驅動程序中使用這些資源時通過platform device 提供的標準接口進行申請并使用[2]。這樣提高了驅動和資源管理的獨立性,并且擁有較好的可移植性和安全性。文中討論的GPIO 設備具有雙重身份:平臺設備與混雜設備(miscdevice)。平臺設備意味著GPIO控制器設備是屬于平臺的獨立模塊;混雜設備(即主設備號為10)是一種特殊的字符型設備,描述了GPIO 控制器的訪問方式是順序的[1]。
1 Linux中平臺設備驅動開發(fā)流程
ML403 開發(fā)板采用vertex-4 系列FPGA,集成了PowerPC405 硬核,帶有內核管理單元(MMU),因此可以在該開發(fā)板上運行Linux2.6 操作系統(tǒng)。在嵌入式Linux2.6 操作系統(tǒng)中,通過Platform 機制,對外設進行管理。開發(fā)設備驅動的流程如圖1 所示:
1.1 定義platform_device
在Linux 2.6 內核中platform 設備用結構體platform_device 來描述, 該結構體定義在kernel\include\linux\platform_device.h 中:
struct platform_device {
const char * name; //平臺設備的設備名
u32 id; //平臺設備的設備ID
struct device dev; //設備結構體
u32 num_resources; //平臺設備使用的各類資源數(shù)
量
struct resource * resource; //資源
};
該結構一個重要的元素是resource,它存入了最為重要的設備資源信息。在嵌入式開發(fā)工具EDK 中生成BSP( 板級支持包)的時候有一個設備參數(shù)頭文件xparameter.h,里面定義了相關設備的設備數(shù)量、地址資源、中斷資源和時鐘資源等。在添加平臺設備信息的時候需要用到該頭文件中定義的地址信息和中斷信息,Xilinx 公司的Virtex-4 平臺設備是kernel/arch/ppc/syslib/virtex_devices.c 中定義的,在編寫驅動之前,需要在該文件中添加有關GPIO 控制器的設備定義:
/*
* ML300/ML403 Gpio Device: shortcut macro for single instance
*/
#define XPAR_GPIO(num) { \
.name = "xilinx_gpio", \
.id = num, \
.dev.platform_data=XPAR_GPIO_##num##
_IS_DUAL, \
.num_resources = 2, \
.resource = (struct resource[]) { \
{ \
.start=XPAR_GPIO_##num##_B
ASEADDR, \
.end=XPAR_GPIO_##num##_HI
GHADDR, \
.flags = IORESOURCE_MEM, \
}, \
{ \
.start=XPAR_INTC_0_GPIO_##n
um##_VEC_ID, \
.flags = IORESOURCE_IRQ, \
}, \
}, \
}
/* GPIO instances */
#if defined(XPAR_GPIO_0_BASEADDR)
XPAR_GPIO(0),
#endif
#if defined(XPAR_GPIO_1_BASEADDR)
XPAR_GPIO(1),
#endif
#if defined(XPAR_GPIO_2_BASEADDR)
XPAR_GPIO(2),
#endif
上述的代碼定義了GPIO 設備名稱——xilinx_gpio,XPAR_GPIO 平臺設備結構中name 元素和設備驅動的platform_driver 結構體中的driver.name 必須是相同的。這是因為在平臺設備驅動注冊時會對所有已注冊的platform_device 中的name 和當前注冊的platform_driver 的driver.name 進行比較, 使得platfrom_device 和platform_driver 建立關聯(lián),只有找到相同的名稱的platfomr_device 才能注冊成功。在平臺設備的描述中GPIO 設備定義了2 個資源,一個是I/O空間資源,描述了GPIO 控制器設備所占用的總線地址范圍,IORESOURCE_MEM 表示第1 組描述的是內存類型的資源信息;另一個是中斷資源,描述了設備的中斷號,IORESOURCE_IRQ 表示第2 組描述的是中斷資源信息,設備驅動會根據(jù)類型來獲取相應的資源信息。本文共用到三個GPIO 設備XPAR_GPIO(0),XPAR_GPIO(1),XPAR_GPIO(2)。
1.2 注冊platform_device
virtex_devices.c 中的platform_device 是在系統(tǒng)啟動時,使用virtex_init(void)函數(shù)進行注冊。
同時被注冊還有很多virtex 平臺的設備,該函數(shù)是在系統(tǒng)初始化階段調用,驅動注冊時需要匹配內核中所有已注冊的設備名,因此platform_device 設備的注冊過程必須在相應設備驅動加載之前被調用。
1.3 定義platform_driver
與平臺設備對應的平臺設備驅動程序由struct platform_driver 描述:
struct platform_driver {
int (*probe)(struct platform_device *); //探測
int (*remove)(struct platform_device *); //移除
void (*shutdown)(struct platform_device *);//關閉
int (*suspend)(struct platform_device *, pm_
message_t state); //掛起
int (*resume)(struct platform_device *); //恢復
struct device_driver driver;
};
GPIO 的驅動程序中結構體struct platform_driver主要實現(xiàn)了xgpio_driver 的探測和移除函數(shù)。代碼如下:
static struct platform_driver xgpio_driver = {
.probe = xgpio_probe,
.remove = xgpio_remove,
.driver = {
.name = xilinx_gpio,
.bus = &platform_bus_type,
.owner = THIS_MODULE,
}
1.4 注冊platform_driver
最后需要調用platform_driver_register()函數(shù)注冊平臺設備驅動,在注冊成功后會調用platform_driver結構元素probe 函數(shù)指針,進入probe 函數(shù)后,需要獲取設備的資源信息。注冊平臺設備驅動的實現(xiàn)函數(shù)如下:
static int __init xgpio_init(void)
{
return platform_driver_register (&xgpio_
driver);
}
2 GPIO控制器設備驅動
Linux 是保護模式的操作系統(tǒng),內核和應用程序分別運行在完全分離的虛擬地址空間,用戶空間的進程一般不能直接訪問硬件。設備驅動充當了硬件和應用軟件之間的紐帶,它與底層硬件直接打交道,按照硬件設備的具體工作方式讀寫設備寄存器,完成設備的輪詢、中斷處理、DMA 通信,進行物理內存向虛擬內存的映射,最終使通信設備能收發(fā)數(shù)據(jù),使顯示設備能否顯示文字和畫面,使存儲設備能夠記錄文件和數(shù)據(jù)[1]。
2.1 GPIO 控制器的平臺設備驅動函數(shù)實現(xiàn)
使用platform_driver_register(&xgpio_driver)注冊GPIO 設備驅動成功后,利用系統(tǒng)探測函數(shù)probe(),獲取設備需要的資源信息。在探測函數(shù)中,需要通過platform_get_resource()函數(shù)分別獲得GPIO內存和IRQ資源:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根據(jù)參數(shù)type 所指定的類型,IORESOURCE_MEM 和IORESOURCE_IRQ 來獲取指定的資源。
驅動程序中相應代碼為:
regs_res = platform_get_resource(pdev,IORESOURCE_MEM, 0);
irq_res = platform_get_resource(pdev,IORESOURCE_IRQ, 0);
在獲取資源成功后,驅動程序會申請內核空間和I/O 空間,將物理地址映射到虛擬地址以及申請中斷
等。在Linux 內核空間申請內存的主要函數(shù)是kmalloc()、kzalloc()。由這兩個函數(shù)申請的內存位于物理內存映射區(qū)域,在物理上也是連續(xù)的。它們與真實的物理地址只有一個固定的偏移,存在較簡單的轉換關系。
驅動程序中與內存申請有關的程序代碼:
xgpio_inst = kmalloc(sizeof(struct xgpio_instance),GFP_KERNEL);
miscdev = kmalloc(sizeof(struct miscdevice),GFP_KERNEL);
第一個參數(shù)是分配的空間大小,第二個標志表示是在內核空間的進程中申請內存。GFP_KERNEL 標志申請內存時,若暫時不能滿足,則進程會睡眠引起阻塞。使用kmalloc()、kzalloc()申請的內存要用kfree()釋放。
2.1.1 申請I/O 內存空間和映射物理內存:
GPIO 設備控制器有一組寄存器用于讀寫設備和獲取設備狀態(tài),即控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄
存器。這些寄存器位于I/O 內存空間[3]。首先需要調用request_mem_region()申請資源,接著將寄存器地址通過ioremap()將物理地址映射到內核空間虛擬地址,之后才可以調用編程接口訪問這些設備的寄存器。訪問完成后用iounmap()對申請的內核虛擬地址進行釋放,并釋放申請的I/O 內存資源。GPIO 控制器驅動程序的相關代碼如下:
/*申請I/O 內存資源 */
request_mem_region(regs_res->start, remap_size,DRIVER_NAME);
ioremap(regs_res->start, remap_size);
/*映射物理地知道虛擬地址*/
2.1.2 申請中斷:
request_irq(irq_res->start,xgpio_interrupt,0,"XGPIO", xgpio_inst)
irq_res->start 是要申請的硬件中斷號;xgpio_interrupt 是向系統(tǒng)登記的中斷處理函數(shù),是一個
回調函數(shù),中斷發(fā)生時,系統(tǒng)調用這個函數(shù),dev_id參數(shù)(即xgpio_inst)將被傳遞。
2.1.3 釋放虛擬地址和內存資源
static int xgpio_remove(struct platform_device*pdev) { iounmap(xgpio_inst->base_address);
release_mem_region(xgpio_inst->phys_addr,xgpio_inst->remap_size);
kfree(xgpio_inst);
return 0; /*success*/}
2.2 GPIO 控制器的字符型設備接口實現(xiàn)
在Linux 的文件操作系統(tǒng)調用中,字符型設備一般涉及到打開,讀寫和關閉文件等操作。在控制器驅動程序中要給內核提供file_operations 結構,才能為設備驅動提供用戶調用的接口,定義如下:
static struct file_operations xgpio_fops = {
.owner = THIS_MODULE,
.read = xgpio_read,
.open = xgpio_open,
.release = xgpio_release,};
當系統(tǒng)啟動后,GPIO 控制器被初始化,申請資源和內核I/O 內存空間。用戶調用open 函數(shù)打開GPIO設備時,系統(tǒng)調用了xgpio_open()函數(shù),主要完成使能中斷等功能。在打開設備后,返回一個文件指針,可以用這個文件指針對設備進行一系列操作。當用戶調用read()函數(shù)對控制器進行讀取的時候,系統(tǒng)調用了xgpio_read()函數(shù),讀取GPIO 設備數(shù)據(jù)寄存器的值。當用戶調用close()函數(shù)關閉GPIO 設備時,系統(tǒng)調了用xgpio_release()函數(shù),禁止中斷。
2.2.1 GPIO 控制器open()函數(shù)的實現(xiàn)
在打開GPIO 控制器后,依據(jù)GPIO 數(shù)據(jù)文檔,向GPIO 全局中斷使能寄存器GIER 寫入0x80000000,向中斷使能寄存器IER 寫入0x00000003 來使能中斷:xgpio_open()函數(shù)實現(xiàn)代碼如下
static int xgpio_open(struct inode *inode, struct file*file)
{
XIo_Out32((int) xgpioinst->v_addr +
XGPIO_GIER_OFFSET, 0x80000000); /* 全局中斷使能 */
XIo_Out32((int) xgpioinst->v_addr +
XGPIO_IER_OFFSET, 0x00000003); /* 使能GPIO中斷 */
return 0;
}
2.2.2 GPIO 控制器read()函數(shù)的實現(xiàn)
當用戶空間調用read()函數(shù)的時,調用put_user函數(shù)實現(xiàn)內核空間數(shù)據(jù)到應用程序的傳遞,將內核空間傳遞給用戶空間的數(shù)據(jù)。xgpio_read()函數(shù)實現(xiàn)代碼如下:
static ssize_t xgpio_read(struct file *file, char *buf,size_t count, loff_t * ppos)
{
if(put_user(gpio_value, (int*)buf))
return - EFAULT;
else
return sizeof(unsigned int);
}
2.2.3 GPIO 控制器close()函數(shù)的實現(xiàn)
在關閉GPIO 控制器后,向GPIO 全局中斷使能寄存器GIER 寫入0x0,向中斷使能寄存器IER 寫入0x0來禁止中斷。xgpio_release()函數(shù)實現(xiàn)代碼如下:
static int xgpio_release(struct inode *inode, struct file *file)
{
XIo_Out32((int) xgpioinst->v_addr +XGPIO_GIER_OFFSET, 0x0); /* 禁止全局中斷*/
XIo_Out32((int) xgpioinst->v_addr +XGPIO_IER_OFFSET, 0x0); /* 禁止GPIO 中斷*/
return 0;
}
3 設備驅動添加到嵌入式Linux內核中
嵌入式Linux 設備驅動程序編寫完成后,需要將驅動程序加到內核中[4],這要求修改嵌入式Linux 的源代碼,然后重新編譯內核。步驟如下:
3.1 將設備驅動文件拷貝到/linux/driver/char 目錄下
3.2 在/linux/driver/char 目錄下Makefile 中增加如下代碼
obj-$(CONFIG_ XMU_GPIO) +=xgpio;
在/linux/driver/char 目錄下Kconfig 中增加如下代碼:
Config XMU_GPIO
tristate “XMU_GPIO”
depends on XILINX_DRIVERS
select XILINX_EDK
help
This option enables support for Xilinx GPIO.
3.3 重新編譯內核,進入Linux 目錄,執(zhí)行以下代碼
#make menuconfig
在Character Devices-->中找到<>XMU_GPIO 選中為加載模塊的形式:<*>XMU_GPIO,然后保存退出。
#make
這樣得到的內核包含了用戶的設備驅動程序。
4 GPIO驅動程序的測試
在應用程序中利用函數(shù)open() 系統(tǒng)調用xgpio_open()函數(shù)來使能GPIO 中斷,當中斷發(fā)生時,執(zhí)行中斷處理程序;應用程序執(zhí)行read()函數(shù)時,系統(tǒng)調用了xgpio_read()函數(shù)讀取GPIO 數(shù)據(jù)寄存器的值;當應用程序執(zhí)行close()函數(shù)時,系統(tǒng)調用xgpio_release()函數(shù),屏蔽GPIO 中斷。此時,驅動程序測試結束。
5 結語
Linux2.6 內核引入的平臺設備機制,使得內核對設備的管理更加簡便。本文介紹了基于PowerPC 架構的嵌入式Linux 平臺設備驅動的一般設計方法。在基于FPGA 的嵌入式系統(tǒng)中,外設通過GPIO 的IP 核與CPU 的互連,因此,本文介紹的設備驅動程序的設計方法,具有的一定的通用性,對底層驅動程序開發(fā)人員有較好的參考價值。此外,在Linux 系統(tǒng)中,字符設備和塊設備都被映射到文件系統(tǒng)的文件和目錄,很好地體現(xiàn)了“一切都是文件”的思想。所有的字符設備和塊設備都被統(tǒng)一地呈現(xiàn)給用戶,通過文件系統(tǒng)的調用接口read()、write()等函數(shù)即可訪問字符設備和塊設備[1]。
評論