diff --git a/avrflash b/avrflash new file mode 100755 index 0000000..20fe890 --- /dev/null +++ b/avrflash @@ -0,0 +1,110 @@ +#! /bin/bash +# +# Flash an AVR with optiboot using the esp-link built-in programmer +# Basically we first reset the AVR and get in sync, and then send the hex file +# +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# Thorsten von Eicken wrote this file. As long as you retain +# this notice you can do whatever you want with this stuff. If we meet some day, +# and you think this stuff is worth it, you can buy me a beer in return. +# ---------------------------------------------------------------------------- + +show_help() { + cat </dev/null; then + echo "ERROR: Cannot find curl: it is required for this script." >&2 + exit 1 +fi + +start=`date +%s` + +# ===== Parse arguments + +verbose= + +while getopts "hvx:" opt; do + case "$opt" in + h) show_help; exit 0 ;; + v) verbose=1 ;; + x) foo="$OPTARG" ;; + '?') show_help >&2; exit 1 ;; + esac +done + +# Shift off the options and optional --. +shift "$((OPTIND-1))" + +# Get the fixed arguments +if [[ $# != 2 ]]; then + show_help >&2 + exit 1 +fi +hostname=$1 +hex=$2 + +re='[-A-Za-z0-9.]+' +if [[ ! "$hostname" =~ $re ]]; then + echo "ERROR: hostname ${hostname} is not a valid hostname or ip address" >&2 + exit 1 +fi + +if [[ ! -r "$hex" ]]; then + echo "ERROR: cannot read hex file ($hex)" >&2 + exit 1 +fi + +# ===== Get AVR in sync + +[[ -n "$verbose" ]] && echo "Resetting AVR with http://$hostname/pgm/sync" >&2 +v=; [[ -n "$verbose" ]] && v=-v +sync=`curl -m 10 $v -s -w '%{http_code}' -XPOST "http://$hostname/pgm/sync"` +if [[ $? != 0 || "$sync" != 204 ]]; then + echo "Error resetting AVR" >&2 + exit 1 +fi + +while true; do + sync=`curl -m 10 $v -s "http://$hostname/pgm/sync"` + if [[ $? != 0 ]]; then + echo "Error checking sync" >&2 + exit 1 + fi + case "$sync" in + SYNC*) + echo "AVR in $sync" >&2 + break;; + "NOT READY"*) + [[ -n "$verbose" ]] && echo " Waiting for sync..." >&2 + ;; + *) + echo "Error checking sync: $sync" >&2 + exit 1 + ;; + esac + sleep 0.1 +done + +# ===== Send HEX file + +[[ -n "$verbose" ]] && echo "Sending HEX file for programming" >&2 +sync=`curl -m 10 $v -s -g -d "@$hex" "http://$hostname/pgm/upload"` +echo $sync +if [[ $? != 0 || ! "$sync" =~ ^Success ]]; then + echo "Error programming AVR" >&2 + exit 1 +fi + +sec=$(( `date +%s` - $start )) +echo "Success, took $sec seconds" >&2 +exit 0 diff --git a/esp-link/cgi.c b/esp-link/cgi.c index 0a52538..bd2a074 100644 --- a/esp-link/cgi.c +++ b/esp-link/cgi.c @@ -17,7 +17,8 @@ Some random cgi routines. #include #include "cgi.h" -void noCacheHeaders(HttpdConnData *connData, int code) { +void ICACHE_FLASH_ATTR +noCacheHeaders(HttpdConnData *connData, int code) { httpdStartResponse(connData, code); httpdHeader(connData, "Cache-Control", "no-cache, no-store, must-revalidate"); httpdHeader(connData, "Pragma", "no-cache"); diff --git a/esp-link/cgi.h b/esp-link/cgi.h index 66757fe..2b8893c 100644 --- a/esp-link/cgi.h +++ b/esp-link/cgi.h @@ -4,6 +4,7 @@ #include #include "httpd.h" +void noCacheHeaders(HttpdConnData *connData, int code); void jsonHeader(HttpdConnData *connData, int code); void errorResponse(HttpdConnData *connData, int code, char *message); diff --git a/esp-link/cgioptiboot.c b/esp-link/cgioptiboot.c new file mode 100644 index 0000000..3396ef0 --- /dev/null +++ b/esp-link/cgioptiboot.c @@ -0,0 +1,580 @@ +// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo + +#include +#include +#include "cgi.h" +#include "cgioptiboot.h" +#include "config.h" +#include "uart.h" +#include "stk500.h" +#include "serbridge.h" +#include "serled.h" + +#define SYNC_TIMEOUT (2000) // to achieve sync, in milliseconds +#define PGM_TIMEOUT (20000) // when sync is achieved, in milliseconds + +#define OPTIBOOT_DBG +#undef DBG +#ifdef OPTIBOOT_DBG +#define DBG(format, ...) os_printf(format, ## __VA_ARGS__) +#else +#define DBG(format, ...) do { } while(0) +#endif + +//===== global state + +static ETSTimer optibootTimer; + +static enum { // overall programming states + stateSync = 0, // trying to get sync + stateGetSig, // reading device signature + stateGetVersLo, // reading optiboot version, low bits + stateGetVersHi, // reading optiboot version, high bits + stateProg, // programming... +} progState; +static short syncCnt; // counter & timeout for sync attempts +static short ackWait; // counter of expected ACKs +static uint16_t optibootVers; + +#define RESP_SZ 64 +static char responseBuf[RESP_SZ]; // buffer to accumulate responses from optiboot +static short responseLen = 0; // amount accumulated so far +#define ERR_MAX 128 +static char errMessage[ERR_MAX]; // error message + +#define MAX_PAGE_SZ 512 // max flash page size supported +#define MAX_SAVED 512 // max chars in saved buffer +// structure used to remember request details from one callback to the next +// allocated dynamically so we don't burn so much static RAM +static struct optibootData { + char *saved; // buffer for saved incomplete hex records + char *pageBuf; // buffer for received data to be sent to AVR + uint16_t pageLen; // number of bytes in pageBuf + uint16_t pgmSz; // size of flash page to be programmed at a time + uint16_t pgmDone; // number of bytes programmed + uint32_t address; // address to write next page to + uint32_t startTime; // time of program POST request + HttpdConnData *conn; // request doing the programming, so we can cancel it + bool eof; // got EOF record +} *optibootData; + +// forward function references +static void optibootTimerCB(void *); +static void optibootUartRecv(char *buffer, short length); +static bool processRecord(char *buf, short len); +static bool programPage(void); +static void ICACHE_FLASH_ATTR armTimer(); + +static void ICACHE_FLASH_ATTR optibootInit() { + progState = stateSync; + syncCnt = 0; + ackWait = 0; + errMessage[0] = 0; + responseLen = 0; + programmingCB = optibootUartRecv; + if (optibootData != NULL) { + if (optibootData->conn != NULL) + optibootData->conn->cgiPrivData = (void *)-1; // signal that request has been aborted + if (optibootData->pageBuf) os_free(optibootData->pageBuf); + if (optibootData->saved) os_free(optibootData->saved); + os_free(optibootData); + optibootData = NULL; + } + os_timer_disarm(&optibootTimer); + DBG("OB init\n"); +} + +//===== Cgi to reset AVR and get Optiboot into sync +int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + + // check that we know the reset pin, else error out with that + if (flashConfig.reset_pin < 0) { + errorResponse(connData, 400, "No reset pin defined"); + + } else if (connData->requestType == HTTPD_METHOD_POST) { + // issue reset + optibootInit(); + serbridgeReset(); + makeGpio(5); + gpio_output_set(0, (1<<5), (1<<5), 0); // output 0 + + // start sync timer + os_timer_disarm(&optibootTimer); + os_timer_setfn(&optibootTimer, optibootTimerCB, NULL); + os_timer_arm(&optibootTimer, 50, 0); // fire in 50ms and don't recur + + // respond with optimistic OK + noCacheHeaders(connData, 204); + httpdEndHeaders(connData); + httpdSend(connData, "", 0); + + } else if (connData->requestType == HTTPD_METHOD_GET) { + noCacheHeaders(connData, 200); + httpdEndHeaders(connData); + if (!errMessage[0] && progState >= stateProg) { + DBG("OB got sync\n"); + char buf[32]; + os_sprintf(buf, "SYNC : Optiboot %d.%d", optibootVers>>8, optibootVers&0xff); + httpdSend(connData, buf, -1); + } else if (errMessage[0] && progState == stateSync) { + DBG("OB lost sync\n"); + char buf[RESP_SZ+64]; + os_sprintf(buf, "FAILED to SYNC: %s, got <%s>", errMessage, responseBuf); + httpdSend(connData, buf, -1); + } else { + httpdSend(connData, errMessage[0] ? errMessage : "NOT READY", -1); + } + + } else { + errorResponse(connData, 404, "Only GET and POST supported"); + } + + return HTTPD_CGI_DONE; +} + +// verify that N chars are hex characters +static bool ICACHE_FLASH_ATTR checkHex(char *buf, short len) { + while (len--) { + char c = *buf++; + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + continue; + DBG("OB non-hex\n"); + os_sprintf(errMessage, "Non hex char in POST record: '%c'/0x%02x", c, c); + return false; + } + return true; +} + +// get hex value of some hex characters +static uint32_t ICACHE_FLASH_ATTR getHexValue(char *buf, short len) { + uint32_t v = 0; + while (len--) { + v = (v<<4) | (uint32_t)(*buf & 0xf); + if (*buf > '9') v += 9; + buf++; + } + return v; +} + +//===== Cgi to write firmware to Optiboot, requires prior sync call +int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData) { + if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. + DBG("OB pgm: state=%d PrivData=%p postLen=%d\n", progState, connData->cgiPrivData, connData->post->len); + + // check that we have sync + if (errMessage[0] || progState < stateProg) { + DBG("OB not in sync\n"); + errorResponse(connData, 400, errMessage[0] ? errMessage : "Optiboot not in sync"); + return HTTPD_CGI_DONE; + } + + // check that we don't have two concurrent programming requests going on + if (connData->cgiPrivData == (void *)-1) { + DBG("OB aborted\n"); + errorResponse(connData, 400, "Request got aborted by a concurrent sync request"); + return HTTPD_CGI_DONE; + } + + // allocate data structure to track programming + if (!optibootData) { + optibootData = os_zalloc(sizeof(struct optibootData)); + char *saved = os_zalloc(MAX_SAVED+1); // need space for string terminator + char *pageBuf = os_zalloc(MAX_PAGE_SZ+MAX_SAVED/2); + if (!optibootData || !pageBuf || !saved) { + errorResponse(connData, 400, "Out of memory"); + return HTTPD_CGI_DONE; + } + optibootData->pageBuf = pageBuf; + optibootData->saved = saved; + optibootData->startTime = system_get_time(); + optibootData->pgmSz = 128; // hard coded for 328p for now, should be query string param + DBG("OB data alloc\n"); + } + + // iterate through the data received and program the AVR one block at a time + HttpdPostData *post = connData->post; + char *saved = optibootData->saved; + while (post->buffLen > 0) { + // first fill-up the saved buffer + short saveLen = strlen(saved); + if (saveLen < MAX_SAVED) { + short cpy = MAX_SAVED-saveLen; + if (cpy > post->buffLen) cpy = post->buffLen; + os_memcpy(saved+saveLen, post->buff, cpy); + saveLen += cpy; + saved[saveLen] = 0; // string terminator + os_memmove(post->buff, post->buff+cpy, post->buffLen-cpy); + post->buffLen -= cpy; + //DBG("OB cp %d buff->saved\n", cpy); + } + + // process HEX records + while (saveLen >= 11) { // 11 is minimal record length + // skip any CR/LF + short skip = 0; + while (skip < saveLen && (saved[skip] == '\n' || saved[skip] == '\r')) + skip++; + if (skip > 0) { + // shift out cr/lf (keep terminating \0) + os_memmove(saved, saved+skip, saveLen+1-skip); + saveLen -= skip; + if (saveLen < 11) break; + DBG("OB skip %d cr/lf\n", skip); + } + + // inspect whether we have a proper record start + if (saved[0] != ':') { + DBG("OB found non-: start\n"); + os_sprintf(errMessage, "Expected start of record in POST data, got %s", saved); + errorResponse(connData, 400, errMessage); + optibootInit(); + return HTTPD_CGI_DONE; + } + + if (!checkHex(saved+1, 2)) { + errorResponse(connData, 400, errMessage); + optibootInit(); + return HTTPD_CGI_DONE; + } + uint8_t recLen = getHexValue(saved+1, 2); + //DBG("OB record %d\n", recLen); + + // process record + if (saveLen >= 11+recLen*2) { + if (!processRecord(saved, 11+recLen*2)) { + DBG("OB process err %s\n", errMessage); + errorResponse(connData, 400, errMessage); + optibootInit(); + return HTTPD_CGI_DONE; + } + short shift = 11+recLen*2; + os_memmove(saved, saved+shift, saveLen+1-shift); + saveLen -= shift; + //DBG("OB %d byte record\n", shift); + } else { + break; + } + } + } + + short code; + if (post->received < post->len) { + //DBG("OB pgm need more\n"); + return HTTPD_CGI_MORE; + } + + if (optibootData->eof) { + // tell optiboot to reboot into the sketch + uart0_write_char(STK_LEAVE_PROGMODE); + uart0_write_char(CRC_EOP); + code = 200; + // calculate some stats + float dt = ((system_get_time() - optibootData->startTime)/1000)/1000.0; // in seconds + uint16_t pgmDone = optibootData->pgmDone; + optibootInit(); + os_sprintf(errMessage, "Success. %d bytes in %d.%ds, %dB/s %d%% efficient", + pgmDone, (int)dt, (int)(dt*10)%10, (int)(pgmDone/dt), + (int)(100.0*(10.0*pgmDone/flashConfig.baud_rate)/dt)); + } else { + code = 400; + optibootInit(); + os_strcpy(errMessage, "Improperly terminated POST data"); + } + DBG("OB pgm done: %d -- %s\n", code, errMessage); + noCacheHeaders(connData, code); + httpdEndHeaders(connData); + httpdSend(connData, errMessage, -1); + errMessage[0] = 0; + return HTTPD_CGI_DONE; +} + +// verify checksum +static bool ICACHE_FLASH_ATTR verifyChecksum(char *buf, short len) { + uint8_t sum = 0; + while (len >= 2) { + sum += (uint8_t)getHexValue(buf, 2); + buf += 2; + len -= 2; + } + return sum == 0; +} + +// Process a hex record -- assumes that the records starts with ':' & hex length +static bool ICACHE_FLASH_ATTR processRecord(char *buf, short len) { + buf++; len--; // skip leading ':' + // check we have all hex chars + if (!checkHex(buf, len)) return false; + // verify checksum + if (!verifyChecksum(buf, len)) { + buf[len] = 0; + os_sprintf(errMessage, "Invalid checksum for record %s", buf); + return false; + } + // dispatch based on record type + uint8_t type = getHexValue(buf+6, 2); + switch (type) { + case 0x00: { // data + //DBG("OB REC data %ld pglen=%d\n", getHexValue(buf, 2), optibootData->pageLen); + uint32_t addr = getHexValue(buf+2, 4); + // check whether we need to program previous record(s) + if (optibootData->pageLen > 0 && + addr != ((optibootData->address+optibootData->pageLen)&0xffff)) { + //DBG("OB addr chg\n"); + if (!programPage()) return false; + } + // set address, unless we're adding to the end (programPage may have changed pageLen) + if (optibootData->pageLen == 0) { + optibootData->address = (optibootData->address & 0xffff0000) | addr; + //DBG("OB set-addr 0x%lx\n", optibootData->address); + } + // append record + uint16_t recLen = getHexValue(buf, 2); + for (uint16_t i=0; ipageBuf[optibootData->pageLen++] = getHexValue(buf+8+2*i, 2); + // program page, if we have a full page + if (optibootData->pageLen >= optibootData->pgmSz) { + //DBG("OB full\n"); + if (!programPage()) return false; + } + break; } + case 0x01: // EOF + DBG("OB EOF\n"); + // program any remaining partial page + if (optibootData->pageLen > 0) + if (!programPage()) return false; + optibootData->eof = true; + break; + case 0x04: // address + DBG("OB address 0x%lx\n", getHexValue(buf+8, 4) << 16); + // program any remaining partial page + if (optibootData->pageLen > 0) + if (!programPage()) return false; + optibootData->address = getHexValue(buf+8, 4) << 16; + break; + case 0x05: // start address + // ignore, there's no way to tell optiboot that... + break; + default: + DBG("OB bad record type\n"); + os_sprintf(errMessage, "Invalid/unknown record type: 0x%02x", type); + return false; + } + return true; +} + +// Poll UART for ACKs, max 50ms +static bool pollAck() { + char recv[16]; + uint16_t need = ackWait*2; + uint16_t got = uart0_rx_poll(recv, need, 50000); + gpio_output_set(0, (1<<5), (1<<5), 0); // output 0 + if (got < need) { + os_strcpy(errMessage, "Timeout waiting for flash page to be programmed"); + return false; + } + ackWait = 0; + if (recv[0] == STK_INSYNC && recv[1] == STK_OK) + return true; + os_sprintf(errMessage, "Did not get ACK after programming cmd: %x02x %x02x", recv[0], recv[1]); + return false; +} + +// Program a flash page +static bool ICACHE_FLASH_ATTR programPage(void) { + if (optibootData->pageLen == 0) return true; + armTimer(); // keep the timerCB out of the picture + + if (ackWait > 7) { + os_sprintf(errMessage, "Lost sync while programming\n"); + return false; + } + + uint16_t pgmLen = optibootData->pageLen; + if (pgmLen > optibootData->pgmSz) pgmLen = optibootData->pgmSz; + DBG("OB pgm %d@0x%lx ackWait=%d\n", pgmLen, optibootData->address, ackWait); + + // send address to optiboot (little endian format) + gpio_output_set((1<<5), 0, (1<<5), 0); // output 1 + ackWait++; + uart0_write_char(STK_LOAD_ADDRESS); + uint16_t addr = optibootData->address >> 1; // word address + uart0_write_char(addr & 0xff); + uart0_write_char(addr >> 8); + uart0_write_char(CRC_EOP); + armTimer(); + if (!pollAck()) { + DBG("OB pgm failed in load address\n"); + return false; + } + armTimer(); + + // send page length (big-endian format, go figure...) + gpio_output_set((1<<5), 0, (1<<5), 0); // output 1 + ackWait++; + uart0_write_char(STK_PROG_PAGE); + uart0_write_char(pgmLen>>8); + uart0_write_char(pgmLen&0xff); + uart0_write_char('F'); // we're writing flash + + // send page content + for (short i=0; ipageBuf[i]); + uart0_write_char(CRC_EOP); + + armTimer(); + bool ok = pollAck(); + armTimer(); + if (!ok) { + DBG("OB pgm failed in prog page\n"); + return false; + } + + // shift data out of buffer + os_memmove(optibootData->pageBuf, optibootData->pageBuf+pgmLen, optibootData->pageLen-pgmLen); + optibootData->pageLen -= pgmLen; + optibootData->address += pgmLen; + optibootData->pgmDone += pgmLen; + + //DBG("OB pgm OK\n"); + return true; +} + +//===== Rebooting and getting sync + +static void ICACHE_FLASH_ATTR armTimer() { + os_timer_disarm(&optibootTimer); + os_timer_arm(&optibootTimer, 100, 0); // 100ms +} + +static void ICACHE_FLASH_ATTR optibootTimerCB(void *arg) { + // see whether we've issued so many sync in a row that it's time to give up + syncCnt++; + if ((progState == stateSync && syncCnt > SYNC_TIMEOUT/50) || + syncCnt > PGM_TIMEOUT/50) { + optibootInit(); + strcpy(errMessage, "abandoned after timeout"); + return; + } + + switch (progState) { + case stateSync: // we're trying to get sync, all we do here is send a sync request + uart0_write_char(STK_GET_SYNC); + uart0_write_char(CRC_EOP); + break; + case stateProg: // we're programming and we timed-out of inaction + uart0_write_char(STK_GET_SYNC); + uart0_write_char(CRC_EOP); + ackWait++; // we now expect an ACK + break; + default: // we're trying to get some info from optiboot and it should have responded! + optibootInit(); // abort + os_sprintf(errMessage, "No response in state %d\n", progState); + return; // do not re-arm timer + } + + // we need to come back... + armTimer(); +} + +// skip in-sync responses +static short ICACHE_FLASH_ATTR skipInSync(char *buf, short length) { + while (length > 1 && buf[0] == STK_INSYNC && buf[1] == STK_OK) { + // not the most efficient, oh well... + os_memcpy(buf, buf+2, length-2); + length -= 2; + } + return length; +} + +// receive response from optiboot, we only store the last response +static void ICACHE_FLASH_ATTR optibootUartRecv(char *buf, short length) { + // append what we got to what we have accumulated + if (responseLen < RESP_SZ-1) { + short cpy = RESP_SZ-1-responseLen; + if (cpy > length) cpy = length; + os_memcpy(responseBuf+responseLen, buf, cpy); + responseLen += cpy; + responseBuf[responseLen] = 0; // string terminator + } + + // dispatch based the current state + switch (progState) { + case stateSync: // we're trying to get a sync response + // look for STK_INSYNC+STK_OK at end of buffer + if (responseLen > 0 && responseBuf[responseLen-1] == STK_INSYNC) { + // missing STK_OK after STK_INSYNC, shift stuff out and try again + responseBuf[0] = STK_INSYNC; + responseLen = 1; + } else if (responseLen > 1 && responseBuf[responseLen-2] == STK_INSYNC && + responseBuf[responseLen-1] == STK_OK) { + // got sync response, send signature request + progState++; + os_memcpy(responseBuf, responseBuf+2, responseLen-2); + responseLen -= 2; + uart0_write_char(STK_READ_SIGN); + uart0_write_char(CRC_EOP); + armTimer(); // reset timer + } else { + // nothing useful, keep at most half the buffer for error message purposes + if (responseLen > RESP_SZ/2) { + os_memcpy(responseBuf, responseBuf+responseLen-RESP_SZ/2, RESP_SZ/2); + responseLen = RESP_SZ/2; + responseBuf[responseLen] = 0; // string terminator + } + } + break; + case stateGetSig: // expecting signature + responseLen = skipInSync(responseBuf, responseLen); + if (responseLen >= 5 && responseBuf[0] == STK_INSYNC && responseBuf[4] == STK_OK) { + if (responseBuf[1] == 0x1e && responseBuf[2] == 0x95 && responseBuf[3] == 0x0f) { + // right on... ask for optiboot version + progState++; + uart0_write_char(STK_GET_PARAMETER); + uart0_write_char(0x82); + uart0_write_char(CRC_EOP); + armTimer(); // reset timer + } else { + optibootInit(); // abort + os_sprintf(errMessage, "Bad programmer signature: 0x%02x 0x%02x 0x%02x\n", + responseBuf[1], responseBuf[2], responseBuf[3]); + } + os_memcpy(responseBuf, responseBuf+5, responseLen-5); + responseLen -= 5; + } + break; + case stateGetVersLo: // expecting version + if (responseLen >= 3 && responseBuf[0] == STK_INSYNC && responseBuf[2] == STK_OK) { + optibootVers = responseBuf[1]; + progState++; + os_memcpy(responseBuf, responseBuf+3, responseLen-3); + responseLen -= 3; + uart0_write_char(STK_GET_PARAMETER); + uart0_write_char(0x81); + uart0_write_char(CRC_EOP); + armTimer(); // reset timer + } + break; + case stateGetVersHi: // expecting version + if (responseLen >= 3 && responseBuf[0] == STK_INSYNC && responseBuf[2] == STK_OK) { + optibootVers |= responseBuf[1]<<8; + progState++; + os_memcpy(responseBuf, responseBuf+3, responseLen-3); + responseLen -= 3; + armTimer(); // reset timer + ackWait = 0; + } + break; + case stateProg: // count down expected sync responses + //DBG("UART recv %d\n", length); + while (responseLen >= 2 && responseBuf[0] == STK_INSYNC && responseBuf[1] == STK_OK) { + if (ackWait > 0) ackWait--; + os_memmove(responseBuf, responseBuf+2, responseLen-2); + responseLen -= 2; + } + armTimer(); // reset timer + default: + break; + } +} + diff --git a/esp-link/cgioptiboot.h b/esp-link/cgioptiboot.h new file mode 100644 index 0000000..bc4d20c --- /dev/null +++ b/esp-link/cgioptiboot.h @@ -0,0 +1,11 @@ +// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo + +#ifndef OPTIBOOT_H +#define OPTIBOOT_H + +#include + +int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData); +int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData); + +#endif diff --git a/esp-link/cgipins.c b/esp-link/cgipins.c index 80326df..da4a8d1 100644 --- a/esp-link/cgipins.c +++ b/esp-link/cgipins.c @@ -17,7 +17,7 @@ static int8_t map_asn[][5] = { { 0, -1, 2, -1, 0 }, // esp-01(AVR) { 0, 2, -1, -1, 0 }, // esp-01(ARM) { 13, 12, 14, 0, 0 }, // esp-br-rev -- for test purposes - { 3, 1, 0, 2, 1 }, // esp-link-12 + { 1, 3, 0, 2, 1 }, // esp-link-12 }; static const int num_map_names = sizeof(map_names)/sizeof(char*); static const int num_map_func = sizeof(map_func)/sizeof(char*); diff --git a/esp-link/log.c b/esp-link/log.c index 1a9a985..1425cb3 100644 --- a/esp-link/log.c +++ b/esp-link/log.c @@ -18,6 +18,8 @@ static int log_pos; static bool log_no_uart; // start out printing to uart static bool log_newline; // at start of a new line +#define uart0_write_char uart1_write_char + // called from wifi reset timer to turn UART on when we loose wifi and back off // when we connect to wifi AP. Here this is gated by the flash setting void ICACHE_FLASH_ATTR @@ -64,20 +66,21 @@ log_read(void) { static void ICACHE_FLASH_ATTR log_write_char(char c) { + // log timestamp + if (log_newline) { + char buff[16]; + int l = os_sprintf(buff, "%6d> ", (system_get_time()/1000)%1000000); + if (!log_no_uart) + for (int i=0; i ", (system_get_time()/1000)%1000000); - for (int i=0; istartTime; if (dt > 0) dt = (system_get_time() - dt) / 1000; if (conn->conn && conn->url) +#if 0 os_printf("HTTP %s %s from %s -> %d in %ums, heap=%ld\n", conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, conn->priv->from, conn->priv->code, dt, (unsigned long)system_get_free_heap_size()); +#else + os_printf("HTTP %s %s: %d, %ums, h=%ld\n", + conn->requestType == HTTPD_METHOD_GET ? "GET" : "POST", conn->url, + conn->priv->code, dt, (unsigned long)system_get_free_heap_size()); +#endif #endif conn->conn = NULL; // don't try to send anything, the SDK crashes... @@ -285,7 +291,8 @@ static void ICACHE_FLASH_ATTR xmitSendBuff(HttpdConnData *conn) { sint8 status = espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen); if (status != 0) { #ifdef HTTPD_DBG - os_printf("%sERROR! espconn_sent returned %d\n", connStr, status); + os_printf("%sERROR! espconn_sent returned %d, trying to send %d to %s\n", + connStr, status, conn->priv->sendBuffLen, conn->url); #endif } conn->priv->sendBuffLen = 0; @@ -341,32 +348,35 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { //See if we can find a CGI that's happy to handle the request. while (1) { //Look up URL in the built-in URL table. - while (builtInUrls[i].url != NULL) { - int match = 0; - //See if there's a literal match - if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1; - //See if there's a wildcard match - if (builtInUrls[i].url[os_strlen(builtInUrls[i].url) - 1] == '*' && - os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url) - 1) == 0) match = 1; - if (match) { - //os_printf("Is url index %d\n", i); - conn->cgiData = NULL; - conn->cgi = builtInUrls[i].cgiCb; - conn->cgiArg = builtInUrls[i].cgiArg; - break; + if (conn->cgi == NULL) { + while (builtInUrls[i].url != NULL) { + int match = 0; + //See if there's a literal match + if (os_strcmp(builtInUrls[i].url, conn->url) == 0) match = 1; + //See if there's a wildcard match + if (builtInUrls[i].url[os_strlen(builtInUrls[i].url) - 1] == '*' && + os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url) - 1) == 0) match = 1; + if (match) { + //os_printf("Is url index %d\n", i); + conn->cgiData = NULL; + conn->cgi = builtInUrls[i].cgiCb; + conn->cgiArg = builtInUrls[i].cgiArg; + break; + } + i++; } - i++; - } - if (builtInUrls[i].url == NULL) { - //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just - //generate a built-in 404 to handle this. + if (builtInUrls[i].url == NULL) { + //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just + //generate a built-in 404 to handle this. #ifdef HTTPD_DBG - os_printf("%s%s not found. 404!\n", connStr, conn->url); + os_printf("%s%s not found. 404!\n", connStr, conn->url); #endif - httpdSend(conn, httpNotFoundHeader, -1); - xmitSendBuff(conn); - conn->cgi = NULL; //mark for destruction - return; + httpdSend(conn, httpNotFoundHeader, -1); + xmitSendBuff(conn); + conn->cgi = NULL; //mark for destruction. + if (conn->post) conn->post->len = 0; // skip any remaining receives + return; + } } //Okay, we have a CGI function that matches the URL. See if it wants to handle the @@ -380,7 +390,8 @@ static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { else if (r == HTTPD_CGI_DONE) { //Yep, it's happy to do so and already is done sending data. xmitSendBuff(conn); - conn->cgi = NULL; //mark conn for destruction + conn->cgi = NULL; //mark for destruction. + if (conn->post) conn->post->len = 0; // skip any remaining receives return; } else if (r == HTTPD_CGI_NOTFOUND || r == HTTPD_CGI_AUTHENTICATED) { diff --git a/include/espmissingincludes.h b/include/espmissingincludes.h index f421b6e..bf4c9d5 100644 --- a/include/espmissingincludes.h +++ b/include/espmissingincludes.h @@ -20,6 +20,7 @@ void ets_isr_unmask(unsigned intr); int ets_memcmp(const void *s1, const void *s2, size_t n); void *ets_memcpy(void *dest, const void *src, size_t n); +void *ets_memmove(void *dest, const void *src, size_t n); void *ets_memset(void *s, int c, size_t n); int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); int ets_str2macaddr(void *, void *); diff --git a/serial/serbridge.c b/serial/serbridge.c index 0f812d9..da8835a 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -20,6 +20,8 @@ static int8_t mcu_reset_pin, mcu_isp_pin; extern uint8_t slip_disabled; // disable slip to allow flashing of attached MCU +void (*programmingCB)(char *buffer, short length) = NULL; + // Connection pool serbridgeConnData connData[MAX_CONN]; @@ -97,7 +99,7 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) os_delay_us(100L); } #ifdef SERBR_DBG - else os_printf("MCU reset: no pin\n"); + else { os_printf("MCU reset: no pin\n"); } #endif break; case DTR_OFF: @@ -115,7 +117,7 @@ telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) os_delay_us(100L); } #ifdef SERBR_DBG - else os_printf("MCU isp: no pin\n"); + else { os_printf("MCU isp: no pin\n"); } #endif slip_disabled++; break; @@ -147,7 +149,7 @@ serbridgeReset() GPIO_OUTPUT_SET(mcu_reset_pin, 1); } #ifdef SERBR_DBG - else os_printf("MCU reset: no pin\n"); + else { os_printf("MCU reset: no pin\n"); } #endif } @@ -346,7 +348,9 @@ console_process(char *buf, short len) void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short length) { - if (!flashConfig.slip_enable || slip_disabled > 0) { + if (programmingCB) { + programmingCB(buf, length); + } else if (!flashConfig.slip_enable || slip_disabled > 0) { //os_printf("SLIP: disabled got %d\n", length); console_process(buf, length); } else { diff --git a/serial/serbridge.h b/serial/serbridge.h index c389961..aad593e 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -36,4 +36,7 @@ void ICACHE_FLASH_ATTR serbridgeInitPins(void); void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len); void ICACHE_FLASH_ATTR serbridgeReset(); +// callback when receiving UART chars when in programming mode +extern void (*programmingCB)(char *buffer, short length); + #endif /* __SER_BRIDGE_H__ */ diff --git a/serial/serled.c b/serial/serled.c index 9a7a66f..6780eb6 100644 --- a/serial/serled.c +++ b/serial/serled.c @@ -29,6 +29,7 @@ void ICACHE_FLASH_ATTR serledFlash(int duration) { } void ICACHE_FLASH_ATTR serledInit(void) { + return; int8_t pin = flashConfig.ser_led_pin; if (pin >= 0) { makeGpio(pin); diff --git a/serial/uart.c b/serial/uart.c index 9b517c4..ec356d0 100644 --- a/serial/uart.c +++ b/serial/uart.c @@ -242,6 +242,23 @@ uart_recvTask(os_event_t *events) ETS_UART_INTR_ENABLE(); } +// Turn UART interrupts off and poll for nchars or until timeout hits +uint16_t ICACHE_FLASH_ATTR +uart0_rx_poll(char *buff, uint16_t nchars, uint32_t timeout_us) { + ETS_UART_INTR_DISABLE(); + uint16_t got = 0; + uint32_t start = system_get_time(); // time in us + while (system_get_time()-start < timeout_us) { + while (READ_PERI_REG(UART_STATUS(UART0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) { + buff[got++] = READ_PERI_REG(UART_FIFO(UART0)) & 0xFF; + if (got == nchars) goto done; + } + } +done: + ETS_UART_INTR_ENABLE(); + return got; +} + void ICACHE_FLASH_ATTR uart0_baud(int rate) { os_printf("UART %d baud\n", rate); diff --git a/serial/uart.h b/serial/uart.h index 3553155..24fb2df 100644 --- a/serial/uart.h +++ b/serial/uart.h @@ -8,19 +8,24 @@ typedef void (*UartRecv_cb)(char *buf, short len); // Initialize UARTs to the provided baud rates (115200 recommended). This also makes the os_printf // calls use uart1 for output (for debugging purposes) -void ICACHE_FLASH_ATTR uart_init(UartBautRate uart0_br, UartBautRate uart1_br); +void uart_init(UartBautRate uart0_br, UartBautRate uart1_br); // Transmit a buffer of characters on UART0 -void ICACHE_FLASH_ATTR uart0_tx_buffer(char *buf, uint16 len); +void uart0_tx_buffer(char *buf, uint16 len); -void ICACHE_FLASH_ATTR uart0_write_char(char c); +void uart0_write_char(char c); STATUS uart_tx_one_char(uint8 uart, uint8 c); +void uart1_write_char(char c); + // Add a receive callback function, this is called on the uart receive task each time a chunk // of bytes are received. A small number of callbacks can be added and they are all called // with all new characters. -void ICACHE_FLASH_ATTR uart_add_recv_cb(UartRecv_cb cb); +void uart_add_recv_cb(UartRecv_cb cb); + +// Turn UART interrupts off and poll for nchars or until timeout hits +uint16_t uart0_rx_poll(char *buff, uint16_t nchars, uint32_t timeout_us); -void ICACHE_FLASH_ATTR uart0_baud(int rate); +void uart0_baud(int rate); #endif /* __UART_H__ */