summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2022-08-18 14:33:12 -0700
committerH. Peter Anvin <hpa@zytor.com>2022-08-18 14:36:22 -0700
commit75e32256b898b2d04c546c6ebf73f6d129e131bb (patch)
treef94b2ac660b4c1a3d0aca9e9c1668828f7bd86d7
parentf9c92995ca03dc97e77558934713903db9c85d27 (diff)
downloadblinktest-75e32256b898b2d04c546c6ebf73f6d129e131bb.tar.gz
blinktest-75e32256b898b2d04c546c6ebf73f6d129e131bb.tar.xz
blinktest-75e32256b898b2d04c546c6ebf73f6d129e131bb.zip
fw: add ability to write a board ID string in ESP flash
Add the ability to write a board ID string in ESP flash, and add beginning infrastructure for handling multiversion firmware images (not quite there yet.)
-rw-r--r--Makefile20
-rw-r--r--common/boardinfo.h25
-rw-r--r--common/fwimg.h3
-rw-r--r--common/matchver.c192
-rwxr-xr-xesp32/flashesp.pl113
-rw-r--r--esp32/max80/boardinfo.c169
-rw-r--r--esp32/max80/boardinfo_esp.h7
-rw-r--r--esp32/max80/common.h4
-rw-r--r--esp32/max80/max80.ino8
-rw-r--r--esp32/max80/tty.cpp8
-rw-r--r--esp32/max80/wifi.cpp6
-rw-r--r--esp32/output/max80.ino.binbin796784 -> 798016 bytes
-rw-r--r--fpga/Makefile56
-rw-r--r--fpga/max80.qpf4
-rw-r--r--fpga/output/bypass.jicbin16777443 -> 16777443 bytes
-rw-r--r--fpga/output/bypass.rpd.gzbin64733 -> 64732 bytes
-rw-r--r--fpga/output/max80.fwbin0 -> 921747 bytes
-rw-r--r--fpga/output/v1.fwbin760519 -> 761060 bytes
-rw-r--r--fpga/output/v1.jicbin16777443 -> 16777443 bytes
-rw-r--r--fpga/output/v1.rbf.gzbin161044 -> 161031 bytes
-rw-r--r--fpga/output/v1.rpd.gzbin216183 -> 218479 bytes
-rw-r--r--fpga/output/v1.sofbin509096 -> 509096 bytes
-rw-r--r--fpga/output/v1.svf.gzbin183083 -> 183091 bytes
-rw-r--r--fpga/output/v1.xsvf.gzbin165428 -> 165410 bytes
-rw-r--r--fpga/output/v2.fwbin761491 -> 762014 bytes
-rw-r--r--fpga/output/v2.jicbin16777443 -> 16777443 bytes
-rw-r--r--fpga/output/v2.rbf.gzbin161584 -> 161574 bytes
-rw-r--r--fpga/output/v2.rpd.gzbin217922 -> 217873 bytes
-rw-r--r--fpga/output/v2.sofbin509096 -> 509096 bytes
-rw-r--r--fpga/output/v2.svf.gzbin184083 -> 184044 bytes
-rw-r--r--fpga/output/v2.xsvf.gzbin165775 -> 165783 bytes
-rw-r--r--rv32/checksum.h2
-rw-r--r--rv32/spiflash.c10
-rwxr-xr-xtools/mkfwimage.pl145
34 files changed, 654 insertions, 118 deletions
diff --git a/Makefile b/Makefile
index d8ab9ba..d8cf2f0 100644
--- a/Makefile
+++ b/Makefile
@@ -5,11 +5,13 @@ SUBDIRS := esp32 tools rv32 fpga
REVISIONS := v1 v2 bypass
GIT_DIR ?= .git
+TEMP ?= /tmp
MAX80_IP ?= max80
CURL = curl
PERL = perl
+ESPTOOL ?= esptool
all clean spotless :
$(MAKE) local.$@ $(SUBDIRS) goal=$@
@@ -54,7 +56,7 @@ fpga: version.vh | rv32 esp32
local.all:
local.clean:
- rm -f *~ ./\#* \# *.bak *.tmp
+ rm -f *~ ./\#* \# *.bak *.tmp stamp_*.bin
local.spotless: local.clean
rm -f version.h version.vh
@@ -75,9 +77,19 @@ upload-esp:
# Update via HTTP or serial port
ip = $(MAX80_IP)
-upload-%:
+upload-v%:
if [ -z '$(PORT)' ]; then \
- $(CURL) -v --data-binary @fpga/output/$*.fw http://$(ip)/sys/fwupdate ; \
+ $(CURL) -v --data-binary @fpga/output/v$*.fw http://$(ip)/sys/fwupdate ; \
else \
- $(PERL) ./esp32/flashesp.pl fpga/output/$*.fw '$(PORT)' $(FLASHOPT); \
+ $(PERL) ./esp32/flashesp.pl fpga/output/v$*.fw '$(PORT)' $(FLASHOPT); \
fi
+
+# Set board revision in flash
+WRITEFLASH = [ ! -z '$(PORT)' ] && $(ESPTOOL) --before default_reset --after hard_reset --port $(PORT) write_flash -z
+
+stamp_max80_v%.bin:
+ $(PERL) -e '$$m = "MAX80 v$*\0"; print $$m, "\xff" x (4096 - length ($$m));' > $@
+
+setver-v%: stamp_max80_v%.bin
+ if [ -z '$(PORT)' ]; then echo 'Set PORT for this command' 2>&1; exit 1; fi
+ $(WRITEFLASH) 0 $< || sleep 1; $(WRITEFLASH) 0 $<
diff --git a/common/boardinfo.h b/common/boardinfo.h
new file mode 100644
index 0000000..429f3d2
--- /dev/null
+++ b/common/boardinfo.h
@@ -0,0 +1,25 @@
+#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];
+};
+
+extern_c struct board_info board_info;
+
+#endif
diff --git a/common/fwimg.h b/common/fwimg.h
index 2c5f985..e59d39d 100644
--- a/common/fwimg.h
+++ b/common/fwimg.h
@@ -29,7 +29,8 @@ enum fw_data_type {
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_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 */
diff --git a/common/matchver.c b/common/matchver.c
new file mode 100644
index 0000000..b2a95a5
--- /dev/null
+++ b/common/matchver.c
@@ -0,0 +1,192 @@
+/*
+ * 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 <stdlib.h>
+#include <stdbool.h>
+#include <inttypes.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
diff --git a/esp32/flashesp.pl b/esp32/flashesp.pl
index 2420427..be7344f 100755
--- a/esp32/flashesp.pl
+++ b/esp32/flashesp.pl
@@ -12,6 +12,8 @@ use v5.10; # For "state"
my $esptool = ($^O eq 'MSWin32') ? 'esptool.exe' : 'esptool.py';
$esptool = $ENV{'ESPTOOL'} || $esptool;
+my $esp_retries = 5;
+
my $FW_MAGIC = 0x7a07fbd6;
my %datatypes = (
@@ -23,7 +25,8 @@ my %datatypes = (
'fpgainit' => 5, # FPGA bypass (transient) image during update
'esppart' => 6, # ESP32 partition table
'espsys' => 7, # ESP32 boot loader, OTA control partition...
- 'esptool' => 8 # esptool.py options for flashing
+ 'esptool' => 8, # esptool.py options for flashing
+ 'boardinfo' => 9 # board_info base address
);
my @type;
foreach my $t (keys(%datatypes)) {
@@ -115,6 +118,79 @@ sub unquote_cmd($) {
return @a;
}
+# Similar to grep, but for a hash; also filters out
+sub hgrep(&%) {
+ my($mfunc, %hash) = @_;
+
+ return map { $_ => $hash{$_} } grep(&$mfunc, keys %hash);
+}
+
+# Wrapper for running esptool, returns a hash with info output
+# or dies on failure
+sub hash2opt($)
+{
+ my($h) = @_;
+
+ return () unless (defined($h));
+ return map { $h->{$_} ne '' ? ('--'.$_, $h->{$_}) : () } sort keys %{$h};
+}
+
+sub run_esptool($$$$@)
+{
+ my($port,$common_options,$cmd,$cmd_options,@args) = @_;
+
+ my @espcmd = unquote_cmd($esptool);
+ push(@espcmd, '--p', $port);
+ push(@espcmd, hash2opt($common_options));
+ push(@espcmd, unquote_cmd($cmd));
+ push(@espcmd, hash2opt($cmd_options));
+ push(@espcmd, @args);
+
+ my $retries = $esp_retries;
+ my $ok = 0;
+ my %outinfo;
+ my @output;
+
+ while (!$ok && $retries--) {
+ %outinfo = ();
+ @output = ();
+
+ print STDERR 'Command: ', join(' ', @espcmd), "\n";
+ print STDERR "Running $espcmd[0] $cmd... ";
+
+ my $esp;
+ if (!open($esp, '-|', @espcmd)) {
+ print STDERR $!, "\n";
+ exit 1;
+ }
+
+ while (defined(my $l = <$esp>)) {
+ if ($l =~ /^Chip is (\S+)/) {
+ $outinfo{'chip'} = $1;
+ } elsif ($l =~ /^MAC: ([0-9a-f:]+)/) {
+ $outinfo{'mac'} = $1;
+ }
+ push(@output, $l);
+ }
+
+ $ok = close($esp);
+ if ($ok) {
+ print STDERR "ok\n";
+ last;
+ } elsif ($retries) {
+ print STDERR "failed, retrying\n";
+ usleep(1000000);
+ } else {
+ print STDERR "failed, giving up\n";
+ }
+ }
+
+ print STDERR @output;
+
+ die "$0: $espcmd[0] $cmd failed\n" unless ($ok);
+ return %outinfo;
+}
+
my @args = @ARGV;
my $esponly = 0;
my $file;
@@ -232,38 +308,9 @@ foreach my $e (keys %espopt) {
}
}
-$espopt{'port'} = $port;
-
-my @espcmd = unquote_cmd($esptool);
-foreach my $o (sort grep (!/^flash_/, keys %espopt)) {
- if (defined($espopt{$o})) {
- push(@espcmd, "--$o", $espopt{$o});
- }
-}
-push(@espcmd, 'write_flash', '-z');
-foreach my $o (sort grep (/^flash_/, keys %espopt)) {
- if (defined($espopt{$o})) {
- push(@espcmd, "--$o", $espopt{$o});
- }
-}
-
-push(@espcmd, @espfiles);
-
-print STDERR join(' ', @espcmd), "\n";
-my $retries = 4;
-my $err = 0;
-while ($retries--) {
- # Sometimes esptool tanks out for no reason, with error 256
- $err = system(@espcmd);
- last if ($err != 256);
- usleep(1000000);
-}
-if ($err == -1) {
- print STDERR "$0: $espcmd[0]: $!\n";
-} elsif ($err) {
- print STDERR "$0: $espcmd[0]: exit $err\n";
-}
-exit $err if ($err || $esponly);
+run_esptool($port, { hgrep sub {!/^flash_/}, %espopt },
+ 'write_flash', { hgrep sub {/^flash_/}, %espopt },
+ '-z', @espfiles);
my $SerialPort = eval {
require Device::SerialPort;
diff --git a/esp32/max80/boardinfo.c b/esp32/max80/boardinfo.c
new file mode 100644
index 0000000..9c00498
--- /dev/null
+++ b/esp32/max80/boardinfo.c
@@ -0,0 +1,169 @@
+#include "common.h"
+#include "boardinfo_esp.h"
+
+#include <esp_attr.h>
+#include <esp_flash.h>
+#include <esp_spi_flash.h>
+#include <esp_flash_internal.h> /* For esp_flash_app_disable_protect() */
+#include <rom/crc.h>
+
+#define BOARDINFO_ADDR 0 /* Flash address on ESP */
+
+union board_info_block {
+ struct board_info i;
+ uint8_t b[BOARDINFO_SIZE];
+};
+
+static const union board_info_block *board_info_flash;
+struct board_info DRAM_ATTR board_info;
+
+static spi_flash_mmap_handle_t board_info_handle;
+
+static const union board_info_block *board_info_map(void)
+{
+ if (!board_info_flash) {
+ const void *bifp;
+
+ if (spi_flash_mmap(BOARDINFO_ADDR, BOARDINFO_SIZE, SPI_FLASH_MMAP_DATA,
+ &bifp, &board_info_handle)
+ == ESP_OK)
+ board_info_flash = bifp;
+ }
+ return board_info_flash;
+}
+
+static void board_info_unmap(void)
+{
+ if (board_info_flash) {
+ spi_flash_munmap(board_info_handle);
+ board_info_flash = NULL;
+ board_info_handle = 0;
+ }
+}
+
+/* Returns 1 if the flash was modified, 0 if unmodified, -1 on error */
+int board_info_init(void)
+{
+ int err = -1;
+ uint32_t crc;
+ const union board_info_block *bif;
+
+ bif = board_info_map();
+ if (!bif)
+ goto unmapped;
+
+ if (bif->i.magic[0] != BOARDINFO_MAGIC_1 ||
+ bif->i.magic[1] != BOARDINFO_MAGIC_2 ||
+ bif->i.len < 16 || bif->i.len > BOARDINFO_SIZE ||
+ board_info.version_str[sizeof board_info.version_str - 1])
+ goto bad;
+
+ memcpy(&board_info, bif, sizeof board_info);
+
+ board_info.crc = 0;
+ crc = crc32_le(0, (const uint8_t *)&board_info, 16);
+ crc = crc32_le(crc, &bif->b[16], bif->i.len - 16);
+ board_info.crc = bif->i.crc;
+
+ if (crc != bif->i.crc)
+ goto bad;
+
+ printf("Board ID/version: %s\n", board_info.version_str);
+
+ err = 0;
+ goto done;
+
+bad:
+ if (!memcmp(board_info_flash->b, "MAX80 ", 6) &&
+ strnlen(board_info_flash->b, sizeof board_info.version_str)
+ < sizeof board_info.version_str) {
+ /*
+ * Contains board version string but nothing else; this
+ * is allowed to simplify the initial programming.
+ * Convert it to a proper structure and write it back.
+ */
+ printf("NOTE: updating board information block in flash\n");
+ return board_info_set((const char *)board_info_flash->b);
+ }
+
+unmapped:
+ printf("WARNING: no board ID/version string set in flash\n");
+ board_info.len = 0;
+
+done:
+ if (board_info.len < sizeof board_info)
+ memset((char *)&board_info + board_info.len, 0,
+ sizeof board_info - board_info.len);
+
+ board_info_unmap();
+ return err;
+}
+
+static int board_info_generate(const char *board_id_string)
+{
+ memset(&board_info, 0, sizeof board_info);
+ board_info.magic[0] = BOARDINFO_MAGIC_1;
+ board_info.magic[1] = BOARDINFO_MAGIC_1;
+ board_info.len = sizeof board_info;
+
+ strncpy(board_info.version_str, board_id_string,
+ sizeof board_info.version_str - 1);
+
+ memcpy(board_info.mac[0], efuse_default_mac, 6);
+
+ board_info.crc = crc32_le(0, (const uint8_t *)&board_info,
+ sizeof board_info);
+
+ return 0; /* For tailcalling convenience */
+}
+
+/* Must be in IRAM to allow flash operations */
+static int __noinline IRAM_ATTR board_info_update_flash(void)
+{
+ esp_err_t err;
+ int rv = -1;
+
+ /* The board_info table is in protected memory */
+ err = esp_flash_app_disable_protect(true);
+ if (err) {
+ printf("board_info_set: failed to unprotect flash (err 0x%x)\n", err);
+ goto err1;
+ }
+
+ err = esp_flash_erase_region(NULL, BOARDINFO_ADDR, BOARDINFO_SIZE);
+ if (err) {
+ printf("board_info_set: failed to erase board info flash region (err 0x%x)\n", err);
+ goto err2;
+ }
+
+ err = esp_flash_write(NULL, &board_info, BOARDINFO_ADDR, sizeof board_info);
+ if (err) {
+ printf("board_info_set: failed to write board info flash region (err 0x%x)\n", err);
+ goto err2;
+ }
+
+ rv = 0;
+
+err2:
+ esp_flash_app_disable_protect(false);
+err1:
+ return rv;
+}
+
+int board_info_set(const char *board_id_string)
+{
+ const union board_info_block *bif;
+
+ board_info_generate(board_id_string);
+ bif = board_info_map();
+ if (!bif)
+ return -1; /* Could not map board info */
+
+ bool unchanged = !memcmp(bif, &board_info, sizeof board_info);
+ board_info_unmap();
+
+ if (unchanged)
+ return 0; /* Not modified, so don't reflash */
+
+ board_info_update_flash();
+}
diff --git a/esp32/max80/boardinfo_esp.h b/esp32/max80/boardinfo_esp.h
new file mode 100644
index 0000000..b0139df
--- /dev/null
+++ b/esp32/max80/boardinfo_esp.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "common.h"
+#include "boardinfo.h"
+
+extern_c int board_info_init(void);
+extern_c int board_info_set(const char *board_id);
diff --git a/esp32/max80/common.h b/esp32/max80/common.h
index 58eb5a2..8940fe0 100644
--- a/esp32/max80/common.h
+++ b/esp32/max80/common.h
@@ -75,9 +75,9 @@ extern_c void reboot_now(void);
extern_c int reboot_delayed(void);
/*
- * Board version
+ * Main MAC address from efuses
*/
-extern_c uint8_t max80_board_version;
+extern_c uint8_t efuse_default_mac[6];
/*
* Time sync status
diff --git a/esp32/max80/max80.ino b/esp32/max80/max80.ino
index 8b2b6a7..5d14f0a 100644
--- a/esp32/max80/max80.ino
+++ b/esp32/max80/max80.ino
@@ -9,6 +9,7 @@
#include "config.h"
#include "led.h"
#include "tty.h"
+#include "boardinfo_esp.h"
#include <freertos/task_snapshot.h>
#include <esp_heap_caps.h>
@@ -21,9 +22,11 @@
#define PIN_USB_PWR_EN 7
#define PIN_USB_PWR_SINK 8
+uint8_t efuse_default_mac[6];
+
void setup_usb_ids()
{
- uint8_t mac[8];
+ uint8_t * const mac = efuse_default_mac;
char serial[16];
esp_efuse_mac_get_default(mac);
@@ -65,6 +68,9 @@ static void init_hw()
for (int i = 1; i <= 18; i++)
pinMode(i, INPUT);
+ // Query board info
+ board_info_init();
+
// Make sure FPGA nCE input (board 2.1+) is not accidentally pulled high
fpga_enable_nce();
diff --git a/esp32/max80/tty.cpp b/esp32/max80/tty.cpp
index b91cc16..60c33c7 100644
--- a/esp32/max80/tty.cpp
+++ b/esp32/max80/tty.cpp
@@ -7,11 +7,11 @@
#include "config.h"
#include "fw.h"
-#include "rom/crc.h"
-
#include <USB.h>
#include <HardwareSerial.h>
+#include <rom/crc.h>
+
#define SOH '\001'
#define STX '\002'
#define ETX '\003'
@@ -36,7 +36,7 @@
#define STREAMBUF_SIZE 2048
#define BUF_SLACK (STREAMBUF_SIZE >> 1)
-static char enq_str[] = "\026\035MAX80 v0\004\r\n";
+static char enq_str[] = "\026\035MAX80 ready\004\r\n";
static const char fwupload_start[] =
"\034\001: /// MAX80 FW UPLOAD ~@~ $\r\n\035";
@@ -446,8 +446,6 @@ static void uart_flush()
void TTY::init()
{
- enq_str[sizeof(enq_str)-5] += max80_board_version;
-
uart_tty = new TTY(Serial0);
uart_tty->_flush = uart_flush;
Serial0.begin(BAUD_RATE);
diff --git a/esp32/max80/wifi.cpp b/esp32/max80/wifi.cpp
index e4744e5..e3b9033 100644
--- a/esp32/max80/wifi.cpp
+++ b/esp32/max80/wifi.cpp
@@ -384,8 +384,10 @@ static void wifi_config_ap(void)
WiFi.softAPmacAddress(mac);
setenv_mac("status.net.ap.mac", mac);
- /* The last two bytes of the MAC */
- snprintf(ap_ssid, sizeof ap_ssid, "MAX80_%02X%02X", mac[4], mac[5]);
+
+ /* The last two bytes of the efuse MAC */
+ snprintf(ap_ssid, sizeof ap_ssid, "MAX80_%02X%02X",
+ efuse_default_mac[4], efuse_default_mac[5]);
printf("[WIFI] AP SSID %s IP %s netmask %s channel %u\n",
ap_ssid, AP_IP.toString(), AP_Netmask.toString(), channel);
diff --git a/esp32/output/max80.ino.bin b/esp32/output/max80.ino.bin
index dbaa62a..acc3fbd 100644
--- a/esp32/output/max80.ino.bin
+++ b/esp32/output/max80.ino.bin
Binary files differ
diff --git a/fpga/Makefile b/fpga/Makefile
index c8984ce..847a4c6 100644
--- a/fpga/Makefile
+++ b/fpga/Makefile
@@ -1,10 +1,13 @@
MAKEFLAGS += -R -r
PROJECT = max80
-REVISIONS = v1 v2 bypass
+REVISIONS = v1 v2
+EXTRAREVS = bypass
VARIANTS =
QU = quartus
+ALLREVS = $(REVISIONS) $(EXTRAREVS)
+
# Common options for all Quartus tools
QPRI = --lower_priority
QCPF = $(QU)_cpf $(QPRI)
@@ -32,7 +35,7 @@ BUILDDATE := $(shell LC_ALL=C date)
SUBDIRS = usb
PREREQFILES = $(mifdir)/sram.mif \
- $(foreach rev,$(REVISIONS),$(foreach coffmt,jic pof, \
+ $(foreach rev,$(ALLREVS),$(foreach coffmt,jic pof, \
$(outdir)/$(rev).$(coffmt).cof))
alltarg := sof pof jic svf svf.gz xsvf xsvf.gz rbf rbf.gz rpf rpf.gz \
@@ -45,6 +48,10 @@ allout = $(foreach o,$(alltarg),$(outdir)/$(1).$(o))
sram_src = ../rv32/
+MKFW = ../tools/mkfwimage.pl
+
+RPF_ADDR := 0
+
DRAM_ADDR := 0x100000
DRAM_IMAGE := ../rv32/dram.bin
@@ -60,6 +67,10 @@ ESP_TOOL_OPT := ../esp32/esptool.opt
IDCODE := 0x020f20dd # JTAG IDCODE of FPGA
+BOARDINFO_ADDR := 0xff000
+
+FWDEPS = $(MKFW) $(DRAM_IMAGE) $(ESP_IMAGE) $(ESP_BOOT_IMAGE) \
+ $(ESP_PART_IMAGE) $(ESP_OTACTL_IMAGE)
.SUFFIXES:
@@ -68,18 +79,18 @@ IDCODE := 0x020f20dd # JTAG IDCODE of FPGA
.DELETE_ON_ERROR:
all: prereq
- $(MAKE) $(REVISIONS:=.targets)
+ $(MAKE) $(ALLREVS:=.targets)
$(MAKE) fwimages
# $(MAKE) $(VARIANTS)
-# $(MAKE) $(REVISIONS:=.update)
+# $(MAKE) $(ALLREVS:=.update)
--include $(REVISIONS:=.deps)
+-include $(ALLREVS:=.deps)
.PHONY: fwimages
-fwimages: $(patsubst %,$(outdir)/%.fw,$(filter-out bypass,$(REVISIONS)))
+fwimages: $(patsubst %,$(outdir)/%.fw,$(REVISIONS) $(PROJECT))
-.PHONY: $(REVISIONS)
-$(REVISIONS): prereq
+.PHONY: $(ALLREVS)
+$(ALLREVS): prereq
$(MAKE) $@.targets
.PHONY: %.targets
@@ -99,7 +110,7 @@ $(VARIANTS):
$(MAKE) --old-file=$(outdir)/$*.fit.rpt $(call varout,$*)
$(outdir)/%/variant.stamp: mif/%.mif \
- $(foreach rev,$(REVISIONS),$(outdir)/$(rev).fit.rpt)
+ $(foreach rev,$(ALLREVS),$(outdir)/$(rev).fit.rpt)
-rm -rf var/$* $(outdir)/$*
mkdir -p var/$* var/$*/mif $(outdir)/$*
( cd $(outdir)/$* && ln -sf ../*.asm.rpt ../*.fit.rpt . )
@@ -107,7 +118,7 @@ $(outdir)/%/variant.stamp: mif/%.mif \
db incremental_db var/$*/
ln -f $< var/$*/mif/sram.mif
( cd var/$* && ln -sf ../../$(outdir)/$* ./output )
- $(MAKE) -C var/$* $(REVISIONS:=.vtargets)
+ $(MAKE) -C var/$* $(ALLREVS:=.vtargets)
echo '$(BUILDDATE)' > $@
$(outdir)/%.map.rpt: %.qsf | $(mifdir)/sram.mif
@@ -139,7 +150,7 @@ $(outdir)/%.sta.rpt: $(outdir)/%.fit.rpt | $(outdir)/%.sof
$(outdir)/%.pow.rpt: $(outdir)/%.sta.rpt | $(outdir)/%.sof
$(QPOW) $(PROJECT) -c $*
-$(foreach rev,$(REVISIONS),$(outdir)/$(rev).%.cof): %.cof.xml $(outdir)
+$(foreach rev,$(ALLREVS),$(outdir)/$(rev).%.cof): %.cof.xml $(outdir)
$(SED) -e 's/@@PROJECT@@/$(@F:.$*.cof=)/g' $< > $@
$(outdir)/%.jic: $(outdir)/%.jic.cof $(outdir)/%.sof ../rv32/dram.hex
@@ -180,14 +191,33 @@ $(outdir)/%.rpf: $(outdir)/%.rpd $(outdir)/%.z.rbf
$(outdir)/%.gz: $(outdir)/%
$(GZIP) -9 < $< > $@
+# New-style combined firmware image
+ALLRPFS = $(foreach rev,$(REVISIONS),$(outdir)/$(rev).rpf)
+everyrev = $(shell n=1; for r in $(REVISIONS); do echo "$(1)" -ver 0,0,$$n,$$n "$(2)"; n=$$((n+1)); done)
+$(outdir)/$(PROJECT).fw: $(ALLRPFS) $(FWDEPS)
+ $(PERL) $(MKFW) -o - -fwmin 2 \
+ $(call everyrev,-type target,-str 'MAX80 $$r') \
+ -type fpgainit -addr $(IDCODE) -file $(outdir)/bypass.rbf \
+ $(call everyrev,-type data -addr $(RPF_ADDR),-file $(outdir)/$$r.rpf) \
+ -type data -addr $(DRAM_ADDR) -file $(DRAM_IMAGE) \
+ -type boardinfo -addr $(BOARDINFO_ADDR) -empty \
+ -type esptool -optional -file $(ESP_TOOL_OPT) \
+ -type espota -addr $(ESP_ADDR) -file $(ESP_IMAGE) \
+ -type esppart -addr $(ESP_PART_ADDR) -file $(ESP_PART_IMAGE) \
+ -type espsys -addr $(ESP_BOOT_ADDR) -file $(ESP_BOOT_IMAGE) \
+ -type espsys -addr $(ESP_OTACTL_ADDR) -file $(ESP_OTACTL_IMAGE) \
+ | $(GZIP) -9 > $@
+
+# Old-style single version firmware images
$(outdir)/%.fw: $(outdir)/%.rpf $(outdir)/bypass.rbf \
$(DRAM_IMAGE) $(ESP_IMAGE) \
../tools/mkfwimage.pl
$(PERL) ../tools/mkfwimage.pl -o - \
-type target -str 'MAX80 $*' \
-type fpgainit -addr $(IDCODE) -optional -file $(outdir)/bypass.rbf \
- -type data -addr 0 -file $< \
+ -type data -addr $(RPF_ADDR) -file $< \
-type data -addr $(DRAM_ADDR) -file $(DRAM_IMAGE) \
+ -type boardinfo -optional -addr $(BOARDINFO_ADDR) -empty \
-type esptool -optional -file $(ESP_TOOL_OPT) \
-type espota -addr $(ESP_ADDR) -file $(ESP_IMAGE) \
-type esppart -optional -addr $(ESP_PART_ADDR) -file $(ESP_PART_IMAGE) \
@@ -235,7 +265,7 @@ clean:
for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done
rm -rf db incremental_db var simulation/modelsim \
greybox_tmp */greybox_tmp iodevs.vh output_files $(mifdir)
- for d in $(REVISIONS); do \
+ for d in $(ALLREVS); do \
rm -f $$d/*.rpt $$d/*.rpt $$d/*.summary $$d/*.smsg \
$$d/*.htm $$d/*.htm_files $$d/*.map $$d/*.eqn $$d/*.sld \
$$d/*.done ; \
diff --git a/fpga/max80.qpf b/fpga/max80.qpf
index 1694235..21cd306 100644
--- a/fpga/max80.qpf
+++ b/fpga/max80.qpf
@@ -19,12 +19,12 @@
#
# Quartus Prime
# Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
-# Date created = 11:36:55 August 08, 2022
+# Date created = 12:16:48 August 18, 2022
#
# -------------------------------------------------------------------------- #
QUARTUS_VERSION = "21.1"
-DATE = "11:36:55 August 08, 2022"
+DATE = "12:16:48 August 18, 2022"
# Revisions
diff --git a/fpga/output/bypass.jic b/fpga/output/bypass.jic
index 8a3274d..191698c 100644
--- a/fpga/output/bypass.jic
+++ b/fpga/output/bypass.jic
Binary files differ
diff --git a/fpga/output/bypass.rpd.gz b/fpga/output/bypass.rpd.gz
index 5ba2c79..6a25d0e 100644
--- a/fpga/output/bypass.rpd.gz
+++ b/fpga/output/bypass.rpd.gz
Binary files differ
diff --git a/fpga/output/max80.fw b/fpga/output/max80.fw
new file mode 100644
index 0000000..b62aa81
--- /dev/null
+++ b/fpga/output/max80.fw
Binary files differ
diff --git a/fpga/output/v1.fw b/fpga/output/v1.fw
index 1e17b75..aed2e42 100644
--- a/fpga/output/v1.fw
+++ b/fpga/output/v1.fw
Binary files differ
diff --git a/fpga/output/v1.jic b/fpga/output/v1.jic
index 7289428..c420099 100644
--- a/fpga/output/v1.jic
+++ b/fpga/output/v1.jic
Binary files differ
diff --git a/fpga/output/v1.rbf.gz b/fpga/output/v1.rbf.gz
index 33a6018..e1e3c3f 100644
--- a/fpga/output/v1.rbf.gz
+++ b/fpga/output/v1.rbf.gz
Binary files differ
diff --git a/fpga/output/v1.rpd.gz b/fpga/output/v1.rpd.gz
index a2533e5..dec5436 100644
--- a/fpga/output/v1.rpd.gz
+++ b/fpga/output/v1.rpd.gz
Binary files differ
diff --git a/fpga/output/v1.sof b/fpga/output/v1.sof
index 318c90b..0160487 100644
--- a/fpga/output/v1.sof
+++ b/fpga/output/v1.sof
Binary files differ
diff --git a/fpga/output/v1.svf.gz b/fpga/output/v1.svf.gz
index db08ba5..3ec1342 100644
--- a/fpga/output/v1.svf.gz
+++ b/fpga/output/v1.svf.gz
Binary files differ
diff --git a/fpga/output/v1.xsvf.gz b/fpga/output/v1.xsvf.gz
index b9da3f6..132f0fa 100644
--- a/fpga/output/v1.xsvf.gz
+++ b/fpga/output/v1.xsvf.gz
Binary files differ
diff --git a/fpga/output/v2.fw b/fpga/output/v2.fw
index a4c287d..a6cf76b 100644
--- a/fpga/output/v2.fw
+++ b/fpga/output/v2.fw
Binary files differ
diff --git a/fpga/output/v2.jic b/fpga/output/v2.jic
index 9d1e745..77fe7c6 100644
--- a/fpga/output/v2.jic
+++ b/fpga/output/v2.jic
Binary files differ
diff --git a/fpga/output/v2.rbf.gz b/fpga/output/v2.rbf.gz
index 8f61102..55f85da 100644
--- a/fpga/output/v2.rbf.gz
+++ b/fpga/output/v2.rbf.gz
Binary files differ
diff --git a/fpga/output/v2.rpd.gz b/fpga/output/v2.rpd.gz
index 6fa13cb..7189320 100644
--- a/fpga/output/v2.rpd.gz
+++ b/fpga/output/v2.rpd.gz
Binary files differ
diff --git a/fpga/output/v2.sof b/fpga/output/v2.sof
index 1f91255..e8ae8d9 100644
--- a/fpga/output/v2.sof
+++ b/fpga/output/v2.sof
Binary files differ
diff --git a/fpga/output/v2.svf.gz b/fpga/output/v2.svf.gz
index 542bb0d..ca40006 100644
--- a/fpga/output/v2.svf.gz
+++ b/fpga/output/v2.svf.gz
Binary files differ
diff --git a/fpga/output/v2.xsvf.gz b/fpga/output/v2.xsvf.gz
index 15d6975..24f811b 100644
--- a/fpga/output/v2.xsvf.gz
+++ b/fpga/output/v2.xsvf.gz
Binary files differ
diff --git a/rv32/checksum.h b/rv32/checksum.h
index 00f6169..55e5f04 100644
--- a/rv32/checksum.h
+++ b/rv32/checksum.h
@@ -1,4 +1,4 @@
#ifndef CHECKSUM_H
#define CHECKSUM_H
-#define SDRAM_SUM 0xa53dbdef
+#define SDRAM_SUM 0x1ce2ddf4
#endif
diff --git a/rv32/spiflash.c b/rv32/spiflash.c
index 1c35096..223a4b3 100644
--- a/rv32/spiflash.c
+++ b/rv32/spiflash.c
@@ -627,6 +627,7 @@ static int spiflash_process_chunk(spz_stream *spz)
return spiflash_flash_chunk(spz);
case FDT_TARGET:
str = spiflash_read_chunk_str(spz);
+ /* XXX: replace with proper matching algorithm */
if (!str || strcmp(str, spz->flash->target)) {
MSG("update: this firmware file targets \"%s\", need \"%s\"\n",
str, spz->flash->target);
@@ -637,12 +638,17 @@ static int spiflash_process_chunk(spz_stream *spz)
str = spiflash_read_chunk_str(spz);
MSG("update: %s\n", str);
break;
- case FDT_FPGA_INIT: /* Used only when flashing from ESP32 */
- return spiflash_skip_chunk(spz);
case FDT_ESP_OTA:
if (!spz->flash->ops)
goto skip;
return esp_ota_chunk(spz);
+ case FDT_FPGA_INIT:
+ case FDT_ESP_PART:
+ case FDT_ESP_SYS:
+ case FDT_ESP_TOOL:
+ case FDT_BOARDINFO:
+ /* Used only when flashing from ESP32 */
+ goto skip;
default:
if (spz->header.flags & FDF_OPTIONAL)
goto skip;
diff --git a/tools/mkfwimage.pl b/tools/mkfwimage.pl
index 088f638..5ea4900 100755
--- a/tools/mkfwimage.pl
+++ b/tools/mkfwimage.pl
@@ -3,18 +3,19 @@
use strict;
use integer;
-my $FW_MAGIC = 0x7a07fbd6;
+my @FW_MAGIC = (undef, 0x7a07fbd6, 0xa924ed0b);
my %datatypes = (
'end' => 0, # End of data
'data' => 1, # FPGA flash data
- 'target' => 2, # Firmware target string
+ 'target' => 2, # Firmware target mask and string
'note' => 3, # Informative string
'espota' => 4, # ESP32 OTA image
'fpgainit' => 5, # FPGA bypass (transient) image during update
'esppart' => 6, # ESP32 partition table
'espsys' => 7, # ESP32 boot loader, OTA control partition...
- 'esptool' => 8 # esptool.py options for flashing
+ 'esptool' => 8, # esptool.py options for flashing
+ 'boardinfo' => 9 # board_info block address (FPGA)
);
my @type;
foreach my $t (keys(%datatypes)) {
@@ -25,29 +26,15 @@ my $FDF_OPTIONAL = 0x0001;
my $STRING_MAX_LEN = 4095;
+my %int_shifts = ('' => 0, 'k' => 10, 'm' => 20, 'g' => 30,
+ 't' => 40, 'p' => 50, 'e' => 60);
sub getint($) {
my($s) = @_;
return undef
- unless ($s =~ /^(([1-9][0-9]+)|(0(x[0-9a-f]+|[0-7]*)))([kmgtpe]?)$/i);
-
- my $o = oct($3) + $2;
- my $p = lc($5);
-
- if ($p eq 'k') {
- $o <<= 10;
- } elsif ($p eq 'm') {
- $o <<= 20;
- } elsif ($p eq 'g') {
- $o <<= 30;
- } elsif ($p eq 't') {
- $o <<= 40;
- } elsif ($p eq 'p') {
- $o <<= 50;
- } elsif ($p eq 'e') {
- $o <<= 60;
- }
- return $o;
+ unless ($s =~ /^(([1-9][0-9]*)|(0(x[0-9a-f]+|[0-7]*)))([kmgtpe]?)$/i);
+
+ return (oct($3) + $2) << $int_shifts{lc($5)};
}
sub filelen($) {
my($f) = @_;
@@ -55,31 +42,78 @@ sub filelen($) {
return $s[7];
}
-sub output_chunk($$$) {
- my($out,$data,$options) = @_;
-
- print $out pack("VvvVV", $FW_MAGIC,
- $options->{'type'}, $options->{'flags'},
- length($data), $options->{'addr'});
- printf STDERR "chunk: type %s (%u) flags 0x%x length %u addr 0x%x\n",
- $type[$options->{'type'}], $options->{'type'}, $options->{'flags'},
- length($data), $options->{'addr'};
+
+our $outfile;
+
+my %default_options = (
+ 'fw' => 1, # Default and minimum firmware version
+ 'type' => $datatypes{'data'},
+ 'addr' => 0,
+ 'flags' => 0,
+ 'vmatch' => 0,
+ 'vmask' => 0,
+ 'vmin' => 0,
+ 'vmax' => 0xffff
+);
+my %need_versions = (
+ 'vmatch' => 2, 'vmask' => 2, 'vmin' => 2, 'vmax' => 2
+);
+
+sub output_chunk($$%) {
+ my($out,$data,%opts) = @_;
+
+ foreach my $o (keys(%default_options)) {
+ $opts{$o} = $default_options{$o} unless (defined($opts{$o}));
+ }
+
+ $opts{'vmatch'} &= 0xffffffff;
+ $opts{'vmask'} &= 0xffffffff;
+ $opts{'vmin'} &= 0xffff;
+ $opts{'vmax'} &= 0xffff;
+
+ my $version = $opts{'fw'};
+ foreach my $o (keys(%need_versions)) {
+ if ($opts{$o} ne $default_options{$o} &&
+ $need_versions{$o} > $version) {
+ $version = $need_versions{$o};
+ }
+ }
+
+ if (!defined($FW_MAGIC[$version])) {
+ die "$0: $outfile: invalid firmware format version: $version\n";
+ }
+
+ print $out pack('VvvVV',
+ $FW_MAGIC[$version],
+ $opts{'type'}, $opts{'flags'},
+ length($data), $opts{'addr'});
+ if ($version >= 2) {
+ print $out pack('VVvvV',
+ $opts{'vmatch'}, $opts{'vmask'},
+ $opts{'vmin'}, $opts{'vmax'},
+ 0);
+ }
+ printf STDERR "chunk: fw %u type %s (%u) flags 0x%x length %u addr 0x%x ver 0x%x/0x%x,%u:%u\n",
+ $version, $type[$opts{'type'}], $opts{'type'},
+ $opts{'flags'},
+ length($data), $opts{'addr'},
+ $opts{'vmatch'}, $opts{'vmask'}, $opts{'vmin'}, $opts{'vmax'};
print $out $data;
}
if (!scalar(@ARGV)) {
- die "Usage: $0 [-o outfile] [options command]...\n".
+ die "Usage: $0 [-o outfile] [-fwmin ver] [options command]...\n".
"Options:\n".
"\t-type datatype\n".
"\t-addr address (or equivalent)\n".
"\t-optional\n".
"\t-required\n".
+ "\t-ver match,mask,min,max\n".
"Commands:\n".
"\t-file data_file\n".
"\t-str data_string\n";
}
-our $outfile;
our $out;
sub delete_out {
@@ -106,13 +140,8 @@ if ($outfile ne '' && $outfile ne '-') {
binmode $out;
-my %default_options = {
- 'type' => $datatypes{'data'},
- 'addr' => 0,
- 'flags' => 0
-};
my $err;
-my %options = %default_options;
+my %options = ();
while (1) {
my $what = shift @ARGV;
@@ -120,13 +149,24 @@ while (1) {
if ($what eq '-type') {
my $arg = lc(shift @ARGV);
- $options{'type'} = $datatypes{$arg} || getint($arg);
- if (!$arg) {
+ my $type = defined($datatypes{$arg}) ? $datatypes{$arg} : getint($arg);
+ if (!defined($type)) {
die "$0: invalid data type: $arg";
}
- } elsif ($what eq '-addr') {
- my $arg = shift @ARGV;
- $options{'addr'} = getint($arg);
+ $options{'type'} = $type;
+ } elsif ($what =~ /^\-(addr|flags|vmatch|vmask|vmin|vmax)$/) {
+ my $opt = $1;
+ my $arg = getint(shift @ARGV);
+ $options{$opt} = $arg if (defined($arg));
+ } elsif ($what eq '-fwmin') {
+ my $arg = getint(shift @ARGV);
+ $default_options{'fw'} = $arg if (defined($arg));
+ } elsif ($what eq '-ver') {
+ my @vp = split(/(?:[,:\/]|\.+)/, shift @ARGV);
+ foreach my $opt (qw(vmatch vmask vmin vmax)) {
+ my $arg = getint(shift @vp);
+ $options{$opt} = $arg if (defined($arg));
+ }
} elsif ($what eq '-optional') {
$options{'flags'} |= $FDF_OPTIONAL;
} elsif ($what eq '-required') {
@@ -144,23 +184,24 @@ while (1) {
my $dlen = read($in, $data, $is[7]);
close($in);
- output_chunk($out, $data, \%options);
- undef $data;
-
- %options = %default_options;
+ output_chunk($out, $data, %options);
+ %options = ();
} elsif ($what eq '-str') {
my $str = shift @ARGV;
if (length($str) > $STRING_MAX_LEN) {
- die "$0: string too long\n";
+ die "$0: $outfile: string too long\n";
}
- output_chunk($out, $str, \%options);
- %options = %default_options;
+ output_chunk($out, $str, %options);
+ %options = ();
+ } elsif ($what eq '-empty') {
+ output_chunk($out, '', %options);
+ %options = ();
} else {
die "$0: unknown argument: $what\n";
}
}
-output_chunk($out, '', {'type' => $datatypes{'end'}});
+output_chunk($out, '', ('type' => $datatypes{'end'}));
close($out);