mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
- all RAN code, CI code, configuration files, dockerfiles, in CSSL v1.0
- all deployment code (openshift, charts, ancillary files like shell
scripts), in MIT
- documentation in CC-BY-4.0
- exceptions might apply and are listed in NOTICE
- there is a new LICENSES folder with all licenses
- CONTRIBUTIONS.md has been updated accordingly
For automated changes based on OAI PL v1.1:
perl -i~ -0pe 's/\/\*.*Licensed to the OpenAirInterface.*openairinterface.org\n#?/\/*\n * SPDX-License-Identifier: LicenseRef-CSSL-1.0\n/s' **/*.{c,h,cpp}
perl -i~ -0pe 's/\/\*.*Licensed to the OpenAirInterface.*openairinterface.org\n#?/\/*\n * SPDX-License-Identifier: LicenseRef-CSSL-1.0\n/s' **/*.ts
perl -i~ -0pe 's/<!--.*Licensed to the OpenAirInterface.*openairinterface.org\n.*-->/<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->/s' **/*.xml
The rest (cmake, files with missing license, cmake) manually.
299 lines
12 KiB
Markdown
299 lines
12 KiB
Markdown
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
|
|
|
|
# SmallCellForum 5G (n)FAPI split
|
|
|
|
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.
|
|
|
|
To read more about the transport mechanisms available, and how to run the split, please refer to
|
|
[this file](./nfapi.md).
|
|
|
|
[[_TOC_]]
|
|
|
|
## VNF/PNF Split
|
|
|
|
The gNB is split into VNF (L2+) and PNF (L1)
|
|
|
|
These component are configured via the functions:
|
|
|
|
- `configure_nr_nfapi_vnf()`
|
|
- `configure_nr_nfapi_pnf()`
|
|
|
|
These functions initialize the configuration appropriate for the transport mechanism selected ( setting the
|
|
pack/unpack function pointers, as well as the appropriate send functions )
|
|
|
|
After this, a thread is created for the P5 receive loop, which initializes the transport and receive loop
|
|
|
|
This created thread may be only for P5 messages, or P5 and P7, depending on the transport type
|
|
|
|
- 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:
|
|
|
|
```
|
|
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 interface main loop
|
|
|
|
> **Note:** As explained before, the P7 reception loop is the same as the P5 messages when not using socket-based communication
|
|
|
|
In this case, when the P5 interface receives appropriate message, it starts the p7 interface by launching a thread
|
|
|
|
- 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.request` and `UL_DCI.request`).
|
|
|
|
After decoding, results are accumulated into the `gNB->UL_INFO` structure at the PNF.
|
|
|
|
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) {
|
|
oai_nfapi_nr_rach_indication(&UL_info->rach_ind); //This function calls the routines required for packing + transmission through socket
|
|
UL_info->rach_ind.number_of_pdus = 0;
|
|
}
|
|
}
|
|
|
|
....
|
|
|
|
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);
|
|
}
|
|
|
|
.....
|
|
|
|
int nfapi_pnf_p7_nr_rach_ind(nfapi_pnf_p7_config_t* config, nfapi_nr_rach_indication_t* ind)
|
|
{
|
|
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 `phy_nr_slot_indication()`.
|
|
|
|
The function `phy_nr_slot_indication(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 their
|
|
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
|
|
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 phy_nr_slot_indication] --> 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];
|
|
|
|
|
|
```
|
|
|
|
## 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:
|
|
|
|
- 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_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 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
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|