Skip to content

fix(driver): keep USB transfers within Linux's usbfs memory pool - #70

Merged
HalfSweet merged 6 commits into
mainfrom
linux-usb-alloc-error-26faf1c9
Oct 31, 2026
Merged

HalfSweet merged 6 commits into
mainfrom
linux-usb-alloc-error-26faf1c9

Conversation

@HalfSweet

Copy link
Copy Markdown
Owner

On Linux, SLogic captures at high sample rates failed after exactly 15 MiB with usb transfer: unknown (errno 12), while macOS was fine. Linux charges the in-flight transfers of every program to one pool, usbcore's usbfs_memory_mb (16 MiB by default). The SLogic driver queued sixteen 1 MiB requests, and the kernel's bookkeeping on top of the sixteenth no longer fit. nusb tried a zero-copy buffer first (the Failed to allocate zero-copy buffer warning), fell back to a heap buffer, and the submission failed with ENOMEM. nusb reports a failed submission in its turn, so the first 15 requests delivered their data and the capture failed on the sixteenth. The sample counts in the report match: 7,864,256 samples × 2 bytes and 15,728,576 × 1 byte are both 15 MiB, less the head bytes. DSLogic USB 3.0 Buffer mode (16 × 1 MiB) and PXLogic at its fastest USB 3.0 rates (four 4 MiB blocks) had the same problem.

Sipeed hit this in its own driver and reworked the transfer ring in libslogic 2.3.3 (sipeed/libsigrok slogic-dev ba92c5fe, sipeed/SLogic f06492f):

  • each request holds about 5 ms of data, from 32 KiB to 2 MiB;
  • the ring has up to 16 requests;
  • at least four must be in flight, because with fewer the analyzer's FIFO overflows and the firmware drops data without reporting it;
  • when usbfs refuses a request, the driver takes the ring back before RUN, waits, and halves the request size.

This PR follows that sizing. It plans the ring to fit instead of probing, because nusb reports a refused submission only in its turn, after RUN.

  • UsbError::NoMemory: Linux's ENOMEM gets its own variant, and the error key error.device.usbMemory tells the user to raise /sys/module/usbcore/parameters/usbfs_memory_mb instead of showing errno 12.
  • UsbTransport::transfer_budget: on Linux, three quarters of the pool, read from sysfs. That is 12 MiB by default, and no limit when the pool is set to 0 (unlimited). Other systems have no budget. Mock devices can set a budget for tests.
  • SLogic:
    • requests hold about 5 ms of data, between 32 KiB and 1 MiB (nusb always reads with WinUSB RAW_IO, so 1 MiB stays the largest request);
    • the ring has up to 16 requests within the budget, which is twelve 1 MiB requests by default;
    • in a pool too small for four, the requests shrink instead;
    • if the system still refuses a request, the capture goes on without it while at least four remain, as Sipeed's driver does. The refused request never reached the device, so no data is missing.
  • DSLogic and PXLogic: their transfer counts keep within the budget. DSLogic USB 3.0 Buffer mode gets twelve 1 MiB uploads instead of sixteen; PXLogic gets twelve 1 MiB transfers (three blocks) instead of sixteen. Nothing changes on macOS and Windows.
  • Docs: the user guide explains Linux's shared pool and what to do if a capture still stops; the SLogic crate README records the later Sipeed revision and how the ring departs from it.

Validation:

  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test for hsync-driver, hsync-slogic, hsync-dslogic, hsync-pxlogic, hsync-atklogic and hsync-session: 280 passed. New cases cover: the budget arithmetic; the ENOMEM mapping (the positive case runs only on Linux, i.e. in CI); SLogic plans under the default and small pools; the ring the transport budget allows; captures that go on through refused requests; and the usbfs hint when fewer than four requests remain.
  • pnpm test: 86 files, 609 tests passed, including the locale key parity
  • Not tested on Linux hardware: the fix is from a user's log and the reference driver; it still needs a capture on a Linux machine with an SLogic16U3 (and ideally a DSLogic U3 in Buffer mode)

…SB memory

Linux charges the in-flight transfers of every program to one pool,
usbcore's usbfs_memory_mb (16 MiB by default). A transfer that does not
fit fails with ENOMEM, which nusb passes on as an unknown error, so the
interface showed "USB communication failed: usb transfer: unknown
(errno 12)" and gave no hint what to do.

Map ENOMEM on Linux to a new UsbError::NoMemory, so drivers can tell a
refused transfer from a failed one, and show error.device.usbMemory,
which names the usbfs pool to raise. Other systems keep their codes:
12 means something else there.
On Linux every SLogic capture at a high sample rate failed after
exactly 15 MiB of data with "usb transfer: unknown (errno 12)". The
driver queued sixteen 1 MiB requests, and Linux charges in-flight
transfers to usbcore's usbfs pool, 16 MiB by default and shared by every
program: the sixteenth request, plus the kernel's bookkeeping, no longer
fit. macOS and Windows have no such pool.

Sipeed's driver hit the same limit and changed its ring in libsigrok
slogic-dev ba92c5fe: requests of about 5 ms of data, within
[32 KiB, 2 MiB], and at least four in flight, halving the requests when
usbfs refuses them. Follow that sizing, keeping 1 MiB as the largest
request (nusb always reads with WinUSB RAW_IO), and plan the ring
within a budget instead of probing: nusb reports a refused submission
only in its turn, after RUN, so it cannot be found and halved before
the capture starts.

UsbTransport::transfer_budget gives that budget: on Linux three
quarters of the pool read from /sys/module/usbcore/parameters/
usbfs_memory_mb (12 MiB by default, none when the pool is unlimited),
elsewhere none. The ring holds what fits, twelve 1 MiB requests by
default, and requests shrink until four fit in a smaller pool. Mock
devices can set a budget for tests.
…fers

The budget leaves a quarter of usbfs's pool to other programs, but one
that holds more can still make Linux refuse an SLogic request. Such a
request never reached the device, so no data is missing, yet the
capture failed on it.

Sipeed's driver keeps the requests it could submit as long as four are
in flight, since with fewer the analyzer's FIFO overflows and the
firmware drops data unreported. Do the same when nusb reports the
refusal in the request's turn: leave the request out of the ring, also
while re-arming, and only fail, with the usbfs hint, once fewer than
four would remain.
Buffer mode uploads on USB 3.0 models queue sixteen 1 MiB transfers,
the numbers DSView uses on Windows (it keeps one in flight elsewhere).
That overflows Linux's default 16 MiB usbfs pool, so the sixteenth
transfer failed and the upload stopped after 15 MiB. Stream mode at the
highest rates could come close as well.

Keep both modes within the transport's transfer budget: twelve 1 MiB
uploads under the default pool, unchanged elsewhere.
At the fastest USB 3.0 rates the driver keeps four 4 MiB blocks in
flight, read as sixteen 1 MiB transfers, which overflows Linux's
default 16 MiB usbfs pool: the sixteenth transfer failed.

Keep the transfers within the transport's transfer budget: twelve under
the default pool, three blocks' worth, unchanged elsewhere.
Tell users that Linux shares one pool of USB transfer memory between
programs, that HSync keeps within three quarters of it, and what to do
when a capture still stops for lack of it. The SLogic crate's README
records the later Sipeed revision its transfer ring follows and how the
ring departs from it.
@HalfSweet
HalfSweet merged commit bc2887c into main Oct 31, 2026
3 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 31, 2026
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.

1 participant