399 lines
13 KiB
C
399 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 antenna-control-mplane.c
|
|
* @brief Implementation of O-RAN WG4 M-Plane compliant antenna control
|
|
*
|
|
* This implements NETCONF-based antenna muting using the actual O-RAN
|
|
* YANG models (o-ran-uplane-conf.yang) and the MPLANE-TRX-CONTROL feature.
|
|
*/
|
|
|
|
#include "antenna-control-mplane.h"
|
|
#include "ru-mplane-api.h"
|
|
#include "xml/get-xml.h"
|
|
#include "common/utils/assertions.h"
|
|
|
|
// Forward declaration - actual function in config-mplane.c
|
|
extern bool edit_val_commmit_rpc(ru_session_t *ru_session, const char *content);
|
|
|
|
// Forward declaration - actual function in get-mplane.c
|
|
extern bool get_mplane(ru_session_t *ru_session, char **answer);
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <libyang/libyang.h>
|
|
#include <nc_client.h>
|
|
|
|
const char *antenna_ctrl_result_str(antenna_ctrl_result_t result)
|
|
{
|
|
switch (result) {
|
|
case ANTENNA_CTRL_SUCCESS:
|
|
return "Success";
|
|
case ANTENNA_CTRL_ERR_NO_SESSION:
|
|
return "No active NETCONF session";
|
|
case ANTENNA_CTRL_ERR_FEATURE_NOT_SUPPORTED:
|
|
return "MPLANE-TRX-CONTROL feature not supported by RU";
|
|
case ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME:
|
|
return "Invalid array name";
|
|
case ANTENNA_CTRL_ERR_INVALID_MASK:
|
|
return "Invalid antenna bitmask";
|
|
case ANTENNA_CTRL_ERR_NETCONF_FAILED:
|
|
return "NETCONF RPC failed";
|
|
case ANTENNA_CTRL_ERR_VALIDATION_FAILED:
|
|
return "Configuration validation failed";
|
|
case ANTENNA_CTRL_ERR_COMMIT_FAILED:
|
|
return "Configuration commit failed";
|
|
default:
|
|
return "Unknown error";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Convert antenna bitmask to O-RAN binary format
|
|
*
|
|
* O-RAN uses binary type in YANG which is base64 encoded in XML.
|
|
* For simplicity, we encode the bitmask as hex bytes.
|
|
*
|
|
* Per O-RAN spec: "The antenna mask contains as many binary values as
|
|
* number of array elements associated with respective tx or rx-array.
|
|
* Value of '1' indicates antenna element shall be kept active and
|
|
* value of '0' indicates antenna element should be turned inactive."
|
|
*/
|
|
static char *bitmask_to_base64(uint8_t bitmask, uint8_t num_antennas)
|
|
{
|
|
// For up to 8 antennas, we need 1 byte
|
|
// Base64 encoding of 1 byte = 4 characters + null terminator
|
|
char *result = malloc(8);
|
|
if (result == NULL) return NULL;
|
|
|
|
// Mask to only include configured antennas
|
|
uint8_t valid_mask = (1 << num_antennas) - 1;
|
|
uint8_t masked_value = bitmask & valid_mask;
|
|
|
|
// Simple base64 encoding for single byte
|
|
// Input: masked_value (8 bits) -> padded to 6+6+4 bits for base64
|
|
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
// Encode single byte with padding
|
|
result[0] = b64chars[(masked_value >> 2) & 0x3F];
|
|
result[1] = b64chars[(masked_value << 4) & 0x30];
|
|
result[2] = '=';
|
|
result[3] = '=';
|
|
result[4] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Decode base64 to antenna bitmask
|
|
*/
|
|
static uint8_t base64_to_bitmask(const char *b64_str)
|
|
{
|
|
if (b64_str == NULL || strlen(b64_str) < 2) return 0xFF;
|
|
|
|
static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
// Find index of first two base64 characters
|
|
const char *p1 = strchr(b64chars, b64_str[0]);
|
|
const char *p2 = strchr(b64chars, b64_str[1]);
|
|
|
|
if (p1 == NULL || p2 == NULL) return 0xFF;
|
|
|
|
uint8_t val1 = (uint8_t)(p1 - b64chars);
|
|
uint8_t val2 = (uint8_t)(p2 - b64chars);
|
|
|
|
// Reconstruct byte
|
|
return (val1 << 2) | (val2 >> 4);
|
|
}
|
|
|
|
bool mplane_create_antenna_mask_xml(ru_session_t *ru_session,
|
|
bool is_tx,
|
|
const char *array_name,
|
|
uint8_t antenna_bitmask,
|
|
uint8_t num_antennas,
|
|
char **xml_content)
|
|
{
|
|
if (ru_session == NULL || ru_session->ctx == NULL || array_name == NULL || xml_content == NULL) {
|
|
return false;
|
|
}
|
|
|
|
*xml_content = NULL;
|
|
LY_ERR ret = LY_SUCCESS;
|
|
struct lyd_node *root = NULL;
|
|
|
|
// Create root node for o-ran-uplane-conf
|
|
ret = lyd_new_path(NULL, (struct ly_ctx *)ru_session->ctx,
|
|
"/o-ran-uplane-conf:user-plane-configuration", NULL, 0, &root);
|
|
if (ret != LY_SUCCESS) {
|
|
MP_LOG_W("Failed to create root user-plane-configuration node.\n");
|
|
return false;
|
|
}
|
|
|
|
// Create the antenna mask configuration
|
|
const char *list_name = is_tx ? "tx-array-antenna-mask-config" : "rx-array-antenna-mask-config";
|
|
struct lyd_node *mask_node = NULL;
|
|
|
|
ret = lyd_new_list(root, NULL, list_name, 0, &mask_node, array_name);
|
|
if (ret != LY_SUCCESS) {
|
|
MP_LOG_W("Failed to create %s list node for array '%s'.\n", list_name, array_name);
|
|
lyd_free_tree(root);
|
|
return false;
|
|
}
|
|
|
|
// Convert bitmask to base64 for YANG binary type
|
|
char *b64_mask = bitmask_to_base64(antenna_bitmask, num_antennas);
|
|
if (b64_mask == NULL) {
|
|
MP_LOG_W("Failed to encode antenna bitmask.\n");
|
|
lyd_free_tree(root);
|
|
return false;
|
|
}
|
|
|
|
// Add antenna-bitmask leaf
|
|
ret = lyd_new_term(mask_node, NULL, "antenna-bitmask", b64_mask, 0, NULL);
|
|
free(b64_mask);
|
|
|
|
if (ret != LY_SUCCESS) {
|
|
MP_LOG_W("Failed to create antenna-bitmask node.\n");
|
|
lyd_free_tree(root);
|
|
return false;
|
|
}
|
|
|
|
// Print to XML
|
|
// Note: LYD_PRINT_SIBLINGS in libyang v2.1+, LYD_PRINT_WITHSIBLINGS in older v2, LYP_WITHSIBLINGS in v1
|
|
#ifdef MPLANE_V1
|
|
lyd_print_mem(xml_content, root, LYD_XML, LYP_WITHSIBLINGS);
|
|
#elif defined MPLANE_V2
|
|
#ifdef LYD_PRINT_SIBLINGS
|
|
lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_SIBLINGS);
|
|
#else
|
|
lyd_print_mem(xml_content, root, LYD_XML, LYD_PRINT_WITHSIBLINGS);
|
|
#endif
|
|
#endif
|
|
lyd_free_tree(root);
|
|
|
|
if (*xml_content == NULL) {
|
|
MP_LOG_W("Failed to generate XML content.\n");
|
|
return false;
|
|
}
|
|
|
|
MP_LOG_I("Generated %s antenna mask XML for array '%s', mask=0x%02X\n",
|
|
is_tx ? "TX" : "RX", array_name, antenna_bitmask);
|
|
|
|
return true;
|
|
}
|
|
|
|
antenna_ctrl_result_t mplane_set_tx_antenna_mask(ru_session_t *ru_session,
|
|
const char *array_name,
|
|
uint8_t antenna_bitmask,
|
|
uint8_t num_antennas)
|
|
{
|
|
if (ru_session == NULL || ru_session->session == NULL) {
|
|
return ANTENNA_CTRL_ERR_NO_SESSION;
|
|
}
|
|
|
|
if (array_name == NULL || strlen(array_name) == 0) {
|
|
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
|
|
}
|
|
|
|
if (num_antennas == 0 || num_antennas > 8) {
|
|
return ANTENNA_CTRL_ERR_INVALID_MASK;
|
|
}
|
|
|
|
// Generate XML for antenna mask configuration
|
|
char *xml_content = NULL;
|
|
if (!mplane_create_antenna_mask_xml(ru_session, true, array_name,
|
|
antenna_bitmask, num_antennas, &xml_content)) {
|
|
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
|
|
}
|
|
|
|
MP_LOG_I("Setting TX antenna mask via NETCONF:\n%s\n", xml_content);
|
|
|
|
// Use existing M-Plane infrastructure to send edit-config, validate, commit
|
|
bool success = edit_val_commmit_rpc(ru_session, xml_content);
|
|
free(xml_content);
|
|
|
|
if (!success) {
|
|
MP_LOG_W("Failed to configure TX antenna mask for array '%s'\n", array_name);
|
|
return ANTENNA_CTRL_ERR_COMMIT_FAILED;
|
|
}
|
|
|
|
MP_LOG_I("Successfully configured TX antenna mask 0x%02X for array '%s'\n",
|
|
antenna_bitmask, array_name);
|
|
|
|
return ANTENNA_CTRL_SUCCESS;
|
|
}
|
|
|
|
antenna_ctrl_result_t mplane_set_rx_antenna_mask(ru_session_t *ru_session,
|
|
const char *array_name,
|
|
uint8_t antenna_bitmask,
|
|
uint8_t num_antennas)
|
|
{
|
|
if (ru_session == NULL || ru_session->session == NULL) {
|
|
return ANTENNA_CTRL_ERR_NO_SESSION;
|
|
}
|
|
|
|
if (array_name == NULL || strlen(array_name) == 0) {
|
|
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
|
|
}
|
|
|
|
if (num_antennas == 0 || num_antennas > 8) {
|
|
return ANTENNA_CTRL_ERR_INVALID_MASK;
|
|
}
|
|
|
|
// Generate XML for antenna mask configuration
|
|
char *xml_content = NULL;
|
|
if (!mplane_create_antenna_mask_xml(ru_session, false, array_name,
|
|
antenna_bitmask, num_antennas, &xml_content)) {
|
|
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
|
|
}
|
|
|
|
MP_LOG_I("Setting RX antenna mask via NETCONF:\n%s\n", xml_content);
|
|
|
|
// Use existing M-Plane infrastructure to send edit-config, validate, commit
|
|
bool success = edit_val_commmit_rpc(ru_session, xml_content);
|
|
free(xml_content);
|
|
|
|
if (!success) {
|
|
MP_LOG_W("Failed to configure RX antenna mask for array '%s'\n", array_name);
|
|
return ANTENNA_CTRL_ERR_COMMIT_FAILED;
|
|
}
|
|
|
|
MP_LOG_I("Successfully configured RX antenna mask 0x%02X for array '%s'\n",
|
|
antenna_bitmask, array_name);
|
|
|
|
return ANTENNA_CTRL_SUCCESS;
|
|
}
|
|
|
|
antenna_ctrl_result_t mplane_get_tx_antenna_mask(ru_session_t *ru_session,
|
|
const char *array_name,
|
|
uint8_t *antenna_bitmask)
|
|
{
|
|
if (ru_session == NULL || ru_session->session == NULL) {
|
|
return ANTENNA_CTRL_ERR_NO_SESSION;
|
|
}
|
|
|
|
if (array_name == NULL || antenna_bitmask == NULL) {
|
|
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
|
|
}
|
|
|
|
// Get operational datastore
|
|
char *operational_ds = NULL;
|
|
if (!get_mplane(ru_session, &operational_ds)) {
|
|
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
|
|
}
|
|
|
|
// Build XPath to find the antenna mask for this array
|
|
char xpath[256];
|
|
snprintf(xpath, sizeof(xpath),
|
|
"tx-array-antenna-mask-config[array-name='%s']/antenna-bitmask",
|
|
array_name);
|
|
|
|
// Extract the value from XML response
|
|
char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask");
|
|
free(operational_ds);
|
|
|
|
if (mask_b64 == NULL) {
|
|
// No mask configured, assume all active
|
|
*antenna_bitmask = 0xFF;
|
|
return ANTENNA_CTRL_SUCCESS;
|
|
}
|
|
|
|
*antenna_bitmask = base64_to_bitmask(mask_b64);
|
|
free(mask_b64);
|
|
|
|
MP_LOG_I("Retrieved TX antenna mask 0x%02X for array '%s'\n",
|
|
*antenna_bitmask, array_name);
|
|
|
|
return ANTENNA_CTRL_SUCCESS;
|
|
}
|
|
|
|
antenna_ctrl_result_t mplane_get_rx_antenna_mask(ru_session_t *ru_session,
|
|
const char *array_name,
|
|
uint8_t *antenna_bitmask)
|
|
{
|
|
if (ru_session == NULL || ru_session->session == NULL) {
|
|
return ANTENNA_CTRL_ERR_NO_SESSION;
|
|
}
|
|
|
|
if (array_name == NULL || antenna_bitmask == NULL) {
|
|
return ANTENNA_CTRL_ERR_INVALID_ARRAY_NAME;
|
|
}
|
|
|
|
// Get operational datastore
|
|
char *operational_ds = NULL;
|
|
if (!get_mplane(ru_session, &operational_ds)) {
|
|
return ANTENNA_CTRL_ERR_NETCONF_FAILED;
|
|
}
|
|
|
|
// Extract the value from XML response (simplified - real impl would use XPath)
|
|
char *mask_b64 = get_ru_xml_node(operational_ds, "antenna-bitmask");
|
|
free(operational_ds);
|
|
|
|
if (mask_b64 == NULL) {
|
|
// No mask configured, assume all active
|
|
*antenna_bitmask = 0xFF;
|
|
return ANTENNA_CTRL_SUCCESS;
|
|
}
|
|
|
|
*antenna_bitmask = base64_to_bitmask(mask_b64);
|
|
free(mask_b64);
|
|
|
|
MP_LOG_I("Retrieved RX antenna mask 0x%02X for array '%s'\n",
|
|
*antenna_bitmask, array_name);
|
|
|
|
return ANTENNA_CTRL_SUCCESS;
|
|
}
|
|
|
|
bool mplane_supports_trx_control(ru_session_t *ru_session)
|
|
{
|
|
if (ru_session == NULL || ru_session->session == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Check if MPLANE-TRX-CONTROL feature is advertised in RU capabilities
|
|
// This is done by checking the server capabilities from the hello exchange
|
|
// or by querying the YANG library
|
|
|
|
struct nc_session *session = (struct nc_session *)ru_session->session;
|
|
const char * const *caps = nc_session_get_cpblts(session);
|
|
|
|
if (caps == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Look for o-ran-uplane-conf module with MPLANE-TRX-CONTROL feature
|
|
for (int i = 0; caps[i] != NULL; i++) {
|
|
if (strstr(caps[i], "o-ran-uplane-conf") != NULL) {
|
|
// Module is supported, assume feature is enabled
|
|
// A more complete implementation would query module features
|
|
MP_LOG_I("RU supports o-ran-uplane-conf module (MPLANE-TRX-CONTROL)\n");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
MP_LOG_W("RU does not advertise o-ran-uplane-conf module\n");
|
|
return false;
|
|
}
|
|
|