Linux驱动程序开发 - 设备IO
生活随笔
收集整理的這篇文章主要介紹了
Linux驱动程序开发 - 设备IO
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
序言:
前面我們提到,設備驅動程序的主要功能操作設備,更準確的說就是如何操作設備寄存器或設備內存。不同的計算機體系結構提供了不同的設備操作接口,主要就是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里的定義:
很明顯,它是一個樹結構。Linux里將IO資源分成不同的類型,如IO(Port)、MEM、IRQ、DMA,同時內核提供了IO Resource的操作函數,用于分配、請求、釋放IO資源。
如果管理的IO資源有多個,直接使用IO資源函數就顯得有些麻煩,還好Linux可以使用IO區域來管理這些資源,具體來說,就是Linux定義了一些宏管理IO資源,定義在<linux/ioport.h>頭文件中,如下:
在實際的編程中,我們基本上是使用這些宏來操作IO資源,即使你只有一個IO資源,這樣可以保證程序的可擴展性和跨平臺的兼容性。當然,你必須獲取到IO資源后才可以在Linux內核中操作IO設備。因此,一般來說,你需要在驅動的初始化函數在調用IO區域請求函數來獲取IO區域。
最后要說明一點,就是這些宏操作的IO資源有兩類,分別是 ioport_resource 和 iomem_resource ,他們定義在<linux/kernel/resource.c>中:
IO 端口映射:
在一些平臺,特別是X86平臺,外設通常具有一個獨立的地址空間,叫IO地址空間,對IO地址空間的訪問必須使用特定的IO指令(如x86的IN/OUT指令)。還有一些平臺并沒有IO地址空間,所有的IO都是內存映射(memory-mapped)的,為了提供程序的跨平臺及兼容性,Linux為那些并不支持IO地址空間的平臺提供了IO端口操作函數,他們實際上還是通過訪問IO內存映射地址來訪問的。因此,不管你的程序是使用IO端口映射還是IO內存映射,它都可以很好的運行到各種平臺上。
回到我們的主題-IO端口映射。前面我們提到,在使用IO設備之前我們必須向Linux內核申請使用的資源,因此通常在我們的設備初始化函數或探測函數之中會有如下的代碼:
如果成功申請了IO端口資源,那么我們就可以調用IO端口訪問函數來訪問IO端口了,它們通常定義在<asm/io.h>頭文件中(每個平臺的定義都有所不同,但類似于下表)具體請參考<asm/io.h>頭文件:
IO內存映射(memory-mapped)
一些新的驅動程序都會使用IO內存映射方式來訪問IO設備,因為有些平臺僅僅支持IO內存映射,如ARM平臺。通常來說,使用IO內存就象使用系統RAM內存一樣的簡單,確實有些平臺是支持這樣的訪問的,但還是有些平臺不能象訪問內存那樣直接使用IO內存地址來訪問外設IO(Register和RAM),因此內核提供了一組通用的API來支持程序的跨平臺及可移植性。
Linux內核提供了兩種操作IO內存的函數,一組類似于IO端口函數用于讀取1、2、4個字節數據,定義在<asm/io.h>頭文件中:
這些是新的IO內存操作函數,我們推薦你使用這些函數。如果你瀏覽Linux內核,你會發現還有其他一些函數接口,它們是老的IO內存操作函數,Linux內核會慢慢舍棄這些函數接口,因此盡量不要使用這些函數,但有必要在這里把這些函數列出來,因為確實還是有一些新的驅動仍然使用它們(參考<asm/io.h>):
如果你想象操作內存那樣成塊的操作IO內存,內核提供了另外的方法,它們類似于內存操作函數。推薦你使用這些函數操作IO內存而不是直接使用IO內存地址,這樣你的程序可以移植到不同的平臺上,它們定義在<asm/io.h>頭文件中。
同樣在使用IO內存之前,你需要向Linux內核申請IO區域:
申請完IO區域后,你還不能直接使用它們,你必須把這個地址映射到Linux內核的虛擬地址空間中來,這個操作是通過ioremap函數來實現的,請參考<asm/io.h>頭文件:
通過這兩步操作后,你就可以調用IO內存函數來訪問設備IO了。
IO端口重映射
這里說的IO端口重映射不是ioremap的功能,ioremap是將IO內存映射到Linux內核的虛擬地址空間中。我們說的IO端口重映射是將IO端口映射為IO內存,這樣就可以象操作IO內存 一樣操作IO端口了。這樣做的好處是我們可以統一驅動程 序的接口(都使用IO內存映射),避免為同一個設備提供不同的驅動接口。這個函數同樣定義在<asm/io.h>頭文件中:
有一點要注意,在調用完IO端口重映射后,還是需要調用ioremap函數把它映射到Linux內核的虛擬地址空間中來。
后記
設備IO的操作就是這些了,其實在我們的編程中只要調用幾個簡單的函數或宏就可以完成IO端口的操作了。這里有個問題沒有說明,就是設備的訪問是需要同步的或著需要延時等待一段時間才能進行下一步的操作。我們在《內核同步技術》一章有個簡單的介紹,后面我們將補充幾個例子來進一步說明如何進行IO操作。 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
前面我們提到,設備驅動程序的主要功能操作設備,更準確的說就是如何操作設備寄存器或設備內存。不同的計算機體系結構提供了不同的設備操作接口,主要就是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資源,定義在<linux/ioport.h>頭文件中,如下:
| #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 ,他們定義在<linux/kernel/resource.c>中:
| 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端口了,它們通常定義在<asm/io.h>頭文件中(每個平臺的定義都有所不同,但類似于下表)具體請參考<asm/io.h>頭文件:
| 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個字節數據,定義在<asm/io.h>頭文件中:
| ioread8(p) ioread16(p) ioread32(p) iowrite8(v,p) iowrite16(v,p) iowrite32(v,p) |
這些是新的IO內存操作函數,我們推薦你使用這些函數。如果你瀏覽Linux內核,你會發現還有其他一些函數接口,它們是老的IO內存操作函數,Linux內核會慢慢舍棄這些函數接口,因此盡量不要使用這些函數,但有必要在這里把這些函數列出來,因為確實還是有一些新的驅動仍然使用它們(參考<asm/io.h>):
| readb() readw() readl() writeb() writew() writel() |
如果你想象操作內存那樣成塊的操作IO內存,內核提供了另外的方法,它們類似于內存操作函數。推薦你使用這些函數操作IO內存而不是直接使用IO內存地址,這樣你的程序可以移植到不同的平臺上,它們定義在<asm/io.h>頭文件中。
| 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函數來實現的,請參考<asm/io.h>頭文件:
| membase = ioremap(mapbase, size); |
通過這兩步操作后,你就可以調用IO內存函數來訪問設備IO了。
IO端口重映射
這里說的IO端口重映射不是ioremap的功能,ioremap是將IO內存映射到Linux內核的虛擬地址空間中。我們說的IO端口重映射是將IO端口映射為IO內存,這樣就可以象操作IO內存 一樣操作IO端口了。這樣做的好處是我們可以統一驅動程 序的接口(都使用IO內存映射),避免為同一個設備提供不同的驅動接口。這個函數同樣定義在<asm/io.h>頭文件中:
| extern void __iomem *ioport_map(unsigned long port, unsigned int nr); extern void ioport_unmap(void __iomem *addr); |
有一點要注意,在調用完IO端口重映射后,還是需要調用ioremap函數把它映射到Linux內核的虛擬地址空間中來。
后記
設備IO的操作就是這些了,其實在我們的編程中只要調用幾個簡單的函數或宏就可以完成IO端口的操作了。這里有個問題沒有說明,就是設備的訪問是需要同步的或著需要延時等待一段時間才能進行下一步的操作。我們在《內核同步技術》一章有個簡單的介紹,后面我們將補充幾個例子來進一步說明如何進行IO操作。 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的Linux驱动程序开发 - 设备IO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 返回函数局部变量的指针和引用
- 下一篇: linux 内核驱动编程 简单例子 与_