Skip to content

rl8822bu support - #105

Draft
RomanLut wants to merge 1 commit into
OpenIPC:masterfrom
RomanLut:rl8812bu_support
Draft

rl8822bu support#105
RomanLut wants to merge 1 commit into
OpenIPC:masterfrom
RomanLut:rl8812bu_support

Conversation

@RomanLut

@RomanLut RomanLut commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Added rtl8822bu support

Devourer PR should be accepted first OpenIPC/devourer#290
Should fast-forward devourer reference then.

image

@RomanLut RomanLut changed the title Rl8812bu support rl8822bu support Jul 14, 2026
vertexodessa pushed a commit that referenced this pull request Sep 2, 2026
"No compatible wifi adapter found." is a common report, and the one thing needed
to act on it - the adapter's vendor and product id - could not be obtained.
sysfs is not readable by the shell on some devices (Horizon OS for one) and
dumpsys usb does not list host devices there either, so the app is the only
thing in a position to report it.

getAttachedAdapters() now logs every attached device with its ids, manufacturer
and product name, and whether usb_device_filter.xml matched:

  usb devices attached: 1
    /dev/bus/usb/001/002  0BDA:8812  Realtek 802.11n NIC  -> supported

which turns "it does not work" into a line that can be pasted into a filter
entry. Relevant to the standing requests for new adapters (#82, #91, #93, #105).

Also fixes a landmine in the same code path: wirelessInfo() is static and reads
a static WifiManager that only VideoActivity.initializeUI() ever assigns, yet it
is called from WfbLinkManager.refreshAdapters(). Any other caller, or this one
before onCreate has got that far, gets a NullPointerException. It now takes a
Context, fetches the service itself and null checks both the manager and the
WifiInfo; the static field is gone, so it cannot come back through a different
entry point.
@vertexodessa

Copy link
Copy Markdown
Collaborator

@RomanLut sorry for the long silence on this one, it's my fault, not yours. I've gone through it now, notes below. Could you rebase onto current master? I have no 8822BU here, so I'd rather the branch stays yours and gets re-tested on your dongle after the rebase; I'll merge right after.

The submodule points at 1511c8ef, which I can't find in upstream devourer or in your fork on GitHub, so fetching the PR fails on it. devourer#290 is merged, and current master bb03774 already carries every jaguar2 file your CMakeLists adds, so moving the submodule to upstream master should resolve everything. The merge conflict is the submodule pointer only; the LaCapture.cpp / PhydmRuntimeJaguar3.cpp hunks are already in master. If your 1511c8ef had jaguar2 fixes that never made it upstream, that's the one thing the re-test would catch.

The 0x prefix in usb_device_filter.xml is a real fix and worth a line in the description. The manifest hands that file to Android for USB_DEVICE_ATTACHED, and DeviceFilter.read parses the ids as decimal unless they start with 0x. The file had decimal ids in 2024, then c8283b4 rewrote them as bare hex and the parser has been throwing on the first 0BDA ever since, so the attach filter matches nothing. Our own parser reads hex, which is why hot plug inside the app kept working.

rtl_devices isn't locked anywhere. The insert was already racy, but the PR adds erase(fd) on every exit path of run() while stop() and nativeSetTxPower read the map from the Java side, and the erase lands right when stop() tends to be called. A mutex around the map accesses is due.

The stop_requested_fds check after CreateRtlDevice and again before StartRxLoop is good. #116 hits the same race and will probably land first; the overlap is one hunk in stop(), your stop-request logic stays as the fix. CRASH() is still defined and now unused.

iflyhere added a commit to iflyhere/PixelPilot that referenced this pull request Sep 2, 2026
The bounded join fixed the ANR but not the reason the join was timing out in the first place,
as pointed out in review.

StopRxLoop() only sets a flag, and RtlJaguarDevice::StartRxLoop() clears it on entry. So a
stop is thrown away anywhere between the fd being handed to run() and the loop actually
starting - which includes the whole chip bring-up in InitWrite(), the longest part of run().
Until CreateRtlDevice() there is not even an entry in rtl_devices for stop() to find, so it
returns "already gone" and does nothing at all. run() then blocks in a loop nobody asked for.

stop() now records the fd in stop_requested_fds before anything else, and run() checks it at
the two points where the flag itself cannot be trusted: after CreateRtlDevice(), and again
immediately before entering the loop. Skipping the loop falls through to the same teardown a
StopRxLoop() would have taken. run() clears the entry on the way in, because fd numbers are
reused and a stale request must not abort a new session. This narrows the window to a few
instructions rather than closing it - closing it needs devourer to stop clearing the flag.

The second half was the timeout path itself. libusb_wrap_sys_device() keeps the fd it is given
rather than duplicating it - the comment claiming otherwise was wrong - so closing the
UsbDeviceConnection after a timed-out join pulled the fd out from under a libusb that was
still polling it. The kernel cancels the URBs on close, but libusb never reaps them, because
op_handle_events() checks POLLERR and not POLLNVAL: poll() then returns immediately forever
and the loop spins on one core waiting for a transfer count that never drops. Dropping the map
entries at the same time hid it from the duplicate check in start(), so the next openDevice()
would most likely be handed the same fd number back and overwrite rtl_devices[fd] underneath
the spinning thread.

So a timed-out join now leaves both the thread and its connection in place. start() refuses a
second RX loop on that device, and releases the connection once the old thread has actually
finished.

Still worth a follow-up: 3s of join on the main thread from onPause is under the ANR limit but
visible. Moving the stop off the main thread would remove it.

OpenIPC#105 touches WfbngLink::stop too, so whichever lands second will need a rebase.
vertexodessa pushed a commit that referenced this pull request Sep 2, 2026
* Harden the USB adapter lifecycle

Four separate ways the adapter path can take the app down or wedge it. All of
them are easy to hit on a powered hub that re-enumerates the dongle, which is
how a lot of ground stations are wired.

1. Deliberate null deref. WfbngLink::stop() ran a CRASH() macro
   (`int *i = 0; *i = 42;`) when the fd was no longer in rtl_devices. That is
   a recoverable state - the adapter was already gone - and it killed the
   process. Removed, now a warning and return.

2. NPE on openDevice(). UsbManager.openDevice() returns null when the
   permission was revoked or the device disappeared between the permission
   check and the open; getFileDescriptor() was called on it unconditionally.
   start() now returns false instead, WfbLinkManager reports it and leaves the
   adapter out of activeWifiAdapters so the next refresh retries it. Before,
   a failed adapter was recorded as active and never retried.

3. Leaked usbfs descriptors. UsbDeviceConnection was never closed and
   linkConns was never cleared, so every attach/detach cycle leaked one fd
   plus the map entry.

4. USB permission dialog on Android 14. requestPermission() got a
   PendingIntent built from an implicit Intent. Android 14 refuses to deliver
   those to a runtime registered receiver, so the result never arrived and the
   app sat on "No permission for wifi adapter(s)". setPackage() added.

Also: refreshAdapters() dereferenced getAttachedAdapters() without checking
for the null it returns when the device filter fails to parse, and the wfb
thread name indexed split()[1] without checking the device name matched
/dev/bus/usb/.

* Bound the join on the driver thread, and refuse a duplicate RX loop

Found on a Quest 3 while the app was unresponsive: the main thread was asleep
inside stopAll()'s t.join() and Android killed the window with

  Input dispatching timed out ... Waited 5000ms for MotionEvent
  ANR in com.openipc.pixelpilot (com.openipc.pixelpilot/.VideoActivity)

stopAdapters() is called from onPause(), onStop() and the channel/bandwidth
menus, so this join runs on the main thread. StopRxLoop() only breaks the
receive loop; the thread then still has to stop the TX frame and the adaptive
link, power the chip down, release the USB interface and exit libusb. If any of
that does not come back, the UI is frozen until the watchdog fires.

The join is now bounded at 3000 ms - about what a healthy unwind needs - and
logs when a thread outstays it instead of hanging the UI.

Also: start() refuses a device that already has a live thread. linkThreads.put()
overwrites the entry, so an older thread would be orphaned, never joined, and
its interface never released.

* Do not blame the device filter when the adapter merely failed to start

Recording an adapter as active only when it actually came up means an empty
activeWifiAdapters now covers two different problems: nothing compatible is
attached, or something compatible is attached and could not be opened. Showing
"No compatible wifi adapter found." for both sends people looking for a
usb_device_filter.xml entry that is already there.

* Do not lose a stop that arrives before the rx loop is running

The bounded join fixed the ANR but not the reason the join was timing out in the first place,
as pointed out in review.

StopRxLoop() only sets a flag, and RtlJaguarDevice::StartRxLoop() clears it on entry. So a
stop is thrown away anywhere between the fd being handed to run() and the loop actually
starting - which includes the whole chip bring-up in InitWrite(), the longest part of run().
Until CreateRtlDevice() there is not even an entry in rtl_devices for stop() to find, so it
returns "already gone" and does nothing at all. run() then blocks in a loop nobody asked for.

stop() now records the fd in stop_requested_fds before anything else, and run() checks it at
the two points where the flag itself cannot be trusted: after CreateRtlDevice(), and again
immediately before entering the loop. Skipping the loop falls through to the same teardown a
StopRxLoop() would have taken. run() clears the entry on the way in, because fd numbers are
reused and a stale request must not abort a new session. This narrows the window to a few
instructions rather than closing it - closing it needs devourer to stop clearing the flag.

The second half was the timeout path itself. libusb_wrap_sys_device() keeps the fd it is given
rather than duplicating it - the comment claiming otherwise was wrong - so closing the
UsbDeviceConnection after a timed-out join pulled the fd out from under a libusb that was
still polling it. The kernel cancels the URBs on close, but libusb never reaps them, because
op_handle_events() checks POLLERR and not POLLNVAL: poll() then returns immediately forever
and the loop spins on one core waiting for a transfer count that never drops. Dropping the map
entries at the same time hid it from the duplicate check in start(), so the next openDevice()
would most likely be handed the same fd number back and overwrite rtl_devices[fd] underneath
the spinning thread.

So a timed-out join now leaves both the thread and its connection in place. start() refuses a
second RX loop on that device, and releases the connection once the old thread has actually
finished.

Still worth a follow-up: 3s of join on the main thread from onPause is under the ANR limit but
visible. Moving the stop off the main thread would remove it.

#105 touches WfbngLink::stop too, so whichever lands second will need a rebase.
@RomanLut

RomanLut commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Rebased, rtl8822bu works.

Crashes on unplug - due to bug in devourer. Devourer has many updates recently. It makes sense to wait for it to stabilize then retest and fix rtl_devices race.

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