在上一节我们已经介绍了u-boot启动linux内核的流程。这一节我们将对u-boot进行改造,使其支持linux-5.2.8版本内核启动。

linux kernel支持多种硬件,所谓内核移植概括的说,就是修改kernel中硬件相关的源码以适应自己的硬件。linux中硬件相关的代码主要集中在arch目录(体系结构相关、单板相关)以及drivers目录(设备驱动)。linux的移植对于产业链上下游的企业来说,要做的事情也不同,比如:

  • IP核厂商:以ARM为例,ARM负责提供指令集以及支持该指令集的硬件实现——CPU核(公版)。ARM在移植kernel的时候,需要实现体系结构相关的代码,比如kernel启动的汇编阶段;
  • SOC厂商:以ARM阵营为例,SOC厂商基于ARM的CPU核(当然也有自研的),添加一些片内外设形成自己的SOC。并且,一般还会基于自家的SOC做公版的开发板,从而方便推广自家产品。因此SOC厂商移植kernel的时候需要做的是,提供平台设备的驱动(即片内外设的驱动)、提供板级支持代码(arch/arm/mach-xxx)。
  • 外设芯片厂商:这里的外设芯片指的是诸如ADC、各类传感器芯片等,比如TI的at24c0x芯片,一个完整的产品需要SOC+各路板载外设协同工作。外设芯片厂商为了推广自己的芯片(降低下游开发成本),需要为芯片提供各种环境的驱动程序,对于linux,他们要做的就是为linux提供其芯片产品的驱动。
  • 电子产品厂商:下游应用厂商主要做方案整合(当然也有通吃全产业链的企业),即采购SOC+各种板载外设芯片,设计自己的电路板。他们要做的是,参考SOC厂商的公版开发板的板级支持代码,实现自己的板级支持代码。对于片内外设,根据SOC厂商的驱动来写相应的设备树;对于板载外设,根据外设芯片厂商的驱动来写设备树。所以底层这块,下游厂商其实发挥空间不大,底层支持主要还是看上游厂商,下游厂商的重点在于行业和应用。因此,网上有下游厂商的底软开发者调侃自己是“设备树工程师”。不过,即便是在下游厂商工作,熟悉kernel的原理也是比较重要的,毕竟你不能保证任何时候只用简单修改就能完成工作交付。

一、u-boot参数配置

我们将u-boot-2016.05-crop,复制一份命名为:u-boot-2016.05-linux。

Mini2440之linux内核移植

1.1 启动参数配置

在smdk2440.h(include/configs/smdk2440.h)文件中配置启动参数:

#define CONFIG_BOOTARGS   "root=/dev/mtdblock3 console=ttySAC0,115200  init=/linuxrc"
  • root:指定文件系统位置这里配置为NAND三号分区,也就是我们根文件系统所在的分区;
  • init:指定内核启动后执行的第一个应用程序;
  • console:指定使用哪个终端,这里的 ttySAC0 指的就是串口0;

1.2 支持yaffs2烧写

打开u-boot-2016.05-linux项目,进入nand的命令文件cmd/nand.c,在do_nand函数里,有nand read或write的代码,而其中有对jffs2的支持,却并没有对yaffs2的支持。以前的老版本uboot是有对yaffs文件系统烧写的支持的,于是我们参考老版本的uboot代码,在do_nand函数里的nand write/read部分加上一段代码,如下:

#ifdef CONFIG_CMD_NAND_TRIMFFS
        } else if (!strcmp(s, ".trimffs")) {
            if (read) {
                printf("Unknown nand command suffix '%s'\n", s);
                return 1;
            }
            ret = nand_write_skip_bad(nand, off, &rwsize, NULL,
                        maxsize, (u_char *)addr,
                        WITH_DROP_FFS | WITH_WR_VERIFY);
#endif
#ifdef CONFIG_CMD_NAND_YAFFS
        } else if (!strcmp(s, ".yaffs2")) {
            if (read) {
                printf("Unknown nand command suffix ‘%s‘.\n", s);
                return 1;
            }
            ret = nand_write_skip_bad(nand, off, &rwsize,NULL,  //这里参数老版本
                        maxsize,(u_char *)addr,
                        WITH_YAFFS_OOB);
#endif

在nand_help_text[]里添加nand write.yaffs的帮助信息:

#ifdef CONFIG_CMD_NAND_TRIMFFS
    "nand write.trimffs - addr off|partition size\n"
    "    write 'size' bytes starting at offset 'off' from memory address\n"
    "    'addr', skipping bad blocks and dropping any pages at the end\n"
    "    of eraseblocks that contain only 0xFF\n"
#endif
#ifdef CONFIG_CMD_NAND_YAFFS
    "nand write.yaffs2 - addr off|partition size\n"
    "    write 'size' bytes starting at offset 'off' with yaffs format\n"
    "    from memory address 'addr', skipping bad blocks.\n"
#endif  

nand_write_skip_bad函数内部也要修改,该函数位于drivers/mtd/nand/nand_util.c文件:

if (actual)
        *actual = 0;
#ifdef CONFIG_CMD_NAND_YAFFS
     if (flags & WITH_YAFFS_OOB) {
        if (flags & ~WITH_YAFFS_OOB)
            return -EINVAL;

        int pages;
        pages = nand->erasesize / nand->writesize;
        blocksize = (pages * nand->oobsize) + nand->erasesize;
        if (*length % (nand->writesize + nand->oobsize)) {
            printf ("Attempt to write incomplete page"
                " in yaffs mode\n");
            return -EINVAL;
        }
    } else
#endif
    {
        blocksize = nand->erasesize;
    }

    ...

if (left_to_write < (blocksize - block_offset))
        write_size = left_to_write;
     else
        write_size = blocksize - block_offset;
#ifdef CONFIG_CMD_NAND_YAFFS
    if (flags & WITH_YAFFS_OOB) {
        int page, pages;
        size_t pagesize = nand->writesize;
        size_t pagesize_oob = pagesize + nand->oobsize;
        struct mtd_oob_ops ops;

        ops.len = pagesize;
        ops.ooblen = nand->oobsize;
        ops.mode = MTD_OPS_RAW;       //这里要改为RAW
        ops.ooboffs = 0;

        pages = write_size / pagesize_oob;
        for (page = 0; page < pages; page++) {
            WATCHDOG_RESET();

        ops.datbuf = p_buffer;
        ops.oobbuf = ops.datbuf + pagesize;

        rval = nand->_write_oob(nand, offset, &ops);
        if (rval != 0)
             break;

             offset += pagesize;
             p_buffer += pagesize_oob;
            }
        }
        else
#endif
     {          //这里要加个左大括号
             truncated_write_size = write_size;
#ifdef CONFIG_CMD_NAND_TRIMFFS
         if (flags & WITH_DROP_FFS)
             truncated_write_size = drop_ffs(nand, p_buffer,
                     &write_size);
#endif

         rval = nand_write(nand, offset, &truncated_write_size,
                 p_buffer);

         if ((flags & WITH_WR_VERIFY) && !rval)
             rval = nand_verify(nand, offset,
                 truncated_write_size, p_buffer);

         offset += write_size;
         p_buffer += write_size;
         } //这里要加个右大括号
         if (rval != 0) {

同时,在include/nand.h中添加WITH_YAFFS_OOB宏的定义:

#define WITH_YAFFS_OOB  (1 << 0)
#define WITH_DROP_FFS   (1 << 0)

最后在配置文件里include/configs/smdk2440.h添加CONFIG_CMD_NAND_YAFFS宏定义,编译烧写,此uboot已经支持yaffs2文件系统的烧写。

#define CONFIG_CMD_NAND_YAFFS    /* 支持 nand write.yaffs2 - addr off|partition size 命令 */ 

1.3 启动命令配置

在smdk2440.h文件中配置启动命令:

#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; bootm 0x30000000" //bootcmd  

1.4 设置matchid

linux内核在启动时,是通过u-boot传入的机器码确定应启动哪种目标平台的。

u-boot在不设置machid环境变量时,u-boot会使用默认的机器id,默认id在board_init函数中设置,该函数位于board/samsung/smdk2440/smdk2440.c:

int board_init(void)
{
    /* arch number of SMDK2410-Board */
    gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;

    /* adress of boot parameters */
    gd->bd->bi_boot_params = 0x30000100;

    icache_enable();
    dcache_enable();

    return 0;
}

我们搜索MACH_TYPE_SMDK2410:

root@zhengyang:/work/sambashare/u-boot-2016.05-linux# grep "MACH_TYPE_SMDK2410" * -nR
arch/arm/include/asm/mach-types.h:59:#define MACH_TYPE_SMDK2410             193
arch/arm/include/asm/mach-types.h:1644:#  define machine_arch_type      MACH_TYPE_SMDK2410
arch/arm/include/asm/mach-types.h:1646:# define machine_is_smdk2410()   (machine_arch_type == MACH_TYPE_SMDK2410)
board/samsung/smdk2410/smdk2410.c:100:  gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
board/samsung/smdk2440/smdk2440.c:100:  gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;

我们在arch/arm/include/asm/mach-types.h新增一行:

#define MACH_TYPE_SMDK2410             193
#define MACH_TYPE_SMDK2440             168    // 新增的

在文件后面新增:

#ifdef CONFIG_ARCH_SMDK2440
# ifdef machine_arch_type
#  undef machine_arch_type
#  define machine_arch_type     __machine_arch_type
# else
#  define machine_arch_type     MACH_TYPE_SMDK2440
# endif
# define machine_is_smdk2440()  (machine_arch_type == MACH_TYPE_SMDK2440)
#else
# define machine_is_smdk2440()  (0)
#endif

并修改board_init函数:

   /* arch number of SMDK2440-Board */
    gd->bd->bi_arch_number = MACH_TYPE_SMDK2440;

1.5 编译下载

重新编译,下载u-boot到NAND FLASH:

make clean
make distclean
make smdk2440_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux- V=1

需要注意编译u-boot使用的是arm-linux-gcc4.3.2不要使用高版本,高版本编译出来的u-boot可能运行不了。

1.6 修改分区参数

Mini440之uboot移植之裁剪、分区与环境变量设置(五)中我们曾经设置分区参数如下:

/* mtdparts command line support */
#define MTDIDS_DEFAULT        "nand0=Mini2440-0"
/* default mtd partition table */
#define MTDPARTS_DEFAULT    "mtdparts=Mini2440-0:512k(u-boot)," \
                            "128k(params)," \
                            "4m(kernel)," \
                            "-(rootfs);" 

我们将u-boot分区设置为512kb,我们使用MiniTools下载u-boot、内核:

然后运行u-boot,串口输出如下信息:

 Mini2440之linux内核移植

可以发现并没有找到内核,这里我们分析一下原因。

我们直接烧入购买开发板时出厂的程序,然后启动linux:

Mini2440之linux内核移植

我们查看linux启动时输出的日志信息:

Mini2440之linux内核移植

可以发现分区情况和我们设置的不一致。我们发现内核起始地址是在0x60000,那么我么修改我们的分区配置include/configs/smdk2440.h:

/* mtdparts command line support */
#define MTDIDS_DEFAULT        "nand0=Mini2440-0"
/* default mtd partition table */
#define MTDPARTS_DEFAULT    "mtdparts=Mini2440-0:256k(u-boot)," \
                            "128k(params)," \
                            "4m(kernel)," \
                            "-(rootfs);" 

同时修改u-boot环境变量的保存地址:

/* 保存环境变量到NOR FLASH */
#if 0
#define CONFIG_ENV_ADDR            (CONFIG_SYS_FLASH_BASE + 0x040000)
#define CONFIG_ENV_IS_IN_FLASH
#define CONFIG_ENV_SIZE            0x10000
#else
/* 保存环境变量到NAND FLASH */
#define CONFIG_ENV_IS_IN_NAND        /* U-Boot env in NAND Flash  */
#define CONFIG_ENV_SIZE            0x20000        //128kb
#define CONFIG_ENV_OFFSET          0x40000        //给uboot预留256kb

然后重新编译u-boot下载运行(需要注意的一点,如果我们内核为zImage,应该使用go命令启动,uImage才是使用bootm命令启动):

#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; go 0x30000000" 

再次编译u-boot:直接运行如下命令即可:

make clean                      // 只清除.o文件和可执行文件
make distclean                  // 清理所有生成的文件,包括配置文件
make smdk2440_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux- V=1

下载u-boot.bin、zImage_P35到NAND FLASH运行,发现已经开始解压内核了,虽然还有其他错误,但是我们可以先忽略。

Mini2440之linux内核移植

这里在将内核从NAND FLASH 读取出来时出现这样的错误

NAND read from offset 60000 failed -74

我们再u-boot命令行模式下,尝试读取NAND 0x60000地址处数据,加载到内存:

SMDK2440 # nand read 0x30000000 0x60000 0x500000

NAND read: device 0 offset 0x60000, size 0x400000
NAND read from offset 60000 failed -74
 0 bytes read: ERROR

发现出现同样的错误。我们参考u-boot_2010.6 nandflash驱动彻底分析中的分析。下面进行具体分析nand read执行流程。

1.7 nand read 错误码-74

执行nand read 命令后,其实是执行了nand_read_skip_bad(nand, off, &size,(u_char *)addr);

跳过坏块读函数的参数简单明了,从哪读,读到哪去,读多少,以及一个公共句柄(包含nand的信息,例如有多少个块,块大小等)

我们定位到nand_read_skip_bad函数,位于drivers/mtd/nand/nand_util.c文件:

/**
 * nand_read_skip_bad:
 *
 * Read image from NAND flash.
 * Blocks that are marked bad are skipped and the next block is read
 * instead as long as the image is short enough to fit even after
 * skipping the bad blocks.  Due to bad blocks we may not be able to
 * perform the requested read.  In the case where the read would extend
 * beyond the end of the NAND device, both length and actual (if not
 * NULL) are set to 0.  In the case where the read would extend beyond
 * the limit we are passed, length is set to 0 and actual is set to the
 * required length.
 *
 * @param nand NAND device
 * @param offset offset in flash
 * @param length buffer length, on return holds number of read bytes
 * @param actual set to size required to read length worth of buffer or 0
 * on error, if not NULL
 * @param lim maximum size that actual may be in order to not exceed the
 * buffer
 * @param buffer buffer to write to
 * @return 0 in case of success
 */
int nand_read_skip_bad(nand_info_t *nand, loff_t offset, size_t *length,
        size_t *actual, loff_t lim, u_char *buffer)
{
    int rval;
    size_t left_to_read = *length;
    size_t used_for_read = 0;
    u_char *p_buffer = buffer;
    int need_skip;

    if ((offset & (nand->writesize - 1)) != 0) {
        printf("Attempt to read non page-aligned data\n");
        *length = 0;
        if (actual)
            *actual = 0;
        return -EINVAL;
    }

    need_skip = check_skip_len(nand, offset, *length, &used_for_read);

    if (actual)
        *actual = used_for_read;

    if (need_skip < 0) {
        printf("Attempt to read outside the flash area\n");
        *length = 0;
        return -EINVAL;
    }

    if (used_for_read > lim) {
        puts("Size of read exceeds partition or device limit\n");
        *length = 0;
        return -EFBIG;
    }

    if (!need_skip) {
        rval = nand_read(nand, offset, length, buffer);
        if (!rval || rval == -EUCLEAN)
            return 0;

        *length = 0;
        printf("NAND read from offset %llx failed %d\n",
            offset, rval);
        return rval;
    }

    while (left_to_read > 0) {
        size_t block_offset = offset & (nand->erasesize - 1);
        size_t read_length;

        WATCHDOG_RESET();

        if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) {
            printf("Skipping bad block 0x%08llx\n",
                offset & ~(nand->erasesize - 1));
            offset += nand->erasesize - block_offset;
            continue;
        }

        if (left_to_read < (nand->erasesize - block_offset))
            read_length = left_to_read;
        else
            read_length = nand->erasesize - block_offset;

        rval = nand_read(nand, offset, &read_length, p_buffer);
        if (rval && rval != -EUCLEAN) {
            printf("NAND read from offset %llx failed %d\n",
                offset, rval);
            *length -= left_to_read;
            return rval;
        }

        left_to_read -= read_length;
        offset       += read_length;
        p_buffer     += read_length;
    }

    return 0;
}

这里会调用nand_read从nand读取数据,而nand_read又调用nand_do_read_ops,该函数位于drivers/mtd/nand/nand_base.c:

/**
 * nand_do_read_ops - [INTERN] Read data with ECC
 * @mtd: MTD device structure
 * @from: offset to read from
 * @ops: oob ops structure
 *
 * Internal function. Called with chip held.
 */
static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
                struct mtd_oob_ops *ops)
{
    int chipnr, page, realpage, col, bytes, aligned, oob_required;
    struct nand_chip *chip = mtd->priv;
    int ret = 0;
    uint32_t readlen = ops->len;
    uint32_t oobreadlen = ops->ooblen;
    uint32_t max_oobsize = ops->mode == MTD_OPS_AUTO_OOB ?
        mtd->oobavail : mtd->oobsize;

    uint8_t *bufpoi, *oob, *buf;
    int use_bufpoi;
    unsigned int max_bitflips = 0;
    int retry_mode = 0;
    bool ecc_fail = false;

    chipnr = (int)(from >> chip->chip_shift);
    chip->select_chip(mtd, chipnr);

    realpage = (int)(from >> chip->page_shift);
    page = realpage & chip->pagemask;

    col = (int)(from & (mtd->writesize - 1));

    buf = ops->datbuf;
    oob = ops->oobbuf;
    oob_required = oob ? 1 : 0;

    while (1) {
        unsigned int ecc_failures = mtd->ecc_stats.failed;

        WATCHDOG_RESET();
        bytes = min(mtd->writesize - col, readlen);
        aligned = (bytes == mtd->writesize);

        if (!aligned)
            use_bufpoi = 1;
        else
            use_bufpoi = 0;

        /* Is the current page in the buffer? */
        if (realpage != chip->pagebuf || oob) {
            bufpoi = use_bufpoi ? chip->buffers->databuf : buf;

            if (use_bufpoi && aligned)
                pr_debug("%s: using read bounce buffer for buf@%p\n",
                         __func__, buf);

read_retry:
            chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);

            /*
             * Now read the page into the buffer.  Absent an error,
             * the read methods return max bitflips per ecc step.
             */
            if (unlikely(ops->mode == MTD_OPS_RAW))
                ret = chip->ecc.read_page_raw(mtd, chip, bufpoi,
                                  oob_required,
                                  page);
            else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) &&
                 !oob)
                ret = chip->ecc.read_subpage(mtd, chip,
                            col, bytes, bufpoi,
                            page);
            else
                ret = chip->ecc.read_page(mtd, chip, bufpoi,
                              oob_required, page);
            if (ret < 0) {
                if (use_bufpoi)
                    /* Invalidate page cache */
                    chip->pagebuf = -1;
                break;
            }

            max_bitflips = max_t(unsigned int, max_bitflips, ret);

            /* Transfer not aligned data */
            if (use_bufpoi) {
                if (!NAND_HAS_SUBPAGE_READ(chip) && !oob &&
                    !(mtd->ecc_stats.failed - ecc_failures) &&
                    (ops->mode != MTD_OPS_RAW)) {
                    chip->pagebuf = realpage;
                    chip->pagebuf_bitflips = ret;
                } else {
                    /* Invalidate page cache */
                    chip->pagebuf = -1;
                }
                memcpy(buf, chip->buffers->databuf + col, bytes);
            }

            if (unlikely(oob)) {
                int toread = min(oobreadlen, max_oobsize);

                if (toread) {
                    oob = nand_transfer_oob(chip,
                        oob, ops, toread);
                    oobreadlen -= toread;
                }
            }

            if (chip->options & NAND_NEED_READRDY) {
                /* Apply delay or wait for ready/busy pin */
                if (!chip->dev_ready)
                    udelay(chip->chip_delay);
                else
                    nand_wait_ready(mtd);
            }

            if (mtd->ecc_stats.failed - ecc_failures) {
                if (retry_mode + 1 < chip->read_retries) {
                    retry_mode++;
                    ret = nand_setup_read_retry(mtd,
                            retry_mode);
                    if (ret < 0)
                        break;

                    /* Reset failures; retry */
                    mtd->ecc_stats.failed = ecc_failures;
                    goto read_retry;
                } else {
                    /* No more retry modes; real failure */
                    ecc_fail = true;
                }
            }

            buf += bytes;
        } else {
            memcpy(buf, chip->buffers->databuf + col, bytes);
            buf += bytes;
            max_bitflips = max_t(unsigned int, max_bitflips,
                         chip->pagebuf_bitflips);
        }

        readlen -= bytes;

        /* Reset to retry mode 0 */
        if (retry_mode) {
            ret = nand_setup_read_retry(mtd, 0);
            if (ret < 0)
                break;
            retry_mode = 0;
        }

        if (!readlen)
            break;

        /* For subsequent reads align to page boundary */
        col = 0;
        /* Increment page address */
        realpage++;

        page = realpage & chip->pagemask;
        /* Check, if we cross a chip boundary */
        if (!page) {
            chipnr++;
            chip->select_chip(mtd, -1);
            chip->select_chip(mtd, chipnr);
        }
    }
    chip->select_chip(mtd, -1);

    ops->retlen = ops->len - (size_t) readlen;
    if (oob)
        ops->oobretlen = ops->ooblen - oobreadlen;

    if (ret < 0)
        return ret;

    if (ecc_fail)
        return -EBADMSG;

    return max_bitflips;
}
View Code

相关文章: