diff options
author | H. Peter Anvin <hpa@zytor.com> | 2022-08-18 14:33:12 -0700 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2022-08-18 14:36:22 -0700 |
commit | 75e32256b898b2d04c546c6ebf73f6d129e131bb (patch) | |
tree | f94b2ac660b4c1a3d0aca9e9c1668828f7bd86d7 | |
parent | f9c92995ca03dc97e77558934713903db9c85d27 (diff) | |
download | blinktest-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-- | Makefile | 20 | ||||
-rw-r--r-- | common/boardinfo.h | 25 | ||||
-rw-r--r-- | common/fwimg.h | 3 | ||||
-rw-r--r-- | common/matchver.c | 192 | ||||
-rwxr-xr-x | esp32/flashesp.pl | 113 | ||||
-rw-r--r-- | esp32/max80/boardinfo.c | 169 | ||||
-rw-r--r-- | esp32/max80/boardinfo_esp.h | 7 | ||||
-rw-r--r-- | esp32/max80/common.h | 4 | ||||
-rw-r--r-- | esp32/max80/max80.ino | 8 | ||||
-rw-r--r-- | esp32/max80/tty.cpp | 8 | ||||
-rw-r--r-- | esp32/max80/wifi.cpp | 6 | ||||
-rw-r--r-- | esp32/output/max80.ino.bin | bin | 796784 -> 798016 bytes | |||
-rw-r--r-- | fpga/Makefile | 56 | ||||
-rw-r--r-- | fpga/max80.qpf | 4 | ||||
-rw-r--r-- | fpga/output/bypass.jic | bin | 16777443 -> 16777443 bytes | |||
-rw-r--r-- | fpga/output/bypass.rpd.gz | bin | 64733 -> 64732 bytes | |||
-rw-r--r-- | fpga/output/max80.fw | bin | 0 -> 921747 bytes | |||
-rw-r--r-- | fpga/output/v1.fw | bin | 760519 -> 761060 bytes | |||
-rw-r--r-- | fpga/output/v1.jic | bin | 16777443 -> 16777443 bytes | |||
-rw-r--r-- | fpga/output/v1.rbf.gz | bin | 161044 -> 161031 bytes | |||
-rw-r--r-- | fpga/output/v1.rpd.gz | bin | 216183 -> 218479 bytes | |||
-rw-r--r-- | fpga/output/v1.sof | bin | 509096 -> 509096 bytes | |||
-rw-r--r-- | fpga/output/v1.svf.gz | bin | 183083 -> 183091 bytes | |||
-rw-r--r-- | fpga/output/v1.xsvf.gz | bin | 165428 -> 165410 bytes | |||
-rw-r--r-- | fpga/output/v2.fw | bin | 761491 -> 762014 bytes | |||
-rw-r--r-- | fpga/output/v2.jic | bin | 16777443 -> 16777443 bytes | |||
-rw-r--r-- | fpga/output/v2.rbf.gz | bin | 161584 -> 161574 bytes | |||
-rw-r--r-- | fpga/output/v2.rpd.gz | bin | 217922 -> 217873 bytes | |||
-rw-r--r-- | fpga/output/v2.sof | bin | 509096 -> 509096 bytes | |||
-rw-r--r-- | fpga/output/v2.svf.gz | bin | 184083 -> 184044 bytes | |||
-rw-r--r-- | fpga/output/v2.xsvf.gz | bin | 165775 -> 165783 bytes | |||
-rw-r--r-- | rv32/checksum.h | 2 | ||||
-rw-r--r-- | rv32/spiflash.c | 10 | ||||
-rwxr-xr-x | tools/mkfwimage.pl | 145 |
34 files changed, 654 insertions, 118 deletions
@@ -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 Binary files differindex dbaa62a..acc3fbd 100644 --- a/esp32/output/max80.ino.bin +++ b/esp32/output/max80.ino.bin 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 Binary files differindex 8a3274d..191698c 100644 --- a/fpga/output/bypass.jic +++ b/fpga/output/bypass.jic diff --git a/fpga/output/bypass.rpd.gz b/fpga/output/bypass.rpd.gz Binary files differindex 5ba2c79..6a25d0e 100644 --- a/fpga/output/bypass.rpd.gz +++ b/fpga/output/bypass.rpd.gz diff --git a/fpga/output/max80.fw b/fpga/output/max80.fw Binary files differnew file mode 100644 index 0000000..b62aa81 --- /dev/null +++ b/fpga/output/max80.fw diff --git a/fpga/output/v1.fw b/fpga/output/v1.fw Binary files differindex 1e17b75..aed2e42 100644 --- a/fpga/output/v1.fw +++ b/fpga/output/v1.fw diff --git a/fpga/output/v1.jic b/fpga/output/v1.jic Binary files differindex 7289428..c420099 100644 --- a/fpga/output/v1.jic +++ b/fpga/output/v1.jic diff --git a/fpga/output/v1.rbf.gz b/fpga/output/v1.rbf.gz Binary files differindex 33a6018..e1e3c3f 100644 --- a/fpga/output/v1.rbf.gz +++ b/fpga/output/v1.rbf.gz diff --git a/fpga/output/v1.rpd.gz b/fpga/output/v1.rpd.gz Binary files differindex a2533e5..dec5436 100644 --- a/fpga/output/v1.rpd.gz +++ b/fpga/output/v1.rpd.gz diff --git a/fpga/output/v1.sof b/fpga/output/v1.sof Binary files differindex 318c90b..0160487 100644 --- a/fpga/output/v1.sof +++ b/fpga/output/v1.sof diff --git a/fpga/output/v1.svf.gz b/fpga/output/v1.svf.gz Binary files differindex db08ba5..3ec1342 100644 --- a/fpga/output/v1.svf.gz +++ b/fpga/output/v1.svf.gz diff --git a/fpga/output/v1.xsvf.gz b/fpga/output/v1.xsvf.gz Binary files differindex b9da3f6..132f0fa 100644 --- a/fpga/output/v1.xsvf.gz +++ b/fpga/output/v1.xsvf.gz diff --git a/fpga/output/v2.fw b/fpga/output/v2.fw Binary files differindex a4c287d..a6cf76b 100644 --- a/fpga/output/v2.fw +++ b/fpga/output/v2.fw diff --git a/fpga/output/v2.jic b/fpga/output/v2.jic Binary files differindex 9d1e745..77fe7c6 100644 --- a/fpga/output/v2.jic +++ b/fpga/output/v2.jic diff --git a/fpga/output/v2.rbf.gz b/fpga/output/v2.rbf.gz Binary files differindex 8f61102..55f85da 100644 --- a/fpga/output/v2.rbf.gz +++ b/fpga/output/v2.rbf.gz diff --git a/fpga/output/v2.rpd.gz b/fpga/output/v2.rpd.gz Binary files differindex 6fa13cb..7189320 100644 --- a/fpga/output/v2.rpd.gz +++ b/fpga/output/v2.rpd.gz diff --git a/fpga/output/v2.sof b/fpga/output/v2.sof Binary files differindex 1f91255..e8ae8d9 100644 --- a/fpga/output/v2.sof +++ b/fpga/output/v2.sof diff --git a/fpga/output/v2.svf.gz b/fpga/output/v2.svf.gz Binary files differindex 542bb0d..ca40006 100644 --- a/fpga/output/v2.svf.gz +++ b/fpga/output/v2.svf.gz diff --git a/fpga/output/v2.xsvf.gz b/fpga/output/v2.xsvf.gz Binary files differindex 15d6975..24f811b 100644 --- a/fpga/output/v2.xsvf.gz +++ b/fpga/output/v2.xsvf.gz 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); |