Skip to content

serial: add an option to resynchronize with the controller on open - #996

Merged
barbibulle merged 1 commit into
google:mainfrom
TheArkadiuszGrzelka:h4-power-on-resync
Oct 3, 2026
Merged

barbibulle merged 1 commit into
google:mainfrom
TheArkadiuszGrzelka:h4-power-on-resync

Conversation

@TheArkadiuszGrzelka

Copy link
Copy Markdown
Contributor

Fixes #956.

This is the optional resync discussed in #956, as a resync flag on the serial moniker, off by default:

serial:/dev/ttyACM0,resync

Tested on an nRF7002 DK running Zephyr's hci_uart sample behind its J-Link OB virtual COM port. With the controller left waiting for the rest of a command, a plain power_on() hung; with resync it completed. A controller left with a partial HCI_Reset header (01 03 0c) also completed with resync.

@TheArkadiuszGrzelka
TheArkadiuszGrzelka marked this pull request as ready for review October 2, 2026 04:37
Comment thread bumble/transport/serial.py Outdated
await exchange(hci.HCI_Reset_Command(), bytes(RESYNC_PADDING_SIZE))
# That completion can be for an earlier reset. Anything still pending arrives
# before the completion of the next command.
await exchange(hci.HCI_Read_Local_Supported_Commands_Command())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be slightly more efficient to use the HCI_Read_Local_Version_Information command, which has a shorter response than HCI_Read_Local_Supported_Commands_Command.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the resync now sends HCI_Read_Local_Version_Information (serial.py:118).

Comment thread bumble/transport/serial.py Outdated
return
self._discarded += data
match = self._awaited.search(self._discarded)
if match and len(self._discarded) >= match.start() + 3 + match[1][0]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use HCI_Read_Local_Version_Information_Command, the regex can take care of matching all the bytes (04, 0e, 03, XX, 01, 10 + 9 bytes).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the pattern now matches the whole event (serial.py:105), and the separate length check is gone. The length byte is 0x0c rather than 03, since Num_HCI_Command_Packets and the op code count toward it along with the 9 bytes of return parameters.

Comment thread bumble/transport/serial.py Outdated
self._discarded.clear()
self._answered.clear()
self._awaited = re.compile(
rb'\x04\x0e(.).' + command.op_code.to_bytes(2, 'little'), re.DOTALL

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about including the length byte in the match

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the HCI_Reset completion is matched with its length byte 0x04 (serial.py:115).

A process that stops part way through writing a command leaves the
controller waiting for the rest of it. The controller then reads the
next host's HCI_Reset as that payload, and power_on() waits forever for
a response.

Add a "resync" option to the serial transport moniker, off by default.
On open it writes 258 zero bytes, enough to complete a pending command
or SCO packet, then an HCI_Reset. Once a reset completes, it sends
HCI_Read_Local_Version_Information and discards input until that
command completes: the first reset completion can be for an earlier
reset, and anything still pending arrives before the second command's.
A command counts as complete once its whole Command Complete event,
length byte and return parameters included, has been received. If the
two commands get no reply within 2 s, the open fails. Not every
controller accepts the padding, which is why it is an option.

The padding goes out in 64-byte writes: through a J-Link OB virtual COM
port, the same bytes sent as one write left the controller hung.

Refs google#956.

Signed-off-by: Arkadiusz Grzelka <devitwise@gmail.com>
@barbibulle
barbibulle merged commit a2058e4 into google:main Oct 3, 2026
36 checks passed
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.

A single stray byte on an H:4 serial link hangs power_on() forever (no parser resync, no reset timeout)

2 participants