在上一节我们已经介绍了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。
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,串口输出如下信息:
可以发现并没有找到内核,这里我们分析一下原因。
我们直接烧入购买开发板时出厂的程序,然后启动linux:
我们查看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运行,发现已经开始解压内核了,虽然还有其他错误,但是我们可以先忽略。
这里在将内核从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; }