<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

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

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

          作者: 時間:2017-12-27 來源:網(wǎng)絡 收藏

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

          本文引用地址:http://www.ex-cimer.com/article/201712/373670.htm

            1.struct resource {

            2. resource_size_t start;// 資源范圍的開始

            3. resource_size_t end;// 資源范圍的結束

            4. const char *name; //資源擁有者的名字

            5. unsigned long flags;// 各種標志

            6. struct resource *parent, *sibling, *child;// 指向資源樹中父親,兄弟和孩子的指針

            };

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

            為什么使用樹?例如,考慮一下IDE硬盤接口所使用的I/O端口地址-比如說從0xf000 到 0xf00f。那么,start字段為0xf000 且end 字段為0xf00f的這樣一個資源包含在樹中,控制器的常規(guī)名字存放在name字段中。但是,IDE設備驅(qū)動程序需要記住另外的信息,也就是IDE鏈主盤使用0xf000 到0xf007的子范圍,從盤使用0xf008 到0xf00f的子范圍。為了做到這點,設備驅(qū)動程序把兩個子范圍對應的孩子插入到從0xf000 到0xf00f的整個范圍對應的資源下。

            一般來說,樹中的每個節(jié)點肯定相當于父節(jié)點對應范圍的一個子范圍。I/O端口資源樹(ioport_resource)的根節(jié)點跨越了整個I/O地址空間(從端口0到65535)。

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

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

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

            release_resource( ) //釋放以前分配給I/O設備的給定范圍。

            內(nèi)核也為以上函數(shù)定義了一些應用于I/O端口的快捷函數(shù):request_region( )分配I/O端口的給定范圍,release_region( )釋放以前分配給I/O端口的范圍。當前分配給I/O設備的所有I/O地址的樹都可以從/proc/ioports文件中獲得。

            2、內(nèi)存映射方式

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

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

            void ioport_unmap(void *addr);

            在設備的物理地址被映射到虛擬地址之后,盡管可以直接通過指針訪問這些地址,但是宜使用內(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ù)對應的較早版本的函數(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ù)對應的較早版本的函數(shù)為(這些函數(shù)在 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設備訪問編程接口訪問這些寄存器了,訪問完成后,使用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為設備的名稱。注意,。如果分配成功,則返回非NULL,否則,返回NULL。

            另外,可以通過/proc/iomem查看系統(tǒng)給各種設備的內(nèi)存范圍。

            要釋放所申請的I/O內(nèi)存,應當使用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空間的和權限有關的標志;

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

            流程如下:

              

           

            七、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)存空間行為實際上是給開發(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來讀寫。

            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將其映射到虛擬地址空間,但實際上這是給開發(fā)人員制造的一個“假象”,并沒有映射到內(nèi)核虛擬地址,僅僅是為了可以使用和IO內(nèi)存一樣的接口訪問IO寄存器;也可以直接使用in/out指令訪問IO寄存器。

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

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


          上一頁 1 2 下一頁

          關鍵詞: Linux I/O

          評論


          相關推薦

          技術專區(qū)

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();