Conversation
libbladeRF tears down the synchronous stream when a direction is
disabled: rfic_host.c calls sync_deinit() on !dir_enable, and bladerf1.c
does the same. That is documented ("this will shut down the underlying
asynchronous stream when enable = false"), but re-enabling the module
does not bring the stream back.
Every later sync_tx()/sync_rx() then fails with
sync tx invalid: not initialized
which gives no hint that sync_config() must be repeated. From the
caller's side the radio simply looks dead: measured on a TX1 -> 50 dB
pad -> RX1 loopback, the received level stopped responding to TX gain
(60 dB and -30 dB both gave -44.4 dB) and 65487 of 66033 transmit calls
failed.
Remember the last sync_config() arguments per direction and replay them
when the module is enabled again. Direction is taken from the low bit:
TX channels are 1 and 3, TX layouts are 1 and 3, RX are even.
Verified on hardware: the disable -> enable -> sync_tx sequence went from
ERR_INVAL to OK, and transmit errors dropped from 65487 to 0.
A stream configured with a *_META format carries per-buffer timestamps and flags. Passing metadata=None leaves libbladeRF with nowhere to report them, so the caller silently loses the timestamp it needs and bladerf_get_timestamp() keeps returning 0. Nothing in the error path points at the cause, so this reads as dead hardware rather than a mismatched call. Measured on a TX1 -> 50 dB pad -> RX1 loopback at 15.36 MSps: with the stream in a metadata format but metadata=None, the frame timestamp stayed at 762229041 across 8 consecutive reads and get_timestamp() returned 0. Consecutive gain steps then analysed the same buffer, so the receive level repeated in pairs (-34.9/-34.9, -20.2/-20.2 dBFS) and a gain ladder that is in fact monotonic came out looking broken. The stream format is already remembered per direction for the enable_module restore path, so the check costs nothing extra: sync_rx()/sync_tx() now raise instead of losing timestamps quietly. After fixing the call sites the same ladder is monotonic, with deviations of +0.1 to +0.8 dB over a 40 dB span.
The RFFE control register lives in the FPGA and carries the RF front-end state that no RFIC register reflects: SPDT switch positions, per-channel enables, and the direction ENABLE/TXNRX bits. Needed to tell "an SPDT was left in its shutdown position" from "the RFIC is fine but the signal is routed nowhere". Both look identical from the RFIC side: every register reads back correct while the output is dead. Requires the matching accessor in libbladeRF.
The FPGA configuration GPIO register carries the per-format mode bits (TIMESTAMP, PACKET, 8BIT_MODE, HIGHLY_PACKED) that both directions share. perform_format_config() writes the whole word from one direction's format while perform_format_deconfig() only forgets the format and leaves the register alone, so this is where a stream configuration on one direction could clobber the mode the other one needs. The declaration was already in cbladerf.pxd; only the method was missing. Used it to rule that out for a transmitter fault: the register reads 0x00010001 with TIMESTAMP set in the working state, in the disabled state, and in the broken one alike.
Without this the AD9361's internal state is unobservable from Python: the ENSM state, the digital datapath status and the filter enables live only in RFIC registers, while every FPGA-side register can read correct at the same time. libbladeRF already had bladerf_get_rfic_register and bladerf_set_rfic_register and cbladerf.pxd already declared both; only the binding was missing. Used to establish that RFIC state is bit-identical between a working and a dead transmit cycle (0x017, 0x05E, 0x002, 0x003, 0x001, 0x004, 0x073), and to drive the RFIC digital loopback via 0x3F5, which is what localized the fault to samples reaching the RFIC bus and then not advancing.
enable_module(dir, false) makes libbladeRF tear the sync stream down, so the wrapper repeats the last sync_config on re-enable. That is right for callers but hides the library's own behaviour, which makes it impossible to tell a wrapper problem from a library one. The switch turns the repeat off so both can be measured.
Both __rx_callback_SC16_Q11 and __rx_callback_SC8_Q7 read after only assigning it inside 'if callback is not None'. With no callback installed the read raises UnboundLocalError inside the C callback, which is declared noexcept nogil, so the exception is swallowed: the stream stops after its very first buffer, ends up in STREAM_DONE, and bladerf_start_stream() returns with no error for the caller to see. Default result to 0 (keep streaming). An async RX stream with no Python callback is a legitimate configuration - it is the cheapest way to drain a direction without doing any per-buffer work, which is exactly what I needed to separate data-plane load from control-plane calls while chasing a TX wedge.
bladerf_read_flash_bytes was declared but not exposed. Needed it to test whether a firmware-side PIB/GPIF reset clears a wedged TX feed, since a flash read is the one safe host call that makes the firmware run NuandConfigureGpif without closing the device.
The .pxd files carry the PyBladerfDevice layout and the libbladeRF declarations, and none of the four extensions listed them in depends. Editing a .pxd therefore left pybladerf_tools compiled against the previous struct layout, with nothing rebuilt and nothing to warn about until something crashed.
Counts the FPGA keeps of samples it had to drop (RX) or transmit as a hole (TX). Distinct from the OVERRUN metadata flag, which is derived on the host from USB queue state and never reads the fabric.
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.
libbladeRF tears down the synchronous stream when a direction is disabled:
rfic_host.ccallssync_deinit()on!dir_enable, andbladerf1.cdoes the same. That is documented behaviour — the header says "this will shut down the underlying asynchronous stream whenenable= false" — but re-enabling the module does not bring the stream back.Every later
sync_tx()/sync_rx()then fails withwhich gives no hint that
sync_config()has to be repeated.How it looks from the caller side
The radio simply appears dead. Measured on a TX1 → 50 dB pad → RX1 loopback:
Nothing in the error text points at
enable_module()as the cause, so this reads as broken hardware rather than a lifecycle contract.Fix
Remember the last
sync_config()arguments per direction and replay them when the module is enabled again.Direction is taken from the low bit: TX channels are 1 and 3, TX layouts are 1 and 3, RX are even — verified against the enum values on device.
Verification
On hardware, the previously failing sequence:
became:
and transmit errors over a full probe run dropped from 65487 to 0.