linux源码解析05–ioremap原理

一、补充点背景知识:

  • 根据不同架构处理器,对内存的访问分为两种方式;
    a.x86 架构,将外设和普通内存分开,通过专门的 I / O 指令 (IN/OUT) 来访问外设的寄存器,称为“I/ O 地址空间”或“I/ O 端口空间”;
  • RISC 的 CPU,比如 ARM/PowerPC 等,采用统一编制,即将所有 I / O 外设的内存空间看作普通内存的一部分;
  • 一般外设 I / O 的物理地址是已知的,但是开启 MMU 后,只能通过虚拟地址访问,因此需要将外设地址映射到虚拟地址;

二、ioremap 映射函数

常用映射函数有

1
2
3
#define ioremap(addr, size)        __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define ioremap_wc(addr, size) __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC))
#define ioremap_np(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRnE))

核心实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static void __iomem *__ioremap_caller(phys_addr_t phys_addr, size_t size,
pgprot_t prot, void *caller)
{
unsigned long last_addr;
unsigned long offset = phys_addr & ~PAGE_MASK;
int err;
unsigned long addr;
struct vm_struct *area;

/*
* Page align the mapping address and size, taking account of any
* offset.
*/
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(size + offset); /// 物理地址页对齐

/*
* Don't allow wraparound, zero size or outside PHYS_MASK.
*/
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr || (last_addr & ~PHYS_MASK))
return NULL;

/*
* Don't allow RAM to be mapped.
*/
if (WARN_ON(pfn_valid(__phys_to_pfn(phys_addr))))
return NULL;

area = get_vm_area_caller(size, VM_IOREMAP, caller); /// 获得一个 vma 区
if (!area)
return NULL;
addr = (unsigned long)area->addr;
area->phys_addr = phys_addr;
/// 物理地址映射到 vmalloc 区
err = ioremap_page_range(addr, addr + size, phys_addr, prot);
if (err) {vunmap((void *)addr);
return NULL;
}

return (void __iomem *)(offset + addr); /// 分配的地址是页对齐,加上偏移得到真实地址
}

三、ioremap_page_range 函数

这部分跟之前普通页面映射差不多,就略过了;

建立好映射之后,应用程序可以通过虚拟地址,访问寄存器地址;