模块计算机型x86yu,ldd3学习之九:与硬件通信
操作 I/O 端口
在驅動程序注冊I/O 端口后,就可以讀/寫這些端口。大部分硬件會把8、16和32位端口區分開,不能像訪問系統內存那樣混淆使用。驅動必須調用不同的函數來存取不同大小的端口。
只支持內存映射的 I/O 寄存器的計算機體系通過重新映射I/O端口到內存地址來偽裝端口I/O。為了提高移植性,內核向驅動隱藏了這些細節。Linux 內核頭文件(體系依賴的頭文件??) 定義了下列內聯函數(有的體系是宏,有的不存在)來訪問 I/O 端口:unsigned?inb(unsigned?port);
void?outb(unsigned?char?byte,?unsigned?port);
/*讀/寫字節端口( 8 位寬 )。port 參數某些平臺定義為 unsigned long ,有些為 unsigned short 。 inb 的返回類型也體系而不同。*/
unsigned?inw(unsigned?port);
void?outw(unsigned?short?word,?unsigned?port);
/*訪問 16位 端口( 一個字寬 )*/
unsigned?inl(unsigned?port);
void?outl(unsigned?longword,?unsigned?port);
/*訪問 32位 端口。 longword 聲明有的平臺為 unsigned long ,有的為 unsigned int。*/
在用戶空間訪問 I/O 端口(x86平臺用法)
以上函數主要提供給設備驅動使用,但它們也可在用戶空間使用,至少在 PC上可以。 GNU C 庫在 中定義了它們。如果在用戶空間代碼中使用必須滿足以下條件:
(1)程序必須使用 -O 選項編譯來強制擴展內聯函數。
(2)必須用ioperm 和 iopl 系統調用(#include )?來獲得對端口 I/O 操作的權限。ioperm 為獲取單獨端口操作權限,而 iopl 為整個 I/O 空間的操作權限。(x86 特有的)
(3)程序以 root 來調用 ioperm?和 iopl,或是其父進程必須以 root 獲得端口操作權限。(x86 特有的)
若平臺沒有 ioperm 和 iopl 系統調用,用戶空間可以仍然通過使用 /dev/prot 設備文件訪問 I/O 端口。注意:這個文件的定義是體系相關的,并且I/O 端口必須先被注冊。
串操作
除了一次傳輸一個數據的I/O操作,一些處理器實現了一次傳輸一個數據序列的特殊指令,序列中的數據單位可以是字節、字或雙字,這是所謂的串操作指令。它們完成任務比一個 C 語言循環更快。下列宏定義實現了串I/O,它們有的通過單個機器指令實現;但如果目標處理器沒有進行串 I/O 的指令,則通過執行一個緊湊的循環實現。 有的體系的原型如下:void?insb(unsigned?port,?void?*addr,?unsigned?long?count);
void?outsb(unsigned?port,?void?*addr,?unsigned?long?count);
void?insw(unsigned?port,?void?*addr,?unsigned?long?count);
void?outsw(unsigned?port,?void?*addr,?unsigned?long?count);
void?insl(unsigned?port,?void?*addr,?unsigned?long?count);
void?outsl(unsigned?port,?void?*addr,?unsigned?long?count);
使用時注意: 它們直接將字節流從端口中讀取或寫入。當端口和主機系統有不同的字節序時,會導致不可預期的結果。 使用 inw 讀取端口應在必要時自行轉換字節序,以匹配主機字節序。
暫停式?I/O
為了匹配低速外設的速度,有時若 I/O 指令后面還緊跟著另一個類似的I/O指令,就必須在 I/O 指令后面插入一個小延時。在這種情況下,可以使用暫停式的I/O函數代替通常的I/O函數,它們的名字以 _p 結尾,如 inb_p、outb_p等等。 這些函數定義被大部分體系支持,盡管它們常常被擴展為與非暫停式I/O 同樣的代碼。因為如果體系使用一個合理的現代外設總線,就沒有必要額外暫停。細節可參考平臺的 asm 子目錄的 io.h 文件。以下是include\asm-arm\io.h中的宏定義:#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使用內部總線,就沒有必要額外暫停,所以暫停式的I/O函數被擴展為與非暫停式I/O 同樣的代碼。
平臺相關性
由于自身的特性,I/O 指令與處理器密切相關的,非常難以隱藏系統間的不同。所以大部分的關于端口 I/O 的源碼是平臺依賴的。IA-32 (x86)x86_64這個體系支持所有的以上描述的函數,端口號是 unsigned short 類型。ARM端口映射到內存,支持所有函數。串操作 用C語言實現。端口是 unsigned int 類型。
4.使用 I/O 內存
除了?x86上普遍使用的I/O 端口外,和設備通訊另一種主要機制是通過使用映射到內存的寄存器或設備內存,統稱為 I/O 內存。因為寄存器和內存之間的區別對軟件是透明的。I/O 內存僅僅是類似 RAM 的一個區域,處理器通過總線訪問這個區域,以實現設備的訪問。
根據平臺和總線的不同,I/O 內存可以就是否通過頁表訪問分類。若通過頁表訪問,內核必須首先安排物理地址使其對設備驅動程序可見,在進行任何 I/O 之前必須調用 ioremap。若不通過頁表,I/O 內存區域就類似I/O 端口,可以使用適當形式的函數訪問它們。因為“side effect”的影響,不管是否需要 ioremap?,都不鼓勵直接使用 I/O 內存的指針。而使用專用的 I/O 內存操作函數,不僅在所有平臺上是安全,而且對直接使用指針操作 I/O 內存的情況進行了優化。
I/O 內存分配和映射
I/O 內存區域使用前必須先分配,函數接口在??定義:struct?resource?*request_mem_region(unsigned?long?start,?unsigned?long?len,?char*name);/* 從 start 開始,分配一個 len 字節的內存區域。成功返回一個非NULL指針,否則返回NULL。所有的 I/O 內存分配情況都 /proc/iomem 中列出。*/
/*I/O內存區域在不再需要時應當釋放*/
void?release_mem_region(unsigned?long?start,?unsigned?long?len);
/*一個舊的檢查 I/O 內存區可用性的函數,不推薦使用*/
int?check_mem_region(unsigned?long?start,?unsigned?long?len);
然后必須設置一個映射,由 ioremap 函數實現,此函數專門用來為I/O 內存區域分配虛擬地址。經過ioremap?之后,設備驅動即可訪問任意的 I/O 內存地址。注意:ioremap 返回的地址不應當直接引用;應使用內核提供的 accessor 函數。以下為函數定義:#include?
void?*ioremap(unsigned?long?phys_addr,?unsigned?long?size);
void?*ioremap_nocache(unsigned?long?phys_addr,?unsigned?long?size);/*如果控制寄存器也在該區域,應使用的非緩存版本,以實現side effect。*/
void?iounmap(void?*?addr);
訪問I/O 內存
訪問I/O 內存的正確方式是通過一系列專用于此目的的函數(在??中定義的):/*I/O 內存讀函數*/
unsigned?int?ioread8(void?*addr);
unsigned?int?ioread16(void?*addr);
unsigned?int?ioread32(void?*addr);
/*addr 是從 ioremap 獲得的地址(可能包含一個整型偏移量), 返回值是從給定 I/O 內存讀取的值*/
/*對應的I/O 內存寫函數*/
void?iowrite8(u8 value,?void?*addr);
void?iowrite16(u16 value,?void?*addr);
void?iowrite32(u32 value,?void?*addr);
/*讀和寫一系列值到一個給定的 I/O 內存地址,從給定的 buf 讀或寫 count 個值到給定的 addr */
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,?const?void?*buf,?unsigned?long?count);
/*需要操作一塊 I/O 地址,使用一下函數*/
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);
/*舊函數接口,仍可工作, 但不推薦。*/
unsigned?readb(address);
unsigned?readw(address);
unsigned?readl(address);
void?writeb(unsigned?value,?address);
void?writew(unsigned?value,?address);
void?writel(unsigned?value,?address);
像?I/O 內存一樣使用端口
一些硬件有一個有趣的特性:一些版本使用 I/O 端口,而其他的使用 I/O 內存。為了統一編程接口,使驅動程序易于編寫,2.6 內核提供了一個ioport_map函數:void?*ioport_map(unsigned?long?port,?unsigned?int?count);/*重映射 count 個I/O 端口,使其看起來像 I/O 內存。,此后,驅動程序可以在返回的地址上使用 ioread8 和同類函數。其在編程時消除了I/O 端口和I/O 內存的區別。
/*這個映射應當在它不再被使用時撤銷:*/
void?ioport_unmap(void?*addr);
/*注意:I/O 端口仍然必須在重映射前使用 request_region 分配I/O 端口。ARM9不支持這兩個函數!*/
ARM9的linux驅動接口
s3c24x0處理器是使用I/O內存的,也就是說:他們的外設接口是通過讀寫相應的寄存器實現的,這些寄存器和內存是使用單一的地址空間,并使用和讀寫內存一樣的指令。所以推薦使用I/O內存的相關指令。
但這并不表示I/O端口的指令在s3c24x0中不可用。但是只要你注意其源碼,你就會發現:其實I/O端口的指令只是一個外殼,內部還是使用和I/O內存一樣的代碼。以下列出一些:
I/O端口#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;?})
I/O內存#define?ioread8(p)????({?unsigned?int?__v?=?__raw_readb(p);?__v;?})
#define?ioread16(p)????({?unsigned?int?__v?=?le16_to_cpu(__raw_readw(p));?__v;?})
#define?ioread32(p)????({?unsigned?int?__v?=?le32_to_cpu(__raw_readl(p));?__v;?})
#define?iowrite8(v,p)????__raw_writeb(v,?p)
#define?iowrite16(v,p)????__raw_writew(cpu_to_le16(v),?p)
#define?iowrite32(v,p)????__raw_writel(cpu_to_le32(v),?p)
在這里值得注意的有4點:
(1)所有的讀寫指令所賦的地址必須都是虛擬地址,你有兩種選擇:使用內核已經定義好的地址,如 S3C2440_GPJCON等等,這些都是內核定義好的虛擬地址,有興趣的可以看源碼。還有一種方法就是使用自己用ioremap映射的虛擬地址。絕對不能使用實際的物理地址,否則會因為內核無法處理地址而出現oops。
(2)在使用I/O指令時,可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因為request的功能只是告訴內核端口被誰占用了,如再次request,內核會制止。
(3)在使用I/O指令時,所賦的地址數據有時必須通過強制類型轉換為?unsigned?long,雖然你的程序可能也可以使用,但是最好還是不要有警告為妙。
(4)在include\asm-arm\arch-s3c2410\hardware.h中定義了很多io口的操作函數,有需要可以在驅動中直接使用,很方便。
總結
以上是生活随笔為你收集整理的模块计算机型x86yu,ldd3学习之九:与硬件通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: b区计算机复试国家线,考研国家线/自主划
- 下一篇: 计算机cpu 和 主板型号,CPU和主板