summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-07-11 02:32:05 +0100
committerAidan MacDonald <amachronic@protonmail.com>2021-07-11 15:39:50 +0100
commit84362141a0d1e33b29d12162caba7537dd5684b8 (patch)
treed966b33e6049cc54abe04d9b66ef0db794ebe948
parente9d228832ccbee7e486c45a51680c6f94ce1ab92 (diff)
downloadrockbox-84362141a0.tar.gz
rockbox-84362141a0.zip
x1000: Unified flash bootloader installer
Change-Id: Ib1d41d4e7d663ff8a21eb08108c13568f7408533
-rw-r--r--bootloader/x1000.c23
-rw-r--r--firmware/SOURCES6
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c253
-rw-r--r--firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.h51
-rw-r--r--firmware/target/mips/ingenic_x1000/installer-x1000.c326
-rw-r--r--firmware/target/mips/ingenic_x1000/installer-x1000.h55
6 files changed, 390 insertions, 324 deletions
diff --git a/bootloader/x1000.c b/bootloader/x1000.c
index 0b28b7449d..2c61773b11 100644
--- a/bootloader/x1000.c
+++ b/bootloader/x1000.c
@@ -48,16 +48,13 @@
#include "loader_strerror.h"
#include "version.h"
#include "boot-x1000.h"
+#include "installer-x1000.h"
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
-#ifdef FIIO_M3K
-# include "installer-fiiom3k.h"
-#endif
-
#if defined(FIIO_M3K)
# define BL_RECOVERY BUTTON_VOL_UP
# define BL_UP BUTTON_VOL_UP
@@ -68,6 +65,7 @@
# define BL_DOWN_NAME "VOL-"
# define BL_SELECT_NAME "PLAY"
# define BL_QUIT_NAME "POWER"
+# define BOOTBACKUP_FILE "/fiiom3k-boot.bin"
#else
# error "Missing keymap!"
#endif
@@ -387,14 +385,12 @@ void reboot(void)
while(1);
}
-/* TODO: clean this up, make the installer generic as well */
enum {
INSTALL,
BACKUP,
RESTORE,
};
-#ifdef FIIO_M3K
void bootloader_action(int which)
{
if(init_disk() != 0) {
@@ -414,14 +410,14 @@ void bootloader_action(int which)
int rc;
switch(which) {
- case INSTALL: rc = install_boot("/bootloader.m3k"); break;
- case BACKUP: rc = backup_boot("/fiiom3k-boot.bin"); break;
- case RESTORE: rc = restore_boot("/fiiom3k-boot.bin"); break;
+ case INSTALL: rc = install_bootloader("/bootloader." BOOTFILE_EXT); break;
+ case BACKUP: rc = backup_bootloader(BOOTBACKUP_FILE); break;
+ case RESTORE: rc = restore_bootloader(BOOTBACKUP_FILE); break;
default: return;
}
static char buf[64];
- snprintf(buf, sizeof(buf), "Failed! Error: %d", rc);
+ snprintf(buf, sizeof(buf), "%s (%d)", installer_strerror(rc), rc);
const char* msg1 = rc == 0 ? "Success" : buf;
const char* msg2 = "Press " BL_QUIT_NAME " to continue";
splash2(0, msg1, msg2);
@@ -429,13 +425,6 @@ void bootloader_action(int which)
button_clear_queue();
while(button_get(true) != BL_QUIT);
}
-#else
-void bootloader_action(int which)
-{
- (void)which;
- splash(5*HZ, "Not implemented!");
-}
-#endif
void bootloader_install(void)
{
diff --git a/firmware/SOURCES b/firmware/SOURCES
index b1024d97da..2414682bb4 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -1676,9 +1676,12 @@ target/mips/ingenic_x1000/msc-x1000.c
#if (CONFIG_STORAGE & STORAGE_SD)
target/mips/ingenic_x1000/sd-x1000.c
#endif
+#ifdef BOOTLOADER
+target/mips/ingenic_x1000/installer-x1000.c
target/mips/ingenic_x1000/spl-start.S
target/mips/ingenic_x1000/spl-x1000.c
common/ucl_decompress.c
+#endif
#endif /* CONFIG_CPU == X1000 */
#if defined(ONDA_VX747) || defined(ONDA_VX747P) || defined(ONDA_VX777)
@@ -1711,9 +1714,6 @@ target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c
target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c
target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c
target/mips/ingenic_x1000/fiiom3k/spl-fiiom3k.c
-#ifdef BOOTLOADER
-target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c
-#endif
#endif /* FIIO_M3K */
#if defined(LYRE_PROTO1)
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c
deleted file mode 100644
index 8ce73bf09e..0000000000
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * 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-fiiom3k.h"
-#include "nand-x1000.h"
-#include "system.h"
-#include "core_alloc.h"
-#include "file.h"
-#include "microtar.h"
-#include <stdint.h>
-#include <string.h>
-#include <stdio.h>
-
-#define IMAGE_SIZE (128 * 1024)
-#define TAR_SIZE (256 * 1024)
-
-static int flash_img_read(uint8_t* buffer)
-{
- nand_drv* drv = nand_init();
- nand_lock(drv);
-
- int rc = nand_open(drv);
- if(rc < 0)
- goto error;
-
- rc = nand_read_bytes(drv, 0, IMAGE_SIZE, buffer);
- if(rc < 0) {
- rc = INSTALL_ERR_FLASH(NAND_READ, rc);
- goto error;
- }
-
- error:
- nand_close(drv);
- nand_unlock(drv);
- return rc;
-}
-
-static int flash_img_write(const uint8_t* buffer)
-{
- nand_drv* drv = nand_init();
- nand_lock(drv);
-
- int rc = nand_open(drv);
- if(rc < 0)
- goto error;
-
- rc = nand_write_bytes(drv, 0, IMAGE_SIZE, buffer);
- if(rc < 0) {
- rc = INSTALL_ERR_FLASH(NAND_WRITE, rc);
- goto error;
- }
-
- error:
- nand_close(drv);
- nand_unlock(drv);
- return rc;
-}
-
-static int patch_img(mtar_t* tar, uint8_t* buffer, const char* filename,
- size_t patch_offset, size_t patch_size)
-{
- /* Seek to file */
- mtar_header_t h;
- int rc = mtar_find(tar, filename, &h);
- if(rc != MTAR_ESUCCESS) {
- rc = INSTALL_ERR_MTAR(TAR_FIND, rc);
- return rc;
- }
-
- /* We need a normal file */
- if(h.type != 0 && h.type != MTAR_TREG)
- return INSTALL_ERR_BAD_FORMAT;
-
- /* Check size does not exceed patch area */
- if(h.size > patch_size)
- return INSTALL_ERR_BAD_FORMAT;
-
- /* Read data directly into patch area, fill unused bytes with 0xff */
- memset(&buffer[patch_offset], 0xff, patch_size);
- rc = mtar_read_data(tar, &buffer[patch_offset], h.size);
- if(rc != MTAR_ESUCCESS) {
- rc = INSTALL_ERR_MTAR(TAR_READ, rc);
- return rc;
- }
-
- return INSTALL_SUCCESS;
-}
-
-int install_boot(const char* srcfile)
-{
- int rc;
- mtar_t* tar = NULL;
- int handle = -1;
-
- /* Allocate enough memory for image and tar state */
- size_t bufsize = IMAGE_SIZE + sizeof(mtar_t) + 2*CACHEALIGN_SIZE;
- handle = core_alloc("boot_image", bufsize);
- if(handle < 0) {
- rc = INSTALL_ERR_OUT_OF_MEMORY;
- goto error;
- }
-
- uint8_t* buffer = core_get_data(handle);
-
- /* Tar state alloc */
- CACHEALIGN_BUFFER(buffer, bufsize);
- tar = (mtar_t*)buffer;
- memset(tar, 0, sizeof(tar));
-
- /* Image buffer alloc */
- buffer += sizeof(mtar_t);
- CACHEALIGN_BUFFER(buffer, bufsize);
-
- /* Read the flash -- we need an existing image to patch */
- rc = flash_img_read(buffer);
- if(rc < 0)
- goto error;
-
- /* Open the tarball */
- rc = mtar_open(tar, srcfile, "r");
- if(rc != MTAR_ESUCCESS) {
- rc = INSTALL_ERR_MTAR(TAR_OPEN, rc);
- goto error;
- }
-
- /* Extract the needed files & patch 'em in */
- rc = patch_img(tar, buffer, "spl.m3k", 0, 12 * 1024);
- if(rc < 0)
- goto error;
-
- rc = patch_img(tar, buffer, "bootloader.ucl", 0x6800, 102 * 1024);
- if(rc < 0)
- goto error;
-
- /* Flash the new image */
- rc = flash_img_write(buffer);
- if(rc < 0)
- goto error;
-
- rc = INSTALL_SUCCESS;
-
- error:
- if(tar && tar->close)
- mtar_close(tar);
- if(handle >= 0)
- core_free(handle);
- return rc;
-}
-
-int backup_boot(const char* destfile)
-{
- int rc;
- int handle = -1;
- int fd = -1;
- size_t bufsize = IMAGE_SIZE + CACHEALIGN_SIZE - 1;
- handle = core_alloc("boot_image", bufsize);
- if(handle < 0) {
- rc = INSTALL_ERR_OUT_OF_MEMORY;
- goto error;
- }
-
- uint8_t* buffer = core_get_data(handle);
- CACHEALIGN_BUFFER(buffer, bufsize);
-
- rc = flash_img_read(buffer);
- if(rc < 0)
- goto error;
-
- fd = open(destfile, O_CREAT|O_TRUNC|O_WRONLY);
- if(fd < 0) {
- rc = INSTALL_ERR_FILE_IO;
- goto error;
- }
-
- ssize_t cnt = write(fd, buffer, IMAGE_SIZE);
- if(cnt != IMAGE_SIZE) {
- rc = INSTALL_ERR_FILE_IO;
- goto error;
- }
-
- error:
- if(fd >= 0)
- close(fd);
- if(handle >= 0)
- core_free(handle);
- return rc;
-}
-
-int restore_boot(const char* srcfile)
-{
- int rc;
- int handle = -1;
- int fd = -1;
- size_t bufsize = IMAGE_SIZE + CACHEALIGN_SIZE - 1;
- handle = core_alloc("boot_image", bufsize);
- if(handle < 0) {
- rc = INSTALL_ERR_OUT_OF_MEMORY;
- goto error;
- }
-
- uint8_t* buffer = core_get_data(handle);
- CACHEALIGN_BUFFER(buffer, bufsize);
-
- fd = open(srcfile, O_RDONLY);
- if(fd < 0) {
- rc = INSTALL_ERR_FILE_NOT_FOUND;
- goto error;
- }
-
- off_t fsize = filesize(fd);
- if(fsize != IMAGE_SIZE) {
- rc = INSTALL_ERR_BAD_FORMAT;
- goto error;
- }
-
- ssize_t cnt = read(fd, buffer, IMAGE_SIZE);
- if(cnt != IMAGE_SIZE) {
- rc = INSTALL_ERR_FILE_IO;
- goto error;
- }
-
- close(fd);
- fd = -1;
-
- rc = flash_img_write(buffer);
- if(rc < 0)
- goto error;
-
- error:
- if(fd >= 0)
- close(fd);
- if(handle >= 0)
- core_free(handle);
- return rc;
-}
diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.h b/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.h
deleted file mode 100644
index eb700e6689..0000000000
--- a/firmware/target/mips/ingenic_x1000/fiiom3k/installer-fiiom3k.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * 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.
- *
- ****************************************************************************/
-
-#ifndef __INSTALLER_FIIOM3K_H__
-#define __INSTALLER_FIIOM3K_H__
-
-#include <stddef.h>
-
-#define INSTALL_SUCCESS 0
-#define INSTALL_ERR_OUT_OF_MEMORY (-1)
-#define INSTALL_ERR_FILE_NOT_FOUND (-2)
-#define INSTALL_ERR_FILE_IO (-3)
-#define INSTALL_ERR_BAD_FORMAT (-4)
-#define INSTALL_ERR_NAND_OPEN (-5)
-#define INSTALL_ERR_NAND_IDENTIFY (-6)
-#define INSTALL_ERR_NAND_READ (-7)
-#define INSTALL_ERR_NAND_ENABLE_WRITES (-8)
-#define INSTALL_ERR_NAND_ERASE (-9)
-#define INSTALL_ERR_NAND_WRITE (-10)
-#define INSTALL_ERR_TAR_OPEN (-11)
-#define INSTALL_ERR_TAR_FIND (-12)
-#define INSTALL_ERR_TAR_READ (-13)
-#define INSTALL_ERR_MTAR(x,y) ((INSTALL_ERR_##x)*100 + (y))
-#define INSTALL_ERR_FLASH(x,y) ((INSTALL_ERR_##x)*100 + (y))
-
-/* Install the Rockbox bootloader from a bootloader.m3k image */
-extern int install_boot(const char* srcfile);
-
-/* Backup or restore the bootloader from a raw NAND image */
-extern int backup_boot(const char* destfile);
-extern int restore_boot(const char* srcfile);
-
-#endif /* __INSTALLER_FIIOM3K_H__ */
diff --git a/firmware/target/mips/ingenic_x1000/installer-x1000.c b/firmware/target/mips/ingenic_x1000/installer-x1000.c
new file mode 100644
index 0000000000..617e6645b7
--- /dev/null
+++ b/firmware/target/mips/ingenic_x1000/installer-x1000.c
@@ -0,0 +1,326 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.h"
+#include <stddef.h>
+
+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);
+
+/* 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(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)
+{
+ mtar_header_t h;
+ int rc = mtar_find(tar, part->filename, &h);
+ if(rc != MTAR_ESUCCESS)
+ return IERR_BAD_FORMAT;
+
+ 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 != MTAR_ESUCCESS)
+ 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;
+ 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("boot_image", u->buf_len);
+ 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(struct mtar_t));
+
+ rc = IERR_SUCCESS;
+
+ error:
+ return rc;
+}
+
+static void updater_cleanup(struct updater* u)
+{
+ if(u->tar && u->tar->close)
+ mtar_close(u->tar);
+
+ if(u->buf_hnd >= 0)
+ 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, "r");
+ 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;
+ }
+
+ /* 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;
+ }
+
+ /* 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";
+ default: return "Unknown error!?";
+ }
+}
diff --git a/firmware/target/mips/ingenic_x1000/installer-x1000.h b/firmware/target/mips/ingenic_x1000/installer-x1000.h
new file mode 100644
index 0000000000..b71839a907
--- /dev/null
+++ b/firmware/target/mips/ingenic_x1000/installer-x1000.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __INSTALLER_X1000_H__
+#define __INSTALLER_X1000_H__
+
+/* This API is for the bootloader recovery menu and Rockbox utility to handle
+ * bootloader installation, backup, and restore.
+ *
+ * Currently the installer can only handle NAND flash, although the X1000 can
+ * boot from NOR flash or SD/MMC. Support for other storage media can be added
+ * when there is a target that needs it.
+ *
+ * Bootloader updates are tarballs, and they can "monkey patch" the flash in
+ * a customizable way (but fixed at compile time).
+ *
+ * Backup and restore simply takes the range of eraseblocks touched by the
+ * monkey patch and copies them to or from a regular file.
+ */
+
+enum {
+ IERR_SUCCESS = 0,
+ IERR_OUT_OF_MEMORY,
+ IERR_FILE_NOT_FOUND,
+ IERR_FILE_IO,
+ IERR_BAD_FORMAT,
+ IERR_NAND_OPEN,
+ IERR_NAND_READ,
+ IERR_NAND_WRITE,
+};
+
+extern int install_bootloader(const char* filename);
+extern int backup_bootloader(const char* filename);
+extern int restore_bootloader(const char* filename);
+extern const char* installer_strerror(int rc);
+
+#endif /* __INSTALLER_X1000_H__ */