From 3f3576bba6eb6351163341f10ebeb0b3e5a3213e Mon Sep 17 00:00:00 2001 From: Gabriele Gemmi Date: Sat, 7 Mar 2026 22:52:24 +0000 Subject: [PATCH 1/5] Minimal TLVs to do Beamforming with Aerial 25.2 --- nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c | 22 ++++++++++++------- .../nfapi/public_inc/nfapi_nr_interface_scf.h | 6 +++++ openair2/LAYER2/NR_MAC_gNB/config.c | 14 +++++++++++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c index 255d7d62e3..bdd7b224ce 100644 --- a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c +++ b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c @@ -1143,15 +1143,21 @@ uint8_t pack_nr_config_request(void *msg, uint8_t **ppWritePackedMsg, uint8_t *e &pack_uint8_tlv_value); numTLVs++; // END Measurement Config -#ifndef ENABLE_AERIAL - // START Digital Beam Table (DBT) PDU - if (pNfapiMsg->dbt_config.num_dig_beams != 0) { - nfapi_nr_dbt_tlv_ve_t dbt_tlv = {.tl.tag = NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG, .value = pNfapiMsg->dbt_config}; - pack_nr_tlv(NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG, &dbt_tlv, ppWritePackedMsg, end, &pack_dbt_table_tlv_value); - numTLVs++; - } - // END Digital Beam Table (DBT) PDU +#ifdef ENABLE_AERIAL + retval &= pack_nr_tlv(NFAPI_NR_CONFIG_NUM_TX_PORT_TAG, + &(pNfapiMsg->carrier_config.num_tx_port), + ppWritePackedMsg, + end, + &pack_uint16_tlv_value); + numTLVs++; + retval &= pack_nr_tlv(NFAPI_NR_CONFIG_NUM_RX_PORT_TAG, + &(pNfapiMsg->carrier_config.num_rx_port), + ppWritePackedMsg, + end, + &pack_uint16_tlv_value); + numTLVs++; +#else // START Precoding Matrix (PM) PDU if (pNfapiMsg->pmi_list.num_pm_idx != 0) { nfapi_nr_pm_tlv_ve_t pm_tlv = {.tl.tag = NFAPI_NR_CONFIG_PRECODING_TABLE_V6_TAG, .value = pNfapiMsg->pmi_list}; diff --git a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h index b466303cd8..5ffff64de4 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h @@ -332,6 +332,10 @@ typedef struct #define NFAPI_NR_CONFIG_TDD_TABLE 0x1035 #define NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG 0x1043 // This tag was added in version 5 of the SCF222 standard ( Table 3-50 of SCF222.10.05 ) #define NFAPI_NR_CONFIG_PRECODING_TABLE_V6_TAG 0x104B // This tag was added in version 6 of the SCF222 standard ( Table 3-52 of SCF222.10.06 ) +#ifdef ENABLE_AERIAL + #define NFAPI_NR_CONFIG_NUM_TX_PORT_TAG 0xA016 + #define NFAPI_NR_CONFIG_NUM_RX_PORT_TAG 0xA017 +#endif //table 3-21 typedef struct @@ -347,6 +351,8 @@ typedef struct nfapi_uint16_tlv_t ul_grid_size[5];//Grid size 𝑁𝑔𝑟𝑖𝑑 𝑠𝑖𝑧𝑒,𝜇 for each of the numerologies [38.211, sec 4.4.2]. Value: 0->275 0 = this numerology not used nfapi_uint16_tlv_t num_rx_ant;// nfapi_uint8_tlv_t frequency_shift_7p5khz;//Indicates presence of 7.5KHz frequency shift. Value: 0 = false 1 = true + nfapi_uint16_tlv_t num_tx_port; //used by Aerial L1 when BF mode is enabled to signal the number of logical antenna ports + nfapi_uint16_tlv_t num_rx_port; //used by Aerial L1 when BF mode is enabled to signal the number of logical antenna ports } nfapi_nr_carrier_config_t; diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index bc402e7125..fa452b5dfd 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -691,7 +691,19 @@ static void config_common(gNB_MAC_INST *nrmac, const nr_mac_config_t *config, NR cfg->carrier_config.num_tx_ant.value); cfg->num_tlv++; cfg->num_tlv++; - +#ifdef ENABLE_AERIAL + if (nrmac->beam_info.beam_mode == PRECONFIGURED_BEAM_IDX) { + // if we are doing BF in Aerial we need these Custom TLV + cfg->carrier_config.num_rx_ant.value = 64; //TOOD: Read number of baseband ports (phy ant) from Config? + cfg->carrier_config.num_tx_ant.value = 64; //TOOD: Read number of baseband ports (phy ant) from Config? + }else{ + // In CAT-A Mode these are equal to num_rx_ant (and Aerial ignores the value) + cfg->carrier_config.num_rx_port.value = pusch_AntennaPorts; + cfg->carrier_config.num_rx_port.tl.tag = NFAPI_NR_CONFIG_NUM_RX_PORT_TAG; + cfg->carrier_config.num_tx_port.value = num_pdsch_antenna_ports; + cfg->carrier_config.num_tx_port.tl.tag = NFAPI_NR_CONFIG_NUM_TX_PORT_TAG; + } +#endif // Frame structure configuration uint8_t mu = frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing; if (cfg->cell_config.frame_duplex_type.value == TDD) { From d448e99e643a242ca7c8dfacab0f5814f6dcc845 Mon Sep 17 00:00:00 2001 From: Gabriele Gemmi Date: Sat, 7 Mar 2026 23:30:54 +0000 Subject: [PATCH 2/5] read DBT from csv --- openair2/GNB_APP/MACRLC_nr_paramdef.h | 11 ++- openair2/GNB_APP/gnb_config.c | 104 ++++++++++++++++++++++++ openair2/LAYER2/NR_MAC_gNB/config.c | 75 +++++++++++++++++ openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 9 ++ 4 files changed, 196 insertions(+), 3 deletions(-) diff --git a/openair2/GNB_APP/MACRLC_nr_paramdef.h b/openair2/GNB_APP/MACRLC_nr_paramdef.h index cb46836cb4..02b16c66b5 100644 --- a/openair2/GNB_APP/MACRLC_nr_paramdef.h +++ b/openair2/GNB_APP/MACRLC_nr_paramdef.h @@ -77,6 +77,7 @@ #define CONFIG_STRING_MACRLC_BEAM_DURATION "beam_duration" #define CONFIG_STRING_MACRLC_BEAMS_PERIOD "beams_per_period" #define CONFIG_STRING_MACRLC_BEAM_WEIGHTS_LIST "beam_weights" +#define CONFIG_STRING_MACRLC_DBT_FILE "dbt_file" #define CONFIG_STRING_MACRLC_PUSCH_RSSI_THRESHOLD "pusch_RSSI_Threshold" #define CONFIG_STRING_MACRLC_PUCCH_RSSI_THRESHOLD "pucch_RSSI_Threshold" #define CONFIG_STRING_MACRLC_STATS_MAX_UE "stats_max_ue" @@ -97,6 +98,7 @@ #define HLP_MACRLC_AB "Flag to enable analog beamforming" #define HLP_MACRLC_BEAM_DURATION "number of consecutive slots for a given set of beams" #define HLP_MACRLC_BEAMS_PERIOD "set of beams that can be simultaneously allocated in a period" +#define HLP_MACRLC_DBT_FILE "File path to CSV file to read digital beamforming table" #define HLP_MACRLC_PUSCH_RSSI_THRESHOLD "Limits PUSCH TPC commands based on RSSI to prevent ADC railing. Value range [-1280, 0], unit 0.1 dBm/dBFS" #define HLP_MACRLC_PUCCH_RSSI_THRESHOLD "Limits PUCCH TPC commands based on RSSI to prevent ADC railing. Value range [-1280, 0], unit 0.1 dBm/dBFS" #define HLP_MACRLC_STATS_MAX_UE "Maximum number of UEs before disabling periodical output (0 to disable)" @@ -147,6 +149,7 @@ {CONFIG_STRING_MACRLC_BEAM_DURATION, HLP_MACRLC_BEAM_DURATION, 0, .u8ptr=NULL, .defintval=1, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_BEAMS_PERIOD, HLP_MACRLC_BEAMS_PERIOD, 0, .u8ptr=NULL, .defintval=1, TYPE_UINT8, 0}, \ {CONFIG_STRING_MACRLC_BEAM_WEIGHTS_LIST, NULL, 0, .iptr=NULL, .defintarrayval=0, TYPE_INTARRAY,0}, \ + {CONFIG_STRING_MACRLC_DBT_FILE, HLP_MACRLC_DBT_FILE, 0, .strptr=NULL, .defstrval=NULL, TYPE_STRING, 0}, \ {CONFIG_STRING_MACRLC_PUSCH_RSSI_THRESHOLD, HLP_MACRLC_PUSCH_RSSI_THRESHOLD, \ 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \ {CONFIG_STRING_MACRLC_PUCCH_RSSI_THRESHOLD, HLP_MACRLC_PUCCH_RSSI_THRESHOLD, \ @@ -195,9 +198,10 @@ #define MACRLC_ANALOG_BEAM_DURATION_IDX 37 #define MACRLC_ANALOG_BEAMS_PERIOD_IDX 38 #define MACRLC_BEAMWEIGHTS_IDX 39 -#define MACRLC_PUSCH_RSSI_THRES_IDX 40 -#define MACRLC_PUCCH_RSSI_THRES_IDX 41 -#define MACRLC_STATS_MAX_UE_IDX 42 +#define MACRLC_DBT_FILE_IDX 40 +#define MACRLC_PUSCH_RSSI_THRES_IDX 41 +#define MACRLC_PUCCH_RSSI_THRES_IDX 42 +#define MACRLC_STATS_MAX_UE_IDX 43 #define MACRLCPARAMS_CHECK { \ { .s5 = { NULL } }, \ @@ -243,6 +247,7 @@ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ { .s5 = { NULL } }, \ + { .s5 = { NULL } }, \ { .s2 = { config_check_intrange, {-1280, 0}} }, /* PUSCH RSSI threshold range */ \ { .s2 = { config_check_intrange, {-1280, 0}} }, /* PUCCH RSSI threshold range */ \ { .s5 = { NULL } }, \ diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index d3618bdaaf..42ee49ca5a 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -28,6 +28,9 @@ */ #include "gnb_config.h" +#include +#include +#include #include #include #include @@ -1361,6 +1364,97 @@ static void get_bwp_config(nr_mac_config_t *configuration, const NR_ServingCellC } } +static bool parse_complex_token(const char *tok, double complex *out) +{ + double re = 0.0; + double im = 0.0; + if (sscanf(tok, "%lf%lfi", &re, &im) == 2 || sscanf(tok, "%lf%lfj", &re, &im) == 2) { + *out = re + I * im; + return true; + } + if (sscanf(tok, "%lfi", &im) == 1 || sscanf(tok, "%lfj", &im) == 1) { + *out = I * im; + return true; + } + if (sscanf(tok, "%lf", &re) == 1) { + *out = re; + return true; + } + return false; +} + +static double complex **read_dbt_from_csv(const char *filename, + int *num_beams, + int *num_weights_per_beam, + uint16_t **beam_ids) +{ + FILE *fp = fopen(filename, "r"); + AssertFatal(fp != NULL, "Failed to open DBT CSV file '%s'\n", filename); + + char line[16384]; + AssertFatal(fgets(line, sizeof(line), fp) != NULL, "Failed to read DBT CSV header from '%s'\n", filename); + + int weights = 1; + for (char *p = line; *p; ++p) + if (*p == ',') + weights++; + weights -= 1; // first column is beam ID + AssertFatal(weights > 0, "DBT CSV '%s' has no weight columns\n", filename); + + int beams = 0; + while (fgets(line, sizeof(line), fp)) + beams++; + AssertFatal(beams > 0, "No DBT beam rows found in CSV file '%s'\n", filename); + + rewind(fp); + AssertFatal(fgets(line, sizeof(line), fp) != NULL, "Failed to reread DBT CSV header from '%s'\n", filename); + + double complex **table = calloc_or_fail(beams, sizeof(*table)); + uint16_t *ids = calloc_or_fail(beams, sizeof(*ids)); + + int b = 0; + while (fgets(line, sizeof(line), fp)) { + line[strcspn(line, "\r\n")] = '\0'; + char *saveptr = NULL; + char *tok = strtok_r(line, ",", &saveptr); + AssertFatal(tok != NULL, "Malformed DBT row %d in '%s'\n", b + 1, filename); + + errno = 0; + char *endptr = NULL; + long beam_id = strtol(tok, &endptr, 10); + AssertFatal(endptr != tok && errno != ERANGE && *endptr == '\0' && beam_id >= 0 && beam_id <= UINT16_MAX, + "Invalid DBT beam id '%s' in file '%s', beam row %d\n", + tok, + filename, + b + 1); + ids[b] = (uint16_t)beam_id; + + table[b] = calloc_or_fail(weights, sizeof(*table[b])); + for (int w = 0; w < weights; ++w) { + tok = strtok_r(NULL, ",", &saveptr); + AssertFatal(tok != NULL, "Missing DBT weight in file '%s', beam row %d column %d\n", filename, b + 1, w + 1); + AssertFatal(parse_complex_token(tok, &table[b][w]), + "Invalid DBT complex weight '%s' in file '%s', beam row %d column %d\n", + tok, + filename, + b + 1, + w + 1); + } + AssertFatal(strtok_r(NULL, ",", &saveptr) == NULL, + "Too many DBT columns in file '%s', beam row %d\n", + filename, + b + 1); + b++; + } + AssertFatal(b == beams, "Parsed %d DBT rows but counted %d in file '%s'\n", b, beams, filename); + + fclose(fp); + *num_beams = beams; + *num_weights_per_beam = weights; + *beam_ids = ids; + return table; +} + void RCconfig_nr_macrlc(configmodule_interface_t *cfg) { int j = 0; @@ -1620,6 +1714,16 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) for (int b = 0; b < n; b++) config.bw_list[b] = MacRLC_ParamList.paramarray[j][MACRLC_BEAMWEIGHTS_IDX].iptr[b]; } + config.bt.num_beams = 0; + config.bt.num_weights_per_beam = 0; + config.bt.beam_ids = NULL; + config.bt.beam_weights = NULL; + char **fptr = MacRLC_ParamList.paramarray[j][MACRLC_DBT_FILE_IDX].strptr; + if (fptr && *fptr && **fptr != '\0') { + LOG_I(GNB_APP, "loading DBT table from file %s\n", *fptr); + config.bt.beam_weights = + read_dbt_from_csv(*fptr, &config.bt.num_beams, &config.bt.num_weights_per_beam, &config.bt.beam_ids); + } // triggers also PHY initialization in case we have L1 via FAPI nr_mac_config_scc(RC.nrmac[j], scc, &config); } // for (j=0;j +#include #include #include #include @@ -64,6 +65,41 @@ c16_t convert_precoder_weight(double complex c_in) return (c16_t) {.r = round(SHRT_MAX*creal(c_in)), .i = round(SHRT_MAX*cimag(c_in))}; } +static void free_dbt_config(nfapi_nr_dbt_pdu_t *dbt) +{ + if (!dbt || !dbt->dig_beam_list) + return; + for (int i = 0; i < dbt->num_dig_beams; ++i) + free(dbt->dig_beam_list[i].txru_list); + free(dbt->dig_beam_list); + dbt->dig_beam_list = NULL; + dbt->num_dig_beams = 0; + dbt->num_txrus = 0; +} + +static void log_dbt_table(const nr_beam_table_t *bt) +{ + if (!bt || bt->num_beams <= 0 || bt->num_weights_per_beam <= 0 || !bt->beam_weights) + return; + + LOG_I(NR_MAC, "DBT dump: num_beams=%d num_weights_per_beam=%d\n", bt->num_beams, bt->num_weights_per_beam); + + for (int b = 0; b < bt->num_beams; ++b) { + uint16_t beam_id = bt->beam_ids ? bt->beam_ids[b] : b; + const int max_line = 2048; + char line[max_line]; + int off = snprintf(line, sizeof(line), "DBT beam[%d] id=%u:", b, beam_id); + for (int w = 0; w < bt->num_weights_per_beam && off > -1 && off < max_line - 1; ++w) + off += snprintf(line + off, + sizeof(line) - off, + " w[%d]=(%0.6f,%0.6f)", + w, + creal(bt->beam_weights[b][w]), + cimag(bt->beam_weights[b][w])); + LOG_I(NR_MAC, "%s\n", line); + } +} + void get_K1_K2(int N1, int N2, int *K1, int *K2, int layers) { // num of allowed k1 and k2 according to 5.2.2.2.1-3 and -4 in 38.214 @@ -395,6 +431,45 @@ static void config_common(gNB_MAC_INST *nrmac, const nr_mac_config_t *config, NR { nfapi_nr_config_request_scf_t *cfg = &nrmac->config[0]; nrmac->common_channels[0].ServingCellConfigCommon = scc; + free_dbt_config(&cfg->dbt_config); + if (config->bt.num_beams > 0) { + AssertFatal(config->bt.beam_weights != NULL, + "DBT config invalid: num_beams=%d but beam_weights is NULL\n", + config->bt.num_beams); + AssertFatal(config->bt.num_weights_per_beam > 0, + "DBT config invalid: num_weights_per_beam=%d\n", + config->bt.num_weights_per_beam); + AssertFatal(config->bt.num_weights_per_beam <= UINT16_MAX, + "DBT num_weights_per_beam %d exceeds uint16 max\n", + config->bt.num_weights_per_beam); + AssertFatal(config->bt.num_beams <= UINT16_MAX, + "DBT num_beams %d exceeds uint16 max\n", + config->bt.num_beams); + + cfg->dbt_config.num_dig_beams = (uint16_t)config->bt.num_beams; + cfg->dbt_config.num_txrus = (uint16_t)config->bt.num_weights_per_beam; + cfg->dbt_config.dig_beam_list = calloc_or_fail(cfg->dbt_config.num_dig_beams, sizeof(*cfg->dbt_config.dig_beam_list)); + for (uint16_t b = 0; b < cfg->dbt_config.num_dig_beams; ++b) { + nfapi_nr_dig_beam_t *beam = &cfg->dbt_config.dig_beam_list[b]; + beam->beam_idx = config->bt.beam_ids ? config->bt.beam_ids[b] : b; + beam->txru_list = calloc_or_fail(cfg->dbt_config.num_txrus, sizeof(*beam->txru_list)); + for (uint16_t w = 0; w < cfg->dbt_config.num_txrus; ++w) { + float re = crealf(config->bt.beam_weights[b][w]); + float im = cimagf(config->bt.beam_weights[b][w]); + AssertFatal(re > -1.0f && re < 1.0f, "DBT real weight out of range [-1,1]: %f\n", re); + AssertFatal(im > -1.0f && im < 1.0f, "DBT imag weight out of range [-1,1]: %f\n", im); + c16_t q15 = convert_precoder_weight(config->bt.beam_weights[b][w]); + beam->txru_list[w].dig_beam_weight_Re = (uint16_t)q15.r; + beam->txru_list[w].dig_beam_weight_Im = (uint16_t)q15.i; + } + } + + LOG_I(NR_MAC, + "Loaded DBT: num_beams=%d num_weights_per_beam=%d\n", + config->bt.num_beams, + config->bt.num_weights_per_beam); + log_dbt_table(&config->bt); + } // Carrier configuration NR_FrequencyInfoDL_t *frequencyInfoDL = scc->downlinkConfigCommon->frequencyInfoDL; diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index adbba9c772..794387a5da 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include "common/utils/ds/seq_arr.h" #include "common/utils/nr/nr_common.h" @@ -184,6 +185,13 @@ typedef enum { SSB_SINR, } nr_config_report_type_t; +typedef struct nr_beam_table { + int num_weights_per_beam; + int num_beams; + uint16_t *beam_ids; + double complex **beam_weights; +} nr_beam_table_t; + typedef struct nr_mac_config_s { nr_pdsch_AntennaPorts_t pdsch_AntennaPorts; int pusch_AntennaPorts; @@ -213,6 +221,7 @@ typedef struct nr_mac_config_s { nr_redcap_config_t *redcap; nr_ptrs_config_t *ptrs; nr_config_report_type_t report_type; + nr_beam_table_t bt; } nr_mac_config_t; typedef struct NR_preamble_ue { From b4a3440128c92b64fdbd8166206688e9ed39c6e8 Mon Sep 17 00:00:00 2001 From: Gabriele Gemmi Date: Sun, 8 Mar 2026 00:29:31 +0000 Subject: [PATCH 3/5] Pack and send DBT to Aerial --- nfapi/oai_integration/aerial/fapi_nvIPC.c | 40 ++++++++++++++++++- nfapi/open-nFAPI/fapi/inc/nr_fapi_p5.h | 1 + nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c | 18 ++++++++- .../nfapi/public_inc/nfapi_nr_interface_scf.h | 4 +- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/nfapi/oai_integration/aerial/fapi_nvIPC.c b/nfapi/oai_integration/aerial/fapi_nvIPC.c index bbb0d66900..89f0786ec4 100644 --- a/nfapi/oai_integration/aerial/fapi_nvIPC.c +++ b/nfapi/oai_integration/aerial/fapi_nvIPC.c @@ -49,6 +49,7 @@ #include #include "fapi_nvIPC.h" #include +#include #include #include "nfapi_interface.h" #include "nfapi.h" @@ -66,6 +67,7 @@ nv_ipc_t *ipc; static nv_ipc_config_t nv_ipc_config; static int cpu_msg_buf_size = 0; static int cpu_data_buf_size = 0; +static int cpu_large_buf_size = 0; void nvIPC_Stop() { @@ -238,11 +240,21 @@ bool aerial_nr_send_p5_message(vnf_t *vnf, uint16_t p5_idx, nfapi_nr_p4_p5_messa // Create the message nv_ipc_msg_t send_msg = {.msg_id = msg->message_id, .cell_id = 0, - // For P5 we don't need CPU_DATA + // By default, P5 uses only message pool. .data_pool = NV_IPC_MEMPOOL_CPU_MSG, .data_len = 0, .data_buf = NULL}; + bool has_separate_dbt_payload = false; + nfapi_nr_config_request_scf_t *config_req = NULL; + if (msg->message_id == NFAPI_NR_PHY_MSG_TYPE_CONFIG_REQUEST) { + config_req = (nfapi_nr_config_request_scf_t *)msg; + has_separate_dbt_payload = config_req->dbt_config.num_dig_beams > 0; + if (has_separate_dbt_payload) { + send_msg.data_pool = NV_IPC_MEMPOOL_CPU_LARGE; + } + } + // Allocate the message if (!allocate_msg(&send_msg)) { return false; @@ -263,6 +275,31 @@ bool aerial_nr_send_p5_message(vnf_t *vnf, uint16_t p5_idx, nfapi_nr_p4_p5_messa } // Set the length send_msg.msg_len = packedMessageLengthFAPI + 8; // adding 8 to account for the size of the FAPI header + + if (has_separate_dbt_payload) { + AssertFatal(send_msg.data_buf != NULL, "CONFIG.request DBT path: data buffer is NULL\n"); + AssertFatal(cpu_large_buf_size > 0, "CONFIG.request DBT path: CPU_LARGE buffer size is not set\n"); + uint8_t *write_ptr = send_msg.data_buf; + uint8_t *buffer_end = send_msg.data_buf + cpu_large_buf_size; + nfapi_nr_dbt_tlv_ve_t dbt_tlv = { + .tl.tag = NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG, + .value = config_req->dbt_config + }; + bool ok = pack_dbt_table_tlv_value(&dbt_tlv, &write_ptr, buffer_end); + if (!ok) { + LOG_E(NFAPI_VNF, "Failed to pack CONFIG.request DBT payload into separate buffer\n"); + release_msg(&send_msg); + return false; + } + send_msg.data_len = (uint32_t)(write_ptr - (uint8_t *)send_msg.data_buf); + LOG_I(NFAPI_VNF, + "CONFIG.request DBT sent in separate pool: num_beams=%u num_txrus=%u data_len=%d pool=%d\n", + config_req->dbt_config.num_dig_beams, + config_req->dbt_config.num_txrus, + send_msg.data_len, + send_msg.data_pool); + } + // Send return send_nvipc_msg(&send_msg); } else { @@ -386,6 +423,7 @@ int nvIPC_Init(nvipc_params_t nvipc_params_s) // save the mempool sizes for later use in packing cpu_msg_buf_size = nv_ipc_get_buf_size(&nv_ipc_config, NV_IPC_MEMPOOL_CPU_MSG); cpu_data_buf_size = nv_ipc_get_buf_size(&nv_ipc_config, NV_IPC_MEMPOOL_CPU_DATA); + cpu_large_buf_size = nv_ipc_get_buf_size(&nv_ipc_config, NV_IPC_MEMPOOL_CPU_LARGE); // Create nv_ipc_t instance LOG_I(NFAPI_VNF, "%s: creating IPC interface with prefix %s\n", __func__, nvipc_params_s.nvipc_shm_prefix); diff --git a/nfapi/open-nFAPI/fapi/inc/nr_fapi_p5.h b/nfapi/open-nFAPI/fapi/inc/nr_fapi_p5.h index d47f0ec929..8204f42e9f 100644 --- a/nfapi/open-nFAPI/fapi/inc/nr_fapi_p5.h +++ b/nfapi/open-nFAPI/fapi/inc/nr_fapi_p5.h @@ -54,4 +54,5 @@ uint8_t pack_nr_stop_indication(void *msg, uint8_t **ppWritePackedMsg, uint8_t * uint8_t unpack_nr_stop_indication(uint8_t **ppReadPackedMsg, uint8_t *end, void *msg, nfapi_p4_p5_codec_config_t *config); uint8_t pack_nr_error_indication(void *msg, uint8_t **ppWritePackedMsg, uint8_t *end, nfapi_p4_p5_codec_config_t *config); uint8_t unpack_nr_error_indication(uint8_t **ppReadPackedMsg, uint8_t *end, void *msg, nfapi_p4_p5_codec_config_t *config); +uint8_t pack_dbt_table_tlv_value(void *tlv, uint8_t **ppWritePackedMsg, uint8_t *end); #endif // OPENAIRINTERFACE_NR_FAPI_P5_H diff --git a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c index bdd7b224ce..e6bb3814ea 100644 --- a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c +++ b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c @@ -721,8 +721,7 @@ uint8_t unpack_nr_param_response(uint8_t **ppReadPackedMsg, uint8_t *end, void * config, &pNfapiMsg->vendor_extension)); } -#ifndef ENABLE_AERIAL -static uint8_t pack_dbt_table_tlv_value(void *tlv, uint8_t **ppWritePackedMsg, uint8_t *end) +uint8_t pack_dbt_table_tlv_value(void *tlv, uint8_t **ppWritePackedMsg, uint8_t *end) { nfapi_nr_dbt_tlv_ve_t *dbt_ve = (nfapi_nr_dbt_tlv_ve_t *)tlv; nfapi_nr_dbt_pdu_t *dbt_config = &dbt_ve->value; @@ -744,6 +743,7 @@ static uint8_t pack_dbt_table_tlv_value(void *tlv, uint8_t **ppWritePackedMsg, u return 1; } +#ifndef ENABLE_AERIAL static uint8_t pack_pm_table_tlv_value(void *tlv, uint8_t **ppWritePackedMsg, uint8_t *end) { nfapi_nr_pm_tlv_ve_t *pm_ve = (nfapi_nr_pm_tlv_ve_t *)tlv; @@ -1143,6 +1143,20 @@ uint8_t pack_nr_config_request(void *msg, uint8_t **ppWritePackedMsg, uint8_t *e &pack_uint8_tlv_value); numTLVs++; // END Measurement Config + + // START Digital Beam Table (DBT) PDU + if (pNfapiMsg->dbt_config.num_dig_beams != 0) { + nfapi_nr_dbt_tlv_ve_t dbt_tlv = {.tl.tag = NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG, .value = pNfapiMsg->dbt_config}; +#ifdef ENABLE_AERIAL + // For Aerial, DBT payload is sent in a separate nvIPC data buffer. + dbt_tlv.value.num_dig_beams = 0; + dbt_tlv.value.num_txrus = 0; +#endif + retval &= pack_nr_tlv(NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG, &dbt_tlv, ppWritePackedMsg, end, &pack_dbt_table_tlv_value); + numTLVs++; + } + // END Digital Beam Table (DBT) PDU + #ifdef ENABLE_AERIAL retval &= pack_nr_tlv(NFAPI_NR_CONFIG_NUM_TX_PORT_TAG, &(pNfapiMsg->carrier_config.num_tx_port), diff --git a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h index 5ffff64de4..ab7c678ea4 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h @@ -330,11 +330,13 @@ typedef struct #define NFAPI_NR_CONFIG_RSSI_MEASUREMENT_TAG 0x1028 #define NFAPI_NR_CONFIG_TDD_TABLE 0x1035 -#define NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG 0x1043 // This tag was added in version 5 of the SCF222 standard ( Table 3-50 of SCF222.10.05 ) #define NFAPI_NR_CONFIG_PRECODING_TABLE_V6_TAG 0x104B // This tag was added in version 6 of the SCF222 standard ( Table 3-52 of SCF222.10.06 ) #ifdef ENABLE_AERIAL #define NFAPI_NR_CONFIG_NUM_TX_PORT_TAG 0xA016 #define NFAPI_NR_CONFIG_NUM_RX_PORT_TAG 0xA017 + #define NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG 0xA010 +#else + #define NFAPI_NR_CONFIG_BEAMFORMING_TABLE_TAG 0x1043 // This tag was added in version 5 of the SCF222 standard ( Table 3-50 of SCF222.10.05 ) #endif //table 3-21 From 610795c7d80a7622f9016d319ca54c4076a8f33b Mon Sep 17 00:00:00 2001 From: Gabriele Gemmi Date: Tue, 10 Mar 2026 01:44:37 +0000 Subject: [PATCH 4/5] Read DBT from libconfig --- openair2/GNB_APP/gnb_config.c | 74 +++++++++++++++++++++++++++++ openair2/LAYER2/NR_MAC_gNB/config.c | 8 ++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 42ee49ca5a..720044c213 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1455,6 +1455,75 @@ static double complex **read_dbt_from_csv(const char *filename, return table; } +#define DBT_PARAMS_DESC { \ + {"", "beam", 0, .strlistptr=NULL, .defstrlistval=NULL, TYPE_STRINGLIST, 0}, \ +} + +static double complex **read_dbt_from_config(const char *prefix, + int *num_beams, + int *num_weights_per_beam, + uint16_t **beam_ids) +{ + paramdef_t dbt_params[] = DBT_PARAMS_DESC; + paramlist_def_t dbt_list = {"dbt", NULL, 0}; + int ret = config_getlist(config_get_if(), &dbt_list, dbt_params, sizeofArray(dbt_params), prefix); + if (ret <= 0) { + *num_beams = 0; + *num_weights_per_beam = 0; + *beam_ids = NULL; + return NULL; + } + + const int beams = ret; + int weights = 0; + double complex **table = calloc_or_fail(beams, sizeof(*table)); + uint16_t *ids = calloc_or_fail(beams, sizeof(*ids)); + + for (int b = 0; b < beams; ++b) { + paramdef_t *row = &dbt_list.paramarray[b][0]; + AssertFatal(row->strlistptr != NULL, "Invalid dbt row %d in section '%s': missing string list\n", b, prefix); + AssertFatal(row->numelt >= 2, + "Invalid dbt row %d in section '%s': expected beam_id + >=1 weights, got %d elements\n", + b, + prefix, + row->numelt); + + const int row_weights = row->numelt - 1; + if (weights == 0) + weights = row_weights; + AssertFatal(weights == row_weights, + "Inconsistent dbt row width in section '%s': row %d has %d weights, expected %d\n", + prefix, + b, + row_weights, + weights); + + errno = 0; + char *endptr = NULL; + long beam_id = strtol(row->strlistptr[0], &endptr, 10); + AssertFatal(endptr != row->strlistptr[0] && errno != ERANGE && *endptr == '\0' && beam_id >= 0 && beam_id <= UINT16_MAX, + "Invalid dbt beam id '%s' in section '%s', row %d\n", + row->strlistptr[0], + prefix, + b); + ids[b] = (uint16_t)beam_id; + + table[b] = calloc_or_fail(weights, sizeof(*table[b])); + for (int w = 0; w < weights; ++w) + AssertFatal(parse_complex_token(row->strlistptr[w + 1], &table[b][w]), + "Invalid dbt complex weight '%s' in section '%s', row %d col %d\n", + row->strlistptr[w + 1], + prefix, + b, + w + 1); + } + + *num_beams = beams; + *num_weights_per_beam = weights; + *beam_ids = ids; + return table; +} + void RCconfig_nr_macrlc(configmodule_interface_t *cfg) { int j = 0; @@ -1723,6 +1792,11 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) LOG_I(GNB_APP, "loading DBT table from file %s\n", *fptr); config.bt.beam_weights = read_dbt_from_csv(*fptr, &config.bt.num_beams, &config.bt.num_weights_per_beam, &config.bt.beam_ids); + } else { + char prefix[MAX_OPTNAME_SIZE * 2 + 8]; + snprintf(prefix, sizeof(prefix), CONFIG_STRING_MACRLC_LIST ".[%d]", j); + config.bt.beam_weights = + read_dbt_from_config(prefix, &config.bt.num_beams, &config.bt.num_weights_per_beam, &config.bt.beam_ids); } // triggers also PHY initialization in case we have L1 via FAPI nr_mac_config_scc(RC.nrmac[j], scc, &config); diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index dc26361712..643b607d25 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -82,7 +82,7 @@ static void log_dbt_table(const nr_beam_table_t *bt) if (!bt || bt->num_beams <= 0 || bt->num_weights_per_beam <= 0 || !bt->beam_weights) return; - LOG_I(NR_MAC, "DBT dump: num_beams=%d num_weights_per_beam=%d\n", bt->num_beams, bt->num_weights_per_beam); + LOG_I(NR_MAC, "DBT: num_beams=%d num_weights_per_beam=%d\n", bt->num_beams, bt->num_weights_per_beam); for (int b = 0; b < bt->num_beams; ++b) { uint16_t beam_id = bt->beam_ids ? bt->beam_ids[b] : b; @@ -96,7 +96,7 @@ static void log_dbt_table(const nr_beam_table_t *bt) w, creal(bt->beam_weights[b][w]), cimag(bt->beam_weights[b][w])); - LOG_I(NR_MAC, "%s\n", line); + LOG_D(NR_MAC, "%s\n", line); } } @@ -456,8 +456,8 @@ static void config_common(gNB_MAC_INST *nrmac, const nr_mac_config_t *config, NR for (uint16_t w = 0; w < cfg->dbt_config.num_txrus; ++w) { float re = crealf(config->bt.beam_weights[b][w]); float im = cimagf(config->bt.beam_weights[b][w]); - AssertFatal(re > -1.0f && re < 1.0f, "DBT real weight out of range [-1,1]: %f\n", re); - AssertFatal(im > -1.0f && im < 1.0f, "DBT imag weight out of range [-1,1]: %f\n", im); + AssertFatal(re >= -1.0f && re <= 1.0f, "DBT real weight out of range [-1,1]: %f\n", re); + AssertFatal(im >= -1.0f && im <= 1.0f, "DBT imag weight out of range [-1,1]: %f\n", im); c16_t q15 = convert_precoder_weight(config->bt.beam_weights[b][w]); beam->txru_list[w].dig_beam_weight_Re = (uint16_t)q15.r; beam->txru_list[w].dig_beam_weight_Im = (uint16_t)q15.i; From 878f6b3dcaaecd0d98dd86a3fb7bb3be11e77ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Soares=20Silva?= Date: Thu, 19 Mar 2026 22:08:08 +0000 Subject: [PATCH 5/5] Change pack_nr_config_request to not pack VE TLVs 0xA000 and 0xA001 when compiled with Aerial, allowing them to be filled, but not sent to Aerial L1. --- nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c index e6bb3814ea..88a6d19c7b 100644 --- a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c +++ b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c @@ -1229,9 +1229,7 @@ uint8_t pack_nr_config_request(void *msg, uint8_t **ppWritePackedMsg, uint8_t *e NFAPI_TRACE(NFAPI_TRACE_DEBUG, "Packing CONFIG.request vendor_extension_tlv %d\n", pNfapiMsg->vendor_extension->tag); numTLVs++; } -#endif - AssertFatal(pNfapiMsg->analog_beamforming_ve.num_beams_period_vendor_ext.tl.tag == 0, "BF Vendor extension shouldn't be set!"); // The call to pack the TLV would be the same as any other TLV, it is only packed if the tag is set, // so, it's safe to add the call to pack_nr_tlv even if it is not always set retval &= pack_nr_tlv(NFAPI_NR_FAPI_NUM_BEAMS_PERIOD_VENDOR_EXTENSION_TAG, @@ -1242,7 +1240,7 @@ uint8_t pack_nr_config_request(void *msg, uint8_t **ppWritePackedMsg, uint8_t *e // only increase if it was set numTLVs += pNfapiMsg->analog_beamforming_ve.num_beams_period_vendor_ext.tl.tag == NFAPI_NR_FAPI_NUM_BEAMS_PERIOD_VENDOR_EXTENSION_TAG; - AssertFatal(pNfapiMsg->analog_beamforming_ve.analog_bf_vendor_ext.tl.tag == 0, "BF Vendor extension shouldn't be set!"); + // The call to pack the TLV would be the same as any other TLV, it is only packed if the tag is set, // so, it's safe to add the call to pack_nr_tlv even if it is not always set retval &= pack_nr_tlv(NFAPI_NR_FAPI_ANALOG_BF_VENDOR_EXTENSION_TAG, @@ -1252,7 +1250,7 @@ uint8_t pack_nr_config_request(void *msg, uint8_t **ppWritePackedMsg, uint8_t *e &pack_uint8_tlv_value); // only increase if it was set numTLVs += pNfapiMsg->analog_beamforming_ve.analog_bf_vendor_ext.tl.tag == NFAPI_NR_FAPI_ANALOG_BF_VENDOR_EXTENSION_TAG; - +#endif retval &= pack_nr_tlv(NFAPI_NR_FAPI_SSB_CASE_VENDOR_EXTENSION_TAG, &(pNfapiMsg->ssb_table.case_v3), ppWritePackedMsg,