linux内存管理笔记(十五)----设备区域映射

我们知道默认外设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资源映射表
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值