summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-11-23 20:13:52 +0000
committerAidan MacDonald <amachronic@protonmail.com>2021-11-27 15:28:19 -0500
commit06423cab58569ef01eb526e5f0d2f5c0c8917aa0 (patch)
treeb1a356600f6f218de8d8d1ad1e839aff65c96a0f
parent98f1271aec1fd461ab20a1ae145bba630a5750fb (diff)
downloadrockbox-06423cab58.tar.gz
rockbox-06423cab58.zip
x1000-installer: Initial commit of new framework
This is a new flash installer framework for the X1000 targets. A bunch of this code is *UNTESTED* but there is an external test harness which allows the library to be built and tested on a PC. Once tests are written and the bugs are ironed out this framework will replace the existing installer code. New features: - Update tarballs are MD5-checksummed to guarantee integrity. - The flash map is no longer fixed -- updates are self describing and carry a map file which specifies the areas to update. - Can take full or partial backups with checksums computed on the fly. - Supports an additional verification mode which reads back data after writing to ensure the flash contents were not silently corrupted. Change-Id: I29a89190c7ff566019f6a844ad0571f01fb7192f
-rw-r--r--lib/x1000-installer/.gitignore3
-rw-r--r--lib/x1000-installer/Makefile38
-rw-r--r--lib/x1000-installer/SOURCES6
-rw-r--r--lib/x1000-installer/include/xf_error.h43
-rw-r--r--lib/x1000-installer/include/xf_flashmap.h91
-rw-r--r--lib/x1000-installer/include/xf_nandio.h130
-rw-r--r--lib/x1000-installer/include/xf_package.h65
-rw-r--r--lib/x1000-installer/include/xf_stream.h64
-rw-r--r--lib/x1000-installer/include/xf_update.h53
-rw-r--r--lib/x1000-installer/src/xf_error.c42
-rw-r--r--lib/x1000-installer/src/xf_flashmap.c313
-rw-r--r--lib/x1000-installer/src/xf_nandio.c295
-rw-r--r--lib/x1000-installer/src/xf_package.c264
-rw-r--r--lib/x1000-installer/src/xf_stream.c214
-rw-r--r--lib/x1000-installer/src/xf_update.c149
-rw-r--r--lib/x1000-installer/test/main.c98
-rw-r--r--lib/x1000-installer/test_lib/core_alloc.c65
-rw-r--r--lib/x1000-installer/test_lib/core_alloc.h33
-rw-r--r--lib/x1000-installer/test_lib/fakenand.c270
-rw-r--r--lib/x1000-installer/test_lib/file.c11
-rw-r--r--lib/x1000-installer/test_lib/file.h18
-rw-r--r--lib/x1000-installer/test_lib/md5.c245
-rw-r--r--lib/x1000-installer/test_lib/md5.h18
-rw-r--r--lib/x1000-installer/test_lib/nand-x1000.h112
-rw-r--r--lib/x1000-installer/test_lib/pathfuncs.c130
-rw-r--r--lib/x1000-installer/test_lib/pathfuncs.h39
-rw-r--r--lib/x1000-installer/test_lib/strlcpy.c50
-rw-r--r--lib/x1000-installer/test_lib/strlcpy.h4
-rw-r--r--lib/x1000-installer/test_lib/system.h10
-rw-r--r--lib/x1000-installer/x1000-installer.make21
30 files changed, 2894 insertions, 0 deletions
diff --git a/lib/x1000-installer/.gitignore b/lib/x1000-installer/.gitignore
new file mode 100644
index 0000000000..dae360e116
--- /dev/null
+++ b/lib/x1000-installer/.gitignore
@@ -0,0 +1,3 @@
+xf_test
+fakeNAND.bin
+fakeNAND_meta.bin
diff --git a/lib/x1000-installer/Makefile b/lib/x1000-installer/Makefile
new file mode 100644
index 0000000000..35fa747aab
--- /dev/null
+++ b/lib/x1000-installer/Makefile
@@ -0,0 +1,38 @@
+OBJ = src/xf_error.o \
+ src/xf_flashmap.o \
+ src/xf_nandio.o \
+ src/xf_package.o \
+ src/xf_stream.o \
+ src/xf_update.o
+LIB = libx1000-installer.a
+
+TOBJ = test_lib/core_alloc.o \
+ test_lib/fakenand.o \
+ test_lib/file.o \
+ test_lib/pathfuncs.o \
+ test_lib/md5.o \
+ test_lib/strlcpy.o \
+ test/main.o
+TBIN = xf_test
+
+# dependency needs to be built manually
+MTARINC = -I../microtar/src
+MTARLIB = ../microtar/libmicrotar.a
+
+CPPFLAGS = -Iinclude -Itest_lib $(MTARINC) -D_XOPEN_SOURCE=500 -D_POSIX_C_SOURCE=200809L
+CFLAGS = -std=c99 -Wall -Wextra -O2
+
+all: $(LIB) $(TBIN)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
+
+$(LIB): $(OBJ)
+ $(AR) rcs $@ $^ >/dev/null
+
+$(TBIN): $(TOBJ) $(LIB) $(MTARLIB)
+ $(CC) -o $@ $^
+
+clean:
+ rm -f $(LIB) $(OBJ)
+ rm -f $(TBIN) $(TOBJ)
diff --git a/lib/x1000-installer/SOURCES b/lib/x1000-installer/SOURCES
new file mode 100644
index 0000000000..cdbd9148d8
--- /dev/null
+++ b/lib/x1000-installer/SOURCES
@@ -0,0 +1,6 @@
+src/xf_error.c
+src/xf_flashmap.c
+src/xf_nandio.c
+src/xf_package.c
+src/xf_stream.c
+src/xf_update.c
diff --git a/lib/x1000-installer/include/xf_error.h b/lib/x1000-installer/include/xf_error.h
new file mode 100644
index 0000000000..2f3f6a1a4f
--- /dev/null
+++ b/lib/x1000-installer/include/xf_error.h
@@ -0,0 +1,43 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 _XF_ERROR_H_
+#define _XF_ERROR_H_
+
+enum {
+ XF_E_SUCCESS = 0,
+ XF_E_IO = -1,
+ XF_E_LINE_TOO_LONG = -2,
+ XF_E_FILENAME_TOO_LONG = -3,
+ XF_E_INT_OVERFLOW = -4,
+ XF_E_BUF_OVERFLOW = -5,
+ XF_E_SYNTAX_ERROR = -6,
+ XF_E_INVALID_PARAMETER = -7,
+ XF_E_NAND = -8,
+ XF_E_OUT_OF_MEMORY = -9,
+ XF_E_OUT_OF_RANGE = -10,
+ XF_E_VERIFY_FAILED = -11,
+ XF_E_CANNOT_OPEN_FILE = -12,
+};
+
+const char* xf_strerror(int err);
+
+#endif /* _XF_ERROR_H_ */
diff --git a/lib/x1000-installer/include/xf_flashmap.h b/lib/x1000-installer/include/xf_flashmap.h
new file mode 100644
index 0000000000..b0470e58e0
--- /dev/null
+++ b/lib/x1000-installer/include/xf_flashmap.h
@@ -0,0 +1,91 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 _XF_FLASHMAP_H_
+#define _XF_FLASHMAP_H_
+
+#include "xf_stream.h"
+#include <stdint.h>
+
+#define XF_MAP_NAMELEN 63
+
+enum {
+ XF_MAP_HAS_MD5 = 0x01, /* 'md5' field is valid */
+ XF_MAP_HAS_FILE_LENGTH = 0x02, /* 'file_length' field is valid */
+};
+
+struct xf_map {
+ char name[XF_MAP_NAMELEN+1]; /* file name */
+ uint8_t md5[16]; /* MD5 sum of file */
+ uint32_t file_length; /* length of file in bytes */
+ uint32_t offset; /* offset in flash */
+ uint32_t length; /* region length in flash, in bytes */
+ int flags;
+};
+
+/* Parse a line with space- or tab-delimited fields of the form
+ * <name> <md5> <file_length> <offset> <length>
+ * <name> '-' <offset> <length>
+ *
+ * - name can be up to XF_FMAP_NAMELEN characters long
+ * - md5 is 32 hexadecimal characters (case insensitive)
+ * - file_length, offset, and length are 32-bit unsigned integers
+ * and can be given in decimal or (with '0x' prefix) hexadecimal
+ *
+ * Parsed data is written to *map. Returns zero on success and
+ * nonzero on error.
+ */
+int xf_map_parseline(const char* line, struct xf_map* map);
+
+/* Parse a file calling xf_map_parseline() on each line to populate
+ * a map of up to 'maxnum' regions. Blank and comment lines are
+ * ignored (comments start with '#').
+ *
+ * Returns the number of regions in the resulting map if the file was
+ * parsed successfully, or a negative value on error.
+ */
+int xf_map_parse(struct xf_stream* s, struct xf_map* map, int maxnum);
+
+/* Sort the map so its members are in ascending order with the lowest
+ * flash offset region first. After sorting, xf_map_validate() is used
+ * to check for overlapping regions.
+ *
+ * The return value is that of xf_map_validate().
+ */
+int xf_map_sort(struct xf_map* map, int num);
+
+/* Check if the input map is sorted and contains no overlap.
+ *
+ * Returns 0 if the map is sorted and contains no overlapping regions,
+ * -1 if the map isn't sorted, or if an overlapping region is detected,
+ * the index of the first overlapping region. (A returned index i is
+ * always positive: the two overlapped entries are map[i] and map[i-1].)
+ */
+int xf_map_validate(const struct xf_map* map, int num);
+
+/* Write the map to a stream. This does not check that the map is valid.
+ * Returns the number of bytes written to the stream or a negative value
+ * on error. The stream may be NULL, in which case the number of bytes
+ * that would be written are returned (similar to snprintf).
+ */
+int xf_map_write(struct xf_map* map, int num, struct xf_stream* s);
+
+#endif /* _XF_FLASHMAP_H_ */
diff --git a/lib/x1000-installer/include/xf_nandio.h b/lib/x1000-installer/include/xf_nandio.h
new file mode 100644
index 0000000000..a10b71992c
--- /dev/null
+++ b/lib/x1000-installer/include/xf_nandio.h
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 _XF_NANDIO_H_
+#define _XF_NANDIO_H_
+
+#include "nand-x1000.h"
+#include <stdint.h>
+#include <stddef.h>
+
+enum xf_nandio_mode {
+ XF_NANDIO_READ = 0,
+ XF_NANDIO_PROGRAM,
+ XF_NANDIO_VERIFY,
+};
+
+struct xf_nandio {
+ nand_drv* ndrv;
+ int nand_err; /* copy of the last NAND error code */
+ int alloc_handle;
+ enum xf_nandio_mode mode;
+
+ size_t block_size; /* size of a block, in bytes */
+ size_t page_size; /* size of a page, in bytes */
+
+ uint8_t* old_buf; /* contains the 'old' block data already on chip */
+ uint8_t* new_buf; /* contains the possibly modified 'new' block data */
+
+ nand_block_t cur_block; /* address of the current block */
+ size_t offset_in_block; /* byte offset in the current block */
+ unsigned block_valid: 1; /* 1 if buffered block data is valid */
+};
+
+int xf_nandio_init(struct xf_nandio* nio);
+void xf_nandio_destroy(struct xf_nandio* nio);
+
+/** Sets the operational mode, which determines read/write/flush semantics.
+ *
+ * - XF_NANDIO_READ: Accesses the chip in read-only mode. Writes are allowed,
+ * but should not be used. (Writes will modify a temporary buffer but this
+ * will not alter the flash contents.)
+ *
+ * - XF_NANDIO_PROGRAM: Writes are allowed to modify the flash contents.
+ * Writes within a block are accumulated in a temporary buffer. When
+ * crossing a block boundary, either by writing past the end the current
+ * block or by seeking to a new one, the data written to the temporary
+ * buffer is compared against the current flash contents. If the block
+ * has been modified, it is erased and any non-blank pages are programmed
+ * with the new data.
+ *
+ * - XF_NANDIO_VERIFY: This mode allows callers to easily check whether the
+ * flash contents match some expected contents. Callers "write" the expected
+ * contents as if programming it with XF_NANDIO_PROGRAM. When a block is
+ * flushed, if the written data doesn't match the block contents, an
+ * XF_E_VERIFY_FAILED error is returned. The flash contents will not be
+ * altered in this mode.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_set_mode(struct xf_nandio* nio, enum xf_nandio_mode mode);
+
+/** Seek to a given byte offset in the NAND flash.
+ *
+ * If the new offset is outside the current block, the current block will
+ * be automatically flushed. Note this can result in verification or program
+ * failures as with any other flush.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_seek(struct xf_nandio* nio, size_t offset);
+
+/** Read or write a contiguous sequence of bytes from flash.
+ *
+ * The read or write starts at the current position and continues for `count`
+ * bytes. Both reads and writes may cross block boundaries. Modified blocks
+ * will be flushed automatically if the operation crosses a block boundary.
+ *
+ * After a successful read or write, the current position is advanced by
+ * exactly `count` bytes. After a failure, the position is indeterminate.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_read(struct xf_nandio* nio, void* buf, size_t count);
+int xf_nandio_write(struct xf_nandio* nio, const void* buf, size_t count);
+
+/** Get a pointer to the block buffer for direct read/write access.
+ *
+ * These functions can be used to read or write data without intermediate
+ * buffers. The caller passes in the amount of data to be transferred in
+ * `*count`. A pointer to part of the block buffer is returned in `*buf`
+ * and the number of bytes available in `*buf` is returned in `*count`.
+ *
+ * Data at the current position can be read from the returned buffer and
+ * it may be modified by writing to the buffer. The buffer is only valid
+ * until the next call to an `xf_nandio` function.
+ *
+ * The read/write position is advanced by the returned `*count` on success,
+ * and is unchanged on failure.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_get_buffer(struct xf_nandio* nio, void** buf, size_t* count);
+
+/** Flush the buffered block to ensure all outstanding program or verification
+ * operations have been performed. This should only be called to ensure the
+ * final modified block is flushed after you have finished writing all data.
+ *
+ * \returns XF_E_SUCCESS or a negative error code on failure.
+ */
+int xf_nandio_flush(struct xf_nandio* nio);
+
+#endif /* _XF_NANDIO_H_ */
diff --git a/lib/x1000-installer/include/xf_package.h b/lib/x1000-installer/include/xf_package.h
new file mode 100644
index 0000000000..6633766bd7
--- /dev/null
+++ b/lib/x1000-installer/include/xf_package.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 _XF_PACKAGE_H_
+#define _XF_PACKAGE_H_
+
+/* package format
+ *
+ * - bootloader-info.txt (contains a version label and optional metadata)
+ * - flashmap.txt (describes the flash update map)
+ * - [package must contain any other files referenced by the flash map]
+ * - [other files may be present, but are ignored]
+ */
+
+#include "xf_flashmap.h"
+#include "xf_stream.h"
+#include "microtar.h"
+
+struct xf_package {
+ int alloc_handle;
+ mtar_t* tar;
+ struct xf_map* map;
+ int map_size;
+ char* metadata;
+ size_t metadata_len;
+};
+
+/** Open an update package
+ *
+ * \param pkg Uninitialized package structure
+ * \param file Name of the package file
+ * \param dflt_map Default flash map for loading old format packages
+ * \param dflt_map_size Size of the default flash map
+ * \returns XF_E_SUCCESS or a negative error code
+ */
+int xf_package_open_ex(struct xf_package* pkg, const char* file,
+ const struct xf_map* dflt_map, int dflt_map_size);
+
+/** Close a package which was previously opened successfully */
+void xf_package_close(struct xf_package* pkg);
+
+static inline int xf_package_open(struct xf_package* pkg, const char* file)
+{
+ return xf_package_open_ex(pkg, file, NULL, 0);
+}
+
+#endif /* _XF_PACKAGE_H_ */
diff --git a/lib/x1000-installer/include/xf_stream.h b/lib/x1000-installer/include/xf_stream.h
new file mode 100644
index 0000000000..adbde1c6db
--- /dev/null
+++ b/lib/x1000-installer/include/xf_stream.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 _XF_STREAM_H_
+#define _XF_STREAM_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h> /* ssize_t */
+#include "microtar.h"
+
+struct xf_stream;
+
+struct xf_stream_ops {
+ off_t(*xget_size)(struct xf_stream* s);
+ ssize_t(*xread)(struct xf_stream* s, void* buf, size_t len);
+ ssize_t(*xwrite)(struct xf_stream* s, const void* buf, size_t len);
+ int(*xclose)(struct xf_stream* s);
+};
+
+struct xf_stream {
+ intptr_t data;
+ const struct xf_stream_ops* ops;
+};
+
+inline size_t xf_stream_get_size(struct xf_stream* s)
+{ return s->ops->xget_size(s); }
+
+inline ssize_t xf_stream_read(struct xf_stream* s, void* buf, size_t len)
+{ return s->ops->xread(s, buf, len); }
+
+inline ssize_t xf_stream_write(struct xf_stream* s, const void* buf, size_t len)
+{ return s->ops->xwrite(s, buf, len); }
+
+inline int xf_stream_close(struct xf_stream* s)
+{ return s->ops->xclose(s); }
+
+int xf_open_file(const char* file, int flags, struct xf_stream* s);
+int xf_open_tar(mtar_t* mtar, const char* file, struct xf_stream* s);
+int xf_create_tar(mtar_t* mtar, const char* file, size_t size, struct xf_stream* s);
+
+/* Utility function needed for a few things */
+int xf_stream_read_lines(struct xf_stream* s, char* buf, size_t bufsz,
+ int(*callback)(int n, char* buf, void* arg), void* arg);
+
+#endif /* _XF_STREAM_H_ */
diff --git a/lib/x1000-installer/include/xf_update.h b/lib/x1000-installer/include/xf_update.h
new file mode 100644
index 0000000000..e421a21793
--- /dev/null
+++ b/lib/x1000-installer/include/xf_update.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 _XF_UPDATE_H_
+#define _XF_UPDATE_H_
+
+#include "xf_package.h"
+#include "xf_nandio.h"
+#include "xf_flashmap.h"
+
+typedef int(*xf_update_open_stream_cb)(void* arg, const char* file,
+ struct xf_stream* stream);
+
+enum xf_update_mode {
+ XF_UPDATE,
+ XF_BACKUP,
+ XF_VERIFY,
+};
+
+/** The main updater entry point
+ *
+ * \param mode Operational mode
+ * \param nio Initialized NAND I/O object.
+ * \param map Flash map describing what regions to update.
+ * \param map_size Number of entries in the map.
+ * \param open_stream Callback used to open a stream for each map entry.
+ * \param arg Argument passed to the `open_stream` callback.
+ *
+ * \returns XF_E_SUCCESS on success or a negative error code on failure.
+ */
+int xf_updater_run(enum xf_update_mode mode, struct xf_nandio* nio,
+ struct xf_map* map, int map_size,
+ xf_update_open_stream_cb open_stream, void* arg);
+
+#endif /* _XF_UPDATE_H_ */
diff --git a/lib/x1000-installer/src/xf_error.c b/lib/x1000-installer/src/xf_error.c
new file mode 100644
index 0000000000..3d4b342a92
--- /dev/null
+++ b/lib/x1000-installer/src/xf_error.c
@@ -0,0 +1,42 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_error.h"
+
+const char* xf_strerror(int err)
+{
+ switch(err) {
+ case XF_E_SUCCESS: return "Success";
+ case XF_E_IO: return "I/O error";
+ case XF_E_LINE_TOO_LONG: return "Line too long";
+ case XF_E_FILENAME_TOO_LONG: return "Filename too long";
+ case XF_E_INT_OVERFLOW: return "Numeric overflow";
+ case XF_E_BUF_OVERFLOW: return "Buffer overflowed";
+ case XF_E_SYNTAX_ERROR: return "Syntax error";
+ case XF_E_INVALID_PARAMETER: return "Invalid parameter";
+ case XF_E_NAND: return "NAND flash error";
+ case XF_E_OUT_OF_MEMORY: return "Out of memory";
+ case XF_E_OUT_OF_RANGE: return "Out of range";
+ case XF_E_VERIFY_FAILED: return "Verification failed";
+ case XF_E_CANNOT_OPEN_FILE: return "Cannot open file";
+ default: return "Unknown error";
+ }
+}
diff --git a/lib/x1000-installer/src/xf_flashmap.c b/lib/x1000-installer/src/xf_flashmap.c
new file mode 100644
index 0000000000..75cd3c5905
--- /dev/null
+++ b/lib/x1000-installer/src/xf_flashmap.c
@@ -0,0 +1,313 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_flashmap.h"
+#include "xf_error.h"
+#include <stdbool.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+static int xdigit_to_int(char c)
+{
+ if(c >= 'a' && c <= 'f')
+ return 10 + (c - 'a');
+ if(c >= 'A' && c <= 'F')
+ return 10 + (c - 'A');
+ if(c >= '0' && c <= '9')
+ return c - '0';
+ return -1;
+}
+
+int xf_map_parseline(const char* line, struct xf_map* map)
+{
+ enum {
+ s_name,
+ s_md5,
+ s_first_num,
+ s_file_len = s_first_num,
+ s_offset,
+ s_length,
+ s_done,
+ };
+
+#define skipws() do { while(*line == ' ' || *line == '\t') ++line; } while(0)
+#define nextstate() do { ++state; length = 0; ++line; skipws(); } while(0)
+
+ int state = s_name;
+ int length = 0;
+ int digit_val;
+ uint32_t int_val;
+ uint32_t* num_ptr[3] = {&map->file_length, &map->offset, &map->length};
+ bool has_md5 = true;
+
+ skipws();
+ while(*line && *line != '\n') {
+ switch(state) {
+ case s_name:
+ if(*line == ' ' || *line == '\t') {
+ nextstate();
+ continue;
+ } else if(isgraph((unsigned char)*line)) {
+ if(length == XF_MAP_NAMELEN)
+ return XF_E_FILENAME_TOO_LONG;
+
+ map->name[length++] = *line++;
+ map->name[length] = '\0';
+ continue;
+ } else {
+ return XF_E_SYNTAX_ERROR;
+ }
+
+ case s_md5:
+ if(*line == '-') {
+ memset(map->md5, 0, 16);
+ map->file_length = 0;
+ has_md5 = false;
+ ++line;
+ } else {
+ for(int i = 0; i < 16; ++i) {
+ int_val = 0;
+ for(int j = 0; j < 2; ++j) {
+ digit_val = xdigit_to_int(*line++);
+ if(digit_val < 0)
+ return XF_E_SYNTAX_ERROR;
+
+ int_val <<= 4;
+ int_val |= digit_val;
+ }
+
+ map->md5[i] = int_val;
+ }
+ }
+
+ if(*line == ' ' || *line == '\t') {
+ /* skip file length if md5 is not present */
+ if(!has_md5)
+ ++state;
+
+ nextstate();
+ continue;
+ } else {
+ return XF_E_SYNTAX_ERROR;
+ }
+
+ case s_file_len:
+ case s_offset:
+ case s_length:
+ int_val = 0;
+
+ if(*line == '0') {
+ ++line;
+ if(*line == 'x' || *line == 'X') {
+ ++line;
+ while((digit_val = xdigit_to_int(*line)) >= 0) {
+ ++line;
+
+ if(int_val > UINT32_MAX/16)
+ return XF_E_INT_OVERFLOW;
+ int_val *= 16;
+
+ if(int_val > UINT32_MAX - digit_val)
+ return XF_E_INT_OVERFLOW;
+ int_val |= digit_val;
+ }
+ }
+ } else if(*line >= '1' && *line <= '9') {
+ do {
+ if(int_val > UINT32_MAX/10)
+ return XF_E_INT_OVERFLOW;
+ int_val *= 10;
+
+ digit_val = *line++ - '0';
+ if(int_val > UINT32_MAX - digit_val)
+ return XF_E_INT_OVERFLOW;
+
+ int_val += digit_val;
+ } while(*line >= '0' && *line <= '9');
+ }
+
+ *num_ptr[state - s_first_num] = int_val;
+
+ if(*line == ' ' || *line == '\t') {
+ nextstate();
+ continue;
+ } else if(state+1 == s_done && *line == '\0') {
+ /* end of input */
+ continue;
+ } else {
+ return XF_E_SYNTAX_ERROR;
+ }
+
+ case s_done:
+ if(isspace(*line)) {
+ line++;
+ continue; /* swallow trailing spaces, carriage return, etc */
+ } else
+ return XF_E_SYNTAX_ERROR;
+ }
+ }
+
+#undef skipws
+#undef nextstate
+
+ /* one last overflow check - ensure mapped range is addressable */
+ if(map->offset > UINT32_MAX - map->length)
+ return XF_E_INT_OVERFLOW;
+
+ if(has_md5)
+ map->flags = XF_MAP_HAS_MD5 | XF_MAP_HAS_FILE_LENGTH;
+ else
+ map->flags = 0;
+
+ return XF_E_SUCCESS;
+}
+
+struct map_parse_args {
+ struct xf_map* map;
+ int num;
+ int maxnum;
+};
+
+int map_parse_line_cb(int n, char* buf, void* arg)
+{
+ (void)n;
+
+ struct map_parse_args* args = arg;
+
+ /* ignore comments and blank lines */
+ if(*buf == '#' || *buf == '\0')
+ return 0;
+
+ struct xf_map dummy_map;
+ struct xf_map* dst_map;
+ if(args->num < args->maxnum)
+ dst_map = &args->map[args->num];
+ else
+ dst_map = &dummy_map;
+
+ int rc = xf_map_parseline(buf, dst_map);
+ if(rc)
+ return rc;
+
+ args->num++;
+ return 0;
+}
+
+int xf_map_parse(struct xf_stream* s, struct xf_map* map, int maxnum)
+{
+ char buf[200];
+ struct map_parse_args args;
+ args.map = map;
+ args.num = 0;
+ args.maxnum = maxnum;
+
+ int rc = xf_stream_read_lines(s, buf, sizeof(buf),
+ map_parse_line_cb, &args);
+ if(rc < 0)
+ return rc;
+
+ return args.num;
+}
+
+static int xf_map_compare(const void* a, const void* b)
+{
+ const struct xf_map* mapA = a;
+ const struct xf_map* mapB = b;
+
+ if(mapA->offset < mapB->offset)
+ return -1;
+ else if(mapA->offset == mapB->offset)
+ return 0;
+ else
+ return 1;
+}
+
+int xf_map_sort(struct xf_map* map, int num)
+{
+ qsort(map, num, sizeof(struct xf_map), xf_map_compare);
+ return xf_map_validate(map, num);
+}
+
+int xf_map_validate(const struct xf_map* map, int num)
+{
+ for(int i = 1; i < num; ++i)
+ if(map[i].offset <= map[i-1].offset)
+ return -1;
+
+ for(int i = 1; i < num; ++i)
+ if(map[i-1].offset + map[i-1].length > map[i].offset)
+ return i;
+
+ return 0;
+}
+
+int xf_map_write(struct xf_map* map, int num, struct xf_stream* s)
+{
+ static const char hex[] = "0123456789abcdef";
+ char buf[200];
+ char md5str[33];
+ int total_len = 0;
+
+ md5str[32] = '\0';
+
+ for(int i = 0; i < num; ++i) {
+ bool has_md5 = false;
+ if(map->flags & XF_MAP_HAS_MD5) {
+ if(!(map->flags & XF_MAP_HAS_FILE_LENGTH))
+ return XF_E_INVALID_PARAMETER;
+
+ has_md5 = true;
+ for(int j = 0; j < 16; ++j) {
+ uint8_t byte = map[i].md5[j];
+ md5str[2*j] = hex[(byte >> 4) & 0xf];
+ md5str[2*j+1] = hex[byte & 0xf];
+ }
+ }
+
+ int len;
+ if(!has_md5) {
+ len = snprintf(buf, sizeof(buf), "%s - %lx %lu\n",
+ map[i].name,
+ (unsigned long)map[i].offset,
+ (unsigned long)map[i].length);
+ } else {
+ len = snprintf(buf, sizeof(buf), "%s %s %lu 0x%lx %lu\n",
+ map[i].name, md5str,
+ (unsigned long)map[i].file_length,
+ (unsigned long)map[i].offset,
+ (unsigned long)map[i].length);
+ }
+
+ if(len < 0 || (size_t)len >= sizeof(buf))
+ return XF_E_LINE_TOO_LONG;
+
+ if(s) {
+ int rc = xf_stream_write(s, buf, len);
+ if(rc != len)
+ return XF_E_IO;
+ }
+
+ total_len += len;
+ }
+
+ return total_len;
+}
diff --git a/lib/x1000-installer/src/xf_nandio.c b/lib/x1000-installer/src/xf_nandio.c
new file mode 100644
index 0000000000..ba79cbbcbf
--- /dev/null
+++ b/lib/x1000-installer/src/xf_nandio.c
@@ -0,0 +1,295 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_nandio.h"
+#include "xf_error.h"
+#include "core_alloc.h"
+#include "system.h"
+#include <string.h>
+#include <stdbool.h>
+
+int xf_nandio_init(struct xf_nandio* nio)
+{
+ int rc;
+
+ memset(nio, 0, sizeof(*nio));
+
+ /* open NAND */
+ nio->ndrv = nand_init();
+ nand_lock(nio->ndrv);
+ rc = nand_open(nio->ndrv);
+ if(rc != NAND_SUCCESS) {
+ nio->nand_err = rc;
+ rc = XF_E_NAND;
+ goto out;
+ }
+
+ /* read chip parameters */
+ nio->page_size = nio->ndrv->chip->page_size;
+ nio->block_size = nio->page_size << nio->ndrv->chip->log2_ppb;
+
+ /* allocate memory */
+ size_t alloc_size = 0;
+ alloc_size += CACHEALIGN_SIZE - 1;
+ alloc_size += nio->block_size * 2;
+
+ nio->alloc_handle = core_alloc("xf_nandio", alloc_size);
+ if(nio->alloc_handle < 0) {
+ rc = XF_E_OUT_OF_MEMORY;
+ goto out_nclose;
+ }
+
+ uint8_t* buffer = core_get_data(nio->alloc_handle);
+ CACHEALIGN_BUFFER(buffer, alloc_size);
+
+ nio->old_buf = buffer;
+ nio->new_buf = &buffer[nio->block_size];
+
+ rc = XF_E_SUCCESS;
+ goto out;
+
+ out_nclose:
+ nand_close(nio->ndrv);
+ out:
+ nand_unlock(nio->ndrv);
+ return rc;
+}
+
+void xf_nandio_destroy(struct xf_nandio* nio)
+{
+ if(nio->alloc_handle > 0) {
+ core_free(nio->alloc_handle);
+ nio->alloc_handle = 0;
+ }
+
+ if(nio->ndrv) {
+ nand_lock(nio->ndrv);
+ nand_close(nio->ndrv);
+ nand_unlock(nio->ndrv);
+ nio->ndrv = NULL;
+ }
+}
+
+static bool is_page_blank(const uint8_t* buf, uint32_t length)
+{
+ for(uint32_t i = 0; i < length; ++i)
+ if(buf[i] != 0xff)
+ return false;
+
+ return true;
+}
+
+static int flush_block(struct xf_nandio* nio, bool invalidate)
+{
+ /* no block, or only reading - flush is a no-op */
+ if(!nio->block_valid || nio->mode == XF_NANDIO_READ)
+ return XF_E_SUCCESS;
+
+ /* nothing to do if new data is same as old data */
+ if(!memcmp(nio->old_buf, nio->new_buf, nio->block_size))
+ return XF_E_SUCCESS;
+
+ /* data mismatch during verification - report the error */
+ if(nio->mode == XF_NANDIO_VERIFY)
+ return XF_E_VERIFY_FAILED;
+
+ /* erase the block */
+ int rc = nand_block_erase(nio->ndrv, nio->cur_block);
+ if(rc != NAND_SUCCESS) {
+ nio->block_valid = false;
+ nio->nand_err = rc;
+ return XF_E_NAND;
+ }
+
+ size_t oob_size = nio->ndrv->chip->oob_size;
+
+ unsigned page = 0;
+ nand_page_t page_addr = nio->cur_block;
+ for(; page < nio->ndrv->ppb; ++page, ++page_addr) {
+ /* skip programming blank pages to go faster & reduce wear */
+ uint8_t* page_data = &nio->new_buf[page * nio->page_size];
+ if(is_page_blank(page_data, nio->page_size))
+ continue;
+
+ /* copy page and write blank OOB data */
+ memcpy(nio->ndrv->page_buf, page_data, nio->page_size);
+ memset(&nio->ndrv->page_buf[nio->page_size], 0xff, oob_size);
+
+ /* program the page */
+ rc = nand_page_program(nio->ndrv, page_addr, nio->ndrv->page_buf);
+ if(rc != NAND_SUCCESS) {
+ nio->block_valid = false;
+ nio->nand_err = rc;
+ return XF_E_NAND;
+ }
+ }
+
+ if(invalidate)
+ nio->block_valid = false;
+ else {
+ /* update our 'old' buffer so a subsequent flush
+ * will not reprogram the same block */
+ memcpy(nio->old_buf, nio->new_buf, nio->block_size);
+ }
+
+ return XF_E_SUCCESS;
+}
+
+static int seek_to_block(struct xf_nandio* nio, nand_block_t block_addr,
+ size_t offset_in_block)
+{
+ /* already on this block? */
+ if(nio->block_valid && block_addr == nio->cur_block) {
+ nio->offset_in_block = offset_in_block;
+ return XF_E_SUCCESS;
+ }
+
+ /* ensure new block is within range */
+ if(block_addr >= (nio->ndrv->chip->nr_blocks << nio->ndrv->chip->log2_ppb))
+ return XF_E_OUT_OF_RANGE;
+
+ /* flush old block */
+ int rc = flush_block(nio, true);
+ if(rc)
+ return rc;
+
+ nio->block_valid = false;
+
+ /* read the new block */
+ unsigned page = 0;
+ nand_page_t page_addr = block_addr;
+ for(; page < nio->ndrv->ppb; ++page, ++page_addr) {
+ rc = nand_page_read(nio->ndrv, page_addr, nio->ndrv->page_buf);
+ if(rc != NAND_SUCCESS) {
+ nio->nand_err = rc;
+ return XF_E_NAND;
+ }
+
+ memcpy(&nio->old_buf[page * nio->page_size], nio->ndrv->page_buf, nio->page_size);
+ }
+
+ /* copy to 2nd buffer */
+ memcpy(nio->new_buf, nio->old_buf, nio->block_size);
+
+ /* update position */
+ nio->cur_block = block_addr;
+ nio->offset_in_block = offset_in_block;
+ nio->block_valid = true;
+ return XF_E_SUCCESS;
+}
+
+int xf_nandio_set_mode(struct xf_nandio* nio, enum xf_nandio_mode mode)
+{
+ nand_lock(nio->ndrv);
+
+ /* flush the current block before switching to the new mode,
+ * to ensure consistency */
+ int rc = flush_block(nio, false);
+ if(rc)
+ goto err;
+
+ nio->mode = mode;
+ rc = XF_E_SUCCESS;
+
+ err:
+ nand_unlock(nio->ndrv);
+ return rc;
+}
+
+static int nandio_rdwr(struct xf_nandio* nio, void* buf, size_t count, bool write)
+{
+ while(count > 0) {
+ void* ptr;
+ size_t amount = count;
+ int rc = xf_nandio_get_buffer(nio, &ptr, &amount);
+ if(rc)
+ return rc;
+
+ if(write)
+ memcpy(ptr, buf, amount);
+ else
+ memcpy(buf, ptr, amount);
+
+ count -= amount;
+ }
+
+ return XF_E_SUCCESS;
+}
+
+int xf_nandio_seek(struct xf_nandio* nio, size_t offset)
+{
+ uint32_t block_nr = offset / nio->block_size;
+ size_t offset_in_block = offset % nio->block_size;
+ nand_block_t block_addr = block_nr << nio->ndrv->chip->log2_ppb;
+
+ nand_lock(nio->ndrv);
+ int rc = seek_to_block(nio, block_addr, offset_in_block);
+ nand_unlock(nio->ndrv);
+
+ return rc;
+}
+
+int xf_nandio_read(struct xf_nandio* nio, void* buf, size_t count)
+{
+ return nandio_rdwr(nio, buf, count, false);
+}
+
+int xf_nandio_write(struct xf_nandio* nio, const void* buf, size_t count)
+{
+ return nandio_rdwr(nio, (void*)buf, count, true);
+}
+
+int xf_nandio_get_buffer(struct xf_nandio* nio, void** buf, size_t* count)
+{
+ nand_lock(nio->ndrv);
+
+ /* make sure the current block data is read in */
+ int rc = seek_to_block(nio, nio->cur_block, nio->offset_in_block);
+ if(rc)
+ goto err;
+
+ size_t amount_left = nio->block_size - nio->offset_in_block;
+ if(amount_left == 0) {
+ amount_left = nio->block_size;
+ rc = seek_to_block(nio, nio->cur_block + nio->ndrv->ppb, 0);
+ if(rc)
+ goto err;
+ }
+
+ *buf = &nio->new_buf[nio->offset_in_block];
+ *count = MIN(*count, amount_left);
+
+ nio->offset_in_block += *count;
+ rc = XF_E_SUCCESS;
+
+ err:
+ nand_unlock(nio->ndrv);
+ return rc;
+}
+
+int xf_nandio_flush(struct xf_nandio* nio)
+{
+ nand_lock(nio->ndrv);
+ int rc = flush_block(nio, false);
+ nand_unlock(nio->ndrv);
+
+ return rc;
+}
diff --git a/lib/x1000-installer/src/xf_package.c b/lib/x1000-installer/src/xf_package.c
new file mode 100644
index 0000000000..78bddded68
--- /dev/null
+++ b/lib/x1000-installer/src/xf_package.c
@@ -0,0 +1,264 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_package.h"
+#include "xf_error.h"
+#include "pathfuncs.h"
+#include "file.h"
+#include "core_alloc.h"
+#include "md5.h"
+#include "system.h"
+#include <stdbool.h>
+#include <string.h>
+
+#ifdef ROCKBOX
+# include "microtar-rockbox.h"
+#else
+# include "microtar-stdio.h"
+#endif
+
+#define METADATA_SIZE 4096 /* size of the metadata buffer */
+#define MAX_MAP_SIZE 32 /* maximum number of map entries */
+
+static int pkg_alloc(struct xf_package* pkg)
+{
+ memset(pkg, 0, sizeof(*pkg));
+
+ /* calculate allocation size */
+ size_t alloc_size = 0;
+ alloc_size += ALIGN_UP_P2(sizeof(mtar_t), 3);
+ alloc_size += ALIGN_UP_P2(sizeof(struct xf_map) * MAX_MAP_SIZE, 3);
+ alloc_size += ALIGN_UP_P2(METADATA_SIZE, 3);
+ alloc_size += 7; /* for alignment */
+
+ pkg->alloc_handle = core_alloc("xf_package", alloc_size);
+ if(pkg->alloc_handle < 0)
+ return XF_E_OUT_OF_MEMORY;
+
+ /* distribute memory */
+ uint8_t* buf = (uint8_t*)core_get_data(pkg->alloc_handle);
+ memset(buf, 0, alloc_size);
+ ALIGN_BUFFER(buf, alloc_size, 8);
+
+ pkg->tar = (mtar_t*)buf;
+ buf += ALIGN_UP_P2(sizeof(mtar_t), 3);
+
+ pkg->map = (struct xf_map*)buf;
+ buf += ALIGN_UP_P2(sizeof(struct xf_map) * MAX_MAP_SIZE, 3);
+
+ pkg->metadata = (char*)buf;
+ buf += ALIGN_UP_P2(METADATA_SIZE, 3);
+
+ return XF_E_SUCCESS;
+}
+
+static int read_meta_line_cb(int n, char* buf, void* arg)
+{
+ struct xf_package* pkg = (struct xf_package*)arg;
+ size_t length = strlen(buf);
+
+ /* skip blank lines and the first line (it's reserved for old format) */
+ if(n == 0 || length == 0)
+ return 0;
+
+ /* metadata lines require an '=' sign to separate key and value */
+ if(!strchr(buf, '='))
+ return XF_E_SYNTAX_ERROR;
+
+ /* we need space to copy the key-value pair plus a null terminator */
+ if(length + 1 >= METADATA_SIZE - pkg->metadata_len)
+ return XF_E_BUF_OVERFLOW;
+
+ memcpy(&pkg->metadata[pkg->metadata_len], buf, length + 1);
+ pkg->metadata_len += length + 1;
+ return 0;
+}
+
+static int pkg_read_meta(struct xf_package* pkg)
+{
+ struct xf_stream stream;
+ int rc = xf_open_tar(pkg->tar, "bootloader-info.txt", &stream);
+ if(rc)
+ return XF_E_CANNOT_OPEN_FILE;
+
+ char buf[200];
+ rc = xf_stream_read_lines(&stream, buf, sizeof(buf), read_meta_line_cb, pkg);
+ xf_stream_close(&stream);
+ return rc;
+}
+
+static int pkg_read_map(struct xf_package* pkg,
+ const struct xf_map* dflt_map, int dflt_map_size)
+{
+ /* Attempt to load and parse the map file */
+ struct xf_stream stream;
+ int rc = xf_open_tar(pkg->tar, "flashmap.txt", &stream);
+
+ /* If the flash map is absent but a default map has been provided,
+ * then the update is in the old fixed format. */
+ if(rc == MTAR_ENOTFOUND && dflt_map) {
+ if(dflt_map_size > MAX_MAP_SIZE)
+ return XF_E_INVALID_PARAMETER;
+
+ for(int i = 0; i < dflt_map_size; ++i) {
+ pkg->map[i] = dflt_map[i];
+ pkg->map[i].flags &= ~(XF_MAP_HAS_MD5 | XF_MAP_HAS_FILE_LENGTH);
+ }
+
+ pkg->map_size = dflt_map_size;
+ return XF_E_SUCCESS;
+ }
+
+ if(rc != MTAR_ESUCCESS)
+ return XF_E_CANNOT_OPEN_FILE;
+
+ rc = xf_map_parse(&stream, pkg->map, MAX_MAP_SIZE);
+ if(rc < 0)
+ goto err;
+
+ /* Sort the map; reject it if there is any overlap. */
+ pkg->map_size = rc;
+ if(xf_map_sort(pkg->map, pkg->map_size)) {
+ rc = XF_E_INVALID_PARAMETER;
+ goto err;
+ }
+
+ /* All packages in the 'new' format are required to have MD5 sums. */
+ for(int i = 0; i < pkg->map_size; ++i) {
+ if(!(pkg->map[i].flags & XF_MAP_HAS_MD5) ||
+ !(pkg->map[i].flags & XF_MAP_HAS_FILE_LENGTH)) {
+ rc = XF_E_VERIFY_FAILED;
+ goto err;
+ }
+ }
+
+ rc = XF_E_SUCCESS;
+
+ err:
+ xf_stream_close(&stream);
+ return rc;
+}
+
+static int pkg_verify(struct xf_package* pkg)
+{
+ struct xf_stream stream;
+ md5_context ctx;
+ uint8_t buffer[128];
+
+ for(int i = 0; i < pkg->map_size; ++i) {
+ /* At a bare minimum, check that the file exists. */
+ int rc = xf_open_tar(pkg->tar, pkg->map[i].name, &stream);
+ if(rc)
+ return XF_E_VERIFY_FAILED;
+
+ /* Also check that it isn't bigger than the update region.
+ * That would normally indicate a problem. */
+ off_t streamsize = xf_stream_get_size(&stream);
+ if(streamsize > (off_t)pkg->map[i].length) {
+ rc = XF_E_VERIFY_FAILED;
+ goto err;
+ }
+
+ /* Check against the listed file length. */
+ if(pkg->map[i].flags & XF_MAP_HAS_FILE_LENGTH) {
+ if(streamsize != (off_t)pkg->map[i].file_length) {
+ rc = XF_E_VERIFY_FAILED;
+ goto err;
+ }
+ }
+
+ /* Check the MD5 sum if we have it. */
+ if(pkg->map[i].flags & XF_MAP_HAS_MD5) {
+ md5_starts(&ctx);
+ while(1) {
+ ssize_t n = xf_stream_read(&stream, buffer, sizeof(buffer));
+ if(n < 0) {
+ rc = XF_E_IO;
+ goto err;
+ }
+
+ md5_update(&ctx, buffer, n);
+ if((size_t)n < sizeof(buffer))
+ break;
+ }
+
+ md5_finish(&ctx, buffer);
+ if(memcpy(buffer, pkg->map[i].md5, 16)) {
+ rc = XF_E_VERIFY_FAILED;
+ goto err;
+ }
+ }
+
+ err:
+ xf_stream_close(&stream);
+ if(rc)
+ return rc;
+ }
+
+ /* All files passed verification */
+ return XF_E_SUCCESS;
+}
+
+int xf_package_open_ex(struct xf_package* pkg, const char* file,
+ const struct xf_map* dflt_map, int dflt_map_size)
+{
+ int rc = pkg_alloc(pkg);
+ if(rc)
+ return rc;
+
+#ifdef ROCKBOX
+ rc = mtar_open(pkg->tar, file, O_RDONLY);
+#else
+ rc = mtar_open(pkg->tar, file, "r");
+#endif
+ if(rc != MTAR_ESUCCESS) {
+ rc = XF_E_CANNOT_OPEN_FILE;
+ goto err;
+ }
+
+ rc = pkg_read_meta(pkg);
+ if(rc)
+ goto err;
+
+ rc = pkg_read_map(pkg, dflt_map, dflt_map_size);
+ if(rc)
+ goto err;
+
+ rc = pkg_verify(pkg);
+ if(rc)
+ goto err;
+
+ err:
+ if(rc)
+ xf_package_close(pkg);
+ return rc;
+}
+
+void xf_package_close(struct xf_package* pkg)
+{
+ if(mtar_is_open(pkg->tar))
+ mtar_close(pkg->tar);
+
+ if(pkg->alloc_handle > 0) {
+ core_free(pkg->alloc_handle);
+ pkg->alloc_handle = 0;
+ }
+}
diff --git a/lib/x1000-installer/src/xf_stream.c b/lib/x1000-installer/src/xf_stream.c
new file mode 100644
index 0000000000..5a0f86123c
--- /dev/null
+++ b/lib/x1000-installer/src/xf_stream.c
@@ -0,0 +1,214 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_stream.h"
+#include "xf_error.h"
+#include "file.h"
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * File streams
+ */
+
+static off_t file_stream_get_size(struct xf_stream* s)
+{
+ return filesize(s->data);
+}
+
+static ssize_t file_stream_read(struct xf_stream* s, void* buf, size_t len)
+{
+ return read(s->data, buf, len);
+}
+
+static ssize_t file_stream_write(struct xf_stream* s, const void* buf, size_t len)
+{
+ return write(s->data, buf, len);
+}
+
+static int file_stream_close(struct xf_stream* s)
+{
+ return close(s->data);
+}
+
+static const struct xf_stream_ops file_stream_ops = {
+ .xget_size = file_stream_get_size,
+ .xread = file_stream_read,
+ .xwrite = file_stream_write,
+ .xclose = file_stream_close,
+};
+
+int xf_open_file(const char* file, int flags, struct xf_stream* s)
+{
+ s->data = open(file, flags, 0666);
+ s->ops = &file_stream_ops;
+ return (s->data >= 0) ? 0 : -1;
+}
+
+/*
+ * Tar streams
+ */
+
+static off_t tar_stream_get_size(struct xf_stream* s)
+{
+ mtar_t* mtar = (mtar_t*)s->data;
+ return mtar_get_header(mtar)->size;
+}
+
+static ssize_t tar_stream_read(struct xf_stream* s, void* buffer, size_t count)
+{
+ mtar_t* mtar = (mtar_t*)s->data;
+
+ int ret = mtar_read_data(mtar, buffer, count);
+ if(ret < 0)
+ return -1;
+
+ return ret;
+}
+
+static ssize_t tar_stream_write(struct xf_stream* s, const void* buffer, size_t count)
+{
+ mtar_t* mtar = (mtar_t*)s->data;
+
+ int ret = mtar_write_data(mtar, buffer, count);
+ if(ret < 0)
+ return -1;
+
+ return ret;
+}
+
+static int tar_stream_close(struct xf_stream* s)
+{
+ mtar_t* mtar = (mtar_t*)s->data;
+
+ if(mtar_access_mode(mtar) == MTAR_WRITE) {
+ if(mtar_update_file_size(mtar) != MTAR_ESUCCESS)
+ return -1;
+ if(mtar_end_data(mtar) != MTAR_ESUCCESS)
+ return -1;
+ }
+
+ return 0;
+}
+
+static const struct xf_stream_ops tar_stream_ops = {
+ .xget_size = tar_stream_get_size,
+ .xread = tar_stream_read,
+ .xwrite = tar_stream_write,
+ .xclose = tar_stream_close,
+};
+
+int xf_open_tar(mtar_t* mtar, const char* file, struct xf_stream* s)
+{
+ int err = mtar_find(mtar, file);
+ if(err != MTAR_ESUCCESS)
+ return err;
+
+ /* must only read normal files */
+ const mtar_header_t* h = mtar_get_header(mtar);
+ if(h->type != 0 && h->type != MTAR_TREG)
+ return MTAR_EFAILURE;
+
+ s->data = (intptr_t)mtar;
+ s->ops = &tar_stream_ops;
+ return MTAR_ESUCCESS;
+}
+
+int xf_create_tar(mtar_t* mtar, const char* file, size_t size, struct xf_stream* s)
+{
+ int err = mtar_write_file_header(mtar, file, size);
+ if(err)
+ return err;
+
+ s->data = (intptr_t)mtar;
+ s->ops = &tar_stream_ops;
+ return MTAR_ESUCCESS;
+}
+
+/*
+ * Utility functions
+ */
+
+int xf_stream_read_lines(struct xf_stream* s, char* buf, size_t bufsz,
+ int(*callback)(int n, char* buf, void* arg), void* arg)
+{
+ char* startp, *endp;
+ ssize_t bytes_read;
+ int rc;
+
+ int n = 0;
+ size_t pos = 0;
+ bool at_eof = false;
+
+ while(!at_eof) {
+ bytes_read = xf_stream_read(s, &buf[pos], bufsz - pos - 1);
+ if(bytes_read < 0)
+ return XF_E_IO;
+
+ /* short read is end of file */
+ if((size_t)bytes_read < bufsz - pos - 1)
+ at_eof = true;
+
+ pos += bytes_read;
+ buf[pos] = '\0';
+
+ startp = endp = buf;
+ while(endp != &buf[pos]) {
+ endp = strchr(startp, '\n');
+ if(endp) {
+ *endp = '\0';
+ } else {
+ if(!at_eof) {
+ if(startp == buf)
+ return XF_E_LINE_TOO_LONG;
+ else
+ break; /* read ahead to look for newline */
+ } else {
+ endp = &buf[pos]; /* treat EOF as a newline */
+ }
+ }
+
+ /* skip whitespace */
+ while(*startp && isspace(*startp))
+ ++startp;
+
+ /* ignore blank lines and comment lines */
+ if(*startp == '#' || *startp == '\0') {
+ startp = endp + 1;
+ continue;
+ }
+
+ rc = callback(n++, startp, arg);
+ if(rc != 0)
+ return rc;
+
+ startp = endp + 1;
+ }
+
+ if(startp <= &buf[pos]) {
+ memmove(buf, startp, &buf[pos] - startp);
+ pos = &buf[pos] - startp;
+ }
+ }
+
+ return 0;
+}
diff --git a/lib/x1000-installer/src/xf_update.c b/lib/x1000-installer/src/xf_update.c
new file mode 100644
index 0000000000..5a7c3b0430
--- /dev/null
+++ b/lib/x1000-installer/src/xf_update.c
@@ -0,0 +1,149 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_update.h"
+#include "xf_error.h"
+#include "file.h"
+#include "md5.h"
+#include <string.h>
+
+static int process_stream(struct xf_nandio* nio,
+ struct xf_map* map,
+ struct xf_stream* stream)
+{
+ void* buffer;
+ size_t count;
+ int rc;
+
+ /* calculate MD5 on the fly if taking a backup */
+ md5_context md5_ctx;
+ if(nio->mode == XF_NANDIO_READ)
+ md5_starts(&md5_ctx);
+
+ /* first deal with the file data */
+ size_t bytes_left = map->length;
+ while(bytes_left > 0) {
+ count = bytes_left;
+ rc = xf_nandio_get_buffer(nio, &buffer, &count);
+ if(rc)
+ return rc;
+
+ if(nio->mode == XF_NANDIO_READ) {
+ md5_update(&md5_ctx, buffer, count);
+ rc = xf_stream_write(stream, buffer, count);
+ } else {
+ rc = xf_stream_read(stream, buffer, count);
+ }
+
+ bytes_left -= count;
+
+ if(rc < 0 || (size_t)rc > count)
+ return XF_E_IO;
+
+ if((size_t)rc < count) {
+ /* backup - we could not write all the data */
+ if(nio->mode == XF_NANDIO_READ)
+ return XF_E_IO;
+
+ /* update - clear rest of buffer to 0xff */
+ memset(buffer + rc, 0xff, count - rc);
+ break;
+ }
+ }
+
+ /* if updating - write blanks to the remainder of the region */
+ while(bytes_left > 0) {
+ count = bytes_left;
+ rc = xf_nandio_get_buffer(nio, &buffer, &count);
+ if(rc)
+ return rc;
+
+ memset(buffer, 0xff, count);
+ bytes_left -= count;
+ }
+
+ /* finalize the MD5 sum */
+ if(nio->mode == XF_NANDIO_READ) {
+ md5_finish(&md5_ctx, map->md5);
+ map->file_length = map->length;
+ map->flags |= XF_MAP_HAS_MD5 | XF_MAP_HAS_FILE_LENGTH;
+ }
+
+ return XF_E_SUCCESS;
+}
+
+static int process_map(struct xf_nandio* nio, struct xf_map* map, int map_size,
+ xf_update_open_stream_cb open_stream, void* os_arg)
+{
+ int rc, rc2;
+ struct xf_stream stream;
+
+ /* ensure the map is sequential and non-overlapping before continuing */
+ if(xf_map_validate(map, map_size) != 0)
+ return XF_E_INVALID_PARAMETER;
+
+ for(int i = 0; i < map_size; ++i) {
+ /* seek to initial offset */
+ rc = xf_nandio_seek(nio, map[i].offset);
+ if(rc)
+ return rc;
+
+ rc = open_stream(os_arg, map[i].name, &stream);
+ if(rc)
+ return XF_E_CANNOT_OPEN_FILE;
+
+ /* process the stream and be sure to close it even on error */
+ rc = process_stream(nio, &map[i], &stream);
+ rc2 = xf_stream_close(&stream);
+
+ /* bail if either operation raised an error */
+ if(rc)
+ return rc;
+ if(rc2)
+ return rc2;
+ }
+
+ /* now flush to ensure all data was written */
+ rc = xf_nandio_flush(nio);
+ if(rc)
+ return rc;
+
+ return XF_E_SUCCESS;
+}
+
+static const enum xf_nandio_mode update_mode_to_nandio[] = {
+ [XF_UPDATE] = XF_NANDIO_PROGRAM,
+ [XF_BACKUP] = XF_NANDIO_READ,
+ [XF_VERIFY] = XF_NANDIO_VERIFY,
+};
+
+int xf_updater_run(enum xf_update_mode mode, struct xf_nandio* nio,
+ struct xf_map* map, int map_size,
+ xf_update_open_stream_cb open_stream, void* arg)
+{
+ /* Switch NAND I/O into the correct mode */
+ int rc = xf_nandio_set_mode(nio, update_mode_to_nandio[mode]);
+ if(rc)
+ return rc;
+
+ /* This does all the real work */
+ return process_map(nio, map, map_size, open_stream, arg);
+}
diff --git a/lib/x1000-installer/test/main.c b/lib/x1000-installer/test/main.c
new file mode 100644
index 0000000000..e952fb9d86
--- /dev/null
+++ b/lib/x1000-installer/test/main.c
@@ -0,0 +1,98 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "xf_nandio.h"
+#include "xf_error.h"
+#include "xf_update.h"
+#include "file.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static struct xf_nandio nio;
+
+static struct xf_map testmap[] = {
+ {
+ .name = "src/xf_error.c",
+ .offset = 0x00000000,
+ .length = 0x00004000,
+ },
+ {
+ .name = "src/xf_update.c",
+ .offset = 0x00010000,
+ .length = 0x00004000,
+ },
+ {
+ .name = "src/xf_package.c",
+ .offset = 0x00020000,
+ .length = 0x00001800,
+ },
+};
+
+void checkrc(int rc)
+{
+ if(rc == XF_E_SUCCESS)
+ return;
+
+ if(rc == XF_E_NAND) {
+ printf("NAND error: %d\n", nio.nand_err);
+ } else {
+ printf("error: %s\n", xf_strerror(rc));
+ printf(" CurBlock = %lu\n", (unsigned long)nio.cur_block);
+ printf(" CurOffset = %lu\n", (unsigned long)nio.offset_in_block);
+ }
+
+ exit(1);
+}
+
+int openstream_file(void* arg, const char* file, struct xf_stream* stream)
+{
+ (void)arg;
+ return xf_open_file(file, O_RDONLY, stream);
+}
+
+int main(int argc, char* argv[])
+{
+ (void)argc;
+ (void)argv;
+
+ nand_trace_reset(65535);
+
+ int rc = xf_nandio_init(&nio);
+ checkrc(rc);
+
+ rc = xf_updater_run(XF_UPDATE, &nio,
+ testmap, sizeof(testmap)/sizeof(struct xf_map),
+ openstream_file, NULL);
+ checkrc(rc);
+
+ for(size_t i = 0; i < nand_trace_length; ++i) {
+ const char* types[] = {"READ", "PROGRAM", "ERASE"};
+ const char* excep[] = {"NONE", "DOUBLE_PROGRAMMED", "CLEARED"};
+ printf("%s %s %lu\n",
+ types[nand_trace[i].type],
+ excep[nand_trace[i].exception],
+ (unsigned long)nand_trace[i].addr);
+ }
+
+ xf_nandio_destroy(&nio);
+ return 0;
+}
diff --git a/lib/x1000-installer/test_lib/core_alloc.c b/lib/x1000-installer/test_lib/core_alloc.c
new file mode 100644
index 0000000000..5d4edb03f7
--- /dev/null
+++ b/lib/x1000-installer/test_lib/core_alloc.c
@@ -0,0 +1,65 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "core_alloc.h"
+#include <stdlib.h>
+
+#define N_POINTERS 100
+
+static void* pointers[N_POINTERS];
+
+int core_alloc(const char* name, size_t size)
+{
+ (void)name;
+
+ void* mem = malloc(size);
+ if(!mem)
+ return -1;
+
+ for(int i = 0; i < N_POINTERS; ++i) {
+ if(pointers[i])
+ continue;
+
+ pointers[i] = mem;
+ return i + 1;
+ }
+
+ free(mem);
+ return -1;
+}
+
+int core_free(int handle)
+{
+ if(handle > 0) {
+ free(pointers[handle-1]);
+ pointers[handle-1] = NULL;
+ }
+
+ return 0;
+}
+
+void* core_get_data(int handle)
+{
+ if(handle > 0)
+ return pointers[handle-1];
+
+ return NULL;
+}
diff --git a/lib/x1000-installer/test_lib/core_alloc.h b/lib/x1000-installer/test_lib/core_alloc.h
new file mode 100644
index 0000000000..6fb06649fb
--- /dev/null
+++ b/lib/x1000-installer/test_lib/core_alloc.h
@@ -0,0 +1,33 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+
+/* fake core_alloc implementation for testing */
+
+#ifndef CORE_ALLOC_H
+#define CORE_ALLOC_H
+
+#include <stddef.h>
+
+int core_alloc(const char* name, size_t size);
+int core_free(int handle);
+void* core_get_data(int handle);
+
+#endif
diff --git a/lib/x1000-installer/test_lib/fakenand.c b/lib/x1000-installer/test_lib/fakenand.c
new file mode 100644
index 0000000000..19f1f31cfd
--- /dev/null
+++ b/lib/x1000-installer/test_lib/fakenand.c
@@ -0,0 +1,270 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "nand-x1000.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+const char* nand_backing_file = "fakeNAND.bin";
+const char* nand_meta_file = "fakeNAND_meta.bin";
+
+struct nand_trace* nand_trace = NULL;
+size_t nand_trace_capacity = 0;
+size_t nand_trace_length = 0;
+
+static struct nand_trace* nand_trace_cur = NULL;
+
+static int injected_err = 0;
+
+#define METAF_PROGRAMMED 1
+
+static const nand_chip fake_chip = {
+ /* ATO25D1GA */
+ .log2_ppb = 6, /* 64 pages */
+ .page_size = 2048,
+ .oob_size = 64,
+ .nr_blocks = 1024,
+};
+
+void nand_trace_reset(size_t size)
+{
+ nand_trace = realloc(nand_trace, size);
+ nand_trace_capacity = size;
+ nand_trace_length = 0;
+ nand_trace_cur = nand_trace;
+}
+
+void nand_inject_error(int rc)
+{
+ injected_err = rc;
+}
+
+nand_drv* nand_init(void)
+{
+ static bool inited = false;
+ static uint8_t scratch_buf[NAND_DRV_SCRATCHSIZE];
+ static uint8_t page_buf[NAND_DRV_MAXPAGESIZE];
+ static nand_drv d;
+
+ if(!inited) {
+ d.scratch_buf = scratch_buf;
+ d.page_buf = page_buf;
+ d.chip = &fake_chip;
+ inited = true;
+ }
+
+ return &d;
+}
+
+static void lock_assert(bool cond, const char* msg)
+{
+ if(!cond) {
+ fprintf(stderr, "%s\n", msg);
+ fflush(stderr);
+ abort();
+ }
+}
+
+void nand_lock(nand_drv* drv)
+{
+ drv->lock_count++;
+}
+
+void nand_unlock(nand_drv* drv)
+{
+ lock_assert(drv->lock_count > 0, "nand_unlock() called when not locked");
+ drv->lock_count--;
+}
+
+#define CHECK_INJECTED_ERROR \
+ do { int __err = injected_err; injected_err = 0; if(__err) return __err; } while(0)
+
+int nand_open(nand_drv* drv)
+{
+ lock_assert(drv->lock_count > 0, "nand_open(): lock not held");
+ CHECK_INJECTED_ERROR;
+
+ if(drv->refcount > 0) {
+ drv->refcount++;
+ return NAND_SUCCESS;
+ }
+
+ /* leaks an fd on error but this is only testing... */
+ drv->fd = open(nand_backing_file, O_RDWR|O_CREAT, 0644);
+ drv->metafd = open(nand_meta_file, O_RDWR|O_CREAT, 0644);
+ if(drv->fd < 0 || drv->metafd < 0)
+ goto err;
+
+ drv->ppb = 1 << drv->chip->log2_ppb;
+ drv->fpage_size = drv->chip->page_size + drv->chip->oob_size;
+
+ /* make backing file the correct size */
+ if(ftruncate(drv->fd, drv->chip->page_size * drv->ppb * drv->chip->nr_blocks) < 0)
+ goto err;
+ if(ftruncate(drv->metafd, drv->chip->nr_blocks * drv->ppb) < 0)
+ goto err;
+
+ drv->refcount++;
+ return NAND_SUCCESS;
+
+ err:
+ if(drv->fd >= 0)
+ close(drv->fd);
+ if(drv->metafd >= 0)
+ close(drv->metafd);
+ return NAND_ERR_OTHER;
+}
+
+void nand_close(nand_drv* drv)
+{
+ lock_assert(drv->lock_count > 0, "nand_close(): lock not held");
+
+ if(--drv->refcount > 0)
+ return;
+
+ close(drv->fd);
+ close(drv->metafd);
+ drv->fd = -1;
+ drv->metafd = -1;
+}
+
+static int read_meta(nand_drv* drv, nand_page_t page)
+{
+ /* probably won't fail */
+ if(lseek(drv->metafd, page, SEEK_SET) < 0)
+ return NAND_ERR_OTHER;
+ if(read(drv->metafd, drv->scratch_buf, 1) != 1)
+ return NAND_ERR_OTHER;
+
+ return drv->scratch_buf[0];
+}
+
+static int write_meta(nand_drv* drv, nand_page_t page, int val)
+{
+ drv->scratch_buf[0] = val;
+
+ if(lseek(drv->metafd, page, SEEK_SET) < 0)
+ return NAND_ERR_OTHER;
+ if(write(drv->metafd, drv->scratch_buf, 1) != 1)
+ return NAND_ERR_OTHER;
+
+ return NAND_SUCCESS;
+}
+
+static int upd_meta(nand_drv* drv, nand_page_t page, uint8_t clr, uint8_t set)
+{
+ int meta = read_meta(drv, page);
+ if(meta < 0)
+ return meta;
+
+ meta &= ~clr;
+ meta |= set;
+
+ return write_meta(drv, page, meta);
+}
+
+static int page_program(nand_drv* drv, nand_page_t page, const void* buffer,
+ uint8_t clr, uint8_t set)
+{
+ if(lseek(drv->fd, page * drv->chip->page_size, SEEK_SET) < 0)
+ return NAND_ERR_OTHER;
+ if(write(drv->fd, buffer, drv->chip->page_size) != (ssize_t)drv->chip->page_size)
+ return NAND_ERR_PROGRAM_FAIL;
+
+ return upd_meta(drv, page, clr, set);
+}
+
+static void trace(enum nand_trace_type ty, enum nand_trace_exception ex, nand_page_t addr)
+{
+ if(nand_trace_length < nand_trace_capacity) {
+ nand_trace_cur->type = ty;
+ nand_trace_cur->exception = ex;
+ nand_trace_cur->addr = addr;
+ nand_trace_cur++;
+ nand_trace_length++;
+ }
+}
+
+int nand_block_erase(nand_drv* drv, nand_block_t block)
+{
+ lock_assert(drv->lock_count > 0, "nand_block_erase(): lock not held");
+ CHECK_INJECTED_ERROR;
+
+ trace(NTT_ERASE, NTE_NONE, block);
+
+ memset(drv->page_buf, 0xff, drv->fpage_size);
+
+ for(unsigned i = 0; i < drv->ppb; ++i) {
+ int rc = page_program(drv, block + i, drv->page_buf, METAF_PROGRAMMED, 0);
+ if(rc < 0)
+ return NAND_ERR_ERASE_FAIL;
+ }
+
+ return NAND_SUCCESS;
+}
+
+int nand_page_program(nand_drv* drv, nand_page_t page, const void* buffer)
+{
+ lock_assert(drv->lock_count > 0, "nand_page_program(): lock not held");
+ CHECK_INJECTED_ERROR;
+
+ int meta = read_meta(drv, page);
+ if(meta < 0)
+ return meta;
+
+ enum nand_trace_exception exception = NTE_NONE;
+ if(meta & METAF_PROGRAMMED)
+ exception = NTE_DOUBLE_PROGRAMMED;
+
+ trace(NTT_PROGRAM, exception, page);
+
+ return page_program(drv, page, buffer, 0, METAF_PROGRAMMED);
+}
+
+int nand_page_read(nand_drv* drv, nand_page_t page, void* buffer)
+{
+ lock_assert(drv->lock_count > 0, "nand_page_read(): lock not held");
+ CHECK_INJECTED_ERROR;
+
+ enum nand_trace_exception exception = NTE_NONE;
+
+ int meta = read_meta(drv, page);
+ if(meta < 0)
+ return meta;
+
+ if(meta & METAF_PROGRAMMED) {
+ if(lseek(drv->fd, page * drv->chip->page_size, SEEK_SET) < 0)
+ return NAND_ERR_OTHER;
+ if(read(drv->fd, buffer, drv->chip->page_size) != (ssize_t)drv->chip->page_size)
+ return NAND_ERR_OTHER;
+ } else {
+ memset(buffer, 0xff, drv->chip->page_size);
+ exception = NTE_CLEARED;
+ }
+
+ trace(NTT_READ, exception, page);
+
+ memset(buffer + drv->chip->page_size, 0xff, drv->chip->oob_size);
+ return NAND_SUCCESS;
+}
diff --git a/lib/x1000-installer/test_lib/file.c b/lib/x1000-installer/test_lib/file.c
new file mode 100644
index 0000000000..8769c009a4
--- /dev/null
+++ b/lib/x1000-installer/test_lib/file.c
@@ -0,0 +1,11 @@
+#include "file.h"
+
+off_t filesize(int osfd)
+{
+ struct stat sb;
+
+ if (!fstat(osfd, &sb))
+ return sb.st_size;
+ else
+ return -1;
+}
diff --git a/lib/x1000-installer/test_lib/file.h b/lib/x1000-installer/test_lib/file.h
new file mode 100644
index 0000000000..2a6554c695
--- /dev/null
+++ b/lib/x1000-installer/test_lib/file.h
@@ -0,0 +1,18 @@
+#ifndef FILE_H
+#define FILE_H
+
+#include <sys/types.h>
+#include <sys/statfs.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef MAX_PATH
+# undef MAX_PATH
+#endif
+
+#define MAX_PATH 260
+
+off_t filesize(int fd);
+
+#endif
diff --git a/lib/x1000-installer/test_lib/md5.c b/lib/x1000-installer/test_lib/md5.c
new file mode 100644
index 0000000000..3050c7ebd8
--- /dev/null
+++ b/lib/x1000-installer/test_lib/md5.c
@@ -0,0 +1,245 @@
+/*
+ * RFC 1321 compliant MD5 implementation
+ *
+ * Copyright (C) 2001-2003 Christophe Devine
+ *
+ * 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 <string.h>
+
+#include "md5.h"
+
+#define GET_UINT32(n,b,i) \
+{ \
+ (n) = ( (uint32_t) (b)[(i) ] ) \
+ | ( (uint32_t) (b)[(i) + 1] << 8 ) \
+ | ( (uint32_t) (b)[(i) + 2] << 16 ) \
+ | ( (uint32_t) (b)[(i) + 3] << 24 ); \
+}
+
+#define PUT_UINT32(n,b,i) \
+{ \
+ (b)[(i) ] = (uint8_t) ( (n) ); \
+ (b)[(i) + 1] = (uint8_t) ( (n) >> 8 ); \
+ (b)[(i) + 2] = (uint8_t) ( (n) >> 16 ); \
+ (b)[(i) + 3] = (uint8_t) ( (n) >> 24 ); \
+}
+
+void md5_starts( md5_context *ctx )
+{
+ ctx->total[0] = 0;
+ ctx->total[1] = 0;
+
+ ctx->state[0] = 0x67452301;
+ ctx->state[1] = 0xEFCDAB89;
+ ctx->state[2] = 0x98BADCFE;
+ ctx->state[3] = 0x10325476;
+}
+
+static void md5_process( md5_context *ctx, uint8_t data[64] )
+{
+ uint32_t X[16], A, B, C, D;
+
+ GET_UINT32( X[0], data, 0 );
+ GET_UINT32( X[1], data, 4 );
+ GET_UINT32( X[2], data, 8 );
+ GET_UINT32( X[3], data, 12 );
+ GET_UINT32( X[4], data, 16 );
+ GET_UINT32( X[5], data, 20 );
+ GET_UINT32( X[6], data, 24 );
+ GET_UINT32( X[7], data, 28 );
+ GET_UINT32( X[8], data, 32 );
+ GET_UINT32( X[9], data, 36 );
+ GET_UINT32( X[10], data, 40 );
+ GET_UINT32( X[11], data, 44 );
+ GET_UINT32( X[12], data, 48 );
+ GET_UINT32( X[13], data, 52 );
+ GET_UINT32( X[14], data, 56 );
+ GET_UINT32( X[15], data, 60 );
+
+#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
+
+#define P(a,b,c,d,k,s,t) \
+{ \
+ a += F(b,c,d) + X[k] + t; a = S(a,s) + b; \
+}
+
+ A = ctx->state[0];
+ B = ctx->state[1];
+ C = ctx->state[2];
+ D = ctx->state[3];
+
+#define F(x,y,z) (z ^ (x & (y ^ z)))
+
+ P( A, B, C, D, 0, 7, 0xD76AA478 );
+ P( D, A, B, C, 1, 12, 0xE8C7B756 );
+ P( C, D, A, B, 2, 17, 0x242070DB );
+ P( B, C, D, A, 3, 22, 0xC1BDCEEE );
+ P( A, B, C, D, 4, 7, 0xF57C0FAF );
+ P( D, A, B, C, 5, 12, 0x4787C62A );
+ P( C, D, A, B, 6, 17, 0xA8304613 );
+ P( B, C, D, A, 7, 22, 0xFD469501 );
+ P( A, B, C, D, 8, 7, 0x698098D8 );
+ P( D, A, B, C, 9, 12, 0x8B44F7AF );
+ P( C, D, A, B, 10, 17, 0xFFFF5BB1 );
+ P( B, C, D, A, 11, 22, 0x895CD7BE );
+ P( A, B, C, D, 12, 7, 0x6B901122 );
+ P( D, A, B, C, 13, 12, 0xFD987193 );
+ P( C, D, A, B, 14, 17, 0xA679438E );
+ P( B, C, D, A, 15, 22, 0x49B40821 );
+
+#undef F
+
+#define F(x,y,z) (y ^ (z & (x ^ y)))
+
+ P( A, B, C, D, 1, 5, 0xF61E2562 );
+ P( D, A, B, C, 6, 9, 0xC040B340 );
+ P( C, D, A, B, 11, 14, 0x265E5A51 );
+ P( B, C, D, A, 0, 20, 0xE9B6C7AA );
+ P( A, B, C, D, 5, 5, 0xD62F105D );
+ P( D, A, B, C, 10, 9, 0x02441453 );
+ P( C, D, A, B, 15, 14, 0xD8A1E681 );
+ P( B, C, D, A, 4, 20, 0xE7D3FBC8 );
+ P( A, B, C, D, 9, 5, 0x21E1CDE6 );
+ P( D, A, B, C, 14, 9, 0xC33707D6 );
+ P( C, D, A, B, 3, 14, 0xF4D50D87 );
+ P( B, C, D, A, 8, 20, 0x455A14ED );
+ P( A, B, C, D, 13, 5, 0xA9E3E905 );
+ P( D, A, B, C, 2, 9, 0xFCEFA3F8 );
+ P( C, D, A, B, 7, 14, 0x676F02D9 );
+ P( B, C, D, A, 12, 20, 0x8D2A4C8A );
+
+#undef F
+
+#define F(x,y,z) (x ^ y ^ z)
+
+ P( A, B, C, D, 5, 4, 0xFFFA3942 );
+ P( D, A, B, C, 8, 11, 0x8771F681 );
+ P( C, D, A, B, 11, 16, 0x6D9D6122 );
+ P( B, C, D, A, 14, 23, 0xFDE5380C );
+ P( A, B, C, D, 1, 4, 0xA4BEEA44 );
+ P( D, A, B, C, 4, 11, 0x4BDECFA9 );
+ P( C, D, A, B, 7, 16, 0xF6BB4B60 );
+ P( B, C, D, A, 10, 23, 0xBEBFBC70 );
+ P( A, B, C, D, 13, 4, 0x289B7EC6 );
+ P( D, A, B, C, 0, 11, 0xEAA127FA );
+ P( C, D, A, B, 3, 16, 0xD4EF3085 );
+ P( B, C, D, A, 6, 23, 0x04881D05 );
+ P( A, B, C, D, 9, 4, 0xD9D4D039 );
+ P( D, A, B, C, 12, 11, 0xE6DB99E5 );
+ P( C, D, A, B, 15, 16, 0x1FA27CF8 );
+ P( B, C, D, A, 2, 23, 0xC4AC5665 );
+
+#undef F
+
+#define F(x,y,z) (y ^ (x | ~z))
+
+ P( A, B, C, D, 0, 6, 0xF4292244 );
+ P( D, A, B, C, 7, 10, 0x432AFF97 );
+ P( C, D, A, B, 14, 15, 0xAB9423A7 );
+ P( B, C, D, A, 5, 21, 0xFC93A039 );
+ P( A, B, C, D, 12, 6, 0x655B59C3 );
+ P( D, A, B, C, 3, 10, 0x8F0CCC92 );
+ P( C, D, A, B, 10, 15, 0xFFEFF47D );
+ P( B, C, D, A, 1, 21, 0x85845DD1 );
+ P( A, B, C, D, 8, 6, 0x6FA87E4F );
+ P( D, A, B, C, 15, 10, 0xFE2CE6E0 );
+ P( C, D, A, B, 6, 15, 0xA3014314 );
+ P( B, C, D, A, 13, 21, 0x4E0811A1 );
+ P( A, B, C, D, 4, 6, 0xF7537E82 );
+ P( D, A, B, C, 11, 10, 0xBD3AF235 );
+ P( C, D, A, B, 2, 15, 0x2AD7D2BB );
+ P( B, C, D, A, 9, 21, 0xEB86D391 );
+
+#undef F
+
+ ctx->state[0] += A;
+ ctx->state[1] += B;
+ ctx->state[2] += C;
+ ctx->state[3] += D;
+}
+
+void md5_update( md5_context *ctx, uint8_t *input, uint32_t length )
+{
+ uint32_t left, fill;
+
+ if( ! length ) return;
+
+ left = ctx->total[0] & 0x3F;
+ fill = 64 - left;
+
+ ctx->total[0] += length;
+ ctx->total[0] &= 0xFFFFFFFF;
+
+ if( ctx->total[0] < length )
+ ctx->total[1]++;
+
+ if( left && length >= fill )
+ {
+ memcpy( (void *) (ctx->buffer + left),
+ (void *) input, fill );
+ md5_process( ctx, ctx->buffer );
+ length -= fill;
+ input += fill;
+ left = 0;
+ }
+
+ while( length >= 64 )
+ {
+ md5_process( ctx, input );
+ length -= 64;
+ input += 64;
+ }
+
+ if( length )
+ {
+ memcpy( (void *) (ctx->buffer + left),
+ (void *) input, length );
+ }
+}
+
+static uint8_t md5_padding[64] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+void md5_finish( md5_context *ctx, uint8_t digest[16] )
+{
+ uint32_t last, padn;
+ uint32_t high, low;
+ uint8_t msglen[8];
+
+ high = ( ctx->total[0] >> 29 )
+ | ( ctx->total[1] << 3 );
+ low = ( ctx->total[0] << 3 );
+
+ PUT_UINT32( low, msglen, 0 );
+ PUT_UINT32( high, msglen, 4 );
+
+ last = ctx->total[0] & 0x3F;
+ padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last );
+
+ md5_update( ctx, md5_padding, padn );
+ md5_update( ctx, msglen, 8 );
+
+ PUT_UINT32( ctx->state[0], digest, 0 );
+ PUT_UINT32( ctx->state[1], digest, 4 );
+ PUT_UINT32( ctx->state[2], digest, 8 );
+ PUT_UINT32( ctx->state[3], digest, 12 );
+}
diff --git a/lib/x1000-installer/test_lib/md5.h b/lib/x1000-installer/test_lib/md5.h
new file mode 100644
index 0000000000..882636ed9a
--- /dev/null
+++ b/lib/x1000-installer/test_lib/md5.h
@@ -0,0 +1,18 @@
+#ifndef _MD5_H
+#define _MD5_H
+
+#include <stdint.h>
+
+typedef struct
+{
+ uint32_t total[2];
+ uint32_t state[4];
+ uint8_t buffer[64];
+}
+md5_context;
+
+void md5_starts( md5_context *ctx );
+void md5_update( md5_context *ctx, uint8_t *input, uint32_t length );
+void md5_finish( md5_context *ctx, uint8_t digest[16] );
+
+#endif /* md5.h */
diff --git a/lib/x1000-installer/test_lib/nand-x1000.h b/lib/x1000-installer/test_lib/nand-x1000.h
new file mode 100644
index 0000000000..f34f2ce026
--- /dev/null
+++ b/lib/x1000-installer/test_lib/nand-x1000.h
@@ -0,0 +1,112 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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.
+ *
+ ****************************************************************************/
+
+/* Stripped down fake version of X1000 NAND API for testing purposes,
+ * uses a normal file to store the data */
+
+#ifndef __NAND_X1000_H__
+#define __NAND_X1000_H__
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#define NAND_SUCCESS 0
+#define NAND_ERR_UNKNOWN_CHIP (-1)
+#define NAND_ERR_PROGRAM_FAIL (-2)
+#define NAND_ERR_ERASE_FAIL (-3)
+#define NAND_ERR_UNALIGNED (-4)
+#define NAND_ERR_OTHER (-5)
+#define NAND_ERR_INJECTED (-6)
+
+/* keep max page size in sync with the NAND chip table in the .c file */
+#define NAND_DRV_SCRATCHSIZE 32
+#define NAND_DRV_MAXPAGESIZE 2112
+
+typedef uint32_t nand_block_t;
+typedef uint32_t nand_page_t;
+
+enum nand_trace_type {
+ NTT_READ,
+ NTT_PROGRAM,
+ NTT_ERASE,
+};
+
+enum nand_trace_exception {
+ NTE_NONE,
+ NTE_DOUBLE_PROGRAMMED,
+ NTE_CLEARED,
+};
+
+struct nand_trace {
+ enum nand_trace_type type;
+ enum nand_trace_exception exception;
+ nand_page_t addr;
+};
+
+typedef struct nand_chip {
+ /* Base2 logarithm of the number of pages per block */
+ unsigned log2_ppb;
+
+ /* Size of a page's main / oob areas, in bytes. */
+ unsigned page_size;
+ unsigned oob_size;
+
+ /* Total number of blocks in the chip */
+ unsigned nr_blocks;
+} nand_chip;
+
+typedef struct nand_drv {
+ /* Backing file */
+ int fd;
+ int metafd;
+ int lock_count;
+
+ unsigned refcount;
+ uint8_t* scratch_buf;
+ uint8_t* page_buf;
+ const nand_chip* chip;
+ unsigned ppb;
+ unsigned fpage_size;
+} nand_drv;
+
+extern const char* nand_backing_file;
+extern const char* nand_meta_file;
+
+extern struct nand_trace* nand_trace;
+extern size_t nand_trace_capacity;
+extern size_t nand_trace_length;
+
+extern void nand_trace_reset(size_t size);
+extern void nand_inject_error(int rc);
+
+extern nand_drv* nand_init(void);
+
+extern void nand_lock(nand_drv* drv);
+extern void nand_unlock(nand_drv* drv);
+
+extern int nand_open(nand_drv* drv);
+extern void nand_close(nand_drv* drv);
+extern int nand_block_erase(nand_drv* drv, nand_block_t block);
+extern int nand_page_program(nand_drv* drv, nand_page_t page, const void* buffer);
+extern int nand_page_read(nand_drv* drv, nand_page_t page, void* buffer);
+
+#endif /* __NAND_X1000_H__ */
diff --git a/lib/x1000-installer/test_lib/pathfuncs.c b/lib/x1000-installer/test_lib/pathfuncs.c
new file mode 100644
index 0000000000..341efd4730
--- /dev/null
+++ b/lib/x1000-installer/test_lib/pathfuncs.c
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2014 by Michael Sevakis
+ *
+ * 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 "pathfuncs.h"
+#include "strlcpy.h"
+#include "system.h"
+#include <string.h>
+
+static const char* GOBBLE_PATH_SEPCH(const char* p)
+{
+ int c;
+ while((c = *p) == PATH_SEPCH)
+ ++p;
+ return p;
+}
+
+static const char* GOBBLE_PATH_COMP(const char* p)
+{
+ int c;
+ while((c = *p) && c != PATH_SEPCH)
+ ++p;
+ return p;
+}
+
+/* Strips the trailing component from the path
+ * "" *nameptr->NUL, len=0: ""
+ * "/" *nameptr->/, len=1: "/"
+ * "//" *nameptr->2nd /, len=1: "/"
+ * "/a" *nameptr->/, len=1: "/"
+ * "a/" *nameptr->a, len=0: ""
+ * "/a/bc" *nameptr->/, len=2: "/a"
+ * "d" *nameptr->d, len=0: ""
+ * "ef/gh" *nameptr->e, len=2: "ef"
+ *
+ * Notes: * Interpret len=0 as ".".
+ * * In the same string, path_dirname() returns a pointer with the
+ * same or lower address as path_basename().
+ * * Pasting a separator between the returns of path_dirname() and
+ * path_basename() will result in a path equivalent to the input.
+ *
+ */
+size_t path_dirname(const char *name, const char **nameptr)
+{
+ const char *p = GOBBLE_PATH_SEPCH(name);
+ const char *q = name;
+ const char *r = p;
+
+ while (*(p = GOBBLE_PATH_COMP(p)))
+ {
+ const char *s = p;
+
+ if (!*(p = GOBBLE_PATH_SEPCH(p)))
+ break;
+
+ q = s;
+ }
+
+ if (q == name && r > name)
+ name = r, q = name--; /* root - return last slash */
+
+ *nameptr = name;
+ return q - name;
+}
+
+/* Appends one path to another, adding separators between components if needed.
+ * Return value and behavior is otherwise as strlcpy so that truncation may be
+ * detected.
+ *
+ * For basepath and component:
+ * PA_SEP_HARD adds a separator even if the base path is empty
+ * PA_SEP_SOFT adds a separator only if the base path is not empty
+ */
+size_t path_append(char *buf, const char *basepath,
+ const char *component, size_t bufsize)
+{
+ const char *base = basepath && basepath[0] ? basepath : buf;
+ if (!base)
+ return bufsize; /* won't work to get lengths from buf */
+
+ if (!buf)
+ bufsize = 0;
+
+ if (path_is_absolute(component))
+ {
+ /* 'component' is absolute; replace all */
+ basepath = component;
+ component = "";
+ }
+
+ /* if basepath is not null or empty, buffer contents are replaced,
+ otherwise buf contains the base path */
+ size_t len = base == buf ? strlen(buf) : my_strlcpy(buf, basepath, bufsize);
+
+ bool separate = false;
+
+ if (!basepath || !component)
+ separate = !len || base[len-1] != PATH_SEPCH;
+ else if (component[0])
+ separate = len && base[len-1] != PATH_SEPCH;
+
+ /* caller might lie about size of buf yet use buf as the base */
+ if (base == buf && bufsize && len >= bufsize)
+ buf[bufsize - 1] = '\0';
+
+ buf += len;
+ bufsize -= MIN(len, bufsize);
+
+ if (separate && (len++, bufsize > 0) && --bufsize > 0)
+ *buf++ = PATH_SEPCH;
+
+ return len + my_strlcpy(buf, component ?: "", bufsize);
+}
diff --git a/lib/x1000-installer/test_lib/pathfuncs.h b/lib/x1000-installer/test_lib/pathfuncs.h
new file mode 100644
index 0000000000..225b3cdd19
--- /dev/null
+++ b/lib/x1000-installer/test_lib/pathfuncs.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2014 by Michael Sevakis
+ *
+ * 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 _PATHFUNCS_H_
+#define _PATHFUNCS_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#define PATH_SEPCH '/'
+
+/* return true if path begins with a root '/' component and is not NULL */
+static inline bool path_is_absolute(const char *path)
+{
+ return path && path[0] == PATH_SEPCH;
+}
+
+size_t path_dirname(const char *name, const char **nameptr);
+size_t path_append(char *buf, const char *basepath,
+ const char *component, size_t bufsize);
+
+#endif
diff --git a/lib/x1000-installer/test_lib/strlcpy.c b/lib/x1000-installer/test_lib/strlcpy.c
new file mode 100644
index 0000000000..681d917503
--- /dev/null
+++ b/lib/x1000-installer/test_lib/strlcpy.c
@@ -0,0 +1,50 @@
+/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+my_strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/lib/x1000-installer/test_lib/strlcpy.h b/lib/x1000-installer/test_lib/strlcpy.h
new file mode 100644
index 0000000000..0c4b16dd5a
--- /dev/null
+++ b/lib/x1000-installer/test_lib/strlcpy.h
@@ -0,0 +1,4 @@
+#ifndef STRLCPY_H
+#define STRLCPY_H
+size_t my_strlcpy(char *dst, const char *src, size_t siz);
+#endif
diff --git a/lib/x1000-installer/test_lib/system.h b/lib/x1000-installer/test_lib/system.h
new file mode 100644
index 0000000000..b0a5076ba9
--- /dev/null
+++ b/lib/x1000-installer/test_lib/system.h
@@ -0,0 +1,10 @@
+#ifndef SYSTEM_H
+#define SYSTEM_H
+
+#define CACHEALIGN_SIZE 1
+#define CACHEALIGN_BUFFER(x,y) do { } while(0)
+#define MIN(a, b) (((a)<(b))?(a):(b))
+#define ALIGN_BUFFER(ptr, size, align) do { } while(0)
+#define ALIGN_UP_P2(x, p) (((x) + ((1 << (p)) - 1)) & ~((1 << (p)) - 1))
+
+#endif
diff --git a/lib/x1000-installer/x1000-installer.make b/lib/x1000-installer/x1000-installer.make
new file mode 100644
index 0000000000..d58bd2042f
--- /dev/null
+++ b/lib/x1000-installer/x1000-installer.make
@@ -0,0 +1,21 @@
+# __________ __ ___.
+# Open \______ \ ____ ____ | | _\_ |__ _______ ___
+# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+# \/ \/ \/ \/ \/
+#
+
+X1000INSTALLER_DIR = $(ROOTDIR)/lib/x1000-installer
+X1000INSTALLER_SRC = $(call preprocess, $(X1000INSTALLER_DIR)/SOURCES)
+X1000INSTALLER_OBJ := $(call c2obj, $(X1000INSTALLER_SRC))
+
+X1000INSTALLERLIB = $(BUILDDIR)/lib/libx1000-installer.a
+
+INCLUDES += -I$(X1000INSTALLER_DIR)/include
+OTHER_SRC += $(X1000INSTALLER_SRC)
+CORE_LIBS += $(X1000INSTALLERLIB)
+
+$(X1000INSTALLERLIB): $(X1000INSTALLER_OBJ)
+ $(SILENT)$(shell rm -f $@)
+ $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null