全部博文(75)
分类: 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=
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]
|