瀏覽代碼

Board 2.1 support; Full FW install via serial port or ESP32 USB (preliminary)

- Add handling of the nCE pin (a.k.a. JTAG_ENABLE) for board rev 2.1.

- Fix a race condition in fpga/Makefile.

- Full firmware flash via physical serial port or JTAG USB serial
  port. Currently somewhat unreliable due to flaws in the Perl
  serial port library (Device::SerialPort or Win32::SerialPort); will
  probably need to replace with C or Python code or by some more low
  level hacking.

  Note: the *.fw files now include ALL flashable information,
  including the esp32 boot loader, partition tables, and parameters
  needed for the ESP32 non-OTA flash.
H. Peter Anvin 2 年之前
父節點
當前提交
fc450b42f2
共有 31 個文件被更改,包括 397 次插入104 次删除
  1. 9 3
      Makefile
  2. 218 11
      esp32/flashesp.pl
  3. 3 1
      esp32/max80/fpga.h
  4. 51 10
      esp32/max80/fpgajtag.c
  5. 1 1
      esp32/max80/fpgasvc.c
  6. 57 18
      esp32/max80/fwupdate.c
  7. 5 2
      esp32/max80/max80.ino
  8. 40 44
      esp32/max80/tty.cpp
  9. 2 3
      esp32/max80/tty.h
  10. 二進制
      esp32/output/max80.ino.bin
  11. 8 8
      fpga/Makefile
  12. 3 3
      fpga/max80.qpf
  13. 二進制
      fpga/output/bypass.rbf.gz
  14. 二進制
      fpga/output/bypass.rpd.gz
  15. 二進制
      fpga/output/bypass.sof
  16. 二進制
      fpga/output/bypass.svf.gz
  17. 二進制
      fpga/output/bypass.xsvf.gz
  18. 二進制
      fpga/output/v1.fw
  19. 二進制
      fpga/output/v1.jic
  20. 二進制
      fpga/output/v1.rbf.gz
  21. 二進制
      fpga/output/v1.rpd.gz
  22. 二進制
      fpga/output/v1.sof
  23. 二進制
      fpga/output/v1.svf.gz
  24. 二進制
      fpga/output/v1.xsvf.gz
  25. 二進制
      fpga/output/v2.fw
  26. 二進制
      fpga/output/v2.jic
  27. 二進制
      fpga/output/v2.rbf.gz
  28. 二進制
      fpga/output/v2.rpd.gz
  29. 二進制
      fpga/output/v2.sof
  30. 二進制
      fpga/output/v2.svf.gz
  31. 二進制
      fpga/output/v2.xsvf.gz

+ 9 - 3
Makefile

@@ -6,6 +6,8 @@ REVISIONS := v1 v2 bypass
 
 GIT_DIR   ?= .git
 
+MAX80_IP  ?= max80
+
 all clean spotless :
 	$(MAKE) local.$@ $(SUBDIRS) goal=$@
 
@@ -68,7 +70,11 @@ program-% flash-%: prefpga
 upload-esp:
 	$(MAKE) -C esp32 upload
 
-# Update via HTTP
-ip=max80
+# Update via HTTP or serial port
+ip = $(MAX80_IP)
 upload-%:
-	curl -v --data-binary @fpga/output/$*.fw http://$(ip)/sys/fwupdate
+	if [ -z '$(PORT)' ]; then \
+		curl -v --data-binary @fpga/output/$*.fw http://$(ip)/sys/fwupdate ; \
+	else ; \
+		$(PERL) ./esp32/flashesp.pl --port $(PORT) fpga/output/$*.fw ; \
+	fi

+ 218 - 11
esp32/flashesp.pl

@@ -5,6 +5,8 @@ use integer;
 use PerlIO::gzip;
 use File::Temp;
 use File::Spec;
+use Time::HiRes qw(usleep tv_interval);
+use Digest::CRC qw(crc32);
 
 my $esptool = ($^O eq 'MSWin32') ? 'esptool.exe' : 'esptool.py';
 $esptool = $ENV{'ESPTOOL'} || $esptool;
@@ -77,11 +79,40 @@ sub unquote_cmd($) {
 }
 
 my @args = @ARGV;
-my $file = shift(@args);
+my $esponly = 0;
+my $file;
+while (1) {
+    $file = shift(@args);
+    last if ($file !~ /^\-/);
+
+    if ($file eq '--esponly') {
+	$esponly = 1;
+    } elsif ($file eq '--') {
+	$file = shift(@args);
+	last;
+    } else {
+	undef $file;		# Invalid argument, print usage
+	last;
+    }
+}
+
+my $port = shift(@args);
+if (!defined($port)) {
+    die "Usage: $0 file.fw port [esptool options...]\n";
+}
 
-if (!defined($file)) {
-    die "Usage: $0 fwfile [esptool options...]\n";
+if (!File::Spec->file_name_is_absolute($port)) {
+    if (-c "/dev/$port") {
+	$port = "/dev/$port";
+    } elsif (-c "/dev/tty$port") {
+	$port = "/dev/tty$port";
+    } elsif ($^O eq 'MSWin32') {
+	$port = "\\\\.\\$port";
+    } else {
+	die "$0: no such serial port: $port\n";
+    }
 }
+print STDERR "Using serial port device $port\n";
 
 open(my $fw, '<:gzip', $file)
     or die "$0: $file: $!\n";
@@ -103,7 +134,6 @@ while (read($fw, $hdr, 16) == 16) {
     }
 
     my $t = $type[$c->{'type'}];
-    last if ($t eq 'end'); # End of stream
 
     my $d;
     if (read($fw, $d, $c->{'len'}) != $c->{'len'}) {
@@ -112,8 +142,9 @@ while (read($fw, $hdr, 16) == 16) {
 	last;
     }
     $c->{'data'} = $d;
-
     push(@chunks, $c);
+
+    last if ($t eq 'end'); # End of stream
 }
 
 close($fw);
@@ -127,6 +158,10 @@ my %espopt = ('before' => 'default_reset', 'after' => 'hard_reset',
 	      'flash_mode' => undef, 'flash_freq' => undef,
 	      'flash_size' => undef);
 
+# Create a compressed data buffer without the ESP32 chunks
+my $fpgadata;
+open(my $fpgafh, '>:gzip', \$fpgadata) or die;
+
 my $nc = 0;
 foreach my $c ( @chunks ) {
     my $t = $type[$c->{'type'}];
@@ -144,10 +179,15 @@ foreach my $c ( @chunks ) {
 	print $tf $c->{'data'};
 	close($tf);
 	push(@espfiles, '0x'.$addr, $tff);
+    } else {
+	print $fpgafh $c->{'hdr'};
+	print $fpgafh $c->{'data'};
     }
     $nc++;
 }
 
+close($fpgafh);
+
 foreach my $e (keys %espopt) {
     my $ev = $ENV{"ESP\U$e"};
     if (defined($ev)) {
@@ -155,28 +195,195 @@ 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, @args);
 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);
 
-if (defined($ENV{'VERBOSE'})) {
-    print STDERR join(' ', @espcmd), "\n";
-}
+push(@espcmd, @espfiles);
 
+print STDERR join(' ', @espcmd), "\n";
 $err = system(@espcmd);
 if ($err == -1) {
     print STDERR "$0: $espcmd[0]: $!\n";
+} elsif ($err) {
+    print STDERR "$0: $espcmd[0]: exit $err\n";
+}
+exit $err if ($err || $esponly);
+
+my $SerialPort = eval {
+    require Device::SerialPort;
+    Device::SerialPort->import(qw(:PARAM));
+    'Device::SerialPort';
+} || eval {
+    require Win32::SerialPort;
+    Win32::SerialPort->import(qw(:STAT));
+    'Win32::SerialPort';
+} || die "$0: need Device::SerialPort or Win32::SerialPort\n";
+
+print STDERR "Waiting for reinit...\n";
+usleep(4000000);
+
+my $tty = $SerialPort->new($port);
+die "$0: $port: $!\n" if (!$tty);
+
+$tty->buffers($tty->buffer_max);
+$tty->reset_error;
+
+$tty->user_msg(1);
+$tty->error_msg(1);
+
+$tty->handshake('none');
+$tty->databits(8);
+$tty->baudrate(115200);
+$tty->parity('none');
+$tty->stopbits(1);
+$tty->datatype('raw');
+$tty->stty_ignbrk(0);
+#$tty->stty_brkint(0);
+$tty->stty_parmrk(0);
+$tty->stty_istrip(0);
+$tty->stty_inlcr(0);
+$tty->stty_igncr(0);
+$tty->stty_icrnl(0);
+$tty->stty_opost(0);
+$tty->stty_echo(0);
+$tty->stty_echonl(0);
+$tty->stty_icanon(0);
+$tty->stty_isig(0);
+#$tty->stty_iexten(0);
+$tty->read_char_time(0);
+$tty->write_settings;
+
+# In case DTR and/or RTS was asserted on the physical serial port.
+# Note that DTR needs to be deasserted before RTS!
+# However, deasserting DTR on the ACM port prevents it from working,
+# so only do this if we don't see CTS (which is always enabled on ACM)...
+if ($tty->can_modemlines && !($tty->modemlines & $tty->MS_CTS_ON)) {
+    usleep(100000);
+    $tty->dtr_active(0);
+    usleep(100000);
+    $tty->rts_active(0);
+    usleep(100000);
+}
+
+sub tty_read {
+    my($tty,$bufref,$timeout) = @_;
+    my $d = $$bufref;
+
+    if ($d eq '') {
+	my $c;
+	$tty->read_const_time($timeout);
+	($c,$d) = $tty->read(256);
+	return '' if (!$c);
+    }
+
+    my $r = substr($d,0,1);
+    $$bufref = substr($d,1);
+
+    return $r;
 }
+my $ttybuf = '';
+
+sub tty_write($$) {
+    my($tty,$data) = @_;
+    my $bytes = length($data);
+    my $offs  = 0;
+
+    while ($bytes) {
+	my $cnt = $tty->write(substr($data,$offs,$bytes));
+	usleep(10000) unless ($cnt);
+	$offs  += $cnt;
+	$bytes -= $cnt;
+    }
+
+    return $offs;
+}
+
+my $found = 0;
+my $tt = time();
+my $start_enq = $tt;
+while (($tt = time()) - $start_enq < 30) {
+    my $d = tty_read($tty, \$ttybuf, 1000);
+    if ($d eq '') {
+	tty_write($tty, "\005");	# ENQ
+    } else {
+	my $ix = index("\026\004\027", $d);
+	if ($ix < 0) {
+	    print STDERR $d;
+	    next;
+	}
+	$found = $found == $ix ? $found+1 : 0;
+	last if ($found == 3);
+    }
+}
+
+if ($found < 3) {
+    die "$0: $port: no MAX80 ESP32 detected\n";
+}
+
+$start_enq = $tt;
+my $winspc;
+while (!defined($winspc)) {
+    $tt = time();
+    if ($tt - $start_enq >= 10) {
+	die "$0: $port: failed to start FPGA firmware upload\n";
+    }
+    tty_write($tty, "\034\001: /// MAX80 FW UPLOAD \~\@\~ \$\r\n\035");
+    my $d;
+    do {
+	$d = tty_read($tty, \$ttybuf, 1000);
+	if ($d eq "\036") {
+	    $winspc = 0;
+	    last;
+	} else {
+	    print $d;
+	}
+    } while ($d ne '');
+}
+
+my $bytes  = length($fpgadata);
+my $offset = 0;
+while ($offset < $bytes) {
+    my $chunk = $bytes - $offset;
+    $chunk = 64 if ($chunk > 64);
+    $chunk = $winspc if ($chunk > $winspc);
+
+    my $d = tty_read($tty, \$ttybuf, $chunk ? undef : 1000);
+    if ($d ne '') {
+	if ($d eq "\022") {
+	    $winspc = 0;
+	} elsif ($d eq "\024") {
+	    $winspc += 256;
+	} elsif ($d eq "\027" || $d eq "\030" || $d eq "\025") {
+	    die "$0: $port: upload terminated by MAX80\n";
+	} elsif ($d ne "\006") {
+	    print STDERR $d;
+	}
+    }
+
+    next unless($chunk);
+
+    my $data = substr($fpgadata, $offset, $chunk);
+    my $hdr = pack("CCvVV", 2, $chunk-1, 0, $offset, crc32($data));
+
+    tty_write($tty, $hdr.$data);
+
+    $offset += $chunk;
+    $winspc -= $chunk;
+}
+
+tty_write($tty, "\004");		# EOT
+$tty->close;
 
-exit !!$err;
+exit 0;

+ 3 - 1
esp32/max80/fpga.h

@@ -42,7 +42,9 @@ extern_c esp_err_t fpga_io_write(unsigned int cmd, const volatile void *addr,
 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_config(void);
+extern_c void fpga_disable_config(void);
+extern_c bool fpga_jtag_busy(void);
 
 struct esplink_head;
 extern_c void esplink_init(void);

+ 51 - 10
esp32/max80/fpgajtag.c

@@ -52,12 +52,18 @@ enum JTAG_IR {
 #define JTAG_FPGA_MS	((JTAG_FPGA_HZ+999)/1000)
 #define JTAG_FPGA_US	((JTAG_FPGA_HZ+999999)/1000000)
 
+#define PIN_FPGA_TDI	16
+#define PIN_FPGA_TDO	17
+#define PIN_FPGA_TMS	14
+#define PIN_FPGA_TCK	18
+#define PIN_FPGA_nCE	26
+
 static const struct jtag_config jtag_config_fpga = {
     .hz      = JTAG_FPGA_HZ,
-    .pin_tdi = 16,
-    .pin_tdo = 17,
-    .pin_tms = 14,
-    .pin_tck = 18,
+    .pin_tdi = PIN_FPGA_TDI,
+    .pin_tdo = PIN_FPGA_TDO,
+    .pin_tms = PIN_FPGA_TMS,
+    .pin_tck = PIN_FPGA_TCK,
     .be      = false
 };
 
@@ -66,7 +72,7 @@ static bool test_bit(const uint32_t *buf, unsigned int bit)
     return (buf[bit >> 5] >> (bit & 31)) & 1;
 }
 
-static void fpga_finish(void)
+static int fpga_finish(int err)
 {
     tap_goto_state(TAP_RUN_TEST_IDLE);
 
@@ -77,6 +83,9 @@ static void fpga_finish(void)
     /* Reset?! */
 
     jtag_disable(NULL);
+    fpga_enable_config();
+
+    return err;
 }
 
 static uint32_t tap_get_idcode(void)
@@ -101,6 +110,9 @@ int fpga_program_spz(spz_stream *spz)
     uint32_t idcode;
     uint32_t check_status_buf[(JTAG_FPGA_CHECK_STATUS_BITS+31) >> 5];
 
+    /* Disable onboard configuration circuitry */
+    fpga_disable_config();
+
     /* Configure JTAG to access the FPGA */
     jtag_enable(&jtag_config_fpga);
 
@@ -183,16 +195,46 @@ int fpga_program_spz(spz_stream *spz)
 
     /* Common finish */
  fail:
-    fpga_finish();
+    return fpga_finish(err);
+}
 
-    return err;
+//
+// Board 2.1 has IO26 connected to nCE on the FPGA; this pin has
+// a pulldown but can be raised high by external JTAG. We don't
+// want the external pulldown to fight an internal pullup, but
+// also don't want the pin to float on the 1.0 and 2.0 board revisions
+// where it is NC.
+//
+// XXX: Actually try to detect board revision 2.1...
+//
+void fpga_enable_config(void)
+{
+    pinMode(PIN_FPGA_nCE, INPUT_PULLDOWN);
+}
+void fpga_disable_config(void)
+{
+    digitalWrite(PIN_FPGA_nCE, 1);
+    pinMode(PIN_FPGA_nCE, OUTPUT);
+    delayMicroseconds(1000);
+}
+bool fpga_jtag_busy(void)
+{
+    return digitalRead(PIN_FPGA_nCE);
 }
 
 int fpga_reset(void)
 {
     int err = 0;
 
+    fpga_enable_config();
     jtag_enable(&jtag_config_fpga);
+    jtag_delay(1000);
+
+    if (fpga_jtag_busy()) {
+	MSG("FPGA nCE = 0; JTAG controlled by external device?\n");
+	err = FWUPDATE_ERR_FPGA_JTAG;
+	goto fail;
+    }
 
     tap_run_test_idle(JTAG_FPGA_MS);
 
@@ -205,7 +247,6 @@ int fpga_reset(void)
     tap_run_test_idle(JTAG_FPGA_MS);
 
     /* Common finish */
-    fpga_finish();
-
-    return err;
+fail:
+    return fpga_finish(err);
 }

+ 1 - 1
esp32/max80/fpgasvc.c

@@ -631,7 +631,7 @@ static void fpga_service_task(void *dummy)
 
 		if (status & (0x100 << EL_UIRQ_OTA))
 		    fpga_ota_update();
-		
+
 		for (unsigned int i = 5; i < 8; i++) {
 		    if (status & (0x100 << i)) {
 			printf("[FPGA] Invalid interrupt %u received\n", i);

+ 57 - 18
esp32/max80/fwupdate.c

@@ -25,14 +25,36 @@
 #define FWUPDATE_STACK		8192
 #define FWUPDATE_PRIORITY	3
 
-/* Normally provided by zlib, but UnzipLIB breaks it */
-static void *z_calloc(void *opaque, unsigned int items, unsigned int size)
+static void *spz_calloc(void *opaque, unsigned int items, unsigned int size)
 {
-    (void)opaque;
-    return calloc(items, size);
+    spz_stream *spz = opaque;
+    MSG("spz_calloc(%u), sram %u/%u, spiram %u/%u = ", items*size,
+	heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
+	heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
+	heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
+	heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+    void *p = calloc(items, size);
+    CMSG("%p\n", p);
+    if (!p)
+	spz->err = Z_MEM_ERROR;
+    return p;
+}
+static void *spz_malloc(void *opaque, unsigned int size)
+{
+    spz_stream *spz = opaque;
+    MSG("spz_malloc(%u), sram %u/%u, spiram %u/%u = ", size,
+	heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
+	heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
+	heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
+	heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+    void *p = malloc(size);
+    CMSG("%p\n", p);
+    if (!p)
+	spz->err = Z_MEM_ERROR;
+    return p;
 }
 
-static void z_free(void *opaque, void *ptr)
+static void spz_free(void *opaque, void *ptr)
 {
     (void)opaque;
     free(ptr);
@@ -95,21 +117,25 @@ int spz_read_data(spz_stream *spz, void *buf, size_t len)
  */
 static int fwupdate_data_init(spz_stream *spz)
 {
-    spz->zs.zalloc = z_calloc;
-    spz->zs.zfree  = z_free;
-    spz->zs.opaque = spz;	/* Might be useful at some point */
+    spz->zs.zalloc = spz_calloc;
+    spz->zs.zfree  = spz_free;
+    spz->zs.opaque = spz;	/* For error reporting */
+    spz->err = Z_OK;
 
     /* This is necessary due to unziplib damage */
-    spz->zs.state = calloc(1, sizeof(struct inflate_state));
+    spz->zs.state = spz_calloc(spz, 1, sizeof(struct inflate_state));
+    if (!spz->zs.state)
+	goto err;
 
     for (int i = 0; i < SPZ_NBUF; i++) {
-	spz->bufs[i] = malloc(BUFFER_SIZE);
+	spz->bufs[i] = spz_malloc(spz, BUFFER_SIZE);
 	if (!spz->bufs[i])
 	    goto err;
     }
 
     /* gzip, max window size */
     int rv = inflateInit2(&spz->zs, 16 + 15);
+    printf("[FWUP] fwupdate_data_init: inflateInit2 returned %d\n", rv);
     if (rv != Z_OK && rv != Z_STREAM_END) {
 	spz->err = rv;
 	goto err;
@@ -140,6 +166,9 @@ static int fwupdate_data_cleanup(spz_stream *spz)
 	if (spz->bufs[i])
 	    free(spz->bufs[i]);
     }
+    if (spz->zs.state)
+	free(spz->zs.state);
+
     return err;
 }
 
@@ -326,20 +355,24 @@ const char *firmware_errstr(int err)
 }
 
 static TaskHandle_t fwupdate_task;
-static struct spz_stream *fwupdate_spz;
+static spz_stream *fwupdate_spz;
 static SemaphoreHandle_t fwupdate_done;
 static int fwupdate_err;
 
 static void firmware_update_task(void *pvt)
 {
-    struct spz_stream *spz = pvt;
+    spz_stream *spz = pvt;
 
     fpga_service_enable(false);
 
+    printf("[FWUP] fwupdate_data_init()\n");
+
     spz->err = fwupdate_data_init(spz);
     if (spz->err)
 	goto fail;
 
+    printf("[FWUP] fwupdate_process_chunk loop\n");
+
     int err;
     while (!(err = fwupdate_process_chunk(spz))) {
 	/* Process data chunks until end */
@@ -348,20 +381,22 @@ static void firmware_update_task(void *pvt)
     if (!spz->err && err != Z_STREAM_END)
 	spz->err = err;
 
+    printf("[FWUP] fwupdate_data_cleanup\n");
+
     err = fwupdate_data_cleanup(spz);
-    if (err) {
-	MSG("failed (err %d)\n", err);
+    if (err)
 	spz->err = err;
-    }
 
 fail:
+    if (spz->err)
+	MSG("failed (err %d)\n", spz->err);
     xSemaphoreGive(fwupdate_done);
     exit_task();
 }
 
 static int firmware_update_cleanup(void)
 {
-    int err = Z_MEM_ERROR;
+    int err = Z_OK;
 
     fwupdate_task = NULL;
 
@@ -369,12 +404,17 @@ static int firmware_update_cleanup(void)
 	SemaphoreHandle_t done = fwupdate_done;
 	fwupdate_done = NULL;
 	vSemaphoreDelete(done);
+    } else {
+	err = Z_MEM_ERROR;
     }
     if (fwupdate_spz) {
 	struct spz_stream *spz = fwupdate_spz;
-	err = spz->err;
+	if (spz->err)
+	    err = spz->err;
 	fwupdate_spz = NULL;
 	free(spz);
+    } else {
+	err = Z_MEM_ERROR;
     }
 
     return err;
@@ -392,7 +432,6 @@ int firmware_update_start(read_func_t read_data, token_t token)
     if (!fwupdate_spz)
 	goto err;
 
-    fwupdate_spz->err       = Z_MEM_ERROR;
     fwupdate_spz->read_data = read_data;
     fwupdate_spz->token     = token;
 

+ 5 - 2
esp32/max80/max80.ino

@@ -55,6 +55,9 @@ static void init_hw()
     for (int i = 1; i <= 18; i++)
 	pinMode(i, INPUT);
 
+    // Make sure FPGA nCE input (board 2.1+) is not pulled high
+    fpga_enable_config();
+
     // Configure USB power control. Try to detect 36k pulldown
     // resistor on USB_PWR_EN first, to determine board version 2
     // (on board v1, this pin in N/C.)
@@ -158,7 +161,7 @@ static void dump_tasks(void)
 }
 
 void loop() {
-    if (0) {
+    if (1) {
 	printf("loop task: %s\n", pcTaskGetName(xTaskGetCurrentTaskHandle()));
 	printf("idle task: %s\n", pcTaskGetName(xTaskGetIdleTaskHandle()));
 
@@ -168,5 +171,5 @@ void loop() {
     }
 
     TTY::ping();
-    vTaskDelay(2 * configTICK_RATE_HZ);
+    vTaskDelay(30 * configTICK_RATE_HZ);
 }

+ 40 - 44
esp32/max80/tty.cpp

@@ -29,13 +29,13 @@
 #define RS   '\036'
 #define US   '\037'
 
-#define WGO_CHUNK	256
-
-#define STREAMBUF_SIZE	8192
+#define WGO_CHUNK	 256
+#define STREAMBUF_SIZE	2048
+#define BUF_SLACK         32
 
 static char enq_str[] = "\026\035MAX80 v0\004\r\n";
 static const char fwupload_start[] =
-    "\034\001: /// MAX80 FW UPLOAD ~@~ $\035\r\n";
+    "\034\001: /// MAX80 FW UPLOAD ~@~ $\r\n\035";
 
 void TTY::reset()
 {
@@ -43,11 +43,11 @@ void TTY::reset()
     rx.state  = rx_state::normal;
 }
 
+
 TTY::TTY(Stream &port)
 {
     _port = &port;
     rx_sbuf = NULL;
-    win_sem = xSemaphoreCreateMutexStatic(&win_sem_ss);
     reset();
 }
 
@@ -57,31 +57,22 @@ TTY::~TTY()
 	vStreamBufferDelete(rx_sbuf);
 }
 
-void TTY::_update_window(bool rst)
-{
-    xSemaphoreTake(win_sem, portMAX_DELAY);
-
-    if (rst) {
-	port().write(WRST);
-	tx_window = 0;
-    }
-
-    while (xStreamBufferSpacesAvailable(rx_sbuf) >= tx_window + WGO_CHUNK) {
-	tx_window += WGO_CHUNK;
-	port().write(WGO);	// Space for one more window
-    }
-
-    xSemaphoreGive(win_sem);
-}
-
 int TTY::rxdata(void *buf, size_t len)
 {
+    printf("[TTY]  rxdata(%zu) ", len);
+
     if (!rx_sbuf)
 	return 0;
 
-    size_t rcv = xStreamBufferReceive(rx_sbuf, buf, len, portMAX_DELAY);
-    _update_window(false);
+    while (tx_credits_ok - tx_credits_sent >= WGO_CHUNK &&
+	   port().write(WGO)) {
+	    tx_credits_sent += WGO_CHUNK;
+    }
+
+    int rcv = xStreamBufferReceive(rx_sbuf, buf, len, portMAX_DELAY);
+    tx_credits_ok += rcv;
 
+    printf("got %d\n", rcv);
     return rcv;
 }
 
@@ -93,24 +84,32 @@ int TTY::rxdata(token_t me, void *buf, size_t len)
 
 void TTY::_upload_begin()
 {
+    printf("[TTY]  _upload_begin\n");
+
     if (rx_sbuf)
 	xStreamBufferReset(rx_sbuf);
     else
 	rx_sbuf = xStreamBufferCreate(STREAMBUF_SIZE, 1);
 
-    if (!rx_sbuf)
-	goto nak;
+    printf("[TTY]  rx_sbuf = %p\n", rx_sbuf);
 
-    if (firmware_update_start((read_func_t)TTY::rxdata, (token_t)this))
-	goto nak;
+    if (!rx_sbuf)
+	goto can;
 
-    // Respond with WRST + n*WGO
-    _update_window(true);
+    port().write(RS);
+    tx_credits_sent = 0;
+    tx_credits_ok   = STREAMBUF_SIZE - BUF_SLACK;
     rx.state = rx_state::hdr;
+    rx.rlen  = 0;
+
+    printf("[TTY]  firmware_update_start()\n");
+    if (firmware_update_start(TTY::rxdata, (token_t)this))
+	goto can;
+
     return;
 
- nak:
-    port().write(NAK);
+can:
+    port().write(CAN);
     rx.state = rx_state::normal;
     return;
 }
@@ -118,7 +117,7 @@ void TTY::_upload_begin()
 void TTY::_onerr()
 {
     if (rx.state != rx_state::normal)
-	_update_window(true);
+	port().write(WRST);
 }
 
 // Change this to be a buffer...
@@ -135,8 +134,10 @@ void TTY::_onrx()
 	switch (rx.state) {
 	case rx_state::normal:
 	    if (byte == fwupload_start[rx.rlen]) {
-		if (!fwupload_start[++rx.rlen])
+		if (!fwupload_start[++rx.rlen]) {
 		    _upload_begin();
+		    byte = -1;
+		}
 	    } else {
 		rx.rlen = 0;
 		switch (byte) {
@@ -189,7 +190,7 @@ void TTY::_onrx()
 	    // rx.hdr.len = packet data len - 1
 	    if (rx.rlen > rx.hdr.len) {
 		int have = rx.rlen;
-		uint32_t crc  = ~crc32_le(0, rx_data, have);
+		uint32_t crc = crc32_le(0, rx_data, have);
 
 		if (crc != rx.hdr.crc) {
 		    byte = NAK;
@@ -198,8 +199,6 @@ void TTY::_onrx()
 		} else {
 		    int sent = 0;
 
-		    tx_window -= rx.rlen;
-
 		    if (rx.hdr.offs + rx.rlen <= rx.last_ack) {
 			have = 0;
 		    } else {
@@ -209,14 +208,11 @@ void TTY::_onrx()
 			rx.last_ack += sent;
 		    }
 
-		    if (sent == have)
-			byte = ACK;
-		    else
-			_update_window(true);
-
-		    rx.state = rx_state::hdr;
-		    rx.rlen  = 0;
+		    byte = (sent == have) ? ACK : WRST;
 		}
+
+		rx.state = rx_state::hdr;
+		rx.rlen  = 0;
 	    }
 	    break;
 	}

+ 2 - 3
esp32/max80/tty.h

@@ -74,8 +74,7 @@ private:
 	uint32_t last_ack;
     } rx;
     StreamBufferHandle_t rx_sbuf;
-    SemaphoreHandle_t win_sem;
-    StaticSemaphore_t win_sem_ss;
-    std::atomic_uint32_t tx_window;
+    uint32_t tx_credits_ok;
+    uint32_t tx_credits_sent;
     uint8_t rx_data[256];
 };

二進制
esp32/output/max80.ino.bin


+ 8 - 8
fpga/Makefile

@@ -67,8 +67,7 @@ IDCODE     := 0x020f20dd		# JTAG IDCODE of FPGA
 
 .DELETE_ON_ERROR:
 
-all:
-	$(MAKE) prereq
+all: prereq
 	$(MAKE) $(REVISIONS:=.targets)
 	$(MAKE) fwimages
 #	$(MAKE) $(VARIANTS)
@@ -80,8 +79,7 @@ all:
 fwimages: $(patsubst %,$(outdir)/%.fw,$(filter-out bypass,$(REVISIONS)))
 
 .PHONY: $(REVISIONS)
-$(REVISIONS):
-	$(MAKE) prereq
+$(REVISIONS): prereq
 	$(MAKE) $@.targets
 
 .PHONY: %.targets
@@ -118,7 +116,7 @@ $(outdir)/%.map.rpt: %.qsf | $(mifdir)/sram.mif
 $(outdir)/%.fit.rpt: $(outdir)/%.map.rpt
 	$(QFIT) $(PROJECT) -c $*
 
-$(mifdir)/%.bin: $(sram_src)/%.bin $(all_map_deps)
+$(mifdir)/%.bin: $(sram_src)/%.bin $(all_map_deps) $(mifdir)
 	cp -f $< $@
 	printf '%s\0' '$(BUILDDATE)' >> $@
 
@@ -141,7 +139,7 @@ $(outdir)/%.sta.rpt: $(outdir)/%.fit.rpt | $(outdir)/%.sof
 $(outdir)/%.pow.rpt: $(outdir)/%.sta.rpt
 	$(QPOW) $(PROJECT) -c $*
 
-$(foreach rev,$(REVISIONS),$(outdir)/$(rev).%.cof): %.cof.xml
+$(foreach rev,$(REVISIONS),$(outdir)/$(rev).%.cof): %.cof.xml $(outdir)
 	$(SED) -e 's/@@PROJECT@@/$(@F:.$*.cof=)/g' $< > $@
 
 $(outdir)/%.jic: $(outdir)/%.jic.cof $(outdir)/%.sof ../rv32/dram.hex
@@ -202,9 +200,11 @@ $(outdir)/%.update.svf: ./scripts/flashsvf.pl \
 	$(PERL) $^ $@
 
 # Prerequisite directories and files
+$(outdir) $(mifdir):
+	mkdir -p $@
+
 .PHONY: prereq
-prereq:
-	mkdir -p $(outdir) $(mifdir)
+prereq: $(outdir) $(mifdir)
 	for d in $(SUBDIRS); do $(MAKE) -C $$d; done
 	$(MAKE) $(PREREQFILES)
 

+ 3 - 3
fpga/max80.qpf

@@ -19,15 +19,15 @@
 #
 # Quartus Prime
 # Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
-# Date created = 17:14:01  June 24, 2022
+# Date created = 15:34:48  July 30, 2022
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "21.1"
-DATE = "17:14:01  June 24, 2022"
+DATE = "15:34:48  July 30, 2022"
 
 # Revisions
 
-PROJECT_REVISION = "v2"
 PROJECT_REVISION = "v1"
+PROJECT_REVISION = "v2"
 PROJECT_REVISION = "bypass"

二進制
fpga/output/bypass.rbf.gz


二進制
fpga/output/bypass.rpd.gz


二進制
fpga/output/bypass.sof


二進制
fpga/output/bypass.svf.gz


二進制
fpga/output/bypass.xsvf.gz


二進制
fpga/output/v1.fw


二進制
fpga/output/v1.jic


二進制
fpga/output/v1.rbf.gz


二進制
fpga/output/v1.rpd.gz


二進制
fpga/output/v1.sof


二進制
fpga/output/v1.svf.gz


二進制
fpga/output/v1.xsvf.gz


二進制
fpga/output/v2.fw


二進制
fpga/output/v2.jic


二進制
fpga/output/v2.rbf.gz


二進制
fpga/output/v2.rpd.gz


二進制
fpga/output/v2.sof


二進制
fpga/output/v2.svf.gz


二進制
fpga/output/v2.xsvf.gz