/*
 * avrdude - A Downloader/Uploader for AVR device programmers
 * Copyright (C) 2022 Stefan Rueger <stefan.rueger@urclocks.com>
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include <ac_cfg.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "avrdude.h"
#include "libavrdude.h"

/*
 * Provides an API for cached bytewise access
 *
 * int avr_read_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const
 *   AVRMEM *mem, unsigned long addr, unsigned char *value);
 *
 * int avr_write_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const
 *  AVRMEM *mem, unsigned long addr, unsigned char data);
 *
 * int avr_flush_cache(const PROGRAMMER *pgm, const AVRPART *p);
 *
 * int avr_chip_erase_cached(const PROGRAMMER *pgm, const AVRPART *p);
 *
 * int avr_page_erase_cached(const PROGRAMMER *pgm, const AVRPART *p, const
 *  AVRMEM *mem, unsigned int baseaddr);
 *
 * int avr_reset_cache(const PROGRAMMER *pgm, const AVRPART *p);
 *
 * avr_read_byte_cached() and avr_write_byte_cached() use a cache if paged
 * routines are available and if the device memory is flash, EEPROM, bootrow
 * or usersig. The AVRXMEGA memories application, apptable and boot are
 * subsumed under flash. Userrow is subsumed under usersig provided
 * avrdude.conf has a memory alias from usersig to userrow. In all other
 * cases the cached read/write functions fall back to pgm->read_byte() and
 * pgm->write_byte(), respectively. Bytewise cached read always gets its data
 * from the cache, possibly after reading a page from the device memory.
 * Bytewise cached write with an address in memory range only ever modifies
 * the cache. Any modifications are written to the device after calling
 * avr_flush_cache() or when attempting to read or write from a location
 * outside the address range of the device memory.
 *
 * avr_flush_cache() synchronises pending writes to flash, EEPROM, bootrow
 * and usersig with the device. With some programmer and part combinations,
 * flash (and sometimes EEPROM, too) looks like a NOR memory, ie, a write can
 * only clear bits, never set them. For NOR memories a page erase or, if not
 * available, a chip erase needs to be issued before writing arbitrary data.
 * Usersig is generally unaffected by a chip erase, so will always need a
 * page erase. When a memory looks like a NOR memory, either page erase is
 * deployed (eg, with parts that have PDI/UPDI interfaces), or if that is not
 * available, both EEPROM and flash caches are fully read in, a
 * pgm->chip_erase() command is issued and both EEPROM and flash are written
 * back to the device. Hence, it can take minutes to ensure that a single
 * previously cleared bit is set and, therefore, this routine should be
 * called sparingly.
 *
 * avr_chip_erase_cached() erases the chip and discards pending writes() to
 * flash or EEPROM. It presets the flash cache to all 0xff alleviating the
 * need to read from the device flash. However, if the programmer serves
 * bootloaders, recognised by is_spm(pgm), then the flash cache is reset
 * instead, necessitating flash memory be fetched from the device on first
 * read; the reason for this is that bootloaders emulate chip erase and they
 * won't overwrite themselves (some bootloaders, eg, optiboot ignore chip
 * erase commands) making it truly unknowable what the flash contents on
 * device is after a chip erase.
 *
 * For EEPROM avr_chip_erase_cached() concludes that it has been deleted if a
 * previously cached EEPROM page that contained cleared bits now no longer
 * has these clear bits on the device. Only with this evidence is the EEPROM
 * cache preset to all 0xff otherwise the cache discards all pending writes
 * to EEPROM and is left unchanged otherwise. avr_chip_erase_cached() does not
 * affect the usersig cache.
 *
 * The avr_page_erase_cached() function erases a page and synchronises it
 * with the cache.
 *
 * Finally, avr_reset_cache() resets the cache without synchronising pending
 * writes() to the device.
 *
 * This file also holds the following utility functions
 *
 * // Does the programmer/memory combo have paged memory access?
 * int avr_has_paged_access(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem);
 * int avr_has_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem);
 * int avr_has_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem);
 *
 * // Read the page containing addr from the device into buf
 * int avr_read_page_default(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, int addr, unsigned char *buf);
 *
 * // Write the data page to the device into the page containing addr
 * int avr_write_page_default(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, int addr, unsigned char *data);
 *
 * // Could memory region s1 be the result of a NOR-memory copy of s3 onto s2?
 * int avr_is_and(const unsigned char *s1, const unsigned char *s2, const unsigned char *s3, size_t n);
 *
 */

/*
 * Paged access?
 *  - Programmer must have paged routines
 *  - Memory has positive page size, which is a power of two
 *  - Memory has positive size, which is a multiple of the page size
 *  - Memory is flash, EEPROM, bootrow or usersig type
 *
 * Note that in this definition the page size can be 1
 */

int avr_has_paged_access(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem) {
  return pgm->paged_load && pgm->paged_write &&
    mem->page_size > 0 && (mem->page_size & (mem->page_size - 1)) == 0 &&
    mem->size > 0 && mem->size%mem->page_size == 0 && mem_is_paged_type(mem) && !(p && avr_mem_exclude(pgm, p, mem));
}

int avr_has_paged_write(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem) {
  return pgm->paged_write &&
    mem->page_size > 0 && (mem->page_size & (mem->page_size - 1)) == 0 &&
    mem->size > 0 && mem->size%mem->page_size == 0 && mem_is_paged_type(mem) && !(p && avr_mem_exclude(pgm, p, mem));
}

int avr_has_paged_load(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem) {
  return pgm->paged_load &&
    mem->page_size > 0 && (mem->page_size & (mem->page_size - 1)) == 0 &&
    mem->size > 0 && mem->size%mem->page_size == 0 && mem_is_paged_type(mem) && !(p && avr_mem_exclude(pgm, p, mem));
}

static int read_byte_error(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned long addr, uint8_t *value) {
  pmsg_error("pgm->read_byte must not be pointing to avr_read_byte_cached()\n");
  return -1;
}

static int write_byte_error(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned long addr, uint8_t data) {
  pmsg_error("pgm->write_byte must not be pointing to avr_write_byte_cached()\n");
  return -1;
}

// Sanity only: guard against a user setting pgm->xyz_byte to avr_xyz_read_byte_cached (AVRDUDE doesn't)
#define fallback_read_byte (pgm->read_byte != avr_read_byte_cached? led_read_byte: read_byte_error)
#define fallback_write_byte (pgm->write_byte != avr_write_byte_cached? led_write_byte: write_byte_error)
#define fallback_update_byte (pgm->write_byte != avr_write_byte_cached? led_update_byte: write_byte_error)

/*
 * Read the page containing addr from the device into buf
 *   - Caller to ensure buf has mem->page_size bytes
 *   - Part memory buffer mem is unaffected by this (though temporarily changed)
 *   - Uses read_byte() if memory page size is one, otherwise paged_load()
 *   - Fall back to bytewise read if paged_load() returned an error
 *   - On failure returns a negative value, on success a non-negative value, which is either
 *       + The number of bytes read by pgm->paged_load() if that succeeded
 *       + LIBAVRDUDE_SUCCESS (0) if the fallback of bytewise read succeeded
 */
int avr_read_page_default(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, int addr, unsigned char *buf) {
  if(!avr_has_paged_load(pgm, p, mem) || addr < 0 || addr >= mem->size)
    return LIBAVRDUDE_GENERAL_FAILURE;

  int rc, pgsize = mem->page_size, base = addr & ~(pgsize - 1);

  if(pgsize == 1)
    return fallback_read_byte(pgm, p, mem, addr, buf);

  led_clr(pgm, LED_ERR);
  led_set(pgm, LED_PGM);

  unsigned char *pagecopy = mmt_malloc(pgsize);

  memcpy(pagecopy, mem->buf + base, pgsize);
  if((rc = pgm->paged_load(pgm, p, mem, pgsize, base, pgsize)) >= 0)
    memcpy(buf, mem->buf + base, pgsize);
  memcpy(mem->buf + base, pagecopy, pgsize);

  if(rc < 0 && pgm->read_byte != avr_read_byte_cached) {
    rc = LIBAVRDUDE_SUCCESS;
    for(int i = 0; i < pgsize; i++) {
      if(pgm->read_byte(pgm, p, mem, base + i, pagecopy + i) < 0) {
        rc = LIBAVRDUDE_GENERAL_FAILURE;
        break;
      }
    }
    if(rc == LIBAVRDUDE_SUCCESS)
      memcpy(buf, pagecopy, pgsize);
  }
  mmt_free(pagecopy);

  if(rc < 0)
    led_set(pgm, LED_ERR);
  led_clr(pgm, LED_PGM);

  return rc;
}

/*
 * Write the data page to the device into the page containing addr
 *   - Caller to provide all mem->page_size bytes incl padding if any
 *   - Part memory buffer mem is unaffected by this (though temporarily changed)
 *   - Uses write_byte() if memory page size is one, otherwise paged_write()
 */
int avr_write_page_default(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, int addr, unsigned char *data) {
  if(!avr_has_paged_write(pgm, p, mem) || addr < 0 || addr >= mem->size)
    return LIBAVRDUDE_GENERAL_FAILURE;

  int rc, pgsize = mem->page_size, base = addr & ~(pgsize - 1);

  if(pgsize == 1)
    return fallback_write_byte(pgm, p, mem, addr, *data);

  unsigned char *pagecopy = mmt_malloc(pgsize);

  memcpy(pagecopy, mem->buf + base, pgsize);
  memcpy(mem->buf + base, data, pgsize);
  rc = pgm->paged_write(pgm, p, mem, pgsize, base, pgsize);
  memcpy(mem->buf + base, pagecopy, pgsize);
  mmt_free(pagecopy);

  return rc;
}

// Could memory region s1 be the result of a NOR-memory copy of s3 onto s2?
int avr_is_and(const unsigned char *s1, const unsigned char *s2, const unsigned char *s3, size_t n) {
  while(n--)
    if(*s1++ != (*s2++ & *s3++))
      return 0;

  return 1;
}

static int cacheAddress(int addr, const AVR_Cache *cp, const AVRMEM *mem) {
  int cacheaddr = addr + (int) (mem->offset - cp->offset);

  if(cacheaddr < 0 || cacheaddr >= cp->size) {  // Should never happen (unless offsets wrong in avrdude.conf)
    pmsg_error("%s cache address 0x%04x out of range [0, 0x%04x]\n", mem->desc, cacheaddr, cp->size - 1);
    return LIBAVRDUDE_GENERAL_FAILURE;
  }

  if(mem->page_size != cp->page_size) { // Should never happen (unless incompatible page sizes in avrdude.conf)
    pmsg_error("%s page size %d incompatible with cache page size %d\n", mem->desc, mem->page_size, cp->page_size);
    return LIBAVRDUDE_GENERAL_FAILURE;
  }

  return cacheaddr;
}

static int loadCachePage(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
  int addr, int cacheaddr, int nlOnErr) {

  int pgno = cacheaddr/cp->page_size;

  if(!cp->iscached[pgno]) {
    // Read cached section from device
    int cachebase = cacheaddr & ~(cp->page_size - 1);

    if(avr_read_page_default(pgm, p, mem, addr & ~(cp->page_size - 1), cp->cont + cachebase) < 0) {
      report_progress(1, -1, NULL);
      if(nlOnErr && quell_progress)
        msg_info("\n");
      pmsg_error("unable to read %s page at addr 0x%04x\n", mem->desc, addr);
      return LIBAVRDUDE_GENERAL_FAILURE;
    }
    // Copy last read device page, so we can later check for changes
    memcpy(cp->copy + cachebase, cp->cont + cachebase, cp->page_size);
    cp->iscached[pgno] = 1;
  }

  return LIBAVRDUDE_SUCCESS;
}

static int initCache(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p) {
  AVRMEM *basemem = cp == pgm->cp_flash? avr_locate_flash(p): cp == pgm->cp_eeprom? avr_locate_eeprom(p):
    cp == pgm->cp_bootrow? avr_locate_bootrow(p): avr_locate_usersig(p);

  if(!basemem || !avr_has_paged_access(pgm, p, basemem))
    return LIBAVRDUDE_GENERAL_FAILURE;

  cp->size = basemem->size;
  cp->page_size = basemem->page_size;
  cp->offset = basemem->offset;
  cp->cont = mmt_malloc(cp->size);
  cp->copy = mmt_malloc(cp->size);
  cp->iscached = mmt_malloc(cp->size/cp->page_size);

  if(is_spm(pgm) && mem_is_in_flash(basemem)) {  // Could be vector bootloader
    // Caching the vector page hands over to the progammer that then can patch the reset vector
    if(loadCachePage(cp, pgm, p, basemem, 0, 0, 0) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;
  }

  return LIBAVRDUDE_SUCCESS;
}

static int writeCachePage(AVR_Cache *cp, const PROGRAMMER *pgm, const AVRPART *p,
  const AVRMEM *mem, int base, int nlOnErr) {

  led_clr(pgm, LED_ERR);
  led_set(pgm, LED_PGM);
  // Write modified page cont to device; if unsuccessful try bytewise access
  if(avr_write_page_default(pgm, p, mem, base, cp->cont + base) < 0) {
    if(pgm->read_byte != avr_read_byte_cached && pgm->write_byte != avr_write_byte_cached) {
      for(int i = 0; i < cp->page_size; i++)
        if(cp->cont[base + i] != cp->copy[base + i])
          if(pgm->write_byte(pgm, p, mem, base + i, cp->cont[base + i]) < 0 ||
            pgm->read_byte(pgm, p, mem, base + i, cp->copy + base + i) < 0) {
            report_progress(1, -1, NULL);
            if(nlOnErr && quell_progress)
              msg_info("\n");
            pmsg_error("%s access error at addr 0x%04x\n", mem->desc, base + i);
            goto error;
          }

      goto success;             // Bytewise writes & reads successful
    }
    report_progress(1, -1, NULL);
    if(nlOnErr && quell_progress)
      msg_info("\n");
    pmsg_error("write %s page error at addr 0x%04x\n", mem->desc, base);
    goto error;
  }
  // Read page back from device and update copy to what is on device
  if(avr_read_page_default(pgm, p, mem, base, cp->copy + base) < 0) {
    report_progress(1, -1, NULL);
    if(nlOnErr && quell_progress)
      msg_info("\n");
    pmsg_error("unable to read %s page at addr 0x%04x\n", mem->desc, base);
    goto error;
  }

success:
  led_clr(pgm, LED_PGM);
  return LIBAVRDUDE_SUCCESS;

error:
  led_set(pgm, LED_ERR);
  led_clr(pgm, LED_PGM);
  return LIBAVRDUDE_GENERAL_FAILURE;
}

// A coarse guess where any bootloader might start (prob underestimates the start)
static int guessBootStart(const PROGRAMMER *pgm, const AVRPART *p) {
  int bootstart = 0;
  const AVR_Cache *cp = pgm->cp_flash;

  if(is_updi(p))   // Modern AVRs put the bootloader at 0
    return 0;

  if(p->n_boot_sections > 0 && p->boot_section_size > 0)
    bootstart = cp->size - (p->boot_section_size << (p->n_boot_sections - 1));

  if(bootstart <= cp->size/2 || bootstart >= cp->size)
    bootstart = cp->size > 32768? cp->size - 16384: cp->size*3/4;

  return bootstart & ~(cp->page_size - 1);
}

// Page erase but without error messages if it does not work
static int silent_page_erase(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *m, unsigned int a) {
  int bakverb = verbose;

  verbose = -123;
  int ret = pgm->page_erase? pgm->page_erase(pgm, p, m, a): -1;

  verbose = bakverb;

  return ret;
}

typedef struct {
  AVRMEM *mem;
  AVR_Cache *cp;
  int isflash, iseeprom, zopaddr, pgerase;
} Cache_desc;

// Write flash, EEPROM, bootrow and usersig caches to device and free them
int avr_flush_cache(const PROGRAMMER *pgm, const AVRPART *p) {
  Cache_desc mems[] = {
    {avr_locate_flash(p), pgm->cp_flash, 1, 0, -1, 0},
    {avr_locate_eeprom(p), pgm->cp_eeprom, 0, 1, -1, 0},
    {avr_locate_bootrow(p), pgm->cp_bootrow, 0, 0, -1, 0},
    {avr_locate_usersig(p), pgm->cp_usersig, 0, 0, -1, 0},
  };

  int chpages = 0;
  bool chiperase = 0;

  // Count page changes and find a page that needs a clear bit set
  for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
    if(mems[i].mem && avr_mem_exclude(pgm, p, mems[i].mem))     // Zap mem if excluded combo
      memset(mems + i, 0, sizeof *mems);

    AVRMEM *mem = mems[i].mem;
    AVR_Cache *cp = mems[i].cp;

    if(!mem || !cp->cont)
      continue;

    for(int pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size) {
      if(cp->iscached[pgno])
        if(memcmp(cp->copy + n, cp->cont + n, cp->page_size)) {
          chpages++;
          if(mems[i].zopaddr == -1 && !avr_is_and(cp->cont + n, cp->copy + n, cp->cont + n, cp->page_size))
            mems[i].zopaddr = n;
        }
    }
  }

  if(!chpages)
    return LIBAVRDUDE_SUCCESS;

  pmsg_info("synching cache to device ... ");
  fflush(stderr);

  // Check whether page erase needed and working and whether chip erase needed
  for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
    AVRMEM *mem = mems[i].mem;
    AVR_Cache *cp = mems[i].cp;

    if(!mem)
      continue;

    if(!cp->cont)               // Ensure cache is initialised from now on
      if(initCache(cp, pgm, p) < 0) {
        if(quell_progress)
          msg_info("\n");
        pmsg_error("unable to initialise the cache\n");
        return LIBAVRDUDE_GENERAL_FAILURE;
      }

    if(chiperase || mems[i].zopaddr < 0)
      continue;

    int n = mems[i].zopaddr;

    if(writeCachePage(cp, pgm, p, mem, n, 1) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;
    // Same? OK, can set cleared bit to one, "normal" memory
    if(!memcmp(cp->copy + n, cp->cont + n, cp->page_size)) {
      chpages--;
      continue;
    }
    // Probably NOR memory, check out page erase
    if(silent_page_erase(pgm, p, mem, n) >= 0) {
      if(writeCachePage(cp, pgm, p, mem, n, 1) < 0)
        return LIBAVRDUDE_GENERAL_FAILURE;
      // Worked OK? Can use page erase on this memory
      if(!memcmp(cp->copy + n, cp->cont + n, cp->page_size)) {
        mems[i].pgerase = 1;
        chpages--;
        continue;
      }
    }

    if(!mem_is_usersig(mems[i].mem))    // CE does not erase usersig
      chiperase = 1;
  }

  if(!chpages) {
    msg_info("done\n");
    return LIBAVRDUDE_SUCCESS;
  }

  if(chiperase) {
    if(quell_progress) {
      msg_info("reading/chip erase/writing cycle needed ... ");
      fflush(stderr);
    }

    int nrd = 0;

    // Count read operations needed
    for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
      AVRMEM *mem = mems[i].mem;
      AVR_Cache *cp = mems[i].cp;

      if(!mem)
        continue;
      if(mem_is_usersig(mem))   // CE does not affect usersig/userrow
        continue;

      for(int pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size)
        if(!cp->iscached[pgno])
          nrd++;
    }

    report_progress(0, 1, "Reading");
    if(nrd) {
      // Read full flash and EEPROM
      for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
        AVRMEM *mem = mems[i].mem;
        AVR_Cache *cp = mems[i].cp;

        if(!mem)
          continue;
        if(mem_is_usersig(mem)) // CE does not affect usersig/userrow
          continue;

        for(int ird = 0, pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size) {
          if(!cp->iscached[pgno]) {
            report_progress(ird++, nrd, NULL);
            if(loadCachePage(cp, pgm, p, mem, n, n, 1) < 0)
              return LIBAVRDUDE_GENERAL_FAILURE;
          }
        }
      }
    }
    report_progress(1, 0, NULL);

    report_progress(0, 1, "Erasing");
    if(avr_chip_erase(pgm, p) < 0) {
      report_progress(1, -1, NULL);
      if(quell_progress)
        msg_info("\n");
      pmsg_error("chip erase failed\n");
      return LIBAVRDUDE_GENERAL_FAILURE;
    }
    // Update cache copies after chip erase so that writing back is efficient
    for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
      AVRMEM *mem = mems[i].mem;
      AVR_Cache *cp = mems[i].cp;

      if(!mem)
        continue;
      if(mem_is_usersig(mem))   // CE does not affect usersig/userrow
        continue;

      if(mems[i].isflash) {
        memset(cp->copy, 0xff, cp->size); // Record device memory as erased
        if(is_spm(pgm)) {       // Bootloaders will not overwrite themselves
          // Read back generously estimated bootloader section to avoid verification errors
          int bootstart = guessBootStart(pgm, p);
          int nbo = (cp->size - bootstart)/cp->page_size;

          for(int ibo = 0, n = bootstart; n < cp->size; n += cp->page_size) {
            report_progress(1 + ibo++, nbo + 2, NULL);
            if(avr_read_page_default(pgm, p, mem, n, cp->copy + n) < 0) {
              report_progress(1, -1, NULL);
              if(quell_progress)
                msg_info("\n");
              pmsg_error("flash read failed at addr 0x%04x\n", n);
              return LIBAVRDUDE_GENERAL_FAILURE;
            }
          }
        }
      } else if(mems[i].iseeprom) {
        // Don't know whether chip erase has zapped EEPROM
        for(int n = 0; n < cp->size; n += cp->page_size) {
          if(!is_memset(cp->copy + n, 0xff, cp->page_size)) { // First page that had EEPROM data
            if(avr_read_page_default(pgm, p, mem, n, cp->copy + n) < 0) {
              report_progress(1, -1, NULL);
              if(quell_progress)
                msg_info("\n");
              pmsg_error("EEPROM read failed at addr 0x%04x\n", n);
              return LIBAVRDUDE_GENERAL_FAILURE;
            }
            // EEPROM zapped by chip erase? Set all of copy to 0xff
            if(is_memset(cp->copy + n, 0xff, cp->page_size))
              memset(cp->copy, 0xff, cp->size);
            break;
          }
        }
      }
    }
    report_progress(1, 0, NULL);
  }

  int nwr = 0;

  // Count number of writes
  for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
    AVRMEM *mem = mems[i].mem;
    AVR_Cache *cp = mems[i].cp;

    if(!mem)
      continue;

    for(int pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size)
      if(cp->iscached[pgno] && memcmp(cp->copy + n, cp->cont + n, cp->page_size))
        nwr++;
  }

  report_progress(0, 1, "Writing");
  if(nwr) {
    // Write all modified pages to the device
    for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
      AVRMEM *mem = mems[i].mem;
      AVR_Cache *cp = mems[i].cp;

      if(!mem || !cp->cont)
        continue;

      for(int iwr = 0, pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size) {
        if(cp->iscached[pgno] && memcmp(cp->copy + n, cp->cont + n, cp->page_size)) {
          if(!chiperase && mems[i].pgerase && pgm->page_erase)
            led_page_erase(pgm, p, mem, n);
          if(writeCachePage(cp, pgm, p, mem, n, 1) < 0)
            return LIBAVRDUDE_GENERAL_FAILURE;
          if(memcmp(cp->copy + n, cp->cont + n, cp->page_size)) {
            report_progress(1, -1, NULL);
            if(quell_progress)
              msg_info("\n");
            pmsg_error("verification mismatch at %s page addr 0x%04x\n", mem->desc, n);
            return LIBAVRDUDE_GENERAL_FAILURE;
          }
          report_progress(iwr++, nwr, NULL);
        }
      }
    }
  }
  report_progress(1, 0, NULL);

  msg_info(quell_progress? "done\n": "\n");
  return LIBAVRDUDE_SUCCESS;
}

/*
 * Read byte via a read/write cache
 *  - Used if paged routines available and if memory is flash, EEPROM, bootrow or usersig
 *  - Otherwise fall back to pgm->read_byte()
 *  - Out of memory addr: synchronise cache and, if successful, pretend reading a zero
 *  - Cache is automagically created and initialised if needed
 */
int avr_read_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
  unsigned long addr, unsigned char *value) {

  // Use pgm->read_byte() if not flash/EEPROM/bootrow/usersig or no paged access
  if(!avr_has_paged_access(pgm, p, mem))
    return fallback_read_byte(pgm, p, mem, addr, value);

  // If address is out of range synchronise cache and, if successful, pretend reading a zero
  if(addr >= (unsigned long) mem->size) {
    if(avr_flush_cache(pgm, p) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;
    *value = 0;
    return LIBAVRDUDE_SUCCESS;
  }

  AVR_Cache *cp = mem_is_eeprom(mem)? pgm->cp_eeprom: mem_is_in_flash(mem)? pgm->cp_flash:
    mem_is_bootrow(mem)? pgm->cp_bootrow: pgm->cp_usersig;

  if(!cp->cont)                 // Init cache if needed
    if(initCache(cp, pgm, p) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;

  int cacheaddr = cacheAddress((int) addr, cp, mem);

  if(cacheaddr < 0)
    return LIBAVRDUDE_GENERAL_FAILURE;

  // Ensure cache page is there
  if(loadCachePage(cp, pgm, p, mem, addr, cacheaddr, 0) < 0)
    return LIBAVRDUDE_GENERAL_FAILURE;

  *value = cp->cont[cacheaddr];

  return LIBAVRDUDE_SUCCESS;
}

/*
 * Write byte via a read/write cache
 *  - Used if paged routines available and if memory is flash, EEPROM, bootrow or usersig
 *  - Otherwise fall back to pgm->write_byte()
 *  - Out of memory addr: synchronise cache with device and return whether successful
 *  - If programmer indicates a readonly spot, return LIBAVRDUDE_SOFTFAIL
 *  - Cache is automagically created and initialised if needed
 */
int avr_write_byte_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem,
  unsigned long addr, unsigned char data) {

  // Use pgm->write_byte() if not flash/EEPROM/bootrow/usersig or no paged access
  if(!avr_has_paged_access(pgm, p, mem))
    return fallback_update_byte(pgm, p, mem, addr, data);

  // If address is out of range synchronise caches with device and return whether successful
  if(addr >= (unsigned long) mem->size)
    return avr_flush_cache(pgm, p);

  AVR_Cache *cp = mem_is_eeprom(mem)? pgm->cp_eeprom: mem_is_in_flash(mem)? pgm->cp_flash:
    mem_is_bootrow(mem)? pgm->cp_bootrow: pgm->cp_usersig;

  if(!cp->cont)                 // Init cache if needed
    if(initCache(cp, pgm, p) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;

  int cacheaddr = cacheAddress((int) addr, cp, mem);

  if(cacheaddr < 0)
    return LIBAVRDUDE_GENERAL_FAILURE;

  // Ensure cache page is there
  if(loadCachePage(cp, pgm, p, mem, addr, cacheaddr, 0) < 0)
    return LIBAVRDUDE_GENERAL_FAILURE;

  if(cp->cont[cacheaddr] == data)
    return LIBAVRDUDE_SUCCESS;

  if(pgm->readonly && pgm->readonly(pgm, p, mem, addr))
    return LIBAVRDUDE_SOFTFAIL;

  cp->cont[cacheaddr] = data;

  return LIBAVRDUDE_SUCCESS;
}

// Erase the chip and set the cache accordingly
int avr_chip_erase_cached(const PROGRAMMER *pgm, const AVRPART *p) {
  Cache_desc mems[] = {
    {avr_locate_flash(p), pgm->cp_flash, 1, 0, -1, 0},
    {avr_locate_eeprom(p), pgm->cp_eeprom, 0, 1, -1, 0},
    {avr_locate_bootrow(p), pgm->cp_bootrow, 0, 0, -1, 0},
    // usersig is unaffected by CE
  };
  int rc;

  if((rc = led_chip_erase(pgm, p)) < 0)
    return rc;

  for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
    AVRMEM *mem = mems[i].mem;
    AVR_Cache *cp = mems[i].cp;

    if(!mem || !avr_has_paged_access(pgm, p, mem))
      continue;

    if(!cp->cont)               // Init cache if needed
      if(initCache(cp, pgm, p) < 0)
        return LIBAVRDUDE_GENERAL_FAILURE;

    if(mems[i].isflash) {
      if(is_spm(pgm)) {         // Reset cache to unknown
        memset(cp->iscached, 0, cp->size/cp->page_size);
      } else {                  // Preset all pages as erased
        memset(cp->copy, 0xff, cp->size);
        memset(cp->cont, 0xff, cp->size);
        memset(cp->iscached, 1, cp->size/cp->page_size);
      }
    } else {                    // Test whether cached EEPROM/bootrow pages were zapped
      bool erased = 0;

      for(int pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size) {
        if(cp->iscached[pgno]) {
          if(!is_memset(cp->copy + n, 0xff, cp->page_size)) { // Page has data?
            if(avr_read_page_default(pgm, p, mem, n, cp->copy + n) < 0)
              return LIBAVRDUDE_GENERAL_FAILURE;
            erased = is_memset(cp->copy + n, 0xff, cp->page_size);
            break;
          }
        }
      }
      if(erased) {              // Memory was erased, set cache correspondingly
        memset(cp->copy, 0xff, cp->size);
        memset(cp->cont, 0xff, cp->size);
        memset(cp->iscached, 1, cp->size/cp->page_size);
      } else {                  // Discard previous writes but leave cache
        for(int pgno = 0, n = 0; n < cp->size; pgno++, n += cp->page_size)
          if(cp->iscached[pgno])
            memcpy(cp->cont + n, cp->copy + n, cp->page_size);
      }
    }
  }

  return LIBAVRDUDE_SUCCESS;
}

// Erase a page and synchronise it with the cache
int avr_page_erase_cached(const PROGRAMMER *pgm, const AVRPART *p, const AVRMEM *mem, unsigned int uaddr) {

  int addr = uaddr;

  if(!avr_has_paged_access(pgm, p, mem) || addr < 0 || addr >= mem->size)
    return LIBAVRDUDE_GENERAL_FAILURE;

  if(mem->page_size == 1) {
    if(fallback_write_byte(pgm, p, mem, uaddr, 0xff) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;
  } else {
    if(silent_page_erase(pgm, p, mem, uaddr) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;
  }

  AVR_Cache *cp = mem_is_eeprom(mem)? pgm->cp_eeprom: mem_is_in_flash(mem)? pgm->cp_flash:
    mem_is_bootrow(mem)? pgm->cp_bootrow: pgm->cp_usersig;

  if(!cp->cont)                 // Init cache if needed
    if(initCache(cp, pgm, p) < 0)
      return LIBAVRDUDE_GENERAL_FAILURE;

  int cacheaddr = cacheAddress(addr, cp, mem);

  if(cacheaddr < 0)
    return LIBAVRDUDE_GENERAL_FAILURE;

  // Invalidate this cache page and read back, ie, we don't trust the page_erase() routine
  cp->iscached[cacheaddr/cp->page_size] = 0;

  // Reload cache page
  if(loadCachePage(cp, pgm, p, mem, (int) addr, cacheaddr, 0) < 0)
    return LIBAVRDUDE_GENERAL_FAILURE;

  if(!is_memset(cp->cont + (cacheaddr & ~(cp->page_size - 1)), 0xff, cp->page_size))
    return LIBAVRDUDE_GENERAL_FAILURE;

  return LIBAVRDUDE_SUCCESS;
}

// Free cache(s) discarding any pending writes
int avr_reset_cache(const PROGRAMMER *pgm, const AVRPART *p_unused) {
  AVR_Cache *mems[] = { pgm->cp_flash, pgm->cp_eeprom, pgm->cp_bootrow, pgm->cp_usersig };

  for(size_t i = 0; i < sizeof mems/sizeof *mems; i++) {
    AVR_Cache *cp = mems[i];

    if(cp->cont)
      mmt_free(cp->cont);
    if(cp->copy)
      mmt_free(cp->copy);
    if(cp->iscached)
      mmt_free(cp->iscached);
    memset(cp, 0, sizeof *cp);
  }

  return LIBAVRDUDE_SUCCESS;
}
