From fdbfc571279312d36b590a6ee44c27700e96ffc7 Mon Sep 17 00:00:00 2001 From: Raphael Hunziker Date: Thu, 10 Sep 2026 19:46:53 +0200 Subject: [PATCH 1/7] msp: reject ESC 4-way passthrough while armed Fixes #11777 --- src/main/fc/fc_msp.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 79ee6da48bb..70c9520daf9 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -226,7 +226,7 @@ static void mspSerialPassthroughFn(serialPort_t *serialPort) } } -static void mspFcSetPassthroughCommand(sbuf_t *dst, sbuf_t *src, mspPostProcessFnPtr *mspPostProcessFn) +static mspResult_e mspFcSetPassthroughCommand(sbuf_t *dst, sbuf_t *src, mspPostProcessFnPtr *mspPostProcessFn) { const unsigned int dataSize = sbufBytesRemaining(src); /* Payload size in Bytes */ @@ -254,6 +254,11 @@ static void mspFcSetPassthroughCommand(sbuf_t *dst, sbuf_t *src, mspPostProcessF break; #ifdef USE_SERIAL_4WAY_BLHELI_INTERFACE case MSP_PASSTHROUGH_ESC_4WAY: + // entering the 4way interface stops the motor outputs, refuse while armed + if (ARMING_FLAG(ARMED)) { + return MSP_RESULT_ERROR; + } + // get channel number // switch all motor lines HI // reply with the count of ESC found @@ -267,6 +272,8 @@ static void mspFcSetPassthroughCommand(sbuf_t *dst, sbuf_t *src, mspPostProcessF default: sbufWriteU8(dst, 0); } + + return MSP_RESULT_ACK; } static void mspRebootNormalFn(serialPort_t *serialPort) @@ -5157,8 +5164,7 @@ mspResult_e mspFcProcessCommand(mspPacket_t *cmd, mspPacket_t *reply, mspPostPro } else if (mspFcProcessOutCommand(cmdMSP, dst, mspPostProcessFn)) { ret = MSP_RESULT_ACK; } else if (cmdMSP == MSP_SET_PASSTHROUGH) { - mspFcSetPassthroughCommand(dst, src, mspPostProcessFn); - ret = MSP_RESULT_ACK; + ret = mspFcSetPassthroughCommand(dst, src, mspPostProcessFn); } else if (cmdMSP == MSP_REBOOT) { if (!ARMING_FLAG(ARMED)) { ret = mspFcRebootCommand(src, mspPostProcessFn); From 03d31811d9a8903e0d2e4e65454bd22c3ac3dd70 Mon Sep 17 00:00:00 2001 From: Raphael Hunziker Date: Thu, 10 Sep 2026 19:47:47 +0200 Subject: [PATCH 2/7] msp: bound dataflash read address before truncating the length Fixes #11778 --- src/main/fc/fc_msp.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/fc/fc_msp.c b/src/main/fc/fc_msp.c index 70c9520daf9..1c91b2ea64f 100644 --- a/src/main/fc/fc_msp.c +++ b/src/main/fc/fc_msp.c @@ -384,7 +384,10 @@ static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, uint16_t // size will be lower than that requested if we reach end of volume const uint32_t flashfsSize = flashfsGetSize(); - if (readLen > flashfsSize - address) { + if (address >= flashfsSize) { + // nothing left to read from this address + readLen = 0; + } else if (readLen > flashfsSize - address) { // truncate the request readLen = flashfsSize - address; } @@ -392,9 +395,11 @@ static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, uint16_t // Write address sbufWriteU32(dst, address); - // Read into streambuf directly - const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen); - sbufAdvance(dst, bytesRead); + if (readLen > 0) { + // Read into streambuf directly + const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen); + sbufAdvance(dst, bytesRead); + } } #endif From a455377c7c2c0614106f74840d9de60c7939396b Mon Sep 17 00:00:00 2001 From: Raphael Hunziker Date: Thu, 10 Sep 2026 19:48:26 +0200 Subject: [PATCH 3/7] msp: require full header length for MSPv1 start frames Fixes #11779 --- src/main/telemetry/msp_shared.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/telemetry/msp_shared.c b/src/main/telemetry/msp_shared.c index f6d4c6eafbd..9685da06a97 100644 --- a/src/main/telemetry/msp_shared.c +++ b/src/main/telemetry/msp_shared.c @@ -143,6 +143,9 @@ bool handleMspFrame(uint8_t *const frameStart, const int payloadLength) uint16_t mspPayloadSize; if (lastRequestVersion == 1) { // MSPv1 + if (payloadLength < MIN_LENGTH_REQUEST_V1) { + return false; // prevent analyzing garbage data + } mspPayloadSize = frameStart[MSP_INDEX_SIZE_V1]; requestPacket->cmd = frameStart[MSP_INDEX_ID_V1]; From dbefadb8bcba027c8a18f19be240aca0ea6aa0b0 Mon Sep 17 00:00:00 2001 From: Raphael Hunziker Date: Thu, 10 Sep 2026 19:49:02 +0200 Subject: [PATCH 4/7] telemetry: zero the SmartPort MSP response frame before use Fixes #11780 --- src/main/telemetry/smartport.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/telemetry/smartport.c b/src/main/telemetry/smartport.c index 5945db83821..4b8e2c7eefb 100755 --- a/src/main/telemetry/smartport.c +++ b/src/main/telemetry/smartport.c @@ -417,6 +417,7 @@ void checkSmartPortTelemetryState(void) #if defined(USE_MSP_OVER_TELEMETRY) static void smartPortSendMspResponse(uint8_t *data, const uint8_t dataSize) { smartPortPayload_t payload; + memset(&payload, 0, sizeof(payload)); // don't leak stack content in the unused bytes payload.frameId = FSSP_MSPS_FRAME; memcpy(&payload.valueId, data, MIN(dataSize,SMARTPORT_MSP_PAYLOAD_SIZE)); From ef4bffa0e294b6eeaa85be858cac1ac421ecffbf Mon Sep 17 00:00:00 2001 From: Raffi1202 Date: Fri, 11 Sep 2026 18:15:03 +0200 Subject: [PATCH 5/7] Discard partial MSP requests on malformed replacement frames --- src/main/telemetry/msp_shared.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/telemetry/msp_shared.c b/src/main/telemetry/msp_shared.c index 9685da06a97..e2102d710cc 100644 --- a/src/main/telemetry/msp_shared.c +++ b/src/main/telemetry/msp_shared.c @@ -125,6 +125,7 @@ bool handleMspFrame(uint8_t *const frameStart, const int payloadLength) } if (payloadLength < MIN_LENGTH_CHUNK) { + mspStarted = 0; return false; // prevent analyzing garbage data } @@ -135,11 +136,14 @@ bool handleMspFrame(uint8_t *const frameStart, const int payloadLength) lastRequestVersion = (status & TELEMETRY_MSP_VER_MASK) >> TELEMETRY_MSP_VER_SHIFT; if (lastRequestVersion > TELEMETRY_MSP_VERSION) { + mspStarted = 0; sendMspErrorResponse(TELEMETRY_MSP_VER_MISMATCH, 0); return true; } if (status & TELEMETRY_MSP_START_MASK) { // first packet in sequence + // A new start supersedes any partial request, even if its own header is malformed. + mspStarted = 0; uint16_t mspPayloadSize; if (lastRequestVersion == 1) { // MSPv1 From 3d0d674b93144ee1339b299513f2e1e33e0c5f36 Mon Sep 17 00:00:00 2001 From: Raphael Hunziker Date: Sun, 13 Sep 2026 21:24:00 +0200 Subject: [PATCH 6/7] docs: note that ESC 4-way passthrough is refused while armed MSP_REBOOT and MSP_ACC_CALIBRATION already document their armed refusal; MSP_SET_PASSTHROUGH now does the same. README.md regenerated with gen_msp_md.py. --- docs/development/msp/README.md | 2 +- docs/development/msp/msp_messages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development/msp/README.md b/docs/development/msp/README.md index f35a79211b4..7b471894572 100644 --- a/docs/development/msp/README.md +++ b/docs/development/msp/README.md @@ -2695,7 +2695,7 @@ When the MSP JSON specification changes, bump `msp_messages.json` version: |---|---|---|---| | `status` | `uint8_t` | 1 | 1 if passthrough started successfully, 0 on error (e.g., port not found). For 4way, returns number of ESCs found | -**Notes:** Accepts 0 bytes (defaults to ESC 4-way) or up to 2 bytes for mode/argument. If successful, sets `mspPostProcessFn` to the appropriate handler (`mspSerialPassthroughFn` or `esc4wayProcess`). This handler takes over the serial port after the reply is sent. Requires `USE_SERIAL_4WAY_BLHELI_INTERFACE` for ESC passthrough. +**Notes:** Accepts 0 bytes (defaults to ESC 4-way) or up to 2 bytes for mode/argument. If successful, sets `mspPostProcessFn` to the appropriate handler (`mspSerialPassthroughFn` or `esc4wayProcess`). This handler takes over the serial port after the reply is sent. Requires `USE_SERIAL_4WAY_BLHELI_INTERFACE` for ESC passthrough. ESC 4-way passthrough will fail if the craft is armed: the reply carries `MSP_RESULT_ERROR` and the serial port is not handed over. Serial passthrough to another device is unaffected. ## `MSP_RTC (246 / 0xf6)` **Description:** Retrieves the current Real-Time Clock time. diff --git a/docs/development/msp/msp_messages.json b/docs/development/msp/msp_messages.json index 176833915ad..5b9702ca622 100644 --- a/docs/development/msp/msp_messages.json +++ b/docs/development/msp/msp_messages.json @@ -5737,7 +5737,7 @@ } ] }, - "notes": "Accepts 0 bytes (defaults to ESC 4-way) or up to 2 bytes for mode/argument. If successful, sets `mspPostProcessFn` to the appropriate handler (`mspSerialPassthroughFn` or `esc4wayProcess`). This handler takes over the serial port after the reply is sent. Requires `USE_SERIAL_4WAY_BLHELI_INTERFACE` for ESC passthrough.", + "notes": "Accepts 0 bytes (defaults to ESC 4-way) or up to 2 bytes for mode/argument. If successful, sets `mspPostProcessFn` to the appropriate handler (`mspSerialPassthroughFn` or `esc4wayProcess`). This handler takes over the serial port after the reply is sent. Requires `USE_SERIAL_4WAY_BLHELI_INTERFACE` for ESC passthrough. ESC 4-way passthrough will fail if the craft is armed: the reply carries `MSP_RESULT_ERROR` and the serial port is not handed over. Serial passthrough to another device is unaffected.", "description": "Enables serial passthrough mode to peripherals like ESCs (BLHeli 4-way) or other serial devices." }, "MSP_RTC": { From 53fa1ae225f443e880f17dc0d145f7ba4aa975b6 Mon Sep 17 00:00:00 2001 From: Raffi1202 Date: Wed, 23 Sep 2026 00:10:17 +0200 Subject: [PATCH 7/7] Complete the MSP catalogue required by the current CI check --- docs/development/msp/README.md | 43 ++++++++++++++ docs/development/msp/msp_messages.json | 77 ++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/docs/development/msp/README.md b/docs/development/msp/README.md index 7b471894572..9d9c87005a1 100644 --- a/docs/development/msp/README.md +++ b/docs/development/msp/README.md @@ -461,6 +461,9 @@ When the MSP JSON specification changes, bump `msp_messages.json` version: [8744 - MSP2_INAV_TIMESYNC](#msp2_inav_timesync) [8752 - MSP2_INAV_SET_AUX_RC](#msp2_inav_set_aux_rc) [8753 - MSP2_INAV_WIND](#msp2_inav_wind) +[8754 - MSP2_INAV_MAG_UNALIGNED](#msp2_inav_mag_unaligned) +[8755 - MSP2_INAV_ESC_SRXL2_STATUS](#msp2_inav_esc_srxl2_status) +[8756 - MSP2_INAV_ESC_SRXL2_CALIBRATE](#msp2_inav_esc_srxl2_calibrate) [12288 - MSP2_BETAFLIGHT_BIND](#msp2_betaflight_bind) [12289 - MSP2_RX_BIND](#msp2_rx_bind) @@ -4878,6 +4881,46 @@ When the MSP JSON specification changes, bump `msp_messages.json` version: **Notes:** Requires `USE_WIND_ESTIMATOR`; returns zeroes when wind estimation is not compiled in or not yet valid. Check bit 0 of `flags` before using speed/angle values. +## `MSP2_INAV_MAG_UNALIGNED (8754 / 0x2232)` +**Description:** Reads the unaligned magnetometer vector. + +**Request Payload:** **None** + +**Reply Payload:** +|Field|C Type|Size (Bytes)|Description| +|---|---|---|---| +| `magADCUnaligned` | `int16_t[3]` | 6 | X, Y and Z components, rounded to signed 16-bit values. | + +**Notes:** Returns rounded mag.magADCUnaligned values before board alignment. Returns three zeroes without USE_MAG. + +## `MSP2_INAV_ESC_SRXL2_STATUS (8755 / 0x2233)` +**Description:** Reads SRXL2 ESC calibration and connection status. + +**Request Payload:** **None** + +**Reply Payload:** +|Field|C Type|Size (Bytes)|Description| +|---|---|---|---| +| `phase` | `uint8_t` | 1 | Calibration phase (srxl2CalPhase_e). | +| `connected` | `uint8_t` | 1 | 1 when every opened ESC is connected; otherwise 0. | +| `lastResult` | `uint8_t` | 1 | Last calibration start result (srxl2CalResult_e). | +| `portCount` | `uint8_t` | 1 | Number of opened SRXL2 motor ports. | +| `motorCount` | `uint8_t` | 1 | Number of motors in the current mixer. | + +**Notes:** Requires USE_MOTOR_SRXL2. Counts report opened motor ports and the current mixer motor count, not hardware capacity. + +## `MSP2_INAV_ESC_SRXL2_CALIBRATE (8756 / 0x2234)` +**Description:** Controls SRXL2 ESC throttle-range calibration. + +**Request Payload:** +|Field|C Type|Size (Bytes)|Description| +|---|---|---|---| +| `phase` | `uint8_t` | 1 | Requested calibration action (srxl2CalPhase_e): 0, 1, 4 or 5. | + +**Reply Payload:** **None** + +**Notes:** Requires USE_MOTOR_SRXL2 and at least one request byte. Accepted commands: 0 abort, 1 automatic start, 4 manual high, 5 manual low. Driver safety checks can reject start requests; read MSP2_INAV_ESC_SRXL2_STATUS for the reason. Other command values return an MSP error. Remove propellers before calibration. + ## `MSP2_BETAFLIGHT_BIND (12288 / 0x3000)` **Description:** Initiates the receiver binding procedure for supported serial protocols (CRSF, SRXL2). diff --git a/docs/development/msp/msp_messages.json b/docs/development/msp/msp_messages.json index 5b9702ca622..740469bd44d 100644 --- a/docs/development/msp/msp_messages.json +++ b/docs/development/msp/msp_messages.json @@ -11674,6 +11674,83 @@ }, "notes": "Requires a receiver using MSP as the protocol, sends MSP2_RX_BIND to the receiver.", "description": "Initiates binding for MSP receivers (mLRS)." + }, + "MSP2_INAV_MAG_UNALIGNED": { + "code": 8754, + "mspv": 2, + "request": null, + "reply": { + "payload": [ + { + "name": "magADCUnaligned", + "ctype": "int16_t", + "desc": "X, Y and Z components, rounded to signed 16-bit values.", + "units": "", + "array": true, + "array_size": 3 + } + ] + }, + "notes": "Returns rounded mag.magADCUnaligned values before board alignment. Returns three zeroes without USE_MAG.", + "description": "Reads the unaligned magnetometer vector." + }, + "MSP2_INAV_ESC_SRXL2_STATUS": { + "code": 8755, + "mspv": 2, + "request": null, + "reply": { + "payload": [ + { + "name": "phase", + "ctype": "uint8_t", + "desc": "Calibration phase (srxl2CalPhase_e).", + "units": "" + }, + { + "name": "connected", + "ctype": "uint8_t", + "desc": "1 when every opened ESC is connected; otherwise 0.", + "units": "" + }, + { + "name": "lastResult", + "ctype": "uint8_t", + "desc": "Last calibration start result (srxl2CalResult_e).", + "units": "" + }, + { + "name": "portCount", + "ctype": "uint8_t", + "desc": "Number of opened SRXL2 motor ports.", + "units": "" + }, + { + "name": "motorCount", + "ctype": "uint8_t", + "desc": "Number of motors in the current mixer.", + "units": "" + } + ] + }, + "notes": "Requires USE_MOTOR_SRXL2. Counts report opened motor ports and the current mixer motor count, not hardware capacity.", + "description": "Reads SRXL2 ESC calibration and connection status." + }, + "MSP2_INAV_ESC_SRXL2_CALIBRATE": { + "code": 8756, + "mspv": 2, + "request": { + "payload": [ + { + "name": "phase", + "ctype": "uint8_t", + "desc": "Requested calibration action (srxl2CalPhase_e): 0, 1, 4 or 5.", + "units": "" + } + ] + }, + "reply": null, + "notes": "Requires USE_MOTOR_SRXL2 and at least one request byte. Accepted commands: 0 abort, 1 automatic start, 4 manual high, 5 manual low. Driver safety checks can reject start requests; read MSP2_INAV_ESC_SRXL2_STATUS for the reason. Other command values return an MSP error. Remove propellers before calibration.", + "description": "Controls SRXL2 ESC throttle-range calibration." } } }