1、为什么需要ioremap
问题:分配mmio(request_mem_region)后,得到的是物理地址,按理只需要将其转换为虚拟地址后(内核中,低端内存只是一个偏移而已),
应该就可以直接访问了吧?但是为什么还需要使用ioremap将其转换为线性地址后才能访问呢?
解答:分配的mmio的物理地址在内核中不一定能直接访问,比如:
1)x86 32位环境中,mmio的地址范围就在3G-4G之间,位于高端内存,内核中不能直接访问,需要进行映射。
2)一些体系架构中,IO内存根本就不能直接访问,必须建立相应的映射后才行,相应的映射工作由架构相关的ioremap完成
2、基本原理
ioremap完成相应的物理地址到内核空间中的线性地址(虚拟地址)的映射,并建立相应页表。
ioremap实际利用了vmalloc区中虚拟地址空间,因为内核虚拟地址空间实际已经分配完了:
3G--3G+896M为线性映射区,896-1024M为vmalloc、kmap和固定映射区
ioremap只能利用现有的地址空间,而能用的估计只有vmalloc区了,相对比较大。
ioremap实现的大致流程为:
__ioremap_caller
--> get_vm_area_caller // 在vmalloc区域中获取空闲的子区域。
--> ioremap_page_range //修改页表,实现物理地址到虚拟地址的映射
3、代码分析
-
/*
-
* ioremap主处理函数,用于映射IO内存至内核虚拟
-
* 地址空间中,入参中包含了IO内存所在的物理
-
* 地址和长度,这个通常通过request_mem_region获得
-
* 返回值为映射后内核虚拟地址空间中的虚拟地址。
-
* 该返回地址实际位于vmalloc区中,但不实际分配
-
* 物理内存(vmalloc是需要分配物理内存的),使用
-
* vm_struct结构中phys_addr和addr实现虚拟地址到物理地址
-
* 间的映射。
-
*/
-
static void __iomem *__ioremap_caller(resource_size_t phys_addr,
-
unsigned long size, unsigned long prot_val, void *caller)
-
{
-
unsigned long offset, vaddr;
-
resource_size_t pfn, last_pfn, last_addr;
-
const resource_size_t unaligned_phys_addr = phys_addr;
-
const unsigned long unaligned_size = size;
-
struct vm_struct *area;
-
unsigned long new_prot_val;
-
pgprot_t prot;
-
int retval;
-
void __iomem *ret_addr;
-
-
/* Don't allow wraparound or zero size */
-
// 防止地址过大发生翻转
-
last_addr = phys_addr + size - 1;
-
if (!size || last_addr < phys_addr)
-
return NULL;
-
-
// 判断物理地址是否有效
-
if (!phys_addr_valid(phys_addr)) {
-
printk(KERN_WARNING "ioremap: invalid physical address %llx\n",
-
(unsigned long long)phys_addr);
-
WARN_ON_ONCE(1);
-
return NULL;
-
}
-
-
/*
-
* Don't remap the low PCI/ISA area, it's always mapped..
-
*/
-
/*
-
* 对于PCI/ISA区域(640k-16M),属于线性映射的区域
-
* 不需要再进行映射了。
-
*/
-
if (is_ISA_range(phys_addr, last_addr))
-
return (__force void __iomem *)phys_to_virt(phys_addr);
-
-
/*
-
* Don't allow anybody to remap normal RAM that we're using..
-
*/
-
/*
-
* 对于常规物理内存所在的区域,不能映射
-
* IO内存和常规物理内存在物理地址空间中的
-
* 分布在BIOS和内核初始化时就已经决定好了
-
*/
-
last_pfn = last_addr >> PAGE_SHIFT;
-
for (pfn = phys_addr >> PAGE_SHIFT; pfn <= last_pfn; pfn++) {
-
int is_ram = page_is_ram(pfn);
-
-
if (is_ram && pfn_valid(pfn) && !PageReserved(pfn_to_page(pfn)))
-
return NULL;
-
WARN_ON_ONCE(is_ram);
-
}
-
-
/*
-
* Mappings have to be page-aligned
-
*/
-
//页对齐处理
-
offset = phys_addr & ~PAGE_MASK;
-
phys_addr &= PHYSICAL_PAGE_MASK;
-
size = PAGE_ALIGN(last_addr+1) - phys_addr;
-
-
retval = reserve_memtype(phys_addr, (u64)phys_addr + size,
-
prot_val, &new_prot_val);
-
if (retval) {
-
printk(KERN_ERR "ioremap reserve_memtype failed %d\n", retval);
-
return NULL;
-
}
-
-
if (prot_val != new_prot_val) {
-
if (!is_new_memtype_allowed(phys_addr, size,
-
prot_val, new_prot_val)) {
-
printk(KERN_ERR
-
"ioremap error for 0x%llx-0x%llx, requested 0x%lx, got 0x%lx\n",
-
(unsigned long long)phys_addr,
-
(unsigned long long)(phys_addr + size),
-
prot_val, new_prot_val);
-
goto err_free_memtype;
-
}
-
prot_val = new_prot_val;
-
}
-
-
switch (prot_val) {
-
case _PAGE_CACHE_UC:
-
default:
-
prot = PAGE_KERNEL_IO_NOCACHE;
-
break;
-
case _PAGE_CACHE_UC_MINUS:
-
prot = PAGE_KERNEL_IO_UC_MINUS;
-
break;
-
case _PAGE_CACHE_WC:
-
prot = PAGE_KERNEL_IO_WC;
-
break;
-
case _PAGE_CACHE_WB:
-
prot = PAGE_KERNEL_IO;
-
break;
-
}
-
-
/*
-
* Ok, go for it..
-
*/
-
// 在vmalloc区域中获取空闲的子区域。
-
area = get_vm_area_caller(size, VM_IOREMAP, caller);
-
if (!area)
-
goto err_free_memtype;
-
/*
-
* 设置vm_struct->phys_addr为ioremap的物理地址
-
* 该字段的详细说明见vm_struct结构的定义
-
*/
-
area->phys_addr = phys_addr;
-
//addr为该内存区域首部的虚拟地址
-
vaddr = (unsigned long) area->addr;
-
-
if (kernel_map_sync_memtype(phys_addr, size, prot_val))
-
goto err_free_area;
-
-
//修改页表,实现物理地址到虚拟地址的映射
-
if (ioremap_page_range(vaddr, vaddr + size, phys_addr, prot))
-
goto err_free_area;
-
-
ret_addr = (void __iomem *) (vaddr + offset);
-
mmiotrace_ioremap(unaligned_phys_addr, unaligned_size, ret_addr);
-
-
/*
-
* Check if the request spans more than any BAR in the iomem resource
-
* tree.
-
*/
-
WARN_ONCE(iomem_map_sanity_check(unaligned_phys_addr, unaligned_size),
-
KERN_INFO "Info: mapping multiple BARs. Your kernel is fine.");
-
-
return ret_addr;
-
err_free_area:
-
free_vm_area(area);
-
err_free_memtype:
-
free_memtype(phys_addr, phys_addr + size);
-
return NULL;
-
}
阅读(6597) | 评论(0) | 转发(3) |