652 lines
22 KiB
C
652 lines
22 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_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>
|
|
#include <time.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;
|
|
int64_t tx_lock_until_ms[NR_MAX_ANTENNAS];
|
|
#ifdef OAI_MPLANE
|
|
ru_session_t *ru_session;
|
|
#else
|
|
void *ru_session;
|
|
#endif
|
|
} g_ctrl = {0};
|
|
|
|
static bool valid_tsm_duration(int seconds)
|
|
{
|
|
return seconds == 0 || seconds == 15 || seconds == 30 ||
|
|
seconds == 45 || seconds == 60;
|
|
}
|
|
|
|
static int64_t monotonic_ms(void)
|
|
{
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000;
|
|
}
|
|
|
|
static int64_t tx_lock_remaining_ms(int ant_id)
|
|
{
|
|
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
|
|
return 0;
|
|
int64_t remaining = g_ctrl.tx_lock_until_ms[ant_id] - monotonic_ms();
|
|
if (remaining <= 0) {
|
|
g_ctrl.tx_lock_until_ms[ant_id] = 0;
|
|
return 0;
|
|
}
|
|
return remaining;
|
|
}
|
|
|
|
static void set_tx_lock(int ant_id, int seconds)
|
|
{
|
|
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
|
|
return;
|
|
g_ctrl.tx_lock_until_ms[ant_id] =
|
|
seconds > 0 ? monotonic_ms() + (int64_t)seconds * 1000 : 0;
|
|
}
|
|
|
|
static void clear_all_tx_locks(void)
|
|
{
|
|
memset(g_ctrl.tx_lock_until_ms, 0, sizeof(g_ctrl.tx_lock_until_ms));
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 = -1;
|
|
int duration = 0;
|
|
int fields = sscanf(p + 8, "%d %d", &ant_id, &duration);
|
|
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
|
|
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
|
|
return;
|
|
}
|
|
if (fields >= 2 && !valid_tsm_duration(duration)) {
|
|
snprintf(response, resp_size,
|
|
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
|
|
duration);
|
|
return;
|
|
}
|
|
nr_mute_tx_antenna(g_ctrl.ant_state, ant_id);
|
|
set_tx_lock(ant_id, duration);
|
|
snprintf(response, resp_size, "OK: TX antenna %d muted (TSM=%ds)\n",
|
|
ant_id, duration);
|
|
}
|
|
else if (strncmp(p, "UNMUTE TX ", 10) == 0) {
|
|
int ant_id = -1;
|
|
char option[16] = {0};
|
|
int fields = sscanf(p + 10, "%d %15s", &ant_id, option);
|
|
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
|
|
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
|
|
return;
|
|
}
|
|
bool force = fields >= 2 && strcmp(option, "FORCE") == 0;
|
|
int64_t remaining = tx_lock_remaining_ms(ant_id);
|
|
if (remaining > 0 && !force) {
|
|
snprintf(response, resp_size,
|
|
"ERROR: TX antenna %d locked for %ld more ms\n",
|
|
ant_id, (long)remaining);
|
|
return;
|
|
}
|
|
set_tx_lock(ant_id, 0);
|
|
nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id);
|
|
snprintf(response, resp_size, "OK: TX antenna %d unmuted%s\n",
|
|
ant_id, force ? " (emergency override)" : "");
|
|
}
|
|
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;
|
|
int duration = 0;
|
|
char option[16] = {0};
|
|
int fields = sscanf(p + 12, "%x %d %15s", &mask, &duration, option);
|
|
if (fields < 1) {
|
|
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
|
|
return;
|
|
}
|
|
if (fields >= 2 && !valid_tsm_duration(duration)) {
|
|
snprintf(response, resp_size,
|
|
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
|
|
duration);
|
|
return;
|
|
}
|
|
bool force = fields >= 3 && strcmp(option, "FORCE") == 0;
|
|
uint8_t valid_mask = (1u << g_ctrl.ant_state->num_tx_antennas) - 1u;
|
|
uint8_t requested = (uint8_t)mask & valid_mask;
|
|
uint8_t old = nr_get_tx_antenna_bitmask(g_ctrl.ant_state);
|
|
uint8_t waking = requested & ~old;
|
|
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
|
|
if ((waking & (1u << ant_id)) && tx_lock_remaining_ms(ant_id) > 0 && !force) {
|
|
snprintf(response, resp_size,
|
|
"ERROR: TX antenna %d is still TSM-locked\n", ant_id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
nr_set_tx_antenna_bitmask(g_ctrl.ant_state, requested);
|
|
uint8_t muted = old & ~requested;
|
|
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
|
|
uint8_t bit = 1u << ant_id;
|
|
if (muted & bit)
|
|
set_tx_lock(ant_id, duration);
|
|
if (waking & bit)
|
|
set_tx_lock(ant_id, 0);
|
|
}
|
|
snprintf(response, resp_size,
|
|
"OK: TX mask 0x%02X -> 0x%02X (TSM=%ds%s)\n",
|
|
old, requested, duration, force ? ", emergency override" : "");
|
|
}
|
|
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");
|
|
size_t used = strlen(response);
|
|
if (used < resp_size) {
|
|
used += snprintf(response + used, resp_size - used, "TSM locks:");
|
|
for (int ant_id = 0;
|
|
ant_id < g_ctrl.ant_state->num_tx_antennas && used < resp_size;
|
|
++ant_id) {
|
|
int64_t remaining = tx_lock_remaining_ms(ant_id);
|
|
if (remaining > 0)
|
|
used += snprintf(response + used, resp_size - used,
|
|
" TX%d=%lds", ant_id, (long)((remaining + 999) / 1000));
|
|
}
|
|
if (used < resp_size)
|
|
snprintf(response + used, resp_size - used, "\n");
|
|
}
|
|
}
|
|
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
|
|
clear_all_tx_locks();
|
|
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 <id> [15|30|45|60] - Mute TX antenna with optional TSM lock\n"
|
|
"UNMUTE TX <id> [FORCE] - Unmute TX antenna\n"
|
|
"MUTE/UNMUTE RX <id> - Change RX antenna state\n"
|
|
"SET TX MASK <hex> [duration] [FORCE] - Set TX mask and TSM lock\n"
|
|
"SET RX MASK <hex> - Set RX 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
|
|
}
|