Set blackbox file handler to NULL after closing file
[inav.git] / src / main / msp / msp_serial.c
blob0dbc1768b8a10fd29bfddf7aecb6ba0d764142b4
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <string.h>
24 #include "platform.h"
26 #include "build/debug.h"
28 #include "common/streambuf.h"
29 #include "common/utils.h"
30 #include "common/maths.h"
31 #include "common/crc.h"
33 #include "drivers/system.h"
34 #include "drivers/serial.h"
36 #include "io/serial.h"
37 #include "fc/cli.h"
39 #include "msp/msp.h"
40 #include "msp/msp_serial.h"
42 static mspPort_t mspPorts[MAX_MSP_PORT_COUNT];
45 void resetMspPort(mspPort_t *mspPortToReset, serialPort_t *serialPort)
47 memset(mspPortToReset, 0, sizeof(mspPort_t));
49 mspPortToReset->port = serialPort;
52 void mspSerialAllocatePorts(void)
54 uint8_t portIndex = 0;
55 serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_MSP);
56 while (portConfig && portIndex < MAX_MSP_PORT_COUNT) {
57 mspPort_t *mspPort = &mspPorts[portIndex];
58 if (mspPort->port) {
59 portIndex++;
60 continue;
63 serialPort_t *serialPort = openSerialPort(portConfig->identifier, FUNCTION_MSP, NULL, NULL, baudRates[portConfig->msp_baudrateIndex], MODE_RXTX, SERIAL_NOT_INVERTED);
64 if (serialPort) {
65 resetMspPort(mspPort, serialPort);
66 portIndex++;
69 portConfig = findNextSerialPortConfig(FUNCTION_MSP);
73 void mspSerialReleasePortIfAllocated(serialPort_t *serialPort)
75 for (uint8_t portIndex = 0; portIndex < MAX_MSP_PORT_COUNT; portIndex++) {
76 mspPort_t *candidateMspPort = &mspPorts[portIndex];
77 if (candidateMspPort->port == serialPort) {
78 closeSerialPort(serialPort);
79 memset(candidateMspPort, 0, sizeof(mspPort_t));
84 static bool mspSerialProcessReceivedData(mspPort_t *mspPort, uint8_t c)
86 switch (mspPort->c_state) {
87 default:
88 case MSP_IDLE: // Waiting for '$' character
89 if (c == '$') {
90 mspPort->mspVersion = MSP_V1;
91 mspPort->c_state = MSP_HEADER_START;
93 else {
94 return false;
96 break;
98 case MSP_HEADER_START: // Waiting for 'M' (MSPv1 / MSPv2_over_v1) or 'X' (MSPv2 native)
99 switch (c) {
100 case 'M':
101 mspPort->c_state = MSP_HEADER_M;
102 break;
103 case 'X':
104 mspPort->c_state = MSP_HEADER_X;
105 break;
106 default:
107 mspPort->c_state = MSP_IDLE;
108 break;
110 break;
112 case MSP_HEADER_M: // Waiting for '<'
113 if (c == '<') {
114 mspPort->offset = 0;
115 mspPort->checksum1 = 0;
116 mspPort->checksum2 = 0;
117 mspPort->c_state = MSP_HEADER_V1;
119 else {
120 mspPort->c_state = MSP_IDLE;
122 break;
124 case MSP_HEADER_X:
125 if (c == '<') {
126 mspPort->offset = 0;
127 mspPort->checksum2 = 0;
128 mspPort->mspVersion = MSP_V2_NATIVE;
129 mspPort->c_state = MSP_HEADER_V2_NATIVE;
131 else {
132 mspPort->c_state = MSP_IDLE;
134 break;
136 case MSP_HEADER_V1: // Now receive v1 header (size/cmd), this is already checksummable
137 mspPort->inBuf[mspPort->offset++] = c;
138 mspPort->checksum1 ^= c;
139 if (mspPort->offset == sizeof(mspHeaderV1_t)) {
140 mspHeaderV1_t * hdr = (mspHeaderV1_t *)&mspPort->inBuf[0];
141 // Check incoming buffer size limit
142 if (hdr->size > MSP_PORT_INBUF_SIZE) {
143 mspPort->c_state = MSP_IDLE;
145 else if (hdr->cmd == MSP_V2_FRAME_ID) {
146 // MSPv1 payload must be big enough to hold V2 header + extra checksum
147 if (hdr->size >= sizeof(mspHeaderV2_t) + 1) {
148 mspPort->mspVersion = MSP_V2_OVER_V1;
149 mspPort->c_state = MSP_HEADER_V2_OVER_V1;
151 else {
152 mspPort->c_state = MSP_IDLE;
155 else {
156 mspPort->dataSize = hdr->size;
157 mspPort->cmdMSP = hdr->cmd;
158 mspPort->cmdFlags = 0;
159 mspPort->offset = 0; // re-use buffer
160 mspPort->c_state = mspPort->dataSize > 0 ? MSP_PAYLOAD_V1 : MSP_CHECKSUM_V1; // If no payload - jump to checksum byte
163 break;
165 case MSP_PAYLOAD_V1:
166 mspPort->inBuf[mspPort->offset++] = c;
167 mspPort->checksum1 ^= c;
168 if (mspPort->offset == mspPort->dataSize) {
169 mspPort->c_state = MSP_CHECKSUM_V1;
171 break;
173 case MSP_CHECKSUM_V1:
174 if (mspPort->checksum1 == c) {
175 mspPort->c_state = MSP_COMMAND_RECEIVED;
176 SD(fprintf(stderr, "[MSPV1] Command received\n"));
177 } else {
178 mspPort->c_state = MSP_IDLE;
179 SD(fprintf(stderr, "[MSPV1] Checksum error!\n"));
181 break;
183 case MSP_HEADER_V2_OVER_V1: // V2 header is part of V1 payload - we need to calculate both checksums now
184 mspPort->inBuf[mspPort->offset++] = c;
185 mspPort->checksum1 ^= c;
186 mspPort->checksum2 = crc8_dvb_s2(mspPort->checksum2, c);
187 if (mspPort->offset == (sizeof(mspHeaderV2_t) + sizeof(mspHeaderV1_t))) {
188 mspHeaderV2_t * hdrv2 = (mspHeaderV2_t *)&mspPort->inBuf[sizeof(mspHeaderV1_t)];
189 mspPort->dataSize = hdrv2->size;
191 // Check for potential buffer overflow
192 if (hdrv2->size > MSP_PORT_INBUF_SIZE) {
193 mspPort->c_state = MSP_IDLE;
195 else {
196 mspPort->cmdMSP = hdrv2->cmd;
197 mspPort->cmdFlags = hdrv2->flags;
198 mspPort->offset = 0; // re-use buffer
199 mspPort->c_state = mspPort->dataSize > 0 ? MSP_PAYLOAD_V2_OVER_V1 : MSP_CHECKSUM_V2_OVER_V1;
202 break;
204 case MSP_PAYLOAD_V2_OVER_V1:
205 mspPort->checksum2 = crc8_dvb_s2(mspPort->checksum2, c);
206 mspPort->checksum1 ^= c;
207 mspPort->inBuf[mspPort->offset++] = c;
209 if (mspPort->offset == mspPort->dataSize) {
210 mspPort->c_state = MSP_CHECKSUM_V2_OVER_V1;
212 break;
214 case MSP_CHECKSUM_V2_OVER_V1:
215 mspPort->checksum1 ^= c;
216 if (mspPort->checksum2 == c) {
217 mspPort->c_state = MSP_CHECKSUM_V1; // Checksum 2 correct - verify v1 checksum
218 } else {
219 mspPort->c_state = MSP_IDLE;
221 break;
223 case MSP_HEADER_V2_NATIVE:
224 mspPort->inBuf[mspPort->offset++] = c;
225 mspPort->checksum2 = crc8_dvb_s2(mspPort->checksum2, c);
226 if (mspPort->offset == sizeof(mspHeaderV2_t)) {
227 mspHeaderV2_t * hdrv2 = (mspHeaderV2_t *)&mspPort->inBuf[0];
229 // Check for potential buffer overflow
230 if (hdrv2->size > MSP_PORT_INBUF_SIZE) {
231 mspPort->c_state = MSP_IDLE;
232 SD(fprintf(stderr, "[MSPV2] Potential buffer overflow!\n"));
234 else {
235 mspPort->dataSize = hdrv2->size;
236 mspPort->cmdMSP = hdrv2->cmd;
237 mspPort->cmdFlags = hdrv2->flags;
238 mspPort->offset = 0; // re-use buffer
239 mspPort->c_state = mspPort->dataSize > 0 ? MSP_PAYLOAD_V2_NATIVE : MSP_CHECKSUM_V2_NATIVE;
242 break;
244 case MSP_PAYLOAD_V2_NATIVE:
245 mspPort->checksum2 = crc8_dvb_s2(mspPort->checksum2, c);
246 mspPort->inBuf[mspPort->offset++] = c;
248 if (mspPort->offset == mspPort->dataSize) {
249 mspPort->c_state = MSP_CHECKSUM_V2_NATIVE;
251 break;
253 case MSP_CHECKSUM_V2_NATIVE:
254 if (mspPort->checksum2 == c) {
255 mspPort->c_state = MSP_COMMAND_RECEIVED;
256 SD(fprintf(stderr, "[MSPV2] command received!\n"));
257 } else {
258 SD(fprintf(stderr, "[MSPV2] Checksum error!\n"));
259 mspPort->c_state = MSP_IDLE;
261 break;
264 return true;
267 static uint8_t mspSerialChecksumBuf(uint8_t checksum, const uint8_t *data, int len)
269 while (len-- > 0) {
270 checksum ^= *data++;
272 return checksum;
275 #define JUMBO_FRAME_SIZE_LIMIT 255
276 static int mspSerialSendFrame(mspPort_t *msp, const uint8_t * hdr, int hdrLen, const uint8_t * data, int dataLen, const uint8_t * crc, int crcLen)
278 // MSP port might be turned into a CLI port, which will make
279 // msp->port become NULL.
280 serialPort_t *port = msp->port;
281 if (!port) {
282 return 0;
284 // VSP MSP port might be unconnected. To prevent blocking - check if it's connected first
285 if (!serialIsConnected(port)) {
286 return 0;
289 // We are allowed to send out the response if
290 // a) TX buffer is completely empty (we are talking to well-behaving party that follows request-response scheduling;
291 // this allows us to transmit jumbo frames bigger than TX buffer (serialWriteBuf will block, but for jumbo frames we don't care)
292 // b) Response fits into TX buffer
293 const int totalFrameLength = hdrLen + dataLen + crcLen;
294 if (!isSerialTransmitBufferEmpty(port) && ((int)serialTxBytesFree(port) < totalFrameLength))
295 return 0;
297 // Transmit frame
298 serialBeginWrite(port);
299 serialWriteBuf(port, hdr, hdrLen);
300 serialWriteBuf(port, data, dataLen);
301 serialWriteBuf(port, crc, crcLen);
302 serialEndWrite(port);
304 return totalFrameLength;
307 static int mspSerialEncode(mspPort_t *msp, mspPacket_t *packet, mspVersion_e mspVersion)
309 static const uint8_t mspMagic[MSP_VERSION_COUNT] = MSP_VERSION_MAGIC_INITIALIZER;
310 const int dataLen = sbufBytesRemaining(&packet->buf);
311 uint8_t hdrBuf[16] = { '$', mspMagic[mspVersion], packet->result == MSP_RESULT_ERROR ? '!' : '>'};
312 uint8_t crcBuf[2];
313 int hdrLen = 3;
314 int crcLen = 0;
316 #define V1_CHECKSUM_STARTPOS 3
317 if (mspVersion == MSP_V1) {
318 mspHeaderV1_t * hdrV1 = (mspHeaderV1_t *)&hdrBuf[hdrLen];
319 hdrLen += sizeof(mspHeaderV1_t);
320 hdrV1->cmd = packet->cmd;
322 // Add JUMBO-frame header if necessary
323 if (dataLen >= JUMBO_FRAME_SIZE_LIMIT) {
324 mspHeaderJUMBO_t * hdrJUMBO = (mspHeaderJUMBO_t *)&hdrBuf[hdrLen];
325 hdrLen += sizeof(mspHeaderJUMBO_t);
327 hdrV1->size = JUMBO_FRAME_SIZE_LIMIT;
328 hdrJUMBO->size = dataLen;
330 else {
331 hdrV1->size = dataLen;
334 // Pre-calculate CRC
335 crcBuf[crcLen] = mspSerialChecksumBuf(0, hdrBuf + V1_CHECKSUM_STARTPOS, hdrLen - V1_CHECKSUM_STARTPOS);
336 crcBuf[crcLen] = mspSerialChecksumBuf(crcBuf[crcLen], sbufPtr(&packet->buf), dataLen);
337 crcLen++;
339 else if (mspVersion == MSP_V2_OVER_V1) {
340 mspHeaderV1_t * hdrV1 = (mspHeaderV1_t *)&hdrBuf[hdrLen];
342 hdrLen += sizeof(mspHeaderV1_t);
344 mspHeaderV2_t * hdrV2 = (mspHeaderV2_t *)&hdrBuf[hdrLen];
345 hdrLen += sizeof(mspHeaderV2_t);
347 const int v1PayloadSize = sizeof(mspHeaderV2_t) + dataLen + 1; // MSPv2 header + data payload + MSPv2 checksum
348 hdrV1->cmd = MSP_V2_FRAME_ID;
350 // Add JUMBO-frame header if necessary
351 if (v1PayloadSize >= JUMBO_FRAME_SIZE_LIMIT) {
352 mspHeaderJUMBO_t * hdrJUMBO = (mspHeaderJUMBO_t *)&hdrBuf[hdrLen];
353 hdrLen += sizeof(mspHeaderJUMBO_t);
355 hdrV1->size = JUMBO_FRAME_SIZE_LIMIT;
356 hdrJUMBO->size = v1PayloadSize;
358 else {
359 hdrV1->size = v1PayloadSize;
362 // Fill V2 header
363 hdrV2->flags = packet->flags;
364 hdrV2->cmd = packet->cmd;
365 hdrV2->size = dataLen;
367 // V2 CRC: only V2 header + data payload
368 crcBuf[crcLen] = crc8_dvb_s2_update(0, (uint8_t *)hdrV2, sizeof(mspHeaderV2_t));
369 crcBuf[crcLen] = crc8_dvb_s2_update(crcBuf[crcLen], sbufPtr(&packet->buf), dataLen);
370 crcLen++;
372 // V1 CRC: All headers + data payload + V2 CRC byte
373 crcBuf[crcLen] = mspSerialChecksumBuf(0, hdrBuf + V1_CHECKSUM_STARTPOS, hdrLen - V1_CHECKSUM_STARTPOS);
374 crcBuf[crcLen] = mspSerialChecksumBuf(crcBuf[crcLen], sbufPtr(&packet->buf), dataLen);
375 crcBuf[crcLen] = mspSerialChecksumBuf(crcBuf[crcLen], crcBuf, crcLen);
376 crcLen++;
378 else if (mspVersion == MSP_V2_NATIVE) {
379 mspHeaderV2_t * hdrV2 = (mspHeaderV2_t *)&hdrBuf[hdrLen];
380 hdrLen += sizeof(mspHeaderV2_t);
382 hdrV2->flags = packet->flags;
383 hdrV2->cmd = packet->cmd;
384 hdrV2->size = dataLen;
386 crcBuf[crcLen] = crc8_dvb_s2_update(0, (uint8_t *)hdrV2, sizeof(mspHeaderV2_t));
387 crcBuf[crcLen] = crc8_dvb_s2_update(crcBuf[crcLen], sbufPtr(&packet->buf), dataLen);
388 crcLen++;
390 else {
391 // Shouldn't get here
392 return 0;
395 // Send the frame
396 return mspSerialSendFrame(msp, hdrBuf, hdrLen, sbufPtr(&packet->buf), dataLen, crcBuf, crcLen);
399 static mspPostProcessFnPtr mspSerialProcessReceivedCommand(mspPort_t *msp, mspProcessCommandFnPtr mspProcessCommandFn)
401 uint8_t outBuf[MSP_PORT_OUTBUF_SIZE];
403 mspPacket_t reply = {
404 .buf = { .ptr = outBuf, .end = ARRAYEND(outBuf), },
405 .cmd = -1,
406 .flags = 0,
407 .result = 0,
409 uint8_t *outBufHead = reply.buf.ptr;
411 mspPacket_t command = {
412 .buf = { .ptr = msp->inBuf, .end = msp->inBuf + msp->dataSize, },
413 .cmd = msp->cmdMSP,
414 .flags = msp->cmdFlags,
415 .result = 0,
418 mspPostProcessFnPtr mspPostProcessFn = NULL;
419 const mspResult_e status = mspProcessCommandFn(&command, &reply, &mspPostProcessFn);
421 if (status != MSP_RESULT_NO_REPLY) {
422 sbufSwitchToReader(&reply.buf, outBufHead); // change streambuf direction
423 mspSerialEncode(msp, &reply, msp->mspVersion);
426 msp->c_state = MSP_IDLE;
427 return mspPostProcessFn;
430 static void mspEvaluateNonMspData(mspPort_t * mspPort, uint8_t receivedChar)
432 if (receivedChar == '#') {
433 mspPort->pendingRequest = MSP_PENDING_CLI;
434 return;
437 if (receivedChar == serialConfig()->reboot_character) {
438 mspPort->pendingRequest = MSP_PENDING_BOOTLOADER;
439 return;
443 static void mspProcessPendingRequest(mspPort_t * mspPort)
445 // If no request is pending or 100ms guard time has not elapsed - do nothing
446 if ((mspPort->pendingRequest == MSP_PENDING_NONE) || (millis() - mspPort->lastActivityMs < 100)) {
447 return;
450 switch(mspPort->pendingRequest) {
451 case MSP_PENDING_BOOTLOADER:
452 systemResetToBootloader();
453 break;
455 case MSP_PENDING_CLI:
456 if (!cliMode) {
457 // When we enter CLI mode - disable this MSP port. Don't care about preserving the port since CLI can only be exited via reboot
458 cliEnter(mspPort->port);
459 mspPort->port = NULL;
461 break;
463 default:
464 break;
468 void mspSerialProcessOnePort(mspPort_t * const mspPort, mspEvaluateNonMspData_e evaluateNonMspData, mspProcessCommandFnPtr mspProcessCommandFn)
470 mspPostProcessFnPtr mspPostProcessFn = NULL;
472 if (serialRxBytesWaiting(mspPort->port)) {
473 // There are bytes incoming - abort pending request
474 mspPort->lastActivityMs = millis();
475 mspPort->pendingRequest = MSP_PENDING_NONE;
477 // Process incoming bytes
478 while (serialRxBytesWaiting(mspPort->port)) {
479 const uint8_t c = serialRead(mspPort->port);
480 const bool consumed = mspSerialProcessReceivedData(mspPort, c);
482 //SD(fprintf(stderr, "[MSP]: received char: %02x (%c) state: %i\n", c, isprint(c) ? c : '.', mspPort->c_state));
483 if (!consumed && evaluateNonMspData == MSP_EVALUATE_NON_MSP_DATA) {
484 mspEvaluateNonMspData(mspPort, c);
487 if (mspPort->c_state == MSP_COMMAND_RECEIVED) {
488 mspPostProcessFn = mspSerialProcessReceivedCommand(mspPort, mspProcessCommandFn);
489 break; // process one command at a time so as not to block.
493 if (mspPostProcessFn) {
494 waitForSerialPortToFinishTransmitting(mspPort->port);
495 mspPostProcessFn(mspPort->port);
498 else {
499 mspProcessPendingRequest(mspPort);
504 * Process MSP commands from serial ports configured as MSP ports.
506 * Called periodically by the scheduler.
508 void mspSerialProcess(mspEvaluateNonMspData_e evaluateNonMspData, mspProcessCommandFnPtr mspProcessCommandFn)
510 for (uint8_t portIndex = 0; portIndex < MAX_MSP_PORT_COUNT; portIndex++) {
511 mspPort_t * const mspPort = &mspPorts[portIndex];
512 if (mspPort->port) {
513 mspSerialProcessOnePort(mspPort, evaluateNonMspData, mspProcessCommandFn);
518 void mspSerialInit(void)
520 memset(mspPorts, 0, sizeof(mspPorts));
521 mspSerialAllocatePorts();
524 int mspSerialPushPort(uint16_t cmd, const uint8_t *data, int datalen, mspPort_t *mspPort, mspVersion_e version)
526 uint8_t pushBuf[MSP_PORT_OUTBUF_SIZE];
528 mspPacket_t push = {
529 .buf = { .ptr = pushBuf, .end = ARRAYEND(pushBuf), },
530 .cmd = cmd,
531 .result = 0,
534 sbufWriteData(&push.buf, data, datalen);
536 sbufSwitchToReader(&push.buf, pushBuf);
538 return mspSerialEncode(mspPort, &push, version);
541 int mspSerialPushVersion(uint8_t cmd, const uint8_t *data, int datalen, mspVersion_e version)
543 int ret = 0;
545 for (int portIndex = 0; portIndex < MAX_MSP_PORT_COUNT; portIndex++) {
546 mspPort_t * const mspPort = &mspPorts[portIndex];
547 if (!mspPort->port) {
548 continue;
551 // Avoid unconnected ports (only VCP for now)
552 if (!serialIsConnected(mspPort->port)) {
553 continue;
556 ret = mspSerialPushPort(cmd, data, datalen, mspPort, version);
558 return ret; // return the number of bytes written
561 int mspSerialPush(uint8_t cmd, const uint8_t *data, int datalen)
563 return mspSerialPushVersion(cmd, data, datalen, MSP_V1);
566 uint32_t mspSerialTxBytesFree(serialPort_t *port)
568 return serialTxBytesFree(port);
571 mspPort_t * mspSerialPortFind(const serialPort_t *serialPort)
573 for (int portIndex = 0; portIndex < MAX_MSP_PORT_COUNT; portIndex++) {
574 mspPort_t * mspPort = &mspPorts[portIndex];
575 if (mspPort->port == serialPort) {
576 return mspPort;
579 return NULL;