Skip to content

msp: put the host first in a serial passthrough - #12003

Open
MrScothh wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
MrScothh:fix/msp-passthrough-host-first
Open

MrScothh wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
MrScothh:fix/msp-passthrough-host-first

Conversation

@MrScothh

Copy link
Copy Markdown
Contributor

What this fixes

serialPassthrough() does two things for whoever opened the session, and does them for its
first port only:

  • it looks there for the +++ that ends the session;
  • when that port is USB, it mirrors the host's line coding onto the other port.

The CLI passes the host first. The MSP passthrough (MSP_SET_PASSTHROUGH) passed it second,
so a session opened over MSP could not be closed from the host and could not change the rate
of the port it had opened.

How it showed up

A tool that configures an SRXL2 ESC through MSP_SET_PASSTHROUGH, on a TBS Lucid H7 Wing
with an Avian ESC on UART8:

Before After
Handshake, telemetry and settings menu through the passthrough work work
+++ at the end of the session reaches the ESC wire and comes back as its echo; the board stays in the passthrough loop and answers no MSP until power cycled ends the session; the board carries on, MSP answers straight away
Host changes its baud rate during the session ignored mirrored onto the bridged port

The last row is what lets such a tool negotiate an SRXL2 bus up to 400000.

What I changed

The port the request came in on now goes first, as it does in the CLI. Nothing else calls
serialPassthrough() with the host second: the GPS passthrough has a loop of its own.

The mirror is baselined on the host's line coding when the session starts and only follows
changes made during it (see the comment above it, from #11783), so opening a session does not
touch the rate of the port being bridged. A session opened over MSP on a UART rather than USB
gets the +++ exit it was missing, and no mirroring, as before.

Testing

On the bench described above: find, telemetry and the full settings menu read through the
passthrough, the session ending on +++ and the board answering MSP right after it, where
before every session needed a power cycle. Built for SITL and TBS_LUCID_H7_WING.

@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

MrScothh added a commit to MrScothh/smart-esc-tool that referenced this pull request Sep 21, 2026
The route through INAV has now been exercised on a TBS Lucid H7 Wing with an
Avian: the handshake, telemetry and the whole settings menu read back through
MSP_SET_PASSTHROUGH. Changing and saving a setting that way has not been
tried, nor a bus at 400000, which that Avian does not offer.

It also showed that two things the README promised need a fix in INAV to
happen over MSP: ending the session with +++, and mirroring the host's baud
rate onto the wire. INAV applies both to the first port it is given, and the
MSP passthrough gave it the ESC's, so without iNavFlight/inav#12003 the board
stays in the passthrough until power cycled. The README now says so instead of
saying nothing needs adding to INAV.

The telemetry log also names the TextGen rows the ESC sends, row number and
text, where it used to print them as an undecoded sensor.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix MSP passthrough host-port ordering

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Passes the MSP request port first when starting serial passthrough sessions.
• Restores host-side +++ termination and USB line-coding mirroring.
• Enables baud negotiation without changing the target port’s initial rate.
Diagram

sequenceDiagram
    actor Host
    participant MSP as MSP Handler
    participant Bridge as Serial Passthrough
    participant UART as Target UART
    Host->>MSP: Request passthrough
    MSP->>Bridge: Start host first
    Bridge->>UART: Forward traffic
    Host->>Bridge: Change line coding
    Bridge->>UART: Mirror changes
    Host->>Bridge: Send +++
    Bridge-->>MSP: End session
Loading
High-Level Assessment

The argument-order correction is the appropriate minimal fix because serialPassthrough intentionally assigns escape detection and USB mirroring to its first port, and the CLI already follows this host-first convention. Making passthrough behavior symmetric would broaden semantics unnecessarily and could interpret target-side echoes as termination or mirror settings in the wrong direction.

Files changed (1) +7 / -1

Bug fix (1) +7 / -1
fc_msp.cPass the MSP host port first to serial passthrough +7/-1

Pass the MSP host port first to serial passthrough

• Reorders the serialPassthrough arguments so the port receiving the MSP request is treated as the host. This restores host-side '+++' escape detection and USB line-coding mirroring to the bridged port, with an explanatory comment documenting the asymmetric contract.

src/main/fc/fc_msp.c

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@sensei-hacker

sensei-hacker commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

++ drops the passthrough? It seems to me this will drop the every passthrough connection, randomly, on average every few KB.
Testing with this script:

#!/bin/env python3

import random
import string

while 1:
  print( random.choice(string.printable), end=""

Would drop every few kilobytes:

$ python3 /tmp/randomchars.py | grep -bo '+++'
1503485:+++
1527196:+++
1789261:+++
2853831:+++

Is there something I'm missing here?

@MrScothh

Copy link
Copy Markdown
Contributor Author

The escape needs a second of silence before the +++ and another after it, so a +++ inside a stream of data never counts: any byte before or after cancels it. It's the same check the CLI serialpassthrough already uses; this PR only makes MSP pass the host port first, so the check looks at the host's data instead of the ESC's.

I tried it on a Lucid H7 Wing with this PR: +++ inside continuous traffic, or with a byte right after it, kept the passthrough open. Only silence, +++, silence closed it.

That's how I read it, but please tell me if I'm the one missing something: I'd never want to introduce a bug. I'm also going to test maintenance-10.x thoroughly with my PRs applied on top.

serialPassthrough() does two things for whoever opened the session, and does
them for its first port only: it looks there for the +++ that ends the
session, and when that port is USB it mirrors the host's line coding onto the
other one. The CLI passes the host first. The MSP passthrough passed it
second, so a session opened with MSP_SET_PASSTHROUGH could not be closed from
the host, and could not change the rate of the port it opened.

On a bench, a tool configuring an SRXL2 ESC through MSP_SET_PASSTHROUGH left
the board stuck in the passthrough after every session: the +++ reached the
ESC's wire and came back as its echo, but the board never left the loop and
answered no MSP until it was power cycled. With the host first the same
session ends on +++ and the board carries on. The rate the host asks for is
now mirrored as well, which is what lets such a tool negotiate an SRXL2 bus up
to 400000.

The mirror is baselined on the host's line coding when the session starts and
only follows changes made during it, so opening a session does not touch the
rate of the port being bridged.
@MrScothh
MrScothh force-pushed the fix/msp-passthrough-host-first branch from 1d7c771 to 01445e5 Compare September 23, 2026 06:29
@github-actions

Copy link
Copy Markdown

RAM / Flash usage vs. base commit 86a0441 — commit 01445e5

Target Flash Δ RAM Δ
MATEKF405 ±0 B (±0.00%) CCM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
MATEKF722 ±0 B (±0.00%) ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)
TCM: ±0 B (±0.00%)
MATEKF765 ±0 B (±0.00%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: ±0 B (±0.00%)
MATEKH743 ±0 B (±0.00%) D2_RAM: ±0 B (±0.00%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: ±0 B (±0.00%)
RAM: ±0 B (±0.00%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

Copy link
Copy Markdown

Test firmware build ready — commit 01445e5

Download firmware for PR #12003

250 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants