Automatic software update from TFTP server

The auto-update feature allows to automatically download software updates
from a TFTP server and store them in Flash memory during boot. Updates are
contained in a FIT file and protected with SHA-1 checksum.

More detailed description can be found in doc/README.update.

Signed-off-by: Rafal Czubak <rcz@semihalf.com>
Signed-off-by: Bartlomiej Sieka <tur@semihalf.com>
This commit is contained in:
Bartlomiej Sieka 2008-10-01 15:26:31 +02:00 committed by Wolfgang Denk
parent 3f0cf51dab
commit 4bae90904b
7 changed files with 487 additions and 0 deletions

12
README
View file

@ -1740,6 +1740,14 @@ The following options need to be configured:
example, some LED's) on your board. At the moment, example, some LED's) on your board. At the moment,
the following checkpoints are implemented: the following checkpoints are implemented:
- Automatic software updates via TFTP server
CONFIG_UPDATE_TFTP
CONFIG_UPDATE_TFTP_CNT_MAX
CONFIG_UPDATE_TFTP_MSEC_MAX
These options enable and control the auto-update feature;
for a more detailed description refer to doc/README.update.
Legacy uImage format: Legacy uImage format:
Arg Where When Arg Where When
@ -2814,6 +2822,10 @@ Some configuration options can be set using Environment Variables:
allowed for use by the bootm command. See also "bootm_low" allowed for use by the bootm command. See also "bootm_low"
environment variable. environment variable.
updatefile - Location of the software update file on a TFTP server, used
by the automatic software update feature. Please refer to
documentation in doc/README.update for more details.
autoload - if set to "no" (any string beginning with 'n'), autoload - if set to "no" (any string beginning with 'n'),
"bootp" will just load perform a lookup of the "bootp" will just load perform a lookup of the
configuration from the BOOTP server, but not try to configuration from the BOOTP server, but not try to

View file

@ -153,6 +153,7 @@ COBJS-y += flash.o
COBJS-y += kgdb.o COBJS-y += kgdb.o
COBJS-$(CONFIG_LCD) += lcd.o COBJS-$(CONFIG_LCD) += lcd.o
COBJS-$(CONFIG_LYNXKDI) += lynxkdi.o COBJS-$(CONFIG_LYNXKDI) += lynxkdi.o
COBJS-$(CONFIG_UPDATE_TFTP) += update.o
COBJS-$(CONFIG_USB_KEYBOARD) += usb_kbd.o COBJS-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
COBJS-$(CONFIG_DDR_SPD) += ddr_spd.o COBJS-$(CONFIG_DDR_SPD) += ddr_spd.o

View file

@ -56,6 +56,9 @@ extern int do_reset (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]); /* fo
extern int do_bootd (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]); extern int do_bootd (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]);
#if defined(CONFIG_UPDATE_TFTP)
void update_tftp (void);
#endif /* CONFIG_UPDATE_TFTP */
#define MAX_DELAY_STOP_STR 32 #define MAX_DELAY_STOP_STR 32
@ -301,6 +304,10 @@ void main_loop (void)
trab_vfd (bmp); trab_vfd (bmp);
#endif /* CONFIG_VFD && VFD_TEST_LOGO */ #endif /* CONFIG_VFD && VFD_TEST_LOGO */
#if defined(CONFIG_UPDATE_TFTP)
update_tftp ();
#endif /* CONFIG_UPDATE_TFTP */
#ifdef CONFIG_BOOTCOUNT_LIMIT #ifdef CONFIG_BOOTCOUNT_LIMIT
bootcount = bootcount_load(); bootcount = bootcount_load();
bootcount++; bootcount++;

315
common/update.c Normal file
View file

@ -0,0 +1,315 @@
/*
* (C) Copyright 2008 Semihalf
*
* Written by: Rafal Czubak <rcz@semihalf.com>
* Bartlomiej Sieka <tur@semihalf.com>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
*/
#include <common.h>
#if !(defined(CONFIG_FIT) && defined(CONFIG_OF_LIBFDT))
#error "CONFIG_FIT and CONFIG_OF_LIBFDT are required for auto-update feature"
#endif
#if defined(CFG_NO_FLASH)
#error "CFG_NO_FLASH defined, but FLASH is required for auto-update feature"
#endif
#include <command.h>
#include <flash.h>
#include <net.h>
#include <malloc.h>
/* env variable holding the location of the update file */
#define UPDATE_FILE_ENV "updatefile"
/* set configuration defaults if needed */
#ifndef CONFIG_UPDATE_LOAD_ADDR
#define CONFIG_UPDATE_LOAD_ADDR 0x100000
#endif
#ifndef CONFIG_UPDATE_TFTP_MSEC_MAX
#define CONFIG_UPDATE_TFTP_MSEC_MAX 100
#endif
#ifndef CONFIG_UPDATE_TFTP_CNT_MAX
#define CONFIG_UPDATE_TFTP_CNT_MAX 0
#endif
extern ulong TftpRRQTimeoutMSecs;
extern int TftpRRQTimeoutCountMax;
extern flash_info_t flash_info[];
extern ulong load_addr;
static uchar *saved_prot_info;
static int update_load(char *filename, ulong msec_max, int cnt_max, ulong addr)
{
int size, rv;
ulong saved_timeout_msecs;
int saved_timeout_count;
char *saved_netretry, *saved_bootfile;
rv = 0;
/* save used globals and env variable */
saved_timeout_msecs = TftpRRQTimeoutMSecs;
saved_timeout_count = TftpRRQTimeoutCountMax;
saved_netretry = strdup(getenv("netretry"));
saved_bootfile = strdup(BootFile);
/* set timeouts for auto-update */
TftpRRQTimeoutMSecs = msec_max;
TftpRRQTimeoutCountMax = cnt_max;
/* we don't want to retry the connection if errors occur */
setenv("netretry", "no");
/* download the update file */
load_addr = addr;
copy_filename(BootFile, filename, sizeof(BootFile));
size = NetLoop(TFTP);
if (size < 0)
rv = 1;
else if (size > 0)
flush_cache(addr, size);
/* restore changed globals and env variable */
TftpRRQTimeoutMSecs = saved_timeout_msecs;
TftpRRQTimeoutCountMax = saved_timeout_count;
setenv("netretry", saved_netretry);
if (saved_netretry != NULL)
free(saved_netretry);
if (saved_bootfile != NULL) {
copy_filename(BootFile, saved_bootfile, sizeof(BootFile));
free(saved_bootfile);
}
return rv;
}
static int update_flash_protect(int prot, ulong addr_first, ulong addr_last)
{
uchar *sp_info_ptr;
ulong s;
int i, bank, cnt;
flash_info_t *info;
sp_info_ptr = NULL;
if (prot == 0) {
saved_prot_info =
calloc(CFG_MAX_FLASH_BANKS * CFG_MAX_FLASH_SECT, 1);
if (!saved_prot_info)
return 1;
}
for (bank = 0; bank < CFG_MAX_FLASH_BANKS; ++bank) {
cnt = 0;
info = &flash_info[bank];
/* Nothing to do if the bank doesn't exist */
if (info->sector_count == 0)
return 0;
/* Point to current bank protection information */
sp_info_ptr = saved_prot_info + (bank * CFG_MAX_FLASH_SECT);
/*
* Adjust addr_first or addr_last if we are on bank boundary.
* Address space between banks must be continuous for other
* flash functions (like flash_sect_erase or flash_write) to
* succeed. Banks must also be numbered in correct order,
* according to increasing addresses.
*/
if (addr_last > info->start[0] + info->size - 1)
addr_last = info->start[0] + info->size - 1;
if (addr_first < info->start[0])
addr_first = info->start[0];
for (i = 0; i < info->sector_count; i++) {
/* Save current information about protected sectors */
if (prot == 0) {
s = info->start[i];
if ((s >= addr_first) && (s <= addr_last))
sp_info_ptr[i] = info->protect[i];
}
/* Protect/unprotect sectors */
if (sp_info_ptr[i] == 1) {
#if defined(CFG_FLASH_PROTECTION)
if (flash_real_protect(info, i, prot))
return 1;
#else
info->protect[i] = prot;
#endif
cnt++;
}
}
if (cnt) {
printf("%sProtected %d sectors\n",
prot ? "": "Un-", cnt);
}
}
if((prot == 1) && saved_prot_info)
free(saved_prot_info);
return 0;
}
static int update_flash(ulong addr_source, ulong addr_first, ulong size)
{
ulong addr_last = addr_first + size - 1;
/* round last address to the sector boundary */
if (flash_sect_roundb(&addr_last) > 0)
return 1;
if (addr_first >= addr_last) {
printf("Error: end address exceeds addressing space\n");
return 1;
}
/* remove protection on processed sectors */
if (update_flash_protect(0, addr_first, addr_last) > 0) {
printf("Error: could not unprotect flash sectors\n");
return 1;
}
printf("Erasing 0x%08lx - 0x%08lx", addr_first, addr_last);
if (flash_sect_erase(addr_first, addr_last) > 0) {
printf("Error: could not erase flash\n");
return 1;
}
printf("Copying to flash...");
if (flash_write((char *)addr_source, addr_first, size) > 0) {
printf("Error: could not copy to flash\n");
return 1;
}
printf("done\n");
/* enable protection on processed sectors */
if (update_flash_protect(1, addr_first, addr_last) > 0) {
printf("Error: could not protect flash sectors\n");
return 1;
}
return 0;
}
static int update_fit_getparams(const void *fit, int noffset, ulong *addr,
ulong *fladdr, ulong *size)
{
const void *data;
if (fit_image_get_data(fit, noffset, &data, (size_t *)size))
return 1;
if (fit_image_get_load(fit, noffset, (ulong *)fladdr))
return 1;
*addr = (ulong)data;
return 0;
}
void update_tftp(void)
{
char *filename, *env_addr;
int images_noffset, ndepth, noffset;
ulong update_addr, update_fladdr, update_size;
ulong addr;
void *fit;
printf("Auto-update from TFTP: ");
/* get the file name of the update file */
filename = getenv(UPDATE_FILE_ENV);
if (filename == NULL) {
printf("failed, env. variable '%s' not found\n",
UPDATE_FILE_ENV);
return;
}
printf("trying update file '%s'\n", filename);
/* get load address of downloaded update file */
if ((env_addr = getenv("loadaddr")) != NULL)
addr = simple_strtoul(env_addr, NULL, 16);
else
addr = CONFIG_UPDATE_LOAD_ADDR;
if (update_load(filename, CONFIG_UPDATE_TFTP_MSEC_MAX,
CONFIG_UPDATE_TFTP_CNT_MAX, addr)) {
printf("Can't load update file, aborting auto-update\n");
return;
}
fit = (void *)addr;
if (!fit_check_format((void *)fit)) {
printf("Bad FIT format of the update file, aborting "
"auto-update\n");
return;
}
/* process updates */
images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
ndepth = 0;
noffset = fdt_next_node(fit, images_noffset, &ndepth);
while (noffset >= 0 && ndepth > 0) {
if (ndepth != 1)
goto next_node;
printf("Processing update '%s' :",
fit_get_name(fit, noffset, NULL));
if (!fit_image_check_hashes(fit, noffset)) {
printf("Error: invalid update hash, aborting\n");
goto next_node;
}
printf("\n");
if (update_fit_getparams(fit, noffset, &update_addr,
&update_fladdr, &update_size)) {
printf("Error: can't get update parameteres, "
"aborting\n");
goto next_node;
}
if (update_flash(update_addr, update_fladdr, update_size)) {
printf("Error: can't flash update, aborting\n");
goto next_node;
}
next_node:
noffset = fdt_next_node(fit, noffset, &ndepth);
}
return;
}

90
doc/README.update Normal file
View file

@ -0,0 +1,90 @@
Automatic software update from a TFTP server
============================================
Overview
--------
This feature allows to automatically store software updates present on a TFTP
server in NOR Flash. In more detail: a TFTP transfer of a file given in
environment variable 'updatefile' from server 'serverip' is attempted during
boot. The update file should be a FIT file, and can contain one or more
updates. Each update in the update file has an address in NOR Flash where it
should be placed, updates are also protected with a SHA-1 checksum. If the
TFTP transfer is successful, the hash of each update is verified, and if the
verification is positive, the update is stored in Flash.
The auto-update feature is enabled by the CONFIG_UPDATE_TFTP macro:
#define CONFIG_UPDATE_TFTP 1
Note that when enabling auto-update, Flash support must be turned on. Also,
one must enable FIT and LIBFDT support:
#define CONFIG_FIT 1
#define CONFIG_OF_LIBFDT 1
The auto-update feature uses the following configuration knobs:
- CONFIG_UPDATE_LOAD_ADDR
Normally, TFTP transfer of the update file is done to the address specified
in environment variable 'loadaddr'. If this variable is not present, the
transfer is made to the address given in CONFIG_UPDATE_LOAD_ADDR (0x100000
by default).
- CONFIG_UPDATE_TFTP_CNT_MAX
CONFIG_UPDATE_TFTP_MSEC_MAX
These knobs control the timeouts during initial connection to the TFTP
server. Since a transfer is attempted during each boot, it is undesirable to
have a long delay when a TFTP server is not present.
CONFIG_UPDATE_TFTP_MSEC_MAX specifies the number of seconds to wait for the
server to respond to initial connection, and CONFIG_UPDATE_TFTP_CNT_MAX
gives the number of such connection retries. CONFIG_UPDATE_TFTP_CNT_MAX must
be non-negative and is 0 by default, CONFIG_UPDATE_TFTP_MSEC_MAX must be
positive and is 1 by default.
Since the update file is in FIT format, it is created from an *.its file using
the mkimage tool. dtc tool with support for binary includes, e.g. in version
1.2.0 or later, must also be available on the system where the update file is
to be prepared. Refer to the doc/uImage.FIT/ directory for more details on FIT
images.
Example .its files
------------------
- doc/uImage.FIT/update_uboot.its
A simple example that can be used to create an update file for automatically
replacing U-Boot image on a system.
Assuming that an U-Boot image u-boot.bin is present in the current working
directory, and that the address given in the 'load' property in the
'update_uboot.its' file is where the U-Boot is stored in Flash, the
following command will create the actual update file 'update_uboot.itb':
mkimage -f update_uboot.its update_uboot.itb
Place 'update_uboot.itb' on a TFTP server, for example as
'/tftpboot/update_uboot.itb', and set the 'updatefile' variable
appropriately, for example in the U-Boot prompt:
setenv updatefile /tftpboot/update_uboot.itb
saveenv
Now, when the system boots up and the update TFTP server specified in the
'serverip' environment variable is accessible, the new U-Boot image will be
automatically stored in Flash.
NOTE: do make sure that the 'u-boot.bin' image used to create the update
file is a good, working image. Also make sure that the address in Flash
where the update will be placed is correct. Making mistake here and
attempting the auto-update can render the system unusable.
- doc/uImage.FIT/update3.its
An example containing three updates. It can be used to update Linux kernel,
ramdisk and FDT blob stored in Flash. The procedure for preparing the update
file is similar to the example above.

View file

@ -0,0 +1,41 @@
/*
* Example Automatic software update file.
*/
/ {
description = "Automatic software updates: kernel, ramdisk, FDT";
#address-cells = <1>;
images {
update@1 {
description = "Linux kernel binary";
data = /incbin/("./vmlinux.bin.gz");
compression = "none";
type = "firmware";
load = <FF700000>;
hash@1 {
algo = "sha1";
};
};
update@2 {
description = "Ramdisk image";
data = /incbin/("./ramdisk_image.gz");
compression = "none";
type = "firmware";
load = <FF8E0000>;
hash@1 {
algo = "sha1";
};
};
update@3 {
description = "FDT blob";
data = /incbin/("./blob.fdt");
compression = "none";
type = "firmware";
load = <FFAC0000>;
hash@1 {
algo = "sha1";
};
};
};
};

View file

@ -0,0 +1,21 @@
/*
* Automatic software update for U-Boot
* Make sure the flashing addresses ('load' prop) is correct for your board!
*/
/ {
description = "Automatic U-Boot update";
#address-cells = <1>;
images {
update@1 {
description = "U-Boot binary";
data = /incbin/("./u-boot.bin");
compression = "none";
type = "firmware";
load = <FFFC0000>;
hash@1 {
algo = "sha1";
};
};
};
};