397 lines
13 KiB
C
397 lines
13 KiB
C
/*
|
|
* 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_state.c
|
|
* \brief O-RAN compliant antenna state management implementation
|
|
* \author OpenAirInterface
|
|
* \date 2025
|
|
* \version 1.0
|
|
* \note Implements O-RAN WG4 MPLANE-TRX-CONTROL feature
|
|
*/
|
|
|
|
#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;
|
|
|
|
int active_tx = nr_get_active_tx_antenna_count(state);
|
|
|
|
// MIMO layers cannot exceed active TX antennas
|
|
int effective = (requested_layers < active_tx) ? requested_layers : active_tx;
|
|
|
|
// Must have at least 1 layer if any antenna is active
|
|
if (effective < 1 && active_tx > 0) {
|
|
effective = 1;
|
|
}
|
|
|
|
return effective;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|