Add O-RAN compliant antenna muting for energy saving
Implements MPLANE-TRX-CONTROL feature from O-RAN WG4: - nr_antenna_state: TX/RX antenna bitmask management - nr_antenna_control: TCP control interface on port 9999 - Integrated into nr_feptx (TX) and nr_fep (RX) paths - Blanks transmission/reception for muted antennas Commands: MUTE TX/RX <id>, UNMUTE TX/RX <id>, STATUS, STATS
This commit is contained in:
@@ -945,6 +945,8 @@ 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_state.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
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# O-RAN Compliant Antenna Muting / Energy Saving Module
|
||||
|
||||
## Overview
|
||||
|
||||
This module implements O-RAN WG4 compliant MIMO antenna muting for energy saving.
|
||||
It follows the `MPLANE-TRX-CONTROL` feature defined in `o-ran-uplane-conf.yang`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Control Interfaces │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ O1 (NETCONF) │ │ Test Socket │ │ Future: rApp/A1 │ │
|
||||
│ │ (Production) │ │ (Development) │ │ (Policy-based) │ │
|
||||
│ └────────┬────────┘ └────────┬────────┘ └─────────┬───────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └────────────────────┼─────────────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ nr_antenna_state_t (Antenna State Manager) │ │
|
||||
│ │ - tx_antenna_bitmask: O-RAN compliant antenna mask │ │
|
||||
│ │ - rx_antenna_bitmask: O-RAN compliant antenna mask │ │
|
||||
│ │ - Statistics for energy saving monitoring │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ PHY Layer Integration │ │
|
||||
│ │ - nr_beam_precoding_with_muting(): Blanks muted antennas │ │
|
||||
│ │ - Beamforming skipped for muted antennas │ │
|
||||
│ │ - Output zeroed = transmission blank (O-RAN compliant) │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## O-RAN Compliance
|
||||
|
||||
This implementation follows O-RAN Alliance specifications:
|
||||
|
||||
| O-RAN Element | Implementation |
|
||||
|---------------|----------------|
|
||||
| WG4 `o-ran-uplane-conf.yang` | `tx-array-antenna-mask-config` |
|
||||
| `antenna-bitmask` parameter | `nr_antenna_state_t.tx_antenna_bitmask` |
|
||||
| `MPLANE-TRX-CONTROL` feature | `nr_set_tx_antenna_bitmask()` |
|
||||
| Transmission blanking | Zeroing `txdataF_BF` for muted antennas |
|
||||
|
||||
## Files
|
||||
|
||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.h` - Antenna state API
|
||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_state.c` - Implementation
|
||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_control.h` - Test control interface
|
||||
- `openair1/PHY/NR_TRANSPORT/nr_antenna_control.c` - Socket server for testing
|
||||
- `openair1/PHY/MODULATION/beamforming.c` - Modified with `nr_beam_precoding_with_muting()`
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Initialize Antenna State (in gNB init)
|
||||
|
||||
```c
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||
|
||||
// Initialize with 4 TX and 4 RX antennas
|
||||
nr_init_antenna_state(&gNB->antenna_state, 4, 4, "tx-array-0", "rx-array-0");
|
||||
|
||||
// Optionally start test control server
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
|
||||
nr_antenna_control_start(&gNB->antenna_state, 9999);
|
||||
```
|
||||
|
||||
### 2. Control Antennas via Test Interface
|
||||
|
||||
Connect with netcat:
|
||||
```bash
|
||||
nc localhost 9999
|
||||
```
|
||||
|
||||
Commands:
|
||||
```
|
||||
> HELP # Show all commands
|
||||
> STATUS # Show current antenna state
|
||||
> MUTE TX 2 # Mute TX antenna 2
|
||||
> MUTE TX 3 # Mute TX antenna 3
|
||||
> SET TX MASK 03 # Set TX mask to 0x03 (antennas 0,1 active)
|
||||
> STATS # Show energy saving statistics
|
||||
```
|
||||
|
||||
### 3. Use in Beamforming
|
||||
|
||||
```c
|
||||
// Original (all antennas active):
|
||||
nr_beam_precoding(txdataF, txdataF_BF, fp, beam_weights, slot, symbol, aa, nb_ap, offset);
|
||||
|
||||
// With antenna muting support:
|
||||
nr_beam_precoding_with_muting(txdataF, txdataF_BF, fp, beam_weights,
|
||||
slot, symbol, aa, nb_ap, offset,
|
||||
&gNB->antenna_state);
|
||||
```
|
||||
|
||||
## Testing with rfsimulator
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Build OAI:
|
||||
```bash
|
||||
cd openairinterface5g/cmake_targets
|
||||
./build_oai --ninja --gNB --nrUE -w SIMU
|
||||
```
|
||||
|
||||
2. Install Open5GS:
|
||||
```bash
|
||||
sudo add-apt-repository ppa:open5gs/latest
|
||||
sudo apt update
|
||||
sudo apt install open5gs
|
||||
```
|
||||
|
||||
3. Add subscriber to Open5GS (using mongosh):
|
||||
```bash
|
||||
mongosh open5gs --eval 'db.subscribers.insertOne({
|
||||
imsi: "999700000000001",
|
||||
security: { k: "fec86ba6eb707ed08905757b1bb44b8f", amf: "8000", opc: "C42449363BBAD02B66D16BC975D77CC1" },
|
||||
slice: [{ sst: 1, default_indicator: true, session: [{ name: "internet", type: 3 }] }]
|
||||
})'
|
||||
```
|
||||
|
||||
### Quick Test (Antenna Control Only)
|
||||
|
||||
The easiest way to verify the implementation is to start the gNB and test the control interface:
|
||||
|
||||
```bash
|
||||
# Terminal 1: Start gNB
|
||||
cd cmake_targets/ran_build/build
|
||||
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf --rfsim
|
||||
|
||||
# Terminal 2: Test antenna control
|
||||
echo "STATUS" | nc -w 2 localhost 9999
|
||||
echo "MUTE TX 0" | nc -w 2 localhost 9999
|
||||
echo "STATUS" | nc -w 2 localhost 9999
|
||||
echo "STATS" | nc -w 2 localhost 9999
|
||||
```
|
||||
|
||||
### Full Test with Open5GS
|
||||
|
||||
Terminal 1 - Core Network (should already be running):
|
||||
```bash
|
||||
# Verify Open5GS is running
|
||||
systemctl status open5gs-amfd open5gs-upfd open5gs-smfd
|
||||
```
|
||||
|
||||
Terminal 2 - gNB (with Open5GS-compatible config):
|
||||
```bash
|
||||
cd cmake_targets/ran_build/build
|
||||
sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.2x2.rfsim.open5gs.conf --rfsim
|
||||
```
|
||||
|
||||
Terminal 3 - UE:
|
||||
```bash
|
||||
cd cmake_targets/ran_build/build
|
||||
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3319680000 \
|
||||
--rfsim -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.open5gs.conf
|
||||
```
|
||||
|
||||
Terminal 4 - Antenna Control:
|
||||
```bash
|
||||
nc localhost 9999
|
||||
> STATUS
|
||||
> MUTE TX 1 # Mute antenna 1
|
||||
> STATUS
|
||||
> STATS
|
||||
```
|
||||
|
||||
Terminal 5 - iperf (after UE attaches):
|
||||
```bash
|
||||
# Server on the UPF
|
||||
iperf3 -s -B 10.45.0.1
|
||||
|
||||
# Client from UE (or use tun interface)
|
||||
ping 10.45.0.1
|
||||
iperf3 -c 10.45.0.1 -t 30
|
||||
```
|
||||
|
||||
### Using the Test Script
|
||||
|
||||
A convenience script is provided:
|
||||
```bash
|
||||
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh gnb # Start gNB only
|
||||
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh mute # Test muting
|
||||
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh status # Check status
|
||||
./targets/PROJECTS/GENERIC-NR-5GC/CONF/run_antenna_muting_test.sh --help # Show all options
|
||||
```
|
||||
|
||||
### Observing MIMO Layer Reduction
|
||||
|
||||
With 4x4 MIMO:
|
||||
- All antennas active: Up to 4 layers possible
|
||||
- 2 antennas muted: Max 2 layers
|
||||
- 3 antennas muted: Max 1 layer
|
||||
|
||||
Throughput should decrease proportionally as antennas are muted.
|
||||
|
||||
## Future Work
|
||||
|
||||
1. **O1 NETCONF Integration**: Integrate with `radio/fhi_72/mplane/` for production O-RAN compliance
|
||||
2. **rApp Development**: Create Non-RT RIC rApp for policy-based antenna control
|
||||
3. **A1 Policy Interface**: Enable policy-based control from SMO
|
||||
4. **Power Measurement**: Add actual power consumption metrics (requires hardware)
|
||||
|
||||
## References
|
||||
|
||||
- O-RAN WG4 M-Plane Specification
|
||||
- `o-ran-uplane-conf.yang` - YANG model for U-plane configuration
|
||||
- `o-ran-wg4-features.yang` - Feature definitions including `MPLANE-TRX-CONTROL`
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
|
||||
#include "PHY/INIT/nr_phy_init.h"
|
||||
#include "PHY/MODULATION/nr_modulation.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"
|
||||
@@ -403,6 +404,13 @@ void init_eNB_afterRU(void)
|
||||
*/
|
||||
//init_precoding_weights(RC.gNB[inst]);
|
||||
init_gNB_Tpool(inst);
|
||||
|
||||
// Start O-RAN antenna control test server (port 9999)
|
||||
// Allows runtime antenna muting for energy saving testing
|
||||
// Connect with: nc localhost 9999
|
||||
if (nr_antenna_control_start(&gNB->antenna_state, 9999)) {
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,10 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
|
||||
|
||||
init_DLSCH_struct(gNB);
|
||||
|
||||
/// O-RAN compliant antenna state initialization for energy saving
|
||||
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);
|
||||
|
||||
gNB->nr_srs_info = (nr_srs_info_t **)malloc16_clear(gNB->max_nb_srs * sizeof(nr_srs_info_t*));
|
||||
for (int id = 0; id < gNB->max_nb_srs; id++) {
|
||||
gNB->nr_srs_info[id] = (nr_srs_info_t *)malloc16_clear(sizeof(nr_srs_info_t));
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "modulation_eNB.h"
|
||||
#include "nr_modulation.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,
|
||||
@@ -143,3 +144,57 @@ void nr_beam_precoding(c16_t **txdataF,
|
||||
15);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief NR beam precoding with O-RAN compliant antenna muting support
|
||||
*
|
||||
* This function extends nr_beam_precoding to support antenna muting for
|
||||
* energy saving per O-RAN WG4 MPLANE-TRX-CONTROL specification.
|
||||
*
|
||||
* When an antenna is muted (via nr_antenna_state), the output buffer
|
||||
* for that antenna is zeroed, effectively blanking transmission.
|
||||
*
|
||||
* @param txdataF Input frequency-domain data per antenna port
|
||||
* @param txdataF_BF Output beamformed data per physical antenna
|
||||
* @param frame_parms Frame parameters
|
||||
* @param beam_weights Beamforming weights
|
||||
* @param slot Current slot number
|
||||
* @param symbol Current symbol number
|
||||
* @param aa Physical antenna index
|
||||
* @param nb_antenna_ports Number of antenna ports
|
||||
* @param offset Frequency offset
|
||||
* @param ant_state Pointer to antenna state (NULL = all antennas 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 slot,
|
||||
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)) {
|
||||
// Antenna is muted - output remains zero (transmission blank)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,34 @@ 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] slot Current slot number
|
||||
* @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 slot,
|
||||
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,
|
||||
const c16_t *symbol_rotation,
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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 Test control interface implementation for antenna muting
|
||||
* \author OpenAirInterface
|
||||
* \date 2025
|
||||
* \version 1.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>
|
||||
|
||||
// Server state
|
||||
static struct {
|
||||
pthread_t thread;
|
||||
int server_fd;
|
||||
nr_antenna_state_t *ant_state;
|
||||
volatile bool running;
|
||||
int port;
|
||||
} g_ctrl = {0};
|
||||
|
||||
// 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) {
|
||||
snprintf(response, resp_size,
|
||||
"=== Antenna Status ===\n"
|
||||
"TX Array: %s\n"
|
||||
" Antennas: %d configured, %d active\n"
|
||||
" Bitmask: 0x%02X\n"
|
||||
"RX Array: %s\n"
|
||||
" Antennas: %d configured, %d active\n"
|
||||
" Bitmask: 0x%02X\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->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));
|
||||
}
|
||||
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, "HELP") == 0) {
|
||||
snprintf(response, resp_size,
|
||||
"=== O-RAN Antenna Control Test Interface ===\n"
|
||||
"Commands:\n"
|
||||
" MUTE TX <ant_id> - Mute TX antenna (0-based)\n"
|
||||
" UNMUTE TX <ant_id> - Unmute TX antenna\n"
|
||||
" MUTE RX <ant_id> - Mute RX antenna\n"
|
||||
" UNMUTE RX <ant_id> - Unmute RX antenna\n"
|
||||
" SET TX MASK <hex> - Set TX bitmask (O-RAN compliant)\n"
|
||||
" SET RX MASK <hex> - Set RX bitmask (O-RAN compliant)\n"
|
||||
" STATUS - Show current antenna state\n"
|
||||
" STATS - Show energy saving statistics\n"
|
||||
" RESET STATS - Reset statistics\n"
|
||||
" HELP - Show this help\n"
|
||||
" QUIT - Close connection\n"
|
||||
"\n"
|
||||
"Example: SET TX MASK 03 (enables antennas 0,1; mutes 2,3)\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
|
||||
const char *welcome =
|
||||
"=== OAI Antenna Control Server ===\n"
|
||||
"O-RAN WG4 MPLANE-TRX-CONTROL Test Interface\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] 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;
|
||||
}
|
||||
|
||||
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
|
||||
{
|
||||
if (g_ctrl.running) {
|
||||
LOG_W(PHY, "[ANTENNA_CTRL] Server already running\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ant_state == NULL) {
|
||||
LOG_E(PHY, "[ANTENNA_CTRL] Antenna state is NULL\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_ctrl.ant_state = ant_state;
|
||||
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_PORT;
|
||||
|
||||
// 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] 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;
|
||||
}
|
||||
|
||||
void nr_antenna_control_stop(void)
|
||||
{
|
||||
if (!g_ctrl.running) return;
|
||||
|
||||
g_ctrl.running = false;
|
||||
|
||||
// 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);
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_CTRL] Server stopped\n");
|
||||
}
|
||||
|
||||
bool nr_antenna_control_is_running(void)
|
||||
{
|
||||
return g_ctrl.running;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.h
|
||||
* \brief Test control interface for antenna muting (simulates O1 NETCONF interface)
|
||||
* \author OpenAirInterface
|
||||
* \date 2025
|
||||
* \version 1.0
|
||||
* \note This provides a simple socket-based interface to test antenna muting
|
||||
* without requiring a full NETCONF/M-plane implementation.
|
||||
* In production, use the O-RAN O1 interface via radio/fhi_72/mplane/
|
||||
*/
|
||||
|
||||
#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 antenna control server
|
||||
#define NR_ANTENNA_CONTROL_PORT 9999
|
||||
|
||||
/**
|
||||
* @brief Start the antenna control server thread
|
||||
*
|
||||
* Creates a simple TCP server that accepts commands to control antenna state.
|
||||
* This simulates the O-RAN O1 interface for testing purposes.
|
||||
*
|
||||
* Commands supported:
|
||||
* 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 (O-RAN compliant)
|
||||
* SET RX MASK <hex> - Set RX antenna bitmask (O-RAN compliant)
|
||||
* 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
|
||||
*/
|
||||
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
|
||||
|
||||
/**
|
||||
* @brief Stop the antenna control server
|
||||
*/
|
||||
void nr_antenna_control_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Check if antenna control server is running
|
||||
* @return true if server is running
|
||||
*/
|
||||
bool nr_antenna_control_is_running(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NR_ANTENNA_CONTROL_H */
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
LOG_I(PHY, "[ANTENNA_STATE] Initialized: TX antennas=%d (mask=0x%02X, array=%s), "
|
||||
"RX antennas=%d (mask=0x%02X, array=%s)\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);
|
||||
}
|
||||
|
||||
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;
|
||||
return state->tx_antenna_bitmask;
|
||||
}
|
||||
|
||||
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state)
|
||||
{
|
||||
if (state == NULL) return 0xFF;
|
||||
return state->rx_antenna_bitmask;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.h
|
||||
* \brief O-RAN compliant antenna state management for MIMO muting / energy saving
|
||||
* \author OpenAirInterface
|
||||
* \date 2025
|
||||
* \version 1.0
|
||||
* \note Implements O-RAN WG4 MPLANE-TRX-CONTROL feature (o-ran-uplane-conf.yang)
|
||||
* - tx-array-antenna-mask-config
|
||||
* - rx-array-antenna-mask-config
|
||||
* - mplane-trx-control-ant-mask-update notification
|
||||
*/
|
||||
|
||||
#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];
|
||||
|
||||
} 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NR_ANTENNA_STATE_H */
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "PHY/CODING/nrLDPC_extern.h"
|
||||
#include "PHY/CODING/nrLDPC_decoder/nrLDPC_types.h"
|
||||
#include "nfapi_nr_interface_scf.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||
|
||||
#define MAX_NUM_RU_PER_gNB 8
|
||||
#define MAX_PUCCH0_NID 8
|
||||
@@ -443,6 +444,10 @@ typedef struct PHY_VARS_gNB_s {
|
||||
|
||||
bool enable_analog_das;
|
||||
|
||||
/// O-RAN compliant antenna state for energy saving / MIMO muting
|
||||
/// Implements WG4 MPLANE-TRX-CONTROL feature
|
||||
nr_antenna_state_t antenna_state;
|
||||
|
||||
time_stats_t l1_tx_proc;
|
||||
time_stats_t l1_rx_proc;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "sched_nr.h"
|
||||
#include "PHY/MODULATION/modulation_common.h"
|
||||
#include "PHY/MODULATION/nr_modulation.h"
|
||||
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
|
||||
|
||||
#include "common/utils/LOG/log.h"
|
||||
#include "common/utils/system.h"
|
||||
@@ -196,14 +197,27 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
|
||||
if (nr_slot_select(cfg,frame_tx,slot_tx) == NR_UPLINK_SLOT)
|
||||
return;
|
||||
|
||||
// O-RAN compliant antenna muting (MPLANE-TRX-CONTROL)
|
||||
nr_antenna_state_t *ant_state = &gNB->antenna_state;
|
||||
|
||||
// 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) {
|
||||
for (int b = 0; b < ru->num_beams_period; b++) {
|
||||
for (int i = 0; i < ru->nb_tx; ++i) {
|
||||
int tx_idx = i + b * ru->nb_tx;
|
||||
memcpy((void*)ru->common.txdataF_BF[tx_idx],
|
||||
(void*)&gNB->common_vars.txdataF[b][i][txdataF_offset],
|
||||
fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||
|
||||
// Check if this TX antenna is muted for energy saving
|
||||
if (!nr_is_tx_antenna_active(ant_state, i)) {
|
||||
// Zero the output buffer for muted antenna - 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 slot %d\n", i, slot_tx);
|
||||
} else {
|
||||
// Normal copy for active antennas
|
||||
memcpy((void*)ru->common.txdataF_BF[tx_idx],
|
||||
(void*)&gNB->common_vars.txdataF[b][i][txdataF_offset],
|
||||
fp->samples_per_slot_wCP * sizeof(int32_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -234,19 +248,34 @@ void nr_feptx(void *arg)
|
||||
if (tx_idx == 0)
|
||||
start_meas(&ru->precoding_stats);
|
||||
|
||||
if (ru->gNB_list[0]->common_vars.analog_bf) {
|
||||
memcpy(&ru->common.beam_id[bb][slot * fp->symbols_per_slot],
|
||||
&ru->gNB_list[0]->common_vars.beam_id[bb][slot * fp->symbols_per_slot],
|
||||
(fp->symbols_per_slot) * sizeof(int));
|
||||
}
|
||||
// O-RAN compliant antenna muting check (MPLANE-TRX-CONTROL)
|
||||
// Check if this TX antenna is muted for energy saving
|
||||
PHY_VARS_gNB *gNB = ru->gNB_list[0];
|
||||
nr_antenna_state_t *ant_state = &gNB->antenna_state;
|
||||
bool antenna_muted = !nr_is_tx_antenna_active(ant_state, aa);
|
||||
|
||||
// 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)
|
||||
memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset],
|
||||
(void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset],
|
||||
numSamples * sizeof(int32_t));
|
||||
else {
|
||||
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
|
||||
if (antenna_muted) {
|
||||
// Zero the output buffer for muted antenna - transmission blanking
|
||||
memset((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset], 0,
|
||||
numSamples * sizeof(int32_t));
|
||||
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted, blanking slot %d symbols %d-%d\n",
|
||||
aa, slot, startSymbol, startSymbol + numSymbols - 1);
|
||||
} else {
|
||||
// Normal processing for active antennas
|
||||
if (ru->gNB_list[0]->common_vars.analog_bf) {
|
||||
memcpy(&ru->common.beam_id[bb][slot * fp->symbols_per_slot],
|
||||
&ru->gNB_list[0]->common_vars.beam_id[bb][slot * fp->symbols_per_slot],
|
||||
(fp->symbols_per_slot) * sizeof(int));
|
||||
}
|
||||
|
||||
// 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)
|
||||
memcpy((void*)&ru->common.txdataF_BF[tx_idx][txdataF_BF_offset],
|
||||
(void*)&ru->gNB_list[0]->common_vars.txdataF[bb][aa][txdataF_offset],
|
||||
numSamples * sizeof(int32_t));
|
||||
else {
|
||||
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (tx_idx == 0)
|
||||
@@ -333,13 +362,30 @@ void nr_fep(void* arg)
|
||||
|
||||
int idx = aid + beam * ru->nb_rx;
|
||||
int offset = (slot % RU_RX_SLOT_DEPTH) * fp->symbols_per_slot * fp->ofdm_symbol_size;
|
||||
for (int l = startSymbol; l <= endSymbol; l++)
|
||||
nr_slot_fep_ul(fp,
|
||||
ru->common.rxdata[idx],
|
||||
&ru->common.rxdataF[idx][offset],
|
||||
l,
|
||||
slot,
|
||||
ru->N_TA_offset);
|
||||
|
||||
// O-RAN compliant RX antenna muting check (MPLANE-TRX-CONTROL)
|
||||
PHY_VARS_gNB *gNB = ru->gNB_list[0];
|
||||
nr_antenna_state_t *ant_state = &gNB->antenna_state;
|
||||
bool rx_muted = !nr_is_rx_antenna_active(ant_state, aid);
|
||||
|
||||
if (rx_muted) {
|
||||
// Zero the output buffer for muted RX antenna - reception blanking
|
||||
int numSymbols = endSymbol - startSymbol + 1;
|
||||
memset(&ru->common.rxdataF[idx][offset + startSymbol * fp->ofdm_symbol_size], 0,
|
||||
numSymbols * fp->ofdm_symbol_size * sizeof(c16_t));
|
||||
LOG_D(PHY, "[ANTENNA_MUTE] RX antenna %d muted, blanking slot %d symbols %d-%d\n",
|
||||
aid, slot, startSymbol, endSymbol);
|
||||
} else {
|
||||
// Normal FFT processing for active RX antennas
|
||||
for (int l = startSymbol; l <= endSymbol; l++)
|
||||
nr_slot_fep_ul(fp,
|
||||
ru->common.rxdata[idx],
|
||||
&ru->common.rxdataF[idx][offset],
|
||||
l,
|
||||
slot,
|
||||
ru->N_TA_offset);
|
||||
}
|
||||
|
||||
VCD_SIGNAL_DUMPER_DUMP_FUNCTION_BY_NAME(VCD_SIGNAL_DUMPER_FUNCTIONS_PHY_PROCEDURES_RU_FEPRX+aid, 0);
|
||||
|
||||
// Task completed in //
|
||||
|
||||
Reference in New Issue
Block a user