Uboot与Linux之间的参数传递
U-boot会给Linux Kernel传递很多参数,如:串口,RAM,videofb等。而Linux kernel也会读取和处理这些参数。两者之间通过struct tag来传递参数。
U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。
本文主要以U-boot传递RAM和Linux kernel读取RAM参数为例进行说明。
setenv bootcmd ' mmc rescan 0;fatload mmc 0 0x81000000 samplelogo_720p.bmp; logo on 0x81000000 0xA0000000 0x81600000 40 60;fatload mmc 0 0x81000000 uImage;bootm 0x81000000' setenv bootargs 'console=ttyO2,115200n8 rootwait rw mem=128M@0x80000000 mem=384M@0xc0000000 notifyk.vpssm3_sva=0xBEE00000 vram=20M root=/dev/nfs nfsroot=192.168.0.188:/home/m/rfs_816x/ ip=dhcp noinitrd' saveenv
1、u-boot给kernel传RAM参数
./common/cmd_bootm.c文件中(指Uboot的根目录),bootm命令对应的do_bootm函数,当分析uImage中信息发现OS是Linux时,调用./lib_arm/bootm.c文件中的do_bootm_linux函数来启动Linux kernel。
在do_bootm_linux函数中:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],\
ulong addr, ulong *len_ptr, int verify)
{
......
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd); //初始化tag结构体开始
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); //设置RAM参数
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); //初始化tag结构体结束
#endif
......
......
theKernel (0, machid, bd->bi_boot_params);
//传给Kernel的参数= (struct tag *)型的bd->bi_boot_params
//bd->bi_boot_params在board_init 函数中初始化,如对于at91rm9200,初始化在at91rm9200dk.c的board_init中进行:bd->bi_boot_params=PHYS_SDRAM + 0x100;
//这个地址也是所有taglist的首地址,见下面的setup_start_tag函数
}
对于setup_start_tag和setup_memory_tags函数说明如下。
函数setup_start_tag也在此文件中定义,如下:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;
//初始化(struct tag *)型的全局变量params为bd->bi_boot_params的地址,之后的setup tags相关函数如下面的setup_memory_tags就把其它tag的数据放在此地址的偏移地址上。
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
RAM相关参数在bootm.c中的函数setup_memory_tags中初始化:
static void setup_memory_tags (bd_t *bd)
{
int i;
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
} //初始化内存相关tag
}
2、Kernel读取U-boot传递的相关参数
对于Linux Kernel,ARM平台启动时,先执行arch/arm/kernel/head.S,此文件会调用arch/arm/kernel/head- common.S和arch/arm/mm/proc-arm920.S中的函数,并最后调用start_kernel:
......
b start_kernel
......
init/main.c中的start_kernel函数中会调用setup_arch函数来处理各种平台相关的动作,包括了u-boot传递过来参数的分析和保存:
start_kernel()
{
......
setup_arch(&command_line);
......
}
调用 setup_arch()函数进行与体系结构相关的第一个初始化工作; 对不同的体系结构来说该函数有不同的定义。对于 ARM 平台而言,该函数定义在arch/arm/kernel/Setup.c。它首先通过检测出来的处理器类型进行处理器内核的初始化,然后通过 bootmem_init()函数根据系统定义的 meminfo 结构进行内存结构的初始化,最后调用paging_init()开启 MMU,创建内核页表,映射所有的物理内存和 IO空间。 函数实现在 arch/arm/kernel/Setup.c 对内核参数的解析:
void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags; //定义了一个默认的内核参数列表 struct machine_desc *mdesc; char *from = default_command_line; unwind_init(); //本架构中没有用 setup_processor(); //汇编的CPU初始化部分 mdesc = setup_machine(machine_arch_type); machine_name = mdesc->name; if (mdesc->soft_reboot) reboot_setup("s"); if (__atags_pointer) tags = phys_to_virt(__atags_pointer); else if (mdesc->boot_params) tags = phys_to_virt(mdesc->boot_params); //由于MMU单元已打开,此处需要而boot_params是物理地址,需要转换成虚拟地址才能访问,因为此时CPU访问的都是虚拟地址 /* * If we have the old style parameters, convert them to * a tag list. */ //内核参数列表第一项必须是ATAG_CORE类型 if (tags->hdr.tag != ATAG_CORE) //如果不是,则需要转换成新的内核参数类型,新的内核参数类型用下面struct tag结构表示 convert_to_tag_list(tags); //此函数完成新旧参数结构转换 if (tags->hdr.tag != ATAG_CORE) //如果没有内核参数 tags = (struct tag *)&init_tags; //则选用默认的内核参数 if (mdesc->fixup) mdesc->fixup(mdesc, tags, &from, &meminfo); //用内核参数列表填充meminfo if (tags->hdr.tag == ATAG_CORE) { if (meminfo.nr_banks != 0) squash_mem_tags(tags); save_atags(tags); parse_tags(tags); //解析内核参数列表,然后调用内核参数列表的处理函数对这些参数进行处理。比如,如果列表为命令行,则最终会用parse_tag_cmdlin函数进行解析,这个函数用_tagtable编译连接到了内核里 }
//下面是记录内核代码的起始,结束虚拟地址 init_mm.start_code = (unsigned long) _text; init_mm.end_code = (unsigned long) _etext; init_mm.end_data = (unsigned long) _edata; init_mm.brk = (unsigned long) _end; //下面是对命令行的处理,刚才在参数列表处理parse_tag_cmdline函数已把命令行拷贝到了from空间 memcpy(boot_command_line, from, COMMAND_LINE_SIZE); boot_command_line[COMMAND_LINE_SIZE-1] = '\0'; parse_cmdline(cmdline_p, from); //解析出命令行,命令行解析出以后,同样会调用相关处理函数进行处理。系统用__early_param宏在编译阶段把处理函数编译进内核。
paging_init(mdesc); //这个函数完成页表初始化,具体的方法为建立线性地址划分后每个地址空间的标志;清除在boot阶段建立的内核映射空间,也即把页表项全部清零;调用bootmem_init,禁止无效的内存节点,由于我们的物理内存都是连续的空间,因此,内存节点为1个。接下来判断INITRD映像是否存在,若存在则检查其所在的地址是否在一个有效的地址内,然后返回此内存节点号。 //先看两个数据结构。 //struct meminfo表示内存的划分情况。Linux的内存划分为bank。每个bank用 //struct membank表示,start表示起始地址,这里是物理地址,size表示大小,node表示此bank所在的节点号,对于只有一个节点的内存,所有bank节点都相等 /*struct membank { unsigned long start; unsigned long size; int node; };
struct meminfo { int nr_banks; struct membank bank[NR_BANKS]; }; */ //在完成内存页映射后即进入request_standard_resources,这个函数比较简单,主要完成从iomem_resource空间申请所需的内存资源,比如内核代码和视频所需的资源等 request_standard_resources(&meminfo, mdesc); #ifdef CONFIG_SMP smp_init_cpus(); #endif cpu_init(); //此函数为空 /* * Set up various architecture-specific pointers */ init_arch_irq = mdesc->init_irq; //初始化与硬件体系相关的指针 system_timer = mdesc->timer; init_machine = mdesc->init_machine; #ifdef CONFIG_VT #if defined(CONFIG_VGA_CONSOLE) conswitchp = &vga_con; #elif defined(CONFIG_DUMMY_CONSOLE) conswitchp = &dummy_con; #endif #endif early_trap_init(); //重定位中断向量,将中断向量代码拷贝到中断向量页,并把信号处理代码指令拷贝到向量页中 }
parse_tags处理各种tags,其中包括了RAM参数的处理。
Board-harmony.c (arch\arm\mach-tegra):__tagtable(ATAG_NVIDIA, parse_tag_nvidia);
Common.c (arch\arm\mach-footbridge):__tagtable(ATAG_MEMCLK, parse_tag_memclk);
Init.c (arch\arm\mm):__tagtable(ATAG_INITRD, parse_tag_initrd);
Init.c (arch\arm\mm):__tagtable(ATAG_INITRD2, parse_tag_initrd2);
Riscpc.c (arch\arm\mach-rpc):__tagtable(ATAG_ACORN, parse_tag_acorn);
Setup.c (arch\arm\kernel):__tagtable(ATAG_CORE, parse_tag_core);
Setup.c (arch\arm\kernel):__tagtable(ATAG_MEM, parse_tag_mem32);
Setup.c (arch\arm\kernel):__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
Setup.c (arch\arm\kernel):__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
Setup.c (arch\arm\kernel):__tagtable(ATAG_SERIAL, parse_tag_serialnr);
Setup.c (arch\arm\kernel):__tagtable(ATAG_REVISION, parse_tag_revision);
Setup.c (arch\arm\kernel):__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
Setup.c (arch\arm\kernel): * The tag table is built by the linker from all the __tagtable
Setup.h (arch\arm\include\asm):#define __tagtable(tag, fn) \
对于处理RAM的tag,调用了parse_tag_mem32函数:
static int __init parse_tag_mem32(const struct tag *tag)
{
......
arm_add_memory(tag->u.mem.start, tag->u.mem.size);
......
}
_tagtable(ATAG_MEM, parse_tag_mem32);
上述的arm_add_memory函数定义如下:
static void __init arm_add_memory(unsigned long start, unsigned long size)
{
struct membank *bank;
size -= start & ~PAGE_MASK;
bank = &meminfo.bank[meminfo.nr_banks++];
bank->start = PAGE_ALIGN(start);
bank->size = size & PAGE_MASK;
bank->node = PHYS_TO_NID(start);
}
如上可见,parse_tag_mem32函数调用arm_add_memory函数把RAM的start和size等参数保存到了meminfo结构的meminfo结构体中。最后,在setup_arch中执行下面语句:
paging_init(&meminfo, mdesc);
对没有MMU的平台上调用arch/arm/mm/nommu.c中的paging_init,否则调用arch/arm/mm/mmu.c中的paging_init函数。这里暂不分析mmu.c中的paging_init函数。
内核核心之 paging_init
paging_init () --> bootmem_init () //为主内存创建映射 --> bootmem_init_node () //为指定节点的主内存创建映射 --> map_memory_bank() //为一个Bank创建映射 --> create_mapping() //为一个物理存储空间创建映射 --> alloc_init_section() paging_init () --> devicemaps_init () //为设备IO 内存创建映射 --> create_mapping() //为一个物理存储空间创建映射 --> alloc_init_section() --> mdesc->map_io(); --> smdk2440_map_io()
paging_init函数定义在arch/arm/mm/mmu.c中:
void __init paging_init(struct machine_desc *mdesc) { void *zero_page; build_mem_type_table(); sanity_check_meminfo(); prepare_page_table();
bootmem_init(); // 为主内存创建映射, 定义在arch/arm/mm/init.c 中 devicemaps_init(mdesc); //为设备IO 内存创建映射 定义在arch/arm/mm/mmu.c中
kmap_init(); top_pmd = pmd_off_k(0xffff0000); /* * allocate the zero page. Note that this always succeeds and * returns a zeroed result. */ zero_page = alloc_bootmem_low_pages(PAGE_SIZE); empty_zero_page = virt_to_page(zero_page); flush_dcache_page(empty_zero_page); }
bootmem_init ()和devicemaps_init(mdesc)这两个函数都调用了create_mapping,它是创建页表的直接操作者。
页表创建create_mapping
arch/arm/mm/mmu.c: void __init create_mapping(struct map_desc *md) { unsigned long phys, addr, length, end; const struct mem_type *type; pgd_t *pgd; //只有虚拟地址处于用户空间(只能是中断向量表) //但又不是映射为0地址的中断向量表时报错 //只能给系统空间 或者 中断向量 所在的空间创建映射,绝对不可给用户虚拟空间创建映射。 if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) { printk(KERN_WARNING "BUG: not creating mapping for " "0x%08llx at 0x%08lx in user region\n", __pfn_to_phys((u64)md->pfn), md->virtual); return; }
//当类型为MT_DEVICE或者MT_ROM,但是他们的虚拟地址 //又处于vmalloc的空间(c0000000----d0000000) //VMALLOC_END是在我们include/asm-arm/arch-sep4020/vmalloc.h中定义 //所以我们在对寄存器进行静态映射其实也是有限制的 if ((md->type == MT_DEVICE || md->type == MT_ROM) && md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) { printk(KERN_WARNING "BUG: mapping for 0x%08llx at 0x%08lx " "overlaps vmalloc space\n", __pfn_to_phys((u64)md->pfn), md->virtual); } type = &mem_types[md->type]; //获取memory类型 /* * Catch 36-bit addresses */ //以下已经超出普通嵌入式应用,忽略 if (md->pfn >= 0x100000) { create_36bit_mapping(md, type); return; } addr = md->virtual & PAGE_MASK; //得到虚拟地址 phys = (unsigned long)__pfn_to_phys(md->pfn); //得到物理地址 length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK)); //映射长度 if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) { printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not " "be mapped using pages, ignoring.\n", __pfn_to_phys(md->pfn), addr); return; } pgd = pgd_offset_k(addr); end = addr + length; do { unsigned long next = pgd_addr_end(addr, end); alloc_init_section(pgd, addr, next, phys, type); phys += next - addr; addr = next; } while (pgd++, addr != end); } 关联函数: alloc_init_section(unsigned long virt, unsigned long phys, int prot) { //pmdp是一级页表描述符的地址 pmd_t *pmdp = pmd_off_k(virt); if (virt & (1 << 20)) pmdp++;
//向一级页表描述符地址中写入一级页表描述符 //一级页表描述符:12bit phys | 20 bit prot //对于内存来说打印信息为: pmdp is c0007000, value is 3000041e // pmdp is c000707c, value is 31f0041e *pmdp = __pmd(phys | prot); flush_pmd_entry(pmdp); } alloc_init_page(unsigned long virt, unsigned long phys, unsigned int prot_l1, pgprot_t prot) { pmd_t *pmdp = pmd_off_k(virt); pte_t *ptep; if (pmd_none(*pmdp)) { //对于粗颗粒小页变换,一级页表描述符是和二级页表的基地址有关的 //所以这里除了有protl1还有ptep的物理地址 ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t)); __pmd_populate(pmdp, __pa(ptep) | prot_l1); } //根据pmd找pte项(ptep用通俗的语言来说就是二级页表描述符地址) ptep = pte_offset_kernel(pmdp, virt); //ptep是二级页表描述符地址,pfn_pte是根据虚拟地址和配置选项得到二级页表描述符 //set_pte函数是在arch/arm/mm/proc_720t.s中实现的 set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, prot)); } /* * Function: arm720_set_pte(pte_t *ptep, pte_t pte) * Params : r0 = Address to set * : r1 = value to set * Purpose : Set a PTE and flush it out of any WB cache */ //其实这一步只是往一个二级页表描述符地址里面存放一个二级页表描述符,一个str指令就能完成的,但正是因为之前所说的在linux中有两种pte机制(linux,硬件),所以在配置完了linux的,还需要配置硬件,因此在这里第一行代码之下,都是为了实现硬件的pte的设置。 .align 5 ENTRY(cpu_arm720_set_pte) str r1, [r0], #-2048 @ linux version eor r1, r1, #L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_WRITE | L_PTE_DIRTY bic r2, r1, #PTE_SMALL_AP_MASK bic r2, r2, #PTE_TYPE_MASK orr r2, r2, #PTE_TYPE_SMALL tst r1, #L_PTE_USER @ User? orrne r2, r2, #PTE_SMALL_AP_URO_SRW tst r1, #L_PTE_WRITE | L_PTE_DIRTY @ Write and Dirty? orreq r2, r2, #PTE_SMALL_AP_UNO_SRW tst r1, #L_PTE_PRESENT | L_PTE_YOUNG @ Present and Young movne r2, #0 str r2, [r0] @ hardware version mov pc, lr
设备IO内存创建映射devicemaps_init
static void __init devicemaps_init(struct machine_desc *mdesc) { struct map_desc map; unsigned long addr; void *vectors; /* * Allocate the vector page early. */ //为中断向量分配1个物理页面: 4KB vectors = alloc_bootmem_low_pages(PAGE_SIZE); for (addr = VMALLOC_END; addr; addr += PGDIR_SIZE) pmd_clear(pmd_off_k(addr)); ....... /* * 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. */ //为中断向量创建映射,把分配的物理页面映射到0xffff 0000或0x0000 0000 map.pfn = __phys_to_pfn(virt_to_phys(vectors)); map.virtual = 0xffff0000; map.length = PAGE_SIZE; map.type = MT_HIGH_VECTORS; create_mapping(&map); if (!vectors_high()) { map.virtual = 0; map.type = MT_LOW_VECTORS; create_mapping(&map); } /* * Ask the machine support to map in the statically mapped devices. */ //调用机器描述块machine_desc中的map_io函数,完成设备IO内存的映射 if (mdesc->map_io) mdesc->map_io(); ...... }
devicemaps_init()调用了机器描述块中定义的map_io 函数。这个函数完成设备IO 内存的映射。对于S3C2440开发板,map_io指向smdk2440_map_io(), 就是调用smdk2440_map_io函数,它也是在arch/arm/mach-s3c2440/mach-smdk2440.c中定义,如下所示:
static void __init smdk2440_map_io(void) { s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc)); s3c24xx_init_clocks(16934400); //初始化clock s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs)); } MACHINE_START(S3C2440, "SMDK2440") //定义在arch/asm/include/asm/mach/arch.h中 #define MACHINE_START(_type,_name) ...... /* Maintainer: Ben Dooks <ben@fluff.org> */ .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .timer = &s3c24xx_timer, MACHINE_END
//这是一个保存虚拟地址和物理地址的映射关系表,内核通过这个参数的指导来完成映射关系
arch/arm/mach-s3c2440/mach-smdk2440.c:
static struct map_desc smdk2440_iodesc[] __initdata = { /* ISA IO Space map (memory space selected by A24) */ { .virtual = (u32)S3C24XX_VA_ISA_WORD, .pfn = __phys_to_pfn(S3C2410_CS2), .length = 0x10000, .type = MT_DEVICE, }, { .virtual = (u32)S3C24XX_VA_ISA_WORD + 0x10000, .pfn = __phys_to_pfn(S3C2410_CS2 + (1<<24)), .length = SZ_4M, .type = MT_DEVICE, }, { .virtual = (u32)S3C24XX_VA_ISA_BYTE, .pfn = __phys_to_pfn(S3C2410_CS2), .length = 0x10000, .type = MT_DEVICE, }, { .virtual = (u32)S3C24XX_VA_ISA_BYTE + 0x10000, .pfn = __phys_to_pfn(S3C2410_CS2 + (1<<24)), .length = SZ_4M, .type = MT_DEVICE, } };
smdk2440_map_io()又调用了s3c24xx_init_io(),其定义在arch/arm/plat-s3c24xx/cpu.c中:
void __init s3c24xx_init_io(struct map_desc *mach_desc, int size) { unsigned long idcode = 0x0; /* initialise the io descriptors we need for initialisation */ iotable_init(mach_desc, size); //映射设备IO内存 iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //完成s3c_iodesc里的映射条目的映射 //获取当前系统的CPU if (cpu_architecture() >= CPU_ARCH_ARMv5) { idcode = s3c24xx_read_idcode_v5(); } else { idcode = s3c24xx_read_idcode_v4(); } arm_pm_restart = s3c24xx_pm_restart; s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids)); }
arch/arm/plat-s3c24xx/cpu.c:
static struct map_desc s3c_iodesc[] __initdata = { IODESC_ENT(GPIO), //GPIO寄存器虚实地址映射 IODESC_ENT(IRQ), //中断寄存器虚实地址映射 IODESC_ENT(MEMCTRL), IODESC_ENT(UART) };
这个就是要进行虚实地址映射的映射表了, 里面的每个条目都对应一个映射关系, 用map_desc来描述, 我们来看map_desc 。
arch/arm/include/asm/mach/map.h:
struct map_desc { unsigned long virtual; //虚拟地址 unsigned long pfn; //对应的物理地址 unsigned long length; //映射长度 unsigned int type; //映射类型,MT_xxx,决定了相应映射页面的访问权限 };
我们以IRQ的映射关系为例来看一下:
先看IODESC_ENT的定义,在arch/arm/plat-s3c/include/plat/cpu.h:
#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }
则 IODESC_ENT(IRQ) 就是:
{ (unsigned long)S3C24XX_VA_IRQ, _phys_to_pfn(S3C24XX_PA_IRQ),S3C24XX_SZ_IRQ, MT_DEVICE }
而S3C24XX_VA_IRQ 等都在arch/arm/plat-s3c24xx/include/plat/map.h下定义过了。
#define S3C24XX_VA_IRQ S3C_VA_IRQ //S3C_VA_IRQ定义在arch/arm/plat-s3c/include/plat/map-base.h中 #define S3C2410_PA_IRQ (0x4A000000) #define S3C24XX_SZ_IRQ SZ_1M
因此这个条目解释就是虚拟地址为0xF0000000开始长度为1M的虚拟地址空间由MMU转换成物理地址为0x4A000000开始的1M地址空间, 这正是IRQ寄存器物理地址所在位置。 条目定义好了, 剩下的就是要让系统MMU知道并在遇到这样一个虚拟地址时能正确映射到具体的物理地址。而这个工作就是由iotable_init完成的。
而iotable_init()又是通过create_mapping()创建映射的,在arch/arm/mm/mmu.c中:
void __init iotable_init(struct map_desc *io_desc, int nr) { int i; for (i = 0; i < nr; i++) create_mapping(io_desc + i); }
主内存创建映射bootmem_init
void __init bootmem_init(void) { struct meminfo *mi = &meminfo; unsigned long memend_pfn = 0; int node, initrd_node; /* * Locate which node contains the ramdisk image, if any. */ initrd_node = check_initrd(mi); /* * Run through each node initialising the bootmem allocator. */ //遍历所有节点,为每个节点调用bootmem_init_node()完成指定节点内存的映射创建 for_each_node(node) { unsigned long end_pfn = bootmem_init_node(node, mi); ....... } }
//bootmem_init_node():为指定节点的主内存创建映射 static unsigned long __init bootmem_init_node(int node, struct meminfo *mi) { unsigned long start_pfn, end_pfn, boot_pfn; unsigned int boot_pages; pg_data_t *pgdat; int i; start_pfn = -1UL; end_pfn = 0; /* * Calculate the pfn range, and map the memory banks for this node. */ for_each_nodebank(i, mi, node) { struct membank *bank = &mi->bank[i]; unsigned long start, end; start = bank_pfn_start(bank); end = bank_pfn_end(bank); if (start_pfn > start) start_pfn = start; if (end_pfn < end) end_pfn = end; map_memory_bank(bank); //为指定节点类型的Bank创建映射 } ....... }
bootmem_init_node()遍历整个meminfo结构,为指定节点类型的Bank创建映射。为一个Bank创建映射是通过函数map_memory_bank()实现的。这个函数也在arch/arm/mm/init.c中:
static inline void map_memory_bank(struct membank *bank) //为一个Bank创建映射 { #ifdef CONFIG_MMU struct map_desc map; map.pfn = bank_pfn_start(bank); //要映射的物理起始页面号 map.virtual = __phys_to_virt(bank_phys_start(bank)); //映射到的虚拟地址 map.length = bank_phys_size(bank); //映射长度 map.type = MT_MEMORY; //映射类型 create_mapping(&map); //为一个物理存储空间创建映射,实际上就是填充页表 #endif }
map_memory_bank()调用create_mapping()完成映射的创建工作。create_mapping()函数用于为一个物理存储空间创建映射,实际上就是填充页表。
需要重点关注的是create_mapping()的参数map_desc结构,它在arch/arm/include/asm/mach/map.h中定义,描述了一个映射区间:
struct map_desc { unsigned long virtual; //映射到的虚拟地址 unsigned long pfn; //要映射的物理起始页面号 unsigned long length; //映射长度 unsigned int type; //映射类型,MT_xxx,决定了相应映射页面的访问权限 };
// type的取值 /* types 0-3 are defined in asm/io.h */ #define MT_UNCACHED 4 #define MT_CACHECLEAN 5 #define MT_MINICLEAN 6 #define MT_LOW_VECTORS 7 #define MT_HIGH_VECTORS 8 #define MT_MEMORY 9 #define MT_ROM 10 #define MT_MEMORY_NONCACHED 11
其中type成员决定了页面的访问权限,这是通过全局数组struct mem_type mem_types[]实现的(type是这个数组的下标):
arch/arm/mm/mm.h
arch/arm/mm/mm.h struct mem_type { unsigned int prot_pte; unsigned int prot_l1; unsigned int prot_sect; unsigned int domain; }; arch/arm/mm/mmu.c static struct mem_type mem_types[] = { [MT_DEVICE] = { /* Strongly ordered / ARMv6 shared device */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | L_PTE_SHARED, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PROT_SECT_DEVICE | PMD_SECT_S, .domain = DOMAIN_IO, }, [MT_DEVICE_NONSHARED] = { /* ARMv6 non-shared device */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_NONSHARED, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PROT_SECT_DEVICE, .domain = DOMAIN_IO, }, [MT_DEVICE_CACHED] = { /* ioremap_cached */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_CACHED, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PROT_SECT_DEVICE | PMD_SECT_WB, .domain = DOMAIN_IO, }, [MT_DEVICE_WC] = { /* ioremap_wc */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_WC, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PROT_SECT_DEVICE, .domain = DOMAIN_IO, }, [MT_UNCACHED] = { .prot_pte = PROT_PTE_DEVICE, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PMD_TYPE_SECT | PMD_SECT_XN, .domain = DOMAIN_IO, }, [MT_CACHECLEAN] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_XN, .domain = DOMAIN_KERNEL, }, [MT_MINICLEAN] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_XN | PMD_SECT_MINICACHE, .domain = DOMAIN_KERNEL, }, [MT_LOW_VECTORS] = { .prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_EXEC, .prot_l1 = PMD_TYPE_TABLE, .domain = DOMAIN_USER, }, [MT_HIGH_VECTORS] = { .prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_USER | L_PTE_EXEC, .prot_l1 = PMD_TYPE_TABLE, .domain = DOMAIN_USER, }, [MT_MEMORY] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, [MT_ROM] = { .prot_sect = PMD_TYPE_SECT, .domain = DOMAIN_KERNEL, }, [MT_MEMORY_NONCACHED] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, };
再回到map_memory_bank() 代码中, 虚拟地址设成了__phys_to_virt(bank_start) 。
__phys_to_virt()和__virt_to_phys()是两个宏定义,用于在虚拟地址和物理地址之间互相转换。
这两个宏定义在arch/arm/include/asm/memory.h 中:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET) #define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
其中PHYS_OFFSET 是物理内存起点, PAGE_OFFSET 是内核虚拟地址空间起点(0xC0000000, 3GB)。所以内核的虚拟地址和物理地址之间只是有一个固定的偏移量。对于AT91SAM9260EK 平台,PHYS_OFFSET 为0x20000000,有以下转换关系:
PA = VA -(0xC0000000 – 0x20000000) = VA – 0xA0000000
VA = PA + (0xC0000000 – 0x20000000) = PA + 0xA0000000
也就是说, AT91SAM9260EK 板上的64MB SDRAM 的物理地址范围是
0x20000000~0x24000000-1,映射后的虚拟地址范围是: 0xC0000000~0xC4000000-1。
4、关于U-boot中的bd和gd
U-boot中有一个用来保存很多有用信息的全局结构体--gd_t(global data缩写),其中包括了bd变量,可以说gd_t结构体包括了u-boot中所有重要全局变量。最后传递给内核的参数,都是从gd和bd中来的,如上述的setup_memory_tags函数的作用就是用bd中的值来初始化RAM相应的tag。
对于ARM平台这个结构体的定义大致如下:
include/asm-arm/global_data.h
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
void **jt; /* jump table */
} gd_t;
在U-boot中使用gd结构之前要用先用宏DECLARE_GLOBAL_DATA_PTR来声明。这个宏的定义如下:
include/asm-arm/global_data.h
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
从这个宏的定义可以看出,gd是一个保存在ARM的r8寄存器中的gd_t结构体的指针。
说明:本文的版本为U-boot-1.3.4、Linux-2.6.28,平台是ARM。
posted on 2014-08-05 10:55 CSlunatic 阅读(2430) 评论(0) 编辑 收藏 举报