1. 程式人生 > >Linux驅動開發雜記(0x08) -I/O記憶體

Linux驅動開發雜記(0x08) -I/O記憶體

儘管 I/O 埠在x86世界中非常流行,但是用來和裝置通訊的主要機制是通過記憶體對映的暫存器和裝置記憶體,兩者都稱為I/O 記憶體,因為暫存器和記憶體之間的區別對軟體是透明的。   I/O 記憶體僅僅是一個類似於RAM 的區域,處理器通過匯流排訪問該區域,以實現對裝置的訪問。同樣,讀寫這個區域是有邊際效應。   根據計算機體系和匯流排不同,I/O 記憶體可分為可以或者不可以通過頁表來存取。若通過頁表存取,核心必須先重新編排實體地址,使其對驅動程式可見,這就意味著在進行任何I/O操作之前,你必須呼叫ioremap;如果不需要頁表,I/O記憶體區域就類似於I/O埠,你可以直接使用適當的I/O函式讀寫它們。   由於邊際效應的緣故,不管是否需要 ioremap,都不鼓勵直接使用I/O記憶體指標,而應使用專門的I/O記憶體操作函式。這些I/O記憶體操作函式不僅在所有平臺上是安全,而且對直接使用指標操作 I/O 記憶體的情況進行了優化。

1 申請I/O 記憶體:

I/O 記憶體區在使用前必須先分配。分配記憶體區的函式介面在<linux/ioport.h>定義中:

/* request_mem_region分配一個開始於start,len位元組的I/O記憶體區。分配成功,返回一個非NULL指標;
否則返回NULL。系統當前所有I/O記憶體分配資訊都在/proc/iomem檔案中列出,你分配失敗時,可以看看該檔案,
看誰先佔用了該記憶體區 */
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

2 對映:

在訪問I/O記憶體之前,分配I/O記憶體並不是唯一要求的步驟,你還必須保證核心可存取該I/O記憶體。訪問I/O記憶體並不只是簡單解引用指標,在許多體系中,I/O 記憶體無法以這種方式直接存取。因此,還必須通過ioremap 函式設定一個對映。

/* ioremap用於將I/O記憶體區對映到虛擬地址。引數phys_addr為要對映的I/O記憶體起始地址,
引數size為要對映的I/O記憶體的大小,返回值為被對映到的虛擬地址 */
void *ioremap(unsigned long phys_addr, unsigned long size);

3 訪問IO記憶體:

經過 ioremap之後,裝置驅動就可以存取任何I/O記憶體地址。注意,ioremap返回的地址不可以直接解引用;相反,應當使用核心提供的訪問函式。訪問I/O記憶體的正確方式是通過一系列專門用於實現此目的的函式:

#include <asm/io.h>
/* I/O記憶體讀函式。引數addr應當是從ioremap獲得的地址(可能包含一個整型偏移); 
返回值是從給定I/O記憶體讀取到的值 */
unsigned int ioread8(void *addr); unsigned int ioread16(void *addr); unsigned int ioread32(void *addr); /* I/O記憶體寫函式。引數addr同I/O記憶體讀函式,引數value為要寫的值 */ void iowrite8(u8 value, void *addr); void iowrite16(u16 value, void *addr); void iowrite32(u32 value, void *addr); /* 以下這些函式讀和寫一系列值到一個給定的 I/O 記憶體地址,從給定的buf讀或寫count個值到給定的addr。 引數count表示要讀寫的資料個數,而不是位元組大小 */ 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 地址時,使用下列函式(這些函式的行為類似於它們的C庫類似函式): */ 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記憶體讀寫函式,不推薦使用 */ unsigned readb(address); unsigned readw(address); unsigned readl(address); void writeb(unsigned value, address); void writew(unsigned value, address); void writel(unsigned value, address);

4 釋放IO記憶體步驟:

void iounmap(void * addr); /* iounmap用於釋放不再需要的對映 */
void release_mem_region(unsigned long start, unsigned long len); /* iounmap用於釋放不再需要的對映 */

5 像IO記憶體一樣使用埠

一些硬體有一個有趣的特性: 有些版本使用 I/O 埠;而有些版本則使用 I/O 記憶體。不管是I/O 埠還是I/O 記憶體,處理器見到的裝置暫存器都是相同的,只是訪問方法不同。為了統一程式設計介面,使驅動程式易於編寫,2.6 核心提供了一個ioport_map函式:

/* ioport_map重新對映count個I/O埠,使它們看起來I/O記憶體。
此後,驅動程式可以在ioport_map返回的地址上使用ioread8和同類函式。
這樣,就可以在程式設計時,消除了I/O 埠和I/O 記憶體的區別 */
void *ioport_map(unsigned long port, unsigned int count);

void ioport_unmap(void *addr);/* ioport_unmap用於釋放不再需要的對映 */

注意,I/O 埠在重新對映前必須使用request_region分配分配所需的I/O 埠。