设备IO
序言:
前面我們提到,設(shè)備驅(qū)動(dòng)程序的主要功能操作設(shè)備,更準(zhǔn)確的說就是如何操作設(shè)備寄存器或設(shè)備內(nèi)存。不同的計(jì)算機(jī)體系結(jié)構(gòu)提供了不同的設(shè)備操作接口,主要就是IO端口映射(Ports)或IO內(nèi)存映射(Memory-Map )。例如X86平臺(tái),它對(duì)設(shè)備的訪問就同時(shí)提供了IO端口映射方式或IO內(nèi)存映射方式,這個(gè)在大學(xué)的匯編語言課程里有詳細(xì)的介紹,當(dāng)然還有一些平臺(tái)緊提供IO內(nèi)存映射。IO端口映射方式是CPU提供了獨(dú)立的地址空間給設(shè)備IO,并且使用特定的匯編指令操作IO端口。IO內(nèi)存映射方式提供了統(tǒng)一的內(nèi)存編址方式來訪問設(shè)備IO,就像你訪問系統(tǒng)內(nèi)存一樣。
通常對(duì)于一個(gè)給定的硬件平臺(tái)電路板,它的設(shè)備寄存器或內(nèi)存的物理地址就是確定的了,或者是相對(duì)確定的了(它們具有自己的IO地址空間)。但對(duì)于向Linux這樣的操作系統(tǒng),驅(qū)動(dòng)程序是不能直接訪問設(shè)備的物理地址的,它必須把設(shè)備的物理地址映射到Linux內(nèi)核的虛擬地址空間,這樣驅(qū)動(dòng)程序才能通過虛擬地址操作設(shè)備。
IO區(qū)域:
Linux中使用IO區(qū)域(IO Region)來管理設(shè)備IO無論它是IO端口映射還是IO內(nèi)存映射。IO區(qū)域是基于IO資源(Resource)來實(shí)現(xiàn)的,我們首先來看看IO資源在Linux里的定義:
很明顯,它是一個(gè)樹結(jié)構(gòu)。Linux里將IO資源分成不同的類型,如IO(Port)、MEM、IRQ、DMA,同時(shí)內(nèi)核提供了IO Resource的操作函數(shù),用于分配、請(qǐng)求、釋放IO資源。
如果管理的IO資源有多個(gè),直接使用IO資源函數(shù)就顯得有些麻煩,還好Linux可以使用IO區(qū)域來管理這些資源,具體來說,就是Linux定義了一些宏管理IO資源,定義在<linux/ioport.h>頭文件中,如下:
在實(shí)際的編程中,我們基本上是使用這些宏來操作IO資源,即使你只有一個(gè)IO資源,這樣可以保證程序的可擴(kuò)展性和跨平臺(tái)的兼容性。當(dāng)然,你必須獲取到IO資源后才可以在Linux內(nèi)核中操作IO設(shè)備。因此,一般來說,你需要在驅(qū)動(dòng)的初始化函數(shù)在調(diào)用IO區(qū)域請(qǐng)求函數(shù)來獲取IO區(qū)域。
最后要說明一點(diǎn),就是這些宏操作的IO資源有兩類,分別是 ioport_resource 和 iomem_resource ,他們定義在<linux/kernel/resource.c>中:
IO 端口映射:
在一些平臺(tái),特別是X86平臺(tái),外設(shè)通常具有一個(gè)獨(dú)立的地址空間,叫IO地址空間,對(duì)IO地址空間的訪問必須使用特定的IO指令(如x86的IN/OUT指令)。還有一些平臺(tái)并沒有IO地址空間,所有的IO都是內(nèi)存映射(memory-mapped)的,為了提供程序的跨平臺(tái)及兼容性,Linux為那些并不支持IO地址空間的平臺(tái)提供了IO端口操作函數(shù),他們實(shí)際上還是通過訪問IO內(nèi)存映射地址來訪問的。因此,不管你的程序是使用IO端口映射還是IO內(nèi)存映射,它都可以很好的運(yùn)行到各種平臺(tái)上。
回到我們的主題-IO端口映射。前面我們提到,在使用IO設(shè)備之前我們必須向Linux內(nèi)核申請(qǐng)使用的資源,因此通常在我們的設(shè)備初始化函數(shù)或探測函數(shù)之中會(huì)有如下的代碼:
如果成功申請(qǐng)了IO端口資源,那么我們就可以調(diào)用IO端口訪問函數(shù)來訪問IO端口了,它們通常定義在<asm/io.h>頭文件中(每個(gè)平臺(tái)的定義都有所不同,但類似于下表)具體請(qǐng)參考<asm/io.h>頭文件:
IO內(nèi)存映射(memory-mapped)
一些新的驅(qū)動(dòng)程序都會(huì)使用IO內(nèi)存映射方式來訪問IO設(shè)備,因?yàn)橛行┢脚_(tái)僅僅支持IO內(nèi)存映射,如ARM平臺(tái)。通常來說,使用IO內(nèi)存就象使用系統(tǒng)RAM內(nèi)存一樣的簡單,確實(shí)有些平臺(tái)是支持這樣的訪問的,但還是有些平臺(tái)不能象訪問內(nèi)存那樣直接使用IO內(nèi)存地址來訪問外設(shè)IO(Register和RAM),因此內(nèi)核提供了一組通用的API來支持程序的跨平臺(tái)及可移植性。
Linux內(nèi)核提供了兩種操作IO內(nèi)存的函數(shù),一組類似于IO端口函數(shù)用于讀取1、2、4個(gè)字節(jié)數(shù)據(jù),定義在<asm/io.h>頭文件中:
這些是新的IO內(nèi)存操作函數(shù),我們推薦你使用這些函數(shù)。如果你瀏覽Linux內(nèi)核,你會(huì)發(fā)現(xiàn)還有其他一些函數(shù)接口,它們是老的IO內(nèi)存操作函數(shù),Linux內(nèi)核會(huì)慢慢舍棄這些函數(shù)接口,因此盡量不要使用這些函數(shù),但有必要在這里把這些函數(shù)列出來,因?yàn)榇_實(shí)還是有一些新的驅(qū)動(dòng)仍然使用它們(參考<asm/io.h>):
如果你想象操作內(nèi)存那樣成塊的操作IO內(nèi)存,內(nèi)核提供了另外的方法,它們類似于內(nèi)存操作函數(shù)。推薦你使用這些函數(shù)操作IO內(nèi)存而不是直接使用IO內(nèi)存地址,這樣你的程序可以移植到不同的平臺(tái)上,它們定義在<asm/io.h>頭文件中。
同樣在使用IO內(nèi)存之前,你需要向Linux內(nèi)核申請(qǐng)IO區(qū)域:
申請(qǐng)完IO區(qū)域后,你還不能直接使用它們,你必須把這個(gè)地址映射到Linux內(nèi)核的虛擬地址空間中來,這個(gè)操作是通過ioremap函數(shù)來實(shí)現(xiàn)的,請(qǐng)參考<asm/io.h>頭文件:
通過這兩步操作后,你就可以調(diào)用IO內(nèi)存函數(shù)來訪問設(shè)備IO了。
IO端口重映射
這里說的IO端口重映射不是ioremap的功能,ioremap是將IO內(nèi)存映射到Linux內(nèi)核的虛擬地址空間中。我們說的IO端口重映射是將IO端口映射為IO內(nèi)存,這樣就可以象操作IO內(nèi)存一樣操作IO端口了。這樣做的好處是我們可以統(tǒng)一驅(qū)動(dòng)程序的接口(都使用IO內(nèi)存映射),避免為同一個(gè)設(shè)備提供不同的驅(qū)動(dòng)接口。這個(gè)函數(shù)同樣定義在<asm/io.h>頭文件中:
有一點(diǎn)要注意,在調(diào)用完IO端口重映射后,還是需要調(diào)用ioremap函數(shù)把它映射到Linux內(nèi)核的虛擬地址空間中來。
后記
設(shè)備IO的操作就是這些了,其實(shí)在我們的編程中只要調(diào)用幾個(gè)簡單的函數(shù)或宏就可以完成IO端口的操作了。這里有個(gè)問題沒有說明,就是設(shè)備的訪問是需要同步的或著需要延時(shí)等待一段時(shí)間才能進(jìn)行下一步的操作。我們?cè)凇秲?nèi)核同步技術(shù)》一章有個(gè)簡單的介紹,后面我們將補(bǔ)充幾個(gè)例子來進(jìn)一步說明如何進(jìn)行IO操作。
前面我們提到,設(shè)備驅(qū)動(dòng)程序的主要功能操作設(shè)備,更準(zhǔn)確的說就是如何操作設(shè)備寄存器或設(shè)備內(nèi)存。不同的計(jì)算機(jī)體系結(jié)構(gòu)提供了不同的設(shè)備操作接口,主要就是IO端口映射(Ports)或IO內(nèi)存映射(Memory-Map )。例如X86平臺(tái),它對(duì)設(shè)備的訪問就同時(shí)提供了IO端口映射方式或IO內(nèi)存映射方式,這個(gè)在大學(xué)的匯編語言課程里有詳細(xì)的介紹,當(dāng)然還有一些平臺(tái)緊提供IO內(nèi)存映射。IO端口映射方式是CPU提供了獨(dú)立的地址空間給設(shè)備IO,并且使用特定的匯編指令操作IO端口。IO內(nèi)存映射方式提供了統(tǒng)一的內(nèi)存編址方式來訪問設(shè)備IO,就像你訪問系統(tǒng)內(nèi)存一樣。
通常對(duì)于一個(gè)給定的硬件平臺(tái)電路板,它的設(shè)備寄存器或內(nèi)存的物理地址就是確定的了,或者是相對(duì)確定的了(它們具有自己的IO地址空間)。但對(duì)于向Linux這樣的操作系統(tǒng),驅(qū)動(dòng)程序是不能直接訪問設(shè)備的物理地址的,它必須把設(shè)備的物理地址映射到Linux內(nèi)核的虛擬地址空間,這樣驅(qū)動(dòng)程序才能通過虛擬地址操作設(shè)備。
IO區(qū)域:
Linux中使用IO區(qū)域(IO Region)來管理設(shè)備IO無論它是IO端口映射還是IO內(nèi)存映射。IO區(qū)域是基于IO資源(Resource)來實(shí)現(xiàn)的,我們首先來看看IO資源在Linux里的定義:
| struct resource { ????resource_size_t start; ????resource_size_t end; ????const char *name; ????unsigned long flags; ????struct resource *parent, *sibling, *child; }; #define IORESOURCE_IO????????0x00000100??? #define IORESOURCE_MEM????????0x00000200 #define IORESOURCE_IRQ????????0x00000400 #define IORESOURCE_DMA????????0x00000800 extern int request_resource(struct resource *root, struct resource *new); extern int release_resource(struct resource *new); |
很明顯,它是一個(gè)樹結(jié)構(gòu)。Linux里將IO資源分成不同的類型,如IO(Port)、MEM、IRQ、DMA,同時(shí)內(nèi)核提供了IO Resource的操作函數(shù),用于分配、請(qǐng)求、釋放IO資源。
如果管理的IO資源有多個(gè),直接使用IO資源函數(shù)就顯得有些麻煩,還好Linux可以使用IO區(qū)域來管理這些資源,具體來說,就是Linux定義了一些宏管理IO資源,定義在<linux/ioport.h>頭文件中,如下:
| #define request_region(start,n,name)??__request_region(&ioport_resource,(start), (n),(name)) #define request_mem_region(start,n,name)__request_region(&iomem_resource,(start), (n),(name)) #define release_region(start,n)????__release_region(&ioport_resource, (start), (n)) #define check_mem_region(start,n)????__check_region(&iomem_resource, (start), (n)) #define release_mem_region(start,n)????__release_region(&iomem_resource, (start), (n)) |
在實(shí)際的編程中,我們基本上是使用這些宏來操作IO資源,即使你只有一個(gè)IO資源,這樣可以保證程序的可擴(kuò)展性和跨平臺(tái)的兼容性。當(dāng)然,你必須獲取到IO資源后才可以在Linux內(nèi)核中操作IO設(shè)備。因此,一般來說,你需要在驅(qū)動(dòng)的初始化函數(shù)在調(diào)用IO區(qū)域請(qǐng)求函數(shù)來獲取IO區(qū)域。
最后要說明一點(diǎn),就是這些宏操作的IO資源有兩類,分別是 ioport_resource 和 iomem_resource ,他們定義在<linux/kernel/resource.c>中:
| struct resource ioport_resource = { ????.name????= "PCI IO", ????.start????= 0, ????.end????= IO_SPACE_LIMIT, ????.flags????= IORESOURCE_IO, }; struct resource iomem_resource = { ????.name????= "PCI mem", ????.start????= 0, ????.end????= -1, ????.flags????= IORESOURCE_MEM, }; |
IO 端口映射:
在一些平臺(tái),特別是X86平臺(tái),外設(shè)通常具有一個(gè)獨(dú)立的地址空間,叫IO地址空間,對(duì)IO地址空間的訪問必須使用特定的IO指令(如x86的IN/OUT指令)。還有一些平臺(tái)并沒有IO地址空間,所有的IO都是內(nèi)存映射(memory-mapped)的,為了提供程序的跨平臺(tái)及兼容性,Linux為那些并不支持IO地址空間的平臺(tái)提供了IO端口操作函數(shù),他們實(shí)際上還是通過訪問IO內(nèi)存映射地址來訪問的。因此,不管你的程序是使用IO端口映射還是IO內(nèi)存映射,它都可以很好的運(yùn)行到各種平臺(tái)上。
回到我們的主題-IO端口映射。前面我們提到,在使用IO設(shè)備之前我們必須向Linux內(nèi)核申請(qǐng)使用的資源,因此通常在我們的設(shè)備初始化函數(shù)或探測函數(shù)之中會(huì)有如下的代碼:
| if (!request_region(io_addr, IO_NUM, DRV_NAME)) ????????return -ENODEV; |
如果成功申請(qǐng)了IO端口資源,那么我們就可以調(diào)用IO端口訪問函數(shù)來訪問IO端口了,它們通常定義在<asm/io.h>頭文件中(每個(gè)平臺(tái)的定義都有所不同,但類似于下表)具體請(qǐng)參考<asm/io.h>頭文件:
| inb(unsigned port) outb(u8 v, unsigned port) inw(unsigned port) oubw(u16 v, unsigned port) inl(unsigned port) outl(u32 v, unsigned port) |
IO內(nèi)存映射(memory-mapped)
一些新的驅(qū)動(dòng)程序都會(huì)使用IO內(nèi)存映射方式來訪問IO設(shè)備,因?yàn)橛行┢脚_(tái)僅僅支持IO內(nèi)存映射,如ARM平臺(tái)。通常來說,使用IO內(nèi)存就象使用系統(tǒng)RAM內(nèi)存一樣的簡單,確實(shí)有些平臺(tái)是支持這樣的訪問的,但還是有些平臺(tái)不能象訪問內(nèi)存那樣直接使用IO內(nèi)存地址來訪問外設(shè)IO(Register和RAM),因此內(nèi)核提供了一組通用的API來支持程序的跨平臺(tái)及可移植性。
Linux內(nèi)核提供了兩種操作IO內(nèi)存的函數(shù),一組類似于IO端口函數(shù)用于讀取1、2、4個(gè)字節(jié)數(shù)據(jù),定義在<asm/io.h>頭文件中:
| ioread8(p) ioread16(p) ioread32(p) iowrite8(v,p) iowrite16(v,p) iowrite32(v,p) |
這些是新的IO內(nèi)存操作函數(shù),我們推薦你使用這些函數(shù)。如果你瀏覽Linux內(nèi)核,你會(huì)發(fā)現(xiàn)還有其他一些函數(shù)接口,它們是老的IO內(nèi)存操作函數(shù),Linux內(nèi)核會(huì)慢慢舍棄這些函數(shù)接口,因此盡量不要使用這些函數(shù),但有必要在這里把這些函數(shù)列出來,因?yàn)榇_實(shí)還是有一些新的驅(qū)動(dòng)仍然使用它們(參考<asm/io.h>):
| readb() readw() readl() writeb() writew() writel() |
如果你想象操作內(nèi)存那樣成塊的操作IO內(nèi)存,內(nèi)核提供了另外的方法,它們類似于內(nèi)存操作函數(shù)。推薦你使用這些函數(shù)操作IO內(nèi)存而不是直接使用IO內(nèi)存地址,這樣你的程序可以移植到不同的平臺(tái)上,它們定義在<asm/io.h>頭文件中。
| extern void _memcpy_fromio(void *, const volatile void __iomem *, size_t); extern void _memcpy_toio(volatile void __iomem *, const void *, size_t); extern void _memset_io(volatile void __iomem *, int, size_t); |
同樣在使用IO內(nèi)存之前,你需要向Linux內(nèi)核申請(qǐng)IO區(qū)域:
| if (!request_mem_region(mapbase, size, DRVNAME)) { ????????????ret = -EBUSY; ????????????break; } |
申請(qǐng)完IO區(qū)域后,你還不能直接使用它們,你必須把這個(gè)地址映射到Linux內(nèi)核的虛擬地址空間中來,這個(gè)操作是通過ioremap函數(shù)來實(shí)現(xiàn)的,請(qǐng)參考<asm/io.h>頭文件:
| membase = ioremap(mapbase, size); |
通過這兩步操作后,你就可以調(diào)用IO內(nèi)存函數(shù)來訪問設(shè)備IO了。
IO端口重映射
這里說的IO端口重映射不是ioremap的功能,ioremap是將IO內(nèi)存映射到Linux內(nèi)核的虛擬地址空間中。我們說的IO端口重映射是將IO端口映射為IO內(nèi)存,這樣就可以象操作IO內(nèi)存一樣操作IO端口了。這樣做的好處是我們可以統(tǒng)一驅(qū)動(dòng)程序的接口(都使用IO內(nèi)存映射),避免為同一個(gè)設(shè)備提供不同的驅(qū)動(dòng)接口。這個(gè)函數(shù)同樣定義在<asm/io.h>頭文件中:
| extern void __iomem *ioport_map(unsigned long port, unsigned int nr); extern void ioport_unmap(void __iomem *addr); |
有一點(diǎn)要注意,在調(diào)用完IO端口重映射后,還是需要調(diào)用ioremap函數(shù)把它映射到Linux內(nèi)核的虛擬地址空間中來。
后記
設(shè)備IO的操作就是這些了,其實(shí)在我們的編程中只要調(diào)用幾個(gè)簡單的函數(shù)或宏就可以完成IO端口的操作了。這里有個(gè)問題沒有說明,就是設(shè)備的訪問是需要同步的或著需要延時(shí)等待一段時(shí)間才能進(jìn)行下一步的操作。我們?cè)凇秲?nèi)核同步技術(shù)》一章有個(gè)簡單的介紹,后面我們將補(bǔ)充幾個(gè)例子來進(jìn)一步說明如何進(jìn)行IO操作。
總結(jié)
- 上一篇: 设备驱动模型初探
- 下一篇: Linux设备驱动模型之platform