【linux开发】IO端口和IO内存的区别及分别使用的函数接口
IO端口和IO內(nèi)存的區(qū)別及分別使用的函數(shù)接口?
?????????每個外設(shè)都是通過讀寫其寄存器來控制的。外設(shè)寄存器也稱為I/O端口,通常包括:控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類。根據(jù)訪問外設(shè)寄存器的不同方式,可以把CPU分成兩大類。一類CPU(如M68K,Power PC等)把這些寄存器看作內(nèi)存的一部分,寄存器參與內(nèi)存統(tǒng)一編址,訪問寄存器就通過訪問一般的內(nèi)存指令進行,所以,這種CPU沒有專門用于設(shè)備I/O的指令。這就是所謂的“I/O內(nèi)存”方式。另一類CPU(典型的如X86),將外設(shè)的寄存器看成一個獨立的地址空間,所以訪問內(nèi)存的指令不能用來訪問這些寄存器,而要為對外設(shè)寄存器的讀/寫設(shè)置專用指令,如IN和OUT指令。這就是所謂的“?I/O端口”方式。但是,用于I/O指令的“地址空間”相對來說是很小的,如x86 CPU的I/O空間就只有64KB(0-0xffff)。
????????結(jié)合下圖,我們徹底講述IO端口和IO內(nèi)存以及內(nèi)存之間的關(guān)系。主存16M字節(jié)的SDRAM,外設(shè)是個視頻采集卡,上面有16M字節(jié)的SDRAM作為緩沖區(qū)。
1.???CPU是i386架構(gòu)的情況
?在i386系列的處理中,內(nèi)存和外部IO是獨立編址,也是獨立尋址的。MEM的內(nèi)存空間是32位可以尋址到4G,IO空間是16位可以尋址到64K。
?在Linux內(nèi)核中,訪問外設(shè)上的IO Port必須通過IO Port的尋址方式。而訪問IO Mem就比較羅嗦,外部MEM不能和主存一樣訪問,雖然大小上不相上下,可是外部MEM是沒有在系統(tǒng)中注冊的。訪問外部IO MEM必須通過remap映射到內(nèi)核的MEM空間后才能訪問。為了達到接口的同一性,內(nèi)核提供了IO Port到IO Mem的映射函數(shù)。映射后IO Port就可以看作是IO Mem,按照IO Mem的訪問方式即可。
3.??? CPU是ARM或PPC架構(gòu)的情況
在這一類的嵌入式處理器中,IO Port的尋址方式是采用內(nèi)存映射,也就是IO bus就是Mem bus。系統(tǒng)的尋址能力如果是32位,IO Port+Mem(包括IO Mem)可以達到4G。
??????
1.使用I/O?端口
I/O?端口是驅(qū)動用來和很多設(shè)備通訊的方法。
1.1、分配I/O?端口
在驅(qū)動還沒獨占設(shè)備之前,不應(yīng)對端口進行操作。內(nèi)核提供了一個注冊接口,以允許驅(qū)動聲明其需要的端口:
| #include?<linux/ioport.h> |
??????1.2、操作I/O端口
在驅(qū)動成功請求到I/O?端口后,就可以讀寫這些端口了。大部分硬件會將8位、16位和32位端口區(qū)分開,無法像訪問內(nèi)存那樣混淆使用。驅(qū)動程序必須調(diào)用不同的函數(shù)來訪問不同大小的端口。
如同前面所講的,僅支持單地址空間的計算機體系通過將I/O端口地址重新映射到內(nèi)存地址來偽裝端口I/O?。為了提高移植性,內(nèi)核對驅(qū)動隱藏了這些細節(jié)。Linux?內(nèi)核頭文件(體系依賴的頭文件<asm/io.h>)?定義了下列內(nèi)聯(lián)函數(shù)來存取I/O端口:
| /*?inb/outb:讀/寫字節(jié)端口(8位寬)。有些體系將port參數(shù)定義為unsigned long;而有些平臺則將它定義為unsigned short。inb的返回類型也是依賴體系的 */ |
從現(xiàn)在開始,當(dāng)我們使用unsigned?沒有進一步指定類型時,表示是一個依賴體系的定義。
注意,沒有64位的I/O端口操作函數(shù)。即便在64位體系中,端口地址空間使用一個32位(最大)的數(shù)據(jù)通路。
1.3、從用戶空間訪問I/O端口
1.2節(jié)介紹的函數(shù)主要是提供給驅(qū)動使用,但它們也可在用戶空間使用,至少在PC機上可以。GNU?C?庫在?<sys/io.h>?中定義它們。如果在用戶空間使用這些函數(shù),必須滿足下列條件:
1)、程序必須使用-O選項編譯來強制擴展內(nèi)聯(lián)函數(shù)
2)、必須使用ioperm和iopl系統(tǒng)調(diào)用(#include <sys/perm.h>)?來獲得進行操作I/O端口的權(quán)限。ioperm?為獲取單個端口的操作許可,iopl?為獲取整個I/O空間許可。這2個函數(shù)都是x86特有的
3)、程序必須以root來調(diào)用ioperm或者iopl,或者其父進程(祖先)必須以root獲得的端口操作權(quán)限
如果平臺不支持ioperm和iopl系統(tǒng)調(diào)用,通過使用/dev/prot設(shè)備文件,用戶空間仍然可以存取I/O?端口。但是要注意的是,這個文件的定義也是依賴平臺的。
1.4、字串操作
除了一次傳遞一個數(shù)據(jù)的I/O操作,某些處理器實現(xiàn)了一次傳遞一序列數(shù)據(jù)(單位可以是字節(jié)、字和雙字)的特殊指令。這些所謂的字串指令,它們完成任務(wù)比一個C語言循環(huán)更快。下列宏定義實現(xiàn)字串操作,在某些體系上,它們通過使用單個機器指令實現(xiàn);但如果目標處理器沒有進行字串I/O指令,則通過執(zhí)行一個緊湊的循環(huán)實現(xiàn)。
字串函數(shù)的原型是:
| /* insb:從I/O端口port讀取count個數(shù)據(jù)(單位字節(jié))到以內(nèi)存地址addr為開始的內(nèi)存空間?*/ |
注意:使用字串函數(shù)時,它們直接將字節(jié)流從端口中讀取或?qū)懭搿.?dāng)端口和主機系統(tǒng)有不同的字節(jié)序時,會導(dǎo)致不可預(yù)期的結(jié)果。使用?inw讀取端口應(yīng)在必要時自行轉(zhuǎn)換字節(jié)序,以匹配主機字節(jié)序。
1.5、暫停式I/O操作函數(shù)
由于處理器的速率可能與外設(shè)(尤其是低速設(shè)備)的并不匹配,當(dāng)處理器過快地傳送數(shù)據(jù)到或自總線時,這時可能就會引起問題。解決方法是:如果在I/O?指令后面緊跟著另一個相似的I/O?指令,就必須插入一個小的延時。為此,Linux提供了暫停式I/O操作函數(shù),這些函數(shù)的名子只是在非暫停式I/O操作函數(shù)(前面提到的那些I/O操作函數(shù)都是非暫停式的)名后加上_p?,如inb_p、outb_p等。大部分體系都支持這些函數(shù),盡管它們常常被擴展為與非暫停?I/O?同樣的代碼,因為如果體系使用一個合理的現(xiàn)代外設(shè)總線,沒有必要額外暫停。
以下是ARM體系暫停式I/O宏的定義:
| #define?outb_p(val,port)????outb((val),(port)) |
因為ARM使用內(nèi)部總線,就沒有必要額外暫停,所以暫停式的I/O函數(shù)被擴展為與非暫停式I/O同樣的代碼。
1.6、平臺依賴性
由于自身的特性,I/O指令高度依賴于處理器,非常難以隱藏各體系間的不同。因此,大部分的關(guān)于端口?I/O的源碼是平臺依賴的。以下是x86和ARM所使用函數(shù)的總結(jié):
IA-32?(x86)
x86_64
這個體系支持本章介紹的所有函數(shù);port參數(shù)的類型為unsigned?short。
ARM
端口映射到內(nèi)存,并且支持本章介紹的所有函數(shù);port參數(shù)的類型為unsigned?int;字串函數(shù)用C語言實現(xiàn)。
?
???????2、使用?I/O?內(nèi)存
盡管?I/O?端口在x86世界中非常流行,但是用來和設(shè)備通訊的主要機制是通過內(nèi)存映射的寄存器和設(shè)備內(nèi)存,兩者都稱為I/O?內(nèi)存,因為寄存器和內(nèi)存之間的區(qū)別對軟件是透明的。
I/O?內(nèi)存僅僅是一個類似于RAM?的區(qū)域,處理器通過總線訪問該區(qū)域,以實現(xiàn)對設(shè)備的訪問。同樣,讀寫這個區(qū)域是有邊際效應(yīng)。
根據(jù)計算機體系和總線不同,I/O?內(nèi)存可分為可以或者不可以通過頁表來存取。若通過頁表存取,內(nèi)核必須先重新編排物理地址,使其對驅(qū)動程序可見,這就意味著在進行任何I/O操作之前,你必須調(diào)用ioremap;如果不需要頁表,I/O內(nèi)存區(qū)域就類似于I/O端口,你可以直接使用適當(dāng)?shù)?/span>I/O函數(shù)讀寫它們。
由于邊際效應(yīng)的緣故,不管是否需要?ioremap,都不鼓勵直接使用I/O內(nèi)存指針,而應(yīng)使用專門的I/O內(nèi)存操作函數(shù)。這些I/O內(nèi)存操作函數(shù)不僅在所有平臺上是安全,而且對直接使用指針操作?I/O?內(nèi)存的情況進行了優(yōu)化。
2.1、I/O?內(nèi)存分配和映射
I/O?內(nèi)存區(qū)在使用前必須先分配。分配內(nèi)存區(qū)的函數(shù)接口在<linux/ioport.h>定義中:
| /* request_mem_region分配一個開始于start,len字節(jié)的I/O內(nèi)存區(qū)。分配成功,返回一個非NULL指針;否則返回NULL。系統(tǒng)當(dāng)前所有I/O內(nèi)存分配信息都在/proc/iomem文件中列出,你分配失敗時,可以看看該文件,看誰先占用了該內(nèi)存區(qū) */ |
在訪問I/O內(nèi)存之前,分配I/O內(nèi)存并不是唯一要求的步驟,你還必須保證內(nèi)核可存取該I/O內(nèi)存。訪問I/O內(nèi)存并不只是簡單解引用指針,在許多體系中,I/O?內(nèi)存無法以這種方式直接存取。因此,還必須通過ioremap?函數(shù)設(shè)置一個映射。
| #include?<asm/io.h> /* ioremap_nocache為ioremap的無緩存版本。實際上,在大部分體系中,ioremap與ioremap_nocache的實現(xiàn)一樣的,因為所有 I/O 內(nèi)存都是在無緩存的內(nèi)存地址空間中 */ |
經(jīng)過?ioremap?(和iounmap)之后,設(shè)備驅(qū)動就可以存取任何I/O內(nèi)存地址。注意,ioremap返回的地址不可以直接解引用;相反,應(yīng)當(dāng)使用內(nèi)核提供的訪問函數(shù)。
2.2、訪問I/O內(nèi)存
訪問I/O內(nèi)存的正確方式是通過一系列專門用于實現(xiàn)此目的的函數(shù):
| #include?<asm/io.h> |
2.3、像I/O?內(nèi)存一樣使用端口
一些硬件有一個有趣的特性:?有些版本使用?I/O?端口;而有些版本則使用?I/O?內(nèi)存。不管是I/O?端口還是I/O?內(nèi)存,處理器見到的設(shè)備寄存器都是相同的,只是訪問方法不同。為了統(tǒng)一編程接口,使驅(qū)動程序易于編寫,2.6內(nèi)核提供了一個ioport_map函數(shù):
| /*?ioport_map重新映射count個I/O端口,使它們看起來I/O內(nèi)存。此后,驅(qū)動程序可以在ioport_map返回的地址上使用ioread8和同類函數(shù)。這樣,就可以在編程時,消除了I/O端口和I/O 內(nèi)存的區(qū)別?*/ |
注意,I/O?端口在重新映射前必須使用request_region分配所需的I/O?端口。
?????? 3、ARM體系的I/O操作接口
s3c24x0處理器使用的是I/O內(nèi)存,也就是說:s3c24x0處理器使用統(tǒng)一編址方式,I/O寄存器和內(nèi)存使用的是單一地址空間,并且讀寫I/O寄存器和讀寫內(nèi)存的指令是相同的。所以推薦使用I/O內(nèi)存的相關(guān)指令和函數(shù)。但這并不表示I/O端口的指令在s3c24x0中不可用。如果你注意過s3c24x0關(guān)于I/O方面的內(nèi)核源碼,你就會發(fā)現(xiàn):其實I/O端口的指令只是一個外殼,內(nèi)部還是使用和I/O內(nèi)存一樣的代碼。
下面是ARM體系原始的I/O操作函數(shù)。其實后面I/O端口和I/O內(nèi)存操作函數(shù),只是對這些函數(shù)進行再封裝。從這里也可以看出為什么我們不推薦直接使用I/O端口和I/O內(nèi)存地址指針,而是要求使用專門的I/O操作函數(shù)——專門的I/O操作函數(shù)會檢查地址指針是否有效是否為IO地址(通過__iomem或__chk_io_ptr)
| #include?<asm-arm/io.h> /* |
關(guān)于__force和__iomem
| #include?<linux/compiler.h> /* __force表示所定義的變量類型是可以做強制類型轉(zhuǎn)換的 */ |
I/O端口
| #include?<asm-arm/io.h> #define?outb(v,p)????????__raw_writeb(v,__io(p)) |
I/O內(nèi)存
| #include?<asm-arm/io.h> #define?ioread8(p)????({?unsigned?int?__v?=?__raw_readb(p);?__v;?}) |
注意:
1)、所有的讀寫指令(I/O操作函數(shù))所賦的地址必須都是虛擬地址,你有兩種選擇:使用內(nèi)核已經(jīng)定義好的地址,如在include/asm-arm/arch-s3c2410/regs-xxx.h中定義了s3c2410處理器各外設(shè)寄存器地址(其他處理器芯片也可在類似路徑找到內(nèi)核定義好的外設(shè)寄存器的虛擬地址;另一種方法就是使用自己用ioremap映射的虛擬地址。絕對不能使用實際的物理地址,否則會因為內(nèi)核無法處理地址而出現(xiàn)oops。
2)、在使用I/O指令時,可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因為request的功能只是告訴內(nèi)核端口被誰占用了,如再次request,內(nèi)核會制止(資源busy)。但是不推薦這么做,這樣的代碼也不規(guī)范,可能會引起并發(fā)問題(很多時候我們都需要獨占設(shè)備)。
3)、在使用I/O指令時,所賦的地址數(shù)據(jù)有時必須通過強制類型轉(zhuǎn)換為?unsigned long,不然會有警告。
4)、在include\asm-arm\arch-s3c2410\hardware.h中定義了很多io口的操作函數(shù),有需要可以在驅(qū)動中直接使用,很方便。
Linux系統(tǒng)對IO端口和IO內(nèi)存的管理
http://blog.csdn.net/ce123/article/details/7204458
一、I/O端口
????? 端口(port)是接口電路中能被CPU直接訪問的寄存器的地址。幾乎每一種外設(shè)都是通過讀寫設(shè)備上的寄存器來進行的。CPU通過這些地址即端口向接口電路中的寄存器發(fā)送命令,讀取狀態(tài)和傳送數(shù)據(jù)。外設(shè)寄存器也稱為“I/O端口”,通常包括:控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類,而且一個外設(shè)的寄存器通常被連續(xù)地編址。
二、IO內(nèi)存
?????? 例如,在PC上可以插上一塊圖形卡,有2MB的存儲空間,甚至可能還帶有ROM,其中裝有可執(zhí)行代碼。
三、IO端口和IO內(nèi)存的區(qū)分及聯(lián)系
???????? 這兩者如何區(qū)分就涉及到硬件知識,X86體系中,具有兩個地址空間:IO空間和內(nèi)存空間,而RISC指令系統(tǒng)的CPU(如ARM、PowerPC等)通常只實現(xiàn)一個物理地址空間,即內(nèi)存空間。
內(nèi)存空間:內(nèi)存地址尋址范圍,32位操作系統(tǒng)內(nèi)存空間為2的32次冪,即4G。
IO空間:X86特有的一個空間,與內(nèi)存空間彼此獨立的地址空間,32位X86有64K的IO空間。
IO端口:當(dāng)寄存器或內(nèi)存位于IO空間時,稱為IO端口。一般寄存器也俗稱I/O端口,或者說I/O ports,這個I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。
IO內(nèi)存:當(dāng)寄存器或內(nèi)存位于內(nèi)存空間時,稱為IO內(nèi)存。
?
四、外設(shè)IO端口物理地址的編址方式
??????? CPU對外設(shè)IO端口物理地址的編址方式有兩種:一種是I/O映射方式(I/O-mapped),另一種是內(nèi)存映射方式(Memory-mapped)。而具體采用哪一種則取決于CPU的體系結(jié)構(gòu)。
1、統(tǒng)一編址
RISC指令系統(tǒng)的CPU(如,PowerPC、m68k、ARM等)通常只實現(xiàn)一個物理地址空間(RAM)。在這種情況下,外設(shè)I/O端口的物理地址就被映射到CPU的單一物理地址空間中,而成為內(nèi)存的一部分。此時,CPU可以象訪問一個內(nèi)存單元那樣訪問外設(shè)I/O端口,而不需要設(shè)立專門的外設(shè)I/O指令。
?????? 統(tǒng)一編址也稱為“I/O內(nèi)存”方式,外設(shè)寄存器位于“內(nèi)存空間”(很多外設(shè)有自己的內(nèi)存、緩沖區(qū),外設(shè)的寄存器和內(nèi)存統(tǒng)稱“I/O空間”)。
2、獨立編址
??????? 而另外一些體系結(jié)構(gòu)的CPU(典型地如X86)則為外設(shè)專門實現(xiàn)了一個單獨地地址空間,稱為“I/O地址空間”或者“I/O端口空間”。這是一個與CPU地RAM物理地址空間不同的地址空間,所有外設(shè)的I/O端口均在這一空間中進行編址。CPU通過設(shè)立專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元(也即I/O端口)。與RAM物理地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O映射方式”的一個主要缺點。
??????? 獨立編址也稱為“I/O端口”方式,外設(shè)寄存器位于“I/O(地址)空間”。
3、優(yōu)缺點
獨立編址主要優(yōu)點是:
1)、I/O端口地址不占用存儲器空間;使用專門的I/O指令對端口進行操作,I/O指令短,執(zhí)行速度快。
2)、并且由于專門I/O指令與存儲器訪問指令有明顯的區(qū)別,使程序中I/O操作和存儲器操作層次清晰,程序的可讀性強。
3)、同時,由于使用專門的I/O指令訪問端口,并且I/O端口地址和存儲器地址是分開的,故I/O端口地址和存儲器地址可以重疊,而不會相互混淆。?
4)、譯碼電路比較簡單(因為I/0端口的地址空間一般較小,所用地址線也就較少)。
其缺點是:只能用專門的I/0指令,訪問端口的方法不如訪問存儲器的方法多。
統(tǒng)一編址優(yōu)點:
1)、由于對I/O設(shè)備的訪問是使用訪問存儲器的指令,所以指令類型多,功能齊全,這不僅使訪問I/O端口可實現(xiàn)輸入/輸出操作,而且還可對端口內(nèi)容進行算術(shù)邏輯運算,移位等等;
2)、另外,能給端口有較大的編址空間,這對大型控制系統(tǒng)和數(shù)據(jù)通信系統(tǒng)是很有意義的。
這種方式的缺點是端口占用了存儲器的地址空間,使存儲器容量減小,另外指令長度比專門I/O指令要長,因而執(zhí)行速度較慢。
??????? 究竟采用哪一種取決于系統(tǒng)的總體設(shè)計。在一個系統(tǒng)中也可以同時使用兩種方式,前提是首先要支持I/O獨立編址。Intel的x86微處理器都支持I/O 獨立編址,因為它們的指令系統(tǒng)中都有I/O指令,并設(shè)置了可以區(qū)分I/O訪問和存儲器訪問的控制信號引腳。而一些微處理器或單片機,為了減少引腳,從而減 少芯片占用面積,不支持I/O獨立編址,只能采用存儲器統(tǒng)一編址。
五、Linux下訪問IO端口
?????????? 對于某一既定的系統(tǒng),它要么是獨立編址、要么是統(tǒng)一編址,具體采用哪一種則取決于CPU的體系結(jié)構(gòu)。 如,PowerPC、m68k等采用統(tǒng)一編址,而X86等則采用獨立編址,存在IO空間的概念。目前,大多數(shù)嵌入式微控制器如ARM、PowerPC等并不提供I/O空間,僅有內(nèi)存空間,可直接用地址、指針訪問。但對于Linux內(nèi)核而言,它可能用于不同的CPU,所以它必須都要考慮這兩種方式,于是它采用一種新的方法,將基于I/O映射方式的或內(nèi)存映射方式的I/O端口通稱為“I/O區(qū)域”(I/O region),不論你采用哪種方式,都要先申請IO區(qū)域:request_resource(),結(jié)束時釋放它:release_resource()。
IO region是一種IO資源,因此它可以用resource結(jié)構(gòu)類型來描述。
???????? 訪問IO端口有2種途徑:I/O映射方式(I/O-mapped)、內(nèi)存映射方式(Memory-mapped)。前一種途徑不映射到內(nèi)存空間,直接使用 intb()/outb()之類的函數(shù)來讀寫IO端口;后一種MMIO是先把IO端口映射到IO內(nèi)存(“內(nèi)存空間”),再使用訪問IO內(nèi)存的函數(shù)來訪問 IO端口。
1、I/O映射方式
?????? 直接使用IO端口操作函數(shù):在設(shè)備打開或驅(qū)動模塊被加載時申請IO端口區(qū)域,之后使用inb(),outb()等進行端口訪問,最后在設(shè)備關(guān)閉或驅(qū)動被卸載時釋放IO端口范圍。
?in、out、ins和outs匯編語言指令都可以訪問I/O端口。內(nèi)核中包含了以下輔助函數(shù)來簡化這種訪問:
inb( )、inw( )、inl( )
分別從I/O端口讀取1、2或4個連續(xù)字節(jié)。后綴“b”、“w”、“l(fā)”分別代表一個字節(jié)(8位)、一個字(16位)以及一個長整型(32位)。
inb_p( )、inw_p( )、inl_p( )
分別從I/O端口讀取1、2或4個連續(xù)字節(jié),然后執(zhí)行一條“啞元(dummy,即空指令)”指令使CPU暫停。
outb( )、outw( )、outl( )
分別向一個I/O端口寫入1、2或4個連續(xù)字節(jié)。
outb_p( )、outw_p( )、outl_p( )
分別向一個I/O端口寫入1、2或4個連續(xù)字節(jié),然后執(zhí)行一條“啞元”指令使CPU暫停。
insb( )、insw( )、insl( )
分別從I/O端口讀入以1、2或4個字節(jié)為一組的連續(xù)字節(jié)序列。字節(jié)序列的長度由該函數(shù)的參數(shù)給出。
outsb( )、outsw( )、outsl( )
分別向I/O端口寫入以1、2或4個字節(jié)為一組的連續(xù)字節(jié)序列。
流程如下:
????? 雖然訪問I/O端口非常簡單,但是檢測哪些I/O端口已經(jīng)分配給I/O設(shè)備可能就不這么簡單了,對基于ISA總線的系統(tǒng)來說更是如此。通常,I/O設(shè)備驅(qū)動程序為了探測硬件設(shè)備,需要盲目地向某一I/O端口寫入數(shù)據(jù);但是,如果其他硬件設(shè)備已經(jīng)使用這個端口,那么系統(tǒng)就會崩潰。為了防止這種情況的發(fā)生,內(nèi)核必須使用“資源”來記錄分配給每個硬件設(shè)備的I/O端口。資源表示某個實體的一部分,這部分被互斥地分配給設(shè)備驅(qū)動程序。在這里,資源表示I/O端口地址的一個范圍。每個資源對應(yīng)的信息存放在resource數(shù)據(jù)結(jié)構(gòu)中:
[plain]?view plaincopy
????????? 所有的同種資源都插入到一個樹型數(shù)據(jù)結(jié)構(gòu)(父親、兄弟和孩子)中;例如,表示I/O端口地址范圍的所有資源都包括在一個根節(jié)點為ioport_resource的樹中。節(jié)點的孩子被收集在一個鏈表中,其第一個元素由child指向。sibling字段指向鏈表中的下一個節(jié)點。
??????? 為什么使用樹?例如,考慮一下IDE硬盤接口所使用的I/O端口地址-比如說從0xf000 到 0xf00f。那么,start字段為0xf000 且end 字段為0xf00f的這樣一個資源包含在樹中,控制器的常規(guī)名字存放在name字段中。但是,IDE設(shè)備驅(qū)動程序需要記住另外的信息,也就是IDE鏈主盤使用0xf000 到0xf007的子范圍,從盤使用0xf008 到0xf00f的子范圍。為了做到這點,設(shè)備驅(qū)動程序把兩個子范圍對應(yīng)的孩子插入到從0xf000 到0xf00f的整個范圍對應(yīng)的資源下。一般來說,樹中的每個節(jié)點肯定相當(dāng)于父節(jié)點對應(yīng)范圍的一個子范圍。I/O端口資源樹(ioport_resource)的根節(jié)點跨越了整個I/O地址空間(從端口0到65535)。
任何設(shè)備驅(qū)動程序都可以使用下面三個函數(shù),傳遞給它們的參數(shù)為資源樹的根節(jié)點和要插入的新資源數(shù)據(jù)結(jié)構(gòu)的地址:
request_resource( )??????? //把一個給定范圍分配給一個I/O設(shè)備。
allocate_resource( )??????? //在資源樹中尋找一個給定大小和排列方式的可用范圍;若存在,將這個范圍分配給一個I/O設(shè)備(主要由PCI設(shè)備驅(qū)動程序使用,可以使用任意的端口號和主板上的內(nèi)存地址對其進行配置)。
release_resource( )????? //釋放以前分配給I/O設(shè)備的給定范圍。
內(nèi)核也為以上函數(shù)定義了一些應(yīng)用于I/O端口的快捷函數(shù):request_region( )分配I/O端口的給定范圍,release_region( )釋放以前分配給I/O端口的范圍。當(dāng)前分配給I/O設(shè)備的所有I/O地址的樹都可以從/proc/ioports文件中獲得。
2、內(nèi)存映射方式
?????????將IO端口映射為內(nèi)存進行訪問,在設(shè)備打開或驅(qū)動模塊被加載時,申請IO端口區(qū)域并使用ioport_map()映射到內(nèi)存,之后使用IO內(nèi)存的函數(shù)進行端口訪問,最后,在設(shè)備關(guān)閉或驅(qū)動模塊被卸載時釋放IO端口并釋放映射。
映射函數(shù)的原型為:
void *ioport_map(unsigned long port, unsigned int count);
通過這個函數(shù),可以把port開始的count個連續(xù)的I/O端口重映射為一段“內(nèi)存空間”。然后就可以在其返回的地址上像訪問I/O內(nèi)存一樣訪問這些I/O端口。但請注意,在進行映射前,還必須通過request_region( )分配I/O端口。
當(dāng)不再需要這種映射時,需要調(diào)用下面的函數(shù)來撤消:
void ioport_unmap(void *addr);
在設(shè)備的物理地址被映射到虛擬地址之后,盡管可以直接通過指針訪問這些地址,但是宜使用Linux內(nèi)核的如下一組函數(shù)來完成訪問I/O內(nèi)存:·讀I/O內(nèi)存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
與上述函數(shù)對應(yīng)的較早版本的函數(shù)為(這些函數(shù)在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
·寫I/O內(nèi)存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
與上述函數(shù)對應(yīng)的較早版本的函數(shù)為(這些函數(shù)在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
流程如下:
六、Linux下訪問IO內(nèi)存
???????? IO內(nèi)存的訪問方法是:首先調(diào)用request_mem_region()申請資源,接著將寄存器地址通過ioremap()映射到內(nèi)核空間的虛擬地址,之后就可以Linux設(shè)備訪問編程接口訪問這些寄存器了,訪問完成后,使用ioremap()對申請的虛擬地址進行釋放,并釋放release_mem_region()申請的IO內(nèi)存資源。
struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
?? 這個函數(shù)從內(nèi)核申請len個內(nèi)存地址(在3G~4G之間的虛地址),而這里的start為I/O物理地址,name為設(shè)備的名稱。注意,。如果分配成功,則返回非NULL,否則,返回NULL。
另外,可以通過/proc/iomem查看系統(tǒng)給各種設(shè)備的內(nèi)存范圍。
要釋放所申請的I/O內(nèi)存,應(yīng)當(dāng)使用release_mem_region()函數(shù):
void release_mem_region(unsigned long start, unsigned long len)
申請一組I/O內(nèi)存后, 調(diào)用ioremap()函數(shù):
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三個參數(shù)的含義為:
phys_addr:與requset_mem_region函數(shù)中參數(shù)start相同的I/O物理地址;
size:要映射的空間的大小;
flags:要映射的IO空間的和權(quán)限有關(guān)的標志;
功能:將一個I/O地址空間映射到內(nèi)核的虛擬地址空間上(通過release_mem_region()申請到的)
流程如下:
六、ioremap和ioport_map
下面具體看一下ioport_map和ioport_umap的源碼:
[plain]?view plaincopy ????????? ioport_map僅僅是將port加上PIO_OFFSET(64k),而ioport_unmap則什么都不做。這樣portio的64k空間就被映射到虛擬地址的64k~128k之間,而ioremap返回的虛擬地址則肯定在3G之上。ioport_map函數(shù)的目的是試圖提供與ioremap一致的虛擬地址空間。分析ioport_map()的源代碼可發(fā)現(xiàn),所謂的映射到內(nèi)存空間行為實際上是給開發(fā)人員制造的一個“假象”,并沒有映射到內(nèi)核虛擬地址,僅僅是為了讓工程師可使用統(tǒng)一的I/O內(nèi)存訪問接口ioread8/iowrite8(......)訪問I/O端口。
????????? 最后來看一下ioread8的源碼,其實現(xiàn)也就是對虛擬地址進行了判斷,以區(qū)分IO端口和IO內(nèi)存,然后分別使用inb/outb和readb/writeb來讀寫。
七、總結(jié)
?????????外設(shè)IO寄存器地址獨立編址的CPU,這時應(yīng)該稱外設(shè)IO寄存器為IO端口,訪問IO寄存器可通過ioport_map將其映射到虛擬地址空間,但實際上這是給開發(fā)人員制造的一個“假象”,并沒有映射到內(nèi)核虛擬地址,僅僅是為了可以使用和IO內(nèi)存一樣的接口訪問IO寄存器;也可以直接使用in/out指令訪問IO寄存器。
????????? 例如:Intel x86平臺普通使用了名為內(nèi)存映射(MMIO)的技術(shù),該技術(shù)是PCI規(guī)范的一部分,IO設(shè)備端口被映射到內(nèi)存空間,映射后,CPU訪問IO端口就如同訪 問內(nèi)存一樣。
????????? 外設(shè)IO寄存器地址統(tǒng)一編址的CPU,這時應(yīng)該稱外設(shè)IO寄存器為IO內(nèi)存,訪問IO寄存器可通過ioremap將其映射到虛擬地址空間,然后再使用read/write接口訪問。
轉(zhuǎn)載于:https://www.cnblogs.com/huty/p/8518614.html
總結(jié)
以上是生活随笔為你收集整理的【linux开发】IO端口和IO内存的区别及分别使用的函数接口的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 作业练习3
- 下一篇: linux 入门教程