2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
27 #include "build/debug.h"
31 #include "common/streambuf.h"
32 #include "common/utils.h"
33 #include "common/crc.h"
35 #include "drivers/system.h"
37 #include "io/displayport_msp.h"
41 #include "msp_serial.h"
45 static mspPort_t mspPorts
[MAX_MSP_PORT_COUNT
];
47 static void resetMspPort(mspPort_t
*mspPortToReset
, serialPort_t
*serialPort
, bool sharedWithTelemetry
)
49 memset(mspPortToReset
, 0, sizeof(mspPort_t
));
51 mspPortToReset
->port
= serialPort
;
52 mspPortToReset
->sharedWithTelemetry
= sharedWithTelemetry
;
53 mspPortToReset
->descriptor
= mspDescriptorAlloc();
56 void mspSerialAllocatePorts(void)
58 uint8_t portIndex
= 0;
59 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_MSP
);
60 while (portConfig
&& portIndex
< MAX_MSP_PORT_COUNT
) {
61 mspPort_t
*mspPort
= &mspPorts
[portIndex
];
68 portOptions_e options
= SERIAL_NOT_INVERTED
;
70 if (mspConfig()->halfDuplex
) {
71 options
|= SERIAL_BIDIR
;
74 serialPort_t
*serialPort
= openSerialPort(portConfig
->identifier
, FUNCTION_MSP
, NULL
, NULL
, baudRates
[portConfig
->msp_baudrateIndex
], MODE_RXTX
, options
);
76 bool sharedWithTelemetry
= isSerialPortShared(portConfig
, FUNCTION_MSP
, TELEMETRY_PORT_FUNCTIONS_MASK
);
77 resetMspPort(mspPort
, serialPort
, sharedWithTelemetry
);
82 portConfig
= findNextSerialPortConfig(FUNCTION_MSP
);
86 void mspSerialReleasePortIfAllocated(serialPort_t
*serialPort
)
88 for (uint8_t portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
89 mspPort_t
*candidateMspPort
= &mspPorts
[portIndex
];
90 if (candidateMspPort
->port
== serialPort
) {
91 closeSerialPort(serialPort
);
92 memset(candidateMspPort
, 0, sizeof(mspPort_t
));
97 mspDescriptor_t
getMspSerialPortDescriptor(const uint8_t portIdentifier
)
99 for (uint8_t portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
100 mspPort_t
*candidateMspPort
= &mspPorts
[portIndex
];
101 if (candidateMspPort
->port
->identifier
== portIdentifier
) {
102 return candidateMspPort
->descriptor
;
108 #if defined(USE_TELEMETRY)
109 void mspSerialReleaseSharedTelemetryPorts(void)
111 for (uint8_t portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
112 mspPort_t
*candidateMspPort
= &mspPorts
[portIndex
];
113 if (candidateMspPort
->sharedWithTelemetry
) {
114 closeSerialPort(candidateMspPort
->port
);
115 memset(candidateMspPort
, 0, sizeof(mspPort_t
));
121 static bool mspSerialProcessReceivedData(mspPort_t
*mspPort
, uint8_t c
)
123 switch (mspPort
->c_state
) {
125 case MSP_IDLE
: // Waiting for '$' character
127 mspPort
->c_state
= MSP_HEADER_START
;
133 case MSP_HEADER_START
: // Waiting for 'M' (MSPv1 / MSPv2_over_v1) or 'X' (MSPv2 native)
135 mspPort
->checksum1
= 0;
136 mspPort
->checksum2
= 0;
139 mspPort
->c_state
= MSP_HEADER_M
;
140 mspPort
->mspVersion
= MSP_V1
;
143 mspPort
->c_state
= MSP_HEADER_X
;
144 mspPort
->mspVersion
= MSP_V2_NATIVE
;
147 mspPort
->c_state
= MSP_IDLE
;
152 case MSP_HEADER_M
: // Waiting for '<' / '>'
153 mspPort
->c_state
= MSP_HEADER_V1
;
156 mspPort
->packetType
= MSP_PACKET_COMMAND
;
159 mspPort
->packetType
= MSP_PACKET_REPLY
;
162 mspPort
->c_state
= MSP_IDLE
;
168 mspPort
->c_state
= MSP_HEADER_V2_NATIVE
;
171 mspPort
->packetType
= MSP_PACKET_COMMAND
;
174 mspPort
->packetType
= MSP_PACKET_REPLY
;
177 mspPort
->c_state
= MSP_IDLE
;
182 case MSP_HEADER_V1
: // Now receive v1 header (size/cmd), this is already checksummable
183 mspPort
->inBuf
[mspPort
->offset
++] = c
;
184 mspPort
->checksum1
^= c
;
185 if (mspPort
->offset
== sizeof(mspHeaderV1_t
)) {
186 mspHeaderV1_t
* hdr
= (mspHeaderV1_t
*)&mspPort
->inBuf
[0];
187 // Check incoming buffer size limit
188 if (hdr
->size
> MSP_PORT_INBUF_SIZE
) {
189 mspPort
->c_state
= MSP_IDLE
;
191 else if (hdr
->cmd
== MSP_V2_FRAME_ID
) {
192 // MSPv1 payload must be big enough to hold V2 header + extra checksum
193 if (hdr
->size
>= sizeof(mspHeaderV2_t
) + 1) {
194 mspPort
->mspVersion
= MSP_V2_OVER_V1
;
195 mspPort
->c_state
= MSP_HEADER_V2_OVER_V1
;
197 mspPort
->c_state
= MSP_IDLE
;
200 mspPort
->dataSize
= hdr
->size
;
201 mspPort
->cmdMSP
= hdr
->cmd
;
202 mspPort
->cmdFlags
= 0;
203 mspPort
->offset
= 0; // re-use buffer
204 mspPort
->c_state
= mspPort
->dataSize
> 0 ? MSP_PAYLOAD_V1
: MSP_CHECKSUM_V1
; // If no payload - jump to checksum byte
210 mspPort
->inBuf
[mspPort
->offset
++] = c
;
211 mspPort
->checksum1
^= c
;
212 if (mspPort
->offset
== mspPort
->dataSize
) {
213 mspPort
->c_state
= MSP_CHECKSUM_V1
;
217 case MSP_CHECKSUM_V1
:
218 if (mspPort
->checksum1
== c
) {
219 mspPort
->c_state
= MSP_COMMAND_RECEIVED
;
221 mspPort
->c_state
= MSP_IDLE
;
225 case MSP_HEADER_V2_OVER_V1
: // V2 header is part of V1 payload - we need to calculate both checksums now
226 mspPort
->inBuf
[mspPort
->offset
++] = c
;
227 mspPort
->checksum1
^= c
;
228 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
229 if (mspPort
->offset
== (sizeof(mspHeaderV2_t
) + sizeof(mspHeaderV1_t
))) {
230 mspHeaderV2_t
* hdrv2
= (mspHeaderV2_t
*)&mspPort
->inBuf
[sizeof(mspHeaderV1_t
)];
231 if (hdrv2
->size
> MSP_PORT_INBUF_SIZE
) {
232 mspPort
->c_state
= MSP_IDLE
;
234 mspPort
->dataSize
= hdrv2
->size
;
235 mspPort
->cmdMSP
= hdrv2
->cmd
;
236 mspPort
->cmdFlags
= hdrv2
->flags
;
237 mspPort
->offset
= 0; // re-use buffer
238 mspPort
->c_state
= mspPort
->dataSize
> 0 ? MSP_PAYLOAD_V2_OVER_V1
: MSP_CHECKSUM_V2_OVER_V1
;
243 case MSP_PAYLOAD_V2_OVER_V1
:
244 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
245 mspPort
->checksum1
^= c
;
246 mspPort
->inBuf
[mspPort
->offset
++] = c
;
248 if (mspPort
->offset
== mspPort
->dataSize
) {
249 mspPort
->c_state
= MSP_CHECKSUM_V2_OVER_V1
;
253 case MSP_CHECKSUM_V2_OVER_V1
:
254 mspPort
->checksum1
^= c
;
255 if (mspPort
->checksum2
== c
) {
256 mspPort
->c_state
= MSP_CHECKSUM_V1
; // Checksum 2 correct - verify v1 checksum
258 mspPort
->c_state
= MSP_IDLE
;
262 case MSP_HEADER_V2_NATIVE
:
263 mspPort
->inBuf
[mspPort
->offset
++] = c
;
264 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
265 if (mspPort
->offset
== sizeof(mspHeaderV2_t
)) {
266 mspHeaderV2_t
* hdrv2
= (mspHeaderV2_t
*)&mspPort
->inBuf
[0];
267 mspPort
->dataSize
= hdrv2
->size
;
268 mspPort
->cmdMSP
= hdrv2
->cmd
;
269 mspPort
->cmdFlags
= hdrv2
->flags
;
270 mspPort
->offset
= 0; // re-use buffer
271 mspPort
->c_state
= mspPort
->dataSize
> 0 ? MSP_PAYLOAD_V2_NATIVE
: MSP_CHECKSUM_V2_NATIVE
;
275 case MSP_PAYLOAD_V2_NATIVE
:
276 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
277 mspPort
->inBuf
[mspPort
->offset
++] = c
;
279 if (mspPort
->offset
== mspPort
->dataSize
) {
280 mspPort
->c_state
= MSP_CHECKSUM_V2_NATIVE
;
284 case MSP_CHECKSUM_V2_NATIVE
:
285 if (mspPort
->checksum2
== c
) {
286 mspPort
->c_state
= MSP_COMMAND_RECEIVED
;
288 mspPort
->c_state
= MSP_IDLE
;
296 static uint8_t mspSerialChecksumBuf(uint8_t checksum
, const uint8_t *data
, int len
)
304 #define JUMBO_FRAME_SIZE_LIMIT 255
305 static int mspSerialSendFrame(mspPort_t
*msp
, const uint8_t * hdr
, int hdrLen
, const uint8_t * data
, int dataLen
, const uint8_t * crc
, int crcLen
)
307 // We are allowed to send out the response if
308 // a) TX buffer is completely empty (we are talking to well-behaving party that follows request-response scheduling;
309 // this allows us to transmit jumbo frames bigger than TX buffer (serialWriteBuf will block, but for jumbo frames we don't care)
310 // b) Response fits into TX buffer
311 const int totalFrameLength
= hdrLen
+ dataLen
+ crcLen
;
312 if (!isSerialTransmitBufferEmpty(msp
->port
) && ((int)serialTxBytesFree(msp
->port
) < totalFrameLength
)) {
317 serialBeginWrite(msp
->port
);
318 serialWriteBuf(msp
->port
, hdr
, hdrLen
);
319 serialWriteBuf(msp
->port
, data
, dataLen
);
320 serialWriteBuf(msp
->port
, crc
, crcLen
);
321 serialEndWrite(msp
->port
);
323 return totalFrameLength
;
326 static int mspSerialEncode(mspPort_t
*msp
, mspPacket_t
*packet
, mspVersion_e mspVersion
)
328 static const uint8_t mspMagic
[MSP_VERSION_COUNT
] = MSP_VERSION_MAGIC_INITIALIZER
;
329 const int dataLen
= sbufBytesRemaining(&packet
->buf
);
330 uint8_t hdrBuf
[16] = { '$', mspMagic
[mspVersion
], packet
->result
== MSP_RESULT_ERROR
? '!' : '>'};
336 #define V1_CHECKSUM_STARTPOS 3
337 if (mspVersion
== MSP_V1
) {
338 mspHeaderV1_t
* hdrV1
= (mspHeaderV1_t
*)&hdrBuf
[hdrLen
];
339 hdrLen
+= sizeof(mspHeaderV1_t
);
340 hdrV1
->cmd
= packet
->cmd
;
342 // Add JUMBO-frame header if necessary
343 if (dataLen
>= JUMBO_FRAME_SIZE_LIMIT
) {
344 mspHeaderJUMBO_t
* hdrJUMBO
= (mspHeaderJUMBO_t
*)&hdrBuf
[hdrLen
];
345 hdrLen
+= sizeof(mspHeaderJUMBO_t
);
347 hdrV1
->size
= JUMBO_FRAME_SIZE_LIMIT
;
348 hdrJUMBO
->size
= dataLen
;
350 hdrV1
->size
= dataLen
;
354 checksum
= mspSerialChecksumBuf(0, hdrBuf
+ V1_CHECKSUM_STARTPOS
, hdrLen
- V1_CHECKSUM_STARTPOS
);
355 checksum
= mspSerialChecksumBuf(checksum
, sbufPtr(&packet
->buf
), dataLen
);
356 crcBuf
[crcLen
++] = checksum
;
357 } else if (mspVersion
== MSP_V2_OVER_V1
) {
358 mspHeaderV1_t
* hdrV1
= (mspHeaderV1_t
*)&hdrBuf
[hdrLen
];
360 hdrLen
+= sizeof(mspHeaderV1_t
);
362 mspHeaderV2_t
* hdrV2
= (mspHeaderV2_t
*)&hdrBuf
[hdrLen
];
363 hdrLen
+= sizeof(mspHeaderV2_t
);
365 const int v1PayloadSize
= sizeof(mspHeaderV2_t
) + dataLen
+ 1; // MSPv2 header + data payload + MSPv2 checksum
366 hdrV1
->cmd
= MSP_V2_FRAME_ID
;
368 // Add JUMBO-frame header if necessary
369 if (v1PayloadSize
>= JUMBO_FRAME_SIZE_LIMIT
) {
370 mspHeaderJUMBO_t
* hdrJUMBO
= (mspHeaderJUMBO_t
*)&hdrBuf
[hdrLen
];
371 hdrLen
+= sizeof(mspHeaderJUMBO_t
);
373 hdrV1
->size
= JUMBO_FRAME_SIZE_LIMIT
;
374 hdrJUMBO
->size
= v1PayloadSize
;
376 hdrV1
->size
= v1PayloadSize
;
380 hdrV2
->flags
= packet
->flags
;
381 hdrV2
->cmd
= packet
->cmd
;
382 hdrV2
->size
= dataLen
;
384 // V2 CRC: only V2 header + data payload
385 checksum
= crc8_dvb_s2_update(0, (uint8_t *)hdrV2
, sizeof(mspHeaderV2_t
));
386 checksum
= crc8_dvb_s2_update(checksum
, sbufPtr(&packet
->buf
), dataLen
);
387 crcBuf
[crcLen
++] = checksum
;
389 // V1 CRC: All headers + data payload + V2 CRC byte
390 checksum
= mspSerialChecksumBuf(0, hdrBuf
+ V1_CHECKSUM_STARTPOS
, hdrLen
- V1_CHECKSUM_STARTPOS
);
391 checksum
= mspSerialChecksumBuf(checksum
, sbufPtr(&packet
->buf
), dataLen
);
392 checksum
= mspSerialChecksumBuf(checksum
, crcBuf
, crcLen
);
393 crcBuf
[crcLen
++] = checksum
;
394 } else if (mspVersion
== MSP_V2_NATIVE
) {
395 mspHeaderV2_t
* hdrV2
= (mspHeaderV2_t
*)&hdrBuf
[hdrLen
];
396 hdrLen
+= sizeof(mspHeaderV2_t
);
398 hdrV2
->flags
= packet
->flags
;
399 hdrV2
->cmd
= packet
->cmd
;
400 hdrV2
->size
= dataLen
;
402 checksum
= crc8_dvb_s2_update(0, (uint8_t *)hdrV2
, sizeof(mspHeaderV2_t
));
403 checksum
= crc8_dvb_s2_update(checksum
, sbufPtr(&packet
->buf
), dataLen
);
404 crcBuf
[crcLen
++] = checksum
;
406 // Shouldn't get here
411 return mspSerialSendFrame(msp
, hdrBuf
, hdrLen
, sbufPtr(&packet
->buf
), dataLen
, crcBuf
, crcLen
);
414 static mspPostProcessFnPtr
mspSerialProcessReceivedCommand(mspPort_t
*msp
, mspProcessCommandFnPtr mspProcessCommandFn
)
416 static uint8_t mspSerialOutBuf
[MSP_PORT_OUTBUF_SIZE
];
418 mspPacket_t reply
= {
419 .buf
= { .ptr
= mspSerialOutBuf
, .end
= ARRAYEND(mspSerialOutBuf
), },
423 .direction
= MSP_DIRECTION_REPLY
,
425 uint8_t *outBufHead
= reply
.buf
.ptr
;
427 mspPacket_t command
= {
428 .buf
= { .ptr
= msp
->inBuf
, .end
= msp
->inBuf
+ msp
->dataSize
, },
430 .flags
= msp
->cmdFlags
,
432 .direction
= MSP_DIRECTION_REQUEST
,
435 mspPostProcessFnPtr mspPostProcessFn
= NULL
;
436 const mspResult_e status
= mspProcessCommandFn(msp
->descriptor
, &command
, &reply
, &mspPostProcessFn
);
438 if (status
!= MSP_RESULT_NO_REPLY
) {
439 sbufSwitchToReader(&reply
.buf
, outBufHead
); // change streambuf direction
440 mspSerialEncode(msp
, &reply
, msp
->mspVersion
);
443 return mspPostProcessFn
;
446 static void mspEvaluateNonMspData(mspPort_t
* mspPort
, uint8_t receivedChar
)
448 if (receivedChar
== serialConfig()->reboot_character
) {
449 mspPort
->pendingRequest
= MSP_PENDING_BOOTLOADER_ROM
;
451 } else if (receivedChar
== '#') {
452 mspPort
->pendingRequest
= MSP_PENDING_CLI
;
454 #if defined(USE_FLASH_BOOT_LOADER)
455 } else if (receivedChar
== 'F') {
456 mspPort
->pendingRequest
= MSP_PENDING_BOOTLOADER_FLASH
;
461 static void mspProcessPendingRequest(mspPort_t
* mspPort
)
463 // If no request is pending or 100ms guard time has not elapsed - do nothing
464 if ((mspPort
->pendingRequest
== MSP_PENDING_NONE
) || (millis() - mspPort
->lastActivityMs
< 100)) {
468 switch(mspPort
->pendingRequest
) {
469 case MSP_PENDING_BOOTLOADER_ROM
:
470 systemResetToBootloader(BOOTLOADER_REQUEST_ROM
);
473 #if defined(USE_FLASH_BOOT_LOADER)
474 case MSP_PENDING_BOOTLOADER_FLASH
:
475 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH
);
480 case MSP_PENDING_CLI
:
481 cliEnter(mspPort
->port
);
490 static void mspSerialProcessReceivedReply(mspPort_t
*msp
, mspProcessReplyFnPtr mspProcessReplyFn
)
492 mspPacket_t reply
= {
495 .end
= msp
->inBuf
+ msp
->dataSize
,
501 mspProcessReplyFn(&reply
);
503 msp
->c_state
= MSP_IDLE
;
507 * Process MSP commands from serial ports configured as MSP ports.
509 * Called periodically by the scheduler.
511 void mspSerialProcess(mspEvaluateNonMspData_e evaluateNonMspData
, mspProcessCommandFnPtr mspProcessCommandFn
, mspProcessReplyFnPtr mspProcessReplyFn
)
513 for (uint8_t portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
514 mspPort_t
* const mspPort
= &mspPorts
[portIndex
];
515 if (!mspPort
->port
) {
519 mspPostProcessFnPtr mspPostProcessFn
= NULL
;
521 if (serialRxBytesWaiting(mspPort
->port
)) {
522 // There are bytes incoming - abort pending request
523 mspPort
->lastActivityMs
= millis();
524 mspPort
->pendingRequest
= MSP_PENDING_NONE
;
526 while (serialRxBytesWaiting(mspPort
->port
)) {
527 const uint8_t c
= serialRead(mspPort
->port
);
528 const bool consumed
= mspSerialProcessReceivedData(mspPort
, c
);
530 if (!consumed
&& evaluateNonMspData
== MSP_EVALUATE_NON_MSP_DATA
) {
531 mspEvaluateNonMspData(mspPort
, c
);
534 if (mspPort
->c_state
== MSP_COMMAND_RECEIVED
) {
535 if (mspPort
->packetType
== MSP_PACKET_COMMAND
) {
536 mspPostProcessFn
= mspSerialProcessReceivedCommand(mspPort
, mspProcessCommandFn
);
537 } else if (mspPort
->packetType
== MSP_PACKET_REPLY
) {
538 mspSerialProcessReceivedReply(mspPort
, mspProcessReplyFn
);
541 mspPort
->c_state
= MSP_IDLE
;
542 break; // process one command at a time so as not to block.
546 if (mspPostProcessFn
) {
547 waitForSerialPortToFinishTransmitting(mspPort
->port
);
548 mspPostProcessFn(mspPort
->port
);
551 mspProcessPendingRequest(mspPort
);
556 bool mspSerialWaiting(void)
558 for (uint8_t portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
559 mspPort_t
* const mspPort
= &mspPorts
[portIndex
];
560 if (!mspPort
->port
) {
564 if (serialRxBytesWaiting(mspPort
->port
)) {
571 void mspSerialInit(void)
573 memset(mspPorts
, 0, sizeof(mspPorts
));
574 mspSerialAllocatePorts();
577 int mspSerialPush(serialPortIdentifier_e port
, uint8_t cmd
, uint8_t *data
, int datalen
, mspDirection_e direction
, mspVersion_e mspVersion
)
581 for (int portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
582 mspPort_t
* const mspPort
= &mspPorts
[portIndex
];
584 // XXX Kludge!!! Avoid zombie VCP port (avoid VCP entirely for now)
586 #ifndef USE_MSP_PUSH_OVER_VCP
587 || mspPort
->port
->identifier
== SERIAL_PORT_USB_VCP
589 || (port
!= SERIAL_PORT_ALL
&& mspPort
->port
->identifier
!= port
)) {
594 .buf
= { .ptr
= data
, .end
= data
+ datalen
, },
597 .direction
= direction
,
600 ret
= mspSerialEncode(mspPort
, &push
, mspVersion
);
602 return ret
; // return the number of bytes written
606 uint32_t mspSerialTxBytesFree(void)
608 uint32_t ret
= UINT32_MAX
;
610 for (int portIndex
= 0; portIndex
< MAX_MSP_PORT_COUNT
; portIndex
++) {
611 mspPort_t
* const mspPort
= &mspPorts
[portIndex
];
612 if (!mspPort
->port
) {
616 // XXX Kludge!!! Avoid zombie VCP port (avoid VCP entirely for now)
617 if (mspPort
->port
->identifier
== SERIAL_PORT_USB_VCP
) {
621 const uint32_t bytesFree
= serialTxBytesFree(mspPort
->port
);
622 if (bytesFree
< ret
) {