forked from len0rd/rockbox
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
270 lines
7 KiB
C
270 lines
7 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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;
|
|
}
|