標籤:
名詞解釋
記憶體空間與IO空間
記憶體空間是電腦系統裡面非系統記憶體地區的地址空間,現在的通用X86體系提供32位地址,定址4G位元組的記憶體空間,但一般的電腦只安裝256M位元組或者更少的記憶體,剩下的高位記憶體就被用於PCI或者AGP及系統橋裝置的使用上面,主機可以像訪問系統記憶體一樣訪問這些高端記憶體,這樣對於擴充的裝置有更大的空間。
Linux使用者空間與核心空間
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 GPB0writel(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 GPB0writel(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
Linux驅動設計——記憶體與IO訪問