Browse Source

sysvar: transfer configuration from esp32 to rv32

System variables can now be transferred from ESP32 to the FPGA rv32,
allowing for a common configuration interface.
H. Peter Anvin 1 year ago
parent
commit
30f99669a2

+ 1 - 0
common/.gitignore

@@ -0,0 +1 @@
+*_gen.?

+ 14 - 0
common/Makefile

@@ -0,0 +1,14 @@
+MAKEFLAGS    += -R -r
+
+PERL	      = perl
+GENFILES      = sysvars_gen.c sysvars_gen.h
+
+all: $(GENFILES)
+
+%_gen.c %_gen.h: %.vars ../tools/sysvars.pl
+	$(PERL) ../tools/sysvars.pl $< $*_gen.h $*_gen.c
+
+clean:
+	rm -rf $(GENFILES)
+
+spotless: clean

+ 8 - 0
common/esplink.h

@@ -61,6 +61,12 @@ struct esplink_ota {
     uint32_t len;
 };
 
+#define ESPLINK_CONFIG_BUFSIZE 16384
+struct esplink_configbuf {
+    volatile void *buf;	       /* Buffer for configuration and data */
+    size_t buflen;	       /* Size of buffer */
+};
+
 #define ESPLINK_HEAD_MAGIC	0x3648dec4
 #define MAX_SIGNATURE_LEN	64
 
@@ -81,6 +87,7 @@ struct esplink_head {
 
     volatile struct esplink_timesync *tsync;
     volatile struct esplink_ota *ota;
+    struct esplink_configbuf cfg;
 
     struct esplink_ringbuf_head {
 	uint32_t count;
@@ -100,6 +107,7 @@ struct esplink_head {
 #define EL_DIRQ_TIME		3
 #define EL_DIRQ_DONE		4	/* Some operation completed */
 #define EL_DIRQ_BOARDINFO	5	/* board_info structure updated */
+#define EL_DIRQ_CONFIG		6	/* config_buf updated */
 
 #define EL_UIRQ_WREN		0	/* Remote write enable bit, not IRQ */
 #define EL_UIRQ_READY		1

+ 5 - 3
common/sysvars.c

@@ -6,10 +6,12 @@
 #include <stdio.h>
 #include <time.h>
 
-#define DEBUG 1
-
 #ifndef DEBUG
-# define DEBUG 0
+# ifdef ON_FPGA
+#  define DEBUG 0
+# else
+#  define DEBUG 1
+# endif
 #endif
 
 static const char *sysvar_bool_tostr(sysvar_t from, char *buf)

+ 3 - 1
common/sysvars.vars

@@ -1,7 +1,9 @@
 @config
 LANG			str "sv"
 TZ			tz "CET-1CEST,M3.5.0,M10.5.0/3"
-abc.hosttype		str "auto"
+abc.hosttype		uint
+abc.reset		bool
+fpga.reset		bool
 hostname		str "max80"
 http.status.refresh	uint 10
 ip4.dhcp.nodns		bool

+ 1 - 2
esp32/.gitignore

@@ -10,5 +10,4 @@ cache/
 *.elf
 *.map
 www/version
-*_gen.h
-*_gen.c
+max80/src/common

+ 1 - 0
esp32/max80/fpga.h

@@ -43,6 +43,7 @@ extern_c esp_err_t fpga_io_read(unsigned int cmd, const volatile void *addr,
 				void *data, size_t len);
 extern_c uint32_t fpga_io_status(unsigned int cmd);
 extern_c void fpga_enable_nce(void);
+extern_c esp_err_t fpga_send_config(void);
 
 struct esplink_head;
 extern_c void esplink_init(void);

+ 30 - 3
esp32/max80/fpgasvc.c

@@ -231,6 +231,32 @@ static void fpga_link_disable(void)
 
 static void fpga_poll_set_time(void);
 
+esp_err_t fpga_send_config(void)
+{
+    char *buf = NULL;
+    size_t bufsize = head.cfg.buflen;
+    esp_err_t err = ESP_ERR_NO_MEM;
+
+    buf = xmalloc_dma(bufsize);
+    if (!buf)
+	goto fail;
+
+    if (sysvar_marshall(sysvar_null, sysvar_count, buf, &bufsize,
+			(uintptr_t)head.cfg.buf) != sysvar_count)
+	goto fail;
+
+    bufsize = (bufsize + 3) & ~3; /* Round to dword */
+    err = fpga_io_write(FPGA_CMD_IRQ(EL_DIRQ_CONFIG), head.cfg.buf,
+			buf, bufsize);
+
+fail:
+    if (buf)
+	free(buf);
+
+    printf("[FPGA] Configuration sent, %zu bytes, status = %u\n", bufsize, err);
+    return err;
+}
+
 static bool fpga_online(void)
 {
     fpga_io_read(FPGA_CMD_ACK(EL_UIRQ_READY), ESPLINK_HDR_ADDR,
@@ -258,6 +284,8 @@ static bool fpga_online(void)
     setvar_bool(status_max80_fpga, true);
     xSemaphoreGiveRecursive(spi_mutex);
 
+    fpga_send_config();
+
     xTimerStart(fpga_timesync_timer, portMAX_DELAY);
     fpga_poll_set_time();
 
@@ -362,7 +390,7 @@ esp_err_t fpga_io_write(unsigned int cmd, const volatile void *addr,
 {
     struct fpga_iov iov;
 
-    iov.cmd   = cmd | ~FPGA_CMD_RD;
+    iov.cmd   = cmd & ~FPGA_CMD_RD;
     iov.addr  = addr;
     iov.wdata = data;
     iov.len   = len;
@@ -581,8 +609,7 @@ static void fpga_service_task(void *dummy)
 	    break;
 
 	case FPGA_OFFLINE:
-	  status = fpga_io_status(FPGA_CMD_ACK(EL_UIRQ_READY)|
-				  FPGA_CMD_IRQ(EL_DIRQ_HELLO));
+	  status = fpga_io_status(FPGA_CMD_IRQ(EL_DIRQ_HELLO));
 	  printf("[FPGA] FPGA status flags = 0x%08x\n", status);
 
 	  if (!digitalRead(PIN_FPGA_INT)) {

+ 30 - 0
esp32/max80/src/common/boardinfo.h

@@ -0,0 +1,30 @@
+#ifndef BOARDINFO_H
+#define BOARDINFO_H
+
+#include "compiler.h"
+
+#define BOARDINFO_SIZE		4096
+
+#define BOARDINFO_MAGIC_1	0x6682df97
+#define BOARDINFO_MAGIC_2	0xe2a0d506
+
+#define IBLK_MAX_MAC_ADDR	8
+
+struct board_info {
+    uint32_t magic[2];
+    uint32_t len;
+    uint32_t crc;		/* 32-bit CRC calculated with crc = 0 */
+
+    char version_str[256];
+
+    uint8_t mac[IBLK_MAX_MAC_ADDR][6];
+};
+
+union board_info_block {
+    struct board_info i;
+    uint8_t b[BOARDINFO_SIZE];
+};
+
+extern_c struct board_info board_info;
+
+#endif

+ 134 - 0
esp32/max80/src/common/compiler.h

@@ -0,0 +1,134 @@
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#ifndef __ASSEMBLY__
+
+#include <stddef.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+# define extern_c extern "C"
+# define EXTERN_C(...) extern "C" { __VA_ARGS__ }
+#else
+# define extern_c extern
+# define EXTERN_C(...) __VA_ARGS__
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
+
+#undef  likely
+#define likely(x)	__builtin_expect(!!(x), 1)
+#undef  unlikely
+#define unlikely(x)	__builtin_expect(!!(x), 0)
+
+/* Handy composite pointer types */
+typedef union xptr {
+    uint32_t *l;
+    uint16_t *w;
+    uint8_t  *b;
+    void     *v;
+    size_t    a;
+} xptr_t;
+typedef union xcptr {
+    const uint32_t *l;
+    const uint16_t *w;
+    const uint8_t  *b;
+    const void     *v;
+    size_t          a;
+} xcptr_t;
+
+/* Subword types */
+typedef union {
+    uint8_t  b[8];
+    uint16_t h[4];
+    uint32_t l[2];
+    uint64_t q;
+} qword_t;
+typedef union {
+    uint8_t  b[4];
+    uint16_t h[2];
+    uint32_t l;
+} lword_t;
+typedef union {
+    uint8_t  b[2];
+    uint16_t h;
+} hword_t;
+
+/* The container_of construct: if p is a pointer to member m of
+   container class c, then return a pointer to the container of which
+   *p is a member. */
+#ifndef container_of
+# define container_of(p, c, m) ((c *)((char *)(p) - offsetof(c,m)))
+#endif
+
+#define offset_diff(c, m1, m2) ((ptrdiff_t)offsetof(c,m2) - \
+				(ptrdiff_t)offsetof(c,m1))
+
+/* Type-safeish comparison macros */
+#define Min(a,b) ({				\
+      __typeof__((a)*(b)) __a = (a);		\
+      __typeof__((a)*(b)) __b = (b);		\
+      (__a < __b) ? __a : __b;			\
+})
+#define Max(a,b) ({				\
+  __typeof__((a)*(b)) __a = (a);		\
+  __typeof__((a)*(b)) __b = (b);		\
+  (__a > __b) ? __a : __b;			\
+})
+
+#if SIZE_MAX == UINT16_MAX
+# define SIZE_BITS 16
+typedef uint32_t size2_t;
+#elif SIZE_MAX == UINT32_MAX
+# define SIZE_BITS 32
+typedef uint64_t size2_t;
+#elif SIZE_MAX == UINT64_MAX
+# define SIZE_BITS 64
+typedef unsigned __int128 size2_t;
+#else
+# error "Unknown size_t"
+#endif
+
+#define alignof(a) __alignof__(a)
+
+#define no_return void __attribute__((noreturn))
+
+#define ___section(s,a,...) __attribute__((__section__(s)))
+
+#define __hot			__attribute__((__hot__))
+#define __cold			__attribute__((__cold__))
+#define __aligned(x)		__attribute__((__aligned__(x)))
+#define __unused		__attribute__((__unused__))
+#define __must_inline		__attribute__((__always_inline__))
+#define __noinline		__attribute__ ((__noinline__))
+#define __constfunc		__attribute__((__const__))
+#define __purefunc		__attribute__((__pure__))
+#undef  __alloc_size
+#define __alloc_size(...)	__attribute__((__alloc_size__(__VA_ARGS__)))
+#define __malloc_func		__attribute__((__malloc__))
+#define __fmt_printf(fstr,farg)	__attribute__((__format__(__printf__,fstr,farg)))
+#define __nonnull_arg(...)	__attribute__((__nonnull__(__VA_ARGS__)))
+#define __no_return		void __attribute__((__noreturn__))
+#define __nonnull_ret		__attribute__((__returns_nonnull__))
+#define __packed		__attribute__((__packed__))
+
+#define __safe_alloc(...)	__nonnull_ret __alloc_size(__VA_ARGS__)
+
+#define __is_constant(expr)	__builtin_constant_p(expr)
+
+#ifndef __cplusplus
+# define atomic(x)		(*(volatile __typeof__(x) *)&(x))
+#endif
+
+#else /* __ASSEMBLY__ */
+
+#define ___section(s,a,...)	.pushsection s, a, ## __VA_ARGS__
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* COMPILER_H */

+ 129 - 0
esp32/max80/src/common/esplink.h

@@ -0,0 +1,129 @@
+/*
+ * Common header file for ESP ("upstream") and FPGA ("downstream")
+ * sides of link. This MUST contain only data structures!
+ */
+#ifndef ESPLINK_H
+#define ESPLINK_H 1
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#define ESPLINK_HDR_ADDR	((const uint32_t *)0x40000000)
+
+/*
+ * Ring buffer descriptor structure; this should be setup time only
+ * and is statically cached on the upstream side.
+ */
+struct esplink_ringbuf_desc {
+    struct esplink_ringbuf {
+	void *start;
+	size_t size;		/* Power of 2 */
+    } dstr, ustr;
+};
+
+/*
+ * Upstream and downstream pointer blocks, with the pointers encoded as
+ * offsets into the buffer.
+ *
+ * Note that the head and tail pointers are reversed between the two
+ * directions to allow one to be copied to the other.
+ */
+struct esplink_ptrs_ustr {
+    size_t head;
+    size_t tail;
+};
+struct esplink_ptrs_dstr {
+    size_t tail;
+    size_t head;
+};
+
+struct esplink_timesync {
+    struct esplink_timesync_buf {
+	uint16_t update;
+	uint16_t tick;
+	union {
+	    struct {
+		unsigned int sec2     : 5;
+		unsigned int min      : 6;
+		unsigned int hour     : 5;
+		unsigned int mday     : 5;
+		unsigned int mon      : 4;
+		unsigned int year     : 7;
+	    } tm;
+	    uint32_t td;
+	};
+    } get, set;
+};
+
+struct esplink_ota {
+    const void *data;
+    uint32_t len;
+};
+
+#define ESPLINK_CONFIG_BUFSIZE 16384
+struct esplink_configbuf {
+    volatile void *buf;	       /* Buffer for configuration and data */
+    size_t buflen;	       /* Size of buffer */
+};
+
+#define ESPLINK_HEAD_MAGIC	0x3648dec4
+#define MAX_SIGNATURE_LEN	64
+
+struct esplink_head {
+    volatile uint32_t magic;
+    uint32_t          hlen;
+    struct {
+	union {
+	    uint32_t    cfg;
+	    struct {
+		uint8_t fixes;
+		uint8_t minor;
+		uint8_t major;
+		uint8_t fpga;
+	    };
+	};
+    } board;
+
+    volatile struct esplink_timesync *tsync;
+    volatile struct esplink_ota *ota;
+    struct esplink_configbuf cfg;
+
+    struct esplink_ringbuf_head {
+	uint32_t count;
+	struct esplink_ringbuf_desc *desc;
+	struct esplink_ptrs_dstr *dstr; /* Downstream (FPGA) side */
+	struct esplink_ptrs_ustr *ustr; /* Upstream (ESP32) side */
+    } rb;
+
+    char signature[MAX_SIGNATURE_LEN]; /* Human-readable signature string */
+
+    const void *board_info;	/* board_info structure pointer */
+};
+
+#define EL_DIRQ_UNDERRUN	0	/* Local interrupt/status bit */
+#define EL_DIRQ_HELLO		1
+#define EL_DIRQ_RINGBUF		2
+#define EL_DIRQ_TIME		3
+#define EL_DIRQ_DONE		4	/* Some operation completed */
+#define EL_DIRQ_BOARDINFO	5	/* board_info structure updated */
+#define EL_DIRQ_CONFIG		6	/* config_buf updated */
+
+#define EL_UIRQ_WREN		0	/* Remote write enable bit, not IRQ */
+#define EL_UIRQ_READY		1
+#define EL_UIRQ_RINGBUF		2
+#define EL_UIRQ_TIME		3
+#define EL_UIRQ_OTA		4
+
+/*
+ * Well known ring buffer indicies; must match for both sides.
+ * Currently assuming one link in each direction; if only a unidirectional
+ * link is needed, leave the descriptor for the unused direction blank.
+ */
+enum esplink_ringbuf_user {
+    EL_RB_CONFIG,
+
+    EL_RB_COUNT
+};
+
+#endif	/* ESPLINK_H */

+ 73 - 0
esp32/max80/src/common/fwimg.h

@@ -0,0 +1,73 @@
+/*
+ * Common firmware image format
+ */
+
+#ifndef FWIMG_H
+#define FWIMG_H
+
+#include <inttypes.h>
+
+/*
+ * Firmware chunk header.
+ */
+#define FW_MAGIC_V1		0x7a07fbd6
+#define FW_MAGIC_V2		0xa924ed0b
+
+#define FW_HDR_LEN_V1		16
+#define FW_HDR_LEN_V2		32
+
+struct fw_header {
+    /* All versions */
+    uint32_t magic;		/* Magic number */
+    uint16_t type;		/* Content type */
+    uint16_t flags;		/* Content flags */
+    uint32_t len;		/* Content length (excluding header) */
+    uint32_t addr;		/* Address or similar */
+
+    /* v2 only */
+    uint32_t vmatch;		/* Flags to match */
+    uint32_t vmask;		/* Mask of flags to match */
+    uint16_t vmin;		/* Minimum version match */
+    uint16_t vmax;		/* Maximum version match */
+    uint32_t resv;		/* For future use */
+};
+
+enum fw_data_type {
+    FDT_END,			/* End of stream */
+    FDT_DATA,			/* FPGA firmware ata to be flashed */
+    FDT_TARGET,			/* Subsystem string (must match) */
+    FDT_NOTE,			/* Version: XXXXX or similar */
+    FDT_ESP_OTA,		/* ESP32 OTA image */
+    FDT_FPGA_INIT,		/* FPGA bitstream for update */
+    FDT_ESP_PART,		/* ESP32 partition table */
+    FDT_ESP_SYS,		/* ESP32 boot loader, OTA control, etc */
+    FDT_ESP_TOOL,		/* esptool.py options for serial flashing */
+    FDT_BOARDINFO		/* Board information flash address */
+};
+enum fw_data_flags {
+    FDF_OPTIONAL     = 0x0001,	/* Ignore if chunk data type unknown */
+    FDF_PRETARGET    = 0x0002	/* Matching FDT_TARGET not required (yet) */
+};
+
+/*
+ * Additional error codes beyond those defined in zlib
+ */
+#define FWUPDATE_ERR_IN_PROGRESS	(-10)
+#define FWUPDATE_ERR_BAD_CHUNK		(-11)
+#define FWUPDATE_ERR_ERASE_FAILED	(-12)
+#define FWUPDATE_ERR_PROGRAM_FAILED	(-13)
+#define FWUPDATE_ERR_WRITE_PROTECT	(-14)
+#define FWUPDATE_ERR_NOT_READY		(-15)
+#define FWUPDATE_ERR_FPGA_JTAG          (-16)
+#define FWUPDATE_ERR_FPGA_MISMATCH	(-17)
+#define FWUPDATE_ERR_FPGA_FAILED	(-18)
+#define FWUPDATE_ERR_UNKNOWN            (-19)
+#define FWUPDATE_ERR_ESP_NO_PARTITION	(-20)
+#define FWUPDATE_ERR_ESP_BAD_OTA	(-21)
+#define FWUPDATE_ERR_ESP_FLASH_FAILED	(-22)
+#define FWUPDATE_ERR_ESP_BAD_DATA	(-23)
+#define FWUPDATE_ERR_CONFIG_READ	(-24)
+#define FWUPDATE_ERR_CONFIG_SAVE	(-25)
+#define FWUPDATE_ERR_NOT_MINE		(-26)
+
+#endif /* FW_H */

+ 190 - 0
esp32/max80/src/common/matchver.c

@@ -0,0 +1,190 @@
+/*
+ * Compare project/version strings of the form:
+ *
+ * PROJECT v<num>.<num>...[ <flags>]
+ *
+ * Pattern is of the form:
+ *
+ * PROJECT[ v[<num>.<num>...][-[<num>.<num>...]] ][+-]flags...
+ *
+ * Chunks must be separated by exactly one space. Flags are A-Za-z0-9.
+ * Any control character is treated as end of string.
+ */
+
+#include "matchver.h"
+
+static int flag_val(char c)
+{
+    if (c < '0')
+	return -1;
+    else if (c <= '9')
+	return c-'0';
+    else if (c < 'A')
+	return -1;
+    else if (c <= 'Z')
+	return c-'A'+10;
+    else if (c < 'a')
+	return -1;
+    else if (c <= 'z')
+	return c-'a'+36;
+    else
+	return -1;
+}
+
+static uint64_t flag_mask(char c)
+{
+    int v = flag_val(c);
+    return (v < 0) ? 0 : UINT64_C(1) << v;
+}
+
+static inline bool is_digit(char c)
+{
+    return (unsigned int)(c - '0') < 10;
+}
+
+/*
+ * Compare numeric strings with components separated by dots, return the end
+ * of each string.
+ */
+static int compare_numbers(const char **ap, const char **bp,
+			   unsigned long bmissing)
+{
+    bool adig, bdig;
+    unsigned long an, bn;
+    int result = 0;
+    const char *a = *ap, *b = *bp;
+
+    for (;;) {
+	adig = is_digit(*a);
+	bdig = is_digit(*b);
+
+	if (!adig && !bdig)
+	    break;
+
+	if (adig) {
+	    an = strtoul(a, (char **)&a, 10);
+	    if (*a == '.')
+		a++;
+	} else {
+	    an = 0;
+	}
+
+	if (bdig) {
+	    bn = strtoul(b, (char **)&b, 10);
+	    if (*b == '.')
+		b++;
+	} else {
+	    bn = bmissing;
+	}
+
+	/* If result is set, the answer is already known, just find the end */
+	if (!result) {
+	    result = (an < bn) ? -1 : (an > bn) ? 1 : 0;
+	}
+    }
+
+    *ap = a; *bp = b;
+    return result;
+}
+
+static const char *parse_flags(const char *str, uint64_t *flags, uint64_t *mask)
+{
+    uint64_t polarity = -1;
+    uint64_t f = 0, m = 0;
+
+
+    while (1) {
+	char c = *str++;
+	uint64_t bit;
+
+	if (c == '+') {
+	    polarity = -1;
+	} else if (c == '-') {
+	    polarity = 0;
+	} else {
+	    bit = flag_mask(c);
+	    if (!bit)
+		break;
+
+	    m |= bit;
+	    f = (f & ~bit) | (polarity & bit);
+	}
+    }
+
+    *flags = f; *mask = m;
+    return str;
+}
+
+bool match_version(const char *version, const char *pattern)
+{
+    char v, p;
+    const char *vstart, *pstart;
+    uint64_t vflags, pflags, pmask;
+
+    while (1) {
+	v = *version++;
+	p = *pattern++;
+
+	if (v <= ' ' && p <= ' ')
+	    break;
+
+	if (v != p)
+	    return false;
+    }
+
+    if (p < ' ')
+	return true;		/* Project-only pattern */
+
+    if (v != ' ' || *version++ != 'v')
+	return false;		/* Invalid/unparsable version string */
+
+    if (*pattern++ != 'v')
+	return false;		/* Invalid pattern */
+
+    vstart = version;
+    pstart = pattern;
+    if (compare_numbers(&version, &pattern, 0UL) < 0)
+	return false;
+
+    if (*pattern == '-')
+	pattern++;
+    else
+	pattern = pstart;
+    if (compare_numbers(&vstart, &pattern, -1UL) > 0)
+	return false;
+
+    v = *version++;
+    vflags = 0;
+    if (v == ' ') {
+	uint64_t dummy;
+	parse_flags(version, &vflags, &dummy);
+    } else if (v > ' ') {
+	return false;
+    }
+
+    p = *pattern++;
+    pflags = pmask = 0;
+    if (p == ' ') {
+	parse_flags(pattern, &pflags, &pmask);
+    } else if (p > ' ') {
+	return false;
+    }
+
+    return (vflags & pmask) == pflags;
+}
+
+#ifdef COMMAND
+
+#include <stdio.h>
+
+int main(int argc, char *argv[])
+{
+    if (argc != 3) {
+	fprintf(stderr, "Usage: %s version pattern\n", argv[0]);
+	return 127;
+    }
+
+    return !match_version(argv[1], argv[2]);
+}
+
+#endif

+ 10 - 0
esp32/max80/src/common/matchver.h

@@ -0,0 +1,10 @@
+#ifndef MATCHVER_H
+#define MATCHVER_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+bool match_version(const char *version, const char *pattern);
+
+#endif /* MATCHVER_H */

+ 421 - 0
esp32/max80/src/common/sysvars.c

@@ -0,0 +1,421 @@
+#include "common.h"
+#include "sysvars.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#ifndef DEBUG
+# ifdef ON_FPGA
+#  define DEBUG 0
+# else
+#  define DEBUG 1
+# endif
+#endif
+
+static const char *sysvar_bool_tostr(sysvar_t from, char *buf)
+{
+    buf[0] = '0' + from.v_bool;
+    buf[1] = '\0';
+    return buf;
+}
+
+static bool sysvar_bool_fromstr(sysvar_t *to, const char *from)
+{
+    char c = from[0] | 0x20;
+    switch (c) {
+    case '1':
+    case 't':
+    case 'y':
+    case 'j':
+    case 's':
+	to->v_bool = true;
+	return true;
+    case ' ':			/* Blank or null */
+    case '0':
+    case 'f':
+    case 'n':
+	to->v_bool = false;
+	return true;
+    default:
+	return false;
+    }
+}
+
+const struct sysvar_ops sysvar_bool_ops = {
+    .tostr = sysvar_bool_tostr,
+    .fromstr = sysvar_bool_fromstr,
+    .buflen = BOOL_BUFLEN
+};
+
+static const char *sysvar_int_tostr(sysvar_t from, char *buf)
+{
+    snprintf(buf, INT_BUFLEN, "%ld", from.v_int);
+    return buf;
+}
+
+static bool sysvar_int_fromstr(sysvar_t *to, const char *from)
+{
+    char *ep;
+    long v = strtol(from, &ep, 0);
+    if (ep == from || *ep)
+	return false;
+    to->v_int = v;
+    return true;
+}
+
+const struct sysvar_ops sysvar_int_ops = {
+    .tostr = sysvar_int_tostr,
+    .fromstr = sysvar_int_fromstr,
+    .buflen = INT_BUFLEN
+};
+
+static const char *sysvar_uint_tostr(sysvar_t from, char *buf)
+{
+    snprintf(buf, UINT_BUFLEN, "%lu", from.v_uint);
+    return buf;
+}
+
+static bool sysvar_uint_fromstr(sysvar_t *to, const char *from)
+{
+    char *ep;
+    unsigned long v = strtoul(from, &ep, 0);
+    if (ep == from || *ep)
+	return false;
+    to->v_uint = v;
+    return true;
+}
+
+const struct sysvar_ops sysvar_uint_ops = {
+    .tostr = sysvar_uint_tostr,
+    .fromstr = sysvar_uint_fromstr,
+    .buflen = UINT_BUFLEN
+};
+
+static const char *sysvar_str_tostr(sysvar_t from, char *buf)
+{
+    (void)buf;
+    return from.v_str;
+}
+
+static bool sysvar_str_fromstr(sysvar_t *to, const char *from)
+{
+    char *ns;
+
+    if (!from) {
+	ns = NULL;
+    } else {
+	ns = strdup(from);
+	if (!ns)
+	    return false;
+    }
+    to->v_str = ns;
+    return true;
+}
+
+static bool sysvar_str_set(sysvar_t *to, sysvar_t from)
+{
+    return sysvar_str_fromstr(to, from.v_str);
+}
+
+static size_t sysvar_str_datasize(sysvar_t val)
+{
+    return (val.v_str ? strlen(val.v_str) : 0) + 1;
+}
+
+const struct sysvar_ops sysvar_str_ops = {
+    .set = sysvar_str_set,
+    .tostr = sysvar_str_tostr,
+    .fromstr = sysvar_str_fromstr,
+    .datasize = sysvar_str_datasize
+};
+
+static void sysvar_tz_update(sysvar_t val, bool isset)
+{
+    if (isset)
+	setenv("TZ", val.v_tz, 1);
+    else
+	unsetenv("TZ");
+    tzset();
+}
+
+const struct sysvar_ops sysvar_tz_ops = {
+    .set = sysvar_str_set,
+    .tostr = sysvar_str_tostr,
+    .fromstr = sysvar_str_fromstr,
+    .update = sysvar_tz_update,
+    .datasize = sysvar_str_datasize
+};
+
+static const char *sysvar_ip_tostr(sysvar_t from, char *buf)
+{
+    union ip_bytes {
+	uint8_t b[4];
+	uint32_t l;
+    } ip;
+
+    ip.l = from.v_ip;
+    snprintf(buf, IP_BUFLEN,
+	     "%u.%u.%u.%u", ip.b[0], ip.b[1], ip.b[2], ip.b[3]);
+    return buf;
+}
+
+static bool sysvar_ip_fromstr(sysvar_t *to, const char *str)
+{
+    union ip_bytes {
+	uint8_t b[4];
+	uint32_t l;
+    } ip;
+
+    ip.l = 0;
+    for (int i = 0; i < 4; i++) {
+	char *ep;
+	unsigned long v = strtoul(str, &ep, 10);
+	if (ep == str || *ep != (i == 3) ? '\0' : '.' || v > 255)
+	    return false;
+	str = ep + 1;
+	ip.b[i] = v;
+    }
+
+    to->v_ip = ip.l;
+    return true;
+}
+
+const struct sysvar_ops sysvar_ip_ops = {
+    .tostr = sysvar_ip_tostr,
+    .fromstr = sysvar_ip_fromstr,
+    .buflen = IP_BUFLEN
+};
+
+static bool sysvar_mac_set(sysvar_t *to, sysvar_t from)
+{
+    uint8_t *buf;
+
+    if (!from.v_mac) {
+	buf = NULL;
+    } else {
+	buf = malloc(6);
+	if (!buf)
+	    return false;
+
+	memcpy(buf, from.v_mac, 6);
+    }
+    to->v_mac = buf;
+    return true;
+}
+
+static const char *sysvar_mac_tostr(sysvar_t from, char *buf)
+{
+    const uint8_t *m = from.v_mac;
+    if (!m)
+	return NULL;
+
+    snprintf(buf, MAC_BUFLEN, "%x:%x:%x:%x:%x:%x",
+	     m[0], m[1], m[2], m[3], m[4], m[5]);
+    return buf;
+}
+
+static bool sysvar_mac_fromstr(sysvar_t *to, const char *str)
+{
+    sysvar_t from;
+    uint8_t m[6];
+
+    if (!str) {
+	from.v_mac = NULL;
+    } else {
+	for (int i = 0; i < 6; i++) {
+	    char *ep;
+	    unsigned long v = strtoul(str, &ep, 16);
+	    if (ep == str || *ep != (i == 5) ? '\0' : ':' || v > 255)
+		return false;
+	    str = ep + 1;
+	    m[i] = v;
+	}
+
+	from.v_mac = m;
+    }
+
+    return sysvar_mac_set(to, from);
+}
+
+static size_t sysvar_mac_datasize(sysvar_t val)
+{
+    (void)val;
+    return 6;
+}
+
+const struct sysvar_ops sysvar_mac_ops = {
+    .set = sysvar_mac_set,
+    .tostr = sysvar_mac_tostr,
+    .fromstr = sysvar_mac_fromstr,
+    .buflen = MAC_BUFLEN,
+    .datasize = sysvar_mac_datasize
+};
+
+/* --- Generic getters/setters --- */
+
+/* Contains the lowest numbered sysvar changed; or sysvar_count if nothing */
+enum sysvar_enum sysvar_changed;
+
+sysvar_t getvar(size_t var)
+{
+    if (var >= (size_t)sysvar_count)
+	return sysvar_defval[sysvar_null];
+
+    return sysvar_val[var];
+}
+
+static bool do_setvar(size_t var, sysvar_t val, bool is_set)
+{
+    const struct sysvar_ops *type = sysvar_types[var];
+    sysvar_t *to = &sysvar_val[var];
+    void *free_ptr = NULL;
+
+    if (type->datasize)
+	free_ptr = to->v_ptr;
+
+    if (DEBUG) {
+	char tostr_buf[SYSVAR_BUFLEN];
+	printf("%ssetvar %zu %s <- %s\n", is_set ? "" : "un",
+	       var, sysvar_name[var],
+	       notempty(type->tostr(val, tostr_buf)));
+    }
+
+    if (!type->set || (type->datasize && !val.v_ptr)) {
+	sysvar_val[var] = val;
+    } else {
+	if (!type->set(to, val))
+	    return false;
+    }
+
+    if (free_ptr)
+	free(free_ptr);
+
+    if (var < (size_t)sysvar_changed)
+	sysvar_changed = (enum sysvar_enum)var;
+
+    sysvar_isset[var] = is_set;
+    if (type->update)
+	type->update(*to, is_set);
+
+    return true;
+}
+
+bool setvar(size_t var, sysvar_t val)
+{
+    if (var >= (size_t)sysvar_count)
+	return false;
+
+    return do_setvar(var, val, true);
+}
+
+bool unsetvar(size_t var)
+{
+    if (var >= (size_t)sysvar_count)
+	return false;
+
+    return do_setvar(var, sysvar_defval[var], false);
+}
+
+/* --- Getters/setters converting to/from strings --- */
+
+const char *getvar_tostr(size_t var)
+{
+    static char buf[SYSVAR_BUFLEN];
+    return getvar_tostr_r(var, buf);
+}
+
+const char *getvar_tostr_r(size_t var, char *buf)
+{
+    if (var >= (size_t)sysvar_count)
+	return NULL;
+
+    const struct sysvar_ops *type = sysvar_types[var];
+
+    /* A tostr method is required */
+    return type->tostr(sysvar_val[var], buf);
+}
+
+bool setvar_fromstr(size_t var, const char *str)
+{
+    if (var >= (size_t)sysvar_count)
+	return NULL;
+
+    if (!str)
+	return unsetvar(var);
+
+    const struct sysvar_ops *type = sysvar_types[var];
+    sysvar_t *to = &sysvar_val[var];
+    void *free_ptr = NULL;
+
+    if (type->datasize)
+	free_ptr = to->v_ptr;
+
+    if (DEBUG) {
+	printf("setvar_fromstr %zu %s <- %s\n", var, sysvar_name[var], str);
+    }
+
+    /* A fromstr method is required */
+    if (!type->fromstr(to, str))
+	return false;
+
+    if (var < (size_t)sysvar_changed)
+	sysvar_changed = (enum sysvar_enum)var;
+
+    if (free_ptr)
+	free(free_ptr);
+
+    sysvar_isset[var] = true;
+    return true;
+}
+
+/* --- Find the index of a specific variable --- */
+
+static int string_ptr_compare(const void *a, const void *b)
+{
+    const char * const *aa = a;
+    const char * const *bb = b;
+    return strcmp(*aa, *bb);
+}
+
+size_t sysvar_find(size_t ns, const char *name)
+{
+    if (ns >= (size_t)sysvar_nscount)
+	return 0;
+
+    const sysvar_ns_t *nsi = &sysvar_ns[ns];
+    const char * const *varname;
+    const size_t count = nsi[1].first - nsi->first;
+
+    varname = bsearch(&name, sysvar_name + nsi->first,
+		      count, sizeof *sysvar_name,
+		      string_ptr_compare);
+    if (!varname)
+	return 0;
+
+    return varname - sysvar_name;
+}
+
+/* --- Initialization/reset to defaults --- */
+
+void sysvar_reset(size_t ns)
+{
+    if (ns >= (size_t)sysvar_nscount)
+	return;
+
+    enum sysvar_enum i;
+    for (i = sysvar_ns[ns].first; i < sysvar_ns[ns+1].first; i++)
+	unsetvar(i);
+
+    if (sysvar_changed < i)
+	sysvar_changed = i;
+}
+
+void sysvar_init(void)
+{
+    for (enum sysvar_enum i = sysvar_null+1; i < sysvar_count; i++)
+	unsetvar(i);
+
+    sysvar_changed = sysvar_count;
+}

+ 120 - 0
esp32/max80/src/common/sysvars.h

@@ -0,0 +1,120 @@
+#ifndef SYSVARS_H
+#define SYSVARS_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#ifndef extern_c
+# ifdef __cplusplus
+#  define extern_c extern "C"
+# else
+#  define extern_c extern
+# endif
+#endif
+
+typedef union sysvar_value {
+    bool v_bool;
+    long int v_int;
+    unsigned long int v_uint;
+    const char *v_str;
+    const char *v_tz;		/* Timezone */
+    uint32_t v_ip;		/* IPv4 address */
+    const uint8_t *v_mac;	/* MAC address */
+    void *v_ptr;
+} sysvar_t;
+
+struct sysvar_ops {
+    bool (*set)(sysvar_t *, sysvar_t);
+    /* bool (*unset)(sysvar_t *); - not used */
+    const char * (*tostr)(sysvar_t, char *);
+    bool (*fromstr)(sysvar_t *, const char *);
+    void (*update)(sysvar_t, bool); /* Called after set or fromstr; bool = isset */
+    size_t (*datasize)(sysvar_t);   /* If pointer, return size of data */
+    size_t buflen;		/* Minimal buffer size for string if needed */
+};
+
+typedef const struct sysvar_ops *sysvar_type_t;
+
+extern_c const struct sysvar_ops sysvar_bool_ops;
+extern_c const struct sysvar_ops sysvar_int_ops;
+extern_c const struct sysvar_ops sysvar_uint_ops;
+extern_c const struct sysvar_ops sysvar_str_ops;
+extern_c const struct sysvar_ops sysvar_tz_ops;
+extern_c const struct sysvar_ops sysvar_ip_ops;
+extern_c const struct sysvar_ops sysvar_mac_ops;
+
+#define SYSVAR_NULLTYPE NULL
+#define SYSVAR_TYPE(x) (&sysvar ## x ## _ops)
+
+#include "sysvars_gen.h"
+
+typedef struct sysvar_namespace {
+    const char *name;
+    enum sysvar_enum first;
+} sysvar_ns_t;
+
+extern_c const sysvar_ns_t sysvar_ns[(size_t)sysvar_nscount+1];
+extern_c enum sysvar_enum sysvar_changed;
+
+/* Buffer size needed to represent some data types */
+#define BOOL_BUFLEN 2
+#define INT_BUFLEN  (3*sizeof(unsigned int)+2)
+#define UINT_BUFLEN INT_BUFLEN
+#define IP_BUFLEN   (4*4)
+#define MAC_BUFLEN  (3*6)
+
+#define SYSVAR_BUFLEN	32	/* Conservative minimum */
+
+extern_c sysvar_t getvar(size_t var);
+extern_c bool setvar(size_t var, sysvar_t val);
+extern_c bool unsetvar(size_t var);
+extern_c const char *getvar_tostr(size_t var);
+extern_c const char *getvar_tostr_r(size_t var, char *buf);
+extern_c bool setvar_fromstr(size_t var, const char *str);
+extern_c void sysvar_init(void);
+extern_c void sysvar_reset(size_t ns);
+extern_c size_t sysvar_find(size_t ns, const char *name);
+extern_c size_t sysvar_marshall(enum sysvar_enum first, size_t count,
+				void *buf, size_t *buflen, uintptr_t extaddr);
+
+/* Type-specific definitions/getters/setters */
+/* Note that t contains a leading underscore to avoid bool/_Bool issues */
+
+#define const_assert(cond, str)					     \
+    do {							     \
+	extern void fail(void) __attribute__((error(str)));	     \
+	if (__builtin_constant_p(cond) && !(cond))		     \
+	    fail();						     \
+    } while (0)
+
+#define TRY_ASSERT_TYPE(var,t)						\
+    const_assert(sysvar_type(var) == SYSVAR_TYPE(t),			\
+	"invalid type for sysvar " #var)
+
+#define SYSVAR_MKTYPE(t,c_type)						\
+    static inline c_type getvar ## t (size_t var)			\
+    {									\
+	TRY_ASSERT_TYPE(var,t);						\
+	/* If var is constant and >= sysvar_count, TRY_ASSERT_TYPE() fails */ \
+	if (__builtin_constant_p(var < (size_t)sysvar_count))		\
+	    return sysvar_val[var].v ## t;				\
+	return getvar(var).v ## t ;					\
+    }									\
+    static inline bool setvar ## t (size_t var, c_type v)		\
+    {									\
+	sysvar_t vv;							\
+	TRY_ASSERT_TYPE(var,t);						\
+	vv.v ## t = v;							\
+	return setvar(var, vv);						\
+    }
+
+SYSVAR_MKTYPE(_bool, bool);
+SYSVAR_MKTYPE(_int, long int);
+SYSVAR_MKTYPE(_uint, unsigned long int);
+SYSVAR_MKTYPE(_str, const char *);
+SYSVAR_MKTYPE(_tz, const char *);
+SYSVAR_MKTYPE(_ip, uint32_t);
+SYSVAR_MKTYPE(_mac, const uint8_t *);
+
+#endif /* SYSVARS_H */

+ 1 - 1
esp32/max80/sysvars_marshall.c

@@ -36,7 +36,7 @@ size_t sysvar_marshall(enum sysvar_enum first, size_t count,
     while (count--) {
 	sysvar_t vv = *vp++ = *v++;
 	sysvar_type_t type = *t++;
-	if (type->datasize && vv.v_ptr) {
+	if (type && type->datasize && vv.v_ptr) {
 	    size_t dsize = type->datasize(vv);
 	    size_t adj_bytes = (bytes + align_mask) & ~align_mask;
 	    if (adj_bytes + dsize > bufsize)

BIN
esp32/output/max80.ino.bin


+ 12 - 4
esp32/www/abcbus.html

@@ -17,16 +17,24 @@
 	  <b>Host system type</b>
 	  <div>
 	    <label class="auto">
-	      <input type="radio" name="abc.hosttype" value="auto" />
+	      <input type="radio" name="abc.hosttype" value="0" />
 	      <span>Autodetect</span>
 	    </label>
 	    <label class="abc80">
-	      <input type="radio" name="abc.hosttype" value="abc80" />
+	      <input type="radio" name="abc.hosttype" value="1" />
 	      <span>ABC80</span>
 	    </label>
+	    <label class="abc80-4680">
+	      <input type="radio" name="abc.hosttype" value="2" />
+	      <span>ABC80/4680 (expansion box)</span>
+	    </label>
 	    <label class="abc800">
-	      <input type="radio" name="abc.hosttype" value="abc800" />
-	      <span>ABC800</span>
+	      <input type="radio" name="abc.hosttype" value="3" />
+	      <span>ABC800C/M</span>
+	    </label>
+	    <label class="abc802">
+	      <input type="radio" name="abc.hosttype" value="4" />
+	      <span>ABC802/806</span>
 	    </label>
 	  </div>
 	</div>

+ 3 - 3
fpga/max80.qpf

@@ -19,15 +19,15 @@
 #
 # Quartus Prime
 # Version 22.1std.0 Build 915 10/25/2022 SC Lite Edition
-# Date created = 13:58:18  September 05, 2023
+# Date created = 17:09:07  September 05, 2023
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "22.1"
-DATE = "13:58:18  September 05, 2023"
+DATE = "17:09:07  September 05, 2023"
 
 # Revisions
 
-PROJECT_REVISION = "v1"
 PROJECT_REVISION = "v2"
+PROJECT_REVISION = "v1"
 PROJECT_REVISION = "bypass"

BIN
fpga/output/bypass.jic


BIN
fpga/output/max80.fw


BIN
fpga/output/v1.fw


BIN
fpga/output/v1.jic


BIN
fpga/output/v1.sof


BIN
fpga/output/v2.fw


BIN
fpga/output/v2.jic


BIN
fpga/output/v2.sof


+ 6 - 3
rv32/Makefile

@@ -13,7 +13,7 @@ GZIP	  = gzip
 
 INCLUDE   = -I. -I./include -I../common -I./zlib -I./fatfs/source
 include ../riscv-opts.mk
-CPPFLAGS  = $(INCLUDE) $(riscv_flags)
+CPPFLAGS  = $(INCLUDE) $(riscv_flags) -DON_FPGA
 CFLAGS    = $(CPPFLAGS) -W -Wextra
 SFLAGS    = $(CPPFLAGS) -D__ASSEMBLY__
 LDFLAGS   = $(CFLAGS) \
@@ -44,17 +44,20 @@ LIBS    = max80.a fatfs.a zlib.a
 
 
 ROMS    := $(wildcard roms/*.rom)
-ROMOBJS  = $(ROMS:.rom=.o)
+ROMOBJ   = $(ROMS:.rom=.o)
 
 FORCEOBJ = head.o dummy.o die.o system.o killed.o
 
+COMMONOBJ := $(patsubst ../common/%.c,%.o,$(wildcard ../common/*.c))
+
 LIBOBJ   = debug.o ioregsa.o irqasm.o irqtable.o spurious_irq.o \
 	   console.o rtc.o romcopy.o spiflash.o esp.o matchver.o \
+	   config.o \
 	   sdcard.o \
 	   abcmem.o abcio.o abcdisk.o abcrtc.o abcpun80.o \
 	   memset.o memcpy.o \
 	   runtest.o start_test.o \
-	   $(ROMOBJS)
+	   $(COMMONOBJ) $(ROMOBJ)
 
 max80.a: $(LIBOBJ)
 	rm -f $@

+ 1 - 1
rv32/checksum.h

@@ -1,4 +1,4 @@
 #ifndef CHECKSUM_H
 #define CHECKSUM_H
-#define SDRAM_SUM 0x674c2aeb
+#define SDRAM_SUM 0x9c38c8ce
 #endif

+ 5 - 0
rv32/common.h

@@ -61,6 +61,11 @@ static uint32_t lowest_set_bit(uint32_t mask)
     return mask - mask_lowest_set_bit(mask);
 }
 
+static const char *notempty(const char *str)
+{
+    return str ? str : "";
+}
+
 struct esplink_head;
 extern struct esplink_head esplink_head;
 extern uint32_t esplink[];

+ 34 - 0
rv32/config.c

@@ -0,0 +1,34 @@
+#include "common.h"
+#include "config.h"
+#include "console.h"
+#include "io.h"
+
+char config_buf[CONFIG_BUFSIZE];
+volatile bool do_update_config;
+bool _configured;
+
+void update_config(void)
+{
+    do_update_config = false;
+
+    memcpy(sysvar_val, config_buf, (size_t)sysvar_count * sizeof *sysvar_val);
+
+    con_puts("[ESP] Configuration received: ");
+    con_puts(_configured ? "update\n" : "initial\n");
+
+    if (!_configured) {
+	_configured = true;
+	return;
+    }
+
+    uint32_t busctl = ABC_BUSCTL;
+
+    if (getvar_bool(config_abc_reset)) {
+	ABC_BUSCTL = busctl | ABC_BUSCTL_RESET;
+	udelay(20000);
+    }
+    if (getvar_bool(config_fpga_reset)) {
+	reset(SYS_RESET_SOFT);
+    }
+    ABC_BUSCTL = busctl;
+}

+ 18 - 0
rv32/config.h

@@ -0,0 +1,18 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include "compiler.h"
+#include "common.h"
+#include "sysvars.h"
+
+#define CONFIG_BUFSIZE 16384
+extern char config_buf[CONFIG_BUFSIZE];
+extern volatile bool do_update_config;
+extern bool _configured;
+static inline bool configured(void)
+{
+    return _configured;
+}
+void update_config(void);
+
+#endif /* CONFIG_H */

+ 7 - 0
rv32/esp.c

@@ -4,6 +4,7 @@
 #include "console.h"
 #include "esp.h"
 #include "boardinfo_fpga.h"
+#include "config.h"
 
 struct esplink_head __esplink_head esplink_head;
 static volatile __esplink struct esplink_timesync tsync;
@@ -16,6 +17,7 @@ IRQHANDLER(esp,0)
     uint32_t irqstatus = ESP_CPU_IRQ;
     ESP_CPU_IRQ_CLR = irqstatus;
 
+    con_printf("[ESP] ESP link IRQ, status = %08x\n", irqstatus);
 
     if (irqstatus & (1 << EL_DIRQ_UNDERRUN)) {
 	con_printf("[ESP] ESP link memory underrun!!\n");
@@ -43,6 +45,9 @@ IRQHANDLER(esp,0)
 	ESP_SPI_IRQ_SET = (1 << EL_UIRQ_READY)|(1 << EL_UIRQ_WREN);
     }
 
+    if (irqstatus & (1 << EL_DIRQ_CONFIG))
+	do_update_config = true;
+
     /*
      * Check to see if we got hello after an OTA process was completed
      * or aborted; ESP will send EL_DIRQ_DONE to wake us up.
@@ -244,6 +249,8 @@ void esp_init(void)
     esplink_head.board_info    = &board_info_raw;
     esplink_head.tsync         = &tsync;
     esplink_head.ota           = &ota;
+    esplink_head.cfg.buf       = config_buf;
+    esplink_head.cfg.buflen    = sizeof config_buf;
 
     esplink_rb_init();
 

+ 4 - 0
rv32/max80.c

@@ -3,6 +3,7 @@
 #include "abcio.h"
 #include "sys.h"
 #include "console.h"
+#include "config.h"
 #include "boardinfo_fpga.h"
 
 void __hot main(void)
@@ -21,6 +22,9 @@ void __hot main(void)
 	if (unlikely(do_update_boardinfo))
 	    rom_update_boardinfo();
 
+	if (unlikely(do_update_config))
+	    update_config();
+
 	abcdisk_io_poll();
 
 	abc_latency = ABC_LATENCY;

+ 13 - 3
rv32/system.c

@@ -5,6 +5,7 @@
 #include "sys.h"
 #include "console.h"
 #include "esp.h"
+#include "config.h"
 
 #define DEBUG     0
 #define MINITESTS 1
@@ -230,11 +231,20 @@ static void __noinline late_init(void)
 
     esp_init();    /* Ready for communications */
 
+    /* Let ESP know we are ready... */
+    ESP_SPI_IRQ_SET = (1 << EL_DIRQ_HELLO);
+
+    /* Wait for ESP to send us a configuration */
+    while (!do_update_config) {
+	mask_irq(ESP_IRQ);
+	if (!do_update_config)
+	    waitfor(ESP_IRQ);
+	unmask_irq(ESP_IRQ);
+    }
+    update_config();
+
     /* Release WAIT# if asserted */
     ABC_BUSCTL = 0;
 
-    /* Let ESP know we are ready... */
-    ESP_SPI_IRQ_SET = (1 << 1);
-
     set_leds(0);
 }