Merge remote-tracking branch 'origin/zmq-radio-3' into integration_2026_w17 (!3975)
ZMQ radio This is a zmq radio implementation that attempts to integrate NR UE with the ocudu project. OAI integration status: This can currently be used to connect OAI gnb and OAI UE. Compilation: cmake --build . --target nr-softmodem nr-uesoftmodem ldpc params_libconfig zmq_radio Running - gNB command: sudo ./nr-softmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --device.name zmq_radio --zmq.[0].tx_channels tcp://127.0.0.1:4556 --zmq.[0].rx_channels tcp://127.0.0.1:4557 - UE command: sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --device.name zmq_radio --zmq.[0].tx_channels tcp://127.0.0.1:4557 --zmq.[0].rx_channels tcp://127.0.0.1:4556 A new CI testcase for 2x2 configuration was added. OCUDU integration status: The OAI NR UE enters RRC Connected state. For more details, please refer to the description of MR !3975.
This commit is contained in:
@@ -84,3 +84,11 @@ Nuand: https://github.com/Nuand/bladeRF/tree/master?tab=License-1-ov-file
|
||||
|
||||
Credits for https://github.com/pothosware/SoapySDR
|
||||
Pothosware: BSL 1.0 License
|
||||
|
||||
Credits for https://github.com/zeromq/libzmq
|
||||
ZeroMQ authors: Mozilla Public License Version 2.0
|
||||
|
||||
Credits for source code:
|
||||
- radio/zmq/zmq_imported.cpp
|
||||
- radio/zmq/zmq_imported.h
|
||||
Software Radio Systems Limited: BSD-3-Clause-Open-MPI
|
||||
|
||||
@@ -13,11 +13,12 @@ RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get upgrade --yes && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --yes \
|
||||
libgtest-dev \
|
||||
libyaml-cpp-dev
|
||||
libyaml-cpp-dev \
|
||||
libzmq3-dev
|
||||
|
||||
RUN rm -Rf /oai-ran
|
||||
WORKDIR /oai-ran
|
||||
COPY . .
|
||||
|
||||
WORKDIR /oai-ran/build
|
||||
RUN cmake -GNinja -DENABLE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DSANITIZE_ADDRESS=True .. && ninja tests
|
||||
RUN cmake -GNinja -DENABLE_TESTS=ON -DOAI_ZMQ=ON -DCMAKE_BUILD_TYPE=Debug -DSANITIZE_ADDRESS=True .. && ninja tests
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->
|
||||
|
||||
<testCaseList>
|
||||
<htmlTabRef>zmq-5gnr-tdd-2x2</htmlTabRef>
|
||||
<htmlTabName>Monolithic SA TDD 2x2 gNB with zmq radio</htmlTabName>
|
||||
<htmlTabIcon>wrench</htmlTabIcon>
|
||||
|
||||
<testCase>
|
||||
<class>Pull_Local_Registry</class>
|
||||
<desc>Pull Images from Local Registry</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Create_Workspace</class>
|
||||
<desc>Create new Workspace</desc>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OAI 5G CoreNetwork</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_2x2/</yaml_path>
|
||||
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OAI 5G gNB+UE RF sim SA</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_2x2</yaml_path>
|
||||
<services>oai-gnb oai-nr-ue</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Attach_UE</class>
|
||||
<desc>Attach OAI UE (Wait for IP)</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping ext-dn from NR-UE</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping NR-UE from ext-dn</desc>
|
||||
<id>rfsim5g_ext_dn</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ue</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (DL/10Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 10M -t 20 -R</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (UL/3Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 20</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Undeploy_Object</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Undeploy all OAI 5G stack</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_2x2</yaml_path>
|
||||
<analysis>
|
||||
<services>oai-gnb=RetxCheck=1,0,0,0 oai-gnb=EndsWithBye oai-nr-ue</services>
|
||||
</analysis>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Clean_Test_Server_Images</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Clean Test Images on Test Server</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
</testCaseList>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->
|
||||
|
||||
<testCaseList>
|
||||
<htmlTabRef>ocudu-5gnr-tdd-1x1</htmlTabRef>
|
||||
<htmlTabName>OCUDU gnb split8 with OAI UE</htmlTabName>
|
||||
<htmlTabIcon>wrench</htmlTabIcon>
|
||||
|
||||
<testCase>
|
||||
<class>Pull_Local_Registry</class>
|
||||
<desc>Pull Images from Local Registry</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Create_Workspace</class>
|
||||
<desc>Create new Workspace</desc>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OAI 5G CoreNetwork</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_1x1_ocudu</yaml_path>
|
||||
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OCUDU Split 8 gNB + OAI UE</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_1x1_ocudu</yaml_path>
|
||||
<services>ocudu-gnb oai-nr-ue</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Attach_UE</class>
|
||||
<desc>Attach OAI UE (Wait for IP)</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping ext-dn from NR-UE</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping NR-UE from ext-dn</desc>
|
||||
<id>rfsim5g_ext_dn</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ue</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (DL/3Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 20 -R</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (UL/3Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 3M -t 20</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Undeploy_Object</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Undeploy all OAI 5G stack</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_1x1_ocudu</yaml_path>
|
||||
<analysis>
|
||||
<services>oai-nr-ue</services>
|
||||
</analysis>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Clean_Test_Server_Images</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Clean Test Images on Test Server</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
</testCaseList>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CSSL-1.0 -->
|
||||
|
||||
<testCaseList>
|
||||
<htmlTabRef>ocudu-5gnr-tdd-2x2</htmlTabRef>
|
||||
<htmlTabName>OCUDU gnb split8 with OAI UE</htmlTabName>
|
||||
<htmlTabIcon>wrench</htmlTabIcon>
|
||||
|
||||
<testCase>
|
||||
<class>Pull_Local_Registry</class>
|
||||
<desc>Pull Images from Local Registry</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Create_Workspace</class>
|
||||
<desc>Create new Workspace</desc>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OAI 5G CoreNetwork</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_2x2_ocudu</yaml_path>
|
||||
<services>mysql oai-amf oai-smf oai-upf oai-ext-dn</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Deploy_Object</class>
|
||||
<desc>Deploy OCUDU Split 8 gNB + OAI UE</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_2x2_ocudu</yaml_path>
|
||||
<services>ocudu-gnb oai-nr-ue</services>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Attach_UE</class>
|
||||
<desc>Attach OAI UE (Wait for IP)</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping ext-dn from NR-UE</desc>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Ping</class>
|
||||
<desc>Ping NR-UE from ext-dn</desc>
|
||||
<id>rfsim5g_ext_dn</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ue</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<ping_args>-c 20 -i 0.25</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (DL/10Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 1M -t 20 -R</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Iperf</class>
|
||||
<desc>Iperf (UL/3Mbps/UDP)(20 sec)</desc>
|
||||
<iperf_args>-u -b 1M -t 20</iperf_args>
|
||||
<id>rfsim5g_ue</id>
|
||||
<node>localhost</node>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>localhost</svr_node>
|
||||
<iperf_packetloss_threshold>5</iperf_packetloss_threshold>
|
||||
<iperf_bitrate_threshold>90</iperf_bitrate_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Undeploy_Object</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Undeploy all OAI 5G stack</desc>
|
||||
<node>localhost</node>
|
||||
<yaml_path>ci-scripts/yaml_files/5g_zmq_radio_2x2_ocudu</yaml_path>
|
||||
<analysis>
|
||||
<services>oai-nr-ue</services>
|
||||
</analysis>
|
||||
</testCase>
|
||||
|
||||
<testCase>
|
||||
<class>Clean_Test_Server_Images</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Clean Test Images on Test Server</desc>
|
||||
<node>localhost</node>
|
||||
<images>oai-gnb-asan oai-nr-ue-asan</images>
|
||||
</testCase>
|
||||
|
||||
</testCaseList>
|
||||
@@ -0,0 +1,162 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
services:
|
||||
mysql:
|
||||
container_name: "rfsim5g-mysql"
|
||||
image: mysql:8.0
|
||||
init: true
|
||||
volumes:
|
||||
- ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||
- ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- MYSQL_DATABASE=oai_db
|
||||
- MYSQL_USER=test
|
||||
- MYSQL_PASSWORD=test
|
||||
- MYSQL_ROOT_PASSWORD=linux
|
||||
healthcheck:
|
||||
test: /bin/bash -c "/tmp/mysql-healthcheck.sh"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.131
|
||||
oai-amf:
|
||||
container_name: "rfsim5g-oai-amf"
|
||||
image: oaisoftwarealliance/oai-amf:v2.1.10
|
||||
environment:
|
||||
- TZ=Europe/paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.132
|
||||
oai-smf:
|
||||
container_name: "rfsim5g-oai-smf"
|
||||
image: oaisoftwarealliance/oai-smf:v2.1.10
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-amf
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.133
|
||||
oai-upf:
|
||||
container_name: "rfsim5g-oai-upf"
|
||||
image: oaisoftwarealliance/oai-upf:v2.1.10
|
||||
init: true
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-smf
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
cap_drop:
|
||||
- ALL
|
||||
privileged: true
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.134
|
||||
interface_name: eth0
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.134
|
||||
interface_name: eth1
|
||||
oai-ext-dn:
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-ext-dn
|
||||
image: oaisoftwarealliance/trf-gen-cn5g:focal
|
||||
init: true
|
||||
entrypoint: /bin/bash -c \
|
||||
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
||||
"ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity"
|
||||
depends_on:
|
||||
- oai-upf
|
||||
networks:
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.135
|
||||
healthcheck:
|
||||
test: /bin/bash -c "ping -c 2 192.168.72.134"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ocudu-gnb:
|
||||
image: ghcr.io/bpodrygajlo/ocudu/ocudu-avx2:7cecca5
|
||||
container_name: ocudu-gnb
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
depends_on:
|
||||
- oai-ext-dn
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.140
|
||||
volumes:
|
||||
- ./ocudu.yml:/ocudu.yml
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep gnb_split_8"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command: ./usr/local/bin/gnb_split_8 -c /ocudu.yml
|
||||
|
||||
oai-nr-ue:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
- SYS_NICE
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -r 51 --numerology 1 --band 78 -C 3489420000 --ssb 0 -E --uicc0.imsi 208990100001100 --uecap_file /opt/oai-nr-ue/etc/uecap.xml --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://0.0.0.0:4556 --zmq.[0].rx_channels tcp://192.168.71.140:4558
|
||||
depends_on:
|
||||
- ocudu-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.150
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml:/opt/oai-nr-ue/etc/uecap.xml
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
public_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-public-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.71.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-public"
|
||||
traffic_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-traffic-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.72.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-traffic"
|
||||
@@ -0,0 +1,52 @@
|
||||
# SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
|
||||
# SPDX-License-Identifier: BSD-3-Clause-Open-MPI
|
||||
# Refer to https://gitlab.com/ocudu/ocudu/-/raw/dev/LICENSE?ref_type=heads
|
||||
|
||||
cu_cp:
|
||||
amf:
|
||||
addrs: 192.168.71.132
|
||||
port: 38412
|
||||
bind_addrs: 192.168.71.140
|
||||
supported_tracking_areas:
|
||||
- tac: 1
|
||||
plmn_list:
|
||||
- plmn: "20899"
|
||||
tai_slice_support_list:
|
||||
- sst: 1
|
||||
|
||||
ru_sdr:
|
||||
device_driver: zmq
|
||||
device_args: tx_port=tcp://0.0.0.0:4558,rx_port=tcp://192.168.71.150:4556
|
||||
srate: 23.04
|
||||
tx_gain: 25
|
||||
rx_gain: 25
|
||||
|
||||
cell_cfg:
|
||||
dl_arfcn: 632628
|
||||
band: 78
|
||||
channel_bandwidth_MHz: 20
|
||||
common_scs: 30
|
||||
plmn: "20899"
|
||||
tac: 1
|
||||
pci: 1
|
||||
tdd_ul_dl_cfg:
|
||||
dl_ul_tx_period: 5
|
||||
nof_dl_slots: 3
|
||||
nof_dl_symbols: 10
|
||||
nof_ul_slots: 1
|
||||
nof_ul_symbols: 2
|
||||
csi:
|
||||
csi_rs_enabled: false
|
||||
pucch:
|
||||
formats: f0_and_f2
|
||||
nof_cell_csi_res: 0
|
||||
|
||||
log:
|
||||
filename: gnb.log
|
||||
all_level: debug
|
||||
|
||||
pcap:
|
||||
mac_enable: false
|
||||
mac_filename: /tmp/gnb_mac.pcap
|
||||
ngap_enable: false
|
||||
ngap_filename: /tmp/gnb_ngap.pcap
|
||||
@@ -0,0 +1,164 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
services:
|
||||
mysql:
|
||||
container_name: "rfsim5g-mysql"
|
||||
image: mysql:8.0
|
||||
init: true
|
||||
volumes:
|
||||
- ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||
- ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- MYSQL_DATABASE=oai_db
|
||||
- MYSQL_USER=test
|
||||
- MYSQL_PASSWORD=test
|
||||
- MYSQL_ROOT_PASSWORD=linux
|
||||
healthcheck:
|
||||
test: /bin/bash -c "/tmp/mysql-healthcheck.sh"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.131
|
||||
oai-amf:
|
||||
container_name: "rfsim5g-oai-amf"
|
||||
image: oaisoftwarealliance/oai-amf:v2.1.10
|
||||
environment:
|
||||
- TZ=Europe/paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.132
|
||||
oai-smf:
|
||||
container_name: "rfsim5g-oai-smf"
|
||||
image: oaisoftwarealliance/oai-smf:v2.1.10
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-amf
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.133
|
||||
oai-upf:
|
||||
container_name: "rfsim5g-oai-upf"
|
||||
image: oaisoftwarealliance/oai-upf:v2.1.10
|
||||
init: true
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-smf
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
cap_drop:
|
||||
- ALL
|
||||
privileged: true
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.134
|
||||
interface_name: eth0
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.134
|
||||
interface_name: eth1
|
||||
oai-ext-dn:
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-ext-dn
|
||||
image: oaisoftwarealliance/trf-gen-cn5g:focal
|
||||
init: true
|
||||
entrypoint: /bin/bash -c \
|
||||
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
||||
"ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity"
|
||||
depends_on:
|
||||
- oai-upf
|
||||
networks:
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.135
|
||||
healthcheck:
|
||||
test: /bin/bash -c "ping -c 2 192.168.72.134"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
oai-gnb:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${GNB_IMG:-oai-gnb}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-gnb
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://0.0.0.0:4558,tcp://0.0.0.0:4559 --zmq.[0].rx_channels tcp://oai-nr-ue:4556,tcp://oai-nr-ue:4557
|
||||
ASAN_OPTIONS: detect_leaks=0
|
||||
depends_on:
|
||||
- oai-ext-dn
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.140
|
||||
volumes:
|
||||
- ../../conf_files/gnb.sa.band78.273prb.rfsim.2x2.conf:/opt/oai-gnb/etc/gnb.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-softmodem"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
oai-nr-ue:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
- SYS_NICE
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -r 273 --numerology 1 --band 78 -C 3450720000 --ssb 1518 --ue-nb-ant-tx 2 --uicc0.imsi 208990100001100 --ue-nb-ant-rx 2 --uecap_file /opt/oai-nr-ue/etc/uecap.xml --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://0.0.0.0:4556,tcp://0.0.0.0:4557 --zmq.[0].rx_channels tcp://oai-gnb:4558,tcp://oai-gnb:4559
|
||||
depends_on:
|
||||
- oai-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.150
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports2.xml:/opt/oai-nr-ue/etc/uecap.xml
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
public_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-public-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.71.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-public"
|
||||
traffic_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-traffic-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.72.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-traffic"
|
||||
@@ -0,0 +1,162 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
services:
|
||||
mysql:
|
||||
container_name: "rfsim5g-mysql"
|
||||
image: mysql:8.0
|
||||
init: true
|
||||
volumes:
|
||||
- ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql
|
||||
- ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- MYSQL_DATABASE=oai_db
|
||||
- MYSQL_USER=test
|
||||
- MYSQL_PASSWORD=test
|
||||
- MYSQL_ROOT_PASSWORD=linux
|
||||
healthcheck:
|
||||
test: /bin/bash -c "/tmp/mysql-healthcheck.sh"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.131
|
||||
oai-amf:
|
||||
container_name: "rfsim5g-oai-amf"
|
||||
image: oaisoftwarealliance/oai-amf:v2.1.10
|
||||
environment:
|
||||
- TZ=Europe/paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.132
|
||||
oai-smf:
|
||||
container_name: "rfsim5g-oai-smf"
|
||||
image: oaisoftwarealliance/oai-smf:v2.1.10
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-amf
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.133
|
||||
oai-upf:
|
||||
container_name: "rfsim5g-oai-upf"
|
||||
image: oaisoftwarealliance/oai-upf:v2.1.10
|
||||
init: true
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
- ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml
|
||||
depends_on:
|
||||
- oai-smf
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_ADMIN
|
||||
cap_drop:
|
||||
- ALL
|
||||
privileged: true
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.134
|
||||
interface_name: eth0
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.134
|
||||
interface_name: eth1
|
||||
oai-ext-dn:
|
||||
privileged: true
|
||||
container_name: rfsim5g-oai-ext-dn
|
||||
image: oaisoftwarealliance/trf-gen-cn5g:focal
|
||||
init: true
|
||||
entrypoint: /bin/bash -c \
|
||||
"iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\
|
||||
"ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity"
|
||||
depends_on:
|
||||
- oai-upf
|
||||
networks:
|
||||
traffic_net:
|
||||
ipv4_address: 192.168.72.135
|
||||
healthcheck:
|
||||
test: /bin/bash -c "ping -c 2 192.168.72.134"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ocudu-gnb:
|
||||
image: ghcr.io/bpodrygajlo/ocudu/ocudu-avx2:7cecca5
|
||||
container_name: ocudu-gnb
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SYS_NICE
|
||||
depends_on:
|
||||
- oai-ext-dn
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.140
|
||||
volumes:
|
||||
- ./ocudu.yml:/ocudu.yml
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep gnb_split_8"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
command: ./usr/local/bin/gnb_split_8 -c /ocudu.yml
|
||||
|
||||
oai-nr-ue:
|
||||
image: ${REGISTRY-oaisoftwarealliance/}${NRUE_IMG:-oai-nr-ue}:${TAG:-develop}
|
||||
container_name: rfsim5g-oai-nr-ue
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_ADMIN # for interface bringup
|
||||
- NET_RAW # for ping
|
||||
- SYS_NICE
|
||||
environment:
|
||||
USE_ADDITIONAL_OPTIONS: -r 106 --numerology 1 --band 78 -C 3489420000 --ssb 42 --ue-nb-ant-tx 2 --uicc0.imsi 208990100001100 --ue-nb-ant-rx 2 --uecap_file /opt/oai-nr-ue/etc/uecap.xml --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://0.0.0.0:4556,tcp://0.0.0.0:4557 --zmq.[0].rx_channels tcp://192.168.71.140:4558,tcp://192.168.71.140:4559
|
||||
depends_on:
|
||||
- ocudu-gnb
|
||||
networks:
|
||||
public_net:
|
||||
ipv4_address: 192.168.71.150
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
volumes:
|
||||
- ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports2.xml:/opt/oai-nr-ue/etc/uecap.xml
|
||||
- ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf
|
||||
healthcheck:
|
||||
test: /bin/bash -c "pgrep nr-uesoftmodem"
|
||||
start_period: 10s
|
||||
start_interval: 500ms
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
public_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-public-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.71.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-public"
|
||||
traffic_net:
|
||||
driver: bridge
|
||||
name: rfsim5g-oai-traffic-net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 192.168.72.128/26
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: "rfsim5g-traffic"
|
||||
@@ -0,0 +1,54 @@
|
||||
# SPDX-FileCopyrightText: Copyright (C) 2021-2026 Software Radio Systems Limited
|
||||
# SPDX-License-Identifier: BSD-3-Clause-Open-MPI
|
||||
# Refer to https://gitlab.com/ocudu/ocudu/-/raw/dev/LICENSE?ref_type=heads
|
||||
|
||||
cu_cp:
|
||||
amf:
|
||||
addrs: 192.168.71.132
|
||||
port: 38412
|
||||
bind_addrs: 192.168.71.140
|
||||
supported_tracking_areas:
|
||||
- tac: 1
|
||||
plmn_list:
|
||||
- plmn: "20899"
|
||||
tai_slice_support_list:
|
||||
- sst: 1
|
||||
|
||||
ru_sdr:
|
||||
device_driver: zmq
|
||||
device_args: tx_port=tcp://0.0.0.0:4558,tx_port=tcp://0.0.0.0:4559,rx_port=tcp://192.168.71.150:4556,rx_port=tcp://192.168.71.150:4557
|
||||
srate: 61.44
|
||||
tx_gain: 25
|
||||
rx_gain: 25
|
||||
|
||||
cell_cfg:
|
||||
dl_arfcn: 632628
|
||||
band: 78
|
||||
channel_bandwidth_MHz: 40
|
||||
common_scs: 30
|
||||
plmn: "20899"
|
||||
tac: 1
|
||||
pci: 1
|
||||
nof_antennas_ul: 2
|
||||
nof_antennas_dl: 2
|
||||
tdd_ul_dl_cfg:
|
||||
dl_ul_tx_period: 5
|
||||
nof_dl_slots: 3
|
||||
nof_dl_symbols: 10
|
||||
nof_ul_slots: 1
|
||||
nof_ul_symbols: 2
|
||||
csi:
|
||||
csi_rs_enabled: false
|
||||
pucch:
|
||||
formats: f0_and_f2
|
||||
nof_cell_csi_res: 0
|
||||
|
||||
log:
|
||||
filename: gnb.log
|
||||
all_level: debug
|
||||
|
||||
pcap:
|
||||
mac_enable: false
|
||||
mac_filename: /tmp/gnb_mac.pcap
|
||||
ngap_enable: false
|
||||
ngap_filename: /tmp/gnb_ngap.pcap
|
||||
@@ -68,7 +68,7 @@ Options:
|
||||
--UE-conf-nvram [configuration file]
|
||||
Specify conf_nvram_path (default \"$conf_nvram_path\")
|
||||
-w | --hardware
|
||||
USRP, BLADERF, LMSSDR, IRIS, SIMU, AW2SORI, AERIAL, None (Default)
|
||||
USRP, BLADERF, LMSSDR, IRIS, SIMU, AW2SORI, AERIAL, ZMQ, None (Default)
|
||||
Adds this RF board support (in external packages installation and in compilation)
|
||||
-t | --transport
|
||||
Selects the transport protocol type, options: None, Ethernet, oran_fhlib_5g, oran_fhlib_5g_mplane, WLS
|
||||
@@ -223,7 +223,7 @@ function main() {
|
||||
shift 2;;
|
||||
-w | --hardware)
|
||||
case "$2" in
|
||||
"USRP" | "BLADERF" | "LMSSDR" | "IRIS")
|
||||
"USRP" | "BLADERF" | "LMSSDR" | "IRIS" | "ZMQ")
|
||||
HWs+=" OAI_"$2
|
||||
TARGET_LIST="$TARGET_LIST oai_${2,,}devif" # ,, makes lowercase
|
||||
CMAKE_CMD="$CMAKE_CMD -DOAI_$2=ON"
|
||||
|
||||
@@ -282,12 +282,9 @@ configmodule_interface_t *load_configmodule(int argc,
|
||||
cfgmode = strdup(CONFIG_LIBCONFIGFILE);
|
||||
}
|
||||
}
|
||||
static configmodule_interface_t *cfgptr = NULL;
|
||||
if (cfgptr)
|
||||
fprintf(stderr, "ERROR: Call load_configmodule more than one time\n");
|
||||
|
||||
// The macros are not thread safe print_params and similar
|
||||
cfgptr = calloc(sizeof(configmodule_interface_t), 1);
|
||||
configmodule_interface_t *cfgptr = calloc(sizeof(configmodule_interface_t), 1);
|
||||
if (!cfgptr) {
|
||||
fprintf(stderr, "ERROR: cannot allocate a memory for configuration\n");
|
||||
return NULL;
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#ifndef INCLUDE_CONFIG_LOADCONFIGMODULE_H
|
||||
#define INCLUDE_CONFIG_LOADCONFIGMODULE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -117,5 +120,8 @@ void write_parsedcfg(configmodule_interface_t *cfg);
|
||||
extern void free_configmodule(void);
|
||||
#define CONFIG_PRINTF_ERROR(f, x... ) if (isLogInitDone ()) { LOG_E(ENB_APP,f,x);} else {printf(f,x);}; if ( !CONFIG_ISFLAGSET(CONFIG_NOABORTONCHKF) ) exit_fun("exit because configuration failed\n");
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* INCLUDE_CONFIG_LOADCONFIGMODULE_H */
|
||||
|
||||
@@ -181,6 +181,11 @@ typedef struct paramdef {
|
||||
{ \
|
||||
OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .u64ptr = ptr, .defuintval = defval, PARAMTYPE(TYPE_UINT64), .numelt = 0 \
|
||||
}
|
||||
#define STRINGLISTPARAM(name, help, flags, ptr, defval) \
|
||||
{ \
|
||||
OPTNAME(name), HELPSTR(help), PARAMFLAG(flags), .strptr = ptr, .defstrlistval = defval, PARAMTYPE(TYPE_STRINGLIST), \
|
||||
.numelt = 0 \
|
||||
}
|
||||
|
||||
typedef struct paramlist_def {
|
||||
char listname[MAX_OPTNAME_SIZE];
|
||||
|
||||
@@ -108,6 +108,7 @@ Some directories under `radio` contain READMEs:
|
||||
- [fhi_72](../radio/fhi_72/README.md)
|
||||
- [vrtsim](../radio/vrtsim/README.md)
|
||||
- [rf_emulator](../radio/emulator/README.md)
|
||||
- [zmq](../radio/zmq/README.md)
|
||||
|
||||
The other SDRs (AW2S, LimeSDR, ...) have no READMEs.
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ RUN apt-get update && \
|
||||
wget \
|
||||
flatbuffers-compiler \
|
||||
libflatbuffers-dev \
|
||||
libnanomsg-dev && \
|
||||
libnanomsg-dev \
|
||||
libzmq3-dev && \
|
||||
# --break-system-packages allows externally managed environment in pip
|
||||
pip3 install --ignore-installed pyyaml --break-system-packages
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ RUN \
|
||||
-w USRP -t Ethernet -w BLADERF \
|
||||
--build-e2 --cmake-opt -DKPM_VERSION=$KPM_VERSION --cmake-opt -DE2AP_VERSION=$E2AP_VERSION \
|
||||
--cmake-opt -DOAI_VRTSIM_TAPS_CLIENT=ON \
|
||||
-w ZMQ \
|
||||
--noavx512 \
|
||||
--build-tool-opt -k10 \
|
||||
--cmake-opt -DT_RECORD_DB=ON \
|
||||
|
||||
@@ -47,7 +47,8 @@ RUN apt-get update && \
|
||||
python3-requests \
|
||||
libusb-1.0-0 \
|
||||
libyaml-cpp-dev \
|
||||
libnanomsg5 && \
|
||||
libnanomsg5 \
|
||||
libzmq5 && \
|
||||
# if the --sanitize option was used to build, additional packages are required
|
||||
/bin/bash -c 'if [[ "$BUILD_OPTION" = "--sanitize" ]]; then DEBIAN_FRONTEND=noninteractive apt-get install --yes \
|
||||
libasan8 \
|
||||
@@ -78,6 +79,7 @@ COPY --from=gnb-build \
|
||||
/oai-ran/cmake_targets/ran_build/build/libtelnetsrv_rrc.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/libtelnetsrv_o1.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/libvrtsim.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/liboai_zmqdevif.so \
|
||||
/usr/local/lib/
|
||||
|
||||
# Now we are copying from builder-image the UHD files.
|
||||
@@ -105,7 +107,8 @@ RUN /bin/bash -c "ln -s /usr/local/lib/liboai_usrpdevif.so /usr/local/lib/liboai
|
||||
/usr/local/lib/libldpc*.so \
|
||||
/usr/local/lib/libtelnetsrv.so \
|
||||
/usr/local/lib/libtelnetsrv_ci.so \
|
||||
/usr/local/lib/libuhd.so.4.8.0
|
||||
/usr/local/lib/libuhd.so.4.8.0 \
|
||||
/usr/local/lib/liboai_zmqdevif.so
|
||||
|
||||
WORKDIR /opt/oai-gnb
|
||||
#EXPOSE 2152/udp # S1U, GTP/UDP
|
||||
|
||||
@@ -50,7 +50,8 @@ RUN apt-get update && \
|
||||
iperf \
|
||||
libyaml-cpp-dev \
|
||||
libzstd1 \
|
||||
libnanomsg5 && \
|
||||
libnanomsg5 \
|
||||
libzmq5 && \
|
||||
# if the --sanitize option was used to build, additional packages are required
|
||||
/bin/bash -c 'if [[ "$BUILD_OPTION" = "--sanitize" ]]; then DEBIAN_FRONTEND=noninteractive apt-get install --yes \
|
||||
libasan8 \
|
||||
@@ -83,6 +84,7 @@ COPY --from=nr-ue-build \
|
||||
/oai-ran/cmake_targets/ran_build/build/libtelnetsrv.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/libparams_yaml.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/libvrtsim.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/liboai_zmqdevif.so \
|
||||
# Wildcards so it doesn't fail if not present
|
||||
/oai-ran/cmake_targets/ran_build/build/_deps*/fmtlog-build*/libfmtlog-shared*.so \
|
||||
/oai-ran/cmake_targets/ran_build/build/_deps*/clickhouse-cpp-build*/clickhouse/libclickhouse*.so \
|
||||
@@ -114,7 +116,8 @@ RUN /bin/bash -c "ln -s /usr/local/lib/liboai_usrpdevif.so /usr/local/lib/liboai
|
||||
/usr/local/lib/libtelnetsrv.so \
|
||||
/usr/local/lib/libtelnetsrv_ciUE.so \
|
||||
/usr/local/lib/libtelnetsrv_5Gue.so \
|
||||
/usr/local/lib/libuhd.so.4.8.0
|
||||
/usr/local/lib/libuhd.so.4.8.0 \
|
||||
/usr/local/lib/liboai_zmqdevif.so
|
||||
|
||||
WORKDIR /opt/oai-nr-ue
|
||||
|
||||
|
||||
@@ -131,13 +131,18 @@ void exit_function(const char *file, const char *function, const int line, const
|
||||
printf("%s:%d %s() Exiting OAI softmodem: %s\n",file,line, function, s);
|
||||
}
|
||||
|
||||
oai_exit = 1;
|
||||
|
||||
if (RC.ru == NULL)
|
||||
exit(-1); // likely init not completed, prevent crash or hang, exit now...
|
||||
|
||||
for (ru_id=0; ru_id<RC.nb_RU; ru_id++) {
|
||||
if (RC.ru[ru_id] && RC.ru[ru_id]->rfdevice.trx_end_func) {
|
||||
if (RC.ru[ru_id] == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (RC.ru[ru_id]->ifdevice.trx_stop_func) {
|
||||
RC.ru[ru_id]->ifdevice.trx_stop_func(&RC.ru[ru_id]->ifdevice);
|
||||
RC.ru[ru_id]->ifdevice.trx_stop_func = NULL;
|
||||
}
|
||||
if (RC.ru[ru_id]->rfdevice.trx_end_func) {
|
||||
if (RC.ru[ru_id]->rfdevice.trx_get_stats_func) {
|
||||
RC.ru[ru_id]->rfdevice.trx_get_stats_func(&RC.ru[ru_id]->rfdevice);
|
||||
RC.ru[ru_id]->rfdevice.trx_get_stats_func = NULL;
|
||||
@@ -146,6 +151,10 @@ void exit_function(const char *file, const char *function, const int line, const
|
||||
RC.ru[ru_id]->rfdevice.trx_end_func = NULL;
|
||||
}
|
||||
|
||||
if (RC.ru[ru_id]->ifdevice.trx_stop_func) {
|
||||
RC.ru[ru_id]->ifdevice.trx_stop_func(&RC.ru[ru_id]->ifdevice);
|
||||
RC.ru[ru_id]->ifdevice.trx_stop_func = NULL;
|
||||
}
|
||||
if (RC.ru[ru_id] && RC.ru[ru_id]->ifdevice.trx_end_func) {
|
||||
if (RC.ru[ru_id]->ifdevice.trx_get_stats_func) {
|
||||
RC.ru[ru_id]->ifdevice.trx_get_stats_func(&RC.ru[ru_id]->ifdevice);
|
||||
@@ -156,6 +165,8 @@ void exit_function(const char *file, const char *function, const int line, const
|
||||
}
|
||||
}
|
||||
|
||||
oai_exit = 1;
|
||||
|
||||
if (assert) {
|
||||
abort();
|
||||
} else {
|
||||
|
||||
@@ -352,6 +352,14 @@ void nrue_ru_start(void)
|
||||
}
|
||||
}
|
||||
|
||||
void nrue_ru_stop(void)
|
||||
{
|
||||
for (int ru_id = 0; ru_id < nrue_ru_count; ru_id++) {
|
||||
if (openair0_dev[ru_id].trx_stop_func)
|
||||
openair0_dev[ru_id].trx_stop_func(&openair0_dev[ru_id]);
|
||||
}
|
||||
}
|
||||
|
||||
void nrue_ru_end(void)
|
||||
{
|
||||
for (int ru_id = 0; ru_id < nrue_ru_count; ru_id++) {
|
||||
|
||||
@@ -22,6 +22,7 @@ void nrue_set_ru_params(configmodule_interface_t *cfg);
|
||||
void nrue_init_openair0(void);
|
||||
|
||||
void nrue_ru_start(void);
|
||||
void nrue_ru_stop(void);
|
||||
void nrue_ru_end(void);
|
||||
void nrue_ru_set_freq(PHY_VARS_NR_UE *UE, uint64_t ul_carrier, uint64_t dl_carrier, int freq_offset);
|
||||
int nrue_ru_adjust_rx_gain(PHY_VARS_NR_UE *UE, int gain_change);
|
||||
|
||||
@@ -112,6 +112,7 @@ void exit_function(const char *file, const char *function, const int line, const
|
||||
|
||||
oai_exit = 1;
|
||||
|
||||
nrue_ru_stop();
|
||||
nrue_ru_end();
|
||||
|
||||
if (assert) {
|
||||
@@ -457,6 +458,8 @@ int main(int argc, char **argv)
|
||||
LOG_W(NR_PHY, "Returned from ITTI signal handler\n");
|
||||
oai_exit = 1;
|
||||
|
||||
nrue_ru_stop();
|
||||
|
||||
if (PHY_vars_UE_g && PHY_vars_UE_g[0]) {
|
||||
for (int CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) {
|
||||
PHY_VARS_NR_UE *phy_vars = PHY_vars_UE_g[0][CC_id];
|
||||
|
||||
@@ -691,9 +691,11 @@ TEST(test_srs_power, tpc_accumulation)
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
logInit();
|
||||
load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY);
|
||||
configmodule_interface_t *uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY);
|
||||
g_log->log_component[MAC].level = OAILOG_DEBUG;
|
||||
g_log->log_component[NR_MAC].level = OAILOG_DEBUG;
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
int ret = RUN_ALL_TESTS();
|
||||
end_configmodule(uniqCfg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -171,9 +171,11 @@ TEST(test_init_ra, four_step_cfra)
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
logInit();
|
||||
load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY);
|
||||
configmodule_interface_t *uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY);
|
||||
g_log->log_component[MAC].level = OAILOG_DEBUG;
|
||||
g_log->log_component[NR_MAC].level = OAILOG_DEBUG;
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
int ret = RUN_ALL_TESTS();
|
||||
end_configmodule(uniqCfg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -51,3 +51,8 @@ add_boolean_option(OAI_RF_EMULATOR ON "Activate OAI's RF emulator" OFF)
|
||||
if(OAI_RF_EMULATOR)
|
||||
add_subdirectory(emulator)
|
||||
endif()
|
||||
|
||||
add_boolean_option(OAI_ZMQ OFF "Activate OAI's ZMQ radio" OFF)
|
||||
if(OAI_ZMQ)
|
||||
add_subdirectory(zmq)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
|
||||
pkg_check_modules(zmq REQUIRED libzmq)
|
||||
add_library(ring_buffer ring_buffer.cpp)
|
||||
add_library(zmq_imported zmq_imported.cpp)
|
||||
target_include_directories(zmq_imported PUBLIC ./)
|
||||
target_link_libraries(zmq_imported PUBLIC ${zmq_LIBRARIES} log_headers)
|
||||
|
||||
target_include_directories(ring_buffer PUBLIC ./)
|
||||
add_library(oai_zmqdevif SHARED zmq_radio.cpp)
|
||||
target_link_libraries(oai_zmqdevif PUBLIC ${zmq_LIBRARIES} ring_buffer log_headers zmq_imported)
|
||||
target_include_directories(oai_zmqdevif PUBLIC ${zmq_INCLUDE_DIRS})
|
||||
set_target_properties(oai_zmqdevif PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
add_library(zmq_radio_static STATIC zmq_radio.cpp)
|
||||
target_link_libraries(zmq_radio_static PUBLIC ${zmq_LIBRARIES} ring_buffer log_headers zmq_imported)
|
||||
target_include_directories(zmq_radio_static PUBLIC ${zmq_INCLUDE_DIRS})
|
||||
|
||||
if (ENABLE_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
|
||||
|
||||
# Overview
|
||||
|
||||
This library implements ZMQ-based radio driver
|
||||
|
||||
# Architecture
|
||||
|
||||
The radio simulates RX/TX pairs between different processes over network or on the same machine
|
||||
as ZMQ REQ/REP socket pairs. All the antennas have to be configured and connected before the
|
||||
simulation can start.
|
||||
|
||||
# Limitations
|
||||
|
||||
You cannot reconnect a device once the simulation has started. The simulation has to be restarted
|
||||
from scratch in order to add devices or restart a process.
|
||||
|
||||
# Requirements
|
||||
|
||||
Depends on zmq library being installed in the system. On ubuntu: `libzmq3-dev`, on RHEL/Fedora: `zeromq-devel`
|
||||
|
||||
# Usage
|
||||
|
||||
## Simple 1-to-1 antenna mapping
|
||||
|
||||
Add `--device.name oai_zmqdevif` to load the library in UE / gNB process. Add `--zmq.[0].tx_channels <channels>` and
|
||||
`--zmq.[0].rx_channels <channels>` to define ZMQ REQ/REP pairs.
|
||||
|
||||
On the opposite side, load the library like specified above but invert the `rx` and `tx` channels. This way all
|
||||
antennas of UE will be directly mapped to all antennas of the gNB.
|
||||
|
||||
### Example:
|
||||
|
||||
```
|
||||
sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4557 --zmq.[0].rx_channels tcp://127.0.0.1:4556 --ssb 516
|
||||
```
|
||||
|
||||
```
|
||||
sudo ./nr-softmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4556 --zmq.[0].rx_channels tcp://127.0.0.1:4557
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#include "ring_buffer.h"
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
ring_buffer::ring_buffer(size_t max_size) : max_size_(max_size)
|
||||
{
|
||||
buffer_ = std::make_unique<cf_t[]>(max_size);
|
||||
}
|
||||
|
||||
size_t ring_buffer::push_samples(const cf_t *samples, const size_t nsamps)
|
||||
{
|
||||
size_t overflow = 0;
|
||||
// if nsamps > max_size skip nsamps - max_size samples
|
||||
size_t nsamps_left = nsamps;
|
||||
if (nsamps > max_size_) {
|
||||
samples += nsamps - max_size_;
|
||||
nsamps_left = max_size_;
|
||||
overflow += nsamps - max_size_;
|
||||
}
|
||||
|
||||
// Detect overflow
|
||||
if (size_ + nsamps_left > max_size_) {
|
||||
size_t newtail__pos = (head_ + nsamps_left) % max_size_;
|
||||
overflow += (size_ + nsamps_left) - max_size_;
|
||||
tail_ = newtail__pos;
|
||||
}
|
||||
|
||||
size_t first_chunk = std::min(nsamps_left, max_size_ - head_);
|
||||
memcpy(&buffer_[head_], samples, first_chunk * sizeof(cf_t));
|
||||
head_ = (head_ + first_chunk) % max_size_;
|
||||
samples += first_chunk;
|
||||
nsamps_left -= first_chunk;
|
||||
if (nsamps_left > 0) {
|
||||
memcpy(&buffer_[0], samples, nsamps_left * sizeof(cf_t));
|
||||
head_ = nsamps_left;
|
||||
}
|
||||
|
||||
size_ = std::min(size_ + nsamps, max_size_);
|
||||
|
||||
return overflow;
|
||||
}
|
||||
|
||||
size_t ring_buffer::push_zeros(const size_t num_zeros)
|
||||
{
|
||||
size_t overflow = 0;
|
||||
// if nsamps > max_size skip nsamps - max_size samples
|
||||
size_t nsamps_left = num_zeros;
|
||||
if (num_zeros > max_size_) {
|
||||
nsamps_left = max_size_;
|
||||
overflow += num_zeros - max_size_;
|
||||
}
|
||||
|
||||
// Detect overflow
|
||||
if (size_ + nsamps_left > max_size_) {
|
||||
size_t new_tail_pos = (head_ + nsamps_left) % max_size_;
|
||||
overflow += (size_ + nsamps_left) - max_size_;
|
||||
tail_ = new_tail_pos;
|
||||
}
|
||||
|
||||
size_t first_chunk = std::min(nsamps_left, max_size_ - head_);
|
||||
memset(&buffer_[head_], 0, first_chunk * sizeof(cf_t));
|
||||
head_ = (head_ + first_chunk) % max_size_;
|
||||
nsamps_left -= first_chunk;
|
||||
if (nsamps_left > 0) {
|
||||
memset(&buffer_[0], 0, nsamps_left * sizeof(cf_t));
|
||||
head_ = nsamps_left;
|
||||
}
|
||||
|
||||
size_ = std::min(size_ + num_zeros, max_size_);
|
||||
|
||||
return overflow;
|
||||
}
|
||||
|
||||
size_t ring_buffer::pop_samples(cf_t *samples, size_t num_samples)
|
||||
{
|
||||
size_t samples_to_pop = std::min(size_, num_samples);
|
||||
if (samples_to_pop > 0) {
|
||||
if (tail_ + samples_to_pop > max_size_) {
|
||||
size_t first_chunk = max_size_ - tail_;
|
||||
memcpy(samples, &buffer_[tail_], first_chunk * sizeof(cf_t));
|
||||
memcpy(samples + first_chunk, &buffer_[0], (samples_to_pop - first_chunk) * sizeof(cf_t));
|
||||
} else {
|
||||
memcpy(samples, &buffer_[tail_], samples_to_pop * sizeof(cf_t));
|
||||
}
|
||||
tail_ = (tail_ + samples_to_pop) % max_size_;
|
||||
size_ -= samples_to_pop;
|
||||
return samples_to_pop;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ring_buffer::clear_samples()
|
||||
{
|
||||
head_ = 0;
|
||||
tail_ = 0;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void ring_buffer::reset()
|
||||
{
|
||||
clear_samples();
|
||||
}
|
||||
|
||||
size_t ring_buffer::size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
size_t overflow_buffer::push_samples(const cf_t *samples, size_t nsamps)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
size_t overflow = buffer_.push_samples(samples, nsamps);
|
||||
zeros_to_send_ += overflow;
|
||||
return overflow;
|
||||
}
|
||||
|
||||
size_t overflow_buffer::push_zeros(size_t num_zeros)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
size_t overflow = buffer_.push_zeros(num_zeros);
|
||||
zeros_to_send_ += overflow;
|
||||
return overflow;
|
||||
}
|
||||
|
||||
size_t overflow_buffer::pop_samples(cf_t *samples, size_t num_samples)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
size_t samples_popped = 0;
|
||||
if (zeros_to_send_ > 0) {
|
||||
size_t num_zeros = std::min(zeros_to_send_, num_samples);
|
||||
memset(samples, 0, num_zeros * sizeof(cf_t));
|
||||
zeros_to_send_ -= num_zeros;
|
||||
samples += num_zeros;
|
||||
num_samples -= num_zeros;
|
||||
samples_popped += num_zeros;
|
||||
}
|
||||
|
||||
if (num_samples > 0) {
|
||||
samples_popped += buffer_.pop_samples(samples, num_samples);
|
||||
}
|
||||
return samples_popped;
|
||||
}
|
||||
|
||||
void overflow_buffer::reset()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
buffer_.reset();
|
||||
zeros_to_send_ = buffer_.size() / 2;
|
||||
}
|
||||
|
||||
void overflow_buffer::clear_samples()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
buffer_.clear_samples();
|
||||
zeros_to_send_ = 0;
|
||||
}
|
||||
|
||||
size_t overflow_buffer::size()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return buffer_.size() + zeros_to_send_;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "common/platform_types.h"
|
||||
|
||||
// A basic cirular sample buffer class
|
||||
class ring_buffer {
|
||||
private:
|
||||
std::unique_ptr<cf_t[]> buffer_;
|
||||
size_t head_ = 0;
|
||||
size_t tail_ = 0;
|
||||
size_t size_ = 0;
|
||||
size_t max_size_;
|
||||
|
||||
public:
|
||||
ring_buffer(size_t max_size = 614400);
|
||||
size_t push_samples(const cf_t *samples, size_t nsamps);
|
||||
size_t push_zeros(size_t num_zeros);
|
||||
size_t pop_samples(cf_t *samples, size_t num_samples);
|
||||
void reset();
|
||||
void clear_samples();
|
||||
size_t size() const;
|
||||
};
|
||||
|
||||
// A thread-safe wrapper around ring_buffer that counts overflows
|
||||
class overflow_buffer {
|
||||
ring_buffer buffer_;
|
||||
size_t zeros_to_send_;
|
||||
std::mutex mutex_;
|
||||
|
||||
public:
|
||||
overflow_buffer(size_t max_size = 614400) : buffer_(max_size), zeros_to_send_(0)
|
||||
{
|
||||
}
|
||||
size_t push_samples(const cf_t *samples, size_t nsamps);
|
||||
size_t push_zeros(size_t num_zeros);
|
||||
size_t pop_samples(cf_t *samples, size_t num_samples);
|
||||
void reset();
|
||||
void clear_samples();
|
||||
size_t size();
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
|
||||
add_executable(test_ring_buffer test_ring_buffer.cpp)
|
||||
target_link_libraries(test_ring_buffer PRIVATE ring_buffer GTest::gtest)
|
||||
add_dependencies(tests test_ring_buffer)
|
||||
add_test(NAME test_ring_buffer
|
||||
COMMAND ./test_ring_buffer)
|
||||
|
||||
add_executable(test_zmq_radio test_zmq_radio.cpp)
|
||||
target_link_libraries(test_zmq_radio PRIVATE zmq_radio_static UTIL CONFIG_LIB GTest::gtest zmq)
|
||||
add_dependencies(tests test_zmq_radio)
|
||||
add_test(NAME test_zmq_radio
|
||||
COMMAND ./test_zmq_radio)
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "ring_buffer.h"
|
||||
|
||||
TEST(CircularBuffer, simplePushPop)
|
||||
{
|
||||
ring_buffer cb(10);
|
||||
cf_t data[10];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
data[i] = { (float)i, (float)(i + 1)};
|
||||
}
|
||||
ASSERT_EQ(cb.push_samples(data, 10), 0);
|
||||
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 10), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(read_data[i].r, data[i].r);
|
||||
ASSERT_EQ(read_data[i].i, data[i].i);
|
||||
}
|
||||
|
||||
ASSERT_EQ(cb.size(), 0);
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 10), 0);
|
||||
}
|
||||
|
||||
TEST(CircularBuffer, overflow)
|
||||
{
|
||||
ring_buffer cb(10);
|
||||
cf_t data[15];
|
||||
for (int i = 0; i < 15; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
ASSERT_EQ(cb.push_samples(data, 15), 5);
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 10), 10);
|
||||
// The first 5 samples are lost due to overflow
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i + 5);
|
||||
ASSERT_EQ(read_data[i].i, i + 6);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularBuffer, partialPop)
|
||||
{
|
||||
ring_buffer cb(10);
|
||||
cf_t data[10];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
ASSERT_EQ(cb.push_samples(data, 10), 0);
|
||||
cf_t read_data[5];
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 5), 5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i);
|
||||
ASSERT_EQ(read_data[i].i, i + 1);
|
||||
}
|
||||
ASSERT_EQ(cb.size(), 5);
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 5), 5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i + 5);
|
||||
ASSERT_EQ(read_data[i].i, i + 6);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularBuffer, popMore)
|
||||
{
|
||||
ring_buffer cb(10);
|
||||
cf_t data[10];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
cb.push_samples(data, 10);
|
||||
cf_t read_data[15];
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 15), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i);
|
||||
ASSERT_EQ(read_data[i].i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CircularBuffer, newOverflow)
|
||||
{
|
||||
ring_buffer cb(10);
|
||||
cf_t data1[8];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
data1[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
ASSERT_EQ(cb.push_samples(data1, 8), 0);
|
||||
|
||||
cf_t data2[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
data2[i] = {(float)(i + 8), (float)(i + 9)};
|
||||
}
|
||||
ASSERT_EQ(cb.push_samples(data2, 4), 2);
|
||||
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(cb.pop_samples(read_data, 10), 10);
|
||||
// The first 2 samples are lost due to overflow
|
||||
for (int i = 0; i < 8; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i + 2);
|
||||
ASSERT_EQ(read_data[i].i, i + 3);
|
||||
}
|
||||
ASSERT_EQ(read_data[8].r, 10);
|
||||
ASSERT_EQ(read_data[8].i, 11);
|
||||
ASSERT_EQ(read_data[9].r, 11);
|
||||
ASSERT_EQ(read_data[9].i, 12);
|
||||
}
|
||||
|
||||
TEST(overflow_buffer, simplePushPop)
|
||||
{
|
||||
overflow_buffer pb(10);
|
||||
cf_t data[10];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
pb.push_samples(data, 10);
|
||||
ASSERT_EQ(pb.size(), 10);
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(pb.pop_samples(read_data, 10), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i);
|
||||
ASSERT_EQ(read_data[i].i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(overflow_buffer, zeros_to_send)
|
||||
{
|
||||
overflow_buffer pb(10);
|
||||
pb.push_zeros(10);
|
||||
cf_t data[5];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
pb.push_samples(data, 5);
|
||||
ASSERT_EQ(pb.size(), 15);
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(pb.pop_samples(read_data, 10), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(read_data[i].r, 0);
|
||||
ASSERT_EQ(read_data[i].i, 0);
|
||||
}
|
||||
ASSERT_EQ(pb.pop_samples(read_data, 10), 5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i);
|
||||
ASSERT_EQ(read_data[i].i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(overflow_buffer, no_zeros_to_send)
|
||||
{
|
||||
overflow_buffer pb(10);
|
||||
cf_t data[10];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
pb.push_samples(data, 10);
|
||||
ASSERT_EQ(pb.size(), 10);
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(pb.pop_samples(read_data, 10), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i);
|
||||
ASSERT_EQ(read_data[i].i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(overflow_buffer, overflow)
|
||||
{
|
||||
overflow_buffer pb(10);
|
||||
cf_t data[15];
|
||||
for (int i = 0; i < 15; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
pb.push_samples(data, 15);
|
||||
ASSERT_EQ(pb.size(), 15);
|
||||
cf_t read_data[15];
|
||||
ASSERT_EQ(pb.pop_samples(read_data, 15), 15);
|
||||
// The first 5 samples were lost due to overflow.
|
||||
int i = 0;
|
||||
for (; i < 5; i++) {
|
||||
ASSERT_EQ(read_data[i].r, 0);
|
||||
ASSERT_EQ(read_data[i].i, 0);
|
||||
}
|
||||
for (; i < 15; i++) {
|
||||
ASSERT_EQ(read_data[i].r, i);
|
||||
ASSERT_EQ(read_data[i].i, i + 1);
|
||||
}
|
||||
ASSERT_EQ(pb.size(), 0);
|
||||
}
|
||||
|
||||
TEST(overflow_buffer, push_zeros)
|
||||
{
|
||||
overflow_buffer pb(10);
|
||||
pb.push_zeros(5);
|
||||
ASSERT_EQ(pb.size(), 5);
|
||||
cf_t data[5];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
data[i] = {(float)i, (float)(i + 1)};
|
||||
}
|
||||
pb.push_samples(data, 5);
|
||||
ASSERT_EQ(pb.size(), 10);
|
||||
cf_t read_data[10];
|
||||
ASSERT_EQ(pb.pop_samples(read_data, 10), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (i < 5) {
|
||||
ASSERT_EQ(read_data[i].r, 0);
|
||||
ASSERT_EQ(read_data[i].i, 0);
|
||||
} else {
|
||||
ASSERT_EQ(read_data[i].r, data[i - 5].r);
|
||||
ASSERT_EQ(read_data[i].i, data[i - 5].i);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(pb.size(), 0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#include "common_lib.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include "common/config/config_userapi.h"
|
||||
#include <zmq.h>
|
||||
extern "C" {
|
||||
#include "common/config/config_userapi.h"
|
||||
#include "openair1/SIMULATION/TOOLS/sim.h"
|
||||
extern int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg);
|
||||
static softmodem_params_t softmodem_params;
|
||||
softmodem_params_t *get_softmodem_params(void)
|
||||
{
|
||||
return &softmodem_params;
|
||||
}
|
||||
}
|
||||
#include "common/platform_types.h"
|
||||
#include <thread>
|
||||
|
||||
configmodule_interface_t *uniqCfg = NULL;
|
||||
|
||||
extern "C" void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
|
||||
{
|
||||
fprintf(stderr, "FATAL: %s at %s:%s:%d\n", s, file, function, line);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
class ZMQTest : public ::testing::Test {
|
||||
protected:
|
||||
configmodule_interface_t *cfg1 = nullptr;
|
||||
configmodule_interface_t *cfg2 = nullptr;
|
||||
openair0_device_t device1 = {0};
|
||||
openair0_device_t device2 = {0};
|
||||
openair0_config_t config1 = {0};
|
||||
openair0_config_t config2 = {0};
|
||||
char *argv[4];
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
argv[0] = strdup("--zmq.[0].tx_channels");
|
||||
argv[1] = strdup("tcp://127.0.0.1:5555");
|
||||
argv[2] = strdup("--zmq.[0].rx_channels");
|
||||
argv[3] = strdup("tcp://127.0.0.1:5556");
|
||||
|
||||
cfg1 = load_configmodule(sizeofArray(argv), argv, CONFIG_ENABLECMDLINEONLY);
|
||||
uniqCfg = cfg1;
|
||||
// 2. Initialize the ZMQ device
|
||||
config1.tx_num_channels = 1;
|
||||
config1.rx_num_channels = 1;
|
||||
config1.sample_rate = 30;
|
||||
ASSERT_EQ(device_init(&device1, &config1), 0);
|
||||
ASSERT_EQ(device1.trx_start_func(&device1), 0);
|
||||
|
||||
// Swap the RX with TX for second device
|
||||
char *tmp = argv[0];
|
||||
argv[0] = argv[2];
|
||||
argv[2] = tmp;
|
||||
cfg2 = load_configmodule(sizeofArray(argv), argv, CONFIG_ENABLECMDLINEONLY);
|
||||
uniqCfg = cfg2;
|
||||
config2.tx_num_channels = 1;
|
||||
config2.rx_num_channels = 1;
|
||||
config2.sample_rate = 1500;
|
||||
ASSERT_EQ(device_init(&device2, &config2), 0);
|
||||
ASSERT_EQ(device2.trx_start_func(&device2), 0);
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
if (device1.trx_end_func) {
|
||||
device1.trx_end_func(&device1);
|
||||
}
|
||||
if (device2.trx_end_func) {
|
||||
device2.trx_end_func(&device2);
|
||||
}
|
||||
if (cfg1) {
|
||||
end_configmodule(cfg1);
|
||||
}
|
||||
if (cfg2) {
|
||||
end_configmodule(cfg2);
|
||||
}
|
||||
for (auto i = 0U; i < sizeofArray(argv); i++) {
|
||||
free(argv[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ZMQTest, RXSamples)
|
||||
{
|
||||
std::thread t1([this]() {
|
||||
c16_t rx_samples[10];
|
||||
openair0_timestamp_t rx_timestamp;
|
||||
void *samples[1] = {rx_samples};
|
||||
ASSERT_EQ(device1.trx_read_func(&device1, &rx_timestamp, samples, 10, 1), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(rx_samples[i].r, 0);
|
||||
ASSERT_EQ(rx_samples[i].i, 0);
|
||||
}
|
||||
});
|
||||
std::thread t2([this]() {
|
||||
c16_t rx_samples[10];
|
||||
openair0_timestamp_t rx_timestamp;
|
||||
void *samples[1] = {rx_samples};
|
||||
ASSERT_EQ(device2.trx_read_func(&device2, &rx_timestamp, samples, 10, 1), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(rx_samples[i].r, 0);
|
||||
ASSERT_EQ(rx_samples[i].i, 0);
|
||||
}
|
||||
});
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
TEST_F(ZMQTest, TxRxSamples)
|
||||
{
|
||||
std::thread t1([this]() {
|
||||
c16_t rx_samples[10];
|
||||
openair0_timestamp_t rx_timestamp;
|
||||
void *samples[1] = {rx_samples};
|
||||
ASSERT_EQ(device1.trx_read_func(&device1, &rx_timestamp, samples, 10, 1), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(rx_samples[i].r, 0);
|
||||
ASSERT_EQ(rx_samples[i].i, 0);
|
||||
}
|
||||
c16_t tx_samples[10];
|
||||
openair0_timestamp_t tx_timestamp = rx_timestamp + 10;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
tx_samples[i].r = i;
|
||||
tx_samples[i].i = i + 1;
|
||||
}
|
||||
samples[0] = tx_samples;
|
||||
ASSERT_EQ(device1.trx_write_func(&device1, tx_timestamp, samples, 10, 1, 0), 10);
|
||||
});
|
||||
std::thread t2([this]() {
|
||||
c16_t rx_samples[10];
|
||||
openair0_timestamp_t rx_timestamp;
|
||||
void *samples[1] = {rx_samples};
|
||||
ASSERT_EQ(device2.trx_read_func(&device2, &rx_timestamp, samples, 10, 1), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(rx_samples[i].r, 0);
|
||||
ASSERT_EQ(rx_samples[i].i, 0);
|
||||
}
|
||||
openair0_timestamp_t rx_timestamp2;
|
||||
ASSERT_EQ(device2.trx_read_func(&device2, &rx_timestamp2, samples, 10, 1), 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(rx_samples[i].r, i);
|
||||
ASSERT_EQ(rx_samples[i].i, i + 1);
|
||||
}
|
||||
ASSERT_EQ(rx_timestamp + 10, rx_timestamp2);
|
||||
});
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
logInit();
|
||||
g_log->log_component[HW].level = OAILOG_DEBUG;
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: BSD-3-Clause-Open-MPI
|
||||
* Based on zmq library in OCUDU project: ocudu/lib/radio/zmq
|
||||
* Refer to https://gitlab.com/ocudu/ocudu/-/raw/dev/LICENSE?ref_type=heads
|
||||
*/
|
||||
|
||||
#include "zmq_imported.h"
|
||||
#include "log.h"
|
||||
|
||||
const float c16_t_to_cf_t_factor = std::numeric_limits<int16_t>::max();
|
||||
static constexpr std::chrono::milliseconds TRANSMIT_TS_ALIGN_TIMEOUT = std::chrono::milliseconds(0);
|
||||
static constexpr std::chrono::milliseconds RECEIVE_TS_ALIGN_TIMEOUT = std::chrono::milliseconds(100);
|
||||
|
||||
void zmq_tx_channel::transmit(c16_t *samples, size_t nsamps, uint64_t timestamp)
|
||||
{
|
||||
std::scoped_lock lock(transmit_alignment_mutex_);
|
||||
size_t overflow = 0;
|
||||
if (timestamp > sample_count_) {
|
||||
overflow += buffer_.push_zeros(timestamp - sample_count_);
|
||||
sample_count_ = timestamp;
|
||||
}
|
||||
cf_t samples_float[nsamps];
|
||||
for (size_t i = 0; i < nsamps; i++) {
|
||||
samples_float[i].r = samples[i].r / c16_t_to_cf_t_factor;
|
||||
samples_float[i].i = samples[i].i / c16_t_to_cf_t_factor;
|
||||
}
|
||||
overflow += buffer_.push_samples(samples_float, nsamps);
|
||||
sample_count_ += nsamps;
|
||||
if (overflow) {
|
||||
LOG_W(HW, "Overflow on ZMQ channel by %lu samples\n", overflow);
|
||||
}
|
||||
is_tx_enabled_ = true;
|
||||
transmit_alignment_cvar_.notify_all();
|
||||
}
|
||||
|
||||
void zmq_tx_channel::start(uint64_t init_time)
|
||||
{
|
||||
sample_count_ = init_time;
|
||||
}
|
||||
|
||||
bool zmq_tx_channel::align(uint64_t timestamp, std::chrono::milliseconds timeout)
|
||||
{
|
||||
if (sample_count_ >= timestamp) {
|
||||
return sample_count_ > timestamp;
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(transmit_alignment_mutex_);
|
||||
if (is_tx_enabled_ && (timeout.count() != 0)) {
|
||||
bool is_not_timeout =
|
||||
transmit_alignment_cvar_.wait_for(lock, timeout, [this, timestamp]() { return sample_count_ >= timestamp; });
|
||||
if (is_not_timeout) {
|
||||
return sample_count_ > timestamp;
|
||||
}
|
||||
LOG_W(HW, "Timeout waiting for TX path to align samples\n");
|
||||
is_tx_enabled_ = false;
|
||||
}
|
||||
if (sample_count_ < timestamp) {
|
||||
buffer_.push_zeros(timestamp - sample_count_);
|
||||
sample_count_ = timestamp;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void zmq_rx_channel::receive(c16_t *samples, size_t nsamps)
|
||||
{
|
||||
size_t samples_popped = 0;
|
||||
cf_t samples_float[nsamps];
|
||||
while (samples_popped < (size_t)nsamps && !stopped_) {
|
||||
size_t popped_now = buffer_.pop_samples(samples_float + samples_popped, nsamps - samples_popped);
|
||||
samples_popped += popped_now;
|
||||
if (popped_now == 0) {
|
||||
usleep(100); // wait for more samples to arrive
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < nsamps; i++) {
|
||||
samples[i].r = samples_float[i].r * c16_t_to_cf_t_factor + 0.5;
|
||||
samples[i].i = samples_float[i].i * c16_t_to_cf_t_factor + 0.5;
|
||||
}
|
||||
}
|
||||
void zmq_rx_channel::stop()
|
||||
{
|
||||
stopped_ = true;
|
||||
}
|
||||
|
||||
void zmq_tx_stream::start(uint64_t init_time)
|
||||
{
|
||||
for (auto &chan : channels_) {
|
||||
chan->start(init_time);
|
||||
}
|
||||
}
|
||||
bool zmq_tx_stream::align(uint64_t timestamp, std::chrono::milliseconds timeout)
|
||||
{
|
||||
bool timestamp_passed = false;
|
||||
for (auto &chan : channels_) {
|
||||
timestamp_passed = timestamp_passed || chan->align(timestamp, timeout);
|
||||
}
|
||||
return timestamp_passed;
|
||||
}
|
||||
void zmq_tx_stream::transmit(c16_t **samples, size_t nsamps, uint64_t timestamp)
|
||||
{
|
||||
bool timestamp_passed = align(timestamp, TRANSMIT_TS_ALIGN_TIMEOUT);
|
||||
if (timestamp_passed) {
|
||||
LOG_W(HW, "Error, channel timeout\n");
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
for (auto chan : channels_) {
|
||||
chan->transmit(samples[i++], nsamps, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void zmq_rx_stream::start(uint64_t init_time)
|
||||
{
|
||||
sample_count_ = init_time;
|
||||
}
|
||||
void zmq_rx_stream::stop()
|
||||
{
|
||||
for (auto &chan : channels_) {
|
||||
chan->stop();
|
||||
}
|
||||
}
|
||||
void zmq_rx_stream::receive(c16_t **samples, size_t nsamps, uint64_t *timestamp)
|
||||
{
|
||||
*timestamp = sample_count_;
|
||||
uint64_t passed_timestamp = sample_count_ + nsamps;
|
||||
tx_stream_->align(passed_timestamp, RECEIVE_TS_ALIGN_TIMEOUT);
|
||||
int i = 0;
|
||||
for (auto chan : channels_) {
|
||||
chan->receive(samples[i++], nsamps);
|
||||
}
|
||||
sample_count_ += nsamps;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: BSD-3-Clause-Open-MPI
|
||||
* Based on zmq library in OCUDU project: ocudu/lib/radio/zmq
|
||||
* Refer to https://gitlab.com/ocudu/ocudu/-/raw/dev/LICENSE?ref_type=heads
|
||||
*/
|
||||
|
||||
#ifndef ZMQ_IMPORTED_H
|
||||
#define ZMQ_IMPORTED_H
|
||||
|
||||
#include <zmq.h>
|
||||
#include "ring_buffer.h"
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
class zmq_tx_channel {
|
||||
public:
|
||||
void *socket_;
|
||||
overflow_buffer buffer_;
|
||||
std::atomic<uint64_t> sample_count_ = 0;
|
||||
std::atomic<bool> is_tx_enabled_ = false;
|
||||
std::mutex transmit_alignment_mutex_;
|
||||
std::condition_variable transmit_alignment_cvar_;
|
||||
|
||||
zmq_tx_channel(void *s, uint64_t buffer_size) : socket_(s), buffer_(buffer_size)
|
||||
{
|
||||
}
|
||||
|
||||
void transmit(c16_t *samples, size_t nsamps, uint64_t timestamp);
|
||||
|
||||
void start(uint64_t init_time);
|
||||
|
||||
bool align(uint64_t timestamp, std::chrono::milliseconds timeout);
|
||||
};
|
||||
|
||||
class zmq_rx_channel {
|
||||
public:
|
||||
void *socket_;
|
||||
overflow_buffer buffer_;
|
||||
bool request_sent_;
|
||||
std::atomic<bool> stopped_;
|
||||
zmq_rx_channel(void *s, uint64_t buffer_size) : socket_(s), buffer_(buffer_size), stopped_(false)
|
||||
{
|
||||
}
|
||||
void receive(c16_t *samples, size_t nsamps);
|
||||
void stop();
|
||||
};
|
||||
|
||||
class zmq_tx_stream {
|
||||
public:
|
||||
std::vector<zmq_tx_channel *> channels_;
|
||||
void start(uint64_t init_time);
|
||||
bool align(uint64_t timestamp, std::chrono::milliseconds timeout);
|
||||
void transmit(c16_t **samples, size_t nsamps, uint64_t timestamp);
|
||||
};
|
||||
|
||||
class zmq_rx_stream {
|
||||
public:
|
||||
std::vector<zmq_rx_channel *> channels_;
|
||||
zmq_tx_stream *tx_stream_;
|
||||
uint64_t sample_count_ = 0;
|
||||
zmq_rx_stream() : sample_count_(0)
|
||||
{
|
||||
}
|
||||
void start(uint64_t init_time);
|
||||
void stop();
|
||||
void receive(c16_t **samples, size_t nsamps, uint64_t *timestamp);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
|
||||
*/
|
||||
|
||||
#include "PHY/TOOLS/tools_defs.h"
|
||||
#include "PHY/defs_common.h"
|
||||
#include "common/platform_types.h"
|
||||
#include "softmodem-common.h"
|
||||
#include "utils.h"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stddef.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include <common/utils/assertions.h>
|
||||
#include <common/utils/LOG/log.h>
|
||||
#include <common/config/config_userapi.h>
|
||||
#include "common_lib.h"
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <ring_buffer.h>
|
||||
#include <zmq.h>
|
||||
#include "zmq_imported.h"
|
||||
|
||||
#define ZMQ_SECTION "zmq"
|
||||
#define ZMQ_TX_CHANNELS "tx_channels"
|
||||
#define ZMQ_RX_CHANNELS "rx_channels"
|
||||
|
||||
#define ZMQ_PARAMS_DESC \
|
||||
{ \
|
||||
STRINGLISTPARAM(ZMQ_TX_CHANNELS, "list of zmq addresses represeting tx channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
|
||||
STRINGLISTPARAM(ZMQ_RX_CHANNELS, "list of zmq addresses represeting rx channels_\n", PARAMFLAG_MANDATORY, nullptr, nullptr), \
|
||||
};
|
||||
|
||||
const size_t sample_size = sizeof(cf_t);
|
||||
const size_t rx_buffer_size = sample_size * 300000;
|
||||
|
||||
typedef struct {
|
||||
void *context;
|
||||
zmq_tx_stream tx_stream;
|
||||
zmq_rx_stream rx_stream;
|
||||
std::thread poll_thread;
|
||||
std::atomic<bool> poll_thread_running;
|
||||
bool stopped = false;
|
||||
double sample_rate;
|
||||
} zmq_state_t;
|
||||
|
||||
static void poll_thread(zmq_state_t *s)
|
||||
{
|
||||
s->poll_thread_running = true;
|
||||
unsigned char *rx_buffer = static_cast<unsigned char *>(malloc(rx_buffer_size));
|
||||
const auto num_tx_channels = s->tx_stream.channels_.size();
|
||||
const auto num_rx_channels = s->rx_stream.channels_.size();
|
||||
std::vector<zmq_pollitem_t> items(num_tx_channels + num_rx_channels);
|
||||
std::vector<bool> reply_requested(num_tx_channels);
|
||||
for (size_t i = 0; i < num_tx_channels; ++i) {
|
||||
items[i] = {s->tx_stream.channels_[i]->socket_, 0, ZMQ_POLLIN, 0};
|
||||
// wait for REQ
|
||||
reply_requested[i] = false;
|
||||
}
|
||||
for (size_t i = 0; i < num_rx_channels; i++) {
|
||||
items[i + num_tx_channels] = {s->rx_stream.channels_[i]->socket_, 0, ZMQ_POLLIN, 0};
|
||||
}
|
||||
|
||||
const auto num_channels = num_tx_channels + num_rx_channels;
|
||||
while (s->poll_thread_running) {
|
||||
for (size_t i = 0; i < num_tx_channels; i++) {
|
||||
auto chan = s->tx_stream.channels_[i];
|
||||
if (!reply_requested[i]) {
|
||||
continue;
|
||||
}
|
||||
std::vector<cf_t> samples(1024);
|
||||
size_t num_popped = chan->buffer_.pop_samples(samples.data(), 1024);
|
||||
if (num_popped == 0) {
|
||||
continue;
|
||||
}
|
||||
int rc = zmq_send(chan->socket_, samples.data(), num_popped * sizeof(cf_t), 0);
|
||||
if (rc < 0) {
|
||||
LOG_E(HW, "[ZMQ] poll_thread zmq_send for TX antenna %d failed: %s\n", (int)i, zmq_strerror(errno));
|
||||
}
|
||||
reply_requested[i] = false;
|
||||
}
|
||||
|
||||
int rc = zmq_poll(items.data(), num_channels, 10); // 10ms timeout
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
LOG_E(HW, "[ZMQ] poll_thread zmq_poll failed: %s\n", zmq_strerror(errno));
|
||||
break;
|
||||
}
|
||||
if (rc == 0) {
|
||||
continue; // timeout
|
||||
}
|
||||
|
||||
// --- TX Sockets (ZMQ_REP) ---
|
||||
for (size_t i = 0; i < num_tx_channels; i++) {
|
||||
if (items[i].revents & ZMQ_POLLIN) {
|
||||
auto chan = s->tx_stream.channels_[i];
|
||||
char dummy;
|
||||
rc = zmq_recv(chan->socket_, &dummy, 1, 0);
|
||||
if (rc < 0) {
|
||||
LOG_E(HW, "[ZMQ] poll_thread zmq_recv for TX antenna %d failed: %s\n", (int)i, zmq_strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if (reply_requested[i]) {
|
||||
LOG_E(HW, "[ZMQ] Error, unexpected REQ before REP on TX antenna %d\n", (int)i);
|
||||
}
|
||||
reply_requested[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- RX Sockets (ZMQ_REQ) ---
|
||||
for (size_t i = 0; i < num_rx_channels; i++) {
|
||||
if (items[i + num_tx_channels].revents & ZMQ_POLLIN) {
|
||||
auto chan = s->rx_stream.channels_[i];
|
||||
rc = zmq_recv(chan->socket_, rx_buffer, rx_buffer_size, 0);
|
||||
if (rc < 0) {
|
||||
LOG_E(HW, "[ZMQ] poll_thread zmq_recv for RX antenna %d failed: %s\n", (int)i, zmq_strerror(errno));
|
||||
} else {
|
||||
size_t received_bytes = rc;
|
||||
if (rx_buffer_size < received_bytes) {
|
||||
LOG_W(HW,
|
||||
"[ZMQ] the RX buffer is too small! The received message size is %lu while the buffer is %lu. Message truncated\n",
|
||||
received_bytes,
|
||||
rx_buffer_size);
|
||||
}
|
||||
size_t num_samples_received = std::min(received_bytes, rx_buffer_size) / sizeof(cf_t);
|
||||
cf_t *samples = reinterpret_cast<cf_t *>(rx_buffer);
|
||||
size_t overflow = chan->buffer_.push_samples(samples, num_samples_received);
|
||||
if (rx_buffer_size < received_bytes) {
|
||||
overflow += chan->buffer_.push_zeros((received_bytes - rx_buffer_size) / sizeof(cf_t));
|
||||
}
|
||||
if (overflow) {
|
||||
LOG_W(HW, "Overflow on receive\n");
|
||||
}
|
||||
// After receiving, send next request to keep the stream flowing
|
||||
char dummy = 0;
|
||||
if (zmq_send(chan->socket_, &dummy, 1, 0) != 1) {
|
||||
LOG_E(HW, "[ZMQ] poll_thread zmq_send for RX antenna %d failed: %s\n", (int)i, zmq_strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(rx_buffer);
|
||||
}
|
||||
|
||||
static int zmq_write(openair0_device_t *device, openair0_timestamp_t timestamp, void **buff, int nsamps, int cc, int flags)
|
||||
{
|
||||
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
|
||||
AssertFatal((uint)cc == s->tx_stream.channels_.size(),
|
||||
"Request to write on more antennas (%d) than configured (%d)",
|
||||
cc,
|
||||
(int)s->tx_stream.channels_.size());
|
||||
|
||||
s->tx_stream.transmit((c16_t **)buff, nsamps, timestamp);
|
||||
|
||||
return nsamps;
|
||||
}
|
||||
|
||||
static int zmq_read(openair0_device_t *device, openair0_timestamp_t *ptimestamp, void **samplesVoid, int nsamps, int nbAnt)
|
||||
{
|
||||
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
|
||||
AssertFatal((uint)nbAnt == s->rx_stream.channels_.size(),
|
||||
"Request to read on more antennas (%d) than configured (%d)",
|
||||
nbAnt,
|
||||
(int)s->rx_stream.channels_.size());
|
||||
uint64_t timestamp;
|
||||
s->rx_stream.receive((c16_t **)samplesVoid, nsamps, ×tamp);
|
||||
*ptimestamp = timestamp;
|
||||
return nsamps;
|
||||
}
|
||||
|
||||
static int zmq_get_stats(openair0_device_t *device)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static int zmq_reset_stats(openair0_device_t *device)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static void zmq_end(openair0_device_t *device)
|
||||
{
|
||||
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
|
||||
if (s) {
|
||||
if (s->poll_thread_running) {
|
||||
s->poll_thread_running = false;
|
||||
if (s->poll_thread.joinable()) {
|
||||
s->poll_thread.join();
|
||||
}
|
||||
}
|
||||
for (auto &chan : s->tx_stream.channels_) {
|
||||
if (chan->socket_)
|
||||
zmq_close(chan->socket_);
|
||||
delete chan;
|
||||
}
|
||||
s->tx_stream.channels_.clear();
|
||||
|
||||
for (auto &chan : s->rx_stream.channels_) {
|
||||
if (chan->socket_)
|
||||
zmq_close(chan->socket_);
|
||||
delete chan;
|
||||
}
|
||||
s->rx_stream.channels_.clear();
|
||||
|
||||
if (s->context)
|
||||
zmq_ctx_destroy(s->context);
|
||||
delete s;
|
||||
}
|
||||
}
|
||||
|
||||
static int zmq_start(openair0_device_t *device)
|
||||
{
|
||||
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
|
||||
s->rx_stream.start(s->sample_rate / 100);
|
||||
s->tx_stream.start(s->sample_rate / 100);
|
||||
for (size_t i = 0; i < s->rx_stream.channels_.size(); i++) {
|
||||
auto channel = s->rx_stream.channels_[i];
|
||||
// Send initial request to start data flow
|
||||
char dummy = 0;
|
||||
if (zmq_send(channel->socket_, &dummy, 1, 0) != 1) {
|
||||
LOG_E(HW, "[ZMQ] zmq_send for initial RX request failed for antenna %lu: %s\n", i, zmq_strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
s->poll_thread = std::thread(poll_thread, s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zmq_stop(openair0_device_t *device)
|
||||
{
|
||||
zmq_state_t *s = static_cast<zmq_state_t *>(device->priv);
|
||||
s->rx_stream.stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zmq_set_freq(openair0_device_t *device, openair0_config_t *openair0_cfg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static int zmq_set_gains(openair0_device_t *device, openair0_config_t *openair0_cfg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static int zmq_write_init(openair0_device_t *device)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" __attribute__((__visibility__("default"))) int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg)
|
||||
{
|
||||
auto *zmq_state = new zmq_state_t();
|
||||
zmq_state->context = zmq_ctx_new();
|
||||
AssertFatal(zmq_state->context != NULL, "zmq_ctx_new failed");
|
||||
|
||||
LOG_I(HW, "[ZMQ] tx_antennas: %d, rx_antennas: %d\n", openair0_cfg->tx_num_channels, openair0_cfg->rx_num_channels);
|
||||
configmodule_interface_t *cfg = config_get_if();
|
||||
paramdef_t param_desc[] = ZMQ_PARAMS_DESC;
|
||||
std::string zmq_section = std::string(ZMQ_SECTION);
|
||||
int ru_id = openair0_cfg->ru_id;
|
||||
std::string zmq_array_section = std::string(ZMQ_SECTION) + ".[" + std::to_string(ru_id) + "]";
|
||||
int ret = config_get(cfg, param_desc, sizeofArray(param_desc), zmq_array_section.c_str());
|
||||
AssertFatal(ret >= 0, "configuration couldn't be performed\n");
|
||||
int num_configured_tx_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_TX_CHANNELS)->numelt;
|
||||
AssertFatal(num_configured_tx_channels == openair0_cfg->tx_num_channels,
|
||||
"Incorrect configuration: Number of zmq tx channels (%d) != number of configured tx channels (%d)\n",
|
||||
num_configured_tx_channels,
|
||||
openair0_cfg->tx_num_channels);
|
||||
int num_configured_rx_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_RX_CHANNELS)->numelt;
|
||||
AssertFatal(num_configured_rx_channels == openair0_cfg->rx_num_channels,
|
||||
"Incorrect configuration: Number of zmq rx channels (%d) != number of configured rx channels (%d)\n",
|
||||
num_configured_rx_channels,
|
||||
openair0_cfg->rx_num_channels);
|
||||
char **tx_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_TX_CHANNELS)->strlistptr;
|
||||
char **rx_channels = gpd(param_desc, sizeofArray(param_desc), ZMQ_RX_CHANNELS)->strlistptr;
|
||||
|
||||
// Setup TX sockets (one per antenna)
|
||||
if (openair0_cfg->tx_num_channels > 0) {
|
||||
zmq_state->tx_stream.channels_.resize(openair0_cfg->tx_num_channels);
|
||||
for (int i = 0; i < openair0_cfg->tx_num_channels; i++) {
|
||||
void *socket = zmq_socket(zmq_state->context, ZMQ_REP);
|
||||
AssertFatal(socket != NULL, "zmq_socket(ZMQ_REP) for TX antenna %d failed", i);
|
||||
int linger = 0;
|
||||
zmq_setsockopt(socket, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
AssertFatal(zmq_bind(socket, tx_channels[i]) == 0, "zmq_bind for TX antenna %d failed on %s", i, tx_channels[i]);
|
||||
auto channel = new zmq_tx_channel(socket, openair0_cfg->sample_rate);
|
||||
LOG_I(HW, "[ZMQ] TX socket for antenna %d bound to %s\n", i, tx_channels[i]);
|
||||
zmq_state->tx_stream.channels_[i] = channel;
|
||||
}
|
||||
}
|
||||
zmq_state->sample_rate = openair0_cfg->sample_rate;
|
||||
|
||||
// Setup RX sockets (one per antenna)
|
||||
if (openair0_cfg->rx_num_channels > 0) {
|
||||
zmq_state->rx_stream.channels_.resize(openair0_cfg->rx_num_channels);
|
||||
for (int i = 0; i < openair0_cfg->rx_num_channels; i++) {
|
||||
void *socket = zmq_socket(zmq_state->context, ZMQ_REQ);
|
||||
AssertFatal(socket != NULL, "zmq_socket(ZMQ_REQ) for RX antenna %d failed", i);
|
||||
int linger = 0;
|
||||
zmq_setsockopt(socket, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
AssertFatal(zmq_connect(socket, rx_channels[i]) == 0, "zmq_connect for RX antenna %d failed on %s", i, rx_channels[i]);
|
||||
auto channel = new zmq_rx_channel(socket, openair0_cfg->sample_rate);
|
||||
LOG_I(HW, "[ZMQ] RX socket for antenna %d connected to %s\n", i, rx_channels[i]);
|
||||
zmq_state->rx_stream.channels_[i] = channel;
|
||||
}
|
||||
zmq_state->rx_stream.tx_stream_ = &zmq_state->tx_stream;
|
||||
}
|
||||
|
||||
device->trx_start_func = zmq_start;
|
||||
device->trx_get_stats_func = zmq_get_stats;
|
||||
device->trx_reset_stats_func = zmq_reset_stats;
|
||||
device->trx_end_func = zmq_end;
|
||||
device->trx_stop_func = zmq_stop;
|
||||
device->trx_set_freq_func = zmq_set_freq;
|
||||
device->trx_set_gains_func = zmq_set_gains;
|
||||
device->trx_write_func = zmq_write;
|
||||
device->trx_read_func = zmq_read;
|
||||
device->type = RFSIMULATOR;
|
||||
IS_SOFTMODEM_RFSIM = 1U;
|
||||
openair0_cfg->rx_gain[0] = 0;
|
||||
device->openair0_cfg = openair0_cfg;
|
||||
device->priv = zmq_state;
|
||||
device->trx_write_init = zmq_write_init;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user