This session is dedicated to the latest set of solutions to this problem. How does the Kernel mount its Root ? This is the message your want to see VFS: Mounted root (romfs filesystem) readonly.
This is where it came from... linux/init/do_mounts.c: printk("VFS: Mounted root (%s filesystem)%s./n",
But how did the kernel get there and what needs to be set up to make it all happen. Here is the code that tries to mount root. linux/init/do_mounts.c: mount_block_root(char *name, int flags)
this looks through all the fs_names ands tries to mount one of them
name is an input root name /dev/root
fs_names is a list of possible file systems
either from the "rootfstype=" cmdline option of from the
list obtained from get_filesystem_list
root_mount_data is derived from the "rootflags=" cmdline option
This is used to specify a Read Only root mount
for (p = fs_names; *p; p += strlen(p)+1) {
int err = sys_mount(name, "/root", p, flags, root_mount_data);
....
The sys_mount call can fail in a number of ways
if err == 0 we are done root have been mounted
if err == -EACCES we try a read only option
if err == -EINVAL we give up with this one.
if we have gone through all the possible file systems and could not
mount one then we give the Kernel Panic message.
Command Line root= Option The other main command line option is root=/dev/mtd0 or root=1f:00. This will override the natural search for mounted file systems and go directly to the chosen device. What does all this mean ? During the system init a number of file systems were "discovered". These were typically systems compiled into the kernel that were initialized during the boot process. As a test, add some debug code code to mount_block_root in linux/init/do_mounts.c to see some of what is happening here. static void __init mount_block_root(char *name, int flags)
{
char *fs_names = __getname();
char *p;
get_fs_names(fs_names);
// New debug code here
printk("*******/nVFS: test name = <%s> /n",name);
for (p = fs_names; *p; p += strlen(p)+1) {
printk("VFS: fs_name = <%s> /n",p);
}
printk("VFS: root name <%s> /n*******/n",kdevname(ROOT_DEV));
//End of new debug code
retry:
for (p = fs_names; *p; p += strlen(p)+1) {
int err = sys_mount(name, "/root", p, flags, root_mount_data);
// More debug code here ( just a single line )
printk("VFS: tried fs_name = <%s> err= %d/n",p,err);
switch (err) {
Here are the results on one of my target systems. *******
VFS: test name = </dev/root>
VFS: fs_name = <ext2>
VFS: fs_name = <romfs>
VFS: root name <1f:02>
*******
mtdblock_open
ok
mtdblock_release
ok
VFS: tried fs_name = <ext2> err= -22
mtdblock_open
ok
VFS: tried fs_name = <romfs> err= 0
This means that :
- the kernel had two file systems in the kernel.
- /dev/root is the chosen root device (more later).
- mounting /dev/root failed as an ext2 filesystem
- mounting /dev/root passed as an romfs filesystem
What is /dev/root ? Good question ... You can also ask what is ROOT_DEV. On the particular target system used for this test ( an ArmTwister ) there was no /dev/root specified. It would not matter even if there was a /dev/root on the file system since that file system is not yet mounted. There is a command line option to set up ROOT_DEV but this was not used in this case. ROOT_DEV did have a value, however, so where was this set up. To answer this you have to look at the kernel init sequence and discover where romfs for example was set up. This used to be done in linux/drivers/block/blkmem.c but funnily enough this file was NOT included in this kernel build. The new MTD flash device drivers were used exclusively. I am writing this article because I wanted to trace how this new MTD root system worked. Even stranger, the root file system is working from a RAM area not a Flash Device. I thought that the MTD driver only worked on Flash Devices !! Lets look at ROOT_DEV first. this search through the kernel gave me some clues find linux/ -name "*.c" | xargs grep "ROOT_DEV" | more
Here are some search results linux/arch/armnommu/kernel/arch.c: ROOT_DEV=MKDEV(RAMDISK_MAJOR,0);
linux/arch/armnommu/kernel/setup.c: ROOT_DEV=to_kdev_t(tag->u.core.rootdev);
linux/arch/armnommu/kernel/setup.c: ROOT_DEV=to_kdev_t(params-u1.s.rootdev);
linux/arch/armnommu/kernel/setup.c: ROOT_DEV=MKDEV(0, 255);
linux/drivers/mtd/maps/uclinux.c: //ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 0);
linux/drivers/mtd/maps/uclinux.c: ROOT_DEV=MKDEV(MTD_BLOCK_MAJOR, mtd->index);
linux/fs/nfs/nfsroot.c: ROOT_DEV=MKDEV(UNNAMED_MAJOR, 255);
linux/init/do_mounts.c: create_dev("/dev/root", ROOT_DEV, root_device_name);
ROOT_DEV is manipulated in various places as the system tries to default its choice of root file system. I'll pick out the most interesting ones. linux/drivers/mtd/maps/uclinux.c: ROOT_DEV=MKDEV(MTD_BLOCK_MAJOR, mtd->index);
linux/init/do_mounts.c: create_dev("/dev/root", ROOT_DEV, root_device_name);
This means that linux/drivers/mtd/maps/uclinux.c set ROOT_DEV up when it was setting up its partitions. The missing /dev/root is explained in linux/init/do_mounts.c, it was simply created for us. A QUICK TIMEOUT When setting you the MTD device you select a whole bundle of options in make menuconfig. All this will change as you change these options. You can, however, still use the techniques I mention here to find out how your system works. Here are the config options chosen for this system. # this is the command used to find the important options
grep MTD linux/.config | grep =y
# these are the results. //with brief comments ? = I think
CONFIG_MTD=y //use the MTD file system
CONFIG_MTD_DEBUG=y // debug
CONFIG_MTD_PARTITIONS=y // use partitions ?
CONFIG_MTD_CMDLINE_PARTS=y // read partitions from command line
CONFIG_MTD_CHAR=y // allow char device access
CONFIG_MTD_BLOCK_RO=y // allow ro block access
CONFIG_MTD_CFI=y // Common (huh?) Flash Interface
CONFIG_MTD_JEDECPROBE=y // use JEDEC Probe
CONFIG_MTD_GEN_PROBE=y // use GEN probe
CONFIG_MTD_CFI_AMDSTD=y // look for AMD devices
CONFIG_MTD_RAM=y // allow MTD_RAM ..... hmm
CONFIG_MTD_PHYSMAP=y // include physmap for flash
CONFIG_MTD_UCLINUX=y // include the uclinux.c ram mapping file
Knowing the above information allowed the search for important files to be narrowed down quickly. MTD Partitions So now take a closer look at linux/drivers/mtd/maps/uclinux.c: . This is one of the files where the partitions are set up for the MTD file system. (Note the other file is physmap.c but more on that later ) Let's step through this code. int __init uclinux_mtd_init(void)
{
struct mtd_info *mtd;
struct map_info *mapp;
//extern char _ebss; // the system used to use _ebss to
// find the rom start
// The armtwister changed this to
// use flashstart defined in the link map
extern char _flashstart;
mapp = &uclinux_ram_map; // set up the partition map.
In the next section we are setting up the map_priv_2 structures with the start address and size of the map. Note that the 3rd word in the romFS image is the size. //mapp->map_priv_2 = (unsigned long) &_ebss;
//mapp->size = PAGE_ALIGN(*((unsigned long *)((&_ebss) + 8)));
mapp->map_priv_2 = (unsigned long) &_flashstart;
mapp->size = PAGE_ALIGN(*((unsigned long *)((&_flashstart) + 8)));
mapp->buswidth = 4;
printk("uclinux[mtd]: RAM probe address=0x%x size=0x%x/n",
(int) mapp->map_priv_2, (int) mapp->size);
// now remap map_priv_1 as the address space.
mapp->map_priv_1 = (unsigned long)
ioremap_nocache(mapp->map_priv_2, mapp->size);
if (mapp->map_priv_1 == 0) {
printk("uclinux[mtd]: ioremap_nocache() failed/n");
return(-EIO);
}
The location and size of the romfs has been found. Now probe the map and get an mtd structure. In this case the probe is non destructive. // normally probes for the device
// then sets up the mtd structure
// the probe is destructive so it should
// be modified.
mtd = do_map_probe("map_ram", mapp);
if (!mtd) {
printk("uclinux[mtd]: failed to find a mapping?/n");
iounmap((void *) mapp->map_priv_1);
return(-ENXIO);
}
Set up more of the structure.
mtd->module = THIS_MODULE;
mtd->point = uclinux_point;
mtd->priv = mapp;
uclinux_ram_mtdinfo = mtd;
The kernel command line now sets up the partitions. // this is now done on the kernel command line
//xx add_mtd_partitions(mtd, uclinux_romfs, NUM_PARTITIONS);
add_mtd_device(mtd);
//printk("uclinux[mtd]: set %s to be root filesystem/n",
// uclinux_romfs[0].name);
//ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 0);
// use this mtd device for the root_dev
printk("uclinux[mtd]: set %s to be root filesystem index = %d/n",
uclinux_romfs[0].name,mtd->index);
ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
put_mtd_device(mtd);
return(0);
}
This is neat the uclinux.c map creates a ram map and finds the addresses and then creates out ROOT_DEV for us. This is "Fine" for ArmTwister Now How Do I use it ? Good question. You have two options here.
- Use _ebss to find the start of the romfs.
- Use an absolute flash partition specified in the command line.
- Use an absolute memory location specified in the command line.
Using _ebss The setting up of _ebss can be quite difficult. You have to modify the linker script to locate the label. Here is an example # extract from file
# linux/arch/armnommu/vmlinux-armv.lds.in
# Note _ebss is commented out here.
.bss : {
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
_end = . ;
/* _ebss = . ; */ /* added PSW */
}
Using partition in the command line Note This may be an "ArmTwister only" modification. The file linux/drivers/mtd/maps/physmap.c has been adapted to accept a command line mapping specification. The command line could look like mtdparts=physmap:256k(ARMboot)ro,-(testpart)
//file linux/drivers/mtd/maps/physmap.c
//function init_physmap(void)
#ifdef CONFIG_MTD_CMDLINE_PARTS
if (parsed_nr_parts == 0) {
int ret = parse_cmdline_partitions(mymtd,
&parsed_parts, "physmap");
if (ret > 0) {
part_type = "Command Line";
parsed_nr_parts = ret;
}
}
#endif
This will take the command line shown and create two partitions. dev: size erasesize name
mtd0: 00040000 00010000 "ARMboot"
mtd1: 001c0000 00010000 "testpart"
If you want to specify a partition as a root fs partition, under this setup, you may choose to create a different layout. mtdparts=physmap:256k(ARMboot)ro,768k(Image),-(Rom fs)ro
The booted partitions now look like dev: size erasesize name
mtd0: 00040000 00010000 "ARMboot"
mtd1: 000c0000 00010000 "Image"
mtd2: 00100000 00010000 "Romfs"
Adding a final statement to the command line mtdparts=physmap:256k(ARMboot)ro,768k(Image),-(Rom fs)ro root=1f:02
Will force the system to use the Romfs flash partition. Specifying an absolute location You may want to do this in a debug situation where you want to update the kernel and the romfs separately. A quick and dirty way of doing this would be to add a rootaddr= option to the command line and add this to do_mounts.c This serves as a mini example of how to add a kernel command line option. // declare the root addr kernel cmdline option
unsigned long root_addr= 0;
static int __init root_add_setup(char *line)
{
root_addr = simple_strtol(line,NULL,0);
printk(" set up root_addr as 0x%x/n",root_addr);
return 1;
}
__setup("rootaddr=", root_add_setup); //psw
Then test for root_addr to be non-zero in uclinux.c // near the top of the file
extern long root_addr;
In the mtd_init section... // in the function uclinux_mtd_init(void)
int use_root_addr = 0;
if ( root_addr != 0 ) {
if (strncmp((char *) root_addr, "-rom1fs-", 8) == 0) {
printk(" RA found a possible rootfs at 0x%x/n",root_addr);
use_root_addr = 1
} else {
printk(" RA no rootfs at 0x%x/n",root_addr);
}
}
if (strncmp((char *) &_flashstart, "-rom1fs-", 8) == 0) {
printk(" FS found a possible rootfs at 0x%x/n",&_flashstart);
} else {
printk(" FS no rootfs at 0x%x/n",&_flashstart);
}
Having done all this it would be safe to switch to the rootfs specified in the command line. You would use the value in root_addr rather than the value in _flashstart in the map_priv_2 if ( use_root_addr == 1 ) {
mapp->map_priv_2 = (unsigned long) root_addr;
mapp->size = PAGE_ALIGN(*((unsigned long *)((root_addr) + 8)));
} else {
mapp->map_priv_2 = (unsigned long) &_flashstart;
mapp->size = PAGE_ALIGN(*((unsigned long *)((&_flashstart) + 8)));
}
What is an ArmTwister ? Check out ArmTwister.com What about cramfs Cramfs is an excellent way to get root file system compression. it occupies about 50% of the space of a normal romfs. It will only use memory on demand as each file is used. The compressed pages are kept in Flash until the kernel tries to access the page. Only at that time is the code extracted into Memory on a page by page basis. I only did a quick test on CramFs and if failed to run on my Kernel. It is probably something stupid I have done. I'll be looking at that in early Feb 2003. Have fun. Phil Wilshire SDCS System Design & Consulting Services philwil@sysdcs.com
Version 0.9: Jan 10 , 2003
Author: Phil Wilshire: System Design & Consulting Services LLC
Email: philwil@sysdcs.com
Revisions: To be added
|