Skip to content

Fix TX gain gating corruption with multi-device TX mute state sharing#1065

Open
MrCry0 wants to merge 2 commits into
Nuand:masterfrom
MrCry0:fix/set-gain
Open

Fix TX gain gating corruption with multi-device TX mute state sharing#1065
MrCry0 wants to merge 2 commits into
Nuand:masterfrom
MrCry0:fix/set-gain

Conversation

@MrCry0

@MrCry0 MrCry0 commented May 23, 2026

Copy link
Copy Markdown

Bug

When multiple bladeRF2 devices are opened in the same process, TX gain updates may be silently skipped due to shared TX mute state stored in a process-wide static (tx_mute_state[2] in fpga_common).

_rfic_host_set_gain() decides whether to perform an AD9361 SPI write based on the current TX mute state. Because this state is shared across all devices, one device can incorrectly influence gain handling on another.

This leads to incorrect gating decisions where valid gain updates are dropped without error.

How to reproduce

Open two devices in the same process:

bladerf_open(devA)
bladerf_open(devB)

Set divergent TX mute states:

bladerf_enable_module(devA, TX, true)   // unmute A
bladerf_enable_module(devB, TX, false)  // mute B

Apply gain on device A:

bladerf_set_gain(devA, BLADERF_CHANNEL_TX, <value>)

Observe:

  • Gain update on device A is silently skipped
  • No error is returned
  • Hardware state of A does not change

This happens because tx_mute_state[] is shared globally and reflects device B’s mute state when A performs the gain update.

Fix

  • Move TX mute gating state from file-scope static into per-device storage (struct bladerf2_board_data)
  • Introduce _host_txmute_get/_host_txmute_set for correct per-device access
  • Update host RFIC gain/mute logic to use per-device state only
  • Gate legacy fpga_common txmute helpers behind BLADERF_NIOS_BUILD (NIOS-only usage)
  • Keep cached attenuation helpers unchanged for both targets

Result

TX gain operations are now correctly evaluated per device. Device A no longer depends on device B’s mute state, eliminating silent gain suppression in multi-device configurations.

MrCry0 added 2 commits May 23, 2026 15:27
bladerf_set_gain(dev, BLADERF_CHANNEL_TX(*), ...) branches in
_rfic_host_set_gain() on the current TX mute state: when unmuted it issues
an AD9361 SPI write through set_gain_stage("dsa"), and when muted it only
updates the cached attenuation. That decision was driven by txmute_get(),
which reads a process-wide file-scope static (tx_mute_state[2]) in
fpga_common/src/ad936x_helpers.c.

With two bladeRFs open in the same process, both phys share that single
2-element array. txmute_set() additionally short-circuits whenever the
requested state already matches the global, so the second device's
init/enable_module mute toggles are silently skipped. The two phys'
actual hardware state then drifts from the gating variable, and a
subsequent bladerf_set_gain() on one device can take the wrong branch:
the requested device's hardware is not written while the other device's
TX attenuation is the most recent SPI write to land, surfacing as "I set
gain on A and B's gain changed."

Move the gating state into struct bladerf2_board_data as
tx_mute_state[2] and introduce _host_txmute_get / _host_txmute_set in
rfic_host.c that read and write that per-device field while keeping the
existing AD9361 attenuation behaviour. Repoint every host call site
(_rfic_host_enable_module, _rfic_host_get_gain, _rfic_host_set_gain,
_rfic_host_get_txmute, _rfic_host_set_txmute) at the new wrappers. The
fpga_common helpers remain in place and continue to serve the NIOS II
RFIC controller, which only ever sees one device.

Signed-off-by: Oleksandr Suvorov <cryosay@gmail.com>
The static tx_mute_state[2] array and the txmute_get/txmute_set helpers
in fpga_common/src/ad936x_helpers.c are only correct when at most one
bladeRF exists in the address space, because the gating state lives in
file scope and is keyed solely by RFIC channel. That holds on the NIOS
II RFIC controller (one device per FPGA) but not in the host library,
where multiple bladeRFs can be open concurrently.

The host library now keeps its own per-device state and wrappers in
struct bladerf2_board_data / rfic_host.c, so the fpga_common variants
have no remaining host callers. Gate the static and both function
bodies/prototypes with #if defined(BLADERF_NIOS_BUILD) so they are only
built into the NIOS firmware. The cached-attenuation helpers
(txmute_get_cached / txmute_set_cached) are inherently per-phy and stay
available to both targets.

Signed-off-by: Oleksandr Suvorov <cryosay@gmail.com>
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