Monday, October 17, 2011

'mkimage' V.S. U-Boot headers, 合并uzImage.bin和cramfs.bin的方法

Ref: http://scyangzhu.wordpress.com/page/3/
Ref: http://www.freeors.com/bbs/forum.php?mod=viewthread&tid=11149
Ref: http://blog.chinaunix.net/space.php?uid=13701930&do=blog&cuid=279896
Ref: http://blog.csdn.net/niuniumenghua/article/details/6490832


以下这种方法最后合并出的文件不是通常uClinux“认为”的标准镜像文件,而且一定要修改bootload代码。请慎用此方法。


(一)、为什么要合并uzImage.bin和cramfs.bin?

一、为了升级时安全、方便

安全。当系统升级程序拿到升级文件时,它必然要判断该文件合法性。uzImage.bin有crc检查,判断起来很容易,cramfs.bin是不带的,有难度。如果两个合为一个,然后使用加在uzImage.bin上的crc机制,那合法性检查上就简单了。

方便。两个合为一个,升级时只须一个文件。

二、节省flash占用空间

分为uzImage.bin和cramfs.bin,则意味着必须给flash人为地分出两个空间,而且这两个空间大小被事先固定。像4M字节 flash,uzImage.bin占用从0x040000开始的1M字节,cramfs.bin占用从0x140000开始的2,752K字节。

既分在两个区就使得分配时不得不存在一个权衡问题,代码不可能写得“恰好”占用。像对于uzImage.bin,这个版本可能是恰好占用,但代码写多了都 知道,功能上的删、加、修改是不确定的,下个版块可能就要因一个意外修改而导致不得不修改uzImage.bin。为解决这个问题,可以使用在 bootload环境变量中写上两区边界方法,但这种方法怎么说也没只是一个文件时方便,而且再“恰好”也肯定是有一定字节“空隙”。


(二)、如何合并

合并采用的方法分为宿主机链接时和uClinux的bootload加载时两个部分。

注:image.0000.img表示最终形成的单个镜像文件。

宿主机链接时

当拿到uzImage.bin.gz(这里多了个gz,这个文件不是最后mkimage后的,而是在mkimage之前gzip之后)和cramfs.bin,链接时执行:

cat $(cramfs.bin) $(uzImage.bin.gz) > $(uzImage.gz)
./mkimage -A arm -O linux -T kernel -C gzip -a <加载地址> -e <执行地址> -n "uClinux Kernel for xxxx" -d $(uzImage.gz) $(IMAGEDIR)/image.0000.img

简单来说,就是在mkimage uzImage.bin.gz之前,先用cat命令合并uzImage.bin.gz和cramfs.bin,合并时cramfs.bin必须放在前头。

cramfs.bin为何要放在前头?

cramfs.bin文件的[4h---7h]四字节存储了该cramfs.bin的文件长度。gzip之后的uzImage.bin.gz无法自识别长度。

至此形成的image.0000.img文件结构

image_header_t:mkImage添加的文件头,固定64字节
cramfs.bin:cramfs.bin部分,它的长度可由偏移68--71字节处得到,假设是cramfs_len。
uzImage.bin:uzImage.bin部分。它在image.0000.img开始址址是64+cramfs_len,它的长度则可以由image_head_t中的有效数据长度字段值减去cramfs_len得到。

uClinux的bootload加载时

加载还是要分两次进行,分别加载uzImage.bin和cramfs.bin。

当运行到要加载uzImage.bin时。开始地址:image.0000.img开始地址加上64+cramfs_len;长度:image_head_t中的有效数据长度字段值减去cramfs_len。

当运行到要加载cramfs.bin时。开始地址:image.0000.img开始地址加上64。长度:image.0000.img偏移处68--71字节处得到的值。


(三)、几点补充

一、检查镜像文件合法性上沿用原先给uzImage.bin的crc机制,可以说不增加较验上开销。

二、在flash向ram加载效率上,只是多了几个判断,和原先分两个文件加载方法上可说一样的执行效率。


=================================================
=================================================

一个实例:对bootload改动


bootload准备好flash和sdram可通信后,它接下执行:

1):把uzImage.bin从flash加载到sdram;
2):把cramfs.bin从flash加载到sdram;
3):把矢量表加载到sdram零处(u-boot代码中,该0x0地址是一个向量表,第一条指令跳转branch到复位代码start_codeDepend on Processor)
4):运行uClinux内核

bootcmd环境变量表示以上四个过程:
    bootcmd=cp.l fc040000 01800000 40000; cp.l fc140000 02000000 AC000; cp.l fc3f8000 0 e;bootm 01800000
    bootm命令是用来引导经过u-boot的工具mkimage打包后的kernel image的,什么叫做经过u-boot的工具mkimage打包后的kernel image,这个就要看mkimage的代码,看看它做了些什么,虽然我很希望大家不要偷懒,认真地去看看,但是我知道还是有很多人懒得去做这件,那么我 就j将分析mkimage代码后得到的总结告诉大家,mkimage做了些什么,怎么用这个工具。

    mkimage的用法
    • uboot源代码的tools/目录下有mkimage工具,这个工具可以用来制作不压缩或者压缩的多种可启动映象文件。
    • mkimage在制作映象文件的时候,是在原来的可执行映象文件的前面加上一个0x40字节的头,记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置, 入口点在内存的那个位置以及映象名是什么
    root@Glym:/tftpboot# ./mkimage
    Usage: ./mkimage -l image
    -l ==> list image header information
    ./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
    -A ==> set architecture to 'arch'
    -O ==> set operating system to 'os'
    -T ==> set image type to 'type'
    -C ==> set compression type 'comp'
    -a ==> set load address to 'addr' (hex)
    -e ==> set entry point to 'ep' (hex)
    -n ==> set image name to 'name'
    -d ==> use image data from 'datafile'
    -x ==> set XIP (execute in place)
    参数说明:
    -A 指定CPU的体系结构:
    取值 表示的体系结构
    alpha Alpha
    arm A RM
    x86 Intel x86
    ia64 IA64
    mips MIPS
    mips64 MIPS 64 Bit
    ppc PowerPC
    s390 IBM S390
    sh SuperH
    sparc SPARC
    sparc64 SPARC 64 Bit
    m68k MC68000
    -O 指定操作系统类型,可以取以下值:
    openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos
    -T 指定映象类型,可以取以下值:
    standalone、kernel、ramdisk、multi、firmware、script、filesystem
    -C 指定映象压缩方式,可以取以下值:
    none 不压缩
    gzip 用gzip的压缩方式
    bzip2 用bzip2的压缩方式
    -a 指定映象在内存中的加载地址,映象下载到内存中时,要按照用mkimage制作映象时,这个参数所指定的地址值来下载
    -e 指定映象运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
    -n 指定映象名
    -d 指定制作映象的源文件

    cp.l是个复制命令,cp==>copy;l==>long,数量以4字节单位,以上的bootcmd类似下以命令
    注:以下的flash地址像fc040000,完全大于了4M/8M,那时因为flash被map后它的起始地址就是0xfc000000,所以fc040000对应的其实是flash上的0x40000地址。
    flash-2-mem(0xfc040000, 0x01800000, 0x40000 << 2);
    flash-2-mem(0xfc140000, 0x02000000, 0xac000 << 2);
    flash-2-mem(0xfc3f8000, 0x00000000, 0xe << 2);
    bootm(0x1800000)
    以下就是从flash加载到sdram函数。uzImage.bin、cramfs.bin、矢量表被分次调用。
    int do_mem_cp(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
    {
            ulong addr, dest, count;
            int        size;
            image_header_t* hdr;
            if (argc != 4) {
                    printf ("Usage:\n%s\n", cmdtp->usage);
                    return 1;
            }
            /* Check for size specification.
            */
            if ((size = cmd_get_data_size(argv[0], 4)) < 0)
                    return 1;
            addr = simple_strtoul(argv[1], NULL, 16);
            addr += base_address;
            dest = simple_strtoul(argv[2], NULL, 16);
            dest += base_address;
            count = simple_strtoul(argv[3], NULL, 16);
            if (count == 0) {
                    puts ("Zero length ???\n");
                    return 1;
            }
            //
            // 由于使用了把uzImage.bin和cramfs.bin合并到一个文件,而在命令行上又保持不变,就须要修正一些变量意义。
            //
            if (addr == 0xfc040000) {
                    // fc040000, flash(uzImage.bin)-->ram
                    // dest = 0x01008000;
                    // 读出cramfs.bin文件长度
                    cramfs_len = *(ulong *)(0xfc040000 + sizeof(image_header_t) + 4);
                    printf("addr == 0xfc040000, this is uzImage.bin, base_address: %lu, cramfs_len: %lu\n", base_address, cramfs_len);
                    hdr = (image_header_t*)addr;
                    count = sizeof(image_header_t) / size;        // sizeo = 4, sizeof(image_header_t)一般0x40字节,可以被4整除
                    // 复制头
                    while (count-- > 0) {
                            if (size == 4)
                                    *((ulong  *)dest) = *((ulong  *)addr);
                            else if (size == 2)
                                    *((ushort *)dest) = *((ushort *)addr);
                            else
                                    *((u_char *)dest) = *((u_char *)addr);
                            addr += size;
                            dest += size;
                    }
                    // 从image_header_t中取出数据长度(count = cramfs.bin + uzImage.bin.gz)
                    count = SWAP_LONG(hdr->ih_size); // ntohl(ih_size)
                   
                    // 注意,此时的addr已经变成, 函数参数中的addr + sizeof(image_header_t)
                    addr = addr + cramfs_len;
                    count = (count - cramfs_len) / size + 1; // cp.l时,size是4, uzImage.bin有效长度会由gzip压缩机制保证(4M flash)或只要至少比有效多就行(8M flash),这里多复制四个字节总不会出错
    #ifndef USE_4M_FLASH
                    // 对于uzImage.bin来说,为减少字节占用一般使用gzip压缩(大概可以达到2:1压缩率),使整个软件能约束在4M字节以内。
                    // 因为使用gzip压缩,bootload必须对uzImage.bin进行解压,这个过程有时较耗时间,为减少启动时间宁愿采用不压缩而使用8M字节flash
                    // 这里就是8M情况
                    // 对于8M, uzImage.bin没经过压缩, 这里就直接复制到解压后的uzImage.bin应该放到的地址, 省掉do_bootm()中的memcpy
                    dest = SWAP_LONG(hdr->ih_load);
    #endif
                   
            } else if ((addr == 0xfc140000) && cramfs_len) {
                    // fc140000, flash(cramfs.bin)-->ram
                    // 总是假定uzImage.bin已被下载
                    printf("addr == 0xfc140000, this is cramfs.bin, base_address: %lu, cramfs_len: %lu\n", base_address, cramfs_len);
                    addr = 0xfc040000 + sizeof(image_header_t);        // addr要回到flash开始内核开始处: 0xfc040000
                    count = cramfs_len / size;
            } else if (addr == 0xfc3f8000) {
                    // 矢量表。这个才60字节,直接是以内存数组表示
                    printf("addr == 0xfc3f8000, this is vector, count: %lu\n", count);
                    addr = vectordata;
            }
            while (count-- > 0) {
                    if (size == 4)
                            *((ulong  *)dest) = *((ulong  *)addr);
                    else if (size == 2)
                            *((ushort *)dest) = *((ushort *)addr);
                    else
                            *((u_char *)dest) = *((u_char *)addr);
                    addr += size;
                    dest += size;
            }
            return 0;
    }
    =================================================


    No comments:

    Post a Comment