123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- #define MODULE "tty"
- #define DEBUG 0
- #define BAUD_RATE 115200
- #include "tty.h"
- #include "config.h"
- #include "fw.h"
- #include <USB.h>
- #include <HardwareSerial.h>
- #include <rom/crc.h>
- #define SOH '\001'
- #define STX '\002'
- #define ETX '\003'
- #define EOT '\004'
- #define ENQ '\005'
- #define ACK '\006'
- #define XON '\021' // DC1
- #define WRST '\022' // DC2 - window = 0
- #define XOFF '\023' // DC3
- #define WGO '\024' // DC4 - window + 256 bytes
- #define NAK '\025'
- #define SYN '\026'
- #define ETB '\027' // Not in upload mode
- #define CAN '\030'
- #define EM '\031' // Packet offset too high
- #define FS '\034'
- #define GS '\035'
- #define RS '\036'
- #define US '\037'
- #define WGO_CHUNK 256
- #define STREAMBUF_SIZE 2048
- #define BUF_SLACK (STREAMBUF_SIZE >> 1)
- static char enq_str[] = "\026\035MAX80 ready\004\r\n";
- static const char fwupload_start[] =
- "\034\001: /// MAX80 FW UPLOAD ~@~ $\r\n\035";
- void TTY::reset()
- {
- rx.rlen = 0;
- rx.state = rx_state::normal;
- rx.b64_bits = 0;
- }
- static void null_flush(void)
- {
- }
- TTY::TTY(Stream &port)
- {
- _port = &port;
- rx_sbuf = true ? xStreamBufferCreate(STREAMBUF_SIZE, 1) : NULL;
- _flush = null_flush;
- reset();
- }
- TTY::~TTY()
- {
- if (rx_sbuf)
- vStreamBufferDelete(rx_sbuf);
- }
- int TTY::rxdata(void *buf, size_t len)
- {
- if (!rx_sbuf)
- return 0;
- int rcv = 0;
- while (!rcv) {
- rcv = xStreamBufferReceive(rx_sbuf, buf, len, 0);
- if (rcv)
- break;
- uint32_t now = millis();
- if (now - last_rx > 500) {
- last_rx = now;
- tx_credits_reset = true;
- }
- if (tx_credits_reset) {
- // Drain input before WRST
- //flush();
- if (port().write(WRST)) {
- MSG("Resetting window, last_ack = %u\n", rx.last_ack);
- tx_credits_reset = false;
- tx_credits = STREAMBUF_SIZE - BUF_SLACK;
- } else {
- // Uhm... wait a tiny bit and then try again to sent WRST?
- static bool failed_once = false;
- if (!failed_once) {
- MSG("Failed to reset window?!\n");
- failed_once = true;
- }
- rcv = xStreamBufferReceive(rx_sbuf, buf, len, 1);
- }
- } else {
- while (tx_credits >= WGO_CHUNK && port().write(WGO))
- tx_credits -= WGO_CHUNK;
- rcv = xStreamBufferReceive(rx_sbuf, buf, len, 1);
- tx_credits += rcv;
- }
- }
- CMSG("got %d\n", rcv);
- return rcv;
- }
- int TTY::rxdata(token_t me, void *buf, size_t len)
- {
- TTY *tty = (TTY *)me;
- return tty->rxdata(buf, len);
- }
- void TTY::_upload_begin()
- {
- MSG("_upload_begin\n");
- if (rx_sbuf)
- xStreamBufferReset(rx_sbuf);
- else
- rx_sbuf = xStreamBufferCreate(STREAMBUF_SIZE, 1);
- MSG("rx_sbuf = %p\n", rx_sbuf);
- if (!rx_sbuf)
- goto can;
- tx_credits_reset = false;
- tx_credits = STREAMBUF_SIZE - BUF_SLACK;
- port().write(RS);
- rx.state = rx_state::stxwait;
- rx.last_ack = 0;
- rx.rlen = 0;
- rx.b64_bits = 0;
- MSG("firmware_update_start()\n");
- if (firmware_update_start(TTY::rxdata, (token_t)this, true))
- goto can;
- return;
- can:
- port().write(CAN);
- rx.state = rx_state::normal;
- return;
- }
- void TTY::_onerr()
- {
- if (rx.state != rx_state::normal) {
- port().write(NAK);
- }
- }
- static int filter_echo(int byte)
- {
- if (byte >= ' ' && byte < 127)
- return byte;
- if (byte == '\t' || byte == '\r' || byte == '\n')
- return byte;
- return -1;
- }
- // Decode a base64 data byte (using simple offset-63)
- // Call with -1 or any invalid value to invalidate the input buffer
- int TTY::_decode_data(int input)
- {
- unsigned int buf = rx.b64_buf;
- unsigned int inval = input - '?';
- if (inval > 63) {
- rx.b64_bits = 0;
- return -2; // Invalid input
- }
- rx.b64_buf = buf = (buf << 6) + inval;
- rx.b64_bits += 6;
- if (rx.b64_bits >= 8) {
- rx.b64_bits -= 8;
- return (uint8_t)(buf >> rx.b64_bits);
- } else {
- return -1;
- }
- }
- // Change this to be a buffer...
- void TTY::_onrx()
- {
- int byte, data;
- while ((byte = port().read()) >= 0) {
- last_rx = millis();
- switch (rx.state) {
- case rx_state::normal:
- if (rx.rlen < sizeof fwupload_start &&
- byte == fwupload_start[rx.rlen]) {
- if (!fwupload_start[++rx.rlen]) {
- _upload_begin();
- byte = -1;
- }
- } else {
- rx.rlen = 0;
- switch (byte) {
- case ENQ:
- port().write(enq_str);
- byte = ETB;
- break;
- case ETX:
- case EOT:
- case CAN:
- byte = ETB; // Not in file upload state
- break;
- default:
- // Echo if printable
- byte = filter_echo(byte);
- break;
- }
- }
- break;
- case rx_state::stxwait:
- switch (byte) {
- case STX:
- rx.rlen = 0;
- rx.hdr_raw[rx.rlen++] = byte;
- rx.b64_bits = 0;
- rx.state = rx_state::hdr;
- byte = -1;
- break;
- case ETX:
- case CAN:
- MSG("Received <%02X> waiting for STX\n", byte);
- reset();
- byte = CAN;
- break;
- case ENQ:
- rx.rlen = 0;
- byte = GS; // In upload wait for STX state
- break;
- case SYN:
- rx.rlen = 0;
- tx_credits_reset = true; // Request to resync credits
- byte = -1;
- break;
- case EOT:
- // Upload complete, no more data
- byte = ETB;
- break;
- default:
- byte = -1; // No echo
- break;
- }
- break;
- case rx_state::hdr:
- data = _decode_data(byte);
- byte = -1;
- if (data < -1) {
- rx.state = rx_state::stxwait;
- rx.rlen = 0;
- tx_credits_reset = true;
- byte = US; // Framing error
- } else if (data == -1) {
- // Nothing to do
- } else if (rx.rlen >= sizeof rx.hdr_raw) {
- // ERROR THIS SHOULD NEVER HAPPEN
- MSG("Header buffer overrun!!!\n");
- reset();
- byte = CAN;
- } else {
- rx.hdr_raw[rx.rlen++] = data;
- if (rx.rlen == sizeof rx.hdr) {
- // Start of data packet
- MSG("Start packet hdr %d length %d offset %d last_ack %d\n",
- rx.rlen, rx.hdr.len+1, rx.hdr.offs, rx.last_ack);
- rx.state = rx_state::data;
- rx.rlen = 0;
- }
- }
- break;
- case rx_state::data:
- data = _decode_data(byte);
- byte = -1;
- if (data < -1) {
- rx.state = rx_state::stxwait;
- rx.rlen = 0;
- tx_credits_reset = true;
- byte = US; // Framing error
- } else if (data == -1) {
- // Nothing to do
- } else if (rx.rlen >= sizeof rx_data) {
- // ERROR THIS SHOULD NEVER HAPPEN
- MSG("Packet data buffer overrun!!!\n");
- reset();
- byte = CAN;
- } else {
- rx_data[rx.rlen++] = data;
- // rx.hdr.len = packet data len - 1
- if (rx.rlen > rx.hdr.len) {
- int have = rx.rlen;
- uint32_t crc;
- if (have != rx.hdr.len + 1) {
- MSG("Invalid packet length (should not happen...)\n");
- byte = NAK;
- } else if ((crc = crc32_le(0, rx_data, have))
- != rx.hdr.crc) {
- MSG("Packet CRC error hdr %08x data %08x\n", rx.hdr.crc, crc);
- byte = NAK;
- } else if (rx.hdr.offs > rx.last_ack) {
- MSG("Invalid packet offsets [%d..%d) at %d\n",
- rx.hdr.offs, rx.hdr.offs + have, rx.last_ack);
- byte = EM;
- } else if (rx.hdr.offs + have <= rx.last_ack) {
- // Ack and discard packet below current window (transmitter is catching up)
- byte = ACK;
- } else {
- int sent = 0;
- uint32_t skip = rx.last_ack - rx.hdr.offs;
- have -= skip;
- sent = xStreamBufferSend(rx_sbuf, rx_data+skip, have, 0);
- rx.last_ack += sent;
- if (sent != have) {
- MSG("Packet underrun, got %d, expected %d\n", sent, have);
- byte = NAK;
- } else {
- MSG("%d bytes received OK\n", sent);
- byte = ACK;
- }
- }
- if (byte != ACK)
- tx_credits_reset = true;
- rx.state = rx_state::stxwait;
- rx.rlen = 0;
- }
- }
- break;
- }
- if (byte >= 0)
- port().write(byte);
- }
- }
- void TTY::_onconnect()
- {
- port().write(XON);
- }
- void TTY::_onbreak()
- {
- reset();
- }
- void TTY::_ondisconnect()
- {
- reset();
- }
- static TTY *usb_tty;
- static TaskHandle_t usb_tty_task;
- #define USB_NOTIFY_INDEX 0 /* Seems to be the only available... */
- #define USB_TASK_STACK 4096
- #define USB_TASK_PRIORITY 5
- void TTY::usb_task_handler(void *pvt)
- {
- (void)pvt;
- while (1) {
- uint32_t notify_value;
- xTaskNotifyWaitIndexed(USB_NOTIFY_INDEX, 0, 1,
- ¬ify_value, portMAX_DELAY);
- usb_tty->_onrx();
- }
- }
- void TTY::usb_onevent(void *arg, esp_event_base_t event_base,
- int32_t event_id, void *event_data)
- {
- if (event_base == ARDUINO_USB_CDC_EVENTS) {
- switch (event_id) {
- case ARDUINO_USB_CDC_CONNECTED_EVENT:
- case ARDUINO_USB_CDC_LINE_STATE_EVENT:
- usb_tty->_onconnect();
- break;
- case ARDUINO_USB_CDC_DISCONNECTED_EVENT:
- usb_tty->_ondisconnect();
- break;
- case ARDUINO_USB_CDC_RX_EVENT:
- xTaskNotifyIndexed(usb_tty_task, USB_NOTIFY_INDEX, 1, eSetBits);
- break;
- case ARDUINO_USB_CDC_RX_OVERFLOW_EVENT:
- usb_tty->_onerr();
- break;
- default:
- // Do nothing
- break;
- }
- }
- }
- static void usb_flush()
- {
- Serial.flush();
- }
- static TTY *uart_tty;
- void TTY::uart_onrx(void)
- {
- uart_tty->_onrx();
- }
- void TTY::uart_onerr(hardwareSerial_error_t err)
- {
- switch (err) {
- case UART_BREAK_ERROR:
- uart_tty->_onbreak();
- break;
- default:
- uart_tty->_onerr();
- break;
- }
- }
- static void uart_flush()
- {
- Serial0.flush(true);
- }
- void TTY::init()
- {
- uart_tty = new TTY(Serial0);
- uart_tty->_flush = uart_flush;
- Serial0.begin(BAUD_RATE);
- Serial0.onReceive(uart_onrx, false);
- Serial0.onReceiveError(uart_onerr);
- usb_tty = new TTY(Serial);
- usb_tty->_flush = usb_flush;
- if (xTaskCreate(usb_task_handler, "usbttyd",
- USB_TASK_STACK, usb_tty,
- USB_TASK_PRIORITY, &usb_tty_task) == pdPASS) {
- Serial.onEvent(usb_onevent);
- xTaskNotifyIndexed(usb_tty_task, USB_NOTIFY_INDEX, 1, eSetBits);
- }
- Serial.enableReboot(true);
- }
- void TTY::ping()
- {
- }
|