Merge antenna muting mechanism onto 2026.w17
Brings the O-RAN compliant antenna muting feature (originally introduced
on the 'new' branch) onto the latest upstream 2026.w17 tag.
Files added:
openair1/PHY/NR_TRANSPORT/nr_antenna_state.{c,h}
openair1/PHY/NR_TRANSPORT/nr_antenna_control.{c,h}
Files modified (integration hooks):
CMakeLists.txt, executables/nr-gnb.c
openair1/PHY/INIT/nr_init.c, openair1/PHY/defs_gNB.h
openair1/PHY/MODULATION/{beamforming.c,nr_modulation.h,nr_beamforming.c}
openair1/SCHED_NR/nr_ru_procedures.c
openair2/LAYER2/NR_MAC_gNB/mac_proto.h
openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_{primitives,dlsch,uci}.c
Conflicts resolved:
- defs_gNB.h: kept upstream's new headers + appended antenna_state.h
- nr_init.c: dropped reference to upstream-removed nr_srs_info init,
kept nr_init_antenna_state() call
- beamforming.c: upstream moved nr_beam_precoding into new file
nr_beamforming.c; nr_beam_precoding_with_muting added there with
upstream's signature (no slot param)
- nr_modulation.h: aligned _with_muting decl to upstream signature
- nr_ru_procedures.c: muting check applied around upstream's new
fft_shift in nr_feptx, and around per-port memcpy in nr_feptx_prec
- gNB_scheduler_uci.c: SINR-based MCS hint uses get_dl_nrOfLayers(NULL,
sched_ctrl, ...) since nrmac is not in scope at the call site
- mac_proto.h: get_dl_nrOfLayers signature updated to take gNB_MAC_INST*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -919,6 +919,7 @@ set(PHY_SRC_RU
|
||||
${OPENAIR1_DIR}/PHY/MODULATION/gen_75KHz.cpp
|
||||
${OPENAIR1_DIR}/PHY/MODULATION/beamforming.c
|
||||
${OPENAIR1_DIR}/PHY/MODULATION/compute_bf_weights.c
|
||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c
|
||||
${OPENAIR1_DIR}/PHY/INIT/lte_init_ru.c
|
||||
${OPENAIR1_DIR}/PHY/LTE_TRANSPORT/prach.c
|
||||
)
|
||||
@@ -998,6 +999,7 @@ set(PHY_SRC_UE
|
||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_prach.c
|
||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_llr_computation.c
|
||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_ulsch_demodulation.c
|
||||
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_control.c
|
||||
${OPENAIR1_DIR}/PHY/NR_REFSIG/ul_ref_seq_nr.c
|
||||
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_dmrs_rx.c
|
||||
${OPENAIR1_DIR}/PHY/NR_REFSIG/nr_gen_mod_table.c
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
||||
#include "PHY/INIT/nr_phy_init.h"
|
||||
#include "PHY/MODULATION/nr_modulation.h"
|
||||
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
|
||||
#include "PHY/TOOLS/tools_defs.h"
|
||||
#include "PHY/defs_RU.h"
|
||||
@@ -372,6 +374,14 @@ void init_eNB_afterRU(void)
|
||||
*/
|
||||
//init_precoding_weights(RC.gNB[inst]);
|
||||
init_gNB_Tpool(inst);
|
||||
|
||||
// antenna control
|
||||
if (nr_antenna_control_start_test_server(&gNB->antenna_state, 9999))
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
|
||||
if (inst < RC.nb_nr_macrlc_inst && RC.nrmac && RC.nrmac[inst]) {
|
||||
RC.nrmac[inst]->antenna_state = &gNB->antenna_state;
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Connected MAC scheduler to PHY antenna state for dynamic layer adaptation\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,9 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
|
||||
|
||||
init_DLSCH_struct(gNB);
|
||||
|
||||
nr_init_antenna_state(&gNB->antenna_state, Ptx, Prx, "tx-array-0", "rx-array-0");
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Initialized with %d TX, %d RX antennas\n", Ptx, Prx);
|
||||
|
||||
/* Do NOT allocate per-antenna rxdataF: the gNB gets a pointer to the
|
||||
* RU to copy/recover freq-domain memory from there */
|
||||
common_vars->rxdataF = (c16_t ***)malloc16(common_vars->num_beams_period * sizeof(c16_t**));
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PHY/LTE_TRANSPORT/transport_eNB.h"
|
||||
#include "modulation_eNB.h"
|
||||
#include "common/utils/LOG/vcd_signal_dumper.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||
|
||||
int beam_precoding(int32_t **txdataF,
|
||||
int32_t **txdataF_BF,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "nr_modulation.h"
|
||||
#include "openair1/PHY/TOOLS/tools_defs.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||
#include "common/utils/LOG/log.h"
|
||||
|
||||
void nr_beam_precoding(c16_t **txdataF,
|
||||
c16_t **txdataF_BF,
|
||||
@@ -25,3 +27,33 @@ void nr_beam_precoding(c16_t **txdataF,
|
||||
15);
|
||||
}
|
||||
}
|
||||
|
||||
void nr_beam_precoding_with_muting(c16_t **txdataF,
|
||||
c16_t **txdataF_BF,
|
||||
NR_DL_FRAME_PARMS *frame_parms,
|
||||
int32_t ***beam_weights,
|
||||
int symbol,
|
||||
int aa,
|
||||
int nb_antenna_ports,
|
||||
int offset,
|
||||
const nr_antenna_state_t *ant_state)
|
||||
{
|
||||
// Clear output buffer first
|
||||
memset(&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size], 0,
|
||||
sizeof(c16_t) * frame_parms->ofdm_symbol_size);
|
||||
// O-RAN energy saving: skip precoding if antenna is muted
|
||||
// This implements transmission blanking per WG4 spec
|
||||
if (ant_state != NULL && !nr_is_tx_antenna_active(ant_state, aa)) {
|
||||
LOG_D(PHY, "[ANTENNA_MUTE] Antenna %d muted, blanking transmission for symbol %d\n",
|
||||
aa, symbol);
|
||||
return;
|
||||
}
|
||||
// Normal precoding for active antennas
|
||||
for (int p = 0; p < nb_antenna_ports; p++) {
|
||||
multadd_cpx_vector((c16_t *)&txdataF[p][(symbol * frame_parms->ofdm_symbol_size) + offset],
|
||||
(c16_t *)beam_weights[p][aa],
|
||||
(c16_t *)&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size],
|
||||
frame_parms->ofdm_symbol_size,
|
||||
15);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,33 @@ void nr_beam_precoding(c16_t **txdataF,
|
||||
int nb_antenna_ports,
|
||||
int offset);
|
||||
|
||||
|
||||
/*! \brief NR beam precoding with O-RAN compliant antenna muting
|
||||
*
|
||||
* Extends nr_beam_precoding to support antenna muting for energy saving
|
||||
* per O-RAN WG4 MPLANE-TRX-CONTROL specification (o-ran-uplane-conf.yang).
|
||||
* When an antenna is muted, transmission is blanked (output zeroed).
|
||||
*
|
||||
* @param[in] txdataF Input frequency-domain data per antenna port
|
||||
* @param[out] txdataF_BF Output beamformed data per physical antenna
|
||||
* @param[in] frame_parms Frame parameters
|
||||
* @param[in] beam_weights Beamforming weights
|
||||
* @param[in] symbol Current symbol number
|
||||
* @param[in] aa Physical antenna index
|
||||
* @param[in] nb_antenna_ports Number of antenna ports
|
||||
* @param[in] offset Frequency offset
|
||||
* @param[in] ant_state Antenna state for muting control (NULL = all active)
|
||||
*/
|
||||
void nr_beam_precoding_with_muting(c16_t **txdataF,
|
||||
c16_t **txdataF_BF,
|
||||
NR_DL_FRAME_PARMS *frame_parms,
|
||||
int32_t ***beam_weights,
|
||||
int symbol,
|
||||
int aa,
|
||||
int nb_antenna_ports,
|
||||
int offset,
|
||||
const nr_antenna_state_t *ant_state);
|
||||
|
||||
void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp,
|
||||
c16_t *txdataF,
|
||||
bool is_flat_buff,
|
||||
|
||||
@@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The OpenAirInterface Software Alliance licenses this file to You under
|
||||
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.openairinterface.org/?page_id=698
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*-------------------------------------------------------------------------------
|
||||
* For more information about the OpenAirInterface (OAI) Software Alliance:
|
||||
* contact@openairinterface.org
|
||||
*/
|
||||
|
||||
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c
|
||||
* \brief Antenna control interface implementation with O-RAN M-Plane support
|
||||
* \author OpenAirInterface
|
||||
* \date 2025
|
||||
* \version 2.0
|
||||
*/
|
||||
|
||||
#include "nr_antenna_control.h"
|
||||
#include "common/utils/LOG/log.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
// Try to include M-Plane header if available
|
||||
#ifdef OAI_MPLANE
|
||||
#include "radio/fhi_72/mplane/antenna-control.h"
|
||||
#include "radio/fhi_72/mplane/ru-mplane-api.h"
|
||||
#endif
|
||||
|
||||
// Global control state
|
||||
static struct {
|
||||
pthread_t thread;
|
||||
int server_fd;
|
||||
nr_antenna_state_t *ant_state;
|
||||
volatile bool running;
|
||||
int port;
|
||||
nr_antenna_ctrl_backend_t backend;
|
||||
#ifdef OAI_MPLANE
|
||||
ru_session_t *ru_session;
|
||||
#else
|
||||
void *ru_session;
|
||||
#endif
|
||||
} g_ctrl = {0};
|
||||
|
||||
// ============================================================================
|
||||
// Test Socket Server Implementation (Development/Testing Mode)
|
||||
// ============================================================================
|
||||
|
||||
// Process a single command and generate response
|
||||
static void process_command(const char *cmd, char *response, size_t resp_size)
|
||||
{
|
||||
char cmd_upper[256];
|
||||
strncpy(cmd_upper, cmd, sizeof(cmd_upper) - 1);
|
||||
cmd_upper[sizeof(cmd_upper) - 1] = '\0';
|
||||
|
||||
// Convert to uppercase for case-insensitive matching
|
||||
for (char *p = cmd_upper; *p; p++) {
|
||||
if (*p >= 'a' && *p <= 'z') *p -= 32;
|
||||
}
|
||||
|
||||
// Trim whitespace
|
||||
char *p = cmd_upper;
|
||||
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
|
||||
char *end = p + strlen(p) - 1;
|
||||
while (end > p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0';
|
||||
|
||||
if (strncmp(p, "MUTE TX ", 8) == 0) {
|
||||
int ant_id = atoi(p + 8);
|
||||
nr_mute_tx_antenna(g_ctrl.ant_state, ant_id);
|
||||
snprintf(response, resp_size, "OK: TX antenna %d muted\n", ant_id);
|
||||
}
|
||||
else if (strncmp(p, "UNMUTE TX ", 10) == 0) {
|
||||
int ant_id = atoi(p + 10);
|
||||
nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id);
|
||||
snprintf(response, resp_size, "OK: TX antenna %d unmuted\n", ant_id);
|
||||
}
|
||||
else if (strncmp(p, "MUTE RX ", 8) == 0) {
|
||||
int ant_id = atoi(p + 8);
|
||||
nr_mute_rx_antenna(g_ctrl.ant_state, ant_id);
|
||||
snprintf(response, resp_size, "OK: RX antenna %d muted\n", ant_id);
|
||||
}
|
||||
else if (strncmp(p, "UNMUTE RX ", 10) == 0) {
|
||||
int ant_id = atoi(p + 10);
|
||||
nr_unmute_rx_antenna(g_ctrl.ant_state, ant_id);
|
||||
snprintf(response, resp_size, "OK: RX antenna %d unmuted\n", ant_id);
|
||||
}
|
||||
else if (strncmp(p, "SET TX MASK ", 12) == 0) {
|
||||
unsigned int mask;
|
||||
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
|
||||
uint8_t old = nr_set_tx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
|
||||
snprintf(response, resp_size, "OK: TX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
|
||||
} else {
|
||||
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
|
||||
}
|
||||
}
|
||||
else if (strncmp(p, "SET RX MASK ", 12) == 0) {
|
||||
unsigned int mask;
|
||||
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
|
||||
uint8_t old = nr_set_rx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
|
||||
snprintf(response, resp_size, "OK: RX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
|
||||
} else {
|
||||
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
|
||||
}
|
||||
}
|
||||
else if (strcmp(p, "STATUS") == 0) {
|
||||
bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state);
|
||||
snprintf(response, resp_size,
|
||||
"=== Antenna Status ===\n"
|
||||
"TX Array: %s\n"
|
||||
" Antennas: %d configured, %d active\n"
|
||||
" Bitmask: 0x%02X\n"
|
||||
" Antenna 0 protected: %s\n"
|
||||
"RX Array: %s\n"
|
||||
" Antennas: %d configured, %d active\n"
|
||||
" Bitmask: 0x%02X\n"
|
||||
"Recovery Mode: %s\n"
|
||||
"Backend: %s\n",
|
||||
g_ctrl.ant_state->tx_array_name,
|
||||
g_ctrl.ant_state->num_tx_antennas,
|
||||
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
|
||||
nr_get_tx_antenna_bitmask(g_ctrl.ant_state),
|
||||
g_ctrl.ant_state->config.protect_antenna0 ? "YES" : "NO",
|
||||
g_ctrl.ant_state->rx_array_name,
|
||||
g_ctrl.ant_state->num_rx_antennas,
|
||||
nr_get_active_rx_antenna_count(g_ctrl.ant_state),
|
||||
nr_get_rx_antenna_bitmask(g_ctrl.ant_state),
|
||||
in_recovery ? "ACTIVE (use identity matrix)" : "OFF",
|
||||
g_ctrl.backend == ANTENNA_CTRL_BACKEND_MPLANE ? "O-RAN M-Plane NETCONF" :
|
||||
g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET ? "Test Socket (NOT O-RAN)" :
|
||||
"None");
|
||||
}
|
||||
else if (strcmp(p, "STATS") == 0) {
|
||||
uint64_t tx_events, rx_events, muted_slots;
|
||||
nr_get_antenna_stats(g_ctrl.ant_state, &tx_events, &rx_events, &muted_slots);
|
||||
snprintf(response, resp_size,
|
||||
"=== Energy Saving Statistics ===\n"
|
||||
"TX mute events: %lu\n"
|
||||
"RX mute events: %lu\n"
|
||||
"Total muted slots: %lu\n",
|
||||
tx_events, rx_events, muted_slots);
|
||||
}
|
||||
else if (strcmp(p, "RESET STATS") == 0) {
|
||||
nr_reset_antenna_stats(g_ctrl.ant_state);
|
||||
snprintf(response, resp_size, "OK: Statistics reset\n");
|
||||
}
|
||||
else if (strcmp(p, "RESET") == 0 || strcmp(p, "UNMUTE ALL") == 0) {
|
||||
// Reset all antennas to active state with recovery mode
|
||||
nr_unmute_all_tx_antennas(g_ctrl.ant_state);
|
||||
nr_unmute_all_rx_antennas(g_ctrl.ant_state);
|
||||
snprintf(response, resp_size,
|
||||
"OK: All antennas unmuted (recovery mode: %u ms)\n"
|
||||
" TX mask: 0x%02X (%d antennas)\n"
|
||||
" RX mask: 0x%02X (%d antennas)\n",
|
||||
g_ctrl.ant_state->recovery.recovery_duration_ms,
|
||||
nr_get_tx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_tx_antennas,
|
||||
nr_get_rx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_rx_antennas);
|
||||
}
|
||||
else if (strcmp(p, "PROTECT ANT0 ON") == 0) {
|
||||
nr_set_antenna0_protection(g_ctrl.ant_state, true);
|
||||
snprintf(response, resp_size, "OK: Antenna 0 protection enabled (SSB)\n");
|
||||
}
|
||||
else if (strcmp(p, "PROTECT ANT0 OFF") == 0) {
|
||||
nr_set_antenna0_protection(g_ctrl.ant_state, false);
|
||||
snprintf(response, resp_size, "OK: Antenna 0 protection disabled (WARNING: may cause UE sync loss)\n");
|
||||
}
|
||||
else if (strcmp(p, "SYNC") == 0) {
|
||||
// Synchronize to RU (only works with M-Plane backend)
|
||||
if (nr_antenna_control_sync_to_ru(g_ctrl.ant_state)) {
|
||||
snprintf(response, resp_size, "OK: Synchronized to RU\n");
|
||||
} else {
|
||||
snprintf(response, resp_size, "ERROR: Sync failed (M-Plane backend required)\n");
|
||||
}
|
||||
}
|
||||
else if (strcmp(p, "HELP") == 0) {
|
||||
snprintf(response, resp_size,
|
||||
"=== Antenna Control ===\n"
|
||||
"MUTE TX/RX <id> - Mute antenna\n"
|
||||
"UNMUTE TX/RX <id> - Unmute antenna\n"
|
||||
"SET TX/RX MASK <hex> - Set bitmask\n"
|
||||
"STATUS - Show state\n"
|
||||
"STATS - Show statistics\n"
|
||||
"RESET - Unmute all\n"
|
||||
"PROTECT ANT0 ON/OFF\n"
|
||||
"HELP/QUIT\n");
|
||||
}
|
||||
else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) {
|
||||
snprintf(response, resp_size, "BYE\n");
|
||||
}
|
||||
else if (strlen(p) == 0) {
|
||||
response[0] = '\0'; // Empty command, no response
|
||||
}
|
||||
else {
|
||||
snprintf(response, resp_size, "ERROR: Unknown command '%s'. Type HELP for usage.\n", cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a single client connection
|
||||
static void handle_client(int client_fd)
|
||||
{
|
||||
char buffer[256];
|
||||
char response[1024];
|
||||
|
||||
// Send welcome message with warning about non-compliance
|
||||
const char *welcome =
|
||||
"=== OAI Antenna Control Test Server ===\n"
|
||||
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
|
||||
"For production, use NETCONF via radio/fhi_72/mplane/\n"
|
||||
"Type HELP for commands.\n\n";
|
||||
send(client_fd, welcome, strlen(welcome), 0);
|
||||
|
||||
while (g_ctrl.running) {
|
||||
// Send prompt
|
||||
send(client_fd, "> ", 2, 0);
|
||||
|
||||
// Receive command
|
||||
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
if (n <= 0) break;
|
||||
|
||||
buffer[n] = '\0';
|
||||
|
||||
// Process command
|
||||
process_command(buffer, response, sizeof(response));
|
||||
|
||||
// Send response
|
||||
if (strlen(response) > 0) {
|
||||
send(client_fd, response, strlen(response), 0);
|
||||
}
|
||||
|
||||
// Check for quit
|
||||
if (strstr(buffer, "QUIT") || strstr(buffer, "quit") ||
|
||||
strstr(buffer, "EXIT") || strstr(buffer, "exit")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close(client_fd);
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n");
|
||||
}
|
||||
|
||||
// Server thread function
|
||||
static void *server_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t addr_len = sizeof(client_addr);
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Test server listening on port %d\n", g_ctrl.port);
|
||||
|
||||
while (g_ctrl.running) {
|
||||
// Accept connection with timeout
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(g_ctrl.server_fd, &read_fds);
|
||||
|
||||
struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
|
||||
int ret = select(g_ctrl.server_fd + 1, &read_fds, NULL, NULL, &tv);
|
||||
|
||||
if (ret <= 0) continue;
|
||||
|
||||
int client_fd = accept(g_ctrl.server_fd, (struct sockaddr *)&client_addr, &addr_len);
|
||||
if (client_fd < 0) continue;
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n",
|
||||
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
|
||||
|
||||
handle_client(client_fd);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Public API Implementation
|
||||
// ============================================================================
|
||||
|
||||
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session)
|
||||
{
|
||||
#ifdef OAI_MPLANE
|
||||
if (g_ctrl.running) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ant_state == NULL || ru_session == NULL) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Invalid parameters\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_ctrl.ant_state = ant_state;
|
||||
g_ctrl.ru_session = (ru_session_t *)ru_session;
|
||||
g_ctrl.backend = ANTENNA_CTRL_BACKEND_MPLANE;
|
||||
g_ctrl.running = true;
|
||||
|
||||
// Check if RU supports TRX control feature
|
||||
if (!mplane_supports_trx_control(g_ctrl.ru_session)) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] RU does not support MPLANE-TRX-CONTROL feature\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Antenna muting may not work on this RU\n");
|
||||
}
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Initialized with O-RAN M-Plane NETCONF backend\n");
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] TX array: %s, RX array: %s\n",
|
||||
ant_state->tx_array_name, ant_state->rx_array_name);
|
||||
|
||||
return true;
|
||||
#else
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] M-Plane support not compiled in (OAI_MPLANE not defined)\n");
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_start_test_server() for testing\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port)
|
||||
{
|
||||
if (g_ctrl.running) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ant_state == NULL) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Antenna state is NULL\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Starting TEST socket server - NOT O-RAN COMPLIANT!\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] For production O-RAN compliance, use M-Plane NETCONF\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] See: radio/fhi_72/mplane/antenna-control-mplane.h\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
|
||||
|
||||
g_ctrl.ant_state = ant_state;
|
||||
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
|
||||
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
|
||||
|
||||
// Create socket
|
||||
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (g_ctrl.server_fd < 0) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
int opt = 1;
|
||||
setsockopt(g_ctrl.server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
|
||||
// Bind
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_ANY),
|
||||
.sin_port = htons(g_ctrl.port)
|
||||
};
|
||||
|
||||
if (bind(g_ctrl.server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno));
|
||||
close(g_ctrl.server_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Listen
|
||||
if (listen(g_ctrl.server_fd, 1) < 0) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno));
|
||||
close(g_ctrl.server_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start server thread
|
||||
g_ctrl.running = true;
|
||||
if (pthread_create(&g_ctrl.thread, NULL, server_thread, NULL) != 0) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n");
|
||||
g_ctrl.running = false;
|
||||
close(g_ctrl.server_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Test server started on port %d\n", g_ctrl.port);
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Connect with: nc localhost %d\n", g_ctrl.port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
|
||||
{
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] nr_antenna_control_start() is DEPRECATED\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_init_mplane() for O-RAN compliance\n");
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] or nr_antenna_control_start_test_server() for testing\n");
|
||||
|
||||
return nr_antenna_control_start_test_server(ant_state, port);
|
||||
}
|
||||
|
||||
void nr_antenna_control_stop(void)
|
||||
{
|
||||
if (!g_ctrl.running) return;
|
||||
|
||||
g_ctrl.running = false;
|
||||
|
||||
if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) {
|
||||
// Close server socket to unblock accept()
|
||||
if (g_ctrl.server_fd >= 0) {
|
||||
close(g_ctrl.server_fd);
|
||||
g_ctrl.server_fd = -1;
|
||||
}
|
||||
|
||||
// Wait for thread
|
||||
pthread_join(g_ctrl.thread, NULL);
|
||||
}
|
||||
|
||||
g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
|
||||
g_ctrl.ant_state = NULL;
|
||||
g_ctrl.ru_session = NULL;
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Control interface stopped\n");
|
||||
}
|
||||
|
||||
bool nr_antenna_control_is_running(void)
|
||||
{
|
||||
return g_ctrl.running;
|
||||
}
|
||||
|
||||
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void)
|
||||
{
|
||||
return g_ctrl.backend;
|
||||
}
|
||||
|
||||
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state)
|
||||
{
|
||||
#ifdef OAI_MPLANE
|
||||
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync to RU\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ant_state == NULL) {
|
||||
ant_state = g_ctrl.ant_state;
|
||||
}
|
||||
|
||||
if (ant_state == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send TX antenna mask to RU
|
||||
antenna_ctrl_result_t result;
|
||||
result = mplane_set_tx_antenna_mask(g_ctrl.ru_session,
|
||||
ant_state->tx_array_name,
|
||||
nr_get_tx_antenna_bitmask(ant_state),
|
||||
ant_state->num_tx_antennas);
|
||||
if (result != ANTENNA_CTRL_SUCCESS) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync TX mask: %s\n",
|
||||
antenna_ctrl_result_str(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send RX antenna mask to RU
|
||||
result = mplane_set_rx_antenna_mask(g_ctrl.ru_session,
|
||||
ant_state->rx_array_name,
|
||||
nr_get_rx_antenna_bitmask(ant_state),
|
||||
ant_state->num_rx_antennas);
|
||||
if (result != ANTENNA_CTRL_SUCCESS) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync RX mask: %s\n",
|
||||
antenna_ctrl_result_str(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Successfully synchronized antenna masks to RU\n");
|
||||
return true;
|
||||
#else
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state)
|
||||
{
|
||||
#ifdef OAI_MPLANE
|
||||
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync from RU\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ant_state == NULL) {
|
||||
ant_state = g_ctrl.ant_state;
|
||||
}
|
||||
|
||||
if (ant_state == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t tx_mask, rx_mask;
|
||||
antenna_ctrl_result_t result;
|
||||
|
||||
// Get TX antenna mask from RU
|
||||
result = mplane_get_tx_antenna_mask(g_ctrl.ru_session,
|
||||
ant_state->tx_array_name,
|
||||
&tx_mask);
|
||||
if (result == ANTENNA_CTRL_SUCCESS) {
|
||||
nr_set_tx_antenna_bitmask(ant_state, tx_mask);
|
||||
}
|
||||
|
||||
// Get RX antenna mask from RU
|
||||
result = mplane_get_rx_antenna_mask(g_ctrl.ru_session,
|
||||
ant_state->rx_array_name,
|
||||
&rx_mask);
|
||||
if (result == ANTENNA_CTRL_SUCCESS) {
|
||||
nr_set_rx_antenna_bitmask(ant_state, rx_mask);
|
||||
}
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Synchronized antenna masks from RU: TX=0x%02X, RX=0x%02X\n",
|
||||
tx_mask, rx_mask);
|
||||
return true;
|
||||
#else
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
#ifndef NR_ANTENNA_CONTROL_H
|
||||
#define NR_ANTENNA_CONTROL_H
|
||||
|
||||
#include "nr_antenna_state.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Default port for the test socket server (development mode only)
|
||||
#define NR_ANTENNA_CONTROL_TEST_PORT 9999
|
||||
|
||||
/**
|
||||
* @brief Antenna control backend type
|
||||
*/
|
||||
typedef enum {
|
||||
ANTENNA_CTRL_BACKEND_NONE = 0, ///< No external control
|
||||
ANTENNA_CTRL_BACKEND_MPLANE, ///< O-RAN M-Plane NETCONF (production)
|
||||
ANTENNA_CTRL_BACKEND_TEST_SOCKET ///< Test socket interface (development)
|
||||
} nr_antenna_ctrl_backend_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize antenna control with O-RAN M-Plane NETCONF backend
|
||||
*
|
||||
* This is the O-RAN compliant production interface. It connects the
|
||||
* antenna state to a real NETCONF session to an O-RU.
|
||||
*
|
||||
* When antenna masks are changed via nr_set_tx_antenna_bitmask() or
|
||||
* similar functions, the changes are automatically propagated to the
|
||||
* RU via NETCONF edit-config operations.
|
||||
*
|
||||
* @param ant_state Pointer to the antenna state to control
|
||||
* @param ru_session Active M-Plane NETCONF session (from radio/fhi_72/mplane/)
|
||||
* @return true if initialization successful
|
||||
*/
|
||||
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state,
|
||||
void *ru_session);
|
||||
|
||||
/**
|
||||
* @brief Start the test socket server (development mode only)
|
||||
*
|
||||
* Creates a simple TCP server for testing antenna control without
|
||||
* NETCONF infrastructure. This is NOT O-RAN compliant.
|
||||
*
|
||||
* Commands supported (for backwards compatibility):
|
||||
* MUTE TX <ant_id> - Mute TX antenna
|
||||
* UNMUTE TX <ant_id> - Unmute TX antenna
|
||||
* MUTE RX <ant_id> - Mute RX antenna
|
||||
* UNMUTE RX <ant_id> - Unmute RX antenna
|
||||
* SET TX MASK <hex> - Set TX antenna bitmask
|
||||
* SET RX MASK <hex> - Set RX antenna bitmask
|
||||
* STATUS - Get current antenna state
|
||||
* STATS - Get energy saving statistics
|
||||
* RESET STATS - Reset statistics
|
||||
* QUIT - Close connection
|
||||
*
|
||||
* @param ant_state Pointer to the antenna state to control
|
||||
* @param port TCP port to listen on (use 0 for default)
|
||||
* @return true if server started successfully
|
||||
*
|
||||
* @note This function logs a warning that it's not O-RAN compliant
|
||||
*/
|
||||
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
|
||||
|
||||
/**
|
||||
* @brief Legacy function - start antenna control server
|
||||
*
|
||||
* @deprecated Use nr_antenna_control_init_mplane() for O-RAN compliance
|
||||
* or nr_antenna_control_start_test_server() for testing.
|
||||
*
|
||||
* This function starts the test socket server for backwards compatibility.
|
||||
* It logs a deprecation warning.
|
||||
*/
|
||||
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
|
||||
|
||||
/**
|
||||
* @brief Stop the antenna control interface
|
||||
*
|
||||
* Stops the test socket server if running.
|
||||
* For M-Plane backend, this disconnects from NETCONF notifications.
|
||||
*/
|
||||
void nr_antenna_control_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Check if antenna control is active
|
||||
* @return true if control interface is running
|
||||
*/
|
||||
bool nr_antenna_control_is_running(void);
|
||||
|
||||
/**
|
||||
* @brief Get current antenna control backend type
|
||||
* @return Backend type currently in use
|
||||
*/
|
||||
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
|
||||
|
||||
/**
|
||||
* @brief Synchronize local antenna state to RU via M-Plane NETCONF
|
||||
*
|
||||
* Sends the current antenna bitmask configuration to the RU.
|
||||
* Only applicable when using the M-Plane NETCONF backend.
|
||||
*
|
||||
* @param ant_state Pointer to the antenna state
|
||||
* @return true if synchronization successful or not applicable
|
||||
*/
|
||||
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
|
||||
|
||||
/**
|
||||
* @brief Synchronize RU antenna state to local via M-Plane NETCONF
|
||||
*
|
||||
* Reads the current antenna bitmask from the RU and updates local state.
|
||||
* Only applicable when using the M-Plane NETCONF backend.
|
||||
*
|
||||
* @param ant_state Pointer to the antenna state
|
||||
* @return true if synchronization successful or not applicable
|
||||
*/
|
||||
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NR_ANTENNA_CONTROL_H */
|
||||
@@ -0,0 +1,367 @@
|
||||
#include "nr_antenna_state.h"
|
||||
#include "common/utils/LOG/log.h"
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
// Helper to count set bits in a byte
|
||||
static inline int popcount8(uint8_t x) {
|
||||
int count = 0;
|
||||
while (x) {
|
||||
count += x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Get current timestamp in microseconds
|
||||
static inline uint64_t get_timestamp_us(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
|
||||
}
|
||||
|
||||
void nr_init_antenna_state(nr_antenna_state_t *state,
|
||||
uint8_t num_tx,
|
||||
uint8_t num_rx,
|
||||
const char *tx_array_name,
|
||||
const char *rx_array_name)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
|
||||
memset(state, 0, sizeof(nr_antenna_state_t));
|
||||
|
||||
state->num_tx_antennas = (num_tx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_tx;
|
||||
state->num_rx_antennas = (num_rx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_rx;
|
||||
|
||||
// Initialize with all configured antennas active
|
||||
// Create bitmask with num_tx/num_rx lowest bits set
|
||||
state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1;
|
||||
state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1;
|
||||
|
||||
// Set O-RAN array names
|
||||
if (tx_array_name) {
|
||||
strncpy(state->tx_array_name, tx_array_name, sizeof(state->tx_array_name) - 1);
|
||||
} else {
|
||||
strncpy(state->tx_array_name, "tx-array-0", sizeof(state->tx_array_name) - 1);
|
||||
}
|
||||
|
||||
if (rx_array_name) {
|
||||
strncpy(state->rx_array_name, rx_array_name, sizeof(state->rx_array_name) - 1);
|
||||
} else {
|
||||
strncpy(state->rx_array_name, "rx-array-0", sizeof(state->rx_array_name) - 1);
|
||||
}
|
||||
|
||||
pthread_mutex_init(&state->lock, NULL);
|
||||
|
||||
state->stats.last_update_timestamp = get_timestamp_us();
|
||||
|
||||
// Initialize recovery mode
|
||||
state->recovery.in_recovery = false;
|
||||
state->recovery.recovery_start_ts = 0;
|
||||
state->recovery.recovery_duration_ms = 500; // 500ms default recovery time
|
||||
|
||||
// Protect antenna 0 by default (needed for SSB/PSS/SSS)
|
||||
state->config.protect_antenna0 = true;
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Initialized: TX antennas=%d (mask=0x%02X, array=%s), "
|
||||
"RX antennas=%d (mask=0x%02X, array=%s), antenna0_protected=%d\n",
|
||||
state->num_tx_antennas, state->tx_antenna_bitmask, state->tx_array_name,
|
||||
state->num_rx_antennas, state->rx_antenna_bitmask, state->rx_array_name,
|
||||
state->config.protect_antenna0);
|
||||
}
|
||||
|
||||
void nr_free_antenna_state(nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
pthread_mutex_destroy(&state->lock);
|
||||
}
|
||||
|
||||
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
|
||||
{
|
||||
if (state == NULL) return 0;
|
||||
|
||||
pthread_mutex_lock(&state->lock);
|
||||
|
||||
uint8_t old_bitmask = state->tx_antenna_bitmask;
|
||||
|
||||
// Mask to only valid antenna bits
|
||||
uint8_t valid_mask = (1 << state->num_tx_antennas) - 1;
|
||||
state->tx_antenna_bitmask = bitmask & valid_mask;
|
||||
|
||||
// Update statistics
|
||||
if (state->tx_antenna_bitmask != old_bitmask) {
|
||||
state->stats.tx_mute_events++;
|
||||
state->stats.last_update_timestamp = get_timestamp_us();
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
|
||||
old_bitmask, state->tx_antenna_bitmask, state->tx_array_name,
|
||||
popcount8(state->tx_antenna_bitmask), state->num_tx_antennas);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&state->lock);
|
||||
|
||||
return old_bitmask;
|
||||
}
|
||||
|
||||
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
|
||||
{
|
||||
if (state == NULL) return 0;
|
||||
|
||||
pthread_mutex_lock(&state->lock);
|
||||
|
||||
uint8_t old_bitmask = state->rx_antenna_bitmask;
|
||||
|
||||
// Mask to only valid antenna bits
|
||||
uint8_t valid_mask = (1 << state->num_rx_antennas) - 1;
|
||||
state->rx_antenna_bitmask = bitmask & valid_mask;
|
||||
|
||||
// Update statistics
|
||||
if (state->rx_antenna_bitmask != old_bitmask) {
|
||||
state->stats.rx_mute_events++;
|
||||
state->stats.last_update_timestamp = get_timestamp_us();
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] RX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
|
||||
old_bitmask, state->rx_antenna_bitmask, state->rx_array_name,
|
||||
popcount8(state->rx_antenna_bitmask), state->num_rx_antennas);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&state->lock);
|
||||
|
||||
return old_bitmask;
|
||||
}
|
||||
|
||||
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return 0xFF;
|
||||
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
|
||||
uint8_t m = state->tx_antenna_bitmask;
|
||||
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
|
||||
return m;
|
||||
}
|
||||
|
||||
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return 0xFF;
|
||||
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
|
||||
uint8_t m = state->rx_antenna_bitmask;
|
||||
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
|
||||
return m;
|
||||
}
|
||||
|
||||
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
|
||||
{
|
||||
if (state == NULL || ant_id >= state->num_tx_antennas) return false;
|
||||
return (state->tx_antenna_bitmask & (1 << ant_id)) != 0;
|
||||
}
|
||||
|
||||
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
|
||||
{
|
||||
if (state == NULL || ant_id >= state->num_rx_antennas) return false;
|
||||
return (state->rx_antenna_bitmask & (1 << ant_id)) != 0;
|
||||
}
|
||||
|
||||
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return 0;
|
||||
return popcount8(state->tx_antenna_bitmask);
|
||||
}
|
||||
|
||||
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return 0;
|
||||
return popcount8(state->rx_antenna_bitmask);
|
||||
}
|
||||
|
||||
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||
{
|
||||
if (state == NULL || ant_id >= state->num_tx_antennas) return;
|
||||
|
||||
// Protect antenna 0 if configured (needed for SSB/PSS/SSS transmission)
|
||||
if (ant_id == 0 && state->config.protect_antenna0) {
|
||||
LOG_W(PHY, "[ANTENNA_STATE] Cannot mute TX antenna 0: protected for SSB transmission\n");
|
||||
LOG_W(PHY, "[ANTENNA_STATE] Use 'SET TX MASK' to override protection if needed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t new_mask = state->tx_antenna_bitmask & ~(1 << ant_id);
|
||||
nr_set_tx_antenna_bitmask(state, new_mask);
|
||||
}
|
||||
|
||||
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||
{
|
||||
if (state == NULL || ant_id >= state->num_tx_antennas) return;
|
||||
|
||||
uint8_t new_mask = state->tx_antenna_bitmask | (1 << ant_id);
|
||||
nr_set_tx_antenna_bitmask(state, new_mask);
|
||||
}
|
||||
|
||||
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||
{
|
||||
if (state == NULL || ant_id >= state->num_rx_antennas) return;
|
||||
|
||||
uint8_t new_mask = state->rx_antenna_bitmask & ~(1 << ant_id);
|
||||
nr_set_rx_antenna_bitmask(state, new_mask);
|
||||
}
|
||||
|
||||
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
|
||||
{
|
||||
if (state == NULL || ant_id >= state->num_rx_antennas) return;
|
||||
|
||||
uint8_t new_mask = state->rx_antenna_bitmask | (1 << ant_id);
|
||||
nr_set_rx_antenna_bitmask(state, new_mask);
|
||||
}
|
||||
|
||||
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers)
|
||||
{
|
||||
if (state == NULL)
|
||||
return requested_layers;
|
||||
|
||||
// Reduce MIMO layers to match the number of active TX antennas.
|
||||
// When antennas are muted, the RU blanks the corresponding TX buffers.
|
||||
// If the scheduler still uses full layer count, the precoded signal is
|
||||
// distributed across all antenna ports (including muted ones), which
|
||||
// corrupts the UE's MIMO reception. By reducing layers to match active
|
||||
// antennas, the PMI fallback to identity precoding maps layers 1:1 to
|
||||
// active ports, avoiding signal loss on muted ports.
|
||||
int active = nr_get_active_tx_antenna_count(state);
|
||||
if (active > 0 && active < requested_layers)
|
||||
return active;
|
||||
return requested_layers;
|
||||
}
|
||||
|
||||
void nr_log_antenna_state(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) {
|
||||
LOG_I(PHY, "[ANTENNA_STATE] State is NULL\n");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] === Antenna State Report ===\n");
|
||||
LOG_I(PHY, "[ANTENNA_STATE] TX Array: %s\n", state->tx_array_name);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_tx_antennas);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->tx_antenna_bitmask);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_tx_antenna_count(state));
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Individual: ");
|
||||
for (int i = 0; i < state->num_tx_antennas; i++) {
|
||||
LOG_I(PHY, "TX%d=%s ", i, nr_is_tx_antenna_active(state, i) ? "ON" : "OFF");
|
||||
}
|
||||
LOG_I(PHY, "\n");
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] RX Array: %s\n", state->rx_array_name);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_rx_antennas);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->rx_antenna_bitmask);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_rx_antenna_count(state));
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Statistics:\n");
|
||||
LOG_I(PHY, "[ANTENNA_STATE] TX mute events: %lu\n", state->stats.tx_mute_events);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] RX mute events: %lu\n", state->stats.rx_mute_events);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Total muted slots: %lu\n", state->stats.total_muted_slots);
|
||||
LOG_I(PHY, "[ANTENNA_STATE] =============================\n");
|
||||
}
|
||||
|
||||
void nr_get_antenna_stats(const nr_antenna_state_t *state,
|
||||
uint64_t *tx_mute_events,
|
||||
uint64_t *rx_mute_events,
|
||||
uint64_t *total_muted_slots)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
|
||||
if (tx_mute_events) *tx_mute_events = state->stats.tx_mute_events;
|
||||
if (rx_mute_events) *rx_mute_events = state->stats.rx_mute_events;
|
||||
if (total_muted_slots) *total_muted_slots = state->stats.total_muted_slots;
|
||||
}
|
||||
|
||||
void nr_reset_antenna_stats(nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
|
||||
pthread_mutex_lock(&state->lock);
|
||||
|
||||
state->stats.tx_mute_events = 0;
|
||||
state->stats.rx_mute_events = 0;
|
||||
state->stats.total_muted_slots = 0;
|
||||
state->stats.energy_saved_estimate = 0;
|
||||
state->stats.last_update_timestamp = get_timestamp_us();
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Statistics reset\n");
|
||||
|
||||
pthread_mutex_unlock(&state->lock);
|
||||
}
|
||||
|
||||
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
|
||||
pthread_mutex_lock(&state->lock);
|
||||
|
||||
uint8_t old_mask = state->tx_antenna_bitmask;
|
||||
uint8_t all_on = (1 << state->num_tx_antennas) - 1;
|
||||
state->tx_antenna_bitmask = all_on;
|
||||
|
||||
// Enter recovery mode if we were muted
|
||||
if (old_mask != all_on) {
|
||||
state->recovery.in_recovery = true;
|
||||
state->recovery.recovery_start_ts = get_timestamp_us();
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Entering recovery mode for %u ms after unmuting TX antennas\n",
|
||||
state->recovery.recovery_duration_ms);
|
||||
}
|
||||
|
||||
state->stats.tx_mute_events++;
|
||||
state->stats.last_update_timestamp = get_timestamp_us();
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] All TX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
|
||||
old_mask, all_on, state->tx_array_name);
|
||||
|
||||
pthread_mutex_unlock(&state->lock);
|
||||
}
|
||||
|
||||
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
|
||||
pthread_mutex_lock(&state->lock);
|
||||
|
||||
uint8_t old_mask = state->rx_antenna_bitmask;
|
||||
uint8_t all_on = (1 << state->num_rx_antennas) - 1;
|
||||
state->rx_antenna_bitmask = all_on;
|
||||
|
||||
state->stats.rx_mute_events++;
|
||||
state->stats.last_update_timestamp = get_timestamp_us();
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] All RX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
|
||||
old_mask, all_on, state->rx_array_name);
|
||||
|
||||
pthread_mutex_unlock(&state->lock);
|
||||
}
|
||||
|
||||
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return false;
|
||||
|
||||
if (!state->recovery.in_recovery) return false;
|
||||
|
||||
// Check if recovery period has elapsed
|
||||
uint64_t now = get_timestamp_us();
|
||||
uint64_t elapsed_us = now - state->recovery.recovery_start_ts;
|
||||
uint64_t duration_us = (uint64_t)state->recovery.recovery_duration_ms * 1000ULL;
|
||||
|
||||
if (elapsed_us >= duration_us) {
|
||||
// Recovery complete - cast away const for state update
|
||||
nr_antenna_state_t *s = (nr_antenna_state_t *)state;
|
||||
s->recovery.in_recovery = false;
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Recovery mode complete after %lu ms\n", elapsed_us / 1000);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect)
|
||||
{
|
||||
if (state == NULL) return;
|
||||
|
||||
pthread_mutex_lock(&state->lock);
|
||||
state->config.protect_antenna0 = protect;
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Antenna 0 protection %s\n", protect ? "enabled" : "disabled");
|
||||
pthread_mutex_unlock(&state->lock);
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
#ifndef NR_ANTENNA_STATE_H
|
||||
#define NR_ANTENNA_STATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Maximum number of physical antennas supported
|
||||
#define NR_MAX_ANTENNAS 8
|
||||
|
||||
/// O-RAN compliant antenna state for TX/RX arrays
|
||||
/// Mirrors o-ran-uplane-conf.yang: tx-array-antenna-mask-config
|
||||
typedef struct {
|
||||
/// Antenna bitmask per O-RAN WG4 spec: bit N = 1 means antenna N is active
|
||||
/// Corresponds to o-ran-uplane-conf:antenna-bitmask
|
||||
uint8_t tx_antenna_bitmask;
|
||||
uint8_t rx_antenna_bitmask;
|
||||
|
||||
/// Total number of configured antennas
|
||||
uint8_t num_tx_antennas;
|
||||
uint8_t num_rx_antennas;
|
||||
|
||||
/// Thread safety for runtime updates
|
||||
pthread_mutex_t lock;
|
||||
|
||||
/// Energy saving statistics (for research/monitoring)
|
||||
struct {
|
||||
uint64_t tx_mute_events; ///< Number of TX mute operations
|
||||
uint64_t rx_mute_events; ///< Number of RX mute operations
|
||||
uint64_t total_muted_slots; ///< Total slots with at least one antenna muted
|
||||
uint64_t energy_saved_estimate; ///< Estimated energy saved (arbitrary units)
|
||||
uint64_t last_update_timestamp; ///< Last update time (for O1 notifications)
|
||||
} stats;
|
||||
|
||||
/// O-RAN compliance: array name for NETCONF identification
|
||||
char tx_array_name[64];
|
||||
char rx_array_name[64];
|
||||
|
||||
/// Recovery mode: after unmuting, allow CSI to update before using full capacity
|
||||
struct {
|
||||
bool in_recovery; ///< Currently in recovery mode
|
||||
uint64_t recovery_start_ts; ///< Timestamp when recovery started (us)
|
||||
uint32_t recovery_duration_ms; ///< Recovery duration in milliseconds (default 500ms)
|
||||
} recovery;
|
||||
|
||||
/// Configuration flags
|
||||
struct {
|
||||
bool protect_antenna0; ///< Never mute antenna 0 (needed for SSB)
|
||||
} config;
|
||||
|
||||
} nr_antenna_state_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize antenna state with all antennas active
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param num_tx Number of TX antennas
|
||||
* @param num_rx Number of RX antennas
|
||||
* @param tx_array_name O-RAN array name for TX (e.g., "tx-array-0")
|
||||
* @param rx_array_name O-RAN array name for RX (e.g., "rx-array-0")
|
||||
*/
|
||||
void nr_init_antenna_state(nr_antenna_state_t *state,
|
||||
uint8_t num_tx,
|
||||
uint8_t num_rx,
|
||||
const char *tx_array_name,
|
||||
const char *rx_array_name);
|
||||
|
||||
/**
|
||||
* @brief Free antenna state resources
|
||||
* @param state Pointer to antenna state structure
|
||||
*/
|
||||
void nr_free_antenna_state(nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Set TX antenna bitmask (O-RAN O1 interface equivalent)
|
||||
*
|
||||
* This function implements the O-RAN WG4 MPLANE-TRX-CONTROL feature.
|
||||
* Corresponds to NETCONF edit-config on tx-array-antenna-mask-config.
|
||||
*
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
|
||||
* @return Previous bitmask value
|
||||
*/
|
||||
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
|
||||
|
||||
/**
|
||||
* @brief Set RX antenna bitmask (O-RAN O1 interface equivalent)
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
|
||||
* @return Previous bitmask value
|
||||
*/
|
||||
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
|
||||
|
||||
/**
|
||||
* @brief Get current TX antenna bitmask
|
||||
* @param state Pointer to antenna state structure
|
||||
* @return Current TX antenna bitmask
|
||||
*/
|
||||
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Get current RX antenna bitmask
|
||||
* @param state Pointer to antenna state structure
|
||||
* @return Current RX antenna bitmask
|
||||
*/
|
||||
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Check if a specific TX antenna is active
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param ant_id Antenna index (0-based)
|
||||
* @return true if antenna is active, false if muted
|
||||
*/
|
||||
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
|
||||
|
||||
/**
|
||||
* @brief Check if a specific RX antenna is active
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param ant_id Antenna index (0-based)
|
||||
* @return true if antenna is active, false if muted
|
||||
*/
|
||||
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
|
||||
|
||||
/**
|
||||
* @brief Get count of currently active TX antennas
|
||||
* @param state Pointer to antenna state structure
|
||||
* @return Number of active TX antennas
|
||||
*/
|
||||
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Get count of currently active RX antennas
|
||||
* @param state Pointer to antenna state structure
|
||||
* @return Number of active RX antennas
|
||||
*/
|
||||
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Mute a specific TX antenna
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param ant_id Antenna index to mute (0-based)
|
||||
*/
|
||||
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||
|
||||
/**
|
||||
* @brief Unmute a specific TX antenna
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param ant_id Antenna index to unmute (0-based)
|
||||
*/
|
||||
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||
|
||||
/**
|
||||
* @brief Mute a specific RX antenna
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param ant_id Antenna index to mute (0-based)
|
||||
*/
|
||||
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||
|
||||
/**
|
||||
* @brief Unmute a specific RX antenna
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param ant_id Antenna index to unmute (0-based)
|
||||
*/
|
||||
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
|
||||
|
||||
/**
|
||||
* @brief Get effective number of MIMO layers based on active antennas
|
||||
*
|
||||
* For MIMO, the number of layers is limited by active antennas.
|
||||
* This function returns min(requested_layers, active_antennas).
|
||||
*
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param requested_layers Originally requested number of layers
|
||||
* @return Effective number of layers that can be used
|
||||
*/
|
||||
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers);
|
||||
|
||||
/**
|
||||
* @brief Log antenna state for debugging
|
||||
* @param state Pointer to antenna state structure
|
||||
*/
|
||||
void nr_log_antenna_state(const nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Get energy saving statistics
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param tx_mute_events Output: number of TX mute events
|
||||
* @param rx_mute_events Output: number of RX mute events
|
||||
* @param total_muted_slots Output: total slots with muting
|
||||
*/
|
||||
void nr_get_antenna_stats(const nr_antenna_state_t *state,
|
||||
uint64_t *tx_mute_events,
|
||||
uint64_t *rx_mute_events,
|
||||
uint64_t *total_muted_slots);
|
||||
|
||||
/**
|
||||
* @brief Reset energy saving statistics
|
||||
* @param state Pointer to antenna state structure
|
||||
*/
|
||||
void nr_reset_antenna_stats(nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Unmute all TX antennas and enter recovery mode
|
||||
* @param state Pointer to antenna state structure
|
||||
*
|
||||
* This initiates a recovery period during which the scheduler
|
||||
* uses conservative settings to allow UE CSI reports to update.
|
||||
*/
|
||||
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Unmute all RX antennas
|
||||
* @param state Pointer to antenna state structure
|
||||
*/
|
||||
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Check if system is in recovery mode after unmuting
|
||||
* @param state Pointer to antenna state structure
|
||||
* @return true if in recovery mode
|
||||
*/
|
||||
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Set antenna 0 protection (SSB antenna)
|
||||
* @param state Pointer to antenna state structure
|
||||
* @param protect true to prevent muting antenna 0
|
||||
*/
|
||||
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NR_ANTENNA_STATE_H */
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "common/utils/threadPool/task_ans.h"
|
||||
#include "openair1/PHY/defs_RU.h"
|
||||
#include "common/utils/ds/spsc_q.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||
|
||||
#define MAX_NUM_RU_PER_gNB 8
|
||||
#define MAX_PUCCH0_NID 8
|
||||
@@ -430,6 +431,8 @@ typedef struct PHY_VARS_gNB_s {
|
||||
|
||||
bool enable_analog_das;
|
||||
|
||||
nr_antenna_state_t antenna_state;
|
||||
|
||||
time_stats_t l1_tx_proc;
|
||||
time_stats_t l1_rx_proc;
|
||||
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static inline int count_nnz_c16(const c16_t *v, int n)
|
||||
{
|
||||
int nnz = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
nnz += (v[i].r != 0) || (v[i].i != 0);
|
||||
return nnz;
|
||||
}
|
||||
|
||||
// RU OFDM Modulator gNodeB
|
||||
// OFDM modulation core routine, generates a first_symbol to first_symbol+num_symbols on a particular slot and TX antenna port
|
||||
void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa)
|
||||
@@ -165,12 +173,20 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
|
||||
int Ptx = cfg->carrier_config.num_tx_ant.value;
|
||||
// If there is no digital beamforming we just need to copy the data to RU
|
||||
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
||||
const nr_antenna_state_t *ant_state = &gNB->antenna_state;
|
||||
for (int b = 0; b < ru->num_beams_period; b++) {
|
||||
for (int i = 0; i < Ptx; ++i) {
|
||||
int tx_idx = i + b * ru->nb_tx;
|
||||
memcpy((void *)ru->common.txdataF_BF[tx_idx],
|
||||
(void *)gNB->common_vars.txdataF[b][i],
|
||||
fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||
// Check antenna muting state for energy saving
|
||||
if (!nr_is_tx_antenna_active(ant_state, i)) {
|
||||
// Antenna is muted - zero out the buffer (transmission blanking)
|
||||
memset((void *)ru->common.txdataF_BF[tx_idx], 0, fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted in nr_feptx_prec, blanking slot %d\n", i, slot_tx);
|
||||
} else {
|
||||
memcpy((void *)ru->common.txdataF_BF[tx_idx],
|
||||
(void *)gNB->common_vars.txdataF[b][i],
|
||||
fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -205,19 +221,38 @@ void nr_feptx(void *arg)
|
||||
|
||||
// If there is no digital beamforming we just need to copy the data to RU
|
||||
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
|
||||
// FFT shift
|
||||
const NR_DL_FRAME_PARMS *fp = &ru->gNB_list[0]->frame_parms;
|
||||
fft_shift(ru->gNB_list[0]->common_vars.txdataF[bb][aa],
|
||||
fp->ofdm_symbol_size,
|
||||
fp->N_RB_DL,
|
||||
(c16_t *)ru->common.txdataF_BF[tx_idx],
|
||||
fp->ofdm_symbol_size,
|
||||
startSymbol,
|
||||
numSymbols);
|
||||
const nr_antenna_state_t *ant_state = &ru->gNB_list[0]->antenna_state;
|
||||
if (!nr_is_tx_antenna_active(ant_state, aa)) {
|
||||
// Antenna is muted - zero out the FFT-shift output (transmission blanking)
|
||||
memset((void *)&ru->common.txdataF_BF[tx_idx][startSymbol * fp->ofdm_symbol_size], 0,
|
||||
numSymbols * fp->ofdm_symbol_size * sizeof(int32_t));
|
||||
} else {
|
||||
// FFT shift
|
||||
fft_shift(ru->gNB_list[0]->common_vars.txdataF[bb][aa],
|
||||
fp->ofdm_symbol_size,
|
||||
fp->N_RB_DL,
|
||||
(c16_t *)ru->common.txdataF_BF[tx_idx],
|
||||
fp->ofdm_symbol_size,
|
||||
startSymbol,
|
||||
numSymbols);
|
||||
}
|
||||
} else {
|
||||
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
|
||||
}
|
||||
|
||||
/* const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset]; */
|
||||
/* const int dst_nnz = count_nnz_c16(dst, numSamples); */
|
||||
/* if (startSymbol == 0 && ru->proc.frame_tx % 10 == 0 && dst_nnz != 0) { */
|
||||
/* const int dst_pwr_db = dB_fixed(signal_energy_nodc((c16_t *)dst, 2 * numSamples)); */
|
||||
|
||||
/* LOG_I(PHY, */
|
||||
/* "[TXF_PWR] frame=%d slot=%d beam=%d aa=%d tx_idx=%d " */
|
||||
/* "DST pwr=%d dB nnz=%d\n", */
|
||||
/* ru->proc.frame_tx, slot, bb, aa, tx_idx, dst_pwr_db, dst_nnz); */
|
||||
/* } */
|
||||
|
||||
|
||||
if (tx_idx == 0)
|
||||
stop_meas(&ru->precoding_stats);
|
||||
|
||||
|
||||
@@ -420,7 +420,7 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
||||
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
|
||||
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
|
||||
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
|
||||
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
|
||||
int layers = get_dl_nrOfLayers(nr_mac, sched_ctrl, dl_bwp->dci_format);
|
||||
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
|
||||
|
||||
// If the RI changed between current rtx and a previous transmission
|
||||
@@ -565,7 +565,6 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
|
||||
static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE)
|
||||
{
|
||||
if (!UE->reconfigCellGroup) {
|
||||
LOG_W(NR_MAC, "Received ACK for RRCReconfiguration, but nothing to apply!\n");
|
||||
return;
|
||||
}
|
||||
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
|
||||
@@ -700,7 +699,7 @@ static void pf_dl(gNB_MAC_INST *mac,
|
||||
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
|
||||
} else
|
||||
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
|
||||
int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format);
|
||||
int l = get_dl_nrOfLayers(mac, sched_ctrl, current_BWP->dci_format);
|
||||
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx);
|
||||
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
|
||||
uint32_t tbs = nr_compute_tbs(Qm,
|
||||
@@ -837,7 +836,7 @@ static void pf_dl(gNB_MAC_INST *mac,
|
||||
sched_ctrl->cce_index = CCEIndex;
|
||||
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
|
||||
|
||||
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
|
||||
int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
|
||||
NR_sched_pdsch_t sched_pdsch = {
|
||||
// rbSize below
|
||||
.rbStart = rbStart,
|
||||
|
||||
@@ -101,14 +101,13 @@ int get_ssbidx_from_beam(gNB_MAC_INST *mac, int beam_idx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
||||
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
||||
{
|
||||
// TODO check this but it should be enough for now
|
||||
// if there is not csi report activated RI is 0 from initialization
|
||||
if(dci_format == NR_DL_DCI_FORMAT_1_0)
|
||||
return 1;
|
||||
else
|
||||
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
|
||||
int layers = (dci_format == NR_DL_DCI_FORMAT_1_0) ? 1
|
||||
: sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
|
||||
if (mac && mac->antenna_state)
|
||||
return nr_get_effective_mimo_layers(mac->antenna_state, layers);
|
||||
return layers;
|
||||
}
|
||||
|
||||
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
|
||||
@@ -165,63 +164,88 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
|
||||
int layers,
|
||||
int xp_pdsch_antenna_ports)
|
||||
{
|
||||
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
|
||||
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
|
||||
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
||||
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
|
||||
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
|
||||
const int N1 = csi_report->N1;
|
||||
const int N2 = csi_report->N2;
|
||||
const int antenna_ports = (N1 * N2) << 1;
|
||||
if (antenna_ports < 2)
|
||||
return 0; // single antenna port
|
||||
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
|
||||
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
|
||||
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
|
||||
|
||||
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
|
||||
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
|
||||
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
|
||||
|
||||
int prev_layers_size = 0;
|
||||
for (int i = 1; i < layers; i++)
|
||||
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
|
||||
|
||||
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
|
||||
// index 0 is for identity matrix
|
||||
// order of matrices depends on layers to be transmitted
|
||||
// elements from 1 to n for 1 layer
|
||||
// elements from n+1 to m for 2 layers etc.
|
||||
if (antenna_ports == 2)
|
||||
return 1 + prev_layers_size + x2; // 0 for identity matrix
|
||||
else {
|
||||
int idx = layers - 1;
|
||||
// the order of i1x in X1 report needs to be verified
|
||||
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
|
||||
// it says: PMI wideband information fields X1 , from left to right
|
||||
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
|
||||
if (layers == 1 && bitlen != 0) {
|
||||
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
|
||||
return 0;
|
||||
int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
|
||||
if (layers != reported_ri_layers) {
|
||||
LOG_D(NR_MAC, "PMI mismatch: using %d layers but RI report is for %d layers, using identity matrix\n",
|
||||
layers, reported_ri_layers);
|
||||
return 0;
|
||||
}
|
||||
const int i13 = x1 & ((1 << bitlen) - 1);
|
||||
x1 >>= bitlen;
|
||||
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
|
||||
const int i12 = x1 & ((1 << bitlen) - 1);
|
||||
x1 >>= bitlen;
|
||||
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
|
||||
const int i11 = x1 & ((1 << bitlen) - 1);
|
||||
const int i2 = x2;
|
||||
int k1, k2;
|
||||
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
|
||||
const int O2 = N2 == 1 ? 1 : 4;
|
||||
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
|
||||
int max_i2 = 0;
|
||||
int lay_index = 0;
|
||||
max_i2 = layers == 1 ? 4 : 2;
|
||||
int K1, K2;
|
||||
get_K1_K2(N1, N2, &K1, &K2, layers);
|
||||
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
|
||||
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
|
||||
return 1 + prev_layers_size + lay_index;
|
||||
}
|
||||
nr_antenna_state_t* ant_state = nrmac->antenna_state;
|
||||
if (ant_state) {
|
||||
if (nr_is_in_recovery_mode(ant_state)) {
|
||||
LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
|
||||
return 0;
|
||||
}
|
||||
// Don't fall back to identity matrix for muted antennas.
|
||||
// Antenna muting is handled at RU level (nr_feptx zeros muted antenna buffers).
|
||||
// Using proper precoding ensures data reaches all active antennas.
|
||||
}
|
||||
|
||||
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
|
||||
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
|
||||
const int N1 = csi_report->N1;
|
||||
const int N2 = csi_report->N2;
|
||||
const int antenna_ports = (N1 * N2) << 1;
|
||||
if (antenna_ports < 2) return 0; // single antenna port
|
||||
|
||||
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
|
||||
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
|
||||
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
|
||||
|
||||
int prev_layers_size = 0;
|
||||
for (int i = 1; i < layers; i++)
|
||||
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
|
||||
|
||||
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
|
||||
// index 0 is for identity matrix
|
||||
// order of matrices depends on layers to be transmitted
|
||||
// elements from 1 to n for 1 layer
|
||||
// elements from n+1 to m for 2 layers etc.
|
||||
int pm_index;
|
||||
if (antenna_ports == 2)
|
||||
pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix
|
||||
else {
|
||||
int idx = layers - 1;
|
||||
// the order of i1x in X1 report needs to be verified
|
||||
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
|
||||
// it says: PMI wideband information fields X1 , from left to right
|
||||
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
|
||||
if (layers == 1 && bitlen != 0) {
|
||||
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
|
||||
return 0;
|
||||
}
|
||||
const int i13 = x1 & ((1 << bitlen) - 1);
|
||||
x1 >>= bitlen;
|
||||
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
|
||||
const int i12 = x1 & ((1 << bitlen) - 1);
|
||||
x1 >>= bitlen;
|
||||
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
|
||||
const int i11 = x1 & ((1 << bitlen) - 1);
|
||||
const int i2 = x2;
|
||||
int k1, k2;
|
||||
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
|
||||
const int O2 = N2 == 1 ? 1 : 4;
|
||||
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
|
||||
int max_i2 = 0;
|
||||
int lay_index = 0;
|
||||
max_i2 = layers == 1 ? 4 : 2;
|
||||
int K1, K2;
|
||||
get_K1_K2(N1, N2, &K1, &K2, layers);
|
||||
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
|
||||
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
|
||||
pm_index = 1 + prev_layers_size + lay_index;
|
||||
}
|
||||
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
|
||||
if (pm_index < 1 || pm_index > max_valid_pm) {
|
||||
LOG_W(NR_MAC, "PM index %d out of range [1, %d] for %d layers, using identity matrix\n",
|
||||
pm_index, max_valid_pm, layers);
|
||||
return 0;
|
||||
}
|
||||
return pm_index;
|
||||
}
|
||||
|
||||
// look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation
|
||||
|
||||
@@ -497,7 +497,9 @@ static void evaluate_sinr_report(NR_UE_info_t *UE,
|
||||
stats->num_sinr_meas++;
|
||||
|
||||
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
|
||||
const int nrOfLayers = get_dl_nrOfLayers(sched_ctrl, UE->current_DL_BWP.dci_format);
|
||||
/* NULL mac arg: SINR-based MCS estimation skips antenna-state rank cap
|
||||
(caller doesn't have nrmac in scope; this is only for max-MCS hint). */
|
||||
const int nrOfLayers = get_dl_nrOfLayers(NULL, sched_ctrl, UE->current_DL_BWP.dci_format);
|
||||
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers);
|
||||
|
||||
LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs);
|
||||
|
||||
@@ -373,7 +373,7 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
|
||||
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl);
|
||||
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
|
||||
|
||||
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
||||
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
||||
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
|
||||
|
||||
void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl);
|
||||
|
||||
Reference in New Issue
Block a user