嵌入式Linux移植之内存初始化和地址映射

2019-07-12 20:28发布

Linux内核有两个重要的宏:PHYS_OFFSET和PAGE_OFFSET。PHYS_OFFSET是物理内存的起始地址,PAGE_OFFSET是Linux内核空间的虚拟起始地址(默认为0xC0000000,可通过menuconfig配置,CONFIG_PAGE_OFFSET)。PHYS_OFFSET可通过menuconfig配置(CONFIG_PHYS_OFFSET),但一般不直接配置。 如果定义了CONFIG_ARM_PATCH_PHYS_VIRT,则内核会根据实际所在的地址调整PHYS_OFFSET的值。 内核镜像生成的时候需要PHYS_OFFSET和PAGE_OFFSET对应的物理地址,在Makefile.boot中指定。对于SMDK2440开发板,则为arch/arm/mach-s3c24xx/Makefile.boot,如下,zreladdr-y即为__pa(PAGE_OFFSET+TEXT_OFFSET)(TEXT_OFSSET在makefile中写死了为0x8000)。params_phys-y即为PHYS_OFFSET ifeq ($(CONFIG_PM_H1940),y)
    zreladdr-y    += 0x30108000
    params_phys-y    := 0x30100100
else
    zreladdr-y    += 0x30008000
    params_phys-y    := 0x30000100
endif
Linux内核中内存初始化主要是初始化内存的地址范围和布局,以及建立页表(MMU),不会再初始化内存控制器。内存控制器在u-boot中已经初始化好并且已经把代码拷贝到内存运行,初始化内存控制器也可能导致内存中的数据异常。uboot中可以通过两种方式传递内存信息:在启动参数中使用mem=xxx或者使用atag_mem。
        对于mem=xxx参数,在early_param中初始化memblock,这里实际上添加了一个起始地址为PHYS_OFFSET,大小为mem=指定的内存bank。 static int __init early_mem(char *p)
{
    static int usermem __initdata = 0;
    u64 size;
    u64 start;
    char *endp;
 
    /*
     * If the user specifies memory size, we
     * blow away any automatically generated
     * size.
     */
    if (usermem == 0) {
        usermem = 1;
        memblock_remove(memblock_start_of_DRAM(),
            memblock_end_of_DRAM() - memblock_start_of_DRAM());
    }
 
    start = PHYS_OFFSET;
    size  = memparse(p, &endp);
    if (*endp == '@')
        start = memparse(endp + 1, NULL);
 
    arm_add_memory(start, size);
 
    return 0;
}
对于ATAG_MEM,则在parse_tag_mem32添加一个内存bank。
static int __init parse_tag_mem32(const struct tag *tag)
{
    return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}
内存布局初始化好后就会根据内存信息进行地址映射,通过paging_init重新初始化页表。     parse_early_param();
 
    early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));
    setup_dma_zone(mdesc);
    sanity_check_meminfo();
    arm_memblock_init(mdesc);
 
    paging_init(mdesc);
mem=xxx比atag_mem优先级较高,会覆盖atag_mem的设置。 内核在setup_arch函数中进行上面的初始化,start_kernel函数执行完setup_arch后,接着初始化zone和页分配器(zone实际上管理的是物理内存)。
    setup_arch(&command_line);
    mm_init_cpumask(&init_mm);
    setup_command_line(command_line);
        .............
 
    build_all_zonelists(NULL, NULL);
    page_alloc_init();
 
        ......
    mm_init();
static void __init mm_init(void)
{
    /*
     * page_cgroup requires contiguous pages,
     * bigger than MAX_ORDER unless SPARSEMEM.
     */
    page_cgroup_init_flatmem();
    mem_init();
    kmem_cache_init();
    percpu_init_late();
    pgtable_init();
    vmalloc_init();
}
在mem_init中打印memory layout,通过这里的数据可以找到修改这些layout的方法。     printk(KERN_NOTICE "Virtual kernel memory layout: "
            "    vector  : 0x%08lx - 0x%08lx   (%4ld kB) "
#ifdef CONFIG_HAVE_TCM
            "    DTCM    : 0x%08lx - 0x%08lx   (%4ld kB) "
            "    ITCM    : 0x%08lx - 0x%08lx   (%4ld kB) "
#endif
            "    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB) "
            "    vmalloc : 0x%08lx - 0x%08lx   (%4ld MB) "
            "    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB) "
#ifdef CONFIG_HIGHMEM
            "    pkmap   : 0x%08lx - 0x%08lx   (%4ld MB) "
#endif
#ifdef CONFIG_MODULES
            "    modules : 0x%08lx - 0x%08lx   (%4ld MB) "
#endif
            "      .text : 0x%p" " - 0x%p" "   (%4td kB) "
            "      .init : 0x%p" " - 0x%p" "   (%4td kB) "
            "      .data : 0x%p" " - 0x%p" "   (%4td kB) "
            "       .bss : 0x%p" " - 0x%p" "   (%4td kB) ",
 
            MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
                (PAGE_SIZE)),
#ifdef CONFIG_HAVE_TCM
            MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
            MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
            MLK(FIXADDR_START, FIXADDR_TOP),
            MLM(VMALLOC_START, VMALLOC_END),
            MLM(PAGE_OFFSET, (unsigned long)high_memory),
#ifdef CONFIG_HIGHMEM
            MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
                (PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
            MLM(MODULES_VADDR, MODULES_END),
#endif
 
            MLK_ROUNDUP(_text, _etext),
            MLK_ROUNDUP(__init_begin, __init_end),
            MLK_ROUNDUP(_sdata, _edata),
            MLK_ROUNDUP(__bss_start, __bss_stop));


    ARM芯片主要使用了Linux的两种地址映射方式,I/O静态映射(通过iotable_init函数)和ioremap动态映射。我的GT2440开发板启动后内核的地址空间如下:
                   在uboot启动参数中,指定了mem=62M。从图中可以发现,mem=62M指定了低端地址空间的大小,事实上是因为物理内存较小,没有超过vmalloc_limit。mem=xxx不能达到实际物理内存的大小(我的开发板内存为64MB),否则系统运行一些耗内存的应用程序可能会段错误,本人曾经用mjpg-streamer驱动pl2303摄像头的时候遇到过。一般至少需要预留2MB的空间,有人说是为DMA保留的,我目前不清楚原因。         iotable_init和ioremap函数都是把物理地址映射到vmalloc区。先看看iotable_init,其一般在machine初始化的map_io函数中初始化。         smdk2440_map_io函数中调用了s3c24xx_init_io void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
{
    arm_pm_idle = s3c24xx_default_idle;
 
    /* initialise the io descriptors we need for initialisation */
    iotable_init(mach_desc, size);
    iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));
 
    if (cpu_architecture() >= CPU_ARCH_ARMv5) {
        samsung_cpu_id = s3c24xx_read_idcode_v5();
    } else {
        samsung_cpu_id = s3c24xx_read_idcode_v4();
    }
 
    s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));
 
    samsung_pwm_set_platdata(&s3c24xx_pwm_variant);
}
这里映射了GPIO、IRQ、MEMCTRL、UART寄存器,即把S3C24XX_PA_XX映射到S3C24XX_VA_XX。
/* minimal IO mapping */
#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }
static struct map_desc s3c_iodesc[] __initdata = {
    IODESC_ENT(GPIO),
    IODESC_ENT(IRQ),
    IODESC_ENT(MEMCTRL),
    IODESC_ENT(UART)
};
#define S3C_ADDR_BASE    0xF6000000
#ifndef __ASSEMBLY__
#define S3C_ADDR(x)    ((void __iomem __force *)S3C_ADDR_BASE + (x))
#else
#define S3C_ADDR(x)    (S3C_ADDR_BASE + (x))
#endif
#define S3C_VA_IRQ    S3C_ADDR(0x00000000)    /* irq controller(s) */
#define S3C_VA_SYS    S3C_ADDR(0x00100000)    /* system control */
#define S3C_VA_MEM    S3C_ADDR(0x00200000)    /* memory control */
#define S3C_VA_TIMER    S3C_ADDR(0x00300000)    /* timer block */
#define S3C_VA_WATCHDOG    S3C_ADDR(0x00400000)    /* watchdog */
#define S3C_VA_UART    S3C_ADDR(0x01000000)    /* UART */
#define S3C24XX_VA_IRQ        S3C_VA_IRQ
#define S3C24XX_VA_MEMCTRL    S3C_VA_MEM
#define S3C24XX_VA_UART        S3C_VA_UART
 
#define S3C24XX_VA_TIMER    S3C_VA_TIMER
#define S3C24XX_VA_CLKPWR    S3C_VA_SYS
#define S3C24XX_VA_WATCHDOG    S3C_VA_WATCHDOG
我尝试发现这些地址映射的规律,但发现无明显的规律,基本是映射到“一个段"。事实上也是任意的,但其地址都是在vmalloc地址区域的末端。
在cpu->map_io中调用了s3c244x_map_io,这里映射了CLKPWR等寄存器。
void __init s3c244x_map_io(void)
{
    /* register our io-tables */
 
    iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));
static struct map_desc s3c244x_iodesc[] __initdata = {
    IODESC_ENT(CLKPWR),
    IODESC_ENT(TIMER),
    IODESC_ENT(WATCHDOG),
};
iotable_init一般只映射基础的组件,需要在内核启动时候的先初始化的寄存器,其余的寄存器在驱动中通过ioremap进行映射。
        以I2C驱动为例,在machine初始化中添加了一个I2C platform_deveice static struct platform_device *smdk2440_devices[] __initdata = {
    &s3c_device_ohci,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_iis,
};
       在其相应的driver i2c-s3c2410.c中s3c24xx_i2c_probe
    /* map the registers */
 
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    i2c->regs = devm_ioremap_resource(&pdev->dev, res);
 
    if (IS_ERR(i2c->regs))
        return PTR_ERR(i2c->regs);
 
    dev_dbg(&pdev->dev, "registers %p (%p) ",
        i2c->regs, res);
       这里获取I2C的物理地址范围,通过ioremap进行了映射。        地址映射之后便可以进行访问了,但一般不直接访问,而是通过include/asm-generic/io.h中定义的writel、readl等相关接口进行访问,这样会有更好的移植性。
--------------------- 
作者:漂洋过海blog 
来源:CSDN 
原文:https://blog.csdn.net/huangbin0709/article/details/52728313 
版权声明:本文为博主原创文章,转载请附上博文链接!