<摘錄>io端口和io內存
linux中的 IO端口映射和IO內存映射
(一)地址的概念
1)物理地址:CPU地址總線傳來的地址,由硬件電路控制其具體含義。物理地址中很大一部分是留給內存條中的內存的,但也常被映射到其他存儲器上 (如顯存、BIOS等)。在程序指令中的虛擬地址經過段映射和頁面映射後,就生成了物理地址,這個物理地址被放到CPU的地址線上。
物理地址空間,一部分給物理RAM(內存)用,一部分給總線用,這是由硬件設計來決定的,因此在32 bits地址線的x86處理器中,物理地址空間是2的32次方,即4GB,但物理RAM一般不能上到4GB,因爲還有一部分要給總線用(總線上還掛着別的 許多設備)。在PC機中,一般是把低端物理地址給RAM用,高端物理地址給總線用。
2)總線地址:總線的地址線或在地址週期上產生的信號。外設使用的是總線地址,CPU使用的是物理地址。
物理地址與總線地址之間的關係由系統的設計決定的。在x86平臺上,物理地址就是總線地址,這是因爲它們共享相同的地址空間——這句話有點難理解,詳見下 面的“獨立編址”。在其他平臺上,可能需要轉換/映射。比如:CPU需要訪問物理地址是0xfa000的單元,那麼在x86平臺上,會產生一個PCI總線 上對0xfa000地址的訪問。因爲物理地址和總線地址相同,所以憑眼睛看是不能確定這個地址是用在哪兒的,它或者在內存中,或者是某個卡上的存儲單元, 甚至可能這個地址上沒有對應的存儲器。
3)虛擬地址:現代操作系統普遍採用虛擬內存管理(Virtual Memory Management)機制,這需要MMU(Memory Management Unit)的支持。MMU通常是CPU的一部分,如果處理器沒有MMU,或者有MMU但沒有啓用,CPU執行單元發出的內存地址將直接傳到芯片引腳上,被
內存芯片(物理內存)接收,這稱爲物理地址(Physical Address),如果處理器啓用了MMU,CPU執行單元發出的內存地址將被MMU截獲,從CPU到MMU的地址稱爲虛擬地址(Virtual Address),而MMU將這個地址翻譯成另一個地址發到CPU芯片的外部地址引腳上,也就是將虛擬地址映射成物理地址。
Linux中,進程的4GB(虛擬)內存分爲用戶空間、內核空間。用戶空間分佈爲0~3GB(即PAGE_OFFSET,在0X86中它等於0xC0000000),剩下的1G爲內核空間。程序員只能使用虛擬地址。系統中每個進程有各自的私有用戶空間(0~3G),這個空間對系統中的其他進程是不可見的。
CPU發出取指令請求時的地址是當前上下文的虛擬地址,MMU再從頁表中找到這個虛擬地址的物理地址,完成取指。同樣讀取數據的也是虛擬地址,比如mov ax, var. 編譯時var就是一個虛擬地址,也是通過MMU從也表中來找到物理地址,再產生總線時序,完成取數據的。
(二)編址方式
1)外設都是通過讀寫設備上的寄存器來進行的,外設寄存器也稱爲“I/O端口”,而IO端口有兩種編址方式:獨立編址和統一編制。
統一編址:外設接口中的IO寄存器(即IO端口)與主存單元一樣看待,每個端口占用一個存儲單元的地址,將主存的一部分劃出來用作IO地址空間,如,在 PDP-11中,把最高的4K主存作爲IO設備寄存器地址。端口占用了存儲器的地址空間,使存儲量容量減小。
統一編址也稱爲“I/O內存”方式,外設寄存器位於“內存空間”(很多外設有自己的內存、緩衝區,外設的寄存器和內存統稱“I/O空間”)。
如,Samsung的S3C2440,是32位ARM處理器,它的4GB地址空間被外設、RAM等瓜分:
0x8000 1000 LED 8*8點陣的地址
0x4800 0000 ~ 0x6000 0000 SFR(特殊暫存器)地址空間
0x3800 1002 鍵盤地址
0x3000 0000 ~ 0x3400 0000 SDRAM空間
0x2000 0020 ~ 0x2000 002e IDE
0x1900 0300 CS8900
獨立編址(單獨編址):IO地址與存儲地址分開獨立編址,I/0端口地址不佔用存儲空間的地址範圍,這樣,在系統中就存在了另一種與存儲地址無關的IO地 址,CPU也必須具有專用與輸入輸出操作的IO指令(IN、OUT等)和控制邏輯。獨立編址下,地址總線上過來一個地址,設備不知道是給IO端口的、還是
給存儲器的,於是處理器通過MEMR/MEMW和IOR/IOW兩組控制信號來實現對I/O端口和存儲器的不同尋址。如,intel 80x86就採用單獨編址,CPU內存和I/O是一起編址的,就是說內存一部分的地址和I/O地址是重疊的。
獨立編址也稱爲“I/O端口”方式,外設寄存器位於“I/O(地址)空間”。
對於x86架構來說,通過IN/OUT指令訪問。PC架構一共有65536個8bit的I/O端口,組成64K個I/O地址空間,編號從 0~0xFFFF,有16位,80x86用低16位地址線A0-A15來尋址。連續兩個8bit的端口可以組成一個16bit的端口,連續4個組成一個 32bit的端口。I/O地址空間和CPU的物理地址空間是兩個不同的概念,例如I/O地址空間爲64K,一個32bit的CPU物理地址空間是4G。 如,在Intel 8086+Redhat9.0 下用“more /proc/ioports”可看到:
0000-001f : dma1
0020-003f : pic1
0040-005f : timer
0060-006f : keyboard
0070-007f : rtc
0080-008f : dma page reg
00a0-00bf : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
……
不過Intel x86平臺普通使用了名爲內存映射(MMIO)的技術,該技術是PCI規範的一部分,IO設備端口被映射到內存空間,映射後,CPU訪問IO端口就如同訪 問內存一樣。看Intel TA 719文檔給出的x86/x64系統典型內存地址分配表:
系統資源 佔用
------------------------------------------------------------------------
BIOS 1M
本地APIC 4K
芯片組保留 2M
IO APIC 4K
PCI設備 256M
PCI Express設備 256M
PCI設備(可選) 256M
顯示幀緩存 16M
TSEG 1M
對於某一既定的系統,它要麼是獨立編址、要麼是統一編址,具體採用哪一種則取決於CPU的體系結構。 如,PowerPC、m68k等採用統一編址,而X86等則採用獨立編址,存在IO空間的概念。目前,大多數嵌入式微控制器如ARM、PowerPC等並 不提供I/O空間,僅有內存空間,可直接用地址、指針訪問。但對於Linux內核而言,它可能用於不同的CPU,所以它必須都要考慮這兩種方式,於是它採 用一種新的方法,將基於I/O映射方式的或內存映射方式的I/O端口通稱爲“I/O區域”(I/O region),不論你採用哪種方式,都要先申請IO區域:request_resource(),結束時釋放
它:release_resource()。
2)對外設的訪問
1、訪問I/O內存的流程是:request_mem_region() -> ioremap() -> ioread8()/iowrite8() -> iounmap() ->release_mem_region() 。
前面說過,IO內存是統一編址下的概念,對於統一編址,IO地址空間是物理主存的一部分,對於編程而言,我們只能操作虛擬內存,所以,訪問的第一步就是要把設備所處的物理地址映射到虛擬地址,Linux2.6下用ioremap():
void *ioremap(unsigned long offset, unsigned long size);
然後,我們可以直接通過指針來訪問這些地址,但是也可以用Linux內核的一組函數來讀寫:
ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()......
2、訪問I/O端口
訪問IO端口有2種途徑:I/O映射方式(I/O-mapped)、內存映射方式(Memory-mapped)。前一種途徑不映射到內存空間,直接使用 intb()/outb()之類的函數來讀寫IO端口;後一種MMIO是先把IO端口映射到IO內存(“內存空間”),再使用訪問IO內存的函數來訪問 IO端口。
void ioport_map(unsigned long port, unsigned int count);
通過這個函數,可以把port開始的count個連續的IO端口映射爲一段“內存空間”,然後就可以在其返回的地址是像訪問IO內存一樣訪問這些IO端口。
Linux下的IO端口和IO內存
CPU對外設端口物理地址的編址方式有兩種:一種是IO映射方式,另一種是內存映射方式。
Linux將基於IO映射方式的和內存映射方式的IO端口統稱爲IO區域(IO region)。
IO region仍然是一種IO資源,因此它仍然可以用resource結構類型來描述。
Linux管理IO region:
1) request_region()
把一個給定區間的IO端口分配給一個IO設備。
2) check_region()
檢查一個給定區間的IO端口是否空閒,或者其中一些是否已經分配給某個IO設備。
3) release_region()
釋放以前分配給一個IO設備的給定區間的IO端口。
Linux中可以通過以下輔助函數來訪問IO端口:
inb(),inw(),inl(),outb(),outw(),outl()
“b”“w”“l”分別代表8位,16位,32位。
對IO內存資源的訪問
1) request_mem_region()
請求分配指定的IO內存資源。
2) check_mem_region()
檢查指定的IO內存資源是否已被佔用。
3) release_mem_region()
釋放指定的IO內存資源。
其中傳給函數的start address參數是內存區的物理地址(以上函數參數表已省略)。
驅動開發人員可以將內存映射方式的IO端口和外設內存統一看作是IO內存資源。
ioremap()用來將IO資源的物理地址映射到內核虛地址空間(3GB - 4GB)中,參數addr是指向內核虛地址的指針。
Linux中可以通過以下輔助函數來訪問IO內存資源:
readb(),readw(),readl(),writeb(),writew(),writel()。
Linux在kernel/resource.c文件中定義了全局變量ioport_resource和iomem_resource,來分別描述基於IO映射方式的整個IO端口空間和基於內存映射方式的IO內存資源空間(包括IO端口和外設內存)。
1)關於IO與內存空間:
在X86處理器中存在着I/O空間的概念,I/O空間是相對於內存空間而言的,它通過特定的指令in、out來訪問。端口號標識了外設的寄存器地址。Intel語法的in、out指令格式爲:
IN 累加器, {端口號│DX}
OUT {端口號│DX},累加器
目前,大多數嵌入式微控制器如ARM、PowerPC等中並不提供I/O空間,而僅存在內存空間。內存空間可以直接通過地址、指針來訪問,程序和程序運行中使用的變量和其他數據都存在於內存空間中。
即便是在X86處理器中,雖然提供了I/O空間,如果由我們自己設計電路板,外設仍然可以只掛接在內存空間。此時,CPU可以像訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的I/O指令。因此,內存空間是必須的,而I/O空間是可選的。
(2)inb和outb:
在Linux設備驅動中,宜使用Linux內核提供的函數來訪問定位於I/O空間的端口,這些函數包括:
· 讀寫字節端口(8位寬)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
· 讀寫字端口(16位寬)
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
· 讀寫長字端口(32位寬)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
· 讀寫一串字節
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
· insb()從端口port開始讀count個字節端口,並將讀取結果寫入addr指向的內存;outsb()將addr指向的內存的count個字節連續地寫入port開始的端口。
· 讀寫一串字
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
· 讀寫一串長字
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函數中I/O端口號port的類型高度依賴於具體的硬件平臺,因此,只是寫出了unsigned。
文章出處:飛諾網(www.diybl.com):http://www.diybl.com/course/6_system/linux/linuxjq/20110922/560798.html
arm ,Linux裏面,全部都會做phy->virt的映射。映射方式中的一種是靜態映射,ioremap是動態映射。在靜態映射之後,仍然可以通過ioremap動態映射,也就是一個IO物理地址可以映射到多個虛擬地址。
(1)關於IO與內存空間:
在X86處理器中存在着I/O空間的概念,I/O空間是相對於內存空間而言的,它通過特定的指令in、out來訪問。端口號標識了外設的寄存器地址。Intel語法的in、out指令格式爲:
IN 累加器, {端口號│DX}
OUT {端口號│DX},累加器
目前,大多數嵌入式微控制器如ARM、PowerPC等中並不提供I/O空間,而僅存在內存空間。內存空間可以直接通過地址、指針來訪問,程序和程序運行中使用的變量和其他數據都存在於內存空間中。
即便是在X86處理器中,雖然提供了I/O空間,如果由我們自己設計電路板,外設仍然可以只掛接在內存空間。此時,CPU可以像訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的I/O指令。因此,內存空間是必須的,而I/O空間是可選的。
(2)inb和outb:
在Linux設備驅動中,宜使用Linux內核提供的函數來訪問定位於I/O空間的端口,這些函數包括:
· 讀寫字節端口(8位寬)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
· 讀寫字端口(16位寬)
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
· 讀寫長字端口(32位寬)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
· 讀寫一串字節
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
· insb()從端口port開始讀count個字節端口,並將讀取結果寫入addr指向的內存;outsb()將addr指向的內存的count個字節連續地寫入port開始的端口。
· 讀寫一串字
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
· 讀寫一串長字
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函數中I/O端口號port的類型高度依賴於具體的硬件平臺,因此,只是寫出了unsigned。
(3)readb和writeb:
在設備的物理地址被映射到虛擬地址之後,儘管可以直接通過指針訪問這些地址,但是工程師宜使用Linux內核的如下一組函數來完成設備內存映射的虛擬地址的讀寫,這些函數包括:
· 讀I/O內存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
與上述函數對應的較早版本的函數爲(這些函數在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
· 寫I/O內存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
與上述函數對應的較早版本的函數爲(這些函數在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
(4)把I/O端口映射到“內存空間”:
void *ioport_map(unsigned long port, unsigned int count);
通過這個函數,可以把port開始的count個連續的I/O端口重映射爲一段“內存空間”。然後就可以在其返回的地址上像訪問I/O內存一樣訪問這些I/O端口。當不再需要這種映射時,需要調用下面的函數來撤消:
void ioport_unmap(void *addr);
實際上,分析ioport_map()的源代碼可發現,所謂的映射到內存空間行爲實際上是給開發人員製造的一個“假象”,並沒有映射到內核虛擬地址,僅僅是爲了讓工程師可使用統一的I/O內存訪問接口訪問I/O端口。
http://blog.csdn.net/ce123/article/details/7204458
一、I/O端口
端口(port)是接口電路中能被CPU直接訪問的寄存器的地址。幾乎每一種外設都是通過讀寫設備上的寄存器來進行的。CPU通過這些地址即端口向接口電路中的寄存器發送命令,讀取狀態和傳送數據。外設寄存器也稱爲“I/O端口”,通常包括:控制寄存器、狀態寄存器和數據寄存器三大類,而且一個外設的寄存器通常被連續地編址。
二、IO內存
例如,在PC上可以插上一塊圖形卡,有2MB的存儲空間,甚至可能還帶有ROM,其中裝有可執行代碼。
三、IO端口和IO內存的區分及聯繫
這兩者如何區分就涉及到硬件知識,X86體系中,具有兩個地址空間:IO空間和內存空間,而RISC指令系統的CPU(如ARM、PowerPC等)通常只實現一個物理地址空間,即內存空間。
內存空間:內存地址尋址範圍,32位操作系統內存空間爲2的32次冪,即4G。
IO空間:X86特有的一個空間,與內存空間彼此獨立的地址空間,32位X86有64K的IO空間。
IO端口:當寄存器或內存位於IO空間時,稱爲IO端口。一般寄存器也俗稱I/O端口,或者說I/O ports,這個I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。
IO內存:當寄存器或內存位於內存空間時,稱爲IO內存。
四、外設IO端口物理地址的編址方式
CPU對外設IO端口物理地址的編址方式有兩種:一種是I/O映射方式(I/O-mapped),另一種是內存映射方式(Memory-mapped)。而具體採用哪一種則取決於CPU的體系結構。
1、統一編址
RISC指令系統的CPU(如,PowerPC、m68k、ARM等)通常只實現一個物理地址空間(RAM)。在這種情況下,外設I/O端口的物理地址就被映射到CPU的單一物理地址空間中,而成爲內存的一部分。此時,CPU可以象訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。
統一編址也稱爲“I/O內存”方式,外設寄存器位於“內存空間”(很多外設有自己的內存、緩衝區,外設的寄存器和內存統稱“I/O空間”)。
2、獨立編址
而另外一些體系結構的CPU(典型地如X86)則爲外設專門實現了一個單獨地地址空間,稱爲“I/O地址空間”或者“I/O端口空間”。這是一個與CPU地RAM物理地址空間不同的地址空間,所有外設的I/O端口均在這一空間中進行編址。CPU通過設立專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元(也即I/O端口)。與RAM物理地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O映射方式”的一個主要缺點。
獨立編址也稱爲“I/O端口”方式,外設寄存器位於“I/O(地址)空間”。
3、優缺點
獨立編址主要優點是:
1)、I/O端口地址不佔用存儲器空間;使用專門的I/O指令對端口進行操作,I/O指令短,執行速度快。
2)、並且由於專門I/O指令與存儲器訪問指令有明顯的區別,使程序中I/O操作和存儲器操作層次清晰,程序的可讀性強。
3)、同時,由於使用專門的I/O指令訪問端口,並且I/O端口地址和存儲器地址是分開的,故I/O端口地址和存儲器地址可以重疊,而不會相互混淆。
4)、譯碼電路比較簡單(因爲I/0端口的地址空間一般較小,所用地址線也就較少)。
其缺點是:只能用專門的I/0指令,訪問端口的方法不如訪問存儲器的方法多。
統一編址優點:
1)、由於對I/O設備的訪問是使用訪問存儲器的指令,所以指令類型多,功能齊全,這不僅使訪問I/O端口可實現輸入/輸出操作,而且還可對端口內容進行算術邏輯運算,移位等等;
2)、另外,能給端口有較大的編址空間,這對大型控制系統和數據通信系統是很有意義的。
這種方式的缺點是端口占用了存儲器的地址空間,使存儲器容量減小,另外指令長度比專門I/O指令要長,因而執行速度較慢。
究竟採用哪一種取決於系統的總體設計。在一個系統中也可以同時使用兩種方式,前提是首先要支持I/O獨立編址。Intel的x86微處理器都支持I/O 獨立編址,因爲它們的指令系統中都有I/O指令,並設置了可以區分I/O訪問和存儲器訪問的控制信號引腳。而一些微處理器或單片機,爲了減少引腳,從而減 少芯片佔用面積,不支持I/O獨立編址,只能採用存儲器統一編址。
五、Linux下訪問IO端口
對於某一既定的系統,它要麼是獨立編址、要麼是統一編址,具體採用哪一種則取決於CPU的體系結構。 如,PowerPC、m68k等採用統一編址,而X86等則採用獨立編址,存在IO空間的概念。目前,大多數嵌入式微控制器如ARM、PowerPC等並不提供I/O空間,僅有內存空間,可直接用地址、指針訪問。但對於Linux內核而言,它可能用於不同的CPU,所以它必須都要考慮這兩種方式,於是它採用一種新的方法,將基於I/O映射方式的或內存映射方式的I/O端口通稱爲“I/O區域”(I/O region),不論你採用哪種方式,都要先申請IO區域:request_resource(),結束時釋放它:release_resource()。
IO region是一種IO資源,因此它可以用resource結構類型來描述。
訪問IO端口有2種途徑:I/O映射方式(I/O-mapped)、內存映射方式(Memory-mapped)。前一種途徑不映射到內存空間,直接使用 intb()/outb()之類的函數來讀寫IO端口;後一種MMIO是先把IO端口映射到IO內存(“內存空間”),再使用訪問IO內存的函數來訪問 IO端口。
1、I/O映射方式
直接使用IO端口操作函數:在設備打開或驅動模塊被加載時申請IO端口區域,之後使用inb(),outb()等進行端口訪問,最後在設備關閉或驅動被卸載時釋放IO端口範圍。
in、out、ins和outs彙編語言指令都可以訪問I/O端口。內核中包含了以下輔助函數來簡化這種訪問:
inb( )、inw( )、inl( )
分別從I/O端口讀取1、2或4個連續字節。後綴“b”、“w”、“l”分別代表一個字節(8位)、一個字(16位)以及一個長整型(32位)。
inb_p( )、inw_p( )、inl_p( )
分別從I/O端口讀取1、2或4個連續字節,然後執行一條“啞元(dummy,即空指令)”指令使CPU暫停。
outb( )、outw( )、outl( )
分別向一個I/O端口寫入1、2或4個連續字節。
outb_p( )、outw_p( )、outl_p( )
分別向一個I/O端口寫入1、2或4個連續字節,然後執行一條“啞元”指令使CPU暫停。
insb( )、insw( )、insl( )
分別從I/O端口讀入以1、2或4個字節爲一組的連續字節序列。字節序列的長度由該函數的參數給出。
outsb( )、outsw( )、outsl( )
分別向I/O端口寫入以1、2或4個字節爲一組的連續字節序列。
流程如下:
雖然訪問I/O端口非常簡單,但是檢測哪些I/O端口已經分配給I/O設備可能就不這麼簡單了,對基於ISA總線的系統來說更是如此。通常,I/O設備驅動程序爲了探測硬件設備,需要盲目地向某一I/O端口寫入數據;但是,如果其他硬件設備已經使用這個端口,那麼系統就會崩潰。爲了防止這種情況的發生,內核必須使用“資源”來記錄分配給每個硬件設備的I/O端口。資源表示某個實體的一部分,這部分被互斥地分配給設備驅動程序。在這裏,資源表示I/O端口地址的一個範圍。每個資源對應的信息存放在resource數據結構中:
- struct resource {
- resource_size_t start;// 資源範圍的開始
- resource_size_t end;// 資源範圍的結束
- const char *name; //資源擁有者的名字
- unsigned long flags;// 各種標誌
- struct resource *parent, *sibling, *child;// 指向資源樹中父親,兄弟和孩子的指針
- };
所有的同種資源都插入到一個樹型數據結構(父親、兄弟和孩子)中;例如,表示I/O端口地址範圍的所有資源都包括在一個根節點爲ioport_resource的樹中。節點的孩子被收集在一個鏈表中,其第一個元素由child指向。sibling字段指向鏈表中的下一個節點。
爲什麼使用樹?例如,考慮一下IDE硬盤接口所使用的I/O端口地址-比如說從0xf000 到 0xf00f。那麼,start字段爲0xf000 且end 字段爲0xf00f的這樣一個資源包含在樹中,控制器的常規名字存放在name字段中。但是,IDE設備驅動程序需要記住另外的信息,也就是IDE鏈主盤使用0xf000 到0xf007的子範圍,從盤使用0xf008 到0xf00f的子範圍。爲了做到這點,設備驅動程序把兩個子範圍對應的孩子插入到從0xf000 到0xf00f的整個範圍對應的資源下。一般來說,樹中的每個節點肯定相當於父節點對應範圍的一個子範圍。I/O端口資源樹(ioport_resource)的根節點跨越了整個I/O地址空間(從端口0到65535)。
任何設備驅動程序都可以使用下面三個函數,傳遞給它們的參數爲資源樹的根節點和要插入的新資源數據結構的地址:
request_resource( ) //把一個給定範圍分配給一個I/O設備。
allocate_resource( ) //在資源樹中尋找一個給定大小和排列方式的可用範圍;若存在,將這個範圍分配給一個I/O設備(主要由PCI設備驅動程序使用,可以使用任意的端口號和主板上的內存地址對其進行配置)。
release_resource( ) //釋放以前分配給I/O設備的給定範圍。
內核也爲以上函數定義了一些應用於I/O端口的快捷函數:request_region( )分配I/O端口的給定範圍,release_region( )釋放以前分配給I/O端口的範圍。當前分配給I/O設備的所有I/O地址的樹都可以從/proc/ioports文件中獲得。
2、內存映射方式
將IO端口映射爲內存進行訪問,在設備打開或驅動模塊被加載時,申請IO端口區域並使用ioport_map()映射到內存,之後使用IO內存的函數進行端口訪問,最後,在設備關閉或驅動模塊被卸載時釋放IO端口並釋放映射。
映射函數的原型爲:
void *ioport_map(unsigned long port, unsigned int count);
通過這個函數,可以把port開始的count個連續的I/O端口重映射爲一段“內存空間”。然後就可以在其返回的地址上像訪問I/O內存一樣訪問這些I/O端口。但請注意,在進行映射前,還必須通過request_region( )分配I/O端口。
當不再需要這種映射時,需要調用下面的函數來撤消:
void ioport_unmap(void *addr);
在設備的物理地址被映射到虛擬地址之後,儘管可以直接通過指針訪問這些地址,但是宜使用Linux內核的如下一組函數來完成訪問I/O內存:·讀I/O內存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
與上述函數對應的較早版本的函數爲(這些函數在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
·寫I/O內存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
與上述函數對應的較早版本的函數爲(這些函數在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
流程如下:
六、Linux下訪問IO內存
IO內存的訪問方法是:首先調用request_mem_region()申請資源,接着將寄存器地址通過ioremap()映射到內核空間的虛擬地址,之後就可以Linux設備訪問編程接口訪問這些寄存器了,訪問完成後,使用ioremap()對申請的虛擬地址進行釋放,並釋放release_mem_region()申請的IO內存資源。
struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
這個函數從內核申請len個內存地址(在3G~4G之間的虛地址),而這裏的start爲I/O物理地址,name爲設備的名稱。注意,。如果分配成功,則返回非NULL,否則,返回NULL。
另外,可以通過/proc/iomem查看系統給各種設備的內存範圍。
要釋放所申請的I/O內存,應當使用release_mem_region()函數:
void release_mem_region(unsigned long start, unsigned long len)
申請一組I/O內存後, 調用ioremap()函數:
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三個參數的含義爲:
phys_addr:與requset_mem_region函數中參數start相同的I/O物理地址;
size:要映射的空間的大小;
flags:要映射的IO空間的和權限有關的標誌;
功能:將一個I/O地址空間映射到內核的虛擬地址空間上(通過release_mem_region()申請到的)
流程如下:
六、ioremap和ioport_map
下面具體看一下ioport_map和ioport_umap的源碼:
- void __iomem *ioport_map(unsigned long port, unsigned int nr)
- {
- if (port > PIO_MASK)
- return NULL;
- return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
- }
- void ioport_unmap(void __iomem *addr)
- {
- /* Nothing to do */
- }
ioport_map僅僅是將port加上PIO_OFFSET(64k),而ioport_unmap則什麼都不做。這樣portio的64k空間就被映射到虛擬地址的64k~128k之間,而ioremap返回的虛擬地址則肯定在3G之上。ioport_map函數的目的是試圖提供與ioremap一致的虛擬地址空間。分析ioport_map()的源代碼可發現,所謂的映射到內存空間行爲實際上是給開發人員製造的一個“假象”,並沒有映射到內核虛擬地址,僅僅是爲了讓工程師可使用統一的I/O內存訪問接口ioread8/iowrite8(......)訪問I/O端口。
最後來看一下ioread8的源碼,其實現也就是對虛擬地址進行了判斷,以區分IO端口和IO內存,然後分別使用inb/outb和readb/writeb來讀寫。
- unsigned int fastcall ioread8(void __iomem *addr)
- {
- IO_COND(addr, return inb(port), return readb(addr));
- }
- #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)
- #define IO_COND(addr, is_pio, is_mmio) do { \
- unsigned long port = (unsigned long __force)addr; \
- if (port < PIO_RESERVED) { \
- VERIFY_PIO(port); \
- port &= PIO_MASK; \
- is_pio; \
- } else { \
- is_mmio; \
- } \
- } while (0)
- 展開:
- unsigned int fastcall ioread8(void __iomem *addr)
- {
- unsigned long port = (unsigned long __force)addr;
- if( port < 0x40000UL ) {
- BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );
- port &= PIO_MASK;
- return inb(port);
- }else{
- return readb(addr);
- }
- }
七、總結
外設IO寄存器地址獨立編址的CPU,這時應該稱外設IO寄存器爲IO端口,訪問IO寄存器可通過ioport_map將其映射到虛擬地址空間,但實際上這是給開發人員製造的一個“假象”,並沒有映射到內核虛擬地址,僅僅是爲了可以使用和IO內存一樣的接口訪問IO寄存器;也可以直接使用in/out指令訪問IO寄存器。
例如:Intel x86平臺普通使用了名爲內存映射(MMIO)的技術,該技術是PCI規範的一部分,IO設備端口被映射到內存空間,映射後,CPU訪問IO端口就如同訪 問內存一樣。
外設IO寄存器地址統一編址的CPU,這時應該稱外設IO寄存器爲IO內存,訪問IO寄存器可通過ioremap將其映射到虛擬地址空間,然後再使用read/write接口訪問。
· “機器人之父”恩格爾伯格逝世,曾造出世界第一個工業機器人
· 資本寒冬下,生活在創業大街的人們
· 百度全家桶全面整合Kindle及Fire平板 成默認搜索引擎
· Uber向第三方應用開放打車功能
· 淘寶拍賣牽手萬科,1212上線百餘套江浙房源部分5折起拍
» 更多新聞...
· 好的架構是進化來的,不是設計來的
· 被誤解的MVC和被神化的MVVM
· 再談設計和編碼
· 什麼時候應該避免寫代碼註釋?
2013-03-07 c++類的構造函數詳解