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