/
serbridge.c
500 lines (447 loc) · 15.8 KB
/
serbridge.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
#include "esp8266.h"
#include "uart.h"
#include "crc16.h"
#include "serbridge.h"
#include "serled.h"
#include "config.h"
#include "console.h"
#include "slip.h"
#include "cmd.h"
#define SKIP_AT_RESET
static struct espconn serbridgeConn1; // plain bridging port
static struct espconn serbridgeConn2; // programming port
static esp_tcp serbridgeTcp1, serbridgeTcp2;
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];
//===== 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<len; i++) {
uint8_t c = inBuf[i];
switch (state) {
default:
case TN_normal:
if (c == IAC) state = TN_iac; // escape char: see what's next
else uart0_write_char(c); // regular char
break;
case TN_iac:
switch (c) {
case IAC: // second escape -> 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) {
#ifdef SERBR_DBG
os_printf("MCU reset gpio%d\n", mcu_reset_pin);
#endif
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
}
#ifdef SERBR_DBG
else { os_printf("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);
#endif
GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L);
}
#ifdef SERBR_DBG
else { os_printf("MCU isp: no pin\n"); }
#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;
}
state = TN_end;
break;
}
}
return state;
}
// Generate a reset pulse for the attached microcontroller
void ICACHE_FLASH_ATTR
serbridgeReset()
{
if (mcu_reset_pin >= 0) {
#ifdef SERBR_DBG
os_printf("MCU reset gpio%d\n", mcu_reset_pin);
#endif
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(2000L); // esp8266 needs at least 1ms reset pulse, it seems...
GPIO_OUTPUT_SET(mcu_reset_pin, 1);
}
#ifdef SERBR_DBG
else { os_printf("MCU reset: no pin\n"); }
#endif
}
// Receive callback
static void ICACHE_FLASH_ATTR
serbridgeRecvCb(void *arg, char *data, unsigned short len)
{
serbridgeConnData *conn = ((struct espconn*)arg)->reverse;
//os_printf("Receive callback on conn %p\n", conn);
if (conn == NULL) return;
bool startPGM = false;
// 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)) {
startPGM = true;
conn->conn_mode = cmPGM;
// 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
#ifdef SERBR_DBG
os_printf("telnet mode\n");
#endif
// 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
} else if (conn->conn_mode == cmPGMInit) {
conn->conn_mode = cmPGM;
startPGM = true;
}
// do the programming reset dance
if (startPGM) {
#ifdef SERBR_DBG
os_printf("MCU Reset=gpio%d ISP=gpio%d\n", mcu_reset_pin, mcu_isp_pin);
os_delay_us(2*1000L); // time for os_printf to happen
#endif
// send reset to arduino/ARM, send "ISP" signal for the duration of the programming
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(2000L);
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); // wait a millisecond before writing to the UART below
conn->conn_mode = cmPGM;
slip_disabled++; // disable SLIP so it doesn't interfere with flashing
#ifdef SKIP_AT_RESET
serledFlash(50); // short blink on serial LED
return;
#endif
}
// 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
}
//===== UART -> TCP
// Send all data in conn->txbuffer
// 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("TX %p %d\n", conn, 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);
conn->txbufferlen = 0;
if (!conn->txoverflow_at) conn->txoverflow_at = system_get_time();
} else {
conn->sentbuffer = conn->txbuffer;
conn->txbuffer = NULL;
conn->txbufferlen = 0;
}
}
return result;
}
// 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.
static sint8 ICACHE_FLASH_ATTR
espbuffsend(serbridgeConnData *conn, const char *data, uint16 len)
{
if (conn->txbufferlen >= MAX_TXBUFFER) goto overflow;
// 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 += avail;
// 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);
}
goto overflow;
}
return result;
overflow:
if (conn->txoverflow_at) {
// we've already been overflowing
if (system_get_time() - conn->txoverflow_at > 10*1000*1000) {
// no progress in 10 seconds, kill the connection
os_printf("serbridge: killing overlowing stuck conn %p\n", conn);
espconn_disconnect(conn->conn);
}
// else be silent, we already printed an error
} else {
// print 1-time message and take timestamp
os_printf("serbridge: txbuffer full, conn %p\n", conn);
conn->txoverflow_at = system_get_time();
}
return -128;
}
//callback after the data are sent
static void ICACHE_FLASH_ATTR
serbridgeSentCb(void *arg)
{
serbridgeConnData *conn = ((struct espconn*)arg)->reverse;
//os_printf("Sent CB %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;
conn->txoverflow_at = 0;
sendtxbuffer(conn); // send possible new data in txbuffer
}
void ICACHE_FLASH_ATTR
console_process(char *buf, short len)
{
// push buffer into web-console
for (short i=0; i<len; i++)
console_write_char(buf[i]);
// push the buffer into each open connection
for (short i=0; i<MAX_CONN; i++) {
if (connData[i].conn) {
espbuffsend(&connData[i], buf, len);
}
}
}
// callback with a buffer of characters that have arrived on the uart
void ICACHE_FLASH_ATTR
serbridgeUartCb(char *buf, short length)
{
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 {
slip_parse_buf(buf, length);
}
serledFlash(50); // short blink on serial LED
}
//===== Connect / disconnect
// Disconnection callback
static void ICACHE_FLASH_ATTR
serbridgeDisconCb(void *arg)
{
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 == cmPGM && mcu_reset_pin >= 0) {
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(100L);
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; i<MAX_CONN; i++) if (connData[i].conn==NULL) break;
#ifdef SERBR_DBG
os_printf("Accept port %d, conn=%p, pool slot %d\n", conn->proto.tcp->local_port, conn, i);
#endif
if (i==MAX_CONN) {
#ifdef SERBR_DBG
os_printf("Aiee, conn pool overflow!\n");
#endif
espconn_disconnect(conn);
return;
}
os_memset(connData+i, 0, sizeof(struct serbridgeConnData));
connData[i].conn = conn;
conn->reverse = connData+i;
connData[i].readytosend = true;
connData[i].conn_mode = cmInit;
// if it's the second port we start out in programming mode
if (conn->proto.tcp->local_port == serbridgeConn2.proto.tcp->local_port)
connData[i].conn_mode = cmPGMInit;
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);
}
//===== Initialization
void ICACHE_FLASH_ATTR
serbridgeInitPins()
{
mcu_reset_pin = flashConfig.reset_pin;
mcu_isp_pin = flashConfig.isp_pin;
#ifdef SERBR_DBG
os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n",
mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart);
#endif
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);
if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_MTDO_U);
else 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);
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
if (flashConfig.rx_pullup) PIN_PULLUP_EN(PERIPHS_IO_MUX_U0RXD_U);
else PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0RXD_U);
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 port1, int port2)
{
serbridgeInitPins();
os_memset(connData, 0, sizeof(connData));
os_memset(&serbridgeTcp1, 0, sizeof(serbridgeTcp1));
os_memset(&serbridgeTcp2, 0, sizeof(serbridgeTcp2));
// set-up the primary port for plain bridging
serbridgeConn1.type = ESPCONN_TCP;
serbridgeConn1.state = ESPCONN_NONE;
serbridgeTcp1.local_port = port1;
serbridgeConn1.proto.tcp = &serbridgeTcp1;
espconn_regist_connectcb(&serbridgeConn1, serbridgeConnectCb);
espconn_accept(&serbridgeConn1);
espconn_tcp_set_max_con_allow(&serbridgeConn1, MAX_CONN);
espconn_regist_time(&serbridgeConn1, SER_BRIDGE_TIMEOUT, 0);
// set-up the secondary port for programming
serbridgeConn2.type = ESPCONN_TCP;
serbridgeConn2.state = ESPCONN_NONE;
serbridgeTcp2.local_port = port2;
serbridgeConn2.proto.tcp = &serbridgeTcp2;
espconn_regist_connectcb(&serbridgeConn2, serbridgeConnectCb);
espconn_accept(&serbridgeConn2);
espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN);
espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0);
}