W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
盡管 I/O 端口在 x86 世界中流行, 用來(lái)和設(shè)備通訊的主要機(jī)制是通過(guò)內(nèi)存映射的寄存器和設(shè)備內(nèi)存. 2 者都稱(chēng)為 I/O 內(nèi)存, 因?yàn)榧拇嫫骱蛢?nèi)存之間的區(qū)別對(duì)軟件是透明的.
I/O 內(nèi)存是簡(jiǎn)單的一個(gè)象 RAM 的區(qū)域, 它被處理器用來(lái)跨過(guò)總線存取設(shè)備. 這個(gè)內(nèi)存可用作幾個(gè)目的, 例如持有視頻數(shù)據(jù)或者以太網(wǎng)報(bào)文, 同時(shí)實(shí)現(xiàn)設(shè)備寄存器就象 I/O 端口一樣的行為(即, 它們有讀和寫(xiě)它們相關(guān)聯(lián)的邊際效果).
存取 I/O 內(nèi)存的方式依賴(lài)計(jì)算機(jī)體系, 總線, 和使用的設(shè)備, 盡管外設(shè)到處都一樣. 本章的討論主要觸及 ISA 和 PCI 內(nèi)存, 而也試圖傳遞通用的信息. 盡管存取 PCI 內(nèi)存在這里介紹, 一個(gè) PCI 的通透介紹安排在第 12 章.
依賴(lài)計(jì)算機(jī)平臺(tái)和使用的總線, I/O 內(nèi)存可以或者不可以通過(guò)頁(yè)表來(lái)存取. 當(dāng)通過(guò)頁(yè)表存取, 內(nèi)核必須首先安排從你的驅(qū)動(dòng)可見(jiàn)的物理地址, 并且這常常意味著你必須調(diào)用 ioremap 在做任何 I/O 之前. 如果不需要頁(yè)表, I/O 內(nèi)存位置看來(lái)很象 I/O 端口, 并且你只可以使用正確的包裝函數(shù)讀和寫(xiě)它們.
不管是否需要 ioremap 來(lái)存取 I/O 內(nèi)存, 不鼓勵(lì)直接使用 I/O 內(nèi)存的指針. 盡管(如同在 "I/O 端口和 I/O 內(nèi)存" 一節(jié)中介紹的 )I/O 內(nèi)存如同在硬件級(jí)別的正常 RAM 一樣尋址, 在"I/O 寄存器和傳統(tǒng)內(nèi)存"一節(jié)中概述的額外的小心建議避免正常的指針. 用來(lái)存取 I/O 內(nèi)存的包裝函數(shù)在所有平臺(tái)上是安全的并且在任何時(shí)候直接的指針解引用能夠進(jìn)行操作時(shí), 會(huì)被優(yōu)化掉.
因此, 盡管在 x86 上解引用一個(gè)指針能工作(在現(xiàn)在), 不使用正確的宏定義阻礙了驅(qū)動(dòng)的移植性和可讀性.
I/O 內(nèi)存區(qū)必須在使用前分配. 分配內(nèi)存區(qū)的接口是( 在 <linux/ioport.h> 定義):
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
這個(gè)函數(shù)分配一個(gè) len 字節(jié)的內(nèi)存區(qū), 從 start 開(kāi)始. 如果一切順利, 一個(gè) 非NULL 指針?lè)祷? 否則返回值是 NULL. 所有的 I/O 內(nèi)存分配來(lái) /proc/iomem 中列出.
內(nèi)存區(qū)在不再需要時(shí)應(yīng)當(dāng)釋放:
void release_mem_region(unsigned long start, unsigned long len);
還有一個(gè)舊的檢查 I/O 內(nèi)存區(qū)可用性的函數(shù):
int check_mem_region(unsigned long start, unsigned long len);
但是, 對(duì)于 check_region, 這個(gè)函數(shù)是不安全和應(yīng)當(dāng)避免的.
在存取內(nèi)存之前, 分配 I/O 內(nèi)嵌不是唯一的要求的步驟. 你必須也保證這個(gè) I/O 內(nèi)存已經(jīng)對(duì)內(nèi)核是可存取的. 使用 I/O 內(nèi)存不只是解引用一個(gè)指針的事情; 在許多系統(tǒng), I/O 內(nèi)存根本不是可以這種方式直接存取的. 因此必須首先設(shè)置一個(gè)映射. 這是 ioremap 函數(shù)的功能, 在第 1 章的 "vmalloc 及其友"一節(jié)中介紹的. 這個(gè)函數(shù)設(shè)計(jì)來(lái)特別的安排虛擬地址給 I/O 內(nèi)存區(qū).
一旦裝備了 ioremap (和iounmap), 一個(gè)設(shè)備驅(qū)動(dòng)可以存取任何 I/O 內(nèi)存地址, 不管是否它是直接映射到虛擬地址空間. 記住, 但是, 從 ioremap 返回的地址不應(yīng)當(dāng)直接解引用; 相反, 應(yīng)當(dāng)使用內(nèi)核提供的存取函數(shù). 在我們進(jìn)入這些函數(shù)之前, 我們最好回顧一下 ioremap 原型和介紹幾個(gè)我們?cè)谇耙徽侣赃^(guò)的細(xì)節(jié).
這些函數(shù)根據(jù)下列定義調(diào)用:
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void * addr);
首先, 你注意新函數(shù) ioremap_nacache. 我們?cè)诘?8 章沒(méi)有涉及它, 因?yàn)樗囊馑际敲鞔_地硬件相關(guān)的. 引用自一個(gè)內(nèi)核頭文件:"It’s useful if some control registers are in such an area, and write combining or read caching is not desirable.". 實(shí)際上, 函數(shù)實(shí)現(xiàn)在大部分計(jì)算機(jī)平臺(tái)上與 ioremap 一致: 在所有 I/O 內(nèi)存已經(jīng)通過(guò)非緩沖地址可見(jiàn)的地方, 沒(méi)有理由使用一個(gè)分開(kāi)的, 非緩沖 ioremap 版本.
在一些平臺(tái)上, 你可能逃過(guò)作為一個(gè)指針使用 ioremap 的返回值的懲罰. 這樣的使用不是可移植的, 并且, 更加地, 內(nèi)核開(kāi)發(fā)者已經(jīng)努力來(lái)消除任何這樣的使用. 使用 I/O 內(nèi)存的正確方式是通過(guò)一系列為此而提供的函數(shù)(通過(guò) <asm/io.h> 定義的).
從 I/O 內(nèi)存讀, 使用下列之一:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
這里, addr 應(yīng)當(dāng)是從 ioremap 獲得的地址(也許與一個(gè)整型偏移); 返回值是從給定 I/O 內(nèi)存讀取的.
有類(lèi)似的一系列函數(shù)來(lái)寫(xiě) I/O 內(nèi)存:
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
如果你必須讀和寫(xiě)一系列值到一個(gè)給定的 I/O 內(nèi)存地址, 你可以使用這些函數(shù)的重復(fù)版本:
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, const void *buf, unsigned long count);
這些函數(shù)讀或?qū)?count 值從給定的 buf 到 給定的 addr. 注意 count 表達(dá)為在被寫(xiě)入的數(shù)據(jù)大小; ioread32_rep 讀取 count 32-位值從 buf 開(kāi)始.
上面描述的函數(shù)進(jìn)行所有的 I/O 到給定的 addr. 如果, 相反, 你需要操作一塊 I/O 地址, 你可使用下列之一:
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);
這些函數(shù)行為如同它們的 C 庫(kù)類(lèi)似物.
如果你通覽內(nèi)核源碼, 你可看到許多調(diào)用舊的一套函數(shù), 當(dāng)使用 I/O 內(nèi)存時(shí). 這些函數(shù)仍然可以工作, 但是它們?cè)谛麓a中的使用不鼓勵(lì). 除了別的外, 它們較少安全因?yàn)樗鼈儾贿M(jìn)行同樣的類(lèi)型檢查. 但是, 我們?cè)谶@里描述它們:
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
這些宏定義用來(lái)從 I/O 內(nèi)存獲取 8-位, 16-位, 和 32-位 數(shù)據(jù)值.
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
如同前面的函數(shù), 這些函數(shù)(宏)用來(lái)寫(xiě) 8-位, 16-位, 和 32-位數(shù)據(jù)項(xiàng).
一些 64-位平臺(tái)也提供 readq 和 writeq, 為 PCI 總線上的 4-字(8-字節(jié))內(nèi)存操作. 這個(gè) 4-字 的命名是一個(gè)從所有的真實(shí)處理器有 16-位 字的時(shí)候的歷史遺留. 實(shí)際上, 用作 32-位 值的 L 命名也已變得不正確, 但是命名任何東西可能使事情更混淆.
一些硬件有一個(gè)有趣的特性: 一些版本使用 I/O 端口, 而其他的使用 I/O 內(nèi)存. 輸出給處理器的寄存器在任一種情況中相同, 但是存取方法是不同的. 作為一個(gè)使驅(qū)動(dòng)處理這類(lèi)硬件的生活容易些的方式, 并且作為一個(gè)使 I/O 端口和內(nèi)存存取的區(qū)別最小化的方法, 2.6 內(nèi)核提供了一個(gè)函數(shù), 稱(chēng)為 ioport_map:
void *ioport_map(unsigned long port, unsigned int count);
這個(gè)函數(shù)重映射 count I/O 端口和使它們出現(xiàn)為 I/O 內(nèi)存. 從這點(diǎn)以后, 驅(qū)動(dòng)可以在返回的地址上使用 ioread8 和其友并且根本忘記它在使用 I/O 端口.
這個(gè)映射應(yīng)當(dāng)在它不再被使用時(shí)恢復(fù):
void ioport_unmap(void *addr);
這些函數(shù)使 I/O 端口看來(lái)象內(nèi)存. 但是, 注意 I/O 端口必須仍然使用 request_region 在它們以這種方式被重映射前分配.
short 例子模塊, 在存取 I/O 端口前介紹的, 也能用來(lái)存取 I/O 內(nèi)存. 為此, 你必須告訴它使用 I/O 內(nèi)存在加載時(shí); 還有, 你需要改變基地址來(lái)使它指向你的 I/O 區(qū).
例如, 這是我們?nèi)绾问褂?short 來(lái)點(diǎn)亮調(diào)試 LED, 在一個(gè) MIPS 開(kāi)發(fā)板上:
mips.root# ./short_load use_mem=1 base=0xb7ffffc0
mips.root# echo -n 7 > /dev/short0
使用 short 給 I/O 內(nèi)存是與它用在 I/O 端口上同樣的.
下列片段顯示了 short 在寫(xiě)入一個(gè)內(nèi)存位置時(shí)用的循環(huán):
while (count--) {
iowrite8(*ptr++, address);
wmb();
}
注意, 這里使用一個(gè)寫(xiě)內(nèi)存屏障. 因?yàn)樵诤芏囿w系上 iowrites8 可能轉(zhuǎn)變?yōu)橐粋€(gè)直接賦值, 需要內(nèi)存屏障來(lái)保證以希望的順序來(lái)發(fā)生.
short 使用 inb 和 outb 來(lái)顯示它如何完成. 對(duì)于讀者它可能是一個(gè)直接的練習(xí), 但是, 改變 short 來(lái)使用 ioport_map 重映射 I/O 端口, 并且相當(dāng)?shù)睾?jiǎn)化剩下的代碼.
一個(gè)最著名的 I/O 內(nèi)存區(qū)是在個(gè)人計(jì)算機(jī)上的 ISA 范圍. 這是在 640 KB(0xA0000)和 1 MB(0x100000)之間的內(nèi)存范圍. 因此, 它正好出現(xiàn)于常規(guī)內(nèi)存 RAM 中間. 這個(gè)位置可能看起來(lái)有點(diǎn)奇怪; 它是一個(gè)在 1980 年代早期所作的決定的產(chǎn)物, 當(dāng)時(shí) 640 KB 內(nèi)存看來(lái)多于任何人可能用到的大小.
這個(gè)內(nèi)存方法屬于非直接映射的內(nèi)存類(lèi)別. [36]你可以讀/寫(xiě)幾個(gè)字節(jié)在這個(gè)內(nèi)存范圍, 如同前面解釋的使用 short 模塊, 就是, 通過(guò)在加載時(shí)設(shè)置 use_mem.
盡管 ISA I/O 內(nèi)存只在 x86-類(lèi) 計(jì)算機(jī)中存在, 我們認(rèn)為值得用幾句話和一個(gè)例子驅(qū)動(dòng).
我們不會(huì)談?wù)?PCI 在本章, 因?yàn)樗亲罡蓛舻囊活?lèi) I/O 內(nèi)存: 一旦你知道內(nèi)存地址, 你可簡(jiǎn)單地重映射和存取它. PCI I/O 內(nèi)存的"問(wèn)題"是它不能為本章提供一個(gè)能工作的例子, 因?yàn)槲覀儾荒苁孪戎滥愕?PCI 內(nèi)存映射到的物理地址, 或者是否它是安全的來(lái)存取任一這些范圍. 我們選擇來(lái)描述 ISA 內(nèi)存范圍, 因?yàn)樗坏俑蓛舨⑶腋m合運(yùn)行例子代碼.
為演示存取 ISA 內(nèi)存, 我們還使用另一個(gè) silly 小模塊( 例子源碼的一部分). 實(shí)際上, 這個(gè)稱(chēng)為 silly, 作為 Simple Tool for Unloading and Printing ISA Data 的縮寫(xiě), 或者如此的東東.
模塊補(bǔ)充了 short 的功能, 通過(guò)存取整個(gè) 384-KB 內(nèi)存空間和通過(guò)顯示所有的不同 I/O 功能. 它特有 4 個(gè)設(shè)備節(jié)點(diǎn)來(lái)進(jìn)行同樣的任務(wù), 使用不同的數(shù)據(jù)傳輸函數(shù). silly 設(shè)備作為一個(gè) I/O 內(nèi)存上的窗口, 以類(lèi)似 /dev/mem 的方式. 你可以讀和寫(xiě)數(shù)據(jù), 并且lseek 到一個(gè)任意 I/O 內(nèi)存地址.
因?yàn)?silly 提供了對(duì) ISA 內(nèi)存的存取, 它必須開(kāi)始于從映射物理 ISA 地址到內(nèi)核虛擬地址. 在 Linux 內(nèi)核的早期, 一個(gè)人可以簡(jiǎn)單地安排一個(gè)指針給一個(gè)感興趣的 ISA 地址, 接著直接對(duì)它解引用. 在現(xiàn)代世界, 但是, 我們必須首先使用虛擬內(nèi)存系統(tǒng)和重映射內(nèi)存范圍. 這個(gè)映射使用 ioremap 完成, 如同前面為 short 解釋的:
#define ISA_BASE 0xA0000
#define ISA_MAX 0x100000 /* for general memory access */
/* this line appears in silly_init */
io_base = ioremap(ISA_BASE, ISA_MAX - ISA_BASE);
ioremap 返回一個(gè)指針值, 它能被用來(lái)使用 ioread8 和其他函數(shù), 在"存取 I/O 內(nèi)存"一節(jié)中解釋.
讓我們回顧我們的例子模塊來(lái)看看這些函數(shù)如何被使用. /dev/sillyb, 特有次編號(hào) 0, 存取 I/O 內(nèi)存使用 ioread8 和 iowrite8. 下列代碼顯示了讀的實(shí)現(xiàn), 它使地址范圍 0xA0000-0xFFFF 作為一個(gè)虛擬文件在范圍 0-0x5FFF. 讀函數(shù)構(gòu)造為一個(gè) switch 語(yǔ)句在不同存取模式上; 這是 sillyb 例子:
case M_8:
while (count) {
*ptr = ioread8(add);
add++;
count--;
ptr++;
}
break;
實(shí)際上, 這不是完全正確. 內(nèi)存范圍是很小和很頻繁的使用, 以至于內(nèi)核在啟動(dòng)時(shí)建立頁(yè)表來(lái)存取這些地址. 但是, 這個(gè)用來(lái)存取它們的虛擬地址不是同一個(gè)物理地址, 并且因此無(wú)論如何需要 ioremap.
下 2 個(gè)設(shè)備是 /dev/sillyw (次編號(hào) 1) 和 /dev/silly1 (次編號(hào) 2). 它們表現(xiàn)象 /dev/sillyb, 除了它們使用 16-位 和 32-位 函數(shù). 這是 sillyl 的寫(xiě)實(shí)現(xiàn), 又一次部分 switch:
case M_32:
while (count >= 4) {
iowrite8(*(u32 *)ptr, add);
add += 4;
count -= 4;
ptr += 4;
}
break;
最后的設(shè)備是 /dev/sillycp (次編號(hào) 3), 它使用 memcpy_*io 函數(shù)來(lái)進(jìn)行同樣的任務(wù). 這是它的讀實(shí)現(xiàn)的核心:
case M_memcpy:
memcpy_fromio(ptr, add, count);
break;
因?yàn)?ioremap 用來(lái)提供對(duì) ISA 內(nèi)存區(qū)的存取, silly 必須調(diào)用 iounmap 當(dāng)模塊卸載時(shí):
iounmap(io_base);
看一下內(nèi)核源碼會(huì)展現(xiàn)另一套函數(shù), 有如 isareadb 的名子. 實(shí)際上, 每個(gè)剛才描述的函數(shù)都有一個(gè) isa 對(duì)等體. 這些函數(shù)提供對(duì) ISA 內(nèi)存的存取不需要一個(gè)單獨(dú)的 ioremap 步驟. 但是, 來(lái)自?xún)?nèi)核開(kāi)發(fā)者的話, 是這些函數(shù)打算用來(lái)作為暫時(shí)的驅(qū)動(dòng)移植輔助, 并且它可能將來(lái)消失. 因此, 你應(yīng)當(dāng)避免使用它們.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: