Contents ...
udn網路城邦
Linux驅動程序開發之設備IO
2015/11/26 16:46
瀏覽574
迴響0
推薦0
引用0
序言:

前面我們提到,設備驅動程序的主要功能操作設備,更準確的說就是如何操作設備寄存器或設備內存。不同的計算機體系結構提供了不同的設備操作接口,主要就是 IO端口映射(Ports)或IO內存映射(Memory-Map )。例如X86平臺,它對設備的訪問就同時提供了IO端口映射方式或IO內存映射方式,這個在大學的匯編語言課程裏有詳細的介紹,當然還有一些平臺緊提供 IO內存映射。IO端口映射方式是CPU提供了獨立的地址空間給設備IO,並且使用特定的匯編指令操作IO端口。IO內存映射方式提供了統一的內存編址方 式來訪問設備IO,就像你訪問系統內存一樣。


通常對於一個給定的硬件平臺電路板,它的設備寄存器或內存的物理地址就是確定的了,或者是相對確定的了(它們具有自己的IO地址空間)。但對於向 Linux這樣的操作系統,驅動程序是不能直接訪問設備的物理地址的,它必須把設備的物理地址映射到Linux內核的虛擬地址空間,這樣驅動程序才能通過 虛擬地址操作設備。


IO區域:

Linux中使用IO區域(IO Region)來管理設備IO無論它是IO端口映射還是IO內存映射。IO區域是基於IO資源(Resource)來實現的,我們首先來看看IO資源在Linux裏的定義:

struct resource {

resource_size_t start;

resource_size_t end;

const char *name;

unsigned long flags;

struct resource *parent, *sibling, *child;

};

#define IORESOURCE_IO        0x00000100

#define IORESOURCE_MEM        0x00000200

#define IORESOURCE_IRQ        0x00000400

#define IORESOURCE_DMA        0x00000800


extern int request_resource(struct resource *root, struct resource *new);

extern int release_resource(struct resource *new);


很明顯,它是一個樹結構。Linux裏將IO資源分成不同的類型,如IO(Port)、MEM、IRQ、DMA,同時內核提供了IO Resource的操作函數,用於分配、請求、釋放IO資源。


如果管理的IO資源有多個,直接使用IO資源函數就顯得有些麻煩,還好Linux可以使用IO區域來管理這些資源,具體來說,就是Linux定義了一些宏管理IO資源,定義在頭文件中,如下:

#define request_region(start,n,name)  __request_region(&ioport_resource,(start), (n),(name))

#define request_mem_region(start,n,name)__request_region(&iomem_resource,(start), (n),(name))


#define release_region(start,n)    __release_region(&ioport_resource, (start), (n))

#define check_mem_region(start,n)    __check_region(&iomem_resource, (start), (n))

#define release_mem_region(start,n)    __release_region(&iomem_resource, (start), (n))


在實際的編程中,我們基本上是使用這些宏來操作IO資源,即使你只有一個IO資源,這樣可以保證程序的可擴展性和跨平臺的兼容性。當然,你必須獲取到IO 資源後才可以在Linux內核中操作IO設備。因此,一般來說,你需要在驅動的初始化函數在調用IO區域請求函數來獲取IO區域。


最後要說明一點,就是這些宏操作的IO資源有兩類,分別是ioport_resource和iomem_resource,他們定義在中:

struct resource ioport_resource = {

.name    = "PCI IO",

.start    = 0,

.end    = IO_SPACE_LIMIT,

.flags    = IORESOURCE_IO,

};


struct resource iomem_resource = {

.name    = "PCI mem",

.start    = 0,

.end    = -1,

.flags    = IORESOURCE_MEM,

};


IO 端口映射:

在一些平臺,特別是X86平臺,外設通常具有一個獨立的地址空間,叫IO地址空間,對IO地址空間的訪問必須使用特定的IO指令(如x86的IN/OUT 指令)。還有一些平臺並沒有IO地址空間,所有的IO都是內存映射(memory-mapped)的,為了提供程序的跨平臺及兼容性,Linux為那些並 不支持IO地址空間的平臺提供了IO端口操作函數,他們實際上還是通過訪問IO內存映射地址來訪問的。因此,不管你的程序是使用IO端口映射還是IO內存 映射,它都可以很好的運行到各種平臺上。


回到我們的主題-IO端口映射。前面我們提到,在使用IO設備之前我們必須向Linux內核申請使用的資源,因此通常在我們的設備初始化函數或探測函數之中會有如下的代碼:

if (!request_region(io_addr, IO_NUM, DRV_NAME))

return -ENODEV;


如果成功申請了IO端口資源,那麽我們就可以調用IO端口訪問函數來訪問IO端口了,它們通常定義在頭文件中(每個平臺的定義都有所不同,但類似於下表)具體請參考頭文件:

inb(unsigned port)

outb(u8 v, unsigned port)

inw(unsigned port)

oubw(u16 v, unsigned port)

inl(unsigned port)

outl(u32 v, unsigned port)

IO內存映射(memory-mapped)

一些新的驅動程序都會使用IO內存映射方式來訪問IO設備,因為有些平臺僅僅支持IO內存映射,如ARM平臺。通常來說,使用IO內存就象使用系統RAM 內存一樣的簡單,確實有些平臺是支持這樣的訪問的,但還是有些平臺不能象訪問內存那樣直接使用IO內存地址來訪問外設IO(Register和RAM), 因此內核提供了一組通用的API來支持程序的跨平臺及可移植性。


Linux內核提供了兩種操作IO內存的函數,一組類似於IO端口函數用於讀取1、2、4個字節數據,定義在頭文件中:

ioread8(p)

ioread16(p)

ioread32(p)

iowrite8(v,p)

iowrite16(v,p)

iowrite32(v,p)


這些是新的IO內存操作函數,我們推薦你使用這些函數。如果你瀏覽Linux內核,你會發現還有其他一些函數接口,它們是老的IO內存操作函 數,Linux內核會慢慢舍棄這些函數接口,因此盡量不要使用這些函數,但有必要在這裏把這些函數列出來,因為確實還是有一些新的驅動仍然使用它們(參 考):

readb()

readw()

readl()

writeb()

writew()

writel()


如果你想象操作內存那樣成塊的操作IO內存,內核提供了另外的方法,它們類似於內存操作函數。推薦你使用這些函數操作IO內存而不是直接使用IO內存地址,這樣你的程序可以移植到不同的平臺上,它們定義在頭文件中。

extern void _memcpy_fromio(void *, const volatile void __iomem *, size_t);

extern void _memcpy_toio(volatile void __iomem *, const void *, size_t);

extern void _memset_io(volatile void __iomem *, int, size_t);


同樣在使用IO內存之前,你需要向Linux內核申請IO區域:

if (!request_mem_region(mapbase, size, DRVNAME)) {

ret = -EBUSY;

break;

}


申請完IO區域後,你還不能直接使用它們,你必須把這個地址映射到Linux內核的虛擬地址空間中來,這個操作是通過ioremap函數來實現的,請參考頭文件:

membase = ioremap(mapbase, size);


通過這兩步操作後,你就可以調用IO內存函數來訪問設備IO了。


IO端口重映射

這裏說的IO端口重映射不是ioremap的功能,ioremap是將IO內存映射到Linux內核的虛擬地址空間中。我們說的IO端口重映射是將IO端 口映射為IO內存,這樣就可以象操作IO內存一樣操作IO端口了。這樣做的好處是我們可以統一驅動程序的接口(都使用IO內存映射),避免為同一個設備提 供不同的驅動接口。這個函數同樣定義在頭文件中:

extern void __iomem *ioport_map(unsigned long port, unsigned int nr);

extern void ioport_unmap(void __iomem *addr);


有一點要註意,在調用完IO端口重映射後,還是需要調用ioremap函數把它映射到Linux內核的虛擬地址空間中來。


後記

設備IO的操作就是這些了,其實在我們的編程中只要調用幾個簡單的函數或宏就可以完成IO端口的操作了。這裏有個問題沒有說明,就是設備的訪問是需要同步 的或著需要延時等待一段時間才能進行下一步的操作。我們在《內核同步技術》一章有個簡單的介紹,後面我們將補充幾個例子來進一步說明如何進行IO操作。
全站分類:知識學習 科學百科
自訂分類:不分類
上一則: linux內核編程
下一則: udev漏洞普通用戶變root

限會員,要發表迴響,請先登入