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
< ARRAYLEN(mspPorts
)) {
61 mspPort_t
*mspPort
= &mspPorts
[portIndex
];
68 portOptions_e options
= SERIAL_NOT_INVERTED
;
70 if (mspConfig()->halfDuplex
) {
71 options
|= SERIAL_BIDIR
;
72 } else if (serialType(portConfig
->identifier
) == SERIALTYPE_UART
73 || serialType(portConfig
->identifier
) == SERIALTYPE_LPUART
) {
74 // TODO: SERIAL_CHECK_TX is broken on F7, disable it until it is fixed
75 #if !defined(STM32F7) || defined(USE_F7_CHECK_TX)
76 options
|= SERIAL_CHECK_TX
;
80 serialPort_t
*serialPort
= openSerialPort(portConfig
->identifier
, FUNCTION_MSP
, NULL
, NULL
, baudRates
[portConfig
->msp_baudrateIndex
], MODE_RXTX
, options
);
82 bool sharedWithTelemetry
= isSerialPortShared(portConfig
, FUNCTION_MSP
, TELEMETRY_PORT_FUNCTIONS_MASK
);
83 resetMspPort(mspPort
, serialPort
, sharedWithTelemetry
);
88 portConfig
= findNextSerialPortConfig(FUNCTION_MSP
);
92 void mspSerialReleasePortIfAllocated(serialPort_t
*serialPort
)
94 for (mspPort_t
*candidateMspPort
= mspPorts
; candidateMspPort
< ARRAYEND(mspPorts
); candidateMspPort
++) {
95 if (candidateMspPort
->port
== serialPort
) {
96 closeSerialPort(serialPort
);
97 memset(candidateMspPort
, 0, sizeof(mspPort_t
));
102 mspDescriptor_t
getMspSerialPortDescriptor(const serialPortIdentifier_e portIdentifier
)
104 for (mspPort_t
*candidateMspPort
= mspPorts
; candidateMspPort
< ARRAYEND(mspPorts
); candidateMspPort
++) {
105 if (candidateMspPort
->port
&& candidateMspPort
->port
->identifier
== portIdentifier
) {
106 return candidateMspPort
->descriptor
;
112 #if defined(USE_TELEMETRY)
113 void mspSerialReleaseSharedTelemetryPorts(void)
115 for (mspPort_t
*candidateMspPort
= mspPorts
; candidateMspPort
< ARRAYEND(mspPorts
); candidateMspPort
++) {
116 if (candidateMspPort
->sharedWithTelemetry
) {
117 closeSerialPort(candidateMspPort
->port
);
118 memset(candidateMspPort
, 0, sizeof(mspPort_t
));
124 static void mspSerialProcessReceivedPacketData(mspPort_t
*mspPort
, uint8_t c
)
126 switch (mspPort
->packetState
) {
129 case MSP_HEADER_START
: // Waiting for 'M' (MSPv1 / MSPv2_over_v1) or 'X' (MSPv2 native)
131 mspPort
->checksum1
= 0;
132 mspPort
->checksum2
= 0;
135 mspPort
->packetState
= MSP_HEADER_M
;
136 mspPort
->mspVersion
= MSP_V1
;
139 mspPort
->packetState
= MSP_HEADER_X
;
140 mspPort
->mspVersion
= MSP_V2_NATIVE
;
143 mspPort
->packetState
= MSP_IDLE
;
148 case MSP_HEADER_M
: // Waiting for '<' / '>'
149 mspPort
->packetState
= MSP_HEADER_V1
;
152 mspPort
->packetType
= MSP_PACKET_COMMAND
;
155 mspPort
->packetType
= MSP_PACKET_REPLY
;
158 mspPort
->packetState
= MSP_IDLE
;
164 mspPort
->packetState
= MSP_HEADER_V2_NATIVE
;
167 mspPort
->packetType
= MSP_PACKET_COMMAND
;
170 mspPort
->packetType
= MSP_PACKET_REPLY
;
173 mspPort
->packetState
= MSP_IDLE
;
178 case MSP_HEADER_V1
: // Now receive v1 header (size/cmd), this is already checksummable
179 mspPort
->inBuf
[mspPort
->offset
++] = c
;
180 mspPort
->checksum1
^= c
;
181 if (mspPort
->offset
== sizeof(mspHeaderV1_t
)) {
182 mspHeaderV1_t
* hdr
= (mspHeaderV1_t
*)&mspPort
->inBuf
[0];
183 // Check incoming buffer size limit
184 if (hdr
->size
> MSP_PORT_INBUF_SIZE
) {
185 mspPort
->packetState
= MSP_IDLE
;
187 else if (hdr
->cmd
== MSP_V2_FRAME_ID
) {
188 // MSPv1 payload must be big enough to hold V2 header + extra checksum
189 if (hdr
->size
>= sizeof(mspHeaderV2_t
) + 1) {
190 mspPort
->mspVersion
= MSP_V2_OVER_V1
;
191 mspPort
->packetState
= MSP_HEADER_V2_OVER_V1
;
193 mspPort
->packetState
= MSP_IDLE
;
196 mspPort
->dataSize
= hdr
->size
;
197 mspPort
->cmdMSP
= hdr
->cmd
;
198 mspPort
->cmdFlags
= 0;
199 mspPort
->offset
= 0; // re-use buffer
200 mspPort
->packetState
= mspPort
->dataSize
> 0 ? MSP_PAYLOAD_V1
: MSP_CHECKSUM_V1
; // If no payload - jump to checksum byte
206 mspPort
->inBuf
[mspPort
->offset
++] = c
;
207 mspPort
->checksum1
^= c
;
208 if (mspPort
->offset
== mspPort
->dataSize
) {
209 mspPort
->packetState
= MSP_CHECKSUM_V1
;
213 case MSP_CHECKSUM_V1
:
214 if (mspPort
->checksum1
== c
) {
215 mspPort
->packetState
= MSP_COMMAND_RECEIVED
;
217 mspPort
->packetState
= MSP_IDLE
;
221 case MSP_HEADER_V2_OVER_V1
: // V2 header is part of V1 payload - we need to calculate both checksums now
222 mspPort
->inBuf
[mspPort
->offset
++] = c
;
223 mspPort
->checksum1
^= c
;
224 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
225 if (mspPort
->offset
== (sizeof(mspHeaderV2_t
) + sizeof(mspHeaderV1_t
))) {
226 mspHeaderV2_t
* hdrv2
= (mspHeaderV2_t
*)&mspPort
->inBuf
[sizeof(mspHeaderV1_t
)];
227 if (hdrv2
->size
> MSP_PORT_INBUF_SIZE
) {
228 mspPort
->packetState
= MSP_IDLE
;
230 mspPort
->dataSize
= hdrv2
->size
;
231 mspPort
->cmdMSP
= hdrv2
->cmd
;
232 mspPort
->cmdFlags
= hdrv2
->flags
;
233 mspPort
->offset
= 0; // re-use buffer
234 mspPort
->packetState
= mspPort
->dataSize
> 0 ? MSP_PAYLOAD_V2_OVER_V1
: MSP_CHECKSUM_V2_OVER_V1
;
239 case MSP_PAYLOAD_V2_OVER_V1
:
240 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
241 mspPort
->checksum1
^= c
;
242 mspPort
->inBuf
[mspPort
->offset
++] = c
;
244 if (mspPort
->offset
== mspPort
->dataSize
) {
245 mspPort
->packetState
= MSP_CHECKSUM_V2_OVER_V1
;
249 case MSP_CHECKSUM_V2_OVER_V1
:
250 mspPort
->checksum1
^= c
;
251 if (mspPort
->checksum2
== c
) {
252 mspPort
->packetState
= MSP_CHECKSUM_V1
; // Checksum 2 correct - verify v1 checksum
254 mspPort
->packetState
= MSP_IDLE
;
258 case MSP_HEADER_V2_NATIVE
:
259 mspPort
->inBuf
[mspPort
->offset
++] = c
;
260 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
261 if (mspPort
->offset
== sizeof(mspHeaderV2_t
)) {
262 mspHeaderV2_t
* hdrv2
= (mspHeaderV2_t
*)&mspPort
->inBuf
[0];
263 mspPort
->dataSize
= hdrv2
->size
;
264 mspPort
->cmdMSP
= hdrv2
->cmd
;
265 mspPort
->cmdFlags
= hdrv2
->flags
;
266 mspPort
->offset
= 0; // re-use buffer
267 mspPort
->packetState
= mspPort
->dataSize
> 0 ? MSP_PAYLOAD_V2_NATIVE
: MSP_CHECKSUM_V2_NATIVE
;
271 case MSP_PAYLOAD_V2_NATIVE
:
272 mspPort
->checksum2
= crc8_dvb_s2(mspPort
->checksum2
, c
);
273 mspPort
->inBuf
[mspPort
->offset
++] = c
;
275 if (mspPort
->offset
== mspPort
->dataSize
) {
276 mspPort
->packetState
= MSP_CHECKSUM_V2_NATIVE
;
280 case MSP_CHECKSUM_V2_NATIVE
:
281 if (mspPort
->checksum2
== c
) {
282 mspPort
->packetState
= MSP_COMMAND_RECEIVED
;
284 mspPort
->packetState
= MSP_IDLE
;
290 static uint8_t mspSerialChecksumBuf(uint8_t checksum
, const uint8_t *data
, int len
)
298 #define JUMBO_FRAME_SIZE_LIMIT 255
299 static int mspSerialSendFrame(mspPort_t
*msp
, const uint8_t * hdr
, int hdrLen
, const uint8_t * data
, int dataLen
, const uint8_t * crc
, int crcLen
)
301 // We are allowed to send out the response if
302 // a) TX buffer is completely empty (we are talking to well-behaving party that follows request-response scheduling;
303 // this allows us to transmit jumbo frames bigger than TX buffer (serialWriteBuf will block, but for jumbo frames we don't care)
304 // b) Response fits into TX buffer
305 const int totalFrameLength
= hdrLen
+ dataLen
+ crcLen
;
306 if (!isSerialTransmitBufferEmpty(msp
->port
) && ((int)serialTxBytesFree(msp
->port
) < totalFrameLength
)) {
311 serialBeginWrite(msp
->port
);
312 serialWriteBufNoFlush(msp
->port
, hdr
, hdrLen
);
313 serialWriteBufNoFlush(msp
->port
, data
, dataLen
);
314 serialWriteBufNoFlush(msp
->port
, crc
, crcLen
);
315 serialEndWrite(msp
->port
);
317 return totalFrameLength
;
320 static int mspSerialEncode(mspPort_t
*msp
, mspPacket_t
*packet
, mspVersion_e mspVersion
)
322 static const uint8_t mspMagic
[MSP_VERSION_COUNT
] = MSP_VERSION_MAGIC_INITIALIZER
;
323 const int dataLen
= sbufBytesRemaining(&packet
->buf
);
324 uint8_t hdrBuf
[16] = { '$', mspMagic
[mspVersion
], packet
->result
== MSP_RESULT_ERROR
? '!' : '>'};
330 #define V1_CHECKSUM_STARTPOS 3
331 if (mspVersion
== MSP_V1
) {
332 mspHeaderV1_t
* hdrV1
= (mspHeaderV1_t
*)&hdrBuf
[hdrLen
];
333 hdrLen
+= sizeof(mspHeaderV1_t
);
334 hdrV1
->cmd
= packet
->cmd
;
336 // Add JUMBO-frame header if necessary
337 if (dataLen
>= JUMBO_FRAME_SIZE_LIMIT
) {
338 mspHeaderJUMBO_t
* hdrJUMBO
= (mspHeaderJUMBO_t
*)&hdrBuf
[hdrLen
];
339 hdrLen
+= sizeof(mspHeaderJUMBO_t
);
341 hdrV1
->size
= JUMBO_FRAME_SIZE_LIMIT
;
342 hdrJUMBO
->size
= dataLen
;
344 hdrV1
->size
= dataLen
;
348 checksum
= mspSerialChecksumBuf(0, hdrBuf
+ V1_CHECKSUM_STARTPOS
, hdrLen
- V1_CHECKSUM_STARTPOS
);
349 checksum
= mspSerialChecksumBuf(checksum
, sbufPtr(&packet
->buf
), dataLen
);
350 crcBuf
[crcLen
++] = checksum
;
351 } else if (mspVersion
== MSP_V2_OVER_V1
) {
352 mspHeaderV1_t
* hdrV1
= (mspHeaderV1_t
*)&hdrBuf
[hdrLen
];
354 hdrLen
+= sizeof(mspHeaderV1_t
);
356 mspHeaderV2_t
* hdrV2
= (mspHeaderV2_t
*)&hdrBuf
[hdrLen
];
357 hdrLen
+= sizeof(mspHeaderV2_t
);
359 const int v1PayloadSize
= sizeof(mspHeaderV2_t
) + dataLen
+ 1; // MSPv2 header + data payload + MSPv2 checksum
360 hdrV1
->cmd
= MSP_V2_FRAME_ID
;
362 // Add JUMBO-frame header if necessary
363 if (v1PayloadSize
>= JUMBO_FRAME_SIZE_LIMIT
) {
364 mspHeaderJUMBO_t
* hdrJUMBO
= (mspHeaderJUMBO_t
*)&hdrBuf
[hdrLen
];
365 hdrLen
+= sizeof(mspHeaderJUMBO_t
);
367 hdrV1
->size
= JUMBO_FRAME_SIZE_LIMIT
;
368 hdrJUMBO
->size
= v1PayloadSize
;
370 hdrV1
->size
= v1PayloadSize
;
374 hdrV2
->flags
= packet
->flags
;
375 hdrV2
->cmd
= packet
->cmd
;
376 hdrV2
->size
= dataLen
;
378 // V2 CRC: only V2 header + data payload
379 checksum
= crc8_dvb_s2_update(0, (uint8_t *)hdrV2
, sizeof(mspHeaderV2_t
));
380 checksum
= crc8_dvb_s2_update(checksum
, sbufPtr(&packet
->buf
), dataLen
);
381 crcBuf
[crcLen
++] = checksum
;
383 // V1 CRC: All headers + data payload + V2 CRC byte
384 checksum
= mspSerialChecksumBuf(0, hdrBuf
+ V1_CHECKSUM_STARTPOS
, hdrLen
- V1_CHECKSUM_STARTPOS
);
385 checksum
= mspSerialChecksumBuf(checksum
, sbufPtr(&packet
->buf
), dataLen
);
386 checksum
= mspSerialChecksumBuf(checksum
, crcBuf
, crcLen
);
387 crcBuf
[crcLen
++] = checksum
;
388 } else if (mspVersion
== MSP_V2_NATIVE
) {
389 mspHeaderV2_t
* hdrV2
= (mspHeaderV2_t
*)&hdrBuf
[hdrLen
];
390 hdrLen
+= sizeof(mspHeaderV2_t
);
392 hdrV2
->flags
= packet
->flags
;
393 hdrV2
->cmd
= packet
->cmd
;
394 hdrV2
->size
= dataLen
;
396 checksum
= crc8_dvb_s2_update(0, (uint8_t *)hdrV2
, sizeof(mspHeaderV2_t
));
397 checksum
= crc8_dvb_s2_update(checksum
, sbufPtr(&packet
->buf
), dataLen
);
398 crcBuf
[crcLen
++] = checksum
;
400 // Shouldn't get here
405 return mspSerialSendFrame(msp
, hdrBuf
, hdrLen
, sbufPtr(&packet
->buf
), dataLen
, crcBuf
, crcLen
);
408 static mspPostProcessFnPtr
mspSerialProcessReceivedCommand(mspPort_t
*msp
, mspProcessCommandFnPtr mspProcessCommandFn
)
410 static uint8_t mspSerialOutBuf
[MSP_PORT_OUTBUF_SIZE
];
412 mspPacket_t reply
= {
413 .buf
= { .ptr
= mspSerialOutBuf
, .end
= ARRAYEND(mspSerialOutBuf
), },
417 .direction
= MSP_DIRECTION_REPLY
,
419 uint8_t *outBufHead
= reply
.buf
.ptr
;
421 mspPacket_t command
= {
422 .buf
= { .ptr
= msp
->inBuf
, .end
= msp
->inBuf
+ msp
->dataSize
, },
424 .flags
= msp
->cmdFlags
,
426 .direction
= MSP_DIRECTION_REQUEST
,
429 mspPostProcessFnPtr mspPostProcessFn
= NULL
;
430 const mspResult_e status
= mspProcessCommandFn(msp
->descriptor
, &command
, &reply
, &mspPostProcessFn
);
432 if (status
!= MSP_RESULT_NO_REPLY
) {
433 sbufSwitchToReader(&reply
.buf
, outBufHead
); // change streambuf direction
434 mspSerialEncode(msp
, &reply
, msp
->mspVersion
);
437 return mspPostProcessFn
;
440 static void mspProcessPendingRequest(mspPort_t
* mspPort
)
442 // If no request is pending or 100ms guard time has not elapsed - do nothing
443 if ((mspPort
->pendingRequest
== MSP_PENDING_NONE
) || (cmp32(millis(), mspPort
->lastActivityMs
) < 100)) {
447 switch(mspPort
->pendingRequest
) {
448 case MSP_PENDING_BOOTLOADER_ROM
:
449 systemResetToBootloader(BOOTLOADER_REQUEST_ROM
);
452 #if defined(USE_FLASH_BOOT_LOADER)
453 case MSP_PENDING_BOOTLOADER_FLASH
:
454 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH
);
459 case MSP_PENDING_CLI
:
460 mspPort
->pendingRequest
= MSP_PENDING_NONE
;
461 mspPort
->portState
= PORT_CLI_ACTIVE
;
462 cliEnter(mspPort
->port
, true);
471 static void mspSerialProcessReceivedReply(mspPort_t
*msp
, mspProcessReplyFnPtr mspProcessReplyFn
)
473 mspPacket_t reply
= {
476 .end
= msp
->inBuf
+ msp
->dataSize
,
482 mspProcessReplyFn(&reply
);
485 void mspProcessPacket(mspPort_t
*mspPort
, mspProcessCommandFnPtr mspProcessCommandFn
, mspProcessReplyFnPtr mspProcessReplyFn
)
487 mspPostProcessFnPtr mspPostProcessFn
= NULL
;
489 while (serialRxBytesWaiting(mspPort
->port
)) {
490 const uint8_t c
= serialRead(mspPort
->port
);
492 mspSerialProcessReceivedPacketData(mspPort
, c
);
494 if (mspPort
->packetState
== MSP_COMMAND_RECEIVED
) {
495 if (mspPort
->packetType
== MSP_PACKET_COMMAND
) {
496 mspPostProcessFn
= mspSerialProcessReceivedCommand(mspPort
, mspProcessCommandFn
);
497 } else if (mspPort
->packetType
== MSP_PACKET_REPLY
) {
498 mspSerialProcessReceivedReply(mspPort
, mspProcessReplyFn
);
501 // process one command at a time so as not to block
502 mspPort
->packetState
= MSP_IDLE
;
505 if (mspPort
->packetState
== MSP_IDLE
) {
506 mspPort
->portState
= PORT_IDLE
;
511 if (mspPostProcessFn
) {
512 waitForSerialPortToFinishTransmitting(mspPort
->port
);
513 mspPostProcessFn(mspPort
->port
);
518 * Process MSP commands from serial ports configured as MSP ports.
520 * Called periodically by the scheduler.
522 void mspSerialProcess(mspEvaluateNonMspData_e evaluateNonMspData
, mspProcessCommandFnPtr mspProcessCommandFn
, mspProcessReplyFnPtr mspProcessReplyFn
)
524 for (mspPort_t
*mspPort
= mspPorts
; mspPort
< ARRAYEND(mspPorts
); mspPort
++) {
525 if (!mspPort
->port
) {
529 // whilst port is idle, poll incoming until portState changes or no more bytes
530 while (mspPort
->portState
== PORT_IDLE
&& serialRxBytesWaiting(mspPort
->port
)) {
532 // There are bytes incoming - abort pending request
533 mspPort
->lastActivityMs
= millis();
534 mspPort
->pendingRequest
= MSP_PENDING_NONE
;
536 const uint8_t c
= serialRead(mspPort
->port
);
538 mspPort
->portState
= PORT_MSP_PACKET
;
539 mspPort
->packetState
= MSP_HEADER_START
;
540 } else if (evaluateNonMspData
== MSP_EVALUATE_NON_MSP_DATA
) {
541 // evaluate the non-MSP data
542 if (c
== serialConfig()->reboot_character
) {
543 mspPort
->pendingRequest
= MSP_PENDING_BOOTLOADER_ROM
;
545 } else if (c
== '#') {
546 mspPort
->pendingRequest
= MSP_PENDING_CLI
;
547 } else if (c
== 0x2) {
548 mspPort
->portState
= PORT_CLI_CMD
;
549 cliEnter(mspPort
->port
, false);
555 switch (mspPort
->portState
) {
557 mspProcessPendingRequest(mspPort
);
559 case PORT_MSP_PACKET
:
560 mspProcessPacket(mspPort
, mspProcessCommandFn
, mspProcessReplyFn
);
563 case PORT_CLI_ACTIVE
:
566 mspPort
->portState
= PORT_IDLE
;
577 bool mspSerialWaiting(void)
579 for (mspPort_t
*mspPort
= mspPorts
; mspPort
< ARRAYEND(mspPorts
); mspPort
++) {
580 if (!mspPort
->port
) {
584 if (serialRxBytesWaiting(mspPort
->port
)) {
591 void mspSerialInit(void)
593 memset(mspPorts
, 0, sizeof(mspPorts
));
594 mspSerialAllocatePorts();
597 int mspSerialPush(serialPortIdentifier_e port
, uint8_t cmd
, uint8_t *data
, int datalen
, mspDirection_e direction
, mspVersion_e mspVersion
)
601 for (mspPort_t
*mspPort
= mspPorts
; mspPort
< ARRAYEND(mspPorts
); mspPort
++) {
603 // XXX Kludge!!! Avoid zombie VCP port (avoid VCP entirely for now)
605 #ifndef USE_MSP_PUSH_OVER_VCP
606 || mspPort
->port
->identifier
== SERIAL_PORT_USB_VCP
608 || (port
!= SERIAL_PORT_ALL
&& mspPort
->port
->identifier
!= port
)
609 || mspPort
->portState
== PORT_CLI_CMD
|| mspPort
->portState
== PORT_CLI_ACTIVE
) {
614 .buf
= { .ptr
= data
, .end
= data
+ datalen
, },
617 .direction
= direction
,
620 ret
= mspSerialEncode(mspPort
, &push
, mspVersion
);
622 return ret
; // return the number of bytes written
625 uint32_t mspSerialTxBytesFree(void)
627 uint32_t ret
= UINT32_MAX
;
629 for (mspPort_t
*mspPort
= mspPorts
; mspPort
< ARRAYEND(mspPorts
); mspPort
++) {
630 if (!mspPort
->port
) {
634 // XXX Kludge!!! Avoid zombie VCP port (avoid VCP entirely for now)
635 if (mspPort
->port
->identifier
== SERIAL_PORT_USB_VCP
) {
639 const uint32_t bytesFree
= serialTxBytesFree(mspPort
->port
);
640 if (bytesFree
< ret
) {