2023-06-11
原文作者:奇小葩 原文地址:https://blog.csdn.net/u012489236/category_9614673.html

我们知道默认外设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资源映射表
阅读全文