| /* |
| * (C) Copyright 2003-2004 |
| * Gary Jennejohn, DENX Software Engineering, gj@denx.de. |
| * Stefan Roese, esd gmbh germany, stefan.roese@esd-electronics.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> |
| #include <command.h> |
| #include <image.h> |
| #include <asm/byteorder.h> |
| #include <linux/mtd/nand.h> |
| #include <fat.h> |
| |
| #include "auto_update.h" |
| |
| #ifdef CONFIG_AUTO_UPDATE |
| |
| #if !(CONFIG_COMMANDS & CFG_CMD_FAT) |
| #error "must define CFG_CMD_FAT" |
| #endif |
| |
| extern au_image_t au_image[]; |
| extern int N_AU_IMAGES; |
| |
| #define AU_DEBUG |
| #undef AU_DEBUG |
| |
| #undef debug |
| #ifdef AU_DEBUG |
| #define debug(fmt,args...) printf (fmt ,##args) |
| #else |
| #define debug(fmt,args...) |
| #endif /* AU_DEBUG */ |
| |
| |
| #define LOAD_ADDR ((unsigned char *)0x100000) /* where to load files into memory */ |
| #define MAX_LOADSZ 0x1e00000 |
| |
| /* externals */ |
| extern int fat_register_device(block_dev_desc_t *, int); |
| extern int file_fat_detectfs(void); |
| extern long file_fat_read(const char *, void *, unsigned long); |
| long do_fat_read (const char *filename, void *buffer, unsigned long maxsize, int dols); |
| #ifdef CONFIG_VFD |
| extern int trab_vfd (ulong); |
| extern int transfer_pic(unsigned char, unsigned char *, int, int); |
| #endif |
| extern int flash_sect_erase(ulong, ulong); |
| extern int flash_sect_protect (int, ulong, ulong); |
| extern int flash_write (char *, ulong, ulong); |
| /* change char* to void* to shutup the compiler */ |
| extern block_dev_desc_t *get_dev (char*, int); |
| |
| #if (CONFIG_COMMANDS & CFG_CMD_NAND) |
| /* references to names in cmd_nand.c */ |
| #define NANDRW_READ 0x01 |
| #define NANDRW_WRITE 0x00 |
| #define NANDRW_JFFS2 0x02 |
| #define NANDRW_JFFS2_SKIP 0x04 |
| extern struct nand_chip nand_dev_desc[]; |
| extern int nand_rw(struct nand_chip* nand, int cmd, size_t start, size_t len, |
| size_t * retlen, u_char * buf); |
| extern int nand_erase(struct nand_chip* nand, size_t ofs, size_t len, int clean); |
| #endif /* (CONFIG_COMMANDS & CFG_CMD_NAND) */ |
| |
| extern block_dev_desc_t ide_dev_desc[CFG_IDE_MAXDEVICE]; |
| |
| |
| int au_check_cksum_valid(int i, long nbytes) |
| { |
| image_header_t *hdr; |
| unsigned long checksum; |
| |
| hdr = (image_header_t *)LOAD_ADDR; |
| |
| if ((au_image[i].type == AU_FIRMWARE) && (au_image[i].size != ntohl(hdr->ih_size))) { |
| printf ("Image %s has wrong size\n", au_image[i].name); |
| return -1; |
| } |
| |
| if (nbytes != (sizeof(*hdr) + ntohl(hdr->ih_size))) { |
| printf ("Image %s bad total SIZE\n", au_image[i].name); |
| return -1; |
| } |
| /* check the data CRC */ |
| checksum = ntohl(hdr->ih_dcrc); |
| |
| if (crc32 (0, (uchar *)(LOAD_ADDR + sizeof(*hdr)), ntohl(hdr->ih_size)) |
| != checksum) { |
| printf ("Image %s bad data checksum\n", au_image[i].name); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| int au_check_header_valid(int i, long nbytes) |
| { |
| image_header_t *hdr; |
| unsigned long checksum; |
| |
| hdr = (image_header_t *)LOAD_ADDR; |
| /* check the easy ones first */ |
| #undef CHECK_VALID_DEBUG |
| #ifdef CHECK_VALID_DEBUG |
| printf("magic %#x %#x ", ntohl(hdr->ih_magic), IH_MAGIC); |
| printf("arch %#x %#x ", hdr->ih_arch, IH_CPU_PPC); |
| printf("size %#x %#lx ", ntohl(hdr->ih_size), nbytes); |
| printf("type %#x %#x ", hdr->ih_type, IH_TYPE_KERNEL); |
| #endif |
| if (nbytes < sizeof(*hdr)) |
| { |
| printf ("Image %s bad header SIZE\n", au_image[i].name); |
| return -1; |
| } |
| if (ntohl(hdr->ih_magic) != IH_MAGIC || hdr->ih_arch != IH_CPU_PPC) |
| { |
| printf ("Image %s bad MAGIC or ARCH\n", au_image[i].name); |
| return -1; |
| } |
| /* check the hdr CRC */ |
| checksum = ntohl(hdr->ih_hcrc); |
| hdr->ih_hcrc = 0; |
| |
| if (crc32 (0, (uchar *)hdr, sizeof(*hdr)) != checksum) { |
| printf ("Image %s bad header checksum\n", au_image[i].name); |
| return -1; |
| } |
| hdr->ih_hcrc = htonl(checksum); |
| |
| /* check the type - could do this all in one gigantic if() */ |
| if ((au_image[i].type == AU_FIRMWARE) && (hdr->ih_type != IH_TYPE_FIRMWARE)) { |
| printf ("Image %s wrong type\n", au_image[i].name); |
| return -1; |
| } |
| if ((au_image[i].type == AU_SCRIPT) && (hdr->ih_type != IH_TYPE_SCRIPT)) { |
| printf ("Image %s wrong type\n", au_image[i].name); |
| return -1; |
| } |
| |
| /* recycle checksum */ |
| checksum = ntohl(hdr->ih_size); |
| |
| #if 0 /* test-only */ |
| /* for kernel and app the image header must also fit into flash */ |
| if (idx != IDX_DISK) |
| checksum += sizeof(*hdr); |
| /* check the size does not exceed space in flash. HUSH scripts */ |
| /* all have ausize[] set to 0 */ |
| if ((ausize[idx] != 0) && (ausize[idx] < checksum)) { |
| printf ("Image %s is bigger than FLASH\n", au_image[i].name); |
| return -1; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| |
| int au_do_update(int i, long sz) |
| { |
| image_header_t *hdr; |
| char *addr; |
| long start, end; |
| int off, rc; |
| uint nbytes; |
| int k; |
| #if (CONFIG_COMMANDS & CFG_CMD_NAND) |
| int total; |
| #endif |
| |
| hdr = (image_header_t *)LOAD_ADDR; |
| |
| switch (au_image[i].type) { |
| case AU_SCRIPT: |
| printf("Executing script %s\n", au_image[i].name); |
| |
| /* execute a script */ |
| if (hdr->ih_type == IH_TYPE_SCRIPT) { |
| addr = (char *)((char *)hdr + sizeof(*hdr)); |
| /* stick a NULL at the end of the script, otherwise */ |
| /* parse_string_outer() runs off the end. */ |
| addr[ntohl(hdr->ih_size)] = 0; |
| addr += 8; |
| |
| /* |
| * Replace cr/lf with ; |
| */ |
| k = 0; |
| while (addr[k] != 0) { |
| if ((addr[k] == 10) || (addr[k] == 13)) { |
| addr[k] = ';'; |
| } |
| k++; |
| } |
| |
| run_command(addr, 0); |
| return 0; |
| } |
| |
| break; |
| |
| case AU_FIRMWARE: |
| case AU_NOR: |
| case AU_NAND: |
| start = au_image[i].start; |
| end = au_image[i].start + au_image[i].size - 1; |
| |
| /* |
| * do not update firmware when image is already in flash. |
| */ |
| if (au_image[i].type == AU_FIRMWARE) { |
| char *orig = (char*)start; |
| char *new = (char *)((char *)hdr + sizeof(*hdr)); |
| nbytes = ntohl(hdr->ih_size); |
| |
| while(--nbytes) { |
| if (*orig++ != *new++) { |
| break; |
| } |
| } |
| if (!nbytes) { |
| printf("Skipping firmware update - images are identical\n"); |
| break; |
| } |
| } |
| |
| /* unprotect the address range */ |
| /* this assumes that ONLY the firmware is protected! */ |
| if (au_image[i].type == AU_FIRMWARE) { |
| flash_sect_protect(0, start, end); |
| } |
| |
| /* |
| * erase the address range. |
| */ |
| if (au_image[i].type != AU_NAND) { |
| printf("Updating NOR FLASH with image %s\n", au_image[i].name); |
| debug ("flash_sect_erase(%lx, %lx);\n", start, end); |
| flash_sect_erase(start, end); |
| } else { |
| #if (CONFIG_COMMANDS & CFG_CMD_NAND) |
| printf("Updating NAND FLASH with image %s\n", au_image[i].name); |
| debug ("nand_erase(%lx, %lx);\n", start, end); |
| rc = nand_erase (nand_dev_desc, start, end - start + 1, 0); |
| debug ("nand_erase returned %x\n", rc); |
| #endif |
| } |
| |
| udelay(10000); |
| |
| /* strip the header - except for the kernel and ramdisk */ |
| if (au_image[i].type != AU_FIRMWARE) { |
| addr = (char *)hdr; |
| off = sizeof(*hdr); |
| nbytes = sizeof(*hdr) + ntohl(hdr->ih_size); |
| } else { |
| addr = (char *)((char *)hdr + sizeof(*hdr)); |
| off = 0; |
| nbytes = ntohl(hdr->ih_size); |
| } |
| |
| /* |
| * copy the data from RAM to FLASH |
| */ |
| if (au_image[i].type != AU_NAND) { |
| debug ("flash_write(%p, %lx %x)\n", addr, start, nbytes); |
| rc = flash_write((char *)addr, start, nbytes); |
| } else { |
| #if (CONFIG_COMMANDS & CFG_CMD_NAND) |
| debug ("nand_rw(%p, %lx %x)\n", addr, start, nbytes); |
| rc = nand_rw(nand_dev_desc, NANDRW_WRITE | NANDRW_JFFS2, |
| start, nbytes, (size_t *)&total, (uchar *)addr); |
| debug ("nand_rw: ret=%x total=%d nbytes=%d\n", rc, total, nbytes); |
| #endif |
| } |
| if (rc != 0) { |
| printf("Flashing failed due to error %d\n", rc); |
| return -1; |
| } |
| |
| /* |
| * check the dcrc of the copy |
| */ |
| if (au_image[i].type != AU_NAND) { |
| rc = crc32 (0, (uchar *)(start + off), ntohl(hdr->ih_size)); |
| } else { |
| #if (CONFIG_COMMANDS & CFG_CMD_NAND) |
| rc = nand_rw(nand_dev_desc, NANDRW_READ | NANDRW_JFFS2 | NANDRW_JFFS2_SKIP, |
| start, nbytes, (size_t *)&total, (uchar *)addr); |
| rc = crc32 (0, (uchar *)(addr + off), ntohl(hdr->ih_size)); |
| #endif |
| } |
| if (rc != ntohl(hdr->ih_dcrc)) { |
| printf ("Image %s Bad Data Checksum After COPY\n", au_image[i].name); |
| return -1; |
| } |
| |
| /* protect the address range */ |
| /* this assumes that ONLY the firmware is protected! */ |
| if (au_image[i].type == AU_FIRMWARE) { |
| flash_sect_protect(1, start, end); |
| } |
| |
| break; |
| |
| default: |
| printf("Wrong image type selected!\n"); |
| } |
| |
| return 0; |
| } |
| |
| |
| static void process_macros (const char *input, char *output) |
| { |
| char c, prev; |
| const char *varname_start = NULL; |
| int inputcnt = strlen (input); |
| int outputcnt = CFG_CBSIZE; |
| int state = 0; /* 0 = waiting for '$' */ |
| /* 1 = waiting for '(' or '{' */ |
| /* 2 = waiting for ')' or '}' */ |
| /* 3 = waiting for ''' */ |
| #ifdef DEBUG_PARSER |
| char *output_start = output; |
| |
| printf ("[PROCESS_MACROS] INPUT len %d: \"%s\"\n", strlen(input), input); |
| #endif |
| |
| prev = '\0'; /* previous character */ |
| |
| while (inputcnt && outputcnt) { |
| c = *input++; |
| inputcnt--; |
| |
| if (state!=3) { |
| /* remove one level of escape characters */ |
| if ((c == '\\') && (prev != '\\')) { |
| if (inputcnt-- == 0) |
| break; |
| prev = c; |
| c = *input++; |
| } |
| } |
| |
| switch (state) { |
| case 0: /* Waiting for (unescaped) $ */ |
| if ((c == '\'') && (prev != '\\')) { |
| state = 3; |
| break; |
| } |
| if ((c == '$') && (prev != '\\')) { |
| state++; |
| } else { |
| *(output++) = c; |
| outputcnt--; |
| } |
| break; |
| case 1: /* Waiting for ( */ |
| if (c == '(' || c == '{') { |
| state++; |
| varname_start = input; |
| } else { |
| state = 0; |
| *(output++) = '$'; |
| outputcnt--; |
| |
| if (outputcnt) { |
| *(output++) = c; |
| outputcnt--; |
| } |
| } |
| break; |
| case 2: /* Waiting for ) */ |
| if (c == ')' || c == '}') { |
| int i; |
| char envname[CFG_CBSIZE], *envval; |
| int envcnt = input-varname_start-1; /* Varname # of chars */ |
| |
| /* Get the varname */ |
| for (i = 0; i < envcnt; i++) { |
| envname[i] = varname_start[i]; |
| } |
| envname[i] = 0; |
| |
| /* Get its value */ |
| envval = getenv (envname); |
| |
| /* Copy into the line if it exists */ |
| if (envval != NULL) |
| while ((*envval) && outputcnt) { |
| *(output++) = *(envval++); |
| outputcnt--; |
| } |
| /* Look for another '$' */ |
| state = 0; |
| } |
| break; |
| case 3: /* Waiting for ' */ |
| if ((c == '\'') && (prev != '\\')) { |
| state = 0; |
| } else { |
| *(output++) = c; |
| outputcnt--; |
| } |
| break; |
| } |
| prev = c; |
| } |
| |
| if (outputcnt) |
| *output = 0; |
| |
| #ifdef DEBUG_PARSER |
| printf ("[PROCESS_MACROS] OUTPUT len %d: \"%s\"\n", |
| strlen(output_start), output_start); |
| #endif |
| } |
| |
| |
| /* |
| * this is called from board_init() after the hardware has been set up |
| * and is usable. That seems like a good time to do this. |
| * Right now the return value is ignored. |
| */ |
| int do_auto_update(void) |
| { |
| block_dev_desc_t *stor_dev; |
| long sz; |
| int i, res, cnt, old_ctrlc, got_ctrlc; |
| char buffer[32]; |
| char str[80]; |
| |
| /* |
| * Check whether a CompactFlash is inserted |
| */ |
| if (ide_dev_desc[0].type == DEV_TYPE_UNKNOWN) { |
| return -1; /* no disk detected! */ |
| } |
| |
| /* check whether it has a partition table */ |
| stor_dev = get_dev("ide", 0); |
| if (stor_dev == NULL) { |
| debug ("Uknown device type\n"); |
| return -1; |
| } |
| if (fat_register_device(stor_dev, 1) != 0) { |
| debug ("Unable to register ide disk 0:1 for fatls\n"); |
| return -1; |
| } |
| |
| /* |
| * Check if magic file is present |
| */ |
| if (do_fat_read(AU_MAGIC_FILE, buffer, sizeof(buffer), LS_NO) <= 0) { |
| return -1; |
| } |
| |
| #ifdef CONFIG_AUTO_UPDATE_SHOW |
| board_auto_update_show(1); |
| #endif |
| puts("\nAutoUpdate Disk detected! Trying to update system...\n"); |
| |
| /* make sure that we see CTRL-C and save the old state */ |
| old_ctrlc = disable_ctrlc(0); |
| |
| /* just loop thru all the possible files */ |
| for (i = 0; i < N_AU_IMAGES; i++) { |
| /* |
| * Try to expand the environment var in the fname |
| */ |
| process_macros(au_image[i].name, str); |
| strcpy(au_image[i].name, str); |
| |
| printf("Reading %s ...", au_image[i].name); |
| /* just read the header */ |
| sz = do_fat_read(au_image[i].name, LOAD_ADDR, sizeof(image_header_t), LS_NO); |
| debug ("read %s sz %ld hdr %d\n", |
| au_image[i].name, sz, sizeof(image_header_t)); |
| if (sz <= 0 || sz < sizeof(image_header_t)) { |
| puts(" not found\n"); |
| continue; |
| } |
| if (au_check_header_valid(i, sz) < 0) { |
| puts(" header not valid\n"); |
| continue; |
| } |
| sz = do_fat_read(au_image[i].name, LOAD_ADDR, MAX_LOADSZ, LS_NO); |
| debug ("read %s sz %ld hdr %d\n", |
| au_image[i].name, sz, sizeof(image_header_t)); |
| if (sz <= 0 || sz <= sizeof(image_header_t)) { |
| puts(" not found\n"); |
| continue; |
| } |
| if (au_check_cksum_valid(i, sz) < 0) { |
| puts(" checksum not valid\n"); |
| continue; |
| } |
| puts(" done\n"); |
| |
| do { |
| res = au_do_update(i, sz); |
| /* let the user break out of the loop */ |
| if (ctrlc() || had_ctrlc()) { |
| clear_ctrlc(); |
| if (res < 0) |
| got_ctrlc = 1; |
| break; |
| } |
| cnt++; |
| } while (res < 0); |
| } |
| |
| /* restore the old state */ |
| disable_ctrlc(old_ctrlc); |
| |
| puts("AutoUpdate finished\n\n"); |
| #ifdef CONFIG_AUTO_UPDATE_SHOW |
| board_auto_update_show(0); |
| #endif |
| |
| return 0; |
| } |
| |
| |
| int auto_update(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) |
| { |
| do_auto_update(); |
| |
| return 0; |
| } |
| U_BOOT_CMD( |
| autoupd, 1, 1, auto_update, |
| "autoupd - Automatically update images\n", |
| NULL |
| ); |
| #endif /* CONFIG_AUTO_UPDATE */ |