一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

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

 Linux_Driver 2012-11-16

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

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

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


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

 i386系列的處理中,內(nèi)存和外部IO是獨(dú)立編址,也是獨(dú)立尋址的。MEM的內(nèi)存空間是32位可以尋址到4G,IO空間是16位可以尋址到64K。

 Linux內(nèi)核中,訪問外設(shè)上的IO Port必須通過IO Port的尋址方式。而訪問IO Mem就比較羅嗦,外部MEM不能和主存一樣訪問,雖然大小上不相上下,可是外部MEM是沒有在系統(tǒng)中注冊(cè)的。訪問外部IO MEM必須通過remap映射到內(nèi)核的MEM空間后才能訪問。為了達(dá)到接口的同一性,內(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)可以達(dá)到4G。

      

1.使用I/O 端口

I/O 端口是驅(qū)動(dòng)用來和很多設(shè)備通訊的方法。

1.1、分配I/O 端口

在驅(qū)動(dòng)還沒獨(dú)占設(shè)備之前,不應(yīng)對(duì)端口進(jìn)行操作。內(nèi)核提供了一個(gè)注冊(cè)接口,以允許驅(qū)動(dòng)聲明其需要的端口:

#include <linux/ioport.h>
/* request_region告訴內(nèi)核:要使用first開始的n個(gè)端口。參數(shù)name為設(shè)備名。如果分配成功返回值是非NULL;否則無法使用需要的端 口(/proc/ioports包含了系統(tǒng)當(dāng)前所有端口的分配信息,若request_region分配失敗時(shí),可以查看該文件,看誰先用了你要的端口) */
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
/* 用完I/O端口后(可能在模塊卸載時(shí)),應(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用于檢查一個(gè)給定的I/O端 口集是否可用。如果給定的端口不可用,check_region返回一個(gè)錯(cuò)誤碼。不推薦使用該函數(shù),因?yàn)榧幢闼祷?(端口可用),它也不能保證后面的端 口分配操作會(huì)成功,因?yàn)闄z查和后面的端口分配并不是一個(gè)原子操作。而request_region通過加鎖來保證操作的原子性,因此是安全的 */
int check_region(unsigned long first, unsigned long n);


     
 1.2、操作I/O端口

在驅(qū)動(dòng)成功請(qǐng)求到I/O 端口后,就可以讀寫這些端口了。大部分硬件會(huì)將8位、16位和32位端口區(qū)分開,無法像訪問內(nèi)存那樣混淆使用。驅(qū)動(dòng)程序必須調(diào)用不同的函數(shù)來訪問不同大小的端口。

如同前面所講的,僅支持單地址空間的計(jì)算機(jī)體系通過將I/O端口地址重新映射到內(nèi)存地址來偽裝端口I/O 。為了提高移植性,內(nèi)核對(duì)驅(qū)動(dòng)隱藏了這些細(xì)節(jié)。Linux 內(nèi)核頭文件(體系依賴的頭文件<asm/io.h>) 定義了下列內(nèi)聯(lián)函數(shù)來存取I/O端口:

/* inb/outb:讀/寫字節(jié)端口(8位寬)。有些體系將port參數(shù)定義為unsigned long;而有些平臺(tái)則將它定義為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 沒有進(jìn)一步指定類型時(shí),表示是一個(gè)依賴體系的定義。

注意,沒有64位的I/O端口操作函數(shù)。即便在64位體系中,端口地址空間使用一個(gè)32(最大)的數(shù)據(jù)通路。

1.3、從用戶空間訪問I/O端口

1.2節(jié)介紹的函數(shù)主要是提供給驅(qū)動(dòng)使用,但它們也可在用戶空間使用,至少在PC機(jī)上可以。GNU C 庫在 <sys/io.h> 中定義它們。如果在用戶空間使用這些函數(shù),必須滿足下列條件:

1)、程序必須使用-O選項(xiàng)編譯來強(qiáng)制擴(kuò)展內(nèi)聯(lián)函數(shù)

2)、必須使用iopermiopl系統(tǒng)調(diào)用(#include <sys/perm.h>) 來獲得進(jìn)行操作I/O端口的權(quán)限。ioperm 為獲取單個(gè)端口的操作許可,iopl 為獲取整個(gè)I/O空間許可。這2個(gè)函數(shù)都是x86特有的

3)、程序必須以root來調(diào)用ioperm或者iopl,或者其父進(jìn)程(祖先)必須以root獲得的端口操作權(quán)限

如果平臺(tái)不支持iopermiopl系統(tǒng)調(diào)用,通過使用/dev/prot設(shè)備文件,用戶空間仍然可以存取I/O 端口。但是要注意的是,這個(gè)文件的定義也是依賴平臺(tái)的。

1.4、字串操作

除了一次傳遞一個(gè)數(shù)據(jù)的I/O操作,某些處理器實(shí)現(xiàn)了一次傳遞一序列數(shù)據(jù)(單位可以是字節(jié)、字和雙字)的特殊指令。這些所謂的字串指令,它們完成任務(wù)比一個(gè)C語言循環(huán)更快。下列宏定義實(shí)現(xiàn)字串操作,在某些體系上,它們通過使用單個(gè)機(jī)器指令實(shí)現(xiàn);但如果目標(biāo)處理器沒有進(jìn)行字串I/O指令,則通過執(zhí)行一個(gè)緊湊的循環(huán)實(shí)現(xiàn)。

字串函數(shù)的原型是:

/* insb:從I/O端口port讀取count個(gè)數(shù)據(jù)(單位字節(jié))到以內(nèi)存地址addr為開始的內(nèi)存空間 */
void insb(unsigned port, void *addr, unsigned long count);
/* outsb:將內(nèi)存地址addr開始的count個(gè)數(shù)據(jù)(單位字節(jié))寫到I/O端口port */
void outsb(unsigned port, void *addr, unsigned long count);
/* insw:從I/O端口port讀取count個(gè)數(shù)據(jù)(單位字)到以內(nèi)存地址addr為開始的內(nèi)存空間 */
void insw(unsigned port, void *addr, unsigned long count);
/* outsw:將內(nèi)存地址addr開始的count個(gè)數(shù)據(jù)(單位字)寫到I/O端口port */
void outsw(unsigned port, void *addr, unsigned long count);
/* insl:從I/O端口port讀取count個(gè)數(shù)據(jù)(單位雙字)到以內(nèi)存地址addr為開始的內(nèi)存空間 */
void insl(unsigned port, void *addr, unsigned long count);
/* outsl:將內(nèi)存地址addr開始的count個(gè)數(shù)據(jù)(單位雙字)寫到I/O端口port */
void outsl(unsigned port, void *addr, unsigned long count);

注意:使用字串函數(shù)時(shí),它們直接將字節(jié)流從端口中讀取或?qū)懭?。?dāng)端口和主機(jī)系統(tǒng)有不同的字節(jié)序時(shí),會(huì)導(dǎo)致不可預(yù)期的結(jié)果。使用 inw讀取端口應(yīng)在必要時(shí)自行轉(zhuǎn)換字節(jié)序,以匹配主機(jī)字節(jié)序。

1.5、暫停式I/O操作函數(shù)

由于處理器的速率可能與外設(shè)(尤其是低速設(shè)備)的并不匹配,當(dāng)處理器過快地傳送數(shù)據(jù)到或自總線時(shí),這時(shí)可能就會(huì)引起問題。解決方法是:如果在I/O 指令后面緊跟著另一個(gè)相似的I/O 指令,就必須插入一個(gè)小的延時(shí)。為此,Linux提供了暫停式I/O操作函數(shù),這些函數(shù)的名子只是在非暫停式I/O操作函數(shù)(前面提到的那些I/O操作函數(shù)都是非暫停式的)名后加上_p ,如inb_p、outb_p等。大部分體系都支持這些函數(shù),盡管它們常常被擴(kuò)展為與非暫停 I/O 同樣的代碼,因?yàn)槿绻w系使用一個(gè)合理的現(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)

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

1.6、平臺(tái)依賴性

由于自身的特性,I/O指令高度依賴于處理器,非常難以隱藏各體系間的不同。因此,大部分的關(guān)于端口 I/O的源碼是平臺(tái)依賴的。以下是x86ARM所使用函數(shù)的總結(jié):

IA-32 (x86)

x86_64

這個(gè)體系支持本章介紹的所有函數(shù);port參數(shù)的類型為unsigned short。

ARM

端口映射到內(nèi)存,并且支持本章介紹的所有函數(shù);port參數(shù)的類型為unsigned int;字串函數(shù)用C語言實(shí)現(xiàn)。

 

       2、使用 I/O 內(nèi)存

盡管 I/O 端口在x86世界中非常流行,但是用來和設(shè)備通訊的主要機(jī)制是通過內(nèi)存映射的寄存器和設(shè)備內(nèi)存,兩者都稱為I/O 內(nèi)存,因?yàn)榧拇嫫骱蛢?nèi)存之間的區(qū)別對(duì)軟件是透明的。

I/O 內(nèi)存僅僅是一個(gè)類似于RAM 的區(qū)域,處理器通過總線訪問該區(qū)域,以實(shí)現(xiàn)對(duì)設(shè)備的訪問。同樣,讀寫這個(gè)區(qū)域是有邊際效應(yīng)。

根據(jù)計(jì)算機(jī)體系和總線不同,I/O 內(nèi)存可分為可以或者不可以通過頁表來存取。若通過頁表存取,內(nèi)核必須先重新編排物理地址,使其對(duì)驅(qū)動(dòng)程序可見,這就意味著在進(jìn)行任何I/O操作之前,你必須調(diào)用ioremap;如果不需要頁表,I/O內(nèi)存區(qū)域就類似于I/O端口,你可以直接使用適當(dāng)?shù)?/span>I/O函數(shù)讀寫它們。

由于邊際效應(yīng)的緣故,不管是否需要 ioremap,都不鼓勵(lì)直接使用I/O內(nèi)存指針,而應(yīng)使用專門的I/O內(nèi)存操作函數(shù)。這些I/O內(nèi)存操作函數(shù)不僅在所有平臺(tái)上是安全,而且對(duì)直接使用指針操作 I/O 內(nèi)存的情況進(jìn)行了優(yōu)化。

2.1、I/O 內(nèi)存分配和映射

I/O 內(nèi)存區(qū)在使用前必須先分配。分配內(nèi)存區(qū)的函數(shù)接口在<linux/ioport.h>定義中:

/* request_mem_region分配一個(gè)開始于start,len字節(jié)的I/O內(nèi)存區(qū)。分配成功,返回一個(gè)非NULL指針;否則返回NULL。系統(tǒng)當(dāng)前所有I/O內(nèi)存分配信息都在/proc/iomem文件中列出,你分配失敗時(shí),可以看看該文件,看誰先占用了該內(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)存并不只是簡(jiǎn)單解引用指針,在許多體系中,I/O 內(nèi)存無法以這種方式直接存取。因此,還必須通過ioremap 函數(shù)設(shè)置一個(gè)映射

#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的無緩存版本。實(shí)際上,在大部分體系中,ioremap與ioremap_nocache的實(shí)現(xiàn)一樣的,因?yà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ū)動(dòng)就可以存取任何I/O內(nèi)存地址。注意,ioremap返回的地址不可以直接解引用;相反,應(yīng)當(dāng)使用內(nèi)核提供的訪問函數(shù)。

2.2、訪問I/O內(nèi)存

訪問I/O內(nèi)存的正確方式是通過一系列專門用于實(shí)現(xiàn)此目的的函數(shù):

#include <asm/io.h>
/* I/O內(nèi)存讀函數(shù)。參數(shù)addr應(yīng)當(dāng)是從ioremap獲得的地址(可能包含一個(gè)整型偏移); 返回值是從給定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ù)讀和寫一系列值到一個(gè)給定的 I/O 內(nèi)存地址,從給定的buf讀或?qū)慶ount個(gè)值到給定的addr。參數(shù)count表示要讀寫的數(shù)據(jù)個(gè)數(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ù)(這些函數(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)存一樣使用端口

一些硬件有一個(gè)有趣的特性有些版本使用 I/O 端口;而有些版本則使用 I/O 內(nèi)存。不管是I/O 端口還是I/O 內(nèi)存,處理器見到的設(shè)備寄存器都是相同的,只是訪問方法不同。為了統(tǒng)一編程接口,使驅(qū)動(dòng)程序易于編寫,2.6內(nèi)核提供了一個(gè)ioport_map函數(shù):

/* ioport_map重新映射count個(gè)I/O端口,使它們看起來I/O內(nèi)存。此后,驅(qū)動(dòng)程序可以在ioport_map返回的地址上使用ioread8和同類函數(shù)。這樣,就可以在編程時(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)核源碼,你就會(huì)發(fā)現(xiàn):其實(shí)I/O端口的指令只是一個(gè)外殼,內(nèi)部還是使用和I/O內(nèi)存一樣的代碼。

下面是ARM體系原始的I/O操作函數(shù)。其實(shí)后面I/O端口和I/O內(nèi)存操作函數(shù),只是對(duì)這些函數(shù)進(jìn)行再封裝。從這里也可以看出為什么我們不推薦直接使用I/O端口和I/O內(nèi)存地址指針,而是要求使用專門的I/O操作函數(shù)——專門的I/O操作函數(shù)會(huì)檢查地址指針是否有效是否為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表示所定義的變量類型是可以做強(qiáng)制類型轉(zhuǎn)換的 */
#define __force __attribute__((force)) 
/* __iomem是用來修飾一個(gè)變量的,這個(gè)變量必須是非解引用(no dereference)的,即這個(gè)變量地址必須是有效的,而且變量所在的地址空間必須是2,即設(shè)備地址映射空間。0表示normal space,即普通地址空間,對(duì)內(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映射的虛擬地址。絕對(duì)不能使用實(shí)際的物理地址,否則會(huì)因?yàn)閮?nèi)核無法處理地址而出現(xiàn)oops

2)、在使用I/O指令時(shí),可以不使用request_regionrequest_mem_region,而直接使用outb、ioread等指令。因?yàn)?/span>request的功能只是告訴內(nèi)核端口被誰占用了,如再次request,內(nèi)核會(huì)制止(資源busy)。但是不推薦這么做,這樣的代碼也不規(guī)范,可能會(huì)引起并發(fā)問題(很多時(shí)候我們都需要獨(dú)占設(shè)備)。

3)、在使用I/O指令時(shí),所賦的地址數(shù)據(jù)有時(shí)必須通過強(qiáng)制類型轉(zhuǎn)換為 unsigned long,不然會(huì)有警告。

4)、在include\asm-arm\arch-s3c2410\hardware.h中定義了很多io口的操作函數(shù),有需要可以在驅(qū)動(dòng)中直接使用,很方便。






Linux系統(tǒng)對(duì)IO端口和IO內(nèi)存的管理

http://blog.csdn.net/ce123/article/details/7204458


一、I/O端口

      端口(port)是接口電路中能被CPU直接訪問的寄存器的地址。幾乎每一種外設(shè)都是通過讀寫設(shè)備上的寄存器來進(jìn)行的。CPU通過這些地址即端口向接口電 路中的寄存器發(fā)送命令,讀取狀態(tài)和傳送數(shù)據(jù)。外設(shè)寄存器也稱為“I/O端口”,通常包括:控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類,而且一個(gè)外設(shè)的寄存 器通常被連續(xù)地編址。

二、IO內(nèi)存

       例如,在PC上可以插上一塊圖形卡,有2MB的存儲(chǔ)空間,甚至可能還帶有ROM,其中裝有可執(zhí)行代碼。

三、IO端口和IO內(nèi)存的區(qū)分及聯(lián)系

         這兩者如何區(qū)分就涉及到硬件知識(shí),X86體系中,具有兩個(gè)地址空間:IO空間和內(nèi)存空間,而RISC指令系統(tǒng)的CPU(如ARM、PowerPC等)通常只實(shí)現(xiàn)一個(gè)物理地址空間,即內(nèi)存空間。
內(nèi)存空間:內(nèi)存地址尋址范圍,32位操作系統(tǒng)內(nèi)存空間為2的32次冪,即4G。
IO空間:X86特有的一個(gè)空間,與內(nèi)存空間彼此獨(dú)立的地址空間,32位X86有64K的IO空間。

IO端口:當(dāng)寄存器或內(nèi)存位于IO空間時(shí),稱為IO端口。一般寄存器也俗稱I/O端口,或者說I/O ports,這個(gè)I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。

IO內(nèi)存:當(dāng)寄存器或內(nèi)存位于內(nèi)存空間時(shí),稱為IO內(nèi)存。

 

四、外設(shè)IO端口物理地址的編址方式

        CPU對(duì)外設(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等)通常只實(shí)現(xiàn)一個(gè)物 理地址空間(RAM)。在這種情況下,外設(shè)I/O端口的物理地址就被映射到CPU的單一物理地址空間中,而成為內(nèi)存的一部分。此時(shí),CPU可以象訪問一個(gè) 內(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、獨(dú)立編址

        而另外一些體系結(jié)構(gòu)的CPU(典型地如X86)則為外設(shè)專門實(shí)現(xiàn)了一個(gè)單獨(dú)地地址空間,稱為“I/O地址空間”或者“I/O端口空間”。這是一個(gè)與CPU 地RAM物理地址空間不同的地址空間,所有外設(shè)的I/O端口均在這一空間中進(jìn)行編址。CPU通過設(shè)立專門的I/O指令(如X86的IN和OUT指令)來訪 問這一空間中的地址單元(也即I/O端口)。與RAM物理地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O映射方式”的一個(gè)主要缺點(diǎn)。

        獨(dú)立編址也稱為“I/O端口”方式,外設(shè)寄存器位于“I/O(地址)空間”。

3、優(yōu)缺點(diǎn)

獨(dú)立編址主要優(yōu)點(diǎn)是:
1)、I/O端口地址不占用存儲(chǔ)器空間;使用專門的I/O指令對(duì)端口進(jìn)行操作,I/O指令短,執(zhí)行速度快。
2)、并且由于專門I/O指令與存儲(chǔ)器訪問指令有明顯的區(qū)別,使程序中I/O操作和存儲(chǔ)器操作層次清晰,程序的可讀性強(qiáng)。
3)、同時(shí),由于使用專門的I/O指令訪問端口,并且I/O端口地址和存儲(chǔ)器地址是分開的,故I/O端口地址和存儲(chǔ)器地址可以重疊,而不會(huì)相互混淆。 
4)、譯碼電路比較簡(jiǎn)單(因?yàn)镮/0端口的地址空間一般較小,所用地址線也就較少)。
其缺點(diǎn)是:只能用專門的I/0指令,訪問端口的方法不如訪問存儲(chǔ)器的方法多。

統(tǒng)一編址優(yōu)點(diǎn):
1)、由于對(duì)I/O設(shè)備的訪問是使用訪問存儲(chǔ)器的指令,所以指令類型多,功能齊全,這不僅使訪問I/O端口可實(shí)現(xiàn)輸入/輸出操作,而且還可對(duì)端口內(nèi)容進(jìn)行算術(shù)邏輯運(yùn)算,移位等等;
2)、另外,能給端口有較大的編址空間,這對(duì)大型控制系統(tǒng)和數(shù)據(jù)通信系統(tǒng)是很有意義的。
這種方式的缺點(diǎn)是端口占用了存儲(chǔ)器的地址空間,使存儲(chǔ)器容量減小,另外指令長(zhǎng)度比專門I/O指令要長(zhǎng),因而執(zhí)行速度較慢。

        究竟采用哪一種取決于系統(tǒng)的總體設(shè)計(jì)。在一個(gè)系統(tǒng)中也可以同時(shí)使用兩種方式,前提是首先要支持I/O獨(dú)立編址。Intel的x86微處理器都支持I/O 獨(dú)立編址,因?yàn)樗鼈兊闹噶钕到y(tǒng)中都有I/O指令,并設(shè)置了可以區(qū)分I/O訪問和存儲(chǔ)器訪問的控制信號(hào)引腳。而一些微處理器或單片機(jī),為了減少引腳,從而減 少芯片占用面積,不支持I/O獨(dú)立編址,只能采用存儲(chǔ)器統(tǒng)一編址。

五、Linux下訪問IO端口

           對(duì)于某一既定的系統(tǒng),它要么是獨(dú)立編址、要么是統(tǒng)一編址,具體采用哪一種則取決于CPU的體系結(jié)構(gòu)。 如,PowerPC、m68k等采用統(tǒng)一編址,而X86等則采用獨(dú)立編址,存在IO空間的概念。目前,大多數(shù)嵌入式微控制器如ARM、PowerPC等并 不提供I/O空間,僅有內(nèi)存空間,可直接用地址、指針訪問。但對(duì)于Linux內(nèi)核而言,它可能用于不同的CPU,所以它必須都要考慮這兩種方式,于是它采 用一種新的方法,將基于I/O映射方式的或內(nèi)存映射方式的I/O端口通稱為“I/O區(qū)域”(I/O region),不論你采用哪種方式,都要先申請(qǐng)IO區(qū)域:request_resource(),結(jié)束時(shí)釋放 它: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ū)動(dòng)模塊被加載時(shí)申請(qǐng)IO端口區(qū)域,之后使用inb(),outb()等進(jìn)行端口訪問,最后在設(shè)備關(guān)閉或驅(qū)動(dòng)被卸載時(shí)釋放IO端口范圍。

 in、out、ins和outs匯編語言指令都可以訪問I/O端口。內(nèi)核中包含了以下輔助函數(shù)來簡(jiǎn)化這種訪問:

inb( )、inw( )、inl( )
分別從I/O端口讀取1、2或4個(gè)連續(xù)字節(jié)。后綴“b”、“w”、“l(fā)”分別代表一個(gè)字節(jié)(8位)、一個(gè)字(16位)以及一個(gè)長(zhǎng)整型(32位)。

inb_p( )、inw_p( )、inl_p( )
分別從I/O端口讀取1、2或4個(gè)連續(xù)字節(jié),然后執(zhí)行一條“啞元(dummy,即空指令)”指令使CPU暫停。

outb( )、outw( )、outl( )
分別向一個(gè)I/O端口寫入1、2或4個(gè)連續(xù)字節(jié)。

outb_p( )、outw_p( )、outl_p( )
分別向一個(gè)I/O端口寫入1、2或4個(gè)連續(xù)字節(jié),然后執(zhí)行一條“啞元”指令使CPU暫停。

insb( )、insw( )、insl( )
分別從I/O端口讀入以1、2或4個(gè)字節(jié)為一組的連續(xù)字節(jié)序列。字節(jié)序列的長(zhǎng)度由該函數(shù)的參數(shù)給出。

outsb( )、outsw( )、outsl( )
分別向I/O端口寫入以1、2或4個(gè)字節(jié)為一組的連續(xù)字節(jié)序列。

流程如下:

      雖然訪問I/O端口非常簡(jiǎn)單,但是檢測(cè)哪些I/O端口已經(jīng)分配給I/O設(shè)備可能就不這么簡(jiǎn)單了,對(duì)基于ISA總線的系統(tǒng)來說更是如此。通常,I/O設(shè)備驅(qū) 動(dòng)程序?yàn)榱颂綔y(cè)硬件設(shè)備,需要盲目地向某一I/O端口寫入數(shù)據(jù);但是,如果其他硬件設(shè)備已經(jīng)使用這個(gè)端口,那么系統(tǒng)就會(huì)崩潰。為了防止這種情況的發(fā)生,內(nèi) 核必須使用“資源”來記錄分配給每個(gè)硬件設(shè)備的I/O端口。資源表示某個(gè)實(shí)體的一部分,這部分被互斥地分配給設(shè)備驅(qū)動(dòng)程序。在這里,資源表示I/O端口地 址的一個(gè)范圍。每個(gè)資源對(duì)應(yīng)的信息存放在resource數(shù)據(jù)結(jié)構(gòu)中:

[plain] view plaincopy
  1. struct resource {  
  2.          resource_size_t start;// 資源范圍的開始  
  3.          resource_size_t end;// 資源范圍的結(jié)束  
  4.          const char *name; //資源擁有者的名字  
  5.          unsigned long flags;// 各種標(biāo)志  
  6.          struct resource *parent, *sibling, *child;// 指向資源樹中父親,兄弟和孩子的指針  
  7. };  


          所有的同種資源都插入到一個(gè)樹型數(shù)據(jù)結(jié)構(gòu)(父親、兄弟和孩子)中;例如,表示I/O端口地址范圍的所有資源都包括在一個(gè)根節(jié)點(diǎn)為 ioport_resource的樹中。節(jié)點(diǎn)的孩子被收集在一個(gè)鏈表中,其第一個(gè)元素由child指向。sibling字段指向鏈表中的下一個(gè)節(jié)點(diǎn)。

        為什么使用樹?例如,考慮一下IDE硬盤接口所使用的I/O端口地址-比如說從0xf000 到 0xf00f。那么,start字段為0xf000 且end 字段為0xf00f的這樣一個(gè)資源包含在樹中,控制器的常規(guī)名字存放在name字段中。但是,IDE設(shè)備驅(qū)動(dòng)程序需要記住另外的信息,也就是IDE鏈主盤 使用0xf000 到0xf007的子范圍,從盤使用0xf008 到0xf00f的子范圍。為了做到這點(diǎn),設(shè)備驅(qū)動(dòng)程序把兩個(gè)子范圍對(duì)應(yīng)的孩子插入到從0xf000 到0xf00f的整個(gè)范圍對(duì)應(yīng)的資源下。一般來說,樹中的每個(gè)節(jié)點(diǎn)肯定相當(dāng)于父節(jié)點(diǎn)對(duì)應(yīng)范圍的一個(gè)子范圍。I/O端口資源樹 (ioport_resource)的根節(jié)點(diǎn)跨越了整個(gè)I/O地址空間(從端口0到65535)。

任何設(shè)備驅(qū)動(dòng)程序都可以使用下面三個(gè)函數(shù),傳遞給它們的參數(shù)為資源樹的根節(jié)點(diǎn)和要插入的新資源數(shù)據(jù)結(jié)構(gòu)的地址:

request_resource( )        //把一個(gè)給定范圍分配給一個(gè)I/O設(shè)備。

allocate_resource( )        //在資源樹中尋找一個(gè)給定大小和排列方式的可用范圍;若存在,將這個(gè)范圍分配給一個(gè)I/O設(shè)備(主要由PCI設(shè)備驅(qū)動(dòng)程序使用,可以使用任意的端口號(hào)和主板上的內(nèi)存地址對(duì)其進(jìn)行配置)。

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)存進(jìn)行訪問,在設(shè)備打開或驅(qū)動(dòng)模塊被加載時(shí),申請(qǐng)IO端口區(qū)域并使用ioport_map()映射到內(nèi)存,之后使用IO內(nèi)存的函數(shù)進(jìn)行端口訪問,最后,在設(shè)備關(guān)閉或驅(qū)動(dòng)模塊被卸載時(shí)釋放IO端口并釋放映射。

映射函數(shù)的原型為:
void *ioport_map(unsigned long port, unsigned int count);
通過這個(gè)函數(shù),可以把port開始的count個(gè)連續(xù)的I/O端口重映射為一段“內(nèi)存空間”。然后就可以在其返回的地址上像訪問I/O內(nèi)存一樣訪問這些I/O端口。但請(qǐng)注意,在進(jìn)行映射前,還必須通過request_region( )分配I/O端口。

當(dāng)不再需要這種映射時(shí),需要調(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ù)對(duì)應(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ù)對(duì)應(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()申請(qǐng)資源,接著將寄存器地址通過ioremap()映射到內(nèi)核空間的虛擬地址, 之后就可以Linux設(shè)備訪問編程接口訪問這些寄存器了,訪問完成后,使用ioremap()對(duì)申請(qǐng)的虛擬地址進(jìn)行釋放,并釋放 release_mem_region()申請(qǐng)的IO內(nèi)存資源。

struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
   這個(gè)函數(shù)從內(nèi)核申請(qǐng)len個(gè)內(nèi)存地址(在3G~4G之間的虛地址),而這里的start為I/O物理地址,name為設(shè)備的名稱。注意,。如果分配成功,則返回非NULL,否則,返回NULL。
另外,可以通過/proc/iomem查看系統(tǒng)給各種設(shè)備的內(nèi)存范圍。

要釋放所申請(qǐng)的I/O內(nèi)存,應(yīng)當(dāng)使用release_mem_region()函數(shù):
void release_mem_region(unsigned long start, unsigned long len)

申請(qǐng)一組I/O內(nèi)存后, 調(diào)用ioremap()函數(shù):
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三個(gè)參數(shù)的含義為:
phys_addr:與requset_mem_region函數(shù)中參數(shù)start相同的I/O物理地址;
size:要映射的空間的大??;
flags:要映射的IO空間的和權(quán)限有關(guān)的標(biāo)志;

功能:將一個(gè)I/O地址空間映射到內(nèi)核的虛擬地址空間上(通過release_mem_region()申請(qǐng)到的)

流程如下:

六、ioremap和ioport_map

下面具體看一下ioport_map和ioport_umap的源碼:

[plain] view plaincopy
  1. void __iomem *ioport_map(unsigned long port, unsigned int nr)  
  2. {  
  3.     if (port > PIO_MASK)  
  4.         return NULL;  
  5.     return (void __iomem *) (unsigned long) (port + PIO_OFFSET);  
  6. }  
  7.   
  8. void ioport_unmap(void __iomem *addr)  
  9. {  
  10.     /* Nothing to do */  
  11. }  

          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)存空間行為實(shí)際上是給開發(fā)人員制造的一個(gè)“假象”,并沒有映射到內(nèi)核虛擬地址,僅僅是為了讓工程師可使用統(tǒng)一的I/O內(nèi)存訪問接口ioread8/iowrite8(......)訪問I/O端口。
          最后來看一下ioread8的源碼,其實(shí)現(xiàn)也就是對(duì)虛擬地址進(jìn)行了判斷,以區(qū)分IO端口和IO內(nèi)存,然后分別使用inb/outb和readb/writeb來讀寫。

[plain] view plaincopy
  1. unsigned int fastcall ioread8(void __iomem *addr)  
  2. {  
  3.     IO_COND(addr, return inb(port), return readb(addr));  
  4. }  
  5.   
  6. #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)  
  7. #define IO_COND(addr, is_pio, is_mmio) do { \  
  8.     unsigned long port = (unsigned long __force)addr; \  
  9.         if (port < PIO_RESERVED) { \  
  10.             VERIFY_PIO(port); \  
  11.             port &= PIO_MASK; \  
  12.             is_pio; \  
  13.         } else { \  
  14.             is_mmio; \  
  15.         } \  
  16. } while (0)  
  17.   
  18. 展開:  
  19. unsigned int fastcall ioread8(void __iomem *addr)  
  20. {  
  21.     unsigned long port = (unsigned long __force)addr;  
  22.     if( port < 0x40000UL ) {  
  23.         BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );  
  24.         port &= PIO_MASK;  
  25.         return inb(port);  
  26.     }else{  
  27.         return readb(addr);  
  28.     }  
  29. }  

七、總結(jié)

         外設(shè)IO寄存器地址獨(dú)立編址的CPU,這時(shí)應(yīng)該稱外設(shè)IO寄存器為IO端 口,訪問IO寄存器可通過ioport_map將其映射到虛擬地址空間,但實(shí)際上這是給開發(fā)人員制造的一個(gè)“假象”,并沒有映射到內(nèi)核虛擬地址,僅僅是為 了可以使用和IO內(nèi)存一樣的接口訪問IO寄存器;也可以直接使用in/out指令訪問IO寄存器。

          例如:Intel x86平臺(tái)普通使用了名為內(nèi)存映射(MMIO)的技術(shù),該技術(shù)是PCI規(guī)范的一部分,IO設(shè)備端口被映射到內(nèi)存空間,映射后,CPU訪問IO端口就如同訪 問內(nèi)存一樣。

          外設(shè)IO寄存器地址統(tǒng)一編址的CPU,這時(shí)應(yīng)該稱外設(shè)IO寄存器為IO內(nèi)存,訪問IO寄存器可通過ioremap將其映射到虛擬地址空間,然后再使用read/write接口訪問。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    欧美一区二区三区播放| 日本高清加勒比免费在线| 亚洲少妇一区二区三区懂色| 欧美av人人妻av人人爽蜜桃| 开心激情网 激情五月天| 欧美日韩综合免费视频| 日本人妻的诱惑在线观看| 国产日产欧美精品视频| 欧美国产精品区一区二区三区| 免费亚洲黄色在线观看| 中文字幕一区二区免费| 亚洲精品成人福利在线| 国内尹人香蕉综合在线| 黄片三级免费在线观看| 最好看的人妻中文字幕| 男女午夜视频在线观看免费| 免费黄片视频美女一区| 国产成人综合亚洲欧美日韩| 高清一区二区三区大伊香蕉| 欧美国产日韩在线综合| 久久亚洲午夜精品毛片| 久热99中文字幕视频在线| 欧美乱视频一区二区三区| 国产精品视频一区麻豆专区| 美女露小粉嫩91精品久久久 | 丝袜av一区二区三区四区五区| 亚洲国产丝袜一区二区三区四| 中文字幕日产乱码一区二区| 午夜福利在线观看免费| 精品老司机视频在线观看| 国产又猛又大又长又粗| 嫩呦国产一区二区三区av| 午夜福利国产精品不卡| 久久老熟女一区二区三区福利| 国产av天堂一区二区三区粉嫩| 女人精品内射国产99| 伊人网免费在线观看高清版| 欧美精品激情视频一区| 亚洲精品伦理熟女国产一区二区 | 色婷婷视频免费在线观看| 日韩欧美一区二区亚洲|