Browse Source

esp32: refactor fw update code to allow one-shot serial port upload

Rejig the firmware update code so that it always runs in a separate
thread, and can receive data either from UART, ACM, or TCP.

This will be used by flashesp.pl to be able to upload the FPGA firmware.
H. Peter Anvin 2 years ago
parent
commit
8f013f9ea9

+ 3 - 1
common/compiler.h

@@ -121,7 +121,9 @@ typedef unsigned __int128 size2_t;
 
 #define __is_constant(expr)	__builtin_constant_p(expr)
 
-#define atomic(x)		(*(volatile __typeof__(x) *)&(x))
+#ifndef __cplusplus
+# define atomic(x)		(*(volatile __typeof__(x) *)&(x))
+#endif
 
 #else /* __ASSEMBLY__ */
 

+ 12 - 0
esp32/max80/common.h

@@ -56,6 +56,18 @@ static inline void suspend(void)
     vTaskSuspend(NULL);
 }
 
+/*
+ * Kill thread; vTaskDelete(NULL) *should* never return...
+ */
+static inline no_return exit_task(void)
+{
+    vTaskDelete(NULL);
+    while (1) {
+	/* vTaskDelete() returned!? */
+	vTaskSuspend(NULL);
+    }
+}
+
 /*
  * Reboot system
  */

+ 2 - 1
esp32/max80/fw.h

@@ -25,6 +25,7 @@
 #define FWUPDATE_ERR_CONFIG_READ	(-24)
 #define FWUPDATE_ERR_CONFIG_SAVE	(-25)
 
-extern_c int firmware_update(read_func_t read_func, token_t token);
+extern_c int firmware_update_start(read_func_t read_data, token_t token);
+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);

+ 75 - 13
esp32/max80/fwupdate.c

@@ -22,6 +22,8 @@
 #endif
 
 #define BUFFER_SIZE		SPIFLASH_SECTOR_SIZE
+#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)
@@ -323,24 +325,22 @@ const char *firmware_errstr(int err)
     }
 }
 
-int firmware_update(read_func_t read_data, token_t token)
-{
-    struct spz_stream *spz = calloc(1, sizeof *spz);
-    int err = 0;
+static TaskHandle_t fwupdate_task;
+static struct spz_stream *fwupdate_spz;
+static SemaphoreHandle_t fwupdate_done;
+static int fwupdate_err;
 
-    if (!spz) {
-	return Z_MEM_ERROR;
-    }
+static void firmware_update_task(void *pvt)
+{
+    struct spz_stream *spz = pvt;
 
     fpga_service_enable(false);
 
-    spz->read_data = read_data;
-    spz->token = token;
-
-    err = fwupdate_data_init(spz);
-    if (err)
+    spz->err = fwupdate_data_init(spz);
+    if (spz->err)
 	goto fail;
 
+    int err;
     while (!(err = fwupdate_process_chunk(spz))) {
 	/* Process data chunks until end */
     }
@@ -353,6 +353,68 @@ int firmware_update(read_func_t read_data, token_t token)
 	MSG("failed (err %d)\n", err);
 
 fail:
-    free(spz);
+    xSemaphoreGive(fwupdate_done);
+    exit_task();
+}
+
+static int firmware_update_cleanup(void)
+{
+    int err = Z_MEM_ERROR;
+
+    fwupdate_task = NULL;
+
+    if (fwupdate_done) {
+	SemaphoreHandle_t done = fwupdate_done;
+	fwupdate_done = NULL;
+	vSemaphoreDelete(done);
+    }
+    if (fwupdate_spz) {
+	struct spz_stream *spz = fwupdate_spz;
+	err = spz->err;
+	fwupdate_spz = NULL;
+	free(spz);
+    }
+
     return err;
 }
+
+int firmware_update_start(read_func_t read_data, token_t token)
+{
+    int err;
+    SemaphoreHandle_t done = NULL;
+
+    if (fwupdate_spz)
+	return FWUPDATE_ERR_IN_PROGRESS;
+
+    fwupdate_spz = calloc(1, sizeof *fwupdate_spz);
+    if (!fwupdate_spz)
+	goto err;
+    fwupdate_spz->err = Z_MEM_ERROR;
+
+    fwupdate_done = xSemaphoreCreateBinary();
+    if (!fwupdate_done)
+	goto err;
+
+    fwupdate_spz->read_data = read_data;
+    fwupdate_spz->token     = token;
+    if (xTaskCreate(fwupdate_task, "fwupdate",
+		    FWUPDATE_STACK, &fwupdate_spz,
+		    FWUPDATE_PRIORITY, &fwupdate_task) != pdPASS) {
+	xSemaphoreGive(fwupdate_done);
+    }
+    return Z_OK;
+
+err:
+    return firmware_update_cleanup();
+}
+
+int firmware_update_wait(TickType_t delay)
+{
+    if (!fwupdate_done)
+	return Z_MEM_ERROR;
+
+    if (!xSemaphoreTake(fwupdate_done, delay))
+	return FWUPDATE_ERR_IN_PROGRESS;
+
+    return firmware_update_cleanup();
+}

+ 4 - 1
esp32/max80/httpd.c

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

+ 12 - 31
esp32/max80/max80.ino

@@ -8,31 +8,16 @@
 #include "wifi.h"
 #include "config.h"
 #include "led.h"
+#include "tty.h"
 
 #include <freertos/task_snapshot.h>
 #include <esp_heap_caps.h>
 #include <esp_task_wdt.h>
 #include <esp_mac.h>
+
 #include <USB.h>
 #include <HardwareSerial.h>
 
-static void Read_Echo(Stream &from)
-{
-    int byte;
-
-    while ((byte = from.read()) >= 0)
-	from.write(byte);
-}
-
-static void onrx_usb(void *foo, const char *bar, int baz, void *quux)
-{
-    Read_Echo(Serial);
-}
-static void onrx_uart(void)
-{
-    Read_Echo(Serial0);
-}
-
 #define PIN_USB_PWR_EN		7
 #define PIN_USB_PWR_SINK	8
 
@@ -66,9 +51,6 @@ uint8_t max80_board_version;
 
 static void init_hw()
 {
-    Serial0.begin(115200);
-    Serial0.onReceive(onrx_uart, false);
-
     // Start out with disabled shared I/O pins
     for (int i = 1; i <= 18; i++)
 	pinMode(i, INPUT);
@@ -99,24 +81,25 @@ static void init_hw()
     digitalWrite(PIN_USB_PWR_EN, 0);
     delayMicroseconds(50);
 
-    Serial.onEvent(ARDUINO_USB_CDC_RX_EVENT, onrx_usb);
-
     // Configure LEDs
     led_init();
     led_set(LED_BLUE, LED_FLASH);	// ESP32 software initializing
-
-    // Enable external PSRAM for heap
-    heap_caps_malloc_extmem_enable(2048); // >= 2K allocations in PSRAM
-
-    printf("[PCB]  MAX80 board version: %u\n", max80_board_version);
-    setenv_ul("status.max80.hw.ver", max80_board_version);
 }
 
 void setup() {
     const char *fwdate = __DATE__ " " __TIME__;
     printf("[START] MAX80 firmware compiled on %s\n", fwdate);
     init_hw();
+
+    // Enable external PSRAM for heap
+    heap_caps_malloc_extmem_enable(2048); // >= 2K allocations in PSRAM
+
+    TTY::init();
+
+    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();
@@ -175,8 +158,6 @@ static void dump_tasks(void)
 }
 
 void loop() {
-    static int x = 0;
-
     if (0) {
 	printf("loop task: %s\n", pcTaskGetName(xTaskGetCurrentTaskHandle()));
 	printf("idle task: %s\n", pcTaskGetName(xTaskGetIdleTaskHandle()));
@@ -186,6 +167,6 @@ void loop() {
 	putchar('\n');
     }
 
-    Serial.println(++x);
+    TTY::ping();
     vTaskDelay(2 * configTICK_RATE_HZ);
 }

+ 300 - 0
esp32/max80/tty.cpp

@@ -0,0 +1,300 @@
+#define BAUD_RATE 115200
+
+#include "tty.h"
+#include "config.h"
+#include "fw.h"
+
+#include "rom/crc.h"
+
+#include <USB.h>
+#include <HardwareSerial.h>
+
+#define SOH  '\001'
+#define STX  '\002'
+#define ETX  '\003'
+#define EOT  '\004'
+#define ENQ  '\005'
+#define ACK  '\006'
+#define XON  '\021'		// DC1
+#define WRST '\022'		// DC2 - window = 0
+#define XOFF '\023'		// DC3
+#define WGO  '\024'		// DC4 - window + 256 bytes
+#define NAK  '\025'
+#define SYN  '\026'
+#define ETB  '\027'		// Not in upload mode
+#define CAN  '\030'
+#define EM   '\031'		// Packet offset too high
+#define FS   '\034'
+#define GS   '\035'
+#define RS   '\036'
+#define US   '\037'
+
+#define WGO_CHUNK	256
+
+#define STREAMBUF_SIZE	8192
+
+static char enq_str[] = "\026\035MAX80 v0\004\r\n";
+static const char fwupload_start[] =
+    "\034\001: /// MAX80 FW UPLOAD ~@~ $\035\r\n";
+
+void TTY::reset()
+{
+    rx.state  = rx_state::normal;
+}
+
+TTY::TTY(Stream &port)
+{
+    _port = &port;
+    rx_sbuf = NULL;
+    win_sem = xSemaphoreCreateMutexStatic(&win_sem_ss);
+    reset();
+}
+
+TTY::~TTY()
+{
+    if (rx_sbuf)
+	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)
+{
+    if (!rx_sbuf)
+	return 0;
+
+    size_t rcv = xStreamBufferReceive(rx_sbuf, buf, len, portMAX_DELAY);
+    _update_window(false);
+
+    return rcv;
+}
+
+int TTY::rxdata(token_t me, void *buf, size_t len)
+{
+    TTY *tty = (TTY *)me;
+    return tty->rxdata(buf, len);
+}
+
+void TTY::_upload_begin()
+{
+    if (rx_sbuf)
+	xStreamBufferReset(rx_sbuf);
+    else
+	rx_sbuf = xStreamBufferCreate(STREAMBUF_SIZE, 1);
+
+    if (!rx_sbuf)
+	goto nak;
+
+    if (firmware_update_start((read_func_t)TTY::rxdata, (token_t)this))
+	goto nak;
+
+    // Respond with WRST + n*WGO
+    _update_window(true);
+    rx.state = rx_state::hdr;
+    return;
+
+ nak:
+    port().write(NAK);
+    rx.state = rx_state::normal;
+    return;
+}
+
+void TTY::_onerr()
+{
+    if (rx.state != rx_state::normal)
+	_update_window(true);
+}
+
+// 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
+
+	switch (rx.state) {
+	case rx_state::normal:
+	    if (byte == fwupload_start[rx.rlen]) {
+		if (!fwupload_start[++rx.rlen])
+		    _upload_begin();
+	    } else {
+		rx.rlen = 0;
+		switch (byte) {
+		case ENQ:
+		    port().write(enq_str);
+		    byte = ETB;
+		    break;
+		case ETX:
+		case EOT:
+		case CAN:
+		    byte = ETB;	// Not in file upload state
+		    break;
+		default:
+		    // Normal echo
+		    break;
+		}
+	    }
+	    break;
+
+	case rx_state::hdr:
+	    if (rx.rlen > 0 || byte == STX) {
+		rx.hdr_raw[rx.rlen++] = byte;
+		byte = -1;
+		if (rx.rlen == sizeof rx.hdr) {
+		    // Start of data packet
+		    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;
+	    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;
+
+		    tx_window -= rx.rlen;
+
+		    if (rx.hdr.offs + rx.rlen <= rx.last_ack) {
+			have = 0;
+		    } else {
+			uint32_t skip = rx.last_ack - rx.hdr.offs;
+			have -= skip;
+			sent = xStreamBufferSend(rx_sbuf, rx_data+skip, have, 0);
+			rx.last_ack += sent;
+		    }
+
+		    if (sent == have)
+			byte = ACK;
+		    else
+			_update_window(true);
+
+		    rx.state = rx_state::hdr;
+		    rx.rlen  = 0;
+		}
+	    }
+	    break;
+	}
+
+	if (byte >= 0)
+	    port().write(byte);
+    }
+}
+
+void TTY::_onconnect()
+{
+    port().write(XON);
+}
+
+void TTY::_onbreak()
+{
+    reset();
+}
+
+void TTY::_ondisconnect()
+{
+    reset();
+}
+
+static TTY *uart_tty, *usb_tty;
+
+void TTY::usb_onevent(void *arg, esp_event_base_t event_base,
+		      int32_t event_id, void *event_data)
+{
+    switch (event_id) {
+    case ARDUINO_USB_CDC_CONNECTED_EVENT:
+    case ARDUINO_USB_CDC_LINE_STATE_EVENT:
+	usb_tty->_onconnect();
+	break;
+    case ARDUINO_USB_CDC_DISCONNECTED_EVENT:
+	usb_tty->_ondisconnect();
+	break;
+    case ARDUINO_USB_CDC_RX_EVENT:
+	usb_tty->_onrx();
+	break;
+    case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT:
+	usb_tty->_onerr();
+	break;
+    default:
+	// Do nothing
+	break;
+    }
+}
+
+void TTY::uart_onrx(void)
+{
+    uart_tty->_onrx();
+}
+
+void TTY::uart_onerr(hardwareSerial_error_t err)
+{
+    switch (err) {
+    case UART_BREAK_ERROR:
+	uart_tty->_onbreak();
+	break;
+    default:
+	uart_tty->_onerr();
+	break;
+    }
+}
+
+void TTY::init()
+{
+    enq_str[sizeof(enq_str)-5] += max80_board_version;
+
+    uart_tty = new TTY(Serial0);
+    Serial0.begin(BAUD_RATE);
+    Serial0.onReceive(uart_onrx, false);
+    Serial0.onReceiveError(uart_onerr);
+
+    usb_tty  = new TTY(Serial);
+    Serial.onEvent(usb_onevent);
+}
+
+void TTY::ping()
+{
+}

+ 81 - 0
esp32/max80/tty.h

@@ -0,0 +1,81 @@
+#pragma once
+
+#include "common.h"
+
+#include <USB.h>
+#include <HardwareSerial.h>
+#include <FreeRTOS.h>
+#include <freertos/stream_buffer.h>
+
+#include <atomic>
+
+void tty_init();
+void tty_ping();
+
+//
+// This is needed because there isn't a common serial device
+// class in Arduino, sigh...
+//
+struct tty_packet_hdr {
+    uint8_t  stx;
+    uint8_t  len;
+    uint16_t resv;
+    uint32_t offs;
+    uint32_t crc;
+};
+
+class TTY {
+private:
+    Stream *_port;		// Android stream
+
+public:
+    TTY(Stream &port);
+    ~TTY();
+    void reset();
+    int  rxdata(void *buf, size_t len);
+    static int rxdata(token_t me, void *buf, size_t len);
+    inline Stream & port() { return *_port; }
+    inline operator Stream & () { return port(); }
+
+    // Global events
+    static void init();
+    static void ping();
+
+private:
+    void _onrx();
+    void _onerr();
+    void _onbreak();
+    void _onconnect();
+    void _ondisconnect();
+
+    // Event handler dispatchers
+    static void usb_onevent(void *arg, esp_event_base_t event_base,
+			    int32_t event_id, void *event_data);
+    static void uart_onrx();
+    static void uart_onerr(hardwareSerial_error_t);
+
+    enum rx_state {
+	normal,		// Normal console mode
+	hdr,		// Getting header
+	data		// Getting data packet
+    };
+
+    void _upload_begin();
+    void _update_window(bool rst = false);
+
+    // Packet receive state machine
+    struct {
+	enum rx_state state;
+	union {
+	    struct tty_packet_hdr hdr;
+	    uint8_t hdr_raw[sizeof(struct tty_packet_hdr)];
+	};
+	size_t rlen;
+	uint32_t last_ack;
+    } rx;
+    StreamBufferHandle_t rx_sbuf;
+    SemaphoreHandle_t win_sem;
+    StaticSemaphore_t win_sem_ss;
+    std::atomic_uint32_t tx_window;
+    uint8_t rx_data[256];
+};

BIN
esp32/output/max80.ino.bin


+ 2 - 2
fpga/max80.qpf

@@ -19,12 +19,12 @@
 #
 # Quartus Prime
 # Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
-# Date created = 15:52:01  June 22, 2022
+# Date created = 17:14:01  June 24, 2022
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "21.1"
-DATE = "15:52:01  June 22, 2022"
+DATE = "17:14:01  June 24, 2022"
 
 # Revisions
 

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