Hi all,

On my project I run into problems with overwriting of the flash caused by
runaway code. So I coded the assembly code that does it's best to avoid
that. For this I use local volatile variable which is initialized to 0xa5
(I believe it's safer to have it on stack - if it would be global it could
somehow stay on 0xa5 value and stack is much more random). If code somehow
jumps anywhere before that, software will take care of skipping the write
code. If it jumps after, this variable will not be set to 0xa5 and spm
instruction will be skipped. The final check is done just within those 4
cycles, so there no chance to jump somewhere in the middle here. I also use
__zero_reg__ to minimize chances that 'valid' value would be already in
register. The only possibility would now be that there is 0xa5 at exactly
the right position on stack.

I found this really useful, so I thought some might be interested. Maybe
this could even be included into avr/boot.h. If nothing else, at least to
make people reading the docs aware of this issue... It took me quite some
time to realize what is going wrong in my application, didn't even think
about such scenario before.

Regards,
Stefan
INLINE inline uint8_t boot_page_modify_checked (uint8_t spmcr_val, uint16_t address, uint8_t check)
{
#if defined(__AVR_ATmega161__) || defined(__AVR_ATmega163__) \
    || defined(__AVR_ATmega323__)
#error not implemented
#elif (FLASHEND > USHRT_MAX)
#error not implemented
#else
  uint8_t result;
  asm
  (
    "mov __zero_reg__, %1\n\t"
    "out %2, %3\n\t"
    "ldi %0, %4\n\t"
    "add %0, __zero_reg__\n\t"
    "brne .+2\n\t"
    "spm\n\t"
    "clr __zero_reg__\n\t"
    : "=d" (result)
    : "r" (check),
      "I" (_SFR_IO_ADDR(__SPM_REG)),
      "r" (spmcr_val),
      "M" ((uint8_t)-BOOT_SPM_CHECK_VAL),
      "z" (address)
  );
  return result;
#endif
}

INLINE inline uint8_t boot_page_erase_checked (uint16_t address, uint8_t check)
{
  return boot_page_modify_checked(__BOOT_PAGE_ERASE, address, check);
}

INLINE inline uint8_t boot_page_write_checked (uint16_t address, uint8_t check)
{
  return boot_page_modify_checked(__BOOT_PAGE_WRITE, address, check);
}
#define BOOT_SPM_CHECK_VAL 0xa5
uint8_t boot_page_erase_checked (uint16_t address, uint8_t check);
uint8_t boot_page_write_checked (uint16_t address, uint8_t check);

bool config_range(uintptr_t adr, uintptr_t len)
{
  extern uint8_t __config_start;
  extern uint8_t __config_end;
  uintptr_t first = adr;
  uintptr_t last  = adr + len - 1;
  return (uintptr_t)&__config_start <= first && first < (uintptr_t)&__config_end &&
         (uintptr_t)&__config_start <= last  && last  < (uintptr_t)&__config_end;
}

bool meta_range(uintptr_t adr, uintptr_t len)
{
  extern uint8_t __meta_start;
  extern uint8_t __meta_end;
  uintptr_t first = adr;
  uintptr_t last  = adr + len - 1;
  return (uintptr_t)&__meta_start <= first && first < (uintptr_t)&__meta_end &&
         (uintptr_t)&__meta_start <= last  && last  < (uintptr_t)&__meta_end;
}

bool writeallow_range(uintptr_t adr, uintptr_t len)
{
  extern uint8_t __fw_end;
  uintptr_t start = (uintptr_t)&__fw_end | (SPM_PAGESIZE - 1);
  extern uint8_t __flash_size;
  uintptr_t first = adr;
  uintptr_t last  = adr + len - 1;
  return start <= first && first < (uintptr_t)&__flash_size &&
         start <= last  && last  < (uintptr_t)&__flash_size;
}

__attribute__((section(".flash_write")))
void flash_write_block(uint8_t * buf, uintptr_t adr, uintptr_t len)
{
  /* try to save as from accidental jump somewhere into writing
   * code. However - this can never be a 100% solution, but 
   * chances should be greatly reduced. */
  volatile uint8_t check;
  
  check = BOOT_SPM_CHECK_VAL;

  if (config_range(adr, len) || writeallow_range(adr, len)) {
    uintptr_t start = adr;
    uintptr_t end   = adr + len;
    uintptr_t i = ~(SPM_PAGESIZE - 1) & start;
    uintptr_t e = i + SPM_PAGESIZE;
    uintptr_t start_a = start & ~0x1;
    uintptr_t end_a   = end   & ~0x1;
  
    DBG_ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      eeprom_busy_wait();
      boot_spm_busy_wait();
  
      bool change = 0;
  
      for (; i < start_a; i += 2) boot_page_fill(i, pgm_read_word(i));
      if (start & 0x1) {
        uint16_t dat_old = pgm_read_word(i);
        uint16_t dat = (dat_old & 0x00ff) | (*buf << 8);
        change |= dat != dat_old;
        buf++;
        boot_page_fill(i, dat);
        i += 2;
      }
      for (; i < end_a; i += 2) {
        uint16_t dat_old = pgm_read_word(i);
        uint16_t dat = *buf;
        buf++;
        dat |= *buf << 8;
        buf++;
        change |= dat != dat_old;
        boot_page_fill(i, dat);
      }
      if (end & 0x1) {
        uint16_t dat_old = pgm_read_word(i);
        uint16_t dat = (dat_old & 0xff00) | *buf;
        change |= dat != dat_old;
        buf++;
        boot_page_fill(i, dat);
        i += 2;
      }
      for (; i < e; i += 2) boot_page_fill(i, pgm_read_word(i));
      
      if (change) {
        assert(boot_page_erase_checked(adr, check) == 0);
        boot_spm_busy_wait();
        assert(boot_page_write_checked(adr, check) == 0);
        boot_spm_busy_wait();
      }
      boot_rww_enable();
    }
  }
  check = 0;
}


__attribute__((used)) NOINIT uint8_t flash_buf [SPM_PAGESIZE]; // for external usage with exexec

USED void flash_write(uint8_t * buf, uintptr_t adr, uintptr_t len)
{
  uintptr_t start_floor =  adr        &  (SPM_PAGESIZE - 1);
  uintptr_t end_floor   = (adr + len) & ~(SPM_PAGESIZE - 1);
  
  if (start_floor) {
    uintptr_t len_s = MIN(len, SPM_PAGESIZE - start_floor);
    flash_write_block(buf, adr, len_s);
    buf += len_s;
    adr += len_s;
    len -= len_s;
  }
  
  while (adr < end_floor) {
    flash_write_block(buf, adr, SPM_PAGESIZE);
    buf += SPM_PAGESIZE;
    adr += SPM_PAGESIZE;
    len -= SPM_PAGESIZE;
  }

  if (len) {
    flash_write_block(buf, adr, len);
    buf += len;
    adr += len;
    len -= len;
  }
}

USED void flash_read(uint8_t * buf, uintptr_t adr, uintptr_t len)
{
  if (config_range(adr, len) || meta_range(adr, len)) {
    uintptr_t end = adr + len;
    while (adr < end) {
      *buf = pgm_read_byte(adr);
      adr++;
      buf++;
    }
  }
}
_______________________________________________
AVR-libc-dev mailing list
AVR-libc-dev@nongnu.org
https://lists.nongnu.org/mailman/listinfo/avr-libc-dev

Reply via email to