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 (&params);
#endif #ifdef CONFIG_REVISION_TAG
  setup_revision_tag (&params);
#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 (, 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 = ;
  params->u.core.pagesize = ;
  params->u.core.rootdev = ;
  params = tag_next (params);
}

RAM相关参数在bootm.c中的函数setup_memory_tags中初始化:

static void setup_memory_tags (bd_t *bd)
{
  int i;
  for (i = ; 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 != )
      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-] = '\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 == && ((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 & ( << ))
    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( * 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
ENTRY(cpu_arm720_set_pte)
str r1, [r0], #- @ 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, # 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 = ;
    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(); //初始化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) >> ) & 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 + (<<)),
    .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 + (<<)),
    .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 = ; i < nr; i++)
    create_mapping(io_desc + i);
}

主内存创建映射bootmem_init

void __init bootmem_init(void)
{
  struct meminfo *mi = &meminfo;
  unsigned long memend_pfn = ;
  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 = ;   /*
  * 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。

Uboot与Linux之间的参数传递的更多相关文章

  1. 如何实现uboot和linux之间的参数传递

    参考http://cgxcn.blog.163.com/blog/static/132312422009101133251202/ 参考:http://blog.chinaunix.net/uid-1 ...

  2. 【linux】U-BOOT与linux kernel通信: struct tag

      欢迎转载,转载时需保留作者信息. 邮箱:tangzhongp@163.com 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http://b ...

  3. Windows Linux 之间rsync同步CODE文件

    Windows Linux 之间rsync同步CODE文件 一.环境Windows:OS:Microsoft Windows Web Server 2008 SP1IP:192.168.88.197 ...

  4. 驱动开发学习笔记. 0.02 基于EASYARM-IMX283 烧写uboot和linux系统

    驱动开发读书笔记. 0.02 基于EASYARM-IMX283 怎么烧写自己裁剪的linux内核?(非所有arm9通用) 手上有一块tq2440,但是不知道什么原因,没有办法烧boot进norflas ...

  5. windows与linux之间文件的传输方式总结(转)

    当然,windows与linux之间文件的传输的两种方式有很多,这里就仅仅列出工作中遇到的,作为笔记: 方法一:安装SSH Secure Shell Client客户端 安装即可登录直接拖拉到linu ...

  6. linux 的 scp 命令 可以 在 linux 之间复制 文件 和 目录

    转自:http://blog.csdn.net/snlying/article/details/6184102 Linux系统中scp命令的用法. scp就是secure copy的简写,用于在lin ...

  7. 基于samba实现win7与linux之间共享文件_阳仔_新浪博客

    基于samba实现win7与linux之间共享文件_阳仔_新浪博客 然后启动samba执行如下指令: /dev/init.d/smb start 至此完成全部配置.

  8. 如何实现windows和linux之间的文件传输

    2010-04-25 18:10 如何实现windows和linux之间的文件传输 如果想从windows中传送大量文件到Linux中,想必会难倒部分Linux初学者,尤其是文件很大时.我曾试过在li ...

  9. 萧墙HTML5手机发展之路(53)——jQueryMobile页面之间的参数传递

    基于单个页面模板HTTP通过路POST和GET请求传递参数.在多页模板,并且不需要server沟通,通常有三种方式在多页模板来实现页面之间的参数传递. 1.GET道路:上一页页生成参数并传递到下一个页 ...

随机推荐

  1. maven install 报错Could not calculate build plan: Plugin org.apache.maven.plugins:maven-resources-plugin

    Could not calculate build plan: Plugin org.apache.maven.plugins:maven-resources-plugin:2.6 or one of ...

  2. (转)iOS Wow体验 - 第六章 - 交互模型与创新的产品概念(1)

    本文是<iOS Wow Factor:Apps and UX Design Techniques for iPhone and iPad>第六章译文精选,其余章节将陆续放出.上一篇:Wow ...

  3. Zedboard甲诊opencv图像处理(三)

    整个工程进展到这一步也算是不容易吧,但技术含量也不怎么高,中间乱起八糟的错误太烦人了,不管怎么样,现在面临了最大的困难吧,图像处理算法.算法确实不好弄啊,虽然以前整过,但都不是针对图像的. 现在的图像 ...

  4. unity3d 学习笔记(一)

    操作:按下shit 点击坐标轴中心 切换透视图 动画烘焙的概念:相当于把原来的控制器动画或者IK(骨骼)动画所有塌陷为逐帧动画,导出的时候必须选这一项 着色器:从技术的角度来看,着色器是渲染器的一个部 ...

  5. iOS 8 Auto Layout界面自动布局系列5-自身内容尺寸约束、修改约束、布局动画

    首先感谢众多网友的支持,最近我实在是事情太多,所以没有写太多.不过看到大家的反馈和评价,我还是要坚持挤出时间给大家分享我的经验.如果你对我写的东西有任何建议.意见或者疑问,请到我的CSDN博客留言: ...

  6. asp Eval()函数的一些使用总结

    一.函数的原型: Eval(string s); s可以是变量,表达式,语句: 二.使用: 1.s为变量时,返回该变量的值,如:string s = "sss";Eval(s);/ ...

  7. FineUI控件之树的应用(二)

    一.Tree控件应用 <f:PageManager ID="PageManager1" runat="server" /> <f:Tree I ...

  8. 记一次令人发狂的 bug Eclipse 开不开 tomcat 7.0

    改项目,结果发现以前的项目也出了问题,就删除了系统用户下面workplace里的文件夹,结果,eclipse被清空,重新添加项目,发现一堆bug; 最让我崩溃的是,用tomcat 7.0跑项目,反复出 ...

  9. libcurl下载文件简易demo

    size_t test_save(void *ptr, size_t size, size_t nmemb, void *stream) { size_t sizes = size * nmemb; ...

  10. MFC中使用ADO方式连接数据库

    文章转自:http://blog.sina.com.cn/s/blog_a43aba5601014z8h.html 一.数据库操作准备 1.导入ADO动态链接库 在工程的stdafx.h中加入如下语句 ...