Linux是一套免費(fèi)使用和自由傳播的類Unix操作系統(tǒng),是一個(gè)基于POSIX和UNIX的多用戶、多任務(wù)、支持多線程和多CPU的操作系統(tǒng)。它能運(yùn)行主要的UNIX工具軟件、應(yīng)用程序和網(wǎng)絡(luò)協(xié)議。它支持32位和64位硬件。Linux繼承了Unix以網(wǎng)絡(luò)為核心的設(shè)計(jì)思想,是一個(gè)性能穩(wěn)定的多用戶網(wǎng)絡(luò)操作系統(tǒng)。
Linux操作系統(tǒng)誕生于1991 年10 月5 日(這是第一次正式向外公布時(shí)間)。Linux存在著許多不同的Linux版本,但它們都使用了Linux內(nèi)核。Linux可安裝在各種計(jì)算機(jī)硬件設(shè)備中,比如手機(jī)、平板電腦、路由器、視頻游戲控制臺(tái)、臺(tái)式計(jì)算機(jī)、大型機(jī)和超級計(jì)算機(jī)。
嚴(yán)格來講,Linux這個(gè)詞本身只表示Linux內(nèi)核,但實(shí)際上人們已經(jīng)習(xí)慣了用Linux來形容整個(gè)基于Linux內(nèi)核,并且使用GNU 工程各種工具和數(shù)據(jù)庫的操作系統(tǒng)。
I/O端口
CPU與外部設(shè)備、存儲(chǔ)器的連接和數(shù)據(jù)交換都需要通過接口設(shè)備來實(shí)現(xiàn),前者被稱為I/O接口,而后者則被稱為存儲(chǔ)器接口。存儲(chǔ)器通常在CPU的同步控制下工作,接口電路比較簡單;而I/O設(shè)備品種繁多,其相應(yīng)的接口電路也各不相同,因此,習(xí)慣上說到接口只是指I/O接口。
I/O內(nèi)存
CPU沒有專門用于設(shè)備I/O的指令。這就是所謂的“I/O內(nèi)存”方式。另一類CPU(典型地如X86)將外設(shè)的寄存器看成一個(gè)獨(dú)立的地址空間,所以訪問內(nèi)存的指令不能用來訪問這些寄存器,而要為對外設(shè)寄存器的讀/寫設(shè)置專用指令,如IN和OUT指令。
Linux設(shè)備驅(qū)動(dòng)之I/O端口與I/O內(nèi)存
從CPU連出來一把線:數(shù)據(jù)總線、地址總線、控制總線,這把線上掛著N個(gè)接口,有相同的,有不同的,名字叫做存儲(chǔ)器接口、中斷控制接口、DMA接口、并行接口、串行接口、AD接口……一個(gè)設(shè)備要想接入,就用自己的接口和總線上的某個(gè)匹配接口對接……于是總線上出現(xiàn)了各種設(shè)備:內(nèi)存、硬盤,鼠標(biāo)、鍵盤,顯示器……
對于CPU而言,如果它要發(fā)數(shù)據(jù)到某個(gè)設(shè)備,其實(shí)是發(fā)到對應(yīng)的接口,接口電路里有多個(gè)寄存器(也稱為端口),訪問設(shè)備實(shí)際上是訪問相關(guān)的端口,所有的信息會(huì)由接口轉(zhuǎn)給它的設(shè)備。那么CPU會(huì)準(zhǔn)備數(shù)據(jù)到數(shù)據(jù)總線,但是諸多接口,該發(fā)給誰呢?這時(shí)就須要為各接口分配一個(gè)地址,然后把地址放在地址總線上,需要的控制信息放到控制總線上,就可以和設(shè)備通信了。
對一個(gè)系統(tǒng)而言,通常會(huì)有多個(gè)外設(shè),每個(gè)外設(shè)的接口電路中,又會(huì)有多個(gè)端口,每個(gè)端口都需要一個(gè)地址,為他們標(biāo)識(shí)一個(gè)具體的地址值,是系統(tǒng)必須解決的事,與此同時(shí),你還有個(gè)內(nèi)存條,可能是512M或1G或更大的金士頓、現(xiàn)代DDR2之類,他們的每一個(gè)地址也都需要分配一個(gè)標(biāo)識(shí)值,另外,很多外設(shè)有自己的內(nèi)存、緩沖區(qū),就像你的內(nèi)存條一樣,你同樣需要為它們分配內(nèi)存……你的CPU可能需要和它們的每一個(gè)字節(jié)都打交道,所以:別指望偷懶,它們的每一寸土地都要規(guī)劃好!這聽起來就很煩,做起來可能就直接導(dǎo)致腦細(xì)胞全部陣亡。但事情總是得有人去做,ARM可能會(huì)這樣做:他這次設(shè)計(jì)的CPU是32位的,最多也就能尋址2^32=4G空間,于是把這4GB空間丟給內(nèi)存和端口,讓他們瓜分。但英特爾或許有更好的分配方式……
1、地址的概念
1)物理地址:CPU地址總線傳來的地址,由硬件電路控制其具體含義。物理地址中很大一部分是留給內(nèi)存條中的內(nèi)存的,但也常被映射到其他存儲(chǔ)器上(如顯存、BIOS等)。在程序指令中的虛擬地址經(jīng)過段映射和頁面映射后,就生成了物理地址,這個(gè)物理地址被放到CPU的地址線上。
物理地址空間,一部分給物理RAM(內(nèi)存)用,一部分給總線用,這是由硬件設(shè)計(jì)來決定的,因此在32 bits地址線的x86處理器中,物理地址空間是2的32次方,即4GB,但物理RAM一般不能上到4GB,因?yàn)檫€有一部分要給總線用(總線上還掛著別的許多設(shè)備)。在PC機(jī)中,一般是把低端物理地址給RAM用,高端物理地址給總線用。
2)總線地址:總線的地址線或在地址周期上產(chǎn)生的信號(hào)。外設(shè)使用的是總線地址,CPU使用的是物理地址。
物理地址與總線地址之間的關(guān)系由系統(tǒng)的設(shè)計(jì)決定的。在x86平臺(tái)上,物理地址就是總線地址,這是因?yàn)樗鼈児蚕硐嗤牡刂房臻g——這句話有點(diǎn)難理解,詳見下面的“獨(dú)立編址”。在其他平臺(tái)上,可能需要轉(zhuǎn)換/映射。比如:CPU需要訪問物理地址是0xfa000的單元,那么在x86平臺(tái)上,會(huì)產(chǎn)生一個(gè)PCI總線上對0xfa000地址的訪問。因?yàn)槲锢淼刂泛涂偩€地址相同,所以憑眼睛看是不能確定這個(gè)地址是用在哪兒的,它或者在內(nèi)存中,或者是某個(gè)卡上的存儲(chǔ)單元,甚至可能這個(gè)地址上沒有對應(yīng)的存儲(chǔ)器。
3)虛擬地址:現(xiàn)代操作系統(tǒng)普遍采用虛擬內(nèi)存管理(Virtual Memory Management)機(jī)制,這需要MMU(Memory Management Unit)的支持。MMU通常是CPU的一部分,如果處理器沒有MMU,或者有MMU但沒有啟用,CPU執(zhí)行單元發(fā)出的內(nèi)存地址將直接傳到芯片引腳上,被內(nèi)存芯片(物理內(nèi)存)接收,這稱為物理地址(Physical Address),如果處理器啟用了MMU,CPU執(zhí)行單元發(fā)出的內(nèi)存地址將被MMU截獲,從CPU到MMU的地址稱為虛擬地址(Virtual Address),而MMU將這個(gè)地址翻譯成另一個(gè)地址發(fā)到CPU芯片的外部地址引腳上,也就是將虛擬地址映射成物理地址。
Linux中,進(jìn)程的4GB(虛擬)內(nèi)存分為用戶空間、內(nèi)核空間。用戶空間分布為0~3GB(即PAGE_OFFSET,在0X86中它等于0xC0000000),剩下的1G為內(nèi)核空間。程序員只能使用虛擬地址。系統(tǒng)中每個(gè)進(jìn)程有各自的私有用戶空間(0~3G),這個(gè)空間對系統(tǒng)中的其他進(jìn)程是不可見的。
CPU發(fā)出取指令請求時(shí)的地址是當(dāng)前上下文的虛擬地址,MMU再從頁表中找到這個(gè)虛擬地址的物理地址,完成取指。同樣讀取數(shù)據(jù)的也是虛擬地址,比如mov ax, var. 編譯時(shí)var就是一個(gè)虛擬地址,也是通過MMU從也表中來找到物理地址,再產(chǎn)生總線時(shí)序,完成取數(shù)據(jù)的。
2、編址方式
1)外設(shè)都是通過讀寫設(shè)備上的寄存器來進(jìn)行的,外設(shè)寄存器也稱為“I/O端口”,而IO端口有兩種編址方式:獨(dú)立編址和統(tǒng)一編制。
統(tǒng)一編址:外設(shè)接口中的IO寄存器(即IO端口)與主存單元一樣看待,每個(gè)端口占用一個(gè)存儲(chǔ)單元的地址,將主存的一部分劃出來用作IO地址空間,如,在PDP-11中,把最高的4K主存作為IO設(shè)備寄存器地址。端口占用了存儲(chǔ)器的地址空間,使存儲(chǔ)量容量減小。
統(tǒng)一編址也稱為“I/O內(nèi)存”方式,外設(shè)寄存器位于“內(nèi)存空間”(很多外設(shè)有自己的內(nèi)存、緩沖區(qū),外設(shè)的寄存器和內(nèi)存統(tǒng)稱“I/O空間”)。
如,Samsung的S3C2440,是32位ARM處理器,它的4GB地址空間被外設(shè)、RAM等瓜分:
0x8000 1000 LED 8*8點(diǎn)陣的地址
0x4800 0000 ~ 0x6000 0000 SFR(特殊暫存器)地址空間
0x3800 1002 鍵盤地址
0x3000 0000 ~ 0x3400 0000 SDRAM空間
0x2000 0020 ~ 0x2000 002e IDE
0x1900 0300 CS8900
獨(dú)立編址(單獨(dú)編址):IO地址與存儲(chǔ)地址分開獨(dú)立編址,I/O端口地址不占用存儲(chǔ)空間的地址范圍,這樣,在系統(tǒng)中就存在了另一種與存儲(chǔ)地址無關(guān)的IO地址,CPU也必須具有專用與輸入輸出操作的IO指令(IN、OUT等)和控制邏輯。獨(dú)立編址下,地址總線上過來一個(gè)地址,設(shè)備不知道是給IO端口的、還是給存儲(chǔ)器的,于是處理器通過MEMR/MEMW和IOR/IOW兩組控制信號(hào)來實(shí)現(xiàn)對I/O端口和存儲(chǔ)器的不同尋址。如,intel 80x86就采用單獨(dú)編址,CPU內(nèi)存和I/O是一起編址的,就是說內(nèi)存一部分的地址和I/O地址是重疊的。
獨(dú)立編址也稱為“I/O端口”方式,外設(shè)寄存器位于“I/O(地址)空間”。
對于x86架構(gòu)來說,通過IN/OUT指令訪問。PC架構(gòu)一共有65536個(gè)8bit的I/O端口,組成64K個(gè)I/O地址空間,編號(hào)從0~0xFFFF,有16位,80x86用低16位地址線A0-A15來尋址。連續(xù)兩個(gè)8bit的端口可以組成一個(gè)16bit的端口,連續(xù)4個(gè)組成一個(gè)32bit的端口。I/O地址空間和CPU的物理地址空間是兩個(gè)不同的概念,例如I/O地址空間為64K,一個(gè)32bit的CPU物理地址空間是4G。如,在Intel 8086+Redhat9.0 下用“more /proc/ioports”可看到:
0000-001f : dma1
0020-003f : pic1
0040-005f : timer
0060-006f : keyboard
0070-007f : rtc
0080-008f : dma page reg
00a0-00bf : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
……
不過Intel x86平臺(tái)普通使用了名為內(nèi)存映射(MMIO)的技術(shù),該技術(shù)是PCI規(guī)范的一部分,IO設(shè)備端口被映射到內(nèi)存空間,映射后,CPU訪問IO端口就如同訪問內(nèi)存一樣。看Intel TA 719文檔給出的x86/x64系統(tǒng)典型內(nèi)存地址分配表:
系統(tǒng)資源 占用
------------------------------------------------------------------------
BIOS 1M
本地APIC 4K
芯片組保留 2M
IO APIC 4K
PCI設(shè)備 256M
PCI Express設(shè)備 256M
PCI設(shè)備(可選) 256M
顯示幀緩存 16M
TSEG 1M
對于某一既定的系統(tǒng),它要么是獨(dú)立編址、要么是統(tǒng)一編址,具體采用哪一種則取決于CPU的體系結(jié)構(gòu)。 如,PowerPC、m68k等采用統(tǒng)一編址,而X86等則采用獨(dú)立編址,存在IO空間的概念。目前,大多數(shù)嵌入式微控制器如ARM、PowerPC等并不提供I/O空間,僅有內(nèi)存空間,可直接用地址、指針訪問。但對于Linux內(nèi)核而言,它可能用于不同的CPU,所以它必須都要考慮這兩種方式,于是它采用一種新的方法,將基于I/O映射方式的或內(nèi)存映射方式的I/O端口通稱為“I/O區(qū)域”(I/O region),不論你采用哪種方式,都要先申請IO區(qū)域:request_resource(),結(jié)束時(shí)釋放它:release_resource()。
linux I/O端口與I/O內(nèi)存
IO端口:當(dāng)一個(gè)寄存器或者內(nèi)存位于IO空間時(shí);
IO內(nèi)存:當(dāng)一個(gè)內(nèi)存或者寄存器位于內(nèi)存空間時(shí);
在一些CPU制造商在其芯片上實(shí)現(xiàn)了一個(gè)單地址空間(統(tǒng)一編址)的同時(shí),其它的CPU制造商認(rèn)為外設(shè)不同于內(nèi)存,應(yīng)該有一個(gè)獨(dú)立的地址空間給外設(shè)(單獨(dú)編址),其生產(chǎn)處理器(特別是x86家族)的I/O端口有自己的讀寫信號(hào)線和特殊的CPU指令來存取端口。因?yàn)橥庠O(shè)要與外設(shè)總線相匹配,并且大部分流行的I/O總線都是以個(gè)人計(jì)算機(jī)(主要是x86家族)作為模型,所以即便那些沒有單獨(dú)地址空間給I/O端口的處理器,也必須在訪問外設(shè)時(shí)模擬成讀寫端口。這通常通過外部芯片組(PC中的南北橋)或者在CPU核中附加額外電路來實(shí)現(xiàn)(基于嵌入式應(yīng)用的處理器)。
由于同樣的理由,Linux在所有計(jì)算機(jī)平臺(tái)上都實(shí)現(xiàn)了I/O端口,甚至在那些單地址空間的CPU平臺(tái)上(模擬I/O端口)。但并不是所有的設(shè)備都會(huì)將其寄存器映射到I/O端口。雖然ISA設(shè)備普遍使用I/O端口,但大部分PCI設(shè)備將寄存器映射到某個(gè)內(nèi)存地址區(qū)。這種I/O內(nèi)存方法通常是首選的,因?yàn)樗鼰o需使用特殊的處理器指令,CPU存取內(nèi)存也更有效率,并且編譯器在存取內(nèi)存時(shí)在寄存器分配和尋址模式的選擇上有更多自由。
1.IO寄存器和常規(guī)內(nèi)存
I/O寄存器和RAM的主要不同是I/O操作有邊際效應(yīng)(side effect),而內(nèi)存操作沒有:訪問內(nèi)存只是在內(nèi)存某一位置存儲(chǔ)數(shù)值。因?yàn)閮?nèi)存存取速度嚴(yán)重影響CPU的性能,編譯器可能會(huì)對源碼進(jìn)行優(yōu)化,主要是:使用高速緩存和重排讀/寫指令的順序。對于傳統(tǒng)內(nèi)存(至少在單處理器系統(tǒng))這些優(yōu)化是透明有益的,但是對于I/O 寄存器,這可能是致命錯(cuò)誤,因?yàn)樗鼈兏蓴_了那些“邊際效應(yīng)”(驅(qū)動(dòng)程序存取I/O 寄存器就是為了獲取邊際效應(yīng))。因此,驅(qū)動(dòng)程序必須確保在存取寄存器時(shí),不能使用高速緩存并且不能重新編排讀寫指令的順序。
side effect 是指:訪問I/O寄存器時(shí),不僅僅會(huì)像訪問普通內(nèi)存一樣影響存儲(chǔ)單元的值,更重要的是它可能改變CPU的I/O端口電平、輸出時(shí)序或CPU對I/O端口電平的反應(yīng)等等,從而實(shí)現(xiàn)CPU的控制功能。CPU在電路中的意義就是實(shí)現(xiàn)其side effect 。舉個(gè)例子,有些設(shè)備的中斷狀態(tài)寄存器只要一讀取,便自動(dòng)清零。
硬件緩沖的問題是最易解決的:只要將底層硬件配置(或者自動(dòng)地或者通過Linux 初始化代碼)為當(dāng)存取I/O區(qū)時(shí),禁止任何硬件緩沖(不管是I/O 內(nèi)存還是I/O 端口)。
編譯器優(yōu)化和硬件重編排讀寫指令順序的解決方法是:在硬件或處理器必須以一個(gè)特定順序執(zhí)行的操作之間安放一個(gè)內(nèi)存屏障(memory barrier)。
2.操作IO端口(申請,訪問,釋放):
I/O 端口是驅(qū)動(dòng)用來和很多設(shè)備通訊的方法。
?。?)申請I/O 端口:
在驅(qū)動(dòng)還沒獨(dú)占設(shè)備之前,不應(yīng)對端口進(jìn)行操作。內(nèi)核提供了一個(gè)注冊接口,以允許驅(qū)動(dòng)聲明其需要的端口:
/* request_region告訴內(nèi)核:要使用first開始的n個(gè)端口。參數(shù)name為設(shè)備名。如果分配成功返回值是非NULL;否則無法使用需要的端口(/proc/ioports包含了系統(tǒng)當(dāng)前所有端口的分配信息,若request_region分配失敗時(shí),可以查看該文件,看誰先用了你要的端口) */
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
?。?)訪問IO端口:
在驅(qū)動(dòng)成功請求到I/O 端口后,就可以讀寫這些端口了。大部分硬件會(huì)將8位、16位和32位端口區(qū)分開,無法像訪問內(nèi)存那樣混淆使用。驅(qū)動(dòng)程序必須調(diào)用不同的函數(shù)來訪問不同大小的端口。
Linux 內(nèi)核頭文件(體系依賴的頭文件《asm/io.h》) 定義了下列內(nèi)聯(lián)函數(shù)來存取I/O端口:
/* inb/outb:讀/寫字節(jié)端口(8位寬)。有些體系將port參數(shù)定義為unsigned long;而有些平臺(tái)則將它定義為unsigned short。inb的返回類型也是依賴體系的 */
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
/* inw/outw:讀/寫字端口(16位寬) */
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
/* inl/outl:讀/寫32位端口。longword也是依賴體系的,有的體系為unsigned long;而有的為unsigned int */
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
(3)釋放IO端口:
/* 用完I/O端口后(可能在模塊卸載時(shí)),應(yīng)當(dāng)調(diào)用release_region將I/O端口返還給系統(tǒng)。參數(shù)start和n應(yīng)與之前傳遞給request_region一致 */
void release_region(unsigned long start, unsigned long n);
3.操作IO內(nèi)存(申請,映射,訪問,釋放):
盡管 I/O 端口在x86世界中非常流行,但是用來和設(shè)備通訊的主要機(jī)制是通過內(nèi)存映射的寄存器和設(shè)備內(nèi)存,兩者都稱為I/O 內(nèi)存,因?yàn)榧拇嫫骱蛢?nèi)存之間的區(qū)別對軟件是透明的。
I/O 內(nèi)存僅僅是一個(gè)類似于RAM 的區(qū)域,處理器通過總線訪問該區(qū)域,以實(shí)現(xiàn)對設(shè)備的訪問。同樣,讀寫這個(gè)區(qū)域是有邊際效應(yīng)。
根據(jù)計(jì)算機(jī)體系和總線不同,I/O 內(nèi)存可分為可以或者不可以通過頁表來存取。若通過頁表存取,內(nèi)核必須先重新編排物理地址,使其對驅(qū)動(dòng)程序可見,這就意味著在進(jìn)行任何I/O操作之前,你必須調(diào)用ioremap;如果不需要頁表,I/O內(nèi)存區(qū)域就類似于I/O端口,你可以直接使用適當(dāng)?shù)腎/O函數(shù)讀寫它們。
由于邊際效應(yīng)的緣故,不管是否需要 ioremap,都不鼓勵(lì)直接使用I/O內(nèi)存指針,而應(yīng)使用專門的I/O內(nèi)存操作函數(shù)。這些I/O內(nèi)存操作函數(shù)不僅在所有平臺(tái)上是安全,而且對直接使用指針操作 I/O 內(nèi)存的情況進(jìn)行了優(yōu)化。
?。?)申請I/O 內(nèi)存:
I/O 內(nèi)存區(qū)在使用前必須先分配。分配內(nèi)存區(qū)的函數(shù)接口在《linux/ioport.h》定義中:
/* request_mem_region分配一個(gè)開始于start,len字節(jié)的I/O內(nèi)存區(qū)。分配成功,返回一個(gè)非NULL指針;否則返回NULL。系統(tǒng)當(dāng)前所有I/O內(nèi)存分配信息都在/proc/iomem文件中列出,你分配失敗時(shí),可以看看該文件,看誰先占用了該內(nèi)存區(qū) */
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
(2)映射:
在訪問I/O內(nèi)存之前,分配I/O內(nèi)存并不是唯一要求的步驟,你還必須保證內(nèi)核可存取該I/O內(nèi)存。訪問I/O內(nèi)存并不只是簡單解引用指針,在許多體系中,I/O 內(nèi)存無法以這種方式直接存取。因此,還必須通過ioremap 函數(shù)設(shè)置一個(gè)映射。
/* ioremap用于將I/O內(nèi)存區(qū)映射到虛擬地址。參數(shù)phys_addr為要映射的I/O內(nèi)存起始地址,參數(shù)size為要映射的I/O內(nèi)存的大小,返回值為被映射到的虛擬地址 */
void *ioremap(unsigned long phys_addr, unsigned long size);
?。?)訪問IO內(nèi)存:
經(jīng)過 ioremap之后,設(shè)備驅(qū)動(dòng)就可以存取任何I/O內(nèi)存地址。注意,ioremap返回的地址不可以直接解引用;相反,應(yīng)當(dāng)使用內(nèi)核提供的訪問函數(shù)。訪問I/O內(nèi)存的正確方式是通過一系列專門用于實(shí)現(xiàn)此目的的函數(shù):
#include 《asm/io.h》
/* I/O內(nèi)存讀函數(shù)。參數(shù)addr應(yīng)當(dāng)是從ioremap獲得的地址(可能包含一個(gè)整型偏移); 返回值是從給定I/O內(nèi)存讀取到的值 */
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
/* I/O內(nèi)存寫函數(shù)。參數(shù)addr同I/O內(nèi)存讀函數(shù),參數(shù)value為要寫的值 */
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
/* 以下這些函數(shù)讀和寫一系列值到一個(gè)給定的 I/O 內(nèi)存地址,從給定的buf讀或?qū)慶ount個(gè)值到給定的addr。參數(shù)count表示要讀寫的數(shù)據(jù)個(gè)數(shù),而不是字節(jié)大小 */
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,,onst void *buf,,nsigned long count);
/* 需要操作一塊I/O 地址時(shí),使用下列函數(shù)(這些函數(shù)的行為類似于它們的C庫類似函數(shù)): */
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);
/* 舊的I/O內(nèi)存讀寫函數(shù),不推薦使用 */
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
?。?)釋放IO內(nèi)存步驟:
void iounmap(void * addr); /* iounmap用于釋放不再需要的映射 */
void release_mem_region(unsigned long start, unsigned long len); /* iounmap用于釋放不再需要的映射 */
4、像IO內(nèi)存一樣使用端口
一些硬件有一個(gè)有趣的特性: 有些版本使用 I/O 端口;而有些版本則使用 I/O 內(nèi)存。不管是I/O 端口還是I/O 內(nèi)存,處理器見到的設(shè)備寄存器都是相同的,只是訪問方法不同。為了統(tǒng)一編程接口,使驅(qū)動(dòng)程序易于編寫,2.6 內(nèi)核提供了一個(gè)ioport_map函數(shù):
/* ioport_map重新映射count個(gè)I/O端口,使它們看起來I/O內(nèi)存。此后,驅(qū)動(dòng)程序可以在ioport_map返回的地址上使用ioread8和同類函數(shù)。這樣,就可以在編程時(shí),消除了I/O 端口和I/O 內(nèi)存的區(qū)別 */
void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);/* ioport_unmap用于釋放不再需要的映射 */
注意,I/O 端口在重新映射前必須使用request_region分配分配所需的I/O 端口。
5、ARM體系的IO操作接口
s3c24x0處理器使用的是I/O內(nèi)存,也就是說:s3c24x0處理器使用統(tǒng)一編址方式,I/O寄存器和內(nèi)存使用的是單一地址空間,并且讀寫I/O寄存器和讀寫內(nèi)存的指令是相同的。所以推薦使用I/O內(nèi)存的相關(guān)指令和函數(shù)。但這并不表示I/O端口的指令在s3c24x0中不可用。如果你注意過s3c24x0關(guān)于I/O方面的內(nèi)核源碼,你就會(huì)發(fā)現(xiàn):其實(shí)I/O端口的指令只是一個(gè)外殼,內(nèi)部還是使用和I/O內(nèi)存一樣的代碼。注意以下幾點(diǎn):
1)所有的讀寫指令(I/O操作函數(shù))所賦的地址必須都是虛擬地址,你有兩種選擇:使用內(nèi)核已經(jīng)定義好的地址,如在include/asm-arm/arch-s3c2410/regs-xxx.h中定義了s3c2410處理器各外設(shè)寄存器地址(其他處理器芯片也可在類似路徑找到內(nèi)核定義好的外設(shè)寄存器的虛擬地址;另一種方法就是使用自己用ioremap映射的虛擬地址。絕對不能使用實(shí)際的物理地址,否則會(huì)因?yàn)閮?nèi)核無法處理地址而出現(xiàn)oops。
2)在使用I/O指令時(shí),可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因?yàn)閞equest的功能只是告訴內(nèi)核端口被誰占用了,如再次request,內(nèi)核會(huì)制止(資源busy)。但是不推薦這么做,這樣的代碼也不規(guī)范,可能會(huì)引起并發(fā)問題(很多時(shí)候我們都需要獨(dú)占設(shè)備)。
3)在使用I/O指令時(shí),所賦的地址數(shù)據(jù)有時(shí)必須通過強(qiáng)制類型轉(zhuǎn)換為 unsigned long ,不然會(huì)有警告。
4)在includeasm-armarch-s3c2410hardware.h中定義了很多io口的操作函數(shù),有需要可以在驅(qū)動(dòng)中直接使用,很方便。
評論
查看更多