Merge remote-tracking branch 'origin/nr_ue_multiple_rf' into integration_2026_w05 (!3593)

NR UE: add support for multiple RF frontends

This MR adds basic support for multiple RF-frontends at the UE.
There are multiple reasons why we might want this:
1. There might be different antennas needed for different bands (e.g. NTN or
   TN antennas). These antennas might be connected to different RF-frontends.
   In case of Handover, the UE needs to dynamically switch between these
   RF-frontends.
2. Each rf-simulator connection carries baseband I/Q samples. To be able to
   perform a handover between two cells at different frequencies, there must
   be two rf-simulator connections, as the baseband channels should not
   interfere and thus cannot simply be added.
3. When we want to support multiple UE simulation within one instance of
   nr-uesoftmodem, and allow multiple cells and probably even handover between
   these cells, we need either:
   1. dedicated RF-frontends for each simulated UE, or
   2. dedicated RF-frontends for each connected cell and dynamically assign
      simulated UEs accordingly.

This MR implements what is needed for 1., 2. and 3.1.
This commit is contained in:
Jaroslava Fiedlerova
2026-01-28 20:19:48 +01:00
153 changed files with 1993 additions and 1082 deletions
+25 -19
View File
@@ -271,7 +271,7 @@ static client_info_t client_read_info(char *descriptor_file)
return client_info;
}
static int vrtsim_connect(openair0_device *device)
static int vrtsim_connect(openair0_device_t *device)
{
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
@@ -332,7 +332,7 @@ static int vrtsim_connect(openair0_device *device)
}
static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state,
openair0_timestamp timestamp,
openair0_timestamp_t timestamp,
c16_t *samples,
int nsamps,
int aarx,
@@ -361,7 +361,7 @@ static int vrtsim_write_internal(vrtsim_state_t *vrtsim_state,
typedef struct {
vrtsim_state_t *vrtsim_state;
openair0_timestamp timestamp;
openair0_timestamp_t timestamp;
c16_t *samples[MAX_NUM_ANTENNAS_TX];
int nsamps;
int nbAnt;
@@ -464,7 +464,7 @@ static void perform_channel_modelling(void *arg)
}
static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
openair0_timestamp timestamp,
openair0_timestamp_t timestamp,
void **samplesVoid,
int nsamps,
int nbAnt,
@@ -473,7 +473,7 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
// Sample history for channel impulse response
static c16_t saved_samples[MAX_NUM_ANTENNAS_TX][SAVED_SAMPLES_LEN] __attribute__((aligned(32))) = {0};
// Indicates what samples are saves in saved_samples
static openair0_timestamp last_timestamp = 0;
static openair0_timestamp_t last_timestamp = 0;
const int batch_size = 4096;
AssertFatal(nbAnt <= MAX_NUM_ANTENNAS_TX, "Number of antennas %d exceeds maximum %d\n", nbAnt, MAX_NUM_ANTENNAS_TX);
@@ -528,7 +528,12 @@ static int vrtsim_write_with_chanmod(vrtsim_state_t *vrtsim_state,
return nsamps;
}
static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, void **samplesVoid, int nsamps, int nbAnt, int flags)
static int vrtsim_write(openair0_device_t *device,
openair0_timestamp_t timestamp,
void **samplesVoid,
int nsamps,
int nbAnt,
int flags)
{
AssertFatal(nsamps > 0, "Number of samples must be greater than 0\n");
AssertFatal(nbAnt > 0 && nbAnt <= MAX_NUM_ANTENNAS_TX,
@@ -543,8 +548,8 @@ static int vrtsim_write(openair0_device *device, openair0_timestamp timestamp, v
: vrtsim_write_internal(vrtsim_state, timestamp, (c16_t *)samplesVoid[0], nsamps, 0, flags, 0);
}
static int vrtsim_write_beams(openair0_device *device,
openair0_timestamp timestamp,
static int vrtsim_write_beams(openair0_device_t *device,
openair0_timestamp_t timestamp,
void ***buff,
int nsamps,
int nb_antennas_tx,
@@ -555,7 +560,7 @@ static int vrtsim_write_beams(openair0_device *device,
return nsamps;
}
static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
static int vrtsim_read(openair0_device_t *device, openair0_timestamp_t *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
{
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
if (shm_td_iq_channel_is_aborted(vrtsim_state->channel)) {
@@ -594,7 +599,7 @@ static int vrtsim_read(openair0_device *device, openair0_timestamp *ptimestamp,
return nsamps;
}
static void vrtsim_end(openair0_device *device)
static void vrtsim_end(openair0_device_t *device)
{
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
if (vrtsim_state->role == ROLE_SERVER && vrtsim_state->run_timing_thread) {
@@ -648,33 +653,34 @@ static void vrtsim_end(openair0_device *device)
}
}
static int vrtsim_stub(openair0_device *device)
{
return 0;
}
static int vrtsim_stub2(openair0_device *device, openair0_config_t *openair0_cfg)
static int vrtsim_stub(openair0_device_t *device)
{
return 0;
}
static int vrtsim_set_freq(openair0_device *device, openair0_config_t *openair0_cfg)
static int vrtsim_stub2(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
return 0;
}
static int vrtsim_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
vrtsim_state_t *s = device->priv;
s->rx_freq = openair0_cfg->rx_freq[0];
return 0;
}
static int vrtsim_set_beams(openair0_device *device, uint64_t beam_map, openair0_timestamp timestamp)
static int vrtsim_set_beams(openair0_device_t *device, uint64_t beam_map, openair0_timestamp_t timestamp)
{
return 0;
}
static int vrtsim_set_beams2(openair0_device *device, int *beam_ids, int num_beams, openair0_timestamp timestamp)
static int vrtsim_set_beams2(openair0_device_t *device, int *beam_ids, int num_beams, openair0_timestamp_t timestamp)
{
return 0;
}
__attribute__((__visibility__("default"))) int device_init(openair0_device *device, openair0_config_t *openair0_cfg)
__attribute__((__visibility__("default"))) int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
randominit();
vrtsim_state_t *vrtsim_state = calloc_or_fail(1, sizeof(vrtsim_state_t));