Chinaunix首页 | 论坛 | 博客
  • 博客访问: 641210
  • 博文数量: 75
  • 博客积分: 7001
  • 博客等级: 少将
  • 技术积分: 1465
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-11 17:39
文章分类

全部博文(75)

文章存档

2010年(1)

2009年(25)

2008年(49)

我的朋友

分类: LINUX

2008-06-10 19:29:04

 

How do linux kenel know the memory configuration on the development board

 

 

Everybody knows linux kernel manages system memory by means of sections and paging methods. But how does linux kernel know about how many BYTEs and BANKs of memory that are configured on the development board. Whether kernel adopt some algorithms to detect it, or some other external tool provides the memory information to linux kernel, if so, who will it be to play the role?

 

Absolutely, it is “Bootloader”.

 

From the following description, we can deny the assumption that linux kernel makes use of some algorithms to understand the memory layout on the development board.

 

The bootloader is expected to find and initialise all RAM that the kernel will use for volatile data storage in the system. It performs this in a machine dependent manner. It may use internal algorithms to automatically locate and size all RAM, or it may use knowledge of the RAM in the machine, or any other method the bootloader designer sees fit.

In all cases it should be noted that all setup is performed by the bootloader. The kernel should have no knowledge of the setup or configuration of the RAM within a system other than that provided by the bootloader. The use of machine_fixup() within the kernel is most definitely not the correct place for this. There is a clear distinction between the bootloaders responsibility and the kernel in this area.

The physical memory layout is passed to the kernel using the parameter. Memory does not necessarily have to be completely contiguous, although the minimum number of fragments is preferred. Multiple blocks allow for several memory regions. The kernel will coalesce blocks passed to it if they are contiguous physical regions.

The bootloader may also manipulate the memory with the kernels command line, using the 'mem=' parameter, the options for this parameter are fully documented in linux/Documentation/kernel-parameters.txt

The kernel command line 'mem=' has the syntax mem=[KM][,@] which allows the size and physical memory location for a memory area to be defined. This allows for specifying multiple discontigous memory blocks at differing offsets by providing the mem= parameter multiple times.”[1]

Not only do we obtain the above conclusion, but also it tells us it is the bootloader that passes memory configuration parameters through ‘tag list’ and command line ‘mem = ‘ to linux kernel statically and dynamically, respectively, on the stage of booting up linux kernel.

 

 

Now, let’s explore the procedures!

 

Bootloader boots up linux kernel with command “bootm”, which calls do_bootm_linux() function implemented in “libarm/armlinux.c” file in bootloader. Command “bootm” passes some parameters in forms of tags list statically, which is preferred from linux kernel 2.4.x. It stores parameters to the specified location, from which linux kernel picks up the parameters, then the linux kernel can understand some information about the development board. Alternatively, Bootloader can transfer parameters through command line, assigend by “bootargs” command, dynamically, and linux kernel parse command line to obtain the corresponding information. In the below statements, I’ll introduce howtwo methods are implemented in the linux and Bootload in detail.

 

 

First, let’s have a look at how Bootloader store parameters on the specified location.

 

There are many parameters to be transferred, such as tag_core, tag_mem32, tag_videotext, tag_ramdisk, tag_initrd, tag_serialnr, tag_revision    , tag_videolfb, tag_cmdline. We set an example of memory parameter.

 

The list of do_bootm_linux() function (libarm/armlinux.c)

 

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

       ulong len = 0, checksum;

       ulong initrd_start, initrd_end;

       ulong data;

       void (*theKernel)(int zero, int arch, uint params);

       image_header_t *hdr = &header;

       bd_t *bd = gd->bd;

 

#ifdef CONFIG_CMDLINE_TAG

       char *commandline = getenv ("bootargs");

#endif

 

       theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

 

       ……

       ……

       ……

 

       SHOW_BOOT_PROGRESS (15);

 

       debug ("## Transferring control to Linux (at address %08lx) ...\n",

              (ulong) theKernel);

 

#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);

#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);

#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);

#endif

 

       /* we assume that the kernel is in place */

       printf ("\nStarting kernel ...\n\n");

 

#ifdef CONFIG_USB_DEVICE

       {

              extern void udc_disconnect (void);

              udc_disconnect ();

       }

#endif

 

       cleanup_before_linux ();

 

       theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

}

 

#ifdef CONFIG_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);

       }

}

 

Where params is a global variable defined in ‘lib_arm/armlinux.c’, “static struct tag *params;” and initialized in function setup_start_tag (bd);

static void setup_start_tag (bd_t *bd)

{

       params = (struct tag *) bd->bi_boot_params; àThe valuse assigned to variable params is stored in specified loacion on the memory.

 

       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);

}

variable ‘bd’ is the pointer points to the memory where the board information is stored, and bd_dram[n] contains the memory configuration of the board, which is initialized in “int dram_init (void)” function of  “board/xxx/xxx.c”.

 

Now that the parameters of memory configuratiion have been stored up, susequently, let’s see how linux kernel extract them from the tag lists?

 

Bootloader prepare nearly everything for linux kernel, then call start_armboot() function (init/main.c) to invoke the linux kernel. The extraction of parameters resides in setup_arch() function (arch/arm/kernel/setup.c).

The extraction of parameters about memory information can be devided into 2 stages:

1.       Extract memory information contained in tag list “ATAG_MEM” by means of invoking parse_tags(tags).

2.       Extract memory information transferred through command line, included in “bootargs” in the form of “mem=”, by means of calling parse_cmdline(cmdline_p, from).

 

No matter which path adopted by linux kernel to obtain the memory configuration of the development board, the memory information is eventually stored in “struct meminfo”, through function arm_add_memory()

static void __init arm_add_memory(unsigned long start, unsigned long size)

{

       /*

        * Ensure that start/size are aligned to a page boundary.

        * Size is appropriately rounded down, start is rounded up.

        */

       size -= start & ~PAGE_MASK;

 

       meminfo.bank[meminfo.nr_banks].start = PAGE_ALIGN(start);

       meminfo.bank[meminfo.nr_banks].size  = size & PAGE_MASK;

       meminfo.bank[meminfo.nr_banks].node  = PHYS_TO_NID(start);

       meminfo.nr_banks += 1;

}

 

Now, let’s walk through the two stages one by one.

 

void __init setup_arch(char **cmdline_p)

{

       struct tag *tags = (struct tag *)&init_tags;

       struct machine_desc *mdesc;

       char *from = default_command_line;

 

       init_tags.mem.start = PHYS_OFFSET;

 

       setup_processor();

       mdesc = setup_machine(machine_arch_type);

       machine_name = mdesc->name;

 

       if (mdesc->soft_reboot)

              reboot_setup("s");

 

#ifndef CONFIG_NAKED_BOOT

       if (mdesc->boot_params)

              tags = phys_to_virt(mdesc->boot_params);

#endif

 

       /*

        * If we have the old style parameters, convert them to

        * a tag list.

        */

       if (tags->hdr.tag != ATAG_CORE)

              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);

 

       if (tags->hdr.tag == ATAG_CORE) {

              if (meminfo.nr_banks != 0)

                     squash_mem_tags(tags);

              1. parse_tags(tags);

       }

 

       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;

 

       ………

       2. parse_cmdline(cmdline_p, from);

       paging_init(&meminfo, mdesc);

       ………

}

 

 

1. parse_tags(tags)

/*

 * Parse all tags in the list, checking both the global and architecture

 * specific tag tables.

 */

static void __init parse_tags(const struct tag *t)

{

       for (; t->hdr.size; t = tag_next(t))

              if (!parse_tag(t))

                     printk(KERN_WARNING

                            "Ignoring unrecognised tag 0x%08x\n",

                            t->hdr.tag);

}

 

 

/*

 * Scan the tag table for this tag, and call its parse function.

 * The tag table is built by the linker from all the __tagtable

 * declarations.

 */

static int __init parse_tag(const struct tag *tag)

{

       extern struct tagtable __tagtable_begin, __tagtable_end; -à defined in “arch/arm/kernel/vmlinux.lds”, The space between __early_begin and __early_end is data with attribution “.taglist.init”.

 

       struct tagtable *t;

 

       for (t = &__tagtable_begin; t < &__tagtable_end; t++)

              if (tag->hdr.tag == t->tag) {

                     t->parse(tag);

                     break;

              }

 

       return t < &__tagtable_end;

}

 

Note: __tagtable_begin & __tagtable_end is defined in vmlinux.lds(arch/arm/kernel). Parse_tag() function associates parameter extraction function with corresponding tag,

herein, __tagtable(ATAG_CORE, parse_tag_core) implements this functionality.

 

__tagtable(ATAG_MEM, parse_tag_mem32);

 

#define __tag __attribute_used__ __attribute__((__section__(".taglist.init")))

#define __tagtable(tag, fn) \

static struct tagtable __tagtable_##fn __tag = { tag, fn }

 

static int __init parse_tag_mem32(const struct tag *tag)

{

       if (meminfo.nr_banks >= NR_BANKS) {

              printk(KERN_WARNING

                     "Ignoring memory bank 0x%08x size %dKB\n",

                     tag->u.mem.start, tag->u.mem.size / 1024);

              return -EINVAL;

       }

       arm_add_memory(tag->u.mem.start, tag->u.mem.size);

       return 0;

}

 

 

2. parse_cmdline(cmdline_p, from);

/*

 * Initial parsing of the command line.

 */

static void __init parse_cmdline(char **cmdline_p, char *from)

{

       char c = ' ', *to = command_line;

       int len = 0;

 

       for (;;) {

              if (c == ' ') {

                     extern struct early_params __early_begin, __early_end; -à defined in “arch/arm/kernel/vmlinux.lds”, The space between __early_begin and __early_end is data with attribution “early_param.init”.

 

 

                     struct early_params *p;

 

                     for (p = &__early_begin; p < &__early_end; p++) {

                            int len = strlen(p->arg);

 

                            if (memcmp(from, p->arg, len) == 0) {

                                   if (to != command_line)

                                          to -= 1;

                                   from += len;

                                   p->fn(&from); -à call corresponding commandline-parsed funciton to extract information for assigned sign, eg. if “mem=” sign is found in command line, early_mem() function is invoked to parse memory information. Herein, the question comes out “How is the association between “mem=” sign and early_mem() functin created?

 

                                   while (*from != ' ' && *from != '\0')

                                          from++;

                                   break;

                            }

                     }

              }

              c = *from++;

              if (!c)

                     break;

              if (COMMAND_LINE_SIZE <= ++len)

                     break;

              *to++ = c;

       }

       *to = '\0';

       *cmdline_p = command_line;

}

/*

 * Pick out the memory size.  We look for mem=size@start,

 * where start and size are "size[KkMm]"

 */

static void __init early_mem(char **p)

{

       static int usermem __initdata = 0;

       unsigned long size, start;

 

       /*

        * If the user specifies memory size, we

        * blow away any automatically generated

        * size.

        */

       if (usermem == 0) {

              usermem = 1;

              meminfo.nr_banks = 0;

       }

 

       start = PHYS_OFFSET;

       size  = memparse(*p, p);

       if (**p == '@')

              start = memparse(*p + 1, p);

 

       arm_add_memory(start, size);

}

__early_param("mem=", early_mem);

 

#define __early_param(name,fn)                               \

static struct early_params __early_##fn __attribute_used__       \

__attribute__((__section__(".early_param.init"))) = { name, fn }

 

 

Up to now, the memory layout of the development board has been transferred to linux kernel stored in “struct meminfo” successfully. In the following lecture, we’ll delve into the prerequisites for the use of MMU.

 


References:

[1]

[2] http://blog.chinaunix.net/u1/46715/showart_367493.html

[3]

[4]

 

 
 
 
 
 
The doc introduces how linux kernel know about the memory layout of the development board.
文件: How do linux kernel know the memory configuration on the development board.rar
大小: 13KB
下载: 下载
阅读(3741) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~