linux驱动静态分配内存,Linux驱动设计——内存与IO访问
名詞解釋
內存空間與IO空間
內存空間是計算機系統里面非系統內存區域的地址空間,現在的通用X86體系提供32位地址,尋址4G字節的內存空間,但一般的計算機只安裝256M字節或者更少的內存,剩下的高位內存就被用于PCI或者AGP及系統橋設備的使用上面,主機可以像訪問系統內存一樣訪問這些高端內存,這樣對于擴展的設備有更大的空間。
IO空間是X86系統上面的專用空間,現在的IO空間大小是64K字節,從0x0000到0xffff,可以供設備使用,比如南橋很多的設備就是掛在IO空間上,很多的PCI設備也使用IO空間,IO空間尋址使用專門的IO命令來完成。
配置空間是即插即用設備的廣義描述,一般的配置空間指的是PCI設備或者PCI橋設備的配置空間,在配置空間里,一般的PCI設備的配置空間是256字節,但很多橋設備都是用擴展的配置空間,比如系統橋空間可以達到1k字節。配置空間為設備提供其配置信息,比如設備的IO基地址,內存基地址和中斷號等信息。這些信息由BIOS或者操作系統寫入,一般只有驅動程序才會訪問配置空間。
在X86 處理器中存在著I/O 空間的概念,I/O 空間是相對于內存空間而言的,它通過特定的指令in、out 來訪問。端口號標識了外設的寄存器地址。
目前,大多數嵌入式微控制器如ARM、PowerPC 等中并不提供I/O 空間,而僅存在內存空間。內存空間可以直接通過地址、指針來訪問,程序和程序運行中使用的變量和其他數據都存在于內存空間中。內存地址可以直接由C 語言指針操作。
例如在186 處理器中執行如下代碼:
unsigned char *p = (unsigned char *)0xF000FF00;
*p=11; //程序的意義為在絕對地址0xF0000+0xFF00(186 處理器使用16 位段地址和16 位偏移地址)寫入11。
ARM、PowerPC 等未采用段地址,p 指向的內存空間就是0xF000FF00,而*p = 11 就是在該地址寫入11。
Question:對于ARM來說,內存空間0xF000FF00是物理地址還是虛擬地址? Answer:虛擬地址,物理地址必須映射為虛擬地址才可以訪問
即便是在X86 處理器中,雖然提供了I/O 空間,如果由我們自己設計電路板,外設仍然可以只掛接在內存空間。此時,CPU 可以像訪問一個內存單元那樣訪問外設I/O 端口,而不需要設立專門的I/O 指令。因此,內存空間是必須的,而I/O 空間是可選的。
內存管理單元(MMU)
功能:內存管理(虛擬地址和物理地址的映射、內存訪問權限保護和Cache 緩存控制等硬件支持)
操作原理:
內存的存取
設備IO 端口和IO 內存的訪問
設備通常會提供一組寄存器來用于控制設備、讀寫設備和獲取設備狀態,即控制寄存器、數據寄存器和狀態寄存器。這些寄存器可能位于I/O 空間,也可能位于內存空間。當位于I/O 空間時,通常被稱為I/O 端口,位于內存空間時,對應的內存空間被稱為I/O 內存。
當然對于ARM來說,這些端口寄存器是位于內存空間(IO內存)。
Linux對IO端口的訪問
把IO端口映射到內存空間
以后用到再來添加
Linux對IO內存的訪問
在內核中訪問I/O 內存之前,需首先使用ioremap()函數將設備所處的物理地址映射到虛擬地址。
//ioremap()的原型如下:
void *ioremap(unsigned long offset, unsigned long size);
//通過ioremap()獲得的虛擬地址應該被iounmap()函數釋放
void iounmap(void * addr);
在設備的物理地址被映射到虛擬地址之后,盡管可以直接通過指針訪問這些地址,但是可以使用Linux 內核的如下一組函數來完成設備內存映射的虛擬地址的讀寫
//(1)讀I/O 內存。
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
//與上述函數對應的較早版本的函數為(這些函數在Linux 2.6 中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
//(2)寫I/O 內存。
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
//與上述函數對應的較早版本的函數為(這些函數在Linux 2.6 中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
//(3)讀一串I/O 內存。
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);
//(4)寫一串I/O 內存。
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);
//(5)復制I/O 內存。
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
//(6)設置I/O 內存。
void memset_io(void *addr, u8 value, unsigned int count);
beep_ioremap實例
...
//Port GPBx Register address declaration
#define GPBCON (unsigned long)ioremap(0x56000010,4)
#define GPBDAT (unsigned long)ioremap(0x56000014,4)
#define GPBUP (unsigned long)ioremap(0x56000018,4)
...
void beep_start( void )
{
/*config GPBCON, set GPB0 as output port*/
port_status = readl(GPBCON);
port_status &= ~0x03;
port_status |= 0x01;
writel(port_status,GPBCON);
port_status = readl(GPBDAT);
port_status |= 0x01; // set 1 to GPB0
writel(port_status,GPBDAT);
}
void beep_stop( void )
{
/*config GPBCON, set GPB0 as output port*/
port_status = readl(GPBCON);
port_status &= ~0x03;
port_status |= 0x01;
writel(port_status,GPBCON);
port_status = readl(GPBDAT);
port_status &= ~0x01;// set 0 to GPB0
writel(port_status,GPBDAT);
}
...
申請和釋放IO端口和IO內存
(此處只看IO內存)
//一組函數用于申請和釋放I/O 內存的范圍,成對存在
//這個函數向內核申請n 個內存地址,這些地址從first 開始,name 參數為設備的名稱。如果分配成功返回值是非 NULL,如果返回NULL,則意味著申請I/O 內存失敗。
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
//
void release_mem_region(unsigned long start, unsigned long len);
上述request_region()和release_mem_region()都不是必須的,但建議使用。其任務是檢查申請的資源是否可用,如果可用則申請成功,并標志為已經使用,其他驅動想再次申請該資源時就會失敗。
有許多設備驅動程序在沒有申請I/O 端口和I/O 內存之前就直接訪問了,這不夠安全。
設備 I/O 端口和I/O 內存訪問流程
I/O 內存的訪問步驟:
1.?調用request_mem_region()申請資源
2.將寄存器地址通過ioremap()映射到內核空間虛擬地址
3.?通過Linux 設備訪問編程接口訪問這些設備的寄存器
4.?訪問完成后,應對ioremap()申請的虛擬地址進行釋放,并釋放release_mem_region()申請的I/O 內存資源
將設備地址映射到用戶空間
1.內存映射與VMA
一般情況下,用戶空間是不可能也不應該直接訪問設備的,但是,設備驅動程序中可實現mmap()函數,這個函數可使得用戶空間直能接訪問設備的物理地址。實際上,mmap()實現了這樣的一個映射過程:它將用戶空間的一段內存與設備內存關聯,當用戶訪問用戶空間的這段地址范圍時,實際上會轉化為對設備的訪問。
(這種能力對于顯示適配器一類的設備非常有意義,如果用戶空間可直接通過內存映射訪問顯存的話,屏幕幀的各點的像素將不再需要一個從用戶空間到內核空間的復制的過程。)
mmap()必須以PAGE_SIZE 為單位進行映射,實際上,內存只能以頁為單位進行映射,若要映射非PAGE_SIZE 整數倍的地址范圍,要先進行頁對齊,強行以PAGE_SIZE 的倍數大小進行映射。
//驅動中mmap()函數的原型
int(*mmap)(struct file *, struct vm_area_struct*);
//應用層的系統調用
caddr_t mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);
/*
參數fd 為文件描述符,一般由open()返回,fd 也可以指定為!1,此時需指定flags 參數MAP_ANON,表明進行的是匿名映射。
len 是映射到調用用戶空間的字節數,它從被映射文件開頭offset 個字節開始算起,offset 參數一般設為0,表示從文件頭開始映射。
prot 參數指定訪問權限,可取如下幾個值的“或”:PROT_READ(可讀)、PROT_WRITE(可寫)、PROT_EXEC(可執行)和PROT_NONE(不可訪問)。
參數addr 指定文件應被映射到用戶空間的起始地址,一般被指定為NULL,這樣,選擇起始地址的任務將由內核完成,而函數的返回值就是映射到用戶空間的地址。其類型caddr_t 實際上就是void *。
*/
當用戶調用mmap()的時候,內核會進行如下處理。
① 在進程的虛擬空間查找一塊VMA。
② 將這塊VMA 進行映射。
③ 如果設備驅動程序或者文件系統的file_operations 定義了mmap()操作,則調用它。
④ 將這個VMA 插入進程的VMA 鏈表中。
IO內存靜態映射
DMA
原文:http://www.cnblogs.com/kwseeker-bolgs/p/4471164.html
總結
以上是生活随笔為你收集整理的linux驱动静态分配内存,Linux驱动设计——内存与IO访问的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 提供多用户telnet,li
- 下一篇: 深度linux 网络配置文件,solve