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/>.
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"
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
];
63 serialPort_t
*serialPort
= openSerialPort(portConfig
->identifier
, FUNCTION_MSP
, NULL
, NULL
, baudRates
[portConfig
->msp_baudrateIndex
], MODE_RXTX
, SERIAL_NOT_INVERTED
);
65 resetMspPort(mspPort
, serialPort
);
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
) {
88 case MSP_IDLE
: // Waiting for '$' character
90 mspPort
->mspVersion
= MSP_V1
;
91 mspPort
->c_state
= MSP_HEADER_START
;
98 case MSP_HEADER_START
: // Waiting for 'M' (MSPv1 / MSPv2_over_v1) or 'X' (MSPv2 native)
101 mspPort
->c_state
= MSP_HEADER_M
;
104 mspPort
->c_state
= MSP_HEADER_X
;
107 mspPort
->c_state
= MSP_IDLE
;
112 case MSP_HEADER_M
: // Waiting for '<'
115 mspPort
->checksum1
= 0;
116 mspPort
->checksum2
= 0;
117 mspPort
->c_state
= MSP_HEADER_V1
;
120 mspPort
->c_state
= MSP_IDLE
;
127 mspPort
->checksum2
= 0;
128 mspPort
->mspVersion
= MSP_V2_NATIVE
;
129 mspPort
->c_state
= MSP_HEADER_V2_NATIVE
;
132 mspPort
->c_state
= MSP_IDLE
;
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
;
152 mspPort
->c_state
= MSP_IDLE
;
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
166 mspPort
->inBuf
[mspPort
->offset
++] = c
;
167 mspPort
->checksum1
^= c
;
168 if (mspPort
->offset
== mspPort
->dataSize
) {
169 mspPort
->c_state
= MSP_CHECKSUM_V1
;
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"));
178 mspPort
->c_state
= MSP_IDLE
;
179 SD(fprintf(stderr
, "[MSPV1] Checksum error!\n"));
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
;
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
;
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
;
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
219 mspPort
->c_state
= MSP_IDLE
;
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"));
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
;
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
;
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"));
258 SD(fprintf(stderr
, "[MSPV2] Checksum error!\n"));
259 mspPort
->c_state
= MSP_IDLE
;
267 static uint8_t mspSerialChecksumBuf(uint8_t checksum
, const uint8_t *data
, int len
)
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
;
284 // VSP MSP port might be unconnected. To prevent blocking - check if it's connected first
285 if (!serialIsConnected(port
)) {
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
))
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
? '!' : '>'};
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
;
331 hdrV1
->size
= dataLen
;
335 crcBuf
[crcLen
] = mspSerialChecksumBuf(0, hdrBuf
+ V1_CHECKSUM_STARTPOS
, hdrLen
- V1_CHECKSUM_STARTPOS
);
336 crcBuf
[crcLen
] = mspSerialChecksumBuf(crcBuf
[crcLen
], sbufPtr(&packet
->buf
), dataLen
);
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
;
359 hdrV1
->size
= v1PayloadSize
;
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
);
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
);
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
);
391 // Shouldn't get here
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
), },
409 uint8_t *outBufHead
= reply
.buf
.ptr
;
411 mspPacket_t command
= {
412 .buf
= { .ptr
= msp
->inBuf
, .end
= msp
->inBuf
+ msp
->dataSize
, },
414 .flags
= msp
->cmdFlags
,
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
;
437 if (receivedChar
== serialConfig()->reboot_character
) {
438 mspPort
->pendingRequest
= MSP_PENDING_BOOTLOADER
;
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)) {
450 switch(mspPort
->pendingRequest
) {
451 case MSP_PENDING_BOOTLOADER
:
452 systemResetToBootloader();
455 case MSP_PENDING_CLI
:
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
;
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
);
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
];
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
];
529 .buf
= { .ptr
= pushBuf
, .end
= ARRAYEND(pushBuf
), },
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
)
545 for (int portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
546 mspPort_t
* const mspPort
= &mspPorts
[portIndex
];
547 if (!mspPort
->port
) {
551 // Avoid unconnected ports (only VCP for now)
552 if (!serialIsConnected(mspPort
->port
)) {
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
) {