Mini2440之linux内核移植
在上一节我们已经介绍了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 源码修改
1.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.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.1.3 启动命令配置
在smdk2440.h文件中配置启动命令:
#define CONFIG_BOOTCOMMAND "nand read 0x30000000 kernel; bootm 0x30000000" //bootcmd
1.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.2 编译下载
重新编译,下载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.3 问题处理
等看了后面的内核编译,烧录内核到开发板,如果有问题再回过来看这里。
1.3.1 修改分区参数
在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、内核。
然后启动开发板,串口输出如下信息:
可以发现并没有找到内核,这里我们分析一下原因。
我们直接烧入购买开发板时出厂的程序,然后启动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.3.2 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; }
EBADMSG定义为74,不难看出最终执行到了:
if (ecc_fail) return -EBADMSG;
所以抛出了异常状态码-74。定位到ecc_fail设置为true的代码:
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; } }
那么大致阅读一下这个代码,我猜想应该是nand_do_read_ops在执行下面函数时,出现了问题:
ret = chip->ecc.read_page(mtd, chip, bufpoi, oob_required, page);
这里我直接在vs code搜索read_page,我们很快定位到了nand_scan_tail这个函数,这个函数也位于drivers/mtd/nand/nand_base.c文件,
我们直接定位到下面这段代码:

/** * nand_scan_tail - [NAND Interface] Scan for the NAND device * @mtd: MTD device structure * * This is the second phase of the normal nand_scan() function. It fills out * all the uninitialized function pointers with the defaults and scans for a * bad block table if appropriate. */ int nand_scan_tail(struct mtd_info *mtd) { int i; struct nand_chip *chip = mtd->priv; struct nand_ecc_ctrl *ecc = &chip->ecc; struct nand_buffers *nbuf; ... /* Set the internal oob buffer location, just after the page data */ chip->oob_poi = chip->buffers->databuf + mtd->writesize; /* * If no default placement scheme is given, select an appropriate one. */ if (!ecc->layout && (ecc->mode != NAND_ECC_SOFT_BCH)) { switch (mtd->oobsize) { case 8: ecc->layout = &nand_oob_8; break; case 16: ecc->layout = &nand_oob_16; break; case 64: ecc->layout = &nand_oob_64; break; case 128: ecc->layout = &nand_oob_128; break; default: pr_warn("No oob scheme defined for oobsize %d\n", mtd->oobsize); BUG(); } } if (!chip->write_page) chip->write_page = nand_write_page; /* * Check ECC mode, default to software if 3byte/512byte hardware ECC is * selected and we have 256 byte pagesize fallback to software ECC */ switch (ecc->mode) { case NAND_ECC_HW_OOB_FIRST: ... case NAND_ECC_HW: ... case NAND_ECC_HW_SYNDROME: ... case NAND_ECC_SOFT: ecc->calculate = nand_calculate_ecc; ecc->correct = nand_correct_data; ecc->read_page = nand_read_page_swecc; ecc->read_subpage = nand_read_subpage; ecc->write_page = nand_write_page_swecc; ecc->read_page_raw = nand_read_page_raw; ecc->write_page_raw = nand_write_page_raw; ecc->read_oob = nand_read_oob_std; ecc->write_oob = nand_write_oob_std; if (!ecc->size) ecc->size = 256; ecc->bytes = 3; ecc->strength = 1; break; case NAND_ECC_SOFT_BCH: ... case NAND_ECC_NONE: ... default: pr_warn("Invalid NAND_ECC_MODE %d\n", ecc->mode); BUG(); } ... return 0; }
还记的在Mini440之uboot移植之实践NAND启动(四)中我们介绍过board_nand_init函数,该函数开启了软件ecc校验:
nand->ecc.mode = NAND_ECC_SOFT;
因此:ecc->read_page = nand_read_page_swecc,nand_read_page_swecc在drivers/mtd/nand/nand_base.c文件中定义:
/** * nand_read_page_swecc - [REPLACEABLE] software ECC based page read function * @mtd: mtd info structure * @chip: nand chip info structure * @buf: buffer to store read data * @oob_required: caller requires OOB data read to chip->oob_poi * @page: page number to read */ static int nand_read_page_swecc(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page) { int i, eccsize = chip->ecc.size; int eccbytes = chip->ecc.bytes; int eccsteps = chip->ecc.steps; uint8_t *p = buf; uint8_t *ecc_calc = chip->buffers->ecccalc; uint8_t *ecc_code = chip->buffers->ecccode; uint32_t *eccpos = chip->ecc.layout->eccpos; unsigned int max_bitflips = 0; chip->ecc.read_page_raw(mtd, chip, buf, 1, page); for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) chip->ecc.calculate(mtd, p, &ecc_calc[i]); for (i = 0; i < chip->ecc.total; i++) ecc_code[i] = chip->oob_poi[eccpos[i]]; eccsteps = chip->ecc.steps; p = buf; for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { int stat; stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]); if (stat < 0) { mtd->ecc_stats.failed++; } else { mtd->ecc_stats.corrected += stat; max_bitflips = max_t(unsigned int, max_bitflips, stat); } } return max_bitflips; }
这个带有软件ecc校验的NAND页数据读取函数,这个函数在从NAND读完一页数据后会进行软件ecc校验,如果校验失败就会修改mtd->ecc_stats.failed,从而导致后面执行了:
ecc_fail = true;
在连续失败chip->read_retry次后,将会跳出循环,不再进行NAND数据读取。
为了不抛出-74这个异常,我们可以尝试关闭ecc校验,通过修改board_nand_init,注释掉下面代码,即关闭软件ecc:
// nand->ecc.mode = NAND_ECC_SOFT;
此时nand->ecc.mode默认采用NAND_ECC_NONE:
case NAND_ECC_NONE: pr_warn("NAND_ECC_NONE selected by board driver. This is not recommended!\n"); ecc->read_page = nand_read_page_raw; ecc->write_page = nand_write_page_raw; ecc->read_oob = nand_read_oob_std; ecc->read_page_raw = nand_read_page_raw; ecc->write_page_raw = nand_write_page_raw; ecc->write_oob = nand_write_oob_std; ecc->size = mtd->writesize; ecc->bytes = 0; ecc->strength = 0; break;
此时在进行按页读写时就不会进行软件ecc校验了。
/** * nand_read_page_raw - [INTERN] read raw page data without ecc * @mtd: mtd info structure * @chip: nand chip info structure * @buf: buffer to store read data * @oob_required: caller requires OOB data read to chip->oob_poi * @page: page number to read * * Not for syndrome calculating ECC controllers, which use a special oob layout. */ static int nand_read_page_raw(struct mtd_info *mtd, struct nand_chip *chip, uint8_t *buf, int oob_required, int page) { chip->read_buf(mtd, buf, mtd->writesize); if (oob_required) chip->read_buf(mtd, chip->oob_poi, mtd->oobsize); return 0; }
我们编译u-boot、烧写进入NAND,再次启动u-boot,引导Linux启动,此时错误信息变成了:
内核在运行的时候ecc校验失败了。后面我们会自己生成uImage,而不是使用出厂自带的zImage。
1.4 测试NAND读取功能
在u-boot引导内核启动前,我们尝试按下键盘任意键,进入u-boot命令行模式。
查看NAND第一页数据:
SMDK2440 # nand dump 0x00 0x800 Page 00000000 dump: be 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5 60 00 f0 33 c0 00 f0 33 20 01 f0 33 80 01 f0 33 e0 01 f0 33 40 02 f0 33 a0 02 f0 33 ef be ad de de c0 ad 0b 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 28 d0 1f e5 00 e0 8d e5 00 e0 4f e1 04 e0 8d e5 13 d0 a0 e3 0d f0 69 e1 0f e0 a0 e1 0e f0 b0 e1 48 d0 4d e2 ff 1f 8d e8 50 20 1f e5 0c 00 92 e8 48 00 8d e2 34 50 8d e2 0e 10 a0 e1 0f 00 85 e8 0d 00 a0 e1 52 04 00 eb 00 00 a0 e1 00 00 a0 e1 ...
可以直接读取NAND FLASH数据到内存来验证该数据:
mw.b 0x30000000 ff 0x800 #清除内存 nand read 0x30000000 0x00 0x800 #读数据到内存 md 0x30000000 0x800 #显示内存
运行结果如下:
SMDK2440 # mw.b 0x30000000 ff 0x800 SMDK2440 # nand read 0x30000000 0x00 0x800 NAND read: device 0 offset 0x0, size 0x800 2048 bytes read: OK SMDK2440 # md 0x30000000 0x800 30000000: ea0000be e59ff014 e59ff014 e59ff014 ................ 30000010: e59ff014 e59ff014 e59ff014 e59ff014 ................ 30000020: 33f00060 33f000c0 33f00120 33f00180 `..3...3 ..3...3 30000030: 33f001e0 33f00240 33f002a0 deadbeef ...3@..3...3.... 30000040: 0badc0de e1a00000 e1a00000 e1a00000 ................ 30000050: e1a00000 e1a00000 e1a00000 e1a00000 ................ 30000060: e51fd028 e58de000 e14fe000 e58de004 (.........O..... 30000070: e3a0d013 e169f00d e1a0e00f e1b0f00e ......i......... 30000080: e24dd048 e88d1fff e51f2050 e892000c H.M.....P ...... 30000090: e28d0048 e28d5034 e1a0100e e885000f H...4P.......... 300000a0: e1a0000d eb000452 e1a00000 e1a00000 ....R........... ...
可以看到nand dump与nand read指定地址到内存中的数据是一样的。这也表明在关闭ecc校验的情况下,我们能够断定移植的u-boot可以对NAND FLASH进行正确的读和写操作。
二、移植linux内核
2.1 内核源码下载
内核源码下载地址为:https://www.kernel.org/,这里我们不要下载最新的,最新的里面已经没有s3c24xx系列的默认配置了:
也可以到内核镜像网址下载https://mirrors.edge.kernel.org/pub/linux/kernel/,这里下载速度更快。
如果下载速度太慢,无法下载,提供另一个链接:http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/。
我们这里下载linux-5.2.8版本,虚拟机ubuntu系统运行:
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.2.8.tar.gz
将源码解压:
tar -zxvf linux-5.2.8.tar.gz
2.2 linux内核目录结构
解压就可以得到全部linux内核源程序,目录结构如下:
下面列出了顶层目录下各级目录存放原则:
- arch:体系结构相关的代码,比如 arm、 avr32、 m68k 等,我们现在用的是 ARM 芯片,所以只需要关心 arm 文件夹即可;
- block:块设备相关的通用函数;
- crypto:常用加密和散列算法(如 AES、SHA等),还有一些压缩和CRC校验算法;
- drivers:所有的设备驱动程序,里面每一个子目录对应一类驱动程序,比如 drivers/block/ 为块设备驱动程序,drivers/char/为字符设备驱动程序,drivers/mtd/为nor flash、nand flash 等存储设备的驱动程序;
- Documentation:Linux内核的使用帮助文档;
- fs:Linux 支持的文件系统的代码,每个子目录对应一种文件系统,比如 fs/jffs2/、fs/ext2/、fs/ext4/等;
- include:内核头文件,有基本头文件(存放在 include/linux/目录下)、各种驱动或功能部件的头文件(如 include/media/、include/video/、include/net等)、 各种体系相关的头文件(如 include/asm-generic/等);
- init:内核的初始化代码(不是系统的引导代码),其中的 main.c 文件中的 start_kernel 函数是内核引导后运行的第一个函数;
- ipc:进程间通信的代码;
- kernel:内核管理的核心代码;
- lib:内核用到的一些库函数代码,如 crc32.c、string.c、shal.c等,这类文件夹中的内容移植时基本不用管;
- mm:内存管理代码;
- net:网络支持代码,每个子目录对应子网络的一个方面;
- samples:一些示例程序,如断点调试,功能测试等;
- scripts:用于配置、编译内核的脚本文件
- security:安全、密钥相关的代码;
- sound:音频设备驱动程序;
- tools:工具类代码,比如 USB 传输等,通常会将 u-boot 下生成的mkimage工具放到此目录下;
- usr:忽略即可;
- virt:忽略即可;
2.3 配置Makefile
修改顶层的 Makefile,打开 Makefile 文件,找到下面语句:
ARCH ?= $(SUBARCH)
修改为:
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-
其中,ARCH 是指定目标平台为arm,CROSS_COMPILE是指定交叉编译器,这里指定的 是系统默认的交叉编译器,如要使用其它的,则要把编译器的全路径在这里写出。
2.4 内核s3c2440_defconfig配置
接下来要做的就是内核配置、编译了。单板的默认配置文件在arch/arm/configs 目录下,如果没有2440相关的默认配置,可以选择比较相近的2410的配置修改,这里我直接选择s3c2410_defconfig,并没有选择mini2440_defconfig。
配置文件s3c2410_defconfig支持很多单板,包括2440、2410,其定义如下:

CONFIG_SYSVIPC=y CONFIG_IKCONFIG=m CONFIG_IKCONFIG_PROC=y CONFIG_LOG_BUF_SHIFT=16 CONFIG_BLK_DEV_INITRD=y CONFIG_SLAB=y CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y # CONFIG_BLK_DEV_BSG is not set CONFIG_PARTITION_ADVANCED=y CONFIG_BSD_DISKLABEL=y CONFIG_SOLARIS_X86_PARTITION=y CONFIG_ARCH_S3C24XX=y CONFIG_CPU_S3C2412=y CONFIG_CPU_S3C2416=y CONFIG_CPU_S3C2440=y CONFIG_CPU_S3C2442=y CONFIG_CPU_S3C2443=y CONFIG_MACH_AML_M5900=y CONFIG_ARCH_BAST=y CONFIG_ARCH_H1940=y CONFIG_MACH_N30=y CONFIG_MACH_OTOM=y CONFIG_MACH_QT2410=y CONFIG_ARCH_SMDK2410=y CONFIG_MACH_TCT_HAMMER=y CONFIG_MACH_VR1000=y CONFIG_MACH_JIVE=y CONFIG_MACH_SMDK2412=y CONFIG_MACH_VSTMS=y CONFIG_MACH_SMDK2416=y CONFIG_MACH_ANUBIS=y CONFIG_MACH_AT2440EVB=y CONFIG_MACH_MINI2440=y CONFIG_MACH_NEXCODER_2440=y CONFIG_MACH_OSIRIS=y CONFIG_MACH_OSIRIS_DVS=m CONFIG_MACH_RX3715=y CONFIG_ARCH_S3C2440=y # 会链接mach-smdk2440.o CONFIG_MACH_NEO1973_GTA02=y CONFIG_MACH_RX1950=y CONFIG_MACH_SMDK2443=y CONFIG_S3C_ADC=y CONFIG_ZBOOT_ROM_TEXT=0x0 CONFIG_ZBOOT_ROM_BSS=0x0 CONFIG_CMDLINE="root=/dev/hda1 ro init=/bin/bash console=ttySAC0" CONFIG_FPE_NWFPE=y CONFIG_FPE_NWFPE_XP=y CONFIG_APM_EMULATION=m CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_XFRM_USER=m CONFIG_NET_KEY=m CONFIG_INET=y CONFIG_IP_MULTICAST=y CONFIG_IP_PNP=y CONFIG_IP_PNP_DHCP=y CONFIG_IP_PNP_BOOTP=y CONFIG_NET_IPIP=m CONFIG_INET_AH=m CONFIG_INET_ESP=m CONFIG_INET_IPCOMP=m CONFIG_TCP_CONG_ADVANCED=y CONFIG_TCP_CONG_HSTCP=m CONFIG_TCP_CONG_HYBLA=m CONFIG_TCP_CONG_SCALABLE=m CONFIG_TCP_CONG_LP=m CONFIG_TCP_CONG_VENO=m CONFIG_TCP_CONG_YEAH=m CONFIG_TCP_CONG_ILLINOIS=m CONFIG_IPV6_ROUTER_PREF=y CONFIG_INET6_AH=m CONFIG_INET6_ESP=m CONFIG_INET6_IPCOMP=m CONFIG_IPV6_MIP6=m CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION=m CONFIG_IPV6_TUNNEL=m CONFIG_NETFILTER=y CONFIG_NF_CONNTRACK=m CONFIG_NF_CONNTRACK_EVENTS=y CONFIG_NF_CONNTRACK_AMANDA=m CONFIG_NF_CONNTRACK_FTP=m CONFIG_NF_CONNTRACK_H323=m CONFIG_NF_CONNTRACK_IRC=m CONFIG_NF_CONNTRACK_NETBIOS_NS=m CONFIG_NF_CONNTRACK_PPTP=m CONFIG_NF_CONNTRACK_SANE=m CONFIG_NF_CONNTRACK_SIP=m CONFIG_NF_CONNTRACK_TFTP=m CONFIG_NF_CT_NETLINK=m CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m CONFIG_NETFILTER_XT_TARGET_CONNMARK=m CONFIG_NETFILTER_XT_TARGET_LED=m CONFIG_NETFILTER_XT_TARGET_LOG=m CONFIG_NETFILTER_XT_TARGET_MARK=m CONFIG_NETFILTER_XT_TARGET_NFLOG=m CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m CONFIG_NETFILTER_XT_TARGET_TCPMSS=m CONFIG_NETFILTER_XT_MATCH_CLUSTER=m CONFIG_NETFILTER_XT_MATCH_COMMENT=m CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m CONFIG_NETFILTER_XT_MATCH_CONNMARK=m CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m CONFIG_NETFILTER_XT_MATCH_DCCP=m CONFIG_NETFILTER_XT_MATCH_DSCP=m CONFIG_NETFILTER_XT_MATCH_ESP=m CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m CONFIG_NETFILTER_XT_MATCH_HELPER=m CONFIG_NETFILTER_XT_MATCH_IPRANGE=m CONFIG_NETFILTER_XT_MATCH_LENGTH=m CONFIG_NETFILTER_XT_MATCH_LIMIT=m CONFIG_NETFILTER_XT_MATCH_MAC=m CONFIG_NETFILTER_XT_MATCH_MARK=m CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m CONFIG_NETFILTER_XT_MATCH_OWNER=m CONFIG_NETFILTER_XT_MATCH_POLICY=m CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m CONFIG_NETFILTER_XT_MATCH_QUOTA=m CONFIG_NETFILTER_XT_MATCH_RATEEST=m CONFIG_NETFILTER_XT_MATCH_REALM=m CONFIG_NETFILTER_XT_MATCH_RECENT=m CONFIG_NETFILTER_XT_MATCH_SCTP=m CONFIG_NETFILTER_XT_MATCH_STATE=m CONFIG_NETFILTER_XT_MATCH_STATISTIC=m CONFIG_NETFILTER_XT_MATCH_STRING=m CONFIG_NETFILTER_XT_MATCH_TCPMSS=m CONFIG_NETFILTER_XT_MATCH_TIME=m CONFIG_NETFILTER_XT_MATCH_U32=m CONFIG_IP_VS=m CONFIG_NF_CONNTRACK_IPV4=m CONFIG_IP_NF_IPTABLES=m CONFIG_IP_NF_MATCH_AH=m CONFIG_IP_NF_MATCH_ECN=m CONFIG_IP_NF_MATCH_TTL=m CONFIG_IP_NF_FILTER=m CONFIG_IP_NF_TARGET_REJECT=m CONFIG_IP_NF_NAT=m CONFIG_IP_NF_TARGET_MASQUERADE=m CONFIG_IP_NF_TARGET_NETMAP=m CONFIG_IP_NF_TARGET_REDIRECT=m CONFIG_IP_NF_MANGLE=m CONFIG_IP_NF_TARGET_CLUSTERIP=m CONFIG_IP_NF_TARGET_ECN=m CONFIG_IP_NF_TARGET_TTL=m CONFIG_IP_NF_RAW=m CONFIG_IP_NF_ARPTABLES=m CONFIG_IP_NF_ARPFILTER=m CONFIG_IP_NF_ARP_MANGLE=m CONFIG_NF_CONNTRACK_IPV6=m CONFIG_IP6_NF_IPTABLES=m CONFIG_IP6_NF_MATCH_AH=m CONFIG_IP6_NF_MATCH_EUI64=m CONFIG_IP6_NF_MATCH_FRAG=m CONFIG_IP6_NF_MATCH_OPTS=m CONFIG_IP6_NF_MATCH_HL=m CONFIG_IP6_NF_MATCH_IPV6HEADER=m CONFIG_IP6_NF_MATCH_MH=m CONFIG_IP6_NF_MATCH_RT=m CONFIG_IP6_NF_TARGET_HL=m CONFIG_IP6_NF_FILTER=m CONFIG_IP6_NF_TARGET_REJECT=m CONFIG_IP6_NF_MANGLE=m CONFIG_IP6_NF_RAW=m CONFIG_BT=m CONFIG_BT_RFCOMM=m CONFIG_BT_RFCOMM_TTY=y CONFIG_BT_BNEP=m CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=m CONFIG_BT_HCIUART=m CONFIG_BT_HCIUART_BCSP=y CONFIG_BT_HCIUART_LL=y CONFIG_BT_HCIBCM203X=m CONFIG_BT_HCIBPA10X=m CONFIG_BT_HCIBFUSB=m CONFIG_BT_HCIVHCI=m CONFIG_CFG80211=m CONFIG_MAC80211=m CONFIG_MAC80211_MESH=y CONFIG_MAC80211_LEDS=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_MTD=y CONFIG_MTD_REDBOOT_PARTS=y CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED=y CONFIG_MTD_CMDLINE_PARTS=y CONFIG_MTD_BLOCK=y CONFIG_MTD_CFI=y CONFIG_MTD_JEDECPROBE=y CONFIG_MTD_CFI_INTELEXT=y CONFIG_MTD_CFI_AMDSTD=y CONFIG_MTD_ROM=y CONFIG_MTD_RAW_NAND=y CONFIG_MTD_NAND_S3C2410=y CONFIG_PARPORT=y CONFIG_PARPORT_PC=m CONFIG_PARPORT_AX88796=m CONFIG_PARPORT_1284=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_NBD=m CONFIG_BLK_DEV_RAM=y CONFIG_ATA_OVER_ETH=m CONFIG_EEPROM_AT24=y CONFIG_BLK_DEV_SD=y CONFIG_CHR_DEV_ST=m CONFIG_BLK_DEV_SR=y CONFIG_BLK_DEV_SR_VENDOR=y CONFIG_CHR_DEV_SG=y CONFIG_CHR_DEV_SCH=m CONFIG_SCSI_CONSTANTS=y CONFIG_SCSI_SCAN_ASYNC=y CONFIG_ATA=y CONFIG_PATA_PLATFORM=y CONFIG_NETDEVICES=y CONFIG_DM9000=y CONFIG_INPUT_EVDEV=y CONFIG_MOUSE_APPLETOUCH=m CONFIG_MOUSE_BCM5974=m CONFIG_INPUT_JOYSTICK=y CONFIG_JOYSTICK_ANALOG=m CONFIG_JOYSTICK_A3D=m CONFIG_JOYSTICK_ADI=m CONFIG_JOYSTICK_COBRA=m CONFIG_JOYSTICK_GF2K=m CONFIG_JOYSTICK_GRIP=m CONFIG_JOYSTICK_GRIP_MP=m CONFIG_JOYSTICK_GUILLEMOT=m CONFIG_JOYSTICK_INTERACT=m CONFIG_JOYSTICK_SIDEWINDER=m CONFIG_JOYSTICK_TMDC=m CONFIG_JOYSTICK_IFORCE=m CONFIG_JOYSTICK_MAGELLAN=m CONFIG_JOYSTICK_SPACEORB=m CONFIG_JOYSTICK_SPACEBALL=m CONFIG_JOYSTICK_STINGER=m CONFIG_JOYSTICK_TWIDJOY=m CONFIG_JOYSTICK_ZHENHUA=m CONFIG_JOYSTICK_DB9=m CONFIG_JOYSTICK_GAMECON=m CONFIG_JOYSTICK_TURBOGRAFX=m CONFIG_JOYSTICK_JOYDUMP=m CONFIG_JOYSTICK_XPAD=m CONFIG_JOYSTICK_XPAD_FF=y CONFIG_JOYSTICK_XPAD_LEDS=y CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_USB_COMPOSITE=m CONFIG_INPUT_MISC=y CONFIG_INPUT_ATI_REMOTE2=m CONFIG_INPUT_KEYSPAN_REMOTE=m CONFIG_INPUT_POWERMATE=m CONFIG_INPUT_YEALINK=m CONFIG_INPUT_CM109=m CONFIG_INPUT_UINPUT=m CONFIG_INPUT_GPIO_ROTARY_ENCODER=m CONFIG_SERIAL_NONSTANDARD=y CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_NR_UARTS=8 CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_MANY_PORTS=y CONFIG_SERIAL_8250_SHARE_IRQ=y CONFIG_SERIAL_SAMSUNG=y CONFIG_SERIAL_SAMSUNG_CONSOLE=y CONFIG_SERIAL_DEV_BUS=m CONFIG_PRINTER=y CONFIG_PPDEV=y CONFIG_HW_RANDOM=y CONFIG_I2C_CHARDEV=m CONFIG_I2C_S3C2410=y CONFIG_I2C_SIMTEC=y CONFIG_SPI=y CONFIG_SPI_GPIO=m CONFIG_SPI_S3C24XX=m CONFIG_SPI_SPIDEV=m CONFIG_SPI_TLE62X0=m CONFIG_SENSORS_LM75=m CONFIG_SENSORS_LM78=m CONFIG_SENSORS_LM85=m CONFIG_WATCHDOG=y CONFIG_S3C2410_WATCHDOG=y CONFIG_MFD_SM501=y CONFIG_TPS65010=y CONFIG_FB=y CONFIG_FIRMWARE_EDID=y CONFIG_FB_MODE_HELPERS=y CONFIG_FB_S3C2410=y CONFIG_FB_SM501=y CONFIG_BACKLIGHT_PWM=m CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_SEQUENCER=m CONFIG_SND_MIXER_OSS=m CONFIG_SND_PCM_OSS=m CONFIG_SND_SEQUENCER_OSS=y CONFIG_SND_VERBOSE_PRINTK=y # CONFIG_SND_DRIVERS is not set # CONFIG_SND_ARM is not set # CONFIG_SND_SPI is not set CONFIG_SND_USB_AUDIO=m CONFIG_SND_USB_CAIAQ=m CONFIG_SND_SOC=y # CONFIG_USB_HID is not set CONFIG_USB=y CONFIG_USB_MON=y CONFIG_USB_OHCI_HCD=y CONFIG_USB_ACM=m CONFIG_USB_PRINTER=m CONFIG_USB_WDM=m CONFIG_USB_STORAGE=m CONFIG_USB_STORAGE_DATAFAB=m CONFIG_USB_STORAGE_FREECOM=m CONFIG_USB_STORAGE_ISD200=m CONFIG_USB_STORAGE_USBAT=m CONFIG_USB_STORAGE_SDDR09=m CONFIG_USB_STORAGE_SDDR55=m CONFIG_USB_STORAGE_JUMPSHOT=m CONFIG_USB_STORAGE_ALAUDA=m CONFIG_USB_STORAGE_ONETOUCH=m CONFIG_USB_STORAGE_KARMA=m CONFIG_USB_STORAGE_CYPRESS_ATACB=m CONFIG_USB_MDC800=m CONFIG_USB_MICROTEK=m CONFIG_USB_USS720=m CONFIG_USB_SERIAL=y CONFIG_USB_SERIAL_GENERIC=y CONFIG_USB_SERIAL_FTDI_SIO=y CONFIG_USB_SERIAL_NAVMAN=m CONFIG_USB_SERIAL_PL2303=y CONFIG_USB_SERIAL_OPTION=m CONFIG_USB_EMI62=m CONFIG_USB_EMI26=m CONFIG_USB_ADUTUX=m CONFIG_USB_SEVSEG=m CONFIG_USB_RIO500=m CONFIG_USB_LEGOTOWER=m CONFIG_USB_LCD=m CONFIG_USB_CYPRESS_CY7C63=m CONFIG_USB_CYTHERM=m CONFIG_USB_IDMOUSE=m CONFIG_USB_FTDI_ELAN=m CONFIG_USB_APPLEDISPLAY=m CONFIG_USB_LD=m CONFIG_USB_TRANCEVIBRATOR=m CONFIG_USB_IOWARRIOR=m CONFIG_USB_TEST=m CONFIG_MMC=y CONFIG_SDIO_UART=m CONFIG_MMC_TEST=m CONFIG_MMC_SDHCI=m CONFIG_MMC_SPI=m CONFIG_MMC_S3C=y CONFIG_LEDS_S3C24XX=m CONFIG_LEDS_PCA9532=m CONFIG_LEDS_GPIO=m CONFIG_LEDS_PCA955X=m CONFIG_LEDS_DAC124S085=m CONFIG_LEDS_PWM=m CONFIG_LEDS_BD2802=m CONFIG_LEDS_TRIGGER_TIMER=m CONFIG_LEDS_TRIGGER_HEARTBEAT=m CONFIG_LEDS_TRIGGER_GPIO=m CONFIG_LEDS_TRIGGER_DEFAULT_ON=m CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_S3C=y CONFIG_DMADEVICES=y CONFIG_S3C24XX_DMAC=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT2_FS_SECURITY=y CONFIG_EXT3_FS=y CONFIG_EXT3_FS_POSIX_ACL=y CONFIG_AUTOFS4_FS=m CONFIG_FUSE_FS=m CONFIG_ISO9660_FS=y CONFIG_JOLIET=y CONFIG_UDF_FS=m CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y CONFIG_NTFS_FS=m CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_CONFIGFS_FS=m CONFIG_JFFS2_FS=y CONFIG_JFFS2_SUMMARY=y CONFIG_CRAMFS=y CONFIG_SQUASHFS=m CONFIG_ROMFS_FS=y CONFIG_NFS_FS=y CONFIG_NFS_V3_ACL=y CONFIG_ROOT_NFS=y CONFIG_NFSD=m CONFIG_NFSD_V3_ACL=y CONFIG_NFSD_V4=y CONFIG_CIFS=m CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_CODEPAGE_737=m CONFIG_NLS_CODEPAGE_775=m CONFIG_NLS_CODEPAGE_850=y CONFIG_NLS_CODEPAGE_852=m CONFIG_NLS_CODEPAGE_855=m CONFIG_NLS_CODEPAGE_857=m CONFIG_NLS_CODEPAGE_860=m CONFIG_NLS_CODEPAGE_861=m CONFIG_NLS_CODEPAGE_862=m CONFIG_NLS_CODEPAGE_863=m CONFIG_NLS_CODEPAGE_864=m CONFIG_NLS_CODEPAGE_865=m CONFIG_NLS_CODEPAGE_866=m CONFIG_NLS_CODEPAGE_869=m CONFIG_NLS_CODEPAGE_936=m CONFIG_NLS_CODEPAGE_950=m CONFIG_NLS_CODEPAGE_932=m CONFIG_NLS_CODEPAGE_949=m CONFIG_NLS_CODEPAGE_874=m CONFIG_NLS_ISO8859_8=m CONFIG_NLS_CODEPAGE_1250=m CONFIG_NLS_CODEPAGE_1251=m CONFIG_NLS_ASCII=y CONFIG_NLS_ISO8859_1=y CONFIG_NLS_ISO8859_2=m CONFIG_NLS_ISO8859_3=m CONFIG_NLS_ISO8859_4=m CONFIG_NLS_ISO8859_5=m CONFIG_NLS_ISO8859_6=m CONFIG_NLS_ISO8859_7=m CONFIG_NLS_ISO8859_9=m CONFIG_NLS_ISO8859_13=m CONFIG_NLS_ISO8859_14=m CONFIG_NLS_ISO8859_15=m CONFIG_NLS_KOI8_R=m CONFIG_NLS_KOI8_U=m CONFIG_NLS_UTF8=m CONFIG_DEBUG_INFO=y CONFIG_MAGIC_SYSRQ=y CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_MUTEXES=y CONFIG_DEBUG_USER=y CONFIG_DEBUG_LL=y
在linux内核根目录下执行如下命令,执行完之后会在内核根目录下生成默认配置文件.config:
make s3c2410_defconfig
然后可以通过make menuconfig修改配置:
make menuconfig
2.4.1 配置EABI编译属性
因为arm-none-linux-gnueabi 4.8.3(这个版本的编译器后面会介绍)使用了EABI方式,所以这就需要内核同样配置EABI编译属性:
Kernel Features ---> [*] Use the ARM EABI to compile the kernel [*] Allow old ABI binaries to run with this kernel (EXPERIMENTAL)
修改完之后,保存文件,输入文件名:
在当前路径下生成s3c2440_defconfig:
存档:
mv s3c2440_defconfig ./arch/arm/configs/
存档之后,下次如果需要重新编译配置直接运行make s3c2440_defconfig,避免了重新进行menuconfig配置。
在arch/arm/mach-s3c24xx目录下有个mach-smdk2440.c文件,这个文件是三星厂商提供的smdk2440开发版对应的示例程序。后面我们会对这个文件进行修改来适配Mini2440开发板。
2.5 源码修改
2.5.1 修改时钟频率
所以修改 arch/arm/mach-s3c24xx/mach-smdk2440.c:
static void __init smdk2440_init_time(void) { s3c2440_init_clocks(12000000); samsung_timer_init(); }
打开 arch/arm/mach-s3c24xx/common-smdk.c 文件,仿照u-boot的分区,修改代码如下:
/* NAND parititon from 2.4.18-swl5 */ static struct mtd_partition smdk_default_nand_part[] = { [0] = { .name = "u-boot", .size = SZ_256K, .offset = 0, }, [1] = { .name = "params", .offset = MTDPART_OFS_APPEND, .size = SZ_128K, }, [2] = { .name = "kermel", .offset = MTDPART_OFS_APPEND, .size = SZ_4M, }, [3] = { .name = "rootfs", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } };
256MB大小的NAND被分成四个分区:
- 0x00000000~0x00040000:256kb存放u-boot;
- 0x00040000~0x00060000: 128kb存放环境变量;
- 0x00060000~0x00460000:4MB存放linux内核;
- 0x00460000~0x10000000:剩下空间存放根文件系统;
上面部分宏的定义位于 include/linux/mtd/partitions.h 文件中,如下所示:
#define MTDPART_OFS_RETAIN (-3) #define MTDPART_OFS_NXTBLK (-2) #define MTDPART_OFS_APPEND (-1) #define MTDPART_SIZ_FULL (0)
2.5.3 关闭ecc软件校验
linux内核在启动的时候回对NAND FLASH进行ecc校验,如果有坏块将会导致ecc检验不通过,导致内核启动失败:
print_req_error: I/O error, dev mtdblock3, sector 0 flags 0 Buffer I/O error on dev mtdblock3, logical block 0, async page read __nand_correct_data: uncorrectable ECC error print_req_error: I/O error, dev mtdblock3, sector 8 flags 0 Buffer I/O error on dev mtdblock3, logical block 1, async page read __nand_correct_data: uncorrectable ECC error print_req_error: I/O error, dev mtdblock3, sector 16 flags 0 Buffer I/O error on dev mtdblock3, logical block 2, async page read print_req_error: I/O error, dev mtdblock3, sector 24 flags 0 Buffer I/O error on dev mtdblock3, logical block 3, async page read print_req_error: I/O error, dev mtdblock3, sector 0 flags 0 FAT-fs (mtdblock3): unable to read boot sector VFS: Cannot open root device "mtdblock3" or unknown-block(31,3): error -5 Please append a correct "root=" boot option; here are the available partitions:
解决方法是禁止NAND FLASH进行软件ecc校验。
修改arch/arm/mach-s3c24xx/common-smdk.c文件:
/* choose a set of timings which should suit most 512Mbit * chips and beyond. */ static struct s3c2410_platform_nand smdk_nand_info = { .tacls = 20, .twrph0 = 60, .twrph1 = 20, .nr_sets = ARRAY_SIZE(smdk_nand_sets), .sets = smdk_nand_sets, .ecc_mode = NAND_ECC_SOFT, };
将NAND_ECC_SOFT改为NAND_ECC_NONE,这个去掉ecc校验的问题,在内核中明确说明是不建议这样做的,因为这样就等于忽略了对NAND FLASH坏块的检测。
2.5.4 设置matchid
linux内核在启动时,是通过u-boot传入的机器码确定应启动哪种目标平台的。
如何确定linux内核机器id呢?在linux-5.2.8/arch/arm/mach-s3c24xx/mach-smdk2440.c中,struct machine_desc定义如下:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END
显然,SMDK2440使用的机器id是MACH_TYPE_S3C2440。具体的数字可以在arch/arm/tools/mach-types文件中找到(kernel在编译过程中会根据此文件生成相应的头文件供源码使用),具体数字是0x16A。
s3c2440 ARCH_S3C2440 S3C2440 362
这里需要修改为:
s3c2440 ARCH_S3C2440 S3C2440 168
168是u-boot里设置的,这个机器码需要跟u-boot中的机器码相对应,要不然u-boot无法引导启动内核,如果你不知道uboot中的机器码是多少,在uboot命令行中输入命令bdinfo查看。
SMDK2440 # bdinfo
arch_number = 0x000000A8
boot_params = 0x30000100
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x04000000
eth0name = dm9000
ethaddr = 08:00:3e:26:0a:5b
current eth = dm9000
ip_addr = 192.168.0.188
baudrate = 115200 bps
TLB addr = 0x33FF0000
relocaddr = 0x33F00000
reloc off = 0x00000000
irq_sp = 0x33AFFEF0
sp start = 0x33AFFEE0
2.6 编译内核制作uImage
先运行如下命令,查看编译是否出错:
make V=s
如果出现下面错误:
原因是libssl-dev没有安装,使用sudo apt-get install libssl-dev来安装libssl-dev:
sudo apt-get install libssl-dev
如果出现类似下面的错误:
cc1: error: unrecognized command line option "..."
一般是由于较新的内核使用了新版本交叉编译器的特性,而我本地安装的交叉编译器版本较低导致,需要升级版本,这个单独小节介绍:
在内核根路径下运行命令:
make V=1 uImage
出现如下错误:
说明缺少 mkimage ,有两种解决办法:
- 利用uboot生成mkimage工具,然后拷贝到/usr/bin 目录下
- 输入 sudo apt-get install u-boot-tools 命令在线安装;
本文选择第二种方法,输入命令:
sudo apt-get install u-boot-tools
2.7 arm-linux-gcc 4.8.3交叉编译环境安装(内核编译失败安装)
我之前使用的为4.3.2版本,在编译高版本linux时出现错误,这里我将会将交叉编译环境升级到4.8.3:
编译器可以在ARM官网下载:https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads ,或者到http://releases.linaro.org/components/toolchain/binaries/下载。
选择编译器时有一点需要注意,我们选择的是arm-none-linux-guneabi-gcc编译器:
- arm-none-eabi-gcc (ARM architecture,no vendor,not target an operating system,complies with the ARM EABI): 用于编译 ARM 架构的裸机系统(包括 ARM Linux 的 boot、kernel,不适用编译 Linux 应用 Application), 一般适合 ARM7、Cortex-M 和 Cortex-R 内核的芯片使用,所以不支持那些跟操作系统关系密切的函数, 比如fork(2),它使用的是 newlib 这个专用于嵌入式系统的C库。
- arm-none-linux-gnueabi-gcc (ARM architecture, no vendor, creates binaries that run on the Linux operating system, and uses the GNU EABI) :主要用于基于ARM架构的Linux系统,可用于编译 ARM 架构的 u-boot、Linux内核、linux应用等。 arm-none-linux-gnueabi基于GCC,使用Glibc库,经过 Codesourcery 公司优化过推出的编译器。 arm-none-linux-gnueabi-xxx 交叉编译工具的浮点运算非常优秀。一般ARM9、ARM11、Cortex-A 内 核,带有 Linux 操作系统的会用到。
此外,关于EABI和ABI也是比较重要的,EABI(嵌入式应用二进制接口)和 ABI(应用程序二进制接口)都是二进制接口的标准,但是它们的应用场景不同;
- ABI 通常指操作系统、处理器等平台在二进制级别上的接口标准。它定义了操作系统内核、库函数和应用程序之间的接口规范,如函数调用、参数传递、返回值、系统调用等等。具体来说,一个 ABI 定义了编译器生成的可执行文件需要满足的规范,以便在特定平台和特定环境下运行。例如,x86 架构上的 Windows ABI 和 Linux ABI 在一些细节上可能有所不同,因为它们使用了不同的寄存器、调用约定等等。在不同 ABI 之间移植代码时,需要注意这些差异并进行相应的修改;
- EABI 则是专门针对嵌入式系统的 ABI 标准。与通用的 ABI 不同,EABI 更关注于嵌入式系统对于可移植性和交叉编译的需求。它定义了编译器生成的二进制代码与嵌入式系统之间的接口规范,包括函数调用、参数传递、返回值等方面,并支持软件浮点数运算。因此,通过 EABI 的规范,可以保证在不同的嵌入式系统之间生成的二进制代码具有良好的可移植性和兼容性。这样,在从一个嵌入式平台迁移到另一个平台时,只需要重新编译源代码即可,而不需要修改程序的源代码。
总之,ABI 和 EABI 都是二进制接口标准,它们都定义了二进制程序和操作系统之间的接口规范,但是应用场景不同。ABI 更关注通用平台上的二进制接口规范,而 EABI 则是专门针对嵌入式系统开发的二进制接口标准。
更多不同编译器之间的区别参考ARM交叉编译器GNUEABI、NONE-EABI、ARM-EABI、GNUEABIHF等的区别。
由于官网下载比较慢,这里我们直接从gitee上下载arm-none-linux-gnueabi 4.8.3:
git clone https://gitee.com/zyly2033/arm-none-linux-gnueabi-4.8.3.git
将其拷贝到/usr/local/arm路径下:
mv arm-none-linux-gnueabi-4.8.3 4.8.3 mv 4.8.3 /usr/local/arm
这样在/usr/local/arm下就有我们安装的三个不同版本的编译器:
接下来配置系统环境变量,把交叉编译工具链的路径添加到环境变量PATH中去,这样就可以在任何目录下使用这些工具:
编辑profile文件,添加环境变量:
vim /etc/profile
添加如下代码:
export PATH=$PATH:/usr/local/arm/4.8.3/bin
同时注释掉4.3.2、4.4.3版本的配置:
接下来使用命令:source /etc/profile,使修改后的profile文件生效。
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了