From 572849ec2af20416c118248b0ce95d4bb70929be Mon Sep 17 00:00:00 2001
From: Stefan Rueger <stefan.rueger@urclocks.com>
Date: Thu, 21 Jul 2022 21:42:07 +0100
Subject: [PATCH] Provide avr_set_addr_mem() to set addresses in SPI opcodes
 within boundaries

The function avr_set_addr_mem(AVRMEM *mem, int opnum, unsigned char *cmd,
unsigned long addr) is meant to replace avr_set_addr(OPCODE *op, unsigned
char *cmd, unsigned long addr) in future.

avr_set_addr_mem() has more information about the context of the task in that
it knows the memory size, memory page size, whether or not the memory is a
flash memory (which gets words addressees supplied) and, crucially, knows
which SPI operation it is meant to compute the address bits for.

avr_set_addr_mem() first computes the interval of bit numbers that must be
supplied for the SPI command to stand a chance to work. The function only
sets those address bits that are needed. Once all avr_set_addr() function
calls have been replaced by avr_set_addr_mem(), the SPI commands that need an
address can afford to declare in avrdude.conf all 16 address bits in the
middle two bytes of the SPI command. This over-declaration will be corrected
during runtime by avr_set_addr_mem(). One consequence of this is that parts
can inherit smaller or larger memories from parents without the need to use
different SPI codes in avrdude.conf. Another consequence is that
avr_set_addr_mem() can, and does, tell the caller whether vital address bits
were not declared in the SPI opcode. During parsing of avrdude.conf this
might be utilised to generate a corresponding warning. This will uncover
problematic SPI codes in avrdude.conf that in the past went undetected.
---
 src/developer_opts.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
 src/developer_opts.h |  1 +
 2 files changed, 95 insertions(+)

diff --git a/src/developer_opts.c b/src/developer_opts.c
index cdc40a21..2f50eda6 100644
--- a/src/developer_opts.c
+++ b/src/developer_opts.c
@@ -295,6 +295,100 @@ static void checkaddr(int memsize, int pagesize, int opnum, OPCODE *op, AVRPART
 }
 
 
+/*
+ * avr_set_addr_mem()
+ *
+ * Set address bits in the specified command based on the memory, opcode and
+ * address; addr must be a word address for flash or, for all other memories,
+ * a byte address; returns 0 on success and -1 on error (no memory or no
+ * opcode) or, if positive, bn+1 where bn is bit number of the highest
+ * necessary bit that the opcode does not provide.
+ */
+int avr_set_addr_mem(AVRMEM *mem, int opnum, unsigned char *cmd, unsigned long addr) {
+  int ret, isflash, lo, hi, memsize, pagesize;
+  OPCODE *op;
+
+  if(!mem)
+    return -1;
+
+  if(!(op = mem->op[opnum]))
+    return -1;
+
+  isflash = !strcmp(mem->desc, "flash"); // ISP parts have only one flash-like memory
+  memsize = mem->size >> isflash;        // word addresses for flash
+  pagesize = mem->page_size >> isflash;
+
+  // compute range lo..hi of needed address bits
+  switch(opnum) {
+  case AVR_OP_READ:
+  case AVR_OP_WRITE:
+  case AVR_OP_READ_LO:
+  case AVR_OP_READ_HI:
+  case AVR_OP_WRITE_LO:
+  case AVR_OP_WRITE_HI:
+    lo = 0;
+    hi = intlog2(memsize-1);    // memsize = 1 implies no addr bit is needed
+    break;
+
+  case AVR_OP_LOADPAGE_LO:
+  case AVR_OP_LOADPAGE_HI:
+    lo = 0;
+    hi = intlog2(pagesize-1);
+    break;
+
+  case AVR_OP_LOAD_EXT_ADDR:
+    lo = 16;
+    hi = intlog2(memsize-1);
+    break;
+
+  case AVR_OP_WRITEPAGE:
+    lo = intlog2(pagesize);
+    hi = intlog2(memsize-1);
+    break;
+
+  case AVR_OP_CHIP_ERASE:
+  case AVR_OP_PGM_ENABLE:
+  default:
+    lo = 0;
+    hi = -1;
+    break;
+  }
+
+  // Unless it's load extended address, ISP chips only deal with 16 bit addresses
+  if(opnum != AVR_OP_LOAD_EXT_ADDR && hi > 15)
+    hi = 15;
+
+  unsigned char avail[32];
+  memset(avail, 0, sizeof avail);
+
+  for(int i=0; i<32; i++) {
+    if(op->bit[i].type == AVR_CMDBIT_ADDRESS) {
+      int bitno, j, bit;
+      unsigned char mask;
+
+      bitno = op->bit[i].bitno & 31;
+      j = 3 - i / 8;
+      bit = i % 8;
+      mask = 1 << bit;
+      avail[bitno] = 1;
+
+      // 'a' bit with number outside bit range [lo, hi] is set to 0
+      if (bitno >= lo && bitno <= hi? (addr >> bitno) & 1: 0)
+        cmd[j] = cmd[j] | mask;
+      else
+        cmd[j] = cmd[j] & ~mask;
+    }
+  }
+
+  ret = 0;
+  if(lo >= 0 && hi < 32 && lo <= hi)
+    for(int bn=lo; bn <= hi; bn++)
+      if(!avail[bn])            // necessary bit bn misses in opcode
+        ret = bn+1;
+
+  return ret;
+}
+
 static char *dev_sprintf(const char *fmt, ...) {
   int size = 0;
   char *p = NULL;
diff --git a/src/developer_opts.h b/src/developer_opts.h
index 00c13005..d112f517 100644
--- a/src/developer_opts.h
+++ b/src/developer_opts.h
@@ -25,6 +25,7 @@ const char *opcodename(int opnum);
 char *opcode2str(OPCODE *op, int opnum, int detailed);
 int opcodecmp(OPCODE *op1, OPCODE *op2, int opnum);
 int intlog2(unsigned int n);
+int avr_set_addr_mem(AVRMEM *mem, int opnum, unsigned char *cmd, unsigned long addr);
 int part_match(const char *pattern, const char *string);
 void dev_output_part_defs(char *partdesc);