@@ -52,6 +52,7 @@ tools/*.out
test/test_crypt
test/test_hash
test/test_json
+test/test_flash_handler
test/test_server_hawkbit
test/test_util
test/test_verify
@@ -14,3 +14,5 @@ CONFIG_LUASCRIPTHANDLER=y
CONFIG_RAW=y
CONFIG_REMOTE_HANDLER=y
CONFIG_SHELLSCRIPTHANDLER=y
+CONFIG_MTD=y
+CONFIG_CFI=y
@@ -21,6 +21,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
+#include <assert.h>
#include <linux/version.h>
#include <sys/ioctl.h>
@@ -33,6 +34,57 @@
#define PROCMTD "/proc/mtd"
#define LINESIZE 80
+#define EMPTY_BYTE 0xFF
+#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
+#define ROUND_DOWN(N, S) (((N) / (S)) * (S))
+
+/* According to linux kernel ENOTSUPP is not a standard error code and should
+ * be avoided in new patches. EOPNOTSUPP should be used instead. Unfortunately
+ * some drivers still return ENOTSUPP. Do not confuse with ENOTSUP! See also:
+ * https://lists.gnu.org/archive/html/bug-glibc/2002-08/msg00017.html
+ * https://linux-fsdevel.vger.kernel.narkive.com/pERNbmWG/kernel-glibc-eopnotsupp-vs-enotsup-vs-enotsupp-also-rfc-posix-acl-kernel-infrastructure
+ */
+#ifndef ENOTSUPP
+#define ENOTSUPP 524 /* Operation is not supported */
+static const char *strerror_enotsupp(int code)
+{
+ if (code == ENOTSUPP)
+ return "Operation is not supported";
+ return strerror(code);
+}
+#define STRERROR(code) strerror_enotsupp(code)
+#else
+#define STRERROR(code) strerror(code)
+#endif
+
+static inline bool is_not_supported(int code)
+{
+ return (code == EOPNOTSUPP) || (code == ENOTSUP) || (code == ENOTSUPP);
+}
+
+static inline int mtd_error(int mtdnum, const char *func, int eb)
+{
+ int code = errno;
+ ERROR("mtd%d: mtd_%s() failed at block %d: %s (errno = %d)", mtdnum,
+ func, eb, STRERROR(code), code);
+ return -code;
+}
+#define MTD_ERROR(func) mtd_error(priv->mtdnum, (func), priv->eb)
+
+#define MTD_TRACE(func, msg) do { \
+ int _code = errno; \
+ TRACE("mtd%d: mtd_%s() %s at block %d: %s (errno = %d)", \
+ priv->mtdnum, (func), (msg), priv->eb, \
+ STRERROR(_code), _code); \
+ } while (0)
+#define MTD_TRACE_FAILED(func) MTD_TRACE((func), "failed")
+#define MTD_TRACE_NOT_SUPPORTED(func) MTD_TRACE((func), "not supported")
+
+static inline int too_many_bad_blocks(int mtdnum)
+{
+ ERROR("mtd%d: too many bad blocks", mtdnum);
+ return -ENOSPC;
+}
void flash_handler(void);
@@ -69,293 +121,438 @@ static inline int buffer_check_pattern(unsigned char *buffer, size_t size,
* The function reassembles nandwrite from mtd-utils
* dropping all options that are not required here.
*/
-
static void erase_buffer(void *buffer, size_t size)
{
- const uint8_t kEraseByte = 0xff;
-
if (buffer != NULL && size > 0)
- memset(buffer, kEraseByte, size);
+ memset(buffer, EMPTY_BYTE, size);
}
-static int flash_write_nand(int mtdnum, struct img_type *img)
+struct flash_priv {
+ /* File descriptor of a flash device (/dev/mtdX) to write into: */
+ int fdout;
+ /* Index of a flash device (X in /dev/mtdX): */
+ int mtdnum;
+ /* Number of image bytes we still need to write into flash: */
+ long long imglen;
+ /* A buffer for caching data soon to be written to flash: */
+ unsigned char *filebuf;
+ /* Amount of bytes we've read from image file into filebuf: */
+ int filebuf_len;
+ /* An offset within filebuf to write to flash starting from: */
+ int writebuf_offset;
+ /* Current flash erase block: */
+ int eb;
+ /*
+ * The following data is kept here only to speed-up execution:
+ */
+ int eb_end; /* First invalid erase block (after flash memory end). */
+ struct mtd_dev_info *mtd;
+ libmtd_t libmtd;
+ bool is_nand; /* is it NAND or some other (e.g. NOR) flash type? */
+ bool check_bad; /* do we need to check for bad blocks? */
+ bool check_locked; /* do we need to check whether a block is locked? */
+ bool do_unlock; /* do we need to try to unlock a block? */
+ unsigned char *readout_buf; /* a buffer to read erase block into */
+};
+
+static int erase_block(struct flash_priv *priv)
{
- char mtd_device[LINESIZE];
- struct flash_description *flash = get_flash_info();
- struct mtd_dev_info *mtd = &flash->mtd_info[mtdnum].mtd;
- int pagelen;
- bool baderaseblock = false;
- long long imglen = 0;
- long long blockstart = -1;
- long long offs;
- unsigned char *filebuf = NULL;
- size_t filebuf_max = 0;
- size_t filebuf_len = 0;
- long long mtdoffset = img->seek;
- int ifd = img->fdin;
- int fd = -1;
- bool failed = true;
int ret;
- unsigned char *writebuf = NULL;
+ ret = mtd_erase(priv->libmtd, priv->mtd, priv->fdout, priv->eb);
+ if (ret) {
+ if (is_not_supported(errno)) {
+ /* Some MTD drivers in linux kernel may not initialize
+ * mtd->erasesize or master->_erase, hence we expect
+ * these errors in such cases. */
+ MTD_TRACE_NOT_SUPPORTED("erase");
+ return 0;
+ } else if (errno == EIO) { /* May happen for a bad block. */
+ MTD_TRACE_FAILED("erase");
+ return -EIO;
+ } else
+ return MTD_ERROR("erase");
+ }
+ return 0;
+}
- /*
- * if nothing to do, returns without errors
- */
- if (!img->size)
- return 0;
+static int mark_bad_block(struct flash_priv *priv)
+{
+ int ret;
+ TRACE("mtd%d: marking block %d (offset %lld) as bad", priv->mtdnum,
+ priv->eb, (long long)(priv->eb) * priv->mtd->eb_size);
+ ret = mtd_mark_bad(priv->mtd, priv->fdout, priv->eb);
+ if (ret) {
+ if (is_not_supported(errno)) {
+ /* Some MTD drivers in linux kernel may not initialize
+ * master->_block_markbad, hence we expect these errors
+ * in such cases. */
+ MTD_TRACE_NOT_SUPPORTED("mark_bad");
+ } else
+ return MTD_ERROR("mark_bad");
+ }
+ return 0;
+}
- if (mtdoffset & (mtd->min_io_size - 1)) {
- ERROR("The start address is not page-aligned !\n"
- "The pagesize of this NAND Flash is 0x%x.\n",
- mtd->min_io_size);
- return -EIO;
+/*
+ * Read input data from (*p_in_buf) (size = (*p_in_len)), adjust both
+ * (*p_in_buf) and (*p_in_len) while reading. Write input data to a proper
+ * offset within priv->filebuf.
+ *
+ * On success return 0 and initialize output arguments:
+ * - (*p_wbuf) (a pointer within priv->filebuf to write data into flash
+ * starting from).
+ * - (*p_to_write) (number of bytes from (*p_wbuf) to write into flash).
+ * If necessary append padding (EMPTY_BYTE) to the data to be written.
+ * (*p_to_write) is guaranteed to be multiple of priv->mtd->min_io_size.
+ *
+ * On failure return -1. In this case there is nothing to write to flash, all
+ * data from (*p_in_buf), has been read and stored in priv->filebuf.
+ * Need to wait for next flash_write() call to get more data.
+ */
+static int read_data(struct flash_priv *priv, const unsigned char **p_in_buf,
+ size_t *p_in_len, unsigned char **p_wbuf, int *p_to_write)
+{
+ int read_available, to_read, write_available, to_write;
+ size_t in_len = *p_in_len;
+
+ assert(priv->filebuf_len <= priv->mtd->eb_size);
+ assert(priv->writebuf_offset <= priv->filebuf_len);
+ assert((priv->writebuf_offset % priv->mtd->min_io_size) == 0);
+ assert(priv->imglen >= in_len);
+
+ /* Read as much as possible data from (*p_in_buf) to priv->filebuf: */
+ read_available = priv->mtd->eb_size - priv->filebuf_len;
+ assert(read_available >= 0);
+ to_read = (in_len > read_available) ? read_available : in_len;
+ assert(to_read <= read_available);
+ assert(to_read <= in_len);
+ memcpy(priv->filebuf + priv->filebuf_len, *p_in_buf, to_read);
+ *p_in_buf += to_read;
+ (*p_in_len) -= to_read;
+ priv->filebuf_len += to_read;
+ priv->imglen -= to_read;
+
+ if (priv->imglen == 0) {
+ /* We've read all image data available.
+ * Add padding to priv->filebuf if necessary: */
+ int len, pad_bytes;
+ len = ROUND_UP(priv->filebuf_len, priv->mtd->min_io_size);
+ assert(len >= priv->filebuf_len);
+ assert(len <= priv->mtd->eb_size);
+ pad_bytes = len - priv->filebuf_len;
+ assert(pad_bytes >= 0);
+ erase_buffer(priv->filebuf + priv->filebuf_len, pad_bytes);
+ priv->filebuf_len = len;
}
- pagelen = mtd->min_io_size;
- imglen = img->size;
- snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum);
+ write_available = priv->filebuf_len - priv->writebuf_offset;
+ assert(write_available >= 0);
- if ((imglen / pagelen) * mtd->min_io_size > mtd->size - mtdoffset) {
- ERROR("Image %s does not fit into mtd%d", img->fname, mtdnum);
- return -EIO;
+ to_write = ROUND_DOWN(write_available, priv->mtd->min_io_size);
+ assert(to_write <= write_available);
+ assert((to_write % priv->mtd->min_io_size) == 0);
+
+ if (to_write == 0) {
+ /* Got not enough data to write. */
+ return -1; /* Wait for more data in next flash_write() call. */
}
- /* Flashing to NAND is currently not streamable */
- if (img->install_directly) {
- ERROR("Raw NAND not streamable");
- return -EINVAL;
+ if (priv->is_nand) {
+ /* For NAND flash limit amount of data to be written to a
+ * single page. This allows us to skip writing "empty" pages
+ * (filled with EMPTY_BYTE).
+ *
+ * Note: for NOR flash typical min_io_size is 1. Writing 1 byte
+ * at time is not practical. */
+ to_write = priv->mtd->min_io_size;
}
- if(flash_erase_sector(mtdnum, img->seek, img->size)) {
- ERROR("I cannot erasing %s",
- img->device);
- return -1;
+ *p_wbuf = priv->filebuf + priv->writebuf_offset;
+ *p_to_write = to_write;
+ return 0; /* Need to write some data. */
+}
+
+/*
+ * Check and process current erase block. Return:
+ * - 0 if proper erase block has been found.
+ * - 1 if current erase block is bad and need to continue the search.
+ * - Negative errno code on system error (in this case error reporting is
+ * already done in this function).
+ */
+static int process_new_erase_block(struct flash_priv *priv)
+{
+ int ret;
+
+ if (priv->check_bad) {
+ int is_bad = mtd_is_bad(priv->mtd, priv->fdout, priv->eb);
+ if (is_bad > 0)
+ return 1;
+ if (is_bad < 0) {
+ if (is_not_supported(errno)) {
+ /* I don't know whether such cases really
+ * exist.. Let's handle them just in case (as
+ * it is currently implemented in mtd-utils and
+ * in previous version of swupdate). */
+ MTD_TRACE_NOT_SUPPORTED("is_bad");
+ priv->check_bad = false;
+ } else
+ return MTD_ERROR("is_bad");
+ }
}
- if ((fd = open(mtd_device, O_RDWR)) < 0) {
- ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno));
- return -ENODEV;
+ if (priv->check_locked) {
+ int is_locked = mtd_is_locked(priv->mtd, priv->fdout,
+ priv->eb);
+ if (is_locked < 0) {
+ if (is_not_supported(errno)) {
+ /* Some MTD drivers in linux kernel may not
+ * initialize mtd->_is_locked, hence we expect
+ * these errors in such cases. At the same time
+ * the driver can initialize mtd->_unlock,
+ * hence we shall try to execute mtd_unlock()
+ * in such cases. */
+ MTD_TRACE_NOT_SUPPORTED("is_locked");
+ priv->check_locked = false;
+ priv->do_unlock = true;
+ } else
+ return MTD_ERROR("is_locked");
+ } else
+ priv->do_unlock = (bool)is_locked;
}
- filebuf_max = mtd->eb_size / mtd->min_io_size * pagelen;
- filebuf = calloc(1, filebuf_max);
- erase_buffer(filebuf, filebuf_max);
+ if (priv->do_unlock) {
+ ret = mtd_unlock(priv->mtd, priv->fdout, priv->eb);
+ if (ret) {
+ if (is_not_supported(errno)) {
+ /* Some MTD drivers in linux kernel may not
+ * initialize mtd->_unlock, hence we expect
+ * these errors in such cases. */
+ MTD_TRACE_NOT_SUPPORTED("unlock");
+ priv->check_locked = false;
+ priv->do_unlock = false;
+ } else
+ return MTD_ERROR("unlock");
+ }
+ }
/*
- * Get data from input and write to the device while there is
- * still input to read and we are still within the device
- * bounds. Note that in the case of standard input, the input
- * length is simply a quasi-boolean flag whose values are page
- * length or zero.
+ * NAND flash should always be erased (follow "write once rule").
+ * For other flash types check if the flash is already empty.
+ * In case of NOR flash, it can save a significant amount of time
+ * because erasing a NOR flash is very time expensive.
*/
- while ((imglen > 0 || writebuf < filebuf + filebuf_len)
- && mtdoffset < mtd->size) {
- /*
- * New eraseblock, check for bad block(s)
- * Stay in the loop to be sure that, if mtdoffset changes because
- * of a bad block, the next block that will be written to
- * is also checked. Thus, we avoid errors if the block(s) after the
- * skipped block(s) is also bad
- */
- while (blockstart != (mtdoffset & (~mtd->eb_size + 1))) {
- blockstart = mtdoffset & (~mtd->eb_size + 1);
- offs = blockstart;
-
- /*
- * if writebuf == filebuf, we are rewinding so we must
- * not reset the buffer but just replay it
- */
- if (writebuf != filebuf) {
- erase_buffer(filebuf, filebuf_len);
- filebuf_len = 0;
- writebuf = filebuf;
- }
-
- baderaseblock = false;
-
- do {
- ret = mtd_is_bad(mtd, fd, offs / mtd->eb_size);
- if (ret < 0) {
- ERROR("mtd%d: MTD get bad block failed", mtdnum);
- goto closeall;
- } else if (ret == 1) {
- baderaseblock = true;
- }
-
- if (baderaseblock) {
- mtdoffset = blockstart + mtd->eb_size;
-
- if (mtdoffset > mtd->size) {
- ERROR("too many bad blocks, cannot complete request");
- goto closeall;
- }
- }
-
- offs += mtd->eb_size;
- } while (offs < blockstart + mtd->eb_size);
+ if (!priv->is_nand) {
+ ret = mtd_read(priv->mtd, priv->fdout, priv->eb, 0,
+ priv->readout_buf, priv->mtd->eb_size);
+ if (ret)
+ return MTD_ERROR("read");
+ /* Check if already empty: */
+ if (buffer_check_pattern(priv->readout_buf, priv->mtd->eb_size,
+ EMPTY_BYTE)) {
+ return 0;
}
+ }
- /* Read more data from the input if there isn't enough in the buffer */
- if (writebuf + mtd->min_io_size > filebuf + filebuf_len) {
- size_t readlen = mtd->min_io_size;
- size_t alreadyread = (filebuf + filebuf_len) - writebuf;
- size_t tinycnt = alreadyread;
- ssize_t cnt = 0;
-
- while (tinycnt < readlen) {
- cnt = read(ifd, writebuf + tinycnt, readlen - tinycnt);
- if (cnt == 0) { /* EOF */
- break;
- } else if (cnt < 0) {
- ERROR("File I/O error on input");
- goto closeall;
- }
- tinycnt += cnt;
- }
-
- /* No padding needed - we are done */
- if (tinycnt == 0) {
- imglen = 0;
- break;
- }
-
- /* Padding */
- if (tinycnt < readlen) {
- erase_buffer(writebuf + tinycnt, readlen - tinycnt);
- }
+ ret = erase_block(priv);
+ if (ret) {
+ switch (ret) {
+ case -EIO:
+ ret = mark_bad_block(priv);
+ if (ret)
+ return ret;
+ return 1;
+ default:
+ return ret;
+ }
+ }
+ return 0;
+}
- filebuf_len += readlen - alreadyread;
+/*
+ * Find a non-bad erase block (starting from priv->eb) we can write data into.
+ * Unlock the block and erase it if necessary.
+ * As a result update (if necessary) priv->eb to point to a block we can write
+ * data into.
+ */
+static int prepare_new_erase_block(struct flash_priv *priv)
+{
+ for (; priv->eb < priv->eb_end; priv->eb++) {
+ int ret = process_new_erase_block(priv);
+ if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ return 0;
+ }
+ return too_many_bad_blocks(priv->mtdnum);
+}
- imglen -= tinycnt - alreadyread;
+/*
+ * A callback to be passed to copyimage().
+ * Write as much input data to flash as possible at this time.
+ * Skip existing and detect new bad blocks (mark them as bad) while writing.
+ * Store leftover data (if any) in private context (priv). The leftover data
+ * will be written into flash during next flash_write() executions.
+ * During last flash_write() execution all image data should be written to
+ * flash (the data should be padded with EMPTY_BYTE if necessary).
+ */
+static int flash_write(void *out, const void *buf, size_t len)
+{
+ int ret;
+ struct flash_priv *priv = (struct flash_priv *)out;
+ const unsigned char **pbuf = (const unsigned char**)&buf;
+
+ while ((len > 0) || (priv->writebuf_offset < priv->filebuf_len)) {
+ unsigned char *wbuf;
+ int to_write;
+
+ assert(priv->eb <= priv->eb_end);
+ if (priv->eb == priv->eb_end)
+ return too_many_bad_blocks(priv->mtdnum);
+
+ ret = read_data(priv, pbuf, &len, &wbuf, &to_write);
+ if (ret < 0) {
+ /* Wait for more data to be written in next
+ * flash_write() call. */
+ break;
+ }
+ /* We've got some data to be written to flash. */
+ if (priv->writebuf_offset == 0) {
+ /* Start of a new erase block. */
+ ret = prepare_new_erase_block(priv);
+ if (ret)
+ return ret;
}
+ /* Now priv->eb points to a valid erased block we can write
+ * data into. */
- ret =0;
- if (!buffer_check_pattern(writebuf, mtd->min_io_size, 0xff)) {
- /* Write out data */
- ret = mtd_write(flash->libmtd, mtd, fd, mtdoffset / mtd->eb_size,
- mtdoffset % mtd->eb_size,
- writebuf,
- mtd->min_io_size,
- NULL,
- 0,
- MTD_OPS_PLACE_OOB);
+ ret = buffer_check_pattern(wbuf, to_write, EMPTY_BYTE);
+ if (ret) {
+ ret = 0; /* There is no need to write "empty" bytes. */
+ } else {
+ /* Write data to flash: */
+ ret = mtd_write(priv->libmtd, priv->mtd, priv->fdout,
+ priv->eb, priv->writebuf_offset, wbuf,
+ to_write, NULL, 0, MTD_OPS_PLACE_OOB);
}
if (ret) {
- long long i;
- if (errno != EIO) {
- ERROR("mtd%d: MTD write failure", mtdnum);
- goto closeall;
- }
-
- /* Must rewind to blockstart if we can */
- writebuf = filebuf;
-
- for (i = blockstart; i < blockstart + mtd->eb_size; i += mtd->eb_size) {
- if (mtd_erase(flash->libmtd, mtd, fd, i / mtd->eb_size)) {
- int errno_tmp = errno;
- TRACE("mtd%d: MTD Erase failure", mtdnum);
- if (errno_tmp != EIO)
- goto closeall;
- }
- }
+ if (errno != EIO)
+ return MTD_ERROR("write");
- TRACE("Marking block at %08llx bad",
- mtdoffset & (~mtd->eb_size + 1));
- if (mtd_mark_bad(mtd, fd, mtdoffset / mtd->eb_size)) {
- ERROR("mtd%d: MTD Mark bad block failure", mtdnum);
- goto closeall;
+ ret = erase_block(priv);
+ if (ret) {
+ if (ret != -EIO)
+ return ret;
}
- mtdoffset = blockstart + mtd->eb_size;
+ ret = mark_bad_block(priv);
+ if (ret)
+ return ret;
+ /* Rewind to erase block start. */
+ priv->writebuf_offset = 0;
+ priv->eb++;
continue;
}
- /*
- * this handler does not use copyfile()
- * and must update itself the progress bar
- */
- swupdate_progress_update((img->size - imglen) * 100 / img->size);
-
- mtdoffset += mtd->min_io_size;
- writebuf += pagelen;
- }
- failed = false;
-
-closeall:
- free(filebuf);
- close(fd);
-
- if (failed) {
- ERROR("Installing image %s into mtd%d failed",
- img->fname,
- mtdnum);
- return -1;
+ priv->writebuf_offset += to_write;
+ assert(priv->writebuf_offset <= priv->filebuf_len);
+ assert(priv->filebuf_len <= priv->mtd->eb_size);
+ if (priv->writebuf_offset == priv->mtd->eb_size) {
+ priv->eb++;
+ priv->writebuf_offset = 0;
+ priv->filebuf_len = 0;
+ }
}
return 0;
}
-static int flash_write_nor(int mtdnum, struct img_type *img)
+static int flash_write_image(int mtdnum, struct img_type *img)
{
- int fdout;
+ struct flash_priv priv;
char mtd_device[LINESIZE];
int ret;
struct flash_description *flash = get_flash_info();
+ priv.mtd = &flash->mtd_info[mtdnum].mtd;
+ assert((priv.mtd->eb_size % priv.mtd->min_io_size) == 0);
- if (!mtd_dev_present(flash->libmtd, mtdnum)) {
+ if (!mtd_dev_present(flash->libmtd, mtdnum)) {
ERROR("MTD %d does not exist", mtdnum);
return -ENODEV;
}
- long long size = get_output_size(img, true);
- if (size < 0) {
- size = get_mtd_size(mtdnum);
- if (size < 0) {
- ERROR("Could not get MTD %d device size", mtdnum);
- return -ENODEV;
- }
+ if (img->seek & (priv.mtd->eb_size - 1)) {
+ ERROR("The start address is not erase-block-aligned!\n"
+ "The erase block of this flash is 0x%x.\n",
+ priv.mtd->eb_size);
+ return -EINVAL;
+ }
- WARN("decompression-size not set, erasing flash device %s from %lld to %lld",
- img->device, img->seek, size);
+ priv.imglen = get_output_size(img, true);
+ if (priv.imglen < 0) {
+ ERROR("Failed to determine output size, bailing out.");
+ return (int)priv.imglen;
}
- if (flash_erase_sector(mtdnum, img->seek, size)) {
- ERROR("Failed to erase sectors on /dev/mtd%d (start: %llu, size: %lld)",
- mtdnum, img->seek, size);
- return -1;
+ if (!priv.imglen)
+ return 0;
+
+ if (priv.imglen > priv.mtd->size - img->seek) {
+ ERROR("Image %s does not fit into mtd%d", img->fname, mtdnum);
+ return -ENOSPC;
}
snprintf(mtd_device, sizeof(mtd_device), "/dev/mtd%d", mtdnum);
- if ((fdout = open(mtd_device, O_RDWR)) < 0) {
- ERROR( "%s: %s: %s", __func__, mtd_device, strerror(errno));
- return -1;
+ priv.fdout = open(mtd_device, O_RDWR);
+ if (priv.fdout < 0) {
+ ret = errno;
+ ERROR( "%s: %s: %s", __func__, mtd_device, STRERROR(ret));
+ return -ret;
}
- ret = copyimage(&fdout, img, NULL);
- close(fdout);
+ priv.mtdnum = mtdnum;
+ priv.filebuf_len = 0;
+ priv.writebuf_offset = 0;
+ priv.eb = (int)(img->seek / priv.mtd->eb_size);
+ priv.eb_end = (int)(priv.mtd->size / priv.mtd->eb_size);
+ priv.libmtd = flash->libmtd;
+ priv.is_nand = isNand(flash, mtdnum);
+ priv.check_bad = true;
+ priv.check_locked = true;
+
+ ret = priv.mtd->eb_size;
+ if (!priv.is_nand)
+ ret += priv.mtd->eb_size;
+ priv.filebuf = malloc(ret);
+ if (!priv.filebuf) {
+ ERROR("Failed to allocate %d bytes of memory", ret);
+ ret = -ENOMEM;
+ goto end;
+ }
+ if (!priv.is_nand)
+ priv.readout_buf = priv.filebuf + priv.mtd->eb_size;
+
+ ret = copyimage(&priv, img, flash_write);
+ free(priv.filebuf);
+
+end:
+ if (close(priv.fdout)) {
+ if (!ret)
+ ret = -errno;
+ ERROR("close() failed: %s", STRERROR(errno));
+ }
/* tell 'nbytes == 0' (EOF) from 'nbytes < 0' (read error) */
if (ret < 0) {
ERROR("Failure installing into: %s", img->device);
- return -1;
+ return ret;
}
return 0;
}
-static int flash_write_image(int mtdnum, struct img_type *img)
-{
- struct flash_description *flash = get_flash_info();
-
- if (!isNand(flash, mtdnum))
- return flash_write_nor(mtdnum, img);
- else
- return flash_write_nand(mtdnum, img);
-}
-
static int install_flash_image(struct img_type *img,
void __attribute__ ((__unused__)) *data)
{
- int mtdnum;
+ int ret, mtdnum;
if (strlen(img->mtdname))
mtdnum = get_mtd_from_name(img->mtdname);
@@ -364,15 +561,16 @@ static int install_flash_image(struct img_type *img,
if (mtdnum < 0) {
ERROR("Wrong MTD device in description: %s",
strlen(img->mtdname) ? img->mtdname : img->device);
- return -1;
+ return -EINVAL;
}
TRACE("Copying %s into /dev/mtd%d", img->fname, mtdnum);
- if (flash_write_image(mtdnum, img)) {
+ ret = flash_write_image(mtdnum, img);
+ if (ret) {
ERROR("I cannot copy %s into %s partition",
img->fname,
img->device);
- return -1;
+ return ret;
}
return 0;
@@ -27,6 +27,7 @@ endif
tests-$(CONFIG_SURICATTA_HAWKBIT) += test_json
tests-$(CONFIG_SURICATTA_HAWKBIT) += test_server_hawkbit
tests-y += test_util
+tests-$(CONFIG_CFI) += test_flash_handler
ccflags-y += -I$(src)/../
new file mode 100644
@@ -0,0 +1,1456 @@
+/*
+ * Author: Viacheslav Volkov
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdint.h>
+#include <libmtd.h>
+#include <mtd/mtd-user.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "util.h"
+#include "pctl.h"
+#include "flash.h"
+#include "handler.h"
+#include "swupdate_image.h"
+#include "swupdate_status.h"
+
+/* #define PRINT_LOGS */
+#define RETURN_CODE_ENOSPC
+#define EMPTY_BYTE 0xFF
+#define DEV_MTD_PREFIX "/dev/mtd"
+#define MTD_DEV_IDX 0
+/* MTD_FD is a big dummy value: it is very unlikely for us to have so many open
+ * file descriptors: */
+#define MTD_FD 999999
+#define LIBMTD_T_VALUE ((libmtd_t)0x12345678) /* dummy value */
+
+#define FOREACH_PAGE_IN_EB(page, mtd, eb) \
+ for (page = (eb) * ((mtd)->eb_size / (mtd)->min_io_size); \
+ page < (eb + 1) * ((mtd)->eb_size / (mtd)->min_io_size); \
+ page++)
+
+#define FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) \
+ for (page = ((eb) * (mtd)->eb_size + (offs)) / (mtd)->min_io_size; \
+ page < ((eb) * (mtd)->eb_size + (offs) + (len)) / (mtd)->min_io_size; \
+ page++)
+
+/*
+ * Mock data
+ */
+static struct img_type image = {
+ .type = "flash",
+};
+static struct mtd_ubi_info mtd_ubi_info_s;
+
+/*
+ * Test-only data
+ */
+static handler handler_func;
+static bool is_nand;
+static unsigned char *image_buf;
+static void (*patch_image_buf)(unsigned char *buf);
+static int eb_bytes;
+static int pages_bytes;
+/* Actual flash state before test run: */
+static unsigned char *bad_blocks;
+static unsigned char *locked_blocks;
+static unsigned char *written_pages;
+static unsigned char *flash_memory;
+/* Expected flash state after test run: */
+static unsigned char *expected_bad_blocks;
+static unsigned char *expected_locked_blocks;
+static unsigned char *expected_written_pages;
+static unsigned char *expected_flash_memory;
+
+void *__real_malloc(size_t size);
+static void *(*impl_malloc)(size_t size) = __real_malloc;
+void *__wrap_malloc(size_t size);
+void *__wrap_malloc(size_t size)
+{
+ return impl_malloc(size);
+}
+
+int __real_open(const char *pathname, int flags);
+static int default_open(const char *pathname, int flags)
+{
+ int ret;
+ if (strcmp(pathname, image.device) == 0)
+ return MTD_FD;
+ ret = __real_open(pathname, flags);
+ /* Usually file descriptors start from 0. Since MTD_FD is big enough,
+ * a collision is very unlikely. */
+ assert_int_not_equal(ret, MTD_FD);
+ return ret;
+}
+static int (*impl_open)(const char *pathname, int flags) = default_open;
+int __wrap_open(const char *pathname, int flags);
+int __wrap_open(const char *pathname, int flags)
+{
+ return impl_open(pathname, flags);
+}
+
+int __real_close(int fd);
+int __wrap_close(int fd);
+int __wrap_close(int fd)
+{
+ if (fd == MTD_FD)
+ return 0;
+ return __real_close(fd);
+}
+
+off_t __real_lseek(int fd, off_t offset, int whence);
+off_t __wrap_lseek(int fd, off_t offset, int whence);
+off_t __wrap_lseek(int fd, off_t offset, int whence)
+{
+ if (fd == MTD_FD) {
+ /* __swupdate_copy() executes lseek() on /dev/mtdX. */
+ assert_int_equal(image.seek, offset);
+ assert_int_equal(SEEK_SET, whence);
+ return offset;
+ }
+ return __real_lseek(fd, offset, whence);
+}
+
+int __wrap_mtd_dev_present(libmtd_t desc, int mtd_num);
+int __wrap_mtd_dev_present(libmtd_t UNUSED desc, int mtd_num)
+{
+ assert_int_equal(MTD_DEV_IDX, mtd_num);
+ return 1;
+}
+
+int __wrap_get_mtd_from_device(char *s);
+int __wrap_get_mtd_from_device(char *s)
+{
+ int ret, mtdnum;
+ assert_ptr_not_equal(NULL, s);
+ if (strlen(s) < sizeof(DEV_MTD_PREFIX))
+ return -1;
+ ret = sscanf(s, DEV_MTD_PREFIX "%d", &mtdnum);
+ if (ret <= 0)
+ return -1;
+ return mtdnum;
+}
+
+static unsigned char get_byte_idx(int bit_idx)
+{
+ return bit_idx / CHAR_BIT;
+}
+
+static unsigned char get_mask(int bit_idx)
+{
+ return 1 << (bit_idx % CHAR_BIT);
+}
+
+static bool get_bit(unsigned char *data, int bit_idx)
+{
+ return (data[get_byte_idx(bit_idx)] & get_mask(bit_idx)) != 0;
+}
+
+static void set_bit(unsigned char *data, int bit_idx)
+{
+ data[get_byte_idx(bit_idx)] |= get_mask(bit_idx);
+}
+
+static void clear_bit(unsigned char *data, int bit_idx)
+{
+ data[get_byte_idx(bit_idx)] &= (~get_mask(bit_idx));
+}
+
+static void set_multiple_bits(unsigned char *data, const int *bit_indices)
+{
+ int bit_idx = bit_indices[0];
+ for (int i = 0; bit_idx >= 0; bit_idx = bit_indices[++i])
+ set_bit(data, bit_idx);
+}
+
+static void clear_multiple_bits(unsigned char *data, const int *bit_indices)
+{
+ int bit_idx = bit_indices[0];
+ for (int i = 0; bit_idx >= 0; bit_idx = bit_indices[++i])
+ clear_bit(data, bit_idx);
+}
+
+static void check_args(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ assert_ptr_equal(&mtd_ubi_info_s.mtd, mtd);
+ assert_true(
+ (mtd->type == MTD_NORFLASH) ||
+ (mtd->type == MTD_NANDFLASH));
+ assert_int_equal(MTD_FD, fd);
+ assert_true(eb >= 0);
+ assert_true(eb < mtd->size / mtd->eb_size);
+}
+
+static int default_mtd_get_bool(const struct mtd_dev_info UNUSED *mtd,
+ int UNUSED fd, int eb, unsigned char *data)
+{
+ return get_bit(data, eb) ? 1 : 0;
+}
+
+static int default_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ return default_mtd_get_bool(mtd, fd, eb, bad_blocks);
+}
+static int (*impl_mtd_is_bad)(const struct mtd_dev_info *mtd, int fd, int eb);
+int __wrap_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb);
+int __wrap_mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ return impl_mtd_is_bad(mtd, fd, eb);
+}
+
+static int default_mtd_mark_bad(const struct mtd_dev_info UNUSED *mtd,
+ int UNUSED fd, int eb)
+{
+ set_bit(bad_blocks, eb);
+ return 0;
+}
+static int (*impl_mtd_mark_bad)(const struct mtd_dev_info *mtd, int fd,
+ int eb);
+int __wrap_mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb);
+int __wrap_mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ assert_false(get_bit(bad_blocks, eb));
+ /* Note: we don't require erasing a block before marking it bad. */
+ return impl_mtd_mark_bad(mtd, fd, eb);
+}
+
+static int default_mtd_is_locked(const struct mtd_dev_info *mtd, int fd,
+ int eb)
+{
+ return default_mtd_get_bool(mtd, fd, eb, locked_blocks);
+}
+static int (*impl_mtd_is_locked)(const struct mtd_dev_info *mtd, int fd,
+ int eb);
+int __wrap_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, int eb);
+int __wrap_mtd_is_locked(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ return impl_mtd_is_locked(mtd, fd, eb);
+}
+
+static int default_mtd_unlock(const struct mtd_dev_info UNUSED *mtd,
+ int UNUSED fd, int eb)
+{
+ clear_bit(locked_blocks, eb);
+ return 0;
+}
+static int (*impl_mtd_unlock)(const struct mtd_dev_info *mtd, int fd, int eb);
+int __wrap_mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb);
+int __wrap_mtd_unlock(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ assert_false(get_bit(bad_blocks, eb));
+ /* Unlocking already unlocked blocks is totaly fine:
+ * - not every flash supports mtd_is_locked() check;
+ * - some implementation might prefer to mtd_unlock() rigth away
+ * without checking mtd_is_locked(). */
+ return impl_mtd_unlock(mtd, fd, eb);
+}
+
+static int default_mtd_erase(libmtd_t UNUSED desc,
+ const struct mtd_dev_info *mtd, int UNUSED fd, int eb)
+{
+ unsigned int page;
+ memset(flash_memory + eb * mtd->eb_size, EMPTY_BYTE, mtd->eb_size);
+ FOREACH_PAGE_IN_EB(page, mtd, eb)
+ clear_bit(written_pages, page);
+ return 0;
+}
+static int (*impl_mtd_erase)(libmtd_t desc, const struct mtd_dev_info *mtd,
+ int fd, int eb);
+int __wrap_mtd_erase(libmtd_t desc, const struct mtd_dev_info *mtd, int fd,
+ int eb);
+int __wrap_mtd_erase(libmtd_t desc, const struct mtd_dev_info *mtd, int fd,
+ int eb)
+{
+ check_args(mtd, fd, eb);
+ assert_false(get_bit(bad_blocks, eb));
+ assert_false(get_bit(locked_blocks, eb));
+ return impl_mtd_erase(desc, mtd, fd, eb);
+}
+
+static int default_mtd_write(libmtd_t UNUSED desc,
+ const struct mtd_dev_info *mtd, int UNUSED fd, int eb,
+ int offs, void *data, int len, void UNUSED *oob,
+ int UNUSED ooblen, uint8_t UNUSED mode)
+{
+ unsigned int page;
+ unsigned int flash_offset = eb * mtd->eb_size + offs;
+ const unsigned char *pdata = (const unsigned char*)data;
+ FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len) {
+ set_bit(written_pages, page);
+ memcpy(flash_memory + flash_offset, pdata, mtd->min_io_size);
+ flash_offset += mtd->min_io_size;
+ pdata += mtd->min_io_size;
+ }
+ return 0;
+}
+
+static int (*impl_mtd_write)(libmtd_t desc, const struct mtd_dev_info *mtd,
+ int fd, int eb, int offs, void *data, int len, void *oob,
+ int ooblen, uint8_t mode);
+int __wrap_mtd_write(libmtd_t desc, const struct mtd_dev_info *mtd, int fd,
+ int eb, int offs, void *data, int len, void *oob, int ooblen,
+ uint8_t mode);
+
+int __wrap_mtd_write(libmtd_t desc, const struct mtd_dev_info *mtd, int fd,
+ int eb, int offs, void *data, int len, void *oob, int ooblen,
+ uint8_t mode)
+{
+ unsigned int page;
+ check_args(mtd, fd, eb);
+ assert_true(offs >= 0);
+ assert_ptr_not_equal(NULL, data);
+ assert_true(len > 0);
+ assert_true(offs + len <= mtd->eb_size);
+ assert_ptr_equal(NULL, oob);
+ assert_int_equal(0, ooblen);
+ assert_false(get_bit(bad_blocks, eb));
+ assert_false(get_bit(locked_blocks, eb));
+ assert_true(offs <= mtd->eb_size - mtd->min_io_size);
+ assert_int_equal(0, offs % mtd->min_io_size);
+ assert_int_equal(0, len % mtd->min_io_size);
+
+ if (mtd->type == MTD_NANDFLASH) {
+ /* Follow "write once rule" for NAND flash. */
+ FOREACH_PAGE_WRITTEN(page, mtd, eb, offs, len)
+ assert_false(get_bit(written_pages, page));
+ } else {
+ /* Assume it is ok to write multiple times to NOR flash. */
+ unsigned int flash_i = eb * mtd->eb_size + offs;
+ for (int data_i = 0; data_i < len; data_i++) {
+ uint8_t old_byte = flash_memory[flash_i + data_i];
+ uint8_t new_byte = ((uint8_t*)data)[data_i];
+ assert_true((old_byte & new_byte) == new_byte);
+ }
+ }
+ return impl_mtd_write(desc, mtd, fd, eb, offs, data, len, oob, ooblen,
+ mode);
+}
+
+static int default_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb,
+ int offs, void *buf, int len)
+{
+ unsigned int flash_offset = eb * mtd->eb_size + offs;
+ check_args(mtd, fd, eb);
+ memcpy(buf, flash_memory + flash_offset, len);
+ return 0;
+}
+static int (*impl_mtd_read)(const struct mtd_dev_info *mtd, int fd, int eb,
+ int offs, void *buf, int len);
+int __wrap_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs,
+ void *buf, int len);
+int __wrap_mtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs,
+ void *buf, int len)
+{
+ check_args(mtd, fd, eb);
+ assert_true(offs >= 0);
+ assert_ptr_not_equal(NULL, buf);
+ assert_true(len > 0);
+ assert_true(offs + len <= mtd->eb_size);
+ assert_false(get_bit(bad_blocks, eb));
+ assert_false(get_bit(locked_blocks, eb));
+ assert_true(offs <= mtd->eb_size - mtd->min_io_size);
+ assert_int_equal(0, offs % mtd->min_io_size);
+ assert_int_equal(0, len % mtd->min_io_size);
+ return impl_mtd_read(mtd, fd, eb, offs, buf, len);
+}
+
+static unsigned char *generate_image_file(const char *name, size_t size)
+{
+ int r, fd;
+ unsigned char *buf = malloc(size);
+ unsigned char *pbuf = buf;
+ assert_ptr_not_equal(NULL, buf);
+ fd = open(name, O_WRONLY);
+ if (fd < 0)
+ free(buf);
+ assert_true(fd >= 0);
+
+ for (size_t i = 0; i < size; i++)
+ buf[i] = (unsigned char)i;
+
+ if (patch_image_buf)
+ patch_image_buf(buf);
+
+ while (size > 0) {
+ ssize_t ret = write(fd, pbuf, size);
+ if (ret < 0) {
+ close(fd);
+ free(buf);
+ }
+ assert_true(ret >= 0);
+ pbuf += ret;
+ size -= ret;
+ }
+
+ r = close(fd);
+ assert_int_equal(0, r);
+ return buf;
+}
+
+static int group_setup(void UNUSED **state)
+{
+ int ret, fd;
+ struct flash_description *flash = get_flash_info();
+ struct installer_handler *hnd = find_handler(&image);
+
+ assert_ptr_not_equal(NULL, hnd);
+ handler_func = hnd->installer;
+ assert_ptr_not_equal(NULL, handler_func);
+ flash->mtd_info = &mtd_ubi_info_s;
+ flash->libmtd = LIBMTD_T_VALUE;
+
+#ifdef PRINT_LOGS
+ loglevel = DEBUGLEVEL;
+ notify_init();
+#endif
+
+ strcpy(image.fname, "swupdate_image_XXXXXX.bin");
+ fd = mkstemps(image.fname, 4 /* = strlen(".bin") */);
+ if (fd < 0)
+ image.fname[0] = 0; /* required for group_teardown() */
+ ret = close(fd);
+ assert_int_equal(0, ret);
+ return 0;
+}
+
+static int group_teardown(void UNUSED **state)
+{
+ if (image.fname[0] != 0) {
+ int ret = unlink(image.fname);
+ assert_int_equal(0, ret);
+ }
+ return 0;
+}
+
+static void copy_flash_state(void)
+{
+ struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd;
+
+ expected_bad_blocks = malloc(eb_bytes);
+ assert_ptr_not_equal(NULL, expected_bad_blocks);
+ memcpy(expected_bad_blocks, bad_blocks, eb_bytes);
+
+ expected_locked_blocks = malloc(eb_bytes);
+ assert_ptr_not_equal(NULL, expected_locked_blocks);
+ memcpy(expected_locked_blocks, locked_blocks, eb_bytes);
+
+ expected_written_pages = malloc(pages_bytes);
+ assert_ptr_not_equal(NULL, expected_written_pages);
+ memcpy(expected_written_pages, written_pages, pages_bytes);
+
+ expected_flash_memory = malloc(mtd->size);
+ assert_ptr_not_equal(NULL, expected_flash_memory);
+ memcpy(expected_flash_memory, flash_memory, mtd->size);
+}
+
+static void init_flash_state(void)
+{
+ struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd;
+ int num = (int)(mtd->size / mtd->eb_size);
+ eb_bytes = num / CHAR_BIT;
+ if (num % CHAR_BIT)
+ eb_bytes++;
+ /* By default there are no any bad blocks: */
+ bad_blocks = calloc(eb_bytes, 1);
+ assert_ptr_not_equal(NULL, bad_blocks);
+ /* By default all blocks are locked: */
+ locked_blocks = malloc(eb_bytes);
+ assert_ptr_not_equal(NULL, locked_blocks);
+ memset(locked_blocks, 0xFF, eb_bytes);
+
+ num = (int)(mtd->size / mtd->min_io_size);
+ pages_bytes = num / CHAR_BIT;
+ if (num % CHAR_BIT)
+ pages_bytes++;
+ /* By default all pages are written (require erase): */
+ written_pages = malloc(pages_bytes);
+ assert_ptr_not_equal(NULL, written_pages);
+ memset(written_pages, 0xFF, pages_bytes);
+
+ flash_memory = malloc(mtd->size);
+ assert_ptr_not_equal(NULL, flash_memory);
+ /* Fill flash memory with initial pattern: */
+ memset(flash_memory, 0xA5, mtd->size);
+}
+
+static void test_init(void)
+{
+ struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd;
+
+ /* Unit test config (precondition) sanity check: */
+ assert_true(mtd->size >= mtd->eb_size);
+ assert_true(mtd->eb_size >= mtd->min_io_size);
+ assert_int_equal(0, mtd->size % mtd->eb_size);
+ assert_int_equal(0, mtd->eb_size % mtd->min_io_size);
+
+ is_nand = (mtd->type == MTD_NANDFLASH) ||
+ (mtd->type == MTD_MLCNANDFLASH);
+ image_buf = generate_image_file(image.fname, image.size);
+ image.fdin = open(image.fname, O_RDONLY);
+ assert_true(image.fdin >= 0);
+
+ init_flash_state();
+}
+
+static void verify_flash_state(void)
+{
+ struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd;
+ assert_memory_equal(expected_bad_blocks, bad_blocks, eb_bytes);
+ assert_memory_equal(expected_locked_blocks, locked_blocks, eb_bytes);
+ assert_memory_equal(expected_written_pages, written_pages,
+ pages_bytes);
+ assert_memory_equal(expected_flash_memory, flash_memory, mtd->size);
+}
+
+static void run_flash_test(void UNUSED **state, int expected_return_code)
+{
+ int ret = handler_func(&image, NULL);
+#ifdef RETURN_CODE_ENOSPC
+ /* Currently on callback() failure __swupdate_copy() always returns
+ * -ENOSPC (no matter what callback() returned). Hence some subset of
+ * flash handler return values are converted to -ENOSPC. Here we'll
+ * convert rest of return values to -ENOSPC to map expected and actual
+ * return values to a simple success/failure check. */
+ if (ret < 0)
+ ret = -ENOSPC;
+ if (expected_return_code < 0)
+ expected_return_code = -ENOSPC;
+#endif
+ assert_int_equal(expected_return_code, ret);
+ verify_flash_state();
+}
+
+static int test_setup(void UNUSED **state)
+{
+ struct mtd_dev_info *mtd = &mtd_ubi_info_s.mtd;
+ image_buf = NULL;
+ patch_image_buf = NULL;
+ image.fdin = -1;
+
+ bad_blocks = NULL;
+ locked_blocks = NULL;
+ written_pages = NULL;
+ flash_memory = NULL;
+
+ expected_bad_blocks = NULL;
+ expected_locked_blocks = NULL;
+ expected_written_pages = NULL;
+ expected_flash_memory = NULL;
+
+ /* Tests can overwrite the following default implementations to inject
+ * errors: */
+ impl_malloc = __real_malloc;
+ impl_open = default_open;
+ impl_mtd_is_bad = default_mtd_is_bad;
+ impl_mtd_mark_bad = default_mtd_mark_bad;
+ impl_mtd_is_locked = default_mtd_is_locked;
+ impl_mtd_unlock = default_mtd_unlock;
+ impl_mtd_erase = default_mtd_erase;
+ impl_mtd_write = default_mtd_write;
+ impl_mtd_read = default_mtd_read;
+
+ /* Some default values: */
+ mtd->type = MTD_NANDFLASH;
+ strcpy(image.device, DEV_MTD_PREFIX STR(MTD_DEV_IDX));
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ return 0;
+}
+
+static int test_teardown(void UNUSED **state)
+{
+ unsigned char *pointer[] = {
+ bad_blocks,
+ locked_blocks,
+ written_pages,
+ flash_memory,
+
+ expected_bad_blocks,
+ expected_locked_blocks,
+ expected_written_pages,
+ expected_flash_memory,
+
+ image_buf,
+ };
+ impl_malloc = __real_malloc;
+ for (int i = 0; i < ARRAY_SIZE(pointer); i++)
+ free(pointer[i]);
+ if (image.fdin >= 0) {
+ int ret = close(image.fdin);
+ assert_int_equal(0, ret);
+ }
+ return 0;
+}
+
+static void test_simple(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_simple_NOR(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 1;
+ mtd_ubi_info_s.mtd.type = MTD_NORFLASH;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_padding_less_than_page(void **state)
+{
+ image.seek = 0;
+ image.size = 42;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ memset(expected_flash_memory + 42, EMPTY_BYTE, 6);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_padding_page(void **state)
+{
+ image.seek = 0;
+ image.size = 40;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ memset(expected_flash_memory + 40, EMPTY_BYTE, 8);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+ clear_bit(expected_written_pages, 5);
+
+ run_flash_test(state, 0);
+}
+
+static void patch_image_buf_empty_bytes_1(unsigned char *buf)
+{
+ memset(buf + 8, EMPTY_BYTE, 24);
+}
+
+static void test_skip_write_page_empty_bytes(void **state)
+{
+ image.seek = 0;
+ image.size = 40;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ patch_image_buf = patch_image_buf_empty_bytes_1;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ memset(expected_flash_memory + 40, EMPTY_BYTE, 8);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+ if (is_nand)
+ clear_bit(expected_written_pages, 1);
+ clear_bit(expected_written_pages, 2);
+ clear_bit(expected_written_pages, 3);
+ clear_bit(expected_written_pages, 5);
+
+ run_flash_test(state, 0);
+}
+
+static void test_padding_more_than_page(void **state)
+{
+ image.seek = 0;
+ image.size = 37;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ memset(expected_flash_memory + 37, EMPTY_BYTE, 11);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+ clear_bit(expected_written_pages, 5);
+
+ run_flash_test(state, 0);
+}
+
+static void test_seek(void **state)
+{
+ image.seek = 16;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory + 16, image_buf, image.size);
+ for (int i = 1; i <= 3; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_seek_not_multiple_of_eb_size(void **state)
+{
+ image.seek = 8;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ test_init();
+ copy_flash_state();
+ run_flash_test(state, -EINVAL);
+}
+
+static void test_not_enough_flash(void **state)
+{
+ image.seek = 16;
+ image.size = 1020;
+ mtd_ubi_info_s.mtd.size = 1024;
+ test_init();
+ copy_flash_state();
+ run_flash_test(state, -ENOSPC);
+}
+
+static void test_invalid_mtd_device(void **state)
+{
+ test_init();
+ strcpy(image.device, "/dev/mtdX");
+ copy_flash_state();
+ run_flash_test(state, -EINVAL);
+}
+
+static void test_invalid_image_size(void **state)
+{
+ test_init();
+ image.size = -42;
+ copy_flash_state();
+ run_flash_test(state, -42);
+}
+
+static void test_empty_image(void **state)
+{
+ test_init();
+ image.size = 0;
+ copy_flash_state();
+ run_flash_test(state, 0);
+}
+
+static int open_mtd_dev_failure_errno;
+static int open_mtd_dev_failure(const char *pathname, int flags)
+{
+ if (strcmp(pathname, image.device) == 0) {
+ errno = open_mtd_dev_failure_errno;
+ return -1;
+ }
+ return default_open(pathname, flags);
+}
+static void test_mtd_dev_open_failure(void **state)
+{
+ test_init();
+ impl_open = open_mtd_dev_failure;
+ open_mtd_dev_failure_errno = EPERM;
+ copy_flash_state();
+ run_flash_test(state, -open_mtd_dev_failure_errno);
+}
+
+static int malloc_filebuf_allocation_failure_size;
+static void *malloc_filebuf_allocation_failure(size_t size)
+{
+ if (size == malloc_filebuf_allocation_failure_size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return __real_malloc(size);
+}
+static void test_malloc_failure(void **state)
+{
+ test_init();
+ copy_flash_state();
+ impl_malloc = malloc_filebuf_allocation_failure;
+ malloc_filebuf_allocation_failure_size = mtd_ubi_info_s.mtd.eb_size;
+ if (!is_nand) {
+ /* filebuf allocation includes space for readout buffer */
+ malloc_filebuf_allocation_failure_size *= 2;
+ }
+ run_flash_test(state, -ENOMEM);
+}
+
+static void test_skip_bad_blocks(void **state)
+{
+ image.seek = 896;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ /* Initial flash state: | flash memory ends here
+ * eb 56 57 58 59 60 61 62 63 | 64
+ * offset 896 912 928 944 960 976 992 1008 | 1024
+ * status bad ok bad ok bad bad ok bad | */
+
+ {
+ int indices[] = {56, 58, 60, 61, 63, -1};
+ set_multiple_bits(bad_blocks, indices);
+ }
+ copy_flash_state();
+
+ {
+ int indices[] = {57, 59, 62, -1};
+ clear_multiple_bits(expected_locked_blocks, indices);
+ }
+ memcpy(expected_flash_memory + 912, image_buf , 16);
+ memcpy(expected_flash_memory + 944, image_buf + 16, 16);
+ memcpy(expected_flash_memory + 992, image_buf + 32, 16);
+
+ run_flash_test(state, 0);
+}
+
+static void test_too_many_known_bad_blocks(void **state)
+{
+ image.seek = 896;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ /* Initial flash state: | flash memory ends here
+ * eb 56 57 58 59 60 61 62 63 | 64
+ * offset 896 912 928 944 960 976 992 1008 | 1024
+ * status bad ok bad ok bad bad bad bad | */
+
+ {
+ int indices[] = {56, 58, 60, 61, 62, 63, -1};
+ set_multiple_bits(bad_blocks, indices);
+ }
+ copy_flash_state();
+
+ {
+ int indices[] = {57, 59, -1};
+ clear_multiple_bits(expected_locked_blocks, indices);
+ }
+ memcpy(expected_flash_memory + 912, image_buf , 16);
+ memcpy(expected_flash_memory + 944, image_buf + 16, 16);
+
+ run_flash_test(state, -ENOSPC);
+}
+
+static int mtd_write_failure_1_eb;
+static int mtd_write_failure_1_offs;
+static int mtd_write_failure_1_errno;
+static int mtd_write_failure_1(libmtd_t desc, const struct mtd_dev_info *mtd,
+ int fd, int eb, int offs, void *data, int len, void *oob,
+ int ooblen, uint8_t mode)
+{
+ if (eb == mtd_write_failure_1_eb) {
+ unsigned char *p_data = (unsigned char *)data;
+ for (int o = offs; o < offs + len; o += mtd->min_io_size) {
+ int ret;
+ if (o == mtd_write_failure_1_offs) {
+ errno = mtd_write_failure_1_errno;
+ return -1;
+ }
+ ret = default_mtd_write(desc, mtd, fd, eb, o,
+ p_data, mtd->min_io_size, oob,
+ ooblen, mode);
+ assert_int_equal(0, ret);
+ p_data += mtd->min_io_size;
+ }
+ }
+ return default_mtd_write(desc, mtd, fd, eb, offs, data, len, oob,
+ ooblen, mode);
+}
+
+static void test_too_many_unknown_bad_blocks(void **state)
+{
+ image.seek = 896;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_offs = 0;
+ mtd_write_failure_1_errno = EIO;
+
+ /* Initial flash state: | flash memory ends here
+ * eb 56 57 58 59 60 61 62 63 | 64
+ * offset 896 912 928 944 960 976 992 1008 | 1024
+ * status bad ok bad ok bad bad bad ok->bad | */
+ {
+ int indices[] = {56, 58, 60, 61, 62, -1};
+ set_multiple_bits(bad_blocks, indices);
+ }
+ mtd_write_failure_1_eb = 63;
+ copy_flash_state();
+
+ {
+ int indices[] = {57, 59, 63, -1};
+ clear_multiple_bits(expected_locked_blocks, indices);
+ }
+ memcpy(expected_flash_memory + 912, image_buf , 16);
+ memcpy(expected_flash_memory + 944, image_buf + 16, 16);
+ memset(expected_flash_memory + 1008, EMPTY_BYTE, 16);
+ set_bit(expected_bad_blocks, 63);
+ clear_bit(expected_written_pages, 63 * 2);
+ clear_bit(expected_written_pages, 63 * 2 + 1);
+
+ run_flash_test(state, -ENOSPC);
+}
+
+static int mtd_is_bad_failure_1_errno;
+static int mtd_is_bad_failure_1(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ errno = mtd_is_bad_failure_1_errno;
+ return -1;
+}
+
+static void test_mtd_is_bad_not_supported(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+ impl_mtd_is_bad = mtd_is_bad_failure_1;
+ mtd_is_bad_failure_1_errno = EOPNOTSUPP;
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_is_bad_failure(void **state)
+{
+ test_init();
+ impl_mtd_is_bad = mtd_is_bad_failure_1;
+ mtd_is_bad_failure_1_errno = ERANGE; /* dummy value */
+ copy_flash_state();
+ run_flash_test(state, -mtd_is_bad_failure_1_errno);
+}
+
+static int mtd_is_locked_failure_1_errno;
+static int mtd_is_locked_failure_1(const struct mtd_dev_info *mtd, int fd,
+ int eb)
+{
+ check_args(mtd, fd, eb);
+ errno = mtd_is_locked_failure_1_errno;
+ return -1;
+}
+
+static void test_mtd_is_locked_not_supported(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+ impl_mtd_is_locked = mtd_is_locked_failure_1;
+ mtd_is_locked_failure_1_errno = EOPNOTSUPP;
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_is_locked_failure(void **state)
+{
+ test_init();
+ impl_mtd_is_locked = mtd_is_locked_failure_1;
+ mtd_is_locked_failure_1_errno = ERANGE; /* dummy value */
+ copy_flash_state();
+ run_flash_test(state, -mtd_is_locked_failure_1_errno);
+}
+
+static int mtd_unlock_failure_1_errno;
+static int mtd_unlock_failure_1(const struct mtd_dev_info *mtd, int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ errno = mtd_unlock_failure_1_errno;
+ return -1;
+}
+
+static void test_mtd_unlock_not_supported(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+ impl_mtd_unlock = mtd_unlock_failure_1;
+ mtd_unlock_failure_1_errno = EOPNOTSUPP;
+ memset(locked_blocks, 0, eb_bytes);
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_unlock_failure(void **state)
+{
+ test_init();
+ impl_mtd_unlock = mtd_unlock_failure_1;
+ mtd_unlock_failure_1_errno = ERANGE; /* dummy value */
+ copy_flash_state();
+ run_flash_test(state, -mtd_unlock_failure_1_errno);
+}
+
+static int mtd_read_failure_1_errno;
+static int mtd_read_failure_1(const struct mtd_dev_info *mtd, int fd, int eb,
+ int UNUSED offs, void UNUSED *buf, int UNUSED len)
+{
+ check_args(mtd, fd, eb);
+ errno = mtd_read_failure_1_errno;
+ return -1;
+}
+
+static void test_mtd_read_failure(void **state)
+{
+ mtd_ubi_info_s.mtd.type = MTD_NORFLASH;
+ test_init();
+ impl_mtd_read = mtd_read_failure_1;
+ mtd_read_failure_1_errno = ERANGE; /* dummy value */
+ copy_flash_state();
+ clear_bit(expected_locked_blocks, 0);
+ run_flash_test(state, -mtd_read_failure_1_errno);
+}
+
+static int mtd_erase_failure_1_eb;
+static int mtd_erase_failure_1_errno;
+static int mtd_erase_failure_1(libmtd_t desc, const struct mtd_dev_info *mtd,
+ int fd, int eb)
+{
+ if (eb == mtd_erase_failure_1_eb) {
+ errno = mtd_erase_failure_1_errno;
+ if (errno == EOPNOTSUPP)
+ default_mtd_erase(desc, mtd, fd, eb);
+ return -1;
+ }
+ return default_mtd_erase(desc, mtd, fd, eb);
+}
+
+static void test_mtd_read_no_erase_empty_flash_bytes(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.type = MTD_NORFLASH;
+ test_init();
+ memset(flash_memory, EMPTY_BYTE, 16);
+ impl_mtd_erase = mtd_erase_failure_1;
+ mtd_erase_failure_1_eb = 0;
+ mtd_erase_failure_1_errno = ERANGE; /* dummy value */
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_erase_not_supported(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+ impl_mtd_erase = mtd_erase_failure_1;
+ mtd_erase_failure_1_eb = 0;
+ mtd_erase_failure_1_errno = EOPNOTSUPP;
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_erase_failure(void **state)
+{
+ test_init();
+ impl_mtd_erase = mtd_erase_failure_1;
+ mtd_erase_failure_1_eb = 0;
+ mtd_erase_failure_1_errno = ERANGE; /* dummy value */
+ copy_flash_state();
+ clear_bit(expected_locked_blocks, 0);
+ run_flash_test(state, -mtd_erase_failure_1_errno);
+}
+
+static void test_mtd_erase_failure_EIO(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+ impl_mtd_erase = mtd_erase_failure_1;
+ mtd_erase_failure_1_eb = 0;
+ mtd_erase_failure_1_errno = EIO;
+
+ copy_flash_state();
+ memcpy(expected_flash_memory + 16, image_buf, image.size);
+ for (int i = 0; i <= 3; i++)
+ clear_bit(expected_locked_blocks, i);
+ set_bit(expected_bad_blocks, 0);
+
+ run_flash_test(state, 0);
+}
+
+static void patch_image_buf_empty_bytes_2(unsigned char *buf)
+{
+ memset(buf, EMPTY_BYTE, 16);
+}
+
+static void test_no_mtd_write_empty_image_bytes(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ patch_image_buf = patch_image_buf_empty_bytes_2;
+ test_init();
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 0;
+ mtd_write_failure_1_offs = 0;
+ mtd_write_failure_1_errno = ERANGE; /* dummy value */
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ for (int i = 0; i <= 2; i++)
+ clear_bit(expected_locked_blocks, i);
+ clear_bit(expected_written_pages, 0);
+ clear_bit(expected_written_pages, 1);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_write_failure(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 1;
+ mtd_write_failure_1_offs = 8;
+ mtd_write_failure_1_errno = ERANGE; /* dummy value */
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, 24);
+ memset(expected_flash_memory + 24, EMPTY_BYTE, 8);
+ for (int i = 0; i <= 1; i++)
+ clear_bit(expected_locked_blocks, i);
+ clear_bit(expected_written_pages, 3);
+
+ run_flash_test(state, -mtd_write_failure_1_errno);
+}
+
+static void test_mtd_write_bad_block(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 1;
+ mtd_write_failure_1_offs = 8;
+ mtd_write_failure_1_errno = EIO;
+
+ copy_flash_state();
+ set_bit(expected_bad_blocks, 1);
+ clear_bit(expected_written_pages, 2);
+ clear_bit(expected_written_pages, 3);
+ memcpy(expected_flash_memory, image_buf, 16);
+ memset(expected_flash_memory + 16, EMPTY_BYTE, 16);
+ memcpy(expected_flash_memory + 32, image_buf + 16, 32);
+ for (int i = 0; i <= 3; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static int mtd_erase_failure_2_eb;
+static int mtd_erase_failure_2_idx;
+static int mtd_erase_failure_2_errno;
+static int mtd_erase_failure_2(libmtd_t desc, const struct mtd_dev_info *mtd,
+ int fd, int eb)
+{
+ check_args(mtd, fd, eb);
+ if (eb == mtd_erase_failure_2_eb) {
+ if (mtd_erase_failure_2_idx == 0) {
+ errno = mtd_erase_failure_2_errno;
+ return -1;
+ }
+ --mtd_erase_failure_2_idx;
+ }
+ return default_mtd_erase(desc, mtd, fd, eb);
+}
+
+static void test_mtd_write_bad_block_erase_failure(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 1;
+ mtd_write_failure_1_offs = 8;
+ mtd_write_failure_1_errno = EIO;
+
+ impl_mtd_erase = mtd_erase_failure_2;
+ mtd_erase_failure_2_eb = 1;
+ mtd_erase_failure_2_idx = 1;
+ mtd_erase_failure_2_errno = ERANGE; /* dummy value */
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, 24);
+ memset(expected_flash_memory + 24, EMPTY_BYTE, 8);
+ for (int i = 0; i <= 1; i++)
+ clear_bit(expected_locked_blocks, i);
+ clear_bit(expected_written_pages, 3);
+
+ run_flash_test(state, -mtd_erase_failure_2_errno);
+}
+
+static void test_mtd_write_bad_block_erase_failure_EIO(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 1;
+ mtd_write_failure_1_offs = 8;
+ mtd_write_failure_1_errno = EIO;
+
+ impl_mtd_erase = mtd_erase_failure_2;
+ mtd_erase_failure_2_eb = 1;
+ mtd_erase_failure_2_idx = 1;
+ mtd_erase_failure_2_errno = EIO;
+
+ copy_flash_state();
+ set_bit(expected_bad_blocks, 1);
+ clear_bit(expected_written_pages, 3);
+ memcpy(expected_flash_memory, image_buf, 24);
+ memset(expected_flash_memory + 24, EMPTY_BYTE, 8);
+ memcpy(expected_flash_memory + 32, image_buf + 16, 32);
+ for (int i = 0; i <= 3; i++)
+ clear_bit(expected_locked_blocks, i);
+
+ run_flash_test(state, 0);
+}
+
+static int mtd_mark_bad_failure_1_errno;
+static int mtd_mark_bad_failure_1(const struct mtd_dev_info *mtd, int fd,
+ int eb)
+{
+ check_args(mtd, fd, eb);
+ errno = mtd_mark_bad_failure_1_errno;
+ return -1;
+}
+
+static void test_mtd_write_bad_block_mark_not_supported(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 1;
+ mtd_write_failure_1_offs = 8;
+ mtd_write_failure_1_errno = EIO;
+
+ impl_mtd_mark_bad = mtd_mark_bad_failure_1;
+ mtd_mark_bad_failure_1_errno = EOPNOTSUPP;
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, 16);
+ memset(expected_flash_memory + 16, EMPTY_BYTE, 16);
+ memcpy(expected_flash_memory + 32, image_buf + 16, 32);
+
+ for (int i = 0; i <= 3; i++)
+ clear_bit(expected_locked_blocks, i);
+ for (int i = 2; i <= 3; i++)
+ clear_bit(expected_written_pages, i);
+
+ run_flash_test(state, 0);
+}
+
+static void test_mtd_write_bad_block_mark_failure(void **state)
+{
+ image.seek = 0;
+ image.size = 48;
+ mtd_ubi_info_s.mtd.size = 1024;
+ mtd_ubi_info_s.mtd.eb_size = 16;
+ mtd_ubi_info_s.mtd.min_io_size = 8;
+ test_init();
+
+ impl_mtd_write = mtd_write_failure_1;
+ mtd_write_failure_1_eb = 1;
+ mtd_write_failure_1_offs = 8;
+ mtd_write_failure_1_errno = EIO;
+
+ impl_mtd_mark_bad = mtd_mark_bad_failure_1;
+ mtd_mark_bad_failure_1_errno = ERANGE; /* dummy value */
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, 16);
+ memset(expected_flash_memory + 16, EMPTY_BYTE, 16);
+ for (int i = 0; i <= 1; i++)
+ clear_bit(expected_locked_blocks, i);
+ for (int i = 2; i <= 3; i++)
+ clear_bit(expected_written_pages, i);
+
+ run_flash_test(state, -mtd_mark_bad_failure_1_errno);
+}
+
+static void test_multiple_callbacks(void **state)
+{
+ /* flash_write() is typically executed with (len <= 16 * 1024) */
+ image.seek = 0;
+ image.size = 63 * 1024;
+ mtd_ubi_info_s.mtd.size = 64 * 1024;
+ mtd_ubi_info_s.mtd.eb_size = 8 * 1024;
+ mtd_ubi_info_s.mtd.min_io_size = 1024;
+ test_init();
+
+ copy_flash_state();
+ memcpy(expected_flash_memory, image_buf, image.size);
+ memset(expected_flash_memory + 63 * 1024, EMPTY_BYTE, 1024);
+ for (int i = 0; i <= 7; i++)
+ clear_bit(expected_locked_blocks, i);
+ clear_bit(expected_written_pages, 63);
+
+ run_flash_test(state, 0);
+}
+
+#define TEST(name) \
+ cmocka_unit_test_setup_teardown(test_##name, test_setup, test_teardown)
+
+int main(void)
+{
+ static const struct CMUnitTest tests[] = {
+ TEST(simple),
+ TEST(simple_NOR),
+ TEST(padding_less_than_page),
+ TEST(padding_page),
+ TEST(skip_write_page_empty_bytes),
+ TEST(padding_more_than_page),
+ TEST(seek),
+ TEST(seek_not_multiple_of_eb_size),
+ TEST(not_enough_flash),
+ TEST(invalid_mtd_device),
+ TEST(invalid_image_size),
+ TEST(empty_image),
+ TEST(mtd_dev_open_failure),
+ TEST(malloc_failure),
+ TEST(skip_bad_blocks),
+ TEST(too_many_known_bad_blocks),
+ TEST(too_many_unknown_bad_blocks),
+ TEST(mtd_is_bad_not_supported),
+ TEST(mtd_is_bad_failure),
+ TEST(mtd_is_locked_not_supported),
+ TEST(mtd_is_locked_failure),
+ TEST(mtd_unlock_not_supported),
+ TEST(mtd_unlock_failure),
+ TEST(mtd_read_failure),
+ TEST(mtd_read_no_erase_empty_flash_bytes),
+ TEST(mtd_erase_not_supported),
+ TEST(mtd_erase_failure),
+ TEST(mtd_erase_failure_EIO),
+ TEST(no_mtd_write_empty_image_bytes),
+ TEST(mtd_write_failure),
+ TEST(mtd_write_bad_block),
+ TEST(mtd_write_bad_block_erase_failure),
+ TEST(mtd_write_bad_block_erase_failure_EIO),
+ TEST(mtd_write_bad_block_mark_not_supported),
+ TEST(mtd_write_bad_block_mark_failure),
+ TEST(multiple_callbacks),
+ };
+ return cmocka_run_group_tests_name("flash_handler", tests, group_setup,
+ group_teardown);
+}