新聞中心

        EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 一文讀懂Linux下如何訪問(wèn)I/O端口和I/O內(nèi)存

        一文讀懂Linux下如何訪問(wèn)I/O端口和I/O內(nèi)存

        作者: 時(shí)間:2017-12-27 來(lái)源:網(wǎng)絡(luò) 收藏

          雖然訪問(wèn)端口非常簡(jiǎn)單,但是檢測(cè)哪些端口已經(jīng)分配給設(shè)備可能就不這么簡(jiǎn)單了,對(duì)基于ISA總線的系統(tǒng)來(lái)說(shuō)更是如此。通常,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)核必須使用“資源”來(lái)記錄分配給每個(gè)硬件設(shè)備的I/O端口。資源表示某個(gè)實(shí)體的一部分,這部分被互斥地分配給設(shè)備驅(qū)動(dòng)程序。在這里,資源表示I/O端口地址的一個(gè)范圍。每個(gè)資源對(duì)應(yīng)的信息存放在resource數(shù)據(jù)結(jié)構(gòu)中:

        本文引用地址:http://www.104case.com/article/201712/373670.htm

          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;// 指向資源樹中父親,兄弟和孩子的指針

          };

          所有的同種資源都插入到一個(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端口地址-比如說(shuō)從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)的資源下。

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

          映射函數(shù)的原型為:

          void *ioport_map(unsigned long port, unsigned int count);

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

          當(dāng)不再需要這種映射時(shí),需要調(diào)用下面的函數(shù)來(lái)撤消:

          void ioport_unmap(void *addr);

          在設(shè)備的物理地址被映射到虛擬地址之后,盡管可以直接通過(guò)指針訪問(wèn)這些地址,但是宜使用內(nèi)核的如下一組函數(shù)來(lái)完成訪問(wèn)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ù)在 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ù)在 2.6中仍然被支持):

          void writeb(unsigned value, address);

          void writew(unsigned value, address);

          void writel(unsigned value, address);

          流程如下:

            

         

          六、Linux下訪問(wèn)IO內(nèi)存

          IO內(nèi)存的訪問(wèn)方法是:首先調(diào)用request_mem_region()申請(qǐng)資源,接著將寄存器地址通過(guò)ioremap()映射到內(nèi)核空間的虛擬地址,之后就可以Linux設(shè)備訪問(wèn)編程接口訪問(wèn)這些寄存器了,訪問(wèn)完成后,使用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。

          另外,可以通過(guò)/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)核的虛擬地址空間上(通過(guò)release_mem_region()申請(qǐng)到的)

          流程如下:

            

         

          七、ioremap和ioport_map

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

          void __iomem *ioport_map(unsigned long port, unsigned int nr)

          {

          1.

          if (port > PIO_MASK)

          2.

          return NULL;

          3.

          return (void __iomem *) (unsigned long) (port + PIO_OFFSET);

          4.

          }

          5.

          6.

          void ioport_unmap(void __iomem *addr)

          7.

          {

          8.

          /* 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)存空間行為實(shí)際上是給開發(fā)人員制造的一個(gè)“假象”,并沒(méi)有映射到內(nèi)核虛擬地址,僅僅是為了讓工程師可使用統(tǒng)一的I/O內(nèi)存訪問(wèn)接口ioread8/iowrite8(......)訪問(wèn)I/O端口。

          最后來(lái)看一下ioread8的源碼,其實(shí)現(xiàn)也就是對(duì)虛擬地址進(jìn)行了判斷,以區(qū)分IO端口和IO內(nèi)存,然后分別使用inb/outb和readb/writeb來(lái)讀寫。

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

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

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


        上一頁(yè) 1 2 下一頁(yè)

        關(guān)鍵詞: Linux I/O

        評(píng)論


        相關(guān)推薦

        技術(shù)專區(qū)

        關(guān)閉
        主站蜘蛛池模板: 涿鹿县| 平遥县| 稷山县| 丹棱县| 南昌县| 台中市| 万州区| 阿拉善盟| 陇西县| 龙江县| 岑溪市| 武乡县| 社会| 柳林县| 万年县| 沅江市| 阳谷县| 海原县| 平谷区| 九台市| 青州市| 田阳县| 永安市| 彩票| 聂拉木县| 千阳县| 修水县| 永善县| 遂宁市| 永新县| 阿瓦提县| 宁远县| 定兴县| 沙洋县| 瑞丽市| 台南市| 阳山县| 美姑县| 星座| 伊通| 岑巩县|