3.4 管理I/O連接埠資源
我們都知道,採用I/O映射方式的X86處理器為外設實現了一個單獨的地址空間,也即“I/O空間”(I/O
Space)或稱為“I/O連接埠空間”,其大小是64KB(0x0000-0xffff)。linux在其所支援的所有平台上都實現了“I/O連接埠空間”這一概念。
由於I/O空間非常小,因此即使外設匯流排有一個單獨的I/O連接埠空間,卻也不是所有的外設都將其I/O連接埠(指寄存器)映射到“I/O連接埠空間”中。
比如,大多數PCI卡都通過記憶體映射方式來將其I/O連接埠或外設記憶體映射到CPU的RAM物理地址空間中。而老式的ISA卡通常將其I/O連接埠映射到I
/O連接埠空間中。
linux是基於“I/O
Region”這一概念來實現對I/O連接埠資源(I/O-mapped 或
Memory-mapped)的管理的。
3.4.1 資源根節點的定義
linux在kernel/Resource.c檔案中定義了全域變數ioport_resource和iomem_resource,來分別描述基
於I/O映射方式的整個I/O連接埠空間和基於記憶體映射方式的I/O記憶體資源空間(包括I/O連接埠和外設記憶體)。其定義如下:
struct resource ioport_resource =
{ "PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO };
struct resource iomem_resource =
{ "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM
};
這裡在2.6.32核心中有變化, iomem_resource的end由0xffffffff改為了-1;更加合理了。可以參照這個網址http://www.spinics.net/lists/linux-pci/msg09857.html (The iomem_resource map reflects the available physical address space.We statically initialize the end to -1, i.e., 0xffffffff_ffffffff, but of course we can only use as much as the CPU can address.)
其中,宏IO_SPACE_LIMIT表示整個I/O空間的大小,對於X86平台而言,它是0xffff(定義在include/asm-i386/io.h標頭檔中)。這裡我多寫一句,對於arm平台而言,以s3c2410為例,IO_SPACE_LIMIT是0xffffffff, 因為對於arm平台來說,它採用的是記憶體映射方式。
顯然,I/O記憶體空間的大小是4GB。
3.4.2 對I/O連接埠空間的操作
基於I/O
Region的操作函數__XXX_region(),linux在標頭檔include/linux/ioport.h中定義了三個對I/O連接埠空間進
行操作的宏:①request_region()宏,請求在I/O連接埠空間中分配指定範圍的I/O連接埠資源。②check_region()宏,檢查I
/O連接埠空間中的指定I/O連接埠資源是否已被佔用。③release_region()宏,釋放I/O連接埠空間中的指定I/O連接埠資源。這三個宏的定義如
下:
#define request_region(start,n,name)
__request_region(&ioport_resource, (start), (n), (name))
#define check_region(start,n)
__check_region(&ioport_resource, (start), (n))
#define release_region(start,n)
__release_region(&ioport_resource, (start), (n))
其中,宏參數start指定I/O連接埠資源的起始物理地址(是I/O連接埠空間中的物理地址),宏參數n指定I/O連接埠資源的大小。
3.4.3 對I/O記憶體資源的操作
基於I/O
Region的操作函數__XXX_region(),linux在標頭檔include/linux/ioport.h中定義了三個對I/O記憶體資源進行操作的宏:①request_mem_region()宏,請求分配指定的I/O記憶體資源。②check_
mem_region()宏,檢查指定的I/O記憶體資源是否已被佔用。③release_
mem_region()宏,釋放指定的I/O記憶體資源。這三個宏的定義如下:
#define request_mem_region(start,n,name)
__request_region(&iomem_resource, (start), (n),
(name))
#define check_mem_region(start,n)
__check_region(&iomem_resource, (start), (n))
#define release_mem_region(start,n)
__release_region(&iomem_resource, (start), (n))
其中,參數start是I/O記憶體資源的起始物理地址(是CPU的RAM物理地址空間中的物理地址),參數n指定I/O記憶體資源的大小。
3.4.4 對/proc/ioports和/proc/iomem的支援
linux在ioport.h標頭檔中定義了兩個宏:
get_ioport_list()和get_iomem_list(),分別用來實現/proc/ioports檔案和/proc/iomem檔案。其定義如下:
#define get_ioport_list(buf)
get_resource_list(&ioport_resource, buf, PAGE_SIZE)
#define get_mem_list(buf) get_resource_list(&iomem_resource,
buf, PAGE_SIZE)
2.6.32移除了這兩個函數,具體怎麼實現對/proc/ioports和/proc/iomem的支援我還不知道,請高手指教阿
3.5 訪問I/O連接埠空間
在驅動程式請求了I/O連接埠空間中的連接埠資源後,它就可以通過CPU的IO指定來讀寫這些I/O連接埠了。在讀寫I/O連接埠時要注意的一點就是,大多數平台都區分8位、16位和32位的連接埠,也即要注意I/O連接埠的寬度。
linux在include/asm/io.h標頭檔(對於i386平台就是include/asm-i386/io.h)中定義了一系列讀寫不同寬度I/O連接埠的宏函數。如下所示:
⑴讀寫8位寬的I/O連接埠
unsigned char inb(unsigned port);
void outb(unsigned char value,unsigned port);
其中,port參數指定I/O連接埠空間中的連接埠地址。在大多數平台上(如x86)它都是unsigned
short類型的,其它的一些平台上則是unsigned
int類型的。顯然,連接埠地址的類型是由I/O連接埠空間的大小來決定的。
⑵讀寫16位寬的I/O連接埠
unsigned short inw(unsigned port);
void outw(unsigned short value,unsigned port);
⑶讀寫32位寬的I/O連接埠
unsigned int inl(unsigned port);
void outl(unsigned int value,unsigned port);
3.5.1 對I/O連接埠的字串操作
除了上述這些“單發”(single-shot)的I/O操作外,某些CPU也支援對某個I/O連接埠進行連續的讀寫操作,也即對單個I/O連接埠讀或寫一系列位元組、字或32位整數,這就是所謂的“字串I/O指令”(String
Instruction)。這種指令在速度上顯然要比用迴圈來實現同樣的功能要快得多。
linux同樣在io.h檔案中定義了字串I/O讀寫函數:
⑴8位寬的字串I/O操作
void insb(unsigned port,void * addr,unsigned long
count);
void outsb(unsigned port ,void * addr,unsigned long
count);
⑵16位寬的字串I/O操作
void insw(unsigned port,void * addr,unsigned long
count);
void outsw(unsigned port ,void * addr,unsigned long
count);
⑶32位寬的字串I/O操作
void insl(unsigned port,void * addr,unsigned long
count);
void outsl(unsigned port ,void * addr,unsigned long
count);
3.5.2 Pausing I/O
在一些平台上(典型地如X86),對於老式匯流排(如ISA)上的慢速外設來說,如果CPU讀寫其I/O連接埠的速度太快,那就可能會發生遺失資料的現
象。對於這個問題的解決方案就是在兩次連續的I/O操作之間插入一段微小的時延,以便等待慢速外設。這就是所謂的“Pausing
I/O”。
對於Pausing
I/O,linux也在io.h標頭檔中定義了它的I/O讀寫函數,而且都以XXX_p命名,比如:inb_p()、outb_p()等等。下面我們就以out_p()為例進行分析。
將io.h中的宏定義__OUT(b,”b”char)展開後可得如下定義:
extern inline void outb(unsigned char value, unsigned short port)
{
__asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
: : "a" (value), "Nd" (port));
}
extern inline void outb_p(unsigned char value, unsigned short port)
{
__asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
__FULL_SLOW_DOWN_IO
: : "a" (value), "Nd" (port));
}
可以看出,outb_p()函數的實現中被插入了宏__FULL_SLOWN_DOWN_IO,以實現微小的延時。宏__FULL_SLOWN_DOWN_IO在標頭檔io.h中一開始就被定義:
#ifdef SLOW_IO_BY_JUMPING
#define __SLOW_DOWN_IO "
jmp 1f
1: jmp 1f
1:"
#else
#define __SLOW_DOWN_IO "
outb %%al,$0x80"
#endif
#ifdef REALLY_SLOW_IO
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
__SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
#else
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
#endif
顯然,__FULL_SLOW_DOWN_IO就是一個或四個__SLOW_DOWN_IO(根據是否定義了宏REALLY_SLOW_IO來決
定),而宏__SLOW_DOWN_IO則被定義成毫無意義的跳躍陳述式或寫連接埠0x80的操作(根據是否定義了宏SLOW_IO_BY_JUMPING來
決定)。