Browse Source

Integrate esp32 build using arduino-cli; www contents in zip file (WIP)

H. Peter Anvin 2 years ago
parent
commit
8a98d85fa5

+ 4 - 2
Makefile

@@ -1,4 +1,6 @@
-SUBDIRS   := tools rv32 fpga
+MAKEFLAGS += -R -r
+
+SUBDIRS   := esp32 tools rv32 fpga
 REVISIONS := v1 v2
 
 all clean spotless :
@@ -16,7 +18,7 @@ $(SUBDIRS):
 
 rv32: | tools
 
-fpga: | rv32
+fpga: | rv32 esp32
 
 local.all:
 

+ 12 - 0
esp32/.gitignore

@@ -0,0 +1,12 @@
+*~
+\#*
+.\#*
+*.zip
+applet/
+build/
+*.bootloader.bin
+*.partitions.bin
+*.elf
+*.map
+
+

+ 36 - 0
esp32/Makefile

@@ -0,0 +1,36 @@
+MAKEFLAGS += -R -r
+
+ARDUINO_CLI = arduino-cli
+ZIP         = zip
+
+SKETCH   = max80
+TARGET   = output/$(SKETCH).ino.bin
+GENFILES = $(SKETCH)/data/www.zip
+WWW      = www
+PORT	?= /dev/ttyACM2
+
+BOARD    = esp32:esp32:esp32s2usb
+
+all: $(TARGET)
+
+$(TARGET): $(shell find $(SKETCH) -type f) $(GENFILES)
+	mkdir -p build output
+	cd $(SKETCH) && \
+		$(ARDUINO_CLI) compile -b $(BOARD) \
+			--build-path ../build --output-dir ../output
+
+wwwfiles := $(shell find www -type f -print)
+
+$(SKETCH)/data/www.zip: $(wwwfiles)
+	rm -f $@
+	( cd www && $(ZIP) -9DrpX - . -x '.*' -x '#*' -x '*~' -x '*.bak' ) \
+		> $@
+
+upload: $(TARGET)
+	$(ARDUINO_CLI) upload -i $(TARGET) -p $(PORT) -b $(BOARD) $(SKETCH)
+
+clean:
+	rm -rf build $(SKETCH)/data/*.zip
+
+spotless: clean
+	rm -rf output

+ 2 - 0
esp32/max80/fpga.c

@@ -75,6 +75,8 @@ static void fpga_finish(void)
     tap_set_ir(JI_BYPASS, FPGA_IR_LEN);
     tap_run_test_idle(JTAG_FPGA_MS);
 
+    /* Reset?! */
+    
     jtag_disable(NULL);
 }
 

+ 191 - 35
esp32/max80/httpd.c

@@ -4,6 +4,9 @@
 #include "fw.h"
 #include "httpd.h"
 
+#include <incbin.h>
+#include <unzipLIB.h>
+
 static httpd_handle_t httpd;
 
 esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
@@ -16,7 +19,7 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 	   req->content_len, req->uri);
 
     if (!req->content_len) {
-	return httpd_resp_send_err(req, 411, NULL);
+	return httpd_resp_send_err(req, 411, "Length required");
     }
 
     rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
@@ -40,51 +43,203 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 		   reboot_delayed());
 
     /* 200 and text/html are the response defaults, no need to set */
+    httpd_resp_set_hdr(req, "Connection", "close");
     err = httpd_resp_send(req, response, len);
     free(response);
 
     return err;
 }
 
-static esp_err_t httpd_get_handler(httpd_req_t *req)
+INCBIN(wwwzip, "data/www.zip");
+
+struct mime_type {
+    const char *ext;
+    size_t ext_len;
+    const char *mime;
+};
+
+static const struct mime_type mime_types[] = {
+    { ".html", 5, "text/html"                },
+    { ".css",  4, "text/css"                  },
+    { ".txt",  4, "text/plain; charset=UTF-8" },
+    { ".bin",  4, "application/octet-stream"  },
+    { ".fw",   3, "application/octet-stream"  },
+    { NULL,    0, "text/html"  } /* Default */
+};
+
+static esp_err_t httpd_static_handler(httpd_req_t *req)
 {
-    char *response;
-    int len;
-    esp_err_t err;
+    static const char index_filename[] = "index.html";
+    const size_t buffer_size = UNZ_BUFSIZE;
+    const char *uri, *enduri;
+    bool add_index;
+    char *buffer = NULL;
+    ZIPFILE *zip = NULL;
+    unzFile unz  = NULL;
+    bool file_open = false;
+    int err = 0;
+    size_t len;
 
-    len = asprintf(&response,
-		   "<!DOCTYPE html>\n"
-		   "<html>\n"
-		   "<head>\n"
-		   "<title>Hello, World!</title>\n"
-		   "</head>\n"
-		   "<body>\n"
-		   "<p>Hello, World!</p>\n"
-		   "<p>GET uri = <tt>%s</tt></p>\n"
-		   "</pre>\n"
-		   "</body>\n"
-		   "</html>\n",
-		   req->uri);
+    uri = req->uri;
+    while (*uri == '/')
+	uri++;			/* Skip leading slashes */
+    
+    enduri = strchr(uri, '\0');
+    if (enduri == uri) {
+	uri = index_filename;	/* Empty string */
+    } else if (enduri[0] == '/') {
+	add_index = true;
+	enduri--;		/* Drop terminal slash */
+    }
 
-    /* 200 and text/html are the response defaults, no need to set */
-    err = httpd_resp_send(req, response, len);
-    free(response);
+    buffer = malloc(buffer_size);
+    zip = malloc(sizeof *zip);
+    if (!buffer || !zip) {
+	httpd_resp_send_err(req, 503, "Out of memory");
+	goto out;
+    }
+
+    if (enduri - req->uri + 1 + sizeof index_filename >= buffer_size) {
+	err = httpd_resp_send_err(req, 414, "URI too long");
+	goto out;
+    }
+
+    char *p = mempcpy(buffer, req->uri, enduri - req->uri);
+    *p = '\0';
+
+    int found;
+    while (1) {
+	if (add_index) {
+	    if (p > buffer)
+		*p++ = '/';
+	    p = mempcpy(p, index_filename, sizeof index_filename);
+	}
+
+	if (unzLocateFile(unz, buffer, 1) == UNZ_OK)
+	    break;
+
+	if (add_index) {
+	    err = httpd_resp_send_404(req);
+	    goto out;
+	}
+
+	add_index = true;	/* Try again with the index file */
+    }
+
+    /* Note: p points to the end of the filename string */
+    size_t filelen = p - buffer;
+    
+    const struct mime_type *mime_type = mime_types;
+    /* The default entry with extension "" will always match */
+    while (mime_type->ext_len) {
+	len = mime_type->ext_len;
+
+	if (len < filelen && !memcmp(p - len, mime_type->ext, len))
+	    break;
+
+	mime_type++;
+    }
+    
+    unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
+		  zip, NULL, NULL, NULL, NULL);
+    if (!unz) {
+	err = httpd_resp_send_err(req, 500, "Cannot open content archive");
+	goto out;
+    }
+
+    unz_file_info fileinfo;
+    memset(&fileinfo, 0, sizeof fileinfo);
+    unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
+
+    /*
+     * This is kind of brain dead, but it seems like the only sane
+     * way to not have to build the whole response in memory even
+     * though the length is known a priori.
+     */
+    
+    len = snprintf(buffer, buffer_size,
+		   "HTTP/1.1 200 OK\r\n"
+		   "Content-Type: %s\r\n"
+		   "Content-Length: %u\r\n"
+		   "Allow: GET, HEAD\r\n"
+		   "Etag: \"%08x:%08x\"\r\n"
+		   "Connection: close\r\n"
+		   "\r\n",
+		   mime_type->mime,
+		   fileinfo.uncompressed_size,
+		   /*
+		    * Hopefully the combination of date and CRC
+		    * is strong enough to quality for a "strong" ETag
+		    */
+		   fileinfo.dosDate, fileinfo.crc);
+
+    if (len >= buffer_size) {
+	err = httpd_resp_send_err(req, 500, "buffer_size too small");
+	goto out;
+    }
+
+    err = httpd_send(req, buffer, len);
+    if (err != len || req->method == HTTP_HEAD ||
+	!fileinfo.uncompressed_size)
+	goto out;
+
+    if (unzOpenCurrentFile(unz) != UNZ_OK) {
+	err = httpd_resp_send_err(req, 500, "Cannot open file in archive");
+	goto out;
+    }
+    file_open = true;
+    
+    len = fileinfo.uncompressed_size;
+    while (len) {
+	size_t chunk = len;
+	if (chunk > buffer_size)
+	    chunk = buffer_size;
+
+	if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
+	    err = ESP_ERR_HTTPD_RESULT_TRUNC;
+	    goto out;
+	}
+
+	err = httpd_send(req, buffer, chunk);
+	if (err != chunk)
+	    goto out;
+
+	len -= chunk;
+    }
+    err = ESP_OK;		/* All good! */
+
+out:
+    if (file_open)
+	unzCloseCurrentFile(unz);
+    if (unz)
+	unzClose(unz);
+    if (zip)
+	free(zip);
+    if (buffer)
+	free(buffer);
 
     return err;
 }
 
-static const httpd_uri_t uri_get = {
-    .uri      = "/",
-    .method   = HTTP_GET,
-    .handler  = httpd_get_handler,
-    .user_ctx = NULL
-};
-
-static const httpd_uri_t uri_firmware_upgrade = {
-    .uri      = "/firmware-upgrade",
-    .method   = HTTP_POST,
-    .handler  = httpd_firmware_upgrade_handler,
-    .user_ctx = NULL
+static const httpd_uri_t uri_handlers[] = {
+    {
+	.uri      = "/",
+	.method   = HTTP_GET,
+	.handler  = httpd_static_handler,
+	.user_ctx = NULL
+    },
+    {
+	.uri      = "/",
+	.method   = HTTP_HEAD,
+	.handler  = httpd_static_handler,
+	.user_ctx = NULL
+    },
+    {
+	.uri      = "/firmware-upgrade",
+	.method   = HTTP_POST,
+	.handler  = httpd_firmware_upgrade_handler,
+	.user_ctx = NULL
+    }
 };
 
 void my_httpd_stop(void)
@@ -114,8 +269,9 @@ void my_httpd_start(void)
       return;
 
     httpd = server;
-    httpd_register_uri_handler(httpd, &uri_get);
-    httpd_register_uri_handler(httpd, &uri_firmware_upgrade);
+    for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++)
+	httpd_register_uri_handler(httpd, &uri_handlers[i]);
+
     esp_register_shutdown_handler(my_httpd_stop);
     printf("[HTTP] httpd started\n");
 }

+ 1 - 1
esp32/max80/max80.ino

@@ -60,5 +60,5 @@ void loop() {
 
     putchar('\n');
 
-    vTaskDelay(20 * configTICK_RATE_HZ);
+    vTaskDelay(120 * configTICK_RATE_HZ);
 }

+ 1 - 1
esp32/max80/reboot.c

@@ -12,7 +12,7 @@ void reboot_now(void)
 
 int reboot_delayed(void)
 {
-    const int reboot_delay = 10; /* seconds */
+    const int reboot_delay = 5; /* seconds */
     TimerHandle_t timer;
 
     timer = xTimerCreate("reboot", configTICK_RATE_HZ*reboot_delay,

+ 1 - 4
esp32/max80/spiflash.c

@@ -372,8 +372,6 @@ int spiflash_write_spz(spz_stream *spz)
 	    bytes = data_left;
 	}
 
-	MSG("sector at %06x bytes %u\n", addr, bytes);
-
 	addr -= pre_padding;
 
 	/* Read the current content of this block into vbuf */
@@ -392,12 +390,11 @@ int spiflash_write_spz(spz_stream *spz)
 
 	rv = spz_read_data(spz, spz->dbuf+pre_padding, bytes);
 	if (rv != (int)bytes) {
-	    MSG("needed %u bytes got %d\n", rv);
+	    MSG("needed %u bytes got %d\n", bytes, rv);
 	    rv = Z_DATA_ERROR;
 	    goto err;
 	}
 
-	MSG("writing new data...\n");
 	rv = spiflash_write_sector(spz, addr);
 	if (rv) {
 	    spz->err = rv;

BIN
esp32/max80/max80.ino.esp32s2usb.bin → esp32/output/max80.ino.bin


+ 11 - 0
esp32/www/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>MAX80: Hello, World!</title>
+  </head>
+  <body>
+    <h1>Hello, World!</h1>
+    <p>This is a file from a zip archive on MAX80.</p>
+  </body>
+</html>
+

+ 11 - 0
esp32/www/other.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>MAX80: Hello, Other!</title>
+  </head>
+  <body>
+    <h1>Hello, Other!</h1>
+    <p>This is a file which is not called index.html.</p>
+  </body>
+</html>
+

+ 11 - 0
esp32/www/subdir/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>MAX80: Hello, Subdirectory!</title>
+  </head>
+  <body>
+    <h1>Hello, Subdirectory!</h1>
+    <p>This is a file from a zip archive on MAX80 in a subdirectory</p>
+  </body>
+</html>
+

+ 1 - 1
fpga/Makefile

@@ -47,7 +47,7 @@ sram_src = ../rv32/
 
 DRAM_ADDR  := 0x100000
 DRAM_IMAGE := ../rv32/dram.bin
-ESP_IMAGE  := ../esp32/max80/max80.ino.esp32s2usb.bin
+ESP_IMAGE  := ../esp32/output/max80.ino.bin
 
 IDCODE     := 0x020f20dd		# JTAG IDCODE of FPGA
 

+ 136 - 0
fpga/esp.sv

@@ -0,0 +1,136 @@
+//
+// Communication with ESP32
+//
+// Components are:
+// a. serial interface
+// b. SPI interface (not yet implemented)
+// c. common open drain IRQ line
+// d. ESP32 EN and IO0 lines
+//
+
+module esp (
+	    input 	      rst_n,
+	    input 	      clk,
+
+	    input 	      cpu_valid,
+	    input [4:0]       cpu_addr,
+	    input [3:0]       cpu_wstrb,
+	    input [31:0]      cpu_wdata,
+	    output reg [31:0] cpu_rdata,
+	    output reg	      irq,
+
+	    input 	      tty_rx,
+	    output 	      tty_tx,
+
+	    output 	      esp_en, // ESP reset#
+	    inout 	      esp_int,
+	    inout 	      esp_io0,
+
+	    inout 	      spi_clk,
+	    inout 	      spi_miso,
+	    inout 	      spi_mosi,
+	    inout 	      spi_cs_esp_n,
+	    inout 	      spi_cs_flash_n
+	    );
+
+   wire [31:0] 	  cpu_reg  = cpu_valid << cpu_addr;
+   wire 	  cpu_read = ~|cpu_wstrb;
+   
+   reg 		  tx_break_q;
+   wire 	  tx_full;
+   wire 	  tx_empty;
+   wire 	  rx_full;
+   wire 	  rx_empty;
+   wire 	  rx_break;
+   reg		  tx_flush;
+   reg 		  rx_flush;
+   wire [7:0] 	  tty_rdata;
+   wire [31:0] 	  tty_divisor;
+   
+   serial #(
+	    .BAUDRATE_SETTABLE ( 1'b1 )
+	    )
+   esptty (
+	   .rst_n  ( rst_n ),
+	   .clk    ( clk ),
+	   .tty_tx ( tty_tx ),
+	   .tty_rx ( tty_rx ),
+		  
+	   .tx_wstrb ( cpu_wstrb[0] & cpu_reg[0] ),
+	   .tx_data  ( cpu_wdata[7:0] ),
+	   .tx_break ( tx_break_q ),
+	   .tx_full  ( tx_full ),
+	   .tx_empty ( tx_empty ),
+	   .tx_flush ( tx_flush ),
+
+	   .rx_rstrb ( cpu_read & cpu_reg[0] ),
+	   .rx_data  ( tty_rdata ),
+	   .rx_break ( rx_break ),
+	   .rx_full  ( rx_full ),
+	   .rx_empty ( rx_empty ),
+	   .rx_flush ( rx_flush ),
+
+	   .divisor_wdata ( cpu_wdata ),
+	   .divisor_wstrb ( cpu_wstrb[0] & cpu_reg[1] ),
+	   .divisor  ( tty_divisor )
+	   );
+
+   // Control/clear status bits
+   reg 		  rx_break_q;
+   reg 		  esp_rst_q;
+   reg 		  esp_dl_q;	// Force download boot
+   reg 		  esp_int_q;
+
+   assign esp_en  = esp_rst_q ? 1'b0 : 1'bz;
+   assign esp_io0 = esp_dl_q  ? 1'b0 : 1'bz;
+   assign esp_int = esp_int_q ? 1'b0 : 1'bz;
+
+   always @(negedge rst_n or posedge clk)
+     if (~rst_n)
+       begin
+	  rx_break_q <= 1'b0;
+	  tx_break_q <= 1'b0;
+	  esp_rst_q  <= 1'b0;
+	  esp_dl_q   <= 1'b0;
+	  esp_int_q  <= 1'b0;
+       end
+     else
+       begin
+	  rx_break_q <= rx_break | rx_break_q;
+
+	  if (cpu_wstrb[0] & cpu_reg[2])
+	    begin
+	       if (cpu_wdata[2])
+		 rx_break_q <= rx_break;
+
+	       tx_break_q <= cpu_wdata[6];
+	    end
+
+	  if (cpu_wstrb[1] & cpu_reg[2])
+	    esp_int_q <= cpu_wdata[8];
+	  
+	  if (cpu_wstrb[2] & cpu_reg[2])
+	    begin
+	       esp_rst_q <= cpu_wdata[16];
+	       esp_dl_q  <= cpu_wdata[17];
+	    end
+       end // else: !if(~rst_n)
+
+   // Not used yet
+   assign irq = 1'b0;
+
+   // Output data MUX
+   always @(*)
+     case (cpu_addr)
+       5'd0: cpu_rdata = { 24'b0, tty_rdata };
+       5'd1: cpu_rdata = tty_divisor;
+       5'd2: cpu_rdata = {
+			  8'b0,
+			  6'b0, esp_dl_q, esp_rst_q,
+			  7'b0, ~esp_int,
+			  1'b0, tx_break_q, tx_full, ~tx_empty,
+			  1'b0, rx_break_q, rx_full, ~rx_empty
+			  };
+       default: cpu_rdata = 32'bx;
+     endcase // case (cpu_addr)
+endmodule // esp

BIN
fpga/output/v1.fw


BIN
fpga/output/v2.fw