我们知道默认外设I/O资源部在linux的内核空间中,如果我们想要访问外设I/O,必须先将其地址映射到内核空间中,然后才能在内核空间中访问它。Linux内核访问外设I/O内存资源的方式有两种
- 动态映射(ioremap)
- 静态映射(map_desc)
动态映射的方式是我们使用的比较多,而且比较简单的方式,即直接通过内核提供的ioremap函数动态创建一段外设I/O内存资源到内核虚拟地址的映射表,从而就可以在内核空间中访问了。同时内核也提供了在系统启动时通过map_desc结构体静态创建I/O资源到内核空间的线性映射表(即page_table)的方式,内核的devicemaps_init函数就是实现这个功能,本章主要是分析其代码流程。
static void __init devicemaps_init(const struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void *vectors;
/*
* Allocate the vector page early.
*/
vectors = early_alloc(PAGE_SIZE * 2); ----------------(1)
early_trap_init(vectors);
/*
* Clear page table except top pmd used by early fixmaps
*/
for (addr = VMALLOC_START; addr < (FIXADDR_TOP & PMD_MASK); addr += PMD_SIZE) --------------(2)
pmd_clear(pmd_off_k(addr));
/*
* Map the kernel if it is XIP.
* It is always first in the modulearea.
*/
#ifdef CONFIG_XIP_KERNEL
map.pfn = __phys_to_pfn(CONFIG_XIP_PHYS_ADDR & SECTION_MASK);
map.virtual = MODULES_VADDR;
map.length = ((unsigned long)_exiprom - map.virtual + ~SECTION_MASK) & SECTION_MASK;
map.type = MT_ROM;
create_mapping(&map);
#endif
/*
* Map the cache flushing regions.
*/
#ifdef FLUSH_BASE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS);
map.virtual = FLUSH_BASE;
map.length = SZ_1M;
map.type = MT_CACHECLEAN;
create_mapping(&map);
#endif
#ifdef FLUSH_BASE_MINICACHE
map.pfn = __phys_to_pfn(FLUSH_BASE_PHYS + SZ_1M);
map.virtual = FLUSH_BASE_MINICACHE;
map.length = SZ_1M;
map.type = MT_MINICLEAN;
create_mapping(&map);
#endif
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors)); -------------(3)
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS
map.type = MT_HIGH_VECTORS;
#else
map.type = MT_LOW_VECTORS;
#endif
create_mapping(&map);
if (!vectors_high()) {
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/* Now create a kernel read-only mapping */
map.pfn += 1;
map.virtual = 0xffff0000 + PAGE_SIZE;
map.length = PAGE_SIZE;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
/*
* Ask the machine support to map in the statically mapped devices.
*/
if (mdesc->map_io) -------------------(4)
mdesc->map_io();
else
debug_ll_io_init();
fill_pmd_gaps();
/* Reserve fixed i/o space in VMALLOC region */
pci_reserve_io();
/*
* Finally flush the caches and tlb to ensure that we're in a
* consistent state wrt the writebuffer. This also ensures that
* any write-allocated cache lines in the vector page are written
* back. After this point, we can start to touch devices again.
*/
local_flush_tlb_all();
flush_cache_all();
/* Enable asynchronous aborts */
early_abt_enable();
}
- 1.分配两个page的物理页帧,然后通过early_trap_init会初始化异常向量表
- 2.主要是清除vmalloc区的相应页表项,该开发板对应的区间是0xe080 0000 ~ 0xffc0 0000
- 3.实际开始创建页表,为一个物理存储空间创建映射,先获取vectors的物理页地址,0xffff0000是arm默认的中断向量所在页,长度为1页, 注意,这里映射大小为4K,小于1M,将使用二级映射!,映射0xffff0000的那个page frame,地址是0xc0007ff8,如果SCTLR.V的值设定为low vectors,那么还要映射0地址开始的memory,然后映射high vecotr开始的第二个page frame,也就是对应到最开始申请的两个物理页帧
- 4.如果当前机器信息的mdesc的成员变量map_io有效,就调用map_io(),该函数指针依据不同机型均有不同的设置,主要是用于当前机器的IO设备映射到内核地址空间,也就是开始描述的静态映射。如果没有提供,使用debug_ll_io_init()自己实现,那么只需要实现debug_ll_addr(&map.pfn, &map.virtual); 这个函数。把map.pfn和map.virtual填上串口物理地址和映射的虚拟地址就行。
而early_trap_init做初始化异常向量表,其代码如下:
void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
unsigned long vectors = (unsigned long)vectors_base;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
unsigned i;
vectors_page = vectors_base;
/*
* Poison the vectors page with an undefined instruction. This
* instruction is chosen to be undefined for both ARM and Thumb
* ISAs. The Thumb version is an undefined instruction with a
* branch back to the undefined instruction.
*/
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M */
/*
* on V7-M there is no need to copy the vector table to a dedicated
* memory area. The address is configurable and so a table in the kernel
* image can be used.
*/
#endif
}
- 将整个vector table那个page frame填充为0xe7fd def1,未定义指令,
- 这个函数把定义在 arch/arm/kernel/entry-armv.S 中的异常向量表和异常处理程序的 stub 进行重定位:异常向量表拷贝到 0xFFFF_0000,异常向量处理程序的 stub 拷贝到 0xFFFF_0200。然后调用 modify_domain()修改了异常向量表所占据的页面的访问权限,这使得用户态无法访问该页,只有核心态才可以访问。
下面就进入到本章的重点内容,设备区域映射,主要使用的是machine_desc这个结构体,而这个是在setup_arch中定义
mdesc = setup_machine_fdt(__atags_pointer);
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
对于支持DTB格式,直接通过compatible = “fsl,imx6ull-14x14-evk”, "fsl,imx6ull"来寻找到对应的machine_desc,其在arch/arm/mach-imx/mach-imx6ul.c 中
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 UltraLite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
这章我们分析了devicemaps_init的用途,其主要有以下两个用途:
- 为中断向量分配内存,为中断向量虚拟地址映射的页表分配内存,建立虚拟地址到物理地址的映射
- 调用mdesc->map_io()进行SOC相关的初始化,通过map_desc结构体静态创建I/O资源映射表