最近调试嵌入式升级swupdate开源工具。
添加了在swupdate/handlers中添加了yaffs_handle.c
原工程文件在flash_erase和flash_write_image函数中没有详细关于nand flash oob区域的管理操作。需要加上这一部分的处理。
yaffs_handle.c代码如下:
/*
* (C) Copyright 2014-2016
* Stefano Babic, DENX Software Engineering, sbabic@denx.de.
*
* Hamming code from
* https://github.com/martinezjavier/writeloader
* Copyright (C) 2011 ISEE 2007, SL
* Author: Javier Martinez Canillas <martinez.javier@gmail.com>
* Author: Agusti Fontquerni Gorchs <afontquerni@iseebcn.com>
* Overview:
* Writes a loader binary to a NAND flash memory device and calculates
* 1-bit Hamming ECC codes to fill the MTD's out-of-band (oob) area
* independently of the ECC technique implemented on the NAND driver.
* This is a workaround required for TI ARM OMAP DM3730 ROM boot to load.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <linux/version.h>
#include <sys/ioctl.h>
#include <mtd/mtd-user.h>
#include <mtd/jffs2-user.h>
#include "swupdate.h"
#include "handler.h"
#include "util.h"
#include "flash.h"
#include "progress.h"
#define PROCMTD "/proc/mtd"
#define LINESIZE 80
#define YAFFS_FS_TYPE 1
#define FILESYSTEM_OOB YAFFS_FS_TYPE
#define MAX_PAGE_SIZE 8192
#define MAX_OOB_SIZE 512
#define DEBUG_BSTAR(a,x...) do{fprintf(stderr, "%s %s %d:"a, __FILE__, __FUNCTION__, __LINE__, ##x);}while(0)
static int jffs2 = 0; // format for jffs2 usage
static bool pad = false;
static int quiet = 0;
static struct jffs2_unknown_node cleanmarker;
static unsigned char writebuf[MAX_PAGE_SIZE];
static unsigned char oobbuf[MAX_OOB_SIZE];
void yaffs_handler(void);
/* Check whether buffer is filled with character 'pattern' */
static inline int buffer_check_pattern(unsigned char *buffer, size_t size,
unsigned char pattern)
{
/* Invalid input */
if (!buffer || (size == 0))
return 0;
/* No match on first byte */
if (*buffer != pattern)
return 0;
/* First byte matched and buffer is 1 byte long, OK. */
if (size == 1)
return 1;
/*
* Check buffer longer than 1 byte. We already know that buffer[0]
* matches the pattern, so the test below only checks whether the
* buffer[0...size-2] == buffer[1...size-1] , which is a test for
* whether the buffer is filled with constant value.
*/
return !memcmp(buffer, buffer + 1, size - 1);
}
/*
* Writing to the NAND must take into account ECC errors
* and BAD sectors.
* This is not required for NOR flashes
* The function reassembles nandwrite from mtd-utils
* dropping all options that are not required here.
*/
static void erase_buffer(void *buffer, size_t size)
{
const uint8_t kEraseByte = 0xff;
if (buffer != NULL && size > 0)
memset(buffer, kEraseByte, size);
}
#define DEBUG_FLAGS 1
static int yaffs_write_nand(int mtdnum, struct img_type *img)
{
char mtd_device[LINESIZE];
struct flash_description *flash = get_flash_info();
struct mtd_dev_info *mtd = &flash->mtd_info[mtdnum].mtd;
int pagelen;
bool baderaseblock = false;
long long imglen = 0;
long long blockstart = -1;
long long offs;
unsigned char *filebuf = NULL;
size_t filebuf_max = 0;
size_t filebuf_len = 0;
size_t ooblen = 0;
size_t pagesize_with_oob = 0;
long long mtdoffset = 0;
int ifd = img->fdin;
int fd = -1;
bool failed = true;
int ret;
unsigned char *writebuf = NULL;
unsigned int perc = 0;
/*
* if nothing to do, returns without errors
*/
if (!img->size)
return 0;
pagelen = mtd->min_io_size; //pagesize
imglen = img->size;
ooblen = mtd->oob_size;
pagesize_with_oob = mtd->min_io_size + mtd->oob_size;
#if DEBUG_FLAGS
printf("\tmtdnum:%d\n \tmtdsize:%lld\n \terasesize:%d\n \tpagesize:%d\n \toobsize:%d\n",\
mtdnum, mtd->size, mtd->eb_size, mtd->min_io_size,mtd->oob_size);
#endif
snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum);
if (imglen / pagesize_with_oob * mtd->min_io_size > mtd->size) {
ERROR("Image %s does not fit into mtd%d", img->fname, mtdnum);
return -EIO;
}
/* Flashing to NAND is currently not streamable */
if (img->install_directly) {
ERROR("Raw NAND not streamable");
return -EINVAL;
}
filebuf_max = mtd->eb_size / mtd->min_io_size * pagesize_with_oob;
filebuf = calloc(1, filebuf_max); //sizeof(filebuf) = block_size
erase_buffer(filebuf, filebuf_max);
if ((fd = open(mtd_device, O_SYNC | O_RDWR)) < 0) {
ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno));
return -ENODEV;
}
/*
* Get data from input and write to the device while there is
* still input to read and we are still within the device
* bounds. Note that in the case of standard input, the input
* length is simply a quasi-boolean flag whose values are page
* length or zero.
*/
while ((imglen > 0 || writebuf < filebuf + filebuf_len)
&& mtdoffset < mtd->size) {
/*
* New eraseblock, check for bad block(s)
* Stay in the loop to be sure that, if mtdoffset changes because
* of a bad block, the next block that will be written to
* is also checked. Thus, we avoid errors if the block(s) after the
* skipped block(s) is also bad
* 申请一个block包含oob的内存空间,然后以pagesize_with_oob为单位进行读写操作
*/
while (blockstart != (mtdoffset & (~mtd->eb_size + 1))) {
blockstart = mtdoffset & (~mtd->eb_size + 1);
offs = blockstart;
/*
* if writebuf == filebuf, we are rewinding so we must
* not reset the buffer but just replay it
*/
if (writebuf != filebuf) {
erase_buffer(filebuf, filebuf_len);
filebuf_len = 0;
writebuf = filebuf;
}
baderaseblock = false;
do {
ret = mtd_is_bad(mtd, fd, offs / mtd->eb_size);
if (ret < 0) {
ERROR("mtd%d: MTD get bad block failed", mtdnum);
goto closeall;
} else if (ret == 1) {
baderaseblock = true;
}
if (baderaseblock) {
mtdoffset = blockstart + mtd->eb_size;
if (mtdoffset > mtd->size) {
ERROR("too many bad blocks, cannot complete request");
goto closeall;
}
}
offs += mtd->eb_size;
}
while (offs < blockstart + mtd->eb_size);
}
/* Read more data from the input if there isn't enough in the buffer */
if (writebuf + pagesize_with_oob > filebuf + filebuf_len) {
size_t readlen = pagesize_with_oob; //sizeof(readlen)=pagesize
size_t alreadyread = (filebuf + filebuf_len) - writebuf;
size_t tinycnt = alreadyread;
ssize_t cnt = 0;
while (tinycnt < readlen) {
cnt = read(ifd, writebuf + tinycnt, readlen - tinycnt);
if (cnt == 0) { /* EOF */
break;
} else if (cnt < 0) {
ERROR("File I/O error on input");
goto closeall;
}
tinycnt += cnt;
}
/* No padding needed - we are done */
if (tinycnt == 0) {
imglen = 0;
break;
}
/* Padding */
if (tinycnt < readlen) {
erase_buffer(writebuf + tinycnt, readlen - tinycnt);
}
filebuf_len += readlen - alreadyread;
imglen -= tinycnt - alreadyread;
}
ret =0;
if (!buffer_check_pattern(writebuf, pagesize_with_oob, 0xff)) {
/* Write out data */
ret = mtd_write(flash->libmtd, mtd, fd, mtdoffset / mtd->eb_size,
mtdoffset % mtd->eb_size,
writebuf,
mtd->min_io_size,
writebuf+mtd->min_io_size,
mtd->oob_size,
MTD_OPS_PLACE_OOB);
}
if (ret) {
long long i;
if (errno != EIO) {
ERROR("mtd%d: MTD write failure", mtdnum);
goto closeall;
}
/* Must rewind to blockstart if we can */
writebuf = filebuf;
for (i = blockstart; i < blockstart + mtd->eb_size; i += mtd->eb_size) {
if (mtd_erase(flash->libmtd, mtd, fd, i / mtd->eb_size)) {
int errno_tmp = errno;
TRACE("mtd%d: MTD Erase failure", mtdnum);
if (errno_tmp != EIO)
goto closeall;
}
}
TRACE("Marking block at %08llx bad",
mtdoffset & (~mtd->eb_size + 1));
if (mtd_mark_bad(mtd, fd, mtdoffset / mtd->eb_size)) {
ERROR("mtd%d: MTD Mark bad block failure", mtdnum);
goto closeall;
}
mtdoffset = blockstart + mtd->eb_size;
continue;
}
/*
* this handler does not use copyfile()
* and must update itself the progress bar
*/
swupdate_progress_update((img->size - imglen) * 100 / img->size);
#if DEBUG_FLAGS
perc = (img->size - imglen) * 100 / img->size;
printf("imgsize:%lld,left:imglen:%lld,yaffs install %d%\n",img->size,imglen,perc);
#endif
mtdoffset += mtd->min_io_size;
writebuf += pagesize_with_oob;
}
failed = false;
closeall:
free(filebuf);
close(fd);
if (failed) {
ERROR("Installing image %s into mtd%d failed",
img->fname,
mtdnum);
return -1;
}
return 0;
}
#if 0 //另外一种操作方式 也是ok的
static int yaffs_write_nand_bstar(int mtdnum, struct img_type *img)
{
char mtd_device[LINESIZE];
struct flash_description *flash = get_flash_info();
struct mtd_dev_info *mtd = &flash->mtd_info[mtdnum].mtd;
struct mtd_write_req eccbuf;
erase_info_t erase;
int pagelen;
bool baderaseblock = false;
long long imglen = 0;
long long blockstart = -1;
long long offs;
unsigned char *filebuf = NULL;
size_t filebuf_max = 0;
size_t filebuf_len = 0;
size_t ooblen = 0;
size_t pagesize_with_oob = 0;
long long mtdoffset = 0;
int ifd = img->fdin;
int fd = -1;
bool failed = true;
int ret;
unsigned char *writebuf = NULL;
unsigned int perc = 0;
/*
* if nothing to do, returns without errors
*/
if (!img->size)
return 0;
pagelen = mtd->min_io_size; //pagesize
imglen = img->size;
ooblen = mtd->oob_size;
pagesize_with_oob = mtd->min_io_size + ((FILESYSTEM_OOB) ? mtd->oob_size : 0);
#if DEBUG_FLAGS
printf("\tmtdnum:%d\n \tmtdsize:%lld\n \terasesize:%d\n \tpagesize:%d\n \toobsize:%d\n",\
mtdnum, mtd->size, mtd->eb_size, mtd->min_io_size,mtd->oob_size);
#endif
snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum);
if (imglen / pagesize_with_oob * mtd->min_io_size > mtd->size) {
ERROR("Image %s does not fit into mtd%d", img->fname, mtdnum);
return -EIO;
}
/* Flashing to NAND is currently not streamable */
if (img->install_directly) {
ERROR("Raw NAND not streamable");
return -EINVAL;
}
filebuf_max = mtd->eb_size / mtd->min_io_size * pagesize_with_oob;
filebuf = calloc(1, filebuf_max); //sizeof(filebuf) = block_size
erase_buffer(filebuf, filebuf_max);
if ((fd = open(mtd_device, O_SYNC |O_RDWR)) < 0) {
ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno));
return -ENODEV;
}
/*
* Get data from input and write to the device while there is
* still input to read and we are still within the device
* bounds. Note that in the case of standard input, the input
* length is simply a quasi-boolean flag whose values are page
* length or zero.
*/
while ((imglen > 0 || writebuf < filebuf + filebuf_len)
&& mtdoffset < mtd->size) {
/*
* New eraseblock, check for bad block(s)
* Stay in the loop to be sure that, if mtdoffset changes because
* of a bad block, the next block that will be written to
* is also checked. Thus, we avoid errors if the block(s) after the
* skipped block(s) is also bad
* 申请一个block包含oob的内存空间,然后以pagesize_with_oob为单位进行读写操作
*/
while (blockstart != (mtdoffset & (~mtd->eb_size + 1))) {
blockstart = mtdoffset & (~mtd->eb_size + 1);
offs = blockstart;
/*
* if writebuf == filebuf, we are rewinding so we must
* not reset the buffer but just replay it
*/
if (writebuf != filebuf) {
erase_buffer(filebuf, filebuf_len);
filebuf_len = 0;
writebuf = filebuf;
}
baderaseblock = false;
do {
//ret = mtd_is_bad(mtd, fd, offs / mtd->eb_size);
ret = ioctl(fd, MEMGETBADBLOCK, &offs);
if (ret < 0) {
ERROR("mtd%d: MTD get bad block failed", mtdnum);
goto closeall;
} else if (ret == 1) {
baderaseblock = true;
}
if (baderaseblock) {
mtdoffset = blockstart + mtd->eb_size;
if (mtdoffset > mtd->size) {
ERROR("too many bad blocks, cannot complete request");
goto closeall;
}
}
offs += mtd->eb_size;
}
while (offs < blockstart + mtd->eb_size);
}
/* Read more data from the input if there isn't enough in the buffer */
if (writebuf + pagesize_with_oob > filebuf + filebuf_len) {
size_t readlen = pagesize_with_oob; //sizeof(readlen)=pagesize
size_t alreadyread = (filebuf + filebuf_len) - writebuf;
size_t tinycnt = alreadyread;
ssize_t cnt = 0;
while (tinycnt < readlen) {
cnt = read(ifd, writebuf + tinycnt, readlen - tinycnt);
if (cnt == 0) { /* EOF */
break;
} else if (cnt < 0) {
ERROR("File I/O error on input");
goto closeall;
}
tinycnt += cnt;
}
/* No padding needed - we are done */
if (tinycnt == 0) {
imglen = 0;
break;
}
/* Padding */
if (tinycnt < readlen) {
erase_buffer(writebuf + tinycnt, readlen - tinycnt);
}
filebuf_len += readlen - alreadyread;
imglen -= tinycnt - alreadyread;
}
ret =0;
if (!buffer_check_pattern(writebuf, pagesize_with_oob, 0xff)) {
eccbuf.usr_data = writebuf;
eccbuf.len = mtd->min_io_size;
eccbuf.start = mtdoffset;
if(FILESYSTEM_OOB)
{
eccbuf.mode = MTD_OPS_PLACE_OOB;
eccbuf.usr_oob = writebuf+mtd->min_io_size;
eccbuf.ooblen = mtd->oob_size;
}
else
{
eccbuf.mode = MTD_OPS_RAW;
eccbuf.usr_oob = NULL;
eccbuf.ooblen = 0;
}
/* Write out data */
#if 0
ret = mtd_write(flash->libmtd, mtd, fd, mtdoffset / mtd->eb_size,
mtdoffset % mtd->eb_size,
writebuf,
mtd->min_io_size,
writebuf+mtd->min_io_size,
mtd->oob_size,
MTD_OPS_PLACE_OOB);
#endif
ret = ioctl(fd, MEMWRITE, &eccbuf);
}
if (ret) {
long long i;
if (errno != EIO) {
ERROR("mtd%d: MTD write failure", mtdnum);
goto closeall;
}
/* Must rewind to blockstart if we can */
writebuf = filebuf;
for (i = blockstart; i < blockstart + mtd->eb_size; i += mtd->eb_size) {
#if 0
if (mtd_erase(flash->libmtd, mtd, fd, i / mtd->eb_size)) {
int errno_tmp = errno;
TRACE("mtd%d: MTD Erase failure", mtdnum);
if (errno_tmp != EIO)
goto closeall;
}
#endif
erase.start = blockstart;
erase.length = mtd->eb_size;
fprintf(stderr, "Erasing failed write from %08lx-%08lx\n",
(long)erase.start, (long)erase.start+erase.length-1);
if (ioctl(fd, MEMERASE, &erase) != 0) {
perror("MEMERASE");
goto closeall;
}
}
TRACE("Marking block at %08llx bad",
mtdoffset & (~mtd->eb_size + 1));
#if 0
if (mtd_mark_bad(mtd, fd, mtdoffset / mtd->eb_size)) {
ERROR("mtd%d: MTD Mark bad block failure", mtdnum);
goto closeall;
}
#endif
mtdoffset = blockstart + mtd->eb_size;
continue;
}
/*
* this handler does not use copyfile()
* and must update itself the progress bar
*/
swupdate_progress_update((img->size - imglen) * 100 / img->size);
#if DEBUG_FLAGS
perc = (img->size - imglen) * 100 / img->size;
printf("imgsize:%lld,left:imglen:%lld,yaffs install %d%\n",img->size,imglen,perc);
#endif
mtdoffset += mtd->min_io_size;
writebuf += pagesize_with_oob;
}
failed = false;
closeall:
free(filebuf);
close(fd);
if (failed) {
ERROR("Installing image %s into mtd%d failed",
img->fname,
mtdnum);
return -1;
}
return 0;
}
#endif
#if 1
static int flash_eraseall(int mtdnum)
{
mtd_info_t meminfo;
char mtd_device[80];
struct mtd_dev_info *mtd;
int fd, clmpos = 0, clmlen = 8;
erase_info_t erase;
int isNAND, bbtest = 1;
struct flash_description *flash = get_flash_info();
if (!mtd_dev_present(flash->libmtd, mtdnum)) {
ERROR("MTD %d does not exist", mtdnum);
return -ENODEV;
}
mtd = &flash->mtd_info[mtdnum].mtd;
snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum);
if ((fd = open(mtd_device, O_SYNC | O_RDWR)) < 0) {
ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno));
return -ENODEV;
}
if (ioctl(fd, MEMGETINFO, &meminfo) != 0) {
fprintf(stderr, " %s: unable to get MTD device info\n", mtd_device);
return 1;
}
erase.length = meminfo.erasesize;
isNAND = meminfo.type == MTD_NANDFLASH ? 1 : 0;
if (jffs2) {
cleanmarker.magic = cpu_to_je16 (JFFS2_MAGIC_BITMASK);
cleanmarker.nodetype = cpu_to_je16 (JFFS2_NODETYPE_CLEANMARKER);
if (!isNAND)
cleanmarker.totlen = cpu_to_je32 (sizeof (struct jffs2_unknown_node));
else {
struct nand_oobinfo oobinfo;
if (ioctl(fd, MEMGETOOBSEL, &oobinfo) != 0) {
fprintf(stderr, " %s: unable to get NAND oobinfo\n", mtd_device);
return 1;
}
/* Check for autoplacement */
if (oobinfo.useecc == MTD_NANDECC_AUTOPLACE) {
/* Get the position of the free bytes */
if (!oobinfo.oobfree[0][1]) {
fprintf (stderr, " Eeep. Autoplacement selected and no empty space in oob\n");
return 1;
}
clmpos = oobinfo.oobfree[0][0];
clmlen = oobinfo.oobfree[0][1];
if (clmlen > 8)
clmlen = 8;
} else {
/* Legacy mode */
switch (meminfo.oobsize) {
case 8:
clmpos = 6;
clmlen = 2;
break;
case 16:
clmpos = 8;
clmlen = 8;
break;
case 64:
clmpos = 16;
clmlen = 8;
break;
}
}
cleanmarker.totlen = cpu_to_je32(8);
}
cleanmarker.hdr_crc = cpu_to_je32 (crc32 (0, &cleanmarker, sizeof (struct jffs2_unknown_node) - 4));
}
//for (erase.start = offsetMtd; erase.start < offsetMtd+SizeMtd; erase.start += meminfo.erasesize)
for (erase.start = 0; erase.start < meminfo.size; erase.start += meminfo.erasesize)
{
if (bbtest) {
loff_t offset = erase.start;
int ret = ioctl(fd, MEMGETBADBLOCK, &offset);
if (ret > 0) {
if (!quiet)
DEBUG_BSTAR ("\nSkipping bad block at 0x%08x\n", erase.start);
continue;
} else if (ret < 0) {
if (errno == EOPNOTSUPP) {
bbtest = 0;
if (isNAND) {
fprintf(stderr, "%s: Bad block check not available\n", mtd_device);
return 1;
}
} else {
fprintf(stderr, "\n %s: MTD get bad block failed: %s\n", mtd_device, strerror(errno));
return 1;
}
}
}
DEBUG_BSTAR("file:%s, line:%d, erase.start:%p\n", __FILE__, __LINE__, erase.start);
if (ioctl(fd, MEMERASE, &erase) != 0) {
fprintf(stderr, "\n %s: MTD Erase failure: %s\n", mtd_device, strerror(errno));
continue;
}
/* format for JFFS2 ? */
if (!jffs2)
continue;
/* write cleanmarker */
if (isNAND) {
struct mtd_oob_buf oob;
oob.ptr = (unsigned char *) &cleanmarker;
oob.start = erase.start + clmpos;
oob.length = clmlen;
if (ioctl (fd, MEMWRITEOOB, &oob) != 0) {
fprintf(stderr, "\n %s: MTD writeoob failure: %s\n", mtd_device, strerror(errno));
continue;
}
} else {
if (lseek (fd, erase.start, SEEK_SET) < 0) {
fprintf(stderr, "\n %s: MTD lseek failure: %s\n", mtd_device, strerror(errno));
continue;
}
if (write (fd , &cleanmarker, sizeof (cleanmarker)) != sizeof (cleanmarker)) {
fprintf(stderr, "\n %s: MTD write failure: %s\n", mtd_device, strerror(errno));
continue;
}
}
}
close(fd);
DEBUG_BSTAR("file:%s, line:%d, meminfo.size:%d\n", __FILE__, __LINE__, meminfo.size);
return 0;
}
#endif
static int yaffs_write_image(int mtdnum, struct img_type *img)
{
//return yaffs_write_nand_bstar(mtdnum, img);
return yaffs_write_nand(mtdnum, img);
}
static int install_yaffs_image(struct img_type *img,
void __attribute__ ((__unused__)) *data)
{
char filename[64];
int mtdnum;
int n;
const char* TMPDIR = get_tmpdir();
n = snprintf(filename, sizeof(filename), "%s%s", TMPDIR, img->fname);
if (n < 0 || n >= sizeof(filename)) {
ERROR("Filename too long: %s", img->fname);
return -1;
}
if (strlen(img->path))
mtdnum = get_mtd_from_name(img->path);
else
mtdnum = get_mtd_from_device(img->device);
if (mtdnum < 0) {
ERROR("Wrong MTD device in description: %s",
strlen(img->path) ? img->path : img->device);
return -1;
}
#if 1
if(flash_eraseall(mtdnum)) {
ERROR("I cannot erasing %s",
img->device);
return -1;
}
#endif
TRACE("Copying %s into /dev/mtd%d", img->fname, mtdnum);
if (yaffs_write_image(mtdnum, img)) {
ERROR("I cannot copy %s into %s partition",
img->fname,
img->device);
return -1;
}
return 0;
}
__attribute__((constructor))
void yaffs_handler(void)
{
register_handler("yaffs", install_yaffs_image,
IMAGE_HANDLER | FILE_HANDLER, NULL);
}
代码仿照flash_handle.c撸完后,发现,文件系统升级成功这个事件呈现奇偶性。一次成功一次失败。怀疑是flash擦除根文件系统时不稳定。使用mtd-uitls工具中移植好的flash_erase工具配合swupdate升级一块调试,一样的效果。
使用hexdump /dev/mtd3 指令(我的yaffs rootfs在mtd3),的确看到执行flash_erase的执行效果不一样。flash擦的干净时就可以升级成功。
怀疑个别进程占用了flash,导致flash擦除异常。kill掉多余进程,保证使用"mount -o remount -o ro /"重新挂载根文件系统,再次执行swupdate升级操作,百分百必成功。
还有一个关于写uboot env的注意事项。
如果需要支持在升级过程中修改uboot 的env变量值,在开源工具swupdate编译时需要引入libenv.a的库。此库实在uboot源码中:
使用make env ARCH=XXX CROSS_COMPILE=XXX来编译出的。
我现在用的版本是u-boot-2017.05,版本太高或太低编译可能会有问题(u-boot-2017.05\tools\env路径下可能生成不了lib.a文件)
lib.a就是swupdate所需的libenv.a。同时需要fw_printenv工具重命名后放到嵌入板"/var/lock/fw_printenv.lock",另外需要u-boot-2017.05\tools\env\fw_env.config,按照uboot中对应平台的配置头文件适当的修改其中参数即可。