IO端口和IO記憶體
一、概念
1.1什麼是IO端口?
晶片核心之外有諸多外設,如GPIO,I2C,USB,LCD等等,在S3C2440中,各種外設挂到了AHB總線或APB總線上,每種外設都是通過讀寫與其相關的寄存器進行控制。外設中寄存器的位址就稱作IO端口。
1.2什麼是IO記憶體?
将寄存器位址和裝置記憶體位址映射到某個記憶體位址區段,這個記憶體區段就是IO記憶體。(有些外部裝置有自己的記憶體,如視訊采集卡,有16M的SDRAM作為緩沖區)
IO記憶體類似于RAM,處理器可以通過總線而通路這些裝置。軟體不用理會寄存器和記憶體的差别(即他們的差别對軟體是透明的)。
二、使用IO端口
驅動程式和裝置很多時候是通過IO端口進行通信的。
2.1 IO端口配置設定:
linux中,對端口進行操作之前,首先應該聲明需要操作的端口。核心提供的注冊操作端口的接口函數是:
#include<linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
這個函數告訴核心,我們要使用起始于first的n個端口,name是裝置名稱。配置設定成功,傳回非NULL值;失敗,傳回NULL。
如果不再使用端口,可将端口釋放:
void release_region(unsigned long start, unsigned long n);
所有端口的配置設定可從/proc/ioports中得到。
2.2 IO端口操作:
linux中,對端口進行操作之前,首先應該聲明需要操作的端口。核心提供的注冊操作端口的接口函數是:
asm/io.h
unsigned inb(unsigned port);
unsigned outb(unsigned char byte, unsigned port);
讀寫8位寬度(位元組)端口。
unsigned inw(unsigned port);
unsigned outw(unsigned short word, unsigned port);
讀寫16位寬度(位元組)端口。
unsigned inl(unsigned port);
unsigned outl(unsigned long word, unsigned port);
讀寫32位寬度(位元組)端口。
三、使用IO記憶體
ARM記憶體是通過頁表通路的,核心必須首先使實體位址其對裝置驅動程式可見,這意味着在IO操作之前先進行ioremap。LDD3不鼓勵直接使用指向IO記憶體的指針,而是使用包裝函數通路IO記憶體。原有有三:首先,使用包裝函數在所有平台上都是安全的;另外。直接對指針指向的記憶體區域執行操作時,這些函數是經過優化的;最後,缺乏可讀性和可移植性。
3.1 IO記憶體的配置設定和映射
使用之前,必先配置設定IO記憶體區域,接口如下:
linux/ioport.h
struct resource * request_mem_region(unsigned long start, unsigned long len,char *name);
函數從start開始配置設定len位元組長的記憶體區域。配置設定成功,傳回非NULL值;失敗,傳回NULL。配置設定的IO記憶體可從/proc/iomem中得到。
如果不再使用配置設定的記憶體,可将端口釋放:
void release_mem_region(unsigned long start, unsigned long len);
擷取另IO記憶體後,萬不可直接應用指針通路。為了確定核心可以通路IO記憶體,必須通過ioremap函數建立映射。Ioremap專為IO記憶體區域配置設定虛拟位址。
asm/io.h
void *ioremap(unsigned long phys_addr, unsigned long size);
void *iounmap(void *addr);
由ioremap傳回的位址不應直接引用,而應該使用核心提供的accessor函數。
2.2 IO記憶體通路:
linux中,對端口進行操作之前,首先應該聲明需要操作的端口。核心提供的注冊操作端口的接口函數是:
asm/io.h
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
從IO記憶體讀取值,addr是ioremap獲得位址。
void iowrite8(u8 val, void *addr);
void iowrite16(u16 val, void *addr);
void iowrite32(u32 val, void *addr);
給IO記憶體寫值。
還有一些老的接口,這裡不做介紹。
四、像IO記憶體一樣使用IO端口
為了最小化IO記憶體和IO端口通路直接的表面差別,2.6核心采取另如下措施:在request_region配置設定IO端口之後,使用ioport_map函數進行操作:
void*ioport_map(unsigned long port, unsigned int count);
該函數重新映射了count個端口,看起來就像IO記憶體。驅動程式可在函數傳回的位址之上使用ioread8等函數。
釋放函數
void ioport_unmap(void *addr);
四、記憶體屏障(memorybarrier)
對IO寄存器的操作可能因為代碼的編譯器優化和硬體重新排序而導緻錯誤。其解決辦法是對硬體的特定順序執行的操作之間設定記憶體屏障。
#include<linux/kernel.h>
void barrier(void);
這個函數通知編譯器插入一個記憶體屏障,對硬體沒有影響。
#include<asm/system.h>
void rmb(void); //保證屏障之前的讀操作一定會在後來讀操作執行之前完成
void read_barrier_depends(void);
void wmb(void); //保證寫操作不會亂序
void mb(void); //保證兩者
五、ARM平台端口映射到記憶體,支援以上所有函數。
我根據LDD3做了led驅動實作 ,用了ioport,iomem和系統調用接口三種方法,存于百度網盤,led_iomem_ioport_sysinterface.rar,下載下傳連結 :http://pan.baidu.com/s/1bnm1FsF
注:以上内容隻是本人對LDD3的讀書筆記。如有差錯,歡迎指正!