Forráskód Böngészése

update: it is slow, but now firmware upload via serial port & USB work

Still slow, but now "make upload-vX PORT=..." can be used to upload a
firmware image (*.fw) to update everything from bare metal, including
ESP32 bootloader and partition table.
H. Peter Anvin 2 éve
szülő
commit
12519f981f

+ 99 - 21
esp32/flashesp.pl

@@ -33,6 +33,42 @@ my $FDF_OPTIONAL = 0x0001;
 
 my $STRING_MAX_LEN = 4095;
 
+# For debug; DC1-4 replaced with functional names
+my @ascii = qw(NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
+	       DLE XON WRST XOFF WGO NAK SYN ETB CAN EM SUB ESC FS GS RS US);
+foreach my $i (9, 10, 13, 32..126) {
+    $ascii[$i] = chr($i);
+}
+$ascii[127] = 'DEL';
+for (my $i = 128; $i < 256; $i++) {
+    $ascii[$i] = sprintf("%02X", $i);
+}
+my @a = map { length($_) == 1 ? $_ : '<'.$_.'>' } @ascii;
+
+# Simple base64 encode using 3F-7E, bytewise bigendian
+sub mybaseencode {
+    my $nbits  = 0;
+    my $bitbuf = 0;
+    my $out    = '';
+
+    foreach my $s (@_) {
+	foreach my $c (unpack('C*', $s)) {
+	    $nbits += 8;
+	    $bitbuf = ($bitbuf << 8) + $c;
+	    while ($nbits >= 6) {
+		$nbits -= 6;
+		$out .= pack('C', 63 + (($bitbuf >> $nbits) & 63));
+	    }
+	}
+    }
+
+    if ($nbits) {
+	$out .= pack('C', 63 + ($bitbuf & ((1 << $nbits) - 1)));
+    }
+
+    return $out;
+}
+
 sub getint($) {
     my($s) = @_;
 
@@ -313,6 +349,7 @@ sub tty_write($$) {
 my $found = 0;
 my $tt = time();
 my $start_enq = $tt;
+tty_write($tty, "\005");	# ENQ
 while (($tt = time()) - $start_enq < 30) {
     my $d = tty_read($tty, \$ttybuf, 1000);
     if ($d eq '') {
@@ -341,44 +378,83 @@ while (!defined($winspc)) {
     }
     tty_write($tty, "\034\001: /// MAX80 FW UPLOAD \~\@\~ \$\r\n\035");
     my $d;
-    do {
+    while (1) {
 	$d = tty_read($tty, \$ttybuf, 1000);
-	if ($d eq "\036") {
+	last if ($d eq '');
+	my $dc = unpack('C', $d);
+	if ($dc == 036) {
 	    $winspc = 0;
 	    last;
 	} else {
-	    print $d;
+	    print STDERR $a[$dc];
 	}
-    } 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") {
+my $maxchunk = 64;
+my $maxahead = 256;
+my $last_ack = 0;
+my @pktends  = ();
+
+print STDERR "\nStarting packet transmit...\n";
+
+while ($last_ack < $bytes) {
+    my $chunk;
+
+    while (1) {
+	$chunk = $bytes - $offset;
+	$chunk = $winspc if ($chunk > $winspc);
+	if ($offset + $chunk > $last_ack + $maxahead) {
+	    $chunk = $last_ack + $maxahead - $offset;
+	}
+	$chunk = 0 if ($chunk <= 0);
+	$chunk = $maxchunk if ($chunk > $maxchunk);
+
+	my $d = tty_read($tty, \$ttybuf, $chunk ? 0 : 1000);
+	last if ($d eq '');
+
+	my $dc = unpack('C', $d);
+	if ($dc == 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;
+	    print STDERR "WRST, window: $winspc\n";
+	} elsif ($dc == 024) {
+	    if (defined($winspc)) {
+		$winspc += 256;
+		print STDERR "WGO, window: $winspc\n";
+	    }
+	} elsif ($dc == 006) {
+	    $last_ack = shift(@pktends) || $last_ack;
+	    print STDERR "ACK to $last_ack\n";
+	} else {
+	    print STDERR $a[$dc];
+	    if ($dc == 025 || $dc == 031 || $dc == 037) {
+		print STDERR "\n$0: $port: resetting upload to $last_ack\n";
+		$offset = $last_ack;
+		@pktends = ();
+		undef $winspc;	# Wait for WRST before resuming
+	    } elsif ($dc == 030) {
+		print STDERR "\n";
+		die "$0: $port: upload aborted by target system\n";
+	    }
 	}
     }
 
-    next unless($chunk);
+    print STDERR "Send offset: $offset, window: $winspc, chunk: $chunk\n";
+    if (!$chunk) {
+	if ($bytes > $offset) {
+	    tty_write($tty, "\026"); # Request to resynchronize credits
+	}
+	next;
+    }
 
     my $data = substr($fpgadata, $offset, $chunk);
-    my $hdr = pack("CCvVV", 2, $chunk-1, 0, $offset, crc32($data));
+    my $hdr = pack("CvVV", $chunk-1, 0, $offset, crc32($data));
 
-    tty_write($tty, $hdr.$data);
+    tty_write($tty, "\002".mybaseencode($hdr, $data));
 
+    push(@pktends, $offset + $chunk);
     $offset += $chunk;
     $winspc -= $chunk;
 }
@@ -386,4 +462,6 @@ while ($offset < $bytes) {
 tty_write($tty, "\004");		# EOT
 $tty->close;
 
+print STDERR "$0: $port: firmware upload complete\n";
+
 exit 0;

+ 12 - 6
esp32/max80/config.c

@@ -34,10 +34,13 @@ int setenv_config(const char *name, const char *value)
 
 int setenv_cond(const char *name, const char *value)
 {
-    if (value)
+    if (value) {
+	printf("env: %s=%s\n", name, value);
 	return setenv(name, value, 1);
-    else
+    } else {
+	printf("env: %s undefined\n", name);
 	return unsetenv(name);
+    }
 }
 
 static int reset_config(void)
@@ -56,16 +59,19 @@ static int reset_config(void)
 	    break;
 
 	const char *eq = strchr(*envp, '=');
+	if (!eq)
+	    continue;		/* This should never happen... */
+
 	char ename[eq - *envp + 1];
 	memcpy(ename, *envp, eq - *envp);
 	ename[eq - *envp] = '\0';
 
-	unsetenv(ename);
+	setenv_cond(ename, NULL);
     }
 
     size_t i;
     for (i = 0; i < ARRAY_SIZE(default_config); i++)
-	setenv(default_config[i].var, default_config[i].val, 1);
+	setenv_cond(default_config[i].var, default_config[i].val);
 
     config_changed = true;
 }
@@ -260,7 +266,7 @@ void setenv_l(const char *var, long val)
 {
     char vbuf[2+3*sizeof val];
     snprintf(vbuf, sizeof vbuf, "%ld", val);
-    setenv(var, vbuf, 1);
+    setenv_cond(var, vbuf);
 }
 
 unsigned long getenv_ul(const char *var, unsigned long def)
@@ -279,7 +285,7 @@ void setenv_ul(const char *var, unsigned long val)
 {
     char vbuf[2+3*sizeof val];
     snprintf(vbuf, sizeof vbuf, "%lu", val);
-    setenv(var, vbuf, 1);
+    setenv_cond(var, vbuf);
 }
 
 bool getenv_bool(const char *var)

+ 8 - 1
esp32/max80/fpgajtag.c

@@ -203,16 +203,23 @@ int fpga_program_spz(spz_stream *spz)
 //
 // XXX: Actually try to detect board revision 2.1...
 //
+// YYY: IO26 is CS# för PSRAM! This is invalid usage... probably will need
+// a rework on the 2.1 board!
+//
 void fpga_enable_nce(void)
 {
-    pinMode(PIN_FPGA_nCE, INPUT_PULLDOWN);
+#if 0
+  pinMode(PIN_FPGA_nCE, INPUT_PULLDOWN);
     delayMicroseconds(100);	/* Just in case */
+#endif
 }
 
 int fpga_reset(void)
 {
     int err = 0;
 
+    printf("[FPGA] Resetting FPGA via JTAG\n");
+
     fpga_enable_nce();
     jtag_enable(&jtag_config_fpga);
     tap_run_test_idle(JTAG_FPGA_MS);

+ 1 - 1
esp32/max80/fw.h

@@ -25,7 +25,7 @@
 #define FWUPDATE_ERR_CONFIG_READ	(-24)
 #define FWUPDATE_ERR_CONFIG_SAVE	(-25)
 
-extern_c int firmware_update_start(read_func_t read_data, token_t token);
+extern_c int firmware_update_start(read_func_t read_data, token_t token, bool autoreboot);
 extern_c int firmware_update_wait(TickType_t delay);
 extern_c int esp_update(read_func_t read_func, token_t token, size_t size);
 extern_c const char *firmware_errstr(int err);

+ 32 - 12
esp32/max80/fwupdate.c

@@ -25,16 +25,25 @@
 #define FWUPDATE_STACK		8192
 #define FWUPDATE_PRIORITY	3
 
+static void heap_info(void)
+{
+#if DEBUG > 1
+    MSG("Heap: sram ");
+    MSG("%u/", heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
+    MSG("%u, spiram ", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
+    MSG("%u/", heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM));
+    MSG("%u\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+#endif
+}
+
 static void *spz_calloc(void *opaque, unsigned int items, unsigned int 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));
+    heap_info();
+    MSG("spz_calloc(%u,%u) = %u = ", items, size, items*size);
     void *p = calloc(items, size);
     CMSG("%p\n", p);
+    heap_info();
     if (!p)
 	spz->err = Z_MEM_ERROR;
     return p;
@@ -42,13 +51,11 @@ static void *spz_calloc(void *opaque, unsigned int items, unsigned int size)
 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));
+    heap_info();
+    MSG("spz_malloc(%u) = ", size);
     void *p = malloc(size);
     CMSG("%p\n", p);
+    heap_info();
     if (!p)
 	spz->err = Z_MEM_ERROR;
     return p;
@@ -56,8 +63,11 @@ static void *spz_malloc(void *opaque, unsigned int size)
 
 static void spz_free(void *opaque, void *ptr)
 {
+    heap_info();
+    MSG("spz_free(%p)\n", ptr);
     (void)opaque;
     free(ptr);
+    heap_info();
 }
 
 int spz_read_data(spz_stream *spz, void *buf, size_t len)
@@ -358,6 +368,7 @@ static TaskHandle_t fwupdate_task;
 static spz_stream *fwupdate_spz;
 static SemaphoreHandle_t fwupdate_done;
 static int fwupdate_err;
+static bool do_reboot;
 
 static void firmware_update_task(void *pvt)
 {
@@ -391,7 +402,14 @@ fail:
     if (spz->err)
 	MSG("failed (err %d)\n", spz->err);
     xSemaphoreGive(fwupdate_done);
-    exit_task();
+
+    if (do_reboot) {
+	printf("[FWUP] rebooting in %d seconds\n", reboot_delayed());
+	while (1)
+	    vTaskSuspend(NULL);
+    } else {
+	exit_task();
+    }
 }
 
 static int firmware_update_cleanup(void)
@@ -420,11 +438,13 @@ static int firmware_update_cleanup(void)
     return err;
 }
 
-int firmware_update_start(read_func_t read_data, token_t token)
+int firmware_update_start(read_func_t read_data, token_t token, bool autoreboot)
 {
     int err;
     SemaphoreHandle_t done = NULL;
 
+    do_reboot = autoreboot;
+
     if (fwupdate_spz)
 	return FWUPDATE_ERR_IN_PROGRESS;
 

+ 1 - 1
esp32/max80/httpd.c

@@ -329,7 +329,7 @@ static esp_err_t httpd_firmware_update(httpd_req_t *req)
     int rv;
 
     /* XXX: use httpd_fopen_read() here */
-    rv = firmware_update_start((read_func_t)httpd_req_recv, (token_t)req);
+    rv = firmware_update_start((read_func_t)httpd_req_recv, (token_t)req, false);
     if (!rv)
 	rv = firmware_update_wait(portMAX_DELAY);
 

+ 23 - 18
esp32/max80/max80.ino

@@ -38,6 +38,16 @@ void setup_usb_ids()
     USB.serialNumber(serial);
 }
 
+static void heap_info()
+{
+    size_t il = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
+    size_t ia = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
+    size_t sl = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
+    size_t sa = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
+
+    printf("Heap: sram %zu/%zu, spiram %zu/%zu\n", il, ia, sl, sa);
+}
+
 static void dump_config()
 {
     printf("--- Configuration:\n");
@@ -68,6 +78,7 @@ static void init_hw()
     pinMode(PIN_USB_PWR_SINK, OUTPUT);		// IO8: USB_PWR_SINK
     digitalWrite(PIN_USB_PWR_SINK, 0);		// This is a power sink
 
+ #if 0
     pinMode(PIN_USB_PWR_EN, OUTPUT);
     digitalWrite(PIN_USB_PWR_EN, 0);
     delayMicroseconds(100);
@@ -79,6 +90,9 @@ static void init_hw()
     delayMicroseconds(50);
 
     max80_board_version = digitalRead(PIN_USB_PWR_EN) ? 1 : 2;
+#else
+    max80_board_version = 2;
+#endif
 
     pinMode(PIN_USB_PWR_EN, OUTPUT);
     digitalWrite(PIN_USB_PWR_EN, 0);
@@ -95,34 +109,26 @@ void setup() {
     init_hw();
 
     // Enable external PSRAM for heap
-    heap_caps_malloc_extmem_enable(2048); // >= 2K allocations in PSRAM
+    heap_caps_malloc_extmem_enable(3000); // >= 3K allocations in PSRAM
+    heap_info();
 
     TTY::init();
+    heap_info();
 
     printf("[PCB]  MAX80 board version: %u\n", max80_board_version);
     setenv_ul("status.max80.hw.ver", max80_board_version);
     Serial.println("MAX80 start");
 
-    fpga_service_init();
-    Serial.println("0.2");
     init_config();
-    Serial.println("0.3");
     setenv_cond("status.max80.fw.date", fwdate);
+    fpga_service_init();
     fpga_service_enable(true);
-    Serial.println("0.4");
     SetupWiFi();
-    Serial.println("0.5");
-    printf("[RDY]\n");
+    Serial.println("[RDY]");
     dump_config();
     led_set(LED_BLUE, LED_ON);	// Software ready
 
-    Serial.println("0.5");
-    printf("Total heap:  %d\n"
-	   "Free heap:   %d\n"
-	   "Total PSRAM: %d\n"
-	   "Free PSRAM:  %d\n",
-	   ESP.getHeapSize(), ESP.getFreeHeap(),
-	   ESP.getPsramSize(), ESP.getFreePsram());
+    heap_info();
 }
 
 static inline char task_state(eTaskState state)
@@ -161,15 +167,14 @@ static void dump_tasks(void)
 }
 
 void loop() {
-    if (1) {
+    if (0) {
 	printf("loop task: %s\n", pcTaskGetName(xTaskGetCurrentTaskHandle()));
 	printf("idle task: %s\n", pcTaskGetName(xTaskGetIdleTaskHandle()));
-
 	dump_tasks();
-
+	heap_info();
 	putchar('\n');
     }
 
     TTY::ping();
-    vTaskDelay(30 * configTICK_RATE_HZ);
+    vTaskDelay(5 * configTICK_RATE_HZ);
 }

+ 179 - 62
esp32/max80/tty.cpp

@@ -31,7 +31,7 @@
 
 #define WGO_CHUNK	 256
 #define STREAMBUF_SIZE	2048
-#define BUF_SLACK         32
+#define BUF_SLACK       (STREAMBUF_SIZE >> 1)
 
 static char enq_str[] = "\026\035MAX80 v0\004\r\n";
 static const char fwupload_start[] =
@@ -39,15 +39,16 @@ static const char fwupload_start[] =
 
 void TTY::reset()
 {
-    rx.rlen   = 0;
-    rx.state  = rx_state::normal;
+    rx.rlen     = 0;
+    rx.state    = rx_state::normal;
+    rx.b64_bits = 0;
 }
 
 
 TTY::TTY(Stream &port)
 {
     _port = &port;
-    rx_sbuf = NULL;
+    rx_sbuf = true ? xStreamBufferCreate(STREAMBUF_SIZE, 1) : NULL;
     reset();
 }
 
@@ -64,13 +65,30 @@ int TTY::rxdata(void *buf, size_t len)
     if (!rx_sbuf)
 	return 0;
 
-    while (tx_credits_ok - tx_credits_sent >= WGO_CHUNK &&
-	   port().write(WGO)) {
-	    tx_credits_sent += WGO_CHUNK;
-    }
+    int rcv = 0;
+    while (!rcv) {
+	if (tx_credits_reset) {
+	    // Drain input before WRST
+	    rcv = xStreamBufferReceive(rx_sbuf, buf, len, 0);
+
+	    if (rcv)
+		break;
 
-    int rcv = xStreamBufferReceive(rx_sbuf, buf, len, portMAX_DELAY);
-    tx_credits_ok += rcv;
+	    if (port().write(WRST)) {
+		tx_credits_reset = false;
+		tx_credits = STREAMBUF_SIZE - BUF_SLACK;
+	    } else {
+		// Uhm... wait one tick and then try again to sent WRST?
+		rcv = xStreamBufferReceive(rx_sbuf, buf, len, 1);
+	    }
+	} else {
+	    while (tx_credits >= WGO_CHUNK && port().write(WGO))
+		tx_credits -= WGO_CHUNK;
+
+	    rcv = xStreamBufferReceive(rx_sbuf, buf, len, 1);
+	    tx_credits += rcv;
+	}
+    }
 
     printf("got %d\n", rcv);
     return rcv;
@@ -97,13 +115,14 @@ void TTY::_upload_begin()
 	goto can;
 
     port().write(RS);
-    tx_credits_sent = 0;
-    tx_credits_ok   = STREAMBUF_SIZE - BUF_SLACK;
-    rx.state = rx_state::hdr;
+    tx_credits_reset = true;
+    rx.state = rx_state::stxwait;
+    rx.last_ack = 0;
     rx.rlen  = 0;
+    rx.b64_bits = 0;
 
     printf("[TTY]  firmware_update_start()\n");
-    if (firmware_update_start(TTY::rxdata, (token_t)this))
+    if (firmware_update_start(TTY::rxdata, (token_t)this, true))
 	goto can;
 
     return;
@@ -116,24 +135,54 @@ can:
 
 void TTY::_onerr()
 {
-    if (rx.state != rx_state::normal)
-	port().write(WRST);
+    if (rx.state != rx_state::normal) {
+	port().write(NAK);
+    }
+}
+
+static int filter_echo(int byte)
+{
+    if (byte >= ' ' && byte < 127)
+	return byte;
+    if (byte == '\t' || byte == '\r' || byte == '\n')
+	return byte;
+
+    return -1;
+}
+
+// Decode a base64 data byte (using simple offset-63)
+// Call with -1 or any invalid value to invalidate the input buffer
+int TTY::_decode_data(int input)
+{
+    unsigned int buf = rx.b64_buf;
+    unsigned int inval = input - '?';
+
+    if (inval > 63) {
+	rx.b64_bits = 0;
+	return -2;		// Invalid input
+    }
+
+    rx.b64_buf = buf = (buf << 6) + inval;
+    rx.b64_bits += 6;
+
+    if (rx.b64_bits >= 8) {
+	rx.b64_bits -= 8;
+	return (uint8_t)(buf >> rx.b64_bits);
+    } else {
+	return -1;
+    }
 }
 
 // Change this to be a buffer...
 void TTY::_onrx()
 {
-    int byte;
-    int len;
-
-    while (1) {
-	int byte = port().read();
-	if (byte == -1)
-	    break;		// No more data
+    int byte, data;
 
+    while ((byte = port().read()) >= 0) {
 	switch (rx.state) {
 	case rx_state::normal:
-	    if (byte == fwupload_start[rx.rlen]) {
+	    if (rx.rlen < sizeof fwupload_start &&
+		byte == fwupload_start[rx.rlen]) {
 		if (!fwupload_start[++rx.rlen]) {
 		    _upload_begin();
 		    byte = -1;
@@ -151,69 +200,137 @@ void TTY::_onrx()
 		    byte = ETB;	// Not in file upload state
 		    break;
 		default:
-		    // Normal echo
+		    // Echo if printable
+		    byte = filter_echo(byte);
 		    break;
 		}
 	    }
 	    break;
 
-	case rx_state::hdr:
-	    if (rx.rlen > 0 || byte == STX) {
+	case rx_state::stxwait:
+	    switch (byte) {
+	    case STX:
+		rx.rlen = 0;
 		rx.hdr_raw[rx.rlen++] = byte;
+		rx.b64_bits = 0;
+		rx.state = rx_state::hdr;
+		byte = -1;
+		break;
+	    case ETX:
+	    case CAN:
+		printf("[UPLD] Received <%02X> waiting for STX\n", byte);
+		reset();
+		byte = CAN;
+		break;
+	    case ENQ:
+		rx.rlen = 0;
+		byte = GS;		 // In upload wait for STX state
+		break;
+	    case SYN:
+		rx.rlen = 0;
+		tx_credits_reset = true; // Request to resync credits
 		byte = -1;
+		break;
+	    case EOT:
+		// Upload complete, no more data
+		byte = ETB;
+		break;
+	    default:
+		byte = -1; // No echo
+		break;
+	    }
+	    break;
+
+	case rx_state::hdr:
+	    data = _decode_data(byte);
+	    byte = -1;
+	    if (data < -1) {
+		rx.state = rx_state::stxwait;
+		rx.rlen = 0;
+		tx_credits_reset = true;
+		byte = US;	// Framing error
+	    } else if (data == -1) {
+		// Nothing to do
+	    } else if (rx.rlen >= sizeof rx.hdr_raw) {
+		// ERROR THIS SHOULD NEVER HAPPEN
+		printf("[UPLD] Header buffer overrun!!!\n");
+		reset();
+		byte = CAN;
+	    } else {
+		rx.hdr_raw[rx.rlen++] = data;
 		if (rx.rlen == sizeof rx.hdr) {
 		    // Start of data packet
+		    printf("[UPLD] Start packet hdr %d length %d offset %d last_ack %d\n",
+			   rx.rlen, rx.hdr.len+1, rx.hdr.offs, rx.last_ack);
 		    rx.state = rx_state::data;
 		    rx.rlen = 0;
 		}
-	    } else {
-		switch (byte) {
-		case ETX:
-		case EOT:
-		case CAN:
-		    reset();
-		    byte = CAN;
-		    break;
-		case ENQ:
-		    byte = SYN;	// In file upload state
-		    break;
-		default:
-		    // Normal echo
-		    break;
-		}
 	    }
 	    break;
 
 	case rx_state::data:
-	    rx_data[rx.rlen++] = byte;
+	    data = _decode_data(byte);
 	    byte = -1;
-	    // 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);
-
-		if (crc != rx.hdr.crc) {
-		    byte = NAK;
-		} else if (rx.hdr.offs > rx.last_ack) {
-		    byte = EM;
-		} else {
-		    int sent = 0;
-
-		    if (rx.hdr.offs + rx.rlen <= rx.last_ack) {
-			have = 0;
+	    if (data < -1) {
+		rx.state = rx_state::stxwait;
+		rx.rlen = 0;
+		tx_credits_reset = true;
+		byte = US;	// Framing error
+	    } else if (data == -1) {
+		// Nothing to do
+	    } else if (rx.rlen >= sizeof rx_data) {
+		// ERROR THIS SHOULD NEVER HAPPEN
+		printf("[UPLD] Packet data buffer overrun!!!\n");
+		reset();
+		byte = CAN;
+	    } else {
+		rx_data[rx.rlen++] = data;
+		// rx.hdr.len = packet data len - 1
+		if (rx.rlen > rx.hdr.len) {
+		    int have = rx.rlen;
+		    uint32_t crc;
+
+		    if (have != rx.hdr.len + 1) {
+			printf("[UPLD] Invalid packet length (should not happen...)\n");
+			byte = NAK;
+		    } else if ((crc = crc32_le(0, rx_data, have))
+			       != rx.hdr.crc) {
+			printf("[UPLD] Packet CRC error hdr %08x data %08x\n", rx.hdr.crc, crc);
+			printf("\"");
+			for (int i = 0; i < have; i++)
+			    printf("\\x%02x", rx_data[i]);
+			printf("\"\n");
+			byte = NAK;
+		    } else if (rx.hdr.offs > rx.last_ack) {
+			printf("[UPLD] Invalid packet offsets [%d..%d) at %d\n",
+			       rx.hdr.offs, rx.hdr.offs + have, rx.last_ack);
+			byte = EM;
+		    } else if (rx.hdr.offs + have <= rx.last_ack) {
+			// Ack and discard packet below current window (transmitter is catching up)
+			byte = ACK;
 		    } else {
+			int sent = 0;
+
 			uint32_t skip = rx.last_ack - rx.hdr.offs;
 			have -= skip;
 			sent = xStreamBufferSend(rx_sbuf, rx_data+skip, have, 0);
 			rx.last_ack += sent;
-		    }
 
-		    byte = (sent == have) ? ACK : WRST;
+			if (sent != have) {
+			    printf("[UPLD] Packet underrun, got %d, expected %d\n", sent, have);
+			    byte = NAK;
+			} else {
+			    printf("[UPLD] %d bytes received OK\n", sent);
+			    byte = ACK;
+			}
+		    }
+		    if (byte != ACK)
+			tx_credits_reset = true;
+		    rx.state = rx_state::stxwait;
+		    rx.rlen  = 0;
 		}
-
-		rx.state = rx_state::hdr;
-		rx.rlen  = 0;
 	    }
+
 	    break;
 	}
 

+ 7 - 3
esp32/max80/tty.h

@@ -22,7 +22,7 @@ struct tty_packet_hdr {
     uint16_t resv;
     uint32_t offs;
     uint32_t crc;
-};
+} __attribute__((packed));
 
 class TTY {
 private:
@@ -56,16 +56,20 @@ private:
 
     enum rx_state {
 	normal,		// Normal console mode
+	stxwait,	// Waiting for header STX
 	hdr,		// Getting header
 	data		// Getting data packet
     };
 
     void _upload_begin();
     void _update_window(bool rst = false);
+    int _decode_data(int);
 
     // Packet receive state machine
     struct {
 	enum rx_state state;
+	uint8_t b64_buf;
+	uint8_t b64_bits;
 	union {
 	    struct tty_packet_hdr hdr;
 	    uint8_t hdr_raw[sizeof(struct tty_packet_hdr)];
@@ -74,7 +78,7 @@ private:
 	uint32_t last_ack;
     } rx;
     StreamBufferHandle_t rx_sbuf;
-    uint32_t tx_credits_ok;
-    uint32_t tx_credits_sent;
+    uint32_t tx_credits;
+    volatile bool tx_credits_reset;
     uint8_t rx_data[256];
 };

+ 48 - 20
esp32/max80/wifi.cpp

@@ -11,7 +11,7 @@
 #include <esp_sntp.h>
 #include <esp_wifi.h>
 
-static String ssid, password, hostname, dnsserver;
+static const char *ssid, *password, *hostname, *dnsserver;
 static TimerHandle_t sta_failure_timer;
 
 static bool sta_timeout_enabled;
@@ -102,7 +102,7 @@ static void sntp_server_show(void)
 	printf("[SNTP] Time server: %s\n", sntp_server);
 	setenv_cond("status.net.sntp.server", sntp_server);
     } else {
-	unsetenv("status.net.sntp.server");
+	setenv_cond("status.net.sntp.server", NULL);
     }
 }
 
@@ -137,7 +137,7 @@ static void start_services(void)
     if (invalid_ip(dns_ip) || getenv_bool("ip4.dhcp.nodns")) {
 	/* Static DNS server configuration */
 	ip_addr_t addr;
-	if (dnsserver != "" && inet_aton(dnsserver.c_str(), &addr)) {
+	if (dnsserver && inet_aton(dnsserver, &addr)) {
 	    if (memcmp(dns_ip, &addr, sizeof addr))
 		dns_setserver(0, &addr);
 	}
@@ -314,7 +314,7 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	sta_timeout_disable();
 	if (!ap_clients)
 	    WiFi.enableAP(false);
-    } else if (ssid != "") {
+    } else if (ssid) {
 	sta_timeout_enable();
     }
 
@@ -341,7 +341,7 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	    setenv_ip("status.net.eth.ip4.gw", WiFi.gatewayIP());
 	}
 
-	if (ssid == "") {
+	if (!ssid) {
 	    // No network configured
 	    led_set(LED_GREEN, connected & CON_AP ? LED_FLASH_SLOW : LED_OFF);
 	} else {
@@ -368,7 +368,7 @@ static void setenv_mac(const char *var, const uint8_t mac[6])
 
     snprintf(mac_str, sizeof mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
 	     mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-    setenv(var, mac_str, 1);
+    setenv_cond(var, mac_str);
 }
 
 static void wifi_config_ap(void)
@@ -377,10 +377,10 @@ static void wifi_config_ap(void)
     IPAddress AP_IP      = IPAddress(192,168,0,1);
     IPAddress AP_Netmask = IPAddress(255,255,255,0);
     IPAddress AP_Gateway = IPAddress(0,0,0,0); // No gateway
-    unsigned int channel = time(NULL) % 11;	   // Pseudo-random
+    unsigned int channel = (time(NULL) % 11) + 1;	   // Pseudo-random
     uint8_t mac[6];
     char mac_str[6*3];
-    char ap_ssid[64];
+    static char ap_ssid[64];
 
     WiFi.softAPmacAddress(mac);
     setenv_mac("status.net.ap.mac", mac);
@@ -388,64 +388,92 @@ static void wifi_config_ap(void)
     snprintf(ap_ssid, sizeof ap_ssid, "MAX80_%02X%02X", mac[4], mac[5]);
 
     printf("[WIFI] AP SSID %s IP %s netmask %s channel %u\n",
-	       ap_ssid, AP_IP.toString(), AP_Netmask.toString(), channel+1);
+	       ap_ssid, AP_IP.toString(), AP_Netmask.toString(), channel);
     setenv_cond("status.net.ap.ssid", ap_ssid);
     setenv_ip("status.net.ap.ip4", AP_IP);
     setenv_ip("status.net.ap.ip4.mask", AP_Netmask);
     setenv_ul("status.net.ap.clients", 0);
 
-    WiFi.softAP(ap_ssid, NULL, channel+1, 0, 4, true);
+    printf("WiFi.softAP\n");
+    WiFi.softAP(ap_ssid, NULL, channel, 0, 4, true);
+    printf("WiFi.softAPConfig\n");
     WiFi.softAPConfig(AP_IP, AP_Gateway, AP_Netmask);
+    printf("WiFi.softAPsetHostname\n");
     WiFi.softAPsetHostname(ap_ssid);
 
     // Conservative setting: 20 MHz (single channel) only; this is for
     // reliability, not performance.
+    printf("esp_wifi_set_bandwidth\n");
     esp_wifi_set_bandwidth((wifi_interface_t)ESP_IF_WIFI_AP, WIFI_BW_HT20);
 
     // Enable unconditionally if no SSID
-    WiFi.enableAP(ssid == "");
+    printf("WiFi.enableAP\n");
+    WiFi.enableAP(!ssid);
 }
 
 static void wifi_config_sta(void)
 {
     uint8_t mac[6];
+
+    printf("WiFi.macAddress\n");
     WiFi.macAddress(mac);
+    printf("setenv_mac\n");
     setenv_mac("status.net.sta.mac", mac);
 
-    setenv_cond("status.net.sta.ssid", ssid.c_str());
-    if (ssid == "") {
+    printf("setenv ssid\n");
+    setenv_cond("status.net.sta.ssid", ssid);
+    if (!ssid) {
 	WiFi.enableSTA(false);
 	return;
     }
 
+    printf("xTimerCreate\n");
     sta_failure_timer = xTimerCreate("wifi_sta", configTICK_RATE_HZ*30,
 				     pdFALSE, NULL,
 				     (TimerCallbackFunction_t)sta_timeout);
+    printf("sta_timeout_enable\n");
     sta_timeout_enable();
 
-    WiFi.begin(ssid.c_str(), password.c_str());
+    printf("WiFi.begin(%s,%s)\n", ssid, password);
+    WiFi.begin(ssid, password);
+    printf("WiFi.setAutoConnect\n");
     WiFi.setAutoConnect(true);
+    printf("WiFi.setAutoReconnect\n");
     WiFi.setAutoReconnect(true);
+    printf("WiFi.enableSTA\n");
     WiFi.enableSTA(true);
 }
 
+static const char *getenv_notempty(const char *env)
+{
+    const char *str = getenv(env);
+    if (str && !*str)
+	str = NULL;
+    return str;
+}
+
 static void wifi_config(void)
 {
-    ssid         = getenv("wifi.ssid");
-    password     = getenv("wifi.psk");
-    hostname     = getenv("hostname");
-    dnsserver    = getenv("ip4.dns");
+    ssid         = getenv_notempty("wifi.ssid");
+    password     = getenv_notempty("wifi.psk");
+    hostname     = getenv_notempty("hostname");
+    dnsserver    = getenv_notempty("ip4.dns");
 
     force_conn_update = true;
 
+    printf("WiFi.persistent\n");
     WiFi.persistent(false);
+    printf("WiFi.setSleep\n");
     WiFi.setSleep(false);
 
-    if (hostname != "")
+    if (hostname)
 	WiFi.hostname(hostname);
 
-    wifi_config_sta();
+    printf("wifi_config_ap\n");
     wifi_config_ap();
+    printf("wifi_config_sta\n");
+    wifi_config_sta();
+    printf("wifi_config done\n");
 }
 
 void SetupWiFi() {

BIN
esp32/output/max80.ino.bin


+ 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 = 15:34:48  July 30, 2022
+# Date created = 11:36:55  August 08, 2022
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "21.1"
-DATE = "15:34:48  July 30, 2022"
+DATE = "11:36:55  August 08, 2022"
 
 # Revisions
 
-PROJECT_REVISION = "v1"
 PROJECT_REVISION = "v2"
+PROJECT_REVISION = "v1"
 PROJECT_REVISION = "bypass"

BIN
fpga/output/bypass.rbf.gz


BIN
fpga/output/bypass.rpd.gz


BIN
fpga/output/bypass.sof


BIN
fpga/output/bypass.svf.gz


BIN
fpga/output/bypass.xsvf.gz


BIN
fpga/output/v1.fw


BIN
fpga/output/v1.jic


BIN
fpga/output/v1.rbf.gz


BIN
fpga/output/v1.rpd.gz


BIN
fpga/output/v1.sof


BIN
fpga/output/v1.svf.gz


BIN
fpga/output/v1.xsvf.gz


BIN
fpga/output/v2.fw


BIN
fpga/output/v2.jic


BIN
fpga/output/v2.rbf.gz


BIN
fpga/output/v2.rpd.gz


BIN
fpga/output/v2.sof


BIN
fpga/output/v2.svf.gz


BIN
fpga/output/v2.xsvf.gz