Compare commits

...

18 Commits

Author SHA1 Message Date
Cedric Roux
bf91b249fb NR-DC: add documentation
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 13:11:37 +02:00
Cedric Roux
8c11c7ee7b NR-DC: check band combinations in UE capabilities
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
5518b4f7a9 NR-DC: get NR-DC parameters from configuration file
All configured NR-DC combinations are searched, the first one that
is possible is chosen. A combination is possible if the MCG cell is
the one where the UE is connected and the SCG cell exists (a DU
using the SCG band is connected to the CU).

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
2d574c9475 NR-DC: configuration in conf file
add this block (at top, outside of any other block) to the CU
configuration file:

nrdc = {
  combinations = (
    { mcg = 77, scg = 261 },
    { mcg = 78, scg = 257 },
  )
}

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
3850acf45c NR-DC: release the UE only when it's configured
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
62ad613ac4 NR-DC: handle SCG Failure Information
When the UE sends an SCG Failure Information, we remove the NR-DC
setup and put back the SCG DRB to the MCG.

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
c7e4940f81 NR-DC: handle UE context release
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
4d165cb42f NR-DC: handle PDU session release
Samsung S25 does a PDU session release request when we do "airplane mode",
we have to deal with it (if not: crash of DU).

Code is not clean.

According to 38.331, it's forbidden to have SRB2 without DRB. So when the
UE asks to remove the last DRB present, we should do what? RRRelease?

With S25 in standard SA mode, we remove DRB + RLC entity + forward PDU
session release coming from core network, all of this in one RRC
Reconfiguration. Trying to do the same with NR-DC miserably fails. If I
understand 38.331 correctly, to remove the RLC that lives in the SCG DU
I have to embed an mrdc-SecondaryCellGroupConfig into the RRC Reconfiguration
one. This mrdc-SecondaryCellGroupConfig contains an RRC Reconfiguration
that contains a secondaryCellGroup. This secondaryCellGroup contains
rlc-BearerToReleaseList with the LCID of the RLC entity to remove.
This is to mimic the SA mode. But it fails. So instead, we only send
the core network PDU session release. Then the S25 is happy and follows
with a Deregistration.

But testing with a quectel module (without quectel-CM, only minicom), and
doing:
    at+cgact=1,1     (activate a PDU session)
    at+cgact=0,1     (release it)
    at+cgact=1,1     (activate again)
fails for the second activation. When the quectel module gets the RRC
Reconfiguration that activates the second PDU session, it rejects it and
does RRC Reestablishment. Probably because we did not release the DRB
and the RLC entity in the previous step.

So be it for the time being, act/deact/act is not supported.

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
fe8d85008a NR-DC: transfer bearer to secondary DU
Plus some modifications here and there.

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
d1dae2afa7 NR-DC: DU handles F1 UE Context Setup Request
- create the UE
- send F1 UE Context Setup Response to the CU

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
2f73d98712 NR-DC: CU sends F1 UE Context Setup Request
send F1 UE Context Setup Request to DU when getting measurement from UE
to transfer DRB 1 (hardcoded for the moment, maybe change in the future)

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
ab653d3335 second step of NR-DC: receive UE capabilities, configure measurements
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-02 11:52:25 +02:00
Cedric Roux
79ae76fec5 add a timeout mechanism for NR-DC processing
nr_ulsim and nr_dlsim need time_management_core to compile

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-01 11:35:57 +02:00
Cedric Roux
c6ce348337 first step of NR-DC: ask for UE capabilities to check for NR-DC support
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-01 11:35:55 +02:00
Cedric Roux
35260c5299 add a module to properly allocate measurement IDs
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-01 11:34:14 +02:00
Cedric Roux
59e24df638 cleanup MAX_MEAS_XX
Those values exist in both LTE and NR, let's rename to NR_MAX_MEAS_XX and make
them known to NR-UE only, they are not used in the gNB.

Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-01 11:34:14 +02:00
Cedric Roux
12ca43049a time_manager based timer/timeout
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-01 11:34:12 +02:00
Cedric Roux
cab6adbe94 simple reference counting shared pointer
Signed-off-by: Cedric Roux <cedric.roux@eurecom.fr>
2026-06-01 11:32:51 +02:00
35 changed files with 2740 additions and 79 deletions

View File

@@ -398,7 +398,9 @@ target_link_libraries(lte_rrc PRIVATE nr_rrc)
#NR RRC
#######
add_library(nr_rrc ${OPENAIR2_DIR}/RRC/NR/MESSAGES/asn1_msg.c)
add_library(nr_rrc
${OPENAIR2_DIR}/RRC/NR/MESSAGES/asn1_msg.c
${OPENAIR2_DIR}/RRC/NR/rrc_gNB_measurements.c)
target_link_libraries(nr_rrc PUBLIC asn1_nr_rrc asn1_lte_rrc rrc_cell_management)
# S1AP
@@ -1134,6 +1136,7 @@ set(L2_NR_SRC
${NR_RRC_DIR}/rrc_gNB_cuup.c
${NR_RRC_DIR}/rrc_gNB_du.c
${NR_RRC_DIR}/rrc_gNB_mobility.c
${NR_RRC_DIR}/rrc_gNB_nrdc.c
)
set(L2_SRC_UE
@@ -1211,6 +1214,7 @@ set (MAC_NR_SRC
${NR_GNB_MAC_DIR}/mac_rrc_ul_direct.c
${NR_GNB_MAC_DIR}/mac_rrc_ul_f1ap.c
${NR_GNB_MAC_DIR}/nr_radio_config.c
${NR_GNB_MAC_DIR}/nrdc_mac_rrc_dl_handler.c
)
@@ -1784,7 +1788,7 @@ target_link_libraries(nr-oru PRIVATE
radio_common softmodem_common nfapi_pnf_lib)
target_link_libraries(nr-oru PRIVATE pthread m CONFIG_LIB rt ${T_LIB} utils
barrier actor nfapi_user_lib)
target_link_libraries(nr-oru PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs nr_phy_common time_management)
target_link_libraries(nr-oru PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs nr_phy_common time_management ds)
# nr-softmodem
###################################################
@@ -1987,7 +1991,7 @@ add_executable(nr_dlsim
${PHY_INTERFACE_DIR}/queue_t.c
)
target_link_libraries(nr_dlsim PRIVATE
-Wl,--start-group UTIL SIMU PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -Wl,--end-group
-Wl,--start-group UTIL SIMU PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR time_management_core -Wl,--end-group
m pthread ${T_LIB} ITTI dl nr_ue_phy_meas physim_common NR_L2_UE
softmodem_common
)
@@ -2016,7 +2020,7 @@ add_executable(nr_ulsim
)
target_link_libraries(nr_ulsim PRIVATE
-Wl,--start-group UTIL SIMU PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR -Wl,--end-group
-Wl,--start-group UTIL SIMU PHY_NR_COMMON PHY_NR PHY_NR_UE SCHED_NR_LIB SCHED_NR_UE_LIB MAC_UE_NR MAC_NR_COMMON CONFIG_LIB L2_NR time_management_core -Wl,--end-group
m pthread ${T_LIB} ITTI dl nr_ue_phy_meas physim_common softmodem_common NR_L2_UE
)
target_link_libraries(nr_ulsim PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs)

View File

@@ -7,6 +7,7 @@ add_library(ds OBJECT
hashtable.c
obj_hashtable.c
spsc_q.c
shared_pointer.c
)
target_include_directories(ds PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "shared_pointer.h"
#include "common/utils/utils.h"
#define fatal(x, ...) do { \
if (!(x)) { \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
exit(1); \
} \
} while (0)
static void lock(shared_pointer_t *p)
{
fatal(pthread_mutex_lock(&p->m) == 0, "pthread_mutex_lock failed\n");
}
static void unlock(shared_pointer_t *p)
{
fatal(pthread_mutex_unlock(&p->m) == 0, "pthread_mutex_unlock failed\n");
}
shared_pointer_t *new_shared_pointer(void *p, void (*free)(void *p))
{
shared_pointer_t *s = malloc_or_fail(sizeof(*s));
s->p = p;
s->free = free;
s->count = 1;
pthread_mutex_init(&s->m, NULL);
return s;
}
void unref_shared_pointer(shared_pointer_t *p)
{
lock(p);
fatal(p->count, "unref_shared_pointer() called for a pointer with count == 0\n");
p->count--;
if (p->count) {
unlock(p);
return;
}
unlock(p);
p->free(p->p);
free(p);
}
void ref_shared_pointer(shared_pointer_t *p)
{
lock(p);
fatal(p->count, "ref_shared_pointer() called for a pointer with count == 0\n");
p->count++;
unlock(p);
}

View File

@@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef COMMON_UTILS_DS_SHARED_POINTER_H
#define COMMON_UTILS_DS_SHARED_POINTER_H
#include <pthread.h>
typedef struct {
void *p;
void (*free)(void *p);
int count;
pthread_mutex_t m;
} shared_pointer_t;
shared_pointer_t *new_shared_pointer(void *p, void (*free)(void *p));
void unref_shared_pointer(shared_pointer_t *p);
void ref_shared_pointer(shared_pointer_t *p);
#endif /* COMMON_UTILS_DS_SHARED_POINTER_H */

View File

@@ -7,6 +7,16 @@
#include "../../alg/foreach.h"
#include <assert.h>
/* needed because shared_pointer.c is using DevAssert() and AssertFatal() */
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
if (assert) {
abort();
} else {
exit(EXIT_SUCCESS);
}
}
/*
Example to show seq_arr_t capabilities and usage
To compile: gcc test_seq_array.c ../seq_arr.c ../../alg/find.c ../../alg/foreach.c

View File

@@ -6,7 +6,8 @@ add_library(time_management STATIC
add_library(time_management_core OBJECT
time_source.c
time_server.c
time_client.c)
time_client.c
time_timer.c)
target_link_libraries(time_management PUBLIC time_management_core)
target_link_libraries(time_management_core PRIVATE log_headers)

View File

@@ -2,11 +2,11 @@
add_executable(test_time_manager_manual test_manual.c)
target_link_libraries(test_time_manager_manual time_management_core)
target_link_libraries(test_time_manager_manual LOG CONFIG_LIB)
target_link_libraries(test_time_manager_manual LOG CONFIG_LIB ds)
add_executable(test_time_manager_auto test_auto.c)
target_link_libraries(test_time_manager_auto time_management)
target_link_libraries(test_time_manager_auto LOG CONFIG_LIB)
target_link_libraries(test_time_manager_auto LOG CONFIG_LIB ds)
add_dependencies(tests test_time_manager_auto)
add_test(NAME time_management_tests

View File

@@ -7,6 +7,7 @@
#include "time_source.h"
#include "time_server.h"
#include "time_client.h"
#include "time_timer.h"
#include "common/config/config_userapi.h"
#include "common/utils/assertions.h"
@@ -27,6 +28,9 @@ static int tick_functions_count;
static void tick(void *unused)
{
UNUSED(unused);
time_timer_ms_tick();
for (int i = 0; i < tick_functions_count; i++)
tick_functions[i]();
}
@@ -74,6 +78,8 @@ void time_manager_start(time_manager_tick_function_t *_tick_functions,
char *server_ip = NULL;
int server_port = -1;
time_timer_init();
tick_functions_count = _tick_functions_count;
tick_functions = calloc_or_fail(tick_functions_count, sizeof(*tick_functions));
memcpy(tick_functions, _tick_functions, tick_functions_count * sizeof(*tick_functions));
@@ -186,4 +192,6 @@ void time_manager_finish(void)
free(tick_functions);
tick_functions = NULL;
tick_functions_count = 0;
time_timer_free();
}

View File

@@ -0,0 +1,249 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "time_timer.h"
#include <stdlib.h>
#include <stdbool.h>
#include "common/utils/ds/shared_pointer.h"
#include "common/utils/assertions.h"
#include "common/utils/system.h"
#include "common/utils/LOG/log.h"
#define MAX_DELAY_MS 32768 /* 32s max of timeout - change if more needed */
typedef struct {
void (*callback)(void **p, uint64_t *v);
void **p;
uint64_t *v;
_Atomic bool disabled;
_Atomic bool callback_running;
pthread_mutex_t m;
pthread_cond_t c;
shared_pointer_t *next; /* this is a shared_pointer_t(time_timer_item_t) */
} time_timer_item_t;
static void lock_item(time_timer_item_t *t)
{
DevAssert(pthread_mutex_lock(&t->m) == 0);
}
static void unlock_item(time_timer_item_t *t)
{
DevAssert(pthread_mutex_unlock(&t->m) == 0);
}
static void do_wait_item(time_timer_item_t *t)
{
DevAssert(pthread_cond_wait(&t->c, &t->m) == 0);
}
static void do_signal_item(time_timer_item_t *t)
{
DevAssert(pthread_cond_signal(&t->c) == 0);
}
static void free_item(void *_item)
{
time_timer_item_t *item = _item;
free(item->p);
free(item->v);
free(item);
}
/* a list of shared_pointer_t(time_timer_item_t) */
typedef struct {
shared_pointer_t *head;
shared_pointer_t *tail;
} time_timer_list_t;
static void list_add(time_timer_list_t *l, shared_pointer_t *p)
{
if (!l->head) {
l->head = p;
l->tail = p;
} else {
time_timer_item_t *last = l->tail->p;
last->next = p;
l->tail = p;
}
}
/* a timer module */
typedef struct {
pthread_t thread_id;
pthread_mutex_t m;
pthread_cond_t c;
uint64_t cur_time;
bool exit;
time_timer_list_t timers[MAX_DELAY_MS];
} time_timer_t;
static void lock(time_timer_t *t)
{
DevAssert(pthread_mutex_lock(&t->m) == 0);
}
static void unlock(time_timer_t *t)
{
DevAssert(pthread_mutex_unlock(&t->m) == 0);
}
static void do_wait(time_timer_t *t)
{
DevAssert(pthread_cond_wait(&t->c, &t->m) == 0);
}
static void do_signal(time_timer_t *t)
{
DevAssert(pthread_cond_signal(&t->c) == 0);
}
static void *time_timer_thread(void *_t)
{
time_timer_t *t = _t;
int cur_pos = 0;
uint64_t cur_time = 0;
while (1) {
lock(t);
while (!(t->exit || cur_time != t->cur_time))
do_wait(t);
if (t->exit) {
unlock(t);
break;
}
uint64_t next_time = t->cur_time;
unlock(t);
/* process all ticks up to t->cur_time */
while (cur_time != next_time) {
lock(t);
shared_pointer_t *l = t->timers[cur_pos].head;
t->timers[cur_pos].head = 0;
t->timers[cur_pos].tail = 0;
unlock(t);
while (l) {
time_timer_item_t *p = l->p;
shared_pointer_t *next = p->next;
lock_item(p);
if (!p->disabled) {
p->callback_running = true;
unlock_item(p);
p->callback(p->p, p->v);
lock_item(p);
p->callback_running = false;
do_signal_item(p);
}
unlock_item(p);
unref_shared_pointer(l);
l = next;
}
cur_pos++;
cur_pos %= MAX_DELAY_MS;
cur_time++;
}
}
return 0;
}
/* global timer module API */
static time_timer_t timer_module;
void time_timer_init(void)
{
pthread_mutex_init(&timer_module.m, 0);
pthread_cond_init(&timer_module.c, 0);
threadCreate(&timer_module.thread_id, time_timer_thread, &timer_module, "timer thread", -1, SCHED_OAI);
}
void time_timer_free(void)
{
lock(&timer_module);
timer_module.exit = true;
do_signal(&timer_module);
unlock(&timer_module);
void *retval;
int ret = pthread_join(timer_module.thread_id, &retval);
if (ret) LOG_E(UTIL, "pthread_join failed for the time_timer module (%s)\n", strerror(ret));
lock(&timer_module);
for (int i = 0; i < MAX_DELAY_MS; i++) {
time_timer_list_t *l = &timer_module.timers[i];
shared_pointer_t *cur = l->head;
while (cur) {
time_timer_item_t *p = cur->p;
shared_pointer_t *next = p->next;
unref_shared_pointer(cur);
cur = next;
}
l->head = 0;
l->tail = 0;
}
unlock(&timer_module);
}
void time_timer_ms_tick(void)
{
lock(&timer_module);
timer_module.cur_time++;
do_signal(&timer_module);
unlock(&timer_module);
}
/* start/stop a timer */
void *tick_timeout_start(int ms, void (*callback)(void **p, uint64_t *v), void **p, int psize, uint64_t *v, int vsize)
{
AssertFatal(ms < MAX_DELAY_MS - 2, "cannot create timer with timeout %d ms, increase MAX_DELAY_MS in the code and recompile\n", ms);
/* create new timer object */
time_timer_item_t *new_timer = malloc_or_fail(sizeof(*new_timer));
new_timer->callback = callback;
new_timer->p = 0;
new_timer->v = 0;
if (psize) {
new_timer->p = malloc_or_fail(sizeof(void *) * psize);
memcpy(new_timer->p, p, sizeof(void *) * psize);
}
if (vsize) {
new_timer->v = malloc_or_fail(sizeof(uint64_t) * vsize);
memcpy(new_timer->v, v, sizeof(uint64_t *) * vsize);
}
new_timer->disabled = false;
new_timer->callback_running = false;
pthread_mutex_init(&new_timer->m, 0);
pthread_cond_init(&new_timer->c, 0);
new_timer->next = 0;
/* make a shared pointer around it */
shared_pointer_t *shared_ptr = new_shared_pointer(new_timer, free_item);
/* ref it, one local copy in the list, one to the caller, so 2 refs are needed */
ref_shared_pointer(shared_ptr);
/* put it in the list */
lock(&timer_module);
int pos = (timer_module.cur_time + ms) % MAX_DELAY_MS;
time_timer_list_t *list = &timer_module.timers[pos];
list_add(list, shared_ptr);
unlock(&timer_module);
return shared_ptr;
}
void tick_timeout_stop(void *_timer)
{
shared_pointer_t *shared_ptr = _timer;
time_timer_item_t *timer = shared_ptr->p;
lock_item(timer);
timer->disabled = true;
/* to delete a timer, its callback must not be running */
while (timer->callback_running)
do_wait_item(timer);
unlock_item(timer);
unref_shared_pointer(shared_ptr);
}

View File

@@ -0,0 +1,22 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef COMMON_UTIL_TIME_MANAGER_TIME_TIMER
#define COMMON_UTIL_TIME_MANAGER_TIME_TIMER
#include <stdint.h>
void time_timer_init(void);
void time_timer_free(void);
void time_timer_ms_tick(void);
/* returns a timer pointer to be passed to tick_timeout_stop()
* p, psize, v, vsize are parameters for the callback (we have
* a many parameters as the user needs)
*/
void *tick_timeout_start(int ms, void (*callback)(void **p, uint64_t *v), void **p, int psize, uint64_t *v, int vsize);
void tick_timeout_stop(void *timer);
#endif /* COMMON_UTIL_TIME_MANAGER_TIME_TIMER */

185
doc/nr-dc.md Normal file
View File

@@ -0,0 +1,185 @@
# NR-DC tutorial
This tutorial explains how to test NR-DC with OAI gNB.
## F1 NR-DC
It is possible to test F1-based NR-DC with OAI gNB.
In F1-based NR-DC, the UE is connected to a unique CU (that handles
upper layer protocols, PDCP and above and is connected to the core
network). Its signalling radio bearers are using the FR1 DU for lower
layers (RLC, MAC and PHY). Its data radio bearer (DRB) (in this scenario,
we suppose that the UE requests only one bearer) is also using the FR1
DU for lower layers when it is established but its lower layers (RLC
and below) are immediately transferred to the FR2 DU.
### Setup
The setup is as follows:
- one CU
- one FR1 DU connected to the CU
- one FR2 DU connected to the same CU
Both DUs need to be tightly synchronized. This can be achieved in different
ways depending on the hardware used.
For O-RAN 7.2 RUs, ensure that you use the same PTP clock for both units.
For USRP-based setups (for example, the FR1 DU is using a B210 and the FR2
DU is using an X410 connected to an Interdigital MHU) you need to set the
following in the `Rus` section for both units:
```
clock_src = "external";
time_src = "external";
```
And you need to connect both units to the same clock and time sources (PPS
and 10 MHz), for example, a USRP OctoClock.
To set up the CU and the DUs, refer to the documents
[F1AP/F1-design.md](F1AP/F1-design.md) and
[handover-tutorial.md](handover-tutorial.md) that explain how to run one CU
with two different DUs connected to it.
### UEs
NR-DC was successfully tested with the following UEs:
- Samsung S25 (FR1 band n77 40MHz and FR2 band n261 100MHz)
- Quectel RG530 (FR1 band n77 40MHz and FR2 band n261 100MHz)
### Compilation
Refer to the standard ways, depending on your hardware setup.
### Configuration
Add the following in the CU configuration file (at the end,
or anywhere but outside of other blocks).
```
nrdc = {
combinations = (
{ mcg = 77, scg = 261 },
{ mcg = 78, scg = 257 }
)
}
```
This will enable two different possible band combinations, n77+n261 and
n78+n257.
If you run one DU in band n77 and another one in band n261, then a UE that
supports this NR-DC combination will be put in NR-DC mode. The same applies
if you run one DU in band n78 and another one in band n257.
### Testing
Once you have your three proper configuration files for the CU and the
two DUs and the system is up and running, connect a UE to the FR1 band.
And if the UE supports the configured band combination, its first PDU
session will be transferred to the FR2 DU.
For example, when testing a Quectel RM530 with bands n77 and n261, run the
following AT command so that it will connect only to band n77 and not
attempt to connect to band n261, which it is otherwise capable of doing.
```
AT+QNWPREFCFG="nr5g_band",77
```
For the Samsung S25, you need to enable all bands and band combinations.
Refer to this
[external link](https://xdaforums.com/t/how-to-guide-how-to-enable-all-bands-through-service-menu-on-us-ca-s25-series-including-sub-6-and-mmwave.4768929).
### Limitations
Only one data radio bearer and one PDU session are supported at the moment.
Disable IMS if possible.
What actually happens is that only the first PDU session that is established
will be transferred to the FR2 DU.
A multi-PDU session scenario was not tested.
### Traces
Some NR-DC traces may be found in the
[wiki](https://gitlab.eurecom.fr/oai/openairinterface5g/-/wikis/nrdc/nrdc).
### Message flow
```mermaid
sequenceDiagram
participant ue as UE
participant dufr1 as DU FR1
participant dufr2 as DU FR2
participant cu as CU
note over cu,ue: initial connection, PDU session establishment
note over cu: CU decides to<br/>initiate NR-DC
cu ->> dufr1: DL RRC Message Transfer<br/>(UE Capability Enquiry)
dufr1 ->> ue: 1. UE Capability Enquiry
ue ->> dufr1: 2. UE Capability Information
dufr1 ->> cu: UL RRC Message Transfer<br/>(UE Capability Information)
note over cu: CU checks the UE capabilities,<br/>finds a valid NR-DC combination
cu ->> dufr1: DL RRC Message Transfer<br/>(RRC Reconfiguration)
dufr1 ->> ue: 3. RRC Reconfiguration<br/>(measConfig for FR2 DU)
ue ->> dufr1: RRC Reconfiguration Complete
dufr1 ->> cu: UL RRC Message Transfer<br/>(RRC Reconfiguration Complete)
cu ->> dufr1: UE Context Modification Request<br/>(RRC Reconfiguration Complete Indicator)
dufr1 ->> cu: UE Context Modification Response
ue ->> dufr1: 4. Measurement Report
dufr1 ->> cu: UL RRC Message Transfer<br/>(Measurement Report)
cu ->> dufr2: 5. UE Context Setup Request
cu ->> dufr1: 6. UE Context Modification Request
dufr1 ->> cu: UE Context Modification Response
dufr2 ->> cu: 7. UE Context Setup Response
cu ->> dufr1: DL RRC Message Transfer<br/>(RRC Reconfiguration)
dufr1 ->> ue: 8. RRC Reconfiguration<br/>(activate NR-DC)
ue ->> dufr1: RRC Reconfiguration Complete
dufr1 ->> cu: UL RRC Message Transfer<br/>(RRC Reconfiguration Complete)
cu ->> dufr1: UE Context Modification Request<br/>(RRC Reconfiguration Complete Indicator)
dufr1 ->> cu: UE Context Modification Response
ue ->> dufr2: 9. Random Access<br/>(using provided CFRA configuration)
dufr2 ->> ue: 10. RAR
note over cu,ue: the PDU session is now using FR2 DU
```
1. The UE Capability Enquiry contains the `includeNR-DC` field and a
`CellGrouping` to specify the MCG and SCG bands
2. The UE Capability Information contains the supported Band Combination List
that is checked by the CU to activate NR-DC
3. The RRC Reconfiguration contains a measurement configuration for the
FR2 DU.
4. The Measurement Report contains a measurement of the FR2 DU cell
5. The UE Context Setup Request contains the UE Capabilities and the DRB
to set up
6. The UE Context Modification Request contains the DRB to remove (the same
that is to be set up at step 5)
7. The UE Context Setup Response contains the Cell Group Config to be sent
to the UE. This Cell Group Config contains the RLC bearer to set up
and all the parameters of the cell, plus the CFRA configuration that
the UE has to use to connect to the FR2 DU
8. The RRC Reconfiguration contains several elements:
- `recoverPDCP` for the DRB to transfer from FR1 to FR2
- removal of the measConfig for the FR2 DU (not necessary for
completion of NR-DC; added for cleaner processing)
- removal of RLC bearer from the Master Cell Group (FR1)
- `mrdc-SecondaryCellGroupConfig` that contains an `RRCReconfiguration`
with the Cell Group Config sent by the FR2 DU, configuring the
RLC bearer on the FR2 Cell and all the parameters of the FR2 DU
cell, plus the CFRA configuration the UE has to use to connect to
the FR2 DU
9. The Random Access is done according to the configuration sent by the
FR2 DU
10. After the transmission of the RAR, the connection to the FR2 DU is
established and data traffic for the DRB is now using the FR2 cell

View File

@@ -62,6 +62,7 @@ MESSAGE_DEF(NAS_INIT_NOS1_IF, MESSAGE_PRIORITY_MED, nas_nos1_msg_t, nas_init_nos
// xNB: realtime -> RRC messages
MESSAGE_DEF(RRC_SUBFRAME_PROCESS, MESSAGE_PRIORITY_MED, RrcSubframeProcess, rrc_subframe_process)
MESSAGE_DEF(NRRRC_FRAME_PROCESS, MESSAGE_PRIORITY_MED, NRRrcFrameProcess, nr_rrc_frame_process)
MESSAGE_DEF(NR_RRC_NRDC_TIMEOUT, MESSAGE_PRIORITY_MED, nr_rrc_nrdc_timeout_t, nr_rrc_nrdc_timeout)
// eNB: RLC -> RRC messages
MESSAGE_DEF(RLC_SDU_INDICATION, MESSAGE_PRIORITY_MED, RlcSduIndication, rlc_sdu_indication)

View File

@@ -79,6 +79,7 @@
#define RRC_SUBFRAME_PROCESS(mSGpTR) (mSGpTR)->ittiMsg.rrc_subframe_process
#define NRRRC_FRAME_PROCESS(mSGpTR) (mSGpTR)->ittiMsg.nr_rrc_frame_process
#define NR_RRC_NRDC_TIMEOUT(mSGpTR) (mSGpTR)->ittiMsg.nr_rrc_nrdc_timeout
#define RLC_SDU_INDICATION(mSGpTR) (mSGpTR)->ittiMsg.rlc_sdu_indication
#define NRDuDlReq(mSGpTR) (mSGpTR)->ittiMsg.nr_du_dl_req
@@ -484,4 +485,10 @@ typedef struct {
} payload;
} nr_mac_rrc_message_t;
typedef struct {
uint64_t ue_id;
int xid;
int state;
} nr_rrc_nrdc_timeout_t;
#endif /* RRC_MESSAGES_TYPES_H_ */

View File

@@ -1278,6 +1278,26 @@ static void config_pdcp(configmodule_interface_t *cfg, nr_pdcp_configuration_t *
pdcp_config->drb.discard_timer = config_get_processedint(cfg, &pdcp_params[CONFIG_NR_PDCP_DRB_DISCARD_TIMER_IDX]);
}
static void config_nrdc(configmodule_interface_t *cfg, nrdc_configuration_t *nrdc_config)
{
GET_PARAMS_LIST(nrdc_combination_list, nrdc_combination, NRDC_PARAMS_DESC, CONFIG_NRDC_COMBINATION_LIST, CONFIG_STRING_NRDC);
nrdc_config->combination_count = nrdc_combination_list.numelt;
LOG_I(NR_RRC, "NR-DC: %d combinations configured\n", nrdc_config->combination_count);
nrdc_config->combinations = calloc_or_fail(nrdc_config->combination_count, sizeof(nrdc_combination_t));
for (int i = 0; i < nrdc_combination_list.numelt; i++) {
const paramdef_t *combination = nrdc_combination_list.paramarray[i];
int mcg = *gpd(combination, sizeofArray(nrdc_combination), CONFIG_NRDC_MCG)->iptr;
int scg = *gpd(combination, sizeofArray(nrdc_combination), CONFIG_NRDC_SCG)->iptr;
LOG_I(NR_RRC, "NR-DC: combination %d: mcg band %d scg band %d\n", i, mcg, scg);
nrdc_config->combinations[i].mcg_band = mcg;
nrdc_config->combinations[i].scg_band = scg;
}
}
void nfapi_stop_l1()
{
if (NFAPI_MODE && (NFAPI_MODE == NFAPI_MODE_AERIAL || NFAPI_MODE == NFAPI_MODE_VNF)) {
@@ -2452,6 +2472,8 @@ gNB_RRC_INST *RCconfig_NRRRC()
config_rlc(config_get_if(), &rrc->rlc_config);
config_pdcp(config_get_if(), &rrc->pdcp_config);
config_nrdc(config_get_if(), &rrc->nrdc_config);
return rrc;
}

View File

@@ -1060,4 +1060,18 @@ typedef enum {
/*----------------------------------------------------------------------*/
#define CONFIG_STRING_NRDC "nrdc"
#define CONFIG_NRDC_COMBINATION_LIST "combinations"
#define CONFIG_NRDC_MCG "mcg"
#define CONFIG_NRDC_SCG "scg"
#define NRDC_PARAMS_DESC { \
{ CONFIG_NRDC_MCG, "mcg", PARAMFLAG_MANDATORY, .iptr = NULL, .defintarrayval = 0, TYPE_INT, 0 }, \
{ CONFIG_NRDC_SCG, "scg", PARAMFLAG_MANDATORY, .iptr = NULL, .defintarrayval = 0, TYPE_INT, 0 }, \
}
/*----------------------------------------------------------------------*/
#endif

View File

@@ -17,6 +17,7 @@
#include "lib/f1ap_rrc_message_transfer.h"
#include "lib/f1ap_interface_management.h"
#include "lib/f1ap_ue_context.h"
#include "nrdc_mac_rrc_dl_handler.h"
#include "executables/softmodem-common.h"
@@ -288,12 +289,12 @@ static NR_QoS_config_t get_qos_config(const f1ap_qos_flow_param_t *qos)
return qos_c;
}
static int handle_ue_context_drbs_setup(NR_UE_info_t *UE,
int drbs_len,
const f1ap_drb_to_setup_t *req_drbs,
f1ap_drb_setup_t **resp_drbs,
NR_CellGroupConfig_t *cellGroupConfig,
const nr_rlc_configuration_t *rlc_config)
int handle_ue_context_drbs_setup(NR_UE_info_t *UE,
int drbs_len,
const f1ap_drb_to_setup_t *req_drbs,
f1ap_drb_setup_t **resp_drbs,
NR_CellGroupConfig_t *cellGroupConfig,
const nr_rlc_configuration_t *rlc_config)
{
DevAssert(req_drbs != NULL && resp_drbs != NULL && cellGroupConfig != NULL);
instance_t f1inst = get_f1_gtp_instance();
@@ -459,7 +460,7 @@ static NR_UE_NR_Capability_t *get_ue_nr_cap_from_ho_prep_info(uint8_t *buf, uint
return cap;
}
static NR_CG_ConfigInfo_t *get_cg_config_info(uint8_t *buf, uint32_t len)
NR_CG_ConfigInfo_t *get_cg_config_info(uint8_t *buf, uint32_t len)
{
struct NR_CG_ConfigInfo *cg_configinfo = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_CG_ConfigInfo, (void **)&cg_configinfo, buf, len);
@@ -471,7 +472,7 @@ static NR_CG_ConfigInfo_t *get_cg_config_info(uint8_t *buf, uint32_t len)
return cg_configinfo;
}
static NR_UE_NR_Capability_t *get_ue_nr_cap_from_cg_config_info(const NR_CG_ConfigInfo_t *cgci)
NR_UE_NR_Capability_t *get_ue_nr_cap_from_cg_config_info(const NR_CG_ConfigInfo_t *cgci)
{
/* INTO DU handler */
if (cgci->criticalExtensions.present != NR_CG_ConfigInfo__criticalExtensions_PR_c1)
@@ -588,7 +589,7 @@ static NR_UE_info_t *create_new_UE(gNB_MAC_INST *mac, uint32_t cu_id, const NR_C
* decode/re-encode cycles per TS 38.473 transparency requirements.
* @param cellGroup CellGroupConfig to encode
* @return Encoded byte array */
static byte_array_t encode_cellgroup_config(const NR_CellGroupConfig_t *cellGroup)
byte_array_t encode_cellgroup_config(const NR_CellGroupConfig_t *cellGroup)
{
byte_array_t cgc = {0};
ssize_t encoded = uper_encode_to_new_buffer(&asn_DEF_NR_CellGroupConfig, NULL, cellGroup, (void **)&cgc.buf);
@@ -692,11 +693,13 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req)
if (cu2du->meas_timing_config != NULL)
mtc = get_nr_mtc(cu2du->meas_timing_config->buf, cu2du->meas_timing_config->len);
/* 38.473: "For DC operation, the CG-ConfigInfo IE shall be included in the CU
* to DU RRC Information IE at the gNB acting as secondary node" As of now,
* we only handle NSA => we check we have CG-ConfigInfo if not SA or have SA
* and no CG-ConfigInfo */
AssertFatal(is_SA ^ (cg_configinfo != NULL), "cannot have SA and CG-ConfigInfo: NR-DC not supported xor need CG-ConfigInfo for NSA/phy-test/do-ra\n");
/* inclusion of CG-ConfigInfo in SA mode is interpreted
* as NR-DC activation in the DU
*/
if (is_SA && cg_configinfo)
return nrdc_ue_context_setup_request(req);
AssertFatal(is_SA || (cg_configinfo != NULL), "CG-ConfigInfo needed for NSA/phy-test/do-ra\n");
NR_SCHED_LOCK(&mac->sched_lock);
@@ -792,6 +795,15 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
.gNB_DU_ue_id = req->gNB_DU_ue_id,
};
/* special handling for NR-DC UEs */
NR_SCHED_LOCK(&mac->sched_lock);
NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[0]->UE_info, req->gNB_DU_ue_id);
if (UE->nrdc_mode) {
NR_SCHED_UNLOCK(&mac->sched_lock);
return nrdc_ue_context_modification_request(req);
}
NR_SCHED_UNLOCK(&mac->sched_lock);
NR_UE_NR_Capability_t *ue_cap = NULL;
if (req->cu_to_du_rrc_info != NULL) {
AssertFatal(req->cu_to_du_rrc_info->cg_configinfo == NULL, "CG-ConfigInfo not handled\n");
@@ -802,7 +814,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
}
NR_SCHED_LOCK(&mac->sched_lock);
NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[0]->UE_info, req->gNB_DU_ue_id);
UE = find_nr_UE(&RC.nrmac[0]->UE_info, req->gNB_DU_ue_id);
if (!UE) {
LOG_E(NR_MAC, "could not find UE with RNTI %04x\n", req->gNB_DU_ue_id);
NR_SCHED_UNLOCK(&mac->sched_lock);
@@ -884,6 +896,29 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
UE->reconfigCellGroup = new_CellGroup;
configure_UE_BWP(mac, scc, UE, false, NR_SearchSpace__searchSpaceType_PR_common, -1, -1);
} else {
/* hack: for NR-DC, remove the RLC bearer from the MCG group config
* of the UE. This bearer is transfered to the SCG.
* We check if the modification request asks to remove a bearer and adds
* no bearer. If yes, we are doing the NR-DC scenario (hopefully).
* This hack has to be removed in the future. (No other solution
* was found.)
*/
if (req->drbs_rel_len == 1 && req->srbs_len == 0 && req->drbs_len == 0
&& req->cu_to_du_rrc_info == NULL && req->rrc_container == NULL) {
/* remove the RLC from UE->CellGroup->rlc_BearerToAddModList */
long lcid = get_lcid_from_drbid(req->drbs_rel[0].id);
LOG_I(NR_MAC, "NR-DC hack: remove RLC lcid %ld from UE cell group config\n", lcid);
NR_CellGroupConfig_t *cellGroupConfig = UE->CellGroup;
int idx = 0;
while (idx < cellGroupConfig->rlc_BearerToAddModList->list.count) {
const NR_RLC_BearerConfig_t *bc = cellGroupConfig->rlc_BearerToAddModList->list.array[idx];
if (bc->logicalChannelIdentity == lcid)
break;
idx++;
}
DevAssert(idx < cellGroupConfig->rlc_BearerToAddModList->list.count);
asn_sequence_del(&cellGroupConfig->rlc_BearerToAddModList->list, idx, 1);
}
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, new_CellGroup); // we actually don't need it
}

View File

@@ -8,6 +8,7 @@
#include "common/platform_types.h"
#include "f1ap_messages_types.h"
#include "openair2/RRC/NR/MESSAGES/asn1_msg.h"
#include "nr_mac_gNB.h"
void f1_reset_cu_initiated(const f1ap_reset_t *reset);
void f1_reset_acknowledge_du_initiated(const f1ap_reset_ack_t *ack);
@@ -23,4 +24,15 @@ void ue_context_release_command(const f1ap_ue_context_rel_cmd_t *cmd);
void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc);
NR_CG_ConfigInfo_t *get_cg_config_info(uint8_t *buf, uint32_t len);
NR_UE_NR_Capability_t *get_ue_nr_cap_from_cg_config_info(const NR_CG_ConfigInfo_t *cgci);
int handle_ue_context_drbs_setup(NR_UE_info_t *UE,
int drbs_len,
const f1ap_drb_to_setup_t *req_drbs,
f1ap_drb_setup_t **resp_drbs,
NR_CellGroupConfig_t *cellGroupConfig,
const nr_rlc_configuration_t *rlc_config);
byte_array_t encode_cellgroup_config(const NR_CellGroupConfig_t *cellGroup);
#endif /* MAC_RRC_DL_HANDLER_H */

View File

@@ -822,6 +822,8 @@ typedef struct NR_UE_info {
// dedicated BWP is always 1 from the UE's point of view, even if the gNB has multiple BWPs.
// The below ID is the "true" (non-consecutive) BWP ID from the gNB's point of view
NR_BWP_Id_t local_bwp_id;
/* if nrdc_mode is true then the DU is an SCG DU */
bool nrdc_mode;
} NR_UE_info_t;
typedef struct {

View File

@@ -0,0 +1,198 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "nrdc_mac_rrc_dl_handler.h"
#include "common/utils/LOG/log.h"
#include "nr_mac_gNB.h"
#include "mac_proto.h"
#include "mac_rrc_dl_handler.h"
#include "openair2/F1AP/f1ap_ids.h"
#include "openair2/F1AP/lib/f1ap_ue_context.h"
#include "openair2/F1AP/f1ap_common.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include "openair3/ocp-gtpu/gtp_itf.h"
static NR_UE_info_t *nrdc_create_new_UE(gNB_MAC_INST *mac, uint32_t cu_id, const NR_CG_ConfigInfo_t *cgci)
{
int CC_id = 0;
rnti_t rnti;
bool found = nr_mac_get_new_rnti(&mac->UE_info, &rnti);
if (!found)
return NULL;
f1_ue_data_t new_ue_data = {.secondary_ue = cu_id};
bool success = du_add_f1_ue_data(rnti, &new_ue_data);
DevAssert(success);
const nr_mac_config_t *configuration = &mac->radio_config;
NR_UE_info_t *UE = get_new_nr_ue_inst(&mac->UE_info.uid_allocator, rnti, NULL, configuration);
AssertFatal(UE->uid < MAX_MOBILES_PER_GNB, "cannot create UE context, UE context setup failure not implemented\n");
NR_COMMON_channels_t *cc = &mac->common_channels[CC_id];
const NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
/* mimic NSA way to create a new UE (with adaptations) */
NR_UE_NR_Capability_t *cap = get_ue_nr_cap_from_cg_config_info(cgci);
int ssb_index = get_ssbidx_from_beam(mac, UE->UE_beam_index);
NR_CellGroupConfig_t *cellGroupConfig = get_default_secondaryCellGroup(scc, cap, 1, 1, configuration, UE->uid, ssb_index);
cellGroupConfig->spCellConfig->reconfigurationWithSync = get_reconfiguration_with_sync(UE->rnti, UE->uid, scc, mac->frame);
UE->capability = cap;
UE->local_bwp_id = 1; // get_default_secondaryCellGroup sets 1st active BWP as 1
// note: we don't pass the cellGroupConfig to add_new_nr_ue() because we need
// the uid to create the CellGroupConfig (which is in the UE context created
// by add_new_nr_ue(); it's a kind of chicken-and-egg problem), so below we
// complete the UE context with the information that add_new_nr_ue() would
// have added
UE->CellGroup = cellGroupConfig;
UE->nrdc_mode = true;
if (!add_new_UE_RA(mac, UE)) {
delete_nr_ue_data(UE, &mac->UE_info.uid_allocator);
LOG_E(NR_MAC, "UE list full while creating new UE\n");
return NULL;
}
nr_mac_prepare_ra_ue(mac, UE);
return UE;
}
void nrdc_ue_context_setup_request(const f1ap_ue_context_setup_req_t *req)
{
LOG_E(NR_MAC, "nrdc_ue_context_setup_request called!\n");
gNB_MAC_INST *mac = RC.nrmac[0];
f1ap_ue_context_setup_resp_t resp = {
.gNB_CU_ue_id = req->gNB_CU_ue_id,
};
const f1ap_cu_to_du_rrc_info_t *cu2du = &req->cu_to_du_rrc_info;
NR_CG_ConfigInfo_t *cg_configinfo = get_cg_config_info(cu2du->cg_configinfo->buf, cu2du->cg_configinfo->len);
NR_SCHED_LOCK(&mac->sched_lock);
NR_UE_info_t *UE = nrdc_create_new_UE(mac, req->gNB_CU_ue_id, cg_configinfo);
AssertFatal(UE, "NR-DC: cannot create a new UE, but UE Context Setup Failed not implemented yet\n");
resp.gNB_DU_ue_id = UE->rnti;
resp.crnti = malloc_or_fail(sizeof(*resp.crnti));
*resp.crnti = UE->rnti;
NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup);
if (req->drbs_len > 0)
resp.drbs_len = handle_ue_context_drbs_setup(UE, req->drbs_len, req->drbs, &resp.drbs, new_CellGroup, &mac->rlc_config);
/* in other parts of the code, we have: UE->reconfigCellGroup = new_CellGroup;
* but doing so crashes the DU, so let's do UE->CellGroup = new_CellGroup;
* (to be fixed?)
*/
UE->CellGroup = new_CellGroup;
int ss_type = NR_SearchSpace__searchSpaceType_PR_common;
NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
configure_UE_BWP(mac, scc, UE, true, ss_type, -1, -1);
NR_SCHED_UNLOCK(&mac->sched_lock);
byte_array_t cgc = { .buf = calloc_or_fail(1,1024) };
asn_enc_rval_t enc_rval =
uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, new_CellGroup, cgc.buf, 1024);
AssertFatal(enc_rval.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval.failed_type->name);
cgc.len = (enc_rval.encoded + 7) >> 3;
resp.du_to_cu_rrc_info.cell_group_config = cgc;
mac->mac_rrc.ue_context_setup_response(&resp);
/* free the memory we allocated above */
free_ue_context_setup_resp(&resp);
ASN_STRUCT_FREE(asn_DEF_NR_CG_ConfigInfo, cg_configinfo);
}
static instance_t get_f1_gtp_instance(void)
{
const f1ap_cudu_inst_t *inst = getCxt(0);
if (!inst)
return -1; // means no F1
return inst->gtpInst;
}
void nrdc_ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
{
/* this only works in F1 mode for the moment */
instance_t f1inst = get_f1_gtp_instance();
DevAssert(f1inst >= 0);
gNB_MAC_INST *mac = RC.nrmac[0];
f1ap_ue_context_mod_resp_t resp = {
.gNB_CU_ue_id = req->gNB_CU_ue_id,
.gNB_DU_ue_id = req->gNB_DU_ue_id,
};
NR_SCHED_LOCK(&mac->sched_lock);
NR_UE_info_t *UE = find_nr_UE(&RC.nrmac[0]->UE_info, req->gNB_DU_ue_id);
if (!UE) {
LOG_E(NR_MAC, "could not find UE with RNTI %04x\n", req->gNB_DU_ue_id);
NR_SCHED_UNLOCK(&mac->sched_lock);
return;
}
/* only support release of 1 drb */
if (req->srbs_len > 0 || req->drbs_len > 0 || req->rrc_container != NULL
|| req->cu_to_du_rrc_info != NULL) {
LOG_E(NR_MAC, "NR-DC UE Context Modification Request contains unhandled fields, ignoring\n");
NR_SCHED_UNLOCK(&mac->sched_lock);
return;
}
if (req->drbs_rel_len != 1) {
LOG_E(NR_MAC, "NR-DC UE Context Modification Request contains wrong number of drbs to release (%d, expecting 1), ignoring\n", req->drbs_rel_len);
NR_SCHED_UNLOCK(&mac->sched_lock);
return;
}
int drb_id = req->drbs_rel[0].id;
int lcid = get_lcid_from_drbid(drb_id);
/* remove the bearer everywhere it has to */
nr_mac_remove_lcid(&UE->UE_sched_ctrl, lcid);
nr_rlc_release_entity(UE->rnti, lcid);
newGtpuDeleteOneTunnel(f1inst, UE->rnti, drb_id);
/* remove the RLC from UE->CellGroup->rlc_BearerToAddModList */
NR_CellGroupConfig_t *cellGroupConfig = UE->CellGroup;
int idx = 0;
while (idx < cellGroupConfig->rlc_BearerToAddModList->list.count) {
const NR_RLC_BearerConfig_t *bc = cellGroupConfig->rlc_BearerToAddModList->list.array[idx];
if (bc->logicalChannelIdentity == lcid)
break;
idx++;
}
if (idx < cellGroupConfig->rlc_BearerToAddModList->list.count)
asn_sequence_del(&cellGroupConfig->rlc_BearerToAddModList->list, idx, 1);
/* generate Context Modification Response */
NR_CellGroupConfig_t cell_group = {
.cellGroupId = 1,
.rlc_BearerToReleaseList = &(struct NR_CellGroupConfig__rlc_BearerToReleaseList) {
.list = {
.array = (NR_LogicalChannelIdentity_t *[]) {
&(NR_LogicalChannelIdentity_t) { lcid }
},
.count = 1
}
}
};
resp.du_to_cu_rrc_info = calloc_or_fail(1, sizeof(du_to_cu_rrc_information_t));
resp.du_to_cu_rrc_info->cell_group_config = encode_cellgroup_config(&cell_group);
NR_SCHED_UNLOCK(&mac->sched_lock);
mac->mac_rrc.ue_context_modification_response(&resp);
free_ue_context_mod_resp(&resp);
}

View File

@@ -0,0 +1,13 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef NRDC_MAC_RRC_DL_HANDLER_H
#define NRDC_MAC_RRC_DL_HANDLER_H
#include "f1ap_messages_types.h"
void nrdc_ue_context_setup_request(const f1ap_ue_context_setup_req_t *req);
void nrdc_ue_context_modification_request(const f1ap_ue_context_mod_req_t *req);
#endif /* NRDC_MAC_RRC_DL_HANDLER_H */

View File

@@ -25,6 +25,7 @@
#include "asn1_msg.h"
#include "../nr_rrc_proto.h"
#include "LAYER2/nr_pdcp/nr_pdcp_asn1_utils.h"
#include "openair2/RRC/NR/rrc_gNB_measurements.h"
#include "openair3/SECU/key_nas_deriver.h"
@@ -1167,7 +1168,8 @@ static NR_MeasIdToAddMod_t *get_MeasId(NR_MeasId_t measId, NR_ReportConfigId_t r
return measid;
}
NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
NR_MeasConfig_t *get_MeasConfig(gNB_RRC_UE_t *ue,
const NR_MeasTiming_t *mt,
int band,
int nr_pci,
NR_ReportConfigToAddMod_t *rc_PER,
@@ -1211,7 +1213,8 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
// cell-specific offsets, blacklisted cells to be ignored and whitelisted cells to consider for measurements.
// Serving cell
NR_MeasObjectToAddMod_t *mo1 = get_MeasObject(ft, band, ft->carrierFreq, 1);
int serving_cell_measobj_id = allocate_measurement_object_id(ue);
NR_MeasObjectToAddMod_t *mo1 = get_MeasObject(ft, band, ft->carrierFreq, serving_cell_measobj_id);
NR_MeasObjectNR_t *monr1 = mo1->measObject.choice.measObjectNR;
monr1->cellsToAddModList = calloc_or_fail(1, sizeof(*monr1->cellsToAddModList));
NR_CellsToAddMod_t *cell = calloc_or_fail(1, sizeof(*cell));
@@ -1219,10 +1222,16 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
ASN_SEQUENCE_ADD(&monr1->cellsToAddModList->list, cell);
asn1cSeqAdd(&mc->measObjectToAddModList->list, mo1);
int *neighbour_measurement_object_id = NULL;
if (neigh_seq && neigh_seq->size)
neighbour_measurement_object_id = calloc_or_fail(neigh_seq->size, sizeof(int));
// Neighbour cells
if (neigh_seq) {
int mo_id = 2;
int i = 0;
FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, neigh_cell, neigh_seq) {
int mo_id = allocate_measurement_object_id(ue);
neighbour_measurement_object_id[i] = mo_id;
NR_MeasObjectToAddMod_t *mo_neighbour = get_MeasObject(ft, neigh_cell->band, neigh_cell->absoluteFrequencySSB, mo_id);
NR_MeasObjectNR_t *monr = mo_neighbour->measObject.choice.measObjectNR;
monr->cellsToAddModList = calloc_or_fail(1, sizeof(*monr->cellsToAddModList));
@@ -1230,7 +1239,7 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
cell->physCellId = neigh_cell->physicalCellId;
ASN_SEQUENCE_ADD(&monr->cellsToAddModList->list, cell);
asn1cSeqAdd(&mc->measObjectToAddModList->list, mo_neighbour);
mo_id++;
i++;
}
}
@@ -1240,19 +1249,20 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
// object.
// MeasId for Periodic report
int meas_idx = 0;
if (rc_PER) {
for (; meas_idx < mc->measObjectToAddModList->list.count; meas_idx++) {
const NR_MeasObjectId_t measObjectId = mc->measObjectToAddModList->list.array[meas_idx]->measObjectId;
NR_MeasIdToAddMod_t *measid = get_MeasId(meas_idx + 1, rc_PER->reportConfigId, measObjectId);
for (int i = 0; i < mc->measObjectToAddModList->list.count; i++) {
const NR_MeasObjectId_t measObjectId = mc->measObjectToAddModList->list.array[i]->measObjectId;
int meas_idx = allocate_measurement_id(ue);
NR_MeasIdToAddMod_t *measid = get_MeasId(meas_idx, rc_PER->reportConfigId, measObjectId);
asn1cSeqAdd(&mc->measIdToAddModList->list, measid);
}
}
// MeasId for Event A2
if (rc_A2) {
NR_MeasIdToAddMod_t *measid_A2 = get_MeasId(meas_idx + 1, rc_A2->reportConfigId, 1);
meas_idx++;
int meas_idx = allocate_measurement_id(ue);
int meas_obj_id = serving_cell_measobj_id;
NR_MeasIdToAddMod_t *measid_A2 = get_MeasId(meas_idx, rc_A2->reportConfigId, meas_obj_id);
asn1cSeqAdd(&mc->measIdToAddModList->list, measid_A2);
}
@@ -1266,8 +1276,9 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
i++;
continue;
}
NR_MeasIdToAddMod_t *measid_A3 = get_MeasId(meas_idx + 1, reportConfigId, i + 2);
meas_idx++;
int meas_idx = allocate_measurement_id(ue);
int meas_obj_id = neighbour_measurement_object_id[i];
NR_MeasIdToAddMod_t *measid_A3 = get_MeasId(meas_idx, reportConfigId, meas_obj_id);
asn1cSeqAdd(&mc->measIdToAddModList->list, measid_A3);
i++;
}
@@ -1282,6 +1293,8 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
asn1cCallocOne(qcnr->quantityConfigCell.csi_RS_FilterConfig.filterCoefficientRSRP, NR_FilterCoefficient_fc6);
asn1cSeqAdd(&mc->quantityConfig->quantityConfigNR_List->list, qcnr);
free(neighbour_measurement_object_id);
return mc;
}

View File

@@ -143,7 +143,10 @@ int do_RRCReestablishment(int8_t nh_ncc, uint8_t *const buffer, size_t buffer_si
int do_RRCReestablishmentComplete(uint8_t *buffer, size_t buffer_size, int64_t rrc_TransactionIdentifier);
NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt,
#include "openair2/RRC/NR/nr_rrc_defs.h"
NR_MeasConfig_t *get_MeasConfig(gNB_RRC_UE_t *ue,
const NR_MeasTiming_t *mt,
int band,
int nr_pci,
NR_ReportConfigToAddMod_t *rc_PER,

View File

@@ -36,6 +36,7 @@
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_configuration.h"
#include "openair2/SDAP/nr_sdap/nr_sdap_configuration.h"
#include "openair2/RRC/NR/nrdc_configuration.h"
typedef enum {
NR_RRC_OK=0,
@@ -45,10 +46,6 @@ typedef enum {
NR_RRC_HO_STARTED
} NR_RRC_status_t;
#define MAX_MEAS_OBJ 7
#define MAX_MEAS_CONFIG 7
#define MAX_MEAS_ID 7
#define UNDEF_SECURITY_MODE 0xff
#define NO_SECURITY_MODE 0x20
@@ -123,6 +120,7 @@ typedef enum {
RRC_PDUSESSION_MODIFY,
RRC_PDUSESSION_RELEASE,
RRC_UECAPABILITY_ENQUIRY,
RRC_F1_NRDC_IN_PROGRESS,
} rrc_action_t;
typedef struct nr_rrc_config {
@@ -265,6 +263,14 @@ typedef struct gNB_RRC_UE_s {
delayed_action_state_t delayed_action;
nr_redcap_ue_cap_t *redcap_cap;
/* measurement IDs */
uint64_t measurement_object_ids; /* bitfield, 64 possible values */
uint64_t measurement_ids; /* bitfield, 64 possible values */
uint64_t report_config_ids; /* bitfield, 64 possible values */
/* opaque pointer to store NR-DC state (NULL when no NR-DC) */
void *nrdc;
} gNB_RRC_UE_t;
typedef struct rrc_gNB_ue_context_s {
@@ -645,6 +651,8 @@ typedef struct gNB_RRC_INST_s {
// PDCP configuration parameters loaded during startup
nr_pdcp_configuration_t pdcp_config;
nr_rlc_configuration_t rlc_config;
nrdc_configuration_t nrdc_config;
} gNB_RRC_INST;
/** Forward declaration for UE log macros */

View File

@@ -45,7 +45,7 @@ void *rrc_gnb_task(void *args_p);
int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p);
NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid);
NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid, gNB_RRC_UE_t *ue);
bool ue_associated_to_cuup(const gNB_RRC_UE_t *ue);
sctp_assoc_t get_existing_cuup_for_ue(const gNB_RRC_UE_t *ue);
@@ -88,6 +88,7 @@ byte_array_t rrc_gNB_encode_RRCReconfiguration(gNB_RRC_UE_t *UE, nr_rrc_reconfig
nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t srb_reest_bitmap, bool drb_reestablish);
f1ap_qos_flow_param_t nr_rrc_get_f1_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param);
void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, nr_rrc_config_t *configuration);
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish);
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish, bool do_integrity, bool do_ciphering);
@@ -105,4 +106,15 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc,
const nr_rrc_cell_container_t *cell,
const byte_array_t *ho_prep_info);
const neighbour_cell_configuration_t *get_neighbour_cell_config(const gNB_RRC_INST *rrc, int cell_id);
void nr_rrc_transfer_protected_rrc_message(const gNB_RRC_INST *rrc,
const gNB_RRC_UE_t *ue_p,
uint8_t srb_id,
const uint32_t message_id,
const uint8_t *buffer,
int size);
void store_du_f1u_tunnel(const f1ap_drb_setup_t *drbs, int n, gNB_RRC_UE_t *ue);
void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs);
#endif

View File

@@ -0,0 +1,18 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef _NRDC_CONFIGURATION_H_
#define _NRDC_CONFIGURATION_H_
typedef struct {
int mcg_band;
int scg_band;
} nrdc_combination_t;
typedef struct {
int combination_count;
nrdc_combination_t *combinations;
} nrdc_configuration_t;
#endif /* _NRDC_CONFIGURATION_H_ */

View File

@@ -127,6 +127,18 @@ nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const
return RB_FIND(rrc_cell_tree, cells, &search_key);
}
/* return the first cell matching the wanted band */
nr_rrc_cell_container_t *get_cell_by_band(struct rrc_cell_tree *cells, int band)
{
nr_rrc_cell_container_t *cell;
RB_FOREACH(cell, rrc_cell_tree, cells) {
int cell_band = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.band : cell->info.fdd.dl.band;
if (cell_band == band)
return cell;
}
return NULL;
}
/** @brief Add cell to global cell tree and increment rrc->num_cells on success
* @param[in] rrc Pointer to RRC instance
* @param[in] cell Pointer to cell container to add

View File

@@ -38,6 +38,8 @@ nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const
nr_rrc_cell_container_t *rrc_add_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell);
void rrc_rm_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell);
nr_rrc_cell_container_t *get_cell_by_band(struct rrc_cell_tree *cells, int band);
// Cell management (DU-specific cell tree)
nr_rrc_cell_container_t *rrc_get_cell_for_du(seq_arr_t *cells, uint64_t cell_id);
nr_rrc_cell_container_t *rrc_get_cell_by_pci_for_du(const seq_arr_t *cells, uint16_t pci);

View File

@@ -81,6 +81,8 @@
#include "alg/find.h"
#include "NR_HandoverCommand.h"
#include "openair2/SDAP/nr_sdap/nr_sdap_configuration.h"
#include "rrc_gNB_measurements.h"
#include "rrc_gNB_nrdc.h"
#ifdef E2_AGENT
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
@@ -294,12 +296,12 @@ static void rrc_deliver_dl_rrc_message(void *deliver_pdu_data, ue_id_t ue_id, in
data->rrc->mac_rrc.dl_rrc_message_transfer(data->assoc_id, data->dl_rrc);
}
static void nr_rrc_transfer_protected_rrc_message(const gNB_RRC_INST *rrc,
const gNB_RRC_UE_t *ue_p,
uint8_t srb_id,
const uint32_t message_id,
const uint8_t *buffer,
int size)
void nr_rrc_transfer_protected_rrc_message(const gNB_RRC_INST *rrc,
const gNB_RRC_UE_t *ue_p,
uint8_t srb_id,
const uint32_t message_id,
const uint8_t *buffer,
int size)
{
DevAssert(size > 0);
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id);
@@ -656,10 +658,10 @@ static nr_a3_event_t *get_a3_configuration(gNB_RRC_INST *rrc, int pci)
return NULL;
}
static NR_ReportConfigToAddMod_t *prepare_periodic_event_report(const nr_per_event_t *per_event)
static NR_ReportConfigToAddMod_t *prepare_periodic_event_report(gNB_RRC_UE_t *ue, const nr_per_event_t *per_event)
{
NR_ReportConfigToAddMod_t *rc = calloc(1, sizeof(*rc));
rc->reportConfigId = 1;
rc->reportConfigId = allocate_report_config_id(ue);
rc->reportConfig.present = NR_ReportConfigToAddMod__reportConfig_PR_reportConfigNR;
NR_PeriodicalReportConfig_t *prc = calloc(1, sizeof(*prc));
@@ -685,10 +687,10 @@ static NR_ReportConfigToAddMod_t *prepare_periodic_event_report(const nr_per_eve
return rc;
}
static NR_ReportConfigToAddMod_t *prepare_a2_event_report(const nr_a2_event_t *a2_event)
static NR_ReportConfigToAddMod_t *prepare_a2_event_report(gNB_RRC_UE_t *ue, const nr_a2_event_t *a2_event)
{
NR_ReportConfigToAddMod_t *rc_A2 = calloc(1, sizeof(*rc_A2));
rc_A2->reportConfigId = 2;
rc_A2->reportConfigId = allocate_report_config_id(ue);
rc_A2->reportConfig.present = NR_ReportConfigToAddMod__reportConfig_PR_reportConfigNR;
NR_EventTriggerConfig_t *etrc_A2 = calloc(1, sizeof(*etrc_A2));
etrc_A2->eventId.present = NR_EventTriggerConfig__eventId_PR_eventA2;
@@ -756,7 +758,7 @@ void free_RRCReconfiguration_params(nr_rrc_reconfig_param_t params)
FREE_AND_ZERO_BYTE_ARRAY(params.dedicated_NAS_msg_list[i]);
}
NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid)
NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid, gNB_RRC_UE_t *ue)
{
nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&((gNB_RRC_INST *)rrc)->cells, nr_cellid);
DevAssert(cell != NULL);
@@ -790,6 +792,7 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell
*/
LOG_D(NR_RRC, "Preparing A3 Event Measurement Configuration!\n");
bool default_a3_added = false; // To ensure that the default configuration is only added once
int default_a3_report_config_id = -1;
int i = 0;
FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, neighbourCell, neighbour_cells) {
seq_arr_push_back(&neigh_seq, neighbourCell, sizeof(nr_neighbour_cell_t));
@@ -798,7 +801,7 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell
/* no A3 event configured for this neighbour, let's try the default one, if it exists */
if (default_a3_added) {
/* default A3 exists and is already added, use it for this neighbour */
neigh_a3_id[i] = 3;
neigh_a3_id[i] = default_a3_report_config_id;
i++;
continue;
}
@@ -810,12 +813,13 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell
i++;
continue;
}
/* allocated default A3 report config ID */
default_a3_report_config_id = allocate_report_config_id(ue);
neigh_a3_id[i] = default_a3_report_config_id;
default_a3_added = true;
/* default A3 report config ID is 3 */
neigh_a3_id[i] = 3;
} else {
/* specific A3 report config ID are 4, 5, ... */
neigh_a3_id[i] = i + 4;
/* allocate specific A3 report config ID */
neigh_a3_id[i] = allocate_report_config_id(ue);
}
NR_ReportConfigId_t reportConfigId = neigh_a3_id[i];
seq_arr_push_back(&rc_A3_seq, prepare_a3_event_report(a3Event, reportConfigId), sizeof(NR_ReportConfigToAddMod_t));
@@ -823,11 +827,11 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell
}
}
if (meas_cfg->per_event)
rc_PER = prepare_periodic_event_report(meas_cfg->per_event);
rc_PER = prepare_periodic_event_report(ue, meas_cfg->per_event);
if (meas_cfg->a2_event)
rc_A2 = prepare_a2_event_report(meas_cfg->a2_event);
rc_A2 = prepare_a2_event_report(ue, meas_cfg->a2_event);
NR_MeasConfig_t *result = get_MeasConfig(mt, band, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id);
NR_MeasConfig_t *result = get_MeasConfig(ue, mt, band, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id);
// Clean up sequence arrays
seq_arr_free(&rc_A3_seq, NULL);
@@ -1361,7 +1365,7 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc,
return;
}
UE->ongoing_reconfiguration = false;
UE->measConfig = nr_rrc_get_measconfig(rrc, msg->nr_cellid);
UE->measConfig = nr_rrc_get_measconfig(rrc, msg->nr_cellid, UE);
activate_srb(UE, 1);
rrc_gNB_generate_RRCSetup(0, ue_context_p, msg->du2cu_rrc_container, msg->du2cu_rrc_container_length);
}
@@ -1788,6 +1792,12 @@ static void rrc_gNB_process_MeasurementReport(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
NR_MeasurementReport_IEs_t *measurementReport_IEs = measurementReport->criticalExtensions.choice.measurementReport;
const NR_MeasId_t measId = measurementReport_IEs->measResults.measId;
/* check NR-DC measurement */
if (measId == get_scg_measurement_id(UE)) {
rrc_gnb_nrdc_measurement_received(rrc, UE, measurementReport);
return;
}
NR_MeasIdToAddMod_t *meas_id_s = NULL;
for (int meas_idx = 0; meas_idx < meas_config->measIdToAddModList->list.count; meas_idx++) {
if (measId == meas_config->measIdToAddModList->list.array[meas_idx]->measId) {
@@ -1871,6 +1881,12 @@ static void handle_ueCapabilityInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE,
int xid = ue_cap_info->rrc_TransactionIdentifier;
rrc_action_t a = UE->xids[xid];
UE->xids[xid] = RRC_ACTION_NONE;
if (a == RRC_F1_NRDC_IN_PROGRESS) {
rrc_gnb_nrdc_ue_capabilities_received(rrc, UE, ue_cap_info);
return;
}
if (a != RRC_UECAPABILITY_ENQUIRY) {
LOG_E(NR_RRC, "UE %d: received unsolicited UE Capability Information, aborting procedure\n", UE->rrc_ue_id);
return;
@@ -2069,6 +2085,8 @@ static void handle_rrcReconfigurationComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
UE->ue_reconfiguration_counter++;
UE->ongoing_reconfiguration = false;
bool start_nrdc = false;
switch (UE->xids[xid]) {
case RRC_PDUSESSION_RELEASE: {
rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(rrc, UE, xid);
@@ -2088,6 +2106,8 @@ static void handle_rrcReconfigurationComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
"UE %d: RRC Reconfiguration Complete for PDU session establishment, but no PDU sessions were setup\n",
UE->rrc_ue_id);
reset_delayed_action(&UE->delayed_action);
/* once a PDU session is established, we can start NR-DC */
start_nrdc = true;
break;
case RRC_PDUSESSION_MODIFY:
rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(rrc, UE, xid);
@@ -2100,6 +2120,10 @@ static void handle_rrcReconfigurationComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
case RRC_ACTION_NONE:
LOG_E(RRC, "UE %d: Received RRC Reconfiguration Complete with xid %d while no transaction is ongoing\n", UE->rrc_ue_id, xid);
break;
case RRC_F1_NRDC_IN_PROGRESS:
rrc_gnb_nrdc_rrc_reconfiguration_complete_received(rrc, UE, xid);
reset_delayed_action(&UE->delayed_action);
break;
default:
LOG_E(RRC, "UE %d: Received unexpected transaction type %d for xid %d\n", UE->rrc_ue_id, UE->xids[xid], xid);
break;
@@ -2130,6 +2154,9 @@ static void handle_rrcReconfigurationComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U
};
rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &req);
/* nothing to be freed */
if (start_nrdc)
rrc_gnb_nrdc_start(rrc, UE);
}
static void rrc_gNB_generate_UECapabilityEnquiry(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue)
@@ -2262,6 +2289,24 @@ static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *m
handle_rrcReestablishmentComplete(rrc, UE, rc);
break;
case NR_UL_DCCH_MessageType__c1_PR_scgFailureInformation: {
long type = ul_dcch_msg->message.choice.c1->choice.scgFailureInformation->criticalExtensions.choice.scgFailureInformation->failureReportSCG->failureType;
char *cause[] = {
"NR_FailureReportSCG__failureType_t310_Expiry",
"NR_FailureReportSCG__failureType_randomAccessProblem",
"NR_FailureReportSCG__failureType_rlc_MaxNumRetx",
"NR_FailureReportSCG__failureType_synchReconfigFailureSCG",
"NR_FailureReportSCG__failureType_scg_ReconfigFailure",
"NR_FailureReportSCG__failureType_srb3_IntegrityFailure",
"NR_FailureReportSCG__failureType_other_r16",
"NR_FailureReportSCG__failureType_spare1"
};
LOG_E(NR_RRC, "scg failure cause %s\n", type >= 0 && type <= 7 ? cause[type] : "unknown");
LOG_UE_UL_EVENT(UE, "received scg failure cause %s\n", type >= 0 && type <= 7 ? cause[type] : "unknown");
nrdc_handle_scg_failure_information(rrc, UE, ul_dcch_msg->message.choice.c1->choice.scgFailureInformation);
break;
}
default:
break;
}
@@ -2374,7 +2419,7 @@ static gtpu_tunnel_t f1u_gtp_update(uint32_t teid, const in_addr_t addr)
/**
* @brief Update DRB TEID information in RRC storage from received DRB list
*/
static void store_du_f1u_tunnel(const f1ap_drb_setup_t *drbs, int n, gNB_RRC_UE_t *ue)
void store_du_f1u_tunnel(const f1ap_drb_setup_t *drbs, int n, gNB_RRC_UE_t *ue)
{
for (int i = 0; i < n; i++) {
const f1ap_drb_setup_t *drb_f1 = &drbs[i];
@@ -2424,7 +2469,7 @@ static void e1_send_bearer_modification_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t
}
/** @brief Send E1 bearer updates for DRBs to setup from F1 UE Context Modification Response */
static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs)
void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs)
{
if (!is_cuup_associated(rrc) || n <= 0)
return;
@@ -2571,6 +2616,9 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
UE->f1_ue_context_active = true;
if (UE->nrdc && rrc_gnb_nrdc_wait_for_f1_context_setup_response(UE))
return nrdc_rrc_CU_process_ue_context_setup_response(UE, rrc, resp);
// Store the encoded CellGroupConfig for transparent forwarding
byte_array_t *cgc = &resp->du_to_cu_rrc_info.cell_group_config;
store_cgc(UE, cgc);
@@ -2640,6 +2688,11 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as
}
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
if (UE->nrdc != NULL)
/* UE has NR-DC connection, send release to the SCG DU */
nrdc_scg_ue_release(rrc, UE);
if (UE->ho_context != NULL) {
nr_ho_source_cu_t *source_ctx = UE->ho_context->source;
bool from_source_du = source_ctx && source_ctx->cell->assoc_id == assoc_id;
@@ -2692,11 +2745,14 @@ static void rrc_delete_ue_data(gNB_RRC_UE_t *UE)
ASN_STRUCT_FREE(asn_DEF_NR_MeasResults, UE->measResults);
FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer);
free_MeasConfig(UE->measConfig);
reset_all_measurement_ids(UE);
free(UE->redcap_cap);
UE->redcap_cap = NULL;
seq_arr_free(&UE->pduSessions, free_pdusession);
seq_arr_free(&UE->drbs, free_drb);
seq_arr_free(&UE->serving_cells, NULL);
if (UE->nrdc)
rrc_gnb_free_nrdc(UE);
}
void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p)
@@ -2723,6 +2779,14 @@ static void rrc_CU_process_ue_context_release_complete(MessageDef *msg_p)
}
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
/* check if this message is for NR-DC */
if (UE->nrdc) {
/* if the message is handled by the NR-DC module, do nothing more */
if (nrdc_handle_f1_context_release_complete(rrc, UE, complete))
return;
}
if (UE->an_release) {
/* only trigger release if it has been requested by core
* otherwise, it might be CU that requested release on a DU during normal
@@ -2743,6 +2807,12 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i
}
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
if (UE->nrdc && rrc_gnb_nrdc_wait_for_f1_context_modification_response(UE))
return nrdc_rrc_CU_process_ue_context_modification_response(UE, rrc, resp);
if (UE->nrdc && rrc_gnb_nrdc_wait_for_scg_failure_context_modification_response(UE))
return nrdc_scg_failure_context_modification_response(UE, rrc, resp);
bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target;
if (resp->drbs_len > 0) { // DRB to setup
store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE);
@@ -2873,7 +2943,7 @@ unsigned int mask_flip(unsigned int x) {
/** @brief Get F1AP QoS flow parameters from PDU session QoS parameters
* @param qos_param PDU session level QoS parameters from NGAP
* @return F1AP QoS flow parameters */
static f1ap_qos_flow_param_t nr_rrc_get_f1_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
f1ap_qos_flow_param_t nr_rrc_get_f1_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
{
f1ap_qos_flow_param_t qos_char = {0};
@@ -3092,6 +3162,27 @@ static void rrc_send_f1_ue_context_modification_request(const gNB_RRC_INST *rrc,
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id);
RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
/* check if one of the bearers to remove is the NR-DC one, remove it if yes */
if (n_rel_drbs) {
int nrdc_bearer_index;
for (nrdc_bearer_index = 0; nrdc_bearer_index < n_rel_drbs; nrdc_bearer_index++) {
if (is_nrdc_bearer(ue_p, rel_drbs[nrdc_bearer_index].id)) {
nrdc_release_bearer(rrc, ue_p, rel_drbs[nrdc_bearer_index].id);
break;
}
}
if (nrdc_bearer_index != n_rel_drbs) {
/* nrdc bearer was removed, do not remove it again */
n_rel_drbs--;
/* do nothing else if no add/rel to do */
if (!n_rel_drbs && !n_drbs)
return;
memcpy(&rel_drbs[nrdc_bearer_index], &rel_drbs[nrdc_bearer_index + 1],
sizeof(int) * n_rel_drbs - nrdc_bearer_index);
}
}
f1ap_ue_context_mod_req_t req = {
.gNB_CU_ue_id = ue_p->rrc_ue_id,
.gNB_DU_ue_id = ue_data.secondary_ue,
@@ -3698,6 +3789,10 @@ void *rrc_gnb_task(void *args_p)
rrc_gNB_free_Handover_Command(&NGAP_HANDOVER_COMMAND(msg_p)); // Free transfered NG message
break;
case NR_RRC_NRDC_TIMEOUT:
rrc_gnb_nrdc_timeout(RC.nrrrc[instance], &NR_RRC_NRDC_TIMEOUT(msg_p));
break;
default:
LOG_E(NR_RRC, "[gNB %ld] Received unexpected message %s\n", instance, msg_name_p);
break;
@@ -3753,6 +3848,10 @@ void rrc_gNB_generate_RRCRelease(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
deliver_ue_ctxt_release_data_t data = {.rrc = rrc, .release_cmd = &ue_context_release_cmd, .assoc_id = ue_data.du_assoc_id};
nr_pdcp_data_req_srb(UE->rrc_ue_id, DL_SCH_LCID_DCCH, rrc_gNB_mui++, size, buffer, rrc_deliver_ue_ctxt_release_cmd, &data);
/* if the UE is in NR-DC, release it from SCG DU */
if (UE->nrdc)
nrdc_scg_ue_release(rrc, UE);
#ifdef E2_AGENT
E2_AGENT_SIGNAL_DL_DCCH_RRC_MSG(buffer, size, NR_DL_DCCH_MessageType__c1_PR_rrcRelease);
#endif
@@ -3930,7 +4029,8 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc,
/* Update measurement config for target DU */
free_MeasConfig(ue->measConfig);
ue->measConfig = nr_rrc_get_measconfig(rrc, cell->info.cell_id);
reset_all_measurement_ids(ue);
ue->measConfig = nr_rrc_get_measconfig(rrc, cell->info.cell_id, ue);
byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config));
meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE);
meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE);

View File

@@ -53,7 +53,7 @@ static int get_ssb_scs(const f1ap_served_cell_info_t *cell_info)
return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.tbw.scs : cell_info->fdd.dl_tbw.scs;
}
static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc)
NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc)
{
// TODO verify which element of the list to pick
NR_MeasTimingList_t *mtlist = mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;

View File

@@ -0,0 +1,81 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "rrc_gNB_measurements.h"
#include "nr_rrc_defs.h"
int allocate_measurement_object_id(void *_ue)
{
/* todo(?) use common/utils/collection/linear_alloc.h */
gNB_RRC_UE_t *ue = _ue;
uint64_t mask = 1;
int ret;
for (ret = 0; ret < 64; ret++, mask <<= 1)
if (!(ue->measurement_object_ids & mask))
break;
DevAssert(ret < 64);
ue->measurement_object_ids |= mask;
return ret + 1;
}
void free_measurement_object_id(void *_ue, int mo_id)
{
gNB_RRC_UE_t *ue = _ue;
uint64_t mask = (uint64_t)1 << (uint64_t)(mo_id - 1);
DevAssert(ue->measurement_object_ids & mask);
ue->measurement_object_ids &= ~mask;
}
int allocate_measurement_id(void *_ue)
{
/* todo(?) use common/utils/collection/linear_alloc.h */
gNB_RRC_UE_t *ue = _ue;
uint64_t mask = 1;
int ret;
for (ret = 0; ret < 64; ret++, mask <<= 1)
if (!(ue->measurement_ids & mask))
break;
DevAssert(ret < 64);
ue->measurement_ids |= mask;
return ret + 1;
}
void free_measurement_id(void *_ue, int mo_id)
{
gNB_RRC_UE_t *ue = _ue;
uint64_t mask = (uint64_t)1 << (uint64_t)(mo_id - 1);
DevAssert(ue->measurement_ids & mask);
ue->measurement_ids &= ~mask;
}
int allocate_report_config_id(void *_ue)
{
/* todo(?) use common/utils/collection/linear_alloc.h */
gNB_RRC_UE_t *ue = _ue;
uint64_t mask = 1;
int ret;
for (ret = 0; ret < 64; ret++, mask <<= 1)
if (!(ue->report_config_ids & mask))
break;
DevAssert(ret < 64);
ue->report_config_ids |= mask;
return ret + 1;
}
void free_report_config_id(void *_ue, int mo_id)
{
gNB_RRC_UE_t *ue = _ue;
uint64_t mask = (uint64_t)1 << (uint64_t)(mo_id - 1);
DevAssert(ue->report_config_ids & mask);
ue->report_config_ids &= ~mask;
}
void reset_all_measurement_ids(void *_ue)
{
gNB_RRC_UE_t *ue = _ue;
ue->measurement_object_ids = 0;
ue->measurement_ids = 0;
ue->report_config_ids = 0;
}

View File

@@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef RRC_GNB_MEASUREMENTS_H_
#define RRC_GNB_MEASUREMENTS_H_
/* void *ue is actually gNB_RRC_UE_t *ue but this cannot be used because
* including "openair2/RRC/NR/nr_rrc_defs.h" breaks the compilation of
* nr-uesoftmodem
* to be fixed if it's seen as a problem
*/
int allocate_measurement_object_id(void *ue);
void free_measurement_object_id(void *ue, int mo_id);
int allocate_measurement_id(void *ue);
void free_measurement_id(void *ue, int mo_id);
int allocate_report_config_id(void *ue);
void free_report_config_id(void *ue, int mo_id);
void reset_all_measurement_ids(void *ue);
#endif /* RRC_GNB_MEASUREMENTS_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef __RRC_GNB_NRDC_H__
#define __RRC_GNB_NRDC_H__
#include "nr_rrc_defs.h"
#include "NR_DL-DCCH-Message.h"
#include "NR_UL-DCCH-Message.h"
void rrc_gnb_nrdc_start(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
void rrc_gnb_free_nrdc(gNB_RRC_UE_t *ue);
void rrc_gnb_nrdc_ue_capabilities_received(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, const NR_UECapabilityInformation_t *ue_cap);
void rrc_gnb_nrdc_rrc_reconfiguration_complete_received(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, int xid);
void rrc_gnb_nrdc_measurement_received(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, NR_MeasurementReport_t *meas);
void rrc_gnb_nrdc_timeout(gNB_RRC_INST *rrc, nr_rrc_nrdc_timeout_t *timeout);
int get_scg_measurement_id(gNB_RRC_UE_t *ue);
bool rrc_gnb_nrdc_wait_for_f1_context_setup_response(gNB_RRC_UE_t *ue);
void nrdc_rrc_CU_process_ue_context_setup_response(gNB_RRC_UE_t *ue, gNB_RRC_INST *rrc, f1ap_ue_context_setup_resp_t *resp);
bool is_nrdc_bearer(gNB_RRC_UE_t *ue, int rb_id);
void nrdc_release_bearer(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, int bearer_id);
bool rrc_gnb_nrdc_wait_for_f1_context_modification_response(gNB_RRC_UE_t *ue);
void nrdc_rrc_CU_process_ue_context_modification_response(gNB_RRC_UE_t *ue, gNB_RRC_INST *rrc, f1ap_ue_context_mod_resp_t *resp);
bool nrdc_handle_f1_context_release_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, f1ap_ue_context_rel_cplt_t *complete);
void nrdc_scg_ue_release(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
void nrdc_handle_scg_failure_information(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, NR_SCGFailureInformation_t *msg);
bool rrc_gnb_nrdc_wait_for_scg_failure_context_modification_response(gNB_RRC_UE_t *ue);
void nrdc_scg_failure_context_modification_response(gNB_RRC_UE_t *ue, gNB_RRC_INST *rrc, f1ap_ue_context_mod_resp_t *resp);
#endif /* __RRC_GNB_NRDC_H__ */

View File

@@ -941,7 +941,7 @@ static void nr_rrc_process_reconfigurationWithSync(NR_UE_RRC_INST_t *rrc,
// Clear neighbor cell lists from measurement objects during handover
rrcPerNB_t *rrcNB = &rrc->perNB[gNB_index];
for (int i = 0; i < MAX_MEAS_OBJ; i++) {
for (int i = 0; i < NR_MAX_MEAS_OBJ; i++) {
if (rrcNB->MeasObj[i] && rrcNB->MeasObj[i]->measObject.present == NR_MeasObjectToAddMod__measObject_PR_measObjectNR) {
NR_MeasObjectNR_t *measObjNR = rrcNB->MeasObj[i]->measObject.choice.measObjectNR;
if (measObjNR->cellsToAddModList) {
@@ -1221,7 +1221,7 @@ static void handle_measobj_remove(rrcPerNB_t *rrc, struct NR_MeasObjectToRemoveL
// remove the entry with the matching measObjectId from the measObjectList
asn1cFreeStruc(asn_DEF_NR_MeasObjectToAddMod, rrc->MeasObj[id - 1]);
// remove all measId associated with this measObjectId from the measIdList
for (int j = 0; j < MAX_MEAS_ID; j++) {
for (int j = 0; j < NR_MAX_MEAS_ID; j++) {
if (rrc->MeasId[j] && rrc->MeasId[j]->measObjectId == id) {
asn1cFreeStruc(asn_DEF_NR_MeasIdToAddMod, rrc->MeasId[j]);
handle_meas_reporting_remove(rrc, j, timers);
@@ -1319,7 +1319,7 @@ static void handle_reportconfig_remove(rrcPerNB_t *rrc,
NR_ReportConfigId_t id = *remove_list->list.array[i];
// remove the entry with the matching reportConfigId from the reportConfigList
asn1cFreeStruc(asn_DEF_NR_ReportConfigToAddMod, rrc->ReportConfig[id]);
for (int j = 0; j < MAX_MEAS_ID; j++) {
for (int j = 0; j < NR_MAX_MEAS_ID; j++) {
if (rrc->MeasId[j] && rrc->MeasId[j]->reportConfigId == id) {
// remove all measId associated with the reportConfigId from the measIdList
asn1cFreeStruc(asn_DEF_NR_MeasIdToAddMod, rrc->MeasId[j]);
@@ -1341,7 +1341,7 @@ static void handle_reportconfig_addmod(rrcPerNB_t *rrc,
}
NR_ReportConfigId_t id = rep->reportConfigId;
if (rrc->ReportConfig[id]) {
for (int j = 0; j < MAX_MEAS_ID; j++) {
for (int j = 0; j < NR_MAX_MEAS_ID; j++) {
// for each measId associated with this reportConfigId included in the measIdList
if (rrc->MeasId[j] && rrc->MeasId[j]->reportConfigId == id)
handle_meas_reporting_remove(rrc, j, timers);
@@ -1371,7 +1371,7 @@ static void handle_quantityconfig(rrcPerNB_t *rrc, NR_QuantityConfig_t *quantity
UPDATE_IE(rrc->QuantityConfig[i]->quantityConfigRS_Index, quantityNR->quantityConfigRS_Index, struct NR_QuantityConfigRS);
}
}
for (int j = 0; j < MAX_MEAS_ID; j++) {
for (int j = 0; j < NR_MAX_MEAS_ID; j++) {
// for each measId included in the measIdList
if (rrc->MeasId[j])
handle_meas_reporting_remove(rrc, j, timers);
@@ -2777,7 +2777,7 @@ static int get_rsrp_value(const meas_t *cell)
static int get_meas_id(rrcPerNB_t *rrcNB, int report_config_id)
{
for (int j = 0; j < MAX_MEAS_ID; j++) {
for (int j = 0; j < NR_MAX_MEAS_ID; j++) {
NR_MeasIdToAddMod_t *meas_id_toAddMod = rrcNB->MeasId[j];
if (meas_id_toAddMod && meas_id_toAddMod->reportConfigId == report_config_id)
return meas_id_toAddMod->measId;
@@ -2944,7 +2944,7 @@ static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_ind
rrcPerNB_t *rrcNB = rrc->perNB + gnb_index;
l3_measurements_t *l3_measurements = &rrcNB->l3_measurements;
for (int i = 0; i < MAX_MEAS_CONFIG; i++) {
for (int i = 0; i < NR_MAX_MEAS_CONFIG; i++) {
NR_ReportConfigToAddMod_t *report_config = rrcNB->ReportConfig[i];
if (report_config == NULL)
continue;

View File

@@ -42,9 +42,9 @@
#include "notified_fifo.h"
#define NB_CNX_UE 2//MAX_MANAGED_RG_PER_MOBILE
#define MAX_MEAS_OBJ 64
#define MAX_MEAS_CONFIG 64
#define MAX_MEAS_ID 64
#define NR_MAX_MEAS_OBJ 64
#define NR_MAX_MEAS_CONFIG 64
#define NR_MAX_MEAS_ID 64
#define MAX_QUANTITY_CONFIG 2
#define NUMBER_OF_NEIGHBORING_CELLS_MAX 1
@@ -187,11 +187,11 @@ typedef struct l3_measurements_s {
} l3_measurements_t;
typedef struct rrcPerNB {
NR_MeasObjectToAddMod_t *MeasObj[MAX_MEAS_OBJ];
NR_ReportConfigToAddMod_t *ReportConfig[MAX_MEAS_CONFIG];
NR_MeasObjectToAddMod_t *MeasObj[NR_MAX_MEAS_OBJ];
NR_ReportConfigToAddMod_t *ReportConfig[NR_MAX_MEAS_CONFIG];
NR_QuantityConfigNR_t *QuantityConfig[MAX_QUANTITY_CONFIG];
NR_MeasIdToAddMod_t *MeasId[MAX_MEAS_ID];
NR_VarMeasReport_t *MeasReport[MAX_MEAS_ID];
NR_MeasIdToAddMod_t *MeasId[NR_MAX_MEAS_ID];
NR_VarMeasReport_t *MeasReport[NR_MAX_MEAS_ID];
NR_MeasGapConfig_t *measGapConfig;
NR_UE_RRC_SI_INFO SInfo;
NR_RSRP_Range_t s_measure;