linux核心函式 ioremap()的原理及意義
1,ioremap
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
引數:
phys_addr:要對映的起始的IO地址;
size:要對映的空間的大小;
flags:要對映的IO空間的和許可權有關的標誌;
功能: 將一個IO地址空間對映到核心的虛擬地址空間上去,便於訪問;
實現:對要對映的IO地址空間進行判斷,低PCI/ISA地址不需要重新對映,也不允許使用者將IO地址空間對映到正在使用的RAM中,最後申請一 個vm_area_struct結構,呼叫remap_area_pages填寫頁表,若填寫過程不成功則釋放申請的vm_area_struct空
間;
意義:
比如isa裝置和pci裝置,或者是fb,硬體的跳線或者是物理連線方式決定了硬體上的記憶體影射到的cpu實體地址。
在核心訪問這些地址必須分配給這段記憶體以虛擬地址
為了使軟體訪問I/O記憶體,必須為裝置分配虛擬地址.這就是ioremap的工作.這個函式專門用來為I/O記憶體區域分配虛擬地址(空間).對於直接對映的I/O地址ioremap不做任何事情(uClinux中是這麼實現的??)
有了ioremap(和iounmap),裝置就可以訪問任何I/O記憶體空間,不論它是否直接對映到虛擬地址空間.但是,這些地址永遠不能直接使用(指實體地址),而要用readb這種函式.
根據計算機平臺和所使用匯流排的不同,I/O 記憶體可能是,也可能不是通過頁表訪問的,通過頁表訪問的是統一編址(PowerPC),否則是獨立編址(Intel)。如果訪問是經由頁表進行的,核心必須首先安排實體地址使其對裝置驅動 程式可見(這通常意味著在進行任何 I/O 之前必須先呼叫ioremap)。如果訪問無需頁表,那麼 I/O 記憶體區域就很象 I/O 埠,可以使 用適當形式的函式讀寫它們。
不管訪問
在X86體系下的,CPU的實體地址和PCI匯流排地址共用一個空間。linux核心將3G-4G的虛擬地址固定對映到了實體地址的0-1G的地方。但是如果外圍裝置上的地址高於1G,例如某塊PCI卡分配到了一個高於1G的地址,就需要呼叫ioremap來重新建立該實體地址(匯流排地址)和虛擬地址之間的對映。這個對映過程是這樣的:在ioremap.c檔案的__ioremap函式中首先對將來對映的實體地址進行檢查,也就是不能重新對映640K-1M地址(由於歷史的原因,實體地址640k到1M空間被保留給了顯示卡),普通的 ram地也不能重新被對映。之後呼叫get_vm_area獲得可用的虛擬地址,然後根這虛擬地址和欲對映的實體地址修改頁表,之後核心就可以用這個虛擬地址來訪問對映的實體地址了。
2,ioremap
offset: 物理空間(I/O裝置上的一塊實體記憶體)的起始地址size: 物理空間的大小
給一段實體地址(起始地址offset)建立頁表(地址對映)
--------------------------------------------------
static inline void __iomem * ioremap(unsigned long offset, unsigned long size)
{
return __ioremap(offset, size, 0);
}
Remap an arbitrary physical address space into the kernel virtual address space. Needed when the kernel wants to access high addresses directly.
--------------------------------------------------
void __iomem * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags){
void __iomem * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr)
return NULL;
if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS)
return (void __iomem *) phys_to_virt(phys_addr);
如果需要對映的空間的起始地址phys_addr為於ZONE_NORMAL區,通過virt_to_page()進行對映
|------------------------------------------------------------------------------|
| if (phys_addr <= virt_to_phys(high_memory - 1)) |
| { |
| char *t_addr, *t_end; |
| struct page *page; |
| t_addr = __va(phys_addr); |
| t_end = t_addr + (size - 1); |
| for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++) -|
| if(!PageReserved(page)) |
| return NULL; |
| } |
|------------------------------------------------------------------------------|
offset = phys_addr & ~PAGE_MASK;
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(last_addr+1) - phys_addr;
get_vm_area()建立型別為vm_struct的新描述符
get_vm_area()首先呼叫kmalloc()為新描述符獲得一個儲存區;然後掃描型別為struct vm_struct的描述符表,查詢一個可用線性地址空間(至少包含size+4096個地址)。
|----------------------------------------------------------|
| area = get_vm_area(size, VM_IOREMAP | (flags << 20)); -|
| if (!area) |
| return NULL; |
|----------------------------------------------------------|
3,參考檔案
1,http://wenku.baidu.com/link?url=uM3vDOg6SgUNegieKZ8yFHQ0cO8CsUs9TcSJuBxx4dHoDoP3XYpWkEXrCzXd2mRgOIFQHV-ZZZbnk8dPgoE4qcZzjLZYeXXOImI1B8VsT4C