Compare commits

...

40 Commits

Author SHA1 Message Date
Maxime Elkael
4bd0e3234d Merge branch 'hol-delay' into 'develop'
Add per-LCID head-of-line delay to DL scheduling candidates

See merge request oai/openairinterface5g!4152
2026-07-02 06:59:24 +00:00
Robert Schmidt
c4125d1c74 Fix license in files from PR #211
Signed-off-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-29 16:27:21 +02:00
Robert Schmidt
09c9bb998f Merge remote-tracking branch 'origin/integration_2026_w26' into develop
Integration: 2026.w26

- #221 Fix for issue 216
- #224 Bugfix PUCCH Format 2
- #210 Cleanup NR RU options
- #223 fix (f1ap): DRB-Information criticality and non-dynamic fiveQI handling in DU
- #218 fix (nas): parse UE Registration Reject and add NAS connection release
- remove OAI logo (image) due to possible trademark problems
- #194 NGAP Procedures for Xn Handover: Path Switch Request and Path Switch Request Acknowledge
- #235 fix(vrtsim): Reduce OS scheduler pressure caused by vrtsim timing thread
- #211 Add NR O-RU
- #241 fix: Make test_vrtsim stable

Closes: #216
Closes: #212
Closes: #174
2026-06-29 15:41:14 +02:00
Robert Schmidt
6d4573905a Merge remote-tracking branch 'bpodrygajlo/test_vrtsim_stability' into integration_2026_w26
fix: Make test_vrtsim stable (#241)

Ensure all resources used by test_vrtsim are unique so that there is no
overlap, which might cause an issue in CI.

Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-29 11:51:15 +02:00
Robert Schmidt
dc01a3540a Merge remote-tracking branch 'bpodrygajlo/nr-oru' into integration_2026_w26
Add NR O-RU (#211)

This is a PR that will add a functional vrtsim-based nr-oru to the build
system. This is only slightly different SW to the one being presented at
the OAI summer 2026 workshop.

Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
Reviewed-By: Merkebu Girmay <merkebu.girmay@openairinterface.org>
2026-06-29 11:50:01 +02:00
Robert Schmidt
c3f6af1008 Merge remote-tracking branch 'bpodrygajlo/vrtsim-perf-fix' into integration_2026_w26
fix(vrtsim): Reduce OS scheduler pressure caused by vrtsim timing thread (#235)

Increase the usleep to 20 uS to reduce the pressure on the OS scheduler.
This reduces the number of context switches and increases performance.
The value 20uS is approximately half a symbol in mu1 and shouldn't cause
too much extra delay.

Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-29 09:20:07 +02:00
Robert Schmidt
633b6de92a Merge remote-tracking branch 'venkatareddy453/ngap-pathswitch-and-ack-for-xnho' into integration_2026_w26
NGAP Procedures for Xn Handover: Path Switch Request Path Switch Request Acknowledge (#194)

This PR adds the following NGAP Path Switch procedures to support Xn
handover in accordance with 3GPP TS 38.413v16.2.0.

NGAP Path Switch Request: add encode and ITTI support

- Add a case in TASK_NGAP to support ITTI message from RRC to NGAP
- Add Path Switch Request message type definitions
- Implement encoder for Path Switch Request (3GPP TS 38.413v16.2.0
  §9.2.3.8)
  * RAN UE NGAP ID (M)
  * Source AMF UE NGAP ID (M)
  * User Location Information (M)
  * UE Security Capabilities (M)
  * PDU Session Resource to be Switched in Downlink List (M)
- Add required ASN.1 header includes for Path Switch Request IEs

NGAP Path Switch Request Acknowledge: add decode and handler

- Add handler at NGAP for Path Switch Request Acknowledge from AMF
- Add Path Switch Request Acknowledge message type definitions
- Implement decoder for Path Switch Request Acknowledge (3GPP TS
  38.413v16.2.0 §9.2.3.9)
  * AMF UE NGAP ID (M)
  * RAN UE NGAP ID (M)
  * Security Context (M)
  * PDU Session Resource Switched List (M)
  * Allowed NSSAI (M)
- Add required ASN.1 header includes for Path Switch Request Acknowledge
  IEs

Notes:

- Added a case NGAP_PATH_SWITCH_REQ in task_ngap to support ITTI message
  from RRC, and this will be allocated at
- RRC when the target gNB receives the RRC complete message from the UE
  during Xn handover.
- OAI CN5G does not support any of the NGAP path switch procedures
  (request, ack, and failure) as of today to test Xn Handover.
- We tested the procedures in an E2E setup at IISc with two gNBs in
  E1+F1+7.2x split containing the Xn handover code and connected to the
  same 5G core (used Open5GS & Aether-OnRamp separately), and also did
  not observe any Path Switch Failure.
- The implementation of Path Switch Failure at OAI RAN will be done
  along with the implementation of Path Switch procedures in OAI CN5G
  once the XNAP integration in OAI RAN is complete.

Acknowledgement:

> This work has been partially carried out as part of Xn Handover
development at the Indian Institute of Science (IISc), Bengaluru.

Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
Reviewed-By: Guido Casati <guido.casati@openairinterface.org>
2026-06-29 09:17:07 +02:00
Bartosz Podrygajlo
ad5e941bc9 fix: Make test_vrtsim stable
Ensure all resources used by test_vrtsim are unique so that there is no overlap,
which might cause an issue in CI.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-27 06:45:39 +02:00
Bartosz Podrygajlo
75d7fa275d fix(vrtsim): Reduce OS scheduler pressure caused by vrtsim timing thread
Increase the usleep to 20 uS to reduce the pressure on the OS scheduler. This
reduces the number of context switches and increases performance. The value 20uS
is approximately half a symbol in mu1 and shouldn't cause too much extra delay.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-26 14:17:18 +02:00
Venkatareddy Akumalla
829d6fe7cf NGAP Path Switch Request Acknowledge: add decode and handler
- Add handler at NGAP for Path Switch Request Acknowledge from AMF
- Add Path Switch Request Acknowledge message type definitions
- Implement decoder for Path Switch Request Acknowledge
  (Reference: 3GPP TS 38.413 §9.2.3.9)
    - AMF UE NGAP ID (M)
    - RAN UE NGAP ID (M)
    - Security Context (M)
    - PDU Session Resource Switched List (M)
    - Allowed NSSAI (M)
- Add required ASN.1 header includes for Path Switch Request Acknowledge IEs

Co-authored-by: Rakesh BB <rakesh.bb@fsid-iisc.in>
Signed-off-by: Venkatareddy Akumalla <venkatareddy.akumalla@openairinterface.org>
2026-06-26 10:09:29 +00:00
Venkatareddy Akumalla
db7dbda8e2 NGAP Path Switch Request: add encode and ITTI support
- Add a case in TASK_NGAP to support ITTI message from RRC to NGAP
- Add Path Switch Request message type definitions
- Implement encoder for Path Switch Request (3GPP TS 38.413v16.2.0 §9.2.3.8)
    - RAN UE NGAP ID (M)
    - Source AMF UE NGAP ID (M)
    - User Location Information (M)
    - UE Security Capabilities (M)
    - PDU Session Resource to be Switched in Downlink List (M)
- Add required ASN.1 header includes for Path Switch Request IEs

Co-authored-by: Rakesh BB <rakesh.bb@fsid-iisc.in>
Signed-off-by: Venkatareddy Akumalla <venkatareddy.akumalla@openairinterface.org>
2026-06-26 10:09:12 +00:00
Robert Schmidt
f448088c8d docs: remove OAI logo due to trademark issues
Duranta is a Linux Foundation networking project and does not have
permission to display the trademarked OAI logo
(https://openairinterface.org/logo/), which belongs to the
OpenAirInterface Software Alliance (legal entity "OPENAIRINTERFACE").

Remove logo to avoid any legal problems.

Remove some superfluous text with HTML code in some documentation.

Signed-off-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-26 11:56:30 +02:00
Robert Schmidt
a3268a9c2f Merge remote-tracking branch 'GuidoCasati/issue-174' into integration_2026_w26
fix (nas): parse UE Registration Reject and add NAS connection release (#218)

The nrUE NAS task read the Registration Reject cause at a hardcoded
offset and called exit(1). This commits adds processing of
plain/security-protected 5GMM headers, decoding of the mandatory cause
per TS 24.501 §8.2.7, set 5GMM state.

Softmodem is stopped via termination_procedure and local AS detach
(send_nas_detach_req without wait_release -> 1) RRC IDLE 2)
NR_NAS_CONN_RELEASE_IND 3) itti_wait_tasks_unblock).

Changes:

- Add fgmm_registration_reject enc/dec
- Add handle_registration_reject()
- Replace inline FGS_REGISTRATION_REJECT case
- Fix unprotected_allowed() for plain Registration Reject
- Add nas_lib_test round-trip (Illegal_UE / 0x03)

Note: optional IEs (T3346, T3502, EAP) not decoded, §5.5.1.2.5 procedure TODO

Closes: #174
Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-26 11:56:06 +02:00
Robert Schmidt
716c29fe1f Merge remote-tracking branch 'GuidoCasati/issue-212' into integration_2026_w26
fix (f1ap): DRB-Information criticality and non-dynamic fiveQI handling in DU (#223)

Aligns OAI F1AP encoding with TS 38.473 for DRB-Information criticality
on UE Context Modification, and clarifies how non-dynamic fiveQI is
handled across F1AP/E1AP decode and CU RRC.

Non-dynamic fiveQI may name standardized or pre-configured profiles on
the wire (0..255). F1/E1 libs now accept the full ASN range and reject
only out-of-range values. Pre-configured and other non-standardized
non-dynamic 5QI remain unsupported at RRC via the existing
is_5qi_standardized() skip.

DU is now gracefully handling unsupported 5QIs instead of crashing.

Closes: #212
Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
2026-06-26 11:55:34 +02:00
Guido Casati
3cbf777835 fix (nas): parse UE Registration Reject and stop UE cleanly
The nrUE NAS task mishandled FGS_REGISTRATION_REJECT: it read the 5GMM
cause at a hardcoded offset and called exit(1) from the NAS ITTI task,
which could abort the process while PHY threads were still running.

This commit adds processing of plain/security-protected 5GMM headers,
decoding of the mandatory cause per TS 24.501 §8.2.7, set 5GMM state.

Softmodem is stopped via termination_procedure and local AS detach
(send_nas_detach_req without wait_release -> 1) RRC IDLE 2)
NR_NAS_CONN_RELEASE_IND 3) itti_wait_tasks_unblock).

Ignore duplicate rejects once termination has started.

Changes:
- Add fgmm_registration_reject enc/dec
- Add handle_registration_reject()
- Replace inline FGS_REGISTRATION_REJECT case
- Fix unprotected_allowed() for plain Registration Reject
- Add nas_lib_test round-trip (Illegal_UE / 0x03)

Note: optional IEs (T3346, T3502, EAP) not decoded, stop-on-reject
replaces 21a7f99 exit(1), full §5.5.1.2.5 procedure remains TODO.

Closes #174

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
2026-06-25 16:59:29 +02:00
Robert Schmidt
813a90bd21 Merge remote-tracking branch 'origin/cleanup_ru_options' into integration_2026_w26
Cleanup NR RU options (#210)

This PR is part of the effort to split LTE and NR code to avoid
cross-compilation as described in #110.

In nr-ru.c, up to now, we relied on if4_tools.c for some if4p5 functions
but what's in that file is strictly LTE using LTE frame parameters, so
it shouldn't work in NR. As a matter of fact, after some investigation
and feedback from @teodora-vladic about 7.2 split, none of those
functions are used in practice in NR and they can be removed. If this
works, we move one step closer to the full compilation split of LTE and
NR.

Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
Reviewed-by: Teodora Vladić <teodora.vladic@openairinterface.org>
2026-06-25 13:20:02 +02:00
Robert Schmidt
0548191fac Merge remote-tracking branch 'sakthivelvelumani/bugfix-pucchf2' into integration_2026_w26
Bugfix PUCCH Format 2 (#224)

Fix RE offset computation in PUCCH format 2 generation. This fixes DL
retx with OCUDU in the CI.

Reviewed-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-25 13:18:22 +02:00
Robert Schmidt
145ab730c0 Merge remote-tracking branch 'alexjiao2021/fix-issue-216' into integration_2026_w26
Fix for issue 216 (#221)

When O_ACK <= 2, the reserved HARQ-ACK positions are selected from the
codeword using a d-factor stepping algorithm. The d-factor was computed
and applied at bit granularity, causing it to step through the flat
positions array mid-modulation-group when Qm > 1.

This scattered ACK/placeholder bits across more REs than intended,
mismatching the RE selection of the demultiplexer which operates at RE
granularity. As a result, placeholder positions were marked on REs that
the gNB treats as pure ULSCH, causing those positions to be transmitted
without the normal scrambling sequence XOR. This corrupts the ULSCH soft
bits at the gNB side, leading to PUSCH CRC failure.

Fix: compute the d-factor in RE units and step through the positions
array in strides of d_factor_re * Qm so that whole Qm-bit modulation
groups are always selected or skipped together.

Observed with O_ACK=1, MCS27 (256QAM), beta_offset_idx=11 where
Q_dash_ACK=6 (reserved) vs Q_dash_ACK_actual=3, giving d_factor=2. PUSCH
CRC was KO at third-party signal analyzers (MATLAB/WaveJudge) while the
OCUDU gNB could still decode due to soft LDPC margin.

Closes: #216
Reviewed-by: Francesco Mani <email@francescomani.it>
Reviewed-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-06-25 13:18:22 +02:00
Guido Casati
5f2108edb0 fix (mac): gracefully handle unsupported 5QIs on DRB setup
When a CU sends a DRB with a non-dynamic 5QI that is not in the
DU MAC lookup table, get_non_dynamic_priority() used to AssertFatal and
abort. Return -1 and log a warning instead, and skip flows with negative
priority when deriving the logical-channel priority so setup can continue.

A more spec-aligned fix would require F1AP UE Context Setup/Modification
Failure (not implemented) in case of a logical error in the DU:

> Where the logical error occurs in a request message of a class 1 procedure,
> and the procedure has a message to report this unsuccessful outcome,
> this message shall be sent with an appropriate cause value.

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
2026-06-25 12:53:24 +02:00
Bartosz Podrygajlo
b7ab31854e feat(oru): PUSCH reception
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-24 13:48:23 +02:00
Sakthivel Velumani
f7d0ec0960 ue,phy,pucchf2: fix re offset calculation
Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-06-23 13:55:18 +00:00
Sakthivel Velumani
7032477784 log: fix typo in printing mac stats
Signed-off-by: Sakthivel Velumani <s.velumani@northeastern.edu>
2026-06-23 13:54:46 +00:00
Guido Casati
7e0c0fe18b fix (f1ap,e1ap,rrc): accept ASN non-dynamic fiveQI at lib decode
Non-dynamic fiveQI on F1AP/E1AP may name standardized or pre-configured
profiles (TS 38.473), the range is 0..255. Decode now accepts
MIN_FIVEQI..MAX_FIVEQI (0..255) and fails only when fiveQI is outside
that range. Pre-configured and other non-standardized non-dynamic values
remain unsupported: RRC still skips them via is_5qi_standardized().

Changes:
- F1AP/E1AP: range-check 0..255 only
- improve documentation

Refs:
- 3GPP TS 38.473 §9.3.1.49 (non-dynamic standardized or pre-configured 5QI)
- 3GPP TS 23.501 §5.7.2.1

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
2026-06-23 12:55:42 +02:00
Guido Casati
5a478deac4 fix (f1ap): encode DRB-Information with ignore criticality in setup-mod
UE Context Modification DRB setup-mod encoding sent id_DRB_Information
with reject criticality. TS 38.473 defines that IE as ignore
(QoSInformation-ExtIEs, id-DRB-Information).

Changes:
- Set QoSInformation extension id_DRB_Information criticality to ignore
  in encode_drbs_to_setupmod()

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
2026-06-23 11:08:41 +02:00
Alex Jiao
40024d4caa fix(NR_UE): UCI on PUSCH ACK mapping d-factor uses RE granularity
When O_ACK <= 2, the reserved HARQ-ACK positions are selected from
the codeword using a d-factor stepping algorithm. The d-factor was
computed and applied at bit granularity, causing it to step through
the flat positions array mid-modulation-group when Qm > 1.

This scattered ACK/placeholder bits across more REs than intended,
mismatching the RE selection of the demultiplexer which operates at
RE granularity. As a result, placeholder positions were marked on
REs that the gNB treats as pure ULSCH, causing those positions to be
transmitted without the normal scrambling sequence XOR. This corrupts
the ULSCH soft bits at the gNB side, leading to PUSCH CRC failure.

Fix: compute the d-factor in RE units and step through the positions
array in strides of d_factor_re * Qm so that whole Qm-bit modulation
groups are always selected or skipped together.

Observed with O_ACK=1, MCS27 (256QAM), beta_offset_idx=11 where
Q_dash_ACK=6 (reserved) vs Q_dash_ACK_actual=3, giving d_factor=2.
PUSCH CRC was KO at third-party signal analyzers (MATLAB/WaveJudge)
while the OCUDU gNB could still decode due to soft LDPC margin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: Alex Jiao <alex.jiao@keysight.com>
2026-06-22 22:41:08 +08:00
Bartosz Podrygajlo
3fb74dc66e feat(oru): PRACH reception
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:23:49 +02:00
Bartosz Podrygajlo
82c3c04018 feat(phy): Allow per-repetition PRACH reception
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:22:56 +02:00
Bartosz Podrygajlo
df5a9facbe fix(oru): Fix timestamp generation in O-RU
Instead of guessing what is the absolute timestamp of each symbol returned
from fronthaul, add absolute hyper frame from GPS Epoch to the O-RU. The
O-RU can calculate the absolute sample index from hyper frame reliably
regardless of the frame/slot/symbol ordering.

Add config file for 1x1 O-DU that matches the edited O-RU config.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:22:30 +02:00
Bartosz Podrygajlo
71c692d727 fix(oru): Clean exit of the O-RU
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:21:02 +02:00
Bartosz Podrygajlo
159c219bdd feat(oru): DL FH processing
Implemented downlink fronthaul processing: symbol rotation, dft and cp insertion

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:20:51 +02:00
Bartosz Podrygajlo
ecfe769205 feat(phy): add a new tx_rf_symbols function to allow per symbol transmission
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:20:41 +02:00
Bartosz Podrygajlo
21329e1b70 feat(oru): Load vrtsim in O-RU and start reading samples
Added oru_south_read_thread that reads samples from vrtsim.
No UL FH processing is done as of this commit.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:20:25 +02:00
Bartosz Podrygajlo
0aa3f89c52 feat(radio): add a function which loads the specified radio library
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:20:15 +02:00
Bartosz Podrygajlo
cd79eec3ec feat(vrtsim): add get_timestamp to openair_device_t and vrtsim
This function can be used to convert a timespec struct to a
openair0_timestamp. It can be used to synchronize the realtime
clock to the device sample number.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:20:06 +02:00
Bartosz Podrygajlo
a1049e8dd1 feat(oru): Add north read thread
Start O-RU fronthaul library inside the O-RU application and start
reading DL IQ data.

Added configuration elements to the ORUs section to support configuring
the library.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:19:51 +02:00
Bartosz Podrygajlo
28786af8bf feat(oru): Setup frame params according to new configuration
Added initialization of frame params according to configuration added
in the new ORUs section. Only one ORU is supported right now.

Added example config that allows starting the ORU and verifying manually
that the config is read.

Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:16:24 +02:00
Bartosz Podrygajlo
eb1ae91051 ci: Build O-RU fronthaul in ubuntu fhi72 build image
Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
2026-06-20 08:16:13 +02:00
Francesco Mani
0f50702483 remove dependence from LTE if4p5 functions
fh_if4p5_south_in and fh_if4p5_south_out are called in NR for 7.2 split but never actually executed
they are overwritten at runtime in ru_thread

Signed-off-by: Francesco Mani <email@francescomani.it>
2026-06-19 11:35:50 +02:00
Francesco Mani
7a0489842b cleanup nr-ru set_functions to leave only used options in NR
Signed-off-by: Francesco Mani <email@francescomani.it>
2026-06-19 09:46:38 +02:00
Maxime
a8de5c4c7a Add per-LCID head-of-line delay to DL scheduling candidates
Signed-off-by: Maxime Elkael <m.elkael@northeastern.edu>
2026-05-28 15:10:32 -04:00
71 changed files with 2585 additions and 1044 deletions

View File

@@ -855,7 +855,6 @@ set(NR_PHY_SRC_RU
${OPENAIR1_DIR}/PHY/MODULATION/nr_beamforming.c
${OPENAIR1_DIR}/PHY/MODULATION/slot_fep_nr.c
${OPENAIR1_DIR}/PHY/INIT/nr_init_ru.c
${OPENAIR1_DIR}/PHY/if4_tools.c
)
set(PHY_SRC_UE
@@ -1769,23 +1768,25 @@ target_link_libraries(lte-uesoftmodem PRIVATE asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs
# force the generation of ASN.1 so that we don't need to wait during the build
target_link_libraries(lte-uesoftmodem PRIVATE
asn1_lte_rrc asn1_s1ap asn1_m2ap asn1_m3ap asn1_x2ap)
# nr RRU
add_executable(nr-oru
${OPENAIR_DIR}/executables/nr-ru.c
${OPENAIR_DIR}/openair1/PHY/INIT/nr_parms.c
${OPENAIR_DIR}/openair1/SCHED_NR/phy_frame_config_nr.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_prach_procedures.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_ru_procedures.c
${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c
${OPENAIR_DIR}/executables/main_nr_ru.c
)
target_link_libraries(nr-oru PRIVATE
UTIL NR_PHY_RU PHY_NR shlib_loader dl
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)
if (OAI_RU_FRONTHAUL)
add_executable(nr-oru
${OPENAIR_DIR}/executables/nr-ru.c
${OPENAIR_DIR}/openair1/PHY/INIT/nr_parms.c
${OPENAIR_DIR}/openair1/SCHED_NR/phy_frame_config_nr.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_prach_procedures.c
${OPENAIR_DIR}/openair1/SCHED_NR/nr_ru_procedures.c
${OPENAIR_DIR}/openair1/SCHED/phy_procedures_lte_common.c
${OPENAIR_DIR}/executables/main_nr_ru.c
${OPENAIR_DIR}/executables/nr-oru.c
)
target_link_libraries(nr-oru PRIVATE
UTIL NR_PHY_RU PHY_NR shlib_loader dl oru_fh MAC_NR_COMMON
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)
endif()
# nr-softmodem
###################################################

View File

@@ -1,7 +1,7 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<h1 align="center">
<a href="https://lfnetworking.org/projects/duranta/"><img src="https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-OAI-Combined.png" alt="Duranta OAI" width="550"></a>
<a href="https://lfnetworking.org/projects/duranta/"><img src="https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png" alt="Duranta OAI" width="550"></a>
</h1>
<p align="center">

View File

@@ -14,7 +14,7 @@ description: A Helm chart for physical simulators network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.

View File

@@ -14,7 +14,7 @@ description: A Helm subchart for 4G physims network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.

View File

@@ -14,7 +14,7 @@ description: A Helm chart for physical simulators network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.

View File

@@ -14,7 +14,7 @@ description: A Helm subchart for 5G physims network function
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
icon: http://www.openairinterface.org/wp-content/uploads/2015/06/cropped-oai_final_logo.png
icon: https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.

View File

@@ -75,12 +75,12 @@ class HTMLManagement():
self.htmlFile.write(' <tr style="border-collapse: collapse; border: none;">\n')
self.htmlFile.write(' <td style="border-collapse: collapse; border: none;">\n')
self.htmlFile.write(' <a href="http://www.openairinterface.org/">\n')
self.htmlFile.write(' <img src="http://www.openairinterface.org/wp-content/uploads/2016/03/cropped-oai_final_logo2.png" alt="" border="none" height=50 width=150>\n')
self.htmlFile.write(' <img src="https://raw.githubusercontent.com/duranta-project/governance/main/logos/Duranta-Logo-Color.png" alt="" border="none" style="margin-right: 2rem;" width=150>\n')
self.htmlFile.write(' </img>\n')
self.htmlFile.write(' </a>\n')
self.htmlFile.write(' </td>\n')
self.htmlFile.write(' <td style="border-collapse: collapse; border: none; vertical-align: center;">\n')
self.htmlFile.write(' <b><font size = "6">Job Summary -- Job: TEMPLATE_JOB_NAME -- Build-ID: TEMPLATE_BUILD_ID</font></b>\n')
self.htmlFile.write(' <b><font size = "6">TEMPLATE_JOB_NAME -- Build-ID: TEMPLATE_BUILD_ID</font></b>\n')
self.htmlFile.write(' </td>\n')
self.htmlFile.write(' </tr>\n')
self.htmlFile.write(' </table>\n')

View File

@@ -1,20 +1,6 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../../../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI Full Stack 4G-LTE RF simulation with containers</font></b>
</td>
</tr>
</table>
This page is only valid for an `Ubuntu 22` host.
OAI Full Stack 4G-LTE RF simulation with containers
**Table of Contents**

View File

@@ -1,22 +1,6 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../../../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI Full Stack 5G-NR RF simulation with containers</font></b>
</td>
</tr>
</table>
This page is only valid for an `Ubuntu 22` host.
**NOTE: this version (2023-01-27) has been updated for the `v1.5.0` version of the `OAI 5G CN`.**
OAI Full Stack 5G-NR RF simulation with containers
**Table of Contents**

View File

@@ -1,18 +1,6 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../../../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI O-RAN 7.2 Front-haul Docker Compose</font></b>
</td>
</tr>
</table>
OAI O-RAN 7.2 Front-haul Docker Compose
![Docker deploy 7.2](../../../doc/images/docker-deploy-oai-7-2.png)

View File

@@ -201,6 +201,7 @@ function main() {
shift;;
--nrRU)
NRRU=1
CMAKE_CMD="$CMAKE_CMD -DOAI_RU_FRONTHAUL=ON"
TARGET_LIST="$TARGET_LIST nr-oru"
echo_info "Will compile NR O-RU"
shift;;

View File

@@ -39,8 +39,9 @@ typedef enum { NON_DYNAMIC, DYNAMIC } fiveQI_t;
/* 5QI (5G QoS Identifier) - 3GPP TS 23.501 §5.7.2.1
* Range: 0..255
* - Standardized 5QI values: have one-to-one mapping to standardized 5G QoS characteristics (Table 5.7.4-1)
* - Pre-configured 5QI values: pre-configured in the AN
* - Dynamically assigned 5QI values: require signaling of QoS characteristics as part of QoS profile */
* - Pre-configured 5QI values: pre-configured in the AN (not in Table 5.7.4-1)
* - Dynamically assigned 5QI values: require signaling of QoS characteristics as part of QoS profile
* OAI implements standardized non-dynamic 5QI only. */
#define MIN_FIVEQI 0
#define MAX_STANDARDIZED_FIVEQI 90
#define MAX_FIVEQI 255

View File

@@ -45,6 +45,8 @@
#define NR_MAX_NB_PDU_SESSIONS (256)
#define NR_MAX_NB_ALLOWED_SNSSAI (8) /* Maximum number of allowed S-NSSAI in TS 38.413 */
#define MAX_DRBS_PER_UE (32) /* Maximum number of Data Radio Bearers per UE
* defined for NGAP in TS 38.413 - maxnoofDRBs */
#define MAX_PDUS_PER_UE (8) /* Maximum number of PDU Sessions per UE */

View File

@@ -108,6 +108,7 @@ typedef struct {
sdu_size_t head_sdu_remaining_size_to_send; /*!< \brief remaining size of sdu: could be the total size or the remaining size of
already segmented sdu */
bool head_sdu_is_segmented; /*!< \brief 0 if head SDU has not been segmented, 1 if already segmented */
uint64_t oldest_sdu_arrival_ms; /*!< \brief Timestamp (ms, RLC time) of oldest SDU in tx/retx queue, 0 if empty */
} mac_rlc_status_resp_t;
#define SDU_CONFIRM_NO false

View File

@@ -51,7 +51,7 @@ PROJECT_BRIEF = "Full experimental OpenSource LTE and NR implementation
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.
PROJECT_LOGO = @CMAKE_SOURCE_DIR@/doc/images/oai_logo.png
PROJECT_LOGO =
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is

View File

@@ -61,7 +61,7 @@ Correct (Markdown):
**Bold text**
*Italic text*
![OAI Logo](./images/oai_final_logo.png) instead of <img src="./images/oai_final_logo.png" alt="OAI Logo">
![some image](./path/to/image.png)
| Feature | Description |
|---------------|---------------------------------|
@@ -75,7 +75,7 @@ Incorrect (HTML):
<b>Bold text</b>
<i>Italic text</i>
<img src="./images/oai_final_logo.png" alt="OAI Logo">
<img src="./path/to/image.png" alt="some image">
<table style="border-collapse: collapse; border: none;">
<tr>
@@ -114,7 +114,7 @@ Incorrect (HTML):
is](https://www.kernel.org/doc/html/latest/process/submitting-patches.html#separate-your-changes).
- See [OAI CN5G configuration files](#22-oai-cn5g-configuration-files) for
details.
- ![OAI Logo](./images/oai_final_logo.png)
- ![some image](./path/to/image.png)
```
## Inline code for technical elements

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,20 +1,6 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OAI Docker/Podman Build and Usage Procedures</font></b>
</td>
</tr>
</table>
---
OAI Docker/Podman Build and Usage Procedures
**Table of Contents**

View File

@@ -22,6 +22,9 @@
#include <executables/softmodem-common.h>
#include <executables/thread-common.h>
#include "executables/nr-softmodem.h"
#include "nr-oru.h"
#include "openair1/PHY/INIT/nr_phy_init.h"
#include "openair1/SCHED_NR/sched_nr.h"
pthread_cond_t sync_cond;
pthread_mutex_t sync_mutex;
@@ -33,6 +36,14 @@ int sf_ahead = 4;
int emulate_rf = 0;
RAN_CONTEXT_t RC;
extern void kill_NR_RU_proc(int inst);
extern void set_function_spec_param(RU_t *ru);
extern void start_NR_RU();
extern void init_NR_RU(configmodule_interface_t *cfg, char *);
void fill_rf_config(RU_t *ru, char *rf_config_file);
void fill_split7_2_config(split7_config_t *split7, const nfapi_nr_config_request_scf_t *config, const NR_DL_FRAME_PARMS *fp);
int64_t uplink_frequency_offset[MAX_NUM_CCs][4];
void nfapi_setmode(nfapi_mode_t nfapi_mode)
@@ -106,26 +117,15 @@ struct timespec timespec_sub(struct timespec, struct timespec)
struct timespec t = {0};
return t;
};
void perform_symbol_rotation(const int nsymb, const int numerology_index, double f0, c16_t *symbol_rotation)
void beam_index_allocation(uint16_t fapi_beam_index,
int ant,
int num_ports,
int symbols_per_slot,
int slot,
uint16_t bitmap_symbols,
int num_ant_max,
uint16_t **ant_beam_id_list)
{
return;
}
void init_timeshift_rotation(const int ofdm_symbol_size,
const int nb_prefix_samples,
const uint ofdm_offset_divisor,
c16_t *timeshift_symbol_rotation)
{
return;
};
int beam_index_allocation(bool das,
int fapi_beam_index,
NR_gNB_COMMON *common_vars,
int slot,
int symbols_per_slot,
int bitmap_symbols)
{
return 0;
}
uint16_t get_first_ant_idx(bool das, uint16_t num_ports_beams, uint16_t beam_id, uint16_t fapi_start_port)
{
@@ -135,6 +135,12 @@ void nr_fill_du(uint16_t N_ZC, const uint16_t *prach_root_sequence_map, uint16_t
{
return;
};
static void sig_handler(int sig_num)
{
oai_exit = 1;
}
uint16_t nr_du[838];
uint64_t downlink_frequency[MAX_NUM_CCs][4];
@@ -171,6 +177,7 @@ int main(int argc, char **argv)
printf("About to Init RU threads\n");
lock_memory_to_ram();
load_dftslib();
RC.nb_RU = 1;
RC.ru = malloc(sizeof(RC.ru));
@@ -178,24 +185,77 @@ int main(int argc, char **argv)
init_NR_RU(config_get_if(), NULL);
RU_t *ru = RC.ru[0];
ORU_t oru = {0};
oru.ru = ru;
int ret = get_oru_options(&oru);
AssertFatal(ret == 0, "Cannot configure oru, check your config file/cmdline");
ru->numerology = oru.numerology;
oru_init_frame_parms(&oru);
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
nr_dump_frame_parms(fp);
init_symbol_rotation(fp);
init_timeshift_rotation(fp->ofdm_symbol_size, fp->nb_prefix_samples, fp->ofdm_offset_divisor, fp->timeshift_symbol_rotation);
ru->if_south = LOCAL_RF;
nr_phy_init_RU(oru.ru);
fill_rf_config(ru, ru->rf_config_file);
ru->N_TA_offset = set_default_nta_offset(fp->freq_range, fp->samples_per_subframe);
while (oai_exit == 0)
/* set PRACH configuration */
nfapi_nr_prach_config_t *prach_config = &ru->config.prach_config;
prach_config->prach_ConfigurationIndex.value = oru.prach_config_index;
prach_config->num_prach_fd_occasions_list[0].k1.value = oru.prach_msg1_freq;
prach_config->prach_sequence_length.value = 1;
prach_config->prach_sub_c_spacing.value = 1;
prach_config->num_prach_fd_occasions.value = 1;
reset_meas(&oru.rx_prach);
oru.prach_info = get_nr_prach_occasion_info_from_index(oru.prach_config_index, FR1, fp->frame_type);
LOG_A(PHY, "PRACH configuration index %d\n", oru.prach_config_index);
LOG_A(PHY,
"PRACH format %d start_symbol %d duration %d\n",
oru.prach_info.format,
oru.prach_info.start_symbol,
oru.prach_info.N_dur);
prepare_prach_item(&oru);
oru.fronthaul = oru_fh_init(&oru.fh_config);
AssertFatal(oru.fronthaul != NULL, "Cannot configure oru fronthaul, check your config file/cmdline");
LOG_I(PHY, "starting vrtsim\n");
ret = openair0_load(&ru->rfdevice, "vrtsim", &ru->openair0_cfg, NULL);
AssertFatal(ret == 0, "RU %u: openair0_load() ret %d: cannot initialize vrtsim\n", ru->idx, ret);
ret = ru->rfdevice.trx_start_func(&ru->rfdevice);
AssertFatal(ret == 0, "RU %u: trx_start_func() ret %d: cannot start vrtsim\n", ru->idx, ret);
threadCreate(&oru.north_read_thread, oru_north_read_thread, (void *)&oru, "north_read_thread", -1, OAI_PRIORITY_RT_MAX);
threadCreate(&oru.south_read_thread, oru_south_read_thread, (void *)&oru, "south_read_thread", -1, OAI_PRIORITY_RT_MAX);
usleep(1000);
oru_fh_start(oru.fronthaul);
// Signal handler
signal(SIGINT, sig_handler);
while (oai_exit == 0) {
oru_fh_print_stats(oru.fronthaul);
sleep(1);
// stop threads
}
kill_NR_RU_proc(0);
pthread_join(oru.north_read_thread, NULL);
pthread_join(oru.south_read_thread, NULL);
end_configmodule(uniqCfg);
oru_fh_stop(oru.fronthaul);
if (ru->rfdevice.trx_stop_func) {
ru->rfdevice.trx_stop_func(&ru->rfdevice);
ru->rfdevice.trx_stop_func = NULL;
}
if (ru->rfdevice.trx_end_func) {
ru->rfdevice.trx_end_func(&ru->rfdevice);
ru->rfdevice.trx_end_func = NULL;
}
if (ru->ifdevice.trx_end_func) {
ru->ifdevice.trx_end_func(&ru->ifdevice);
ru->ifdevice.trx_end_func = NULL;
}
end_configmodule(uniqCfg);
logClean();
printf("Bye.\n");

766
executables/nr-oru.c Normal file
View File

@@ -0,0 +1,766 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "common/config/config_userapi.h"
#include "common/utils/system.h"
#include "nr-oru.h"
#include "openair1/PHY/defs_nr_common.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "oru_packet_processor.h"
#include <time.h>
#include "openair1/PHY/MODULATION/nr_modulation.h"
#include "openair1/SCHED_NR/sched_nr.h"
#include "openair1/PHY/MODULATION/modulation_common.h"
#include "openair2/LAYER2/NR_MAC_COMMON/nr_mac_common.h"
#define CONFIG_SECTION_ORU "ORUs.[0]"
#define CONFIG_STRING_ORU_TX_BW_LIST "tx_bw"
#define CONFIG_STRING_ORU_RX_BW_LIST "rx_bw"
#define CONFIG_STRING_ORU_CARRIER_TX_LIST "carrier_tx"
#define CONFIG_STRING_ORU_CARRIER_RX_LIST "carrier_rx"
#define CONFIG_STRING_ORU_FRAME_TYPE "frame_type"
#define CONFIG_STRING_ORU_PRACH_CONFIGID "prach_config_index"
#define CONFIG_STRING_ORU_PRACH_MSG1FREQ "prach_msg1_start"
#define CONFIG_STRING_ORU_NUMEROLOGY "mu"
#define CONFIG_STRING_ORU_TDD_PERIOD "tdd_period"
#define CONFIG_STRING_ORU_NUM_DL_SLOTS "num_dl_slots"
#define CONFIG_STRING_ORU_NUM_UL_SLOTS "num_ul_slots"
#define CONFIG_STRING_ORU_NUM_DL_SYMBOLS "num_dl_symbols"
#define CONFIG_STRING_ORU_NUM_UL_SYMBOLS "num_ul_symbols"
#define HLP_ORU_TX_BW "set the TX bandwidth list per component carrier"
#define HLP_ORU_RX_BW "set the RX bandwidth list per component carrier"
#define HLP_ORU_CARRIER_TX "set the TX carrier frequencies per component carrier"
#define HLP_ORU_CARRIER_RX "set the RX carrier frequencies per component carrier"
#define HLP_ORU_FRAMETYPE "set the Frame type TDD/FDD of all component carriers"
#define HLP_ORU_PRACH_CONFIGID "set the PRACH configuration id of all component carriers"
#define HLP_ORU_PRACH_MSG1FREQ "set the PRACH MSG1 frequency of all component carriers"
#define HLP_ORU_NUMEROLOGY "set the numerology of the RU"
#define HLP_ORU_TDD_PERIOD "set the 3GPP TDD periodificty 0-9"
#define HLP_ORU_NUM_DL_SLOTS "set the number of DL Slots in TDD"
#define HLP_ORU_NUM_UL_SLOTS "set the number of UL Slots in TDD"
#define HLP_ORU_NUM_DL_SYMBOLS "set the number of DL symbols in the mixed slot"
#define HLP_ORU_NUM_UL_SYMBOLS "set the number of UL symbols in the mixed slot"
// clang-format off
#define CMDLINE_PARAMS_DESC_ORU \
{ \
{CONFIG_STRING_ORU_TX_BW_LIST, HLP_ORU_TX_BW, 0, .iptr=NULL, .defintarrayval=DEFBW, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_RX_BW_LIST, HLP_ORU_RX_BW, 0, .iptr=NULL, .defintarrayval=DEFBW, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_CARRIER_TX_LIST, HLP_ORU_CARRIER_TX, 0, .iptr=NULL, .defintarrayval=DEFCARRIER, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_CARRIER_RX_LIST, HLP_ORU_CARRIER_RX, 0, .iptr=NULL, .defintarrayval=DEFCARRIER, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_ORU_FRAME_TYPE, HLP_ORU_FRAMETYPE, 0, .uptr=NULL, .defintval=1, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_PRACH_CONFIGID, HLP_ORU_PRACH_CONFIGID, 0, .uptr=NULL, .defintval=152, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_PRACH_MSG1FREQ, HLP_ORU_PRACH_MSG1FREQ, 0, .uptr=NULL, .defintval=0, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUMEROLOGY, HLP_ORU_NUMEROLOGY, 0, .uptr=NULL, .defintval=1, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_TDD_PERIOD, HLP_ORU_TDD_PERIOD, 0, .uptr=NULL, .defintval=5, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_DL_SLOTS, HLP_ORU_NUM_DL_SLOTS, 0, .uptr=NULL, .defintval=3, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_UL_SLOTS, HLP_ORU_NUM_UL_SLOTS, 0, .uptr=NULL, .defintval=1, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_DL_SYMBOLS, HLP_ORU_NUM_DL_SYMBOLS, 0, .uptr=NULL, .defintval=7, TYPE_UINT, 0}, \
{CONFIG_STRING_ORU_NUM_UL_SYMBOLS, HLP_ORU_NUM_UL_SYMBOLS, 0, .uptr=NULL, .defintval=3, TYPE_UINT, 0}, \
}
// clang-format on
#define CONFIG_SECTION_ORU_FH "ORUs.[0].fronthaul"
#define CONFIG_STRING_ORU_DPDK_DEVICES "dpdk_devices"
#define CONFIG_STRING_RX_CORE "rx_core"
#define CONFIG_STRING_EXTRA_EAL_ARGS "extra_eal_args"
#define CONFIG_STRING_DU_MAC_ADDRESSES "du_mac_addr"
#define CONFIG_STRING_MTU "mtu"
#define CONFIG_STRING_T2A_UP "T2a_up"
#define CONFIG_STRING_T2A_CP "T2a_cp"
#define CONFIG_STRING_PRACH_EAXC_OFFSET "prach_eaxc_offset"
#define HLP_DPDK_DEVICES "DPDK devices to use for the O-RU."
#define HLP_RX_CORE "The CPU core to be used to deploy dpdk RX worker for O-RU."
#define HLP_EXTRA_EAL_ARGS "Extra arguments passed to RTE_EAL_INIT."
#define HLP_DU_MAC_ADDRESSES "DU MAC addreses, used to prepare Ethernet headers."
#define HLP_MTU "MTU for RX and TX."
#define HLP_PRACH_EAXC_OFFSET "PRACH eAxC offset."
// clang-format off
#define CMDLINE_PARAMS_DESC_ORU_FH \
{ \
{CONFIG_STRING_ORU_DPDK_DEVICES, HLP_DPDK_DEVICES, PARAMFLAG_MANDATORY, .strptr=NULL, .defstrval=NULL, TYPE_STRINGLIST, 0}, \
{CONFIG_STRING_RX_CORE, HLP_RX_CORE, PARAMFLAG_MANDATORY, .iptr=NULL, .defintval=-1, TYPE_INT, 0}, \
{CONFIG_STRING_EXTRA_EAL_ARGS, HLP_EXTRA_EAL_ARGS, 0, .strptr=NULL, .defstrval=NULL, TYPE_STRINGLIST, 0}, \
{CONFIG_STRING_DU_MAC_ADDRESSES, HLP_DU_MAC_ADDRESSES, PARAMFLAG_MANDATORY, .strptr=NULL, .defstrval=NULL, TYPE_STRINGLIST, 0}, \
{CONFIG_STRING_MTU, HLP_MTU, 0, .iptr=NULL, .defintval=9600, TYPE_INT, 0}, \
{CONFIG_STRING_T2A_UP, "", 0, .iptr=NULL, .defintarrayval=NULL, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_T2A_CP, "", 0, .iptr=NULL, .defintarrayval=NULL, TYPE_INTARRAY, 0}, \
{CONFIG_STRING_PRACH_EAXC_OFFSET, HLP_PRACH_EAXC_OFFSET, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0} \
}
// clang-format on
extern void set_scs_parameters(NR_DL_FRAME_PARMS *fp, int mu, int N_RB_DL, int ssb_case);
void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_symbol, int num_symbols);
void prepare_prach_item(ORU_t *oru)
{
AssertFatal(oru->ru != NULL, "ORU not configured\n");
AssertFatal(oru->ru->nr_frame_parms != NULL, "ORU not configured\n");
NR_DL_FRAME_PARMS *fp = oru->ru->nr_frame_parms;
RU_t *ru = oru->ru;
prach_item_t *prach_item = &oru->prach_item;
prach_item->num_slots = oru->prach_info.format < 4 ? get_long_prach_dur(oru->prach_info.format, fp->numerology_index) : 1;
prach_item->msg1_frequencystart = oru->prach_msg1_freq;
prach_item->mu = fp->numerology_index;
nfapi_nr_config_request_scf_t *cfg = &ru->config;
prach_item->prach_sequence_length = cfg->prach_config.prach_sequence_length.value;
prach_item->restricted_set = 0;
prach_item->numerology_index = fp->numerology_index;
prach_item->nb_rx = ru->nb_rx;
prach_item->rx_prach = &oru->rx_prach;
// Fill PRACH PDU
nfapi_nr_prach_pdu_t *prach_pdu = &prach_item->pdu;
prach_pdu->prach_start_symbol = oru->prach_info.start_symbol;
prach_pdu->num_prach_ocas = 1; // TODO: Hardcoded.
uint16_t format0 = oru->prach_info.format & 0xff;
uint16_t format1 = (oru->prach_info.format >> 8) & 0xff;
if (format1 != 0xff) {
switch (format0) {
case 0xa1:
prach_pdu->prach_format = 11;
break;
case 0xa2:
prach_pdu->prach_format = 12;
break;
case 0xa3:
prach_pdu->prach_format = 13;
break;
default:
AssertFatal(1 == 0, "Only formats A1/B1 A2/B2 A3/B3 are valid for dual format");
}
} else {
switch (format0) {
case 0:
prach_pdu->prach_format = 0;
break;
case 1:
prach_pdu->prach_format = 1;
break;
case 2:
prach_pdu->prach_format = 2;
break;
case 3:
prach_pdu->prach_format = 3;
break;
case 0xa1:
prach_pdu->prach_format = 4;
break;
case 0xa2:
prach_pdu->prach_format = 5;
break;
case 0xa3:
prach_pdu->prach_format = 6;
break;
case 0xb1:
prach_pdu->prach_format = 7;
break;
case 0xb4:
prach_pdu->prach_format = 8;
break;
case 0xc0:
prach_pdu->prach_format = 9;
break;
case 0xc2:
prach_pdu->prach_format = 10;
break;
default:
AssertFatal(1 == 0, "Invalid PRACH format");
}
}
}
int get_oru_options(ORU_t *oru)
{
int DEFBW[] = {273};
int DEFCARRIER[] = {3430560};
paramdef_t param[] = CMDLINE_PARAMS_DESC_ORU;
int nump = sizeofArray(param);
int ret = config_get(config_get_if(), param, nump, CONFIG_SECTION_ORU);
if (ret <= 0) {
LOG_E(NR_PHY, "problem reading section \"%s\"\n", CONFIG_SECTION_ORU);
return -1;
}
for (int i = 0; i < oru->ru->num_bands; i++) {
oru->bw_tx[i] = gpd(param, nump, CONFIG_STRING_ORU_TX_BW_LIST)->iptr[i];
oru->bw_rx[i] = gpd(param, nump, CONFIG_STRING_ORU_RX_BW_LIST)->iptr[i];
oru->carrier_freq_tx[i] = gpd(param, nump, CONFIG_STRING_ORU_CARRIER_TX_LIST)->iptr[i];
oru->carrier_freq_rx[i] = gpd(param, nump, CONFIG_STRING_ORU_CARRIER_RX_LIST)->iptr[i];
}
oru->frame_type = *gpd(param, nump, CONFIG_STRING_ORU_FRAME_TYPE)->iptr;
oru->prach_config_index = *gpd(param, nump, CONFIG_STRING_ORU_PRACH_CONFIGID)->iptr;
oru->prach_msg1_freq = *gpd(param, nump, CONFIG_STRING_ORU_PRACH_MSG1FREQ)->iptr;
oru->numerology = *gpd(param, nump, CONFIG_STRING_ORU_NUMEROLOGY)->iptr;
oru->tdd_period = *gpd(param, nump, CONFIG_STRING_ORU_TDD_PERIOD)->iptr;
oru->num_DL_slots = *gpd(param, nump, CONFIG_STRING_ORU_NUM_DL_SLOTS)->iptr;
oru->num_UL_slots = *gpd(param, nump, CONFIG_STRING_ORU_NUM_UL_SLOTS)->iptr;
oru->num_DL_symbols = *gpd(param, nump, CONFIG_STRING_ORU_NUM_DL_SYMBOLS)->iptr;
oru->num_UL_symbols = *gpd(param, nump, CONFIG_STRING_ORU_NUM_UL_SYMBOLS)->iptr;
paramdef_t fh_param[] = CMDLINE_PARAMS_DESC_ORU_FH;
nump = sizeofArray(fh_param);
oru_fh_config_t *fh_cfg = &oru->fh_config;
ret = config_get(config_get_if(), fh_param, nump, CONFIG_SECTION_ORU_FH);
if (ret <= 0) {
printf("problem reading section \"%s\"\n", CONFIG_SECTION_ORU_FH);
return -1;
}
oru_fh_dpdk_config_t *dpdk_conf = &fh_cfg->dpdk_conf;
int num_dpdk_devices = gpd(fh_param, nump, CONFIG_STRING_ORU_DPDK_DEVICES)->numelt;
dpdk_conf->num_dpdk_devices = num_dpdk_devices;
AssertFatal(num_dpdk_devices > 0 && num_dpdk_devices <= 2,
"Invalid number of DPDK devices (%d). Configure 1 or 2 devices\n",
num_dpdk_devices);
for (int i = 0; i < num_dpdk_devices; i++) {
dpdk_conf->dpdk_devices[i] = gpd(fh_param, nump, CONFIG_STRING_ORU_DPDK_DEVICES)->strlistptr[i];
}
dpdk_conf->extra_eal_args = gpd(fh_param, nump, CONFIG_STRING_EXTRA_EAL_ARGS)->strlistptr;
dpdk_conf->num_extra_eal_args = gpd(fh_param, nump, CONFIG_STRING_EXTRA_EAL_ARGS)->numelt;
fh_cfg->num_du_mac_addrs = gpd(fh_param, nump, CONFIG_STRING_DU_MAC_ADDRESSES)->numelt;
for (int i = 0; i < fh_cfg->num_du_mac_addrs; i++) {
fh_cfg->du_mac_addrs[i] = gpd(fh_param, nump, CONFIG_STRING_DU_MAC_ADDRESSES)->strlistptr[i];
AssertFatal(strlen(fh_cfg->du_mac_addrs[i]) == 17, "Invalid MAC address\n");
}
fh_cfg->enable_compression = false;
fh_cfg->rx_core = *gpd(fh_param, nump, CONFIG_STRING_RX_CORE)->iptr;
fh_cfg->mtu = *gpd(fh_param, nump, CONFIG_STRING_MTU)->iptr;
fh_cfg->num_prbs = oru->bw_tx[0];
fh_cfg->numerology = oru->numerology;
fh_cfg->prach_eaxc_offset = *gpd(fh_param, nump, CONFIG_STRING_PRACH_EAXC_OFFSET)->iptr;
AssertFatal(gpd(fh_param, nump, CONFIG_STRING_T2A_UP)->numelt == 2, "Two parameters required for %s\n", CONFIG_STRING_T2A_UP);
fh_cfg->T2a_up_min_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_UP)->iptr[0];
fh_cfg->T2a_up_max_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_UP)->iptr[1];
AssertFatal(fh_cfg->T2a_up_min_uS <= fh_cfg->T2a_up_max_uS,
"T2a max (%d) has to be greater than T2a min (%d)\n",
fh_cfg->T2a_up_max_uS,
fh_cfg->T2a_up_min_uS);
AssertFatal(gpd(fh_param, nump, CONFIG_STRING_T2A_CP)->numelt == 2, "Two parameters required for %s\n", CONFIG_STRING_T2A_CP);
fh_cfg->T2a_cp_min_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_CP)->iptr[0];
fh_cfg->T2a_cp_max_uS = gpd(fh_param, nump, CONFIG_STRING_T2A_CP)->iptr[1];
AssertFatal(fh_cfg->T2a_cp_min_uS <= fh_cfg->T2a_cp_max_uS,
"T2a max (%d) has to be greater than T2a min (%d)\n",
fh_cfg->T2a_cp_max_uS,
fh_cfg->T2a_cp_min_uS);
oru_fh_tdd_pattern_t *tdd_pattern = &fh_cfg->tdd_pattern;
tdd_pattern->num_dl_slots = oru->num_DL_slots;
tdd_pattern->num_ul_slots = oru->num_UL_slots;
tdd_pattern->num_dl_symbols = oru->num_DL_symbols;
tdd_pattern->num_ul_symbols = oru->num_UL_symbols;
int num_slots_frame = (1 << oru->numerology) * NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
int num_period_frame = get_nb_periods_per_frame(oru->tdd_period);
int num_slots_period = num_slots_frame / num_period_frame;
tdd_pattern->tdd_pattern_length_slots = num_slots_period;
return 0;
}
void oru_init_frame_parms(ORU_t *oru)
{
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
fp->frame_type = oru->frame_type;
ru->config.cell_config.frame_duplex_type.value = oru->frame_type;
ru->config.cell_config.frame_duplex_type.tl.tag = 0x100D;
fp->N_RB_DL = oru->bw_tx[0];
ru->config.ssb_config.scs_common.value = ru->numerology;
ru->config.carrier_config.dl_grid_size[ru->config.ssb_config.scs_common.value].value = oru->bw_tx[0];
fp->N_RB_UL = oru->bw_rx[0];
ru->config.carrier_config.ul_grid_size[ru->config.ssb_config.scs_common.value].value = oru->bw_rx[0];
fp->numerology_index = ru->numerology;
LOG_I(NR_PHY,
"Set RU frame type to %s, N_RB_DL %d, N_RB_UL %d, mu %d\n",
oru->frame_type == TDD ? "TDD" : "FDD",
oru->bw_tx[0],
oru->bw_rx[0],
ru->numerology);
set_scs_parameters(fp, fp->numerology_index, oru->bw_tx[0], 0);
fp->slots_per_frame = 10 * fp->slots_per_subframe;
fp->nb_antennas_rx = ru->nb_rx;
fp->nb_antennas_tx = ru->nb_tx;
fp->symbols_per_slot = 14;
fp->samples_per_subframe_wCP = fp->ofdm_symbol_size * fp->symbols_per_slot * fp->slots_per_subframe;
fp->samples_per_frame_wCP = 10 * fp->samples_per_subframe_wCP;
fp->samples_per_slot_wCP = fp->symbols_per_slot * fp->ofdm_symbol_size;
fp->samples_per_slotN0 = (fp->nb_prefix_samples + fp->ofdm_symbol_size) * fp->symbols_per_slot;
fp->samples_per_slot0 =
fp->nb_prefix_samples0 + ((fp->symbols_per_slot - 1) * fp->nb_prefix_samples) + (fp->symbols_per_slot * fp->ofdm_symbol_size);
fp->samples_per_subframe = (fp->nb_prefix_samples0 + fp->ofdm_symbol_size) * 2
+ (fp->nb_prefix_samples + fp->ofdm_symbol_size) * (fp->symbols_per_slot * fp->slots_per_subframe - 2);
fp->samples_per_frame = 10 * fp->samples_per_subframe;
fp->freq_range = (oru->carrier_freq_tx[0] < 6e6) ? FR1 : FR2;
fp->dl_CarrierFreq = (double)oru->carrier_freq_tx[0] * 1000;
fp->ul_CarrierFreq = (double)oru->carrier_freq_rx[0] * 1000;
fp->Ncp = NORMAL;
fp->ofdm_offset_divisor = 8;
// Split 7.2 parameters
ru->config.prach_config.num_prach_fd_occasions.value = 1;
ru->config.prach_config.prach_ConfigurationIndex.value = oru->prach_config_index;
ru->config.prach_config.prach_ConfigurationIndex.tl.tag = 0x1029;
ru->config.prach_config.num_prach_fd_occasions_list = malloc(sizeof(*ru->config.prach_config.num_prach_fd_occasions_list));
ru->config.prach_config.num_prach_fd_occasions_list[0].k1.value = oru->prach_msg1_freq;
if (ru->config.cell_config.frame_duplex_type.value == 1 /* TDD */) {
ru->config.tdd_table.tdd_period.value = oru->tdd_period;
ru->config.tdd_table.tdd_period.tl.tag = 0x1026;
int numb_slots_frame = (1 << ru->numerology) * NR_NUMBER_OF_SUBFRAMES_PER_FRAME;
int numb_period_frame = get_nb_periods_per_frame(oru->tdd_period);
int numb_slots_period = numb_slots_frame / numb_period_frame;
ru->config.tdd_table.max_tdd_periodicity_list =
malloc(sizeof(*ru->config.tdd_table.max_tdd_periodicity_list) * (numb_slots_frame));
for (int n = 0; n < numb_slots_frame; n++) {
int s = 0;
int p = n % numb_slots_period;
if (p < oru->num_DL_slots) {
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list =
malloc(sizeof(*ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list) * NR_SYMBOLS_PER_SLOT);
for (s = 0; s < 14; s++)
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list[s].slot_config.value = 0;
} else if (p == oru->num_DL_slots) {
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list =
malloc(sizeof(*ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list) * NR_SYMBOLS_PER_SLOT);
for (s = 0; s < oru->num_DL_symbols; s++)
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list[s].slot_config.value = 0;
for (; s < NR_SYMBOLS_PER_SLOT - oru->num_UL_symbols; s++)
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list[s].slot_config.value = 2;
for (; s < NR_SYMBOLS_PER_SLOT; s++)
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list[s].slot_config.value = 1;
} else {
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list =
malloc(sizeof(*ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list) * NR_SYMBOLS_PER_SLOT);
for (s = 0; s < NR_SYMBOLS_PER_SLOT; s++)
ru->config.tdd_table.max_tdd_periodicity_list[n].max_num_of_symbol_per_slot_list[s].slot_config.value = 1;
}
}
}
}
void fft_and_cp_insertion(NR_DL_FRAME_PARMS *fp, c16_t *txdataF, c16_t *txdata, int slot, int symbol)
{
if (fp->Ncp == 1) {
PHY_ofdm_mod((int *)txdataF, (int *)txdata, fp->ofdm_symbol_size, 1, fp->nb_prefix_samples, CYCLIC_PREFIX);
} else {
if (fp->numerology_index != 0) {
if (!(slot % (fp->slots_per_subframe / 2)) && (symbol == 0)) {
PHY_ofdm_mod((int *)txdataF, (int *)txdata, fp->ofdm_symbol_size, 1, fp->nb_prefix_samples0, CYCLIC_PREFIX);
} else {
PHY_ofdm_mod((int *)txdataF, (int *)txdata, fp->ofdm_symbol_size, 1, fp->nb_prefix_samples, CYCLIC_PREFIX);
}
} else {
if (symbol % 0x7) {
PHY_ofdm_mod((int *)txdataF, (int *)txdata, fp->ofdm_symbol_size, 1, fp->nb_prefix_samples, CYCLIC_PREFIX);
} else {
PHY_ofdm_mod((int *)txdataF, (int *)txdata, fp->ofdm_symbol_size, 1, fp->nb_prefix_samples0, CYCLIC_PREFIX);
}
}
}
}
static void dl_symbol_process(RU_t *ru, int frame, int slot, int symbol, c16_t **txDataF, int64_t timestamp)
{
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
uint32_t slot_offset = get_samples_slot_timestamp(fp, slot);
uint32_t symbol_offset = get_samples_symbol_timestamp(fp, slot, symbol);
__attribute__((aligned(64))) c16_t txdataF_shifted[fp->ofdm_symbol_size];
memset(txdataF_shifted, 0, sizeof(txdataF_shifted));
c16_t *rotation = fp->symbol_rotation[0] + (slot % fp->slots_per_subframe) * fp->symbols_per_slot + symbol;
for (int aatx = 0; aatx < ru->nb_tx; aatx++) {
// Phase compensation
rotate_cpx_vector(txDataF[aatx], *rotation, txDataF[aatx], fp->N_RB_DL * NR_NB_SC_PER_RB, 15);
// FFT Shift
const int num_samp_half = fp->N_RB_DL * NR_NB_SC_PER_RB / 2;
const int first_carrier_offset = fp->ofdm_symbol_size - num_samp_half;
memcpy(txdataF_shifted + first_carrier_offset, txDataF[aatx], num_samp_half * sizeof(c16_t));
memcpy(txdataF_shifted, txDataF[aatx] + num_samp_half, num_samp_half * sizeof(c16_t));
fft_and_cp_insertion(ru->nr_frame_parms,
txdataF_shifted,
(c16_t *)&ru->common.txdata[aatx][slot_offset + symbol_offset],
slot,
symbol);
}
tx_rf_symbols(ru, frame, slot, timestamp, symbol, 1);
}
void *oru_north_read_thread(void *arg)
{
ORU_t *oru = (ORU_t *)arg;
RU_t *ru = (RU_t *)oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
__attribute__((aligned(64))) c16_t txDataF[ru->nb_tx][fp->N_RB_DL * NR_NB_SC_PER_RB];
memset(txDataF, 0, sizeof(txDataF));
c16_t *txDataF_ptr[ru->nb_tx];
for (int aatx = 0; aatx < ru->nb_tx; aatx++) {
txDataF_ptr[aatx] = txDataF[aatx];
}
uint32_t start_frame, start_slot;
uint64_t start_hyper_frame;
struct timespec utc_anchor_point;
oru_fh_get_utc_anchor_point(oru->fronthaul, &start_hyper_frame, &start_frame, &start_slot, &utc_anchor_point);
AssertFatal(ru->rfdevice.get_timestamp != NULL, "rfdevice has no capability to translate UTC timestamp to sample index\n");
int64_t start_timestamp = ru->rfdevice.get_timestamp(&ru->rfdevice, &utc_anchor_point);
// subtract the start_frame and start_slot from the timestamp simplify calculation below.
start_timestamp -= (start_frame * fp->samples_per_frame + get_samples_slot_timestamp(fp, start_slot));
// Now start_timestamp points to the start sample of the frame 0 slot 0 symbol 0 of hyperframe 0
LOG_A(PHY, "DL thread started: start_timestamp %ld, start_frame %d, start_slot %d\n", start_timestamp, start_frame, start_slot);
while (!oai_exit) {
int frame = -1, slot = -1, symbol = -1;
uint64_t hyper_frame;
int ret = oru_fh_tx_read_symbol(oru->fronthaul, (uint32_t **)txDataF_ptr, ru->nb_tx, &hyper_frame, &frame, &slot, &symbol);
if (ret != 0) {
LOG_E(PHY, "[RU_thread] read data error: frame %d, slot %d, symbol %d\n", frame, slot, symbol);
continue;
}
if (start_hyper_frame > hyper_frame) {
continue;
}
uint64_t num_frames = (hyper_frame - start_hyper_frame) * 1024 + frame;
int64_t timestamp = start_timestamp + num_frames * fp->samples_per_frame + get_samples_slot_timestamp(fp, slot)
+ get_samples_symbol_timestamp(fp, slot, symbol);
if (timestamp < 0) {
continue;
}
dl_symbol_process(ru, frame, slot, symbol, txDataF_ptr, timestamp);
if (frame % 256 == 0 && slot == 0 && symbol == 0) {
LOG_I(PHY, "[RU_thread] read data: frame %d, slot %d, symbol %d\n", frame, slot, symbol);
}
}
return NULL;
}
// Returns PRACH symbol that was received in current frame, slot and symbol.
// If no PRACH symbol was received, returns -1
int get_prach_symbol(ORU_t *oru, int frame, int slot, int symbol, int numerology)
{
uint16_t RA_sfn_index;
AssertFatal(oru->ru->nr_frame_parms->frame_type == TDD, "Only supports TDD\n");
if (get_nr_prach_sched_from_info(oru->prach_info, oru->prach_config_index, frame, slot, numerology, FR1, &RA_sfn_index, true)) {
int format = oru->prach_item.pdu.prach_format;
int start_symbol = oru->prach_item.pdu.prach_start_symbol;
symbol -= start_symbol;
// TODO: Support more PRACH formats
AssertFatal(format == 8, "only support format B4\n");
// TODO: This is not exactly the case but it is correct
if (symbol >= 0 && symbol < 12) {
return symbol;
}
}
return -1;
}
void receive_prach(ORU_t *oru, int frame, int slot, int symbol, int prach_symbol)
{
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
oru->prach_item.frame = frame;
oru->prach_item.slot = slot;
c16_t rxdataF[ru->nb_rx][NR_PRACH_SEQ_LEN_L];
memset(rxdataF, 0, sizeof(rxdataF));
rx_nr_prach_ru_rep(&oru->prach_item,
ru->common.rxdata,
fp,
ru->N_TA_offset,
prach_symbol,
0, // prachOccasion
rxdataF);
c16_t *rxdataF_ptr[ru->nb_rx];
for (int aarx = 0; aarx < ru->nb_rx; aarx++) {
rxdataF_ptr[aarx] = rxdataF[aarx];
}
oru_fh_rx_send_prach(oru->fronthaul, (uint32_t **)rxdataF_ptr, ru->nb_rx, frame, slot, symbol);
}
#define MAX_PENDING_UL_JOBS 64
typedef struct {
ul_job_t job;
bool active;
int symbols_sent;
} ul_pending_t;
static void receive_pusch(ORU_t *oru, int frame, int slot, int symbol, ul_job_t *job)
{
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
int aarx = job->antenna_id;
if (aarx < 0 || aarx >= fp->nb_antennas_rx) {
LOG_W(PHY, "[ORU south] receive_pusch: invalid antenna_id %d\n", aarx);
return;
}
// CP removal + FFT → full ofdm_symbol_size frequency-domain output
c16_t rxdataF_fft[fp->ofdm_symbol_size] __attribute__((aligned(32)));
nr_symbol_fep_ul(fp, (c16_t *)ru->common.rxdata[aarx], rxdataF_fft, symbol, slot, ru->N_TA_offset);
// Phase decompensation (conjugate rotation for UL)
apply_nr_rotation_symbol_RX(fp->symbols_per_slot,
fp->slots_per_subframe,
fp->timeshift_symbol_rotation,
fp->first_carrier_offset,
rxdataF_fft,
fp->symbol_rotation[link_type_ul],
fp->N_RB_UL,
slot,
symbol);
// Inverse FFT shift: split format → contiguous PRB format sent to DU.
// DL TX shift: contiguous[0..N/2-1] → FFT_input[first_carrier_offset..] (negative freqs)
// contiguous[N/2..N-1] → FFT_input[0..N/2-1] (positive freqs)
// UL RX inverse: FFT_out[first_carrier_offset..] → contiguous[0..N/2-1]
// FFT_out[0..N/2-1] → contiguous[N/2..N-1]
const int num_samp_half = fp->N_RB_UL * NR_NB_SC_PER_RB / 2;
const int first_carrier_offset = fp->ofdm_symbol_size - num_samp_half;
c16_t rxdataF[fp->N_RB_UL * NR_NB_SC_PER_RB];
memcpy(rxdataF, rxdataF_fft + first_carrier_offset, num_samp_half * sizeof(c16_t));
memcpy(rxdataF + num_samp_half, rxdataF_fft, num_samp_half * sizeof(c16_t));
oru_fh_rx_send_pusch(oru->fronthaul, (uint32_t *)rxdataF, symbol, job);
}
#define UL_WORK_QUEUE_DEPTH 128
typedef struct {
ORU_t *oru;
int frame;
int slot;
int symbol;
ul_job_t job;
} ul_work_item_t;
typedef struct {
ul_work_item_t ring[UL_WORK_QUEUE_DEPTH];
int head;
int tail;
int count;
pthread_mutex_t lock;
pthread_cond_t work_available;
pthread_cond_t space_available;
bool running;
} ul_work_queue_t;
static void *ul_worker_thread(void *arg)
{
ul_work_queue_t *q = arg;
while (1) {
pthread_mutex_lock(&q->lock);
while (q->count == 0 && q->running)
pthread_cond_wait(&q->work_available, &q->lock);
if (!q->running && q->count == 0) {
pthread_mutex_unlock(&q->lock);
break;
}
ul_work_item_t item = q->ring[q->head];
q->head = (q->head + 1) % UL_WORK_QUEUE_DEPTH;
q->count--;
pthread_cond_signal(&q->space_available);
pthread_mutex_unlock(&q->lock);
receive_pusch(item.oru, item.frame, item.slot, item.symbol, &item.job);
}
return NULL;
}
static void dispatch_ul_work(ul_work_queue_t *q, ORU_t *oru, int frame, int slot, int symbol, const ul_job_t *job)
{
pthread_mutex_lock(&q->lock);
while (q->count == UL_WORK_QUEUE_DEPTH)
pthread_cond_wait(&q->space_available, &q->lock);
q->ring[q->tail] = (ul_work_item_t){.oru = oru, .frame = frame, .slot = slot, .symbol = symbol, .job = *job};
q->tail = (q->tail + 1) % UL_WORK_QUEUE_DEPTH;
q->count++;
pthread_cond_signal(&q->work_available);
pthread_mutex_unlock(&q->lock);
}
void *oru_south_read_thread(void *arg)
{
ORU_t *oru = arg;
RU_t *ru = oru->ru;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
struct timespec utc_anchor_point;
AssertFatal(ru->rfdevice.get_timestamp != NULL, "rfdevice has no capability to translate UTC timestamp to sample index\n");
uint32_t start_frame, start_slot;
uint64_t hyper_frame;
oru_fh_get_utc_anchor_point(oru->fronthaul, &hyper_frame, &start_frame, &start_slot, &utc_anchor_point);
int64_t start_timestamp = ru->rfdevice.get_timestamp(&ru->rfdevice, &utc_anchor_point);
const int num_samples = 3000;
c16_t throwaway_samples[ru->nb_rx][num_samples];
void *rxp[ru->nb_rx];
for (int i = 0; i < ru->nb_rx; i++)
rxp[i] = throwaway_samples[i];
openair0_timestamp_t timestamp;
int num_samples_read = ru->rfdevice.trx_read_func(&ru->rfdevice, &timestamp, rxp, num_samples, ru->nb_rx);
AssertFatal(num_samples_read == num_samples, "Unexpected number of samples received\n");
openair0_timestamp_t next_timestamp = timestamp + num_samples_read;
while (next_timestamp > start_timestamp) {
start_timestamp += get_samples_slot_duration(fp, start_slot, 1);
start_slot++;
if (start_slot == fp->slots_per_frame) {
start_slot = 0;
start_frame++;
if (start_frame == 1024) {
start_frame = 0;
}
}
}
while (next_timestamp < start_timestamp) {
int num_samples_to_read = min(num_samples, (int)(start_timestamp - next_timestamp));
int num_samples_read = ru->rfdevice.trx_read_func(&ru->rfdevice, &timestamp, rxp, num_samples_to_read, ru->nb_rx);
AssertFatal(num_samples_read == num_samples_to_read, "Unexpected number of samples received\n");
next_timestamp += num_samples_read;
}
AssertFatal(next_timestamp == start_timestamp, "O-RU South thread could not sync to UTC anchor point\n");
int slot = start_slot;
int frame = start_frame;
// Worker pool: one thread per RX antenna so all antennas in a symbol process in parallel.
const int num_workers = ru->nb_rx;
pthread_t workers[num_workers];
ul_work_queue_t work_queue = {
.lock = PTHREAD_MUTEX_INITIALIZER,
.work_available = PTHREAD_COND_INITIALIZER,
.space_available = PTHREAD_COND_INITIALIZER,
.running = true,
};
for (int i = 0; i < num_workers; i++) {
char name[32];
snprintf(name, sizeof(name), "ul_worker_%d", i);
threadCreate(&workers[i], ul_worker_thread, &work_queue, name, -1, OAI_PRIORITY_RT_MAX);
}
ul_pending_t pending_ul[MAX_PENDING_UL_JOBS] = {0};
while (!oai_exit) {
int rx_slot_type = nr_slot_select(&ru->config, frame, slot);
for (int symbol = 0; symbol < 14; symbol++) {
int samples_to_read = get_samples_symbol_duration(fp, slot, symbol, 1);
size_t offset = get_samples_slot_timestamp(fp, slot) + get_samples_symbol_timestamp(fp, slot, symbol);
c16_t *rxp[fp->nb_antennas_rx];
for (int aarx = 0; aarx < fp->nb_antennas_rx; aarx++) {
rxp[aarx] = (c16_t *)&ru->common.rxdata[aarx][offset];
}
openair0_timestamp_t timestamp;
int num_samples_read = ru->rfdevice.trx_read_func(&ru->rfdevice, &timestamp, (void **)rxp, samples_to_read, ru->nb_rx);
AssertFatal(num_samples_read == samples_to_read, "Unexpected number of samples received\n");
LOG_D(PHY,
"[ORU south] read data: frame %d, slot %d, symbol %d, timestamp %ld num_symbols %d, samples %d\n",
frame,
slot,
symbol,
timestamp,
1,
num_samples_read);
// Drain the UL job ring
ul_job_t new_job;
while (oru_fh_poll_ul_job(oru->fronthaul, &new_job) == 0) {
bool added = false;
for (int i = 0; i < MAX_PENDING_UL_JOBS; i++) {
if (!pending_ul[i].active) {
pending_ul[i] = (ul_pending_t){.job = new_job, .active = true, .symbols_sent = 0};
added = true;
break;
}
}
if (!added)
LOG_W(PHY, "[ORU south] UL pending queue full, dropping job frame=%d slot=%d sym=%d\n",
new_job.frame, new_job.slot_in_frame, new_job.symbol);
}
if (rx_slot_type == NR_UPLINK_SLOT || rx_slot_type == NR_MIXED_SLOT) {
// Process pending jobs whose next symbol matches the current one
for (int i = 0; i < MAX_PENDING_UL_JOBS; i++) {
if (!pending_ul[i].active)
continue;
ul_job_t *j = &pending_ul[i].job;
// Skip jobs scheduled for a future slot or frame, and drop jobs in the past
int slots_per_frame = fp->slots_per_frame;
int total_slots = 1024 * slots_per_frame;
int diff_slots = (j->frame * slots_per_frame + j->slot_in_frame) - (frame * slots_per_frame + slot);
if (diff_slots < -total_slots / 2) {
diff_slots += total_slots;
} else if (diff_slots > total_slots / 2) {
diff_slots -= total_slots;
}
if (diff_slots > 0) {
// Future slot: skip
continue;
}
if (diff_slots < 0) {
// Past slot: drop
LOG_W(PHY, "[ORU south] missed UL slot %d.%d (now %d.%d), dropping job ant=%d\n",
j->frame, j->slot_in_frame, frame, slot, j->antenna_id);
pending_ul[i].active = false;
continue;
}
// Same slot: check symbol
int expected_symbol = j->symbol + pending_ul[i].symbols_sent;
if (expected_symbol < symbol) {
LOG_W(PHY, "[ORU south] missed UL symbol %d (now %d), dropping job ant=%d\n",
expected_symbol, symbol, j->antenna_id);
pending_ul[i].active = false;
} else if (expected_symbol == symbol) {
dispatch_ul_work(&work_queue, oru, frame, slot, symbol, j);
if (++pending_ul[i].symbols_sent == j->num_symbols)
pending_ul[i].active = false;
}
// expected_symbol > symbol: job spans multiple symbols, revisit next iteration
}
}
int prach_symbol = get_prach_symbol(oru, frame, slot, symbol, ru->numerology);
if (prach_symbol != -1)
receive_prach(oru, frame, slot, symbol, prach_symbol);
}
slot++;
if (slot == fp->slots_per_frame) {
slot = 0;
frame++;
if (frame == 1024) {
frame = 0;
}
}
}
pthread_mutex_lock(&work_queue.lock);
work_queue.running = false;
pthread_cond_broadcast(&work_queue.work_available);
pthread_mutex_unlock(&work_queue.lock);
for (int i = 0; i < num_workers; i++)
pthread_join(workers[i], NULL);
pthread_cond_destroy(&work_queue.work_available);
pthread_cond_destroy(&work_queue.space_available);
pthread_mutex_destroy(&work_queue.lock);
return NULL;
}

59
executables/nr-oru.h Normal file
View File

@@ -0,0 +1,59 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef __NR_ORU_H__
#define __NR_ORU_H__
#include "openair1/PHY/defs_RU.h"
#include <pthread.h>
#include "oru_fh.h"
#include "openair2/LAYER2/NR_MAC_COMMON/nr_prach_config.h"
#include "openair1/PHY/defs_gNB.h"
typedef struct {
RU_t *ru;
/// tx carrier
uint64_t carrier_freq_tx[MAX_BANDS_PER_RRU];
/// rx carrier
uint64_t carrier_freq_rx[MAX_BANDS_PER_RRU];
/// tx BW in PRBs
int bw_tx[MAX_BANDS_PER_RRU];
/// rx BW in PRBs
int bw_rx[MAX_BANDS_PER_RRU];
/// 3GPP FRAME Type FDD/TDD
int frame_type;
/// 3GPP PRACH configuration index
int prach_config_index;
/// 3GPP MSG1 Start frequency
int prach_msg1_freq;
/// 3GPP TDD periodicity (0.5 ms, 1 0.625ms, 2 1ms, 3 1.25ms, 4 2ms,5 2.5ms, 6 5ms, 7 10ms, 8 3ms, 9 4ms
int tdd_period;
/// number of DL slots
int num_DL_slots;
/// number of UL slots
int num_UL_slots;
/// number of DL symbols
int num_DL_symbols;
/// number of UL symbols
int num_UL_symbols;
int numerology;
pthread_t north_read_thread;
pthread_t south_read_thread;
oru_fh_config_t fh_config;
void *fronthaul;
// PRACH related
nr_prach_info_t prach_info;
time_stats_t rx_prach;
time_stats_t rx;
prach_item_t prach_item;
} ORU_t;
int get_oru_options(ORU_t *oru);
void oru_init_frame_parms(ORU_t *oru);
void *oru_north_read_thread(void *arg);
void *oru_south_read_thread(void *arg);
void prepare_prach_item(ORU_t *oru);
#endif

View File

@@ -21,8 +21,6 @@
#include "radio/COMMON/common_lib.h"
#include "radio/ETHERNET/ethernet_lib.h"
#include "PHY/if4_tools.h"
#include "PHY/defs_nr_common.h"
#include "PHY/phy_extern.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
@@ -76,15 +74,6 @@ void fh_if5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp)
ru->ifdevice.trx_write_func2(&ru->ifdevice, timestamp, buffs, 0, get_samples_per_slot(slot, ru->nr_frame_parms), 0, ru->nb_tx);
}
// southbound IF4p5 fronthaul
void fh_if4p5_south_out(RU_t *ru, int frame, int slot, uint64_t timestamp)
{
LOG_D(PHY,"Sending IF4p5 for frame %d subframe %d\n",ru->proc.frame_tx,ru->proc.tti_tx);
if ((nr_slot_select(&ru->config, ru->proc.frame_tx, ru->proc.tti_tx) & NR_DOWNLINK_SLOT) > 0)
send_IF4p5(ru,frame, slot, IF4p5_PDLFFT);
}
/*************************************************************/
/* Input Fronthaul from south RCC/RAU */
@@ -148,196 +137,6 @@ void fh_if5_south_in(RU_t *ru, int *frame, int *tti)
rxmeas.tv_nsec);
}
// Synchronous if4p5 from south
void fh_if4p5_south_in(RU_t *ru,
int *frame,
int *slot) {
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
RU_proc_t *proc = &ru->proc;
int f,sl;
uint16_t packet_type;
uint32_t symbol_number=0;
uint32_t symbol_mask_full=0;
do { // Blocking, we need a timeout on this !!!!!!!!!!!!!!!!!!!!!!!
recv_IF4p5(ru, &f, &sl, &packet_type, &symbol_number);
if (packet_type == IF4p5_PULFFT) proc->symbol_mask[sl] = proc->symbol_mask[sl] | (1<<symbol_number);
else if (packet_type == IF4p5_PULTICK) {
if ((proc->first_rx == 0) && (f != *frame))
LOG_E(PHY, "rx_fh_if4p5: PULTICK received frame %d != expected %d\n", f, *frame);
if ((proc->first_rx == 0) && (sl != *slot))
LOG_E(PHY, "rx_fh_if4p5: PULTICK received subframe %d != expected %d (first_rx %d)\n", sl, *slot, proc->first_rx);
break;
} else if (packet_type == IF4p5_PRACH) {
// nothing in RU for RAU
}
LOG_D(PHY,"rx_fh_if4p5: subframe %d symbol mask %x\n",*slot,proc->symbol_mask[sl]);
} while(proc->symbol_mask[sl] != symbol_mask_full);
//caculate timestamp_rx, timestamp_tx based on frame and subframe
proc->tti_rx = sl;
proc->frame_rx = f;
proc->timestamp_rx = (proc->frame_rx * fp->samples_per_subframe * 10) + get_samples_slot_timestamp(fp, proc->tti_rx);
// proc->timestamp_tx = proc->timestamp_rx + (4*fp->samples_per_subframe);
proc->tti_tx = (sl+ru->sl_ahead)%fp->slots_per_frame;
proc->frame_tx = (sl > (fp->slots_per_frame - 1 - (ru->sl_ahead))) ? (f + 1) & 1023 : f;
if (proc->first_rx == 0) {
if (proc->tti_rx != *slot) {
LOG_E(PHY,"Received Timestamp (IF4p5) doesn't correspond to the time we think it is (proc->tti_rx %d, subframe %d)\n",proc->tti_rx,*slot);
exit_fun("Exiting");
}
if (proc->frame_rx != *frame) {
LOG_E(PHY,"Received Timestamp (IF4p5) doesn't correspond to the time we think it is (proc->frame_rx %d frame %d)\n",proc->frame_rx,*frame);
exit_fun("Exiting");
}
} else {
proc->first_rx = 0;
*frame = proc->frame_rx;
*slot = proc->tti_rx;
}
proc->symbol_mask[proc->tti_rx] = 0;
LOG_D(PHY,"RU %d: fh_if4p5_south_in sleeping ...\n",ru->idx);
}
// asynchronous inbound if4p5 fronthaul from south
void fh_if4p5_south_asynch_in(RU_t *ru,int *frame,int *slot) {
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
RU_proc_t *proc = &ru->proc;
uint16_t packet_type;
uint32_t symbol_number = 0;
uint32_t symbol_mask = (1 << fp->symbols_per_slot) - 1;
uint32_t prach_rx = 0;
do { // Blocking, we need a timeout on this !!!!!!!!!!!!!!!!!!!!!!!
recv_IF4p5(ru, &proc->frame_rx, &proc->tti_rx, &packet_type, &symbol_number);
if (proc->first_rx != 0) {
*frame = proc->frame_rx;
*slot = proc->tti_rx;
proc->first_rx = 0;
} else {
if (proc->frame_rx != *frame) {
LOG_E(PHY,"frame_rx %d is not what we expect %d\n",proc->frame_rx,*frame);
exit_fun("Exiting");
}
if (proc->tti_rx != *slot) {
LOG_E(PHY,"tti_rx %d is not what we expect %d\n",proc->tti_rx,*slot);
exit_fun("Exiting");
}
}
if (packet_type == IF4p5_PULFFT)
symbol_mask &= ~(1 << symbol_number);
else if (packet_type == IF4p5_PRACH)
prach_rx &= ~0x1;
} while (symbol_mask > 0 || prach_rx > 0); // haven't received all PUSCH symbols and PRACH information
}
/*************************************************************/
/* Input Fronthaul from North RRU */
// RRU IF4p5 TX fronthaul receiver. Assumes an if_device on input and if or rf device on output
// receives one subframe's worth of IF4p5 OFDM symbols and OFDM modulates
void fh_if4p5_north_in(RU_t *ru,int *frame,int *slot)
{
uint32_t symbol_number=0;
uint32_t symbol_mask, symbol_mask_full;
uint16_t packet_type;
/// **** incoming IF4p5 from remote RCC/RAU **** ///
symbol_number = 0;
symbol_mask = 0;
symbol_mask_full = (1<<(ru->nr_frame_parms->symbols_per_slot))-1;
do {
recv_IF4p5(ru, frame, slot, &packet_type, &symbol_number);
symbol_mask = symbol_mask | (1<<symbol_number);
} while (symbol_mask != symbol_mask_full);
}
void fh_if5_north_asynch_in(RU_t *ru, int *frame, int *slot)
{
AssertFatal(1 == 0, "Shouldn't get here\n");
}
void fh_if4p5_north_asynch_in(RU_t *ru,int *frame,int *slot) {
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
nfapi_nr_config_request_scf_t *cfg = &ru->config;
RU_proc_t *proc = &ru->proc;
uint16_t packet_type;
uint32_t symbol_mask_full = 0;
int slot_tx,frame_tx;
LOG_D(PHY, "%s(ru:%p frame, subframe)\n", __FUNCTION__, ru);
uint32_t symbol_number = 0;
uint32_t symbol_mask = 0;
// symbol_mask_full = ((subframe_select(fp,*slot) == SF_S) ? (1<<fp->dl_symbols_in_S_subframe) : (1<<fp->symbols_per_slot))-1;
do {
recv_IF4p5(ru, &frame_tx, &slot_tx, &packet_type, &symbol_number);
if (((nr_slot_select(cfg, frame_tx, slot_tx) & NR_DOWNLINK_SLOT) > 0) && (symbol_number == 0))
start_meas(&ru->rx_fhaul);
LOG_D(PHY,"slot %d (%d): frame %d, slot %d, symbol %d\n",
*slot,nr_slot_select(cfg,frame_tx,*slot),frame_tx,slot_tx,symbol_number);
if (proc->first_tx != 0) {
*frame = frame_tx;
*slot = slot_tx;
proc->first_tx = 0;
} else {
AssertFatal(frame_tx == *frame,
"frame_tx %d is not what we expect %d\n",frame_tx,*frame);
AssertFatal(slot_tx == *slot,
"slot_tx %d is not what we expect %d\n",slot_tx,*slot);
}
if (packet_type == IF4p5_PDLFFT) {
symbol_mask = symbol_mask | (1<<symbol_number);
} else
AssertFatal(false, "Illegal IF4p5 packet type (should only be IF4p5_PDLFFT%d\n", packet_type);
} while (symbol_mask != symbol_mask_full);
if ((nr_slot_select(cfg, frame_tx, slot_tx) & NR_DOWNLINK_SLOT) > 0)
stop_meas(&ru->rx_fhaul);
proc->tti_tx = slot_tx;
proc->frame_tx = frame_tx;
if (frame_tx == 0 && slot_tx == 0)
proc->frame_tx_unwrap += 1024;
proc->timestamp_tx =
((uint64_t)frame_tx + proc->frame_tx_unwrap) * fp->samples_per_subframe * 10 + get_samples_slot_timestamp(fp, slot_tx);
LOG_D(PHY, "RU %d/%d TST %lu, frame %d, subframe %d\n", ru->idx, 0, proc->timestamp_tx, frame_tx, slot_tx);
if (ru->feptx_ofdm)
ru->feptx_ofdm(ru, frame_tx, slot_tx);
if (ru->fh_south_out)
ru->fh_south_out(ru, frame_tx, slot_tx, proc->timestamp_tx);
}
void fh_if5_north_out(RU_t *ru)
{
/// **** send_IF5 of rxdata to BBU **** ///
AssertFatal(1 == 0, "Shouldn't get here\n");
}
// RRU IF4p5 northbound interface (RX)
void fh_if4p5_north_out(RU_t *ru)
{
RU_proc_t *proc=&ru->proc;
start_meas(&ru->tx_fhaul);
send_IF4p5(ru, proc->frame_rx, proc->tti_rx, IF4p5_PULFFT);
stop_meas(&ru->tx_fhaul);
}
static void rx_rf(RU_t *ru, int *frame, int *slot)
{
RU_proc_t *proc = &ru->proc;
@@ -494,7 +293,7 @@ static radio_tx_gpio_flag_t get_gpio_flags(RU_t *ru, int slot)
return flags_gpio;
}
void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp)
void tx_rf_symbols(RU_t *ru, int frame, int slot, uint64_t timestamp, int start_symbol, int num_symbols)
{
RU_proc_t *proc = &ru->proc;
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
@@ -521,22 +320,23 @@ void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp)
txsymb++;
}
AssertFatal(txsymb>0,"illegal txsymb %d\n",txsymb);
AssertFatal(txsymb > 0, "illegal txsymb %d\n", txsymb);
if (fp->slots_per_subframe == 1) {
if (txsymb <= 7)
siglen = (fp->ofdm_symbol_size + fp->nb_prefix_samples0) + (txsymb - 1) * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
else
siglen = 2 * (fp->ofdm_symbol_size + fp->nb_prefix_samples0) + (txsymb - 2) * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
} else {
if(slot%(fp->slots_per_subframe/2))
siglen = txsymb * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
else
siglen = (fp->ofdm_symbol_size + fp->nb_prefix_samples0) + (txsymb - 1) * (fp->ofdm_symbol_size + fp->nb_prefix_samples);
if (txsymb < start_symbol) {
// No DL symbols in this transmission
return;
}
//+ ru->end_of_burst_delay;
flags_burst = TX_BURST_END;
int end_symbol = start_symbol + num_symbols - 1;
if (end_symbol >= txsymb) {
flags_burst = TX_BURST_END;
} else {
flags_burst = TX_BURST_MIDDLE;
}
int num_symbols_this_transmission = min(txsymb, end_symbol) - start_symbol + 1;
siglen = get_samples_symbol_duration(fp, slot, start_symbol, num_symbols_this_transmission);
} else if (slot_type == NR_DOWNLINK_SLOT) {
int prevslot_type = nr_slot_select(cfg,frame,(slot+(fp->slots_per_frame-1))%fp->slots_per_frame);
int nextslot_type = nr_slot_select(cfg,frame,(slot+1)%fp->slots_per_frame);
@@ -548,9 +348,11 @@ void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp)
} else {
flags_burst = proc->first_tx == 1 ? TX_BURST_START : TX_BURST_MIDDLE;
}
siglen = get_samples_symbol_duration(fp, slot, start_symbol, num_symbols);
}
} else { // FDD
flags_burst = proc->first_tx == 1 ? TX_BURST_START : TX_BURST_MIDDLE;
siglen = get_samples_symbol_duration(fp, slot, start_symbol, num_symbols);
}
if (ru->openair0_cfg.gpio_controller != RU_GPIO_CONTROL_NONE)
@@ -561,8 +363,9 @@ void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp)
int nt = ru->nb_tx;
void *txp[nt];
uint32_t time_offset = get_samples_slot_timestamp(fp, slot) + get_samples_symbol_timestamp(fp, slot, start_symbol);
for (int i = 0; i < nt; i++)
txp[i] = (void *)&ru->common.txdata[i][get_samples_slot_timestamp(fp, slot)] - sf_extension * sizeof(int32_t);
txp[i] = (void *)&ru->common.txdata[i][time_offset] - sf_extension * sizeof(int32_t);
// prepare tx buffer pointers
uint32_t txs = ru->rfdevice.trx_write_func(&ru->rfdevice,
@@ -586,7 +389,12 @@ void tx_rf(RU_t *ru, int frame,int slot, uint64_t timestamp)
10 * log10((double)signal_energy(txp[0], siglen + sf_extension)));
}
static void fill_rf_config(RU_t *ru, char *rf_config_file)
void tx_rf(RU_t *ru, int frame, int slot, uint64_t timestamp)
{
tx_rf_symbols(ru, frame, slot, timestamp, 0, 14);
}
void fill_rf_config(RU_t *ru, char *rf_config_file)
{
NR_DL_FRAME_PARMS *fp = ru->nr_frame_parms;
nfapi_nr_config_request_scf_t *config = &ru->config; //tmp index
@@ -649,7 +457,7 @@ static void fill_rf_config(RU_t *ru, char *rf_config_file)
}
}
static void fill_split7_2_config(split7_config_t *split7, const nfapi_nr_config_request_scf_t *config, const NR_DL_FRAME_PARMS *fp)
void fill_split7_2_config(split7_config_t *split7, const nfapi_nr_config_request_scf_t *config, const NR_DL_FRAME_PARMS *fp)
{
const nfapi_nr_prach_config_t *prach_config = &config->prach_config;
const nfapi_nr_tdd_table_t *tdd_table = &config->tdd_table;
@@ -833,7 +641,7 @@ void *ru_thread(void *param)
ret = openair0_transport_load(&ru->ifdevice, &ru->openair0_cfg, &ru->eth_params);
AssertFatal(ret == 0, "RU %u: openair0_transport_init() ret %d: cannot initialize transport protocol\n", ru->idx, ret);
if (ru->ifdevice.get_internal_parameter != NULL) {
if (ru->ifdevice.get_internal_parameter) {
/* it seems the device can "overwrite" (request?) to set the callbacks
* for fh_south_in()/fh_south_out() differently */
void *t = ru->ifdevice.get_internal_parameter("fh_if4p5_south_in");
@@ -842,8 +650,6 @@ void *ru_thread(void *param)
t = ru->ifdevice.get_internal_parameter("fh_if4p5_south_out");
if (t != NULL)
ru->fh_south_out = t;
} else {
malloc_IF4p5_buffer(ru);
}
int cpu = sched_getcpu();
@@ -1088,94 +894,59 @@ void set_function_spec_param(RU_t *ru)
switch (ru->if_south) {
case LOCAL_RF: // this is an RU with integrated RF (RRU, gNB)
reset_meas(&ru->rx_fhaul);
if (ru->function == NGFI_RRU_IF5) { // IF5 RRU
ru->do_prach = 0; // no prach processing in RU
ru->fh_north_in = NULL; // no shynchronous incoming fronthaul from north
ru->fh_north_out = fh_if5_north_out; // need only to do send_IF5 reception
ru->fh_south_out = tx_rf; // send output to RF
ru->fh_north_asynch_in = fh_if5_north_asynch_in; // TX packets come asynchronously
ru->feprx = NULL; // nothing (this is a time-domain signal)
ru->feptx_ofdm = NULL; // nothing (this is a time-domain signal)
ru->feptx_prec = NULL; // nothing (this is a time-domain signal)
ru->nr_start_if = nr_start_if; // need to start the if interface for if5
ru->ifdevice.host_type = RRU_HOST;
ru->rfdevice.host_type = RRU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
reset_meas(&ru->rx_fhaul);
reset_meas(&ru->tx_fhaul);
reset_meas(&ru->compression);
reset_meas(&ru->transport);
} else if (ru->function == NGFI_RRU_IF4p5) {
ru->do_prach = 1; // do part of prach processing in RU
ru->fh_north_in = NULL; // no synchronous incoming fronthaul from north
ru->fh_north_out = fh_if4p5_north_out; // send_IF4p5 on reception
ru->fh_south_out = tx_rf; // send output to RF
ru->fh_north_asynch_in = fh_if4p5_north_asynch_in; // TX packets come asynchronously
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_ofdm = nr_feptx_tp; // this is fep with idft only (no precoding in RRU)
ru->feptx_prec = NULL;
ru->nr_start_if = nr_start_if; // need to start the if interface for if4p5
ru->ifdevice.host_type = RRU_HOST;
ru->rfdevice.host_type = RRU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
reset_meas(&ru->tx_fhaul);
reset_meas(&ru->compression);
reset_meas(&ru->transport);
} else if (ru->function == gNodeB_3GPP) {
ru->do_prach = 0; // no prach processing in RU
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_ofdm = nr_feptx_tp; // this is fep with idft and precoding
ru->feptx_prec = NULL;
ru->fh_north_in = NULL; // no incoming fronthaul from north
ru->fh_north_out = NULL; // no outgoing fronthaul to north
ru->nr_start_if = NULL; // no if interface
ru->rfdevice.host_type = RAU_HOST;
ru->fh_south_in = rx_rf; // local synchronous RF RX
ru->fh_south_out = tx_rf; // local synchronous RF TX
ru->start_rf = start_rf; // need to start the local RF interface
ru->stop_rf = stop_rf;
ru->start_write_thread = start_write_thread; // starting RF TX in different thread
}
AssertFatal(ru->function == gNodeB_3GPP, "ru->function %d not supported for LOCAL_RF\n", ru->function);
ru->do_prach = 0; // no prach processing in RU
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_ofdm = nr_feptx_tp; // this is fep with idft and precoding
ru->feptx_prec = NULL;
ru->fh_north_in = NULL; // no incoming fronthaul from north
ru->fh_north_out = NULL; // no outgoing fronthaul to north
ru->nr_start_if = NULL; // no if interface
ru->rfdevice.host_type = RAU_HOST;
ru->fh_south_in = rx_rf; // local synchronous RF RX
ru->fh_south_out = tx_rf; // local synchronous RF TX
ru->start_rf = start_rf; // need to start the local RF interface
ru->stop_rf = stop_rf;
ru->start_write_thread = start_write_thread; // starting RF TX in different thread
break;
case REMOTE_IF5: // the remote unit is IF5 RRU
ru->do_prach = 0;
ru->txfh_in_fep = 0;
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_prec = NULL; // need to do transmit Precoding + IDFTs
ru->feptx_ofdm = nr_feptx_tp; // need to do transmit Precoding + IDFTs
ru->fh_south_in = fh_if5_south_in; // synchronous IF5 reception
ru->fh_south_out = (ru->txfh_in_fep>0) ? NULL : fh_if5_south_out; // synchronous IF5 transmission
ru->fh_south_asynch_in = NULL; // no asynchronous UL
ru->start_rf = ru->eth_params.transp_preference == ETH_UDP_IF5_ECPRI_MODE ? start_streaming : NULL;
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
ru->do_prach = 0;
ru->txfh_in_fep = 0;
ru->feprx = nr_fep_tp; // this is frequency-shift + DFTs
ru->feptx_prec = NULL; // need to do transmit Precoding + IDFTs
ru->feptx_ofdm = nr_feptx_tp; // need to do transmit Precoding + IDFTs
ru->fh_south_in = fh_if5_south_in; // synchronous IF5 reception
ru->fh_south_out = (ru->txfh_in_fep > 0) ? NULL : fh_if5_south_out; // synchronous IF5 transmission
ru->fh_south_asynch_in = NULL; // no asynchronous UL
ru->start_rf = ru->eth_params.transp_preference == ETH_UDP_IF5_ECPRI_MODE ? start_streaming : NULL;
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
break;
case REMOTE_IF4p5:
ru->do_prach = 0;
ru->feprx = NULL; // DFTs
ru->feptx_prec = nr_feptx_prec; // Precoding operation
ru->feptx_ofdm = NULL; // no OFDM mod
ru->fh_south_in = fh_if4p5_south_in; // synchronous IF4p5 reception
ru->fh_south_out = fh_if4p5_south_out; // synchronous IF4p5 transmission
ru->fh_south_asynch_in = (ru->if_timing == synch_to_other) ? fh_if4p5_south_in : NULL; // asynchronous UL if synch_to_other
ru->fh_north_out = NULL;
ru->fh_north_asynch_in = NULL;
ru->start_rf = NULL; // no local RF
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF4p5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
ru->do_prach = 0;
ru->feprx = NULL; // DFTs
ru->feptx_prec = nr_feptx_prec; // Precoding operation
ru->feptx_ofdm = NULL; // no OFDM mod
ru->fh_south_in = NULL;
ru->fh_south_out = NULL;
ru->fh_south_asynch_in = NULL;
ru->fh_north_out = NULL;
ru->fh_north_asynch_in = NULL;
ru->start_rf = NULL; // no local RF
ru->stop_rf = NULL;
ru->start_write_thread = NULL;
ru->nr_start_if = nr_start_if; // need to start if interface for IF4p5
ru->ifdevice.host_type = RAU_HOST;
ru->ifdevice.eth_params = &ru->eth_params;
break;
default:
LOG_E(PHY,"RU with invalid or unknown southbound interface type %d\n",ru->if_south);
LOG_E(PHY, "RU with invalid or unknown southbound interface type %d\n", ru->if_south);
break;
} // switch on interface type
}
@@ -1369,47 +1140,14 @@ static void NRRCconfig_RU(configmodule_interface_t *cfg)
ru->openair0_cfg.tune_offset = get_softmodem_params()->tune_offset;
if (strcmp(*param[RU_LOCAL_RF_IDX].strptr, "yes") == 0) {
if (!config_isparamset(param, RU_LOCAL_IF_NAME_IDX)) {
ru->if_south = LOCAL_RF;
ru->function = gNodeB_3GPP;
LOG_D(PHY, "Setting function for RU %d to gNodeB_3GPP\n", j);
} else {
ru->eth_params.local_if_name = strdup(*param[RU_LOCAL_IF_NAME_IDX].strptr);
ru->eth_params.my_addr = strdup(*param[RU_LOCAL_ADDRESS_IDX].strptr);
ru->eth_params.remote_addr = strdup(*param[RU_REMOTE_ADDRESS_IDX].strptr);
ru->eth_params.my_portc = *param[RU_LOCAL_PORTC_IDX].uptr;
ru->eth_params.remote_portc = *param[RU_REMOTE_PORTC_IDX].uptr;
ru->eth_params.my_portd = *param[RU_LOCAL_PORTD_IDX].uptr;
ru->eth_params.remote_portd = *param[RU_REMOTE_PORTD_IDX].uptr;
char *str = *param[RU_TRANSPORT_PREFERENCE_IDX].strptr;
if (strcmp(str, "udp") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF5;
ru->eth_params.transp_preference = ETH_UDP_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF5 (udp)\n", j);
} else if (strcmp(str, "raw") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF5;
ru->eth_params.transp_preference = ETH_RAW_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF5 (raw)\n", j);
} else if (strcmp(str, "udp_if4p5") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF4p5;
ru->eth_params.transp_preference = ETH_UDP_IF4p5_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF4p5 (udp)\n", j);
} else if (strcmp(str, "raw_if4p5") == 0) {
ru->if_south = LOCAL_RF;
ru->function = NGFI_RRU_IF4p5;
ru->eth_params.transp_preference = ETH_RAW_IF4p5_MODE;
LOG_D(PHY, "Setting function for RU %d to NGFI_RRU_IF4p5 (raw)\n", j);
}
}
AssertFatal(!config_isparamset(param, RU_LOCAL_IF_NAME_IDX), "RU_TRANSPORT_PREFERENCE not supported for local RF\n");
ru->if_south = LOCAL_RF;
ru->function = gNodeB_3GPP;
LOG_D(PHY, "Setting function for RU %d to gNodeB_3GPP\n", j);
ru->max_pdschReferenceSignalPower = *param[RU_MAX_RS_EPRE_IDX].uptr;
ru->max_rxgain = *param[RU_MAX_RXGAIN_IDX].uptr;
ru->sf_extension = *param[RU_SF_EXTENSION_IDX].uptr;
} // strcmp(local_rf, "yes") == 0
else {
} else { // strcmp(local_rf, "yes") == 0
char *str = *param[RU_TRANSPORT_PREFERENCE_IDX].strptr;
LOG_D(PHY, "RU %d: Transport %s\n", j, str);
ru->eth_params.local_if_name = strdup(*param[RU_LOCAL_IF_NAME_IDX].strptr);
@@ -1432,10 +1170,6 @@ static void NRRCconfig_RU(configmodule_interface_t *cfg)
ru->if_south = REMOTE_IF5;
ru->function = NGFI_RAU_IF5;
ru->eth_params.transp_preference = ETH_RAW_MODE;
} else if (strcmp(str, "udp_if4p5") == 0) {
ru->if_south = REMOTE_IF4p5;
ru->function = NGFI_RAU_IF4p5;
ru->eth_params.transp_preference = ETH_UDP_IF4p5_MODE;
} else if (strcmp(str, "raw_if4p5") == 0) {
ru->if_south = REMOTE_IF4p5;
ru->function = NGFI_RAU_IF4p5;

View File

@@ -223,12 +223,12 @@ void oru_fh_cleanup(void *handle)
free(fh);
}
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol)
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol)
{
if (!handle)
return -1;
oru_fh_t *fh = (oru_fh_t *)handle;
read_dl_iq(fh->packet_processor, txdataF, nb_tx, frame, slot, symbol);
read_dl_iq(fh->packet_processor, txdataF, nb_tx, hyper_frame, frame, slot, symbol);
return 0;
}
@@ -240,7 +240,7 @@ int oru_fh_get_ready_jobs(void *handle)
return get_ready_job_count(fh->packet_processor);
}
int oru_fh_get_utc_anchor_point(void *handle, uint32_t *frame, uint32_t *slot, struct timespec *ts)
int oru_fh_get_utc_anchor_point(void *handle, uint64_t *hyper_frame, uint32_t *frame, uint32_t *slot, struct timespec *ts)
{
if (!handle || !frame || !slot || !ts)
return -1;
@@ -249,6 +249,7 @@ int oru_fh_get_utc_anchor_point(void *handle, uint32_t *frame, uint32_t *slot, s
absolute_gps_symbol -= absolute_gps_symbol % NR_SYMBOLS_PER_SLOT; // Round down to start of current slot
uint64_t absolute_slot = absolute_gps_symbol / NR_SYMBOLS_PER_SLOT;
uint32_t slots_per_frame = 10 << fh->cfg.numerology;
*hyper_frame = (absolute_slot / slots_per_frame) / 1024;
*frame = (absolute_slot / slots_per_frame) % 1024;
*slot = absolute_slot % slots_per_frame;
@@ -267,11 +268,17 @@ void oru_fh_rx_send_prach(void *handle, uint32_t **prachF, int nb_rx, int frame,
write_prach_iq(fh->packet_processor, prachF, nb_rx, frame, slot, symbol);
}
void oru_fh_rx_send_pusch(void *handle, uint32_t **puschF, int nb_rx, int frame, int slot, int symbol)
int oru_fh_poll_ul_job(void *handle, ul_job_t *job) {
oru_fh_t *fh = (oru_fh_t *)handle;
AssertFatal(fh, "Invalid handle\n");
return poll_ul_job(fh->packet_processor, job);
}
void oru_fh_rx_send_pusch(void *handle, uint32_t *puschF, int symbol_index, const ul_job_t *job)
{
oru_fh_t *fh = (oru_fh_t *)handle;
AssertFatal(fh, "Invalid handle\n");
write_ul_iq(fh->packet_processor, puschF, nb_rx, frame, slot, symbol);
write_ul_iq(fh->packet_processor, puschF, symbol_index, job);
}
int oru_fh_start(void *handle)

View File

@@ -7,6 +7,7 @@
#include "oru_io.h"
#include <stdint.h>
#include "oru_packet_processor.h"
typedef struct {
char *dpdk_devices[MAX_RU_PORTS];
@@ -74,23 +75,25 @@ int oru_fh_get_ready_jobs(void *handle);
* @param handle Pointer to the fronthaul handle.
* @param txdataF Array of pointers to buffers to store the received frequency-domain IQ samples (per TX antenna).
* @param nb_tx Number of TX antennas.
* @param hyper_frame Absolute GPS hyper-frame number
* @param frame Pointer to store the frame number of the read symbol.
* @param slot Pointer to store the slot number of the read symbol.
* @param symbol Pointer to store the symbol number.
* @return 0 on success or -1 on failure
*/
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol);
int oru_fh_tx_read_symbol(void *handle, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol);
/**
* @brief Get the UTC anchor point mapping between 5G time and system time.
*
* @param handle Pointer to the fronthaul handle.
* @param frame Pointer to store the reference frame number.
* @param hyper_frame Pointer to store the reference hyperframe number (1024 frames each)
* @param slot Pointer to store the reference slot number.
* @param ts Pointer to a timespec structure to store the corresponding system time.
* @return 0 on success, negative on error.
*/
int oru_fh_get_utc_anchor_point(void *handle, uint32_t* frame, uint32_t* slot, struct timespec *ts);
int oru_fh_get_utc_anchor_point(void *handle, uint64_t *hyper_frame, uint32_t* frame, uint32_t* slot, struct timespec *ts);
/**
* @brief Send PRACH symbol data (U-Plane) over the Fronthaul interface.
@@ -104,17 +107,25 @@ int oru_fh_get_utc_anchor_point(void *handle, uint32_t* frame, uint32_t* slot, s
*/
void oru_fh_rx_send_prach(void *handle, uint32_t **prachF, int nb_rx, int frame, int slot, int symbol);
/**
* @brief Poll for pending UL jobs
*
* @param handle Pointer to the fronthaul handle.
* @param job Job descriptor
*
* @returns 0 if successful, -1 otherwise
*/
int oru_fh_poll_ul_job(void *handle, ul_job_t *job);
/**
* @brief Send PUSCH symbol data (U-Plane) over the Fronthaul interface.
*
* @param handle Pointer to the fronthaul handle.
* @param puschF Array of pointers to buffers containing the frequency-domain PUSCH IQ samples.
* @param nb_rx Number of RX antennas.
* @param frame Target frame number.
* @param slot Target slot number.
* @param symbol Target symbol number.
* @param symbol Absolute symbol index within slot (013); must be within the job's symbol range
* @param job Job descriptor
*/
void oru_fh_rx_send_pusch(void *handle, uint32_t **puschF, int nb_rx, int frame, int slot, int symbol);
void oru_fh_rx_send_pusch(void *handle, uint32_t *puschF, int symbol, const ul_job_t *job);
/**
* @brief Start the O-RU Fronthaul processing threads and loops.

View File

@@ -9,6 +9,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include "assertions.h"
#include "log.h"
#include <rte_ring.h>
@@ -16,8 +17,21 @@
#include <sys/types.h>
#include <sys/types.h>
#include <stdatomic.h>
#define PRACH_ERR_LOG_RATELIMIT 10000
#define RATELIMIT(n, block) \
do { \
static _Atomic unsigned long counter = 0; \
unsigned long current = atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed); \
if (current % (n) == 0) { \
block \
} \
} while (0)
#define DL_JOB_RING_SIZE 128
#define UL_JOB_RING_SIZE 128
#define MAX_CONCURRENT_DL_JOBS (DL_JOB_RING_SIZE - 1)
#define NUM_CONCURRENT_DL_SYMBOL_WINDOWS MAX_CONCURRENT_DL_JOBS
#define NUM_CONCURRENT_UL_SYMBOL_WINDOWS 128
@@ -28,13 +42,7 @@
#define SYMBOL_BITMASK_SIZE ((NR_SYMBOLS_PER_SLOT * MAX_TDD_PATTERN_LENGTH_MS * MAX_SLOTS_PER_MS + 7) / 8)
#define MAX_RX_FRAGMENTS 4
#define MAX_MBUFS_PER_SYMBOL 64
typedef enum {
SYM_UL_IDLE,
SYM_UL_ACTIVE,
SYM_UL_READY,
SYM_UL_EMPTY,
} ul_symbol_job_state_t;
#define MAX_SLOTS_PER_FRAME 160
typedef struct {
struct {
@@ -54,43 +62,43 @@ typedef struct {
} dl_symbol_job_t;
typedef struct {
struct {
bool cplane_received;
int section_id;
int num_prb;
int start_prb;
} per_antenna[MAX_ANTENNAS];
uint64_t absolute_symbol;
ul_symbol_job_state_t state;
} ul_symbol_job_t;
typedef struct {
struct {
bool cplane_received;
int section_id;
int num_prb;
int start_prb;
int filter_id;
} per_antenna[MAX_ANTENNAS];
uint64_t absolute_symbol;
} prach_symbol_job_t;
bool active;
uint64_t start_absolute_symbol;
uint32_t num_symbols;
int section_id;
int num_prb;
int start_prb;
int filter_id;
} prach_job_t;
typedef struct {
_Atomic(uint64_t) dl_tdd_mismatch;
_Atomic(uint64_t) ul_tdd_mismatch;
_Atomic(uint64_t) ul_cplane_missing;
_Atomic(uint64_t) prach_cplane_missing;
_Atomic(uint64_t) prach_cplane_missing_ant;
_Atomic(uint64_t) prach_cplane_missing_inactive;
_Atomic(uint64_t) prach_cplane_missing_stale;
_Atomic(uint64_t) prach_cplane_missing_early;
_Atomic(uint64_t) prach_out_of_mbufs;
_Atomic(uint64_t) prach_jobs_pool_exhausted;
_Atomic(uint64_t) out_of_mbufs;
_Atomic(uint64_t) total_uplane_sent;
_Atomic(int64_t) ul_uplane_ota_delay_sum;
_Atomic(uint64_t) ul_uplane_ota_delay_count;
} thread_safe_stats_t;
typedef struct {
dl_symbol_job_t dl_symbol_jobs[MAX_CONCURRENT_DL_JOBS];
dl_symbol_job_t *dl_symbol_rx_window[NUM_CONCURRENT_DL_SYMBOL_WINDOWS];
bool was_dl_symbol_completed[NUM_CONCURRENT_DL_SYMBOL_WINDOWS];
ul_symbol_job_t ul_symbol_jobs[NUM_CONCURRENT_UL_SYMBOL_WINDOWS];
prach_symbol_job_t prach_jobs[NUM_CONCURRENT_UL_SYMBOL_WINDOWS];
prach_job_t prach_jobs[MAX_SLOTS_PER_FRAME][MAX_ANTENNAS];
uint64_t current_absolute_symbol;
uint64_t last_pushed_symbol;
struct rte_ring *dl_free_jobs;
struct rte_ring *dl_ready_jobs;
struct rte_ring *ul_free_jobs;
struct rte_ring *ul_ready_jobs;
ul_job_t ul_jobs_pool[UL_JOB_RING_SIZE];
uint32_t T2a_min_cp_sym_diff;
uint32_t T2a_max_cp_sym_diff;
uint32_t T2a_min_up_dl_sym_diff;
@@ -169,6 +177,13 @@ void *init_packet_processor(int numerology,
for (int i = 0; i < MAX_CONCURRENT_DL_JOBS; i++) {
rte_ring_enqueue(ctx->dl_free_jobs, (void *)&ctx->dl_symbol_jobs[i]);
}
ctx->ul_ready_jobs = rte_ring_create("ul_ready_jobs", UL_JOB_RING_SIZE, rte_socket_id(), 0);
AssertFatal(ctx->ul_ready_jobs != NULL, "Failed to create ring ul_ready_jobs\n");
ctx->ul_free_jobs = rte_ring_create("ul_free_jobs", UL_JOB_RING_SIZE, rte_socket_id(), 0);
AssertFatal(ctx->ul_free_jobs != NULL, "Failed to create ring ul_free_jobs\n");
for (int i = 0; i < UL_JOB_RING_SIZE - 1; i++) {
rte_ring_enqueue(ctx->ul_free_jobs, (void *)&ctx->ul_jobs_pool[i]);
}
ctx->eaxcid_config = (struct xran_eaxcid_config){.mask_cuPortId = 0xF000,
.mask_bandSectorId = 0x0F00,
@@ -201,6 +216,12 @@ void cleanup_packet_processor(void *context)
if (ctx->dl_free_jobs) {
rte_ring_free(ctx->dl_free_jobs);
}
if (ctx->ul_ready_jobs) {
rte_ring_free(ctx->ul_ready_jobs);
}
if (ctx->ul_free_jobs) {
rte_ring_free(ctx->ul_free_jobs);
}
free(ctx);
}
}
@@ -465,6 +486,7 @@ static void handle_dl_cplane_packet(oru_packet_processor_context_t *ctx,
}
if (job->per_antenna[ant_id].cplane_received) {
ctx->stats.cplane_err_dup++;
ctx->stats.cplane_err_dup_dl++;
return;
}
}
@@ -507,30 +529,25 @@ static void handle_ul_cplane_packet(oru_packet_processor_context_t *ctx,
ctx->stats.ul_tdd_mismatch++;
return;
}
for (int i = 0; i < num_symbols; i++) {
uint32_t job_index = (target_absolute_symbol + i) % NUM_CONCURRENT_UL_SYMBOL_WINDOWS;
ul_symbol_job_t *job = &ctx->ul_symbol_jobs[job_index];
if (job->state == SYM_UL_IDLE) {
job->absolute_symbol = target_absolute_symbol + i;
job->state = SYM_UL_ACTIVE;
for (int j = 0; j < MAX_ANTENNAS; j++) {
job->per_antenna[j].cplane_received = false;
}
} else if (job->state == SYM_UL_READY) {
ctx->stats.application_too_slow++;
return;
}
if (job->per_antenna[ant_id].cplane_received) {
ctx->stats.cplane_err_dup++;
return;
}
job->absolute_symbol = target_absolute_symbol + i;
if (!job->per_antenna[ant_id].cplane_received) {
job->per_antenna[ant_id].cplane_received = true;
job->per_antenna[ant_id].section_id = section->hdr.u1.common.sectionId;
job->per_antenna[ant_id].num_prb = section->hdr.u1.common.numPrbc == 0 ? ctx->num_prb : section->hdr.u1.common.numPrbc;
job->per_antenna[ant_id].start_prb = section->hdr.u1.common.startPrbc;
}
ul_job_t *ul_job = NULL;
if (rte_ring_dequeue(ctx->ul_free_jobs, (void **)&ul_job) == 0) {
memset(ul_job, 0, sizeof(*ul_job));
ul_job->response_payload.section_id = section->hdr.u1.common.sectionId;
ul_job->response_payload.comp_method = hdr->udComp.udCompMeth;
ul_job->response_payload.iq_width = hdr->udComp.udIqWidth == 0 ? 16 : hdr->udComp.udIqWidth;
uint64_t absolute_gps_symbol = target_absolute_symbol;
ul_job->hyper_frame = absolute_gps_symbol / (1024 * (10 * (1 << ctx->numerology) * 14));
ul_job->frame = (absolute_gps_symbol / (10 * (1 << ctx->numerology) * 14)) % 1024;
ul_job->slot_in_frame = (absolute_gps_symbol % (10 * (1 << ctx->numerology) * 14)) / 14;
ul_job->symbol = absolute_gps_symbol % 14;
ul_job->num_symbols = num_symbols;
ul_job->antenna_id = ant_id;
ul_job->num_prb = section->hdr.u1.common.numPrbc == 0 ? ctx->num_prb : section->hdr.u1.common.numPrbc;
ul_job->start_prb = section->hdr.u1.common.startPrbc;
int ret = rte_ring_enqueue(ctx->ul_ready_jobs, (void *)ul_job);
AssertFatal(ret == 0, "Failed to enqueue ul_job to ul_ready_jobs ring\n");
} else {
ctx->stats.application_too_slow++;
}
}
@@ -541,6 +558,8 @@ void handle_prach_cplane_packet(oru_packet_processor_context_t *ctx,
{
if (hdr->cmnhdr.numOfSections != 1) {
ctx->stats.cplane_err_hdr++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT,
{ LOG_W(HW, "PRACH CP: Invalid numOfSections %d (expected 1)\n", hdr->cmnhdr.numOfSections); });
return;
}
@@ -550,11 +569,14 @@ void handle_prach_cplane_packet(oru_packet_processor_context_t *ctx,
struct xran_cp_radioapp_section3 *section = (void *)rte_pktmbuf_adj(pkt, sizeof(struct xran_cp_radioapp_section3_header));
if (section == NULL) {
ctx->stats.cplane_err_hdr++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, { LOG_W(HW, "PRACH CP: Failed to adjust mbuf for section3 header\n"); });
return;
}
*((uint64_t *)section) = rte_be_to_cpu_64(*((uint64_t *)section));
int aarx = ant_id - ctx->prach_eaxc_offset;
if (aarx < 0 || aarx >= MAX_ANTENNAS) {
RATELIMIT(PRACH_ERR_LOG_RATELIMIT,
{ LOG_W(HW, "PRACH CP: Invalid aarx %d (ant_id %d, eaxc_offset %d)\n", aarx, ant_id, ctx->prach_eaxc_offset); });
return;
}
@@ -574,26 +596,34 @@ void handle_prach_cplane_packet(oru_packet_processor_context_t *ctx,
txrx_window_histogram_count(&ctx->stats.prach_cplane_hist, diff);
uint64_t target_absolute_symbol = ctx->current_absolute_symbol + diff;
for (int i = 0; i < num_symbols; i++) {
uint32_t job_index = (target_absolute_symbol + i) % NUM_CONCURRENT_UL_SYMBOL_WINDOWS;
prach_symbol_job_t *job = &ctx->prach_jobs[job_index];
if (job->absolute_symbol != target_absolute_symbol + i) {
job->absolute_symbol = target_absolute_symbol + i;
for (int j = 0; j < MAX_ANTENNAS; j++) {
job->per_antenna[j].cplane_received = false;
}
}
if (job->per_antenna[aarx].cplane_received) {
continue;
}
job->per_antenna[aarx].cplane_received = true;
job->per_antenna[aarx].section_id = section->hdr.u1.common.sectionId;
job->per_antenna[aarx].num_prb = section->hdr.u1.common.numPrbc == 0 ? ctx->num_prb : section->hdr.u1.common.numPrbc;
job->per_antenna[aarx].start_prb = section->hdr.u1.common.startPrbc;
job->per_antenna[aarx].filter_id = hdr->cmnhdr.field.filterIndex;
if (slot_in_frame < 0 || slot_in_frame >= MAX_SLOTS_PER_FRAME) {
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, { LOG_W(HW, "PRACH CP: Invalid slot_in_frame %d\n", slot_in_frame); });
return;
}
prach_job_t *job = &ctx->prach_jobs[slot_in_frame][aarx];
if (job->active && job->start_absolute_symbol == target_absolute_symbol) {
ctx->stats.cplane_err_dup++;
ctx->stats.cplane_err_dup_prach++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, {
LOG_W(HW, "PRACH CP: Duplicate packet for slot %d, aarx %d, start_symbol %lu\n", slot_in_frame, aarx, target_absolute_symbol);
});
return;
}
job->active = true;
job->start_absolute_symbol = target_absolute_symbol;
job->num_symbols = num_symbols;
job->section_id = section->hdr.u1.common.sectionId;
job->num_prb = section->hdr.u1.common.numPrbc == 0 ? ctx->num_prb : section->hdr.u1.common.numPrbc;
job->start_prb = section->hdr.u1.common.startPrbc;
job->filter_id = hdr->cmnhdr.field.filterIndex;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, {
LOG_A(HW,
"PRACH JOB added slot_in_frame %d, aarx %d target_absolute_symbol %lu\n",
slot_in_frame,
aarx,
target_absolute_symbol);
});
}
void handle_cplane_packet(void *context, void *pkt)
@@ -633,20 +663,24 @@ void handle_cplane_packet(void *context, void *pkt)
}
*((uint64_t *)section) = rte_be_to_cpu_64(*((uint64_t *)section));
if (hdr->cmnhdr.field.dataDirection == XRAN_DIR_DL) {
ctx->stats.cplane_received_dl++;
handle_dl_cplane_packet(ctx, pkt, hdr, section, ant_id);
} else {
ctx->stats.cplane_received_ul++;
handle_ul_cplane_packet(ctx, pkt, hdr, section, ant_id);
}
rte_pktmbuf_free(pkt);
return;
}
case XRAN_CP_SECTIONTYPE_3: {
ctx->stats.cplane_received_prach++;
struct xran_cp_radioapp_section3_header *hdr = (struct xran_cp_radioapp_section3_header *)apphdr;
handle_prach_cplane_packet(ctx, pkt, hdr, ant_id);
rte_pktmbuf_free(pkt);
return;
}
default:
ctx->stats.cplane_received_other++;
rte_pktmbuf_free(pkt);
return;
}
@@ -656,19 +690,38 @@ static void print_histogram(const char *name, txrx_histogram_t *hist, uint32_t w
{
if (hist->count == 0)
return;
printf(" %s (mean: %.2f symbols) window start: %u, end %u:\n", name, (double)hist->sum / hist->count, window_start, window_end);
char buf[4096];
int len = snprintf(buf,
sizeof(buf),
" %s (mean: %.2f symbols) window [%u, %u]:",
name,
(double)hist->sum / hist->count,
window_start,
window_end);
bool first = true;
for (int i = 0; i < HIST_SIZE; i++) {
if (hist->hist[i] > 0) {
int bucket = i - HIST_SIZE / 2;
char bin_str[64];
int bin_len = 0;
if (i == 0) {
printf(" <= %+d : %lu\n", bucket, hist->hist[i]);
bin_len = snprintf(bin_str, sizeof(bin_str), "%s<=%+d:%lu", first ? " " : ", ", bucket, hist->hist[i]);
} else if (i == HIST_SIZE - 1) {
printf(" >= %+d : %lu\n", bucket, hist->hist[i]);
bin_len = snprintf(bin_str, sizeof(bin_str), "%s>=%+d:%lu", first ? " " : ", ", bucket, hist->hist[i]);
} else {
printf(" %+d : %lu\n", bucket, hist->hist[i]);
bin_len = snprintf(bin_str, sizeof(bin_str), "%s%+d:%lu", first ? " " : ", ", bucket, hist->hist[i]);
}
first = false;
if (len + bin_len < sizeof(buf)) {
strcpy(buf + len, bin_str);
len += bin_len;
} else {
break; // buffer full
}
}
}
LOG_I(HW, "%s\n", buf);
memset(hist, 0, sizeof(*hist));
}
void print_packet_processor_stats(void *context)
@@ -677,39 +730,73 @@ void print_packet_processor_stats(void *context)
if (ctx == NULL)
return;
printf("ORU Packet Processor Stats:\n");
printf(" Total C-Plane Packets received: %lu\n", ctx->stats.total_cplane);
printf(" Total U-Plane Packets received: %lu\n", ctx->stats.total_uplane_received);
printf(" Total U-Plane Packets sent: %lu\n", ctx->thread_safe_stats.total_uplane_sent);
LOG_I(HW, "ORU Packet Processor Stats:\n");
LOG_I(HW,
" Total C-Plane Packets received: %lu (DL: %lu, UL: %lu, PRACH: %lu, Other: %lu)\n",
ctx->stats.total_cplane,
ctx->stats.cplane_received_dl,
ctx->stats.cplane_received_ul,
ctx->stats.cplane_received_prach,
ctx->stats.cplane_received_other);
LOG_I(HW, " Total U-Plane Packets received: %lu\n", ctx->stats.total_uplane_received);
LOG_I(HW, " Total U-Plane Packets sent: %lu\n", ctx->thread_safe_stats.total_uplane_sent);
if (ctx->thread_safe_stats.ul_uplane_ota_delay_count > 0)
LOG_I(HW,
" UL U-Plane OTA delay (mean symbols): %.2f (%lu packets)\n",
(double)(int64_t)ctx->thread_safe_stats.ul_uplane_ota_delay_sum
/ (double)ctx->thread_safe_stats.ul_uplane_ota_delay_count,
(uint64_t)ctx->thread_safe_stats.ul_uplane_ota_delay_count);
if (ctx->stats.cplane_err_hdr > 0)
printf(" C-Plane Header Errors: %lu\n", ctx->stats.cplane_err_hdr);
LOG_I(HW, " C-Plane Header Errors: %lu\n", ctx->stats.cplane_err_hdr);
if (ctx->stats.cplane_err_ver > 0)
printf(" C-Plane Protocol Version Errors: %lu\n", ctx->stats.cplane_err_ver);
LOG_I(HW, " C-Plane Protocol Version Errors: %lu\n", ctx->stats.cplane_err_ver);
if (ctx->stats.cplane_err_early > 0)
printf(" C-Plane Timing Early Errors: %lu\n", ctx->stats.cplane_err_early);
LOG_I(HW, " C-Plane Timing Early Errors: %lu\n", ctx->stats.cplane_err_early);
if (ctx->stats.cplane_err_late > 0)
printf(" C-Plane Timing Late Errors: %lu\n", ctx->stats.cplane_err_late);
LOG_I(HW, " C-Plane Timing Late Errors: %lu\n", ctx->stats.cplane_err_late);
if (ctx->stats.cplane_err_dup > 0)
printf(" C-Plane Duplicate Packet Errors: %lu\n", ctx->stats.cplane_err_dup);
LOG_I(HW,
" C-Plane Duplicate Packet Errors: %lu (DL: %lu, UL: %lu, PRACH: %lu)\n",
ctx->stats.cplane_err_dup,
ctx->stats.cplane_err_dup_dl,
ctx->stats.cplane_err_dup_ul,
ctx->stats.cplane_err_dup_prach);
if (ctx->stats.uplane_err_early > 0)
printf(" U-Plane Timing Early Errors: %lu\n", ctx->stats.uplane_err_early);
LOG_I(HW, " U-Plane Timing Early Errors: %lu\n", ctx->stats.uplane_err_early);
if (ctx->stats.uplane_err_late > 0)
printf(" U-Plane Timing Late Errors: %lu\n", ctx->stats.uplane_err_late);
LOG_I(HW, " U-Plane Timing Late Errors: %lu\n", ctx->stats.uplane_err_late);
if (ctx->stats.uplane_err_dup > 0)
printf(" U-Plane Duplicate Packet Errors: %lu\n", ctx->stats.uplane_err_dup);
LOG_I(HW, " U-Plane Duplicate Packet Errors: %lu\n", ctx->stats.uplane_err_dup);
if (ctx->stats.uplane_missing_cplane > 0)
printf(" U-Plane Missing C-Plane Errors: %lu\n", ctx->stats.uplane_missing_cplane);
LOG_I(HW, " U-Plane Missing C-Plane Errors: %lu\n", ctx->stats.uplane_missing_cplane);
if (ctx->stats.dl_tdd_mismatch + ctx->thread_safe_stats.dl_tdd_mismatch > 0)
printf(" DL TDD Mismatch Errors: %lu\n", ctx->stats.dl_tdd_mismatch + ctx->thread_safe_stats.dl_tdd_mismatch);
LOG_I(HW, " DL TDD Mismatch Errors: %lu\n", ctx->stats.dl_tdd_mismatch + ctx->thread_safe_stats.dl_tdd_mismatch);
if (ctx->stats.ul_tdd_mismatch + ctx->thread_safe_stats.ul_tdd_mismatch > 0)
printf(" UL TDD Mismatch Errors: %lu\n", ctx->stats.ul_tdd_mismatch + ctx->thread_safe_stats.ul_tdd_mismatch);
LOG_I(HW, " UL TDD Mismatch Errors: %lu\n", ctx->stats.ul_tdd_mismatch + ctx->thread_safe_stats.ul_tdd_mismatch);
if (ctx->stats.ul_cplane_missing + ctx->thread_safe_stats.ul_cplane_missing > 0)
printf(" UL C-Plane Missing Errors: %lu\n", ctx->stats.ul_cplane_missing + ctx->thread_safe_stats.ul_cplane_missing);
LOG_I(HW, " UL C-Plane Missing Errors: %lu\n", ctx->stats.ul_cplane_missing + ctx->thread_safe_stats.ul_cplane_missing);
if (ctx->stats.prach_cplane_missing + ctx->thread_safe_stats.prach_cplane_missing > 0)
LOG_I(HW,
" PRACH C-Plane Missing Errors: %lu (Never Received: %lu, Stale: %lu, Early: %lu)\n",
ctx->stats.prach_cplane_missing + ctx->thread_safe_stats.prach_cplane_missing,
ctx->stats.prach_cplane_missing_inactive + ctx->thread_safe_stats.prach_cplane_missing_inactive,
ctx->stats.prach_cplane_missing_stale + ctx->thread_safe_stats.prach_cplane_missing_stale,
ctx->stats.prach_cplane_missing_early + ctx->thread_safe_stats.prach_cplane_missing_early);
if (ctx->stats.prach_cplane_missing_ant + ctx->thread_safe_stats.prach_cplane_missing_ant > 0)
LOG_I(HW,
" PRACH Ant C-Plane Missing Errors: %lu\n",
ctx->stats.prach_cplane_missing_ant + ctx->thread_safe_stats.prach_cplane_missing_ant);
if (ctx->stats.prach_out_of_mbufs + ctx->thread_safe_stats.prach_out_of_mbufs > 0)
LOG_I(HW, " PRACH Out Of Mbufs Errors: %lu\n", ctx->stats.prach_out_of_mbufs + ctx->thread_safe_stats.prach_out_of_mbufs);
if (ctx->stats.prach_jobs_pool_exhausted + ctx->thread_safe_stats.prach_jobs_pool_exhausted > 0)
LOG_I(HW,
" PRACH Jobs Pool Exhausted Errors: %lu\n",
ctx->stats.prach_jobs_pool_exhausted + ctx->thread_safe_stats.prach_jobs_pool_exhausted);
if (ctx->stats.out_of_mbufs + ctx->thread_safe_stats.out_of_mbufs > 0)
printf(" Out Of Mbufs Errors: %lu\n", ctx->stats.out_of_mbufs + ctx->thread_safe_stats.out_of_mbufs);
LOG_I(HW, " Out Of Mbufs Errors: %lu\n", ctx->stats.out_of_mbufs + ctx->thread_safe_stats.out_of_mbufs);
if (ctx->stats.application_too_slow > 0)
printf(" Application Too Slow Errors: %lu\n", ctx->stats.application_too_slow);
LOG_I(HW, " Application Too Slow Errors: %lu\n", ctx->stats.application_too_slow);
print_histogram("DL C-Plane", &ctx->stats.dl_cplane_hist, ctx->T2a_max_cp_sym_diff, ctx->T2a_min_cp_sym_diff);
print_histogram("DL U-Plane", &ctx->stats.dl_uplane_hist, ctx->T2a_max_up_dl_sym_diff, ctx->T2a_min_up_dl_sym_diff);
@@ -725,8 +812,17 @@ void get_packet_processor_stats(void *context, oru_packet_processor_stats_t *out
out_stats->dl_tdd_mismatch += ctx->thread_safe_stats.dl_tdd_mismatch;
out_stats->ul_tdd_mismatch += ctx->thread_safe_stats.ul_tdd_mismatch;
out_stats->ul_cplane_missing += ctx->thread_safe_stats.ul_cplane_missing;
out_stats->prach_cplane_missing += ctx->thread_safe_stats.prach_cplane_missing;
out_stats->prach_cplane_missing_ant += ctx->thread_safe_stats.prach_cplane_missing_ant;
out_stats->prach_cplane_missing_inactive += ctx->thread_safe_stats.prach_cplane_missing_inactive;
out_stats->prach_cplane_missing_stale += ctx->thread_safe_stats.prach_cplane_missing_stale;
out_stats->prach_cplane_missing_early += ctx->thread_safe_stats.prach_cplane_missing_early;
out_stats->prach_out_of_mbufs += ctx->thread_safe_stats.prach_out_of_mbufs;
out_stats->prach_jobs_pool_exhausted += ctx->thread_safe_stats.prach_jobs_pool_exhausted;
out_stats->out_of_mbufs += ctx->thread_safe_stats.out_of_mbufs;
out_stats->total_uplane_sent = ctx->thread_safe_stats.total_uplane_sent;
out_stats->ul_uplane_ota_delay_sum += ctx->thread_safe_stats.ul_uplane_ota_delay_sum;
out_stats->ul_uplane_ota_delay_count += ctx->thread_safe_stats.ul_uplane_ota_delay_count;
}
}
@@ -739,7 +835,7 @@ static void unpack_iq(c16_t *txdataF, void *iqdata, int start_prb, int num_prb)
}
}
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol)
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol)
{
oru_packet_processor_context_t *ctx = (oru_packet_processor_context_t *)context;
if (ctx == NULL)
@@ -754,6 +850,7 @@ void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, int *frame, int *s
uint64_t absolute_gps_symbol = job->absolute_symbol;
int numerology = ctx->numerology;
int num_symbols_per_frame = NR_NUMBER_OF_SUBFRAMES_PER_FRAME * (1 << numerology) * NR_SYMBOLS_PER_SLOT;
*hyper_frame = (absolute_gps_symbol / num_symbols_per_frame) / 1024;
*frame = (absolute_gps_symbol / num_symbols_per_frame) % 1024;
*slot = (absolute_gps_symbol % num_symbols_per_frame) / NR_SYMBOLS_PER_SLOT;
*symbol = absolute_gps_symbol % NR_SYMBOLS_PER_SLOT;
@@ -834,11 +931,142 @@ void fill_data_section_header(struct data_section_hdr *data_section_hdr, int num
data_section_hdr->fields.all_bits = rte_cpu_to_be_32(data_section_hdr->fields.all_bits);
}
void write_ul_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int slot_in_frame, int symbol)
int poll_ul_job(void *context, ul_job_t *job)
{
oru_packet_processor_context_t *ctx = (oru_packet_processor_context_t *)context;
if (ctx == NULL || job == NULL) {
return -1;
}
ul_job_t *dequeued_job = NULL;
if (rte_ring_dequeue(ctx->ul_ready_jobs, (void **)&dequeued_job) == 0) {
*job = *dequeued_job;
rte_ring_enqueue(ctx->ul_free_jobs, (void *)dequeued_job);
return 0;
}
return -1;
}
void write_ul_iq(void *context, uint32_t *rxdataF, int symbol, const ul_job_t *job)
{
oru_packet_processor_context_t *ctx = (oru_packet_processor_context_t *)context;
if (ctx == NULL || job == NULL)
return;
AssertFatal(symbol >= job->symbol && symbol < job->symbol + job->num_symbols && symbol < NR_SYMBOLS_PER_SLOT,
"Symbol %d outside of job range [%d, %d)\n",
symbol,
job->symbol,
job->symbol + job->num_symbols);
// Delay from OTA symbol to packet send: current timer symbol minus the absolute symbol of this UL symbol.
const int slots_per_frame = NR_NUMBER_OF_SUBFRAMES_PER_FRAME * (1 << ctx->numerology);
const uint64_t ota_absolute_symbol = (uint64_t)job->hyper_frame * 1024ULL * slots_per_frame * NR_SYMBOLS_PER_SLOT
+ (uint64_t)job->frame * slots_per_frame * NR_SYMBOLS_PER_SLOT
+ (uint64_t)job->slot_in_frame * NR_SYMBOLS_PER_SLOT
+ (uint64_t)symbol;
int64_t delay = (int64_t)ctx->current_absolute_symbol - (int64_t)ota_absolute_symbol;
atomic_fetch_add_explicit(&ctx->thread_safe_stats.ul_uplane_ota_delay_sum, delay, memory_order_relaxed);
atomic_fetch_add_explicit(&ctx->thread_safe_stats.ul_uplane_ota_delay_count, 1, memory_order_relaxed);
int aarx = job->antenna_id;
if (aarx < 0 || aarx >= MAX_ANTENNAS) {
LOG_W(HW, "ORU: Invalid antenna index %d\n", aarx);
return;
}
const bool use_comp = (job->response_payload.comp_method != 0);
int section_id = job->response_payload.section_id;
int total_ul_rbs = job->num_prb;
int start_prb_base = job->start_prb;
int frame = job->frame;
int slot_in_frame = job->slot_in_frame;
int mu = ctx->numerology;
struct rte_mbuf *mbufs[MAX_MBUFS_PER_SYMBOL];
uint32_t num_mbufs = 0;
int rbs_sent = 0;
size_t overhead = sizeof(struct rte_ether_hdr) + sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr)
+ sizeof(struct data_section_hdr);
if (use_comp) {
overhead += sizeof(struct data_section_compression_hdr);
}
int max_prb_per_packet = (int)((ctx->mtu - overhead) / (NR_NB_SC_PER_RB * sizeof(int32_t)));
while (rbs_sent < total_ul_rbs) {
int num_ul_rbs = total_ul_rbs - rbs_sent;
if (num_ul_rbs > max_prb_per_packet) {
num_ul_rbs = max_prb_per_packet;
}
struct rte_mbuf *pkt = ctx->alloc_func(ctx->io_controller);
if (pkt == NULL) {
ctx->thread_safe_stats.out_of_mbufs++;
break;
}
size_t header_length = sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr) + sizeof(struct data_section_hdr);
if (use_comp) {
header_length += sizeof(struct data_section_compression_hdr);
}
const uint num_sc = num_ul_rbs * NR_NB_SC_PER_RB;
size_t data_len = sizeof(int32_t) * num_sc;
char *buf = rte_pktmbuf_append(pkt, (uint16_t)(header_length + data_len));
if (buf == NULL) {
LOG_W(HW, "ORU: Failed to append data to mbuf (insufficient space)\n");
rte_pktmbuf_free(pkt);
break;
}
if (num_mbufs == (MAX_MBUFS_PER_SYMBOL - 1)) {
ctx->send_func(ctx->io_controller, mbufs, num_mbufs);
ctx->thread_safe_stats.total_uplane_sent += num_mbufs;
num_mbufs = 0;
}
mbufs[num_mbufs++] = pkt;
struct xran_ecpri_hdr *ecpri_header = (struct xran_ecpri_hdr *)buf;
uint16_t ecpri_payload_size = (uint16_t)(header_length - 4 + data_len);
fill_ecpri_header(ecpri_header, &ctx->eaxcid_config, ECPRI_IQ_DATA, ecpri_payload_size, 0, aarx, ctx->pusch_seq_id[aarx]++, 0);
struct radio_app_common_hdr *radio_app_header = (struct radio_app_common_hdr *)(ecpri_header + 1);
fill_radio_app_header(radio_app_header, 0, XRAN_DIR_UL, frame, slot_in_frame, symbol, mu);
struct data_section_hdr *data_section_header = (struct data_section_hdr *)(radio_app_header + 1);
fill_data_section_header(data_section_header, num_ul_rbs, start_prb_base + rbs_sent, section_id);
void *iq_data_start;
if (use_comp) {
struct data_section_compression_hdr *compression_header = (struct data_section_compression_hdr *)(data_section_header + 1);
compression_header->ud_comp_hdr.ud_comp_meth = job->response_payload.comp_method;
compression_header->ud_comp_hdr.ud_iq_width = XRAN_CONVERT_IQWIDTH(job->response_payload.iq_width);
compression_header->rsrvd = 0;
iq_data_start = (void *)(compression_header + 1);
} else {
iq_data_start = (void *)(data_section_header + 1);
}
uint16_t *src = (uint16_t *)&rxdataF[(start_prb_base + rbs_sent) * NR_NB_SC_PER_RB];
uint16_t *dst = (uint16_t *)iq_data_start;
for (int i = 0; i < num_sc * 2; i++) {
*dst++ = rte_cpu_to_be_16(*src++);
}
rbs_sent += num_ul_rbs;
}
if (num_mbufs > 0) {
ctx->send_func(ctx->io_controller, mbufs, num_mbufs);
ctx->thread_safe_stats.total_uplane_sent += num_mbufs;
}
}
void write_prach_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int slot_in_frame, int symbol)
{
oru_packet_processor_context_t *ctx = (oru_packet_processor_context_t *)context;
if (ctx == NULL)
return;
int numerology = ctx->numerology;
int num_symbols_per_frame = NR_NUMBER_OF_SUBFRAMES_PER_FRAME * (1 << numerology) * NR_SYMBOLS_PER_SLOT;
uint32_t current_symbol_in_frame = ctx->current_absolute_symbol % num_symbols_per_frame;
@@ -850,135 +1078,56 @@ void write_ul_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int sl
diff -= num_symbols_per_frame;
}
uint64_t target_absolute_symbol = ctx->current_absolute_symbol + diff;
bool is_ul_symbol = test_bit(ctx->ul_symbol_bitmask, target_absolute_symbol % ctx->symbol_bitmask_length);
if (!is_ul_symbol) {
ctx->thread_safe_stats.ul_tdd_mismatch++;
return;
}
ul_symbol_job_t *job = &ctx->ul_symbol_jobs[target_absolute_symbol % NUM_CONCURRENT_UL_SYMBOL_WINDOWS];
if (job->state == SYM_UL_IDLE) {
ctx->thread_safe_stats.ul_cplane_missing++;
return;
}
int mu = ctx->numerology;
struct rte_mbuf *mbufs[MAX_MBUFS_PER_SYMBOL];
uint32_t num_mbufs = 0;
for (int aarx = 0; aarx < nb_rx; aarx++) {
if (!job->per_antenna[aarx].cplane_received) {
ctx->thread_safe_stats.ul_cplane_missing++;
continue;
}
int section_id = job->per_antenna[aarx].section_id;
int total_ul_rbs = job->per_antenna[aarx].num_prb;
int start_prb_base = job->per_antenna[aarx].start_prb;
int rbs_sent = 0;
size_t overhead = sizeof(struct rte_ether_hdr) + sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr)
+ sizeof(struct data_section_hdr) + sizeof(struct data_section_compression_hdr);
int max_prb_per_packet = (int)((ctx->mtu - overhead) / (NR_NB_SC_PER_RB * sizeof(int32_t)));
while (rbs_sent < total_ul_rbs) {
int num_ul_rbs = total_ul_rbs - rbs_sent;
if (num_ul_rbs > max_prb_per_packet) {
num_ul_rbs = max_prb_per_packet;
}
struct rte_mbuf *pkt = ctx->alloc_func(ctx->io_controller);
if (pkt == NULL) {
ctx->thread_safe_stats.out_of_mbufs++;
break;
}
size_t header_length = sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr) + sizeof(struct data_section_hdr)
+ sizeof(struct data_section_compression_hdr);
const uint num_sc = num_ul_rbs * NR_NB_SC_PER_RB;
size_t data_len = sizeof(int32_t) * num_sc;
char *buf = rte_pktmbuf_append(pkt, (uint16_t)(header_length + data_len));
if (buf == NULL) {
LOG_W(HW, "ORU: Failed to append data to mbuf (insufficient space)\n");
rte_pktmbuf_free(pkt);
// Do not increment rbs_sent, just break and let the next symbol or antenna be processed,
// or we could reduce max_prb_per_packet. Breaking is safer to prevent infinite loop.
break;
}
if (num_mbufs == (MAX_MBUFS_PER_SYMBOL - 1)) {
ctx->send_func(ctx->io_controller, mbufs, num_mbufs);
ctx->thread_safe_stats.total_uplane_sent += num_mbufs;
num_mbufs = 0;
}
mbufs[num_mbufs++] = pkt;
struct xran_ecpri_hdr *ecpri_header = (struct xran_ecpri_hdr *)buf;
// eCPRI payload size excludes the 4-byte common header
uint16_t ecpri_payload_size = (uint16_t)(header_length - 4 + data_len);
fill_ecpri_header(ecpri_header,
&ctx->eaxcid_config,
ECPRI_IQ_DATA,
ecpri_payload_size,
0,
aarx,
ctx->pusch_seq_id[aarx]++,
0);
struct radio_app_common_hdr *radio_app_header = (struct radio_app_common_hdr *)(ecpri_header + 1);
fill_radio_app_header(radio_app_header, 0, XRAN_DIR_UL, frame, slot_in_frame, symbol, mu);
struct data_section_hdr *data_section_header = (struct data_section_hdr *)(radio_app_header + 1);
fill_data_section_header(data_section_header, num_ul_rbs, start_prb_base + rbs_sent, section_id);
struct data_section_compression_hdr *compression_header = (struct data_section_compression_hdr *)(data_section_header + 1);
compression_header->ud_comp_hdr.ud_comp_meth = 0;
compression_header->ud_comp_hdr.ud_iq_width = XRAN_CONVERT_IQWIDTH(16);
compression_header->rsrvd = 0;
void *iq_data_start = (void *)(compression_header + 1);
uint16_t *src = (uint16_t *)&txdataF[aarx][(start_prb_base + rbs_sent) * NR_NB_SC_PER_RB];
uint16_t *dst = (uint16_t *)iq_data_start;
for (int i = 0; i < num_sc * 2; i++) {
*dst++ = rte_cpu_to_be_16(*src++);
}
rbs_sent += num_ul_rbs;
}
}
ctx->send_func(ctx->io_controller, mbufs, num_mbufs);
ctx->thread_safe_stats.total_uplane_sent += num_mbufs;
for (int i = 0; i < MAX_ANTENNAS; i++) {
job->per_antenna[i].cplane_received = false;
}
job->state = SYM_UL_IDLE;
}
void write_prach_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int slot_in_frame, int symbol)
{
oru_packet_processor_context_t *ctx = (oru_packet_processor_context_t *)context;
if (ctx == NULL)
return;
int mu = ctx->numerology;
uint64_t absolute_symbol = (frame * NR_NUMBER_OF_SUBFRAMES_PER_FRAME * (1 << mu) + slot_in_frame) * NR_SYMBOLS_PER_SLOT + symbol;
uint32_t job_index = absolute_symbol % NUM_CONCURRENT_UL_SYMBOL_WINDOWS;
prach_symbol_job_t *job = &ctx->prach_jobs[job_index];
if (job->absolute_symbol != absolute_symbol)
return;
struct rte_mbuf *mbufs[MAX_MBUFS_PER_SYMBOL];
uint32_t num_mbufs = 0;
for (int aarx = 0; aarx < nb_rx; aarx++) {
if (!job->per_antenna[aarx].cplane_received)
if (slot_in_frame < 0 || slot_in_frame >= MAX_SLOTS_PER_FRAME) {
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, { LOG_W(HW, "PRACH UP: Invalid slot_in_frame %d\n", slot_in_frame); });
continue;
}
prach_job_t *job = &ctx->prach_jobs[slot_in_frame][aarx];
if (!job->active || target_absolute_symbol < job->start_absolute_symbol
|| target_absolute_symbol >= job->start_absolute_symbol + job->num_symbols) {
ctx->thread_safe_stats.prach_cplane_missing++;
if (!job->active) {
ctx->thread_safe_stats.prach_cplane_missing_inactive++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT,
{ LOG_W(HW, "PRACH UP: Missing C-Plane - Inactive job for slot %d, aarx %d\n", slot_in_frame, aarx); });
} else if (target_absolute_symbol < job->start_absolute_symbol) {
ctx->thread_safe_stats.prach_cplane_missing_early++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, {
LOG_W(HW,
"PRACH UP: Missing C-Plane - Early symbol %lu (job start %lu) for slot %d, aarx %d\n",
target_absolute_symbol,
job->start_absolute_symbol,
slot_in_frame,
aarx);
});
} else {
ctx->thread_safe_stats.prach_cplane_missing_stale++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, {
LOG_W(HW,
"PRACH UP: Missing C-Plane - Stale symbol %lu (job end %lu) for slot %d, aarx %d\n",
target_absolute_symbol,
job->start_absolute_symbol + job->num_symbols,
slot_in_frame,
aarx);
});
}
continue;
}
int section_id = job->per_antenna[aarx].section_id;
int num_ul_rbs = job->per_antenna[aarx].num_prb;
int start_prb = job->per_antenna[aarx].start_prb;
int filter_id = job->per_antenna[aarx].filter_id;
int section_id = job->section_id;
int num_ul_rbs = job->num_prb;
int start_prb = job->start_prb;
int filter_id = job->filter_id;
struct rte_mbuf *pkt = ctx->alloc_func(ctx->io_controller);
if (pkt == NULL) {
ctx->thread_safe_stats.prach_out_of_mbufs++;
ctx->thread_safe_stats.out_of_mbufs++;
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, { LOG_W(HW, "PRACH UP: Failed to allocate mbuf\n"); });
continue;
}
@@ -988,7 +1137,10 @@ void write_prach_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int
char *buf = rte_pktmbuf_append(pkt, (uint16_t)(header_length + data_len));
if (buf == NULL) {
ctx->thread_safe_stats.prach_out_of_mbufs++;
ctx->thread_safe_stats.out_of_mbufs++;
rte_pktmbuf_free(pkt);
RATELIMIT(PRACH_ERR_LOG_RATELIMIT, { LOG_W(HW, "PRACH UP: Failed to append data to mbuf\n"); });
continue;
}
@@ -1007,7 +1159,7 @@ void write_prach_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int
0);
struct radio_app_common_hdr *radio_app_header = (struct radio_app_common_hdr *)(ecpri_header + 1);
fill_radio_app_header(radio_app_header, filter_id, XRAN_DIR_UL, frame, slot_in_frame, symbol, mu);
fill_radio_app_header(radio_app_header, filter_id, XRAN_DIR_UL, frame, slot_in_frame, symbol, numerology);
struct data_section_hdr *data_section_header = (struct data_section_hdr *)(radio_app_header + 1);
fill_data_section_header(data_section_header, num_ul_rbs, start_prb, section_id);
@@ -1018,8 +1170,6 @@ void write_prach_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int
for (int i = 0; i < prach_length * 2; i++) {
*dst++ = rte_cpu_to_be_16(*src++);
}
job->per_antenna[aarx].cplane_received = false;
}
if (num_mbufs > 0) {

View File

@@ -2,6 +2,8 @@
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
@@ -17,8 +19,30 @@ typedef struct {
uint64_t count;
} txrx_histogram_t;
typedef struct {
int section_id;
int comp_method;
int iq_width;
} opaque_response_data_t;
typedef struct {
opaque_response_data_t response_payload;
uint64_t hyper_frame;
int frame;
int slot_in_frame;
int symbol;
int num_symbols;
int antenna_id;
int start_prb;
int num_prb;
} ul_job_t;
typedef struct {
uint64_t total_cplane;
uint64_t cplane_received_dl;
uint64_t cplane_received_ul;
uint64_t cplane_received_prach;
uint64_t cplane_received_other;
uint64_t total_uplane_received;
uint64_t total_uplane_sent;
uint64_t cplane_err_hdr; // apphdr or section extraction
@@ -26,6 +50,9 @@ typedef struct {
uint64_t cplane_err_early;
uint64_t cplane_err_late;
uint64_t cplane_err_dup; // duplicate cplane
uint64_t cplane_err_dup_dl;
uint64_t cplane_err_dup_ul;
uint64_t cplane_err_dup_prach;
uint64_t uplane_err_late;
uint64_t uplane_err_early;
uint64_t uplane_err_dup;
@@ -35,10 +62,19 @@ typedef struct {
uint64_t ul_tdd_mismatch;
uint64_t out_of_mbufs;
uint64_t ul_cplane_missing;
uint64_t prach_cplane_missing;
uint64_t prach_cplane_missing_ant;
uint64_t prach_cplane_missing_inactive;
uint64_t prach_cplane_missing_stale;
uint64_t prach_cplane_missing_early;
uint64_t prach_out_of_mbufs;
uint64_t prach_jobs_pool_exhausted;
txrx_histogram_t dl_uplane_hist;
txrx_histogram_t dl_cplane_hist;
txrx_histogram_t ul_cplane_hist;
txrx_histogram_t prach_cplane_hist;
int64_t ul_uplane_ota_delay_sum;
uint64_t ul_uplane_ota_delay_count;
} oru_packet_processor_stats_t;
typedef void *(*alloc_func_t)(void *io_controller);
@@ -60,7 +96,7 @@ void *init_packet_processor(int numerology,
void *io_controller,
size_t mtu,
int prach_eaxc_offset);
void write_ul_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int slot_in_frame, int symbol);
void write_ul_iq(void *context, uint32_t *rxdataF, int symbol, const ul_job_t *job);
void write_prach_iq(void *context, uint32_t **txdataF, int nb_rx, int frame, int slot_in_frame, int symbol);
void cleanup_packet_processor(void *context);
void handle_absolute_symbol_tick(void *context, uint64_t absolute_symbol);
@@ -68,8 +104,9 @@ void handle_uplane_packet(void *context, void *pkt);
void handle_cplane_packet(void *context, void *pkt);
void print_packet_processor_stats(void *context);
void get_packet_processor_stats(void *context, oru_packet_processor_stats_t *out_stats);
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, int *frame, int *slot, int *symbol);
void read_dl_iq(void *context, uint32_t **txdataF, int nb_tx, uint64_t *hyper_frame, int *frame, int *slot, int *symbol);
int get_ready_job_count(void *context);
int poll_ul_job(void *context, ul_job_t *job);
#ifdef __cplusplus
}

View File

@@ -11,6 +11,7 @@
#include <unistd.h>
#include "common/config/config_userapi.h"
#include <rte_eal.h>
#include <assert.h>
// OAI Linkage Satisfiers
void exit_function(const char *file, const char *function, const int line, const char *s, const int assertflag)
@@ -84,20 +85,32 @@ int main(int argc, char **argv)
return 1;
}
// Testing UTC Anchor Point and Hyper-frame
printf("Testing UTC Anchor Point and Hyper-frame...\n");
uint64_t hf;
uint32_t f, s;
struct timespec ts;
if (oru_fh_get_utc_anchor_point(handle, &hf, &f, &s, &ts) < 0) {
fprintf(stderr, "FAIL: oru_fh_get_utc_anchor_point failed\n");
oru_fh_cleanup(handle);
return 1;
}
printf("UTC Anchor Point: hf=%lu, f=%u, s=%u, ts=%ld.%09ld\n", hf, f, s, ts.tv_sec, ts.tv_nsec);
assert(f < 1024);
assert(s < (10 << cfg.numerology));
printf("Running live loop for 2 seconds...\n");
uint32_t *txData[1];
txData[0] = malloc(273 * 12 * sizeof(uint32_t));
for (int i = 0; i < 2000; i++) {
if (i % 400 == 0) {
oru_fh_rx_send_pusch(handle, txData, 1, 0, i / 400, 0);
}
uint64_t start_cycles = rte_get_timer_cycles();
uint64_t target_cycles = start_cycles + (rte_get_timer_hz() / 1000);
while (rte_get_timer_cycles() < target_cycles) {
int f, s, sym;
uint64_t hf;
while (oru_fh_get_ready_jobs(handle) > 0) {
oru_fh_tx_read_symbol(handle, txData, 1, &f, &s, &sym);
oru_fh_tx_read_symbol(handle, txData, 1, &hf, &f, &s, &sym);
}
}
}

View File

@@ -235,8 +235,9 @@ void test_cplane_uplane_match()
txdataF[0] = output_iq;
int frame, slot, symbol;
uint64_t hyper_frame;
do {
read_dl_iq(ctx, txdataF, 1, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (target_sym / num_symbols_per_frame) % 1024 && symbol == target_sym % 14));
assert(symbol == target_sym % 14);
@@ -343,8 +344,9 @@ void test_frame_wrap_around()
txdataF[0] = output_iq;
int frame, slot, symbol;
uint64_t hyper_frame;
do {
read_dl_iq(ctx, txdataF, 1, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (target_sym / num_symbols_per_frame) % 1024 && symbol == target_sym % 14));
assert(frame == (target_sym / num_symbols_per_frame) % 1024);
@@ -456,9 +458,10 @@ void test_cplane_14_symbols()
for (int i = 0; i < 14; i++) {
int frame, slot, symbol;
uint64_t hyper_frame;
uint64_t sym_i = target_sym + i;
do {
read_dl_iq(ctx, txdataF, 1, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (sym_i / num_symbols_per_frame) % 1024 && symbol == sym_i % 14));
assert(frame == (sym_i / num_symbols_per_frame) % 1024);
@@ -566,8 +569,9 @@ void test_other_bw_4ant_prb_offset()
txdataF[3] = out_iq3;
int frame, slot, symbol;
uint64_t hyper_frame;
do {
read_dl_iq(ctx, txdataF, 4, &frame, &slot, &symbol);
read_dl_iq(ctx, txdataF, 4, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == frameId && symbol == startSymbolId));
// Verify memory contents for each antenna
@@ -611,7 +615,7 @@ void test_send_mbuf_no_frag(void *io_controller, struct rte_mbuf **mbufs, uint32
sent_sec->fields.all_bits = rte_be_to_cpu_32(sent_sec->fields.all_bits);
size_t expected_header_len = sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr)
+ sizeof(struct data_section_hdr) + sizeof(struct data_section_compression_hdr);
+ sizeof(struct data_section_hdr);
// In this test, we expect exactly 20 PRBs in a single packet
assert(sent_sec->fields.num_prbu == (uint8_t)XRAN_CONVERT_NUMPRBC(20));
@@ -659,7 +663,7 @@ void test_send_mbuf_frag(void *io_controller, struct rte_mbuf **mbufs, uint32_t
sent_sec->fields.all_bits = rte_be_to_cpu_32(sent_sec->fields.all_bits);
size_t expected_header_len = sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr)
+ sizeof(struct data_section_hdr) + sizeof(struct data_section_compression_hdr);
+ sizeof(struct data_section_hdr);
if (g_packets_sent == 1) {
// First fragment: 30 PRBs
@@ -688,7 +692,7 @@ void test_send_mbuf_large_mtu(void *io_controller, struct rte_mbuf **mbufs, uint
sent_sec->fields.all_bits = rte_be_to_cpu_32(sent_sec->fields.all_bits);
size_t expected_header_len = sizeof(struct xran_ecpri_hdr) + sizeof(struct radio_app_common_hdr)
+ sizeof(struct data_section_hdr) + sizeof(struct data_section_compression_hdr);
+ sizeof(struct data_section_hdr);
// With MTU 9600, 100 PRBs should fit in a single packet
assert(g_packets_sent == 1);
@@ -721,7 +725,7 @@ void test_send_mbuf_prb_offset(void *io_controller, struct rte_mbuf **mbufs, uin
// Check data: should match the offset in txdataF
uint32_t *iq =
(uint32_t *)((uint8_t *)sent_sec + sizeof(struct data_section_hdr) + sizeof(struct data_section_compression_hdr));
(uint32_t *)((uint8_t *)sent_sec + sizeof(struct data_section_hdr));
assert(rte_be_to_cpu_32(iq[0]) == 0x10101010);
rte_pktmbuf_free(mbuf);
@@ -779,15 +783,20 @@ void test_uplink_basic()
handle_cplane_packet(ctx, c_mbuf);
// 2. Call write_ul_iq
uint32_t *txdataF[1];
// 2. Poll the UL job created by handle_cplane_packet and call write_ul_iq
ul_job_t job;
int poll_ret = poll_ul_job(ctx, &job);
assert(poll_ret == 0);
assert(job.num_prb == 20);
assert(job.start_prb == 0);
assert(job.antenna_id == 0);
uint32_t iq_input[100 * 12];
for (int i = 0; i < 100 * 12; i++) {
iq_input[i] = 0x11223344;
}
txdataF[0] = iq_input;
write_ul_iq(ctx, txdataF, 1, frameId, slot_in_frame, startSymbolId);
write_ul_iq(ctx, iq_input, startSymbolId, &job);
oru_packet_processor_stats_t stats;
get_packet_processor_stats(ctx, &stats);
@@ -850,15 +859,20 @@ void test_uplink_fragmentation()
handle_cplane_packet(ctx, c_mbuf);
// 2. Call write_ul_iq
uint32_t *txdataF[1];
// 2. Poll the UL job created by handle_cplane_packet and call write_ul_iq
ul_job_t job;
int poll_ret = poll_ul_job(ctx, &job);
assert(poll_ret == 0);
assert(job.num_prb == 40);
assert(job.start_prb == 0);
assert(job.antenna_id == 0);
uint32_t iq_input[100 * 12];
for (int i = 0; i < 100 * 12; i++) {
iq_input[i] = 0x11223344;
}
txdataF[0] = iq_input;
write_ul_iq(ctx, txdataF, 1, frameId, slot_in_frame, startSymbolId);
write_ul_iq(ctx, iq_input, startSymbolId, &job);
oru_packet_processor_stats_t stats;
get_packet_processor_stats(ctx, &stats);
@@ -936,15 +950,20 @@ void test_uplink_large_mtu()
handle_cplane_packet(ctx, c_mbuf);
// 2. Call write_ul_iq
uint32_t *txdataF[1];
// 2. Poll the UL job created by handle_cplane_packet and call write_ul_iq
ul_job_t job;
int poll_ret = poll_ul_job(ctx, &job);
assert(poll_ret == 0);
assert(job.num_prb == 100);
assert(job.start_prb == 0);
assert(job.antenna_id == 0);
uint32_t iq_input[num_prb * 12];
for (int i = 0; i < num_prb * 12; i++) {
iq_input[i] = 0x55667788;
}
txdataF[0] = iq_input;
write_ul_iq(ctx, txdataF, 1, frameId, slot_in_frame, startSymbolId);
write_ul_iq(ctx, iq_input, startSymbolId, &job);
oru_packet_processor_stats_t stats;
get_packet_processor_stats(ctx, &stats);
@@ -1021,14 +1040,19 @@ void test_uplink_prb_offset()
handle_cplane_packet(ctx, c_mbuf);
// 2. Call write_ul_iq
uint32_t *txdataF[1];
// 2. Poll the UL job created by handle_cplane_packet and call write_ul_iq
ul_job_t job;
int poll_ret = poll_ul_job(ctx, &job);
assert(poll_ret == 0);
assert(job.num_prb == 20);
assert(job.start_prb == 10);
assert(job.antenna_id == 0);
uint32_t iq_input[100 * 12];
memset(iq_input, 0, sizeof(iq_input));
iq_input[10 * 12] = 0x10101010; // Mark PRB 10
txdataF[0] = iq_input;
write_ul_iq(ctx, txdataF, 1, frameId, slot_in_frame, startSymbolId);
write_ul_iq(ctx, iq_input, startSymbolId, &job);
oru_packet_processor_stats_t stats;
get_packet_processor_stats(ctx, &stats);
@@ -1129,6 +1153,102 @@ void test_prach_generation()
printf("PRACH generation passed!\n");
}
void test_hyper_frame_calculation()
{
printf("Testing hyper-frame calculation...\n");
int mu = 1; // 30kHz
int slots_per_subframe = 1 << mu;
void *ctx = init_packet_processor(mu, 273, 200, 400, 100, 300, 2, 2, 0, 0, 5, test_alloc_mbuf, test_send_mbuf, NULL, 1500, 0);
assert(ctx != NULL);
int num_symbols_per_frame = 10 * slots_per_subframe * 14; // 280
// One hyper-frame has 1024 frames. So 1024 * 280 = 286720 symbols.
// Target absolute symbol index: 3 * 286720 + 5 * 280 + 1 * 14 + 7 = 860160 + 1400 + 14 + 7 = 861581.
uint64_t target_sym = 861581;
uint64_t current_sym = target_sym - 7;
handle_absolute_symbol_tick(ctx, current_sym);
// 1. Send C-plane packet for target_sym
struct rte_mbuf *c_mbuf = rte_pktmbuf_alloc(mp);
struct xran_ecpri_hdr *ecpri = (struct xran_ecpri_hdr *)rte_pktmbuf_append(c_mbuf, sizeof(struct xran_ecpri_hdr));
ecpri->ecpri_xtc_id = xran_compose_cid(&g_eaxcid_config, 0, 0, 0, 0);
struct xran_cp_radioapp_section1_header *apphdr =
(struct xran_cp_radioapp_section1_header *)rte_pktmbuf_append(c_mbuf, sizeof(struct xran_cp_radioapp_section1_header));
memset(apphdr, 0, sizeof(*apphdr));
apphdr->cmnhdr.field.dataDirection = XRAN_DIR_DL;
apphdr->cmnhdr.field.payloadVer = XRAN_PAYLOAD_VER;
apphdr->cmnhdr.field.frameId = (target_sym / num_symbols_per_frame) % 256;
int slot_in_frame = (target_sym % num_symbols_per_frame) / 14;
apphdr->cmnhdr.field.subframeId = slot_in_frame / slots_per_subframe;
apphdr->cmnhdr.field.slotId = slot_in_frame % slots_per_subframe;
apphdr->cmnhdr.field.startSymbolId = target_sym % 14;
apphdr->cmnhdr.sectionType = XRAN_CP_SECTIONTYPE_1;
apphdr->cmnhdr.field.all_bits = rte_cpu_to_be_32(apphdr->cmnhdr.field.all_bits);
struct xran_cp_radioapp_section1 *sec =
(struct xran_cp_radioapp_section1 *)rte_pktmbuf_append(c_mbuf, sizeof(struct xran_cp_radioapp_section1));
memset(sec, 0, sizeof(*sec));
sec->hdr.u.s1.numSymbol = 1;
sec->hdr.u1.common.numPrbc = 1;
*((uint64_t *)sec) = rte_be_to_cpu_64(*((uint64_t *)sec));
handle_cplane_packet(ctx, c_mbuf);
// 2. Send U-plane packet for target_sym
current_sym += 3;
handle_absolute_symbol_tick(ctx, current_sym);
struct rte_mbuf *u_mbuf = rte_pktmbuf_alloc(mp);
struct xran_ecpri_hdr *u_ecpri = (struct xran_ecpri_hdr *)rte_pktmbuf_append(u_mbuf, sizeof(struct xran_ecpri_hdr));
u_ecpri->ecpri_xtc_id = xran_compose_cid(&g_eaxcid_config, 0, 0, 0, 0);
struct radio_app_common_hdr *u_app =
(struct radio_app_common_hdr *)rte_pktmbuf_append(u_mbuf, sizeof(struct radio_app_common_hdr));
u_app->frame_id = (target_sym / num_symbols_per_frame) % 256;
u_app->sf_slot_sym.subframe_id = slot_in_frame / slots_per_subframe;
u_app->sf_slot_sym.slot_id = slot_in_frame % slots_per_subframe;
u_app->sf_slot_sym.symb_id = target_sym % 14;
u_app->sf_slot_sym.value = rte_cpu_to_be_16(u_app->sf_slot_sym.value);
struct data_section_hdr *u_data = (struct data_section_hdr *)rte_pktmbuf_append(u_mbuf, sizeof(struct data_section_hdr));
u_data->fields.num_prbu = 1;
u_data->fields.start_prbu = 0;
u_data->fields.sect_id = 0;
u_data->fields.all_bits = rte_cpu_to_be_32(u_data->fields.all_bits);
// IQ Data
uint16_t *iq = (uint16_t *)rte_pktmbuf_append(u_mbuf, 1 * 12 * 4);
assert(iq != NULL);
iq[0] = 0xAAAA;
handle_uplane_packet(ctx, u_mbuf);
// 3. Advance to trigger window expiry and job completion
current_sym += 10;
handle_absolute_symbol_tick(ctx, current_sym);
uint32_t *txdataF[1] = {0};
uint32_t output_iq[273 * 12] = {0};
txdataF[0] = output_iq;
int frame, slot, symbol;
uint64_t hyper_frame = 0xFFFFFFFF;
do {
read_dl_iq(ctx, txdataF, 1, &hyper_frame, &frame, &slot, &symbol);
} while (!(frame == (target_sym / num_symbols_per_frame) % 1024 && symbol == target_sym % 14));
assert(hyper_frame == 3);
assert(frame == 5);
assert(slot == 1);
assert(symbol == 7);
cleanup_packet_processor(ctx);
printf("Hyper-frame calculation test passed!\n");
}
int main(int argc, char **argv)
{
setup_dpdk(argc, argv);
@@ -1155,6 +1275,8 @@ int main(int argc, char **argv)
usleep(10000);
test_prach_generation();
usleep(10000);
test_hyper_frame_calculation();
usleep(10000);
printf("All tests passed!\n");
return 0;

View File

@@ -197,16 +197,23 @@ int main(int argc, char *argv[])
// For UL verification, we call write_ul_iq for every symbol.
// It will internally check TDD and UL C-plane presence.
ul_job_t job;
int poll_ret = poll_ul_job(ctx, &job);
if (poll_ret == 0) {
for (int i = 0; i < job.num_symbols; i++) {
write_ul_iq(ctx, txdataF[0], job.symbol + i, &job);
}
}
int frame = (s / 280) % 1024;
int slot = (s / 14) % 20;
int sym = s % 14;
write_ul_iq(ctx, txdataF, MAX_ANTENNAS, frame, slot, sym);
write_prach_iq(ctx, txdataF, MAX_ANTENNAS, frame, slot, sym);
// Drain ready jobs to prevent ring overflow
int f, sl, sy;
uint64_t hf;
while (get_ready_job_count(ctx) > 0) {
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &f, &sl, &sy);
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &hf, &f, &sl, &sy);
}
}
last_tick_sym = current_sym;
@@ -230,16 +237,11 @@ int main(int argc, char *argv[])
// Flush remaining symbols
for (int i = 0; i < 100; i++) {
handle_absolute_symbol_tick(ctx, ++last_tick_sym);
int frame = (last_tick_sym / 280) % 1024;
int slot = (last_tick_sym / 14) % 20;
int sym = last_tick_sym % 14;
write_ul_iq(ctx, txdataF, MAX_ANTENNAS, frame, slot, sym);
write_prach_iq(ctx, txdataF, MAX_ANTENNAS, frame, slot, sym);
// Drain ready jobs to prevent ring overflow during flush
int f, sl, sy;
uint64_t hf;
while (get_ready_job_count(ctx) > 0) {
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &f, &sl, &sy);
read_dl_iq(ctx, txdataF, MAX_ANTENNAS, &hf, &f, &sl, &sy);
}
}

View File

@@ -67,7 +67,7 @@ int nr_get_ssb_start_symbol(const NR_DL_FRAME_PARMS *fp, uint8_t i_ssb)
return symbol;
}
static void set_scs_parameters(NR_DL_FRAME_PARMS *fp, int mu, int N_RB_DL, int ssb_case)
void set_scs_parameters(NR_DL_FRAME_PARMS *fp, int mu, int N_RB_DL, int ssb_case)
{
switch(mu) {
case NR_MU_0: //15kHz scs

View File

@@ -12,6 +12,202 @@
#include "PHY/NR_TRANSPORT/nr_transport_common_proto.h"
#include "openair1/PHY/NR_TRANSPORT/nr_prach.h"
typedef struct {
int reps;
int Ncp;
int dftlen;
int N_ZC;
int k;
dft_size_idx_t dftsize;
int sample_offset_slot;
} prach_ru_params_t;
static prach_ru_params_t get_prach_ru_params(prach_item_t *p,
int prachStartSymbol,
NR_DL_FRAME_PARMS *fp)
{
prach_ru_params_t par = {0};
const int sum = fp->ofdm_symbol_size + fp->nb_prefix_samples;
const int sum0 = fp->ofdm_symbol_size + fp->nb_prefix_samples0;
if (prachStartSymbol == 0) {
par.sample_offset_slot = 0;
} else if (fp->slots_per_subframe == 1) {
if (prachStartSymbol <= 7)
par.sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
else
par.sample_offset_slot = sum * (prachStartSymbol - 2) + sum0 * 2;
} else {
if (!(p->slot % (fp->slots_per_subframe / 2)))
par.sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
else
par.sample_offset_slot = sum * prachStartSymbol;
}
int mu = p->numerology_index;
if (p->prach_sequence_length == 0) {
switch (p->pdu.prach_format) {
case 0:
par.reps = 1;
par.Ncp = 3168;
par.dftlen = 24576;
break;
case 1:
par.reps = 2;
par.Ncp = 21024;
par.dftlen = 24576;
break;
case 2:
par.reps = 4;
par.Ncp = 4688;
par.dftlen = 24576;
break;
case 3:
par.reps = 4;
par.Ncp = 3168;
par.dftlen = 6144;
break;
default:
AssertFatal(1 == 0, "Illegal prach format %d for length 839\n", p->pdu.prach_format);
break;
}
} else {
switch (p->pdu.prach_format) {
case 4: // A1
par.reps = 2;
par.Ncp = 288 >> mu;
break;
case 5: // A2
par.reps = 4;
par.Ncp = 576 >> mu;
break;
case 6: // A3
par.reps = 6;
par.Ncp = 864 >> mu;
break;
case 7: // B1
par.reps = 2;
par.Ncp = 216 >> mu;
break;
case 8: // B4
par.reps = 12;
par.Ncp = 936 >> mu;
break;
case 9: // C0
par.reps = 1;
par.Ncp = 1240 >> mu;
break;
case 10: // C2
par.reps = 4;
par.Ncp = 2048 >> mu;
break;
default:
AssertFatal(1 == 0, "unknown prach format %x\n", p->pdu.prach_format);
break;
}
par.dftlen = 2048 >> mu;
}
if (p->numerology_index == 0) {
if (prachStartSymbol == 0 || prachStartSymbol == 7)
par.Ncp += 16;
} else {
if (p->slot % (fp->slots_per_subframe / 2) == 0 && prachStartSymbol == 0)
par.Ncp += 16;
}
switch(fp->samples_per_subframe) {
case 7680:
// 5 MHz @ 7.68 Ms/s
par.Ncp >>= 2;
par.dftlen >>= 2;
break;
case 15360:
// 10, 15 MHz @ 15.36 Ms/s
par.Ncp >>= 1;
par.dftlen >>= 1;
break;
case 23040:
// 20 MHz @ 23.04 Ms/s
par.Ncp = (par.Ncp * 3) / 4;
par.dftlen = (par.dftlen * 3) / 4;
break;
case 30720:
// 20, 25, 30 MHz @ 30.72 Ms/s
break;
case 46080:
// 40 MHz @ 46.08 Ms/s
par.Ncp = (par.Ncp*3)/2;
par.dftlen = (par.dftlen*3)/2;
break;
case 61440:
// 40, 50, 60 MHz @ 61.44 Ms/s
par.Ncp <<= 1;
par.dftlen <<= 1;
break;
case 92160:
// 50, 60, 70, 80, 90 MHz @ 92.16 Ms/s
par.Ncp *= 3;
par.dftlen *= 3;
break;
case 122880:
// 70, 80, 90, 100 MHz @ 122.88 Ms/s
par.Ncp <<= 2;
par.dftlen <<= 2;
break;
case 184320:
// 100 MHz @ 184.32 Ms/s
par.Ncp = par.Ncp*6;
par.dftlen = par.dftlen*6;
break;
case 245760:
// 200 MHz @ 245.76 Ms/s
par.Ncp <<= 3;
par.dftlen <<= 3;
break;
default:
AssertFatal(1==0,"sample rate %f MHz not supported for numerology %d\n", fp->samples_per_subframe / 1000.0, mu);
}
par.dftsize = get_dft(par.dftlen);
par.N_ZC = (p->prach_sequence_length == 0) ? 839 : 139;
const unsigned int K = get_prach_K(p->prach_sequence_length, p->pdu.prach_format, p->numerology_index, p->mu);
const uint8_t kbar = get_PRACH_k_bar(p->mu, p->numerology_index);
int n_ra_prb = p->msg1_frequencystart;
int k = (12*n_ra_prb) - 6*fp->N_RB_UL;
if (k<0) k+=(fp->ofdm_symbol_size);
k*=K;
k+=kbar;
par.k = k;
return par;
}
void init_nr_prach(PHY_VARS_gNB *gNB)
{
int num_prach = 16;
@@ -114,6 +310,38 @@ void nr_schedule_rx_prach(PHY_VARS_gNB *gNB, int SFN, int Slot, nfapi_nr_prach_p
LOG_W(NR_PHY, "%4d.%2d PRACH occ queue is full: dropping PRACH request\n", SFN, Slot);
}
static void rx_nr_prach_ru_internal_rep(prach_item_t *p,
int ant_offset,
int32_t **rxdata,
NR_DL_FRAME_PARMS *fp,
int N_TA_offset,
int rep,
const prach_ru_params_t *params,
c16_t (*rxsigF)[NR_PRACH_SEQ_LEN_L])
{
AssertFatal(rep >= 0 && rep < params->reps, "rep %d is out of range (reps = %d)\n", rep, params->reps);
int slot2 = p->prach_sequence_length ? p->slot : p->slot;
int sample_offset = get_samples_slot_timestamp(fp, slot2) + params->sample_offset_slot - N_TA_offset + params->Ncp + rep * params->dftlen;
for (int aa = 0; aa < p->nb_rx; aa++) {
int idx = ant_offset + aa;
c16_t *prach2 = (c16_t *)&rxdata[idx][sample_offset];
// do DFT for the specific repetition
c16_t tmp[params->dftlen] __attribute__((aligned(32)));
dft(params->dftsize, (int16_t *)prach2, (int16_t *)tmp, 1);
// Coherent combining of PRACH repetitions (assumes channel does not change, to be revisted for "long" PRACH)
LOG_D(PHY, "Doing PRACH combining of repetition %d/%d N_ZC %d\n", rep, params->reps, params->N_ZC);
int k2 = params->k;
for (int j = 0; j < params->N_ZC; j++, k2++) {
if (k2 == params->dftlen)
k2 = 0;
rxsigF[aa][j] = c16add(rxsigF[aa][j], tmp[k2]);
}
}
}
static void rx_nr_prach_ru_internal(prach_item_t *p,
int prachStartSymbol,
int prachOccasion,
@@ -122,229 +350,9 @@ static void rx_nr_prach_ru_internal(prach_item_t *p,
int N_TA_offset,
bool das)
{
int sample_offset_slot;
const int sum = fp->ofdm_symbol_size + fp->nb_prefix_samples;
const int sum0 = fp->ofdm_symbol_size + fp->nb_prefix_samples0;
if (prachStartSymbol == 0) {
sample_offset_slot = 0;
} else if (fp->slots_per_subframe == 1) {
if (prachStartSymbol <= 7)
sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
else
sample_offset_slot = sum * (prachStartSymbol - 2) + sum0 * 2;
} else {
if (!(p->slot % (fp->slots_per_subframe / 2)))
sample_offset_slot = sum * (prachStartSymbol - 1) + sum0;
else
sample_offset_slot = sum * prachStartSymbol;
}
LOG_D(PHY,
"frame %d, slot %d: doing rx_nr_prach_ru for format %d, numRA %d, prachStartSymbol %d, prachOccasion %d\n",
p->frame,
p->slot,
p->pdu.prach_format,
p->pdu.num_ra,
prachStartSymbol,
prachOccasion);
int reps;
int Ncp;
int dftlen;
int mu = p->numerology_index;
if (p->prach_sequence_length == 0) {
LOG_D(PHY,
"PRACH in %d.%d, format %d, msg1_frequencyStart %d\n",
p->frame,
p->slot,
p->pdu.prach_format,
p->msg1_frequencystart);
switch (p->pdu.prach_format) {
case 0:
reps = 1;
Ncp = 3168;
dftlen = 24576;
break;
case 1:
reps = 2;
Ncp = 21024;
dftlen = 24576;
break;
case 2:
reps = 4;
Ncp = 4688;
dftlen = 24576;
break;
case 3:
reps = 4;
Ncp = 3168;
dftlen = 6144;
break;
default:
AssertFatal(1 == 0, "Illegal prach format %d for length 839\n", p->pdu.prach_format);
break;
}
} else {
LOG_D(PHY,
"PRACH in %d.%d, format %s, msg1_frequencyStart %d,startSymbol %d\n",
p->frame,
p->slot,
prachfmt[p->pdu.prach_format],
p->msg1_frequencystart,
prachStartSymbol);
switch (p->pdu.prach_format) {
case 4: // A1
reps = 2;
Ncp = 288 >> mu;
break;
case 5: // A2
reps = 4;
Ncp = 576 >> mu;
break;
case 6: // A3
reps = 6;
Ncp = 864 >> mu;
break;
case 7: // B1
reps = 2;
Ncp = 216 >> mu;
break;
/*
// B2 and B3 do not exist in FAPI
case 4: //B2
reps = 4;
Ncp = 360 >> mu;
break;
case 5: //B3
reps = 6;
Ncp = 504 >> mu;
break;
*/
case 8: // B4
reps = 12;
Ncp = 936 >> mu;
break;
case 9: // C0
reps = 1;
Ncp = 1240 >> mu;
break;
case 10: // C2
reps = 4;
Ncp = 2048 >> mu;
break;
default:
AssertFatal(1 == 0, "unknown prach format %x\n", p->pdu.prach_format);
break;
}
dftlen = 2048 >> mu;
}
//actually what we should be checking here is how often the current prach crosses a 0.5ms boundary.
//I am not quite sure for which paramter set this would be the case,
//so I will ignore it for now and just check if the prach starts on a 0.5ms boundary
if (p->numerology_index == 0) {
if (prachStartSymbol == 0 || prachStartSymbol == 7)
Ncp += 16;
} else {
if (p->slot % (fp->slots_per_subframe / 2) == 0 && prachStartSymbol == 0)
Ncp += 16;
}
switch(fp->samples_per_subframe) {
case 7680:
// 5 MHz @ 7.68 Ms/s
Ncp >>= 2;
dftlen >>= 2;
break;
case 15360:
// 10, 15 MHz @ 15.36 Ms/s
Ncp >>= 1;
dftlen >>= 1;
break;
case 23040:
// 20 MHz @ 23.04 Ms/s
Ncp = (Ncp * 3) / 4;
dftlen = (dftlen * 3) / 4;
break;
case 30720:
// 20, 25, 30 MHz @ 30.72 Ms/s
break;
case 46080:
// 40 MHz @ 46.08 Ms/s
Ncp = (Ncp*3)/2;
dftlen = (dftlen*3)/2;
break;
case 61440:
// 40, 50, 60 MHz @ 61.44 Ms/s
Ncp <<= 1;
dftlen <<= 1;
break;
case 92160:
// 50, 60, 70, 80, 90 MHz @ 92.16 Ms/s
Ncp *= 3;
dftlen *= 3;
break;
case 122880:
// 70, 80, 90, 100 MHz @ 122.88 Ms/s
Ncp <<= 2;
dftlen <<= 2;
break;
case 184320:
// 100 MHz @ 184.32 Ms/s
Ncp = Ncp*6;
dftlen = dftlen*6;
break;
case 245760:
// 200 MHz @ 245.76 Ms/s
Ncp <<= 3;
dftlen <<= 3;
break;
default:
AssertFatal(1==0,"sample rate %f MHz not supported for numerology %d\n", fp->samples_per_subframe / 1000.0, mu);
}
const dft_size_idx_t dftsize = get_dft(dftlen);
// Do forward transform
if (LOG_DEBUGFLAG(DEBUG_PRACH)) {
LOG_D(PHY, "rx_prach: Doing PRACH FFT for nb_rx:%d Ncp:%d dftlen:%d\n", p->nb_rx, Ncp, dftlen);
}
const unsigned int K = get_prach_K(p->prach_sequence_length, p->pdu.prach_format, p->numerology_index, p->mu);
const uint8_t kbar = get_PRACH_k_bar(p->mu, p->numerology_index);
int n_ra_prb = p->msg1_frequencystart;
int k = (12*n_ra_prb) - 6*fp->N_RB_UL;
int N_ZC = (p->prach_sequence_length == 0) ? 839 : 139;
if (k<0) k+=(fp->ofdm_symbol_size);
k*=K;
k+=kbar;
prach_ru_params_t params = get_prach_ru_params(p, prachStartSymbol, fp);
c16_t rxsigF_tmp[p->nb_rx][NR_PRACH_SEQ_LEN_L];
memset(rxsigF_tmp, 0, sizeof(rxsigF_tmp));
const uint8_t num_beams = p->pdu.beamforming.dig_bf_interface;
// When more than one beams, then each occasion is on one beam
@@ -353,36 +361,20 @@ static void rx_nr_prach_ru_internal(prach_item_t *p,
AssertFatal(prachOccasion < num_beams, "Num of PRACH Occasions must be same as number of beams in beamforming mode\n");
ant_offset = prachOccasion * p->nb_rx;
}
// TODO: Remove assumption of contiguous ports after DAS is properly handled in beamforming
uint16_t ant_start =
get_first_ant_idx(das,
p->nb_rx,
p->pdu.beamforming.prgs_list[0].dig_bf_interface_list[0].beam_idx,
p->pdu.param_v4.numSpatialStreamIndices > 0 ? p->pdu.param_v4.spatialStreamIndices[ant_offset] : 0);
for (int aa = 0; aa < p->nb_rx; aa++) {
// Fixme: slot or slot makes no sense ???
int slot2 = p->prach_sequence_length ? p->slot : p->slot;
int idx = ant_start + aa;
c16_t *prach = (c16_t *)&rxdata[idx][get_samples_slot_timestamp(fp, slot2) + sample_offset_slot - N_TA_offset];
// do DFT
c16_t *prach2 = prach + Ncp;
c16_t rxsigF_tmp[N_ZC];
memset(rxsigF_tmp, 0, sizeof(rxsigF_tmp));
for (int i = 0; i < reps; i++, prach2 += dftlen) {
c16_t tmp[dftlen] __attribute__((aligned(32)));
dft(dftsize, (int16_t *)prach2, (int16_t *)tmp, 1);
// Coherent combining of PRACH repetitions (assumes channel does not change, to be revisted for "long" PRACH)
LOG_D(PHY, "Doing PRACH combining of %d reptitions N_ZC %d\n", reps, N_ZC);
// if (k+N_ZC > dftlen) { // PRACH signal is split around DC
int k2 = k;
for (int j = 0; j < N_ZC; j++, k2++) {
if (k2 == dftlen)
k2 = 0;
rxsigF_tmp[j] = c16add(rxsigF_tmp[j], tmp[k2]);
}
}
memcpy(p->prach_buf[aa][prachOccasion], rxsigF_tmp, sizeof(rxsigF_tmp));
for (int rep = 0; rep < params.reps; rep++) {
rx_nr_prach_ru_internal_rep(p, ant_start, rxdata, fp, N_TA_offset, rep, &params, rxsigF_tmp);
}
for (int aa = 0; aa < p->nb_rx; aa++) {
memcpy(p->prach_buf[aa][prachOccasion], rxsigF_tmp[aa], sizeof(c16_t) * params.N_ZC);
}
}
@@ -399,6 +391,20 @@ void rx_nr_prach_ru(prach_item_t *p, int32_t **rxdata, NR_DL_FRAME_PARMS *fp, in
}
}
void rx_nr_prach_ru_rep(prach_item_t *p,
int32_t **rxdata,
NR_DL_FRAME_PARMS *fp,
int N_TA_offset,
int rep,
int prachOccasion,
c16_t (*rxsigF)[NR_PRACH_SEQ_LEN_L])
{
int N_dur = get_nr_prach_duration(p->pdu.prach_format);
int prachStartSymbol = p->pdu.prach_start_symbol + prachOccasion * N_dur;
prach_ru_params_t params = get_prach_ru_params(p, prachStartSymbol, fp);
rx_nr_prach_ru_internal_rep(p, 0, rxdata, fp, N_TA_offset, rep, &params, rxsigF);
}
rx_prach_out_t rx_nr_prach(const prach_item_t *in, int occasion)
{
rx_prach_out_t out = {};

View File

@@ -124,6 +124,13 @@ typedef struct rx_prach_out {
rx_prach_out_t rx_nr_prach(const prach_item_t *, int occasion);
void rx_nr_prach_ru(prach_item_t *, int32_t **, NR_DL_FRAME_PARMS *frame_parms, int N_TA_offset, bool das);
void rx_nr_prach_ru_rep(prach_item_t *p,
int32_t **rxdata,
NR_DL_FRAME_PARMS *fp,
int N_TA_offset,
int rep,
int prachOccasion,
c16_t (*rxsigF)[NR_PRACH_SEQ_LEN_L]);
void nr_fill_pucch(PHY_VARS_gNB *gNB,
int frame,

View File

@@ -882,15 +882,19 @@ static void map_overlapped_ack(uci_on_pusch_bit_type_t *template,
const int32_t num_ack_remaining = G_ack - ack_bits_marked;
if (num_ack_remaining <= 0)
continue;
const uint32_t d_factor_re = get_d_factor_re(num_ack_remaining, num_reserved_bits_on_sym);
for (uint32_t i = 0; i < num_reserved_bits_on_sym && ack_bits_marked < G_ack; i += d_factor_re) {
uint32_t pos = reserved_indices_on_this_sym[i];
int bit_in_group = pos % Qm;
if (template[pos] == BIT_TYPE_ULSCH) // puncturing ULSCH
template[pos] = (bit_in_group >= placeholder_start) ? BIT_TYPE_ACK_PLACEHOLDER : BIT_TYPE_ACK_RESERVED;
else // puncturing CSIp2
template[pos] = (bit_in_group >= placeholder_start) ? BIT_TYPE_ACK_PLACEHOLDER_CSI2 : BIT_TYPE_ACK_RESERVED_CSI2;
ack_bits_marked++;
const uint32_t num_reserved_re = num_reserved_bits_on_sym / Qm;
const uint32_t num_ack_re_remaining = num_ack_remaining / Qm;
const uint32_t d_factor_re = get_d_factor_re(num_ack_re_remaining, num_reserved_re);
for (uint32_t re = 0; re < num_reserved_re && ack_bits_marked < G_ack; re += d_factor_re) {
for (int b = 0; b < Qm; b++) {
uint32_t pos = reserved_indices_on_this_sym[re * Qm + b];
int bit_in_group = pos % Qm;
if (template[pos] == BIT_TYPE_ULSCH) // puncturing ULSCH
template[pos] = (bit_in_group >= placeholder_start) ? BIT_TYPE_ACK_PLACEHOLDER : BIT_TYPE_ACK_RESERVED;
else // puncturing CSIp2
template[pos] = (bit_in_group >= placeholder_start) ? BIT_TYPE_ACK_PLACEHOLDER_CSI2 : BIT_TYPE_ACK_RESERVED_CSI2;
ack_bits_marked++;
}
}
}
}

View File

@@ -733,26 +733,15 @@ void nr_generate_pucch2(c16_t **txdataF,
const int seq_sz = ((startingPRB + pucch_pdu->prb_size) * NR_PUCCH_DMRS_RB * DMRS_MOD_ORDER + 31) / 32;
uint32_t *seq = gold_cache(temp_x2, seq_sz);
for (int rb=0; rb<pucch_pdu->prb_size; rb++) {
//startingPRB = startingPRB + rb;
const bool nb_rb_is_even = frame_parms->N_RB_DL & 1;
const bool nb_rb_is_even = !(frame_parms->N_RB_DL & 1);
const int halfRBs = frame_parms->N_RB_DL / 2;
const int baseRB = rb + startingPRB;
int re_offset = (l + startingSymbolIndex) * frame_parms->ofdm_symbol_size + 12 * baseRB;
if (nb_rb_is_even) {
if (baseRB < halfRBs) // if number RBs in bandwidth is even and current PRB is lower band
re_offset += frame_parms->first_carrier_offset;
else
re_offset -= halfRBs;
} else {
if (baseRB < halfRBs) // if number RBs in bandwidth is odd and current PRB is lower band
re_offset += frame_parms->first_carrier_offset;
else if (baseRB > halfRBs) // if number RBs in bandwidth is odd and current PRB is upper band
re_offset += -halfRBs + 6;
else
re_offset += frame_parms->first_carrier_offset;
}
int re_offset = (l + startingSymbolIndex) * frame_parms->ofdm_symbol_size + NR_NB_SC_PER_RB * baseRB;
if (baseRB > halfRBs)
re_offset -= (nb_rb_is_even) ? (halfRBs * NR_NB_SC_PER_RB) : (halfRBs * NR_NB_SC_PER_RB + 6);
else
re_offset += frame_parms->first_carrier_offset;
//txptr = &txdataF[0][re_offset];
int k=0;
#ifdef DEBUG_NR_PUCCH_TX
int kk=0;

View File

@@ -53,6 +53,8 @@ MESSAGE_DEF(NGAP_HANDOVER_CANCEL, MESSAGE_PRIORITY_MED, ngap_handover_cancel_t,
MESSAGE_DEF(NGAP_UL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_ul_ran_status_transfer)
MESSAGE_DEF(NGAP_DL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_dl_ran_status_transfer)
MESSAGE_DEF(NGAP_RECONNECT_TIMER, MESSAGE_PRIORITY_MED, IttiMsgText, ngap_gNB_amf_data)
MESSAGE_DEF(NGAP_PATH_SWITCH_REQ, MESSAGE_PRIORITY_MED, ngap_path_switch_req_t, ngap_path_switch_req)
MESSAGE_DEF(NGAP_PATH_SWITCH_REQ_ACK, MESSAGE_PRIORITY_MED, ngap_path_switch_req_ack_t, ngap_path_switch_req_ack)
/* NGAP -> RRC messages */
MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas )

View File

@@ -47,6 +47,8 @@
#define NGAP_HANDOVER_NOTIFY(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_notify
#define NGAP_HANDOVER_CANCEL(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel
#define NGAP_HANDOVER_CANCEL_ACK(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel_ack
#define NGAP_PATH_SWITCH_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_path_switch_req
#define NGAP_PATH_SWITCH_REQ_ACK(mSGpTR) (mSGpTR)->ittiMsg.ngap_path_switch_req_ack
#define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req
#define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command
@@ -669,6 +671,69 @@ typedef struct {
uint64_t amf_ue_ngap_id;
} ngap_handover_cancel_ack_t;
/* Path Switch Request 9.2.3.8 3GPP TS 38.413 */
typedef struct ngap_path_switch_req_s {
// RAN UE NGAP ID
uint32_t gNB_ue_ngap_id;
// Source AMF UE NGAP ID
uint64_t amf_ue_ngap_id;
// User Location Information
user_location_information_t user_info;
// UE Security Capabilities
ngap_security_capabilities_t security_capabilities;
// Number of pdusession to be switched in the downlink list
uint16_t nb_of_pdusessions;
// List of PDU Session Resource to be Switched in Downlink
pdusession_setup_t pdusessions_tobeswitched[NR_MAX_NB_PDU_SESSIONS];
} ngap_path_switch_req_t;
typedef enum ngap_security_ind_s {
NGAP_SECURITY_REQUIRED = 0,
NGAP_SECURITY_PREFERRED = 1,
NGAP_SECURITY_NOT_NEEDED = 2,
} ngap_security_ind_t;
/* 9.3.1.27 3GPP TS 38.413 */
typedef struct security_ind_s {
ngap_security_ind_t integrity_protection_ind;
ngap_security_ind_t confidentiality_protection_ind;
} security_ind_t;
/* 9.3.4.9 3GPP TS 38.413 */
typedef struct path_switch_request_ack_transfer_s {
// UL NG-U UP TNL Information (O)
gtpu_tunnel_t *n3_incoming;
// Security Indication (O)
security_ind_t *security_ind;
} path_switch_request_ack_transfer_t;
/* Path Switch Request Acknowledge 9.2.3.9 3GPP TS 38.413
* PDU Session Resource Switched Item */
typedef struct path_switch_request_ack_pdusession_s {
// PDU Session ID (M)
int pdusession_id;
// Path Switch Request Acknowledge Transfer (M)
path_switch_request_ack_transfer_t pathSwitchReqAckTransfer;
} path_switch_request_ack_pdusession_t;
/* Path Switch Request Acknowledge 9.2.3.9 3GPP TS 38.413 */
typedef struct ngap_path_switch_req_ack_s {
// AMF UE NGAP ID (M)
uint64_t amf_ue_ngap_id;
// RAN UE NGAP ID (M)
uint32_t gNB_ue_ngap_id;
// Security Context - Next-Hop Chaining Count (M)
uint8_t nh_ncc;
// Security Context - Next-Hop NH (M)
uint8_t next_security_key[SECURITY_KEY_LENGTH];
// List of PDU Session Resource Switched (M)
uint16_t nb_of_pdusessions;
path_switch_request_ack_pdusession_t pdusessions_switched[NR_MAX_NB_PDU_SESSIONS];
// Allowed NSSAI (M)
uint8_t nb_allowed_nssais;
nssai_t allowed_nssai[NR_MAX_NB_ALLOWED_SNSSAI];
} ngap_path_switch_req_ack_t;
typedef struct ngap_ue_cap_info_ind_s {
uint32_t gNB_ue_ngap_id;
byte_array_t ue_radio_cap;

View File

@@ -233,10 +233,16 @@ bool e1_decode_qos_flow_to_setup(qos_flow_to_setup_t *out, const E1AP_QoS_Flow_Q
const E1AP_QoSFlowLevelQoSParameters_t *qosParams = &in->qoSFlowLevelQoSParameters;
const E1AP_QoS_Characteristics_t *qoS_Characteristics = &qosParams->qoS_Characteristics;
switch (qoS_Characteristics->present) {
case E1AP_QoS_Characteristics_PR_non_Dynamic_5QI:
case E1AP_QoS_Characteristics_PR_non_Dynamic_5QI: {
const int fiveqi = qoS_Characteristics->choice.non_Dynamic_5QI->fiveQI;
if (fiveqi < MIN_FIVEQI || fiveqi > MAX_FIVEQI) {
PRINT_ERROR("non-dynamic fiveQI %d out of range %d..%d\n", fiveqi, MIN_FIVEQI, MAX_FIVEQI);
return false;
}
qos_char->qos_type = NON_DYNAMIC;
qos_char->non_dynamic.fiveqi = qoS_Characteristics->choice.non_Dynamic_5QI->fiveQI;
qos_char->non_dynamic.fiveqi = fiveqi;
break;
}
case E1AP_QoS_Characteristics_PR_dynamic_5QI: {
E1AP_Dynamic5QIDescriptor_t *dynamic5QI = qoS_Characteristics->choice.dynamic_5QI;
qos_char->qos_type = DYNAMIC;

View File

@@ -434,7 +434,12 @@ static bool decode_qos_flow_param(const F1AP_QoSFlowLevelQoSParameters_t *f1ap,
if (f1ap->qoS_Characteristics.present == F1AP_QoS_Characteristics_PR_non_Dynamic_5QI) {
out->qos_type = NON_DYNAMIC;
const F1AP_NonDynamic5QIDescriptor_t *nondyn = f1ap->qoS_Characteristics.choice.non_Dynamic_5QI;
out->nondyn.fiveQI = nondyn->fiveQI;
const int fiveqi = nondyn->fiveQI;
if (fiveqi < MIN_FIVEQI || fiveqi > MAX_FIVEQI) {
PRINT_ERROR("non-dynamic fiveQI %d out of range %d..%d\n", fiveqi, MIN_FIVEQI, MAX_FIVEQI);
return false;
}
out->nondyn.fiveQI = fiveqi;
} else {
_EQ_CHECK_INT(f1ap->qoS_Characteristics.present, F1AP_QoS_Characteristics_PR_dynamic_5QI);
const F1AP_Dynamic5QIDescriptor_t *d = f1ap->qoS_Characteristics.choice.dynamic_5QI;
@@ -757,7 +762,7 @@ static F1AP_DRBs_ToBeSetupMod_List_t encode_drbs_to_setupmod(int n, const f1ap_d
F1AP_QoSInformation_ExtIEs_t *qos_ext_ie = calloc_or_fail(1, sizeof(*qos_ext_ie));
it->qoSInformation.choice.choice_extension = (struct F1AP_ProtocolIE_SingleContainer *)qos_ext_ie;
qos_ext_ie->id = F1AP_ProtocolIE_ID_id_DRB_Information;
qos_ext_ie->criticality = F1AP_Criticality_reject;
qos_ext_ie->criticality = F1AP_Criticality_ignore;
qos_ext_ie->value.present = F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information;
qos_ext_ie->value.choice.DRB_Information = encode_drb_info_nr(&drb->nr);

View File

@@ -442,6 +442,7 @@ static int collect_dl_candidates(gNB_MAC_INST *mac,
int n = 0;
const frame_structure_t *fs = &mac->frame_structure;
const float dl_slots_per_s = (float)get_dl_slots_per_period(fs) / fs->numb_slots_period * fs->numb_slots_frame * 100;
const uint64_t now_ms = get_nr_rlc_current_time();
UE_iterator (UE_list, UE) {
if (n >= max_candidates)
@@ -527,6 +528,11 @@ static int collect_dl_candidates(gNB_MAC_INST *mac,
.alloc_beam_idx = 0,
.alloc_new_beam = false,
};
nr_dl_candidate_t *c = &candidates[n - 1];
FOR_EACH_SEQ_ARR(const nr_lc_config_t *, lc, &sched_ctrl->lc_config) {
const uint64_t ts = sched_ctrl->rlc_status[lc->lcid].oldest_sdu_arrival_ms;
c->hol_delay_ms[lc->lcid] = (ts > 0 && ts <= now_ms) ? (now_ms - ts) : 0;
}
} else {
/* new transmission candidate */
if (sched_ctrl->available_dl_harq.head < 0)
@@ -572,8 +578,11 @@ static int collect_dl_candidates(gNB_MAC_INST *mac,
.alloc_new_beam = false,
};
nr_dl_candidate_t *c = &candidates[n - 1];
for (int lcid = 0; lcid < NR_MAX_NUM_LCID; lcid++)
c->pending_bytes_per_lcid[lcid] = sched_ctrl->rlc_status[lcid].bytes_in_buffer;
FOR_EACH_SEQ_ARR(const nr_lc_config_t *, lc, &sched_ctrl->lc_config) {
c->pending_bytes_per_lcid[lc->lcid] = sched_ctrl->rlc_status[lc->lcid].bytes_in_buffer;
const uint64_t ts = sched_ctrl->rlc_status[lc->lcid].oldest_sdu_arrival_ms;
c->hol_delay_ms[lc->lcid] = (ts > 0 && ts <= now_ms) ? (now_ms - ts) : 0;
}
}
}

View File

@@ -265,8 +265,8 @@ static int get_non_dynamic_priority(int fiveqi)
for (int i = 0; i < sizeofArray(qos_fiveqi); ++i)
if (qos_fiveqi[i] == fiveqi)
return qos_priority[i];
AssertFatal(false, "illegal 5QI value %d\n", fiveqi);
return 0;
LOG_W(NR_MAC, "unsupported non-dynamic 5QI %d\n", fiveqi);
return -1;
}
static NR_QoS_config_t get_qos_config(const f1ap_qos_flow_param_t *qos)
@@ -319,6 +319,8 @@ static int handle_ue_context_drbs_setup(NR_UE_info_t *UE,
int prio = 100;
for (int q = 0; q < drb->nr.flows_len; ++q) {
c.qos_config[q] = get_qos_config(&drb->nr.flows[q].param);
if (c.qos_config[q].priority < 0)
continue;
prio = min(prio, c.qos_config[q].priority);
}
c.priority = prio;

View File

@@ -917,6 +917,7 @@ struct nr_dl_candidate {
int retx_rbSize; ///< RBs needed for retx, 0 for new tx
uint32_t pending_bytes; ///< total bytes waiting in RLC buffers
uint32_t pending_bytes_per_lcid[NR_MAX_NUM_LCID]; ///< per-LCID bytes waiting in RLC buffers
uint64_t hol_delay_ms[NR_MAX_NUM_LCID]; ///< per-LCID head-of-line delay in milliseconds (age of oldest SDU)
float avg_throughput; ///< EWMA goodput in bps (dl_thr_ue)
float bler; ///< current BLER estimate
int current_mcs; ///< current MCS state (retx: from HARQ, new tx: from BLER tracker)

View File

@@ -78,6 +78,7 @@ typedef struct {
int status_size;
int tx_size;
int retx_size;
uint64_t oldest_sdu_arrival_ms; /* timestamp (ms, from sdu->arrival_ms) of oldest SDU in tx/retx queue, 0 if empty */
} nr_rlc_entity_buffer_status_t;
typedef struct nr_rlc_entity_s {

View File

@@ -1777,6 +1777,12 @@ nr_rlc_entity_buffer_status_t nr_rlc_entity_am_buffer_status(
ret.tx_size = entity->common.bstatus.tx_size;
ret.retx_size = entity->common.bstatus.retx_size;
ret.oldest_sdu_arrival_ms = 0;
if (entity->retransmit_list && entity->retransmit_list->sdu)
ret.oldest_sdu_arrival_ms = entity->retransmit_list->sdu->arrival_ms;
else if (entity->tx_list && entity->tx_list->sdu)
ret.oldest_sdu_arrival_ms = entity->tx_list->sdu->arrival_ms;
return ret;
}
@@ -1851,6 +1857,8 @@ void nr_rlc_entity_am_recv_sdu(nr_rlc_entity_t *_entity,
if (entity->common.avg_time_is_on)
sdu->sdu->time_of_arrival = time_average_now();
sdu->sdu->arrival_ms = entity->t_current;
}
/*************************************************************************/

View File

@@ -88,6 +88,10 @@ nr_rlc_entity_buffer_status_t nr_rlc_entity_tm_buffer_status(nr_rlc_entity_t *_e
ret.tx_size = entity->common.bstatus.tx_size;
ret.retx_size = 0;
ret.oldest_sdu_arrival_ms = 0;
if (entity->tx_list && entity->tx_list->sdu)
ret.oldest_sdu_arrival_ms = entity->tx_list->sdu->arrival_ms;
return ret;
}
@@ -147,6 +151,8 @@ void nr_rlc_entity_tm_recv_sdu(nr_rlc_entity_t *_entity,
if (entity->common.avg_time_is_on)
sdu->sdu->time_of_arrival = time_average_now();
sdu->sdu->arrival_ms = entity->t_current;
}
/*************************************************************************/

View File

@@ -534,6 +534,10 @@ nr_rlc_entity_buffer_status_t nr_rlc_entity_um_buffer_status(nr_rlc_entity_t *_e
ret.tx_size = entity->common.bstatus.tx_size;
ret.retx_size = 0;
ret.oldest_sdu_arrival_ms = 0;
if (entity->tx_list && entity->tx_list->sdu)
ret.oldest_sdu_arrival_ms = entity->tx_list->sdu->arrival_ms;
return ret;
}
@@ -594,6 +598,8 @@ void nr_rlc_entity_um_recv_sdu(nr_rlc_entity_t *_entity,
if (entity->common.avg_time_is_on)
sdu->sdu->time_of_arrival = time_average_now();
sdu->sdu->arrival_ms = entity->t_current;
}
/*************************************************************************/

View File

@@ -47,7 +47,7 @@ void unlock_nr_rlc_current_time(void)
AssertFatal(0, "error locking mutex");
}
static uint64_t get_nr_rlc_current_time(void)
uint64_t get_nr_rlc_current_time(void)
{
lock_nr_rlc_current_time();
@@ -267,10 +267,12 @@ static mac_rlc_status_resp_t _nr_rlc_status_ind(nr_rlc_ue_t *ue, frame_t frame,
// Fix me: temproary reduction meanwhile cpu cost of this computation is optimized
buf_stat = rb->buffer_status(rb, 1000 * 1000);
ret.bytes_in_buffer = buf_stat.status_size + buf_stat.retx_size + buf_stat.tx_size;
ret.oldest_sdu_arrival_ms = buf_stat.oldest_sdu_arrival_ms;
} else {
if (!(frame % 128) || channel_idP == 0) //to suppress this warning message
LOG_W(RLC, "Radio Bearer (channel ID %d) is NULL for UE %d\n", channel_idP, ue->ue_id);
ret.bytes_in_buffer = 0;
ret.oldest_sdu_arrival_ms = 0;
}
ret.pdus_in_buffer = 0;

View File

@@ -81,6 +81,8 @@ int nr_rlc_tx_list_occupancy(int ue_id, logical_chan_id_t lcid);
void nr_rlc_activate_avg_time_to_tx(const int ue_id, const logical_chan_id_t channel_id, const bool is_on);
uint64_t get_nr_rlc_current_time(void);
void nr_rlc_srb_recv_sdu(const int ue_id, const logical_chan_id_t channel_id, unsigned char *buf, int size);
bool nr_rlc_activate_srb0(int ue_id,

View File

@@ -25,6 +25,11 @@ typedef struct nr_rlc_sdu_t {
* transmission is used for statistics
*/
uint64_t time_of_arrival; /* unit microsecond */
/* RLC time when the SDU was first received .Used for the HOL-delay
* in MAC. TODO: unify with time_of_arrival (feed E2AP/KPM in us)
*/
uint64_t arrival_ms;
} nr_rlc_sdu_t;
typedef struct nr_rlc_sdu_segment_t {

View File

@@ -54,7 +54,7 @@ void print_ue_mac_stats(const module_id_t mod, const int frame_rx, const int slo
cur += snprintf(cur, end - cur, " DL harq: %lu", mac->stats.dl.rounds[0]);
int nb;
for (nb = NR_MAX_HARQ_ROUNDS_FOR_STATS - 1; nb > 1; nb--)
if (mac->stats.ul.rounds[nb])
if (mac->stats.dl.rounds[nb])
break;
for (int i = 1; i < nb + 1; i++)
cur += snprintf(cur, end - cur, "/%lu", mac->stats.dl.rounds[i]);

View File

@@ -1080,6 +1080,7 @@ static void nr_rrc_apply_qos_add_modify(gNB_RRC_INST *rrc,
const non_dynamic_5qi_t *in_non_dynamic = &q_in->qos_characteristics.non_dynamic;
const dynamic_5qi_t *in_dynamic = &q_in->qos_characteristics.dynamic;
if (q_in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in_non_dynamic->fiveQI)) {
// This is a pre-configured 5QI, not a standardized non-dynamic value: not implemented
LOG_W(NR_RRC,
"QoS flow QFI=%d: 5QI %u is not a standardized value (1-9, 65-90). Skipping QoS flow.\n",
q_in->qfi,

View File

@@ -84,8 +84,8 @@ nr_rrc_qos_t *add_qos(seq_arr_t *qos, const pdusession_level_qos_parameter_t *in
return existing;
}
// Validate 5QI value (TS 23.501 allows dynamically assigned 5QIs)
if (in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in->qos_characteristics.non_dynamic.fiveQI)) {
// This is a pre-configured 5QI, not a standardized non-dynamic value: not implemented
LOG_W(NR_RRC,
"QoS flow QFI=%d: 5QI %d is not a standardized value. Skipping QoS flow.\n",
in->qfi,

View File

@@ -5,6 +5,7 @@ add_library(fgs_5gmm_lib OBJECT
fgs_service_request.c
fgmm_service_accept.c
fgmm_service_reject.c
fgmm_registration_reject.c
RegistrationRequest.c
RegistrationAccept.c
fgmm_identity_request.c

View File

@@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include "fgmm_registration_reject.h"
#include "common/platform_types.h"
#include "common/utils/ds/byte_array.h"
#include "common/utils/eq_check.h"
#include "fgmm_lib.h"
#define REGISTRATION_REJECT_MIN_LEN 1
/** @brief Encode Registration Reject (8.2.7 of 3GPP TS 24.501) */
int encode_fgs_registration_reject(byte_array_t *buffer, const fgs_registration_reject_msg_t *msg)
{
if (buffer->len < REGISTRATION_REJECT_MIN_LEN) {
PRINT_ERROR("Failed to encode Registration Reject: missing Cause IE!\n");
return -1;
}
return encode_fgs_nas_cause(buffer, &msg->cause);
}
/** @brief Decode Registration Reject (8.2.7 of 3GPP TS 24.501) — mandatory 5GMM cause only */
int decode_fgs_registration_reject(fgs_registration_reject_msg_t *msg, const byte_array_t *buffer)
{
if (buffer->len < REGISTRATION_REJECT_MIN_LEN) {
PRINT_ERROR("Nothing to decode: missing Cause IE!\n");
return -1;
}
int decoded = decode_fgs_nas_cause(&msg->cause, buffer);
if (decoded < 0) {
return -1;
}
if (buffer->len > decoded) {
PRINT_ERROR("Optional Registration Reject IEs present but not handled\n");
}
return decoded;
}
bool eq_registration_reject(const fgs_registration_reject_msg_t *a, const fgs_registration_reject_msg_t *b)
{
_EQ_CHECK_INT(a->cause, b->cause);
return true;
}
void free_fgs_registration_reject(fgs_registration_reject_msg_t *msg)
{
UNUSED(msg);
}

View File

@@ -0,0 +1,20 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef FGS_REGISTRATION_REJECT_H
#define FGS_REGISTRATION_REJECT_H
#include "fgmm_lib.h"
#include "common/utils/ds/byte_array.h"
typedef struct {
cause_id_t cause;
} fgs_registration_reject_msg_t;
int decode_fgs_registration_reject(fgs_registration_reject_msg_t *msg, const byte_array_t *buffer);
int encode_fgs_registration_reject(byte_array_t *buffer, const fgs_registration_reject_msg_t *msg);
bool eq_registration_reject(const fgs_registration_reject_msg_t *a, const fgs_registration_reject_msg_t *b);
void free_fgs_registration_reject(fgs_registration_reject_msg_t *msg);
#endif /* FGS_REGISTRATION_REJECT_H */

View File

@@ -12,6 +12,7 @@
#include "fgs_service_request.h"
#include "fgmm_service_accept.h"
#include "fgmm_service_reject.h"
#include "fgmm_registration_reject.h"
#include "fgmm_authentication_failure.h"
#include "FGSNASSecurityModeReject.h"
#include "fgmm_authentication_reject.h"
@@ -258,6 +259,33 @@ static void test_service_reject(void)
free_fgs_service_reject(&orig);
}
/** @brief Test NAS Registration Reject enc/dec (plain AMF reject: cause only) */
static void test_registration_reject(void)
{
fgs_registration_reject_msg_t orig = {
.cause = Illegal_UE,
};
uint8_t expected_enc[] = {0x03};
uint8_t buf[64] = {0};
byte_array_t buffer = {.buf = buf, .len = sizeof(expected_enc)};
int encoded_length = encode_fgs_registration_reject(&buffer, &orig);
AssertFatal(encoded_length == sizeofArray(expected_enc),
"encode_fgs_registration_reject() failed: %d != %ld\n",
encoded_length,
sizeofArray(expected_enc));
AssertFatal(memcmp(buffer.buf, expected_enc, buffer.len) == 0, "Encoding mismatch!\n");
fgs_registration_reject_msg_t dec = {0};
int decoded_length = decode_fgs_registration_reject(&dec, &buffer);
AssertFatal(decoded_length >= 0, "decode_fgs_registration_reject() failed\n");
AssertFatal(eq_registration_reject(&orig, &dec),
"test_registration_reject() failed: original and decoded messages do not match\n");
free_fgs_registration_reject(&dec);
}
/** @brief Test NAS Authentication Failure enc/dec */
static void test_auth_failure(void)
{
@@ -373,6 +401,7 @@ int main()
test_service_request();
test_service_accept();
test_service_reject();
test_registration_reject();
test_auth_failure();
test_auth_reject();
test_security_mode_reject();

View File

@@ -49,6 +49,7 @@
#include "fgs_nas_utils.h"
#include "fgmm_service_accept.h"
#include "fgmm_service_reject.h"
#include "fgmm_registration_reject.h"
#include "fgmm_authentication_reject.h"
#include "ds/byte_array.h"
#include "key_nas_deriver.h"
@@ -128,6 +129,9 @@ static bool unprotected_allowed(byte_array_t buffer, fgs_nas_msg_t msg_type)
case FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING: // for non switch off: deregistration type IE set to NORMAL_DEREGISTRATION
return true;
case FGS_REGISTRATION_REJECT:
// unprotected if the 5GMM cause is not #76 (9.11.3.2)
return buffer.len >= sizeof(fgmm_msg_header_t) + 1
&& buffer.buf[3] != Not_authorized_for_this_CAG_or_authorized_for_CAG_cells_only;
case FGS_SERVICE_REJECT:
// unprotected if the 5GMM cause is not #76
return buffer.buf[4] != Not_authorized_for_this_CAG_or_authorized_for_CAG_cells_only;
@@ -2133,6 +2137,64 @@ static void handle_service_reject(nr_ue_nas_t *nas, const byte_array_t *buffer)
LOG_E(NAS, "Received NAS Service Reject message with cause %s\n", fgmm_cause_s[msg.cause].text);
}
/** @brief Handle Registration Reject (8.2.7 / 5.5.1.2.5 of 3GPP TS 24.501)
* @todo Per §5.5.1.2.5: forbidden PLMN/TAI, registration attempt counter
* @todo N1 NAS signalling release per §5.3.1.3
* @todo Optional IEs T3346/T3502, EAP per §5.4.1.2.2.11 */
static void handle_registration_reject(nr_ue_nas_t *nas, const byte_array_t *buffer)
{
DevAssert(buffer && buffer->buf);
if (nas->termination_procedure) {
return; // already in termination procedure, ignore registration reject
}
fgs_registration_reject_msg_t msg = {0};
if (buffer->len < sizeof(fgmm_msg_header_t)) {
LOG_E(NAS, "Failed to extract Registration Reject message body: buffer length too short\n");
return;
}
const uint8_t *pdu_buffer = buffer->buf;
uint32_t msg_length = buffer->len;
const uint8_t *end = pdu_buffer + msg_length;
if (pdu_buffer[1] != PLAIN_5GS_MSG) {
fgs_nas_message_security_header_t sp_header = {0};
int decoded = decode_5gs_security_protected_header(&sp_header, pdu_buffer, msg_length);
if (decoded < 0) {
LOG_E(NAS, "Registration Reject: failed to decode security protected header\n");
return;
}
pdu_buffer += decoded;
}
fgmm_msg_header_t mm_header = {0};
int decoded = decode_5gmm_msg_header(&mm_header, pdu_buffer, end - pdu_buffer);
if (decoded < 0) {
LOG_E(NAS, "Registration Reject: failed to decode NAS message header\n");
return;
}
if (mm_header.message_type != FGS_REGISTRATION_REJECT) {
LOG_E(NAS, "Expected NAS message type FGS_REGISTRATION_REJECT, got %#x\n", mm_header.message_type);
return;
}
pdu_buffer += decoded;
const byte_array_t ba = {.buf = (uint8_t *)pdu_buffer, .len = end - pdu_buffer};
if (decode_fgs_registration_reject(&msg, &ba) < 0) {
LOG_E(NAS, "Registration Reject: failed to decode NAS message body\n");
free_fgs_registration_reject(&msg);
return;
}
LOG_E(NAS, "Received Registration Reject cause: %s\n", print_info(msg.cause, fgmm_cause_s, sizeofArray(fgmm_cause_s)));
free_fgs_registration_reject(&msg);
nas->fiveGMM_state = FGS_DEREGISTERED;
nas->termination_procedure = true;
send_nas_detach_req(nas, false);
}
void *nas_nrue(void *args_p)
{
UNUSED(args_p);
@@ -2398,23 +2460,9 @@ void *nas_nrue(void *args_p)
case FGS_PDU_SESSION_ESTABLISHMENT_REJ:
LOG_E(NAS, "Received PDU Session Establishment reject\n");
break;
case FGS_REGISTRATION_REJECT: {
if (pdu_length < 18) {
LOG_E(NAS, "Received Registration reject message too short\n");
break;
}
uint8_t cause = pdu_buffer[17];
if (cause >= sizeof(cause_text_info) / sizeof(cause_text_info[0])) {
LOG_E(NAS, "Received Registration reject cause %d unknown\n", cause);
break;
}
LOG_E(NAS, "Received Registration reject cause: %s\n", cause_text_info[cause].text);
exit(1);
case FGS_REGISTRATION_REJECT:
handle_registration_reject(nas, &buffer);
break;
}
case FGS_SERVICE_ACCEPT: {
handle_service_accept(nas, &buffer);
break;

View File

@@ -133,6 +133,14 @@ nssai_t decode_ngap_nssai(const NGAP_S_NSSAI_t *in)
return nssai;
}
void encode_ngap_security_capabilities(NGAP_UESecurityCapabilities_t *out, const ngap_security_capabilities_t *in)
{
ENCRALG_TO_BIT_STRING(in->nRencryption_algorithms, &out->nRencryptionAlgorithms);
ENCRALG_TO_BIT_STRING(in->eUTRAencryption_algorithms, &out->eUTRAencryptionAlgorithms);
INTPROTALG_TO_BIT_STRING(in->nRintegrity_algorithms, &out->nRintegrityProtectionAlgorithms);
INTPROTALG_TO_BIT_STRING(in->eUTRAintegrity_algorithms, &out->eUTRAintegrityProtectionAlgorithms);
}
ngap_security_capabilities_t decode_ngap_security_capabilities(const NGAP_UESecurityCapabilities_t *in)
{
ngap_security_capabilities_t out = {0};

View File

@@ -41,6 +41,7 @@ pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQo
void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf);
bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in);
byte_array_t encode_ngap_pdusession_setup_response_transfer(const pdusession_setup_t *pdusession);
void encode_ngap_security_capabilities(NGAP_UESecurityCapabilities_t *out, const ngap_security_capabilities_t *in);
bool eq_ngap_plmn(const plmn_id_t *a, const plmn_id_t *b);

View File

@@ -281,6 +281,56 @@ void ngap_gNB_handle_sctp_data_ind(sctp_data_ind_t *sctp_data_ind) {
AssertFatal (result == EXIT_SUCCESS, "Failed to free memory (%d)!\n", result);
}
static int ngap_gNB_path_switch_request(instance_t instance, ngap_path_switch_req_t *msg)
{
DevAssert(msg != NULL);
NGAP_DEBUG("Triggered Pathswitch Request\n");
/*Retrieve the NGAP gNB instance associated with Mod_id */
ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
DevAssert(ngap_gNB_instance_p != NULL);
NGAP_NGAP_PDU_t *pdu = encode_ng_path_switch_request(msg);
DevAssert(pdu != NULL);
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, pdu);
byte_array_t out = { .buf = NULL, .len = 0 };
if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) {
NGAP_ERROR("Failed to encode Path Switch Request message\n");
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
return -1;
}
ngap_gNB_amf_data_t *amf_ref = ngap_gNB_get_AMF_from_instance(ngap_gNB_instance_p);
if (!amf_ref) {
NGAP_ERROR("Failed to fetch AMF for current NGAP instance\n");
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
return -1;
}
// Create and then store the NGAP UE context
ngap_gNB_ue_context_t ue_context_p = {
.amf_ref = amf_ref,
.gNB_ue_ngap_id = msg->gNB_ue_ngap_id,
.amf_ue_ngap_id = msg->amf_ue_ngap_id,
.gNB_instance = ngap_gNB_instance_p,
.ue_state = NGAP_UE_CONNECTED,
};
ngap_store_ue_context(&ue_context_p);
/* UE associated signalling -> use the allocated stream */
ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance,
ue_context_p.amf_ref->assoc_id,
out.buf,
out.len,
ue_context_p.tx_stream);
NGAP_INFO("Sent Path Switch Request to AMF\n");
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
return 0;
}
/** @brief UE Mobility Management: callback for Handover Required */
int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg)
{
@@ -613,6 +663,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed)
ngap_gNB_pdusession_release_resp(instance, &NGAP_PDUSESSION_RELEASE_RESPONSE(received_msg));
break;
case NGAP_PATH_SWITCH_REQ:
ngap_gNB_path_switch_request(instance, &NGAP_PATH_SWITCH_REQ(received_msg));
break;
case NGAP_HANDOVER_REQUIRED:
if (ngap_handover_required(instance, &NGAP_HANDOVER_REQUIRED(received_msg)) < 0) {
NGAP_ERROR("Handover Required failure: indication to RRC is not sent!\n");

View File

@@ -1092,6 +1092,54 @@ static int ngap_gNB_handle_handover_cancel_ack(sctp_assoc_t assoc_id, uint32_t s
return 0;
}
/** @brief Handler for NGAP Path Switch Request Acknowledge
* AMF -> NG-RAN Node */
static int ngap_gNB_handle_ng_path_switch_request_ack(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
{
NGAP_INFO("Received NG Path Switch Request Acknowledge\n");
ngap_gNB_amf_data_t *amf_desc_p = NULL;
DevAssert(pdu != NULL);
if ((amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0)) == NULL) {
NGAP_ERROR("[SCTP %u] Received Path Switch Request "
"Acknowledge for non existing AMF context\n", assoc_id);
return -1;
}
MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_PATH_SWITCH_REQ_ACK);
ngap_path_switch_req_ack_t *msg = &NGAP_PATH_SWITCH_REQ_ACK(message_p);
memset(msg, 0, sizeof(*msg));
if (decode_ng_path_switch_request_acknowledge(msg, pdu) < 0) {
NGAP_ERROR("Failed to decode NG Path Switch Request Acknowledge\n");
free_ng_path_switch_req_ack(msg);
itti_free(TASK_NGAP, message_p);
return -1;
}
ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(msg->gNB_ue_ngap_id);
if (!ue_desc_p) {
NGAP_ERROR("[SCTP %u] Received Path Switch Request Acknowledge for non "
"existing UE context (gNB_ue_ngap_id %d)\n",
assoc_id,
msg->gNB_ue_ngap_id);
free_ng_path_switch_req_ack(msg);
itti_free(TASK_NGAP, message_p);
return -1;
}
ue_desc_p->rx_stream = stream;
if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) {
NGAP_ERROR("UE context amf_ue_ngap_id is different from that of the message (%ld != %ld)",
ue_desc_p->amf_ue_ngap_id,
msg->amf_ue_ngap_id);
free_ng_path_switch_req_ack(msg);
itti_free(TASK_NGAP, message_p);
return -1;
}
itti_send_msg_to_task(TASK_RRC_GNB, amf_desc_p->ngap_gNB_instance->instance, message_p);
return 0;
}
/**
* @brief Handle NGAP Paging message from AMF
* @param assoc_id SCTP association ID
@@ -1542,7 +1590,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = {
{0, 0, 0}, /* OverloadStart */
{0, 0, 0}, /* OverloadStop */
{ngap_gNB_handle_paging, 0, 0}, /* Paging */
{0, 0, 0}, /* PathSwitchRequest */
{0, ngap_gNB_handle_ng_path_switch_request_ack, 0}, /* PathSwitchRequest */
{ngap_gNB_handle_pdusession_modify_request, 0, 0}, /* PDUSessionResourceModify */
{0, 0, 0}, /* PDUSessionResourceModifyIndication */
{ngap_gNB_handle_pdusession_release_command, 0, 0}, /* PDUSessionResourceRelease */

View File

@@ -732,3 +732,184 @@ NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer
return pdu;
}
/** @brief Encode NGAP Path Switch Request (9.2.3.8 3GPP TS 38.413) NG-RAN node -> AMF */
NGAP_NGAP_PDU_t *encode_ng_path_switch_request(const ngap_path_switch_req_t *msg)
{
NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1,sizeof(*pdu));
/* Prepare the NGAP message to encode */
pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage;
asn1cCalloc(pdu->choice.initiatingMessage, head);
head->procedureCode = NGAP_ProcedureCode_id_PathSwitchRequest;
head->criticality = NGAP_Criticality_reject;
head->value.present = NGAP_InitiatingMessage__value_PR_PathSwitchRequest;
NGAP_PathSwitchRequest_t *out = &head->value.choice.PathSwitchRequest;
// RAN UE NGAP ID (M)
asn1cSequenceAdd(out->protocolIEs.list, NGAP_PathSwitchRequestIEs_t, ie1);
ie1->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID;
ie1->criticality = NGAP_Criticality_reject;
ie1->value.present = NGAP_PathSwitchRequestIEs__value_PR_RAN_UE_NGAP_ID;
ie1->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id;
// Source AMF UE NGAP ID (M)
asn1cSequenceAdd(out->protocolIEs.list, NGAP_PathSwitchRequestIEs_t, ie2);
ie2->id = NGAP_ProtocolIE_ID_id_SourceAMF_UE_NGAP_ID;
ie2->criticality = NGAP_Criticality_reject;
ie2->value.present = NGAP_PathSwitchRequestIEs__value_PR_AMF_UE_NGAP_ID;
asn_uint642INTEGER(&ie2->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
// User Location Information (M)
{
asn1cSequenceAdd(out->protocolIEs.list, NGAP_PathSwitchRequestIEs_t, ie3);
ie3->id = NGAP_ProtocolIE_ID_id_UserLocationInformation;
ie3->criticality = NGAP_Criticality_ignore;
ie3->value.present = NGAP_PathSwitchRequestIEs__value_PR_UserLocationInformation;
ie3->value.choice.UserLocationInformation.present = NGAP_UserLocationInformation_PR_userLocationInformationNR;
asn1cCalloc(ie3->value.choice.UserLocationInformation.choice.userLocationInformationNR, userinfo_nr_p);
// NR user location information
const target_ran_node_id_t *target = &msg->user_info.target_ng_ran;
// CGI (M)
MACRO_GNB_ID_TO_CELL_IDENTITY(target->targetgNBId, msg->user_info.nrCellIdentity, &userinfo_nr_p->nR_CGI.nRCellIdentity);
MCC_MNC_TO_TBCD(target->plmn_identity.mcc,
target->plmn_identity.mnc,
target->plmn_identity.mnc_digit_length,
&userinfo_nr_p->nR_CGI.pLMNIdentity);
// TAI (M)
INT24_TO_OCTET_STRING(target->tac, &userinfo_nr_p->tAI.tAC);
MCC_MNC_TO_PLMNID(target->plmn_identity.mcc,
target->plmn_identity.mnc,
target->plmn_identity.mnc_digit_length,
&userinfo_nr_p->tAI.pLMNIdentity);
}
// UE Security Capabilities (M)
asn1cSequenceAdd(out->protocolIEs.list, NGAP_PathSwitchRequestIEs_t, ie4);
ie4->id = NGAP_ProtocolIE_ID_id_UESecurityCapabilities;
ie4->criticality = NGAP_Criticality_ignore;
ie4->value.present = NGAP_PathSwitchRequestIEs__value_PR_UESecurityCapabilities;
encode_ngap_security_capabilities(&ie4->value.choice.UESecurityCapabilities, &msg->security_capabilities);
// PDU Session Resource to be Switched in Downlink List (M)
{
asn1cSequenceAdd(out->protocolIEs.list, NGAP_PathSwitchRequestIEs_t, ie5);
ie5->id = NGAP_ProtocolIE_ID_id_PDUSessionResourceToBeSwitchedDLList;
ie5->criticality = NGAP_Criticality_reject;
ie5->value.present = NGAP_PathSwitchRequestIEs__value_PR_PDUSessionResourceToBeSwitchedDLList;
for (const pdusession_setup_t *pdu = msg->pdusessions_tobeswitched; pdu < msg->pdusessions_tobeswitched + msg->nb_of_pdusessions; pdu++) {
asn1cSequenceAdd(ie5->value.choice.PDUSessionResourceToBeSwitchedDLList.list, NGAP_PDUSessionResourceToBeSwitchedDLItem_t, item);
item->pDUSessionID = pdu->pdusession_id;
/* dLQoSFlowPerTNLInformation */
NGAP_PathSwitchRequestTransfer_t transfer = {0};
transfer.dL_NGU_UP_TNLInformation.present = NGAP_UPTransportLayerInformation_PR_gTPTunnel;
asn1cCalloc(transfer.dL_NGU_UP_TNLInformation.choice.gTPTunnel, tmp);
GTP_TEID_TO_ASN1(pdu->n3_outgoing.teid, &tmp->gTP_TEID);
tnl_to_bitstring(&tmp->transportLayerAddress, pdu->n3_outgoing.addr);
for (int j = 0; j < pdu->nb_of_qos_flow; j++) {
asn1cSequenceAdd(transfer.qosFlowAcceptedList.list, NGAP_QosFlowAcceptedItem_t, qosItem);
qosItem->qosFlowIdentifier = pdu->associated_qos_flows[j].qfi;
}
void *buf;
ssize_t encoded = aper_encode_to_new_buffer(&asn_DEF_NGAP_PathSwitchRequestTransfer, NULL, &transfer, &buf);
AssertFatal(encoded > 0, "ASN1 message encoding failed !\n");
item->pathSwitchRequestTransfer.buf = buf;
item->pathSwitchRequestTransfer.size = encoded;
ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PathSwitchRequestTransfer, &transfer);
}
}
return pdu;
}
void free_ng_path_switch_req_ack(ngap_path_switch_req_ack_t *msg)
{
if (msg == NULL)
return;
for (int i = 0; i < msg->nb_of_pdusessions; i++) {
path_switch_request_ack_transfer_t *transfer = &msg->pdusessions_switched[i].pathSwitchReqAckTransfer;
free(transfer->n3_incoming);
transfer->n3_incoming = NULL;
free(transfer->security_ind);
transfer->security_ind = NULL;
}
}
/** @brief Decode NGAP Path Switch Request Acknowledge (9.2.3.9 3GPP TS 38.413) */
int decode_ng_path_switch_request_acknowledge(ngap_path_switch_req_ack_t *msg, NGAP_NGAP_PDU_t *pdu)
{
DevAssert(pdu != NULL);
NGAP_PathSwitchRequestAcknowledgeIEs_t *ie;
NGAP_PathSwitchRequestAcknowledge_t *container = &pdu->choice.successfulOutcome->value.choice.PathSwitchRequestAcknowledge;
// AMF UE NGAP ID (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PathSwitchRequestAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true);
asn_INTEGER2ulong(&(ie->value.choice.AMF_UE_NGAP_ID), &msg->amf_ue_ngap_id);
// RAN UE NGAP ID (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PathSwitchRequestAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true);
msg->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID;
// Security Context (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PathSwitchRequestAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_SecurityContext, true);
msg->nh_ncc = ie->value.choice.SecurityContext.nextHopChainingCount;
memcpy(msg->next_security_key, ie->value.choice.SecurityContext.nextHopNH.buf, ie->value.choice.SecurityContext.nextHopNH.size);
// PDU Session Resource Switched List (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PathSwitchRequestAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_PDUSessionResourceSwitchedList, true);
msg->nb_of_pdusessions = ie->value.choice.PDUSessionResourceSwitchedList.list.count;
for (int i = 0; i < msg->nb_of_pdusessions; ++i) {
NGAP_PDUSessionResourceSwitchedItem_t *item = ie->value.choice.PDUSessionResourceSwitchedList.list.array[i];
path_switch_request_ack_pdusession_t *switched = &msg->pdusessions_switched[i];
// PDU Session ID (M)
switched->pdusession_id = item->pDUSessionID;
// Path Switch Request Acknowledge Transfer (M)
NGAP_PathSwitchRequestAcknowledgeTransfer_t *psrAckTransfer = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
&asn_DEF_NGAP_PathSwitchRequestAcknowledgeTransfer,
(void **)&psrAckTransfer,
item->pathSwitchRequestAcknowledgeTransfer.buf,
item->pathSwitchRequestAcknowledgeTransfer.size);
if (dec_rval.code != RC_OK) {
NGAP_ERROR("Failed to decode Path Switch Request Acknowledge Transfer\n");
ASN_STRUCT_FREE(asn_DEF_NGAP_PathSwitchRequestAcknowledgeTransfer, psrAckTransfer);
return -1;
}
// UL NG-U UP TNL Information (O)
if (psrAckTransfer->uL_NGU_UP_TNLInformation != NULL) {
NGAP_UPTransportLayerInformation_t *up_tnl = psrAckTransfer->uL_NGU_UP_TNLInformation;
if (up_tnl->present == NGAP_UPTransportLayerInformation_PR_gTPTunnel && up_tnl->choice.gTPTunnel != NULL) {
switched->pathSwitchReqAckTransfer.n3_incoming = calloc_or_fail(1, sizeof(*switched->pathSwitchReqAckTransfer.n3_incoming));
OCTET_STRING_TO_INT32(&(up_tnl->choice.gTPTunnel->gTP_TEID), switched->pathSwitchReqAckTransfer.n3_incoming->teid);
bitstring_to_tnl(&switched->pathSwitchReqAckTransfer.n3_incoming->addr, up_tnl->choice.gTPTunnel->transportLayerAddress);
}
}
// Security Indication (O)
if (psrAckTransfer->securityIndication != NULL) {
switched->pathSwitchReqAckTransfer.security_ind = calloc_or_fail(1, sizeof(*switched->pathSwitchReqAckTransfer.security_ind));
switched->pathSwitchReqAckTransfer.security_ind->integrity_protection_ind = psrAckTransfer->securityIndication->integrityProtectionIndication;
switched->pathSwitchReqAckTransfer.security_ind->confidentiality_protection_ind = psrAckTransfer->securityIndication->confidentialityProtectionIndication;
}
ASN_STRUCT_FREE(asn_DEF_NGAP_PathSwitchRequestAcknowledgeTransfer, psrAckTransfer);
}
// Allowed NSSAI (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PathSwitchRequestAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AllowedNSSAI, true);
NGAP_DEBUG("AllowedNSSAI.list.count %d\n", ie->value.choice.AllowedNSSAI.list.count);
msg->nb_allowed_nssais = ie->value.choice.AllowedNSSAI.list.count;
for (int i = 0; i < msg->nb_allowed_nssais; ++i) {
msg->allowed_nssai[i] = decode_ngap_nssai(&ie->value.choice.AllowedNSSAI.list.array[i]->s_NSSAI);
}
return 0;
}

View File

@@ -20,5 +20,8 @@ NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg);
NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg);
NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg);
int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu);
NGAP_NGAP_PDU_t *encode_ng_path_switch_request(const ngap_path_switch_req_t *msg);
int decode_ng_path_switch_request_acknowledge(ngap_path_switch_req_ack_t *msg, NGAP_NGAP_PDU_t *pdu);
void free_ng_path_switch_req_ack(ngap_path_switch_req_ack_t *msg);
#endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */

View File

@@ -97,4 +97,9 @@
#include "NGAP_DRBStatusDL12.h"
#include "NGAP_QosFlowListWithCause.h"
#include "NGAP_QosFlowWithCauseItem.h"
#include "NGAP_PDUSessionResourceToBeSwitchedDLItem.h"
#include "NGAP_QosFlowAcceptedItem.h"
#include "NGAP_PathSwitchRequestTransfer.h"
#include "NGAP_PDUSessionResourceSwitchedItem.h"
#include "NGAP_PathSwitchRequestAcknowledgeTransfer.h"
#endif // NGAP_MSG_INCLUDES_H

View File

@@ -1,18 +1,6 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
<table style="border-collapse: collapse; border: none;">
<tr style="border-collapse: collapse; border: none;">
<td style="border-collapse: collapse; border: none;">
<a href="http://www.openairinterface.org/">
<img src="../doc/images/oai_final_logo.png" alt="" border=3 height=50 width=150>
</img>
</a>
</td>
<td style="border-collapse: collapse; border: none; vertical-align: center;">
<b><font size = "5">OpenShift Build and Usage Procedures</font></b>
</td>
</tr>
</table>
OpenShift Build and Usage Procedures
[[_TOC_]]

View File

@@ -147,6 +147,19 @@ int openair0_transport_load(openair0_device_t *device, openair0_config_t *openai
return rc;
}
int openair0_load(openair0_device_t *device, char *name, openair0_config_t *openair0_cfg, eth_params_t *eth_params)
{
loader_shlibfunc_t shlib_fdesc[1];
int ret = 0;
shlib_fdesc[0].fname = eth_params == NULL ? "device_init" : "transport_init";
ret = load_module_shlib(name, shlib_fdesc, 1, NULL);
AssertFatal((ret >= 0), "Library %s couldn't be loaded\n", name);
return ((devfunc_t)shlib_fdesc[0].fptr)(device, openair0_cfg, eth_params);
}
static void writerEnqueue(re_order_t *ctx, openair0_timestamp_t timestamp, void **txp, int nsamps, int nbAnt, int flags)
{
pthread_mutex_lock(&ctx->mutex_store);

View File

@@ -535,6 +535,10 @@ struct openair0_device {
*/
int (*trx_stop_func)(openair0_device_t *device);
/*! \brief Get timestamp from timespec
*/
openair0_timestamp_t (*get_timestamp)(openair0_device_t *device, struct timespec *ts);
/* Functions API related to UE*/
/*! \brief Set RX feaquencies
@@ -661,6 +665,7 @@ const char *get_devname(int devtype);
int openair0_device_load(openair0_device_t *device, openair0_config_t *openair0_cfg);
/*! \brief Initialize transport protocol . It returns 0 if OK */
int openair0_transport_load(openair0_device_t *device, openair0_config_t *openair0_cfg, eth_params_t *eth_params);
int openair0_load(openair0_device_t *device, char *name, openair0_config_t *openair0_cfg, eth_params_t *eth_params);
/*! \brief Set RX frequencies
* \param device the hardware to use

View File

@@ -44,16 +44,26 @@ class VRTSIMTest : public ::testing::TestWithParam<VRTSIMTestCase> {
openair0_device_t client_device = {0};
openair0_config_t server_config = {0};
openair0_config_t client_config = {0};
std::string socket_path;
std::string shm_name;
void SetUp() override
{
const auto &param = GetParam();
std::string unique_suffix = std::to_string(getpid()) + "_" + std::to_string(rand());
socket_path = "/tmp/vrtsim_connection_" + unique_suffix;
shm_name = "vrtsim_channel_" + unique_suffix;
// Setup server
std::vector<const char *> server_argv = {"--vrtsim.role",
"server",
"--vrtsim.disable-timing-thread",
"1",
"--vrtsim.connection_descriptor",
socket_path.c_str(),
"--vrtsim.shm_channel_name",
shm_name.c_str(),
"--vrtsim.ue_config.[0].antennas",
param.ue_antennas.c_str()};
cfg1 = load_configmodule(server_argv.size(), (char **)server_argv.data(), CONFIG_ENABLECMDLINEONLY);
@@ -65,7 +75,12 @@ class VRTSIMTest : public ::testing::TestWithParam<VRTSIMTestCase> {
ASSERT_EQ(server_device.trx_start_func(&server_device), 0);
// Setup client
std::vector<const char *> client_argv = {"--vrtsim.role", "client"};
std::vector<const char *> client_argv = {"--vrtsim.role",
"client",
"--vrtsim.connection_descriptor",
socket_path.c_str(),
"--vrtsim.shm_channel_name",
shm_name.c_str()};
cfg2 = load_configmodule(client_argv.size(), (char **)client_argv.data(), CONFIG_ENABLECMDLINEONLY);
uniqCfg = cfg2;
client_config.tx_num_channels = param.client_tx;
@@ -85,6 +100,7 @@ class VRTSIMTest : public ::testing::TestWithParam<VRTSIMTestCase> {
end_configmodule(cfg1);
if (cfg2)
end_configmodule(cfg2);
uniqCfg = nullptr;
}
};
@@ -180,17 +196,27 @@ class VRTSIMMultiUETest : public ::testing::TestWithParam<VRTSIMMultiTestCase> {
std::vector<openair0_device_t> client_devices;
openair0_config_t server_config = {0};
std::vector<openair0_config_t> client_configs;
std::string socket_path;
std::string shm_name;
void SetUp() override
{
const auto &param = GetParam();
std::string unique_suffix = std::to_string(getpid()) + "_" + std::to_string(rand());
socket_path = "/tmp/vrtsim_connection_" + unique_suffix;
shm_name = "vrtsim_channel_" + unique_suffix;
// Setup server
std::vector<std::string> server_args_store = {"test_vrtsim",
"--vrtsim.role",
"server",
"--vrtsim.disable-timing-thread",
"1",
"--vrtsim.connection_descriptor",
socket_path,
"--vrtsim.shm_channel_name",
shm_name,
"--vrtsim.num_ues",
std::to_string(param.num_ues)};
for (int i = 0; i < param.num_ues; i++) {
@@ -217,7 +243,15 @@ class VRTSIMMultiUETest : public ::testing::TestWithParam<VRTSIMMultiTestCase> {
cfg_clients.resize(param.num_ues);
for (int i = 0; i < param.num_ues; i++) {
std::vector<std::string> client_args_store = {"test_vrtsim", "--vrtsim.role", "client", "--vrtsim.ue_id", std::to_string(i)};
std::vector<std::string> client_args_store = {"test_vrtsim",
"--vrtsim.role",
"client",
"--vrtsim.connection_descriptor",
socket_path,
"--vrtsim.shm_channel_name",
shm_name,
"--vrtsim.ue_id",
std::to_string(i)};
std::vector<const char *> client_argv;
for (const auto &arg : client_args_store) {
client_argv.push_back(arg.c_str());
@@ -254,6 +288,7 @@ class VRTSIMMultiUETest : public ::testing::TestWithParam<VRTSIMMultiTestCase> {
if (cfg_server) {
end_configmodule(cfg_server);
}
uniqCfg = nullptr;
}
};

View File

@@ -272,7 +272,7 @@ static void *vrtsim_timing_job(void *arg)
shm_td_iq_channel_produce_samples(vrtsim_state->channel, samples_to_produce);
last_sample_index = sample_index;
}
usleep(1);
usleep(20);
}
return 0;
}
@@ -1100,6 +1100,14 @@ __attribute__((__visibility__("default"))) void vrtsim_produce_samples(openair0_
shm_td_iq_channel_produce_samples(vrtsim_state->channel, num_samples);
}
openair0_timestamp_t vrtsim_get_timestamp(openair0_device_t *device, struct timespec *ts)
{
vrtsim_state_t *vrtsim_state = (vrtsim_state_t *)device->priv;
int64_t diff = (ts->tv_sec - vrtsim_state->start_ts.tv_sec) * 1000000000 + (ts->tv_nsec - vrtsim_state->start_ts.tv_nsec);
double diff_samples = vrtsim_state->sample_rate * vrtsim_state->timescale * diff / 1e9;
return diff_samples;
}
__attribute__((__visibility__("default"))) int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg)
{
randominit();
@@ -1119,6 +1127,9 @@ __attribute__((__visibility__("default"))) int device_init(openair0_device_t *de
device->trx_write_beams_func = vrtsim_write_beams;
device->trx_set_beams = vrtsim_set_beams;
device->trx_set_beams2 = vrtsim_set_beams2;
if (vrtsim_state->role == ROLE_SERVER) {
device->get_timestamp = vrtsim_get_timestamp;
}
device->type = RFSIMULATOR;
device->openair0_cfg = &openair0_cfg[0];