From 466f0138dbe9df16d9d6e1fedd6adc9c1784849c Mon Sep 17 00:00:00 2001 From: Oleksandr Suvorov Date: Sat, 23 May 2026 11:53:18 +0300 Subject: [PATCH 1/2] bladeRF2: track TX mute state per-device 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 --- .../libbladeRF/src/board/bladerf2/common.h | 5 ++ .../libbladeRF/src/board/bladerf2/rfic_host.c | 81 ++++++++++++++++--- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/host/libraries/libbladeRF/src/board/bladerf2/common.h b/host/libraries/libbladeRF/src/board/bladerf2/common.h index 4f86f0f33..37c7dfa88 100644 --- a/host/libraries/libbladeRF/src/board/bladerf2/common.h +++ b/host/libraries/libbladeRF/src/board/bladerf2/common.h @@ -186,6 +186,11 @@ struct bladerf2_board_data { bladerf_rfic_rxfir rxfir; bladerf_rfic_txfir txfir; + /* Per-device TX mute state, indexed by RFIC channel (0,1). Tracked here + * rather than as a process-wide static so that multiple bladeRFs opened in + * the same process don't share the same mute-gating bookkeeping. */ + bool tx_mute_state[2]; + /* If true, RFIC control will be fully de-initialized on close, instead of * just put into a standby state. */ bool rfic_reset_on_close; diff --git a/host/libraries/libbladeRF/src/board/bladerf2/rfic_host.c b/host/libraries/libbladeRF/src/board/bladerf2/rfic_host.c index 6e61c8fba..88d44aff1 100644 --- a/host/libraries/libbladeRF/src/board/bladerf2/rfic_host.c +++ b/host/libraries/libbladeRF/src/board/bladerf2/rfic_host.c @@ -229,6 +229,71 @@ int rfic_host_start_tx_recal_update(struct bladerf *dev, return 0; } +/******************************************************************************/ +/* TX mute (per-device) */ +/* */ +/* These mirror txmute_get/txmute_set in fpga_common/src/ad936x_helpers.c, */ +/* but keep the gating state in struct bladerf2_board_data rather than a */ +/* process-wide static so that two bladeRFs opened in the same process do not */ +/* alias each other's mute-state bookkeeping. The fpga_common variants remain */ +/* in use by the NIOS II RFIC controller, which only ever sees one device. */ +/******************************************************************************/ + +static int _host_txmute_get(struct bladerf *dev, + bladerf_channel ch, + bool *state) +{ + struct bladerf2_board_data *board_data = dev->board_data; + int rfic_ch = (ch >> 1); + + *state = board_data->tx_mute_state[rfic_ch]; + return 0; +} + +static int _host_txmute_set(struct bladerf *dev, + bladerf_channel ch, + bool state) +{ + struct bladerf2_board_data *board_data = dev->board_data; + struct ad9361_rf_phy *phy = board_data->phy; + int rfic_ch = (ch >> 1); + uint32_t const MUTED_ATTEN = 89750; + uint32_t atten, cached; + int status; + + if (board_data->tx_mute_state[rfic_ch] == state) { + return 0; + } + + if (state) { + uint32_t readval; + + status = ad9361_get_tx_attenuation(phy, rfic_ch, &readval); + if (status < 0) { + return errno_ad9361_to_bladerf(status); + } + + cached = readval; + atten = MUTED_ATTEN; + } else { + cached = txmute_get_cached(phy, ch); + atten = cached; + } + + status = ad9361_set_tx_attenuation(phy, rfic_ch, atten); + if (status < 0) { + return errno_ad9361_to_bladerf(status); + } + + status = txmute_set_cached(phy, ch, cached); + if (status < 0) { + return status; + } + + board_data->tx_mute_state[rfic_ch] = state; + return 0; +} + static int _rfic_host_initialize(struct bladerf *dev) { struct bladerf2_board_data *board_data = dev->board_data; @@ -425,7 +490,7 @@ static int _rfic_host_enable_module(struct bladerf *dev, /* Set/unset TX mute */ if (BLADERF_CHANNEL_IS_TX(ch)) { - txmute_set(phy, ch, !enable); + _host_txmute_set(dev, ch, !enable); } } @@ -786,7 +851,7 @@ static int _rfic_host_get_gain(struct bladerf *dev, if (BLADERF_CHANNEL_IS_TX(ch)) { bool muted; - CHECK_STATUS(txmute_get(phy, ch, &muted)); + CHECK_STATUS(_host_txmute_get(dev, ch, &muted)); if (muted) { struct bladerf_range const *range = NULL; @@ -824,7 +889,7 @@ static int _rfic_host_set_gain(struct bladerf *dev, if (BLADERF_CHANNEL_IS_TX(ch)) { bool muted; - CHECK_STATUS(txmute_get(phy, ch, &muted)); + CHECK_STATUS(_host_txmute_get(dev, ch, &muted)); if (muted) { struct bladerf_range const *range = NULL; @@ -1091,11 +1156,8 @@ static int _rfic_host_get_txmute(struct bladerf *dev, bladerf_channel ch, bool *state) { - struct bladerf2_board_data *board_data = dev->board_data; - struct ad9361_rf_phy *phy = board_data->phy; - if (BLADERF_CHANNEL_IS_TX(ch)) { - return txmute_get(phy, ch, state); + return _host_txmute_get(dev, ch, state); } return BLADERF_ERR_UNSUPPORTED; @@ -1105,11 +1167,8 @@ static int _rfic_host_set_txmute(struct bladerf *dev, bladerf_channel ch, bool state) { - struct bladerf2_board_data *board_data = dev->board_data; - struct ad9361_rf_phy *phy = board_data->phy; - if (BLADERF_CHANNEL_IS_TX(ch)) { - return txmute_set(phy, ch, state); + return _host_txmute_set(dev, ch, state); } return BLADERF_ERR_UNSUPPORTED; From f04a97fd116961c1ea9a4be0e5895c01ad0bd8b8 Mon Sep 17 00:00:00 2001 From: Oleksandr Suvorov Date: Sat, 23 May 2026 11:53:47 +0300 Subject: [PATCH 2/2] fpga_common: gate single-device txmute helpers behind BLADERF_NIOS_BUILD 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 --- fpga_common/include/ad936x_helpers.h | 6 ++++++ fpga_common/src/ad936x_helpers.c | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/fpga_common/include/ad936x_helpers.h b/fpga_common/include/ad936x_helpers.h index bd0037a55..043483593 100644 --- a/fpga_common/include/ad936x_helpers.h +++ b/fpga_common/include/ad936x_helpers.h @@ -59,6 +59,11 @@ int txmute_set_cached(struct ad9361_rf_phy *phy, bladerf_channel ch, uint32_t atten); +/* txmute_get/txmute_set track mute state in a file-scope static and are only + * safe when a single bladeRF exists per process. They remain available to the + * NIOS II RFIC controller (one device per FPGA); the host library uses + * per-device equivalents declared in board/bladerf2/rfic_host.c. */ +#if defined(BLADERF_NIOS_BUILD) /** * @brief Get the transmit mute state * @@ -85,6 +90,7 @@ int txmute_get(struct ad9361_rf_phy *phy, bladerf_channel ch, bool *state); * @return 0 on success, value from \ref RETCODES list on failure */ int txmute_set(struct ad9361_rf_phy *phy, bladerf_channel ch, bool state); +#endif /** * @brief Set AD9361 RFIC RF port diff --git a/fpga_common/src/ad936x_helpers.c b/fpga_common/src/ad936x_helpers.c index 15982faa5..9c882ea98 100644 --- a/fpga_common/src/ad936x_helpers.c +++ b/fpga_common/src/ad936x_helpers.c @@ -33,7 +33,15 @@ #include "ad936x_helpers.h" #include "bladerf2_common.h" +/* The NIOS II RFIC controller only ever serves one bladeRF, so a single + * file-scope state array suffices there. On the host, where multiple + * bladeRFs may be open in the same process, the equivalent per-device + * bookkeeping lives in struct bladerf2_board_data (see _host_txmute_get/ + * _host_txmute_set in board/bladerf2/rfic_host.c); the symbols below are + * therefore not built into the host library. */ +#if defined(BLADERF_NIOS_BUILD) static bool tx_mute_state[2] = { false }; +#endif uint32_t txmute_get_cached(struct ad9361_rf_phy *phy, bladerf_channel ch) { @@ -63,6 +71,7 @@ int txmute_set_cached(struct ad9361_rf_phy *phy, } } +#if defined(BLADERF_NIOS_BUILD) int txmute_get(struct ad9361_rf_phy *phy, bladerf_channel ch, bool *state) { int rfic_ch = (ch >> 1); @@ -115,6 +124,7 @@ int txmute_set(struct ad9361_rf_phy *phy, bladerf_channel ch, bool state) return 0; } +#endif /* BLADERF_NIOS_BUILD */ int set_ad9361_port_by_freq(struct ad9361_rf_phy *phy, bladerf_channel ch,