/*
 * (C) Copyright 2004-2009 Andre McCurdy, NXP Semiconductors
 *
 * 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 <common.h>
#include <malloc.h>
#include <asm/errno.h>

#include <bl_image_db.h>

#if 0
#define IMAGE_DB_DEBUG
#endif

#define VERIFY_BYTES_PER_DOT            (256 * 1024)

#define ENABLE_SHADOW_PARTITIONS_NOR
#define ENABLE_SHADOW_PARTITIONS_NAND


/*****************************************************************************
*****************************************************************************/

#define NOR_PARTITION_BOOT_SIZE         (      512 * 1024)
#define NOR_PARTITION_ENV_SIZE          (      128 * 1024)
#define NOR_PARTITION_KERNEL_SIZE       (     1664 * 1024)
#define NOR_PARTITION_FS_SIZE           (11 * 1024 * 1024)
#define NOR_PARTITION_USER_SIZE         (      640 * 1024)

#define NOR_PARTITION_BOOT_BASE         (MIU_CS0_BASE_CACHED)
#define NOR_PARTITION_ENV_BASE          (NOR_PARTITION_BOOT_BASE    + NOR_PARTITION_BOOT_SIZE  )
#define NOR_PARTITION_KERNEL_BASE       (NOR_PARTITION_ENV_BASE     + NOR_PARTITION_ENV_SIZE   )
#define NOR_PARTITION_FS_BASE           (NOR_PARTITION_KERNEL_BASE  + NOR_PARTITION_KERNEL_SIZE)
#define NOR_PARTITION_USER_BASE         (NOR_PARTITION_FS_BASE      + NOR_PARTITION_FS_SIZE    )

#if defined (ENABLE_SHADOW_PARTITIONS_NOR)
#define NOR_PARTITION_KERNEL2_BASE      (NOR_PARTITION_USER_BASE    + NOR_PARTITION_USER_SIZE  )
#define NOR_PARTITION_FS2_BASE          (NOR_PARTITION_KERNEL2_BASE + NOR_PARTITION_KERNEL_SIZE)
#endif

static int flash_partition_dimensions_nor (char *partition_name, unsigned char **base, unsigned int *size,
                                           char **device_name, char **mtdparts)
{
    if (strcmp (partition_name, "Boot") == 0) {
        *base = (unsigned char *) NOR_PARTITION_BOOT_BASE;
        *size = NOR_PARTITION_BOOT_SIZE;
        *device_name = "/dev/mtdblock0";
        goto success;
    }

    if (strcmp (partition_name, "Env") == 0) {
        *base = (unsigned char *) NOR_PARTITION_ENV_BASE;
        *size = NOR_PARTITION_ENV_SIZE;
        *device_name = "/dev/mtdblock1";
        goto success;
    }

    if (strcmp (partition_name, "Kernel") == 0) {
        *base = (unsigned char *) NOR_PARTITION_KERNEL_BASE;
        *size = NOR_PARTITION_KERNEL_SIZE;
        *device_name = "/dev/mtdblock2";
        goto success;
    }

    if (strcmp (partition_name, "Filesystem") == 0) {
        *base = (unsigned char *) NOR_PARTITION_FS_BASE;
        *size = NOR_PARTITION_FS_SIZE;
        *device_name = "/dev/mtdblock3";
        goto success;
    }

    if (strcmp (partition_name, "User") == 0) {
        *base = (unsigned char *) NOR_PARTITION_USER_BASE;
        *size = NOR_PARTITION_USER_SIZE;
        *device_name = "/dev/mtdblock4";
        goto success;
    }

#if defined (ENABLE_SHADOW_PARTITIONS_NOR)
    if (strcmp (partition_name, "Kernel2") == 0) {
        *base = (unsigned char *) NOR_PARTITION_KERNEL2_BASE;
        *size = NOR_PARTITION_KERNEL_SIZE;
        *device_name = "/dev/mtdblock5";
        goto success;
    }

    if (strcmp (partition_name, "Filesystem2") == 0) {
        *base = (unsigned char *) NOR_PARTITION_FS2_BASE;
        *size = NOR_PARTITION_FS_SIZE;
        *device_name = "/dev/mtdblock6";
        goto success;
    }
#endif

    printf ("%s: unknown partition '%s'\n", __FUNCTION__, partition_name);
    return -1;

success:
#if defined (ENABLE_SHADOW_PARTITIONS_NOR)
    *mtdparts = "physmap-flash.0:512k(Boot),128k(Env),1664k(Kernel),11M(Filesystem),640k(User),1664k(Kernel2),11M(Filesystem2)";
#else
    *mtdparts = "physmap-flash.0:512k(Boot),128k(Env),1664k(Kernel),11M(Filesystem),640k(User)";
#endif

//  printf ("%s: %11s base 0x%08x, size 0x%08x, devname %s\n", __FUNCTION__, partition_name, *base, *size, *device_name);
    return 0;
}


/*****************************************************************************
*****************************************************************************/

#if (CONFIG_COMMANDS & CFG_CMD_NAND)

#define NAND_PARTITION_BOOT0_SIZE       (      128 * 1024)
#define NAND_PARTITION_BOOT1_SIZE       (     1024 * 1024)
#define NAND_PARTITION_ENV_SIZE         (      512 * 1024)
#define NAND_PARTITION_KERNEL_SIZE      ( 4 * 1024 * 1024)
#define NAND_PARTITION_FS_SIZE          (16 * 1024 * 1024)

#if defined (ENABLE_SHADOW_PARTITIONS_NAND)
#define NAND_MTDPARTS_STRING            "Nand:128k(Boot0),1M(Boot1),512k(Env),4M(Kernel),16M(Filesystem),4M(Kernel2),16M(Filesystem2)"
#else
#define NAND_MTDPARTS_STRING            "Nand:128k(Boot0),1M(Boot1),512k(Env),4M(Kernel),16M(Filesystem)"
#endif

#define NAND_PARTITION_BOOT0_OFFSET     (0)
#define NAND_PARTITION_BOOT1_OFFSET     (NAND_PARTITION_BOOT0_OFFSET   + NAND_PARTITION_BOOT0_SIZE  )
#define NAND_PARTITION_ENV_OFFSET       (NAND_PARTITION_BOOT1_OFFSET   + NAND_PARTITION_BOOT1_SIZE  )
#define NAND_PARTITION_KERNEL_OFFSET    (NAND_PARTITION_ENV_OFFSET     + NAND_PARTITION_ENV_SIZE    )
#define NAND_PARTITION_FS_OFFSET        (NAND_PARTITION_KERNEL_OFFSET  + NAND_PARTITION_KERNEL_SIZE )

#if defined (ENABLE_SHADOW_PARTITIONS_NAND)
#define NAND_PARTITION_KERNEL2_OFFSET   (NAND_PARTITION_FS_OFFSET      + NAND_PARTITION_FS_SIZE     )
#define NAND_PARTITION_FS2_OFFSET       (NAND_PARTITION_KERNEL2_OFFSET + NAND_PARTITION_KERNEL_SIZE )
#endif

#if 0
#define NAND_MTDBLOCK_BASE	"/dev/mtdblock"
#else
#define NAND_MTDBLOCK_BASE	"/dev/mtdblock_robbs"
#endif

static int flash_partition_dimensions_nand (char *partition_name, unsigned int *offset, unsigned int *size,
                                            char **device_name, char **mtdparts)
{
    if (strcmp (partition_name, "Boot0") == 0) {
        *offset = NAND_PARTITION_BOOT0_OFFSET;
        *size = NAND_PARTITION_BOOT0_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "0";
        goto success;
    }
    if (strcmp (partition_name, "Boot1") == 0) {
        *offset = NAND_PARTITION_BOOT1_OFFSET;
        *size = NAND_PARTITION_BOOT1_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "1";
        goto success;
    }
    if (strcmp (partition_name, "Env") == 0) {
        *offset = NAND_PARTITION_ENV_OFFSET;
        *size = NAND_PARTITION_ENV_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "2";
        goto success;
    }
    if (strcmp (partition_name, "Kernel") == 0) {
        *offset = NAND_PARTITION_KERNEL_OFFSET;
        *size = NAND_PARTITION_KERNEL_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "3";
        goto success;
    }
    if (strcmp (partition_name, "Filesystem") == 0) {
        *offset = NAND_PARTITION_FS_OFFSET;
        *size = NAND_PARTITION_FS_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "4";
        goto success;
    }

#if defined (ENABLE_SHADOW_PARTITIONS_NAND)
    if (strcmp (partition_name, "Kernel2") == 0) {
        *offset = NAND_PARTITION_KERNEL2_OFFSET;
        *size = NAND_PARTITION_KERNEL_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "5";
        goto success;
    }
    if (strcmp (partition_name, "Filesystem2") == 0) {
        *offset = NAND_PARTITION_FS2_OFFSET;
        *size = NAND_PARTITION_FS_SIZE;
        *device_name = NAND_MTDBLOCK_BASE "6";
        goto success;
    }
#endif

    printf ("%s: unknown partition '%s'\n", __FUNCTION__, partition_name);
    return -1;

success:
    *mtdparts = NAND_MTDPARTS_STRING;

//  printf ("%s: %11s offset 0x%08x, size 0x%08x, devname %s\n", __FUNCTION__, partition_name, *offset, *size, *device_name);
    return 0;
}

#endif

/*****************************************************************************
*****************************************************************************/

#define IMAGE_DB_TYPE_FS(type)          ((type) == IMG_TYPE_INITFS_CRAMFS)
#define IMAGE_DB_TYPE_KERNEL(type)      ((type) == IMG_TYPE_UIMAGE)
#define IMAGE_DB_TYPE_OTHER(type)       ((type) == IMG_TYPE_SPLASHSCREEN)

#define IMAGE_DB_VALID_TYPE(type)       (IMAGE_DB_TYPE_FS(type)         || \
                                         IMAGE_DB_TYPE_KERNEL(type)     || \
                                         IMAGE_DB_TYPE_OTHER(type))

#define IMAGE_DB_VALIDATE_MORE(image)   (1)

#define IMAGE_DB_ENTRIES_MAX            (1 + /* splashscreen    */ \
                                         1 + /* uimage          */ \
                                         1 + /* cramfs          */ \
                                         1 + /* one for luck... */ \
                                         0 )

#define IMAGE_DB_BOOT_TYPE              (IMG_TYPE_UIMAGE)


/*****************************************************************************
*****************************************************************************/

static imagedb_entry_t image_db_table[IMAGE_DB_ENTRIES_MAX];
static unsigned int release_id_selection;

#if defined (IMAGE_DB_DEBUG)
static const unsigned int image_db_debug = 1;
#else
static const unsigned int image_db_debug = 0;
#endif

/*****************************************************************************
*****************************************************************************/

#if 0
static void hexdump_local (unsigned char *data, int byte_count)
{
    int i;

    for (i = 0; i < byte_count; i++) {
        if (i % 16 == 0)
            printf ("\n%08x: ", (unsigned long) data);
        if (i % 16 == 8)
            printf ("- ");
        printf ("%02x ", *data++);
    }
    printf ("\n");
}
#endif

static int release_id_selection_read (void)
{
    return -1;
}

static char *flash_address_to_name_string (unsigned char *address)
{
    return "flash";
}

/*****************************************************************************
*****************************************************************************/

#if 0
static void image_db_display_image_summary (aimage_v1_header_t *image, char *devname, char *trailing_message)
{
    char *where_from;
#if 0
    char *build_host = image->build_host_offset == 0 ? "unknown" : ((char *) image) + image->build_host_offset;
#else
    char *build_host = "-";
#endif

    where_from = (devname) ? devname : flash_address_to_name_string ((unsigned char *) image);

    printf ("%-13s: %s (%8d,%08x) %-5s : %-12s%s\n",
            aimage_type_to_string (image->type),
            aimage_release_id_to_string (image->release_id),
            image->data_length,
            (unsigned long) image,
            where_from,
            build_host,
            trailing_message);
}
#endif

/*****************************************************************************
*****************************************************************************/

static void image_db_entry_init (imagedb_entry_t *db_entry, aimage_v1_header_t *image, int source_type, char *device_name, char *mtdparts)
{
    db_entry->source_type = source_type;
    memcpy (&db_entry->cached, image, sizeof (aimage_v1_header_t));
    db_entry->mem_image = (source_type == IMAGEDB_SOURCE_TYPE_MEM) ? image : NULL;
    strncpy (db_entry->devname, device_name, sizeof(db_entry->devname) - 1);
    db_entry->devname[sizeof(db_entry->devname) - 1] = 0;
    db_entry->mtdparts = mtdparts;
}

static void image_db_entry_display_summary (imagedb_entry_t *db_entry, char *trailing_message)
{
    char mem_address_string[16];            /* worst case: "12345678", ie 8 + 1 */
    aimage_v1_header_t *image;
    char *build_host;

    if (db_entry->source_type == IMAGEDB_SOURCE_TYPE_MEM) {
        sprintf (mem_address_string, "%08lx", (unsigned long) db_entry->mem_image);
        image = db_entry->mem_image;
        build_host = (image->build_host_offset == 0) ? "<unknown>" : ((char *) image) + image->build_host_offset;
    }
    else {
        sprintf (mem_address_string, "--------");
        image = &db_entry->cached;
        build_host = "??";
    }

    printf ("%-13s: %s (%8d,%s) %-5s : %-12s%s\n",
            aimage_type_to_string (image->type),
            aimage_release_id_to_string (image->release_id),
            image->data_length,
            mem_address_string,
            db_entry->devname,
            build_host,
            trailing_message);
}

/*****************************************************************************
*****************************************************************************/

void image_db_init (void)
{
    memset (image_db_table, 0, sizeof(image_db_table));
    release_id_selection = release_id_selection_read();                 /* returns -1 if no valid selection was found */
}

int image_db_entry_add (aimage_v1_header_t *image, int source_type, char *device_name, char *mtdparts)
{
    imagedb_entry_t temp;
    imagedb_entry_t *db_entry;

    image_db_entry_init (&temp, image, source_type, device_name, mtdparts);

    if ((!(IMAGE_DB_VALID_TYPE(image->type))) || (!(IMAGE_DB_VALIDATE_MORE(image)))) {
        image_db_entry_display_summary (&temp, "<- ignored");
        goto reject_entry;
    }

    image_db_entry_display_summary (&temp, "<- considered");

    if ((db_entry = image_db_find (image->type)) == NULL) {             /* existing image of same type as new image is not in db */
        db_entry = image_db_find_unused();                              /* find the first available unused entry in db */
        goto update_entry;                                              /* write new image into the unused entry */
    }

    /*
       Else...
       An existing image of the same type as the new image _is_ already
       present in the database. Do some checks on release ID values to
       determine which image to keep.

       Basic algorithm is accept new images if their release ID is
       greater than or equal to the highest release ID already found
       or if their release ID matches the requested release ID.

       ie if two or more images are equally selectable, use the one scanned last.
    */

    if (image->release_id >= db_entry->cached.release_id)               /* if new image has a more recent (or same) release ID */
        if (release_id_selection == (unsigned int) -1)                  /*   if a release ID has not been specified */
            if (image->build_time >= db_entry->cached.build_time)       /*     if new image has a more recent (or same) build time (== time image was signed) */
                goto update_entry;                                      /*       then overwrite existing image with new image */
            else                                                        /*     else */
                goto reject_entry;                                      /*       keep the existing image */
        else                                                            /*   if a release ID has been specified */
            if ((image->release_id == release_id_selection) ||          /*     if new image matches the requested release */
                (db_entry->cached.release_id != release_id_selection))  /*     or existing image does not match requested release */
                goto update_entry;                                      /*       then overwrite existing image with new image */
            else                                                        /*   else */
                goto reject_entry;                                      /*     keep the existing image */
    else                                                                /* if new image has an older release ID than the existing one */
        if ((release_id_selection != (unsigned int) -1) &&              /*   if a release ID has been specified */
            (image->release_id == release_id_selection))                /*   and the new image matches the request */
            goto update_entry;                                          /*     then replace existing image with new (older...) one */
        else                                                            /*   else */
            goto reject_entry;                                          /*     keep the existing image */

update_entry:

    if ((image_db_debug) && 0)
        printf ("image_db_entry_add: updating\n");

    if (db_entry == NULL) {                                             /* could not find an unused entry (database is full) */
        printf ("image db is full !!\n");
        return -1;
    }

#if 1
    memcpy (db_entry, &temp, sizeof(imagedb_entry_t));
#else
    db_entry->source_type = source_type;
    strncpy (db_entry->devname, device_name, sizeof(db_entry->devname) - 1);
    db_entry->devname[sizeof(db_entry->devname) - 1] = 0;
    db_entry->mtdparts = mtdparts;
    memcpy (&db_entry->cached, image, sizeof (aimage_v1_header_t));
    db_entry->mem_image = (source_type == IMAGEDB_SOURCE_TYPE_MEM) ? image : NULL;
#endif

    return 0;

reject_entry:

    return 0;
}

imagedb_entry_t *image_db_find (unsigned int type)
{
    int i;

    for (i = 0; i < IMAGE_DB_ENTRIES_MAX; i++) {
        if (image_db_table[i].source_type != IMAGEDB_SOURCE_TYPE_UNUSED) {
            if (image_db_table[i].cached.type == type)
                return &image_db_table[i];
        }
    }

    return NULL;    /* no entry of requested type was found */
}

imagedb_entry_t *image_db_find_bootable (void)
{
    imagedb_entry_t *db_entry = image_db_find (IMAGE_DB_BOOT_TYPE);

    return db_entry;
}

imagedb_entry_t *image_db_find_unused (void)
{
    int i;

    for (i = 0; i < IMAGE_DB_ENTRIES_MAX; i++)
        if (image_db_table[i].source_type == IMAGEDB_SOURCE_TYPE_UNUSED)
            return &image_db_table[i];

    return NULL;    /* no unused entry found in image database */
}

/*
   Get the release ID used by the image db code.
   Should be the same value as returned by release_id_selection_read(),
   but faster and without the debug output from that function...
*/
unsigned int image_db_get_release_id (void)
{
    return release_id_selection;            /* returns -1 if no valid selection was found */
}

void image_db_show_selected (void)
{
    int i;
    int some_images = 0;

    for (i = 0; i < IMAGE_DB_ENTRIES_MAX; i++) {
        if (image_db_table[i].source_type != IMAGEDB_SOURCE_TYPE_UNUSED) {
            if (some_images++ == 0)
                printf ("======\n");
            image_db_entry_display_summary (&image_db_table[i], "<- selected");
        }
    }

    if (some_images)
        printf ("======\n");
}

/*****************************************************************************
*****************************************************************************/

int image_db_scan_flash_nor (void)
{
    static const char *scannable_partitions[] =
    {
        "Kernel",
        "Filesystem",
#if defined (ENABLE_SHADOW_PARTITIONS_NOR)
        "Kernel2",
        "Filesystem2",
#endif
    };

    int i;
    aimage_v1_header_t *image;
    int within_a_blob;
    unsigned int part_size;
    unsigned char *base_address;
    unsigned char *limit_address;
    char *device_name;
    char *mtdparts;
    int verbose = (getenv ("scan_verbose") == NULL) ? 0 : 1;

    for (i = 0; i < (sizeof(scannable_partitions) / sizeof(scannable_partitions[0])); i++)
    {
        char *part_name = (char *) scannable_partitions[i];

        if (flash_partition_dimensions_nor (part_name, &base_address, &part_size, &device_name, &mtdparts) != 0)
            continue;

        printf ("%s: %11s base 0x%08x, size 0x%08x, %s\n", __FUNCTION__, part_name, base_address, part_size, device_name);

        limit_address = base_address + part_size;
        within_a_blob = 0;

        while (1) {
            image = (aimage_v1_header_t *) base_address;

            if ((image_db_debug) && 0)
                printf ("scanning: base 0x%08x, limit 0x%08x (partition '%s')...\n",
                           (unsigned long) base_address,
                           (unsigned long) limit_address,
                           flash_address_to_name_string (base_address));

            if (within_a_blob == 0) {   /* check each header unless we are in a blob of already verified images */
                if (aimage_v1_verify_signature_dots_dk (image, 0, (limit_address - base_address), VERIFY_BYTES_PER_DOT) != 0) {
                    if ((image_db_debug) && 0)
                        printf ("aborting: invalid image 0x%08x, 0x%08x, %d\n",
                                   (unsigned long) base_address,
                                   (unsigned long) limit_address,
                                   (limit_address - base_address));
                    break;
                }

                /*
                   We haven't aborted... ie signature must be OK
                */
                if (((image_db_debug) && 1) || (verbose))
                    aimage_v1_display_info_with_confidence (image, AIMAGE_CONFIDENCE_SIGNATURE_OK);

                if (image->type == IMG_TYPE_FIRMWARE_BLOB) {
                    /*
                        Fake limit address of this partition to equal end of blob image.
                        ie ignore any images which follow after the blob in this partition.
                        If we didn't do this, we'd have to keep track of when we exit the
                        blob so we known when to go back to validating images individually.
                    */
                    limit_address = base_address + image->length;
                    base_address = base_address + image->data_start_offset;
                    within_a_blob = 1;
                    continue;
                }
            }

            /*
               Try to add image to image database. If adding the image fails (ie because
               there's no more room in database) then stop scanning for new images...
            */
            if (image_db_entry_add (image, IMAGEDB_SOURCE_TYPE_MEM, device_name, mtdparts) != 0) {
                if (image_db_debug)
                    printf ("done (add entry failed)\n");
                goto done;
            }

            base_address += image->length;                              /* move to next image in this partition (or blob) */
            if (base_address >= (limit_address - sizeof(aimage_v1_header_t))) {
                if (image_db_debug)
                    printf ("aborting: limit reached, base 0x%08x, limit 0x%08x\n",
                               (unsigned long) base_address,
                               (unsigned long) limit_address);
                break;
            }
        }
    }

done:
    return 0;
}

/*****************************************************************************
*****************************************************************************/

#if (CONFIG_COMMANDS & CFG_CMD_NAND)

#if (CFG_MAX_NAND_DEVICE != 1)
#error "Unexpected number of NAND devices..."
#endif

#include <nand.h>

#define NAND_INFO               (&nand_info[0])     /* support first NAND device only... */
#define NAND_BLOCKSIZE_MAX      (128 * 1024)        /* fixme: this goes on the stack... 128k is a little cheeky */

extern void sha1sum_display (unsigned char *buf, unsigned int length);

static int nand_read_local (unsigned long offset, unsigned long len, unsigned char *buf)
{
    nand_info_t *meminfo = NAND_INFO;
    unsigned int pagesize = meminfo->oobblock;
    unsigned int erasesize = meminfo->erasesize;
    unsigned long offset_end;
    int result;

    if (len != erasesize) {
        printf ("%s: read length (%d) must equal NAND erase block size (%d)\n",
                __FUNCTION__, len, erasesize);
        return -1;
    }

    if (offset != (offset & (~erasesize + 1))) {
        printf ("%s: read offset (0x%08x) not NAND erase block aligned (erase block size 0x%08x)\n",
                __FUNCTION__, offset, erasesize);
        return -1;
    }

    result = meminfo->block_isbad (meminfo, offset);

    if (result < 0) {
        printf ("%s: meminfo->block_isbad() unexpected error (offset 0x%08x)\n", __FUNCTION__, offset);
        return -1;
    }

    if (result == 1) {
//      printf ("%s: meminfo->block_isbad() bad block (offset 0x%08x)\n", __FUNCTION__, offset);
        return -1;
    }

    offset_end = offset + len;

    while (offset < offset_end) {
        size_t readlen = 0;
        result = meminfo->read (meminfo, offset, pagesize, &readlen, buf);

        /*
           See drivers/nand/nand_base.c -> nand_read_ecc(), e.g.
           -EINVAL  : We tried to read past end of the device... (should never happen).
           -EBADMSG : ECC failure... we know the block isn't marked bad, so an ECC
                      failure here means something unexpected is happening, probably that
                      ECC information wasn't setup correctly when the data was written
                      (which can happen if Flash isn't erased before writing ?).
        */
        if (result == -EBADMSG) {
            printf ("reading NAND page at offset 0x%08x failed (ECC failure ?!?)\n", offset);
            return -EBADMSG;
        }
        else if (result != 0) {
            printf ("reading NAND page at offset 0x%08x failed (result %d)\n", offset, result);
            return -1;
        }
        else if (readlen != pagesize) {
            printf ("reading NAND page at offset 0x%08x failed (readlen %d)\n", offset, readlen);
            return -1;
        }

        buf += pagesize;
        offset += pagesize;
    }

#if 0
    printf ("0x%08x: ", offset);
    sha1sum_display (buf - len, len);
#endif

    return 0;
}

static int image_db_nand_scan (char *part_name, unsigned char *membuf, unsigned int membuf_size)
{
    aimage_stream_context_t ctx;
    aimage_v1_header_t header_temp;
    unsigned char block_buffer[NAND_BLOCKSIZE_MAX];

    char *mtdparts;
    char *device_name;
    unsigned char *buf;
    unsigned int buf_inc;
    unsigned int part_size;
    unsigned int offset_base;
    unsigned int offset_end;
    unsigned int offset_cur;
    unsigned int offset_dot;
    aimage_v1_header_t *image;
    unsigned int block_size = NAND_INFO->erasesize;
    unsigned long ticks_start, ticks_stop, ticks_delta;
    unsigned int kbps;
    int valid_images_count = 0;
    int result = 0;
    int bb_count;
    int verbose;

    if (block_size > NAND_BLOCKSIZE_MAX)
        return -1;

    if ((membuf != NULL) && (membuf_size < block_size))
        return -1;

    if (flash_partition_dimensions_nand (part_name, &offset_base, &part_size, &device_name, &mtdparts) != 0)
        return -1;

    verbose = (getenv ("scan_verbose") == NULL) ? 0 : 1;

    if (verbose)
        printf ("%s: %s : offset 0x%08x, size 0x%08x, %s (block_size %d)\n", __FUNCTION__, part_name, offset_base, part_size, device_name, block_size);

    offset_end = offset_base + part_size;
    offset_cur = offset_base - block_size;          /* NB: offset_cur is always incremented before use */
    offset_dot = offset_base;

retry_next_image:

    ticks_start = get_ticks();

retry_read_header:

    offset_cur += block_size;
    if (offset_cur >= offset_end) {
        printf ("%s: no more input data, reading header\n");
        return -1;
    }
    if (nand_read_local (offset_cur, block_size, block_buffer) != 0) {
        printf ("%s: retry read header (error @ 0x%08x)\n", __FUNCTION__, offset_cur);
        goto retry_read_header;
    }

    if (aimage_v1_sanity_check ((aimage_v1_header_t *) block_buffer, 0, (offset_end - offset_cur)) != 0) {
        if (valid_images_count != 0)
            return 0;
        if (verbose)
            printf ("%s: header not found (offset 0x%08x)\n", __FUNCTION__, offset_cur);
        return -1;
    }

    if (IMAGE_DB_TYPE_FS(((aimage_v1_header_t *) block_buffer)->type)) {
        if (valid_images_count != 0) {
            printf ("%s: filesystem found, but not at start of partition (offset 0x%08x)\n", __FUNCTION__, offset_cur);
            return -1;
        }
        memcpy (&header_temp, block_buffer, sizeof(header_temp));
        image = &header_temp;
        buf = block_buffer;
        buf_inc = 0;
//      printf ("%s: FS image, length %d\n", __FUNCTION__, image->length);
    }
    else if (IMAGE_DB_TYPE_KERNEL(((aimage_v1_header_t *) block_buffer)->type)) {
        if (membuf == NULL) {
            printf ("%s: kernel found, but no buffer available (offset 0x%08x)\n", __FUNCTION__, offset_cur);
            return -1;
        }
        memcpy (membuf, block_buffer, block_size);
        image = (aimage_v1_header_t *) membuf;
        buf = membuf;
        buf_inc = block_size;
//      printf ("%s: Kernel image, length %d\n", __FUNCTION__, image->length);
    }
    else {
        printf ("%s: unexpected image type (offset 0x%08x)\n", __FUNCTION__, offset_cur);
        return -1;
    }

    printf ("%d ", valid_images_count);
    aimage_v1_verify_signature_stream_init_dk (&ctx, 0, image->length, NULL);
    bb_count = 0;

    while (1) {
        result = aimage_v1_verify_signature_stream_update (&ctx, buf, block_size);
        if (result == 0)                    /* signature check passed */
            break;
        if (result == -1)                   /* signature check failed */
            break;

        if (buf_inc) {
            buf += buf_inc;
            if (buf >= (membuf + membuf_size)) {
                result = -2;
                break;
            }
        }

retry_read_block:

        offset_cur += block_size;
        if (offset_cur >= offset_end) {
            result = -3;
            break;
        }
        if ((offset_cur - offset_dot) >= VERIFY_BYTES_PER_DOT) {
            offset_dot += VERIFY_BYTES_PER_DOT;
            printf ((bb_count == 0) ? "." : (bb_count == 1) ? "x" : "X");
            bb_count = 0;
        }
        if (nand_read_local (offset_cur, block_size, buf) != 0) {
//          printf ("\n%s: skipping bad block (offset 0x%08x)\n", __FUNCTION__, offset_cur);
            bb_count++;
            goto retry_read_block;
        }
    }

    if (result == 0) {
        ticks_stop = get_ticks();
        ticks_delta = ticks_stop - ticks_start;
        kbps = (unsigned int) ((((unsigned long long) image->length) * get_tbclk()) /
                               (((unsigned long long) ticks_delta ) * 1024));

#if 0
        printf ("\n%s: read + verified %d bytes @ %4d kbps\n", __FUNCTION__, image->length, kbps);
#else
        printf (" (verified %d.%02d MBytes @ %d kbps)\n",
                ((image->length + ((((1024*1024)+99)/100)/2)) / (1024*1024)),
                ((image->length + ((((1024*1024)+99)/100)/2)) % (1024*1024)) / (((1024*1024)+99)/100), kbps);
#endif
        if (IMAGE_DB_TYPE_FS(image->type)) {
            if (((image_db_debug) && 1) || verbose)
                aimage_v1_display_info_with_confidence (image, AIMAGE_CONFIDENCE_SIGNATURE_OK);
            if (image_db_entry_add (image, IMAGEDB_SOURCE_TYPE_NAND, device_name, mtdparts) != 0)
                return -1;
        }
        else if (IMAGE_DB_TYPE_KERNEL(image->type)) {
            if (((image_db_debug) && 1) || verbose)
                aimage_v1_display_info_with_confidence ((aimage_v1_header_t *) membuf, AIMAGE_CONFIDENCE_SIGNATURE_OK);
            if (image_db_entry_add ((aimage_v1_header_t *) membuf, IMAGEDB_SOURCE_TYPE_MEM, device_name, mtdparts) != 0)
                return -1;
            membuf = NULL;
        }
        else {
            printf ("%s: huh ?!?\n", __FUNCTION__);
            return -1;
        }

        valid_images_count++;
        goto retry_next_image;
    }
    else if (result == -1) {
        printf ("\n%s: corrupt image (final read offset 0x%08x)\n", __FUNCTION__, offset_cur);
    }
    else if (result == -2) {
        printf ("\n%s: no more membuf output space\n", __FUNCTION__);
    }
    else if (result == -3) {
        printf ("\n%s: no more input data, (offset 0x%08x)\n", __FUNCTION__, offset_cur);
    }
    else {
        printf ("\n%s: unexpected error %d ?!?\n", __FUNCTION__, result);
    }

    return result;
}

static void *malloc_local (size_t size)
{
#if defined (IMAGE_DB_DEBUG)
    void *mem;
    unsigned long ticks_start, ticks_stop;

    ticks_start = get_ticks();
    mem = malloc (size);
    ticks_stop = get_ticks();

    printf ("bl_image_db malloc: %dk @ 0x%08x, %d usec\n", ((size + 512) / 1024), (unsigned long) mem,
            (unsigned int) ((((unsigned long long) (ticks_stop - ticks_start)) * 1000000) / get_tbclk()));

    return mem;
#else
    return malloc (size);
#endif
}

static unsigned char *find_available_kernel_buffer (void)
{
    static unsigned char *kernel_buffer[2];

    imagedb_entry_t *db_entry;

    if (kernel_buffer[0] == NULL) {
        kernel_buffer[0] = malloc_local (KERNEL_SIZE_MAX);
        return kernel_buffer[0];
    }
    if (kernel_buffer[1] == NULL) {
        kernel_buffer[1] = malloc_local (KERNEL_SIZE_MAX);
        return kernel_buffer[1];
    }

    /*
       Both kernel buffers have already been allocated.
       If there are no bootable image in the image database, then
       we can re-use either kernel buffer. If there IS a bootable
       image in the database, reuse which ever kernel buffer does
       not contain the kernel held in the images database...
    */
    if ((db_entry = image_db_find_bootable()) == NULL)
        return kernel_buffer[0];

    if (((unsigned char *) db_entry->mem_image) == kernel_buffer[0])
        return kernel_buffer[1];
    if (((unsigned char *) db_entry->mem_image) == kernel_buffer[1])
        return kernel_buffer[0];

    return NULL;
}

int image_db_scan_flash_nand (void)
{
    static const char *scannable_partitions[] =
    {
        "Kernel",
        "Filesystem",
#if defined (ENABLE_SHADOW_PARTITIONS_NAND)
        "Kernel2",
        "Filesystem2",
#endif
    };

    int i;
    char *part_name;
    unsigned char *membuf;

    if (NAND_INFO->name == NULL) {
        printf ("%s: NAND device not initialised ?!?\n", __FUNCTION__);
        return -1;
    }

    printf ("%s: %s, block size %luk\n", __FUNCTION__,
            NAND_INFO->name, (NAND_INFO->erasesize / 1024));

    for (i = 0; i < (sizeof(scannable_partitions) / sizeof(scannable_partitions[0])); i++) {
        part_name = (char *) scannable_partitions[i];
        if ((membuf = find_available_kernel_buffer()) == NULL)
            return -1;
        image_db_nand_scan (part_name, membuf, KERNEL_SIZE_MAX);
    }

    return 0;
}

#else

int image_db_scan_flash_nand (void)
{
    return 0;
}

#endif

/****************************************************************************
****************************************************************************/

