1. 程式人生 > >linux 驅動訪問外裝置的方法

linux 驅動訪問外裝置的方法

1. 操作IO埠(申請,訪問,釋放)
  I/O 埠是驅動用來和很多裝置通訊的方法。

(1)申請I/O 埠

  在驅動還沒獨佔裝置之前,不應對埠進行操作。核心提供了一個註冊介面,以允許驅動宣告其需要的埠:
/* request_region告訴核心:要使用first開始的n個埠。引數name為裝置名。如果分配成功返回值是非NULL;否則無法使用需要的埠(/proc/ioports包含了系統當前所有埠的分配資訊,若request_region分配失敗時,可以檢視該檔案,看誰先用了你要的埠) */
struct resource *request_region(unsigned long first, unsigned long
n, const char *name);

(2)訪問IO埠:

  在驅動成功請求到I/O 埠後,就可以讀寫這些埠了。大部分硬體會將8位、16位和32位埠區分開,無法像訪問記憶體那樣混淆使用。驅動程式必須呼叫不同的函式來訪問不同大小的埠。
  Linux 核心標頭檔案(體系依賴的標頭檔案<asm/io.h>) 定義了下列行內函數來存取I/O埠:

複製程式碼
/* inb/outb:讀/寫位元組埠(8位寬)。有些體系將port引數定義為unsigned long;而有些平臺則將它定義為unsigned short。inb的返回型別也是依賴體系的 */
unsigned inb(unsigned port);
void
outb(unsigned char byte, unsigned port); /* inw/outw:讀/寫字埠(16位寬) */ unsigned inw(unsigned port); void outw(unsigned short word, unsigned port); /* inl/outl:讀/寫32位埠。longword也是依賴體系的,有的體系為unsigned long;而有的為unsigned int */ unsigned inl(unsigned port); void outl(unsigned longword, unsigned port);
複製程式碼

(3)釋放IO埠:

/* 用完I/O埠後(可能在模組解除安裝時),應當呼叫release_region將I/O埠返還給系統。引數start和n應與之前傳遞給request_region一致 
*/ void release_region(unsigned long start, unsigned long n);

2.操作IO記憶體(申請,對映,訪問,釋放):
  儘管 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用於釋放不再需要的對映 */

3、像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 埠。

注意:通過I/O埠訪問裝置時,只需要申請裝置的I/O埠,就可以使用linux核心提供的行內函數如:inb,outb等。

但是通過I/O記憶體訪問埠,除了申請I/O記憶體外,還需要,將實體記憶體對映成虛擬記憶體,這樣核心才能訪問外裝置

這是因為:

根據計算機體系和匯流排不同,I/O 記憶體可分為可以或者不可以通過頁表來存取。若通過頁表存取,核心必須先重新編排實體地址,使其對驅動程式可見,
這就意味著在進行任何I/O操作之前,你必須呼叫ioremap;如果不需要頁表,I/O記憶體區域就類似於I/O埠,你可以直接使用適當的I/O函式讀寫它們。
由於邊際效應的緣故,不管是否需要 ioremap,都不鼓勵直接使用I/O記憶體指標,而應使用專門的I/O記憶體操作函式。這些I/O記憶體操作函式不僅在所有平臺上是安全,
而且對直接使用指標操作 I/O 記憶體的情況進行了優化