bindings/jni: Add NS16550A ring-buffer bridge for JVM serial routing#1
Merged
Conversation
Expose a second NS16550A UART whose chardev backend is a pair of 64 KiB
ring buffers instead of stdio. Java-side consumers drain guest TX via
ns16550a_bridge_poll and inject guest RX via ns16550a_bridge_feed —
enough to implement an in-game serial console or a host-driven RPC
transport on top of a stock NS16550A, without patching the UART or
exposing chardev internals through JNI.
Architecture mirrors feat/sound-backend-api's HDA ring pattern:
- ns16550a_bridge_init attaches the UART via the existing
ns16550a_init_auto(machine, chardev) path and returns an opaque
bridge handle.
- poll / feed own a spinlock guarding two ringbufs; chardev read/write
from the UART's thread and the Java thread contend on the same lock.
- chardev_notify is called outside the lock after an RX/TX flag edge,
so the UART's IRQ path doesn't re-enter while we hold state.
- TX overflow drops oldest bytes (HDA's latency-first policy). RX
overflow reports a short write; caller retries.
- ns16550a_bridge_stats exposes {pushed, popped, fed, consumed,
dropped} for instrumentation and tests.
The bridge lifetime is owned by the MMIO device: ns16550a_remove calls
chardev_free on our chardev, which frees the bridge struct and destroys
the ringbufs. No explicit JNI teardown call.
Zero changes to librvvm core — ns16550a.c already accepts a pluggable
chardev (ns16550a_init_auto). The stdio default (ns16550a_init_term_auto)
is unchanged; this just adds a second factory for embedders.
SolAstrius
added a commit
that referenced
this pull request
Apr 27, 2026
Linux's snd_hdac_bus_init_cmd_io (sound/hda/core/controller.c) does:
writew(CORBRP, AZX_CORBRP_RST); // bit 15 = 1
for (timeout = 1000; timeout > 0; timeout--)
if (readw(CORBRP) & AZX_CORBRP_RST) // poll for bit 15 = 1
break;
// ↑ "CORB reset timeout #1" if poll times out
writew(CORBRP, 0);
for (timeout = 1000; timeout > 0; timeout--)
if (readw(CORBRP) == 0) // poll for bit 15 = 0
break;
// ↑ "CORB reset timeout #2" if poll times out
Our handler reset corb_rp=0 immediately on the bit-15 write but never
echoed bit 15 back on read, so Linux's first poll always timed out
(1000 µs of busy-looping, then the dev_err warning). The cascade from
there causes spurious response timeouts during codec discovery —
Linux falls back to polling mode, retries, and somewhere in that
chaos NID 2's widget caps response gets dropped or returns garbage.
Linux mis-classifies the topology: NID 2 disappears entirely from the
codec dump, and snd_hda_codec_generic configures the pin (NID 3) as
"mono_out=0x3" with no converter underneath. No PCM device is
created, no Master mixer control exists, speaker-test errors with
ENOENT.
Fix: store corb_rprst (bit 15) separately from corb_rp (bits 7:0),
echo on read. SW writes 1 → set rprst, reset corb_rp to 0; SW writes
0 → clear rprst, store new RP value. Both Linux poll loops now exit
on the first iteration.
This regression went unnoticed because all the standalone smoke
boots in this session ran rvvm_x86_64 without the -hda_test flag —
HDA wasn't actually attached to the PCI bus. The user discovered it
when running through the ScalarEvolution mod's full machine config
(which always attaches HDA) and seeing playback fail.
The CORBRP RST handshake has been broken since this device was first
written; before commit 5d10843 (table-driven SD dispatch) the code
also reset corb_rp=0 on bit-15 write without echoing the bit. It
worked for users in practice because Linux only logs the timeout as
a warning and continues — the real damage was the 1 ms × 1000 = 1 s
delay during which subsequent codec verbs raced. Some guests were
lucky and got a clean topology; others (apparently anyone with the
new Beep widget bumping subnode count to 3) tipped over.
SolAstrius
added a commit
that referenced
this pull request
May 1, 2026
) Expose a second NS16550A UART whose chardev backend is a pair of 64 KiB ring buffers instead of stdio. Java-side consumers drain guest TX via ns16550a_bridge_poll and inject guest RX via ns16550a_bridge_feed — enough to implement an in-game serial console or a host-driven RPC transport on top of a stock NS16550A, without patching the UART or exposing chardev internals through JNI. Architecture mirrors feat/sound-backend-api's HDA ring pattern: - ns16550a_bridge_init attaches the UART via the existing ns16550a_init_auto(machine, chardev) path and returns an opaque bridge handle. - poll / feed own a spinlock guarding two ringbufs; chardev read/write from the UART's thread and the Java thread contend on the same lock. - chardev_notify is called outside the lock after an RX/TX flag edge, so the UART's IRQ path doesn't re-enter while we hold state. - TX overflow drops oldest bytes (HDA's latency-first policy). RX overflow reports a short write; caller retries. - ns16550a_bridge_stats exposes {pushed, popped, fed, consumed, dropped} for instrumentation and tests. The bridge lifetime is owned by the MMIO device: ns16550a_remove calls chardev_free on our chardev, which frees the bridge struct and destroys the ringbufs. No explicit JNI teardown call. Zero changes to librvvm core — ns16550a.c already accepts a pluggable chardev (ns16550a_init_auto). The stdio default (ns16550a_init_term_auto) is unchanged; this just adds a second factory for embedders.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Architecture
Mirrors the HDA ring pattern from `feat/sound-backend-api`:
Test plan
```
Java_lekkit_rvvm_RVVMNative_ns16550a_1bridge_1{init,poll,feed,stats}
```