/* * 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 #include #include #include #include #include #include #include #include #include // Try to include M-Plane header if available #ifdef OAI_MPLANE #include "radio/fhi_72/mplane/antenna-control-mplane.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 - Mute antenna\n" "UNMUTE TX/RX - Unmute antenna\n" "SET TX/RX MASK - 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 = 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 }