/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2021 Aidan MacDonald * * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "installer-x1000.h" #include "nand-x1000.h" #include "core_alloc.h" #include "file.h" #include "microtar-rockbox.h" #include struct update_part { const char* filename; size_t offset; size_t length; }; /* Parts of the flash to update. The offset and length are given in bytes, * offset relative to start of flash. The region's new contents are given * by the named file inside the update archive. If any file is missing, the * update will fail. (gracefully! nothing is written unless the package has * all its components) * * If the update file is smaller than the region size, unused space at the * end of the region is padded with 0xff. * * NOTE: The current code assumes all parts are contiguous. The current * update map fits in one eraseblock, but if it ever needs extending beyond * that, better implement a bitmap to indicate which blocks need updating * and which can be skipped. We don't want to erase and reprogram blocks * for no good reason, it's bad for the flash lifespan. */ static const struct update_part updates[] = { { .filename = "spl." BOOTFILE_EXT, .offset = 0, .length = 12 * 1024, }, { .filename = "bootloader.ucl", .offset = 0x6800, .length = 102 * 1024, }, }; static const int num_updates = sizeof(updates) / sizeof(struct update_part); static const uint8_t flash_sig_magic[8] = {0x06, 0x05, 0x04, 0x03, 0x02, 0x55, 0xaa, 0x55}; /* calculate the offset and length of the update image; this is constant * for a given target, based on the update parts and the NAND chip geometry. */ static void get_image_loc(struct nand_drv* ndrv, size_t* offptr, size_t* lenptr) { size_t blk_size = ndrv->chip->page_size << ndrv->chip->log2_ppb; size_t img_off = 0; size_t img_len = 0; /* calculate minimal image needed to contain all update blocks */ for(int i = 0; i < num_updates; ++i) { img_len = MAX(img_len, updates[i].offset + updates[i].length); img_off = MIN(img_off, updates[i].offset); } /* round everything to a multiple of the block size */ size_t r_off = blk_size * (img_off / blk_size); size_t r_len = blk_size * ((img_len + img_off - r_off + blk_size - 1) / blk_size); *offptr = r_off; *lenptr = r_len; } /* Read in a single part of the update from the tarball, and patch it * into the image */ static int patch_part(mtar_t* tar, const struct update_part* part, uint8_t* img_buf, size_t img_off) { int rc = mtar_find(tar, part->filename); if(rc != MTAR_ESUCCESS) return IERR_BAD_FORMAT; const mtar_header_t* h = mtar_get_header(tar); if(h->type != 0 && h->type != MTAR_TREG) return IERR_BAD_FORMAT; if(h->size > part->length) return IERR_BAD_FORMAT; /* wipe the patched area, and read in the new data */ memset(&img_buf[part->offset - img_off], 0xff, part->length); rc = mtar_read_data(tar, &img_buf[part->offset - img_off], h->size); if(rc < 0 || (unsigned)rc != h->size) return IERR_FILE_IO; return IERR_SUCCESS; } struct updater { int buf_hnd; /* core_alloc handle for our memory buffer */ size_t buf_len; /* sizeof the buffer */ uint8_t* img_buf; size_t img_off; /* image address in flash */ size_t img_len; /* image length in flash = size of the buffer */ mtar_t* tar; struct nand_drv* ndrv; }; static int updater_init(struct updater* u) { int rc; /* initialize stuff correctly */ u->buf_hnd = -1; u->buf_len = 0; u->img_buf = NULL; u->img_off = 0; u->img_len = 0; u->tar = NULL; u->ndrv = NULL; /* open NAND */ u->ndrv = nand_init(); nand_lock(u->ndrv); rc = nand_open(u->ndrv); if(rc != NAND_SUCCESS) { rc = IERR_NAND_OPEN; goto error; } get_image_loc(u->ndrv, &u->img_off, &u->img_len); /* buf_len is a bit oversized here, but it's not really important */ u->buf_len = u->img_len + sizeof(mtar_t) + 2*CACHEALIGN_SIZE; u->buf_hnd = core_alloc_ex(u->buf_len, &buflib_ops_locked); if(u->buf_hnd < 0) { rc = IERR_OUT_OF_MEMORY; goto error; } /* allocate from the buffer */ uint8_t* buffer = (uint8_t*)core_get_data(u->buf_hnd); size_t buf_len = u->buf_len; CACHEALIGN_BUFFER(buffer, buf_len); u->img_buf = buffer; buffer += u->img_len; buf_len -= u->img_len; CACHEALIGN_BUFFER(buffer, buf_len); u->tar = (mtar_t*)buffer; memset(u->tar, 0, sizeof(mtar_t)); rc = IERR_SUCCESS; error: return rc; } static void updater_cleanup(struct updater* u) { if(u->tar && mtar_is_open(u->tar)) mtar_close(u->tar); core_free(u->buf_hnd); if(u->ndrv) { nand_close(u->ndrv); nand_unlock(u->ndrv); } } int install_bootloader(const char* filename) { struct updater u; int rc = updater_init(&u); if(rc != IERR_SUCCESS) goto error; /* get the image */ rc = nand_read_bytes(u.ndrv, u.img_off, u.img_len, u.img_buf); if(rc != NAND_SUCCESS) { rc = IERR_NAND_READ; goto error; } /* get the tarball */ rc = mtar_open(u.tar, filename, O_RDONLY); if(rc != MTAR_ESUCCESS) { if(rc == MTAR_EOPENFAIL) rc = IERR_FILE_NOT_FOUND; else if(rc == MTAR_EREADFAIL) rc = IERR_FILE_IO; else rc = IERR_BAD_FORMAT; goto error; } /* patch stuff */ for(int i = 0; i < num_updates; ++i) { rc = patch_part(u.tar, &updates[i], u.img_buf, u.img_off); if(rc != IERR_SUCCESS) goto error; } /* write back the patched image */ rc = nand_write_bytes(u.ndrv, u.img_off, u.img_len, u.img_buf); if(rc != NAND_SUCCESS) { rc = IERR_NAND_WRITE; goto error; } rc = IERR_SUCCESS; error: updater_cleanup(&u); return rc; } int backup_bootloader(const char* filename) { int rc, fd = 0; struct updater u; rc = updater_init(&u); if(rc != IERR_SUCCESS) goto error; /* read image */ rc = nand_read_bytes(u.ndrv, u.img_off, u.img_len, u.img_buf); if(rc != NAND_SUCCESS) { rc = IERR_NAND_READ; goto error; } /* bail if we're backing up something that looks like garbage */ if (memcmp(u.img_buf, flash_sig_magic, 8)) { rc = IERR_CORRUPTED_BACKUP; goto error; } /* write to file */ fd = open(filename, O_CREAT|O_TRUNC|O_WRONLY); if(fd < 0) { rc = IERR_FILE_IO; goto error; } ssize_t cnt = write(fd, u.img_buf, u.img_len); if(cnt < 0 || (size_t)cnt != u.img_len) { rc = IERR_FILE_IO; goto error; } rc = IERR_SUCCESS; error: if(fd >= 0) close(fd); updater_cleanup(&u); return rc; } int restore_bootloader(const char* filename) { int rc, fd = 0; struct updater u; rc = updater_init(&u); if(rc != IERR_SUCCESS) goto error; /* read from file */ fd = open(filename, O_RDONLY); if(fd < 0) { rc = IERR_FILE_NOT_FOUND; goto error; } ssize_t cnt = read(fd, u.img_buf, u.img_len); if(cnt < 0 || (size_t)cnt != u.img_len) { rc = IERR_FILE_IO; goto error; } /* safety check to reduce risk of flashing complete garbage */ if (memcmp(u.img_buf, flash_sig_magic, 8)) { rc = IERR_CORRUPTED_BACKUP; goto error; } /* write image */ rc = nand_write_bytes(u.ndrv, u.img_off, u.img_len, u.img_buf); if(rc != NAND_SUCCESS) { rc = IERR_NAND_WRITE; goto error; } rc = IERR_SUCCESS; error: if(fd >= 0) close(fd); updater_cleanup(&u); return rc; } const char* installer_strerror(int rc) { switch(rc) { case IERR_SUCCESS: return "Success"; case IERR_OUT_OF_MEMORY: return "Out of memory"; case IERR_FILE_NOT_FOUND: return "File not found"; case IERR_FILE_IO: return "Disk I/O error"; case IERR_BAD_FORMAT: return "Bad archive"; case IERR_NAND_OPEN: return "NAND open error"; case IERR_NAND_READ: return "NAND read error"; case IERR_NAND_WRITE: return "NAND write error"; case IERR_CORRUPTED_BACKUP: return "Backup is corrupt"; default: return "Unknown error!?"; } }