From 45cfe914d5a3a3b668ecafd6be8f936cee17b7d9 Mon Sep 17 00:00:00 2001 From: Thorsten von Eicken Date: Fri, 11 Sep 2015 20:56:51 -0700 Subject: [PATCH] rewrite serbridge buffering --- serial/serbridge.c | 556 +++++++++++++++++++++++---------------------- serial/serbridge.h | 13 +- 2 files changed, 288 insertions(+), 281 deletions(-) diff --git a/serial/serbridge.c b/serial/serbridge.c index 0d6dc57..8a912c8 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -22,65 +22,79 @@ sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, // Connection pool serbridgeConnData connData[MAX_CONN]; -// Transmit buffers for the connection pool -static char txbuffer[MAX_CONN][MAX_TXBUFFER]; - -// Given a pointer to an espconn struct find the connection that correcponds to it -static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) { - for (int i=0; itxbuffer // returns result from espconn_sent if data in buffer or ESPCONN_OK (0) // Use only internally from espbuffsend and serbridgeSentCb static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) { - sint8 result = ESPCONN_OK; - if (conn->txbufferlen != 0) { - //os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen); - conn->readytosend = false; - result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); - conn->txbufferlen = 0; - if (result != ESPCONN_OK) { - os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); - } - } - return result; + sint8 result = ESPCONN_OK; + if (conn->txbufferlen != 0) { + //os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen); + conn->readytosend = false; + result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); + if (result != ESPCONN_OK) { + os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); + conn->txbufferlen = 0; + } else { + conn->sentbuffer = conn->txbuffer; + conn->txbuffer = NULL; + conn->txbufferlen = 0; + } + } + return result; } +static char *tx_full_msg = "espbuffsend: txbuffer full on conn %p\n"; + // espbuffsend adds data to the send buffer. If the previous send was completed it calls // sendtxbuffer and espconn_sent. // Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from espconn_sent // Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must // only be called *after* receiving an espconn_sent_callback for the previous packet. sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) { - if (conn->txbufferlen + len > MAX_TXBUFFER) { - os_printf("espbuffsend: txbuffer full on conn %p\n", conn); - return -128; - } - os_memcpy(conn->txbuffer + conn->txbufferlen, data, len); - conn->txbufferlen += len; - if (conn->readytosend) { - return sendtxbuffer(conn); - } else { - //os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen); - } - return ESPCONN_OK; + if (conn->txbufferlen >= MAX_TXBUFFER) { + os_printf(tx_full_msg, conn); + return -128; + } + + // make sure we indeed have a buffer + if (conn->txbuffer == NULL) conn->txbuffer = os_zalloc(MAX_TXBUFFER); + if (conn->txbuffer == NULL) { + os_printf("espbuffsend: cannot alloc tx buffer\n"); + return -128; + } + + // add to send buffer + uint16_t avail = conn->txbufferlen+len > MAX_TXBUFFER ? MAX_TXBUFFER-conn->txbufferlen : len; + os_memcpy(conn->txbuffer + conn->txbufferlen, data, avail); + conn->txbufferlen += len; + + // try to send + sint8 result = ESPCONN_OK; + if (conn->readytosend) result = sendtxbuffer(conn); + + if (avail < len) { + // some data didn't fit into the buffer + if (conn->txbufferlen == 0) { + // we sent the prior buffer, so try again + return espbuffsend(conn, data+avail, len-avail); + } + os_printf(tx_full_msg, conn); + return -128; + } + return result; } //callback after the data are sent static void ICACHE_FLASH_ATTR serbridgeSentCb(void *arg) { - serbridgeConnData *conn = serbridgeFindConnData(arg); - //os_printf("Sent callback on conn %p\n", conn); - if (conn == NULL) return; - //os_printf("%d ST\n", system_get_time()); - conn->readytosend = true; - sendtxbuffer(conn); // send possible new data in txbuffer + serbridgeConnData *conn = ((struct espconn*)arg)->reverse; + //os_printf("Sent callback on conn %p\n", conn); + if (conn == NULL) return; + //os_printf("%d ST\n", system_get_time()); + if (conn->sentbuffer != NULL) os_free(conn->sentbuffer); + conn->sentbuffer = NULL; + conn->readytosend = true; + sendtxbuffer(conn); // send possible new data in txbuffer } // Telnet protocol characters @@ -102,265 +116,257 @@ enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl } 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) { - os_printf("MCU reset gpio%d\n", mcu_reset_pin); - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - } else os_printf("MCU reset: no pin\n"); - 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) { - os_printf("MCU ISP gpio%d\n", mcu_isp_pin); - GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - } else os_printf("MCU isp: no pin\n"); - break; - case RTS_OFF: - if (mcu_isp_pin >= 0) { - GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(100L); - } - break; - } - state = TN_end; - break; - } - } - return 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) { + os_printf("MCU reset gpio%d\n", mcu_reset_pin); + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + } else os_printf("MCU reset: no pin\n"); + 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) { + os_printf("MCU ISP gpio%d\n", mcu_isp_pin); + GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + } else os_printf("MCU isp: no pin\n"); + break; + case RTS_OFF: + if (mcu_isp_pin >= 0) { + GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(100L); + } + break; + } + state = TN_end; + break; + } + } + return state; } void ICACHE_FLASH_ATTR serbridgeReset() { - if (mcu_reset_pin >= 0) { - os_printf("MCU reset gpio%d\n", mcu_reset_pin); - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - } else os_printf("MCU reset: no pin\n"); + if (mcu_reset_pin >= 0) { + os_printf("MCU reset gpio%d\n", mcu_reset_pin); + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } else os_printf("MCU reset: no pin\n"); } // Receive callback static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned short len) { - serbridgeConnData *conn = serbridgeFindConnData(arg); - //os_printf("Receive callback on conn %p\n", conn); - if (conn == NULL) return; - - // at the start of a connection we're in cmInit mode and we wait for the first few characters - // to arrive in order to decide what type of connection this is.. The following if statements - // do this dispatch. An issue here is that we assume that the first few characters all arrive - // in the same TCP packet, which is true if the sender is a program, but not necessarily - // if the sender is a person typing (although in that case the line-oriented TTY input seems - // to make it work too). If this becomes a problem we need to buffer the first few chars... - if (conn->conn_mode == cmInit) { - - // If the connection starts with the Arduino or ARM reset sequence we perform a RESET - if ((len == 2 && strncmp(data, "0 ", 2) == 0) || - (len == 2 && strncmp(data, "?\n", 2) == 0) || - (len == 3 && strncmp(data, "?\r\n", 3) == 0)) { - os_printf("MCU Reset=%d ISP=%d\n", mcu_reset_pin, mcu_isp_pin); - os_delay_us(2*1000L); // time for os_printf to happen - // send reset to arduino/ARM - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); - os_delay_us(100L); - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(1000L); - conn->conn_mode = cmAVR; - - - // If the connection starts with a telnet negotiation we will do telnet - } else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { - conn->conn_mode = cmTelnet; - conn->telnet_state = TN_normal; - // note that the three negotiation chars will be gobbled-up by telnetUnwrap - os_printf("telnet mode\n"); - - // looks like a plain-vanilla connection! - } else { - conn->conn_mode = cmTransparent; - } - } - - // write the buffer to the uart - if (conn->conn_mode == cmTelnet) { - conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); - } else { - uart0_tx_buffer(data, len); - } - - serledFlash(50); // short blink on serial LED -} - -// Error callback (it's really poorly named, it's not a "connection reconnected" callback, -// it's really a "connection broken, please reconnect" callback) -static void ICACHE_FLASH_ATTR serbridgeReconCb(void *arg, sint8 err) { - serbridgeConnData *conn=serbridgeFindConnData(arg); - if (conn == NULL) return; - // Close the connection - espconn_disconnect(conn->conn); - conn->conn = NULL; + serbridgeConnData *conn = ((struct espconn*)arg)->reverse; + //os_printf("Receive callback on conn %p\n", conn); + if (conn == NULL) return; + + // at the start of a connection we're in cmInit mode and we wait for the first few characters + // to arrive in order to decide what type of connection this is.. The following if statements + // do this dispatch. An issue here is that we assume that the first few characters all arrive + // in the same TCP packet, which is true if the sender is a program, but not necessarily + // if the sender is a person typing (although in that case the line-oriented TTY input seems + // to make it work too). If this becomes a problem we need to buffer the first few chars... + if (conn->conn_mode == cmInit) { + + // If the connection starts with the Arduino or ARM reset sequence we perform a RESET + if ((len == 2 && strncmp(data, "0 ", 2) == 0) || + (len == 2 && strncmp(data, "?\n", 2) == 0) || + (len == 3 && strncmp(data, "?\r\n", 3) == 0)) { + os_printf("MCU Reset=%d ISP=%d\n", mcu_reset_pin, mcu_isp_pin); + os_delay_us(2*1000L); // time for os_printf to happen + // send reset to arduino/ARM + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); + os_delay_us(100L); + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(1000L); + conn->conn_mode = cmAVR; + + + // If the connection starts with a telnet negotiation we will do telnet + } else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { + conn->conn_mode = cmTelnet; + conn->telnet_state = TN_normal; + // note that the three negotiation chars will be gobbled-up by telnetUnwrap + os_printf("telnet mode\n"); + + // looks like a plain-vanilla connection! + } else { + conn->conn_mode = cmTransparent; + } + } + + // write the buffer to the uart + if (conn->conn_mode == cmTelnet) { + conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); + } else { + uart0_tx_buffer(data, len); + } + + serledFlash(50); // short blink on serial LED } // Disconnection callback static void ICACHE_FLASH_ATTR serbridgeDisconCb(void *arg) { - // Iterate through all the connections and deallocate the ones that are in a state that - // indicates that they're closed - for (int i=0; istate == ESPCONN_NONE || connData[i].conn->state == ESPCONN_CLOSE)) - { - if (connData[i].conn_mode == cmAVR) { - // send reset to arduino/ARM - if (mcu_reset_pin >= 0) { - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - } - } - connData[i].conn = NULL; - } - } + serbridgeConnData *conn = ((struct espconn*)arg)->reverse; + if (conn == NULL) return; + // Free buffers + if (conn->sentbuffer != NULL) os_free(conn->sentbuffer); + conn->sentbuffer = NULL; + if (conn->txbuffer != NULL) os_free(conn->txbuffer); + conn->txbuffer = NULL; + conn->txbufferlen = 0; + // Send reset to attached uC if it was in programming mode + if (conn->conn_mode == cmAVR && mcu_reset_pin >= 0) { + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } + conn->conn = NULL; +} + +// Connection reset callback (note that there will be no DisconCb) +static void ICACHE_FLASH_ATTR serbridgeResetCb(void *arg, sint8 err) { + os_printf("serbridge: connection reset err=%d\n", err); + serbridgeDisconCb(arg); } // New connection callback, use one of the connection descriptors, if we have one left. static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) { - struct espconn *conn = arg; - //Find empty conndata in pool - int i; - for (i=0; ireverse = connData+i; + connData[i].readytosend = true; + connData[i].conn_mode = cmInit; + + espconn_regist_recvcb(conn, serbridgeRecvCb); + espconn_regist_disconcb(conn, serbridgeDisconCb); + espconn_regist_reconcb(conn, serbridgeResetCb); + espconn_regist_sentcb(conn, serbridgeSentCb); + + espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY); } // callback with a buffer of characters that have arrived on the uart void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, int length) { - // push the buffer into the microcontroller console - for (int i=0; i= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); - // switch pin mux to make these pins GPIO pins - if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); - if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); + mcu_reset_pin = flashConfig.reset_pin; + mcu_isp_pin = flashConfig.isp_pin; + os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n", + mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart); + + if (flashConfig.swap_uart) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); + system_uart_swap(); + } else { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0); + system_uart_de_swap(); + } + + // set both pins to 1 before turning them on so we don't cause a reset + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); + // switch pin mux to make these pins GPIO pins + if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); + if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); } // Start transparent serial bridge TCP server on specified port (typ. 23) void ICACHE_FLASH_ATTR serbridgeInit(int port) { - serbridgeInitPins(); - - int i; - for (i = 0; i < MAX_CONN; i++) { - connData[i].conn = NULL; - connData[i].txbuffer = txbuffer[i]; - } - serbridgeConn.type = ESPCONN_TCP; - serbridgeConn.state = ESPCONN_NONE; - serbridgeTcp.local_port = port; - serbridgeConn.proto.tcp = &serbridgeTcp; - - espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb); - espconn_accept(&serbridgeConn); - espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN); - espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0); + serbridgeInitPins(); + + for (int i = 0; i < MAX_CONN; i++) { + connData[i].conn = NULL; + } + serbridgeConn.type = ESPCONN_TCP; + serbridgeConn.state = ESPCONN_NONE; + serbridgeTcp.local_port = port; + serbridgeConn.proto.tcp = &serbridgeTcp; + + espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb); + espconn_accept(&serbridgeConn); + espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN); + espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0); } diff --git a/serial/serbridge.h b/serial/serbridge.h index 364f833..f824027 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -8,8 +8,8 @@ #define MAX_CONN 4 #define SER_BRIDGE_TIMEOUT 28799 -//Max send buffer len -#define MAX_TXBUFFER 1024 +// Send buffer size +#define MAX_TXBUFFER 2048 typedef struct serbridgeConnData serbridgeConnData; @@ -25,10 +25,11 @@ enum connModes { struct serbridgeConnData { struct espconn *conn; - enum connModes conn_mode; // connection mode - char *txbuffer; // buffer for the data to send - uint16 txbufferlen; // length of data in txbuffer - bool readytosend; // true, if txbuffer can send by espconn_sent + enum connModes conn_mode; // connection mode + char *txbuffer; // buffer for the data to send + uint16 txbufferlen; // length of data in txbuffer + char *sentbuffer; // buffer sent, awaiting callback to get freed + bool readytosend; // true, if txbuffer can be sent by espconn_sent uint8_t telnet_state; };