diff --git a/.gitignore b/.gitignore index e90d818..65d3a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ esp-link.opensdf esp-link.sdf espfs/mkespfsimage/mman-win32/libmman.a .localhistory/ +Makefile.local tools/ local.conf *.tgz diff --git a/Makefile b/Makefile index 523f85f..8dc7840 100644 --- a/Makefile +++ b/Makefile @@ -48,6 +48,10 @@ CHANGE_TO_STA ?= yes # hostname or IP address for wifi flashing ESP_HOSTNAME ?= esp-link +# Include optionnal local configuration +# +-include Makefile.local + # --------------- toolchain configuration --------------- # Base directory for the compiler. Needs a / at the end. diff --git a/serial/serbridge.c b/serial/serbridge.c index 1e82e3d..77bcc28 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -1,5 +1,32 @@ // Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +/* Modified by Christophe Duparquet: extended implementation of RFC 2217 + * + * Verified on ESP-WROOM-02 with the following configuration: + * Reset: gpio5 + * ISP/Flash: disabled + * Conn LED: gpio4 + * Serial LED: disabled + * UART pins: swapped + * RX pull-up: yes + * + * ESP connections: + * GPIO2 (#7) / 3V3 (#1) -> 1kR + * GPIO0 (#8) / 3V3 (#1) -> 1kR + * GPIO0 (#8) = USB serial RTS + * GPIO13 (#5) / GPIO15 (#6) -> 1kR + * GPIO15 (#6) / GND (#9) -> 10kR + * EN (#2) / 3V3 (#1) -> 10 kR + * #18 = #13 = #1 = GND + * RST (#15) -> USB serial RTS + * + * ATtiny85 (3V3) connections: + * RESET (#1) -> ESP pin GPIO5 (#14) + * RXTX (#2) -> ESP pin GPIO13 (#5) + * + * Command: diabolo -t rfc2217://192.168.1.78:23 + */ + #include "esp8266.h" #include "uart.h" @@ -14,6 +41,15 @@ #define SKIP_AT_RESET + +/* ESP8266 + */ +#define IROM ICACHE_FLASH_ATTR +#define REG_BRR (*(volatile uint32_t*)0x60000014) +#define REG_CONF0 (*(volatile uint32_t*)0x60000020) +#define REG_CONF1 (*(volatile uint32_t*)0x60000024) + + static struct espconn serbridgeConn1; // plain bridging port static struct espconn serbridgeConn2; // programming port static esp_tcp serbridgeTcp1, serbridgeTcp2; @@ -26,117 +62,523 @@ void (*programmingCB)(char *buffer, short length) = NULL; // Connection pool serbridgeConnData connData[MAX_CONN]; -//===== TCP -> UART - -// Telnet protocol characters -#define IAC 255 // escape -#define WILL 251 // negotiation -#define SB 250 // subnegotiation begin -#define SE 240 // subnegotiation end -#define ComPortOpt 44 // COM port options -#define SetControl 5 // Set control lines -#define DTR_ON 8 // used here to reset microcontroller -#define DTR_OFF 9 -#define RTS_ON 11 // used here to signal ISP (in-system-programming) to uC -#define RTS_OFF 12 - -// telnet state machine states -enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl }; - -// process a buffer-full on a telnet connection and return the ending telnet state -static uint8_t ICACHE_FLASH_ATTR -telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) -{ - for (int i=0; i write one to outbuf and go normal again - state = TN_normal; - uart0_write_char(c); - break; - case WILL: // negotiation - state = TN_will; - break; - case SB: // command sequence begin - state = TN_start; - break; - case SE: // command sequence end - state = TN_normal; - break; - default: // not sure... let's ignore - uart0_write_char(IAC); - uart0_write_char(c); - } - break; - case TN_will: - state = TN_normal; // yes, we do COM port options, let's go back to normal - break; - case TN_start: // in command seq, now comes the type of cmd - if (c == ComPortOpt) state = TN_comPort; - else state = TN_end; // an option we don't know, skip 'til the end seq - break; - case TN_end: // wait for end seq - if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq - break; - case TN_comPort: - if (c == SetControl) state = TN_setControl; - else state = TN_end; - break; - case TN_setControl: // switch control line and delay a tad - switch (c) { - case DTR_ON: - if (mcu_reset_pin >= 0) { +static sint8 IROM espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) ; +static sint8 IROM espbuffsend_tn(serbridgeConnData *conn, const char *data, uint16 len) ; + + +// Telnet protocol (RFC854) characters +// +#define IAC 0xFF // escape +#define DONT 0xFE // negociation (RFC855) +#define DO 0xFD // negociation +#define WONT 0xFC // negociation +#define WILL 0xFB // negociation +#define SB 0xFA // subnegotiation begin +#define SE 0xF0 // subnegotiation end + +#define BINARY 0x00 // RFC856 +#define ECHO 0x01 // RFC857 +#define SUPPRESS_GO_AHEAD 0x03 // RFC858 + +#define COM_PORT_OPTION 0x2C // RFC2217 +#define SET_BAUDRATE 1 // suboption "BAUDRATE" +#define SET_DATASIZE 2 // suboption "DATASIZE" +#define SET_PARITY 3 // suboption "PARITY" +#define SET_STOPSIZE 4 // suboption "STOPSIZE" +#define SET_CONTROL 5 // suboption "CONTROL" +#define PURGE_DATA 12 // suboption "PURGE" + + #ifdef SERBR_DBG - os_printf("MCU reset gpio%d\n", mcu_reset_pin); +# define dbgf(...) do { os_printf(__VA_ARGS__); }while(0) +#else +# define dbgf(...) do{}while(0) #endif - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - } -#ifdef SERBR_DBG - else { os_printf("MCU reset: no pin\n"); } + + +/* Telnet state machine states + */ +enum { + ST_NORMAL, + ST_IAC, + ST_NEGO, + ST_SB, + ST_COMPORT, + ST_BAUDRATE, + ST_DATASIZE, + ST_PARITY, + ST_STOPSIZE, + ST_CONTROL, + ST_PURGE, + ST_WAIT_IAC +}; + + +/* Telnet state machine: process one byte comming from a telnet connection + */ +static void IROM telnet_process_char ( serbridgeConnData *conn, char c ) +{ +#define state conn->tn_state +#define opt conn->tn_opt +#define vlen conn->tn_vlen +#define value conn->tn_value + + if ( state == ST_NORMAL ) { + if ( c == IAC ) + state = ST_IAC ; + else + uart0_write_char(c) ; + return ; + } + + if ( state == ST_IAC ) { + if ( c == IAC ) { + /* + * Dual IAC means char \xFF + */ + uart0_write_char(0xFF); + state = ST_NORMAL ; + return ; + } + else if ( c == DONT || c == DO || c == WONT || c == WILL ) { + /* + * Beginning of negociation + */ + opt = c ; + state = ST_NEGO ; + return ; + } + else if ( c == SB ) { + /* + * Beginning of sub option + */ + opt = c ; + state = ST_SB ; + return ; + } + else if ( c == SE ) { + /* + * End of sub option + */ + opt = c ; + state = ST_NORMAL ; + return ; + } + return ; + } + + if ( state == ST_NEGO ) { + dbgf("TELNET: IAC NEGO (%02X)", c); + if ( c == BINARY || + c == ECHO || + c == SUPPRESS_GO_AHEAD || + c == COM_PORT_OPTION ) { + /* + * Acknowledge positive requests for known options + */ + dbgf(": OK\n"); + if ( opt == DO ) + opt = WILL ; + else if ( opt == WILL ) + opt = DO ; + } + else { + /* + * Deny positive requests for unknown options + */ + dbgf(": REJECTED\n"); + if ( opt == DO ) + opt = WONT ; + else if ( opt == WILL ) + opt = DONT ; + } + /* + * Send reply + */ + espbuffsend( conn, (char[]){IAC,opt,c}, 3 ); + state = ST_NORMAL ; + return ; + } + + if ( state == ST_SB ) { + if ( c == COM_PORT_OPTION ) + state = ST_COMPORT ; + else + state = ST_WAIT_IAC ; + return ; + } + + if ( state == ST_WAIT_IAC ) { + if ( c == IAC ) + state = ST_IAC ; + return ; + } + + if ( state == ST_COMPORT ) { + if ( c == SET_BAUDRATE ) { + dbgf(" BAUDRATE:"); + vlen = 0 ; + state = ST_BAUDRATE ; + } else if ( c == SET_DATASIZE ) { + dbgf(" DATASIZE:"); + state = ST_DATASIZE ; + } else if ( c == SET_PARITY ) { + dbgf(" PARITY:"); + state = ST_PARITY ; + } else if ( c == SET_STOPSIZE ) { + dbgf(" STOPSIZE:"); + state = ST_STOPSIZE ; + } else if ( c == SET_CONTROL ) { + dbgf(" CONTROL:"); + state = ST_CONTROL ; + } else if ( c == PURGE_DATA ) { + dbgf(" PURGE:"); + state = ST_PURGE ; + } else { + dbgf("UNKNOWN: %02X\n", c); + state = ST_WAIT_IAC ; + } + return ; + } + + if ( state == ST_BAUDRATE ) { + /* + * Get 4 bytes of baudrate (MSB first) + */ + dbgf(" %02X", c); + + value <<= 8 ; + value += c ; + vlen++ ; + if ( vlen == 4 ) { + /* + * 4 bytes received, process the value + * + * Note: must acknowledge with the value that was set otherwise the + * pyserial client considers that the set value is rejected. + */ + if ( value == 0 ) + /* + * Get actual baudrate + */ + value = (int)(0.5 + 80e6/REG_BRR); + else + /* + * Set baudrate + */ + REG_BRR = (int)(0.5 + 80e6/value); + + /* Acknowledge + */ + dbgf(" = %ld\n", value); + char v0 = value ; + char v1 = value >> 8 ; + char v2 = value >> 16 ; + char v3 = value >> 24 ; + espbuffsend( conn, (char[]){IAC,SB,COM_PORT_OPTION,100+SET_BAUDRATE}, 4 ); + espbuffsend_tn( conn, (char[]){v3,v2,v1,v0}, 4 ); + espbuffsend( conn, (char[]){IAC,SE}, 2 ); + state = ST_WAIT_IAC ; + return ; + } + return ; + } + + if ( state == ST_DATASIZE ) { + if ( c >= 5 && c <= 8 ) { + /* + * Set data size + * Store databits in bits 3..2 of register conf0 + */ + REG_CONF0 = (REG_CONF0 & ~0xC) | ((c-5)<<2) ; + } + else { + /* + * Get data size + */ + c = 5 + ((REG_CONF0>>2) & 0x03) ; + } + + /* Acknowledge datasize + */ + dbgf(" %d\n", c ); + espbuffsend( conn, (char[]){IAC,SB,COM_PORT_OPTION,100+SET_DATASIZE,c,IAC,SE},7 ); + state = ST_WAIT_IAC ; + return ; + } + + if ( state == ST_PARITY ) { + if ( c == 0 ) { + /* + * Get actual parity: CONF0 bits 1..0 + */ + c = (REG_CONF0>>2) & 0x03 ; + if ( c==2 ) + c = 1 ; + else if ( c==3 ) + c = 2 ; + else + c = 3 ; + } + else if ( c==1 || c==2 || c==3 ) { + /* + * Set parity + * 1 NONE + * 2 ODD + * 3 EVEN + */ + char d ; + if ( c==1 ) + d = 0 ; + else if ( c==2 ) + d = 3 ; + else + d = 2 ; + REG_CONF0 = (REG_CONF0 & ~0x3) | d ; + } + else { + /* + * Do not acknowledge unknown parity value + */ + state = ST_WAIT_IAC ; + return ; + } + + /* Acknowledge parity + */ + dbgf(" %d\n", c ); + espbuffsend( conn, (char[]){IAC,SB,COM_PORT_OPTION,100+SET_PARITY,c,IAC,SE},7 ); + state = ST_WAIT_IAC ; + return ; + } + + if ( state == ST_STOPSIZE ) { + if ( c == 0 ) { + /* + * Get actual stop bits: CONF0 bits 5..4 + */ + c = REG_CONF0>>4 & 0x03 ; + if ( c==2 ) + c = 3 ; + else if ( c==3 ) + c = 2 ; + } + else if ( c >= 1 && c <= 3 ) { + /* + * Set stop bits + * 1 1 bit + * 2 2 bits + * 3 1.5 bit + */ + char d = c ; + if ( c==2 ) + d = 3 ; + else if ( c==3 ) + d = 2 ; + REG_CONF0 = (REG_CONF0 & ~0x30) | (d<<4) ; + } + else { + /* + * Do not acknowledge unknown stop bits value + */ + state = ST_WAIT_IAC ; + return ; + } + + /* Acknowledge stop bits + */ + dbgf(" %d\n", c ); + espbuffsend( conn, (char[]){IAC,SB,COM_PORT_OPTION,100+SET_STOPSIZE,c,IAC,SE},7 ); + state = ST_WAIT_IAC ; + return ; + } + + if ( state == ST_CONTROL ) { + if ( c == 1 ) { + /* + * Use No Flow Control (outbound/both) + * + * Disable TX hardware flow: CONF0 bit 15 = 0 + * Disable RX hardware flow: CONF1 bit 23 = 0 + */ + REG_CONF0 &= ~(1ULL<<15) ; + REG_CONF1 &= ~(1ULL<<23) ; + } + else if ( c == 5 ) { + /* + * Set BREAK State ON + */ + REG_CONF0 |= (1ULL<<8) ; + } + else if ( c == 6 ) { + /* + * Set BREAK State OFF + */ + REG_CONF0 &= ~(1ULL<<8) ; + } + else if ( c == 8 ) { +#ifdef USE_UART_CONTROL_LINES + /* + * Set DTR Signal State ON + * + * Assert DTR: CONF0 bit 7 + */ + REG_CONF0 |= (1ULL<<7) ; +#else + /* + * Set DTR Signal State ON + * + * Drive MCU reset LOW + */ + if (mcu_reset_pin >= 0) { + dbgf("MCU reset gpio%d\n", mcu_reset_pin); + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + } + else + dbgf("MCU reset: no pin\n"); #endif - break; - case DTR_OFF: - if (mcu_reset_pin >= 0) { - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - os_delay_us(100L); - } - break; - case RTS_ON: - if (mcu_isp_pin >= 0) { -#ifdef SERBR_DBG - os_printf("MCU ISP gpio%d\n", mcu_isp_pin); + } + else if ( c == 9 ) { +#ifdef USE_UART_CONTROL_LINES + /* + * Set DTR Signal State OFF + * + * Assert DTR: CONF0 bit 7 + */ + REG_CONF0 &= ~(1ULL<<7) ; +#else + /* + * Set DTR Signal State OFF + * + * Drive MCU reset HIGH + */ + if (mcu_reset_pin >= 0) { + dbgf("MCU reset gpio%d\n", mcu_reset_pin); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } + else + dbgf("MCU reset: no pin\n"); #endif - GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - } -#ifdef SERBR_DBG - else { os_printf("MCU isp: no pin\n"); } + } + else if ( c == 11 ) { +#ifdef USE_UART_CONTROL_LINES + /* + * Set RTS Signal State ON + * + * Assert RTS: CONF0 bit 6 = 1 + */ + REG_CONF0 |= (1ULL<<6) ; +#else + /* + * Set RTS Signal State ON + * + * Drive MCU ISP pin LOW + */ + if (mcu_isp_pin >= 0) { + dbgf("MCU ISP gpio%d\n", mcu_isp_pin); + GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + } + else + dbgf("MCU isp: no pin\n"); + slip_disabled++; #endif - slip_disabled++; - break; - case RTS_OFF: - if (mcu_isp_pin >= 0) { - GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(100L); - } - if (slip_disabled > 0) slip_disabled--; - break; + } + else if ( c == 12 ) { +#ifdef USE_UART_CONTROL_LINES + /* + * Set RTS Signal State OFF + * + * Assert RTS: CONF0 bit 6 = 0 + */ + REG_CONF0 &= ~(1ULL<<6) ; +#else + /* + * Set RTS Signal State OFF + * + * Drive MCU ISP pin HIGH + */ + if (mcu_isp_pin >= 0) { + GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(100L); } - state = TN_end; - break; + if (slip_disabled > 0) slip_disabled--; +#endif + } + else { + /* + * Do not acknowledge unknown control + */ + state = ST_WAIT_IAC ; + return ; } + + /* Acknowledge control + */ + dbgf(" %d\n", c ); + espbuffsend( conn, (char[]){IAC,SB,COM_PORT_OPTION,100+SET_CONTROL,c,IAC,SE},7 ); + state = ST_WAIT_IAC ; + return ; } - return state; + + if ( state == ST_PURGE ) { + if ( c == 1 ) { + /* + * Purge access server receive data buffer + * + * Reset RX FIFO: CONF0 bit 17 = 1 + */ + // REG_CONF0 |= (1ULL<<17) ; + } + else if ( c == 2 ) { + /* + * Purge access server transmit data buffer + * + * Reset TX FIFO: CONF0 bit 18 = 1 + */ + // REG_CONF0 |= (1ULL<<18) ; + } + else { + state = ST_WAIT_IAC ; + return ; + } + + /* Acknowledge purge + */ + dbgf(" %d\n", c ); + espbuffsend( conn, (char[]){IAC,SB,COM_PORT_OPTION,100+PURGE_DATA,c,IAC,SE},7 ); + state = ST_WAIT_IAC ; + return ; + } + + /* Unexpected char + */ + state = ST_WAIT_IAC ; + +#undef state +#undef opt +#undef vlen +#undef value } + +/* Process bytes comming from a telnet connection + */ +static void IROM telnet_process_buf ( serbridgeConnData *conn, uint8_t *buf, int len ) +{ +#ifdef SERBR_DBG + os_printf("TELNET:"); + for ( int i=0; iconn_mode = cmPGM; - // If the connection starts with a telnet negotiation we will do telnet + // If the connection starts with a telnet negotiation we will do telnet } - else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { + // else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, COM_PORT_OPTION}, 3) == 0) { + else if ( len>2 && data[0]==IAC && (data[1]==WILL || data[1]==DO) ) { conn->conn_mode = cmTelnet; - conn->telnet_state = TN_normal; + conn->tn_state = ST_NORMAL; // note that the three negotiation chars will be gobbled-up by telnetUnwrap #ifdef SERBR_DBG os_printf("telnet mode\n"); #endif - // looks like a plain-vanilla connection! + // looks like a plain-vanilla connection! } else { conn->conn_mode = cmTransparent; } - // if we start out in cmPGM mode due to a connection to the second port we need to do the - // reset dance right away + // if we start out in cmPGM mode due to a connection to the second port we need to do the + // reset dance right away } else if (conn->conn_mode == cmPGMInit) { conn->conn_mode = cmPGM; startPGM = true; @@ -225,12 +668,14 @@ serbridgeRecvCb(void *arg, char *data, unsigned short len) #endif } - - // write the buffer to the uart - if (conn->conn_mode == cmTelnet) { - conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); + if ( conn->conn_mode == cmTelnet ) { + /* + * Process Telnet protocol + */ + telnet_process_buf( conn, (uint8_t *)data, len ); } else { - uart0_tx_buffer(data, len); + // write the buffer to the uart + uart0_tx_buffer( data, len ); } serledFlash(50); // short blink on serial LED @@ -263,13 +708,54 @@ sendtxbuffer(serbridgeConnData *conn) return result; } + +// Escape '\xFF' bytes in buffer for Telnet protocol before sending it over the +// air +// +static sint8 ICACHE_FLASH_ATTR +espbuffsend_tn ( serbridgeConnData *conn, const char *data, uint16 len ) +{ + // How many bytes for the new buffer? + // + int n = 0 ; + for ( int i=0 ; itxbufferlen >= MAX_TXBUFFER) goto overflow; @@ -299,7 +785,7 @@ espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) } return result; -overflow: + overflow: if (conn->txoverflow_at) { // we've already been overflowing if (system_get_time() - conn->txoverflow_at > 10*1000*1000) { @@ -340,11 +826,15 @@ console_process(char *buf, short len) // push the buffer into each open connection for (short i=0; i