日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

【linux开发】IO端口和IO内存的区别及分别使用的函数接口

發(fā)布時間:2025/3/21 linux 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【linux开发】IO端口和IO内存的区别及分别使用的函数接口 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

IO端口和IO內(nèi)存的區(qū)別及分別使用的函數(shù)接口?

?????????每個外設(shè)都是通過讀寫其寄存器來控制的。外設(shè)寄存器也稱為I/O端口,通常包括:控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類。根據(jù)訪問外設(shè)寄存器的不同方式,可以把CPU分成兩大類。一類CPU(如M68KPower PC等)把這些寄存器看作內(nèi)存的一部分,寄存器參與內(nèi)存統(tǒng)一編址訪問寄存器就通過訪問一般的內(nèi)存指令進行,所以,這種CPU沒有專門用于設(shè)備I/O的指令。這就是所謂的I/O內(nèi)存方式。另一類CPU(典型的如X86),將外設(shè)的寄存器看成一個獨立的地址空間,所以訪問內(nèi)存的指令不能用來訪問這些寄存器,而要為對外設(shè)寄存器的讀/寫設(shè)置專用指令,如INOUT指令。這就是所謂的?I/O端口方式。但是,用于I/O指令的地址空間相對來說是很小的,如x86 CPUI/O空間就只有64KB00xffff)

????????結(jié)合下圖,我們徹底講述IO端口和IO內(nèi)存以及內(nèi)存之間的關(guān)系。主存16M字節(jié)的SDRAM,外設(shè)是個視頻采集卡,上面有16M字節(jié)的SDRAM作為緩沖區(qū)。


1.???CPUi386架構(gòu)的情況

?i386系列的處理中,內(nèi)存和外部IO是獨立編址,也是獨立尋址的。MEM的內(nèi)存空間是32位可以尋址到4GIO空間是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 PortIO Mem的映射函數(shù)。映射后IO Port就可以看作是IO Mem,按照IO Mem的訪問方式即可。

3.??? CPUARMPPC架構(gòu)的情況

在這一類的嵌入式處理器中,IO Port的尋址方式是采用內(nèi)存映射,也就是IO bus就是Mem bus。系統(tǒng)的尋址能力如果是32位,IO PortMem(包括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>
/* request_region告訴內(nèi)核:要使用first開始的n個端口。參數(shù)name為設(shè)備名。如果分配成功返回值是非NULL;否則無法使用需要的端口(/proc/ioports包含了系統(tǒng)當(dāng)前所有端口的分配信息,若request_region分配失敗時,可以查看該文件,看誰先用了你要的端口) */
struct?resource?*request_region(unsigned?long?first,?unsigned?long?n,?const?char?*name);
/* 用完I/O端口后(可能在模塊卸載時),應(yīng)當(dāng)調(diào)用release_region將I/O端口返還給系統(tǒng)。參數(shù)start和n應(yīng)與之前傳遞給request_region一致 */
void?release_region(unsigned?long?start,?unsigned?long?n);?
/*?check_region用于檢查一個給定的I/O端口集是否可用。如果給定的端口不可用,check_region返回一個錯誤碼。不推薦使用該函數(shù),因為即便它返回0(端口可用),它也不能保證后面的端口分配操作會成功,因為檢查和后面的端口分配并不是一個原子操作。而request_region通過加鎖來保證操作的原子性,因此是安全的 */
int?check_region(unsigned?long?first,?unsigned?long?n);


?????
?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的返回類型也是依賴體系的 */
unsigned?inb(unsigned?port);
void?outb(unsigned?char?byte,?unsigned?port);
/*?inw/outw:讀/寫字端口(16位寬)?*/
unsigned?inw(unsigned?port);
void?outw(unsigned?short?word,?unsigned?port);
/*?inl/outl:讀/寫32位端口。longword也是依賴體系的,有的體系為unsigned long;而有的為unsigned int */
unsigned?inl(unsigned?port);
void?outl(unsigned?longword,?unsigned?port);

從現(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)、必須使用iopermiopl系統(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)限

如果平臺不支持iopermiopl系統(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)存空間?*/
void?insb(unsigned?port,?void?*addr,?unsigned?long?count);
/* outsb:將內(nèi)存地址addr開始的count個數(shù)據(jù)(單位字節(jié))寫到I/O端口port?*/
void?outsb(unsigned?port,?void?*addr,?unsigned?long?count);
/* insw:從I/O端口port讀取count個數(shù)據(jù)(單位字)到以內(nèi)存地址addr為開始的內(nèi)存空間?*/
void?insw(unsigned?port,?void?*addr,?unsigned?long?count);
/* outsw:將內(nèi)存地址addr開始的count個數(shù)據(jù)(單位字)寫到I/O端口port?*/
void?outsw(unsigned?port,?void?*addr,?unsigned?long?count);
/* insl:從I/O端口port讀取count個數(shù)據(jù)(單位雙字)到以內(nèi)存地址addr為開始的內(nèi)存空間?*/
void?insl(unsigned?port,?void?*addr,?unsigned?long?count);
/* outsl:將內(nèi)存地址addr開始的count個數(shù)據(jù)(單位雙字)寫到I/O端口port?*/
void?outsl(unsigned?port,?void?*addr,?unsigned?long?count);

注意:使用字串函數(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_poutb_p等。大部分體系都支持這些函數(shù),盡管它們常常被擴展為與非暫停?I/O?同樣的代碼,因為如果體系使用一個合理的現(xiàn)代外設(shè)總線,沒有必要額外暫停。

以下是ARM體系暫停式I/O宏的定義:

#define?outb_p(val,port)????outb((val),(port))
#define?outw_p(val,port)????outw((val),(port))
#define?outl_p(val,port)????outl((val),(port))
#define?inb_p(port)????????inb((port))
#define?inw_p(port)????????inw((port))
#define?inl_p(port)????????inl((port))
#define?outsb_p(port,from,len)????outsb(port,from,len)
#define?outsw_p(port,from,len)????outsw(port,from,len)
#define?outsl_p(port,from,len)????outsl(port,from,len)
#define?insb_p(port,to,len)????insb(port,to,len)
#define?insw_p(port,to,len)????insw(port,to,len)
#define?insl_p(port,to,len)????insl(port,to,len)

因為ARM使用內(nèi)部總線,就沒有必要額外暫停,所以暫停式的I/O函數(shù)被擴展為與非暫停式I/O同樣的代碼。

1.6、平臺依賴性

由于自身的特性,I/O指令高度依賴于處理器,非常難以隱藏各體系間的不同。因此,大部分的關(guān)于端口?I/O的源碼是平臺依賴的。以下是x86ARM所使用函數(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.1I/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ū) */
struct?resource?*request_mem_region(unsigned?long?start,?unsigned?long?len,?char?*name);
/* release_mem_region用于釋放不再需要的I/O內(nèi)存區(qū)?*/
void?release_mem_region(unsigned?long?start,?unsigned?long?len);?
/* check_mem_region用于檢查I/O內(nèi)存區(qū)的可用性。同樣,該函數(shù)不安全,不推薦使用 */
int?check_mem_region(unsigned?long?start,?unsigned?long?len);

在訪問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用于將I/O內(nèi)存區(qū)映射到虛擬地址。參數(shù)phys_addr為要映射的I/O內(nèi)存起始地址,參數(shù)size為要映射的I/O內(nèi)存的大小,返回值為被映射到的虛擬地址 */
void?*ioremap(unsigned?long?phys_addr,?unsigned?long?size);

/* ioremap_nocache為ioremap的無緩存版本。實際上,在大部分體系中,ioremap與ioremap_nocache的實現(xiàn)一樣的,因為所有 I/O 內(nèi)存都是在無緩存的內(nèi)存地址空間中 */
void?*ioremap_nocache(unsigned?long?phys_addr,?unsigned?long?size);
/* iounmap用于釋放不再需要的映射 */
void?iounmap(void?*?addr);

經(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>
/*?I/O內(nèi)存讀函數(shù)。參數(shù)addr應(yīng)當(dāng)是從ioremap獲得的地址(可能包含一個整型偏移); 返回值是從給定I/O內(nèi)存讀取到的值 */
unsigned?int?ioread8(void?*addr);
unsigned?int?ioread16(void?*addr);
unsigned?int?ioread32(void?*addr);
/*?I/O內(nèi)存寫函數(shù)。參數(shù)addr同I/O內(nèi)存讀函數(shù),參數(shù)value為要寫的值 */
void?iowrite8(u8 value,?void?*addr);
void?iowrite16(u16 value,?void?*addr);
void?iowrite32(u32 value,?void?*addr);
/* 以下這些函數(shù)讀和寫一系列值到一個給定的 I/O 內(nèi)存地址,從給定的buf讀或?qū)慶ount個值到給定的addr。參數(shù)count表示要讀寫的數(shù)據(jù)個數(shù),而不是字節(jié)大小 */
void?ioread8_rep(void?*addr,?void?*buf,?unsigned?long?count);
void?ioread16_rep(void?*addr,?void?*buf,?unsigned?long?count);
void?ioread32_rep(void?*addr,?void?*buf,?unsigned?long?count);
void?iowrite8_rep(void?*addr,?const?void?*buf,?unsigned?long?count);
void?iowrite16_rep(void?*addr,?const?void?*buf,?unsigned?long?count);
void?iowrite32_rep(void?*addr,,onst?void?*buf,,nsigned?long?count);
/* 需要操作一塊I/O?地址時,使用下列函數(shù)(這些函數(shù)的行為類似于它們的C庫類似函數(shù)): */
void?memset_io(void?*addr,?u8 value,?unsigned?int?count);
void?memcpy_fromio(void?*dest,?void?*source,?unsigned?int?count);
void?memcpy_toio(void?*dest,?void?*source,?unsigned?int?count);
/* 舊的I/O內(nèi)存讀寫函數(shù),不推薦使用 */
unsigned?readb(address);
unsigned?readw(address);
unsigned?readl(address);?
void?writeb(unsigned?value,?address);
void?writew(unsigned?value,?address);
void?writel(unsigned?value,?address);?

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重新映射countI/O端口,使它們看起來I/O內(nèi)存。此后,驅(qū)動程序可以在ioport_map返回的地址上使用ioread8和同類函數(shù)。這樣,就可以在編程時,消除了I/O端口和I/O 內(nèi)存的區(qū)別?*/
void?*ioport_map(unsigned?long?port,?unsigned?int?count);?
/* ioport_unmap用于釋放不再需要的映射 */
void?ioport_unmap(void?*addr);

注意,I/O?端口在重新映射前必須使用request_region分配所需的I/O?端口。


?????? 3ARM體系的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>

/*
?* Generic IO read/write. These perform native-endian accesses. Note
?* that some architectures will want to re-define __raw_{read,write}w.
?*/
extern?void?__raw_writesb(void?__iomem?*addr,?const?void?*data,?int?bytelen);
extern?void?__raw_writesw(void?__iomem?*addr,?const?void?*data,?int?wordlen);
extern?void?__raw_writesl(void?__iomem?*addr,?const?void?*data,?int?longlen);
extern?void?__raw_readsb(const?void?__iomem?*addr,?void?*data,?int?bytelen);
extern?void?__raw_readsw(const?void?__iomem?*addr,?void?*data,?int?wordlen);
extern?void?__raw_readsl(const?void?__iomem?*addr,?void?*data,?int?longlen);
#define?__raw_writeb(v,a)????(__chk_io_ptr(a),?*(volatile?unsigned?char?__force?*)(a)?=(v))
#define?__raw_writew(v,a)????(__chk_io_ptr(a),?*(volatile?unsigned?short?__force?*)(a)?=(v))
#define?__raw_writel(v,a)????(__chk_io_ptr(a),?*(volatile?unsigned?int?__force?*)(a)?=(v))
#define?__raw_readb(a)????????(__chk_io_ptr(a),?*(volatile?unsigned?char?__force?*)(a))
#define?__raw_readw(a)????????(__chk_io_ptr(a),?*(volatile?unsigned?short?__force?*)(a))
#define?__raw_readl(a)????????(__chk_io_ptr(a),?*(volatile?unsigned?int?__force?*)(a))

關(guān)于__force__iomem

#include?<linux/compiler.h>

/* __force表示所定義的變量類型是可以做強制類型轉(zhuǎn)換的 */
#define?__force __attribute__((force))?
/* __iomem是用來修飾一個變量的,這個變量必須是非解引用(no dereference)的,即這個變量地址必須是有效的,而且變量所在的地址空間必須是2,即設(shè)備地址映射空間。0表示normal space,即普通地址空間,對內(nèi)核代碼來說,當(dāng)然就是內(nèi)核空間地址了。1表示用戶地址空間,2表示是設(shè)備地址映射空間 */
#define?__iomem __attribute__((noderef,?address_space(2)))

I/O端口

#include?<asm-arm/io.h>

#define?outb(v,p)????????__raw_writeb(v,__io(p))
#define?outw(v,p)????????__raw_writew((__force __u16)?\
????????????????????cpu_to_le16(v),__io(p))
#define?outl(v,p)????????__raw_writel((__force __u32)?\
????????????????????cpu_to_le32(v),__io(p))
#define?inb(p)????({?__u8 __v?=?__raw_readb(__io(p));?__v;?})
#define?inw(p)????({?__u16 __v?=?le16_to_cpu((__force __le16)?\
????????????__raw_readw(__io(p)));?__v;?})
#define?inl(p)????({?__u32 __v?=?le32_to_cpu((__force __le32)?\
????????????__raw_readl(__io(p)));?__v;?})
#define?outsb(p,d,l)????????__raw_writesb(__io(p),d,l)
#define?outsw(p,d,l)????????__raw_writesw(__io(p),d,l)
#define?outsl(p,d,l)????????__raw_writesl(__io(p),d,l)
#define?insb(p,d,l)????????__raw_readsb(__io(p),d,l)
#define?insw(p,d,l)????????__raw_readsw(__io(p),d,l)
#define?insl(p,d,l)????????__raw_readsl(__io(p),d,l)

I/O內(nèi)存

#include?<asm-arm/io.h>

#define?ioread8(p)????({?unsigned?int?__v?=?__raw_readb(p);?__v;?})
#define?ioread16(p)????({?unsigned?int?__v?=?le16_to_cpu((__force __le16)__raw_readw(p));__v;?})
#define?ioread32(p)????({?unsigned?int?__v?=?le32_to_cpu((__force __le32)__raw_readl(p));__v;?})
#define?iowrite8(v,p)????__raw_writeb(v,?p)
#define?iowrite16(v,p)????__raw_writew((__force __u16)cpu_to_le16(v),?p)
#define?iowrite32(v,p)????__raw_writel((__force __u32)cpu_to_le32(v),?p)
#define?ioread8_rep(p,d,c)????__raw_readsb(p,d,c)
#define?ioread16_rep(p,d,c)????__raw_readsw(p,d,c)
#define?ioread32_rep(p,d,c)????__raw_readsl(p,d,c)
#define?iowrite8_rep(p,s,c)????__raw_writesb(p,s,c)
#define?iowrite16_rep(p,s,c)????__raw_writesw(p,s,c)
#define?iowrite32_rep(p,s,c)????__raw_writesl(p,s,c)

注意:

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_regionrequest_mem_region,而直接使用outbioread等指令。因為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
  • struct?resource?{??
  • ?????????resource_size_t?start;//?資源范圍的開始??
  • ?????????resource_size_t?end;//?資源范圍的結(jié)束??
  • ?????????const?char?*name;?//資源擁有者的名字??
  • ?????????unsigned?long?flags;//?各種標志??
  • ?????????struct?resource?*parent,?*sibling,?*child;//?指向資源樹中父親,兄弟和孩子的指針??
  • };??

  • ????????? 所有的同種資源都插入到一個樹型數(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
  • 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函數(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來讀寫。

    [plain]?view plaincopy
  • 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);??
  • ????}??
  • }??
  • 七、總結(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。