mirror of https://github.com/jeelabs/esp-link.git
parent
8867c8e9b6
commit
adb0706349
@ -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 <<EOT |
||||
Usage: ${0##*/} [-options...] hostname sketch.hex |
||||
Flash the AVR running optiboot attached to esp-link with the sketch. |
||||
-v Be verbose |
||||
-h show this help |
||||
|
||||
Example: ${0##*/} -v esp-link mysketch.hex |
||||
${0##*/} 192.168.4.1 mysketch.hex |
||||
EOT |
||||
} |
||||
|
||||
if ! which curl >/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 |
@ -0,0 +1,580 @@ |
||||
// Copyright (c) 2015 by Thorsten von Eicken, see LICENSE.txt in the esp-link repo
|
||||
|
||||
#include <esp8266.h> |
||||
#include <osapi.h> |
||||
#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; i<recLen; i++) |
||||
optibootData->pageBuf[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; i<pgmLen; i++) |
||||
uart0_write_char(optibootData->pageBuf[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; |
||||
} |
||||
} |
||||
|
@ -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 <httpd.h> |
||||
|
||||
int ICACHE_FLASH_ATTR cgiOptibootSync(HttpdConnData *connData); |
||||
int ICACHE_FLASH_ATTR cgiOptibootData(HttpdConnData *connData); |
||||
|
||||
#endif |
@ -0,0 +1,44 @@ |
||||
/* STK500 constants list, from AVRDUDE
|
||||
* |
||||
* Trivial set of constants derived from Atmel App Note AVR061 |
||||
* Not copyrighted. Released to the public domain. |
||||
*/ |
||||
|
||||
#define STK_OK 0x10 |
||||
#define STK_FAILED 0x11 // Not used
|
||||
#define STK_UNKNOWN 0x12 // Not used
|
||||
#define STK_NODEVICE 0x13 // Not used
|
||||
#define STK_INSYNC 0x14 // ' '
|
||||
#define STK_NOSYNC 0x15 // Not used
|
||||
#define ADC_CHANNEL_ERROR 0x16 // Not used
|
||||
#define ADC_MEASURE_OK 0x17 // Not used
|
||||
#define PWM_CHANNEL_ERROR 0x18 // Not used
|
||||
#define PWM_ADJUST_OK 0x19 // Not used
|
||||
#define CRC_EOP 0x20 // 'SPACE'
|
||||
#define STK_GET_SYNC 0x30 // '0'
|
||||
#define STK_GET_SIGN_ON 0x31 // '1'
|
||||
#define STK_SET_PARAMETER 0x40 // '@'
|
||||
#define STK_GET_PARAMETER 0x41 // 'A'
|
||||
#define STK_SET_DEVICE 0x42 // 'B'
|
||||
#define STK_SET_DEVICE_EXT 0x45 // 'E'
|
||||
#define STK_ENTER_PROGMODE 0x50 // 'P'
|
||||
#define STK_LEAVE_PROGMODE 0x51 // 'Q'
|
||||
#define STK_CHIP_ERASE 0x52 // 'R'
|
||||
#define STK_CHECK_AUTOINC 0x53 // 'S'
|
||||
#define STK_LOAD_ADDRESS 0x55 // 'U'
|
||||
#define STK_UNIVERSAL 0x56 // 'V'
|
||||
#define STK_PROG_FLASH 0x60 // '`'
|
||||
#define STK_PROG_DATA 0x61 // 'a'
|
||||
#define STK_PROG_FUSE 0x62 // 'b'
|
||||
#define STK_PROG_LOCK 0x63 // 'c'
|
||||
#define STK_PROG_PAGE 0x64 // 'd'
|
||||
#define STK_PROG_FUSE_EXT 0x65 // 'e'
|
||||
#define STK_READ_FLASH 0x70 // 'p'
|
||||
#define STK_READ_DATA 0x71 // 'q'
|
||||
#define STK_READ_FUSE 0x72 // 'r'
|
||||
#define STK_READ_LOCK 0x73 // 's'
|
||||
#define STK_READ_PAGE 0x74 // 't'
|
||||
#define STK_READ_SIGN 0x75 // 'u'
|
||||
#define STK_READ_OSCCAL 0x76 // 'v'
|
||||
#define STK_READ_FUSE_EXT 0x77 // 'w'
|
||||
#define STK_READ_OSCCAL_EXT 0x78 // 'x'
|
Loading…
Reference in new issue