mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
Add documentation for different VNF/PNF transport
Add a section in nfapi.md related to the different transport mechanisms available for VNF/PNF communication Edit NR_NFAPI_archi.md to describe the VNF and PNF processing logic, as well as adding a flowchart for each of the components execution Add the necessary patch to install the WLS library in the system
This commit is contained in:
209
cmake_targets/tools/install_wls_lib.patch
Normal file
209
cmake_targets/tools/install_wls_lib.patch
Normal file
@@ -0,0 +1,209 @@
|
||||
diff --git a/wls_lib/Makefile b/wls_lib/Makefile
|
||||
index 2f8e4b2..e657488 100644
|
||||
--- a/wls_lib/Makefile
|
||||
+++ b/wls_lib/Makefile
|
||||
@@ -1,20 +1,6 @@
|
||||
-###############################################################################
|
||||
-#
|
||||
-# Copyright (c) 2021 Intel.
|
||||
-#
|
||||
-# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
-# you may not use this file except in compliance with the License.
|
||||
-# You may obtain a copy of the License at
|
||||
-#
|
||||
-# http://www.apache.org/licenses/LICENSE-2.0
|
||||
-#
|
||||
-# Unless required by applicable law or agreed to in writing, software
|
||||
-# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-# See the License for the specific language governing permissions and
|
||||
-# limitations under the License.
|
||||
-#
|
||||
-###############################################################################
|
||||
+################################################################
|
||||
+# <COPYRIGHT_TAG>
|
||||
+################################################################
|
||||
|
||||
MYCUSTOMTAB=' '
|
||||
MYCUSTOMSPACE='============================================================================================'
|
||||
@@ -24,11 +10,17 @@ MYCUSTOMSPACE1='------------------------------------------------------------'
|
||||
# Tools configuration
|
||||
##############################################################
|
||||
ifeq ($(WIRELESS_SDK_TOOLCHAIN),icc)
|
||||
-CC := icc
|
||||
+ CC := icc
|
||||
CPP := icpc
|
||||
-AS := as
|
||||
-AR := ar
|
||||
-LD := icc
|
||||
+ AS := as
|
||||
+ AR := ar
|
||||
+ LD := icc
|
||||
+else ifeq ($(WIRELESS_SDK_TOOLCHAIN),gcc)
|
||||
+ CC := gcc
|
||||
+ CPP := g++
|
||||
+ AS := as
|
||||
+ AR := ar
|
||||
+ LD := gcc
|
||||
else ifeq ($(WIRELESS_SDK_TOOLCHAIN),icx)
|
||||
CC := icx
|
||||
CPP := icx
|
||||
@@ -44,9 +36,9 @@ ifeq ($(WIRELESS_SDK_TARGET_ISA),sse)
|
||||
else ifeq ($(WIRELESS_SDK_TARGET_ISA),avx2)
|
||||
TARGET_PROCESSOR := -xCORE-AVX2
|
||||
else ifeq ($(WIRELESS_SDK_TARGET_ISA),avx512)
|
||||
- TARGET_PROCESSOR := -xCORE-AVX512
|
||||
+ TARGET_PROCESSOR := -march=skylake-avx512
|
||||
else ifeq ($(WIRELESS_SDK_TARGET_ISA),snc)
|
||||
- TARGET_PROCESSOR := -xicelake-server
|
||||
+ TARGET_PROCESSOR := -march=icelake-server
|
||||
else ifeq ($(WIRELESS_SDK_TARGET_ISA),spr)
|
||||
TARGET_PROCESSOR := -march=sapphirerapids
|
||||
endif
|
||||
@@ -66,15 +58,38 @@ PROJECT_DIR := ./
|
||||
BUILDDIR := make
|
||||
PROJECT_BINARY := $(PROJECT_NAME).so
|
||||
|
||||
+PKGCONF ?= pkg-config
|
||||
+LIBDPDK_VERSION=20.11.3
|
||||
+
|
||||
+ifeq ($(shell $(PKGCONF) --modversion "libdpdk = '$(LIBDPDK_VERSION)'"),$(LIBDPDK_VERSION))
|
||||
+ DPDK_INSTALLED=1
|
||||
+else
|
||||
+ DPDK_INSTALLED=0
|
||||
+endif
|
||||
+
|
||||
ifeq ($(RTE_SDK),)
|
||||
-$(error "Please define RTE_SDK environment variable")
|
||||
+ifeq ($(DPDK_INSTALLED),0)
|
||||
+$(error "Please define RTE_SDK environment variable OR install DPDK version $(LIBDPDK_VERSION)")
|
||||
+endif
|
||||
endif
|
||||
|
||||
-RTE_INC := $(shell PKG_CONFIG_PATH=/usr/lib64/pkgconfig:$(RTE_SDK)/build/meson-uninstalled pkgconf --cflags-only-I libdpdk)
|
||||
+# At least one of the options is defined
|
||||
+
|
||||
+ifneq ($(RTE_SDK),)
|
||||
+ RTE_INC := $(shell PKG_CONFIG_PATH=/usr/lib64/pkgconfig:$(RTE_SDK)/build/meson-uninstalled pkgconf --cflags-only-I libdpdk)
|
||||
+else ifeq ($(DPDK_INSTALLED),1)
|
||||
+ RTE_INC := $(shell PKG_CONFIG_PATH=/usr/lib64/pkgconfig $(PKGCONF) --cflags-only-I libdpdk)
|
||||
+endif
|
||||
|
||||
CC_SRC = wls_lib_dpdk.c \
|
||||
syslib.c
|
||||
|
||||
+CC_HDR = syslib.h \
|
||||
+ ttypes.h \
|
||||
+ wls.h \
|
||||
+ wls_debug.h \
|
||||
+ wls_lib.h
|
||||
+
|
||||
CC_FLAGS += -std=gnu11 -Wall -Wno-deprecated-declarations \
|
||||
-fdata-sections \
|
||||
-ffunction-sections \
|
||||
@@ -84,18 +99,19 @@ CC_FLAGS += -std=gnu11 -Wall -Wno-deprecated-declarations \
|
||||
-Wimplicit-function-declaration \
|
||||
-g -O3 -mcmodel=large $(TARGET_PROCESSOR)
|
||||
|
||||
-INC := -I$(RTE_INC)
|
||||
+INC := -I$(RTE_INC) -I../intel_fapi/
|
||||
DEF :=
|
||||
|
||||
AS_FLAGS :=
|
||||
AR_FLAGS := rc
|
||||
|
||||
PROJECT_OBJ_DIR := $(BUILDDIR)/obj
|
||||
+PROJECT_DEP_DIR := $(PROJECT_OBJ_DIR)/$(PROJECT_NAME).dep.d
|
||||
|
||||
CC_OBJS := $(patsubst %.c,%.o,$(CC_SRC))
|
||||
AS_OBJS := $(patsubst %.s,%.o,$(AS_SRC))
|
||||
OBJS := $(CC_OBJS) $(AS_OBJS) $(LIBS)
|
||||
-DIRLIST := $(addprefix $(PROJECT_OBJ_DIR)/,$(sort $(dir $(OBJS))))
|
||||
+DIRLIST := $(addprefix $(PROJECT_OBJ_DIR)/,$(sort $(dir $(OBJS)))) $(PROJECT_DEP_DIR)
|
||||
|
||||
CC_OBJTARGETS := $(addprefix $(PROJECT_OBJ_DIR)/,$(CC_OBJS))
|
||||
|
||||
@@ -104,9 +120,7 @@ CC_FLAGS_FULL := $(CC_FLAGS) $(INC) $(DEF)
|
||||
|
||||
AS_FLAGS := $(AS_FLAGS) $(INC)
|
||||
|
||||
-PROJECT_DEP_FILE := $(PROJECT_OBJ_DIR)/$(PROJECT_NAME).dep
|
||||
-
|
||||
-ifeq ($(wildcard $(PROJECT_DEP_FILE)),$(PROJECT_DEP_FILE))
|
||||
+ifeq ($(wildcard $(PROJECT_DEP_DIR)),$(PROJECT_DEP_DIR))
|
||||
GENERATE_DEPS :=
|
||||
else
|
||||
|
||||
@@ -119,11 +133,11 @@ all : welcome_line $(PROJECT_BINARY)
|
||||
|
||||
.PHONY : clear_dep
|
||||
clear_dep:
|
||||
- @$(RM) $(PROJECT_DEP_FILE)
|
||||
- @echo [DEP] $(subst $(PROJECT_OBJ_DIR)/,,$(PROJECT_DEP_FILE))
|
||||
+ @echo [DEP] rm $(PROJECT_DEP_DIR)/$(@F).dep
|
||||
+ @$(RM) $(PROJECT_DEP_DIR)/$(@F).dep
|
||||
|
||||
-$(CC_DEPS) :
|
||||
- @$(CC) -MM $(subst __up__,../,$(subst __dep__,,$@)) -MT $(PROJECT_OBJ_DIR)/$(patsubst %.c,%.o,$(subst __up__,../,$(subst __dep__,,$@))) $(CC_FLAGS_FULL) >> $(PROJECT_DEP_FILE)
|
||||
+$(CC_DEPS) : $(PROJECT_DEP_DIR)
|
||||
+ @$(CC) $(CC_FLAGS_FULL) -MM $(subst __up__,../,$(subst __dep__,,$@)) -MT $(PROJECT_OBJ_DIR)/$(patsubst %.c,%.o,$(subst __up__,../,$(subst __dep__,,$@))) >> $(PROJECT_DEP_DIR)/$(@F).dep
|
||||
|
||||
.PHONY : generate_deps
|
||||
generate_deps : clear_dep $(CC_DEPS)
|
||||
@@ -136,17 +150,17 @@ echo_start_build :
|
||||
$(DIRLIST) :
|
||||
-@$(MD) $@
|
||||
|
||||
-$(CC_OBJTARGETS) :
|
||||
+$(CC_OBJTARGETS) : $(GENERATE_DEPS)
|
||||
@echo [CC] $(subst $(PROJECT_OBJ_DIR)/,,$@)
|
||||
@$(CC) -c $(CC_FLAGS_FULL) -o"$@" $(patsubst %.o,%.c,$(subst $(PROJECT_OBJ_DIR)/,,$@))
|
||||
|
||||
-$(AS_OBJTARGETS) :
|
||||
+$(AS_OBJTARGETS) : $(CC_OBJTARGETS) $(CPP_OBJTARGETS)
|
||||
@echo [AS] $(subst $(PROJECT_OBJ_DIR)/,,$@)
|
||||
@$(AS) $(AS_FLAGS) -o"$@" $(patsubst %.o,%.s,$(subst $(PROJECT_OBJ_DIR)/,,$@))
|
||||
|
||||
-ifeq ($(wildcard $(PROJECT_DEP_FILE)),$(PROJECT_DEP_FILE))
|
||||
+ifeq ($(wildcard $(PROJECT_DEP_DIR)),$(PROJECT_DEP_DIR))
|
||||
|
||||
-include $(PROJECT_DEP_FILE)
|
||||
+include $(PROJECT_DEP_DIR)/*
|
||||
|
||||
endif
|
||||
|
||||
@@ -156,7 +170,7 @@ clean:
|
||||
@$(RM) $(CC_OBJTARGETS) $(AS_OBJTARGETS)
|
||||
ifneq ($(wildcard $(PROJECT_DIR)/$(PROJECT_MAKE)),)
|
||||
@echo [CLEAN] : $(PROJECT_NAME)
|
||||
- @$(RM) $(PROJECT_BINARY) $(PROJECT_BINARY_LIB) $(PROJECT_DEP_FILE)
|
||||
+ @$(RM) -rf $(PROJECT_BINARY) $(PROJECT_BINARY_LIB) $(PROJECT_DEP_DIR)
|
||||
endif
|
||||
|
||||
xclean: clean
|
||||
@@ -172,7 +186,24 @@ welcome_line :
|
||||
debug : all
|
||||
release : all
|
||||
|
||||
-$(PROJECT_BINARY) : $(DIRLIST) echo_start_build $(GENERATE_DEPS) $(PRE_BUILD) $(CC_OBJTARGETS) $(AS_OBJTARGETS)
|
||||
+$(PROJECT_BINARY) : $(DIRLIST) echo_start_build $(GENERATE_DEPS) $(CC_OBJTARGETS) $(AS_OBJTARGETS)
|
||||
@echo [AR] $(subst $(BUILDDIR)/,,$@)
|
||||
@$(CC) $(CC_OBJTARGETS) $(AS_OBJTARGETS) -shared -fPIC -o $@
|
||||
|
||||
+##############################################################
|
||||
+# Install configuration
|
||||
+##############################################################
|
||||
+
|
||||
+INSTALL_DIR := /usr/local
|
||||
+INCLUDE_DIR := $(INSTALL_DIR)/include/
|
||||
+
|
||||
+.PHONY : install
|
||||
+install: $(PROJECT_BINARY) install_headers
|
||||
+ @echo [INSTALL] : $(PROJECT_BINARY)
|
||||
+ @$(CP) $(PROJECT_BINARY) $(INSTALL_DIR)/lib
|
||||
+
|
||||
+.PHONY : install_headers
|
||||
+install_headers: $(CC_SRC)
|
||||
+ @echo [INSTALL] : $(PROJECT_NAME) headers
|
||||
+ @$(MD) $(INCLUDE_DIR)
|
||||
+ @$(CP) $(CC_HDR) $(INCLUDE_DIR)
|
||||
@@ -1,53 +1,90 @@
|
||||
# global
|
||||
This document describes the broad way in which the VNF and PNF work internally,
|
||||
from configuration, to P5 and P7 message exchange and processing.
|
||||
Their internal processing is broadly independent on which transport mechanism
|
||||
is responsible for exchanging data between the 2 components.
|
||||
|
||||
xnf is pnf or vnf
|
||||
To read more about the transport mechanisms available, and how to run the split, please refer to
|
||||
[this file](./nfapi.md).
|
||||
|
||||
The xnf starting functions are configure_nfapi_xnf()
|
||||
[[_TOC_]]
|
||||
|
||||
The OAI code that read the configuration parameters call these functions that will create autonomous thread for xnf control part
|
||||
# VNF/PNF Split
|
||||
|
||||
These control threads create a SCTP socket by Linux API call (no use of OpenAir internal architecture with SCTP itti thread).
|
||||
The gNB is split into VNF(L2+) and PNF(L1)
|
||||
|
||||
The main() function of softmodem has to create and start the other threads+loop on event for appropriate work: RF Rx in pnf, NGAP, GTP-U, X2, RLC, PDCP for vnf
|
||||
These component are configured via the functions:
|
||||
|
||||
NFAPI has it's own code directly on Linux+Posix, it doesn't reuse any piece of the OpenAir framework: SCTP thread, thread creation and management, ...
|
||||
- configure_nr_nfapi_vnf()
|
||||
- configure_nr_nfapi_pnf()
|
||||
|
||||
## signaling (P5) xnf main loop
|
||||
These functions initialize the configuration appropriate for the transport mechanism selected ( setting the
|
||||
pack/unpack function pointers, as well as the appropriate send functions )
|
||||
|
||||
nfapi_vnf_start() create the SCTP socket, then do event waiting in a infinite loop while(vnf->terminate == 0). It accepts the pnf connections, then process the messages on reception
|
||||
After this, a thread is created for the P5 receive loop, which initializes the transport and receive loop
|
||||
|
||||
nfapi_pnf_start() initiate connection to vnf until it has a link. Then it enter a similar infinite loop in pnf_message_pump()
|
||||
This created thread may be only for P5 messages, or P5 and P7, depending on the transport type
|
||||
|
||||
After some checks, when a message is obtained, it calls pnf_handle_p5_message() or vnf_handle_p4_p5_message() that have a switch on each p5 message types: the enum nfapi_message_id_e
|
||||
- When using socket-based communication (which uses nFAPI encoding), the receiving loop for P7 messages is separate from
|
||||
P5, it starts upon the PNF_START exchange
|
||||
- In the other transport mechanisms (WLS and nvIPC, which use FAPI encoding), the receiving loop is the same for P5 and
|
||||
P7 messages, the P7 configuration is done immediately after the P5 configuration.
|
||||
|
||||
Since the first P7 messages is only sent/received after the START exchange, we can logically treat the loops as separate
|
||||
, we won't receive P7 messages before finishing the P5 ones ( except for the STOP exchange )
|
||||
|
||||
## P5 interface main loop
|
||||
|
||||
After the P5 configuration, both the VNF and PNF have a thread waiting to "consume" (n)FAPI messages exchanged between
|
||||
them
|
||||
|
||||
Upon the reception of a message (whether in its entirety or segmented in the case of nFAPI), the messages are sent to
|
||||
the appropriate handler:
|
||||
|
||||
- pnf_nr_handle_p5_message
|
||||
- vnf_nr_handle_p4_p5_message
|
||||
|
||||
In each of these functions, there's a switch calling the appropriate handler according to the message ID, for example:
|
||||
|
||||
Each message type has it's own processing function in the switch, like
|
||||
```
|
||||
case NFAPI_START_RESPONSE:
|
||||
vnf_handle_start_response(pRecvMsg, recvMsgLen, config, p5_idx);
|
||||
break;
|
||||
case NFAPI_NR_PHY_MSG_TYPE_START_RESPONSE:
|
||||
vnf_nr_handle_start_response(pRecvMsg, recvMsgLen, config, p5_idx);
|
||||
break;
|
||||
```
|
||||
|
||||
These loops are autonomous in their thread waiting incoming message.
|
||||
|
||||
## P7 xnf main loop
|
||||
## P7 interface main loop
|
||||
|
||||
When the p5 interface receives appropriate message, it starts the p7 interface by launching a thread (see the calls to pthread_create() in nfapi/oai_integration/nfapi_xnf.c)
|
||||
- Note: As explained before, the P7 reception loop is the same as the P5 messages when not using socket-based
|
||||
communication
|
||||
|
||||
The p7 main loops starting is a bit simpler, thanks to UDP non connected protocol. The xnf_dispatch_p7_message() do p7 fragments re-assembly, then
|
||||
calls xnf_dispatch_p7_message() that have the big switch on message type (as in p5 above description)
|
||||
In this case, when the P5 interface receives appropriate message, it starts the p7 interface by launching a thread
|
||||
|
||||
So, we have the logic for UL reception in vnf, and DL reception in pnf
|
||||
- On the PNF, this is done in the START.request handler ( nr_start_request(...) )
|
||||
- On the VNF, this is done in ( configure_nr_p7_vnf(...) )
|
||||
|
||||
Much in the same way as when processing P5 messages, the following functions are called to unpack and process them:
|
||||
|
||||
- pnf_nr_handle_p7_message
|
||||
- vnf_nr_handle_p7_message
|
||||
|
||||
```
|
||||
case NFAPI_NR_PHY_MSG_TYPE_SLOT_INDICATION:
|
||||
vnf_handle_nr_slot_indication(pRecvMsg, recvMsgLen, vnf_p7);
|
||||
break;
|
||||
```
|
||||
|
||||
## P7 UL transmission by PNF
|
||||
|
||||
RF samples are received, and decoding is done by the PNF using control data transmitted by the VNF to the PNF through downlink p7 messages (UL_TTI_req and UL_DCI_req).
|
||||
RF samples are received, and decoding is done by the PNF using control data transmitted by the VNF to the PNF through
|
||||
downlink p7 messages (UL_TTI.request and UL_DCI.request).
|
||||
|
||||
After decoding, results are accumulated into the xNB->UL_INFO structure at the PNF.
|
||||
After decoding, results are accumulated into the gNB->UL_INFO structure at the PNF.
|
||||
|
||||
The data in the UL_INFO struct is transmitted through a socket in the form of 'uplink indication functions' from the PNF to the VNF. Each uplink indication message is transmitted from their respective handle functions in NR_UL_indication(). For example,
|
||||
The data in the UL_INFO struct is transmitted through the configured send function pointer (send_p7_msg), which packs
|
||||
the message into a buffer according to the encoding and sends it to the VNF
|
||||
|
||||
```
|
||||
|
||||
void handle_nr_rach(NR_UL_IND_t *UL_info) {
|
||||
if(NFAPI_MODE == NFAPI_MODE_PNF) {
|
||||
if (UL_info->rach_ind.number_of_pdus>0) {
|
||||
@@ -56,67 +93,186 @@ void handle_nr_rach(NR_UL_IND_t *UL_info) {
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
....
|
||||
|
||||
## P7 UL reception at VNF
|
||||
int oai_nfapi_nr_rach_indication(nfapi_nr_rach_indication_t *ind) {
|
||||
ind->header.phy_id = 1; // HACK TODO FIXME - need to pass this around!!!!
|
||||
ind->header.message_id = NFAPI_NR_PHY_MSG_TYPE_RACH_INDICATION;
|
||||
return nfapi_pnf_p7_nr_rach_ind(p7_config_g, ind);
|
||||
}
|
||||
|
||||
Through the infinite loop [while(vnf_p7->terminate == 0)] running in nfapi_nr_vnf_p7_start(), the VNF receives and unpacks the uplink indication message received on its socket. Based on the unpacked messages, UL_INFO struct on the VNF side gets populated.
|
||||
.....
|
||||
|
||||
```
|
||||
|
||||
// have a p7 message
|
||||
if(FD_ISSET(vnf_p7->socket, &rfds))
|
||||
{
|
||||
vnf_nr_p7_read_dispatch_message(vnf_p7);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
vnf_nr_dispatch_p7_message() is the function that contains the switch on various message headers so that the appropriate unpack function is called.
|
||||
|
||||
## P7 DL Transmission by VNF
|
||||
|
||||
DL messages are scheduled at the VNF, through NR_UL_indication(). NR_UL_indication() is called when the SFN/slot in the UL_info structure changes (this acts as a trigger for next slot processing, instead of running a separate clock at the VNF). The SFN/slot at the VNF in UL_info is updated using the slot_indication uplink p7 message, which is sent at the beginning of every slot by the PNF. The slot_indication message contains SFN/slot of the TX_thread, so that the scheduler operates slot_ahead slots ahead of the RX thread. This ensures that UL_tti_req is received before RX slot processing at the PNF.
|
||||
|
||||
The function NR_schedule_response calls oai_nfapi_[DL P7 msg]_req(), which contains the routines for packing + transmission of scheduled messages through the socket to the PNF. For example, to send the 'TX_data_req' p7 message
|
||||
|
||||
```
|
||||
|
||||
if (Sched_INFO->TX_req->Number_of_PDUs > 0)
|
||||
int nfapi_pnf_p7_nr_rach_ind(nfapi_pnf_p7_config_t* config, nfapi_nr_rach_indication_t* ind)
|
||||
{
|
||||
oai_nfapi_tx_data_req(Sched_INFO->TX_req);
|
||||
if(config == NULL || ind == NULL)
|
||||
{
|
||||
NFAPI_TRACE(NFAPI_TRACE_ERROR, "%s: invalid input params\n", __FUNCTION__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pnf_p7_t* _this = (pnf_p7_t*)(config);
|
||||
AssertFatal(_this->_public.send_p7_msg, "Function pointer must be configured|");
|
||||
return _this->_public.send_p7_msg(_this, (nfapi_nr_p7_message_header_t*)ind, sizeof(nfapi_nr_rach_indication_t));
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## P7 UL reception at VNF
|
||||
|
||||
Through the P7 reception loop, the VNF receives a buffer containing the messages, which it handles by the following
|
||||
process:
|
||||
|
||||
- Unpack the header by use of the hdr_unpack_func function pointer
|
||||
- According to the Message ID in the header, send the buffer to the appropriate handler by use of a switch statement:
|
||||
|
||||
```
|
||||
case NFAPI_NR_PHY_MSG_TYPE_SLOT_INDICATION:
|
||||
vnf_handle_nr_slot_indication(pRecvMsg, recvMsgLen, vnf_p7);
|
||||
break;
|
||||
|
||||
```
|
||||
|
||||
- In the handler function, unpack the entire message, by using the unpack_func function pointer.
|
||||
- If the unpack procedure is successful, call the previously configure callback for that message type:
|
||||
|
||||
```
|
||||
nfapi_nr_slot_indication_scf_t ind = {0};
|
||||
const bool result = vnf_p7->_public.unpack_func(pRecvMsg, recvMsgLen, &ind, sizeof(ind), &vnf_p7->_public.codec_config);
|
||||
if(!result)
|
||||
{
|
||||
NFAPI_TRACE(NFAPI_TRACE_ERROR, "%s: Failed to unpack message\n", __FUNCTION__);
|
||||
}
|
||||
else
|
||||
{
|
||||
NFAPI_TRACE(NFAPI_TRACE_DEBUG, "%s: Handling NR SLOT Indication\n", __FUNCTION__);
|
||||
if(vnf_p7->_public.nr_slot_indication)
|
||||
{
|
||||
(vnf_p7->_public.nr_slot_indication)(&ind);
|
||||
}
|
||||
free_slot_indication(&ind);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
vnf_nr_dispatch_p7_message() is the function that contains the switch on various message headers so that the appropriate
|
||||
unpack function is called.
|
||||
|
||||
## P7 DL Transmission by VNF
|
||||
|
||||
DL messages are scheduled at the VNF, through gNB_dlsch_ulsch_scheduler(). gNB_dlsch_ulsch_scheduler() is called when
|
||||
handling a SLOT.indication message in trigger_scheduler().
|
||||
|
||||
The function trigger_scheduler(nfapi_nr_slot_indication_scf_t *slot_ind) calls the functions oai_nfapi_[DL P7 msg]_
|
||||
req(), calling in turn call the send_p7_msg function pointer, which contain the logic to pack the message into a buffer
|
||||
and send it to the PNF.
|
||||
Finally, NR_UL_indication is called to process the other P7 messages received from the PNF that were put in theire
|
||||
respective queues.
|
||||
For example, the TX_DATA.request message is sent in the following manner:
|
||||
|
||||
```
|
||||
if (g_sched_resp.TX_req.Number_of_PDUs > 0)
|
||||
oai_nfapi_tx_data_req(&g_sched_resp.TX_req);
|
||||
|
||||
...
|
||||
|
||||
int oai_nfapi_tx_data_req(nfapi_nr_tx_data_request_t *tx_data_req)
|
||||
{
|
||||
LOG_D(NR_PHY, "Entering oai_nfapi_nr_tx_data_req sfn:%d,slot:%d\n", tx_data_req->SFN, tx_data_req->Slot);
|
||||
nfapi_vnf_p7_config_t *p7_config = vnf.p7_vnfs[0].config;
|
||||
tx_data_req->header.phy_id = 1; // HACK TODO FIXME - need to pass this around!!!!
|
||||
tx_data_req->header.message_id = NFAPI_NR_PHY_MSG_TYPE_TX_DATA_REQUEST;
|
||||
//LOG_D(PHY, "[VNF] %s() TX_REQ sfn_sf:%d number_of_pdus:%d\n", __FUNCTION__, NFAPI_SFNSF2DEC(tx_req->sfn_sf), tx_req->tx_request_body.number_of_pdus);
|
||||
bool retval = nfapi_vnf_p7_tx_data_req(p7_config, tx_data_req);
|
||||
|
||||
if (!retval) {
|
||||
LOG_E(PHY, "%s() Problem sending retval:%d\n", __FUNCTION__, retval);
|
||||
} else {
|
||||
tx_data_req->Number_of_PDUs = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
bool nfapi_vnf_p7_tx_data_req(nfapi_vnf_p7_config_t* config, nfapi_nr_tx_data_request_t* req)
|
||||
{
|
||||
if(config == 0 || req == 0)
|
||||
return -1;
|
||||
|
||||
vnf_p7_t* vnf_p7 = (vnf_p7_t*)config;
|
||||
AssertFatal(config->send_p7_msg, "Function pointer must be configured|");
|
||||
return config->send_p7_msg(vnf_p7, &req->header);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## VNF functional flowchart
|
||||
|
||||
```mermaid
|
||||
|
||||
graph TD
|
||||
pselect[VNF socket pselect] -- timed out : end of slot --> call_sched[Call Scheduler NR_UL_indication] -- oai_nfapi_***_req sends the DL p7 msg--> slot_inc[Increment sfn/slot];
|
||||
pselect[VNF socket pselect] -- UL p7 xyz msg recvd --> msg_recvd[Read message vnf_nr_p7_read_dispatch_message] --> header_unpack[Unpack Header] -- switch cases on header --> unpack_msg[Handle Message vnf_handle_nr_xyz] --> fill_ul_info[Fill UL info struct with fn pointer vnf_p7->_public.nr_rx_xyz];
|
||||
fill_ul_info -- update pselect_timeout: next_slot_start - time_xyz_msg_recvd-->pselect;
|
||||
slot_inc -- next slot --> pselect
|
||||
softmodem_start[nr-softmodem init] --> P5_configuration[configure_nr_nfapi_vnf] --> transport_init[Transport mechanism init] --> send_first_msg[Send first P5 message to PNF]
|
||||
transport_init[Transport mechanism init] --> P5_recv_loop[P5 receive loop] -- P5 Message processing --> P5_recv_loop[P5 receive loop]
|
||||
P5_recv_loop[P5 receive loop] -- Start.response callback called --> P7_config[P7 Configuration] --> P7_loop[P7 Loop] --> P7_msg_recv[P7 message received] --> call_vnf_handle_p7[Call vnf_nr_handle_p7_msg] --> hdr_unpack[Unpack header] --> call_specific_p7_handler[Call specific P7 message handler] --> slot_ind[Is a SLOT.indication?] -- Yes --> trig_scheduler[Call trigger_scheduler] --> gNB_ulsch_dlsch_sched[Call gNB_dlsch_ulsch_scheduler] --> send_p7_dl[Process and send P7 messages to PNF] --> call_ul_ind[Call NR_UL_indication] --> proc_p7[Process P7 messages in queue] --> P7_loop[P7 Loop]
|
||||
slot_ind[Is a SLOT.indication?] -- No --> put_queue[Put message in queue] --> P7_loop[P7 Loop];
|
||||
|
||||
|
||||
```
|
||||
Note that since DL P7 message reception and TX/RX processing are done on separate threads, there is the issue of the L1 processing threads trying to do their job before the required P7 message is received. In the case of RX processing, since the scheduler operates slot_ahead slots ahead of the RX thread, the required messages conveniently arrive earlier than they are required. However, in the case of TX processing, this cannot be ensured if the scheduler is exactly in sync with the TX thread.
|
||||
|
||||
Therefore, we operate the VNF vnf_slot_ahead (which is currently 2) slots ahead of the PNF. This is to ensure that the DL fapi structures for a particular TX slot are all received before TX processing for that slot.
|
||||
## P7 DL Reception at PNF
|
||||
|
||||
## P7 DL Reception at PNF
|
||||
Through the infinite loop [while(pnf_p7->terminate == 0)] running in pnf_nr_p7_message_pump(), the PNF receives and
|
||||
unpacks the downlink P7 message received on its socket. Based on the unpacked message, the appropriate message
|
||||
structures are filled in the PNF, and these are used further down the pipeline for processing.
|
||||
Through the P7 reception loop, the PNF receives a buffer containing a P7 message from the VNF, which it processes the
|
||||
following way:
|
||||
|
||||
Through the infinite loop [while(pnf_p7->terminate == 0)] running in pnf_nr_p7_message_pump(), the PNF receives and unpacks the downlink P7 message received on its socket. Based on the unpacked message, the appropriate message structures are filled in the PNF, and these are used further down the pipeline for processing.
|
||||
- Unpack the header by use of the hdr_unpack_func function pointer
|
||||
- According to the Message ID in the header, send the buffer to the appropriate handler by use of a switch statement:
|
||||
|
||||
While receiving the DL P7 message, we check whether the message was received within a timing window from which it was sent. The duration of the window can be set by the user (set as a parameter for xnf in the p5 messages). Note that the DL information must be received by the PNF within a timing window at least lesser than the duration of slot_ahead variable (timing_window <= slot_ahead * slot_duration).
|
||||
```
|
||||
case NFAPI_NR_PHY_MSG_TYPE_DL_TTI_REQUEST:
|
||||
pnf_handle_dl_tti_request(pRecvMsg, recvMsgLen, pnf_p7);
|
||||
break;
|
||||
```
|
||||
|
||||
- In the handler function, the SFN and Slot are peeked from the message
|
||||
- The SFN and slot are checked to determine if the message is inside the appropriate timing window
|
||||
- If so, the message is unpacked into the P7 Slot buffer:
|
||||
|
||||
```
|
||||
pnf_p7->slot_buffer[buffer_index].sfn = frame;
|
||||
pnf_p7->slot_buffer[buffer_index].slot = slot;
|
||||
nfapi_nr_dl_tti_request_t *req = &pnf_p7->slot_buffer[buffer_index].dl_tti_req;
|
||||
pnf_p7->nr_stats.dl_tti.ontime++;
|
||||
|
||||
NFAPI_TRACE(NFAPI_TRACE_DEBUG,
|
||||
"POPULATE DL_TTI_REQ current tx sfn/slot:%d.%d p7 msg sfn/slot: %d.%d buffer_index:%d\n",
|
||||
pnf_p7->sfn,
|
||||
pnf_p7->slot,
|
||||
frame,
|
||||
slot,
|
||||
buffer_index);
|
||||
const bool result = pnf_p7->_public.unpack_func(pRecvMsg, recvMsgLen, req, sizeof(*req), &(pnf_p7->_public.codec_config));
|
||||
|
||||
```
|
||||
|
||||
- The messages are later processed in the NR_slot_indication function, which is called in the tx_func function (
|
||||
L1_tx_thread )
|
||||
|
||||
## PNF functional flowchart
|
||||
|
||||
```mermaid
|
||||
|
||||
graph TB
|
||||
pselect[PNF socket pselect] -- timed out : end of slot --> slot_inc[Increment sfn/slot];
|
||||
pselect[PNF socket pselect] -- DL p7 xyz msg recvd --> msg_recvd[Read message pnf_nr_nfapi_p7_read_dispatch_message] --> header_unpack[Unpack Header] -- switch cases on header --> unpack_msg[Unpack Message pnf_handle_nr_xyz] --> fill_pnf_p7[Fill pnf_p7 global structure pnf_handle_nr_xyz] --Data from pnf_p7 struct copied to fapi structures using pnf_phy_***_req. Called every slot from handle_nr_slot_ind-->pselect;
|
||||
|
||||
slot_inc -- next slot --> pselect
|
||||
graph TD
|
||||
softmodem_start[nr-softmodem init] --> init_l1_tx_thread[Init L1 TX Thread] --> tx_func[Call tx_func] --> nr_slot_ind[Call NR_slot_indication] --> pnf_slot_ind[Call handle_nr_slot_ind] --> send_slot_ind[Send SLOT.ind to VNF] --> get_from_slot_buffer[Get P7 messages from slot_buffer] --> proc_p7_dl[Process P7 messages from VNF] --> tx_func
|
||||
softmodem_start[nr-softmodem init] --> init_l1_rx_thread[Init L1 RX Thread] --> rx_func[Call rx_func] --> nr_ul_ind[Call NR_UL_indication] --> send_p7_vnf[Send P7 messages to VNF] --> rx_func
|
||||
softmodem_start[nr-softmodem init] --> P5_configuration[configure_nr_nfapi_pnf] --> transport_init[Transport mechanism init] --> p5_loop[P5 Loop] -- Process P5 messages --> p5_loop
|
||||
p5_loop -- START.request callback called --> p7_config[P7 Configuration] --> l1_north_init_gnb[Call l1_north_init_gNB] --> nr_if_module_init[Call NR_IF_Module_Init] --> install_slot_ind_cb[Install NR_slot_indication callback]
|
||||
p7_config[P7 Configuration] --> p7_loop[P7 Loop] --> p7_recv[Receive P7 message] --> pnf_handle_p7[Call pnf_nr_handle_p7_message] --> hdr_unpack[Unpack header with hdr_unpack] --> p7_handler[Call pnf_handle_<msg_type>] --> peek_sfn[Peek SFN/Slot and check if in timing window] --> p7_unpack[Unpack message into slot_buffer] --> p7_loop
|
||||
```
|
||||
Once the messages are received, they are filled into slot buffers, and are stored until the processing of the slot that they were meant for.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https://
|
||||
- [How to use the L2 simulator](./L2NFAPI.md)
|
||||
- [How to use the OAI channel simulator](../openair1/SIMULATION/TOOLS/DOC/channel_simulation.md)
|
||||
- [How to use multiple BWPs](./RUN_NR_multiple_BWPs.md)
|
||||
- [How to run OAI-VNF and OAI-PNF](./nfapi.md): how to run the nFAPI split,
|
||||
- [How to run OAI-VNF and OAI-PNF](./nfapi.md): how to run the FAPI/nFAPI split,
|
||||
including some general remarks on FAPI/nFAPI.
|
||||
- [How to use the positioning reference signal (PRS)](./RUN_NR_PRS.md)
|
||||
- [How to use device-to-device communication (D2D, 4G)](./d2d_emulator_setup.txt)
|
||||
|
||||
124
doc/nfapi.md
124
doc/nfapi.md
@@ -1,5 +1,6 @@
|
||||
This document describes the SmallCellForum (SCF) (n)FAPI split in 5G, i.e.,
|
||||
between the MAC/L2 and PHY/L1.
|
||||
between the MAC/L2 and PHY/L1. It also describes how to make use of the
|
||||
multiple transport mechanisms between the 2.
|
||||
|
||||
The interested reader is recommended to read a copy of the SCF 222.10
|
||||
specification ("FAPI"). This includes information on what is P5, P7, and how
|
||||
@@ -7,6 +8,8 @@ FAPI works. The currently used version is SCF 222.10.02, with some messages
|
||||
upgraded to SCF 222.10.04 due to bugfixes in the spec. Further information
|
||||
about nFAPI can be found in SCF 225.2.0.
|
||||
|
||||
[[_TOC_]]
|
||||
|
||||
# Quickstart
|
||||
|
||||
Compile OAI as normal. Start the CN and make sure that the VNF configuration
|
||||
@@ -108,6 +111,125 @@ can proceed as follows:
|
||||
Note: all L1-specific options have to be passed to the PNF, and remaining
|
||||
options to the VNF.
|
||||
|
||||
## Transport mechanisms between VNF and PNF
|
||||
|
||||
Currently, the VNF/PNF split supports three transport mechanisms between each
|
||||
other:
|
||||
|
||||
1. Socket communication (either regular, or SCTP), this is the default
|
||||
|
||||
The socket type may be changed by editing `nfapi_pnf_config_create()` and
|
||||
`nfapi_vnf_config_create()`, in both of which `_this->sctp = <value, 0 or
|
||||
1>;` indicate whether SCTP or regular sockets are to be used. Note: The
|
||||
value of `_this->sctp` **must** be the same on the VNF and PNF.
|
||||
2. Intel WLS Lib, which uses DPDK to achieve a shared memory communication between components.
|
||||
3. nvIPC, which is used exclusively for the NVIDIA Aerial L1. Thus, it is only
|
||||
applicable for the VNF.
|
||||
|
||||
The change between transport mechanisms is done at compilation time:
|
||||
- No changes to the `build_oai` call are required in order to select socket communication, as it is the default.
|
||||
- In order to select WLS as the transport mechanism between VNF and PNF, first install the WLS library, and afterwards use `-t WLS` as a parameter of `build_oai`:
|
||||
|
||||
### How to use nFAPI
|
||||
|
||||
nFAPI is used by default. Compile and configure as indicated above.
|
||||
|
||||
### How to use Aerial
|
||||
|
||||
Refer to [this document](./Aerial_FAPI_Split_Tutorial.md) for more information.
|
||||
|
||||
### How to use WLS lib
|
||||
|
||||
Before the first compilation with WLS support, the [WLS
|
||||
library](https://docs.o-ran-sc.org/projects/o-ran-sc-o-du-phy/en/latest/wls-lib.html)
|
||||
must first be compiled and installed to the system.
|
||||
|
||||
The WLS library has a few dependencies:
|
||||
- [DPDK](https://doc.dpdk.org/guides/prog_guide/build-sdk-meson.html), specifically version [20.11.3](https://fast.dpdk.org/rel/dpdk-20.11.3.tar.xz).
|
||||
- libelf-dev
|
||||
- libhugetlbfs-dev
|
||||
|
||||
Additionally, a patch needs to be applied to the WLS lib Makefile in order for
|
||||
the shared library and headers to be installed into the system, the necessary
|
||||
patch is available [here](../cmake_targets/tools/install_wls_lib.patch)
|
||||
|
||||
Clone the code and apply the patch
|
||||
|
||||
git clone -b oran_f_release https://gerrit.o-ran-sc.org/r/o-du/phy.git
|
||||
cd phy/wls_lib/
|
||||
git apply ~/openairinterface5g/cmake_targets/tools/install_wls_lib.patch
|
||||
|
||||
Then compile and install the library
|
||||
|
||||
WIRELESS_SDK_TOOLCHAIN=gcc WIRELESS_SDK_TARGET_ISA=avx2 make
|
||||
sudo WIRELESS_SDK_TOOLCHAIN=gcc WIRELESS_SDK_TARGET_ISA=avx2 make install
|
||||
|
||||
After installing WLS, you can run the build command as shown below:
|
||||
|
||||
./build_oai -t WLS -w USRP --gNB --nrUE --ninja -C
|
||||
|
||||
#### How to run OAI PNF with OAI VNF
|
||||
|
||||
Refer to the above steps in [Quickstart](.#quickstart), but run the PNF first as it is the WLS "master".
|
||||
|
||||
#### How to run OAI PNF with OSC/Radisys O-DU
|
||||
|
||||
Set up the hugepages for DPDK (1GB page size, 6 pages; this only needs to be
|
||||
done once):
|
||||
|
||||
sudo nano /etc/default/grub
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT} default_hugepagesz=1G hugepagesz=1G hugepages=6"
|
||||
|
||||
Then rewrite the bootloader and reboot
|
||||
|
||||
sudo update-grub
|
||||
sudo reboot
|
||||
|
||||
Install dependencies:
|
||||
|
||||
sudo apt-get install libstdc++-14-dev libnsl-dev libpcap-dev libxml2-dev
|
||||
|
||||
Clone the Radisys repository (We're currently using the `oai_integration` branch):
|
||||
|
||||
git clone https://gerrit.o-ran-sc.org/r/o-du/l2 -b oai-integration
|
||||
|
||||
Build the O-DU
|
||||
|
||||
cd ~/l2/build/odu
|
||||
make clean_all;make odu PHY=INTEL_L1 MACHINE=BIT64 MODE=TDD;make cu_stub NODE=TEST_STUB MACHINE=BIT64 MODE=TDD;make ric_stub NODE=TEST_STUB MACHINE=BIT64 MODE=TDD
|
||||
|
||||
Setup local interfaces for the cu_stub, ric_stub and o_du
|
||||
|
||||
sudo ifconfig enp7s0:ODU "192.168.130.81" && sudo ifconfig enp7s0:CU_STUB "192.168.130.82" && sudo ifconfig enp7s0:RIC_STUB "192.168.130.80"
|
||||
|
||||
Run cu_stu and ric_stub in separate terminals
|
||||
|
||||
cd ~/l2/bin/
|
||||
./cu_stub/cu_stub
|
||||
clear && ./ric_stub/ric_stub
|
||||
|
||||
Run the OAI PNF first, as it is the WLS memory master
|
||||
|
||||
sudo NFAPI_TRACE_LEVEL=info ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb-pnf.band78.rfsim.2x2.conf --nfapi PNF --rfsim --rfsimulator.serveraddr server
|
||||
|
||||
Run the O-DU over GDB
|
||||
|
||||
sudo -E gdb -ex run --readnever --args ./odu/odu
|
||||
|
||||
Note: If you see the following prompt in GDB
|
||||
|
||||
This GDB supports auto-downloading debuginfo from the following URLs:
|
||||
<https://debuginfod.ubuntu.com>
|
||||
Enable debuginfod for this session? (y or [n])
|
||||
|
||||
You can run the following command one time before executing gdb to disable it:
|
||||
|
||||
export DEBUGINFOD_URLS=
|
||||
|
||||
Run the OAI-UE
|
||||
|
||||
sudo ./nr-uesoftmodem -r 273 --numerology 1 --band 78 -C 3400140000 --ssb 1518 --uicc0.imsi 001010000000001 --rfsim
|
||||
|
||||
# nFAPI logging system
|
||||
|
||||
nFAPI has its own logging system, independent of OAI's. It can be activated by
|
||||
|
||||
Reference in New Issue
Block a user