diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5495f55944..41f0a98042 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -13,61 +13,87 @@ jobs: strategy: matrix: include: + # --> metrics server + - TAGNAME: "" + REPOSITORY: metrics-server + EXTRA_CMAKE_ARGS: "" + ARCH: "" + PLATFORM: amd64 + LIB: "" + LIB_VERSION: "" + DOCKERFILE: ./docker/grafana/Dockerfile + CONTEXT: ./docker/grafana + # --> grafana + - TAGNAME: "" + REPOSITORY: grafana + EXTRA_CMAKE_ARGS: "" + ARCH: "" + PLATFORM: amd64 + LIB: "" + LIB_VERSION: "" + DOCKERFILE: ./docker/metrics_server/Dockerfile + CONTEXT: ./docker/metrics_server # --> split72 # AMD AVX2 - - SUFFIX: release_avx2 - SPLIT: "split72" + - TAGNAME: split72_release_avx2 + REPOSITORY: srsran-project EXTRA_CMAKE_ARGS: -DAUTO_DETECT_ISA=Off ARCH: x86-64-v3 - TAG: amd64-avx2 PLATFORM: amd64 LIB: dpdk LIB_VERSION: "23.11" - - SUFFIX: release_with_debug_avx2 - SPLIT: "split72" + DOCKERFILE: ./docker/Dockerfile + CONTEXT: ./ + - TAGNAME: split72_release_with_debug_avx2 + REPOSITORY: srsran-project EXTRA_CMAKE_ARGS: -DAUTO_DETECT_ISA=Off -DFORCE_DEBUG_INFO=On ARCH: x86-64-v3 - TAG: amd64-avx2 PLATFORM: amd64 LIB: dpdk LIB_VERSION: "23.11" + DOCKERFILE: ./docker/Dockerfile + CONTEXT: ./ # AMD AVX512 - - SUFFIX: release_avx512 - SPLIT: "split72" + - TAGNAME: split72_release_avx512 + REPOSITORY: srsran-project EXTRA_CMAKE_ARGS: -DAUTO_DETECT_ISA=Off ARCH: x86-64-v4 - TAG: amd64-avx2-avx512 PLATFORM: amd64 LIB: dpdk LIB_VERSION: "23.11" - - SUFFIX: release_with_debug_avx512 - SPLIT: "split72" + DOCKERFILE: ./docker/Dockerfile + CONTEXT: ./ + - TAGNAME: split72_release_with_debug_avx512 + REPOSITORY: srsran-project EXTRA_CMAKE_ARGS: -DAUTO_DETECT_ISA=Off -DFORCE_DEBUG_INFO=On ARCH: x86-64-v4 - TAG: amd64-avx2-avx512 PLATFORM: amd64 LIB: dpdk LIB_VERSION: "23.11" + DOCKERFILE: ./docker/Dockerfile + CONTEXT: ./ # --> split8 # AMD AVX2 - - SUFFIX: release_avx2 - SPLIT: "split8" + - TAGNAME: split8_release_avx2 + REPOSITORY: srsran-project EXTRA_CMAKE_ARGS: -DAUTO_DETECT_ISA=Off ARCH: x86-64-v3 - TAG: amd64-avx2 PLATFORM: amd64 LIB: uhd LIB_VERSION: "4.6.0.0" - - SUFFIX: release_with_debug_avx2 - SPLIT: "split8" + DOCKERFILE: ./docker/Dockerfile + CONTEXT: ./ + - TAGNAME: split8_release_with_debug_avx2 + REPOSITORY: srsran-project EXTRA_CMAKE_ARGS: -DAUTO_DETECT_ISA=Off -DFORCE_DEBUG_INFO=On ARCH: x86-64-v3 - TAG: amd64-avx2 PLATFORM: amd64 LIB: uhd LIB_VERSION: "4.6.0.0" + DOCKERFILE: ./docker/Dockerfile + CONTEXT: ./ env: - NAME: srsran_${{ matrix.SPLIT }}_${{ matrix.SUFFIX }} + PREFIX: softwareradiosystems/${{ matrix.REPOSITORY }}:${{ matrix.TAGNAME }} environment: dockerhub steps: - name: Checkout code @@ -83,18 +109,20 @@ jobs: id: tags run: | BRANCH_NAME="${GITHUB_REF#refs/heads/}" - DATE_TAG="${GITHUB_SHA:0:10}-$(date +'%Y-%m-%d')" + DATE_TAG="${GITHUB_SHA:0:10}__$(date +'%Y-%m-%d')" RELEASE_NAME="${{ github.event.release.name }}" if [ -n "$RELEASE_NAME" ]; then - tags="${{ env.NAME }}:${DATE_TAG},${{ env.NAME }}:${RELEASE_NAME}" + tags="${{ env.PREFIX }}-${DATE_TAG},${{ env.PREFIX }}-${RELEASE_NAME}" else if [ "$BRANCH_NAME" == "main" ]; then - tags="${{ env.NAME }}:${DATE_TAG},${{ env.NAME }}:latest" + tags="${{ env.PREFIX }}-${DATE_TAG},${{ env.PREFIX }}-latest" elif [ "$BRANCH_NAME" == "test" ]; then - tags="${{ env.NAME }}:${DATE_TAG},${{ env.NAME }}:next" + tags="${{ env.PREFIX }}-${DATE_TAG},${{ env.PREFIX }}-next" fi fi + tags="${tags//:-/:}" + echo "tags=$tags" echo "tags=$tags" >> $GITHUB_OUTPUT - name: Login to Docker Hub @@ -110,10 +138,11 @@ jobs: with: push: true tags: ${{ steps.tags.outputs.tags }} - file: ./docker/Dockerfile + file: ${{ matrix.DOCKERFILE }} platforms: ${{ matrix.PLATFORM }} + context: ${{ matrix.CONTEXT }} build-args: | - NAME="srsran_${SPLIT}_${SUFFIX}" + NAME="${{ env.NAME }}" LIB=${{ matrix.LIB }} LIB_VERSION=${{ matrix.LIB_VERSION }} ARCH=${{ matrix.ARCH }} \ No newline at end of file diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 6b7431e0c8..fb1106fccf 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -325,9 +325,13 @@ variables: rm -Rf build_time_metrics.txt else mv ${CI_PROJECT_DIR}/build/apps/gnb/gnb /tmp/gnb + mv ${CI_PROJECT_DIR}/build/apps/cu/srscu /tmp/srscu + mv ${CI_PROJECT_DIR}/build/apps/du/srsdu /tmp/srsdu cd build make clean mv /tmp/gnb ${CI_PROJECT_DIR}/build/apps/gnb/gnb + mv /tmp/srscu ${CI_PROJECT_DIR}/build/apps/cu/srscu + mv /tmp/srsdu ${CI_PROJECT_DIR}/build/apps/du/srsdu fi timeout: 4h artifacts: &build_artifacts diff --git a/.gitlab/ci/e2e.yml b/.gitlab/ci/e2e.yml index a32e2b48e7..c18dc0523a 100644 --- a/.gitlab/ci/e2e.yml +++ b/.gitlab/ci/e2e.yml @@ -368,6 +368,20 @@ amari 32UE memcheck: - *txrx-lib - *retina-needs +amari 4 cudu: + extends: .zmq + variables: + TESTBED: zmq_cudu + MARKERS: "smoke" + RETINA_PARAM_ARGS: "gnb.all.pcap=True gnb.all.mac_enable=True gnb.all.rlc_enable=True gnb.all.enable_integrity_protection=True" + E2E_LOG_LEVEL: "info" + allow_failure: true + needs: + - job: "basic relwithdeb" + artifacts: true + - *txrx-lib + - *retina-needs + ################################################################################ # TEST MODE ################################################################################ @@ -546,11 +560,12 @@ viavi: matrix: - KEYWORDS: [ "ideal and 1UE", - "ideal and 32UE", + "ideal and 32UE and not experimental", "fading and 1UE", # "fading and 32UE", "birth-death and 1UE", # "birth-death and 32UE", + "32UE and experimental", ] viavi-debug: diff --git a/.gitlab/ci/e2e/.env b/.gitlab/ci/e2e/.env index 7e40079375..eb76b38fe9 100644 --- a/.gitlab/ci/e2e/.env +++ b/.gitlab/ci/e2e/.env @@ -2,7 +2,7 @@ GNB_REMOTE_PATH=/usr/local/bin/gnb GNB_IS_EXECUTABLE=true SRSGNB_REGISTRY_URI=registry.gitlab.com/softwareradiosystems/srsgnb RETINA_REGISTRY_PREFIX=registry.gitlab.com/softwareradiosystems/ci/retina -RETINA_VERSION=0.50.4 +RETINA_VERSION=0.50.9 UBUNTU_VERSION=24.04 AMARISOFT_VERSION=2023-09-08 SRSUE_VERSION=23.11 diff --git a/.gitlab/ci/e2e/retina_request_zmq_cudu.yml b/.gitlab/ci/e2e/retina_request_zmq_cudu.yml new file mode 100644 index 0000000000..fc8cc614a8 --- /dev/null +++ b/.gitlab/ci/e2e/retina_request_zmq_cudu.yml @@ -0,0 +1,79 @@ +# +# Copyright 2013-2024 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +- name: amarisoft-ue + type: ue + image: ${RETINA_REGISTRY_PREFIX}/amarisoftue:${AMARISOFT_VERSION}_${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_1} + nof_ports: 32 + requirements: + arch: amd64 + cpu: + requests: 5 + limits: 5 + memory: + requests: "26G" + limits: "26G" + ephemeral-storage: + requests: "6G" + limits: "6G" + resources: + - type: zmq + - type: license + model: amarisoft-5g + shared_files: + - local_path: ${AMARISOFT_TXRX_BINARY_PATH} + remote_path: /opt/lteue/trx_srsran.so + is_executable: true + +- name: srs-gnb + type: gnb + image: ${RETINA_REGISTRY_PREFIX}/srscudu:${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_1} + requirements: + arch: amd64 + cpu: + requests: 5 + limits: 5 + memory: + requests: "26G" + limits: "26G" + ephemeral-storage: + requests: "15G" + limits: "15G" + resources: + - type: zmq + shared_files: + - local_path: ${GNB_BINARY_PATH} + remote_path: ${GNB_REMOTE_PATH} + is_executable: ${GNB_IS_EXECUTABLE} + - local_path: ../../build/apps/cu/srscu + remote_path: /usr/local/bin/srscu + is_executable: true + - local_path: ../../build/apps/du/srsdu + remote_path: /usr/local/bin/srsdu + is_executable: true + +- name: open5gs + type: 5gc + requirements: + arch: amd64 + cpu: + requests: 1 + limits: 1 + memory: + requests: "8G" + limits: "8G" + ephemeral-storage: + requests: "6G" + limits: "6G" + image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_1} diff --git a/COPYRIGHT b/COPYRIGHT index 2b08009f4c..3cc7d7e778 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -54,6 +54,10 @@ Files: external/cameron314/concurrentqueue.hpp Copyright: 2013-2020, Cameron Desrochers License: Simplified BSD +Files: external/TartanLlama/expected.hpp +Copyright: 2017 Sy Brand +License: CC0 1.0 Universal + License: MIT Permission is hereby granted, free of charge, to any person obtaining @@ -158,3 +162,127 @@ License: Boost Software License - Version 1.0 - August 17th, 2003 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +License: CC0 1.0 Universal + Creative Commons Legal Code + + CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + + Statement of Purpose + + The laws of most jurisdictions throughout the world automatically confer + exclusive Copyright and Related Rights (defined below) upon the creator + and subsequent owner(s) (each and all, an "owner") of an original work of + authorship and/or a database (each, a "Work"). + + Certain owners wish to permanently relinquish those rights to a Work for + the purpose of contributing to a commons of creative, cultural and + scientific works ("Commons") that the public can reliably and without fear + of later claims of infringement build upon, modify, incorporate in other + works, reuse and redistribute as freely as possible in any form whatsoever + and for any purposes, including without limitation commercial purposes. + These owners may contribute to the Commons to promote the ideal of a free + culture and the further production of creative, cultural and scientific + works, or to gain reputation or greater distribution for their Work in + part through the use and efforts of others. + + For these and/or other purposes and motivations, and without any + expectation of additional consideration or compensation, the person + associating CC0 with a Work (the "Affirmer"), to the extent that he or she + is an owner of Copyright and Related Rights in the Work, voluntarily + elects to apply CC0 to the Work and publicly distribute the Work under its + terms, with knowledge of his or her Copyright and Related Rights in the + Work and the meaning and intended legal effect of CC0 on those rights. + + 1. Copyright and Related Rights. A Work made available under CC0 may be + protected by copyright and related or neighboring rights ("Copyright and + Related Rights"). Copyright and Related Rights include, but are not + limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); + iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and + vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + + 2. Waiver. To the greatest extent permitted by, but not in contravention + of, applicable law, Affirmer hereby overtly, fully, permanently, + irrevocably and unconditionally waives, abandons, and surrenders all of + Affirmer's Copyright and Related Rights and associated claims and causes + of action, whether now known or unknown (including existing as well as + future claims and causes of action), in the Work (i) in all territories + worldwide, (ii) for the maximum duration provided by applicable law or + treaty (including future time extensions), (iii) in any current or future + medium and for any number of copies, and (iv) for any purpose whatsoever, + including without limitation commercial, advertising or promotional + purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each + member of the public at large and to the detriment of Affirmer's heirs and + successors, fully intending that such Waiver shall not be subject to + revocation, rescission, cancellation, termination, or any other legal or + equitable action to disrupt the quiet enjoyment of the Work by the public + as contemplated by Affirmer's express Statement of Purpose. + + 3. Public License Fallback. Should any part of the Waiver for any reason + be judged legally invalid or ineffective under applicable law, then the + Waiver shall be preserved to the maximum extent permitted taking into + account Affirmer's express Statement of Purpose. In addition, to the + extent the Waiver is so judged Affirmer hereby grants to each affected + person a royalty-free, non transferable, non sublicensable, non exclusive, + irrevocable and unconditional license to exercise Affirmer's Copyright and + Related Rights in the Work (i) in all territories worldwide, (ii) for the + maximum duration provided by applicable law or treaty (including future + time extensions), (iii) in any current or future medium and for any number + of copies, and (iv) for any purpose whatsoever, including without + limitation commercial, advertising or promotional purposes (the + "License"). The License shall be deemed effective as of the date CC0 was + applied by Affirmer to the Work. Should any part of the License for any + reason be judged legally invalid or ineffective under applicable law, such + partial invalidity or ineffectiveness shall not invalidate the remainder + of the License, and in such case Affirmer hereby affirms that he or she + will not (i) exercise any of his or her remaining Copyright and Related + Rights in the Work or (ii) assert any associated claims and causes of + action with respect to the Work, in either case contrary to Affirmer's + express Statement of Purpose. + + 4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/apps/du/du_appconfig_cli11_schema.cpp b/apps/du/du_appconfig_cli11_schema.cpp index 3a7732db39..e8bce3add0 100644 --- a/apps/du/du_appconfig_cli11_schema.cpp +++ b/apps/du/du_appconfig_cli11_schema.cpp @@ -62,18 +62,21 @@ static void configure_cli11_metrics_args(CLI::App& app, srs_du::metrics_appconfi static void configure_cli11_e2_args(CLI::App& app, e2_appconfig& e2_params) { - add_option(app, "--enable_du_e2", e2_params.enable_du_e2, "Enable DU E2 agent"); - add_option(app, "--addr", e2_params.ip_addr, "RIC IP address"); + add_option(app, "--enable_du_e2", e2_params.enable_du_e2, "Enable DU E2 agent")->capture_default_str(); + add_option(app, "--addr", e2_params.ip_addr, "RIC IP address")->capture_default_str(); add_option(app, "--port", e2_params.port, "RIC port")->capture_default_str()->check(CLI::Range(20000, 40000)); add_option(app, "--bind_addr", e2_params.bind_addr, "Local IP address to bind for RIC connection") + ->capture_default_str() ->check(CLI::ValidIPV4); - add_option(app, "--sctp_rto_initial", e2_params.sctp_rto_initial, "SCTP initial RTO value"); - add_option(app, "--sctp_rto_min", e2_params.sctp_rto_min, "SCTP RTO min"); - add_option(app, "--sctp_rto_max", e2_params.sctp_rto_max, "SCTP RTO max"); - add_option(app, "--sctp_init_max_attempts", e2_params.sctp_init_max_attempts, "SCTP init max attempts"); - add_option(app, "--sctp_max_init_timeo", e2_params.sctp_max_init_timeo, "SCTP max init timeout"); - add_option(app, "--e2sm_kpm_enabled", e2_params.e2sm_kpm_enabled, "Enable KPM service module"); - add_option(app, "--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module"); + add_option(app, "--sctp_rto_initial", e2_params.sctp_rto_initial, "SCTP initial RTO value")->capture_default_str(); + add_option(app, "--sctp_rto_min", e2_params.sctp_rto_min, "SCTP RTO min")->capture_default_str(); + add_option(app, "--sctp_rto_max", e2_params.sctp_rto_max, "SCTP RTO max")->capture_default_str(); + add_option(app, "--sctp_init_max_attempts", e2_params.sctp_init_max_attempts, "SCTP init max attempts") + ->capture_default_str(); + add_option(app, "--sctp_max_init_timeo", e2_params.sctp_max_init_timeo, "SCTP max init timeout") + ->capture_default_str(); + add_option(app, "--e2sm_kpm_enabled", e2_params.e2sm_kpm_enabled, "Enable KPM service module")->capture_default_str(); + add_option(app, "--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module")->capture_default_str(); } static void configure_cli11_buffer_pool_args(CLI::App& app, buffer_pool_appconfig& config) diff --git a/apps/gnb/gnb.cpp b/apps/gnb/gnb.cpp index 6d4b31398c..d8820d255a 100644 --- a/apps/gnb/gnb.cpp +++ b/apps/gnb/gnb.cpp @@ -81,6 +81,7 @@ #include "apps/units/flexible_du/split_dynamic/dynamic_du_unit_cli11_schema.h" #include "apps/units/flexible_du/split_dynamic/dynamic_du_unit_config_validator.h" #include "apps/units/flexible_du/split_dynamic/dynamic_du_unit_logger_registrator.h" +#include "srsran/support/cli11_utils.h" #ifdef DPDK_FOUND #include "srsran/hal/dpdk/dpdk_eal_factory.h" @@ -249,6 +250,8 @@ int main(int argc, char** argv) // Log input configuration. srslog::basic_logger& config_logger = srslog::fetch_basic_logger("CONFIG"); if (config_logger.debug.enabled()) { + // Refesh defaults in case some parameters may have changed after the autoderivation process. + refresh_defaults(app); config_logger.debug("Input configuration (all values): \n{}", app.config_to_str(true, false)); } else { config_logger.info("Input configuration (only non-default values): \n{}", app.config_to_str(false, false)); diff --git a/apps/gnb/gnb_appconfig_cli11_schema.cpp b/apps/gnb/gnb_appconfig_cli11_schema.cpp index a1b949ad79..d8d17b9365 100644 --- a/apps/gnb/gnb_appconfig_cli11_schema.cpp +++ b/apps/gnb/gnb_appconfig_cli11_schema.cpp @@ -49,7 +49,7 @@ static void configure_cli11_metrics_args(CLI::App& app, metrics_appconfig& metri add_option(app, "--enable_json_metrics", metrics_params.enable_json_metrics, "Enable JSON metrics reporting") ->always_capture_default(); - app.add_option("--addr", metrics_params.addr, "Metrics address.")->capture_default_str()->check(CLI::ValidIPV4); + app.add_option("--addr", metrics_params.addr, "Metrics address.")->capture_default_str(); app.add_option("--port", metrics_params.port, "Metrics UDP port.") ->capture_default_str() ->check(CLI::Range(0, 65535)); @@ -67,18 +67,21 @@ static void configure_cli11_metrics_args(CLI::App& app, metrics_appconfig& metri static void configure_cli11_e2_args(CLI::App& app, e2_appconfig& e2_params) { - add_option(app, "--enable_du_e2", e2_params.enable_du_e2, "Enable DU E2 agent"); - add_option(app, "--addr", e2_params.ip_addr, "RIC IP address"); - add_option(app, "--port", e2_params.port, "RIC port")->capture_default_str()->check(CLI::Range(20000, 40000)); + add_option(app, "--enable_du_e2", e2_params.enable_du_e2, "Enable DU E2 agent")->capture_default_str(); + add_option(app, "--addr", e2_params.ip_addr, "RIC IP address")->capture_default_str(); + add_option(app, "--port", e2_params.port, "RIC port")->check(CLI::Range(20000, 40000))->capture_default_str(); add_option(app, "--bind_addr", e2_params.bind_addr, "Local IP address to bind for RIC connection") + ->capture_default_str() ->check(CLI::ValidIPV4); - add_option(app, "--sctp_rto_initial", e2_params.sctp_rto_initial, "SCTP initial RTO value"); - add_option(app, "--sctp_rto_min", e2_params.sctp_rto_min, "SCTP RTO min"); - add_option(app, "--sctp_rto_max", e2_params.sctp_rto_max, "SCTP RTO max"); - add_option(app, "--sctp_init_max_attempts", e2_params.sctp_init_max_attempts, "SCTP init max attempts"); - add_option(app, "--sctp_max_init_timeo", e2_params.sctp_max_init_timeo, "SCTP max init timeout"); - add_option(app, "--e2sm_kpm_enabled", e2_params.e2sm_kpm_enabled, "Enable KPM service module"); - add_option(app, "--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module"); + add_option(app, "--sctp_rto_initial", e2_params.sctp_rto_initial, "SCTP initial RTO value")->capture_default_str(); + add_option(app, "--sctp_rto_min", e2_params.sctp_rto_min, "SCTP RTO min")->capture_default_str(); + add_option(app, "--sctp_rto_max", e2_params.sctp_rto_max, "SCTP RTO max")->capture_default_str(); + add_option(app, "--sctp_init_max_attempts", e2_params.sctp_init_max_attempts, "SCTP init max attempts") + ->capture_default_str(); + add_option(app, "--sctp_max_init_timeo", e2_params.sctp_max_init_timeo, "SCTP max init timeout") + ->capture_default_str(); + add_option(app, "--e2sm_kpm_enabled", e2_params.e2sm_kpm_enabled, "Enable KPM service module")->capture_default_str(); + add_option(app, "--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module")->capture_default_str(); } static void configure_cli11_buffer_pool_args(CLI::App& app, buffer_pool_appconfig& config) @@ -252,8 +255,10 @@ static void configure_cli11_expert_execution_args(CLI::App& app, expert_executio static void manage_hal_optional(CLI::App& app, gnb_appconfig& gnb_cfg) { // Clean the HAL optional. - if (app.get_subcommand("hal")->count_all() == 0) { + if (auto subcmd = app.get_subcommand("hal"); subcmd->count_all() == 0) { gnb_cfg.hal_config.reset(); + // As HAL configuration is optional, disable the command when it is not present in the configuration. + subcmd->disabled(); } } diff --git a/apps/units/cu_up/cu_up_builder.h b/apps/units/cu_up/cu_up_builder.h index d1070771dc..40c8f1d097 100644 --- a/apps/units/cu_up/cu_up_builder.h +++ b/apps/units/cu_up/cu_up_builder.h @@ -23,6 +23,7 @@ #pragma once #include "srsran/cu_up/cu_up.h" +#include "srsran/support/timers.h" #include namespace srsran { diff --git a/apps/units/cu_up/cu_up_wrapper.cpp b/apps/units/cu_up/cu_up_wrapper.cpp index d0383d9fea..b7368b07a1 100644 --- a/apps/units/cu_up/cu_up_wrapper.cpp +++ b/apps/units/cu_up/cu_up_wrapper.cpp @@ -46,48 +46,3 @@ std::optional cu_up_wrapper::get_n3_bind_port() { return cu_up->get_n3_bind_port(); } - -e1ap_message_handler& cu_up_wrapper::get_e1ap_message_handler() -{ - return cu_up->get_e1ap_message_handler(); -} - -void cu_up_wrapper::handle_bearer_context_release_command(const srs_cu_up::e1ap_bearer_context_release_command& msg) -{ - cu_up->handle_bearer_context_release_command(msg); -} - -bool cu_up_wrapper::e1ap_is_connected() -{ - return cu_up->e1ap_is_connected(); -} - -void cu_up_wrapper::on_e1ap_connection_establish() -{ - cu_up->on_e1ap_connection_establish(); -} - -void cu_up_wrapper::on_e1ap_connection_drop() -{ - cu_up->on_e1ap_connection_drop(); -} - -srs_cu_up::e1ap_bearer_context_setup_response -cu_up_wrapper::handle_bearer_context_setup_request(const srs_cu_up::e1ap_bearer_context_setup_request& msg) -{ - return cu_up->handle_bearer_context_setup_request(msg); -} - -// TODO remove from public interface -async_task -cu_up_wrapper::handle_bearer_context_modification_request( - const srs_cu_up::e1ap_bearer_context_modification_request& msg) -{ - return cu_up->handle_bearer_context_modification_request(msg); -} - -// TODO remove from public interface -void cu_up_wrapper::schedule_ue_async_task(srs_cu_up::ue_index_t ue_index, async_task task) -{ - cu_up->schedule_ue_async_task(ue_index, std::move(task)); -} diff --git a/apps/units/cu_up/cu_up_wrapper.h b/apps/units/cu_up/cu_up_wrapper.h index ed05279863..e511270d05 100644 --- a/apps/units/cu_up/cu_up_wrapper.h +++ b/apps/units/cu_up/cu_up_wrapper.h @@ -23,7 +23,6 @@ #pragma once #include "srsran/cu_up/cu_up.h" -#include "srsran/e1ap/cu_up/e1ap_cu_up_bearer_context_update.h" #include "srsran/gtpu/ngu_gateway.h" namespace srsran { @@ -45,33 +44,6 @@ class cu_up_wrapper : public srs_cu_up::cu_up_interface // See interface for documentation. std::optional get_n3_bind_port() override; - // See interface for documentation. - e1ap_message_handler& get_e1ap_message_handler() override; - - // See interface for documentation. - void handle_bearer_context_release_command(const srs_cu_up::e1ap_bearer_context_release_command& msg) override; - - // See interface for documentation. - bool e1ap_is_connected() override; - - // See interface for documentation. - void on_e1ap_connection_establish() override; - - // See interface for documentation. - void on_e1ap_connection_drop() override; - - // See interface for documentation. - srs_cu_up::e1ap_bearer_context_setup_response - handle_bearer_context_setup_request(const srs_cu_up::e1ap_bearer_context_setup_request& msg) override; - - // See interface for documentation. - async_task - handle_bearer_context_modification_request(const srs_cu_up::e1ap_bearer_context_modification_request& msg) override; - - /// \brief Schedule an async task for an UE. - /// Can be used to initiate UE routines. - void schedule_ue_async_task(srs_cu_up::ue_index_t ue_index, async_task task) override; - private: std::unique_ptr gateway; std::unique_ptr cu_up; diff --git a/apps/units/flexible_du/du_high/du_high_config_cli11_schema.cpp b/apps/units/flexible_du/du_high/du_high_config_cli11_schema.cpp index 76f16045dc..47e002ad91 100644 --- a/apps/units/flexible_du/du_high/du_high_config_cli11_schema.cpp +++ b/apps/units/flexible_du/du_high/du_high_config_cli11_schema.cpp @@ -28,6 +28,7 @@ #include "srsran/ran/duplex_mode.h" #include "srsran/support/cli11_utils.h" #include "srsran/support/config_parsers.h" +#include "srsran/support/format_utils.h" using namespace srsran; @@ -43,6 +44,28 @@ static expected parse_int(const std::string& value) } } +/// Returns a default capture function for vectors of integers. +template +static std::function get_vector_default_function(span value) +{ + static_assert(std::is_integral_v, "Invalid Integer"); + + return [value]() -> std::string { + if (value.empty()) { + return {}; + } + + fmt::memory_buffer buffer; + fmt::format_to(buffer, "["); + for (unsigned i = 0, e = value.size() - 1; i != e; ++i) { + fmt::format_to(buffer, "{},", value[i]); + } + fmt::format_to(buffer, "{}]", value.back()); + + return to_c_str(buffer); + }; +} + static void configure_cli11_log_args(CLI::App& app, du_high_unit_logger_config& log_params) { app_services::add_log_option(app, log_params.mac_level, "--mac_level", "MAC log level"); @@ -115,6 +138,7 @@ static void configure_cli11_pdcch_common_args(CLI::App& app, pdcch_common_unit_c "--ss1_n_candidates", common_params.ss1_n_candidates, "Number of PDCCH candidates per aggregation level for SearchSpace#1. Default: {0, 0, 1, 0, 0}") + ->default_function(get_vector_default_function(span(common_params.ss1_n_candidates))) ->capture_default_str() ->check(CLI::IsMember({0, 1, 2, 3, 4, 5, 6, 8})); @@ -162,6 +186,7 @@ static void configure_cli11_pdcch_dedicated_args(CLI::App& app, pdcch_dedicated_ ded_params.ss2_n_candidates, "Number of PDCCH candidates per aggregation level for SearchSpace#2. Default: {0, 0, 0, 0, 0} i.e. " "auto-compute nof. candidates") + ->default_function(get_vector_default_function(span(ded_params.ss2_n_candidates))) ->capture_default_str() ->check(CLI::IsMember({0, 1, 2, 3, 4, 5, 6, 8})); @@ -298,11 +323,12 @@ static void configure_cli11_pdsch_args(CLI::App& app, du_high_unit_pdsch_config& ->capture_default_str() ->check(CLI::Range(static_cast(dc_offset_t::min), static_cast(dc_offset_t::max)) | CLI::IsMember({"outside", "undetermined", "center"})); - add_option(app, - "--harq_la_cqi_drop_threshold", - pdsch_params.harq_la_cqi_drop_threshold, - "Link Adaptation (LA) threshold for drop in CQI of the first HARQ transmission above which HARQ " - "retransmissions are cancelled. Set this value to 0 to disable this feature") + add_option(app, + "--harq_la_cqi_drop_threshold", + pdsch_params.harq_la_cqi_drop_threshold, + "Link Adaptation (LA) threshold for drop in CQI of the first HARQ transmission above which HARQ " + "retransmissions are cancelled. Set this value to 0 to disable this feature") + ->default_function([values = pdsch_params.harq_la_cqi_drop_threshold]() { return std::to_string(values); }) ->capture_default_str() ->check(CLI::Range(0, 15)); add_option(app, @@ -310,6 +336,7 @@ static void configure_cli11_pdsch_args(CLI::App& app, du_high_unit_pdsch_config& pdsch_params.harq_la_ri_drop_threshold, "Link Adaptation (LA) threshold for drop in nof. layers of the first HARQ transmission above which " "HARQ retransmission is cancelled. Set this value to 0 to disable this feature") + ->default_function([values = pdsch_params.harq_la_ri_drop_threshold]() { return std::to_string(values); }) ->capture_default_str() ->check(CLI::Range(0, 4)); add_option(app, "--dmrs_additional_position", pdsch_params.dmrs_add_pos, "PDSCH DMRS additional position") @@ -317,7 +344,7 @@ static void configure_cli11_pdsch_args(CLI::App& app, du_high_unit_pdsch_config& ->check(CLI::Range(0, 3)); } -static void configure_cli11_du_args(CLI::App& app, bool warn_on_drop) +static void configure_cli11_du_args(CLI::App& app, bool& warn_on_drop) { add_option( app, "--warn_on_drop", warn_on_drop, "Log a warning for dropped packets in F1-U, RLC and MAC due to full queues") @@ -441,6 +468,9 @@ static void configure_cli11_tdd_ul_dl_args(CLI::App& app, du_high_unit_tdd_ul_dl if (sub_cmd->count() != 0) { tdd_ul_dl_params.pattern2.emplace(pattern2_cfg); } + if (!tdd_ul_dl_params.pattern2.has_value()) { + pattern2_sub_cmd->disabled(); + } }; pattern2_sub_cmd->parse_complete_callback(tdd_pattern2_verify_callback); } @@ -843,6 +873,7 @@ static void configure_cli11_si_sched_info(CLI::App& app, du_high_unit_sib_config "--sib_mapping", si_sched_info.sib_mapping_info, "Mapping of SIB types to SI-messages. SIB numbers should not be repeated") + ->default_function(get_vector_default_function(span(si_sched_info.sib_mapping_info))) ->capture_default_str() ->check(CLI::IsMember({2, 19})); add_option( @@ -900,18 +931,23 @@ static void configure_cli11_prach_args(CLI::App& app, du_high_unit_prach_config& "--preamble_trans_max", prach_params.preamble_trans_max, "Max number of RA preamble transmissions performed before declaring a failure") + ->default_function([value = prach_params.preamble_trans_max]() { return std::to_string(value); }) ->capture_default_str() ->check(CLI::IsMember({3, 4, 5, 6, 7, 8, 10, 20, 50, 100, 200})); add_option(app, "--power_ramping_step_db", prach_params.power_ramping_step_db, "Power ramping steps for PRACH") + ->default_function([value = prach_params.power_ramping_step_db]() { return std::to_string(value); }) ->capture_default_str() ->check(CLI::IsMember({0, 2, 4, 6})); - add_option(app, "--ports", prach_params.ports, "List of antenna ports")->capture_default_str(); + add_option(app, "--ports", prach_params.ports, "List of antenna ports") + ->default_function(get_vector_default_function(span(prach_params.ports))) + ->capture_default_str(); add_option(app, "--nof_ssb_per_ro", prach_params.nof_ssb_per_ro, "Number of SSBs per RACH occasion") ->check(CLI::IsMember({1})); add_option(app, "--nof_cb_preambles_per_ssb", prach_params.nof_cb_preambles_per_ssb, "Number of Contention Based preambles per SSB") + ->default_function([&value = prach_params.nof_cb_preambles_per_ssb]() { return std::to_string(value); }) ->check(CLI::Range(1, 64)); } @@ -999,14 +1035,17 @@ static void configure_cli11_common_cell_args(CLI::App& app, du_high_unit_base_ce add_option(app, "--pci", cell_params.pci, "PCI")->capture_default_str()->check(CLI::Range(0, 1007)); add_option(app, "--dl_arfcn", cell_params.dl_arfcn, "Downlink ARFCN")->capture_default_str(); add_auto_enum_option(app, "--band", cell_params.band, "NR band"); - add_option(app, "--common_scs", cell_params.common_scs, "Cell common subcarrier spacing") - ->transform([](const std::string& value) { - subcarrier_spacing scs = to_subcarrier_spacing(value); + add_option_function( + app, + "--common_scs", + [&scs = cell_params.common_scs](const std::string& value) -> std::string { + scs = to_subcarrier_spacing(value); if (scs == subcarrier_spacing::invalid) { - return "Invalid common subcarrier spacing '" + value + "'"; + return fmt::format("Invalid common subcarrier spacing '{}'", value); } - return std::to_string(to_numerology_value(scs)); - }) + return {}; + }, + "Cell common subcarrier spacing") ->capture_default_str(); add_option(app, "--channel_bandwidth_MHz", cell_params.channel_bw_mhz, "Channel bandwidth in MHz") ->capture_default_str() @@ -1120,6 +1159,9 @@ static void configure_cli11_common_cell_args(CLI::App& app, du_high_unit_base_ce if (tdd_sub_cmd->count() != 0) { cell_params.tdd_ul_dl_cfg.emplace(cell_tdd_pattern); } + if (!cell_params.tdd_ul_dl_cfg.has_value()) { + tdd_sub_cmd->disabled(); + } }; tdd_ul_dl_subcmd->parse_complete_callback(tdd_ul_dl_verify_callback); @@ -1423,24 +1465,29 @@ static void configure_cli11_qos_args(CLI::App& app, du_high_unit_qos_config& qos static void configure_cli11_e2_args(CLI::App& app, du_high_unit_e2_config& e2_params) { - add_option(app, "--enable_du_e2", e2_params.enable_du_e2, "Enable DU E2 agent"); - add_option(app, "--addr", e2_params.ip_addr, "RIC IP address"); - add_option(app, "--port", e2_params.port, "RIC port")->capture_default_str()->check(CLI::Range(20000, 40000)); + add_option(app, "--enable_du_e2", e2_params.enable_du_e2, "Enable DU E2 agent")->capture_default_str(); + add_option(app, "--addr", e2_params.ip_addr, "RIC IP address")->capture_default_str(); + add_option(app, "--port", e2_params.port, "RIC port")->check(CLI::Range(20000, 40000))->capture_default_str(); add_option(app, "--bind_addr", e2_params.bind_addr, "Local IP address to bind for RIC connection") + ->capture_default_str() ->check(CLI::ValidIPV4); - add_option(app, "--sctp_rto_initial", e2_params.sctp_rto_initial, "SCTP initial RTO value"); - add_option(app, "--sctp_rto_min", e2_params.sctp_rto_min, "SCTP RTO min"); - add_option(app, "--sctp_rto_max", e2_params.sctp_rto_max, "SCTP RTO max"); - add_option(app, "--sctp_init_max_attempts", e2_params.sctp_init_max_attempts, "SCTP init max attempts"); - add_option(app, "--sctp_max_init_timeo", e2_params.sctp_max_init_timeo, "SCTP max init timeout"); - add_option(app, "--e2sm_kpm_enabled", e2_params.e2sm_kpm_enabled, "Enable KPM service module"); - add_option(app, "--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module"); + add_option(app, "--sctp_rto_initial", e2_params.sctp_rto_initial, "SCTP initial RTO value")->capture_default_str(); + add_option(app, "--sctp_rto_min", e2_params.sctp_rto_min, "SCTP RTO min")->capture_default_str(); + add_option(app, "--sctp_rto_max", e2_params.sctp_rto_max, "SCTP RTO max")->capture_default_str(); + add_option(app, "--sctp_init_max_attempts", e2_params.sctp_init_max_attempts, "SCTP init max attempts") + ->capture_default_str(); + add_option(app, "--sctp_max_init_timeo", e2_params.sctp_max_init_timeo, "SCTP max init timeout") + ->capture_default_str(); + add_option(app, "--e2sm_kpm_enabled", e2_params.e2sm_kpm_enabled, "Enable KPM service module")->capture_default_str(); + add_option(app, "--e2sm_rc_enabled", e2_params.e2sm_rc_enabled, "Enable RC service module")->capture_default_str(); } void srsran::configure_cli11_with_du_high_config_schema(CLI::App& app, du_high_parsed_config& parsed_cfg) { add_option(app, "--gnb_id", parsed_cfg.config.gnb_id.id, "gNodeB identifier")->capture_default_str(); + // Adding a default function to display correctly the uint8_t type. add_option(app, "--gnb_id_bit_length", parsed_cfg.config.gnb_id.bit_length, "gNodeB identifier length in bits") + ->default_function([&value = parsed_cfg.config.gnb_id.bit_length]() { return std::to_string(value); }) ->capture_default_str() ->check(CLI::Range(22, 32)); add_option(app, "--gnb_du_id", parsed_cfg.config.gnb_du_id, "gNB-DU Id") @@ -1571,6 +1618,8 @@ static void manage_ntn_optional(CLI::App& app, du_high_unit_config& gnb_cfg) } if (app.get_subcommand("ntn")->count_all() == 0) { gnb_cfg.ntn_cfg.reset(); + // As NTN configuration is optional, disable the command when it is not present in the configuration. + ntn_app->disabled(); } } diff --git a/apps/units/flexible_du/split_7_2/ru_ofh_config_cli11_schema.cpp b/apps/units/flexible_du/split_7_2/ru_ofh_config_cli11_schema.cpp index e4b2ef3548..0a10ab79ef 100644 --- a/apps/units/flexible_du/split_7_2/ru_ofh_config_cli11_schema.cpp +++ b/apps/units/flexible_du/split_7_2/ru_ofh_config_cli11_schema.cpp @@ -340,8 +340,10 @@ void srsran::configure_cli11_with_ru_ofh_config_schema(CLI::App& app, ru_ofh_uni static void manage_hal_optional(CLI::App& app, std::optional& hal_config) { // Clean the HAL optional. - if (app.get_subcommand("hal")->count_all() == 0) { + if (auto subcmd = app.get_subcommand("hal"); subcmd->count_all() == 0) { hal_config.reset(); + // As HAL configuration is optional, disable the command when it is not present in the configuration. + subcmd->disabled(); } } diff --git a/apps/units/flexible_du/split_8/ru_sdr_config_cli11_schema.cpp b/apps/units/flexible_du/split_8/ru_sdr_config_cli11_schema.cpp index efda790373..aab667ff2c 100644 --- a/apps/units/flexible_du/split_8/ru_sdr_config_cli11_schema.cpp +++ b/apps/units/flexible_du/split_8/ru_sdr_config_cli11_schema.cpp @@ -216,7 +216,23 @@ static void configure_cli11_lower_phy_threads_args(CLI::App& app, lower_phy_thre } return "Invalid executor profile. Valid profiles are: single, dual and quad."; - }); + }) + ->default_function([&execution_profile]() -> std::string { + switch (execution_profile) { + case lower_phy_thread_profile::blocking: + return "blocking"; + case lower_phy_thread_profile::dual: + return "dual"; + case lower_phy_thread_profile::quad: + return "quad"; + case lower_phy_thread_profile::single: + return "single"; + default: + break; + } + return {}; + }) + ->capture_default_str(); } static void configure_cli11_expert_execution_args(CLI::App& app, ru_sdr_unit_expert_execution_config& config) diff --git a/apps/units/flexible_du/split_dynamic/dynamic_du_unit_cli11_schema.cpp b/apps/units/flexible_du/split_dynamic/dynamic_du_unit_cli11_schema.cpp index ea4025f677..c6b3d9d67d 100644 --- a/apps/units/flexible_du/split_dynamic/dynamic_du_unit_cli11_schema.cpp +++ b/apps/units/flexible_du/split_dynamic/dynamic_du_unit_cli11_schema.cpp @@ -103,9 +103,12 @@ void srsran::configure_cli11_with_dynamic_du_unit_config_schema(CLI::App& app, d static void manage_ru(CLI::App& app, dynamic_du_unit_config& parsed_cfg) { // Manage the RU optionals - unsigned nof_ofh_entries = app.get_subcommand("ru_ofh")->count_all(); - unsigned nof_sdr_entries = app.get_subcommand("ru_sdr")->count_all(); - unsigned nof_dummy_entries = app.get_subcommand("ru_dummy")->count_all(); + auto ofh_subcmd = app.get_subcommand("ru_ofh"); + auto sdr_subcmd = app.get_subcommand("ru_sdr"); + auto dummy_subcmd = app.get_subcommand("ru_dummy"); + unsigned nof_ofh_entries = ofh_subcmd->count_all(); + unsigned nof_sdr_entries = sdr_subcmd->count_all(); + unsigned nof_dummy_entries = dummy_subcmd->count_all(); // Count the number of RU types. unsigned nof_ru_types = (nof_ofh_entries != 0) ? 1 : 0; @@ -119,15 +122,23 @@ static void manage_ru(CLI::App& app, dynamic_du_unit_config& parsed_cfg) if (nof_ofh_entries != 0) { parsed_cfg.ru_cfg = ofh_cfg; + sdr_subcmd->disabled(); + dummy_subcmd->disabled(); + return; } if (nof_sdr_entries != 0) { parsed_cfg.ru_cfg = sdr_cfg; + ofh_subcmd->disabled(); + dummy_subcmd->disabled(); + return; } parsed_cfg.ru_cfg = dummy_cfg; + sdr_subcmd->disabled(); + ofh_subcmd->disabled(); } void srsran::autoderive_dynamic_du_parameters_after_parsing(CLI::App& app, dynamic_du_unit_config& parsed_cfg) diff --git a/configs/low_latency.yml b/configs/low_latency.yml new file mode 100644 index 0000000000..d12731980f --- /dev/null +++ b/configs/low_latency.yml @@ -0,0 +1,26 @@ +# Low latency configuration section. + +# This is a supplementary configuration file to achieve the low latency. This config will overwrite the `cell_cfg` and +# `expert_phy` options in the base configuration to achieve low latency. This config can be run with the following +# command when running the gNB: +# sudo ./gnb -c gnb_rf_b200_tdd_n78_20mhz.yml -c low_latency.yml + +cell_cfg: + pusch: + min_k2: 2 + tdd_ul_dl_cfg: + dl_ul_tx_period: 4 + nof_dl_slots: 2 + nof_dl_symbols: 10 + nof_ul_slots: 1 + nof_ul_symbols: 0 + pucch: + sr_period_ms: 2 + min_k1: 2 + mac_cell_group: + bsr_cfg: + periodic_bsr_timer: 1 + +expert_phy: + max_request_headroom_slots: 0 + max_proc_delay: 1 diff --git a/include/srsran/adt/bf16.h b/include/srsran/adt/bf16.h index f49a93d354..3dea53fced 100644 --- a/include/srsran/adt/bf16.h +++ b/include/srsran/adt/bf16.h @@ -23,6 +23,7 @@ #pragma once #include "strong_type.h" +#include #include #include @@ -54,6 +55,15 @@ inline bf16_t to_bf16(float value) return bf16_t(temp_u16); } +/// Converts an \c int16 value to \c bfloat16. The conversion adjusts the input magnitude range [-scale, +scale] to the +/// output magnitude range [-1.0, 1.0]. +inline bf16_t to_bf16(int16_t value, float scale) +{ + const float gain = 1.0f / scale; + float out = static_cast(value) * gain; + return to_bf16(out); +} + /// Converts a \c bfloat16 to IEEE 754 single-precision 32-bit float. inline float to_float(bf16_t value) { @@ -71,4 +81,11 @@ inline float to_float(bf16_t value) return ret; } +/// Converts a \c bfloat16 value to \c int16. The conversion scales the input before rounding it to the nearest integer. +inline int16_t to_int16(bf16_t value, float scale) +{ + float temp = to_float(value); + return static_cast(std::round(temp * scale)); +} + } // namespace srsran diff --git a/include/srsran/cu_up/cu_up.h b/include/srsran/cu_up/cu_up.h index 2f5b7b6453..9355fe7428 100644 --- a/include/srsran/cu_up/cu_up.h +++ b/include/srsran/cu_up/cu_up.h @@ -22,63 +22,16 @@ #pragma once -#include "srsran/adt/optional.h" -#include "srsran/e1ap/common/e1ap_common.h" -#include "srsran/e1ap/cu_up/e1ap_cu_up.h" -#include "srsran/gtpu/gtpu_demux.h" +#include +#include namespace srsran::srs_cu_up { -/// Interface to notify about E1AP connections (from the CU-CP) to the CU-UP -class cu_up_e1ap_connection_notifier +/// \brief Public interface to the main CU-UP class +class cu_up_interface { public: - virtual ~cu_up_e1ap_connection_notifier() = default; - - /// \brief Notifies the CU-UP about a successful E1AP connection. - virtual void on_e1ap_connection_establish() = 0; - - /// \brief Notifies the CU-CP about a dropped E1AP connection. - virtual void on_e1ap_connection_drop() = 0; -}; - -class cu_up_e1ap_interface -{ -public: - virtual ~cu_up_e1ap_interface() = default; - - /// \brief Create a new UE context and handle bearer setup request. - /// \param[in] msg The original bearer setup request. - /// \return Returns message containing the index of the created UE and all response/failure message. - virtual e1ap_bearer_context_setup_response - handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) = 0; - - /// \brief Create a new UE context and handle bearer modification request. - /// \param[in] msg The original bearer modification request. - /// \return Returns message containing the index of the created UE and all response/failure message. - virtual async_task - handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& msg) = 0; - - /// \brief Handle bearer release command and remove the associated UE context. - /// \param[in] msg The original bearer release command. - virtual void handle_bearer_context_release_command(const e1ap_bearer_context_release_command& msg) = 0; - - /// \brief Get the E1AP message handler interface. - virtual e1ap_message_handler& get_e1ap_message_handler() = 0; - - /// \brief Get the state of the E1AP connection. - /// \return True if E1AP is connected, false otherwise. - virtual bool e1ap_is_connected() = 0; - - /// \brief Schedule an async task for an UE. - /// Can be used to initiate UE routines. - virtual void schedule_ue_async_task(ue_index_t ue_index, async_task task) = 0; -}; - -class cu_up_interface : public cu_up_e1ap_connection_notifier, public cu_up_e1ap_interface -{ -public: - ~cu_up_interface() override = default; + virtual ~cu_up_interface() = default; virtual void start() = 0; diff --git a/include/srsran/cu_up/cu_up_manager.h b/include/srsran/cu_up/cu_up_manager.h new file mode 100644 index 0000000000..50ff47b8db --- /dev/null +++ b/include/srsran/cu_up/cu_up_manager.h @@ -0,0 +1,79 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "srsran/e1ap/cu_up/e1ap_cu_up_bearer_context_update.h" +#include "srsran/support/async/async_task.h" + +namespace srsran::srs_cu_up { + +/// Interface to notify about E1AP connections (from the CU-CP) to the CU-UP +class cu_up_manager_e1ap_connection_notifier +{ +public: + virtual ~cu_up_manager_e1ap_connection_notifier() = default; + + /// \brief Notifies the CU-UP about a successful E1AP connection. + virtual void on_e1ap_connection_establish() = 0; + + /// \brief Notifies the CU-CP about a dropped E1AP connection. + virtual void on_e1ap_connection_drop() = 0; +}; + +class cu_up_manager_e1ap_interface +{ +public: + virtual ~cu_up_manager_e1ap_interface() = default; + + /// \brief Create a new UE context and handle bearer setup request. + /// \param[in] msg The original bearer setup request. + /// \return Returns message containing the index of the created UE and all response/failure message. + virtual e1ap_bearer_context_setup_response + handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) = 0; + + /// \brief Create a new UE context and handle bearer modification request. + /// \param[in] msg The original bearer modification request. + /// \return Returns message containing the index of the created UE and all response/failure message. + virtual async_task + handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& msg) = 0; + + /// \brief Handle bearer release command and remove the associated UE context. + /// \param[in] msg The original bearer release command. + virtual void handle_bearer_context_release_command(const e1ap_bearer_context_release_command& msg) = 0; + + /// \brief Get the state of the E1AP connection. + /// \return True if E1AP is connected, false otherwise. + virtual bool e1ap_is_connected() = 0; + + virtual void schedule_ue_async_task(srs_cu_up::ue_index_t ue_index, async_task task) = 0; +}; + +class cu_up_manager : public cu_up_manager_e1ap_connection_notifier, public cu_up_manager_e1ap_interface +{ +public: + ~cu_up_manager() override = default; + + virtual size_t get_nof_ues() = 0; +}; + +} // namespace srsran::srs_cu_up diff --git a/include/srsran/e1ap/cu_up/e1ap_cu_up.h b/include/srsran/e1ap/cu_up/e1ap_cu_up.h index cb16cd371a..71ff588453 100644 --- a/include/srsran/e1ap/cu_up/e1ap_cu_up.h +++ b/include/srsran/e1ap/cu_up/e1ap_cu_up.h @@ -62,11 +62,11 @@ class e1ap_control_message_handler handle_bearer_context_inactivity_notification(const e1ap_bearer_context_inactivity_notification& msg) = 0; }; -/// Methods used by E1AP to notify the CU-UP. -class e1ap_cu_up_notifier +/// Methods used by E1AP to notify the CU-UP manager. +class e1ap_cu_up_manager_notifier { public: - virtual ~e1ap_cu_up_notifier() = default; + virtual ~e1ap_cu_up_manager_notifier() = default; /// \brief Notifies the UE manager to create a UE context. /// \param[in] msg The received bearer context setup message. diff --git a/include/srsran/e1ap/cu_up/e1ap_cu_up_factory.h b/include/srsran/e1ap/cu_up/e1ap_cu_up_factory.h index d21c6af2cc..14956dba5d 100644 --- a/include/srsran/e1ap/cu_up/e1ap_cu_up_factory.h +++ b/include/srsran/e1ap/cu_up/e1ap_cu_up_factory.h @@ -32,10 +32,10 @@ namespace srsran { namespace srs_cu_up { /// Creates an instance of an E1AP interface, notifying outgoing packets on the specified listener object. -std::unique_ptr create_e1ap(e1_connection_client& e1_client_handler_, - e1ap_cu_up_notifier& cu_up_notifier_, - timer_manager& timers_, - task_executor& cu_up_exec_); +std::unique_ptr create_e1ap(e1_connection_client& e1_client_handler_, + e1ap_cu_up_manager_notifier& cu_up_notifier_, + timer_manager& timers_, + task_executor& cu_up_exec_); } // namespace srs_cu_up } // namespace srsran diff --git a/include/srsran/ngap/ngap.h b/include/srsran/ngap/ngap.h index f1dcbee5f8..6157029922 100644 --- a/include/srsran/ngap/ngap.h +++ b/include/srsran/ngap/ngap.h @@ -24,6 +24,7 @@ #include "srsran/cu_cp/cu_cp_types.h" #include "srsran/ngap/ngap_handover.h" +#include "srsran/ngap/ngap_init_context_setup.h" #include "srsran/ngap/ngap_reset.h" #include "srsran/ngap/ngap_setup.h" #include "srsran/support/async/async_task.h" @@ -114,9 +115,6 @@ class ngap_rrc_ue_control_notifier public: virtual ~ngap_rrc_ue_control_notifier() = default; - /// \brief Notify about the reception of new security context. - virtual async_task on_new_security_context() = 0; - /// \brief Get packed handover preparation message for inter-gNB handover. virtual byte_buffer on_handover_preparation_message_required() = 0; }; @@ -171,6 +169,12 @@ class ngap_cu_cp_notifier /// \return True if the security context was successfully initialized, false otherwise. virtual bool on_handover_request_received(ue_index_t ue_index, security::security_context sec_ctxt) = 0; + /// \brief Notify about the reception of a new Initial Context Setup Request. + /// \param[in] request The received Initial Context Setup Request. + /// \returns The Initial Context Setup Response or the Initial Context Setup Failure. + virtual async_task> + on_new_initial_context_setup_request(ngap_init_context_setup_request& request) = 0; + /// \brief Notify about the reception of a new PDU Session Resource Setup Request. /// \param[in] request The received PDU Session Resource Setup Request. /// \returns The PDU Session Resource Setup Response. diff --git a/include/srsran/pdcp/pdcp_sn_util.h b/include/srsran/pdcp/pdcp_sn_util.h index ce29345fde..c02705b8a9 100644 --- a/include/srsran/pdcp/pdcp_sn_util.h +++ b/include/srsran/pdcp/pdcp_sn_util.h @@ -24,6 +24,7 @@ #include "srsran/adt/byte_buffer.h" #include "srsran/pdcp/pdcp_sn_size.h" +#include "srsran/srslog/logger.h" #include "srsran/support/bit_encoding.h" #include #include @@ -37,30 +38,40 @@ namespace srsran { /// /// \param pdcp_pdu PDCP PDU (or RLC SDU) from which the PDCP SN shall be extracted. /// \param pdcp_sn_len The length of the PDCP SN (12 bit or 18 bit) in the PDU. +/// \param is_srb Determines the bearer type: SRB (true) or DRB (false). /// \return The PDCP SN of the PDU in case of a data PDU; or no value in case of control PDU. inline std::optional -get_pdcp_sn(byte_buffer_view pdcp_pdu, pdcp_sn_size pdcp_sn_len, srslog::basic_logger& logger) +get_pdcp_sn(byte_buffer_view pdcp_pdu, pdcp_sn_size pdcp_sn_len, bool is_srb, srslog::basic_logger& logger) { if (pdcp_pdu.empty()) { logger.error("Cannot get PDCP SN from empty PDU"); - srsran_assertion_failure("Cannot get PDCP SN from empty PDU"); + return {}; + } + + if (is_srb && pdcp_sn_len != pdcp_sn_size::size12bits) { + logger.error( + "Cannot get PDCP SN of SRB PDU: Invalid pdcp_sn_len={}. pdcp_pdu_len=", pdcp_sn_len, pdcp_pdu.length()); return {}; } bit_decoder decoder{pdcp_pdu}; bool read_ok; - // D/C field + // D/C field (or R for SRBs) uint8_t dc_field = {}; read_ok = decoder.unpack(dc_field, 1); if (!read_ok) { logger.error("Failed to get PDCP SN: Cannot read D/C field. pdcp_pdu_len={}", pdcp_pdu.length()); - srsran_assertion_failure("Failed to get PDCP SN: Cannot read D/C field. pdcp_pdu_len={}", pdcp_pdu.length()); return {}; } - if (dc_field == 0) { - logger.debug("Cannot get PDCP SN from control PDU"); + if (!is_srb && dc_field == 0) { + logger.debug("Cannot get PDCP SN of DRB control PDU"); + return {}; + } + + if (is_srb && dc_field == 1) { + logger.warning("Cannot get PDCP SN of SRB PDU: Reserved MSB set. pdcp_pdu_len={}", pdcp_pdu.length()); return {}; } @@ -77,13 +88,20 @@ get_pdcp_sn(byte_buffer_view pdcp_pdu, pdcp_sn_size pdcp_sn_len, srslog::basic_l break; default: logger.error("Cannot get PDCP SN: Unsupported pdcp_sn_len={}", pdcp_sn_len); - srsran_assertion_failure("Cannot get PDCP SN: Unsupported pdcp_sn_len={}", pdcp_sn_len); return {}; } if (!read_ok) { logger.error("Failed to get PDCP SN: Cannot read PDCP header. pdcp_pdu_len={}", pdcp_pdu.length()); - srsran_assertion_failure("Failed to get PDCP SN: Cannot read PDCP header. pdcp_pdu_len={}", pdcp_pdu.length()); + return {}; + } + + if (reserved != 0) { + if (is_srb) { + logger.warning("Cannot get PDCP SN for SRB PDU with reserved bits set. pdcp_pdu_len={}", pdcp_pdu.length()); + } else { + logger.warning("Cannot get PDCP SN for DRB data PDU with reserved bits set. pdcp_pdu_len={}", pdcp_pdu.length()); + } return {}; } diff --git a/include/srsran/ran/pucch/pucch_configuration.h b/include/srsran/ran/pucch/pucch_configuration.h index 6e9b23ed5a..ced23bd36d 100644 --- a/include/srsran/ran/pucch/pucch_configuration.h +++ b/include/srsran/ran/pucch/pucch_configuration.h @@ -180,7 +180,7 @@ struct pucch_resource { /// \ref pucch_config. struct pucch_resource_set { /// \c PUCCH-ResourceSetId. - uint8_t pucch_res_set_id; + pucch_res_set_idx pucch_res_set_id; /// \c resourceList. static_vector pucch_res_id_list; /// \c maxPayloadSize. @@ -213,6 +213,16 @@ struct pucch_config { /// \c dl-DataToUL-ACK. Values {0..15}. static_vector dl_data_to_ul_ack; + /// PUCCH resource max UCI payload, depending on the format. The index defines the format. + /// \remark The UCI payload is the same for all UE's PUCCH resources belonging to the same format, regardless of + /// whether they are used for HARQ-ACK or CSI. + /// \remark For Format 0 and 1, only the max number of HARQ-ACK bits are considered. + static_vector format_max_payload{0, 0, 0, 0, 0}; + + /// Returns the PUCCH resource max UCI payload for the given format. + /// \remark For Format 0 and 1, it returns only the max number of HARQ-ACK bits. + unsigned get_max_payload(pucch_format format) const { return format_max_payload[pucch_format_to_uint(format)]; } + bool operator==(const pucch_config& rhs) const { return pucch_res_set == rhs.pucch_res_set && pucch_res_list == rhs.pucch_res_list && diff --git a/include/srsran/ran/pucch/pucch_mapping.h b/include/srsran/ran/pucch/pucch_mapping.h index b323115f07..b2d5e55f52 100644 --- a/include/srsran/ran/pucch/pucch_mapping.h +++ b/include/srsran/ran/pucch/pucch_mapping.h @@ -36,8 +36,22 @@ enum class pucch_group_hopping { DISABLE }; +/// \brief PUCCH resource set index, as per \c PUCCH-ResourceSetId, TS 38.331. +/// At the moment, we only supports PUCCH resource set index 0 and 1. +enum class pucch_res_set_idx : uint8_t { set_0 = 0, set_1 }; + +inline uint8_t pucch_res_set_idx_to_uint(pucch_res_set_idx format) +{ + return static_cast(format); +} + /// \brief PUCCH Formats as described in TS38.213 Section 9.2. -enum class pucch_format { FORMAT_0, FORMAT_1, FORMAT_2, FORMAT_3, FORMAT_4, NOF_FORMATS }; +enum class pucch_format : uint8_t { FORMAT_0, FORMAT_1, FORMAT_2, FORMAT_3, FORMAT_4, NOF_FORMATS }; + +inline uint8_t pucch_format_to_uint(pucch_format format) +{ + return static_cast(format); +} /// Defines whether the PUCCH within the current slot belongs to a PUCCH repetition. For more details, refer to /// TS38.213, Section 9.2.6. diff --git a/include/srsran/rrc/rrc_ue.h b/include/srsran/rrc/rrc_ue.h index 9fc007f36c..1c4073861b 100644 --- a/include/srsran/rrc/rrc_ue.h +++ b/include/srsran/rrc/rrc_ue.h @@ -136,10 +136,6 @@ class rrc_ue_security_mode_command_proc_notifier /// \brief Setup AS security in the UE. This includes configuring /// the PDCP entity security on SRB1 with the new AS keys. virtual void on_new_as_security_context() = 0; - - /// \brief Setup AS security in the UE. This includes configuring - /// the PDCP entity security on SRB1 with the new AS keys. - virtual void on_security_context_sucessful() = 0; }; /// Interface used by the RRC reestablishment procedure to @@ -191,6 +187,12 @@ class rrc_ue_control_notifier const unsigned tac) = 0; }; +struct rrc_ue_security_mode_command_context { + unsigned transaction_id; + nr_cell_global_id_t sp_cell_id; + byte_buffer rrc_ue_security_mode_command_pdu; +}; + struct rrc_ue_release_context { cu_cp_user_location_info_nr user_location_info; byte_buffer rrc_release_pdu; @@ -208,6 +210,10 @@ class rrc_ue_control_message_handler public: virtual ~rrc_ue_control_message_handler() = default; + /// \brief Get the packed Security Mode Command. + /// \returns The Security Mode Command context. + virtual rrc_ue_security_mode_command_context get_security_mode_command_context() = 0; + /// \brief Handle an RRC Reconfiguration Request. /// \param[in] msg The new RRC Reconfiguration Request. /// \returns The result of the rrc reconfiguration. @@ -253,25 +259,17 @@ class rrc_ue_control_message_handler virtual byte_buffer get_rrc_handover_command(const rrc_reconfiguration_procedure_request& request, unsigned transaction_id) = 0; + /// \brief Get the packed RRC Handover Preparation Message. virtual byte_buffer get_packed_handover_preparation_message() = 0; }; -/// Handler to initialize the security context from NGAP. -class rrc_ue_init_security_context_handler -{ -public: - virtual ~rrc_ue_init_security_context_handler() = default; - - /// \brief Handle the received Init Security Context. - virtual async_task handle_init_security_context() = 0; -}; - /// Handler to get the handover preparation context to the NGAP. class rrc_ue_handover_preparation_handler { public: virtual ~rrc_ue_handover_preparation_handler() = default; + /// \brief Get the packed Handover Preparation Message. virtual byte_buffer get_packed_handover_preparation_message() = 0; }; @@ -403,7 +401,6 @@ class rrc_ue_interface : public rrc_ul_ccch_pdu_handler, public rrc_dl_nas_message_handler, public rrc_ue_srb_handler, public rrc_ue_control_message_handler, - public rrc_ue_init_security_context_handler, public rrc_ue_setup_proc_notifier, public rrc_ue_security_mode_command_proc_notifier, public rrc_ue_reconfiguration_proc_notifier, @@ -415,15 +412,14 @@ class rrc_ue_interface : public rrc_ul_ccch_pdu_handler, rrc_ue_interface() = default; virtual ~rrc_ue_interface() = default; - virtual rrc_ue_controller& get_controller() = 0; - virtual rrc_ul_ccch_pdu_handler& get_ul_ccch_pdu_handler() = 0; - virtual rrc_ul_dcch_pdu_handler& get_ul_dcch_pdu_handler() = 0; - virtual rrc_dl_nas_message_handler& get_rrc_dl_nas_message_handler() = 0; - virtual rrc_ue_srb_handler& get_rrc_ue_srb_handler() = 0; - virtual rrc_ue_control_message_handler& get_rrc_ue_control_message_handler() = 0; - virtual rrc_ue_init_security_context_handler& get_rrc_ue_init_security_context_handler() = 0; - virtual rrc_ue_context_handler& get_rrc_ue_context_handler() = 0; - virtual rrc_ue_handover_preparation_handler& get_rrc_ue_handover_preparation_handler() = 0; + virtual rrc_ue_controller& get_controller() = 0; + virtual rrc_ul_ccch_pdu_handler& get_ul_ccch_pdu_handler() = 0; + virtual rrc_ul_dcch_pdu_handler& get_ul_dcch_pdu_handler() = 0; + virtual rrc_dl_nas_message_handler& get_rrc_dl_nas_message_handler() = 0; + virtual rrc_ue_srb_handler& get_rrc_ue_srb_handler() = 0; + virtual rrc_ue_control_message_handler& get_rrc_ue_control_message_handler() = 0; + virtual rrc_ue_context_handler& get_rrc_ue_context_handler() = 0; + virtual rrc_ue_handover_preparation_handler& get_rrc_ue_handover_preparation_handler() = 0; }; } // namespace srs_cu_cp diff --git a/include/srsran/scheduler/scheduler_pucch_format.h b/include/srsran/scheduler/scheduler_pucch_format.h index b2e86c2d0f..ca2a634856 100644 --- a/include/srsran/scheduler/scheduler_pucch_format.h +++ b/include/srsran/scheduler/scheduler_pucch_format.h @@ -55,7 +55,7 @@ enum class pucch_format_4_sf { sf2, sf4 }; struct pucch_resources { prb_interval prbs; ofdm_symbol_range symbols; - prb_interval second_hop_prbs; + prb_interval second_hop_prbs{0U, 0U}; }; /// Scheduler output for PUCCH Format 0. diff --git a/include/srsran/scheduler/scheduler_slot_handler.h b/include/srsran/scheduler/scheduler_slot_handler.h index 0d4b42a049..67bdc48473 100644 --- a/include/srsran/scheduler/scheduler_slot_handler.h +++ b/include/srsran/scheduler/scheduler_slot_handler.h @@ -494,6 +494,15 @@ struct prach_occasion_info { /// Info about PUCCH used resource. struct pucch_info { + /// This information only is used by the scheduler and not passed to the PHY. + struct context { + /// Identifier of the PUCCH PDU within the list of PUCCH PDUs for a given slot. The ID is only meaningful for a + /// given UE; i.e., different UEs can reuse the same ID, but a UE cannot reuse the same ID for different PDUs. + unsigned id = MAX_PUCCH_PDUS_PER_SLOT; + /// Determines whether the PUCCH PDU uses common resources. + bool is_common = false; + }; + rnti_t crnti; const bwp_configuration* bwp_cfg; pucch_format format; @@ -508,6 +517,8 @@ struct pucch_info { }; /// In case the PUCCH will contain CSI bits, this struct contains information how those bits are to be decoded. std::optional csi_rep_cfg; + + context pdu_context; }; struct ul_sched_result { diff --git a/include/srsran/srsvec/conversion.h b/include/srsran/srsvec/conversion.h index c91f1a6bd0..07c1053eed 100644 --- a/include/srsran/srsvec/conversion.h +++ b/include/srsran/srsvec/conversion.h @@ -64,11 +64,53 @@ void convert(span x, float scale, span z); /// \param[in] in Data to convert. void convert(span out, span in); +/// \brief Converts a sequence of numbers from single precision float to brain float. +/// +/// \param[out] out Resultant data. +/// \param[in] in Data to convert. +void convert(span out, span in); + /// \brief Converts a sequence of numbers from complex brain float to complex float. /// /// \param[out] out Resultant data. /// \param[in] in Data to convert. void convert(span out, span in); +/// \brief Converts a sequence of numbers from brain float to single precision float. +/// +/// \param[out] out Resultant data. +/// \param[in] in Data to convert. +void convert(span out, span in); + +/// \brief Converts a sequence of numbers from complex brain float to int16 applying the given scaling and rounding the +/// result to the nearest integer. +/// +/// \param [out] z Resultant data. +/// \param [in] x Data to convert. +/// \param [in] scale Input data scaling prior conversion. +void convert(span z, span x, float scale); + +/// \brief Converts from int16 to complex brain float applying the given scaling. +/// +/// \param [out] z Resultant data. +/// \param [in] x Data to convert. +/// \param [in] scale Input data scaling after conversion. +void convert(span z, span x, float scale); + +/// \brief Converts a sequence of numbers from brain float to int16 applying the given scaling and rounding the result +/// to the nearest integer. +/// +/// \param [out] z Resultant data. +/// \param [in] x Data to convert. +/// \param [in] scale Input data scaling prior conversion. +void convert(span z, span x, float scale); + +/// Converts from int16 to brain float applying the given scaling. +/// +/// \param [out] z Resultant data. +/// \param [in] x Data to convert. +/// \param [in] scale Input data scaling after conversion. +void convert(span z, span x, float scale); + } // namespace srsvec } // namespace srsran diff --git a/include/srsran/support/cli11_utils.h b/include/srsran/support/cli11_utils.h index 0352bed76d..da60594382 100644 --- a/include/srsran/support/cli11_utils.h +++ b/include/srsran/support/cli11_utils.h @@ -81,7 +81,37 @@ CLI::Option* add_option(CLI::App& app, const std::string& option_name, T& param, callback(res); return CLI::detail::lexical_conversion(res, param); }, - desc) + desc, + false, + [¶m]() -> std::string { return CLI::detail::checked_to_string(param); }) + ->run_callback_for_default(); +} + +/// Specialization for bools than changes the default function for capture the default string. +template <> +inline CLI::Option* add_option(CLI::App& app, const std::string& option_name, bool& param, const std::string& desc) +{ + auto* opt = app.get_option_no_throw(option_name); + if (!opt) { + return app.add_option(option_name, param, desc)->default_function([¶m]() -> std::string { + return param ? "true" : "false"; + }); + } + + // Option was found. Get the callback and create new option. + auto callbck = opt->get_callback(); + app.remove_option(opt); + + return app + .add_option( + option_name, + [¶m, callback = std::move(callbck)](const CLI::results_t& res) { + callback(res); + return CLI::detail::lexical_conversion(res, param); + }, + desc, + false, + [¶m]() -> std::string { return param ? "true" : "false"; }) ->run_callback_for_default(); } @@ -200,4 +230,23 @@ void add_auto_enum_option(CLI::App& app, ->default_str("auto"); } +/// Refresh the defaults values of the given app. +inline void refresh_defaults(CLI::App& app) +{ + for (CLI::Option* opt : app.get_options()) { + // Only process the option that has a long-name (starts with a --) and is configurable. + if (opt->get_lnames().empty() || !opt->get_configurable()) { + continue; + } + opt->capture_default_str(); + } + + for (CLI::App* subcom : app.get_subcommands({})) { + if (subcom->get_disabled()) { + continue; + } + refresh_defaults(*subcom); + } +} + } // namespace srsran diff --git a/lib/cu_cp/CMakeLists.txt b/lib/cu_cp/CMakeLists.txt index 922f2cf488..81a8980081 100644 --- a/lib/cu_cp/CMakeLists.txt +++ b/lib/cu_cp/CMakeLists.txt @@ -39,6 +39,7 @@ set(SOURCES metrics_handler/metrics_handler_impl.cpp routine_managers/cu_cp_routine_manager.cpp routines/amf_connection_setup_routine.cpp + routines/initial_context_setup_routine.cpp routines/pdu_session_routine_helpers.cpp routines/pdu_session_resource_setup_routine.cpp routines/pdu_session_resource_release_routine.cpp diff --git a/lib/cu_cp/adapters/ngap_adapters.h b/lib/cu_cp/adapters/ngap_adapters.h index 8d93700fd9..22d28feb0c 100644 --- a/lib/cu_cp/adapters/ngap_adapters.h +++ b/lib/cu_cp/adapters/ngap_adapters.h @@ -75,6 +75,13 @@ class ngap_cu_cp_adapter : public ngap_cu_cp_du_repository_notifier, public ngap return cu_cp_handler->handle_handover_request(ue_index, sec_ctxt); } + async_task> + on_new_initial_context_setup_request(ngap_init_context_setup_request& request) override + { + srsran_assert(cu_cp_handler != nullptr, "CU-CP NGAP handler must not be nullptr"); + return cu_cp_handler->handle_new_initial_context_setup_request(request); + } + async_task on_new_pdu_session_resource_setup_request(cu_cp_pdu_session_resource_setup_request& request) override { @@ -184,13 +191,11 @@ class ngap_rrc_ue_adapter : public ngap_rrc_ue_pdu_notifier, public ngap_rrc_ue_ public: ngap_rrc_ue_adapter() = default; - void connect_rrc_ue(rrc_dl_nas_message_handler& rrc_ue_msg_handler_, - rrc_ue_init_security_context_handler& rrc_ue_security_handler_, - rrc_ue_handover_preparation_handler& rrc_ue_ho_prep_handler_) + void connect_rrc_ue(rrc_dl_nas_message_handler& rrc_ue_msg_handler_, + rrc_ue_handover_preparation_handler& rrc_ue_ho_prep_handler_) { - rrc_ue_msg_handler = &rrc_ue_msg_handler_; - rrc_ue_security_handler = &rrc_ue_security_handler_; - rrc_ue_ho_prep_handler = &rrc_ue_ho_prep_handler_; + rrc_ue_msg_handler = &rrc_ue_msg_handler_; + rrc_ue_ho_prep_handler = &rrc_ue_ho_prep_handler_; } void on_new_pdu(byte_buffer nas_pdu) override @@ -199,12 +204,6 @@ class ngap_rrc_ue_adapter : public ngap_rrc_ue_pdu_notifier, public ngap_rrc_ue_ rrc_ue_msg_handler->handle_dl_nas_transport_message(std::move(nas_pdu)); } - async_task on_new_security_context() override - { - srsran_assert(rrc_ue_security_handler != nullptr, "RRC UE security handler must not be nullptr"); - return rrc_ue_security_handler->handle_init_security_context(); - } - byte_buffer on_handover_preparation_message_required() override { srsran_assert(rrc_ue_ho_prep_handler != nullptr, "RRC UE UP manager must not be nullptr"); @@ -212,9 +211,8 @@ class ngap_rrc_ue_adapter : public ngap_rrc_ue_pdu_notifier, public ngap_rrc_ue_ } private: - rrc_dl_nas_message_handler* rrc_ue_msg_handler = nullptr; - rrc_ue_init_security_context_handler* rrc_ue_security_handler = nullptr; - rrc_ue_handover_preparation_handler* rrc_ue_ho_prep_handler = nullptr; + rrc_dl_nas_message_handler* rrc_ue_msg_handler = nullptr; + rrc_ue_handover_preparation_handler* rrc_ue_ho_prep_handler = nullptr; }; } // namespace srs_cu_cp diff --git a/lib/cu_cp/cu_cp_impl.cpp b/lib/cu_cp/cu_cp_impl.cpp index c8c4f9df94..3d0a821741 100644 --- a/lib/cu_cp/cu_cp_impl.cpp +++ b/lib/cu_cp/cu_cp_impl.cpp @@ -384,6 +384,22 @@ bool cu_cp_impl::handle_handover_request(ue_index_t ue_index, security::security return ue->get_security_manager().init_security_context(sec_ctxt); } +async_task> +cu_cp_impl::handle_new_initial_context_setup_request(const ngap_init_context_setup_request& request) +{ + cu_cp_ue* ue = ue_mng.find_du_ue(request.ue_index); + srsran_assert(ue != nullptr, "ue={}: Could not find UE", request.ue_index); + rrc_ue_interface* rrc_ue = rrc_du_adapters.at(ue->get_du_index()).find_rrc_ue(request.ue_index); + srsran_assert(rrc_ue != nullptr, "ue={}: Could not find RRC UE", request.ue_index); + + return routine_mng.start_initial_context_setup_routine( + request, + *rrc_ue, + ue->get_security_manager(), + du_db.get_du_processor(ue->get_du_index()).get_f1ap_interface().get_f1ap_ue_context_manager(), + get_cu_cp_ngap_handler()); +} + async_task cu_cp_impl::handle_new_pdu_session_resource_setup_request(cu_cp_pdu_session_resource_setup_request& request) { @@ -627,7 +643,6 @@ void cu_cp_impl::handle_rrc_ue_creation(ue_index_t ue_index, rrc_ue_interface& r { // Connect RRC UE to NGAP to RRC UE adapter ue_mng.get_ngap_rrc_ue_adapter(ue_index).connect_rrc_ue(rrc_ue.get_rrc_dl_nas_message_handler(), - rrc_ue.get_rrc_ue_init_security_context_handler(), rrc_ue.get_rrc_ue_handover_preparation_handler()); // Connect cu-cp to rrc ue adapters diff --git a/lib/cu_cp/cu_cp_impl.h b/lib/cu_cp/cu_cp_impl.h index 2f2afccb59..02a698ad90 100644 --- a/lib/cu_cp/cu_cp_impl.h +++ b/lib/cu_cp/cu_cp_impl.h @@ -82,6 +82,8 @@ class cu_cp_impl final : public cu_cp, // cu_cp_ngap_handler bool handle_handover_request(ue_index_t ue_index, security::security_context sec_ctxt) override; + async_task> + handle_new_initial_context_setup_request(const ngap_init_context_setup_request& request) override; async_task handle_new_pdu_session_resource_setup_request(cu_cp_pdu_session_resource_setup_request& request) override; async_task @@ -124,6 +126,7 @@ class cu_cp_impl final : public cu_cp, cu_cp_e1_handler& get_e1_handler() override { return cu_up_db; } cu_cp_e1ap_event_handler& get_cu_cp_e1ap_handler() override { return *this; } cu_cp_ng_handler& get_ng_handler() override { return *this; } + cu_cp_ngap_handler& get_cu_cp_ngap_handler() override { return *this; } cu_cp_command_handler& get_command_handler() override { return *this; } cu_cp_rrc_ue_interface& get_cu_cp_rrc_ue_interface() override { return *this; } cu_cp_measurement_handler& get_cu_cp_measurement_handler() override { return *this; } diff --git a/lib/cu_cp/cu_cp_impl_interface.h b/lib/cu_cp/cu_cp_impl_interface.h index 495044e1fd..1f55d6969b 100644 --- a/lib/cu_cp/cu_cp_impl_interface.h +++ b/lib/cu_cp/cu_cp_impl_interface.h @@ -76,6 +76,12 @@ class cu_cp_ngap_handler : public cu_cp_ue_context_release_handler, public cu_cp /// \return True if the security context was successfully initialized, false otherwise. virtual bool handle_handover_request(ue_index_t ue_index, security::security_context sec_ctxt) = 0; + /// \brief Handle the reception of a new Initial Context Setup Request. + /// \param[in] request The received Initial Context Setup Request. + /// \returns The Initial Context Setup Response or the Initial Context Setup Failure. + virtual async_task> + handle_new_initial_context_setup_request(const ngap_init_context_setup_request& request) = 0; + /// \brief Handle the reception of a new PDU Session Resource Setup Request. /// \param[in] request The received PDU Session Resource Setup Request. /// \returns The PDU Session Resource Setup Response. @@ -333,6 +339,7 @@ class cu_cp_impl_interface : public cu_cp_e1ap_event_handler, virtual ~cu_cp_impl_interface() = default; virtual cu_cp_e1ap_event_handler& get_cu_cp_e1ap_handler() = 0; + virtual cu_cp_ngap_handler& get_cu_cp_ngap_handler() = 0; virtual cu_cp_rrc_ue_interface& get_cu_cp_rrc_ue_interface() = 0; virtual cu_cp_ue_context_manipulation_handler& get_cu_cp_ue_context_handler() = 0; virtual cu_cp_measurement_handler& get_cu_cp_measurement_handler() = 0; diff --git a/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp b/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp index 4147635f86..e72c59e1c2 100644 --- a/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp +++ b/lib/cu_cp/routine_managers/cu_cp_routine_manager.cpp @@ -21,6 +21,7 @@ */ #include "cu_cp_routine_manager.h" +#include "../routines/initial_context_setup_routine.h" #include "../routines/mobility/inter_cu_handover_target_routine.h" #include "../routines/mobility/inter_du_handover_routine.h" #include "../routines/pdu_session_resource_modification_routine.h" @@ -50,6 +51,17 @@ bool cu_cp_routine_manager::schedule_async_task(async_task task) return main_ctrl_loop.schedule(std::move(task)); } +async_task> +cu_cp_routine_manager::start_initial_context_setup_routine(const ngap_init_context_setup_request& request, + rrc_ue_interface& rrc_ue, + ue_security_manager& security_mng, + f1ap_ue_context_manager& f1ap_ue_ctxt_mng, + cu_cp_ngap_handler& pdu_session_setup_handler) +{ + return launch_async( + request, rrc_ue, security_mng, f1ap_ue_ctxt_mng, pdu_session_setup_handler, logger); +} + async_task cu_cp_routine_manager::start_pdu_session_resource_setup_routine( const cu_cp_pdu_session_resource_setup_request& setup_msg, const srsran::security::sec_as_config& security_cfg, diff --git a/lib/cu_cp/routine_managers/cu_cp_routine_manager.h b/lib/cu_cp/routine_managers/cu_cp_routine_manager.h index cc718cab5d..a33ed05407 100644 --- a/lib/cu_cp/routine_managers/cu_cp_routine_manager.h +++ b/lib/cu_cp/routine_managers/cu_cp_routine_manager.h @@ -42,6 +42,13 @@ class cu_cp_routine_manager : public common_task_scheduler bool schedule_async_task(async_task task) override; + async_task> + start_initial_context_setup_routine(const ngap_init_context_setup_request& request, + rrc_ue_interface& rrc_ue, + ue_security_manager& security_mng, + f1ap_ue_context_manager& f1ap_ue_ctxt_mng, + cu_cp_ngap_handler& pdu_session_setup_handler); + async_task start_pdu_session_resource_setup_routine(const cu_cp_pdu_session_resource_setup_request& setup_msg, const srsran::security::sec_as_config& security_cfg, diff --git a/lib/cu_cp/routines/initial_context_setup_routine.cpp b/lib/cu_cp/routines/initial_context_setup_routine.cpp new file mode 100644 index 0000000000..4ccfae83ff --- /dev/null +++ b/lib/cu_cp/routines/initial_context_setup_routine.cpp @@ -0,0 +1,177 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "initial_context_setup_routine.h" +#include "pdu_session_routine_helpers.h" +#include "srsran/ran/cause/ngap_cause.h" +#include "srsran/rrc/rrc_ue.h" +#include + +using namespace srsran; +using namespace srsran::srs_cu_cp; +using namespace asn1::rrc_nr; + +initial_context_setup_routine::initial_context_setup_routine(const ngap_init_context_setup_request& request_, + rrc_ue_interface& rrc_ue_, + ue_security_manager& security_mng_, + f1ap_ue_context_manager& f1ap_ue_ctxt_mng_, + cu_cp_ngap_handler& pdu_session_setup_handler_, + srslog::basic_logger& logger_) : + request(request_), + rrc_ue(rrc_ue_), + security_mng(security_mng_), + f1ap_ue_ctxt_mng(f1ap_ue_ctxt_mng_), + pdu_session_setup_handler(pdu_session_setup_handler_), + logger(logger_) +{ +} + +void initial_context_setup_routine::operator()( + coro_context>>& ctx) +{ + CORO_BEGIN(ctx); + + logger.info("ue={}: \"{}\" initialized", request.ue_index, name()); + + // Initialize security context + if (!security_mng.init_security_context(request.security_context)) { + handle_failure(); + CORO_EARLY_RETURN(make_unexpected(fail_msg)); + } + + // Get Security Mode Command from RRC UE + { + rrc_smc_ctxt = rrc_ue.get_security_mode_command_context(); + if (rrc_smc_ctxt.rrc_ue_security_mode_command_pdu.empty()) { + handle_failure(); + CORO_EARLY_RETURN(make_unexpected(fail_msg)); + } + } + + // Prepare F1AP UE Context Setup Request and call F1AP notifier + { + // Add remaining fields to UE Context Setup Request + ue_context_setup_request.ue_index = request.ue_index; + ue_context_setup_request.sp_cell_id = rrc_smc_ctxt.sp_cell_id; + ue_context_setup_request.serv_cell_idx = 0; // TODO: Remove hardcoded value + ue_context_setup_request.rrc_container = rrc_smc_ctxt.rrc_ue_security_mode_command_pdu.copy(); + // Setup SRB2 + ue_context_setup_request.srbs_to_be_setup_list.emplace(srb_id_t::srb2, + f1ap_srbs_to_be_setup_mod_item{srb_id_t::srb2}); + + // Call F1AP procedure + CORO_AWAIT_VALUE(ue_context_setup_response, + f1ap_ue_ctxt_mng.handle_ue_context_setup_request(ue_context_setup_request, std::nullopt)); + // Handle UE Context Setup Response + if (!ue_context_setup_response.success) { + handle_failure(); + CORO_EARLY_RETURN(make_unexpected(fail_msg)); + } + } + + // Start UE Capability Enquiry Procedure + { + /// TODO: Move UE Capability Enquiry Procedure here + } + + // Handle optional IEs + + // Handle PDU Session Resource Setup List Context Request + /// NOTE: The handling of this includes the RRC Reconfiguration procedure + if (request.pdu_session_res_setup_list_cxt_req.has_value()) { + request.pdu_session_res_setup_list_cxt_req.value().ue_index = request.ue_index; + request.pdu_session_res_setup_list_cxt_req.value().serving_plmn = request.guami.plmn; + if (request.ue_aggr_max_bit_rate.has_value()) { + request.pdu_session_res_setup_list_cxt_req.value().ue_aggregate_maximum_bit_rate_dl = + request.ue_aggr_max_bit_rate.value().ue_aggr_max_bit_rate_dl; + } else { + request.pdu_session_res_setup_list_cxt_req.value().ue_aggregate_maximum_bit_rate_dl = 0; + } + + CORO_AWAIT_VALUE(pdu_session_setup_response, + pdu_session_setup_handler.handle_new_pdu_session_resource_setup_request( + request.pdu_session_res_setup_list_cxt_req.value())); + + resp_msg.pdu_session_res_setup_response_items = pdu_session_setup_response.pdu_session_res_setup_response_items; + resp_msg.pdu_session_res_failed_to_setup_items = pdu_session_setup_response.pdu_session_res_failed_to_setup_items; + + // Handle NAS PDUs from PDU Session Resource Setup List Context Request + for (auto& session : request.pdu_session_res_setup_list_cxt_req.value().pdu_session_res_setup_items) { + if (!session.pdu_session_nas_pdu.empty()) { + handle_nas_pdu(session.pdu_session_nas_pdu.copy()); + } + } + + // Handle NAS PDUs from Initial Context Setup Request + if (request.nas_pdu.has_value()) { + handle_nas_pdu(request.nas_pdu.value().copy()); + } + + } else { + // prepare RRC Reconfiguration and call RRC UE notifier + if (!fill_rrc_reconfig_args(rrc_reconfig_args, + ue_context_setup_request.srbs_to_be_setup_list, + {} /* No DRB to setup */, + {} /* No extra DRB to be removed */, + ue_context_setup_response.du_to_cu_rrc_info, + request.nas_pdu.has_value() ? std::vector{request.nas_pdu.value().copy()} + : std::vector{}, + rrc_ue.generate_meas_config(std::nullopt), + false /* No SRBs to reestablish */, + false /* No DRBs to reestablish */, + false /* No keys to update */, + {} /* No SIB1 */, + logger)) { + logger.warning("ue={}: \"{}\" Failed to fill RRCReconfiguration", request.ue_index, name()); + CORO_EARLY_RETURN(make_unexpected(fail_msg)); + } + + CORO_AWAIT_VALUE(rrc_reconfig_result, rrc_ue.handle_rrc_reconfiguration_request(rrc_reconfig_args)); + } + + logger.info("ue={}: \"{}\" finished successfully", request.ue_index, name()); + CORO_RETURN(resp_msg); +} + +void initial_context_setup_routine::handle_failure() +{ + fail_msg.cause = cause_protocol_t::unspecified; + // Add failed PDU Sessions + if (request.pdu_session_res_setup_list_cxt_req.has_value()) { + for (const auto& pdu_session_item : + request.pdu_session_res_setup_list_cxt_req.value().pdu_session_res_setup_items) { + cu_cp_pdu_session_res_setup_failed_item failed_item; + failed_item.pdu_session_id = pdu_session_item.pdu_session_id; + failed_item.unsuccessful_transfer.cause = ngap_cause_radio_network_t::unspecified; + + fail_msg.pdu_session_res_failed_to_setup_items.emplace(pdu_session_item.pdu_session_id, failed_item); + } + } + + logger.info("ue={}: \"{}\" failed", request.ue_index, name()); +} + +void initial_context_setup_routine::handle_nas_pdu(byte_buffer nas_pdu) +{ + logger.debug("ue={}: Forwarding NAS PDU to RRC", request.ue_index); + rrc_ue.handle_dl_nas_transport_message(std::move(nas_pdu)); +} \ No newline at end of file diff --git a/lib/cu_cp/routines/initial_context_setup_routine.h b/lib/cu_cp/routines/initial_context_setup_routine.h new file mode 100644 index 0000000000..23bb3ecb4f --- /dev/null +++ b/lib/cu_cp/routines/initial_context_setup_routine.h @@ -0,0 +1,73 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "../ue_manager/ue_manager_impl.h" +#include "srsran/ngap/ngap_init_context_setup.h" + +namespace srsran { +namespace srs_cu_cp { + +/// \brief Handles the setup of PDU session resources from the RRC viewpoint. +class initial_context_setup_routine +{ +public: + initial_context_setup_routine(const ngap_init_context_setup_request& request_, + rrc_ue_interface& rrc_ue_, + ue_security_manager& security_mng_, + f1ap_ue_context_manager& f1ap_ue_ctxt_mng_, + cu_cp_ngap_handler& pdu_session_setup_handler_, + srslog::basic_logger& logger_); + + void operator()( + coro_context>>& ctx); + + static const char* name() { return "Initial Context Setup Routine"; } + + void handle_failure(); + void handle_nas_pdu(byte_buffer nas_pdu); + +private: + ngap_init_context_setup_request request; + + rrc_ue_interface& rrc_ue; + ue_security_manager& security_mng; + f1ap_ue_context_manager& f1ap_ue_ctxt_mng; // to trigger UE context setup at F1AP + cu_cp_ngap_handler& pdu_session_setup_handler; // to setup PDU sessions + srslog::basic_logger& logger; + + // (sub-)routine requests + rrc_ue_security_mode_command_context rrc_smc_ctxt; + f1ap_ue_context_setup_request ue_context_setup_request; + rrc_reconfiguration_procedure_request rrc_reconfig_args; + + // (sub-)routine results + f1ap_ue_context_setup_response ue_context_setup_response; + cu_cp_pdu_session_resource_setup_response pdu_session_setup_response; + bool rrc_reconfig_result = false; // the final UE reconfiguration + ngap_init_context_setup_failure fail_msg; + ngap_init_context_setup_response resp_msg; +}; + +} // namespace srs_cu_cp +} // namespace srsran diff --git a/lib/cu_cp/routines/pdu_session_resource_release_routine.cpp b/lib/cu_cp/routines/pdu_session_resource_release_routine.cpp index e548d57d67..572b55427a 100644 --- a/lib/cu_cp/routines/pdu_session_resource_release_routine.cpp +++ b/lib/cu_cp/routines/pdu_session_resource_release_routine.cpp @@ -79,8 +79,18 @@ void pdu_session_resource_release_routine::operator()( next_config = up_resource_mng.calculate_update(release_cmd); } - // Inform CU-UP about the release of a bearer - { + // Inform the CU-UP about the release of the bearer context + if (next_config.pdu_sessions_to_remove_list.size() == up_resource_mng.get_nof_pdu_sessions()) { + bearer_context_release_command.ue_index = release_cmd.ue_index; + bearer_context_release_command.cause = e1ap_cause_radio_network_t::unspecified; + + /// NOTE: Only the Bearer Context at the CU-UP will be released. We don't want to release the UE. + + // Request BearerContextRelease + CORO_AWAIT(e1ap_bearer_ctxt_mng.handle_bearer_context_release_command(bearer_context_release_command)); + + } else { // Inform CU-UP about the release of a bearer + // prepare BearerContextModificationRequest and call e1 notifier bearer_context_modification_request.ue_index = release_cmd.ue_index; diff --git a/lib/cu_cp/routines/pdu_session_resource_setup_routine.cpp b/lib/cu_cp/routines/pdu_session_resource_setup_routine.cpp index 0d0b570672..7ffaf30c86 100644 --- a/lib/cu_cp/routines/pdu_session_resource_setup_routine.cpp +++ b/lib/cu_cp/routines/pdu_session_resource_setup_routine.cpp @@ -240,7 +240,7 @@ void pdu_session_resource_setup_routine::operator()( CORO_AWAIT_VALUE(rrc_reconfig_result, rrc_ue_notifier.on_rrc_reconfiguration_request(rrc_reconfig_args)); - // Handle UE Context Modification Response + // Handle RRC Reconfiguration Response if (!handle_procedure_response(response_msg, setup_msg, rrc_reconfig_result, logger)) { logger.warning("ue={}: \"{}\" RRC reconfiguration failed", setup_msg.ue_index, name()); CORO_EARLY_RETURN(handle_pdu_session_resource_setup_result(false)); diff --git a/lib/cu_up/CMakeLists.txt b/lib/cu_up/CMakeLists.txt index f3b0a307b1..9e0ce28aca 100644 --- a/lib/cu_up/CMakeLists.txt +++ b/lib/cu_up/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES cu_up_executor_pool.cpp cu_up_factory.cpp cu_up_impl.cpp + cu_up_manager_impl.cpp ue_manager.cpp pdu_session_manager_impl.cpp routines/initial_cu_up_setup_routine.cpp diff --git a/lib/cu_up/adapters/e1ap_adapters.h b/lib/cu_up/adapters/e1ap_adapters.h index 25a12cab26..d1a029abc2 100644 --- a/lib/cu_up/adapters/e1ap_adapters.h +++ b/lib/cu_up/adapters/e1ap_adapters.h @@ -22,18 +22,18 @@ #pragma once -#include "srsran/cu_up/cu_up.h" +#include "srsran/cu_up/cu_up_manager.h" #include "srsran/e1ap/cu_up/e1ap_cu_up.h" namespace srsran::srs_cu_up { -/// Adapter between E1AP and CU-UP -class e1ap_cu_up_adapter : public e1ap_cu_up_notifier +/// Adapter between E1AP and CU-UP manager +class e1ap_cu_up_manager_adapter : public e1ap_cu_up_manager_notifier { public: - e1ap_cu_up_adapter() : logger(srslog::fetch_basic_logger("CU-UP-E1")) {} + e1ap_cu_up_manager_adapter() : logger(srslog::fetch_basic_logger("CU-UP-E1")) {} - void connect_cu_up(cu_up_e1ap_interface& cu_up_handler_) { cu_up_handler = &cu_up_handler_; } + void connect_cu_up_manager(cu_up_manager_e1ap_interface& cu_up_handler_) { cu_up_handler = &cu_up_handler_; } void disconnect() { cu_up_handler = nullptr; } @@ -76,8 +76,8 @@ class e1ap_cu_up_adapter : public e1ap_cu_up_notifier } private: - cu_up_e1ap_interface* cu_up_handler = nullptr; - srslog::basic_logger& logger; + cu_up_manager_e1ap_interface* cu_up_handler = nullptr; + srslog::basic_logger& logger; }; } // namespace srsran::srs_cu_up diff --git a/lib/cu_up/adapters/gtpu_adapters.h b/lib/cu_up/adapters/gtpu_adapters.h index 6af015376f..a580503546 100644 --- a/lib/cu_up/adapters/gtpu_adapters.h +++ b/lib/cu_up/adapters/gtpu_adapters.h @@ -27,6 +27,7 @@ #include "srsran/gtpu/gtpu_tunnel_common_tx.h" #include "srsran/gtpu/gtpu_tunnel_ngu_rx.h" #include "srsran/sdap/sdap.h" +#include "srsran/srslog/srslog.h" namespace srsran::srs_cu_up { diff --git a/lib/cu_up/cu_up_impl.cpp b/lib/cu_up/cu_up_impl.cpp index 1492c3b1a0..195b4e2754 100644 --- a/lib/cu_up/cu_up_impl.cpp +++ b/lib/cu_up/cu_up_impl.cpp @@ -21,12 +21,12 @@ */ #include "cu_up_impl.h" +#include "cu_up_manager_impl.h" #include "routines/initial_cu_up_setup_routine.h" #include "srsran/e1ap/cu_up/e1ap_cu_up_factory.h" #include "srsran/gtpu/gtpu_demux_factory.h" #include "srsran/gtpu/gtpu_echo_factory.h" #include "srsran/gtpu/gtpu_teid_pool_factory.h" -#include "srsran/support/async/execute_on.h" #include using namespace srsran; @@ -91,24 +91,16 @@ cu_up::cu_up(const cu_up_configuration& config_) : cfg(config_), main_ctrl_loop( f1u_teid_allocator = create_gtpu_allocator(f1u_alloc_msg); /// > Create e1ap - e1ap = create_e1ap(*cfg.e1ap.e1_conn_client, e1ap_cu_up_ev_notifier, *cfg.timers, *cfg.ctrl_executor); - e1ap_cu_up_ev_notifier.connect_cu_up(*this); + e1ap = create_e1ap(*cfg.e1ap.e1_conn_client, e1ap_cu_up_mng_adapter, *cfg.timers, *cfg.ctrl_executor); cfg.e1ap.e1ap_conn_mng = e1ap.get(); - /// > Create UE manager - ue_mng = std::make_unique(cfg.net_cfg, - cfg.n3_cfg, - *e1ap, - *cfg.timers, - *cfg.f1u_gateway, - gtpu_gw_adapter, - *ngu_demux, - *n3_teid_allocator, - *f1u_teid_allocator, - *cfg.ue_exec_pool, - *cfg.gtpu_pcap, - logger); + /// > Create CU-UP manager + cu_up_mng = std::make_unique( + cfg, *e1ap, gtpu_gw_adapter, *ngu_demux, *n3_teid_allocator, *f1u_teid_allocator); + + /// > Connect E1AP to CU-UP manager + e1ap_cu_up_mng_adapter.connect_cu_up_manager(*cu_up_mng); // Start statistics report timer if (cfg.statistics_report_period.count() > 0) { @@ -119,6 +111,11 @@ cu_up::cu_up(const cu_up_configuration& config_) : cfg(config_), main_ctrl_loop( } } +cu_up::~cu_up() +{ + stop(); +} + void cu_up::start() { logger.info("CU-UP starting..."); @@ -200,287 +197,17 @@ void cu_up::stop() logger.info("CU-UP stopped successfully"); } -cu_up::~cu_up() -{ - stop(); -} - void cu_up::disconnect() { gw_data_gtpu_demux_adapter.disconnect(); gtpu_gw_adapter.disconnect(); - e1ap_cu_up_ev_notifier.disconnect(); -} - -void cu_up::schedule_ue_async_task(ue_index_t ue_index, async_task task) -{ - ue_mng->schedule_ue_async_task(ue_index, std::move(task)); -} - -void process_successful_pdu_resource_setup_mod_outcome( - slotted_id_vector& - pdu_session_resource_setup_list, - const pdu_session_setup_result& result) -{ - if (result.success) { - e1ap_pdu_session_resource_setup_modification_item res_setup_item; - res_setup_item.pdu_session_id = result.pdu_session_id; - res_setup_item.ng_dl_up_tnl_info = result.gtp_tunnel; - res_setup_item.security_result = result.security_result; - for (const auto& drb_setup_item : result.drb_setup_results) { - if (drb_setup_item.success) { - e1ap_drb_setup_item_ng_ran res_drb_setup_item; - res_drb_setup_item.drb_id = drb_setup_item.drb_id; - - e1ap_up_params_item up_param_item; - up_param_item.up_tnl_info = drb_setup_item.gtp_tunnel; - res_drb_setup_item.ul_up_transport_params.push_back(up_param_item); - - for (const auto& flow_item : drb_setup_item.qos_flow_results) { - if (flow_item.success) { - e1ap_qos_flow_item res_flow_setup_item; - res_flow_setup_item.qos_flow_id = flow_item.qos_flow_id; - res_drb_setup_item.flow_setup_list.emplace(flow_item.qos_flow_id, res_flow_setup_item); - } else { - e1ap_qos_flow_failed_item res_flow_failed_item; - res_flow_failed_item.qos_flow_id = flow_item.qos_flow_id; - res_flow_failed_item.cause = flow_item.cause; - res_drb_setup_item.flow_failed_list.emplace(flow_item.qos_flow_id, res_flow_failed_item); - } - } - res_setup_item.drb_setup_list_ng_ran.emplace(drb_setup_item.drb_id, res_drb_setup_item); - } else { - e1ap_drb_failed_item_ng_ran asn1_drb_failed_item; - asn1_drb_failed_item.drb_id = drb_setup_item.drb_id; - asn1_drb_failed_item.cause = drb_setup_item.cause; - - res_setup_item.drb_failed_list_ng_ran.emplace(drb_setup_item.drb_id, asn1_drb_failed_item); - } - } - pdu_session_resource_setup_list.emplace(result.pdu_session_id, res_setup_item); - } -} - -void process_successful_pdu_resource_modification_outcome( - slotted_id_vector& pdu_session_resource_modified_list, - slotted_id_vector& - pdu_session_resource_failed_to_modify_list, - const pdu_session_modification_result& result, - const srslog::basic_logger& logger) -{ - if (result.success) { - e1ap_pdu_session_resource_modified_item modified_item; - modified_item.pdu_session_id = result.pdu_session_id; - - for (const auto& drb_setup_item : result.drb_setup_results) { - logger.debug("Adding DRB setup result item. {}, success={}", drb_setup_item.drb_id, drb_setup_item.success); - if (drb_setup_item.success) { - e1ap_drb_setup_item_ng_ran res_drb_setup_item; - res_drb_setup_item.drb_id = drb_setup_item.drb_id; - - e1ap_up_params_item up_param_item; - up_param_item.up_tnl_info = drb_setup_item.gtp_tunnel; - res_drb_setup_item.ul_up_transport_params.push_back(up_param_item); - - for (const auto& flow_item : drb_setup_item.qos_flow_results) { - if (flow_item.success) { - e1ap_qos_flow_item res_flow_setup_item; - res_flow_setup_item.qos_flow_id = flow_item.qos_flow_id; - res_drb_setup_item.flow_setup_list.emplace(flow_item.qos_flow_id, res_flow_setup_item); - } else { - e1ap_qos_flow_failed_item res_flow_failed_item; - res_flow_failed_item.qos_flow_id = flow_item.qos_flow_id; - res_flow_failed_item.cause = flow_item.cause; - res_drb_setup_item.flow_failed_list.emplace(flow_item.qos_flow_id, res_flow_failed_item); - } - } - modified_item.drb_setup_list_ng_ran.emplace(drb_setup_item.drb_id, res_drb_setup_item); - } else { - e1ap_drb_failed_item_ng_ran asn1_drb_failed_item; - asn1_drb_failed_item.drb_id = drb_setup_item.drb_id; - asn1_drb_failed_item.cause = drb_setup_item.cause; - - modified_item.drb_failed_list_ng_ran.emplace(drb_setup_item.drb_id, asn1_drb_failed_item); - } - } - for (const auto& drb_modified_item : result.drb_modification_results) { - logger.debug( - "Adding DRB modified result item. {}, success={}", drb_modified_item.drb_id, drb_modified_item.success); - e1ap_drb_modified_item_ng_ran e1ap_mod_item; - e1ap_mod_item.drb_id = drb_modified_item.drb_id; - - e1ap_up_params_item up_param_item; - up_param_item.up_tnl_info = drb_modified_item.gtp_tunnel; - e1ap_mod_item.ul_up_transport_params.push_back(up_param_item); - modified_item.drb_modified_list_ng_ran.emplace(e1ap_mod_item.drb_id, e1ap_mod_item); - } - - pdu_session_resource_modified_list.emplace(modified_item.pdu_session_id, modified_item); - } else { - e1ap_pdu_session_resource_failed_item failed_item; - failed_item.pdu_session_id = result.pdu_session_id; - failed_item.cause = e1ap_cause_radio_network_t::unspecified; - pdu_session_resource_failed_to_modify_list.emplace(failed_item.pdu_session_id, failed_item); - } -} - -e1ap_bearer_context_setup_response -cu_up::handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) -{ - e1ap_bearer_context_setup_response response = {}; - response.ue_index = INVALID_UE_INDEX; - response.success = false; - - // 1. Create new UE context - ue_context_cfg ue_cfg = {}; - fill_sec_as_config(ue_cfg.security_info, msg.security_info); - ue_cfg.activity_level = msg.activity_notif_level; - ue_cfg.ue_inactivity_timeout = msg.ue_inactivity_timer; - ue_cfg.qos = cfg.qos; - ue_context* ue_ctxt = ue_mng->add_ue(ue_cfg); - if (ue_ctxt == nullptr) { - logger.error("Could not create UE context"); - return response; - } - ue_ctxt->get_logger().log_info("UE created"); - - // 2. Handle bearer context setup request - for (const auto& pdu_session : msg.pdu_session_res_to_setup_list) { - pdu_session_setup_result result = ue_ctxt->setup_pdu_session(pdu_session); - if (result.success) { - process_successful_pdu_resource_setup_mod_outcome(response.pdu_session_resource_setup_list, result); - } else { - e1ap_pdu_session_resource_failed_item res_failed_item; - - res_failed_item.pdu_session_id = result.pdu_session_id; - res_failed_item.cause = result.cause; - - response.pdu_session_resource_failed_list.emplace(result.pdu_session_id, res_failed_item); - } - } - - // 3. Create response - response.ue_index = ue_ctxt->get_index(); - - response.success = true; - - return response; -} - -async_task -cu_up::handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& msg) -{ - ue_context* ue_ctxt = ue_mng->find_ue(msg.ue_index); - if (ue_ctxt == nullptr) { - logger.error("Could not find UE context"); - return {}; - } - return execute_and_continue_on_blocking( - ue_ctxt->ue_exec_mapper->ctrl_executor(), *cfg.ctrl_executor, [this, ue_ctxt, msg]() { - return handle_bearer_context_modification_request_impl(*ue_ctxt, msg); - }); -} - -e1ap_bearer_context_modification_response -cu_up::handle_bearer_context_modification_request_impl(ue_context& ue_ctxt, - const e1ap_bearer_context_modification_request& msg) -{ - e1ap_bearer_context_modification_response response = {}; - ue_ctxt.get_logger().log_debug("Handling BearerContextModificationRequest"); - - response.ue_index = ue_ctxt.get_index(); - response.success = true; - - bool new_ul_tnl_info_required = msg.new_ul_tnl_info_required == std::string("required"); - - if (msg.security_info.has_value()) { - security::sec_as_config security_info; - fill_sec_as_config(security_info, msg.security_info.value()); - ue_ctxt.set_security_config(security_info); - } - - if (msg.ng_ran_bearer_context_mod_request.has_value()) { - // Traverse list of PDU sessions to be setup/modified - for (const auto& pdu_session_item : - msg.ng_ran_bearer_context_mod_request.value().pdu_session_res_to_setup_mod_list) { - ue_ctxt.get_logger().log_debug("Setup/Modification of {}", pdu_session_item.pdu_session_id); - pdu_session_setup_result session_result = ue_ctxt.setup_pdu_session(pdu_session_item); - process_successful_pdu_resource_setup_mod_outcome(response.pdu_session_resource_setup_list, session_result); - response.success &= session_result.success; // Update final result. - } - - // Traverse list of PDU sessions to be modified. - for (const auto& pdu_session_item : msg.ng_ran_bearer_context_mod_request.value().pdu_session_res_to_modify_list) { - ue_ctxt.get_logger().log_debug("Modifying {}", pdu_session_item.pdu_session_id); - pdu_session_modification_result session_result = - ue_ctxt.modify_pdu_session(pdu_session_item, new_ul_tnl_info_required); - process_successful_pdu_resource_modification_outcome(response.pdu_session_resource_modified_list, - response.pdu_session_resource_failed_to_modify_list, - session_result, - logger); - ue_ctxt.get_logger().log_debug("Modification {}", session_result.success ? "successful" : "failed"); - - response.success &= session_result.success; // Update final result. - } - - // Traverse list of PDU sessions to be removed. - for (const auto& pdu_session_item : msg.ng_ran_bearer_context_mod_request.value().pdu_session_res_to_rem_list) { - ue_ctxt.get_logger().log_info("Removing {}", pdu_session_item); - ue_ctxt.remove_pdu_session(pdu_session_item); - // There is no IE to confirm successful removal. - } - } else { - ue_ctxt.get_logger().log_warning("Ignoring empty Bearer Context Modification Request"); - } - - // 3. Create response - response.success = true; - return response; -} - -void cu_up::handle_bearer_context_release_command(const e1ap_bearer_context_release_command& msg) -{ - ue_context* ue_ctxt = ue_mng->find_ue(msg.ue_index); - if (ue_ctxt == nullptr) { - logger.error("ue={}: Discarding E1 Bearer Context Release Command. UE context not found", msg.ue_index); - return; - } - - ue_ctxt->get_logger().log_debug("Received E1 Bearer Context Release Command"); - - ue_mng->remove_ue(msg.ue_index); -} - -void fill_sec_as_config(security::sec_as_config& sec_as_config, const e1ap_security_info& sec_info) -{ - sec_as_config.domain = security::sec_domain::up; - if (!sec_info.up_security_key.integrity_protection_key.empty()) { - sec_as_config.k_int = security::sec_key{}; - std::copy(sec_info.up_security_key.integrity_protection_key.begin(), - sec_info.up_security_key.integrity_protection_key.end(), - sec_as_config.k_int.value().begin()); - } - std::copy(sec_info.up_security_key.encryption_key.begin(), - sec_info.up_security_key.encryption_key.end(), - sec_as_config.k_enc.begin()); - sec_as_config.integ_algo = sec_info.security_algorithm.integrity_protection_algorithm; - sec_as_config.cipher_algo = sec_info.security_algorithm.ciphering_algo; -} - -void cu_up::on_e1ap_connection_establish() -{ - e1ap_connected = true; -} - -void cu_up::on_e1ap_connection_drop() -{ - e1ap_connected = false; + e1ap_cu_up_mng_adapter.disconnect(); } void cu_up::on_statistics_report_timer_expired() { // Log statistics - logger.debug("num_e1ap_ues={} num_cu_up_ues={}", e1ap->get_nof_ues(), ue_mng->get_nof_ues()); + logger.debug("num_e1ap_ues={} num_cu_up_ues={}", e1ap->get_nof_ues(), cu_up_mng->get_nof_ues()); // Restart timer statistics_report_timer.set(cfg.statistics_report_period, diff --git a/lib/cu_up/cu_up_impl.h b/lib/cu_up/cu_up_impl.h index 5a359435b9..b0b390624f 100644 --- a/lib/cu_up/cu_up_impl.h +++ b/lib/cu_up/cu_up_impl.h @@ -28,6 +28,7 @@ #include "ue_manager.h" #include "srsran/cu_up/cu_up.h" #include "srsran/cu_up/cu_up_configuration.h" +#include "srsran/cu_up/cu_up_manager.h" #include "srsran/e1ap/cu_up/e1ap_cu_up.h" #include "srsran/gtpu/gtpu_echo.h" #include "srsran/gtpu/gtpu_teid_pool.h" @@ -46,25 +47,9 @@ class cu_up final : public cu_up_interface void start() override; void stop() override; + /// helper functions for testing std::optional get_n3_bind_port() override { return ngu_session->get_bind_port(); } - - // cu_up_e1ap_interface - void schedule_ue_async_task(ue_index_t ue_index, async_task task) override; - - e1ap_message_handler& get_e1ap_message_handler() override { return *e1ap; } - - e1ap_bearer_context_setup_response - handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) override; - - async_task - handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& msg) override; - - void handle_bearer_context_release_command(const e1ap_bearer_context_release_command& msg) override; - - // cu_up_e1ap_connection_notifier - void on_e1ap_connection_establish() override; - void on_e1ap_connection_drop() override; - bool e1ap_is_connected() override { return e1ap_connected; } + cu_up_manager* get_cu_up_manager() { return cu_up_mng.get(); } private: void disconnect(); @@ -91,12 +76,12 @@ class cu_up final : public cu_up_interface std::unique_ptr ngu_echo; std::unique_ptr n3_teid_allocator; std::unique_ptr f1u_teid_allocator; - std::unique_ptr ue_mng; + std::unique_ptr cu_up_mng; // Adapters network_gateway_data_gtpu_demux_adapter gw_data_gtpu_demux_adapter; gtpu_network_gateway_adapter gtpu_gw_adapter; - e1ap_cu_up_adapter e1ap_cu_up_ev_notifier; + e1ap_cu_up_manager_adapter e1ap_cu_up_mng_adapter; std::mutex mutex; bool running{false}; diff --git a/lib/cu_up/cu_up_manager_impl.cpp b/lib/cu_up/cu_up_manager_impl.cpp new file mode 100644 index 0000000000..ead4c93e86 --- /dev/null +++ b/lib/cu_up/cu_up_manager_impl.cpp @@ -0,0 +1,317 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "cu_up_manager_impl.h" +#include "srsran/support/async/execute_on.h" + +using namespace srsran; +using namespace srs_cu_up; + +/// Helper functions +void fill_sec_as_config(security::sec_as_config& sec_as_config, const e1ap_security_info& sec_info); +void process_successful_pdu_resource_modification_outcome( + slotted_id_vector& pdu_session_resource_modified_list, + slotted_id_vector& + pdu_session_resource_failed_to_modify_list, + const pdu_session_modification_result& result, + const srslog::basic_logger& logger); +void process_successful_pdu_resource_setup_mod_outcome( + slotted_id_vector& + pdu_session_resource_setup_list, + const pdu_session_setup_result& result); + +cu_up_manager_impl::cu_up_manager_impl(const cu_up_configuration& config_, + e1ap_interface& e1ap, + gtpu_network_gateway_adapter& gtpu_gw_adapter, + gtpu_demux& ngu_demux, + gtpu_teid_pool& n3_teid_allocator, + gtpu_teid_pool& f1u_teid_allocator) : + cfg(config_) +{ + /// > Create UE manager + ue_mng = std::make_unique(cfg.net_cfg, + cfg.n3_cfg, + e1ap, + *cfg.timers, + *cfg.f1u_gateway, + gtpu_gw_adapter, + ngu_demux, + n3_teid_allocator, + f1u_teid_allocator, + *cfg.ue_exec_pool, + *cfg.gtpu_pcap, + logger); +} + +void cu_up_manager_impl::schedule_ue_async_task(ue_index_t ue_index, async_task task) +{ + ue_mng->schedule_ue_async_task(ue_index, std::move(task)); +} + +e1ap_bearer_context_setup_response +cu_up_manager_impl::handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) +{ + e1ap_bearer_context_setup_response response = {}; + response.ue_index = INVALID_UE_INDEX; + response.success = false; + + // 1. Create new UE context + ue_context_cfg ue_cfg = {}; + fill_sec_as_config(ue_cfg.security_info, msg.security_info); + ue_cfg.activity_level = msg.activity_notif_level; + ue_cfg.ue_inactivity_timeout = msg.ue_inactivity_timer; + ue_cfg.qos = cfg.qos; + ue_context* ue_ctxt = ue_mng->add_ue(ue_cfg); + if (ue_ctxt == nullptr) { + logger.error("Could not create UE context"); + return response; + } + ue_ctxt->get_logger().log_info("UE created"); + + // 2. Handle bearer context setup request + for (const auto& pdu_session : msg.pdu_session_res_to_setup_list) { + pdu_session_setup_result result = ue_ctxt->setup_pdu_session(pdu_session); + if (result.success) { + process_successful_pdu_resource_setup_mod_outcome(response.pdu_session_resource_setup_list, result); + } else { + e1ap_pdu_session_resource_failed_item res_failed_item; + + res_failed_item.pdu_session_id = result.pdu_session_id; + res_failed_item.cause = result.cause; + + response.pdu_session_resource_failed_list.emplace(result.pdu_session_id, res_failed_item); + } + } + + // 3. Create response + response.ue_index = ue_ctxt->get_index(); + response.success = true; + return response; +} + +async_task +cu_up_manager_impl::handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& msg) +{ + ue_context* ue_ctxt = ue_mng->find_ue(msg.ue_index); + if (ue_ctxt == nullptr) { + logger.error("Could not find UE context"); + return {}; + } + return execute_and_continue_on_blocking( + ue_ctxt->ue_exec_mapper->ctrl_executor(), *cfg.ctrl_executor, [this, ue_ctxt, msg]() { + return handle_bearer_context_modification_request_impl(*ue_ctxt, msg); + }); +} + +e1ap_bearer_context_modification_response +cu_up_manager_impl::handle_bearer_context_modification_request_impl(ue_context& ue_ctxt, + const e1ap_bearer_context_modification_request& msg) +{ + ue_ctxt.get_logger().log_debug("Handling BearerContextModificationRequest"); + + e1ap_bearer_context_modification_response response = {}; + response.ue_index = ue_ctxt.get_index(); + response.success = true; + + bool new_ul_tnl_info_required = msg.new_ul_tnl_info_required == std::string("required"); + + if (msg.security_info.has_value()) { + security::sec_as_config security_info; + fill_sec_as_config(security_info, msg.security_info.value()); + ue_ctxt.set_security_config(security_info); + } + + if (msg.ng_ran_bearer_context_mod_request.has_value()) { + // Traverse list of PDU sessions to be setup/modified + for (const auto& pdu_session_item : + msg.ng_ran_bearer_context_mod_request.value().pdu_session_res_to_setup_mod_list) { + ue_ctxt.get_logger().log_debug("Setup/Modification of {}", pdu_session_item.pdu_session_id); + pdu_session_setup_result session_result = ue_ctxt.setup_pdu_session(pdu_session_item); + process_successful_pdu_resource_setup_mod_outcome(response.pdu_session_resource_setup_list, session_result); + response.success &= session_result.success; // Update final result. + } + + // Traverse list of PDU sessions to be modified. + for (const auto& pdu_session_item : msg.ng_ran_bearer_context_mod_request.value().pdu_session_res_to_modify_list) { + ue_ctxt.get_logger().log_debug("Modifying {}", pdu_session_item.pdu_session_id); + pdu_session_modification_result session_result = + ue_ctxt.modify_pdu_session(pdu_session_item, new_ul_tnl_info_required); + process_successful_pdu_resource_modification_outcome(response.pdu_session_resource_modified_list, + response.pdu_session_resource_failed_to_modify_list, + session_result, + logger); + ue_ctxt.get_logger().log_debug("Modification {}", session_result.success ? "successful" : "failed"); + + response.success &= session_result.success; // Update final result. + } + + // Traverse list of PDU sessions to be removed. + for (const auto& pdu_session_item : msg.ng_ran_bearer_context_mod_request.value().pdu_session_res_to_rem_list) { + ue_ctxt.get_logger().log_info("Removing {}", pdu_session_item); + ue_ctxt.remove_pdu_session(pdu_session_item); + // There is no IE to confirm successful removal. + } + } else { + ue_ctxt.get_logger().log_warning("Ignoring empty Bearer Context Modification Request"); + } + + // 3. Create response + response.success = true; + return response; +} + +void cu_up_manager_impl::handle_bearer_context_release_command(const e1ap_bearer_context_release_command& msg) +{ + ue_context* ue_ctxt = ue_mng->find_ue(msg.ue_index); + if (ue_ctxt == nullptr) { + logger.error("ue={}: Discarding E1 Bearer Context Release Command. UE context not found", msg.ue_index); + return; + } + + ue_ctxt->get_logger().log_debug("Received E1 Bearer Context Release Command"); + + ue_mng->remove_ue(msg.ue_index); +} + +/// Helper functions +void process_successful_pdu_resource_modification_outcome( + slotted_id_vector& pdu_session_resource_modified_list, + slotted_id_vector& + pdu_session_resource_failed_to_modify_list, + const pdu_session_modification_result& result, + const srslog::basic_logger& logger) +{ + if (result.success) { + e1ap_pdu_session_resource_modified_item modified_item; + modified_item.pdu_session_id = result.pdu_session_id; + + for (const auto& drb_setup_item : result.drb_setup_results) { + logger.debug("Adding DRB setup result item. {}, success={}", drb_setup_item.drb_id, drb_setup_item.success); + if (drb_setup_item.success) { + e1ap_drb_setup_item_ng_ran res_drb_setup_item; + res_drb_setup_item.drb_id = drb_setup_item.drb_id; + + e1ap_up_params_item up_param_item; + up_param_item.up_tnl_info = drb_setup_item.gtp_tunnel; + res_drb_setup_item.ul_up_transport_params.push_back(up_param_item); + + for (const auto& flow_item : drb_setup_item.qos_flow_results) { + if (flow_item.success) { + e1ap_qos_flow_item res_flow_setup_item; + res_flow_setup_item.qos_flow_id = flow_item.qos_flow_id; + res_drb_setup_item.flow_setup_list.emplace(flow_item.qos_flow_id, res_flow_setup_item); + } else { + e1ap_qos_flow_failed_item res_flow_failed_item; + res_flow_failed_item.qos_flow_id = flow_item.qos_flow_id; + res_flow_failed_item.cause = flow_item.cause; + res_drb_setup_item.flow_failed_list.emplace(flow_item.qos_flow_id, res_flow_failed_item); + } + } + modified_item.drb_setup_list_ng_ran.emplace(drb_setup_item.drb_id, res_drb_setup_item); + } else { + e1ap_drb_failed_item_ng_ran asn1_drb_failed_item; + asn1_drb_failed_item.drb_id = drb_setup_item.drb_id; + asn1_drb_failed_item.cause = drb_setup_item.cause; + + modified_item.drb_failed_list_ng_ran.emplace(drb_setup_item.drb_id, asn1_drb_failed_item); + } + } + for (const auto& drb_modified_item : result.drb_modification_results) { + logger.debug( + "Adding DRB modified result item. {}, success={}", drb_modified_item.drb_id, drb_modified_item.success); + e1ap_drb_modified_item_ng_ran e1ap_mod_item; + e1ap_mod_item.drb_id = drb_modified_item.drb_id; + + e1ap_up_params_item up_param_item; + up_param_item.up_tnl_info = drb_modified_item.gtp_tunnel; + e1ap_mod_item.ul_up_transport_params.push_back(up_param_item); + modified_item.drb_modified_list_ng_ran.emplace(e1ap_mod_item.drb_id, e1ap_mod_item); + } + + pdu_session_resource_modified_list.emplace(modified_item.pdu_session_id, modified_item); + } else { + e1ap_pdu_session_resource_failed_item failed_item; + failed_item.pdu_session_id = result.pdu_session_id; + failed_item.cause = e1ap_cause_radio_network_t::unspecified; + pdu_session_resource_failed_to_modify_list.emplace(failed_item.pdu_session_id, failed_item); + } +} + +void process_successful_pdu_resource_setup_mod_outcome( + slotted_id_vector& + pdu_session_resource_setup_list, + const pdu_session_setup_result& result) +{ + if (result.success) { + e1ap_pdu_session_resource_setup_modification_item res_setup_item; + res_setup_item.pdu_session_id = result.pdu_session_id; + res_setup_item.ng_dl_up_tnl_info = result.gtp_tunnel; + res_setup_item.security_result = result.security_result; + for (const auto& drb_setup_item : result.drb_setup_results) { + if (drb_setup_item.success) { + e1ap_drb_setup_item_ng_ran res_drb_setup_item; + res_drb_setup_item.drb_id = drb_setup_item.drb_id; + + e1ap_up_params_item up_param_item; + up_param_item.up_tnl_info = drb_setup_item.gtp_tunnel; + res_drb_setup_item.ul_up_transport_params.push_back(up_param_item); + + for (const auto& flow_item : drb_setup_item.qos_flow_results) { + if (flow_item.success) { + e1ap_qos_flow_item res_flow_setup_item; + res_flow_setup_item.qos_flow_id = flow_item.qos_flow_id; + res_drb_setup_item.flow_setup_list.emplace(flow_item.qos_flow_id, res_flow_setup_item); + } else { + e1ap_qos_flow_failed_item res_flow_failed_item; + res_flow_failed_item.qos_flow_id = flow_item.qos_flow_id; + res_flow_failed_item.cause = flow_item.cause; + res_drb_setup_item.flow_failed_list.emplace(flow_item.qos_flow_id, res_flow_failed_item); + } + } + res_setup_item.drb_setup_list_ng_ran.emplace(drb_setup_item.drb_id, res_drb_setup_item); + } else { + e1ap_drb_failed_item_ng_ran asn1_drb_failed_item; + asn1_drb_failed_item.drb_id = drb_setup_item.drb_id; + asn1_drb_failed_item.cause = drb_setup_item.cause; + + res_setup_item.drb_failed_list_ng_ran.emplace(drb_setup_item.drb_id, asn1_drb_failed_item); + } + } + pdu_session_resource_setup_list.emplace(result.pdu_session_id, res_setup_item); + } +} + +void fill_sec_as_config(security::sec_as_config& sec_as_config, const e1ap_security_info& sec_info) +{ + sec_as_config.domain = security::sec_domain::up; + if (!sec_info.up_security_key.integrity_protection_key.empty()) { + sec_as_config.k_int = security::sec_key{}; + std::copy(sec_info.up_security_key.integrity_protection_key.begin(), + sec_info.up_security_key.integrity_protection_key.end(), + sec_as_config.k_int.value().begin()); + } + std::copy(sec_info.up_security_key.encryption_key.begin(), + sec_info.up_security_key.encryption_key.end(), + sec_as_config.k_enc.begin()); + sec_as_config.integ_algo = sec_info.security_algorithm.integrity_protection_algorithm; + sec_as_config.cipher_algo = sec_info.security_algorithm.ciphering_algo; +} diff --git a/lib/cu_up/cu_up_manager_impl.h b/lib/cu_up/cu_up_manager_impl.h new file mode 100644 index 0000000000..015cc2d06c --- /dev/null +++ b/lib/cu_up/cu_up_manager_impl.h @@ -0,0 +1,82 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "adapters/gtpu_adapters.h" +#include "ue_manager.h" +#include "srsran/cu_up/cu_up_configuration.h" +#include "srsran/cu_up/cu_up_manager.h" +#include "srsran/e1ap/cu_up/e1ap_cu_up.h" +#include "srsran/gtpu/gtpu_teid_pool.h" +#include + +namespace srsran::srs_cu_up { + +class cu_up_manager_impl final : public cu_up_manager +{ +public: + explicit cu_up_manager_impl(const cu_up_configuration& cfg_, + e1ap_interface& e1ap, + gtpu_network_gateway_adapter& gtpu_gw_adapter, + gtpu_demux& ngu_demux, + gtpu_teid_pool& n3_teid_allocator, + gtpu_teid_pool& f1u_teid_allocator); + ~cu_up_manager_impl() override = default; + + e1ap_bearer_context_setup_response + handle_bearer_context_setup_request(const e1ap_bearer_context_setup_request& msg) override; + + async_task + handle_bearer_context_modification_request(const e1ap_bearer_context_modification_request& msg) override; + + void handle_bearer_context_release_command(const e1ap_bearer_context_release_command& msg) override; + + void schedule_ue_async_task(srs_cu_up::ue_index_t ue_index, async_task task) override; + + // cu_up_e1ap_connection_notifier + void on_e1ap_connection_establish() override { e1ap_connected = true; } + void on_e1ap_connection_drop() override { e1ap_connected = false; } + bool e1ap_is_connected() override { return e1ap_connected; } + + size_t get_nof_ues() override { return ue_mng->get_nof_ues(); } + +private: + void on_statistics_report_timer_expired(); + + e1ap_bearer_context_modification_response + handle_bearer_context_modification_request_impl(ue_context& ue_ctxt, + const e1ap_bearer_context_modification_request& msg); + + cu_up_configuration cfg; + + // logger + srslog::basic_logger& logger = srslog::fetch_basic_logger("CU-UP", false); + + // Components + std::atomic e1ap_connected = {false}; + std::unique_ptr ue_mng; + + unique_timer statistics_report_timer; +}; + +} // namespace srsran::srs_cu_up diff --git a/lib/du_manager/converters/asn1_rrc_config_helpers.cpp b/lib/du_manager/converters/asn1_rrc_config_helpers.cpp index 4af058e37d..04b836d0b8 100644 --- a/lib/du_manager/converters/asn1_rrc_config_helpers.cpp +++ b/lib/du_manager/converters/asn1_rrc_config_helpers.cpp @@ -1301,7 +1301,7 @@ static bool calculate_bwp_dl_dedicated_diff(asn1::rrc_nr::bwp_dl_ded_s& out, asn1::rrc_nr::pucch_res_set_s srsran::srs_du::make_asn1_rrc_pucch_resource_set(const pucch_resource_set& cfg) { pucch_res_set_s pucch_res_set; - pucch_res_set.pucch_res_set_id = cfg.pucch_res_set_id; + pucch_res_set.pucch_res_set_id = static_cast(cfg.pucch_res_set_id); for (const auto& it : cfg.pucch_res_id_list) { pucch_res_set.res_list.push_back(it.ue_res_id); } @@ -1529,7 +1529,7 @@ calculate_pucch_config_diff(asn1::rrc_nr::pucch_cfg_s& out, const pucch_config& src.pucch_res_set, dest.pucch_res_set, [](const pucch_resource_set& res_set) { return make_asn1_rrc_pucch_resource_set(res_set); }, - [](const pucch_resource_set& res_set) { return res_set.pucch_res_set_id; }); + [](const pucch_resource_set& res_set) { return static_cast(res_set.pucch_res_set_id); }); // PUCCH Resource. calculate_addmodremlist_diff( diff --git a/lib/du_manager/ran_resource_management/du_pucch_resource_manager.cpp b/lib/du_manager/ran_resource_management/du_pucch_resource_manager.cpp index 5869ed17d9..00bd917122 100644 --- a/lib/du_manager/ran_resource_management/du_pucch_resource_manager.cpp +++ b/lib/du_manager/ran_resource_management/du_pucch_resource_manager.cpp @@ -300,6 +300,18 @@ bool du_pucch_resource_manager::alloc_resources(cell_group_config& cell_grp_cfg) .report_slot_offset = csi_res_offset.value().second; } + // Update the PUCCH max payload. + // As per TS 38.231, Section 9.2.1, with PUCCH Format 1, we can have up to 2 HARQ-ACK bits (SR doesn't count as part + // of the payload). + constexpr static unsigned pucch_f1_max_harq_payload = 2U; + cell_grp_cfg.cells[0].serv_cell_cfg.ul_config->init_ul_bwp.pucch_cfg.value().format_max_payload[pucch_format_to_uint( + pucch_format::FORMAT_1)] = pucch_f1_max_harq_payload; + cell_grp_cfg.cells[0].serv_cell_cfg.ul_config->init_ul_bwp.pucch_cfg.value().format_max_payload[pucch_format_to_uint( + pucch_format::FORMAT_2)] = + get_pucch_format2_max_payload(user_defined_pucch_cfg.f2_params.max_nof_rbs, + user_defined_pucch_cfg.f2_params.nof_symbols.to_uint(), + to_max_code_rate_float(default_pucch_cfg.format_2_common_param.value().max_c_rate)); + ++cells[0].ue_idx; return true; } diff --git a/lib/du_manager/ran_resource_management/pucch_resource_generator.cpp b/lib/du_manager/ran_resource_management/pucch_resource_generator.cpp index 27696fd3d9..ba85cc5d00 100644 --- a/lib/du_manager/ran_resource_management/pucch_resource_generator.cpp +++ b/lib/du_manager/ran_resource_management/pucch_resource_generator.cpp @@ -585,9 +585,6 @@ bool srsran::srs_du::ue_pucch_config_builder(serving_cell_config& return false; } - const unsigned f1_pucch_res_set_id = 0; - const unsigned f2_pucch_res_set_id = 1; - // PUCCH resource ID corresponding to \c pucch-ResourceId, as part of \c PUCCH-Resource, in \c PUCCH-Config, // TS 38.331. By default, we index the PUCCH resource ID for ASN1 message from 0 to pucch_res_list.size() - 1. unsigned ue_pucch_res_id = 0; @@ -595,12 +592,14 @@ bool srsran::srs_du::ue_pucch_config_builder(serving_cell_config& pucch_config& pucch_cfg = serv_cell_cfg.ul_config.value().init_ul_bwp.pucch_cfg.value(); // Clears current PUCCH resource list and PUCCH resource list set 0 and 1. pucch_cfg.pucch_res_list.clear(); - pucch_cfg.pucch_res_set[f1_pucch_res_set_id].pucch_res_id_list.clear(); - pucch_cfg.pucch_res_set[f2_pucch_res_set_id].pucch_res_id_list.clear(); + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(pucch_res_set_idx::set_0)].pucch_res_id_list.clear(); + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(pucch_res_set_idx::set_1)].pucch_res_id_list.clear(); // Ensure the PUCCH resource sets ID are 0 and 1. - pucch_cfg.pucch_res_set[f1_pucch_res_set_id].pucch_res_set_id = f1_pucch_res_set_id; - pucch_cfg.pucch_res_set[f2_pucch_res_set_id].pucch_res_set_id = f2_pucch_res_set_id; + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(pucch_res_set_idx::set_0)].pucch_res_set_id = + pucch_res_set_idx::set_0; + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(pucch_res_set_idx::set_1)].pucch_res_set_id = + pucch_res_set_idx::set_1; // Add F1 for HARQ. const unsigned f1_idx_offset = (du_harq_set_idx % nof_harq_pucch_sets) * nof_ue_pucch_f1_res_harq.to_uint(); @@ -615,7 +614,7 @@ bool srsran::srs_du::ue_pucch_config_builder(serving_cell_config& .format_params = cell_res.format_params}); // Add PUCCH resource index to pucch_res_id_list of PUCCH resource set id=0. - pucch_cfg.pucch_res_set[f1_pucch_res_set_id].pucch_res_id_list.emplace_back( + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(pucch_res_set_idx::set_0)].pucch_res_id_list.emplace_back( pucch_res_id_t{cell_res.res_id.cell_res_id, ue_pucch_res_id}); // Increment the PUCCH resource ID for ASN1 message. @@ -647,7 +646,7 @@ bool srsran::srs_du::ue_pucch_config_builder(serving_cell_config& .format_params = cell_res.format_params}); // Add PUCCH resource index to pucch_res_id_list of PUCCH resource set id=1. - pucch_cfg.pucch_res_set[f2_pucch_res_set_id].pucch_res_id_list.emplace_back( + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(pucch_res_set_idx::set_1)].pucch_res_id_list.emplace_back( pucch_res_id_t{cell_res.res_id.cell_res_id, ue_pucch_res_id}); // Increment the PUCCH resource ID for ASN1 message. ++ue_pucch_res_id; diff --git a/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp b/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp index 197690781e..09ca138a91 100644 --- a/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp +++ b/lib/e1ap/cu_cp/e1ap_cu_cp_impl.cpp @@ -106,7 +106,16 @@ e1ap_cu_cp_impl::handle_bearer_context_setup_request(const e1ap_bearer_context_s } // add new e1ap_ue_context - ue_ctxt_list.add_ue(request.ue_index, cu_cp_ue_e1ap_id); + if (ue_ctxt_list.add_ue(request.ue_index, cu_cp_ue_e1ap_id) == nullptr) { + logger.warning("Bearer Context Setup failed. Cause: bearer context already exists"); + return launch_async([](coro_context>& ctx) mutable { + CORO_BEGIN(ctx); + e1ap_bearer_context_setup_response res; + res.success = false; + CORO_RETURN(res); + }); + } + e1ap_ue_context& ue_ctxt = ue_ctxt_list[cu_cp_ue_e1ap_id]; e1ap_message e1ap_msg; @@ -174,7 +183,7 @@ e1ap_cu_cp_impl::handle_bearer_context_release_command(const e1ap_bearer_context fill_asn1_bearer_context_release_command(bearer_context_release_cmd, command); - return launch_async(e1ap_msg, ue_ctxt.bearer_ev_mng, pdu_notifier, ue_ctxt.logger); + return launch_async(e1ap_msg, command.ue_index, ue_ctxt_list, pdu_notifier); } void e1ap_cu_cp_impl::handle_message(const e1ap_message& msg) diff --git a/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp b/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp index 2139c70776..afcde49691 100644 --- a/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp +++ b/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.cpp @@ -23,6 +23,8 @@ #include "bearer_context_release_procedure.h" #include "cu_cp/e1ap_cu_cp_impl.h" #include "cu_cp/ue_context/e1ap_bearer_transaction_manager.h" +#include "cu_cp/ue_context/e1ap_cu_cp_ue_context.h" +#include "srsran/support/srsran_assert.h" using namespace srsran; using namespace srsran::srs_cu_cp; @@ -30,22 +32,24 @@ using namespace asn1::e1ap; constexpr std::chrono::milliseconds bearer_context_release_response_timeout{5000}; -bearer_context_release_procedure::bearer_context_release_procedure(const e1ap_message& command_, - e1ap_bearer_transaction_manager& ev_mng_, - e1ap_message_notifier& e1ap_notif_, - e1ap_ue_logger& logger_) : - command(command_), ev_mng(ev_mng_), e1ap_notifier(e1ap_notif_), logger(logger_) +bearer_context_release_procedure::bearer_context_release_procedure(const e1ap_message& command_, + ue_index_t ue_index_, + e1ap_ue_context_list& ue_ctxt_list_, + e1ap_message_notifier& e1ap_notif_) : + command(command_), ue_index(ue_index_), ue_ctxt_list(ue_ctxt_list_), e1ap_notifier(e1ap_notif_) { + srsran_assert(ue_ctxt_list.contains(ue_index), "Bearer context does not exist in UE context list."); } void bearer_context_release_procedure::operator()(coro_context>& ctx) { CORO_BEGIN(ctx); - logger.log_debug("\"{}\" initialized", name()); + ue_ctxt_list[ue_index].logger.log_debug("\"{}\" initialized", name()); // Subscribe to respective publisher to receive BEARER CONTEXT RELEASE COMPLETE message. - transaction_sink.subscribe_to(ev_mng.context_release_complete, bearer_context_release_response_timeout); + transaction_sink.subscribe_to(ue_ctxt_list[ue_index].bearer_ev_mng.context_release_complete, + bearer_context_release_response_timeout); // Send command to CU-UP. send_bearer_context_release_command(); @@ -55,6 +59,8 @@ void bearer_context_release_procedure::operator()(coro_context> handle_bearer_context_release_complete(); + /// NOTE: From this point on the UE context is removed and only locally stored variables can be used. + // Handle response from CU-UP and return bearer index CORO_RETURN(); } @@ -68,10 +74,13 @@ void bearer_context_release_procedure::send_bearer_context_release_command() void bearer_context_release_procedure::handle_bearer_context_release_complete() { if (transaction_sink.successful()) { - logger.log_debug("\"{}\" finalized", name()); + ue_ctxt_list[ue_index].logger.log_debug("\"{}\" finalized", name()); } else { - logger.log_warning("BearerContextReleaseComplete timeout"); - logger.log_error("\"{}\" failed", name()); + ue_ctxt_list[ue_index].logger.log_warning("BearerContextReleaseComplete timeout"); + ue_ctxt_list[ue_index].logger.log_error("\"{}\" failed", name()); } + + // Remove UE context + ue_ctxt_list.remove_ue(ue_index); } \ No newline at end of file diff --git a/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.h b/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.h index 4e2e1e25f2..52d718a997 100644 --- a/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.h +++ b/lib/e1ap/cu_cp/procedures/bearer_context_release_procedure.h @@ -35,10 +35,10 @@ namespace srs_cu_cp { class bearer_context_release_procedure { public: - bearer_context_release_procedure(const e1ap_message& command_, - e1ap_bearer_transaction_manager& ev_mng_, - e1ap_message_notifier& e1ap_notif_, - e1ap_ue_logger& logger_); + bearer_context_release_procedure(const e1ap_message& command_, + ue_index_t ue_index_, + e1ap_ue_context_list& ue_ctxt_list_, + e1ap_message_notifier& e1ap_notif_); void operator()(coro_context>& ctx); @@ -51,10 +51,10 @@ class bearer_context_release_procedure /// Handles procedure result and returns back to procedure caller. void handle_bearer_context_release_complete(); - const e1ap_message command; - e1ap_bearer_transaction_manager& ev_mng; - e1ap_message_notifier& e1ap_notifier; - e1ap_ue_logger& logger; + const e1ap_message command; + ue_index_t ue_index; + e1ap_ue_context_list& ue_ctxt_list; + e1ap_message_notifier& e1ap_notifier; protocol_transaction_outcome_observer transaction_sink; }; diff --git a/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.cpp b/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.cpp index b3d80ebea7..ca17a526ae 100644 --- a/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.cpp +++ b/lib/e1ap/cu_cp/ue_context/e1ap_cu_cp_ue_context.cpp @@ -31,6 +31,11 @@ e1ap_ue_context* e1ap_ue_context_list::add_ue(ue_index_t ue_index, gnb_cu_cp_ue_ srsran_assert(ue_index != ue_index_t::invalid, "Invalid ue_index={}", ue_index); srsran_assert(cu_cp_ue_e1ap_id != gnb_cu_cp_ue_e1ap_id_t::invalid, "Invalid cu_cp_ue_e1ap_id={}", cu_cp_ue_e1ap_id); + if (ue_index_to_ue_e1ap_id.find(ue_index) != ue_index_to_ue_e1ap_id.end()) { + logger.error("ue={}: UE already exists", ue_index); + return nullptr; + } + auto ret = ues.emplace(std::piecewise_construct, std::forward_as_tuple(cu_cp_ue_e1ap_id), std::forward_as_tuple(ue_index, cu_cp_ue_e1ap_id, timers)); diff --git a/lib/e1ap/cu_up/e1ap_cu_up_factory.cpp b/lib/e1ap/cu_up/e1ap_cu_up_factory.cpp index 2ddaf00079..efef2d901a 100644 --- a/lib/e1ap/cu_up/e1ap_cu_up_factory.cpp +++ b/lib/e1ap/cu_up/e1ap_cu_up_factory.cpp @@ -28,10 +28,10 @@ using namespace srsran; using namespace srs_cu_up; -std::unique_ptr srsran::srs_cu_up::create_e1ap(e1_connection_client& e1_client_handler_, - e1ap_cu_up_notifier& cu_up_notifier_, - timer_manager& timers_, - task_executor& cu_up_exec_) +std::unique_ptr srsran::srs_cu_up::create_e1ap(e1_connection_client& e1_client_handler_, + e1ap_cu_up_manager_notifier& cu_up_notifier_, + timer_manager& timers_, + task_executor& cu_up_exec_) { auto e1ap_cu_up = std::make_unique(e1_client_handler_, cu_up_notifier_, timers_, cu_up_exec_); return e1ap_cu_up; diff --git a/lib/e1ap/cu_up/e1ap_cu_up_impl.cpp b/lib/e1ap/cu_up/e1ap_cu_up_impl.cpp index 9f96eaa977..af14476199 100644 --- a/lib/e1ap/cu_up/e1ap_cu_up_impl.cpp +++ b/lib/e1ap/cu_up/e1ap_cu_up_impl.cpp @@ -51,10 +51,10 @@ class e1ap_rx_pdu_adapter final : public e1ap_message_notifier } // namespace -e1ap_cu_up_impl::e1ap_cu_up_impl(e1_connection_client& e1_client_handler_, - e1ap_cu_up_notifier& cu_up_notifier_, - timer_manager& timers_, - task_executor& cu_up_exec_) : +e1ap_cu_up_impl::e1ap_cu_up_impl(e1_connection_client& e1_client_handler_, + e1ap_cu_up_manager_notifier& cu_up_notifier_, + timer_manager& timers_, + task_executor& cu_up_exec_) : logger(srslog::fetch_basic_logger("CU-UP-E1")), cu_up_notifier(cu_up_notifier_), timers(timers_), diff --git a/lib/e1ap/cu_up/e1ap_cu_up_impl.h b/lib/e1ap/cu_up/e1ap_cu_up_impl.h index db2db5f388..c93248826d 100644 --- a/lib/e1ap/cu_up/e1ap_cu_up_impl.h +++ b/lib/e1ap/cu_up/e1ap_cu_up_impl.h @@ -22,17 +22,14 @@ #pragma once -#include "../common/e1ap_asn1_utils.h" #include "e1ap_cu_up_connection_handler.h" #include "ue_context/e1ap_cu_up_ue_context.h" #include "srsran/asn1/e1ap/e1ap.h" #include "srsran/e1ap/cu_up/e1ap_cu_up.h" #include "srsran/support/executors/task_executor.h" #include "srsran/support/timers.h" -#include -namespace srsran { -namespace srs_cu_up { +namespace srsran::srs_cu_up { class e1_connection_client; class e1ap_event_manager; @@ -40,10 +37,10 @@ class e1ap_event_manager; class e1ap_cu_up_impl final : public e1ap_interface { public: - e1ap_cu_up_impl(e1_connection_client& e1_client_handler_, - e1ap_cu_up_notifier& cu_up_notifier_, - timer_manager& timers_, - task_executor& cu_up_exec_); + e1ap_cu_up_impl(e1_connection_client& e1_client_handler_, + e1ap_cu_up_manager_notifier& cu_up_notifier_, + timer_manager& timers_, + task_executor& cu_up_exec_); ~e1ap_cu_up_impl() override; // e1ap connection manager functions @@ -110,7 +107,7 @@ class e1ap_cu_up_impl final : public e1ap_interface srslog::basic_logger& logger; // nofifiers and handles - e1ap_cu_up_notifier& cu_up_notifier; + e1ap_cu_up_manager_notifier& cu_up_notifier; timer_manager& timers; task_executor& cu_up_exec; @@ -124,5 +121,4 @@ class e1ap_cu_up_impl final : public e1ap_interface std::unique_ptr ev_mng; }; -} // namespace srs_cu_up -} // namespace srsran +} // namespace srsran::srs_cu_up diff --git a/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.cpp b/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.cpp index 9510fa85b0..38073f8910 100644 --- a/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.cpp +++ b/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.cpp @@ -33,7 +33,7 @@ bearer_context_modification_procedure::bearer_context_modification_procedure( const e1ap_ue_context& ue_ctxt_, const asn1::e1ap::bearer_context_mod_request_s& request_, e1ap_message_notifier& pdu_notifier_, - e1ap_cu_up_notifier& cu_up_notifier_) : + e1ap_cu_up_manager_notifier& cu_up_notifier_) : ue_ctxt(ue_ctxt_), request(request_), pdu_notifier(pdu_notifier_), cu_up_notifier(cu_up_notifier_) { prepare_failure_message(); diff --git a/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.h b/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.h index 3aeb39f44d..bbe824719a 100644 --- a/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.h +++ b/lib/e1ap/cu_up/procedures/bearer_context_modification_procedure.h @@ -39,7 +39,7 @@ class bearer_context_modification_procedure bearer_context_modification_procedure(const e1ap_ue_context& ue_ctxt_, const asn1::e1ap::bearer_context_mod_request_s& request_, e1ap_message_notifier& pdu_notifier_, - e1ap_cu_up_notifier& cu_up_notifier_); + e1ap_cu_up_manager_notifier& cu_up_notifier_); void operator()(coro_context>& ctx); @@ -49,7 +49,7 @@ class bearer_context_modification_procedure const e1ap_ue_context ue_ctxt; const asn1::e1ap::bearer_context_mod_request_s request; e1ap_message_notifier& pdu_notifier; - e1ap_cu_up_notifier& cu_up_notifier; + e1ap_cu_up_manager_notifier& cu_up_notifier; // local variables e1ap_message e1ap_msg = {}; diff --git a/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp b/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp index 5c77ae4634..a6abe72144 100644 --- a/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp +++ b/lib/f1ap/cu_cp/procedures/ue_context_setup_procedure.cpp @@ -190,6 +190,8 @@ f1ap_ue_context_setup_response ue_context_setup_procedure::handle_procedure_resu // Create UE RRC context in CU-CP, if required. resp.success = create_ue_rrc_context(resp); + logger.debug("ue={} proc=\"{}\": finished successfully", request.ue_index, name()); + return resp; } diff --git a/lib/ngap/ngap_impl.cpp b/lib/ngap/ngap_impl.cpp index 6d1b74e32c..3638b24b09 100644 --- a/lib/ngap/ngap_impl.cpp +++ b/lib/ngap/ngap_impl.cpp @@ -418,14 +418,8 @@ void ngap_impl::handle_initial_context_setup_request(const asn1::ngap::init_cont init_ctxt_setup_req.security_context.supported_enc_algos); // start routine - ue->schedule_async_task(launch_async(init_ctxt_setup_req, - ue_ctxt.ue_ids, - ue->get_rrc_ue_control_notifier(), - ue->get_rrc_ue_pdu_notifier(), - cu_cp_notifier, - *ue, - *tx_pdu_notifier, - ue_ctxt.logger)); + ue->schedule_async_task(launch_async( + init_ctxt_setup_req, ue_ctxt.ue_ids, cu_cp_notifier, *tx_pdu_notifier, ue_ctxt.logger)); } void ngap_impl::handle_pdu_session_resource_setup_request(const asn1::ngap::pdu_session_res_setup_request_s& request) diff --git a/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp b/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp index 3d131b4313..4c228f6b90 100644 --- a/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp +++ b/lib/ngap/procedures/ngap_initial_context_setup_procedure.cpp @@ -22,11 +22,9 @@ #include "ngap_initial_context_setup_procedure.h" #include "../ngap_asn1_helpers.h" -#include "ngap_procedure_helpers.h" #include "srsran/asn1/ngap/common.h" -#include "srsran/ngap/ngap.h" #include "srsran/ngap/ngap_message.h" -#include "srsran/ran/cause/ngap_cause.h" +#include "srsran/support/async/coroutine.h" using namespace srsran; using namespace srsran::srs_cu_cp; @@ -35,20 +33,10 @@ using namespace asn1::ngap; ngap_initial_context_setup_procedure::ngap_initial_context_setup_procedure( const ngap_init_context_setup_request& request_, const ngap_ue_ids& ue_ids_, - ngap_rrc_ue_control_notifier& rrc_ue_ctrl_notifier_, - ngap_rrc_ue_pdu_notifier& rrc_ue_pdu_notifier_, ngap_cu_cp_notifier& cu_cp_notifier_, - ngap_cu_cp_ue_notifier& cu_cp_ue_notifier_, ngap_message_notifier& amf_notifier_, ngap_ue_logger& logger_) : - request(request_), - ue_ids(ue_ids_), - rrc_ue_ctrl_notifier(rrc_ue_ctrl_notifier_), - rrc_ue_pdu_notifier(rrc_ue_pdu_notifier_), - cu_cp_notifier(cu_cp_notifier_), - cu_cp_ue_notifier(cu_cp_ue_notifier_), - amf_notifier(amf_notifier_), - logger(logger_) + request(request_), ue_ids(ue_ids_), cu_cp_notifier(cu_cp_notifier_), amf_notifier(amf_notifier_), logger(logger_) { } @@ -58,89 +46,16 @@ void ngap_initial_context_setup_procedure::operator()(coro_context init_ctxt_setup_routine_outcome; }; } // namespace srs_cu_cp diff --git a/lib/phy/upper/channel_processors/prach_detector_generic_impl.cpp b/lib/phy/upper/channel_processors/prach_detector_generic_impl.cpp index db82005a8f..b85cdf201d 100644 --- a/lib/phy/upper/channel_processors/prach_detector_generic_impl.cpp +++ b/lib/phy/upper/channel_processors/prach_detector_generic_impl.cpp @@ -102,6 +102,10 @@ prach_detection_result prach_detector_generic_impl::detect(const prach_buffer& i preamble_info = get_prach_preamble_short_info(config.format, config.ra_scs, false); } + // Create range of preambles to detect. + interval preamble_indices(config.start_preamble_index, + config.start_preamble_index + config.nof_preamble_indices); + // Get cyclic shift. unsigned N_cs = prach_cyclic_shifts_get(config.ra_scs, config.restricted_set, config.zero_correlation_zone); srsran_assert(N_cs != PRACH_CYCLIC_SHIFTS_RESERVED, "Reserved cyclic shift."); @@ -197,6 +201,14 @@ prach_detection_result prach_detector_generic_impl::detect(const prach_buffer& i srsvec::zero(idft_input); for (unsigned i_sequence = 0; i_sequence != nof_sequences; ++i_sequence) { + // Range of preambles to detect within this sequence. + interval sequence_preambles(i_sequence * nof_shifts, (i_sequence + 1) * nof_shifts); + + // Skip sequence if it does not overlap with the preambles to detect. + if (!preamble_indices.overlaps(sequence_preambles)) { + continue; + } + // Prepare root sequence configuration. prach_generator::configuration generator_config; generator_config.format = config.format; @@ -303,6 +315,14 @@ prach_detection_result prach_detector_generic_impl::detect(const prach_buffer& i // Process global metric. for (unsigned i_window = 0; i_window != nof_shifts; ++i_window) { + // Calculate preamble index for the sequence and shift. + unsigned preamble_index = i_sequence * nof_shifts + i_window; + + // Skip preamble if it is not contained within the preambles to detect. + if (!preamble_indices.contains(preamble_index)) { + continue; + } + // Select metric global. span metric_global = span(temp).first(win_width); span window_metric_global_num = metric_global_num.get_view({i_window}); @@ -326,7 +346,7 @@ prach_detection_result prach_detector_generic_impl::detect(const prach_buffer& i if ((delay < metric_global.size()) && (peak > threshold) && (delay < static_cast(max_delay_samples) * 0.8)) { prach_detection_result::preamble_indication& info = result.preambles.emplace_back(); - info.preamble_index = i_sequence * nof_shifts + i_window; + info.preamble_index = preamble_index; info.time_advance = phy_time_unit::from_seconds(static_cast(delay) / static_cast(sample_rate_Hz)); // Normalize the detection metric with respect to the threshold. diff --git a/lib/rlc/rlc_tx_am_entity.cpp b/lib/rlc/rlc_tx_am_entity.cpp index 7746f75e2e..3a5b6db88b 100644 --- a/lib/rlc/rlc_tx_am_entity.cpp +++ b/lib/rlc/rlc_tx_am_entity.cpp @@ -89,7 +89,7 @@ void rlc_tx_am_entity::handle_sdu(byte_buffer sdu_buf, bool is_retx) sdu.buf = std::move(sdu_buf); sdu.is_retx = is_retx; - sdu.pdcp_sn = get_pdcp_sn(sdu.buf, cfg.pdcp_sn_len, logger.get_basic_logger()); + sdu.pdcp_sn = get_pdcp_sn(sdu.buf, cfg.pdcp_sn_len, rb_id.is_srb(), logger.get_basic_logger()); // Sanity check for PDCP ReTx in SRBs if (SRSRAN_UNLIKELY(rb_id.is_srb() && sdu.is_retx)) { diff --git a/lib/rlc/rlc_tx_um_entity.cpp b/lib/rlc/rlc_tx_um_entity.cpp index 97f7805934..e688652dc3 100644 --- a/lib/rlc/rlc_tx_um_entity.cpp +++ b/lib/rlc/rlc_tx_um_entity.cpp @@ -67,7 +67,7 @@ void rlc_tx_um_entity::handle_sdu(byte_buffer sdu_buf, bool is_retx) sdu_.time_of_arrival = std::chrono::high_resolution_clock::now(); sdu_.buf = std::move(sdu_buf); - sdu_.pdcp_sn = get_pdcp_sn(sdu_.buf, cfg.pdcp_sn_len, logger.get_basic_logger()); + sdu_.pdcp_sn = get_pdcp_sn(sdu_.buf, cfg.pdcp_sn_len, /* is_srb = */ false, logger.get_basic_logger()); // Sanity check for PDCP ReTx in RLC UM if (SRSRAN_UNLIKELY(is_retx)) { diff --git a/lib/rrc/CMakeLists.txt b/lib/rrc/CMakeLists.txt index 8fee7d1d3a..78450571b0 100644 --- a/lib/rrc/CMakeLists.txt +++ b/lib/rrc/CMakeLists.txt @@ -28,7 +28,6 @@ set(SOURCES ue/rrc_ue_message_senders.cpp ue/rrc_ue_helpers.cpp ue/procedures/rrc_setup_procedure.cpp - ue/procedures/rrc_security_mode_command_procedure.cpp ue/procedures/rrc_reconfiguration_procedure.cpp ue/procedures/rrc_ue_capability_transfer_procedure.cpp ue/procedures/rrc_reestablishment_procedure.cpp diff --git a/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp b/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp deleted file mode 100644 index f1967a4432..0000000000 --- a/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "rrc_security_mode_command_procedure.h" -#include "../rrc_asn1_helpers.h" - -using namespace srsran; -using namespace srsran::srs_cu_cp; -using namespace asn1::rrc_nr; - -rrc_security_mode_command_procedure::rrc_security_mode_command_procedure( - rrc_ue_context_t& context_, - security::sec_selected_algos security_algos_, - rrc_ue_security_mode_command_proc_notifier& rrc_ue_notifier_, - rrc_ue_event_manager& event_mng_, - rrc_ue_logger& logger_) : - context(context_), security_algos(security_algos_), rrc_ue(rrc_ue_notifier_), event_mng(event_mng_), logger(logger_) -{ -} - -void rrc_security_mode_command_procedure::operator()(coro_context>& ctx) -{ - CORO_BEGIN(ctx); - - logger.log_debug("\"{}\" initialized", name()); - // create new transaction for RRCSecurityModeCommand - transaction = - event_mng.transactions.create_transaction(std::chrono::milliseconds(context.cfg.rrc_procedure_timeout_ms)); - - // activate SRB1 PDCP security - rrc_ue.on_new_as_security_context(); - - // send RRC SMC to UE - send_rrc_security_mode_command(); - - // Await UE response - CORO_AWAIT(transaction); - - if (transaction.has_response()) { - logger.log_debug("\"{}\" finished successfully", name()); - rrc_ue.on_security_context_sucessful(); - procedure_result = true; - } else { - logger.log_warning("\"{}\" timed out after {}ms", name(), context.cfg.rrc_procedure_timeout_ms); - } - - logger.log_debug("\"{}\" finalized", name()); - CORO_RETURN(procedure_result); -} - -void rrc_security_mode_command_procedure::send_rrc_security_mode_command() -{ - dl_dcch_msg_s dl_dcch_msg; - dl_dcch_msg.msg.set_c1().set_security_mode_cmd(); - security_mode_cmd_s& rrc_smc = dl_dcch_msg.msg.c1().security_mode_cmd(); - fill_asn1_rrc_smc_msg(rrc_smc, security_algos.integ_algo, security_algos.cipher_algo, transaction.id()); - rrc_ue.on_new_dl_dcch(srb_id_t::srb1, dl_dcch_msg); -} diff --git a/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.h b/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.h deleted file mode 100644 index a8ed4d42e7..0000000000 --- a/lib/rrc/ue/procedures/rrc_security_mode_command_procedure.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "../rrc_ue_context.h" -#include "../rrc_ue_logger.h" -#include "rrc_ue_event_manager.h" -#include "srsran/asn1/rrc_nr/dl_dcch_msg_ies.h" -#include "srsran/rrc/rrc_du.h" -#include "srsran/rrc/rrc_ue.h" -#include "srsran/support/async/async_task.h" -#include "srsran/support/async/eager_async_task.h" - -namespace srsran { -namespace srs_cu_cp { - -/// \brief Handles the setup of AS security keys in the RRC UE. -/// TODO Add seqdiag -class rrc_security_mode_command_procedure -{ -public: - rrc_security_mode_command_procedure(rrc_ue_context_t& context_, - security::sec_selected_algos security_algos_, - rrc_ue_security_mode_command_proc_notifier& rrc_ue_notifier_, - rrc_ue_event_manager& ev_mng_, - rrc_ue_logger& logger_); - - void operator()(coro_context>& ctx); - - static const char* name() { return "RRC Security Mode Command Procedure"; } - -private: - /// \remark Send RRC Security Mode Command, see section 5.3.3 in TS 38.331 - void send_rrc_security_mode_command(); - - rrc_ue_context_t& context; - security::sec_selected_algos security_algos; - rrc_ue_security_mode_command_proc_notifier& rrc_ue; // handler to the parent RRC UE object - rrc_ue_event_manager& event_mng; // event manager for the RRC UE entity - rrc_ue_logger& logger; - - rrc_transaction transaction; - eager_async_task task; - - bool procedure_result = false; -}; - -/// \brief Fills ASN.1 RRC Security Mode Command struct. -/// \param[out] rrc_smc The RRC security mode command ASN.1 struct to fill. -/// \param[in] int_algo The selected integrity protection algorithm. -/// \param[in] ciph_algo The selected ciphering algorithm. -/// \param[in] rrc_transaction_id The RRC transaction id. -inline void fill_asn1_rrc_smc_msg(asn1::rrc_nr::security_mode_cmd_s& rrc_smc, - const security::integrity_algorithm& int_algo, - const security::ciphering_algorithm& ciph_algo, - uint8_t rrc_transaction_id) -{ - using namespace asn1::rrc_nr; - security_mode_cmd_ies_s& smc_ies = rrc_smc.crit_exts.set_security_mode_cmd(); - rrc_smc.rrc_transaction_id = rrc_transaction_id; - - // Set security algorithms - security_cfg_smc_s& security_cfg_smc = smc_ies.security_cfg_smc; - security_algorithm_cfg_s& security_algorithm_cfg = security_cfg_smc.security_algorithm_cfg; - - security_algorithm_cfg.integrity_prot_algorithm_present = true; - switch (int_algo) { - case security::integrity_algorithm::nia0: - security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia0; - break; - case security::integrity_algorithm::nia1: - security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia1; - break; - case security::integrity_algorithm::nia2: - security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia2; - break; - case security::integrity_algorithm::nia3: - security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia3; - break; - } - switch (ciph_algo) { - case security::ciphering_algorithm::nea0: - security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea0; - break; - case security::ciphering_algorithm::nea1: - security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea1; - break; - case security::ciphering_algorithm::nea2: - security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea2; - break; - case security::ciphering_algorithm::nea3: - security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea3; - break; - } -} - -} // namespace srs_cu_cp -} // namespace srsran diff --git a/lib/rrc/ue/rrc_asn1_helpers.h b/lib/rrc/ue/rrc_asn1_helpers.h index 2ea5461284..ef4bda46ad 100644 --- a/lib/rrc/ue/rrc_asn1_helpers.h +++ b/lib/rrc/ue/rrc_asn1_helpers.h @@ -26,6 +26,9 @@ #include "rrc_measurement_types_asn1_converters.h" #include "srsran/adt/byte_buffer.h" #include "srsran/adt/expected.h" +#include "srsran/asn1/rrc_nr/ul_dcch_msg.h" +#include "srsran/asn1/rrc_nr/ul_dcch_msg_ies.h" +#include "srsran/rrc/rrc_types.h" namespace srsran { @@ -78,6 +81,55 @@ inline expected get_transaction_id(const asn1::rrc_nr::ul_dcch_msg_s& m return make_unexpected(default_error_t{}); } +/// \brief Fills ASN.1 RRC Security Mode Command struct. +/// \param[out] rrc_smc The RRC security mode command ASN.1 struct to fill. +/// \param[in] int_algo The selected integrity protection algorithm. +/// \param[in] ciph_algo The selected ciphering algorithm. +/// \param[in] rrc_transaction_id The RRC transaction id. +inline void fill_asn1_rrc_smc_msg(asn1::rrc_nr::security_mode_cmd_s& rrc_smc, + const security::integrity_algorithm& int_algo, + const security::ciphering_algorithm& ciph_algo, + uint8_t rrc_transaction_id) +{ + using namespace asn1::rrc_nr; + security_mode_cmd_ies_s& smc_ies = rrc_smc.crit_exts.set_security_mode_cmd(); + rrc_smc.rrc_transaction_id = rrc_transaction_id; + + // Set security algorithms + security_cfg_smc_s& security_cfg_smc = smc_ies.security_cfg_smc; + security_algorithm_cfg_s& security_algorithm_cfg = security_cfg_smc.security_algorithm_cfg; + + security_algorithm_cfg.integrity_prot_algorithm_present = true; + switch (int_algo) { + case security::integrity_algorithm::nia0: + security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia0; + break; + case security::integrity_algorithm::nia1: + security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia1; + break; + case security::integrity_algorithm::nia2: + security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia2; + break; + case security::integrity_algorithm::nia3: + security_algorithm_cfg.integrity_prot_algorithm = integrity_prot_algorithm_e::nia3; + break; + } + switch (ciph_algo) { + case security::ciphering_algorithm::nea0: + security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea0; + break; + case security::ciphering_algorithm::nea1: + security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea1; + break; + case security::ciphering_algorithm::nea2: + security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea2; + break; + case security::ciphering_algorithm::nea3: + security_algorithm_cfg.ciphering_algorithm = ciphering_algorithm_e::nea3; + break; + } +} + /// \brief Fills ASN.1 RRC Setup struct. /// \param[out] asn1_rrc_reconf The RRC Reconfiguration ASN.1 struct to fill. /// \param[in] rrc_transaction_id The RRC transaction id. diff --git a/lib/rrc/ue/rrc_ue_impl.cpp b/lib/rrc/ue/rrc_ue_impl.cpp index d3b6c02081..004cfc83d4 100644 --- a/lib/rrc/ue/rrc_ue_impl.cpp +++ b/lib/rrc/ue/rrc_ue_impl.cpp @@ -21,7 +21,6 @@ */ #include "rrc_ue_impl.h" -#include "procedures/rrc_security_mode_command_procedure.h" #include "rrc_ue_helpers.h" #include "srsran/asn1/rrc_nr/dl_dcch_msg.h" #include "srsran/asn1/rrc_nr/ho_prep_info.h" @@ -140,26 +139,6 @@ void rrc_ue_impl::on_new_as_security_context() cu_cp_ue_notifier.enable_security(); } -void rrc_ue_impl::on_security_context_sucessful() -{ - srsran_sanity_check(context.srbs.find(srb_id_t::srb1) != context.srbs.end(), - "Attempted to configure security, but there is no interface to PDCP"); - - context.srbs.at(srb_id_t::srb1) - .enable_rx_security( - security::integrity_enabled::on, security::ciphering_enabled::on, cu_cp_ue_notifier.get_rrc_128_as_config()); - context.srbs.at(srb_id_t::srb1) - .enable_tx_security( - security::integrity_enabled::on, security::ciphering_enabled::on, cu_cp_ue_notifier.get_rrc_128_as_config()); -} - -async_task rrc_ue_impl::handle_init_security_context() -{ - // Launch RRC security mode procedure - return launch_async( - context, cu_cp_ue_notifier.get_security_algos(), *this, *event_mng, logger); -} - byte_buffer rrc_ue_impl::get_packed_handover_preparation_message() { struct ho_prep_info_s ho_prep; diff --git a/lib/rrc/ue/rrc_ue_impl.h b/lib/rrc/ue/rrc_ue_impl.h index 3b8dcd565f..bd209be2fd 100644 --- a/lib/rrc/ue/rrc_ue_impl.h +++ b/lib/rrc/ue/rrc_ue_impl.h @@ -26,6 +26,7 @@ #include "rrc_ue_context.h" #include "rrc_ue_logger.h" #include "srsran/asn1/rrc_nr/ul_dcch_msg.h" +#include "srsran/asn1/rrc_nr/ul_dcch_msg_ies.h" #include "srsran/rrc/rrc_ue.h" namespace srsran { @@ -55,15 +56,14 @@ class rrc_ue_impl final : public rrc_ue_interface, public rrc_ue_controller void handle_ul_dcch_pdu(const srb_id_t srb_id, byte_buffer pdcp_pdu) override; // rrc_ue_interface - rrc_ue_controller& get_controller() override { return *this; } - rrc_ul_ccch_pdu_handler& get_ul_ccch_pdu_handler() override { return *this; } - rrc_ul_dcch_pdu_handler& get_ul_dcch_pdu_handler() override { return *this; } - rrc_dl_nas_message_handler& get_rrc_dl_nas_message_handler() override { return *this; } - rrc_ue_srb_handler& get_rrc_ue_srb_handler() override { return *this; } - rrc_ue_control_message_handler& get_rrc_ue_control_message_handler() override { return *this; } - rrc_ue_init_security_context_handler& get_rrc_ue_init_security_context_handler() override { return *this; } - rrc_ue_context_handler& get_rrc_ue_context_handler() override { return *this; } - rrc_ue_handover_preparation_handler& get_rrc_ue_handover_preparation_handler() override { return *this; } + rrc_ue_controller& get_controller() override { return *this; } + rrc_ul_ccch_pdu_handler& get_ul_ccch_pdu_handler() override { return *this; } + rrc_ul_dcch_pdu_handler& get_ul_dcch_pdu_handler() override { return *this; } + rrc_dl_nas_message_handler& get_rrc_dl_nas_message_handler() override { return *this; } + rrc_ue_srb_handler& get_rrc_ue_srb_handler() override { return *this; } + rrc_ue_control_message_handler& get_rrc_ue_control_message_handler() override { return *this; } + rrc_ue_context_handler& get_rrc_ue_context_handler() override { return *this; } + rrc_ue_handover_preparation_handler& get_rrc_ue_handover_preparation_handler() override { return *this; } // rrc_ue_srb_handler void create_srb(const srb_creation_message& msg) override; @@ -73,6 +73,7 @@ class rrc_ue_impl final : public rrc_ue_interface, public rrc_ue_controller void handle_dl_nas_transport_message(byte_buffer nas_pdu) override; // rrc_ue_control_message_handler + rrc_ue_security_mode_command_context get_security_mode_command_context() override; async_task handle_rrc_reconfiguration_request(const rrc_reconfiguration_procedure_request& msg) override; rrc_ue_handover_reconfiguration_context get_rrc_ue_handover_reconfiguration_context(const rrc_reconfiguration_procedure_request& request) override; @@ -100,6 +101,7 @@ class rrc_ue_impl final : public rrc_ue_interface, public rrc_ue_controller void handle_rrc_reest_request(const asn1::rrc_nr::rrc_reest_request_s& msg); void handle_ul_info_transfer(const asn1::rrc_nr::ul_info_transfer_ies_s& ul_info_transfer); void handle_rrc_transaction_complete(const asn1::rrc_nr::ul_dcch_msg_s& msg, uint8_t transaction_id_); + void handle_security_mode_complete(const asn1::rrc_nr::security_mode_complete_s& msg); void handle_measurement_report(const asn1::rrc_nr::meas_report_s& msg); // message senders @@ -114,10 +116,6 @@ class rrc_ue_impl final : public rrc_ue_interface, public rrc_ue_controller // rrc_ue_security_mode_command_proc_notifier void on_new_dl_dcch(srb_id_t srb_id, const asn1::rrc_nr::dl_dcch_msg_s& dl_ccch_msg) override; void on_new_as_security_context() override; - void on_security_context_sucessful() override; - - // Triggers the SMC procedure - async_task handle_init_security_context() override; rrc_ue_context_t context; rrc_pdu_f1ap_notifier& f1ap_pdu_notifier; // PDU notifier to the F1AP diff --git a/lib/rrc/ue/rrc_ue_message_handlers.cpp b/lib/rrc/ue/rrc_ue_message_handlers.cpp index aa61d8bd7d..59abe60cee 100644 --- a/lib/rrc/ue/rrc_ue_message_handlers.cpp +++ b/lib/rrc/ue/rrc_ue_message_handlers.cpp @@ -162,7 +162,7 @@ void rrc_ue_impl::handle_pdu(const srb_id_t srb_id, byte_buffer rrc_pdu) handle_rrc_transaction_complete(ul_dcch_msg, ul_dcch_msg.msg.c1().rrc_setup_complete().rrc_transaction_id); break; case ul_dcch_msg_type_c::c1_c_::types_opts::security_mode_complete: - handle_rrc_transaction_complete(ul_dcch_msg, ul_dcch_msg.msg.c1().security_mode_complete().rrc_transaction_id); + handle_security_mode_complete(ul_dcch_msg.msg.c1().security_mode_complete()); break; case ul_dcch_msg_type_c::c1_c_::types_opts::ue_cap_info: handle_rrc_transaction_complete(ul_dcch_msg, ul_dcch_msg.msg.c1().ue_cap_info().rrc_transaction_id); @@ -214,6 +214,19 @@ void rrc_ue_impl::handle_ul_dcch_pdu(const srb_id_t srb_id, byte_buffer pdcp_pdu } } +void rrc_ue_impl::handle_security_mode_complete(const asn1::rrc_nr::security_mode_complete_s& msg) +{ + srsran_sanity_check(context.srbs.find(srb_id_t::srb1) != context.srbs.end(), + "Attempted to configure security, but there is no interface to PDCP"); + + context.srbs.at(srb_id_t::srb1) + .enable_rx_security( + security::integrity_enabled::on, security::ciphering_enabled::on, cu_cp_ue_notifier.get_rrc_128_as_config()); + context.srbs.at(srb_id_t::srb1) + .enable_tx_security( + security::integrity_enabled::on, security::ciphering_enabled::on, cu_cp_ue_notifier.get_rrc_128_as_config()); +} + void rrc_ue_impl::handle_ul_info_transfer(const ul_info_transfer_ies_s& ul_info_transfer) { cu_cp_ul_nas_transport ul_nas_msg = {}; @@ -262,6 +275,52 @@ void rrc_ue_impl::handle_rrc_transaction_complete(const ul_dcch_msg_s& msg, uint } } +rrc_ue_security_mode_command_context rrc_ue_impl::get_security_mode_command_context() +{ + // activate SRB1 PDCP security + on_new_as_security_context(); + + rrc_ue_security_mode_command_context smc_ctxt; + + if (context.srbs.find(srb_id_t::srb1) == context.srbs.end()) { + logger.log_error("Can't get security mode command. {} is not set up", srb_id_t::srb1); + return smc_ctxt; + } + + // Create transaction to get transaction ID + rrc_transaction transaction = event_mng->transactions.create_transaction(); + smc_ctxt.transaction_id = transaction.id(); + + // Get selected security algorithms + security::sec_selected_algos security_algos = cu_cp_ue_notifier.get_security_algos(); + + // Pack SecurityModeCommand + dl_dcch_msg_s dl_dcch_msg; + dl_dcch_msg.msg.set_c1().set_security_mode_cmd().crit_exts.set_security_mode_cmd(); + fill_asn1_rrc_smc_msg(dl_dcch_msg.msg.c1().security_mode_cmd(), + security_algos.integ_algo, + security_algos.cipher_algo, + smc_ctxt.transaction_id); + + // Pack DL DCCH msg + pdcp_tx_result pdcp_packing_result = + context.srbs.at(srb_id_t::srb1).pack_rrc_pdu(pack_into_pdu(dl_dcch_msg, "SecurityModeCommand")); + if (!pdcp_packing_result.is_successful()) { + logger.log_info("Requesting UE release. Cause: PDCP packing failed with {}", + pdcp_packing_result.get_failure_cause()); + on_ue_release_required(pdcp_packing_result.get_failure_cause()); + return smc_ctxt; + } + + smc_ctxt.rrc_ue_security_mode_command_pdu = pdcp_packing_result.pop_pdu(); + smc_ctxt.sp_cell_id = context.cell.cgi; + + // Log Tx message + log_rrc_message(logger, Tx, smc_ctxt.rrc_ue_security_mode_command_pdu, dl_dcch_msg, "DCCH DL"); + + return smc_ctxt; +} + async_task rrc_ue_impl::handle_rrc_reconfiguration_request(const rrc_reconfiguration_procedure_request& msg) { return launch_async( @@ -273,16 +332,21 @@ rrc_ue_impl::get_rrc_ue_handover_reconfiguration_context(const rrc_reconfigurati { rrc_ue_handover_reconfiguration_context ho_reconf_ctxt; + if (context.srbs.find(srb_id_t::srb1) == context.srbs.end()) { + logger.log_error("Can't get handover reconfiguraion context. {} is not set up", srb_id_t::srb1); + return ho_reconf_ctxt; + } + // Create transaction to get transaction ID rrc_transaction transaction = event_mng->transactions.create_transaction(); ho_reconf_ctxt.transaction_id = transaction.id(); - // pack RRC Reconfig + // Pack RRC Reconfig dl_dcch_msg_s dl_dcch_msg; dl_dcch_msg.msg.set_c1().set_rrc_recfg().crit_exts.set_rrc_recfg(); fill_asn1_rrc_reconfiguration_msg(dl_dcch_msg.msg.c1().rrc_recfg(), ho_reconf_ctxt.transaction_id, request); - // pack DL CCCH msg + // Pack DL DCCH msg pdcp_tx_result pdcp_packing_result = context.srbs.at(srb_id_t::srb1).pack_rrc_pdu(pack_into_pdu(dl_dcch_msg, "RRCReconfiguration")); if (!pdcp_packing_result.is_successful()) { diff --git a/lib/scheduler/config/serving_cell_config_factory.cpp b/lib/scheduler/config/serving_cell_config_factory.cpp index d7e3a1197f..adce5066a5 100644 --- a/lib/scheduler/config/serving_cell_config_factory.cpp +++ b/lib/scheduler/config/serving_cell_config_factory.cpp @@ -29,6 +29,7 @@ #include "srsran/ran/pdcch/search_space.h" #include "srsran/ran/prach/prach_configuration.h" #include "srsran/ran/prach/prach_helper.h" +#include "srsran/ran/pucch/pucch_info.h" #include "srsran/ran/resource_allocation/ofdm_symbol_range.h" #include "srsran/scheduler/config/csi_helper.h" #include "srsran/srslog/srslog.h" @@ -538,14 +539,14 @@ uplink_config srsran::config_helpers::make_default_ue_uplink_config(const cell_c // PUCCH Resource Set ID 0. This is for PUCCH Format 1 only (Format 0 not yet supported), used for HARQ-ACK only. auto& pucch_res_set_0 = pucch_cfg.pucch_res_set.emplace_back(); - pucch_res_set_0.pucch_res_set_id = 0; + pucch_res_set_0.pucch_res_set_id = pucch_res_set_idx::set_0; pucch_res_set_0.pucch_res_id_list.emplace_back(pucch_res_id_t{0, 0}); pucch_res_set_0.pucch_res_id_list.emplace_back(pucch_res_id_t{1, 1}); pucch_res_set_0.pucch_res_id_list.emplace_back(pucch_res_id_t{2, 2}); // PUCCH Resource Set ID 1. This is for PUCCH Format 2 only and used for HARQ-ACK + optionally SR and/or CSI. auto& pucch_res_set_1 = pucch_cfg.pucch_res_set.emplace_back(); - pucch_res_set_1.pucch_res_set_id = 1; + pucch_res_set_1.pucch_res_set_id = pucch_res_set_idx::set_1; pucch_res_set_1.pucch_res_id_list.emplace_back(pucch_res_id_t{3, 3}); pucch_res_set_1.pucch_res_id_list.emplace_back(pucch_res_id_t{4, 4}); pucch_res_set_1.pucch_res_id_list.emplace_back(pucch_res_id_t{5, 5}); @@ -657,6 +658,15 @@ uplink_config srsran::config_helpers::make_default_ue_uplink_config(const cell_c pucch_cfg.dl_data_to_ul_ack = generate_k1_candidates(*params.tdd_ul_dl_cfg_common, params.min_k1); } + // Compute the max UCI payload per format. + // As per TS 38.231, Section 9.2.1, with PUCCH Format 1, we can have up to 2 HARQ-ACK bits (SR doesn't count as part + // of the payload). + constexpr static unsigned pucch_f1_max_harq_payload = 2U; + pucch_cfg.format_max_payload[pucch_format_to_uint(pucch_format::FORMAT_1)] = pucch_f1_max_harq_payload; + const auto& res_f2 = std::get(res_basic_f2.format_params); + pucch_cfg.format_max_payload[pucch_format_to_uint(pucch_format::FORMAT_2)] = get_pucch_format2_max_payload( + res_f2.nof_prbs, res_f2.nof_symbols, to_max_code_rate_float(pucch_cfg.format_2_common_param.value().max_c_rate)); + // > PUSCH config. ul_config.init_ul_bwp.pusch_cfg.emplace(make_default_pusch_config()); diff --git a/lib/scheduler/config/serving_cell_config_validator.cpp b/lib/scheduler/config/serving_cell_config_validator.cpp index 51578d021c..502062db26 100644 --- a/lib/scheduler/config/serving_cell_config_validator.cpp +++ b/lib/scheduler/config/serving_cell_config_validator.cpp @@ -150,7 +150,8 @@ validator_result srsran::config_validators::validate_pucch_cfg(const serving_cel // Verify that the PUCCH resources IDs of each PUCCH resource set point at a corresponding item in the PUCCH reource // list. VERIFY(pucch_cfg.pucch_res_set.size() >= 2, "At least 2 PUCCH resource sets need to be configured in PUCCH-Config"); - VERIFY(pucch_cfg.pucch_res_set[0].pucch_res_set_id == 0 and pucch_cfg.pucch_res_set[1].pucch_res_set_id == 1, + VERIFY(pucch_cfg.pucch_res_set[0].pucch_res_set_id == pucch_res_set_idx::set_0 and + pucch_cfg.pucch_res_set[1].pucch_res_set_id == pucch_res_set_idx::set_1, "PUCCH resouce sets 0 and 1 are expected to have PUCCH-ResourceSetId 0 and 1, respectively"); VERIFY((not pucch_cfg.pucch_res_set[0].pucch_res_id_list.empty()) and (not pucch_cfg.pucch_res_set[1].pucch_res_id_list.empty()), diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator.h b/lib/scheduler/pucch_scheduling/pucch_allocator.h index 1e5600abeb..28424378c3 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator.h +++ b/lib/scheduler/pucch_scheduling/pucch_allocator.h @@ -36,8 +36,13 @@ struct pucch_uci_bits { /// Number of SR info bits that should have been reported in the removed PUCCH grant. sr_nof_bits sr_bits{sr_nof_bits::no_sr}; /// Number of CSI Part 1 info bits that should have been reported in the removed PUCCH grant. - unsigned csi_part1_bits{0}; + unsigned csi_part1_nof_bits{0}; // TODO: add extra bits for CSI Part 2. + + [[nodiscard]] unsigned get_total_bits() const + { + return harq_ack_nof_bits + sr_nof_bits_to_uint(sr_bits) + csi_part1_nof_bits; + } }; /// PUCCH scheduling interface. @@ -132,7 +137,7 @@ class pucch_allocator /// \param[in] rnti RNTI of the UE. /// \param[in] sl_tx Slot to search PUCCH grants. /// \return Returns true if a PUCCH grant using common PUCCH resource exits. False, otherwise. - virtual bool has_common_pucch_f1_grant(rnti_t rnti, slot_point sl_tx) const = 0; + virtual bool has_common_pucch_grant(rnti_t rnti, slot_point sl_tx) const = 0; }; } // namespace srsran diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp index 3e4c8c935d..dd74b10332 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp +++ b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.cpp @@ -27,6 +27,7 @@ #include "srsran/ran/csi_report/csi_report_on_pucch_helpers.h" #include "srsran/ran/pucch/pucch_info.h" #include "srsran/srslog/srslog.h" +#include ////////////// Helper functions ////////////// @@ -89,6 +90,19 @@ static unsigned get_n_id0_scrambling(const ue_cell_configuration& ue_cell_cfg, u return cell_pci; } +static pucch_resource* get_sr_pucch_res_cfg(const pucch_config& pucch_cfg) +{ + const auto& pucch_res_list = pucch_cfg.pucch_res_list; + const unsigned sr_pucch_res_id = pucch_cfg.sr_res_list[0].pucch_res_id.cell_res_id; + // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. + const auto* res_cfg = + std::find_if(pucch_res_list.begin(), pucch_res_list.end(), [sr_pucch_res_id](const pucch_resource& res) { + return res.res_id.cell_res_id == sr_pucch_res_id; + }); + + return res_cfg != pucch_res_list.end() ? const_cast(&(*res_cfg)) : nullptr; +} + ////////////// Public functions ////////////// pucch_allocator_impl::pucch_allocator_impl(const cell_configuration& cell_cfg_, @@ -110,12 +124,16 @@ std::optional pucch_allocator_impl::alloc_common_pucch_harq_ack_ue(cel const pdcch_dl_information& dci_info) { // Get the slot allocation grid considering the PDSCH delay (k0) and the PUCCH delay wrt PDSCH (k1). - cell_slot_resource_allocator& pucch_slot_alloc = slot_alloc[k0 + k1 + slot_alloc.cfg.ntn_cs_koffset]; + cell_slot_resource_allocator& pucch_slot_alloc = slot_alloc[k0 + k1 + slot_alloc.cfg.ntn_cs_koffset]; + auto& pucch_grants_slot = pucch_grants_alloc_grid[pucch_slot_alloc.slot.to_uint()]; + auto* grants_ue_it = std::find_if(pucch_grants_slot.begin(), + pucch_grants_slot.end(), + [tcrnti](const ue_grants& grants) { return grants.rnti == tcrnti; }); // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. if (pucch_slot_alloc.result.ul.pucchs.size() >= get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size())) or - pucch_common_alloc_grid[slot_alloc[k0 + k1 + slot_alloc.cfg.ntn_cs_koffset].slot.to_uint()].full()) { + (grants_ue_it == pucch_grants_slot.end() and pucch_grants_slot.full())) { return std::nullopt; } @@ -126,15 +144,11 @@ std::optional pucch_allocator_impl::alloc_common_pucch_harq_ack_ue(cel // If there are existing PUCCH grants that are either F2 for CSI or F1 for SR, allocate the PUCCH common grant anyway // without multiplexing it with the existing one. Otherwise, if the existing grant is F1 for HARQ-ACK, do not allocate // on the same slot. - const bool has_existing_pucch_f1_grants = std::find_if(pucch_slot_alloc.result.ul.pucchs.begin(), - pucch_slot_alloc.result.ul.pucchs.end(), - [tcrnti](const pucch_info& pucch) { - return tcrnti == pucch.crnti and - pucch.format == pucch_format::FORMAT_1 and - pucch.format_1.harq_ack_nof_bits != 0; - }) != pucch_slot_alloc.result.ul.pucchs.end(); - if (has_existing_pucch_f1_grants or has_common_pucch_f1_grant(tcrnti, pucch_slot_alloc.slot)) { - logger.debug("tc-rnti={}: PUCCH common not allocated for slot={}. Cause: a PUCCH F1 grant with HARQ-ACK bits " + const bool has_existing_ded_harq_grants = + grants_ue_it != pucch_grants_slot.end() and grants_ue_it->pucch_grants.harq_resource.has_value(); + const bool has_existing_common_grants = grants_ue_it != pucch_grants_slot.end() and grants_ue_it->has_common_pucch; + if (has_existing_ded_harq_grants or has_existing_common_grants) { + logger.debug("tc-rnti={}: PUCCH common not allocated for slot={}. Cause: A PUCCH grant with HARQ-ACK bits " "already exists in the same slot", tcrnti, pucch_slot_alloc.slot); @@ -159,7 +173,11 @@ std::optional pucch_allocator_impl::alloc_common_pucch_harq_ack_ue(cel fill_pucch_harq_common_grant(pucch_info, tcrnti, pucch_res.value()); unsigned pucch_res_indicator = pucch_res.value().pucch_res_indicator; - pucch_common_alloc_grid[slot_alloc[k0 + k1 + slot_alloc.cfg.ntn_cs_koffset].slot.to_uint()].emplace_back(tcrnti); + if (grants_ue_it != pucch_grants_slot.end()) { + grants_ue_it->has_common_pucch = true; + } else { + pucch_grants_slot.emplace_back(ue_grants{.rnti = tcrnti, .has_common_pucch = true}); + } return pucch_res_indicator; } @@ -179,67 +197,62 @@ std::optional pucch_allocator_impl::alloc_common_and_ded_harq_res(cell return std::nullopt; } - existing_pucch_grants existing_grants = - get_existing_pucch_grants(pucch_slot_alloc.result.ul.pucchs, rnti, pucch_slot); + auto& pucch_grants = pucch_grants_alloc_grid[pucch_slot.to_uint()]; + auto* ue_grants_it = std::find_if( + pucch_grants.begin(), pucch_grants.end(), [rnti](const ue_grants& grants) { return grants.rnti == rnti; }); // NOTE: this function is called by the UE fallback scheduler, which iterates over several PDCCH slots and different // k1 values. It can happen that the UE fallback scheduler attempts to allocate a grant on a slot where it previously // allocated another grant. If that is the case, quit the PUCCH allocation. - if (existing_grants.format1_harq_common_grant != nullptr or existing_grants.format1_harq_grant != nullptr) { - logger.debug("rnti={}: PUCCH HARQ-ACK for slot={} not allocated. Cause: another F1 PUCCH grant with HARQ-ACK bits " + const bool has_existing_ded_harq_grants = + ue_grants_it != pucch_grants.end() and ue_grants_it->pucch_grants.harq_resource.has_value(); + const bool has_existing_common_grants = ue_grants_it != pucch_grants.end() and ue_grants_it->has_common_pucch; + if (has_existing_ded_harq_grants or has_existing_common_grants) { + logger.debug("rnti={}: PUCCH HARQ-ACK for slot={} not allocated. Cause: another PUCCH grant with HARQ-ACK bits " "already exists for the same UE for the same slot", rnti, pucch_slot); return std::nullopt; } - srsran_assert(not(existing_grants.format1_sr_grant != nullptr and existing_grants.format2_grant != nullptr), - "It is expected that there are either no grants, or at most 1 PUCCH grant (F1 SR and F2 for CSI)"); - - // If a F2 PUCCH grant with HARQ-ACK bits exists, then there must be as well a common PUCCH F1 grant (with 1 HARQ-ACK - // bit); in that case, the function should have returned already in the previous "if" check. - srsran_assert(existing_grants.format2_grant == nullptr or - existing_grants.format2_grant->format_2.harq_ack_nof_bits == 0, - "If the existing PUCCH grant is F2, it must be for CSI or CSI/SR reporting only"); + srsran_assert(not(ue_grants_it != pucch_grants.end() and ue_grants_it->pucch_grants.sr_resource.has_value() and + ue_grants_it->pucch_grants.csi_resource.has_value()), + "It is expected that there are either no grants, or at most 1 PUCCH grant (SR grant or CSI grant)"); - pucch_info* current_existing_grant = nullptr; - // If there are no existing grants or if the existing one is F1 (for SR), we need to add 2 additional PUCCH grants: 1 - // on common resources and 1 on dedicated resources (for HARQ-ACK bit). + // If there are no existing grants or if the existing one is for SR with Format 1, we need to add 2 additional PUCCH + // grants: 1 on common resources and 1 on dedicated resources (for HARQ-ACK bit). + // In the case of CSI, the number of PUCCH additional grants depends on the PUCCH resources configuration, but it will + // never be more than 2. The exact number cannot be known at this point. unsigned extra_pucch_grants_to_allocate = 2; - if (existing_grants.format1_sr_grant != nullptr) { - current_existing_grant = existing_grants.format1_sr_grant; - } else if (existing_grants.format2_grant != nullptr) { - current_existing_grant = existing_grants.format2_grant; - // If the existing PUCCH grant is F2 (for CSI), we need to allocate only 1 additional PUCCH grant on common - // resource; the CSI + additional HARQ-ACK bit will be allocated on a F2 resource which will replace the existing F2 - // resource for CSI. - extra_pucch_grants_to_allocate = 1; - } // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. if (pucch_slot_alloc.result.ul.pucchs.size() + extra_pucch_grants_to_allocate > pucch_slot_alloc.result.ul.pucchs.capacity() or pucch_slot_alloc.result.ul.pucchs.size() + extra_pucch_grants_to_allocate > get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size())) or - pucch_common_alloc_grid[pucch_slot.to_uint()].full()) { + (ue_grants_it == pucch_grants.end() and pucch_grants.full())) { return std::nullopt; } + const bool new_ue_grant_added = ue_grants_it == pucch_grants.end(); + + // Keep track of whether a new ue_grant has been added; in case the function fails, remove it before exiting. + if (new_ue_grant_added) { + pucch_grants.emplace_back(ue_grants{.rnti = rnti}); + } + // Find a couple of PUCCH resources (1 common, 1 dedicated) that are (i) are available and that (ii) have the same // PUCCH resource indicator. - std::optional harq_res_cfgs = - find_common_and_ded_harq_res_available(pucch_slot_alloc, current_existing_grant, rnti, ue_cell_cfg, dci_info.ctx); + std::optional pucch_common_info = + find_common_and_ded_harq_res_available(pucch_slot_alloc, *ue_grants_it, rnti, ue_cell_cfg, dci_info.ctx); + + if (pucch_common_info.has_value()) { + compute_pucch_common_params_and_alloc(pucch_slot_alloc, rnti, pucch_common_info.value()); + return pucch_common_info.value().pucch_res_indicator; + } - // If both PUCCH common and dedicated resources are available, allocate them. If it is not possible to allocate the - // dedicated resource (this can only happen if the UCI bits exceeds the PUCCH F2 capacity), then the function will - // abort the allocation. The caller will attempt a new allocation in the next UL slot. - if (harq_res_cfgs.has_value()) { - return exec_common_and_ded_res_alloc(pucch_slot_alloc, - current_existing_grant, - rnti, - ue_cell_cfg, - harq_res_cfgs.value().pucch_common_info, - harq_res_cfgs.value().pucch_ded_cfg); + if (new_ue_grant_added) { + pucch_grants.pop_back(); } logger.debug( @@ -256,91 +269,47 @@ std::optional pucch_allocator_impl::alloc_ded_pucch_harq_ack_ue(cell_r unsigned k0, unsigned k1) { - // TS 38.213, Section 9.2.3, explains the UE's procedure to multiplex HARQ-ACK reporting of multiple slot and for - // different cells. - // "The PUCCH resource determination is based on a PUCCH resource indicator field [5, TS 38.212] in a last DCI - // format 1_0 or DCI format 1_1, among the DCI formats 1_0 or DCI formats 1_1 that have a value of a - // PDSCH-to-HARQ_feedback timing indicator field indicating a same slot for the PUCCH transmission, that the UE - // detects and for which the UE transmits corresponding HARQ-ACK information in the PUCCH where, for PUCCH resource - // determination, detected DCI formats are first indexed in an ascending order across serving cells indexes for a - // same PDCCH monitoring occasion and are then indexed in an ascending order across PDCCH monitoring occasion - // indexes". As a result of this, and depending on whether there is any scheduled SRs, the PUCCH allocator can - // either allocate a new PUCCH grant or update an existing one by changing the number of HARQ-ACK bits to be - // reported. - // NOTE: This function does not check whether there are PUSCH grants allocated for the same UE. The check needs to // be performed by the caller. // Get the slot allocation grid considering the PDSCH delay (k0) and the PUCCH delay wrt PDSCH (k1). cell_slot_resource_allocator& pucch_slot_alloc = res_alloc[k0 + k1 + res_alloc.cfg.ntn_cs_koffset]; - auto& pucchs = pucch_slot_alloc.result.ul.pucchs; - - // Retrieve the existing PUCCH grants. - const existing_pucch_grants existing_grants = get_existing_pucch_grants(pucchs, crnti, pucch_slot_alloc.slot); - - // [Implementation-defined] Multiplexing of common and dedicated PUCCH grants are not yet supported. - if (existing_grants.format1_harq_common_grant != nullptr) { - logger.debug( - "rnti={}: PUCCH HARQ-ACK for slot={} not allocated. Cause: Multiplexing of common and dedicated PUCCH grants " - "are not supported", - crnti, - pucch_slot_alloc.slot); - return std::nullopt; - } + // The PUCCH allocation may result in a temporary reservation of PUCCH resources, which need to be released in case of + // failure or in case the multiplexing results in a different final PUCCH resource. If we don't reset the previous + // record, we could release the resources that have been allocated for other UEs of allocated for this UE in a + // different slot. + resource_manager.reset_latest_reserved_res_tracker(); - const unsigned harq_ack_bits_increment = 1; + slot_point sl_ack = pucch_slot_alloc.slot; - // Case 1) If there is a PUCCH format 2 grant, update it. - if (existing_grants.format2_grant != nullptr) { - // Case 1-A) If the allocated resource is the one specific for CSI, change resource and allocate grant. - if (existing_grants.format2_grant->format_2.harq_ack_nof_bits == 0 and - existing_grants.format2_grant->format_2.csi_part1_bits > 0) { - return change_format2_resource( - pucch_slot_alloc, *existing_grants.format2_grant, crnti, ue_cell_cfg, harq_ack_bits_increment, {}); - } + auto& ue_pucchs = pucch_grants_alloc_grid[sl_ack.to_uint()]; - // Case 1-B) If the allocated resource is for HARQ too, just update the resource. - return add_harq_bits_to_harq_f2_grant( - *existing_grants.format2_grant, pucch_slot_alloc.slot, crnti, ue_cell_cfg, harq_ack_bits_increment); - } + auto* existing_grant_it = + std::find_if(ue_pucchs.begin(), ue_pucchs.end(), [crnti](const ue_grants& pucch) { return pucch.rnti == crnti; }); - // Case 2) An HARQ Format 1 is already scheduled. Update the existing HARQ grant and the SR grant, if present. - if (existing_grants.format1_harq_grant != nullptr) { - if (existing_grants.format1_sr_grant != nullptr) { - srsran_sanity_check(existing_grants.format1_harq_grant->format_1.harq_ack_nof_bits == - existing_grants.format1_sr_grant->format_1.harq_ack_nof_bits, - "Mismatch HARQ bits mismatch between SR and HARQ grants"); - } + // Allocate PUCCH HARQ-ACK grant depending on whether there existing PUCCH grants. + if (existing_grant_it != ue_pucchs.end()) { + pucch_uci_bits new_bits = existing_grant_it->pucch_grants.get_uci_bits(); + ++new_bits.harq_ack_nof_bits; - // Case 2-A) If the existing grant is for at least 2 HARQ-ACK bits, then the PUCCH needs to be converted to - // format 2. - if (existing_grants.format1_harq_grant->format_1.harq_ack_nof_bits > 1) { - return convert_to_format2_harq(pucch_slot_alloc, - *existing_grants.format1_harq_grant, - existing_grants.format1_sr_grant, - crnti, - ue_cell_cfg, - harq_ack_bits_increment); + std::optional pucch_res_ind = + multiplex_and_allocate_pucch(pucch_slot_alloc, new_bits, *existing_grant_it, ue_cell_cfg); + if (not pucch_res_ind) { + resource_manager.cancel_last_ue_res_reservations(sl_ack, crnti, ue_cell_cfg); } - - // Case 2-B) - return add_harq_ack_bit_to_format1_grant( - *existing_grants.format1_harq_grant, - existing_grants.format1_sr_grant, - crnti, - pucch_slot_alloc.slot, - ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + return pucch_res_ind; + } else { + return allocate_harq_grant(pucch_slot_alloc, crnti, ue_cell_cfg); } - - // Case C) If there is no existing HARQ_ACK grant, allocate a new one and update the SR grant, if present. - return allocate_new_format1_harq_grant(pucch_slot_alloc, crnti, ue_cell_cfg, existing_grants.format1_sr_grant); } void pucch_allocator_impl::pucch_allocate_sr_opportunity(cell_slot_resource_allocator& pucch_slot_alloc, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg) { + const slot_point sl_tx = pucch_slot_alloc.slot; + // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. if (pucch_slot_alloc.result.ul.pucchs.size() >= get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { @@ -350,12 +319,32 @@ void pucch_allocator_impl::pucch_allocate_sr_opportunity(cell_slot_resource_allo return; } - const existing_pucch_grants existing_grants = - get_existing_pucch_grants(pucch_slot_alloc.result.ul.pucchs, crnti, pucch_slot_alloc.slot); - srsran_assert( - existing_grants.format1_harq_grant == nullptr and existing_grants.format1_sr_grant == nullptr and - existing_grants.format2_grant == nullptr, - "The SR is the first dedicated PUCCH grant that is expected to be allocated; no grants expected at this point."); + if (pucch_grants_alloc_grid[sl_tx.to_uint()].full()) { + logger.info("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH allocator grant list is full", + crnti, + pucch_slot_alloc.slot); + return; + } + + const auto* existing_grant_it = std::find_if(pucch_grants_alloc_grid[sl_tx.to_uint()].begin(), + pucch_grants_alloc_grid[sl_tx.to_uint()].end(), + [crnti](const ue_grants& ue) { return ue.rnti == crnti; }); + if (existing_grant_it != pucch_grants_alloc_grid[sl_tx.to_uint()].end()) { + // Allocation of dedicated + common resources are handled by allocating PUCCH common on existing SR, not the other + // way around. If the function enters the path, it means it too early to start scheduling the SR. + if (existing_grant_it->has_common_pucch) { + logger.info("rnti={}: SR occasion allocation for slot={} skipped. Cause: existing PUCCH common grant", + crnti, + pucch_slot_alloc.slot); + return; + } + // NOTE: This check can be removed in future refactors, it's not required by the SR allocator. At the moment, we + // schedule the SRs before anything else, therefore we don't expect to find any existing PUCCH grant. + if (not existing_grant_it->pucch_grants.is_emtpy()) { + logger.info("No PUCCH grants are expected before allocating a new SR grant", crnti, pucch_slot_alloc.slot); + return; + } + } // Get the index of the PUCCH resource to be used for SR. const pucch_resource* pucch_sr_res = resource_manager.reserve_sr_res_available( @@ -367,13 +356,25 @@ void pucch_allocator_impl::pucch_allocate_sr_opportunity(cell_slot_resource_allo return; } + const pucch_format format = pucch_sr_res->format; + // Allocate PUCCH SR grant only. const unsigned harq_ack_bits_increment = 0U; - fill_pucch_ded_format1_grant(pucch_slot_alloc.result.ul.pucchs.emplace_back(), - crnti, - *pucch_sr_res, - harq_ack_bits_increment, - sr_nof_bits::one); + if (format == pucch_format::FORMAT_0) { + srsran_assertion_failure("PUCCH Format 0 is not yet supported for SR"); + } else { + fill_pucch_ded_format1_grant(pucch_slot_alloc.result.ul.pucchs.emplace_back(), + crnti, + *pucch_sr_res, + harq_ack_bits_increment, + sr_nof_bits::one); + } + + // Save the info in the scheduler list of PUCCH grants. + auto& sr_pucch_grant = pucch_grants_alloc_grid[sl_tx.to_uint()].emplace_back(ue_grants{.rnti = crnti}); + sr_pucch_grant.pucch_grants.sr_resource.emplace( + pucch_grant{.type = pucch_grant_type::sr, .pucch_res_cfg = pucch_sr_res}); + sr_pucch_grant.pucch_grants.sr_resource.value().bits.sr_bits = sr_nof_bits::one; } void pucch_allocator_impl::pucch_allocate_csi_opportunity(cell_slot_resource_allocator& pucch_slot_alloc, @@ -381,17 +382,51 @@ void pucch_allocator_impl::pucch_allocate_csi_opportunity(cell_slot_resource_all const ue_cell_configuration& ue_cell_cfg, unsigned csi_part1_nof_bits) { - auto& pucchs = pucch_slot_alloc.result.ul.pucchs; + const slot_point sl_tx = pucch_slot_alloc.slot; + + // The PUCCH allocation may result in a temporary reservation of PUCCH resources, which need to be released in case of + // failure or in case the multiplexing results in a different final PUCCH resource. If we don't reset the previous + // record, we could release the resources that have been allocated for other UEs of allocated for this UE in a + // different slot. + resource_manager.reset_latest_reserved_res_tracker(); + + // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. + if (pucch_slot_alloc.result.ul.pucchs.size() >= + get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { + logger.warning("rnti={}: CSI occasion allocation for slot={} skipped. Cause: max number of UL grants reached", + crnti, + pucch_slot_alloc.slot); + return; + } + + auto* existing_grant_it = std::find_if(pucch_grants_alloc_grid[sl_tx.to_uint()].begin(), + pucch_grants_alloc_grid[sl_tx.to_uint()].end(), + [crnti](const ue_grants& ue) { return ue.rnti == crnti; }); - const existing_pucch_grants existing_grants = get_existing_pucch_grants(pucchs, crnti, pucch_slot_alloc.slot); - srsran_assert(existing_grants.format2_grant == nullptr and existing_grants.format1_harq_grant == nullptr, - "The CSI is the first dedicated PUCCH grant that is expected to be allocated."); + // Handle case of no existing PUCCH grant. + if (existing_grant_it == pucch_grants_alloc_grid[sl_tx.to_uint()].end()) { + allocate_csi_grant(pucch_slot_alloc, crnti, ue_cell_cfg, csi_part1_nof_bits); + return; + } - if (existing_grants.format1_sr_grant != nullptr) { - convert_to_format2_csi(pucch_slot_alloc, *existing_grants.format1_sr_grant, crnti, ue_cell_cfg, csi_part1_nof_bits); + if (existing_grant_it != pucch_grants_alloc_grid[sl_tx.to_uint()].end() and existing_grant_it->has_common_pucch) { + // Allocation of dedicated + common resources are handled by allocating PUCCH common on existing CSI, not the other + // way around. If the function enters the path, it means it too early to start scheduling the CSI. + logger.info("rnti={}: CSI occasion allocation for slot={} skipped. Cause: There is a PUCCH common grant" + "allocated at this slot", + crnti, + pucch_slot_alloc.slot); + return; } - allocate_new_csi_grant(pucch_slot_alloc, crnti, ue_cell_cfg, csi_part1_nof_bits); + // Handle case of existing PUCCHs with possible multiplexing. + pucch_uci_bits bits_for_uci = existing_grant_it->pucch_grants.get_uci_bits(); + srsran_assert(bits_for_uci.csi_part1_nof_bits == 0, "PUCCH grant for CSI has already been allocated"); + bits_for_uci.csi_part1_nof_bits = csi_part1_nof_bits; + auto alloc_outcome = multiplex_and_allocate_pucch(pucch_slot_alloc, bits_for_uci, *existing_grant_it, ue_cell_cfg); + if (not alloc_outcome.has_value()) { + resource_manager.cancel_last_ue_res_reservations(sl_tx, crnti, ue_cell_cfg); + } } pucch_uci_bits pucch_allocator_impl::remove_ue_uci_from_pucch(cell_slot_resource_allocator& slot_alloc, @@ -400,49 +435,48 @@ pucch_uci_bits pucch_allocator_impl::remove_ue_uci_from_pucch(cell_slot_resource { pucch_uci_bits removed_uci_info; - auto& pucchs = slot_alloc.result.ul.pucchs; + slot_point sl_uci = slot_alloc.slot; + const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); - // Remove HARQ-ACK grant first. - auto* it = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and - ((pucch.format == pucch_format::FORMAT_1 and pucch.format_1.sr_bits == sr_nof_bits::no_sr and - pucch.format_1.harq_ack_nof_bits > 0) or - (pucch.format == pucch_format::FORMAT_2)); - }); + // Get the PUCCH grants for the slot. + auto& ue_pucchs = pucch_grants_alloc_grid[sl_uci.to_uint()]; - const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); + auto* grant_it = + std::find_if(ue_pucchs.begin(), ue_pucchs.end(), [crnti](ue_grants& grants) { return grants.rnti == crnti; }); - if (it != pucchs.end()) { - // Search for Format 2 first; if present, then remove only that resource and exit. - if (it->format == pucch_format::FORMAT_2) { - removed_uci_info.harq_ack_nof_bits = it->format_2.harq_ack_nof_bits; - removed_uci_info.sr_bits = it->format_2.sr_bits; - removed_uci_info.csi_part1_bits = it->format_2.csi_part1_bits; - pucchs.erase(it); - resource_manager.release_harq_f2_resource(slot_alloc.slot, crnti, pucch_cfg); - if (removed_uci_info.csi_part1_bits > 0) { - resource_manager.release_csi_resource(slot_alloc.slot, crnti, ue_cell_cfg); + // Get the bits from the PUCCH grants and remove the item from the list. + if (grant_it != ue_pucchs.end()) { + // Get the UCI bits. + removed_uci_info = grant_it->pucch_grants.get_uci_bits(); + + // Release the resources used in the PUCCH resource manager first. + if (grant_it->pucch_grants.harq_resource.has_value()) { + if (grant_it->pucch_grants.harq_resource.value().harq_id.pucch_set_idx == pucch_res_set_idx::set_0) { + resource_manager.release_harq_set_0_resource(slot_alloc.slot, crnti, pucch_cfg); + } else { + resource_manager.release_harq_set_1_resource(slot_alloc.slot, crnti, pucch_cfg); } - // If there is a PUCCH resource Format 2, then no Format 1 should be present. - return removed_uci_info; } - // Proceed with Format 1. - // Only remove HARQ-ACK grant, handle SR grant separately. - removed_uci_info.harq_ack_nof_bits = it->format_1.harq_ack_nof_bits; - pucchs.erase(it); - resource_manager.release_harq_f1_resource(slot_alloc.slot, crnti, pucch_cfg); + if (grant_it->pucch_grants.sr_resource.has_value()) { + resource_manager.release_sr_resource(slot_alloc.slot, crnti, pucch_cfg); + } + if (grant_it->pucch_grants.csi_resource.has_value()) { + resource_manager.release_csi_resource(slot_alloc.slot, crnti, ue_cell_cfg); + } + + // TODO: optimize this by swapping the it with the last of the list. + ue_pucchs.erase(grant_it); } - // Remove SR grant, if any. - it = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_1 and - pucch.format_1.sr_bits == sr_nof_bits::one; - }); + auto& pucch_pdus = slot_alloc.result.ul.pucchs; - if (it != pucchs.end()) { - removed_uci_info.sr_bits = it->format_1.sr_bits; - pucchs.erase(it); - resource_manager.release_sr_resource(slot_alloc.slot, crnti, pucch_cfg); + for (auto* pdu_it = pucch_pdus.begin(); pdu_it != pucch_pdus.end();) { + if (pdu_it->crnti == crnti) { + // TODO: optimize this by swapping the it with the last of the list. + pdu_it = pucch_pdus.erase(pdu_it); + } else { + ++pdu_it; + } } return removed_uci_info; @@ -458,8 +492,272 @@ void pucch_allocator_impl::slot_indication(slot_point sl_tx) resource_manager.slot_indication(sl_tx); - // Clear previous slot PUCCH common allocations. - pucch_common_alloc_grid[(sl_tx - 1).to_uint()].clear(); + // Clear previous slot PUCCH grants allocations. + pucch_grants_alloc_grid[(sl_tx - 1).to_uint()].clear(); +} + +////////////// Sub-class definitions ////////////// + +ofdm_symbol_range pucch_allocator_impl::pucch_grant::get_symbols() const +{ + if (pucch_res_cfg == nullptr) { + return ofdm_symbol_range{NOF_OFDM_SYM_PER_SLOT_NORMAL_CP + 1, NOF_OFDM_SYM_PER_SLOT_NORMAL_CP + 1}; + } + + switch (pucch_res_cfg->format) { + case pucch_format::FORMAT_0: { + const auto& f0 = std::get(pucch_res_cfg->format_params); + return ofdm_symbol_range{f0.starting_sym_idx, f0.starting_sym_idx + f0.nof_symbols}; + } + case pucch_format::FORMAT_1: { + const auto& f1 = std::get(pucch_res_cfg->format_params); + return ofdm_symbol_range{f1.starting_sym_idx, f1.starting_sym_idx + f1.nof_symbols}; + } + case pucch_format::FORMAT_2: { + const auto& f2 = std::get(pucch_res_cfg->format_params); + return ofdm_symbol_range{f2.starting_sym_idx, f2.starting_sym_idx + f2.nof_symbols}; + } + default: + return ofdm_symbol_range{NOF_OFDM_SYM_PER_SLOT_NORMAL_CP + 1, NOF_OFDM_SYM_PER_SLOT_NORMAL_CP + 1}; + } +} + +pucch_uci_bits pucch_allocator_impl::pucch_grant_list::get_uci_bits() const +{ + pucch_uci_bits bits; + + // If there is an SR grant, then the SR bits are retrieved from it. Otherwise, the SR bits are retrieved from the + // HARQ or CSI grants; as HARQ and CSI grants can separate, we need to take the SR value that is non-zero, if any. + if (sr_resource.has_value()) { + bits.sr_bits = sr_resource->bits.sr_bits; + } else { + if (harq_resource.has_value()) { + bits.sr_bits = harq_resource->bits.sr_bits; + } + if (csi_resource.has_value() and bits.sr_bits == sr_nof_bits::no_sr) { + bits.sr_bits = csi_resource->bits.sr_bits; + } + } + + // If there is a CSI grant, then the CSI part 1 bits are retrieved from it. Otherwise, the HARQ bits are retrieved + // from the HARQ grant (it can be zero). + if (csi_resource.has_value()) { + bits.csi_part1_nof_bits = csi_resource.value().bits.csi_part1_nof_bits; + } else if (harq_resource.has_value()) { + bits.csi_part1_nof_bits = harq_resource.value().bits.csi_part1_nof_bits; + } + + // If there is an HARQ grant, then the HARQ bits are retrieved from it. Otherwise, they are retrieved from the + // SR or CSI grants; as SR and CSI grants can separate, we need to take the HARQ bits value that is non-zero, if any. + if (harq_resource.has_value()) { + bits.harq_ack_nof_bits = harq_resource.value().bits.harq_ack_nof_bits; + } else { + if (sr_resource.has_value()) { + bits.harq_ack_nof_bits = sr_resource.value().bits.harq_ack_nof_bits; + } + if (csi_resource.has_value() and bits.harq_ack_nof_bits == 0U) { + bits.harq_ack_nof_bits = csi_resource.value().bits.harq_ack_nof_bits; + } + } + + return bits; +} + +bool pucch_allocator_impl::pucch_grant_list::is_emtpy() const +{ + return not sr_resource.has_value() and not harq_resource.has_value() and not csi_resource.has_value(); +} + +// Contains the existing PUCCH grants currently allocated to a given UE. +class existing_pucch_pdus_handler +{ +public: + existing_pucch_pdus_handler(rnti_t crnti, span pucchs, const pucch_resource* pucch_res_cfg); + + static bool sr_id_match(const pucch_resource& pucch_res_cfg_lhs, const pucch_info& rhs) + { + const auto& f1_cfg = std::get(pucch_res_cfg_lhs.format_params); + const bool prb_match = + pucch_res_cfg_lhs.starting_prb == rhs.resources.prbs.start() and + ((not pucch_res_cfg_lhs.second_hop_prb.has_value() and rhs.resources.second_hop_prbs.empty()) or + (pucch_res_cfg_lhs.second_hop_prb.has_value() and pucch_res_cfg_lhs.second_hop_prb.value() and + pucch_res_cfg_lhs.second_hop_prb == rhs.resources.second_hop_prbs.start())); + const bool symb_match = f1_cfg.starting_sym_idx == rhs.resources.symbols.start() and + f1_cfg.nof_symbols == rhs.resources.symbols.length(); + return prb_match && symb_match && f1_cfg.initial_cyclic_shift == rhs.format_1.initial_cyclic_shift && + f1_cfg.time_domain_occ == rhs.format_1.time_domain_occ; + } + + [[nodiscard]] bool is_empty() const { return pdus_cnt == 0; } + [[nodiscard]] unsigned get_nof_unallocated_pdu() const { return pdus_cnt; } + pucch_info* get_next_grant(static_vector& pucchs); + void remove_unused_pdus(static_vector& pucchs, rnti_t rnti) const; + void update_sr_pdu_bits(sr_nof_bits sr_bits, unsigned harq_ack_bits); + void update_csi_pdu_bits(unsigned csi_part1_bits, sr_nof_bits sr_bits); + void update_harq_pdu_bits(unsigned harq_ack_bits, sr_nof_bits sr_bits, unsigned csi_part1_bits); + + pucch_info* sr_pdu{nullptr}; + pucch_info* harq_pdu{nullptr}; + pucch_info* csi_pdu{nullptr}; + +private: + unsigned pdus_cnt = 0; + unsigned pdu_id = 0; +}; + +existing_pucch_pdus_handler::existing_pucch_pdus_handler(rnti_t crnti, + span pucchs, + const pucch_resource* pucch_res_cfg) +{ + pdu_id = 0; + for (auto& pucch : pucchs) { + if (pucch.crnti == crnti and not pucch.pdu_context.is_common) { + pucch.pdu_context.id = MAX_PUCCH_PDUS_PER_SLOT; + + if (pucch.format == srsran::pucch_format::FORMAT_0) { + // With Format 0, when there are both HARQ bits and SR bits, we only use the HARQ-ACK resource; the only + // case when the SR PUCCH F0 is used is when there are only SR bits. + if (pucch.format_0.sr_bits != sr_nof_bits::one and pucch.format_0.harq_ack_nof_bits == 0U) { + sr_pdu = &pucch; + ++pdus_cnt; + } else if (pucch.format_0.harq_ack_nof_bits != 0U and pucch.format_0.harq_ack_nof_bits <= 2U) { + harq_pdu = &pucch; + ++pdus_cnt; + } else { + srsran_assertion_failure("Invalid HARQ/SR bits for PUCCH Format 0"); + } + } + + else if (pucch.format == srsran::pucch_format::FORMAT_1) { + if (pucch.format_1.sr_bits == sr_nof_bits::one and pucch_res_cfg != nullptr and + sr_id_match(*pucch_res_cfg, pucch)) { + sr_pdu = &pucch; + } else { + harq_pdu = &pucch; + } + ++pdus_cnt; + } + + else if (pucch.format == srsran::pucch_format::FORMAT_2) { + if (pucch.format_2.csi_part1_bits != 0U and pucch.format_2.harq_ack_nof_bits == 0U) { + csi_pdu = &pucch; + } else { + harq_pdu = &pucch; + } + ++pdus_cnt; + } + } + } +} + +pucch_info* existing_pucch_pdus_handler::get_next_grant(static_vector& pucchs) +{ + if (is_empty()) { + srsran_assert(not pucchs.full(), "PUCCH grants list is full"); + auto* new_pdu = &pucchs.emplace_back(); + new_pdu->pdu_context.id = pdu_id++; + return new_pdu; + } + pucch_info* ret_grant = nullptr; + if (csi_pdu != nullptr) { + ret_grant = csi_pdu; + ret_grant->pdu_context.id = pdu_id++; + --pdus_cnt; + } else if (sr_pdu != nullptr) { + ret_grant = sr_pdu; + ret_grant->pdu_context.id = pdu_id++; + --pdus_cnt; + } else if (harq_pdu != nullptr) { + ret_grant = harq_pdu; + ret_grant->pdu_context.id = pdu_id++; + --pdus_cnt; + } + return ret_grant; +} + +void existing_pucch_pdus_handler::remove_unused_pdus(static_vector& pucchs, + rnti_t rnti) const +{ + if (pdus_cnt == 0) { + return; + } + auto* it = pucchs.begin(); + while (it != pucchs.end()) { + if (it->crnti == rnti and not it->pdu_context.is_common and it->pdu_context.id >= MAX_PUCCH_PDUS_PER_SLOT) { + it = pucchs.erase(it); + } else { + ++it; + } + } +}; + +void existing_pucch_pdus_handler::update_sr_pdu_bits(sr_nof_bits sr_bits, unsigned harq_ack_bits) +{ + if (sr_pdu == nullptr) { + return; + } + if (sr_pdu->format == pucch_format::FORMAT_0) { + sr_pdu->format_0.sr_bits = sr_bits; + sr_pdu->format_0.harq_ack_nof_bits = harq_ack_bits; + sr_pdu->pdu_context.id = pdu_id++; + // Once the grant is updated, set the pointer to null, as we don't want to process this again. + sr_pdu = nullptr; + --pdus_cnt; + } else if (sr_pdu->format == pucch_format::FORMAT_1) { + sr_pdu->format_1.sr_bits = sr_bits; + sr_pdu->format_1.harq_ack_nof_bits = harq_ack_bits; + sr_pdu->pdu_context.id = pdu_id++; + // Once the grant is updated, set the pointer to null, as we don't want to process this again. + sr_pdu = nullptr; + --pdus_cnt; + } else { + srsran_assertion_failure("Only PUCCH Format 0 or 1 can be used for SR grant"); + } +} + +void existing_pucch_pdus_handler::update_csi_pdu_bits(unsigned csi_part1_bits, sr_nof_bits sr_bits) +{ + if (csi_pdu->format == pucch_format::FORMAT_2) { + csi_pdu->format_2.csi_part1_bits = csi_part1_bits; + csi_pdu->format_2.sr_bits = sr_bits; + csi_pdu->pdu_context.id = pdu_id++; + // Once the grant is updated, set the pointer to null, as we don't want to process this again. + csi_pdu = nullptr; + --pdus_cnt; + } else { + srsran_assertion_failure("Only PUCCH Format 2 currently supported for CSI grant"); + } +} + +void existing_pucch_pdus_handler::update_harq_pdu_bits(unsigned harq_ack_bits, + sr_nof_bits sr_bits, + unsigned csi_part1_bits) +{ + if (harq_pdu->format == pucch_format::FORMAT_0) { + harq_pdu->format_0.harq_ack_nof_bits = harq_ack_bits; + harq_pdu->format_0.sr_bits = sr_bits; + harq_pdu->pdu_context.id = pdu_id++; + // Once the grant is updated, set the pointer to null, as we don't want to process this again. + harq_pdu = nullptr; + --pdus_cnt; + } else if (harq_pdu->format == pucch_format::FORMAT_1) { + harq_pdu->format_1.harq_ack_nof_bits = harq_ack_bits; + harq_pdu->format_1.sr_bits = sr_bits; + harq_pdu->pdu_context.id = pdu_id++; + // Once the grant is updated, set the pointer to null, as we don't want to process this again. + harq_pdu = nullptr; + --pdus_cnt; + } else if (harq_pdu->format == pucch_format::FORMAT_2) { + harq_pdu->format_2.harq_ack_nof_bits = harq_ack_bits; + harq_pdu->format_2.sr_bits = sr_bits; + harq_pdu->format_2.csi_part1_bits = csi_part1_bits; + harq_pdu->pdu_context.id = pdu_id++; + // Once the grant is updated, set the pointer to null, as we don't want to process this again. + harq_pdu = nullptr; + --pdus_cnt; + } else { + srsran_assertion_failure("Only PUCCH Format 0, 1, and 2 currently supported"); + } } ////////////// Private functions ////////////// @@ -576,59 +874,73 @@ pucch_allocator_impl::alloc_pucch_common_res_harq(cell_slot_resource_allocator& return std::nullopt; } -std::optional pucch_allocator_impl::exec_common_and_ded_res_alloc(cell_slot_resource_allocator& pucch_alloc, - pucch_info* existing_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - pucch_res_alloc_cfg common_res_cfg, - const pucch_resource& ded_res_cfg) +void pucch_allocator_impl::compute_pucch_common_params_and_alloc(cell_slot_resource_allocator& pucch_alloc, + rnti_t rnti, + pucch_common_params pucch_params) { - const unsigned pucch_res_indicator = common_res_cfg.pucch_res_indicator; - const unsigned HARQ_BITS_IN_NEW_PUCCH_GRANT = 1; + // Get the parameter N_bwp_size, which is the Initial UL BWP size in PRBs, as per TS 38.213, Section 9.2.1. + const unsigned size_ul_bwp = cell_cfg.ul_cfg_common.init_ul_bwp.generic_params.crbs.length(); - // Allocate the dedicated PUCCH HARQ-ACK resource first; this is because the allocation can may and, in that case, we - // can quit the allocation without the need to remove the PUCCH common grant. - if (existing_grant == nullptr) { - pucch_info& pucch_pdu = pucch_alloc.result.ul.pucchs.emplace_back(); - fill_pucch_ded_format1_grant(pucch_pdu, rnti, ded_res_cfg, HARQ_BITS_IN_NEW_PUCCH_GRANT, sr_nof_bits::no_sr); - } else if (existing_grant->format == pucch_format::FORMAT_1) { - // Update the HARQ-ACK bits in the PUCCH resource for SR. - ++existing_grant->format_1.harq_ack_nof_bits; - - // Allocate the new grant on ded. PUCCH F1 resource for HARQ-ACK. - pucch_info& pucch_pdu = pucch_alloc.result.ul.pucchs.emplace_back(); - fill_pucch_ded_format1_grant(pucch_pdu, rnti, ded_res_cfg, HARQ_BITS_IN_NEW_PUCCH_GRANT, sr_nof_bits::no_sr); - } else { - // Change the existing PUCCH F2 grant for CSI into one for HARQ-ACK bits, if available. - std::optional result = change_format2_resource( - pucch_alloc, - *existing_grant, - rnti, - ue_cell_cfg, - HARQ_BITS_IN_NEW_PUCCH_GRANT, - pucch_harq_resource_alloc_record{.pucch_res = &ded_res_cfg, .pucch_res_indicator = pucch_res_indicator}); - if (not result.has_value()) { - return std::nullopt; - } - } + // Get PUCCH common resource config from Table 9.2.1-1, TS 38.213. + pucch_default_resource pucch_res = get_pucch_default_resource( + cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->pucch_resource_common, size_ul_bwp); - // Fill scheduler output. - pucch_info& pucch_info = pucch_alloc.result.ul.pucchs.emplace_back(); - fill_pucch_harq_common_grant(pucch_info, rnti, common_res_cfg); + // Compute the PUCCH resource common configuration parameters. - // Allocate common HARQ-ACK resource. - pucch_alloc.ul_res_grid.fill(common_res_cfg.first_hop_res); - pucch_alloc.ul_res_grid.fill(common_res_cfg.second_hop_res); + // As per TS 38.211, Section 6.3.2.1, the first floor(N_symb_PUCCH/2) are for the first hop, the remaining ones for + // the second hop. + const ofdm_symbol_range first_hop_symbols{pucch_res.first_symbol_index, + pucch_res.first_symbol_index + pucch_res.nof_symbols / 2}; + const ofdm_symbol_range second_hop_symbols{pucch_res.first_symbol_index + pucch_res.nof_symbols / 2, + pucch_res.first_symbol_index + pucch_res.nof_symbols}; + + const bwp_configuration& init_ul_bwp_param = cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; + + // Compute PRB_first_hop and PRB_second_hop as per Section 9.2.1, TS 38.213. + auto prbs = get_pucch_default_prb_index( + pucch_params.r_pucch, pucch_res.rb_bwp_offset, pucch_res.cs_indexes.size(), size_ul_bwp); + + // With the default PUCCH resource configs, Format is either 0 or 1, which only occupy 1 RB. + const unsigned crb_first_hop = prb_to_crb(init_ul_bwp_param, prbs.first); + const grant_info first_hop_grant{ + init_ul_bwp_param.scs, first_hop_symbols, crb_interval{crb_first_hop, crb_first_hop + 1}}; + const unsigned crb_second_hop = prb_to_crb(init_ul_bwp_param, prbs.second); + const grant_info second_hop_grant{ + init_ul_bwp_param.scs, second_hop_symbols, crb_interval{crb_second_hop, crb_second_hop + 1}}; + + // Compute CS index as per Section 9.2.1, TS 38.213. + const size_t cs_idx = pucch_params.r_pucch < 8 + ? static_cast(pucch_params.r_pucch) % pucch_res.cs_indexes.size() + : static_cast(pucch_params.r_pucch - 8) % pucch_res.cs_indexes.size(); + srsran_assert(cs_idx < pucch_res.cs_indexes.size(), "CS index exceeds static vector size"); + const uint8_t cyclic_shift = pucch_res.cs_indexes[cs_idx]; - pucch_common_alloc_grid[pucch_alloc.slot.to_uint()].emplace_back(rnti); + // Fill scheduler output. + pucch_info& pucch_pdu_common = pucch_alloc.result.ul.pucchs.emplace_back(); + fill_pucch_harq_common_grant(pucch_pdu_common, + rnti, + pucch_res_alloc_cfg{.pucch_res_indicator = pucch_params.pucch_res_indicator, + .first_hop_res = first_hop_grant, + .second_hop_res = second_hop_grant, + .cs = cyclic_shift, + .format = pucch_res.format}); - logger.debug("rnti={}: PUCCH on common {}resource with res_ind={} allocated for slot={}", + // Allocate common HARQ-ACK resource. + pucch_alloc.ul_res_grid.fill(first_hop_grant); + pucch_alloc.ul_res_grid.fill(second_hop_grant); + + // Update the PUCCH grants with the common resource. + auto& pucch_grants = pucch_grants_alloc_grid[pucch_alloc.slot.to_uint()]; + auto* ue_grants_it = std::find_if( + pucch_grants.begin(), pucch_grants.end(), [rnti](const ue_grants& grants) { return grants.rnti == rnti; }); + srsran_assert(ue_grants_it != pucch_grants.end(), "UE grants allocation failed at some point"); + ue_grants_it->has_common_pucch = true; + + // TODO: once the code has been properly tested, remove the debug message. + logger.debug("rnti={}: PUCCH on common and ded. resource with res_ind={} allocated for slot={}", rnti, - existing_grant != nullptr and existing_grant->format == pucch_format::FORMAT_2 ? "" : "and ded. ", - pucch_res_indicator, + pucch_params.pucch_res_indicator, pucch_alloc.slot); - - return pucch_res_indicator; } void pucch_allocator_impl::fill_pucch_harq_common_grant(pucch_info& pucch_info, @@ -642,6 +954,7 @@ void pucch_allocator_impl::fill_pucch_harq_common_grant(pucch_info& pucch_info.resources.second_hop_prbs = crb_to_prb(*pucch_info.bwp_cfg, pucch_res.second_hop_res.crbs); pucch_info.resources.symbols = ofdm_symbol_range{pucch_res.first_hop_res.symbols.start(), pucch_res.second_hop_res.symbols.stop()}; + pucch_info.pdu_context.is_common = true; switch (pucch_res.format) { case pucch_format::FORMAT_0: { @@ -682,20 +995,13 @@ void pucch_allocator_impl::fill_pucch_harq_common_grant(pucch_info& // The function returns an available common PUCCH resource (i.e., not used by other UEs); it returns a null optional // if no resource is available. -std::optional +std::optional pucch_allocator_impl::find_common_and_ded_harq_res_available(cell_slot_resource_allocator& pucch_alloc, - pucch_info* existing_grant, + ue_grants& existing_grants, rnti_t rnti, const ue_cell_configuration& ue_cell_cfg, const dci_context_information& dci_info) { - // Get the parameter N_bwp_size, which is the Initial UL BWP size in PRBs, as per TS 38.213, Section 9.2.1. - const unsigned size_ul_bwp = cell_cfg.ul_cfg_common.init_ul_bwp.generic_params.crbs.length(); - - // Get PUCCH common resource config from Table 9.2.1-1, TS 38.213. - pucch_default_resource pucch_res = get_pucch_default_resource( - cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->pucch_resource_common, size_ul_bwp); - // Get N_CCE (nof_coreset_cces) and n_{CCE,0} (start_cce_idx), as per TS 38.213, Section 9.2.1. const unsigned nof_coreset_cces = dci_info.coreset_cfg->get_nof_cces(); const unsigned start_cce_idx = dci_info.cces.ncce; @@ -703,6 +1009,13 @@ pucch_allocator_impl::find_common_and_ded_harq_res_available(cell_slot_resource_ // As per Section 9.2.1, TS 38.213, this is the max value of \f$\Delta_{PRI}\f$, which is a 3-bit unsigned. const unsigned max_d_pri = 7; for (unsigned d_pri = 0; d_pri != max_d_pri + 1; ++d_pri) { + // The PUCCH allocation may result in a temporary reservation of PUCCH resources, which need to be released in case + // of failure or in case the multiplexing results in a different final PUCCH resource. If we don't reset the + // previous record, we could release the resources that have been allocated for other UEs of allocated for this UE + // in a different slot. + // Reset at each iteration, as a new iteration indicates that the previous allocation failed. + resource_manager.reset_latest_reserved_res_tracker(); + // r_PUCCH, as per Section 9.2.1, TS 38.213. const unsigned r_pucch = get_pucch_default_resource_index(start_cce_idx, nof_coreset_cces, d_pri); srsran_assert(r_pucch < 16, "r_PUCCH must be less than 16"); @@ -712,60 +1025,54 @@ pucch_allocator_impl::find_common_and_ded_harq_res_available(cell_slot_resource_ continue; } + const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); + std::optional pucch_res_ind; + // Look for an available PUCCH dedicated resource with the same PUCCH resource indicator as the common's. - const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); const pucch_resource* ded_resource = - existing_grant == nullptr or existing_grant->format == pucch_format::FORMAT_1 - ? resource_manager.reserve_f1_res_by_res_indicator(pucch_alloc.slot, rnti, d_pri, pucch_cfg) - : resource_manager.reserve_f2_res_by_res_indicator(pucch_alloc.slot, rnti, d_pri, pucch_cfg); + resource_manager.reserve_set_0_res_by_res_indicator(pucch_alloc.slot, rnti, d_pri, pucch_cfg); if (ded_resource == nullptr) { continue; } + resource_manager.set_new_resource_allocation(rnti, pucch_resource_usage::HARQ_SET_0); + + // Add a current grant entry with the PUCCH resource indicator found above; this will force the function that + // multiplexes the resources to use the specific resource with the given PUCCH resource indicator (it could be + // either from resource set 1 or 0, depending on whether there is a CSI grant in the same slot). + pucch_grant& harq_grant = existing_grants.pucch_grants.harq_resource.emplace( + pucch_grant{.type = pucch_grant_type::harq_ack, .pucch_res_cfg = ded_resource}); + harq_grant.bits.harq_ack_nof_bits = 1U; + harq_grant.harq_id.pucch_set_idx = pucch_res_set_idx::set_0; + harq_grant.harq_id.pucch_res_ind = d_pri; + // In the bit to transmit, we have 1 HARQ-ACK bit, plus the SR and CSI bits if any existing grants. + pucch_uci_bits bits_for_uci{.harq_ack_nof_bits = 1U}; + bits_for_uci.sr_bits = existing_grants.pucch_grants.sr_resource.has_value() + ? existing_grants.pucch_grants.sr_resource.value().bits.sr_bits + : sr_nof_bits::no_sr; + bits_for_uci.csi_part1_nof_bits = existing_grants.pucch_grants.csi_resource.has_value() + ? existing_grants.pucch_grants.csi_resource.value().bits.csi_part1_nof_bits + : 0U; + + pucch_res_ind = multiplex_and_allocate_pucch(pucch_alloc, bits_for_uci, existing_grants, ue_cell_cfg, true); + + if (not pucch_res_ind.has_value()) { + resource_manager.cancel_last_ue_res_reservations(pucch_alloc.slot, rnti, ue_cell_cfg); + continue; + } - // Compute the PUCCH resource common configuration parameters. - - // As per TS 38.211, Section 6.3.2.1, the first floor(N_symb_PUCCH/2) are for the first hop, the remaining ones for - // the second hop. - const ofdm_symbol_range first_hop_symbols{pucch_res.first_symbol_index, - pucch_res.first_symbol_index + pucch_res.nof_symbols / 2}; - const ofdm_symbol_range second_hop_symbols{pucch_res.first_symbol_index + pucch_res.nof_symbols / 2, - pucch_res.first_symbol_index + pucch_res.nof_symbols}; - - const bwp_configuration& init_ul_bwp_param = cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; - - // Compute PRB_first_hop and PRB_second_hop as per Section 9.2.1, TS 38.213. - auto prbs = get_pucch_default_prb_index(r_pucch, pucch_res.rb_bwp_offset, pucch_res.cs_indexes.size(), size_ul_bwp); - - // With the default PUCCH resource configs, Format is either 0 or 1, which only occupy 1 RB. - const unsigned crb_first_hop = prb_to_crb(init_ul_bwp_param, prbs.first); - const grant_info first_hop_grant{ - init_ul_bwp_param.scs, first_hop_symbols, crb_interval{crb_first_hop, crb_first_hop + 1}}; - const unsigned crb_second_hop = prb_to_crb(init_ul_bwp_param, prbs.second); - const grant_info second_hop_grant{ - init_ul_bwp_param.scs, second_hop_symbols, crb_interval{crb_second_hop, crb_second_hop + 1}}; - - // Compute CS index as per Section 9.2.1, TS 38.213. - const size_t cs_idx = r_pucch < 8 ? static_cast(r_pucch) % pucch_res.cs_indexes.size() - : static_cast(r_pucch - 8) % pucch_res.cs_indexes.size(); - srsran_assert(cs_idx < pucch_res.cs_indexes.size(), "CS index exceeds static vector size"); - const uint8_t cyclic_shift = pucch_res.cs_indexes[cs_idx]; + srsran_assert(pucch_res_ind.value() == d_pri, "PUCCH resource indicator must match the one given as an input"); - return pucch_com_ded_res{pucch_res_alloc_cfg{.pucch_res_indicator = d_pri, - .first_hop_res = first_hop_grant, - .second_hop_res = second_hop_grant, - .cs = cyclic_shift, - .format = pucch_res.format}, - *ded_resource}; + return pucch_common_params{.pucch_res_indicator = d_pri, .r_pucch = r_pucch}; }; return std::nullopt; } -std::optional -pucch_allocator_impl::allocate_new_format1_harq_grant(cell_slot_resource_allocator& pucch_slot_alloc, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg, - pucch_info* existing_sr_grant) +std::optional pucch_allocator_impl::allocate_harq_grant(cell_slot_resource_allocator& pucch_slot_alloc, + rnti_t crnti, + const ue_cell_configuration& ue_cell_cfg) { + slot_point sl_tx = pucch_slot_alloc.slot; + // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. if (pucch_slot_alloc.result.ul.pucchs.size() >= get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { @@ -775,7 +1082,14 @@ pucch_allocator_impl::allocate_new_format1_harq_grant(cell_slot_resource_allocat return std::nullopt; } - const pucch_harq_resource_alloc_record pucch_harq_res_info = resource_manager.reserve_next_f1_harq_res_available( + if (pucch_grants_alloc_grid[sl_tx.to_uint()].full()) { + logger.info("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH allocator grant list is full", + crnti, + pucch_slot_alloc.slot); + return std::nullopt; + } + + const pucch_harq_resource_alloc_record pucch_harq_res_info = resource_manager.reserve_next_set_0_harq_res_available( pucch_slot_alloc.slot, crnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); if (pucch_harq_res_info.pucch_res == nullptr) { logger.debug("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH F1 ded. resource not available", @@ -784,12 +1098,6 @@ pucch_allocator_impl::allocate_new_format1_harq_grant(cell_slot_resource_allocat return std::nullopt; } - // Update the number of HARQ-ACK bits in the SR grant, if present. - if (existing_sr_grant != nullptr) { - srsran_sanity_check(existing_sr_grant->format == pucch_format::FORMAT_1, "Only PUCCH format 1 expected for SR"); - existing_sr_grant->format_1.harq_ack_nof_bits++; - } - // Allocate the new grant on PUCCH F1 resources for HARQ-ACK bits (without SR). pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); const unsigned HARQ_BITS_IN_NEW_PUCCH_GRANT = 1; @@ -797,578 +1105,967 @@ pucch_allocator_impl::allocate_new_format1_harq_grant(cell_slot_resource_allocat pucch_pdu, crnti, *pucch_harq_res_info.pucch_res, HARQ_BITS_IN_NEW_PUCCH_GRANT, sr_nof_bits::no_sr); const auto pucch_res_indicator = static_cast(pucch_harq_res_info.pucch_res_indicator); + // Save the info in the scheduler list of PUCCH grants. + auto& grants = pucch_grants_alloc_grid[sl_tx.to_uint()].emplace_back(ue_grants{.rnti = crnti}); + grants.pucch_grants.harq_resource.emplace( + pucch_grant{.type = pucch_grant_type::harq_ack, .pucch_res_cfg = pucch_harq_res_info.pucch_res}); + grants.pucch_grants.harq_resource.value().harq_id.pucch_set_idx = pucch_res_set_idx::set_0; + grants.pucch_grants.harq_resource.value().harq_id.pucch_res_ind = pucch_res_indicator; + grants.pucch_grants.harq_resource.value().bits.harq_ack_nof_bits = HARQ_BITS_IN_NEW_PUCCH_GRANT; + return pucch_res_indicator; } -void pucch_allocator_impl::convert_to_format2_csi(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_sr_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned csi_part1_nof_bits) +void pucch_allocator_impl::allocate_csi_grant(cell_slot_resource_allocator& pucch_slot_alloc, + rnti_t crnti, + const ue_cell_configuration& ue_cell_cfg, + unsigned csi_part1_bits) { - // Get a PUCCH Format 2 resource. - const pucch_resource* pucch_res = resource_manager.reserve_csi_resource(pucch_slot_alloc.slot, rnti, ue_cell_cfg); - if (pucch_res == nullptr) { - logger.warning( - "rnti={}: CSI could not be allocated on PUCCH Format2 for slot={}. Cause: PUCCH F2 resource not available", - rnti, - pucch_slot_alloc.slot); - return; - } - - const float max_pucch_code_rate = to_max_code_rate_float(ue_cell_cfg.cfg_dedicated() - .ul_config.value() - .init_ul_bwp.pucch_cfg.value() - .format_2_common_param.value() - .max_c_rate); - - // This function can be only be called after the SR gets allocated. The \c sr_bits_to_report will be passed on the - // PUCCH grant for CSI. The same SR bits will be used to compute the expected payload to be carried by the PUCCH F2 - // CSI-specific resource. - const sr_nof_bits sr_bits_to_report = existing_sr_grant.format_1.sr_bits; - const unsigned candidate_uci_bits = sr_nof_bits_to_uint(sr_bits_to_report) + csi_part1_nof_bits; - - const unsigned max_payload = - get_pucch_format2_max_payload(std::get(pucch_res->format_params).nof_prbs, - std::get(pucch_res->format_params).nof_symbols, - max_pucch_code_rate); - - // It's the config validator that should ensure this is verified. - srsran_assert(max_payload >= candidate_uci_bits, - "rnti={}: PUCCH F2 max payload {} is insufficient for {} candidate UCI bits", - rnti, - max_payload, - candidate_uci_bits); - - // Remove the previously allocated PUCCH format-1 resources. - remove_pucch_format1_from_grants( - pucch_slot_alloc, rnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + srsran_assert(csi_part1_bits != 0, "This function can only be called to allocate a PUCCH F2 resource for CSI"); + const slot_point sl_tx = pucch_slot_alloc.slot; // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. if (pucch_slot_alloc.result.ul.pucchs.size() >= get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { - logger.warning("rnti={}: UCI allocation on PUCCH Format2 for slot={} skipped. Cause: UL grants reached", - rnti, + logger.warning("rnti={}: CSI allocation on PUCCH Format2 for slot={} skipped. Cause: UL grants reached", + crnti, pucch_slot_alloc.slot); return; } - if (pucch_slot_alloc.result.ul.pucchs.full()) { - logger.warning("rnti={}: UCI could not be allocated on PUCCH Format2 for slot={}. Cause: List still full after " - "removing PUCCH F1 grant", - rnti, - pucch_slot_alloc.slot); + if (pucch_grants_alloc_grid[sl_tx.to_uint()].full()) { + logger.info("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH allocator grant list is full", + crnti, + pucch_slot_alloc.slot); return; } - pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); - const unsigned harq_bits_only_csi = 0U; - fill_pucch_format2_grant(pucch_pdu, - rnti, - *pucch_res, - ue_cell_cfg, - std::get(pucch_res->format_params).nof_prbs, - harq_bits_only_csi, - sr_bits_to_report, - csi_part1_nof_bits); -} - -std::optional pucch_allocator_impl::convert_to_format2_harq(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_harq_grant, - pucch_info* existing_sr_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned int harq_ack_bits_increment) -{ - const unsigned curr_harq_bits = existing_harq_grant.format_1.harq_ack_nof_bits; - const sr_nof_bits sr_bits = existing_sr_grant != nullptr ? existing_sr_grant->format_1.sr_bits : sr_nof_bits::no_sr; - - const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); - - // Get a PUCCH Format 2 resource. - const pucch_harq_resource_alloc_record format2_res = - resource_manager.reserve_next_f2_harq_res_available(pucch_slot_alloc.slot, rnti, pucch_cfg); + // Get the F2 resource specific for with CSI. + const pucch_resource* csi_f2_res = resource_manager.reserve_csi_resource(pucch_slot_alloc.slot, crnti, ue_cell_cfg); - if (format2_res.pucch_res == nullptr) { - logger.debug("rnti={}: HARQ-ACK could not be allocated on PUCCH Format2 for slot={}. Cause: PUCCH F2 resource " - "not available", - rnti, - pucch_slot_alloc.slot); - return std::nullopt; + if (csi_f2_res == nullptr) { + logger.warning( + "rnti={}: CSI could not be allocated on PUCCH Format2 for slot={}. Cause: PUCCH F2 resource not available", + crnti, + pucch_slot_alloc.slot); + return; } - - const unsigned candidate_uci_bits = curr_harq_bits + harq_ack_bits_increment + sr_nof_bits_to_uint(sr_bits); - - const float max_pucch_code_rate = to_max_code_rate_float(ue_cell_cfg.cfg_dedicated() - .ul_config.value() - .init_ul_bwp.pucch_cfg.value() - .format_2_common_param.value() - .max_c_rate); + // When this function is called, it means that there are no SR grants to be multiplexed with CSI; thus, the CSI bits + // are the only UCI bits to be considered. + // It's the validator that should make sure the CSI bits fit into a PUCCH Format 2 resource. const unsigned max_payload = - get_pucch_format2_max_payload(std::get(format2_res.pucch_res->format_params).nof_prbs, - std::get(format2_res.pucch_res->format_params).nof_symbols, - max_pucch_code_rate); - - if (max_payload < candidate_uci_bits) { - logger.debug("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH F2 max payload {} is " - "insufficient for " - "{} candidate UCI bits", - rnti, - pucch_slot_alloc.slot, - max_payload, - candidate_uci_bits); - resource_manager.release_harq_f2_resource(pucch_slot_alloc.slot, rnti, pucch_cfg); - return std::nullopt; - } - - // Compute the number of PRBs required for the uci bits computed above. - const unsigned nof_prbs = - get_pucch_format2_nof_prbs(candidate_uci_bits, - std::get(format2_res.pucch_res->format_params).nof_prbs, - std::get(format2_res.pucch_res->format_params).nof_symbols, - max_pucch_code_rate); - - // Remove the previously allocated PUCCH format-1 resources. - remove_pucch_format1_from_grants( - pucch_slot_alloc, rnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); - - // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. - if (pucch_slot_alloc.result.ul.pucchs.size() >= - get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { - logger.info("rnti={}: HARQ-ACK allocation on PUCCH Format2 for slot={} skipped. Cause: UL grants reached", - rnti, - pucch_slot_alloc.slot); - return std::nullopt; - } + ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value().get_max_payload(csi_f2_res->format); + srsran_assert(csi_part1_bits <= max_payload, + "rnti={}: PUCCH F2 max payload {} is insufficient for {} candidate UCI bits", + crnti, + max_payload, + csi_part1_bits); - pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); - const unsigned csi1_nof_bits_only_harq = 0U; + // Allocate a PUCCH PDU in the list and fill it with the parameters. + pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); + // Neither HARQ-ACK bits + const unsigned harq_ack_bits_only_csi = 0U; + const sr_nof_bits sr_bits_only_csi = sr_nof_bits::no_sr; fill_pucch_format2_grant(pucch_pdu, - rnti, - *format2_res.pucch_res, + crnti, + *csi_f2_res, ue_cell_cfg, - nof_prbs, - curr_harq_bits + harq_ack_bits_increment, - sr_bits, - csi1_nof_bits_only_harq); + std::get(csi_f2_res->format_params).nof_prbs, + harq_ack_bits_only_csi, + sr_bits_only_csi, + csi_part1_bits); - return format2_res.pucch_res_indicator; + // Save the info in the scheduler list of PUCCH grants. + auto& csi_pucch_grant = pucch_grants_alloc_grid[sl_tx.to_uint()].emplace_back(ue_grants{.rnti = crnti}); + csi_pucch_grant.pucch_grants.csi_resource.emplace( + pucch_grant{.type = pucch_grant_type::csi, .pucch_res_cfg = csi_f2_res}); + csi_pucch_grant.pucch_grants.csi_resource.value().bits.csi_part1_nof_bits = csi_part1_bits; } -std::optional -pucch_allocator_impl::change_format2_resource(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment, - std::optional harq_f2_res) +void pucch_allocator_impl::fill_pucch_ded_format1_grant(pucch_info& pucch_pdu, + rnti_t crnti, + const pucch_resource& pucch_ded_res_cfg, + unsigned harq_ack_bits, + sr_nof_bits sr_bits) { - const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); - const pucch_harq_resource_alloc_record format2_res = - harq_f2_res.has_value() - ? harq_f2_res.value() - : resource_manager.reserve_next_f2_harq_res_available(pucch_slot_alloc.slot, rnti, pucch_cfg); - - if (format2_res.pucch_res == nullptr) { - logger.debug("rnti={}: HARQ-ACK could not be allocated on PUCCH Format2 for slot={}. Cause: PUCCH F2 resource " - "not available", - rnti, - pucch_slot_alloc.slot); - return std::nullopt; + pucch_pdu.crnti = crnti; + pucch_pdu.bwp_cfg = &cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; + pucch_pdu.format = pucch_format::FORMAT_1; + + // Set PRBs and symbols, first. + // The number of PRBs is not explicitly stated in the TS, but it can be inferred it's 1. + const auto& res_f1 = std::get(pucch_ded_res_cfg.format_params); + pucch_pdu.resources.prbs.set(pucch_ded_res_cfg.starting_prb, + pucch_ded_res_cfg.starting_prb + PUCCH_FORMAT_1_NOF_PRBS); + pucch_pdu.resources.symbols.set(res_f1.starting_sym_idx, res_f1.starting_sym_idx + res_f1.nof_symbols); + if (pucch_ded_res_cfg.second_hop_prb.has_value()) { + pucch_pdu.resources.second_hop_prbs.set(pucch_ded_res_cfg.second_hop_prb.value(), + pucch_ded_res_cfg.second_hop_prb.value() + PUCCH_FORMAT_1_NOF_PRBS); } + // \c pucch-GroupHopping and \c hoppingId are set as per TS 38.211, Section 6.3.2.2.1. + pucch_pdu.format_1.group_hopping = cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->group_hopping; + pucch_pdu.format_1.n_id_hopping = cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->hopping_id.has_value() + ? cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->hopping_id.value() + : cell_cfg.pci; + pucch_pdu.format_1.initial_cyclic_shift = res_f1.initial_cyclic_shift; + pucch_pdu.format_1.time_domain_occ = res_f1.time_domain_occ; + // For PUCCH Format 1, only 1 SR bit. + pucch_pdu.format_1.sr_bits = sr_bits; + pucch_pdu.format_1.harq_ack_nof_bits = harq_ack_bits; + // [Implementation-defined] We do not implement PUCCH over several slots. + pucch_pdu.format_1.slot_repetition = pucch_repetition_tx_slot::no_multi_slot; +} - // This function would only be called in case CSI and SR gets allocated before the HARQ. In that case, if there are - // SR bits or CSI bits to be carried by the PUCCH F2 grant, they would have already been allocated and there is no - // need to check if the slot is a CSI or SR opportunity. - const sr_nof_bits sr_bits_to_report = existing_grant.format_2.sr_bits; - const unsigned csi_bits_to_report = existing_grant.format_2.csi_part1_bits; +void pucch_allocator_impl::fill_pucch_format2_grant(pucch_info& pucch_pdu, + rnti_t crnti, + const pucch_resource& pucch_ded_res_cfg, + const ue_cell_configuration& ue_cell_cfg, + unsigned nof_prbs, + unsigned harq_ack_bits, + sr_nof_bits sr_bits, + unsigned csi_part1_bits) +{ + pucch_pdu.crnti = crnti; + pucch_pdu.bwp_cfg = &cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; + pucch_pdu.format = pucch_format::FORMAT_2; - const unsigned candidate_uci_bits = - harq_ack_bits_increment + sr_nof_bits_to_uint(sr_bits_to_report) + csi_bits_to_report; + // Set PRBs and symbols, first. + // The number of PRBs is not explicitly stated in the TS, but it can be inferred it's 1. + pucch_pdu.resources.prbs.set(pucch_ded_res_cfg.starting_prb, pucch_ded_res_cfg.starting_prb + nof_prbs); + const auto& res_f2 = std::get(pucch_ded_res_cfg.format_params); + pucch_pdu.resources.symbols.set(res_f2.starting_sym_idx, res_f2.starting_sym_idx + res_f2.nof_symbols); + if (pucch_ded_res_cfg.second_hop_prb.has_value()) { + pucch_pdu.resources.second_hop_prbs.set(pucch_ded_res_cfg.second_hop_prb.value(), + pucch_ded_res_cfg.second_hop_prb.value() + nof_prbs); + } - const float max_pucch_code_rate = to_max_code_rate_float(ue_cell_cfg.cfg_dedicated() - .ul_config.value() - .init_ul_bwp.pucch_cfg.value() - .format_2_common_param.value() - .max_c_rate); - const unsigned max_payload = - get_pucch_format2_max_payload(std::get(format2_res.pucch_res->format_params).nof_prbs, - std::get(format2_res.pucch_res->format_params).nof_symbols, - max_pucch_code_rate); + pucch_pdu.format_2.sr_bits = sr_bits; + pucch_pdu.format_2.harq_ack_nof_bits = harq_ack_bits; + pucch_pdu.format_2.csi_part1_bits = csi_part1_bits; + // \f$n_{ID}\f$ as per Section 6.3.2.5.1 and 6.3.2.6.1, TS 38.211. + pucch_pdu.format_2.n_id_scambling = + ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().data_scrambling_id_pusch.has_value() + ? ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().data_scrambling_id_pusch.value() + : cell_cfg.pci; + // \f$N_{ID}^0\f$ as per TS 38.211, Section 6.4.1.3.2.1. + pucch_pdu.format_2.n_id_0_scrambling = get_n_id0_scrambling(ue_cell_cfg, cell_cfg.pci); + pucch_pdu.format_2.max_code_rate = ue_cell_cfg.cfg_dedicated() + .ul_config.value() + .init_ul_bwp.pucch_cfg.value() + .format_2_common_param.value() + .max_c_rate; - if (max_payload < candidate_uci_bits) { - logger.debug("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH F2 max payload {} is " - "insufficient for {} candidate UCI bits", - rnti, - pucch_slot_alloc.slot, - max_payload, - candidate_uci_bits); - // The allocation will be aborted, we need to deallocate the resource that was reserved at the beginning of the - // function. - resource_manager.release_harq_f2_resource(pucch_slot_alloc.slot, rnti, pucch_cfg); - return std::nullopt; + // Generate CSI report configuration if there are CSI bits in UCI. + if (csi_part1_bits > 0) { + pucch_pdu.csi_rep_cfg = create_csi_report_configuration(*ue_cell_cfg.cfg_dedicated().csi_meas_cfg); } +} - // Compute the number of PRBs required for the uci bits computed above. - const unsigned nof_prbs = - get_pucch_format2_nof_prbs(candidate_uci_bits, - std::get(format2_res.pucch_res->format_params).nof_prbs, - std::get(format2_res.pucch_res->format_params).nof_symbols, - max_pucch_code_rate); +bool pucch_allocator_impl::has_common_pucch_grant(rnti_t rnti, slot_point sl_tx) const +{ + return std::find_if(pucch_grants_alloc_grid[sl_tx.to_uint()].begin(), + pucch_grants_alloc_grid[sl_tx.to_uint()].end(), + [rnti](const ue_grants& grant) { return grant.has_common_pucch and grant.rnti == rnti; }) != + pucch_grants_alloc_grid[sl_tx.to_uint()].end(); +} - // Remove the previously allocated PUCCH format-2 resource. - // TODO: instead of removing the grant, change the data according to the new resource used. - remove_format2_csi_from_grants(pucch_slot_alloc, rnti, ue_cell_cfg); +unsigned pucch_allocator_impl::get_max_pucch_grants(unsigned currently_allocated_puschs) +{ + return std::min(max_pucch_grants_per_slot, max_ul_grants_per_slot - currently_allocated_puschs); +} - // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. - if (pucch_slot_alloc.result.ul.pucchs.size() >= - get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { - logger.info("rnti={}: HARQ-ACK allocation on PUCCH Format2 for slot={} skipped. Cause: UL grants reached", - rnti, - pucch_slot_alloc.slot); - return std::nullopt; +void pucch_allocator_impl::remove_unsed_pucch_res(slot_point sl_tx, + pucch_grant_list grants_to_tx, + ue_grants& existing_pucchs, + const ue_cell_configuration& ue_cell_cfg) +{ + // Remove the PUCCH resources by evaluating the difference between the previously allocated resources and the current + // ones. + if (existing_pucchs.pucch_grants.csi_resource.has_value() and not grants_to_tx.csi_resource.has_value()) { + resource_manager.release_csi_resource(sl_tx, existing_pucchs.rnti, ue_cell_cfg); + } + if (existing_pucchs.pucch_grants.sr_resource.has_value() and not grants_to_tx.sr_resource.has_value()) { + resource_manager.release_sr_resource( + sl_tx, existing_pucchs.rnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); } - pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); - fill_pucch_format2_grant(pucch_pdu, - rnti, - *format2_res.pucch_res, - ue_cell_cfg, - nof_prbs, - harq_ack_bits_increment, - sr_bits_to_report, - csi_bits_to_report); + if (existing_pucchs.pucch_grants.harq_resource.has_value() and + (not grants_to_tx.harq_resource.has_value() or + existing_pucchs.pucch_grants.harq_resource->get_format() != grants_to_tx.harq_resource->get_format())) { + if (existing_pucchs.pucch_grants.harq_resource.value().harq_id.pucch_set_idx == pucch_res_set_idx::set_0) { + resource_manager.release_harq_set_0_resource( + sl_tx, existing_pucchs.rnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + } else { + resource_manager.release_harq_set_1_resource( + sl_tx, existing_pucchs.rnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + } + } - return static_cast(format2_res.pucch_res_indicator); + // This is a special case, in which the PUCCH from resource set 0 is first reserved, but later it is converted into a + // PUCCH from resource set 1 due to the multiplexing process. + if (resource_manager.is_resource_allocated(existing_pucchs.rnti, pucch_resource_usage::HARQ_SET_1) and + resource_manager.is_resource_allocated(existing_pucchs.rnti, pucch_resource_usage::HARQ_SET_0)) { + resource_manager.release_harq_set_0_resource( + sl_tx, existing_pucchs.rnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + } } -std::optional pucch_allocator_impl::add_harq_ack_bit_to_format1_grant(pucch_info& existing_harq_grant, - pucch_info* existing_sr_grant, - rnti_t rnti, - slot_point sl_tx, - const pucch_config& pucch_cfg) +std::optional +pucch_allocator_impl::multiplex_and_allocate_pucch(cell_slot_resource_allocator& pucch_slot_alloc, + pucch_uci_bits new_bits, + ue_grants& current_grants, + const ue_cell_configuration& ue_cell_cfg, + bool preserve_res_indicator) { - const int pucch_res_idx = resource_manager.fetch_f1_pucch_res_indic(sl_tx, rnti, pucch_cfg); - if (pucch_res_idx < 0) { - srsran_assert(pucch_res_idx >= 0, "PUCCH resource index should not be negative"); + // NOTE: In this function, the \c candidate_grants report the data about the grants BEFORE the multiplexing is + // applied. Each grant contains only one UCI type (HARQ grant contains HARQ bits, SR grant contains SR bits and so + // on); on the contrary, \c grants_to_tx contains the grants AFTER the multiplexing; this means that 1 grant can + // contain more than 1 UCI bit type. + + slot_point sl_ack = pucch_slot_alloc.slot; + + // Find the grants/resources needed for the UCI bits to be reported, assuming the resources are not multiplexed. + std::optional candidate_grants = + get_pucch_res_pre_multiplexing(sl_ack, new_bits, current_grants, ue_cell_cfg); + if (not candidate_grants.has_value()) { return std::nullopt; } - srsran_sanity_check(existing_harq_grant.format == pucch_format::FORMAT_1, "Only PUCCH format 1 expected for HARQ"); - // Update the SR, if present. - if (existing_sr_grant != nullptr) { - srsran_sanity_check(existing_sr_grant->format == pucch_format::FORMAT_1, "Only PUCCH format 1 expected for SR"); - existing_sr_grant->format_1.harq_ack_nof_bits++; + + // As per TS 38.213, Section 9.2.1, we can infer that, when the HARQ-ACK bit count changes from 1 to 2, or when it + // changes from 3 to more than 3, the PUCCH resource for the HARQ-ACK is the same as the previous one; this means + // that the multiplexing would yield the same result as in the last UE's allocation; in this case, we skip the + // multiplexing. Refer to paragraph "If the UE transmits O_UCI UCI information bits, that include HARQ-ACK + // information bits, the UE determines a PUCCH resource set to be ...". + const pucch_uci_bits current_uci_bits = current_grants.pucch_grants.get_uci_bits(); + const bool is_multiplexing_needed = + not((current_uci_bits.harq_ack_nof_bits == 1 and new_bits.harq_ack_nof_bits == 2) or + (current_uci_bits.harq_ack_nof_bits == 3 and new_bits.harq_ack_nof_bits > 3)); + + pucch_grant_list grants_to_tx = + is_multiplexing_needed + ? multiplex_resources( + sl_ack, current_grants.rnti, candidate_grants.value(), ue_cell_cfg, preserve_res_indicator) + : update_grants_no_multiplexing( + sl_ack, current_grants.rnti, candidate_grants.value(), ue_cell_cfg, current_grants); + + if (grants_to_tx.is_emtpy()) { + return std::nullopt; } - // Update the HARQ, if present. - existing_harq_grant.format_1.harq_ack_nof_bits++; - const auto pucch_res_indicator = static_cast(pucch_res_idx); - return pucch_res_indicator; + // Allocate the grants. + return allocate_grants(pucch_slot_alloc, current_grants, current_grants.rnti, grants_to_tx, ue_cell_cfg); } -void pucch_allocator_impl::remove_pucch_format1_from_grants(cell_slot_resource_allocator& slot_alloc, - rnti_t crnti, - const pucch_config& pucch_cfg) +std::optional +pucch_allocator_impl::get_pucch_res_pre_multiplexing(slot_point sl_tx, + pucch_uci_bits new_bits, + ue_grants ue_current_grants, + const ue_cell_configuration& ue_cell_cfg) { - auto& pucchs = slot_alloc.result.ul.pucchs; - slot_point sl_tx = slot_alloc.slot; + pucch_grant_list candidate_resources; - // Remove dedicated HARQ-ACK grant first. - auto* it_harq = std::find_if(pucchs.begin(), pucchs.end(), [crnti, sl_tx, this](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_1 and - pucch.format_1.sr_bits == sr_nof_bits::no_sr and pucch.format_1.harq_ack_nof_bits > 0 and - not has_common_pucch_f1_grant(crnti, sl_tx); - }); + const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); - if (it_harq != pucchs.end()) { - pucchs.erase(it_harq); - resource_manager.release_harq_f1_resource(slot_alloc.slot, crnti, pucch_cfg); + if (new_bits.harq_ack_nof_bits > 0) { + // Case HARQ ACK bits 1 or 2, resource to be chosen from PUCCH resource set 0; else, pick from PUCCH resource set 1. + const pucch_res_set_idx pucch_set_idx = + new_bits.harq_ack_nof_bits <= 2U ? pucch_res_set_idx::set_0 : pucch_res_set_idx::set_1; + + candidate_resources.harq_resource.emplace(pucch_grant{.type = pucch_grant_type::harq_ack}); + pucch_grant& harq_candidate_grant = candidate_resources.harq_resource.value(); + + // There is already a PUCCH resource for HARQ-ACK; if so, we use the info and configuration from this resource. + if (ue_current_grants.pucch_grants.harq_resource.has_value() and + ue_current_grants.pucch_grants.harq_resource.value().harq_id.pucch_set_idx == pucch_set_idx) { + const pucch_resource* pucch_res = + pucch_set_idx == pucch_res_set_idx::set_0 + ? resource_manager.reserve_set_0_res_by_res_indicator( + sl_tx, + ue_current_grants.rnti, + ue_current_grants.pucch_grants.harq_resource.value().harq_id.pucch_res_ind, + pucch_cfg) + : resource_manager.reserve_set_1_res_by_res_indicator( + sl_tx, + ue_current_grants.rnti, + ue_current_grants.pucch_grants.harq_resource.value().harq_id.pucch_res_ind, + pucch_cfg); + if (pucch_res == nullptr) { + srsran_assertion_failure("rnti={}: PUCCH HARQ-ACK resource previously reserved not available anymore", + ue_current_grants.rnti); + return std::nullopt; + } + harq_candidate_grant.harq_id.pucch_set_idx = pucch_set_idx; + harq_candidate_grant.harq_id.pucch_res_ind = + static_cast(ue_current_grants.pucch_grants.harq_resource.value().harq_id.pucch_res_ind); + harq_candidate_grant.pucch_res_cfg = pucch_res; + } + // Get a new PUCCH resource for HARQ-ACK from the correct PUCCH resource set. + else { + // Only copy the HARQ-ACK bits, as at this stage we only need to consider the UCI bits before multiplexing. + pucch_harq_resource_alloc_record harq_resource = + pucch_set_idx == pucch_res_set_idx::set_0 + ? resource_manager.reserve_next_set_0_harq_res_available(sl_tx, ue_current_grants.rnti, pucch_cfg) + : resource_manager.reserve_next_set_1_harq_res_available(sl_tx, ue_current_grants.rnti, pucch_cfg); + // Save the resources that have been generated; if at some point the allocation fails, we need to release them. + if (pucch_set_idx == pucch_res_set_idx::set_0) { + resource_manager.set_new_resource_allocation(ue_current_grants.rnti, pucch_resource_usage::HARQ_SET_0); + } else { + resource_manager.set_new_resource_allocation(ue_current_grants.rnti, pucch_resource_usage::HARQ_SET_1); + } + if (harq_resource.pucch_res == nullptr) { + return std::nullopt; + } + harq_candidate_grant.harq_id.pucch_set_idx = pucch_set_idx; + harq_candidate_grant.harq_id.pucch_res_ind = static_cast(harq_resource.pucch_res_indicator); + harq_candidate_grant.pucch_res_cfg = harq_resource.pucch_res; + } + // Only copy the HARQ-ACK bits, as at this stage we only need to consider the UCI bits assuming the resources still + // need to be multiplexed. + harq_candidate_grant.bits.harq_ack_nof_bits = new_bits.harq_ack_nof_bits; + harq_candidate_grant.bits.sr_bits = sr_nof_bits::no_sr; + harq_candidate_grant.bits.csi_part1_nof_bits = 0U; } - // Remove SR grant, if any. - auto* it_sr = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_1 and - pucch.format_1.sr_bits == sr_nof_bits::one; - }); + if (new_bits.sr_bits != sr_nof_bits::no_sr) { + candidate_resources.sr_resource.emplace(pucch_grant{.type = pucch_grant_type::sr}); + pucch_grant& sr_candidate_grant = candidate_resources.sr_resource.value(); + + // Get the resource from the resource manager; the UCI bits will be added later. + // NOTE: if the SR resource was already assigned to this UE, the resource manager will only return the PUCCH config + // that was reserved previously. + const pucch_resource* sr_resource = + resource_manager.reserve_sr_res_available(sl_tx, ue_current_grants.rnti, pucch_cfg); + // Save the resources that have been generated; if at some point the allocation fails, we need to release them. + resource_manager.set_new_resource_allocation(ue_current_grants.rnti, pucch_resource_usage::SR); + if (sr_resource == nullptr) { + srsran_assertion_failure("rnti={}: PUCCH SR resource previously reserved not available anymore", + ue_current_grants.rnti); + return std::nullopt; + } + sr_candidate_grant.pucch_res_cfg = sr_resource; + // Only copy the SR bits, as at this stage we only need to consider the UCI bits assuming the resources still + // need to be multiplexed. + sr_candidate_grant.bits.harq_ack_nof_bits = 0U; + sr_candidate_grant.bits.sr_bits = new_bits.sr_bits; + sr_candidate_grant.bits.csi_part1_nof_bits = 0U; + } - if (it_sr != pucchs.end()) { - pucchs.erase(it_sr); - resource_manager.release_sr_resource(slot_alloc.slot, crnti, pucch_cfg); + if (new_bits.csi_part1_nof_bits != 0U) { + candidate_resources.csi_resource.emplace(pucch_grant{.type = pucch_grant_type::csi}); + pucch_grant& csi_candidate_grant = candidate_resources.csi_resource.value(); + + // Get the resource from the resource manager; the UCI bits will be added later. + // NOTE: if the CSI resource was already assigned to this UE, the resource manager will only return the PUCCH config + // that was reserved previously. + const pucch_resource* csi_resource = + resource_manager.reserve_csi_resource(sl_tx, ue_current_grants.rnti, ue_cell_cfg); + // Save the resources that have been generated; if at some point the allocation fails, we need to release them. + resource_manager.set_new_resource_allocation(ue_current_grants.rnti, pucch_resource_usage::CSI); + if (csi_resource == nullptr) { + srsran_assertion_failure("rnti={}: PUCCH CSI resource previously reserved not available anymore", + ue_current_grants.rnti); + return std::nullopt; + } + csi_candidate_grant.pucch_res_cfg = csi_resource; + // Only copy the HARQ-ACK bits, as at this stage we only need to consider the UCI bits assuming the resources still + // need to be multiplexed. + csi_candidate_grant.bits.harq_ack_nof_bits = 0U; + csi_candidate_grant.bits.sr_bits = sr_nof_bits::no_sr; + csi_candidate_grant.bits.csi_part1_nof_bits = new_bits.csi_part1_nof_bits; } + + // TODO: handle the failure case, in which the resources that had been reserved are not used and need to be released. + + return candidate_resources; } -void pucch_allocator_impl::remove_format2_csi_from_grants(cell_slot_resource_allocator& slot_alloc, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg) +pucch_allocator_impl::pucch_grant_list +pucch_allocator_impl::update_grants_no_multiplexing(slot_point sl_tx, + rnti_t crnti, + pucch_grant_list candidate_grants, + const ue_cell_configuration& ue_cell_cfg, + ue_grants ue_current_grants) { - auto& pucchs = slot_alloc.result.ul.pucchs; + pucch_grant_list grants_to_tx; - // Remove PUCCH Format2 resource specific for CSI. - auto* it = std::find_if(pucchs.begin(), pucchs.end(), [crnti](pucch_info& pucch) { - return pucch.crnti == crnti and pucch.format == pucch_format::FORMAT_2 and pucch.format_2.harq_ack_nof_bits == 0 and - pucch.format_2.csi_part1_bits > 0; - }); + srsran_assert(candidate_grants.harq_resource.has_value(), "PUCCH HARQ-ACK resource must be present"); + pucch_grant harq_grant = candidate_grants.harq_resource.value(); - if (it != pucchs.end()) { - pucchs.erase(it); - resource_manager.release_csi_resource(slot_alloc.slot, crnti, ue_cell_cfg); + // If the PUCCH is from PUCCH resource set 1 and the total UCI bits exceeds the PUCCH payload, we abort the + // allocation. + if (harq_grant.harq_id.pucch_set_idx != pucch_res_set_idx::set_0 and + candidate_grants.get_uci_bits().get_total_bits() > + ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value().get_max_payload( + harq_grant.get_format())) { + logger.debug( + "rnti={}: PUCCH allocation (HARQ-ACK) for slot={} skipped. Cause: UCI bits exceed PUCCH payload", crnti, sl_tx); + return grants_to_tx; } + + // In the case, the only elements that might have changed since the last allocation are the UCI bits and (possibly) + // the pointers to PUCCH resources. + srsran_assert(ue_current_grants.pucch_grants.harq_resource.has_value(), + "PUCCH HARQ resource must be present in the current allocation list"); + grants_to_tx = ue_current_grants.pucch_grants; + + // Update the grants with the new UCI bits and PUCCH resource configurations (this is to prevent the pointers have + // changed since the last allocation). + // NOTE: no need to update the CSI grant, because: (i) if the CSI grant exists, then it doesn't carry the HARQ-ACK + // bits (they are in the HARQ-ACK grangt); (ii) if it doesn't exist, then it's trivial. + grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits = harq_grant.bits.harq_ack_nof_bits; + grants_to_tx.harq_resource.value().pucch_res_cfg = harq_grant.pucch_res_cfg; + if (ue_current_grants.pucch_grants.sr_resource.has_value()) { + srsran_assert(candidate_grants.sr_resource.has_value(), "PUCCH SR resource must be present"); + grants_to_tx.sr_resource.value().pucch_res_cfg = candidate_grants.sr_resource->pucch_res_cfg; + grants_to_tx.sr_resource.value().bits.harq_ack_nof_bits = harq_grant.bits.harq_ack_nof_bits; + } + + return grants_to_tx; } -void pucch_allocator_impl::allocate_new_csi_grant(cell_slot_resource_allocator& pucch_slot_alloc, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned csi_part1_bits) +pucch_allocator_impl::pucch_grant_list +pucch_allocator_impl::multiplex_resources(slot_point sl_tx, + rnti_t crnti, + pucch_grant_list candidate_grants, + const ue_cell_configuration& ue_cell_cfg, + bool preserve_res_indicator) { - srsran_assert(csi_part1_bits != 0, "This function can only be called to allocate a PUCCH F2 resource for CSI"); + // This function implements the multiplexing pseudo-code for PUCCH resources defined in Section 9.2.5, TS 38.213. + // Refer to paragraph starting as "Set Q to the set of resources for transmission of corresponding PUCCHs in a single + // slot without repetitions where". + pucch_grant_list mplexed_grants; - // [Implementation-defined] We only allow a max number of PUCCH + PUSCH grants per slot. - if (pucch_slot_alloc.result.ul.pucchs.size() >= - get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size()))) { - logger.warning("rnti={}: CSI allocation on PUCCH Format2 for slot={} skipped. Cause: UL grants reached", - crnti, - pucch_slot_alloc.slot); - return; + std::vector resource_set_q; + + // Build the resource set Q. Refer to Section 9.2.5, TS 38.213. + if (candidate_grants.harq_resource.has_value()) { + resource_set_q.emplace_back(candidate_grants.harq_resource.value()); + } + if (candidate_grants.sr_resource.has_value()) { + resource_set_q.emplace_back(candidate_grants.sr_resource.value()); + } + if (candidate_grants.csi_resource.has_value()) { + resource_set_q.emplace_back(candidate_grants.csi_resource.value()); } - // Get the F2 resource specific for with CSI. - const pucch_resource* csi_f2_res = resource_manager.reserve_csi_resource(pucch_slot_alloc.slot, crnti, ue_cell_cfg); + // Sort the resources in the set based on the number of symbols. + auto sort_res_set_q = [&resource_set_q]() { + std::sort(resource_set_q.begin(), resource_set_q.end(), [](const pucch_grant& lhs_res, const pucch_grant& rhs_res) { + return lhs_res.get_symbols().start() < rhs_res.get_symbols().start() or + (lhs_res.get_symbols().start() == rhs_res.get_symbols().start() and + lhs_res.get_symbols().length() > rhs_res.get_symbols().length()); + }); + }; - if (csi_f2_res == nullptr) { - logger.warning( - "rnti={}: CSI could not be allocated on PUCCH Format2 for slot={}. Cause: PUCCH F2 resource not available", - crnti, - pucch_slot_alloc.slot); - return; - } + sort_res_set_q(); + + // This is the implementation of the pseudo-code for multiplexing the resources provided in Section 9.2.5, TS 38.213. + unsigned o_cnt = 0; + unsigned j_cnt = 0; + while (j_cnt < resource_set_q.size()) { + if (j_cnt < resource_set_q.size() - 1 and + resource_set_q[j_cnt - o_cnt].get_symbols().overlaps(resource_set_q[j_cnt + 1].get_symbols())) { + ++j_cnt; + ++o_cnt; + } else { + if (o_cnt > 0U) { + // Merge the overlapping resources. + std::optional new_res = + merge_pucch_resources(span(&resource_set_q[j_cnt - o_cnt], o_cnt + 1), + sl_tx, + crnti, + ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(), + preserve_res_indicator); + if (not new_res.has_value()) { + return {}; + } + // Remove the old resources that got merged from the set. + // TODO: check if, by using a different data structure, we can achive the deletion more efficiently. + resource_set_q.erase(resource_set_q.begin() + j_cnt - o_cnt, resource_set_q.begin() + j_cnt + 1); - const float max_pucch_code_rate = to_max_code_rate_float(ue_cell_cfg.cfg_dedicated() - .ul_config.value() - .init_ul_bwp.pucch_cfg.value() - .format_2_common_param.value() - .max_c_rate); - const unsigned max_payload = - get_pucch_format2_max_payload(std::get(csi_f2_res->format_params).nof_prbs, - std::get(csi_f2_res->format_params).nof_symbols, - max_pucch_code_rate); + // Add the new resource (resulting from the previous merge) to the set. + resource_set_q.push_back(new_res.value()); - // When this function is called, it means that there are no SR grants to be multiplexed with CSI; thus, the CSI bits - // are the only UCI bits to be considered. - // It's the validator that should make sure the CSI bits fit into a PUCCH Format 2 resource. - srsran_assert(max_payload >= csi_part1_bits, - "rnti={}: PUCCH F2 max payload {} is insufficient for {} candidate UCI bits", - crnti, - max_payload, - csi_part1_bits); + // Sort the resources in the set based on the first symbol position and number of symbols. + sort_res_set_q(); - // Allocate a PUCCH PDU in the list and fill it with the parameters. - pucch_info& pucch_pdu = pucch_slot_alloc.result.ul.pucchs.emplace_back(); - // Neither HARQ-ACK bits - const unsigned harq_ack_bits_only_csi = 0U; - const sr_nof_bits sr_bits_only_csi = sr_nof_bits::no_sr; - fill_pucch_format2_grant(pucch_pdu, - crnti, - *csi_f2_res, - ue_cell_cfg, - std::get(csi_f2_res->format_params).nof_prbs, - harq_ack_bits_only_csi, - sr_bits_only_csi, - csi_part1_bits); -} + // Reset the counter and start from the beginning. + j_cnt = 0; + o_cnt = 0; + } else { + ++j_cnt; + } + } + } -std::optional pucch_allocator_impl::add_harq_bits_to_harq_f2_grant(pucch_info& existing_f2_grant, - slot_point sl_tx, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment) -{ - const unsigned current_csi_part1_bits = existing_f2_grant.format_2.csi_part1_bits; - const unsigned current_harq_ack_bits = existing_f2_grant.format_2.harq_ack_nof_bits; - const sr_nof_bits current_sr_bits = existing_f2_grant.format_2.sr_bits; + // The PUCCH resource multiplexing algorithm above is specified from the UE's perspective. In the GNB, we need to add + // an extra resource Format 1 if slot there is a SR opportunity and HARQ bits to be reported with PUCCH Format 1. + if (resource_set_q.size() == 1 and resource_set_q.front().get_format() == pucch_format::FORMAT_1 and + resource_set_q.front().bits.harq_ack_nof_bits != 0 and + resource_set_q.front().bits.sr_bits != sr_nof_bits::no_sr) { + const pucch_resource* sr_res = resource_manager.reserve_sr_res_available( + sl_tx, crnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + resource_manager.set_new_resource_allocation(crnti, pucch_resource_usage::SR); + if (sr_res == nullptr) { + logger.error("This is not expected"); + } + pucch_uci_bits bits = {.harq_ack_nof_bits = resource_set_q.front().bits.harq_ack_nof_bits, + .sr_bits = resource_set_q.front().bits.sr_bits, + .csi_part1_nof_bits = 0}; + resource_set_q.emplace_back(pucch_grant{.type = pucch_grant_type::sr, .bits = bits, .pucch_res_cfg = sr_res}); + } - // This function cannot be called if the resource of for CSI and needs to be converted into HARQ-ACK. - srsran_sanity_check(current_harq_ack_bits != 0 and harq_ack_bits_increment != 0, - "This PUCCH grant is expected to have HARQ-ACK bits to report"); + // Build the final list of PUCCH resources that need to be transmitted. + for (const auto& mplex_res : resource_set_q) { + if (mplex_res.type == pucch_grant_type::harq_ack) { + mplexed_grants.harq_resource.emplace(mplex_res); + ++mplexed_grants.nof_grants; + } else if (mplex_res.type == pucch_grant_type::sr) { + mplexed_grants.sr_resource.emplace(mplex_res); + ++mplexed_grants.nof_grants; + } else if (mplex_res.type == pucch_grant_type::csi) { + mplexed_grants.csi_resource.emplace(mplex_res); + ++mplexed_grants.nof_grants; + } + } + return mplexed_grants; +} - const pucch_config& pucch_cfg = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); - const pucch_harq_resource_alloc_record pucch_f2_harq_cfg = - resource_manager.fetch_allocated_f2_harq_resource(sl_tx, existing_f2_grant.crnti, pucch_cfg); +std::optional +pucch_allocator_impl::merge_pucch_resources(span resources_to_merge, + slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg, + bool preserve_res_indicator) +{ + // This function implements the merging rules for HARQ-ACK, SR and CSI defined in Section 9.2.5.1 and 9.2.5.2, + // TS 38.213. - if (pucch_f2_harq_cfg.pucch_res == nullptr) { - srsran_assertion_failure( - "PUCCH F2 resource previously allocated for HARQ-ACK not found in the PUCCH resource manager"); + // This function should only be called if there are 2 or 3 resources. + if (resources_to_merge.size() == 1U or resources_to_merge.size() > 3U) { return std::nullopt; } - const unsigned candidate_uci_bits = - current_harq_ack_bits + harq_ack_bits_increment + sr_nof_bits_to_uint(current_sr_bits) + current_csi_part1_bits; + if (resources_to_merge.size() == 2) { + const pucch_grant& r_0 = resources_to_merge[0]; + const pucch_grant& r_1 = resources_to_merge[1]; + + // SR and HARQ only. + if ((r_0.type == pucch_grant_type::sr and r_1.type == pucch_grant_type::harq_ack) or + (r_0.type == pucch_grant_type::harq_ack and r_1.type == pucch_grant_type::sr)) { + const pucch_grant& r_harq = r_0.type == pucch_grant_type::harq_ack ? r_0 : r_1; + const pucch_grant& r_sr = r_0.type == pucch_grant_type::sr ? r_0 : r_1; + + // When merging SR and HARQ, no matter the format, the resource to be used is the one from HARQ-ACK. + pucch_grant new_resource{.type = pucch_grant_type::harq_ack}; + // For the case of HARQ-ACK with Format 0 or Format 1, the HARQ-ACK resource only has HARQ-ACK bits. + // NOTE: This rule is defined in Section 9.2.5.1, TS 38.213. + if (r_harq.get_format() == pucch_format::FORMAT_0 or r_harq.get_format() == pucch_format::FORMAT_1) { + srsran_assert(r_sr.get_format() == r_harq.get_format(), "The two resources must have the same format"); + // TODO: Multiplexing of SR + HARQ on F0 needs to be checked, as the TS is not clear about it. + new_resource = r_harq; + new_resource.bits.sr_bits = r_sr.bits.sr_bits; + return new_resource; + } + // Apply HARQ merging rule for Format >= 2: all PUCCH resources will be multiplexed in a PUCCH resource from PUCCH + // res set 1. + // NOTE: This rule is defined in Section 9.2.5.1 or 9.2.5.2 (if any CSI bits), TS 38.213. + // NOTE: For the case of HARQ-ACK with Format >= 2, the HARQ-ACK resource has HARQ-ACK bits and optionally CSI + // bits. + else { + srsran_assert(r_sr.get_format() == pucch_format::FORMAT_0 or r_sr.get_format() == pucch_format::FORMAT_1, + "The two resources must have the same format"); + // Apply F2 CSI merging rule: SR and CSI PUCCH resources will be multiplexed in the CSI PUCCH resource. + // A HARQ resource from PUCCH resource set idx 1 already exits. Use that one. + if (r_harq.harq_id.pucch_set_idx == pucch_res_set_idx::set_1) { + new_resource = r_harq; + } + // Get a resource from PUCCH resource set idx 1, if available, with the same PUCCH resource indicator as for the + // PUCCH resource from set idx 0. + // NOTE: This sub-case is used by the PUCCH common and dedicated allocator. + else if (preserve_res_indicator) { + const pucch_resource* pucch_res = resource_manager.reserve_set_1_res_by_res_indicator( + slot_harq, crnti, r_harq.harq_id.pucch_res_ind, pucch_cfg); + resource_manager.set_new_resource_allocation(crnti, pucch_resource_usage::HARQ_SET_1); + if (pucch_res != nullptr) { + return std::nullopt; + } - // NOTE: The process of (i) checking the constraint max_payload >= candidate_uci_bits and (ii) the number of required - // PRBs and (iii) updating the PUCCH grant PRBs values needs to be repeated every time we add HARQ bits; this is - // because the required number of PRBs can increase as a result of adding HARQ-ACK bits. + new_resource.harq_id.pucch_set_idx = pucch_res_set_idx::set_1; + new_resource.harq_id.pucch_res_ind = r_harq.harq_id.pucch_res_ind; + new_resource.pucch_res_cfg = pucch_res; + } + // Get a new HARQ resource (from PUCCH resource set idx 1) from the resource manager. + else { + pucch_harq_resource_alloc_record res_alloc = + resource_manager.reserve_next_set_1_harq_res_available(slot_harq, crnti, pucch_cfg); + resource_manager.set_new_resource_allocation(crnti, pucch_resource_usage::HARQ_SET_1); + if (res_alloc.pucch_res != nullptr) { + return std::nullopt; + } - // Check if the number of PRBs is sufficient for the number of bits to be acked. - const float max_pucch_code_rate = to_max_code_rate_float(ue_cell_cfg.cfg_dedicated() - .ul_config.value() - .init_ul_bwp.pucch_cfg.value() - .format_2_common_param.value() - .max_c_rate); + new_resource.harq_id.pucch_set_idx = pucch_res_set_idx::set_1; + new_resource.harq_id.pucch_res_ind = res_alloc.pucch_res_indicator; + new_resource.pucch_res_cfg = res_alloc.pucch_res; + } + // If any CSI bits, these are contained in the HARQ-ACK resource. + new_resource.bits.harq_ack_nof_bits = r_harq.bits.harq_ack_nof_bits; + new_resource.bits.csi_part1_nof_bits = r_harq.bits.csi_part1_nof_bits; + new_resource.bits.sr_bits = r_sr.bits.sr_bits; + return new_resource; + } + } - const auto& f2_params = std::get(pucch_f2_harq_cfg.pucch_res->format_params); - const unsigned max_nof_prbs = f2_params.nof_prbs; - const unsigned nof_symbols = f2_params.nof_symbols; - const unsigned max_payload = get_pucch_format2_max_payload(max_nof_prbs, nof_symbols, max_pucch_code_rate); + // SR and CSI only. In the case, the SR resource has only SR bits, and the CSI resource has only CSI bits. + // Apply CSI merging rule for SR + CSI; this is defined in Section 9.2.5.1, TS 38.213. + if ((r_0.type == pucch_grant_type::sr and r_1.type == pucch_grant_type::csi) or + (r_0.type == pucch_grant_type::csi and r_1.type == pucch_grant_type::sr)) { + // Apply F2 CSI merging rule: SR and CSI PUCCH resources will be multiplexed in the CSI PUCCH resource. + pucch_grant new_resource{.type = pucch_grant_type::csi}; + const pucch_grant& r_csi = r_0.type == pucch_grant_type::csi ? r_0 : r_1; + const pucch_grant& r_sr = r_0.type == pucch_grant_type::sr ? r_0 : r_1; + // We don't support SR with Format 0 on the same slot as CSI. + srsran_assert(r_sr.get_format() != pucch_format::FORMAT_0, + "SR with Format 0 is not supported on the same slot as CSI"); + // Copy the SR bits in the CSI resource. + new_resource = r_csi; + new_resource.bits.sr_bits = r_sr.bits.sr_bits; + + // Check if the UCI payload fits in the PUCCH resource. + if (new_resource.bits.get_total_bits() <= pucch_cfg.get_max_payload(new_resource.get_format())) { + return new_resource; + } else { + return std::nullopt; + } + } - if (max_payload < candidate_uci_bits) { - logger.debug("rnti={}: PUCCH HARQ-ACK allocation for slot={} skipped. Cause: PUCCH F2 max payload {} is " - "insufficient for {} candidate UCI bits", - crnti, - sl_tx, - max_payload, - candidate_uci_bits); + // HARQ and CSI only. Apply HARQ merging rule for Format >= 2: all PUCCH resources will be multiplexed in a PUCCH + // resource from PUCCH resource set 1. + // NOTE: This rule is defined in Section 9.2.5.2, TS 38.213. + // NOTE: SR bits, if present, can be in either HARQ or CSI, but not in both. + if ((r_0.type == pucch_grant_type::harq_ack and r_1.type == pucch_grant_type::csi) or + (r_0.type == pucch_grant_type::csi and r_1.type == pucch_grant_type::harq_ack)) { + // Apply F2 HARQ merging rule: all PUCCH resources will be multiplexed in a PUCCH resource from PUCCH res set 1. + pucch_grant new_resource{.type = pucch_grant_type::harq_ack}; + const pucch_grant& r_harq = r_0.type == pucch_grant_type::harq_ack ? r_0 : r_1; + const pucch_grant& r_csi = r_0.type == pucch_grant_type::csi ? r_0 : r_1; + + // A HARQ resource from PUCCH resource set idx 1 already exits. Use that one. + if (r_harq.harq_id.pucch_set_idx == pucch_res_set_idx::set_1) { + new_resource = r_harq; + } + // Get a resource from PUCCH resource set idx 1, if available, with the same PUCCH resource indicator as for the + // PUCCH resource from set idx 0. + // NOTE: This sub-case is used by the PUCCH common and dedicated allocator. + else if (preserve_res_indicator) { + const pucch_resource* pucch_res = resource_manager.reserve_set_1_res_by_res_indicator( + slot_harq, crnti, r_harq.harq_id.pucch_res_ind, pucch_cfg); + resource_manager.set_new_resource_allocation(crnti, pucch_resource_usage::HARQ_SET_1); + if (pucch_res == nullptr) { + return std::nullopt; + } - // No need to release the resource, as it was previously allocated for other HARQ processes. - return std::nullopt; - } + new_resource.harq_id.pucch_set_idx = pucch_res_set_idx::set_1; + new_resource.harq_id.pucch_res_ind = r_harq.harq_id.pucch_res_ind; + new_resource.pucch_res_cfg = pucch_res; + } + // Get a new HARQ resource (from PUCCH resource set idx 1) from the resource manager. + else { + pucch_harq_resource_alloc_record res_alloc = + resource_manager.reserve_next_set_1_harq_res_available(slot_harq, crnti, pucch_cfg); + resource_manager.set_new_resource_allocation(crnti, pucch_resource_usage::HARQ_SET_1); + if (res_alloc.pucch_res == nullptr) { + return std::nullopt; + } - const unsigned nof_prbs = - get_pucch_format2_nof_prbs(candidate_uci_bits, max_nof_prbs, nof_symbols, max_pucch_code_rate); - // NOTE: there is no need to check if the code rate is within the limit, as the UCI bits are computed so that not to - // exceed the code rate. - existing_f2_grant.resources.prbs.set(pucch_f2_harq_cfg.pucch_res->starting_prb, - pucch_f2_harq_cfg.pucch_res->starting_prb + nof_prbs); - if (pucch_f2_harq_cfg.pucch_res->second_hop_prb.has_value()) { - existing_f2_grant.resources.second_hop_prbs.set(pucch_f2_harq_cfg.pucch_res->second_hop_prb.value(), - pucch_f2_harq_cfg.pucch_res->second_hop_prb.value() + nof_prbs); + new_resource.harq_id.pucch_set_idx = pucch_res_set_idx::set_1; + new_resource.harq_id.pucch_res_ind = res_alloc.pucch_res_indicator; + new_resource.pucch_res_cfg = res_alloc.pucch_res; + } + + new_resource.bits.harq_ack_nof_bits = r_harq.bits.harq_ack_nof_bits; + new_resource.bits.csi_part1_nof_bits = r_csi.bits.csi_part1_nof_bits; + // SR bits, if present, can be in either HARQ or CSI, but not in both. + if (r_harq.bits.sr_bits != sr_nof_bits::no_sr) { + new_resource.bits.sr_bits = r_harq.bits.sr_bits; + } else if (r_csi.bits.sr_bits != sr_nof_bits::no_sr) { + new_resource.bits.sr_bits = r_csi.bits.sr_bits; + } + + // Check if the UCI payload fits in the PUCCH resource. + if (new_resource.bits.get_total_bits() <= pucch_cfg.get_max_payload(new_resource.get_format())) { + return new_resource; + } else { + return std::nullopt; + } + } } - existing_f2_grant.format_2.harq_ack_nof_bits += harq_ack_bits_increment; + // Apply HARQ merging rule for Format >= 2: all PUCCH resources will be multiplexed in a PUCCH resource from PUCCH + // resource set 1. + // NOTE: This rule is defined in Section 9.2.5.2, TS 38.213. + if (resources_to_merge.size() == 3) { + // Apply F2 HARQ merging rule: all PUCCH resources will be multiplexed in a PUCCH resource from PUCCH res set 1. + pucch_grant new_resource{.type = pucch_grant_type::harq_ack}; + const pucch_grant* r_harq_ptr = nullptr; + const pucch_grant* r_sr_ptr = nullptr; + const pucch_grant* r_csi_ptr = nullptr; + for (const auto& grant : resources_to_merge) { + if (grant.type == pucch_grant_type::harq_ack) { + r_harq_ptr = &grant; + } else if (grant.type == pucch_grant_type::sr) { + r_sr_ptr = &grant; + } else if (grant.type == pucch_grant_type::csi) { + r_csi_ptr = &grant; + } + } - return pucch_f2_harq_cfg.pucch_res_indicator; -} + srsran_assert(r_harq_ptr != nullptr and r_sr_ptr != nullptr and r_csi_ptr != nullptr, + "The three resources must be present"); + if (r_sr_ptr->get_format() == pucch_format::FORMAT_0) { + srsran_assertion_failure("This case is not yet supported"); + // SR and CSI are not supported on the same slot if SR uses Format 0. + return std::nullopt; + } -void pucch_allocator_impl::fill_pucch_ded_format1_grant(pucch_info& pucch_grant, - rnti_t crnti, - const pucch_resource& pucch_ded_res_cfg, - unsigned harq_ack_bits, - sr_nof_bits sr_bits) -{ - pucch_grant.crnti = crnti; - pucch_grant.bwp_cfg = &cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; - pucch_grant.format = pucch_format::FORMAT_1; + if (r_harq_ptr->get_format() != pucch_format::FORMAT_0 and r_harq_ptr->get_format() != pucch_format::FORMAT_1) { + new_resource = *r_harq_ptr; + } else { + pucch_harq_resource_alloc_record res_alloc = + resource_manager.reserve_next_set_1_harq_res_available(slot_harq, crnti, pucch_cfg); + resource_manager.set_new_resource_allocation(crnti, pucch_resource_usage::HARQ_SET_1); + if (res_alloc.pucch_res == nullptr) { + return std::nullopt; + } - // Set PRBs and symbols, first. - // The number of PRBs is not explicitly stated in the TS, but it can be inferred it's 1. - const auto& res_f1 = std::get(pucch_ded_res_cfg.format_params); - pucch_grant.resources.prbs.set(pucch_ded_res_cfg.starting_prb, - pucch_ded_res_cfg.starting_prb + PUCCH_FORMAT_1_NOF_PRBS); - pucch_grant.resources.symbols.set(res_f1.starting_sym_idx, res_f1.starting_sym_idx + res_f1.nof_symbols); - if (pucch_ded_res_cfg.second_hop_prb.has_value()) { - pucch_grant.resources.second_hop_prbs.set(pucch_ded_res_cfg.second_hop_prb.value(), - pucch_ded_res_cfg.second_hop_prb.value() + PUCCH_FORMAT_1_NOF_PRBS); + new_resource.harq_id.pucch_set_idx = pucch_res_set_idx::set_1; + new_resource.harq_id.pucch_res_ind = res_alloc.pucch_res_indicator; + new_resource.pucch_res_cfg = res_alloc.pucch_res; + } + new_resource.bits.harq_ack_nof_bits = r_harq_ptr->bits.harq_ack_nof_bits; + new_resource.bits.sr_bits = r_sr_ptr->bits.sr_bits; + new_resource.bits.csi_part1_nof_bits = r_csi_ptr->bits.csi_part1_nof_bits; + + // Check if the UCI payload fits in the PUCCH resource. + if (new_resource.bits.get_total_bits() <= pucch_cfg.get_max_payload(new_resource.get_format())) { + return new_resource; + } else { + return std::nullopt; + } } - // \c pucch-GroupHopping and \c hoppingId are set as per TS 38.211, Section 6.3.2.2.1. - pucch_grant.format_1.group_hopping = cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->group_hopping; - pucch_grant.format_1.n_id_hopping = cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->hopping_id.has_value() - ? cell_cfg.ul_cfg_common.init_ul_bwp.pucch_cfg_common->hopping_id.value() - : cell_cfg.pci; - pucch_grant.format_1.initial_cyclic_shift = res_f1.initial_cyclic_shift; - pucch_grant.format_1.time_domain_occ = res_f1.time_domain_occ; - // For PUCCH Format 1, only 1 SR bit. - pucch_grant.format_1.sr_bits = sr_bits; - pucch_grant.format_1.harq_ack_nof_bits = harq_ack_bits; - // [Implementation-defined] We do not implement PUCCH over several slots. - pucch_grant.format_1.slot_repetition = pucch_repetition_tx_slot::no_multi_slot; + + return std::nullopt; } -void pucch_allocator_impl::fill_pucch_format2_grant(pucch_info& pucch_grant, - rnti_t crnti, - const pucch_resource& pucch_ded_res_cfg, - const ue_cell_configuration& ue_cell_cfg, - unsigned nof_prbs, - unsigned harq_ack_bits, - sr_nof_bits sr_bits, - unsigned csi_part1_bits) +std::optional pucch_allocator_impl::allocate_grants(cell_slot_resource_allocator& pucch_slot_alloc, + ue_grants& existing_pucchs, + rnti_t crnti, + pucch_grant_list grants_to_tx, + const ue_cell_configuration& ue_cell_cfg) { - pucch_grant.crnti = crnti; - pucch_grant.bwp_cfg = &cell_cfg.ul_cfg_common.init_ul_bwp.generic_params; - pucch_grant.format = pucch_format::FORMAT_2; - - // Set PRBs and symbols, first.º - // The number of PRBs is not explicitly stated in the TS, but it can be inferred it's 1. - pucch_grant.resources.prbs.set(pucch_ded_res_cfg.starting_prb, pucch_ded_res_cfg.starting_prb + nof_prbs); - const auto& res_f2 = std::get(pucch_ded_res_cfg.format_params); - pucch_grant.resources.symbols.set(res_f2.starting_sym_idx, res_f2.starting_sym_idx + res_f2.nof_symbols); - if (pucch_ded_res_cfg.second_hop_prb.has_value()) { - pucch_grant.resources.second_hop_prbs.set(pucch_ded_res_cfg.second_hop_prb.value(), - pucch_ded_res_cfg.second_hop_prb.value() + nof_prbs); + auto& pucch_pdus = pucch_slot_alloc.result.ul.pucchs; + + // Retrieve the existing PUCCH PDUs. + existing_pucch_pdus_handler existing_pdus( + crnti, + pucch_pdus, + get_sr_pucch_res_cfg(ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value())); + + // Check if we can fit the new PUCCH PDUs in the output results. + if ((grants_to_tx.nof_grants >= existing_pdus.get_nof_unallocated_pdu()) and + (pucch_slot_alloc.result.ul.pucchs.size() + (grants_to_tx.nof_grants - existing_pdus.get_nof_unallocated_pdu()) > + get_max_pucch_grants(static_cast(pucch_slot_alloc.result.ul.puschs.size())))) { + logger.info( + "rnti={}: PUCCH allocation for slot={} skipped. Cause: UL grants reached", crnti, pucch_slot_alloc.slot); + return std::nullopt; } - pucch_grant.format_2.sr_bits = sr_bits; - pucch_grant.format_2.harq_ack_nof_bits = harq_ack_bits; - pucch_grant.format_2.csi_part1_bits = csi_part1_bits; - // \f$n_{ID}\f$ as per Section 6.3.2.5.1 and 6.3.2.6.1, TS 38.211. - pucch_grant.format_2.n_id_scambling = - ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().data_scrambling_id_pusch.has_value() - ? ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().data_scrambling_id_pusch.value() - : cell_cfg.pci; - // \f$N_{ID}^0\f$ as per TS 38.211, Section 6.4.1.3.2.1. - pucch_grant.format_2.n_id_0_scrambling = get_n_id0_scrambling(ue_cell_cfg, cell_cfg.pci); - pucch_grant.format_2.max_code_rate = ue_cell_cfg.cfg_dedicated() - .ul_config.value() - .init_ul_bwp.pucch_cfg.value() - .format_2_common_param.value() - .max_c_rate; - - // Generate CSI report configuration if there are CSI bits in UCI. - if (csi_part1_bits > 0) { - pucch_grant.csi_rep_cfg = create_csi_report_configuration(*ue_cell_cfg.cfg_dedicated().csi_meas_cfg); + bool harq_grant_alloc_completed = false; + bool sr_grant_alloc_completed = false; + bool csi_grant_alloc_completed = false; + // If there was a CSI grant, re-use the previous one and update the UCI bits with SR. + if (grants_to_tx.csi_resource.has_value() and existing_pucchs.pucch_grants.csi_resource.has_value() and + existing_pdus.csi_pdu != nullptr) { + logger.info( + "rnti={}: PUCCH PDU allocated on CSI resource: slot={}, prbs={}, sym={}, h_bits={}, sr_bits={}, cs_bits={}", + crnti, + pucch_slot_alloc.slot, + existing_pdus.csi_pdu->resources.prbs, + existing_pdus.csi_pdu->resources.symbols, + existing_pdus.csi_pdu->format_2.harq_ack_nof_bits, + grants_to_tx.csi_resource.value().bits.sr_bits, + grants_to_tx.csi_resource.value().bits.csi_part1_nof_bits); + existing_pdus.update_csi_pdu_bits(grants_to_tx.csi_resource.value().bits.csi_part1_nof_bits, + grants_to_tx.csi_resource.value().bits.sr_bits); + csi_grant_alloc_completed = true; + } + // If there was a SR grant, re-use the previous one and update UCI bits with HARQ bits. + else if (grants_to_tx.sr_resource.has_value() and existing_pucchs.pucch_grants.sr_resource.has_value() and + existing_pdus.sr_pdu != nullptr) { + // NOTE: the validator is responsible for checking that the is no mix of PUCCH Format 0 and Format 1. + logger.info("rnti={}: PUCCH PDU allocated on SR resource (updated): slot={}, prbs={}, sym={}, cs={}, occ={}, " + "h_bits={}, sr_bits={}", + crnti, + pucch_slot_alloc.slot, + existing_pdus.sr_pdu->resources.prbs, + existing_pdus.sr_pdu->resources.symbols, + existing_pdus.sr_pdu->format_1.initial_cyclic_shift, + existing_pdus.sr_pdu->format_1.time_domain_occ, + grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.harq_resource.value().bits.sr_bits); + existing_pdus.update_sr_pdu_bits(grants_to_tx.sr_resource.value().bits.sr_bits, + grants_to_tx.sr_resource.value().bits.harq_ack_nof_bits); + sr_grant_alloc_completed = true; + } + // If there was a HARQ grant of the same PUCCH format, re-use the previous one and update the UCI bits HARQ/CSI/SR + // bits. + if (grants_to_tx.harq_resource.has_value() and existing_pucchs.pucch_grants.harq_resource.has_value() and + grants_to_tx.harq_resource.value().get_format() == + existing_pucchs.pucch_grants.harq_resource.value().get_format() and + existing_pdus.harq_pdu != nullptr) { + // Update bits; + if (grants_to_tx.harq_resource.value().get_format() == pucch_format::FORMAT_1) { + logger.info("rnti={}: PUCCH PDU allocated on F1 HARQ resource (updated): slot={}, prbs={}, sym={}, cs={}, " + "occ={}, h_bits={}, sr_bits={}", + crnti, + pucch_slot_alloc.slot, + existing_pdus.harq_pdu->resources.prbs, + existing_pdus.harq_pdu->resources.symbols, + existing_pdus.harq_pdu->format_1.initial_cyclic_shift, + existing_pdus.harq_pdu->format_1.time_domain_occ, + grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.harq_resource.value().bits.sr_bits); + } else { + logger.info("rnti={}: PUCCH PDU allocated on F2 HARQ resource (updated): slot={}, prbs={}, sym={}, h_bits={}, " + "sr_bits={}, cs_bits={}", + crnti, + pucch_slot_alloc.slot, + existing_pdus.harq_pdu->resources.prbs, + existing_pdus.harq_pdu->resources.symbols, + grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.harq_resource.value().bits.sr_bits, + grants_to_tx.harq_resource.value().bits.csi_part1_nof_bits); + } + existing_pdus.update_harq_pdu_bits(grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.harq_resource.value().bits.sr_bits, + grants_to_tx.harq_resource.value().bits.csi_part1_nof_bits); + harq_grant_alloc_completed = true; } -} -pucch_allocator_impl::existing_pucch_grants -pucch_allocator_impl::get_existing_pucch_grants(static_vector& pucchs, - rnti_t rnti, - slot_point sl_ack) -{ - existing_pucch_grants grants; - for (auto& pucch : pucchs) { - if (pucch.crnti == rnti) { - // First look for first for Format 2; if present, this is the only PUCCH resource allocated to the UE. - if (pucch.format == srsran::pucch_format::FORMAT_2) { - grants.format2_grant = &pucch; - } else if (pucch.format == srsran::pucch_format::FORMAT_1) { - if (pucch.format_1.sr_bits != sr_nof_bits::no_sr) { - grants.format1_sr_grant = &pucch; - } - // In the following, we need to check whether the PUCCH grant found in the scheduler output is a common or - // dedicated resource. - else if (pucch.format_1.harq_ack_nof_bits > 0) { - auto* pucch_common_it = std::find( - pucch_common_alloc_grid[sl_ack.to_uint()].begin(), pucch_common_alloc_grid[sl_ack.to_uint()].end(), rnti); - if (pucch_common_it != pucch_common_alloc_grid[sl_ack.to_uint()].end()) { - grants.format1_harq_common_grant = &pucch; - } else { - grants.format1_harq_grant = &pucch; - } - } else { - logger.error("rnti={}: unexpected PUCCH grant found in slot={}", rnti, sl_ack); - } - } + pucch_uci_bits bits = grants_to_tx.get_uci_bits(); + if (grants_to_tx.csi_resource.has_value() and not csi_grant_alloc_completed) { + pucch_info* grant = existing_pdus.get_next_grant(pucch_pdus); + srsran_assert(grant != nullptr, "The return grant cannot be nullptr"); + const unsigned nof_prbs = + std::get(grants_to_tx.csi_resource.value().pucch_res_cfg->format_params).nof_prbs; + fill_pucch_format2_grant(*grant, + crnti, + *grants_to_tx.csi_resource.value().pucch_res_cfg, + ue_cell_cfg, + nof_prbs, + 0U, + grants_to_tx.csi_resource.value().bits.sr_bits, + grants_to_tx.csi_resource.value().bits.csi_part1_nof_bits); + logger.info( + "rnti={}: PUCCH PDU allocated on CSI resource: slot={}, prbs={}, sym={}, h_bits={}, sr_bits={}, cs_bits={}", + crnti, + pucch_slot_alloc.slot, + grant->resources.prbs, + grant->resources.symbols, + grant->format_2.harq_ack_nof_bits, + grant->format_2.sr_bits, + grant->format_2.csi_part1_bits); + } + if (grants_to_tx.sr_resource.has_value() and not sr_grant_alloc_completed) { + pucch_info* grant = existing_pdus.get_next_grant(pucch_pdus); + srsran_assert(grant != nullptr, "The return grant cannot be nullptr"); + if (grants_to_tx.sr_resource.value().get_format() == pucch_format::FORMAT_0) { + // TODO + srsran_assertion_failure("PUCCH Format 0 is not yet implemented"); + } else { + fill_pucch_ded_format1_grant(*grant, + crnti, + *grants_to_tx.sr_resource.value().pucch_res_cfg, + grants_to_tx.sr_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.sr_resource.value().bits.sr_bits); + } + logger.info( + "rnti={}: PUCCH PDU allocated on SR resource: slot={}, prbs={}, sym={}, cs={}, occ={}, h_bits={}, sr_bits={}", + crnti, + pucch_slot_alloc.slot, + grant->resources.prbs, + grant->resources.symbols, + grant->format_1.initial_cyclic_shift, + grant->format_1.time_domain_occ, + grant->format_1.harq_ack_nof_bits, + grant->format_1.sr_bits); + } + if (grants_to_tx.harq_resource.has_value() and not harq_grant_alloc_completed) { + pucch_info* grant = existing_pdus.get_next_grant(pucch_pdus); + if (grants_to_tx.harq_resource.value().get_format() == pucch_format::FORMAT_0) { + // TODO + srsran_assertion_failure("PUCCH Format 0 is not yet implemented"); + } else if (grants_to_tx.harq_resource.value().get_format() == pucch_format::FORMAT_1) { + fill_pucch_ded_format1_grant(*grant, + crnti, + *grants_to_tx.harq_resource.value().pucch_res_cfg, + grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.harq_resource.value().bits.sr_bits); + } else { + const auto& f2_cfg = + std::get(grants_to_tx.harq_resource.value().pucch_res_cfg->format_params); + const float max_pucch_code_rate = to_max_code_rate_float(ue_cell_cfg.cfg_dedicated() + .ul_config.value() + .init_ul_bwp.pucch_cfg.value() + .format_2_common_param.value() + .max_c_rate); + const unsigned nof_prbs = + get_pucch_format2_nof_prbs(bits.get_total_bits(), f2_cfg.nof_prbs, f2_cfg.nof_symbols, max_pucch_code_rate); + fill_pucch_format2_grant(*grant, + crnti, + *grants_to_tx.harq_resource.value().pucch_res_cfg, + ue_cell_cfg, + nof_prbs, + grants_to_tx.harq_resource.value().bits.harq_ack_nof_bits, + grants_to_tx.harq_resource.value().bits.sr_bits, + grants_to_tx.harq_resource.value().bits.csi_part1_nof_bits); + } + if (grant->format == pucch_format::FORMAT_1) { + logger.info("rnti={}: PUCCH PDU allocated on F1 HARQ resource: slot={}, prbs={}, sym={}, cs={}, occ={}, " + "h_bits={}, sr_bits={}", + crnti, + pucch_slot_alloc.slot, + grant->resources.prbs, + grant->resources.symbols, + grant->format_1.initial_cyclic_shift, + grant->format_1.time_domain_occ, + grant->format_1.harq_ack_nof_bits, + grant->format_1.sr_bits); + } else { + logger.info("rnti={}: PUCCH PDU allocated on F2 HARQ resource: slot={}, format={}, prbs={}, sym={}, h_bits={}, " + "sr_bits={}, cs_bits={}", + crnti, + pucch_slot_alloc.slot, + grant->format, + grant->resources.prbs, + grant->resources.symbols, + grant->format_2.harq_ack_nof_bits, + grant->format_2.sr_bits, + grant->format_2.csi_part1_bits); } } - return grants; -} + slot_point sl_tx = pucch_slot_alloc.slot; -bool pucch_allocator_impl::has_common_pucch_f1_grant(rnti_t rnti, slot_point sl_tx) const -{ - return std::find(pucch_common_alloc_grid[sl_tx.to_uint()].begin(), - pucch_common_alloc_grid[sl_tx.to_uint()].end(), - rnti) != pucch_common_alloc_grid[sl_tx.to_uint()].end(); -} + // Remove unsed PUCCH PDU, if any. + existing_pdus.remove_unused_pdus(pucch_pdus, crnti); -unsigned pucch_allocator_impl::get_max_pucch_grants(unsigned currently_allocated_puschs) -{ - return std::min(max_pucch_grants_per_slot, max_ul_grants_per_slot - currently_allocated_puschs); + // Remove the previously allocated PUCCH resources which are not needed after the new allocation. + remove_unsed_pucch_res(sl_tx, grants_to_tx, existing_pucchs, ue_cell_cfg); + + // Update the new grants to the UE allocation record. + existing_pucchs.pucch_grants = grants_to_tx; + + // The return value is only relevant if the allocation was called for a HARQ-ACK grant. + return grants_to_tx.harq_resource.has_value() ? grants_to_tx.harq_resource.value().harq_id.pucch_res_ind : 0U; } diff --git a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h index c8f24864d8..3550bbb1fd 100644 --- a/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h +++ b/lib/scheduler/pucch_scheduling/pucch_allocator_impl.h @@ -27,6 +27,7 @@ #include "pucch_allocator.h" #include "pucch_resource_manager.h" #include "srsran/scheduler/scheduler_dci.h" +#include namespace srsran { @@ -75,9 +76,11 @@ class pucch_allocator_impl final : public pucch_allocator rnti_t crnti, const ue_cell_configuration& ue_cell_cfg) override; - bool has_common_pucch_f1_grant(rnti_t rnti, slot_point sl_tx) const override; + [[nodiscard]] bool has_common_pucch_grant(rnti_t rnti, slot_point sl_tx) const override; private: + /// //////////// Helper struct and classes ////////////// + // Structs with the info about the PUCCH resources. struct pucch_res_alloc_cfg { // True if the struct has a valid config. @@ -91,107 +94,146 @@ class pucch_allocator_impl final : public pucch_allocator pucch_format format; }; - // Contains the existing PUCCH grants currently allocated to a given UE. - struct existing_pucch_grants { - pucch_info* format1_sr_grant{nullptr}; - pucch_info* format1_harq_grant{nullptr}; - pucch_info* format1_harq_common_grant{nullptr}; - pucch_info* format2_grant{nullptr}; + struct pucch_common_params { + unsigned pucch_res_indicator; + unsigned r_pucch; }; - // Allocates the PUCCH (common) resource for HARQ-(N)-ACK. - std::optional alloc_pucch_common_res_harq(cell_slot_resource_allocator& pucch_alloc, - const dci_context_information& dci_info); + struct harq_res_id { + pucch_res_set_idx pucch_set_idx = pucch_res_set_idx::set_0; + uint8_t pucch_res_ind = 0; + }; - std::optional exec_common_and_ded_res_alloc(cell_slot_resource_allocator& pucch_alloc, - pucch_info* existing_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - pucch_res_alloc_cfg common_res_cfg, - const pucch_resource& ded_res_cfg); - - // Helper that allocates a NEW PUCCH HARQ grant (Format 1). - std::optional allocate_new_format1_harq_grant(cell_slot_resource_allocator& pucch_slot_alloc, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg, - pucch_info* existing_sr_grant); - - // Helper that add an HARQ-ACK bit to existing PUCCH HARQ grant (Format 1). - std::optional add_harq_ack_bit_to_format1_grant(pucch_info& existing_harq_grant, - pucch_info* existing_sr_grant, - rnti_t rnti, - slot_point sl_tx, - const pucch_config& pucch_cfg); - - // Helper that allocates a new PUCCH HARQ grant (Format 2) for CSI. - void allocate_new_csi_grant(cell_slot_resource_allocator& pucch_slot_alloc, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned csi_part1_bits); - - // Helper that replaces PUCCH grant Format 1 with Format 2 grant for CSI reporting. - void convert_to_format2_csi(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_sr_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned csi_part1_nof_bits); - - // Helper that replaces PUCCH grant Format 1 with Format 2 grant for HARQ-ACK reporting. - std::optional convert_to_format2_harq(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_harq_grant, - pucch_info* existing_sr_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment); - - // Helper that changes the current PUCCH Format 2 grant (specifically used for CSI reporting) into a PUCCH Format 2 - // resource for the HARQ-ACK + CSI. - std::optional change_format2_resource(cell_slot_resource_allocator& pucch_slot_alloc, - pucch_info& existing_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment, - std::optional harq_f2_res); - - // Helper that adds HARQ-ACK bits to a PUCCH Format 2 grant for HARQ-ACK. - std::optional add_harq_bits_to_harq_f2_grant(pucch_info& existing_f2_grant, - slot_point sl_tx, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg, - unsigned harq_ack_bits_increment); - - struct pucch_com_ded_res { - pucch_res_alloc_cfg pucch_common_info; - const pucch_resource& pucch_ded_cfg; + /// Defines the type of resource. + /// HARQ indicates the HAR-ACK resource (it can carry HARQ-ACK and/or SR and/or CSI bits). + /// SR indicates the resource dedicated for SR (it can carry SR and HARQ-ACK bits). + /// CSI indicates the resource dedicated for CSI (it can carry CSI and SR bits). + enum class pucch_grant_type { harq_ack, sr, csi }; + + /// \brief Defines a PUCCH grant (and its relevant information) currently allocated to a given UE. + /// It is used internally to keep track of the UEs' allocations of the PUCCH grants with dedicated resources. + class pucch_grant + { + public: + pucch_grant_type type; + // Only relevant for HARQ-ACK resources. + harq_res_id harq_id; + pucch_uci_bits bits; + // NOTE: The pointer to the PUCCH resource configuration can only be used within the same slot; this is to prevent + // the possibility that the re-configurations can null the pointer before an allocation and the next. + const pucch_resource* pucch_res_cfg = nullptr; + + [[nodiscard]] pucch_format get_format() const + { + return pucch_res_cfg != nullptr ? pucch_res_cfg->format : pucch_format::NOF_FORMATS; + } + [[nodiscard]] ofdm_symbol_range get_symbols() const; }; - std::optional find_common_and_ded_harq_res_available(cell_slot_resource_allocator& pucch_alloc, - pucch_info* existing_grant, - rnti_t rnti, - const ue_cell_configuration& ue_cell_cfg, - const dci_context_information& dci_info); + /// \brief List of possible PUCCH grants that allocated to a UE, at a given slot. + class pucch_grant_list + { + public: + std::optional harq_resource; + std::optional sr_resource; + std::optional csi_resource; + unsigned nof_grants = 0; + + [[nodiscard]] pucch_uci_bits get_uci_bits() const; + [[nodiscard]] bool is_emtpy() const; + }; + + /// Keeps track of the PUCCH grants (common + dedicated) for a given UE. + struct ue_grants { + rnti_t rnti; + // Information about the common PUCCH grant. + bool has_common_pucch = false; + // List of PUCCH grants with dedicated resources. + pucch_grant_list pucch_grants; + }; - // Helper that removes the existing PUCCH Format 1 grants (both HARQ-ACK and SR). - void remove_pucch_format1_from_grants(cell_slot_resource_allocator& slot_alloc, - rnti_t crnti, - const pucch_config& pucch_cfg); + using slot_pucch_grants = static_vector; - // Helper that removes the existing PUCCH Format 2 grant for CSI. - void remove_format2_csi_from_grants(cell_slot_resource_allocator& slot_alloc, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg); + /// //////////// Main private functions ////////////// - // Fills the PUCCH HARQ grant for common resources. + // Allocates the PUCCH (common) resource for HARQ-(N)-ACK. + std::optional alloc_pucch_common_res_harq(cell_slot_resource_allocator& pucch_alloc, + const dci_context_information& dci_info); + + void compute_pucch_common_params_and_alloc(cell_slot_resource_allocator& pucch_alloc, + rnti_t rnti, + pucch_common_params pucch_params); + + std::optional find_common_and_ded_harq_res_available(cell_slot_resource_allocator& pucch_alloc, + ue_grants& existing_grants, + rnti_t rnti, + const ue_cell_configuration& ue_cell_cfg, + const dci_context_information& dci_info); + + // Helper that allocates a NEW PUCCH HARQ grant (Format 0 or 1). + std::optional allocate_harq_grant(cell_slot_resource_allocator& pucch_slot_alloc, + rnti_t crnti, + const ue_cell_configuration& ue_cell_cfg); + + // Helper that allocates a new PUCCH HARQ grant (Format 2/3/4) for CSI. + void allocate_csi_grant(cell_slot_resource_allocator& pucch_slot_alloc, + rnti_t crnti, + const ue_cell_configuration& ue_cell_cfg, + unsigned csi_part1_bits); + + // Implements the main steps of the multiplexing procedure as defined in TS 38.213, Section 9.2.5. + std::optional multiplex_and_allocate_pucch(cell_slot_resource_allocator& pucch_slot_alloc, + pucch_uci_bits new_bits, + ue_grants& current_grants, + const ue_cell_configuration& ue_cell_cfg, + bool preserve_res_indicator = false); + + // Computes which resources are expected to be sent, depending on the UCI bits to be sent, before any multiplexing. + std::optional get_pucch_res_pre_multiplexing(slot_point sl_tx, + pucch_uci_bits new_bits, + ue_grants ue_current_grants, + const ue_cell_configuration& ue_cell_cfg); + + // Update the grants data for the case in which multiplexing is not needed. + pucch_grant_list update_grants_no_multiplexing(slot_point sl_tx, + rnti_t crnti, + pucch_grant_list candidate_grants, + const ue_cell_configuration& ue_cell_cfg, + ue_grants ue_current_grants); + + // Execute the multiplexing algorithm as defined in TS 38.213, Section 9.2.5. + pucch_grant_list multiplex_resources(slot_point sl_tx, + rnti_t crnti, + pucch_grant_list candidate_grants, + const ue_cell_configuration& ue_cell_cfg, + bool preserve_res_indicator = false); + + // Applies the multiplexing rules depending on the PUCCH resource format, as per TS 38.213, Section 9.2.5.1/2. + std::optional merge_pucch_resources(span resources_to_merge, + slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg, + bool preserve_res_indicator = false); + + // Allocate the PUCCH PDUs in the scheduler output, depending on the new PUCCH grants to be transmitted, and depending + // on the PUCCH PDUs currently allocated. + std::optional allocate_grants(cell_slot_resource_allocator& pucch_slot_alloc, + ue_grants& existing_pucchs, + rnti_t crnti, + pucch_grant_list grants_to_tx, + const ue_cell_configuration& ue_cell_cfg); + + // Fills the PUCCH HARQ PDU for common resources. void fill_pucch_harq_common_grant(pucch_info& pucch_info, rnti_t rnti, const pucch_res_alloc_cfg& pucch_res); - // Fills the PUCCH Format 1 grant. + // Fills the PUCCH Format 1 PDU. void fill_pucch_ded_format1_grant(pucch_info& pucch_grant, rnti_t crnti, const pucch_resource& pucch_ded_res_cfg, unsigned harq_ack_bits, sr_nof_bits sr_bits); - // Fills the PUCCH Format 2 grant. + // Fills the PUCCH Format 2 PDU. void fill_pucch_format2_grant(pucch_info& pucch_grant, rnti_t crnti, const pucch_resource& pucch_ded_res_cfg, @@ -201,18 +243,19 @@ class pucch_allocator_impl final : public pucch_allocator sr_nof_bits sr_bits, unsigned csi_part1_bits); - // Helper that retrieves the existing grants allocated to a given UE for a given slot. - existing_pucch_grants - get_existing_pucch_grants(static_vector& pucchs, rnti_t rnti, slot_point sl_ack); + /// //////////// Private helpers ////////////// - unsigned get_max_pucch_grants(unsigned currently_allocated_puschs); + void remove_unsed_pucch_res(slot_point sl_tx, + pucch_grant_list grants_to_tx, + ue_grants& existing_pucchs, + const ue_cell_configuration& ue_cell_cfg); - using slot_alloc_list = static_vector; + unsigned get_max_pucch_grants(unsigned currently_allocated_puschs); // \brief Ring of PUCCH allocations indexed by slot. - circular_array pucch_common_alloc_grid; + circular_array pucch_grants_alloc_grid; - const unsigned PUCCH_FORMAT_1_NOF_PRBS{1}; + constexpr static unsigned PUCCH_FORMAT_1_NOF_PRBS{1}; const cell_configuration& cell_cfg; const unsigned max_pucch_grants_per_slot; const unsigned max_ul_grants_per_slot; diff --git a/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp b/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp index 66891399e3..27158ac4ba 100644 --- a/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp +++ b/lib/scheduler/pucch_scheduling/pucch_resource_manager.cpp @@ -37,7 +37,7 @@ static int get_pucch_res_idx_for_csi(const ue_cell_configuration& ue_cell_cfg) // TODO: extend by passing the BWP id. const bwp_id_t bwp_id = srsran::MIN_BWP_ID; const auto& csi_report_cfg = ue_cell_cfg.cfg_dedicated().csi_meas_cfg.value().csi_report_cfg_list[csi_report_cfg_idx]; - auto& csi_pucch_res_list = + const auto& csi_pucch_res_list = std::get(csi_report_cfg.report_cfg_type) .pucch_csi_res_list; @@ -101,35 +101,35 @@ void pucch_resource_manager::reserve_common_resource(slot_point sl, size_t r_puc } pucch_harq_resource_alloc_record -pucch_resource_manager::reserve_next_f1_harq_res_available(slot_point slot_harq, - rnti_t crnti, - const pucch_config& pucch_cfg) +pucch_resource_manager::reserve_next_set_0_harq_res_available(slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg) { - return reserve_next_harq_res_available(slot_harq, crnti, pucch_cfg, pucch_format::FORMAT_1); + return reserve_next_harq_res_available(slot_harq, crnti, pucch_cfg, pucch_res_set_idx::set_0); }; pucch_harq_resource_alloc_record -pucch_resource_manager::reserve_next_f2_harq_res_available(slot_point slot_harq, - rnti_t crnti, - const pucch_config& pucch_cfg) +pucch_resource_manager::reserve_next_set_1_harq_res_available(slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg) { - return reserve_next_harq_res_available(slot_harq, crnti, pucch_cfg, pucch_format::FORMAT_2); + return reserve_next_harq_res_available(slot_harq, crnti, pucch_cfg, pucch_res_set_idx::set_1); }; -const pucch_resource* pucch_resource_manager::reserve_f1_res_by_res_indicator(slot_point slot_harq, - rnti_t crnti, - unsigned res_indicator, - const pucch_config& pucch_cfg) +const pucch_resource* pucch_resource_manager::reserve_set_0_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg) { - return reserve_harq_res_by_res_indicator(slot_harq, crnti, res_indicator, pucch_cfg, pucch_format::FORMAT_1); + return reserve_harq_res_by_res_indicator(slot_harq, crnti, res_indicator, pucch_cfg, pucch_res_set_idx::set_0); } -const pucch_resource* pucch_resource_manager::reserve_f2_res_by_res_indicator(slot_point slot_harq, - rnti_t crnti, - unsigned res_indicator, - const pucch_config& pucch_cfg) +const pucch_resource* pucch_resource_manager::reserve_set_1_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg) { - return reserve_harq_res_by_res_indicator(slot_harq, crnti, res_indicator, pucch_cfg, pucch_format::FORMAT_2); + return reserve_harq_res_by_res_indicator(slot_harq, crnti, res_indicator, pucch_cfg, pucch_res_set_idx::set_1); } const pucch_resource* pucch_resource_manager::reserve_csi_resource(slot_point slot_csi, @@ -144,11 +144,12 @@ const pucch_resource* pucch_resource_manager::reserve_csi_resource(slot_point if (csi_pucch_res_idx_int < 0) { return nullptr; } - const unsigned csi_pucch_res_idx = static_cast(csi_pucch_res_idx_int); + const auto csi_pucch_res_idx = static_cast(csi_pucch_res_idx_int); // Get resource list of wanted slot. auto& slot_record = get_slot_resource_counter(slot_csi); - if (slot_record.ues_using_pucch_res[csi_pucch_res_idx].rnti != rnti_t::INVALID_RNTI) { + if (slot_record.ues_using_pucch_res[csi_pucch_res_idx].rnti != rnti_t::INVALID_RNTI and + slot_record.ues_using_pucch_res[csi_pucch_res_idx].rnti != crnti) { return nullptr; } @@ -160,14 +161,18 @@ const pucch_resource* pucch_resource_manager::reserve_csi_resource(slot_point return res.res_id.cell_res_id == csi_pucch_res_idx; }); - // If the PUCCH res with correct ID is found, then allocate it to the user. - if (res_cfg != pucch_res_list.end()) { + if (res_cfg == pucch_res_list.end()) { + return nullptr; + } + + // If the PUCCH res with correct ID was not allocated to the UE's RNTI, allocate it to this RNTI; otherwise, it means + // the resource had already been allocated, just return it. + if (slot_record.ues_using_pucch_res[csi_pucch_res_idx].rnti != crnti) { slot_record.ues_using_pucch_res[csi_pucch_res_idx].rnti = crnti; slot_record.ues_using_pucch_res[csi_pucch_res_idx].resource_usage = pucch_resource_usage::CSI; - return &(*res_cfg); } - return nullptr; + return &(*res_cfg); }; const pucch_resource* @@ -181,7 +186,8 @@ pucch_resource_manager::reserve_sr_res_available(slot_point slot_sr, rnti_t crnt // We assume each UE only has 1 SR Resource Config configured. const unsigned sr_pucch_res_id = pucch_cfg.sr_res_list[0].pucch_res_id.cell_res_id; - if (slot_record.ues_using_pucch_res[sr_pucch_res_id].rnti != rnti_t::INVALID_RNTI) { + if (slot_record.ues_using_pucch_res[sr_pucch_res_id].rnti != rnti_t::INVALID_RNTI and + slot_record.ues_using_pucch_res[sr_pucch_res_id].rnti != crnti) { return nullptr; } @@ -192,24 +198,32 @@ pucch_resource_manager::reserve_sr_res_available(slot_point slot_sr, rnti_t crnt return res.res_id.cell_res_id == sr_pucch_res_id; }); - // If the PUCCH res with correct ID is found, then allocate it to the user. - if (res_cfg != pucch_res_list.end()) { + if (res_cfg == pucch_res_list.end()) { + return nullptr; + } + + // If the PUCCH res with correct ID was not allocated to the UE's RNTI, allocate it to this RNTI; otherwise, it means + // the resource had already been allocated, just return it. + if (slot_record.ues_using_pucch_res[sr_pucch_res_id].rnti != crnti and res_cfg != pucch_res_list.end()) { slot_record.ues_using_pucch_res[sr_pucch_res_id].rnti = crnti; slot_record.ues_using_pucch_res[sr_pucch_res_id].resource_usage = pucch_resource_usage::SR; - return &(*res_cfg); } - return nullptr; + return &(*res_cfg); }; -bool pucch_resource_manager::release_harq_f1_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg) +bool pucch_resource_manager::release_harq_set_0_resource(slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg) { - return release_harq_resource(slot_harq, crnti, pucch_cfg, pucch_format::FORMAT_1); + return release_harq_resource(slot_harq, crnti, pucch_cfg, pucch_res_set_idx::set_0); } -bool pucch_resource_manager::release_harq_f2_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg) +bool pucch_resource_manager::release_harq_set_1_resource(slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg) { - return release_harq_resource(slot_harq, crnti, pucch_cfg, pucch_format::FORMAT_2); + return release_harq_resource(slot_harq, crnti, pucch_cfg, pucch_res_set_idx::set_1); } bool pucch_resource_manager::release_sr_resource(slot_point slot_sr, rnti_t crnti, const pucch_config& pucch_cfg) @@ -232,14 +246,14 @@ bool pucch_resource_manager::release_sr_resource(slot_point slot_sr, rnti_t crnt return true; } -bool pucch_resource_manager::release_csi_resource(slot_point slot_sr, +bool pucch_resource_manager::release_csi_resource(slot_point slot_csi, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg) { - srsran_sanity_check(slot_sr < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, + srsran_sanity_check(slot_csi < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, "PUCCH being allocated to far into the future"); - auto& slot_record = get_slot_resource_counter(slot_sr); + auto& slot_record = get_slot_resource_counter(slot_csi); // We assume each UE only has 1 CSI Resource Config configured. const int csi_pucch_res_idx = get_pucch_res_idx_for_csi(ue_cell_cfg); @@ -255,137 +269,130 @@ bool pucch_resource_manager::release_csi_resource(slot_point s return true; } -int pucch_resource_manager::fetch_f1_pucch_res_indic(slot_point slot_tx, rnti_t crnti, const pucch_config& pucch_cfg) +void pucch_resource_manager::reset_latest_reserved_res_tracker() { - return fetch_pucch_res_indic(slot_tx, crnti, pucch_cfg, pucch_format::FORMAT_1); + last_ue_allocations.rnti = rnti_t::INVALID_RNTI; + last_ue_allocations.harq_set_0 = false; + last_ue_allocations.harq_set_1 = false; + last_ue_allocations.sr = false; + last_ue_allocations.csi = false; } -int pucch_resource_manager::fetch_f2_pucch_res_indic(slot_point slot_tx, rnti_t crnti, const pucch_config& pucch_cfg) +void pucch_resource_manager::set_new_resource_allocation(rnti_t crnti, pucch_resource_usage res_type) { - return fetch_pucch_res_indic(slot_tx, crnti, pucch_cfg, pucch_format::FORMAT_2); + if (last_ue_allocations.rnti != crnti and last_ue_allocations.rnti != rnti_t::INVALID_RNTI) { + srsran_assertion_failure("The last UE allocation tracker was already in used by another RNTI"); + return; + } + + last_ue_allocations.rnti = crnti; + + switch (res_type) { + case pucch_resource_usage::HARQ_SET_0: + last_ue_allocations.harq_set_0 = true; + break; + case pucch_resource_usage::HARQ_SET_1: + last_ue_allocations.harq_set_1 = true; + break; + case pucch_resource_usage::SR: + last_ue_allocations.sr = true; + break; + case pucch_resource_usage::CSI: + last_ue_allocations.csi = true; + break; + default: + srsran_assertion_failure("Invalid PUCCH resource usage type"); + } } -const pucch_harq_resource_alloc_record -pucch_resource_manager::fetch_allocated_f2_harq_resource(slot_point slot_harq, - rnti_t crnti, - const pucch_config& pucch_cfg) +bool pucch_resource_manager::is_resource_allocated(rnti_t rnti, pucch_resource_usage res_type) const { - srsran_sanity_check(slot_harq < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, - "PUCCH being allocated too far into the future"); - - // Get resource list of wanted slot. - rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_harq); - - auto& slot_res_array = res_counter.ues_using_pucch_res; - - // Get the span over the array of resources for the specific UE. - const auto& ue_res_id_set_for_harq = pucch_cfg.pucch_res_set[PUCCH_HARQ_F2_RES_SET_ID].pucch_res_id_list; - unsigned ue_first_res_id = ue_res_id_set_for_harq.front().cell_res_id; - srsran_assert(ue_first_res_id + ue_res_id_set_for_harq.size() <= slot_res_array.size(), - "Indexing of PUCCH resource set exceeds the size of the cell resource array"); - span slot_ue_res_array(&slot_res_array[ue_first_res_id], ue_res_id_set_for_harq.size()); - - // Check first if the target PUCCH resource (given the CRNTI and usage) exists within the resource tracker. - auto* target_ue_resource = std::find_if( - slot_ue_res_array.begin(), slot_ue_res_array.end(), [target_rnti = crnti](const resource_tracker& res) { - return res.rnti == target_rnti and res.resource_usage == pucch_resource_usage::HARQ_F2; - }); - - const auto& pucch_res_list = pucch_cfg.pucch_res_list; - - // If the resource is found, get the resource indicator and the configuration from the PUCCH resource list. - if (target_ue_resource != slot_ue_res_array.end() and - static_cast(target_ue_resource - slot_ue_res_array.begin()) < - pucch_cfg.pucch_res_set[PUCCH_HARQ_F2_RES_SET_ID].pucch_res_id_list.size()) { - // Get the PUCCH resource indicator from the available resource position within the span. - const unsigned pucch_res_indicator = static_cast(target_ue_resource - slot_ue_res_array.begin()); - // Get the PUCCH resource ID from the PUCCH resource indicator and the PUCCH resource set. - const unsigned pucch_res_idx_from_list = ue_res_id_set_for_harq[pucch_res_indicator].cell_res_id; - - // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. - const auto* res_cfg = std::find_if( - pucch_res_list.begin(), pucch_res_list.end(), [pucch_res_idx_from_list](const pucch_resource& res) { - return res.res_id.cell_res_id == pucch_res_idx_from_list; - }); + if (last_ue_allocations.rnti != rnti and last_ue_allocations.rnti != rnti_t::INVALID_RNTI) { + srsran_assertion_failure("The last UE allocation tracker was already in used by another RNTI"); + return false; + } - return pucch_harq_resource_alloc_record{.pucch_res = &(*res_cfg), .pucch_res_indicator = pucch_res_indicator}; + switch (res_type) { + case pucch_resource_usage::HARQ_SET_0: + return last_ue_allocations.harq_set_0; + case pucch_resource_usage::HARQ_SET_1: + return last_ue_allocations.harq_set_1; + case pucch_resource_usage::SR: + return last_ue_allocations.sr; + case pucch_resource_usage::CSI: + return last_ue_allocations.csi; + default: + srsran_assertion_failure("Invalid PUCCH resource usage type"); + return false; } - return pucch_harq_resource_alloc_record{.pucch_res = nullptr}; } -const pucch_resource* pucch_resource_manager::fetch_csi_pucch_res_config(slot_point slot_tx, - rnti_t crnti, - const ue_cell_configuration& ue_cell_cfg) +void pucch_resource_manager::cancel_last_ue_res_reservations(slot_point slot_tx, + rnti_t crnti, + const ue_cell_configuration& ue_cell_cfg) { - srsran_sanity_check(slot_tx < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, - "PUCCH being allocated to far into the future"); - - rnti_pucch_res_id_slot_record& slot_record = get_slot_resource_counter(slot_tx); - - const int csi_pucch_res_idx_int = get_pucch_res_idx_for_csi(ue_cell_cfg); - if (csi_pucch_res_idx_int < 0) { - return nullptr; + if (crnti != last_ue_allocations.rnti) { + srsran_assertion_failure("Trying to cancel a UE allocation that was not the last one"); + return; } - const unsigned csi_pucch_res_idx = static_cast(csi_pucch_res_idx_int); - if (slot_record.ues_using_pucch_res[csi_pucch_res_idx].rnti != crnti) { - return nullptr; + if (last_ue_allocations.harq_set_0) { + release_harq_set_0_resource( + slot_tx, crnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); } - - const auto& pucch_res_list = - ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value().pucch_res_list; - - // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. - const auto* res_cfg = - std::find_if(pucch_res_list.begin(), pucch_res_list.end(), [csi_pucch_res_idx](const pucch_resource& res) { - return res.res_id.cell_res_id == csi_pucch_res_idx; - }); - - // If the PUCCH res with correct ID is found, then return it to the user. - if (res_cfg != pucch_res_list.end()) { - return &(*res_cfg); + if (last_ue_allocations.harq_set_1) { + release_harq_set_1_resource( + slot_tx, crnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + } + if (last_ue_allocations.sr) { + release_sr_resource(slot_tx, crnti, ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value()); + } + if (last_ue_allocations.csi) { + release_csi_resource(slot_tx, crnti, ue_cell_cfg); } - - return nullptr; } pucch_harq_resource_alloc_record pucch_resource_manager::reserve_next_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg, - pucch_format format) + pucch_res_set_idx res_set_idx) { srsran_sanity_check(slot_harq < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, "PUCCH being allocated to far into the future"); - srsran_assert(format == pucch_format::FORMAT_1 or format == pucch_format::FORMAT_2, - "Only PUCCH Format 1 and Format 2 are currently supported"); // Get resource list of wanted slot. rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_harq); auto& slot_res_array = res_counter.ues_using_pucch_res; - const unsigned res_set_idx = format == pucch_format::FORMAT_1 ? PUCCH_HARQ_F1_RES_SET_ID : PUCCH_HARQ_F2_RES_SET_ID; - // Get the span over the array of resources for the specific UE. - const auto& ue_res_id_set_for_harq = pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list; - unsigned ue_first_res_id = ue_res_id_set_for_harq.front().cell_res_id; + const auto& ue_res_id_set_for_harq = + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(res_set_idx)].pucch_res_id_list; + unsigned ue_first_res_id = ue_res_id_set_for_harq.front().cell_res_id; srsran_assert(ue_first_res_id + ue_res_id_set_for_harq.size() <= slot_res_array.size(), "Indexing of PUCCH resource set exceeds the size of the cell resource array"); span slot_ue_res_array(&slot_res_array[ue_first_res_id], ue_res_id_set_for_harq.size()); - // Check first if there is any PUCCH resource is available. + // Check first if there is any PUCCH resource already used by this UE. auto* available_resource = std::find_if(slot_ue_res_array.begin(), slot_ue_res_array.end(), - [](const resource_tracker res) { return res.rnti == rnti_t::INVALID_RNTI; }); + [crnti](const resource_tracker res) { return res.rnti == crnti; }); + + // If not, find an available resource. + if (available_resource == slot_ue_res_array.end()) { + available_resource = std::find_if(slot_ue_res_array.begin(), + slot_ue_res_array.end(), + [](const resource_tracker res) { return res.rnti == rnti_t::INVALID_RNTI; }); + } const auto& pucch_res_list = pucch_cfg.pucch_res_list; // If there is an available resource, try to allocate it. if (available_resource != slot_ue_res_array.end() and static_cast(available_resource - slot_ue_res_array.begin()) < - pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list.size()) { + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(res_set_idx)].pucch_res_id_list.size()) { // Get the PUCCH resource indicator from the available resource position within the span. - const unsigned pucch_res_indicator = static_cast(available_resource - slot_ue_res_array.begin()); + const auto pucch_res_indicator = static_cast(available_resource - slot_ue_res_array.begin()); // Get the PUCCH resource ID from the PUCCH resource indicator and the PUCCH resource set. const unsigned pucch_res_idx_from_list = ue_res_id_set_for_harq[pucch_res_indicator].cell_res_id; @@ -399,7 +406,7 @@ pucch_harq_resource_alloc_record pucch_resource_manager::reserve_next_harq_res_a if (res_cfg != pucch_res_list.end()) { available_resource->rnti = crnti; available_resource->resource_usage = - format == pucch_format::FORMAT_1 ? pucch_resource_usage::HARQ_F1 : pucch_resource_usage::HARQ_F2; + res_set_idx == pucch_res_set_idx::set_0 ? pucch_resource_usage::HARQ_SET_0 : pucch_resource_usage::HARQ_SET_1; return pucch_harq_resource_alloc_record{.pucch_res = &(*res_cfg), .pucch_res_indicator = pucch_res_indicator}; } } @@ -410,19 +417,16 @@ const pucch_resource* pucch_resource_manager::reserve_harq_res_by_res_indicator( rnti_t crnti, unsigned res_indicator, const pucch_config& pucch_cfg, - pucch_format format) + pucch_res_set_idx res_set_idx) { srsran_sanity_check(slot_harq < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, "PUCCH being allocated to far into the future"); - srsran_assert(format == pucch_format::FORMAT_1 or format == pucch_format::FORMAT_2, - "Only PUCCH Format 1 and Format 2 are currently supported"); - - const unsigned res_set_idx = format == pucch_format::FORMAT_1 ? PUCCH_HARQ_F1_RES_SET_ID : PUCCH_HARQ_F2_RES_SET_ID; // Get resource list of wanted slot. rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_harq); // Retrieve the PUCCH resource set. - const auto& ue_res_id_set_for_harq = pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list; + const auto& ue_res_id_set_for_harq = + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(res_set_idx)].pucch_res_id_list; // Make sure the resource indicator points to a valid resource. if (res_indicator >= ue_res_id_set_for_harq.size()) { @@ -436,47 +440,49 @@ const pucch_resource* pucch_resource_manager::reserve_harq_res_by_res_indicator( const auto& pucch_res_list = pucch_cfg.pucch_res_list; // Check first if the wanted PUCCH resource is available. - if (pucch_res_tracker.rnti == rnti_t::INVALID_RNTI) { - // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. - const auto* res_cfg = - std::find_if(pucch_res_list.begin(), pucch_res_list.end(), [pucch_res_id](const pucch_resource& res) { - return res.res_id.cell_res_id == pucch_res_id; - }); + if (pucch_res_tracker.rnti != rnti_t::INVALID_RNTI and pucch_res_tracker.rnti != crnti) { + return nullptr; + } - // If the PUCCH res with correct ID is found, then allocate it to the user. - if (res_cfg != pucch_res_list.end()) { - pucch_res_tracker.rnti = crnti; - pucch_res_tracker.resource_usage = - format == pucch_format::FORMAT_1 ? pucch_resource_usage::HARQ_F1 : pucch_resource_usage::HARQ_F2; - return &(*res_cfg); - } + // Search for the PUCCH resource with the correct PUCCH resource ID from the PUCCH resource list. + const auto* res_cfg = + std::find_if(pucch_res_list.begin(), pucch_res_list.end(), [pucch_res_id](const pucch_resource& res) { + return res.res_id.cell_res_id == pucch_res_id; + }); + + if (res_cfg == pucch_res_list.end()) { + return nullptr; } - return nullptr; + // If the PUCCH res with correct ID is found and previously not used by given UE's RNTI, then allocate it this RNTI. + if (pucch_res_tracker.rnti == rnti_t::INVALID_RNTI) { + pucch_res_tracker.rnti = crnti; + pucch_res_tracker.resource_usage = + res_set_idx == pucch_res_set_idx::set_0 ? pucch_resource_usage::HARQ_SET_0 : pucch_resource_usage::HARQ_SET_1; + } + return &(*res_cfg); } bool pucch_resource_manager::release_harq_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg, - pucch_format format) + pucch_res_set_idx res_set_idx) { srsran_sanity_check(slot_harq < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, "PUCCH being allocated to far into the future"); - srsran_assert(format == pucch_format::FORMAT_1 or format == pucch_format::FORMAT_2, - "Only PUCCH Format 1 and Format 2 are currently supported"); // Get resource list of wanted slot. rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_harq); auto& slot_res_array = res_counter.ues_using_pucch_res; - const unsigned res_set_idx = format == pucch_format::FORMAT_1 ? PUCCH_HARQ_F1_RES_SET_ID : PUCCH_HARQ_F2_RES_SET_ID; const pucch_resource_usage res_usage = - format == pucch_format::FORMAT_1 ? pucch_resource_usage::HARQ_F1 : pucch_resource_usage::HARQ_F2; + res_set_idx == pucch_res_set_idx::set_0 ? pucch_resource_usage::HARQ_SET_0 : pucch_resource_usage::HARQ_SET_1; // Get the span over the array of resources for the specific UE. - const auto& ue_res_id_set_for_harq = pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list; - unsigned ue_first_res_id = ue_res_id_set_for_harq.front().cell_res_id; + const auto& ue_res_id_set_for_harq = + pucch_cfg.pucch_res_set[pucch_res_set_idx_to_uint(res_set_idx)].pucch_res_id_list; + unsigned ue_first_res_id = ue_res_id_set_for_harq.front().cell_res_id; srsran_assert(ue_first_res_id + ue_res_id_set_for_harq.size() <= slot_res_array.size(), "Indexing of PUCCH resource set exceeds the size of the cell resource array"); span slot_ue_res_array(&slot_res_array[ue_first_res_id], ue_res_id_set_for_harq.size()); @@ -497,49 +503,6 @@ bool pucch_resource_manager::release_harq_resource(slot_point slot_harq return false; } -int pucch_resource_manager::fetch_pucch_res_indic(slot_point slot_tx, - rnti_t crnti, - const pucch_config& pucch_cfg, - pucch_format format) -{ - srsran_sanity_check(slot_tx < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, - "PUCCH being allocated to far into the future"); - srsran_assert(format == pucch_format::FORMAT_1 or format == pucch_format::FORMAT_2, - "Only PUCCH Format 1 and Format 2 are currently supported"); - - // Get resource list of wanted slot. - rnti_pucch_res_id_slot_record& res_counter = get_slot_resource_counter(slot_tx); - - auto& slot_res_array = res_counter.ues_using_pucch_res; - - const unsigned res_set_idx = format == pucch_format::FORMAT_1 ? PUCCH_HARQ_F1_RES_SET_ID : PUCCH_HARQ_F2_RES_SET_ID; - const pucch_resource_usage res_usage = - format == pucch_format::FORMAT_1 ? pucch_resource_usage::HARQ_F1 : pucch_resource_usage::HARQ_F2; - - // Get the span over the array of resources for the specific UE. - const auto& ue_res_id_set_for_harq = pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list; - unsigned ue_first_res_id = ue_res_id_set_for_harq.front().cell_res_id; - srsran_assert(ue_first_res_id + ue_res_id_set_for_harq.size() <= slot_res_array.size(), - "Indexing of PUCCH resource set exceeds the size of the cell resource array"); - span slot_ue_res_array(&slot_res_array[ue_first_res_id], ue_res_id_set_for_harq.size()); - - // Check first if the target PUCCH resource (given the CRNTI and usage) exists within the resource tracker. - auto* ue_resource = std::find_if(slot_ue_res_array.begin(), - slot_ue_res_array.end(), - [target_rnti = crnti, res_usage](const resource_tracker& res) { - return res.rnti == target_rnti and res.resource_usage == res_usage; - }); - - if (ue_resource != slot_ue_res_array.end() and static_cast(ue_resource - slot_ue_res_array.begin()) < - pucch_cfg.pucch_res_set[res_set_idx].pucch_res_id_list.size()) { - // Get the PUCCH resource indicator from the available resource position within the span. - return static_cast(ue_resource - slot_ue_res_array.begin()); - } - - // -1 indicates that the there is no UE record for given RNTI. - return -1; -} - pucch_resource_manager::rnti_pucch_res_id_slot_record& pucch_resource_manager::get_slot_resource_counter(slot_point sl) { srsran_sanity_check(sl < last_sl_ind + RES_MANAGER_RING_BUFFER_SIZE, diff --git a/lib/scheduler/pucch_scheduling/pucch_resource_manager.h b/lib/scheduler/pucch_scheduling/pucch_resource_manager.h index 56535aadb3..7d99b5da9f 100644 --- a/lib/scheduler/pucch_scheduling/pucch_resource_manager.h +++ b/lib/scheduler/pucch_scheduling/pucch_resource_manager.h @@ -23,7 +23,6 @@ #pragma once #include "../config/ue_configuration.h" -#include "pucch_allocator.h" namespace srsran { @@ -35,19 +34,22 @@ struct pucch_harq_resource_alloc_record { unsigned pucch_res_indicator; }; +/// Defines the PUCCH resource usage. +enum class pucch_resource_usage { NOT_USED = 0, HARQ_SET_0, HARQ_SET_1, SR, CSI }; + /// \brief Class that manages the cell allocation of PUCCH resources across UEs. /// The correct functioning of pucch_resource_manager is based on the following assumptions: -/// (i) Each UE has max 8 PUCCH F1 and max 8 PUCCH F2 dedicated to HARQ-ACK reporting. -/// (ii) Each UE has max 1 SR-dedicated PUCCH F1 resource and max 1 CSI-dedicated PUCCH F2 resource. +/// (i) Each UE has max 8 PUCCH F0/F1 and max 8 PUCCH F2 dedicated to HARQ-ACK reporting. +/// (ii) Each UE has max 1 SR-dedicated PUCCH F0/F1 resource and max 1 CSI-dedicated PUCCH F2 resource. /// (iii) The cell PUCCH resource list can have max 128 PUCCH resource, including all formats; at cell level, there is -/// no constraint on how many resource must be F1, F2, or for SR or for CSI. +/// no constraint on how many resource must be F0/F1, F2, or for SR or for CSI. /// (vi) UEs can have different PUCCH resource lists; however the PUCCH resource ID is unique with the cell. This /// implies that if two UEs have the same PUCCH resource within their lists, their PUCCH resource ID must be the /// same. -/// (v) Indexing of the PUCCH F1 and PUCCH F2 resources for HARQ-ACK reporting must be contiguous within the F1 group -/// and with F2 group. However, the last PUCCH F1 group resource's and the first PUCCH F2 group resource's indices -/// need not be contiguous. E.g., PUCCH F1 indices (for HARQ-ACK reporting) = {0, ..., 7}, and PUCCH F2 indices -/// (for HARQ-ACK reporting) = {10, ..., 17}. +/// (v) Indexing of the PUCCH F0/F1 and PUCCH F2 resources for HARQ-ACK reporting must be contiguous within the F0/F1 +/// group and with F2 group. However, the last PUCCH F0/F1 group resource's and the first PUCCH F2 group +/// resource's indices need not be contiguous. E.g., PUCCH F0/F1 indices (for HARQ-ACK reporting) = {0, ..., 7}, +/// and PUCCH F2 indices (for HARQ-ACK reporting) = {10, ..., 17}. class pucch_resource_manager { public: @@ -62,37 +64,41 @@ class pucch_resource_manager /// Set the common PUCCH resource indexed by r_pucch at the given slot as currently "not available". void reserve_common_resource(slot_point sl, size_t r_pucch); - /// \brief Returns the next PUCCH resource available to be used for HARQ-ACK (format 1). + /// \brief Returns the next PUCCH resource available to be used for HARQ-ACK (format 0 or 1). + /// \remark The format of the resource to be reserved depends on how PUCCH resource set 0 is configured. /// \return If any PUCCH resource available, it returns (i) the pointer to the configuration and (ii) the PUCCH /// resource indicator corresponding to the PUCCH resource that will be used by the UE. If there are no PUCCH /// resources available, the pointer passed will be \c nullptr, whereas the PUCCH resource indicator is to be ignored. pucch_harq_resource_alloc_record - reserve_next_f1_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); + reserve_next_set_0_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); /// \brief Returns the next PUCCH format 2 resource available to be used for HARQ-ACK. + /// \remark Format 2 is the only format currently supported for PUCCH resource set 1. /// \remark If SR and CSI multiplexing is enabled, this resource can be used for HARQ-ACK + SR and/or CSI. /// \return If any PUCCH resource available, it returns (i) the pointer to the configuration and (ii) the PUCCH /// resource indicator corresponding to the PUCCH resource that will be used by the UE. If there are no PUCCH /// resources available, the pointer passed will be \c nullptr, whereas the PUCCH resource indicator is to be ignored. pucch_harq_resource_alloc_record - reserve_next_f2_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); + reserve_next_set_1_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); - /// \brief Returns a specific PUCCH format 1 resource (identified by the res. indicator) to be used for HARQ-ACK. + /// \brief Returns a specific PUCCH format 0 or 1 resource (identified by the res. indicator) to be used for HARQ-ACK. + /// \remark The format of the resource to be reserved depends on how PUCCH resource set 0 is configured. /// \return If the specific PUCCH resource is available, it returns the pointer to the configuration. Else, it returns /// \c nullptr. - const pucch_resource* reserve_f1_res_by_res_indicator(slot_point slot_harq, - rnti_t crnti, - unsigned res_indicator, - const pucch_config& pucch_cfg); + const pucch_resource* reserve_set_0_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg); /// \brief Returns a specific PUCCH format 2 resource (identified by the res. indicator) to be used for HARQ-ACK. + /// \remark Format 2 is the only format currently supported for PUCCH resource set 1. /// \remark If SR and CSI multiplexing is enabled, this resource can be used for HARQ-ACK + SR and/or CSI. /// \return If the specific PUCCH resource is available, it returns the pointer to the configuration. Else, it returns /// \c nullptr. - const pucch_resource* reserve_f2_res_by_res_indicator(slot_point slot_harq, - rnti_t crnti, - unsigned res_indicator, - const pucch_config& pucch_cfg); + const pucch_resource* reserve_set_1_res_by_res_indicator(slot_point slot_harq, + rnti_t crnti, + unsigned res_indicator, + const pucch_config& pucch_cfg); /// \brief Returns the specific PUCCH format 2 resource config to be used for CSI, if available. /// \remark If SR multiplexing is enabled, this resource can be used for CSI + SR. @@ -102,27 +108,29 @@ class pucch_resource_manager const pucch_resource* reserve_csi_resource(slot_point slot_harq, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg); - /// \brief Returns the specific PUCCH format 1 resource config to be used for SR, if available. + /// \brief Returns the specific PUCCH resource config to be used for SR, if available. /// \return If the specific PUCCH resource is available, it returns (i) the pointer to the configuration and (ii) the /// PUCCH resource indicator corresponding to the PUCCH resource that will be used by the UE. Else, the pointer passed /// will be \c nullptr, whereas the PUCCH resource indicator is to be ignored. const pucch_resource* reserve_sr_res_available(slot_point slot_sr, rnti_t crnti, const pucch_config& pucch_cfg); - /// \brief Release PUCCH (format 1) resource from being allocated to a given UE. + /// \brief Release PUCCH (format 0 or 1) resource from being allocated to a given UE. + /// \remark The format of the resource to be released depends on how PUCCH resource set 0 is configured. /// \param[in] slot_harq slot for which the PUCCH resource was scheduled. /// \param[in] crnti UE from which the resource needs to be released. /// \param[in] pucch_cfg UE's PUCCH config. /// \return True if the resource for the UE was found in the allocation records for the given slot. - bool release_harq_f1_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); + bool release_harq_set_0_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); /// \brief Release PUCCH (format 2) resource from being allocated to a given UE. + /// \remark Format 2 is the only format currently supported for PUCCH resource set 1. /// \param[in] slot_harq slot for which the PUCCH resource was scheduled. /// \param[in] crnti UE from which the resource needs to be released. /// \param[in] pucch_cfg UE's PUCCH config. /// \return True if the resource for the UE was found in the allocation records for the given slot. - bool release_harq_f2_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); + bool release_harq_set_1_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); - /// \brief Release PUCCH (format 1) resource used for SR from being allocated to a given UE. + /// \brief Release PUCCH (format 0 or 1) resource used for SR from being allocated to a given UE. /// \param[in] slot_harq slot for which the PUCCH resource was scheduled. /// \param[in] crnti UE from which the resource needs to be released. /// \param[in] pucch_cfg UE's PUCCH config. @@ -136,26 +144,19 @@ class pucch_resource_manager /// \return True if the resource for the UE was found in the allocation records for the given slot. bool release_csi_resource(slot_point slot_sr, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg); - /// \brief Returns the PUCCH resource indicator (format 1) of the resource used for a given RNTI at a given slot. - /// \return PUCCH resource indicator of the resource used allocated to the UE; if UE is not found, returns -1. - int fetch_f1_pucch_res_indic(slot_point slot_tx, rnti_t crnti, const pucch_config& pucch_cfg); + /// \brief Reset the record of the PUCCH resources reserved to UE at the current slot. + /// \remark This function doesn't release the resources, it only resets the record of which resources have been + /// reserved. + void reset_latest_reserved_res_tracker(); - /// \brief Returns the PUCCH resource indicator (format 2) of the resource used for a given RNTI at a given slot. - /// \return PUCCH resource indicator of the resource used allocated to the UE; if UE is not found, returns -1. - int fetch_f2_pucch_res_indic(slot_point slot_tx, rnti_t crnti, const pucch_config& pucch_cfg); + /// \brief Adds a records that a given resource has been reserved for the current slot for a given UE. + void set_new_resource_allocation(rnti_t rnti, pucch_resource_usage res_type); - /// \brief Fetches the configuration of the PUCCH Format 2 resource used for HARQ previously allocated to a given RNTI - /// for a given slot. - /// \return If the resource is found, it returns (i) the pointer to the configuration and (ii) the PUCCH resource - /// indicator corresponding to the PUCCH resource that previously allocated to the UE. Else, the pointer passed will - /// be \c nullptr, whereas the PUCCH resource indicator is to be ignored. - const pucch_harq_resource_alloc_record - fetch_allocated_f2_harq_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg); + /// \brief Checks whether a given resource has been reserved for the current slot for a given UE. + [[nodiscard]] bool is_resource_allocated(rnti_t rnti, pucch_resource_usage res_type) const; - /// \brief Returns the configuration of the PUCCH resource used for CSI (format 2) for a given RNTI at a given slot. - /// \return Pointer to the resource configuration used allocated to the UE; if UE is not found, returns \c nullptr. - const pucch_resource* - fetch_csi_pucch_res_config(slot_point slot_tx, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg); + /// \brief Releases the resources that have been recorded as reserved for the current slot for a given UE. + void cancel_last_ue_res_reservations(slot_point slot_tx, rnti_t crnti, const ue_cell_configuration& ue_cell_cfg); private: /// Size of the ring buffer of pucch_resource_manager. This size sets a limit on how far in advance a PUCCH can be @@ -163,17 +164,12 @@ class pucch_resource_manager static const size_t RES_MANAGER_RING_BUFFER_SIZE = get_allocator_ring_size_gt_min(SCHEDULER_MAX_K0 + SCHEDULER_MAX_K1 + NTN_CELL_SPECIFIC_KOFFSET_MAX); - static const unsigned PUCCH_HARQ_F1_RES_SET_ID = 0; - static const unsigned PUCCH_HARQ_F2_RES_SET_ID = 1; - // [Implementation-defined] We assume as the maximum number of PUCCH resources that can be handled by the resource // manager \c maxNrofPUCCH-Resources, TS 38.331. static const size_t MAX_PUCCH_RESOURCES{128}; // As per Section 9.2.1, TS 38.213, this is given by the number of possible values of r_PUCCH, which is 16. static const size_t MAX_COMMON_PUCCH_RESOURCES{16}; - enum class pucch_resource_usage { NOT_USED = 0, HARQ_F1, HARQ_F2, SR, CSI }; - // Tracks usage of PUCCH resources. struct resource_tracker { rnti_t rnti; @@ -183,12 +179,26 @@ class pucch_resource_manager using pucch_res_record_array = std::array; using common_res_record_array = std::array; - // Record for the RNTI and PUCCH resource indicator used for a given resource at a given slot. + // Record for the RNTI and PUCCH resource indicator used for a given resource at a given slot; this information is + // used to keep track of which resources are available and which UE is using them. This information is preserved over + // the slots. struct rnti_pucch_res_id_slot_record { common_res_record_array used_common_resources; pucch_res_record_array ues_using_pucch_res; }; + // Keeps track of which PUCCH cell resources have been allocated to a UE at the current slot. + // This info is needed to release some resources after the PUCCH multiplexing (by the PUCCH allocator), during which + // resources can be reserved and then released, depending on the multiplexing algorithm. + // The lifespan of this information lasts for a single UE PUCCH allocation. + struct current_slot_ue_allocations { + rnti_t rnti = rnti_t::INVALID_RNTI; + bool harq_set_0 = false; + bool harq_set_1 = false; + bool csi = false; + bool sr = false; + }; + // Returns the resource manager allocation record for a given slot. rnti_pucch_res_id_slot_record& get_slot_resource_counter(slot_point sl); @@ -196,21 +206,24 @@ class pucch_resource_manager pucch_harq_resource_alloc_record reserve_next_harq_res_available(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg, - pucch_format format); + pucch_res_set_idx res_set_idx); const pucch_resource* reserve_harq_res_by_res_indicator(slot_point slot_harq, rnti_t crnti, unsigned res_indicator, const pucch_config& pucch_cfg, - pucch_format format); + pucch_res_set_idx res_set_idx); - bool release_harq_resource(slot_point slot_harq, rnti_t crnti, const pucch_config& pucch_cfg, pucch_format format); - - int fetch_pucch_res_indic(slot_point slot_tx, rnti_t crnti, const pucch_config& pucch_cfg, pucch_format format); + bool release_harq_resource(slot_point slot_harq, + rnti_t crnti, + const pucch_config& pucch_cfg, + pucch_res_set_idx res_set_idx); // Ring buffer of rnti_pucch_res_id_slot_record for PUCCH resources. std::array resource_slots; + current_slot_ue_allocations last_ue_allocations; + // Keeps track of the last slot_point used by the resource manager. slot_point last_sl_ind; }; diff --git a/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp b/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp index 2831271be3..e7ccbf5a93 100644 --- a/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp +++ b/lib/scheduler/uci_scheduling/uci_allocator_impl.cpp @@ -196,6 +196,7 @@ std::optional uci_allocator_impl::alloc_uci_harq_ue(cell_resourc { // [Implementation-defined] We restrict the number of HARQ bits per PUCCH that are expected to carry CSI reporting to // 2 , until the PUCCH allocator supports more than this. + // TODO: remove this, as with the new refactor we are not constrained by this anymore. static const uint8_t max_harq_bits_per_uci = 2U; const slot_point pdsch_slot = res_alloc[k0].slot; @@ -274,7 +275,7 @@ void uci_allocator_impl::multiplex_uci_on_pusch(ul_sched_info& pu const pucch_uci_bits pucch_uci = pucch_alloc.remove_ue_uci_from_pucch(slot_alloc, crnti, ue_cell_cfg); // In case there are no UCI bits from PUCCH, then there is no UCI to be multiplexed on the PUSCH. - if (pucch_uci.harq_ack_nof_bits == 0 and pucch_uci.csi_part1_bits == 0) { + if (pucch_uci.harq_ack_nof_bits == 0 and pucch_uci.csi_part1_nof_bits == 0) { return; } @@ -282,7 +283,7 @@ void uci_allocator_impl::multiplex_uci_on_pusch(ul_sched_info& pu uci_info& uci = pusch_grant.uci.emplace(); uci.alpha = ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pusch_cfg.value().uci_cfg.value().scaling; - if (pucch_uci.csi_part1_bits != 0) { + if (pucch_uci.csi_part1_nof_bits != 0) { // The number of bits is computed based on the CSI report configuration. add_csi_to_uci_on_pusch(uci.csi.emplace(uci_info::csi_info()), ue_cell_cfg); } @@ -359,5 +360,5 @@ uint8_t uci_allocator_impl::get_scheduled_pdsch_counter_in_ue_uci(cell_slot_reso bool uci_allocator_impl::has_uci_harq_on_common_pucch_res(rnti_t rnti, slot_point sl_tx) { - return pucch_alloc.has_common_pucch_f1_grant(rnti, sl_tx); + return pucch_alloc.has_common_pucch_grant(rnti, sl_tx); } diff --git a/lib/scheduler/ue_scheduling/ue.cpp b/lib/scheduler/ue_scheduling/ue.cpp index fce0936a35..adcb3531ea 100644 --- a/lib/scheduler/ue_scheduling/ue.cpp +++ b/lib/scheduler/ue_scheduling/ue.cpp @@ -53,6 +53,19 @@ void ue::slot_indication(slot_point sl_tx) if (ue_du_cells[i] != nullptr) { // Clear old HARQs. ue_du_cells[i]->harqs.slot_indication(sl_tx); + + // [Implementation-defined] + // Clear last PxSCH allocated slot if gap to current \c sl_tx is too large. This is done in order to circumvent + // the ambiguity caused by the slot_point wrap around while scheduling next PxSCHs. e.g. last PxSCH allocated + // slot=289.0 and next PxSCH to be allocated slot=(289.0 - SCHEDULER_MAX_K0/SCHEDULER_MAX_K2) after wrap around. + if (ue_du_cells[i]->last_pdsch_allocated_slot.valid() and + std::abs(sl_tx - ue_du_cells[i]->last_pdsch_allocated_slot) > SCHEDULER_MAX_K0) { + ue_du_cells[i]->last_pdsch_allocated_slot.clear(); + } + if (ue_du_cells[i]->last_pusch_allocated_slot.valid() and + std::abs(sl_tx - ue_du_cells[i]->last_pusch_allocated_slot) > SCHEDULER_MAX_K2) { + ue_du_cells[i]->last_pusch_allocated_slot.clear(); + } } } diff --git a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp index 4e29178504..5741e6443a 100644 --- a/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp +++ b/lib/scheduler/ue_scheduling/ue_cell_grid_allocator.cpp @@ -133,17 +133,6 @@ alloc_result ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& gra return {alloc_status::invalid_params}; } - // [Implementation-defined] - // If there is large gap between two PDSCHs scheduled for a UE, \c last_pdsch_allocated_slot could be having an old - // slot value and the condition pdsch_alloc.slot (e.g. 47.0) <= ue_cc->last_pdsch_allocated_slot (e.g. 989.0) maybe be - // true for long time and UE may not get scheduled. - // This scenario can be prevented by resetting \c last_pdsch_allocated_slot when its behind current PDCCH slot by - // SCHEDULER_MAX_K0 number of slots. - if (ue_cc->last_pdsch_allocated_slot.valid() and - std::abs(pdcch_alloc.slot - ue_cc->last_pdsch_allocated_slot) > SCHEDULER_MAX_K0) { - ue_cc->last_pdsch_allocated_slot.clear(); - } - // Create PDSCH param candidate search object. ue_pdsch_alloc_param_candidate_searcher candidates{ u, grant.cell_index, h_dl, pdcch_alloc.slot, slots_with_no_pdsch_space}; @@ -227,10 +216,6 @@ alloc_result ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& gra : ue_cc->required_dl_prbs(pdsch_td_cfg, grant.recommended_nof_bytes.value(), dci_type); // Try to limit the grant PRBs. if (not is_retx) { - // Limit nof. RBs to allocate to maximum RBs provided in grant. - if (grant.max_nof_rbs.has_value()) { - mcs_prbs.n_prbs = std::min(mcs_prbs.n_prbs, grant.max_nof_rbs.value()); - } // [Implementation-defined] In case of partial slots and nof. PRBs allocated equals to 1 probability of KO is // high due to code not being able to cope with interference. So the solution is to increase the PRB allocation // to greater than 1 PRB. @@ -246,6 +231,10 @@ alloc_result ue_cell_grid_allocator::allocate_dl_grant(const ue_pdsch_grant& gra if (twice_grant_crbs_length < (mcs_prbs.n_prbs * 2)) { mcs_prbs.n_prbs = twice_grant_crbs_length; } + // Limit nof. RBs to allocate to maximum RBs provided in grant. + if (grant.max_nof_rbs.has_value()) { + mcs_prbs.n_prbs = std::min(mcs_prbs.n_prbs, grant.max_nof_rbs.value()); + } } if (mcs_prbs.n_prbs == 0) { @@ -573,18 +562,6 @@ alloc_result ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& gra return {alloc_status::invalid_params}; } - // [Implementation-defined] - // If there is large gap between two PUSCHs scheduled for a UE, \c last_pusch_allocated_slot could be having an old - // slot value and the condition pusch_alloc.slot (e.g. 47.3) <= ue_cc->last_pusch_allocated_slot (e.g. 989.3) maybe be - // true for long time and UE may not get scheduled even after receiving maximum nof. SR indication configured to UE - // and eventually UE PRACHes. - // This scenario can be prevented by resetting \c last_pusch_allocated_slot when its behind current PDCCH slot by - // SCHEDULER_MAX_K2 number of slots. - if (ue_cc->last_pusch_allocated_slot.valid() and - std::abs(pdcch_alloc.slot - ue_cc->last_pusch_allocated_slot) > SCHEDULER_MAX_K2) { - ue_cc->last_pusch_allocated_slot.clear(); - } - // Create PUSCH param candidate search object. ue_pusch_alloc_param_candidate_searcher candidates{ u, grant.cell_index, h_ul, pdcch_alloc.slot, slots_with_no_pusch_space}; @@ -713,10 +690,6 @@ alloc_result ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& gra : ue_cc->required_ul_prbs(pusch_td_cfg, grant.recommended_nof_bytes.value(), dci_type); // Try to limit the grant PRBs. if (not is_retx) { - // Limit nof. RBs to allocate to maximum RBs provided in grant. - if (grant.max_nof_rbs.has_value()) { - mcs_prbs.n_prbs = std::min(mcs_prbs.n_prbs, grant.max_nof_rbs.value()); - } // [Implementation-defined] Check whether max. UL grants per slot is reached if PUSCH for current UE succeeds. If // so, allocate remaining RBs to the current UE only if it's a new Tx. if (pusch_pdu_rem_space == 1) { @@ -742,6 +715,10 @@ alloc_result ue_cell_grid_allocator::allocate_ul_grant(const ue_pusch_grant& gra if (twice_grant_crbs_length < (mcs_prbs.n_prbs * 2)) { mcs_prbs.n_prbs = twice_grant_crbs_length; } + // Limit nof. RBs to allocate to maximum RBs provided in grant. + if (grant.max_nof_rbs.has_value()) { + mcs_prbs.n_prbs = std::min(mcs_prbs.n_prbs, grant.max_nof_rbs.value()); + } } // NOTE: This should never happen, but it's safe not to proceed if we get n_prbs == 0. diff --git a/lib/srslog/sinks/udp_sink.h b/lib/srslog/sinks/udp_sink.h index 704bd1050c..6e976ba819 100644 --- a/lib/srslog/sinks/udp_sink.h +++ b/lib/srslog/sinks/udp_sink.h @@ -24,6 +24,7 @@ #include "srsran/srslog/sink.h" #include +#include #include namespace srslog { @@ -53,8 +54,15 @@ class udp_sink : public sink remote_address = {}; remote_address.sin_family = AF_INET; remote_address.sin_port = ::htons(port); - if (::inet_pton(AF_INET, remote_ip.c_str(), &remote_address.sin_addr) < 1) { - return "Invalid IP address format"; + + // First treat remote_ip as an ip address. + if (::inet_pton(AF_INET, remote_ip.c_str(), &remote_address.sin_addr) == 1) { + // IP address found, do nothing. + } else { + // Treat remote_ip as a hostname. + if (auto err = try_to_resolve_hostname(); !err.get_error().empty()) { + return err; + } } } @@ -70,6 +78,41 @@ class udp_sink : public sink detail::error_string flush() override { return {}; } +private: + /// Tries to resolve remote_ip as a hostname. Returns an error string on failure. + detail::error_string try_to_resolve_hostname() + { + // IP address not found, try resolving ip address using DNS. + ::addrinfo hints = {}; + // Accept all flags. + hints.ai_flags = 0; + // Use IPv4 + hints.ai_family = AF_INET; + // Accept all socket types. + hints.ai_socktype = SOCK_DGRAM; + // As this is a UDP sink, use UDP for protocol. + hints.ai_protocol = IPPROTO_UDP; + ::addrinfo* result = nullptr; + if (::getaddrinfo(remote_ip.c_str(), nullptr, &hints, &result) != 0) { + return fmt::format("Could not resolve '{}' as IP address or hostname", remote_ip); + } + unsigned nof_ip_addresses = 0; + for (auto addr = result; addr != nullptr; addr = addr->ai_next) { + ::sockaddr_in* ipv4 = reinterpret_cast<::sockaddr_in*>(addr->ai_addr); + remote_address.sin_addr = ipv4->sin_addr; + ++nof_ip_addresses; + } + + // Check that only one valid IP was found, otherwise fail. + if (nof_ip_addresses != 1) { + return fmt::format("More than one hostname resolution for '{}'. Using IP address '{}'", + remote_ip, + ::inet_ntoa(remote_address.sin_addr)); + } + + return {}; + } + private: std::string remote_ip; unsigned port; diff --git a/lib/srsvec/conversion.cpp b/lib/srsvec/conversion.cpp index 3439cd900d..6cde4e216f 100644 --- a/lib/srsvec/conversion.cpp +++ b/lib/srsvec/conversion.cpp @@ -144,6 +144,25 @@ static void convert_cbf16_to_cf_simd(cf_t* out, const cbf16_t* in, unsigned len) } } +static void convert_bf16_to_f_simd(float* out, const bf16_t* in, unsigned len) +{ + unsigned i = 0; + +#if SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_S_SIZE + for (unsigned end = (len / SRSRAN_SIMD_S_SIZE) * SRSRAN_SIMD_S_SIZE; i != end; i += SRSRAN_SIMD_S_SIZE) { + simd_f_t even, odd; + // Load and unpack bf16 values into two vectors of floats: even part of each 32-bit register storing two bf16 values + // goes into the first simd register, odd part - into the second one. + srsran_simd_bf16_loadu(even, odd, in + i); + srsran_simd_f_storeu_interleaved(out + i, even, odd); + } +#endif // SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_S_SIZE + + for (; i != len; ++i) { + out[i] = to_float(in[i]); + } +} + static void convert_cf_to_cbf16_simd(cbf16_t* out, const cf_t* in, unsigned len) { unsigned i = 0; @@ -159,6 +178,148 @@ static void convert_cf_to_cbf16_simd(cbf16_t* out, const cf_t* in, unsigned len) } } +static void convert_f_to_bf16_simd(bf16_t* out, const float* in, unsigned len) +{ + unsigned i = 0; + +#if SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_S_SIZE + constexpr unsigned FLOATS_PER_ITERATION = 2 * SRSRAN_SIMD_F_SIZE; + + for (unsigned end = (len / FLOATS_PER_ITERATION) * FLOATS_PER_ITERATION; i != end; i += FLOATS_PER_ITERATION) { + simd_f_t float_vec_1 = srsran_simd_f_loadu(in + i); + simd_f_t float_vec_2 = srsran_simd_f_loadu(in + i + SRSRAN_SIMD_F_SIZE); + + // Convert float to brain float and store the result back to memory. + srsran_simd_bf16_storeu(out + i, float_vec_1, float_vec_2); + } +#endif // SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_S_SIZE + + for (; i != len; ++i) { + out[i] = to_bf16(in[i]); + } +} + +static void convert_bf16_to_int16_simd(int16_t* out, const bf16_t* in, float scale, unsigned len) +{ + unsigned i = 0; + +#if SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_S_SIZE + for (unsigned end = (len / SRSRAN_SIMD_S_SIZE) * SRSRAN_SIMD_S_SIZE; i != end; i += SRSRAN_SIMD_S_SIZE) { + simd_f_t temp_even; + simd_f_t temp_odd; + // Load and unpack bf16 values into two vectors of floats: even part of each 32-bit register storing two bf16 values + // goes into the first simd register, odd part - into the second one. + srsran_simd_bf16_loadu(temp_even, temp_odd, in + i); + + // Multiply with the scaling factor. + simd_f_t s = srsran_simd_f_set1(scale); + simd_f_t scaled_even = srsran_simd_f_mul(temp_even, s); + simd_f_t scaled_odd = srsran_simd_f_mul(temp_odd, s); + + // Convert float to int16. + simd_s_t i16 = srsran_simd_convert_2f_interleaved_s(scaled_even, scaled_odd); + + // Store the resulting int16 vector. + srsran_simd_s_storeu(out + i, i16); + } +#endif // SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_S_SIZE + + for (; i != len; ++i) { + out[i] = static_cast(std::round(to_float(in[i]) * scale)); + } +} + +static void convert_int16_to_bf16_simd(bf16_t* out, const int16_t* in, float scale, unsigned len) +{ + unsigned i = 0; + const float gain = 1.0f / scale; + +#if defined(__AVX__) && defined(__AVX512F__) + // Load the scale factor into a vector register. + __m512 scale512 = _mm512_set1_ps(gain); + + // Process 32 elements at a time (512 bits / 16 bits per brain float = 32 floats). + for (unsigned i_end = (len / 32) * 32; i != i_end; i += 32) { + // Load 32 int16_t elements into a 256-bit vector register. + __m256i input_vec_1 = _mm256_loadu_si256(reinterpret_cast(in + i)); + __m256i input_vec_2 = _mm256_loadu_si256(reinterpret_cast(in + i + 16)); + + // Convert the int16_t elements to float and scale them. + __m512 float_vec_1 = _mm512_cvtepi32_ps(_mm512_cvtepi16_epi32(input_vec_1)); + __m512 float_vec_2 = _mm512_cvtepi32_ps(_mm512_cvtepi16_epi32(input_vec_2)); + float_vec_1 = _mm512_mul_ps(float_vec_1, scale512); + float_vec_2 = _mm512_mul_ps(float_vec_2, scale512); + + // Convert float to brain float and store the result back to memory. + srsran_simd_bf16_storeu(out + i, float_vec_1, float_vec_2); + } + + // Process 16 elements at a time. + for (unsigned i_end = (len / 16) * 16; i < i_end; i += 16) { + // Load 16 int16_t elements into a 256-bit vector register. + __m256i input_vec = _mm256_loadu_si256(reinterpret_cast(in + i)); + + // Convert the int16_t elements to float and scale them. + __m512 float_vec = _mm512_cvtepi32_ps(_mm512_cvtepi16_epi32(input_vec)); + float_vec = _mm512_mul_ps(float_vec, scale512); + + // Convert float to brain float, the second half of the resulting vector is empty. + __m512i bf16_vec = srsran_simd_convert_1f_bf16(float_vec); + + // Store first half of the resulting bf16 vector to memory. + _mm512_mask_storeu_epi32(out + i, 0x00ff, bf16_vec); + } +#if defined(__AVX512VL__) && defined(__AVX512BW__) + { + unsigned remainder = len % 16; + + // Select the LSB values. + __mmask16 mask = (1 << remainder) - 1; + + // Load remaining int16_t elements into a 256-bit vector register. + __m256i input_vec = _mm256_maskz_loadu_epi16(mask, reinterpret_cast(in + i)); + + // Convert the int16_t elements to float and scale them. + __m512 float_vec = _mm512_maskz_cvtepi32_ps(mask, _mm512_maskz_cvtepi16_epi32(mask, input_vec)); + float_vec = _mm512_mul_ps(float_vec, scale512); + + // Convert float to brain float, the second half of the resulting vector is empty. + __m512i bf16_vec = srsran_simd_convert_1f_bf16(float_vec); + + // Store the result back to memory. + _mm512_mask_storeu_epi16(out + i, static_cast<__mmask32>(mask), bf16_vec); + return; + } +#endif // defined(__AVX512VL__) +#else // defined(__AVX__) && defined(__AVX512F__) + +#if defined(__AVX__) && defined(__AVX2__) + // Load the scale factor into a vector register. + __m256 scale256 = _mm256_set1_ps(gain); + + // Process 16 elements at a time (256 bits /16 bits per float = 16 floats). + for (unsigned i_end = (len / 16) * 16; i != i_end; i += 16) { + // Load 8 int16_t elements into two 128-bit vector registers. + __m128i input_vec_1 = _mm_loadu_si128(reinterpret_cast(in + i)); + __m128i input_vec_2 = _mm_loadu_si128(reinterpret_cast(in + i + 8)); + + // Convert the int16_t elements to float and scale them + __m256 float_vec_1 = _mm256_cvtepi32_ps(_mm256_cvtepi16_epi32(input_vec_1)); + __m256 float_vec_2 = _mm256_cvtepi32_ps(_mm256_cvtepi16_epi32(input_vec_2)); + float_vec_1 = _mm256_mul_ps(float_vec_1, scale256); + float_vec_2 = _mm256_mul_ps(float_vec_2, scale256); + + // Convert float to brain float and store the result back to memory. + srsran_simd_bf16_storeu(out + i, float_vec_1, float_vec_2); + } +#endif // defined(__AVX__) && defined(__AVX2__) +#endif // defined(__AVX__) && defined(__AVX512F__) + + for (; i != len; ++i) { + out[i] = to_bf16(static_cast(in[i]) * gain); + } +} + void srsran::srsvec::convert(span x, float scale, span z) { srsran_assert(2 * x.size() == z.size(), "Invalid input or output span sizes"); @@ -193,8 +354,48 @@ void srsran::srsvec::convert(span out, span in) convert_cbf16_to_cf_simd(out.data(), in.data(), in.size()); } +void srsran::srsvec::convert(span out, span in) +{ + srsran_assert(in.size() == out.size(), "Invalid input or output span sizes"); + convert_bf16_to_f_simd(out.data(), in.data(), in.size()); +} + void srsran::srsvec::convert(span out, span in) { srsran_assert(in.size() == out.size(), "Invalid input or output span sizes"); convert_cf_to_cbf16_simd(out.data(), in.data(), in.size()); } + +void srsran::srsvec::convert(span out, span in) +{ + srsran_assert(in.size() == out.size(), "Invalid input or output span sizes"); + convert_f_to_bf16_simd(out.data(), in.data(), in.size()); +} + +void srsran::srsvec::convert(span z, span x, float scale) +{ + srsran_assert(2 * x.size() == z.size(), "Invalid input or output span sizes"); + + convert_bf16_to_int16_simd(z.data(), reinterpret_cast(x.data()), scale, z.size()); +} + +void srsran::srsvec::convert(span z, span x, float scale) +{ + srsran_assert(x.size() == 2 * z.size(), "Invalid input or output span sizes"); + + convert_int16_to_bf16_simd(reinterpret_cast(z.data()), x.data(), scale, z.size()); +} + +void srsran::srsvec::convert(span z, span x, float scale) +{ + srsran_assert(x.size() == z.size(), "Invalid input or output span sizes"); + + convert_bf16_to_int16_simd(z.data(), x.data(), scale, z.size()); +} + +void srsran::srsvec::convert(span z, span x, float scale) +{ + srsran_assert(x.size() == z.size(), "Invalid input or output span sizes"); + + convert_int16_to_bf16_simd(z.data(), x.data(), scale, z.size()); +} \ No newline at end of file diff --git a/lib/srsvec/simd.h b/lib/srsvec/simd.h index a7f8b2d101..e27cf6d618 100644 --- a/lib/srsvec/simd.h +++ b/lib/srsvec/simd.h @@ -257,6 +257,47 @@ inline void srsran_simd_f_storeu(float* ptr, simd_f_t simdreg) #endif /* __AVX512F__ */ } +// Interleaves values from two input vectors and stores the resulting vectors in memory. +inline void srsran_simd_f_storeu_interleaved(float* ptr, simd_f_t a, simd_f_t b) +{ +#ifdef __AVX512F__ + __m512 s1 = _mm512_permutex2var_ps( + a, + _mm512_setr_epi32(0x00, 0x10, 0x01, 0x11, 0x02, 0x12, 0x03, 0x13, 0x04, 0x14, 0x05, 0x15, 0x06, 0x16, 0x07, 0x17), + b); + __m512 s2 = _mm512_permutex2var_ps( + a, + _mm512_setr_epi32(0x08, 0x18, 0x09, 0x19, 0x0a, 0x1a, 0x0b, 0x1b, 0x0c, 0x1c, 0x0d, 0x1d, 0x0e, 0x1e, 0x0f, 0x1f), + b); + _mm512_storeu_ps(reinterpret_cast(ptr), s1); + _mm512_storeu_ps(reinterpret_cast(ptr + 16), s2); +#else /* __AVX512F__ */ +#ifdef __AVX2__ + // Permute for AVX registers (reorders data across 128-bit registers). + const __m256i idx = _mm256_setr_epi32(0, 4, 1, 5, 2, 6, 3, 7); + __m256 tmp_a = _mm256_permutevar8x32_ps(a, idx); + __m256 tmp_b = _mm256_permutevar8x32_ps(b, idx); + + __m256 out1 = _mm256_permute_ps(tmp_a, 0b11011000); + __m256 out2 = _mm256_permute_ps(tmp_b, 0b11011000); + _mm256_storeu_ps(reinterpret_cast(ptr), _mm256_unpacklo_ps(out1, out2)); + _mm256_storeu_ps(reinterpret_cast(ptr + 8), _mm256_unpackhi_ps(out1, out2)); +#else /* __AVX2__ */ +#ifdef __SSE4_1__ + _mm_storeu_ps(reinterpret_cast(ptr), _mm_unpacklo_ps(a, b)); + _mm_storeu_ps(reinterpret_cast(ptr + 4), _mm_unpackhi_ps(a, b)); +#else /* __ARM_NEON */ +#ifdef __ARM_NEON + float32x4x2_t ab_combined; + ab_combined.val[0] = a; + ab_combined.val[1] = b; + vst2q_f32(reinterpret_cast(ptr), ab_combined); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ +} + inline simd_f_t srsran_simd_f_set1(float x) { #ifdef __AVX512F__ @@ -891,123 +932,6 @@ inline void srsran_simd_cf_storeu(float* re, float* im, simd_cf_t simdreg) #endif /* __AVX512F__ */ } -inline simd_cf_t srsran_simd_cbf16_loadu(const cbf16_t* ptr) -{ - simd_cf_t ret; -#ifdef __AVX512F__ - __m512i temp = _mm512_loadu_si512(reinterpret_cast(ptr)); - - __m512i temp_re = _mm512_slli_epi32(temp, 16); - ret.re = _mm512_castsi512_ps(temp_re); - - __m512i temp_im = _mm512_and_si512(temp, _mm512_set1_epi32(0xffff0000)); - ret.im = _mm512_castsi512_ps(temp_im); -#else /* __AVX512F__ */ -#ifdef __AVX2__ - __m256i temp = _mm256_loadu_si256(reinterpret_cast(ptr)); - - __m256i temp_re = _mm256_slli_epi32(temp, 16); - ret.re = _mm256_castsi256_ps(temp_re); - - __m256i temp_im = _mm256_and_si256(temp, _mm256_set1_epi32(0xffff0000)); - ret.im = _mm256_castsi256_ps(temp_im); -#else /* __AVX2__ */ -#ifdef __SSE4_1__ - __m128i temp = _mm_loadu_si128(reinterpret_cast(ptr)); - - __m128i temp_re = _mm_slli_epi32(temp, 16); - ret.re = _mm_castsi128_ps(temp_re); - - __m128i temp_im = _mm_and_si128(temp, _mm_set1_epi32(0xffff0000)); - ret.im = _mm_castsi128_ps(temp_im); -#else /* __ARM_NEON */ -#ifdef __ARM_NEON - uint32x4_t temp = vld1q_u32(reinterpret_cast(ptr)); - uint32x4_t temp_re = vshlq_n_u32(temp, 16); - ret.val[0] = vreinterpretq_f32_u32(temp_re); - - uint32x4_t temp_im = vandq_u32(temp, vdupq_n_u32(0xffff0000)); - ret.val[1] = vreinterpretq_f32_u32(temp_im); -#endif /* __ARM_NEON */ -#endif /* __SSE4_1__ */ -#endif /* __AVX2__ */ -#endif /* __AVX512F__ */ - return ret; -} - -inline void srsran_simd_cbf16_storeu(cbf16_t* ptr, simd_cf_t simdreg) -{ -#ifdef __AVX512F__ - __m512i bias = _mm512_set1_epi32(0x7fff); - __m512i one = _mm512_set1_epi32(0x1); - - __m512i temp_re = _mm512_castps_si512(simdreg.re); - __m512i temp_im = _mm512_castps_si512(simdreg.im); - - // Round to nearest even. - temp_re = _mm512_add_epi32(temp_re, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(temp_re, 16), one))); - temp_im = _mm512_add_epi32(temp_im, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(temp_im, 16), one))); - - // Pack both parts in 32-bit registers. - __m512i temp = - _mm512_or_si512(_mm512_and_si512(temp_im, _mm512_set1_epi32(0xffff0000)), _mm512_srli_epi32(temp_re, 16)); - - _mm512_storeu_si512(reinterpret_cast<__m512i*>(ptr), temp); -#else /* __AVX512F__ */ -#ifdef __AVX2__ - __m256i bias = _mm256_set1_epi32(0x7fff); - __m256i one = _mm256_set1_epi32(0x1); - - __m256i temp_re = _mm256_castps_si256(simdreg.re); - __m256i temp_im = _mm256_castps_si256(simdreg.im); - - // Round to nearest even. - temp_re = _mm256_add_epi32(temp_re, _mm256_add_epi32(bias, _mm256_and_si256(_mm256_srli_epi32(temp_re, 16), one))); - temp_im = _mm256_add_epi32(temp_im, _mm256_add_epi32(bias, _mm256_and_si256(_mm256_srli_epi32(temp_im, 16), one))); - - // Pack both parts in 32-bit registers. - __m256i temp = - _mm256_or_si256(_mm256_and_si256(temp_im, _mm256_set1_epi32(0xffff0000)), _mm256_srli_epi32(temp_re, 16)); - - _mm256_storeu_si256(reinterpret_cast<__m256i*>(ptr), temp); -#else /* __AVX512F__ */ -#ifdef __SSE4_1__ - __m128i bias = _mm_set1_epi32(0x7fff); - __m128i one = _mm_set1_epi32(0x1); - - __m128i temp_re = _mm_castps_si128(simdreg.re); - __m128i temp_im = _mm_castps_si128(simdreg.im); - - // Round to nearest even. - temp_re = _mm_add_epi32(temp_re, _mm_add_epi32(bias, _mm_and_si128(_mm_srli_epi32(temp_re, 16), one))); - temp_im = _mm_add_epi32(temp_im, _mm_add_epi32(bias, _mm_and_si128(_mm_srli_epi32(temp_im, 16), one))); - - // Pack both parts in 32-bit registers. - __m128i temp = - _mm_or_si128(_mm_and_si128(_mm_and_si128(temp_im, _mm_set1_epi32(0xffff0000)), _mm_set1_epi32(0xffff0000)), - _mm_srli_epi32(temp_re, 16)); - - _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), temp); -#else /* __ARM_NEON */ -#ifdef __ARM_NEON - uint32x4_t bias = vdupq_n_u32(0x7fff); - uint32x4_t one = vdupq_n_u32(0x1); - - uint32x4_t temp_re = vreinterpretq_u32_f32(simdreg.val[0]); - uint32x4_t temp_im = vreinterpretq_u32_f32(simdreg.val[1]); - - temp_re = vaddq_u32(temp_re, vaddq_u32(bias, vandq_u32(vshrq_n_u32(temp_re, 16), one))); - temp_im = vaddq_u32(temp_im, vaddq_u32(bias, vandq_u32(vshrq_n_u32(temp_im, 16), one))); - - uint32x4_t temp = vorrq_u32(vandq_u32(temp_im, vdupq_n_u32(0xffff0000)), vshrq_n_u32(temp_re, 16)); - - vst1q_u32(reinterpret_cast(ptr), temp); -#endif /* __ARM_NEON */ -#endif /* __SSE4_1__ */ -#endif /* __AVX2__ */ -#endif /* __AVX512F__ */ -} - inline simd_f_t srsran_simd_cf_re(simd_cf_t in) { #ifdef __ARM_NEON @@ -2179,6 +2103,404 @@ inline simd_s_t srsran_simd_convert_2f_s(simd_f_t a, simd_f_t b) #endif /* __AVX512F__ */ } +// Converts 2 vectors of single-precision floats to a vector of int16_t, given that input vectors contain values of the +// interleaved data read from memory. +inline simd_s_t srsran_simd_convert_2f_interleaved_s(simd_f_t a, simd_f_t b) +{ +#ifdef __AVX512F__ + __m512 aa = _mm512_unpacklo_ps(a, b); + __m512 bb = _mm512_unpackhi_ps(a, b); + __m512i ai = _mm512_cvt_roundps_epi32(aa, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + __m512i bi = _mm512_cvt_roundps_epi32(bb, _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC); + return _mm512_packs_epi32(ai, bi); +#else /* __AVX512F__ */ +#ifdef __AVX2__ + __m256 aa = _mm256_round_ps(_mm256_unpacklo_ps(a, b), _MM_FROUND_NINT); + __m256 bb = _mm256_round_ps(_mm256_unpackhi_ps(a, b), _MM_FROUND_NINT); + __m256i ai = _mm256_cvtps_epi32(aa); + __m256i bi = _mm256_cvtps_epi32(bb); + return _mm256_packs_epi32(ai, bi); +#else +#ifdef __SSE4_1__ + __m128 aa = _mm_round_ps(_mm_unpacklo_ps(a, b), _MM_FROUND_NINT); + __m128 bb = _mm_round_ps(_mm_unpackhi_ps(a, b), _MM_FROUND_NINT); + __m128i ai = _mm_cvtps_epi32(aa); + __m128i bi = _mm_cvtps_epi32(bb); + return _mm_packs_epi32(ai, bi); +#else +#ifdef __ARM_NEON + int32x4_t ai = vcvtnq_s32_f32(a); + int32x4_t bi = vcvtnq_s32_f32(b); + int16x4x2_t ab_s16_interleaved = vzip_s16(vqmovn_s32(ai), vqmovn_s32(bi)); + return (simd_s_t)vcombine_s16(ab_s16_interleaved.val[0], ab_s16_interleaved.val[1]); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ +} + +#ifdef __AVX512F__ +inline simd_s_t srsran_simd_convert_1f_bf16(simd_f_t a) +{ + simd_s_t ret; + + const __m512i bias = _mm512_set1_epi32(0x7fff); + const __m512i one = _mm512_set1_epi32(0x1); + const __m512i mask = _mm512_set1_epi32(0xffff0000); + const uint16_t zero_mask = 0x00ff; + + __m512i a_i32 = _mm512_castps_si512(a); + + // Round to nearest even. + a_i32 = _mm512_add_epi32(a_i32, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(a_i32, 16), one))); + + // Input: a0 xx | a1 xx | a2 xx | a3 xx | a4 xx | a5 xx | a6 xx | a7 xx | ... | a15 xx | + // Transformations: + //_mm512_srli_epi32 00 a0 | 00 a1 | 00 a2 | 00 a3 | 00 a4 | 00 a5 | 00 a6 | 00 a7 | ... | 00 a15 | + //_mm512_and_si512 a0 00 | a1 00 | a2 00 | a3 00 | a4 00 | a5 00 | a6 00 | a7 00 | ... | a15 00 | + // _mm512_maskz_permutex2var_epi32 + // a1 00 | a2 00 | a3 00 | a4 00 | a5 00 | a6 00 | a7 00 | a8 00 | a9 00 | a10 00 | ... | a0 00 | + //_mm512_or_si512 a1 a0 | a2 a1 | a3 a2 | a4 a3 | a5 a4 | a6 a5 | a7 a6 | a8 a7 | a9 a8 | a10 a9 | a11 a10 | ... | + + // Remove the 16 least significant bits of the fractional part. + __m512i tmp_a_lsb = _mm512_srli_epi32(a_i32, 16); + __m512i tmp_a_masked = _mm512_and_si512(a_i32, mask); + + __m512i tmp_a_msb = _mm512_maskz_permutex2var_epi32( + 0x7fff, + tmp_a_masked, + _mm512_setr_epi32(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0), + tmp_a_masked); + __m512i a_packed = _mm512_or_si512(tmp_a_lsb, tmp_a_msb); + + // Pack results into an output vector. + ret = _mm512_maskz_permutex2var_epi32( + zero_mask, + a_packed, + _mm512_setr_epi32(0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe), + a_packed); + + return ret; +} +#endif // __AVX512F__ + +static inline simd_s_t srsran_simd_convert_2f_bf16(simd_f_t a, simd_f_t b) +{ + simd_s_t ret; +#ifdef __AVX512F__ + const __m512i bias = _mm512_set1_epi32(0x7fff); + const __m512i one = _mm512_set1_epi32(0x1); + + __m512i a_i32 = _mm512_castps_si512(a); + __m512i b_i32 = _mm512_castps_si512(b); + + // Round to nearest even. + a_i32 = _mm512_add_epi32(a_i32, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(a_i32, 16), one))); + b_i32 = _mm512_add_epi32(b_i32, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(b_i32, 16), one))); + +#ifdef __AVX512BW__ + // Use 16 MSBs of each 32-bit input register and pack them in one resulting vector. + ret = _mm512_permutex2var_epi16(a_i32, + _mm512_setr_epi32(0x030001, + 0x070005, + 0x0b0009, + 0x0f000d, + 0x130011, + 0x170015, + 0x1b0019, + 0x1f001d, + 0x230021, + 0x270025, + 0x2b0029, + 0x2f002d, + 0x330031, + 0x370035, + 0x3b0039, + 0x3f003d), + b_i32); +#else // __AVX512BW__ + const __m512i mask = _mm512_set1_epi32(0xffff0000); + // Input: a0 xx | a1 xx | a2 xx | a3 xx | a4 xx | a5 xx | a6 xx | a7 xx | ... | a15 xx | + // Transformations: + //_mm512_srli_epi32 00 a0 | 00 a1 | 00 a2 | 00 a3 | 00 a4 | 00 a5 | 00 a6 | 00 a7 | ... | 00 a15 | + //_mm512_and_si512 a0 00 | a1 00 | a2 00 | a3 00 | a4 00 | a5 00 | a6 00 | a7 00 | ... | a15 00 | + // _mm512_maskz_permutex2var_epi32 + // a1 00 | a2 00 | a3 00 | a4 00 | a5 00 | a6 00 | a7 00 | a8 00 | a9 00 | a10 00 | ... | a0 00 | + //_mm512_or_si512 a1 a0 | a2 a1 | a3 a2 | a4 a3 | a5 a4 | a6 a5 | a7 a6 | a8 a7 | a9 a8 | a10 a9 | a11 a10 | ... | + + // Remove the 16 least significant bits of the fractional part. + __m512i tmp_a_lsb = _mm512_srli_epi32(a_i32, 16); + __m512i tmp_a_masked = _mm512_and_si512(a_i32, mask); + + __m512i tmp_a_msb = _mm512_maskz_permutex2var_epi32( + 0x7fff, + tmp_a_masked, + _mm512_setr_epi32(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0), + tmp_a_masked); + __m512i a_packed = _mm512_or_si512(tmp_a_lsb, tmp_a_msb); + + // Remove the 16 least significant bits of the fractional part. + __m512i tmp_b_lsb = _mm512_srli_epi32(b_i32, 16); + __m512i tmp_b_masked = _mm512_and_si512(b_i32, mask); + + __m512i tmp_b_msb = _mm512_maskz_permutex2var_epi32( + 0x7fff, + tmp_b_masked, + _mm512_setr_epi32(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0), + tmp_b_masked); + __m512i b_packed = _mm512_or_si512(tmp_b_lsb, tmp_b_msb); + + // Pack results into an output vector. + ret = _mm512_permutex2var_epi32( + a_packed, + _mm512_setr_epi32(0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e), + b_packed); +#endif + +#else /* __AVX512F__ */ +#ifdef __AVX2__ + const __m256i bias = _mm256_set1_epi32(0x7fff); + const __m256i one = _mm256_set1_epi32(0x1); + const __m256i mask = _mm256_set1_epi32(0xffff0000); + + __m256i a_i32 = _mm256_castps_si256(a); + __m256i b_i32 = _mm256_castps_si256(b); + + // Round to nearest even. + a_i32 = _mm256_add_epi32(a_i32, _mm256_add_epi32(bias, _mm256_and_si256(_mm256_srli_epi32(a_i32, 16), one))); + b_i32 = _mm256_add_epi32(b_i32, _mm256_add_epi32(bias, _mm256_and_si256(_mm256_srli_epi32(b_i32, 16), one))); + + // Remove the 16 least significant bits of the fractional part. + __m256i tmp_a_lsb = _mm256_srli_epi32(a_i32, 16); + __m256i tmp_a_masked = _mm256_and_si256(a_i32, mask); + + __m256i tmp_a_msb = + _mm256_permutevar8x32_epi32(tmp_a_masked, _mm256_setr_epi32(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0)); + __m256i a_packed = _mm256_or_si256(tmp_a_lsb, tmp_a_msb); + a_packed = _mm256_permutevar8x32_epi32(a_packed, _mm256_setr_epi32(0x0, 0x2, 0x4, 0x6, 0x1, 0x3, 0x5, 0x7)); + + // Remove the 16 least significant bits of the fractional part. + __m256i tmp_b_lsb = _mm256_srli_epi32(b_i32, 16); + __m256i tmp_b_masked = _mm256_and_si256(b_i32, mask); + + __m256i tmp_b_msb = + _mm256_permutevar8x32_epi32(tmp_b_masked, _mm256_setr_epi32(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0)); + __m256i b_packed = _mm256_or_si256(tmp_b_lsb, tmp_b_msb); + b_packed = _mm256_permutevar8x32_epi32(b_packed, _mm256_setr_epi32(0x1, 0x3, 0x5, 0x7, 0x0, 0x2, 0x4, 0x6)); + + // Pack results into an output vector. + ret = _mm256_blend_epi32(a_packed, b_packed, 0b11110000); + +#else /* __AVX2__ */ +#ifdef __SSE4_1__ + const __m128i bias = _mm_set1_epi32(0x7fff); + const __m128i one = _mm_set1_epi32(0x1); + const __m128i mask = _mm_set1_epi32(0xffff0000); + + __m128i a_i32 = _mm_castps_si128(a); + __m128i b_i32 = _mm_castps_si128(b); + + // Round to nearest even. + a_i32 = _mm_add_epi32(a_i32, _mm_add_epi32(bias, _mm_and_si128(_mm_srli_epi32(a_i32, 16), one))); + b_i32 = _mm_add_epi32(b_i32, _mm_add_epi32(bias, _mm_and_si128(_mm_srli_epi32(b_i32, 16), one))); + + // Remove the 16 least significant bits of the fractional part. + __m128i tmp_a_lsb = _mm_srli_epi32(a_i32, 16); + __m128i tmp_a_masked = _mm_and_si128(a_i32, mask); + __m128i tmp_a_msb = _mm_shuffle_epi32(tmp_a_masked, 0b00111001); + __m128i a_packed = _mm_or_si128(tmp_a_lsb, tmp_a_msb); + a_packed = _mm_shuffle_epi32(a_packed, 0b11001000); + + // Remove the 16 least significant bits of the fractional part. + __m128i tmp_b_lsb = _mm_srli_epi32(b_i32, 16); + __m128i tmp_b_masked = _mm_and_si128(b_i32, mask); + __m128i tmp_b_msb = _mm_shuffle_epi32(tmp_b_masked, 0b00111001); + __m128i b_packed = _mm_or_si128(tmp_b_lsb, tmp_b_msb); + b_packed = _mm_shuffle_epi32(b_packed, 0b10001100); + + // Pack results into an output vector. + ret = _mm_blend_epi16(a_packed, b_packed, 0xf0); +#else /* __ARM_NEON */ +#ifdef __ARM_NEON + const uint32x4_t bias = vdupq_n_u32(0x7fff); + const uint32x4_t one = vdupq_n_u32(0x1); + + uint32x4_t a_i32 = vreinterpretq_u32_f32(a); + uint32x4_t b_i32 = vreinterpretq_u32_f32(b); + + a_i32 = vaddq_u32(a_i32, vaddq_u32(bias, vandq_u32(vshrq_n_u32(a_i32, 16), one))); + b_i32 = vaddq_u32(b_i32, vaddq_u32(bias, vandq_u32(vshrq_n_u32(b_i32, 16), one))); + + // Remove the 16 least significant bits of the fractional part. + uint16x8_t tmp_a_1 = vreinterpretq_u16_u32(vshrq_n_u32(a_i32, 16)); + uint16x8_t tmp_a_2 = vextq_u16(tmp_a_1, tmp_a_1, 1); + uint32x4_t a_packed = vreinterpretq_u32_u16(vorrq_u16(tmp_a_1, tmp_a_2)); + + // Remove the 16 least significant bits of the fractional part. + uint16x8_t tmp_b_1 = vreinterpretq_u16_u32(vshrq_n_u32(b_i32, 16)); + uint16x8_t tmp_b_2 = vextq_u16(tmp_b_1, tmp_b_1, 1); + uint32x4_t b_packed = vreinterpretq_u32_u16(vorrq_u16(tmp_b_1, tmp_b_2)); + + ret = vreinterpretq_s16_u32(vuzpq_u32(a_packed, b_packed).val[0]); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ + return ret; +} + +// Converts 2 vectors of single-precision floats to a vector of bf16_t, given that input vectors contain values of the +// interleaved data read from memory. +static inline simd_s_t srsran_simd_convert_2f_interleaved_bf16(simd_f_t a, simd_f_t b) +{ +#ifdef __AVX512F__ + const __m512i bias = _mm512_set1_epi32(0x7fff); + const __m512i one = _mm512_set1_epi32(0x1); + + __m512i a_i32 = _mm512_castps_si512(a); + __m512i b_i32 = _mm512_castps_si512(b); + + // Round to nearest even. + a_i32 = _mm512_add_epi32(a_i32, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(a_i32, 16), one))); + b_i32 = _mm512_add_epi32(b_i32, _mm512_add_epi32(bias, _mm512_and_si512(_mm512_srli_epi32(b_i32, 16), one))); + + // Pack both parts in 32-bit registers. + return _mm512_or_si512(_mm512_and_si512(b_i32, _mm512_set1_epi32(0xffff0000)), _mm512_srli_epi32(a_i32, 16)); +#else /* __AVX512F__ */ +#ifdef __AVX2__ + const __m256i bias = _mm256_set1_epi32(0x7fff); + const __m256i one = _mm256_set1_epi32(0x1); + + __m256i a_i32 = _mm256_castps_si256(a); + __m256i b_i32 = _mm256_castps_si256(b); + + // Round to nearest even. + a_i32 = _mm256_add_epi32(a_i32, _mm256_add_epi32(bias, _mm256_and_si256(_mm256_srli_epi32(a_i32, 16), one))); + b_i32 = _mm256_add_epi32(b_i32, _mm256_add_epi32(bias, _mm256_and_si256(_mm256_srli_epi32(b_i32, 16), one))); + + // Pack both parts in 32-bit registers. + return _mm256_or_si256(_mm256_and_si256(b_i32, _mm256_set1_epi32(0xffff0000)), _mm256_srli_epi32(a_i32, 16)); +#else /* __AVX512F__ */ +#ifdef __SSE4_1__ + const __m128i bias = _mm_set1_epi32(0x7fff); + const __m128i one = _mm_set1_epi32(0x1); + + __m128i a_i32 = _mm_castps_si128(a); + __m128i b_i32 = _mm_castps_si128(b); + + // Round to nearest even. + a_i32 = _mm_add_epi32(a_i32, _mm_add_epi32(bias, _mm_and_si128(_mm_srli_epi32(a_i32, 16), one))); + b_i32 = _mm_add_epi32(b_i32, _mm_add_epi32(bias, _mm_and_si128(_mm_srli_epi32(b_i32, 16), one))); + + // Pack both parts in 32-bit registers. + return _mm_or_si128(_mm_and_si128(b_i32, _mm_set1_epi32(0xffff0000)), _mm_srli_epi32(a_i32, 16)); +#else /* __ARM_NEON */ +#ifdef __ARM_NEON + const uint32x4_t bias = vdupq_n_u32(0x7fff); + const uint32x4_t one = vdupq_n_u32(0x1); + + uint32x4_t a_i32 = vreinterpretq_u32_f32(a); + uint32x4_t b_i32 = vreinterpretq_u32_f32(b); + + a_i32 = vaddq_u32(a_i32, vaddq_u32(bias, vandq_u32(vshrq_n_u32(a_i32, 16), one))); + b_i32 = vaddq_u32(b_i32, vaddq_u32(bias, vandq_u32(vshrq_n_u32(b_i32, 16), one))); + + return vreinterpretq_s16_u32(vorrq_u32(vandq_u32(b_i32, vdupq_n_u32(0xffff0000)), vshrq_n_u32(a_i32, 16))); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ +} + +inline void srsran_simd_bf16_loadu(simd_f_t& even, simd_f_t& odd, const bf16_t* ptr) +{ +#ifdef __AVX512F__ + // Load 32 16-bit brain float values. + __m512i temp = _mm512_loadu_si512(reinterpret_cast(ptr)); + // Unpack brain floats from 32-bit registers. + __m512i temp_even = _mm512_slli_epi32(temp, 16); + __m512i temp_odd = _mm512_and_si512(temp, _mm512_set1_epi32(0xffff0000)); + // Cast them to single-precision floating point numbers (doesn't generate any instruction). + even = _mm512_castsi512_ps(temp_even); + odd = _mm512_castsi512_ps(temp_odd); +#else /* __AVX512F__ */ +#ifdef __AVX2__ + __m256i temp = _mm256_loadu_si256(reinterpret_cast(ptr)); + even = _mm256_castsi256_ps(_mm256_slli_epi32(temp, 16)); + odd = _mm256_castsi256_ps(_mm256_and_si256(temp, _mm256_set1_epi32(0xffff0000))); +#else /* __AVX2__ */ +#ifdef __SSE4_1__ + __m128i temp = _mm_loadu_si128(reinterpret_cast(ptr)); + even = _mm_castsi128_ps(_mm_slli_epi32(temp, 16)); + odd = _mm_castsi128_ps(_mm_and_si128(temp, _mm_set1_epi32(0xffff0000))); +#else /* __ARM_NEON */ +#ifdef __ARM_NEON + uint32x4_t temp = vld1q_u32(reinterpret_cast(ptr)); + even = vreinterpretq_f32_u32(vshlq_n_u32(temp, 16)); + odd = vreinterpretq_f32_u32(vandq_u32(temp, vdupq_n_u32(0xffff0000))); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ +} + +#ifdef SRSRAN_SIMD_CF_SIZE +inline simd_cf_t srsran_simd_cbf16_loadu(const cbf16_t* ptr) +{ + simd_cf_t ret; +#ifdef __ARM_NEON + srsran_simd_bf16_loadu(ret.val[0], ret.val[1], reinterpret_cast(ptr)); +#else // __ARM_NEON + srsran_simd_bf16_loadu(ret.re, ret.im, reinterpret_cast(ptr)); +#endif + return ret; +} +#endif // SRSRAN_SIMD_CF_SIZE + +inline void srsran_simd_bf16_storeu(bf16_t* ptr, simd_f_t a, simd_f_t b) +{ + simd_s_t bf16_vec = srsran_simd_convert_2f_bf16(a, b); +#ifdef __AVX512F__ + _mm512_storeu_si512(reinterpret_cast<__m512i*>(ptr), bf16_vec); +#else /* __AVX512F__ */ +#ifdef __AVX2__ + _mm256_storeu_si256(reinterpret_cast<__m256i*>(ptr), bf16_vec); +#else /* __AVX2__ */ +#ifdef __SSE4_1__ + _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), bf16_vec); +#else /* __SSE4_1__ */ +#ifdef __ARM_NEON + vst1q_u32(reinterpret_cast(ptr), vreinterpretq_u32_s16(bf16_vec)); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ +} + +#ifdef SRSRAN_SIMD_CF_SIZE +inline void srsran_simd_cbf16_storeu(cbf16_t* ptr, simd_cf_t simdreg) +{ + simd_s_t packed_iq_bf16 = + srsran_simd_convert_2f_interleaved_bf16(srsran_simd_cf_re(simdreg), srsran_simd_cf_im(simdreg)); + +#ifdef __AVX512F__ + _mm512_storeu_si512(reinterpret_cast<__m512i*>(ptr), packed_iq_bf16); +#else /* __AVX512F__ */ +#ifdef __AVX2__ + _mm256_storeu_si256(reinterpret_cast<__m256i*>(ptr), packed_iq_bf16); +#else /* __AVX2__ */ +#ifdef __SSE4_1__ + _mm_storeu_si128(reinterpret_cast<__m128i*>(ptr), packed_iq_bf16); +#else /* __SSE4_1__ */ +#ifdef __ARM_NEON + vst1q_u32(reinterpret_cast(ptr), vreinterpretq_u32_s16(packed_iq_bf16)); +#endif /* __ARM_NEON */ +#endif /* __SSE4_1__ */ +#endif /* __AVX2__ */ +#endif /* __AVX512F__ */ +} +#endif // SRSRAN_SIMD_CF_SIZE + #endif /* SRSRAN_SIMD_F_SIZE && SRSRAN_SIMD_C16_SIZE */ #if SRSRAN_SIMD_B_SIZE diff --git a/lib/support/config_yaml.cpp b/lib/support/config_yaml.cpp index 3a13566e80..173fd4f7c6 100644 --- a/lib/support/config_yaml.cpp +++ b/lib/support/config_yaml.cpp @@ -43,7 +43,8 @@ class yaml_config_parser : public CLI::Config } // namespace -std::string yaml_config_parser::to_config(const CLI::App* app, bool default_also, bool, std::string) const +std::string +yaml_config_parser::to_config(const CLI::App* app, bool default_also, bool write_description, std::string) const { YAML::Node config; @@ -63,12 +64,16 @@ std::string yaml_config_parser::to_config(const CLI::App* app, bool default_also } else if (opt->count() > 1) { // Recover the items from the string. for (const auto& str : opt->results()) { - config[name].push_back(YAML::Load(str)); + YAML::Node node(YAML::Load(str)); + // Write the arrays as YAML flow instead of YAML block. + config[name].SetStyle((node.size() == 0) ? YAML::EmitterStyle::Flow : YAML::EmitterStyle::Block); + config[name].push_back(node); } } else if (default_also && !opt->get_default_str().empty()) { // If the option has a default and is requested by optional argument. - config[name] = opt->get_default_str(); + config[name] = YAML::Load(opt->get_default_str()); } + continue; } @@ -91,7 +96,7 @@ std::string yaml_config_parser::to_config(const CLI::App* app, bool default_also } for (const CLI::App* subcom : app->get_subcommands({})) { - if (!default_also and !subcom->count()) { + if ((!default_also && !subcom->count()) || subcom->get_disabled()) { continue; } config[subcom->get_name()] = YAML::Load(to_config(subcom, default_also, false, "")); diff --git a/tests/benchmarks/du_high/CMakeLists.txt b/tests/benchmarks/du_high/CMakeLists.txt index c2646adc90..9710c0a417 100644 --- a/tests/benchmarks/du_high/CMakeLists.txt +++ b/tests/benchmarks/du_high/CMakeLists.txt @@ -23,5 +23,11 @@ set_directory_properties(PROPERTIES LABELS "du_high|tsan") include_directories(../../..) add_executable(du_high_benchmark du_high_benchmark.cpp) -target_link_libraries(du_high_benchmark srsran_du_high f1ap_du_test_helpers sched_test_doubles srsran_pcap gtest) +target_link_libraries(du_high_benchmark + srsran_du_high + f1ap_du_test_helpers + pdcp_test_doubles + sched_test_doubles + srsran_pcap gtest +) add_test(du_high_benchmark du_high_benchmark) diff --git a/tests/benchmarks/du_high/du_high_benchmark.cpp b/tests/benchmarks/du_high/du_high_benchmark.cpp index 45ef1f02ae..ddf756f3c0 100644 --- a/tests/benchmarks/du_high/du_high_benchmark.cpp +++ b/tests/benchmarks/du_high/du_high_benchmark.cpp @@ -43,6 +43,7 @@ #include "lib/du_high/du_high_impl.h" #include "lib/mac/mac_ul/ul_bsr.h" #include "tests/test_doubles/f1ap/f1ap_test_messages.h" +#include "tests/test_doubles/pdcp/pdcp_pdu_generator.h" #include "tests/test_doubles/scheduler/scheduler_result_test.h" #include "tests/unittests/f1ap/du/f1ap_du_test_helpers.h" #include "srsran/asn1/f1ap/common.h" @@ -587,7 +588,8 @@ class phy_simulator : public mac_result_notifier, public mac_cell_result_notifie /// \brief TestBench for the DU-high. class du_high_bench { - static const unsigned DEFAULT_DL_PDU_SIZE = 1500; + static constexpr unsigned DEFAULT_DL_PDU_SIZE = 1500; + static constexpr unsigned PDCP_MAX_HDR_LEN = 3; public: du_high_bench(unsigned dl_buffer_state_bytes_, @@ -649,9 +651,10 @@ class du_high_bench du_hi = std::make_unique(cfg); - // Create PDCP PDU. - report_fatal_error_if_not(pdcp_pdu.append(test_rgen::random_vector(f1u_pdu_size.value())), - "Unable to allocate PDU"); + // Create PDCP PDU Payload. + report_fatal_error_if_not( + pdcp_pdu_payload.append(test_rgen::random_vector(f1u_pdu_size.value() - PDCP_MAX_HDR_LEN)), + "Unable to allocate PDU"); // Create MAC PDU. report_fatal_error_if_not(mac_pdu.append(test_rgen::random_vector( buff_size_field_to_bytes(lbsr_buff_sz, srsran::bsr_format::LONG_BSR))), @@ -825,7 +828,8 @@ class du_high_bench while (not workers.dl_exec.defer([this]() { static std::array pdcp_sn_list{0}; const unsigned nof_dl_pdus_per_slot = divide_ceil(f1u_dl_pdu_bytes_per_slot, this->f1u_pdu_size.value()); - const unsigned last_dl_pdu_size = f1u_dl_pdu_bytes_per_slot % this->f1u_pdu_size.value(); + const unsigned last_dl_pdu_size = + std::max(PDCP_MAX_HDR_LEN, f1u_dl_pdu_bytes_per_slot % this->f1u_pdu_size.value()); // Forward DL buffer occupancy updates to all bearers in a Round-robin fashion. for (unsigned i = 0; i != nof_dl_pdus_per_slot; ++i) { @@ -836,20 +840,26 @@ class du_high_bench pdcp_sn_list[bearer_idx] = (pdcp_sn_list[bearer_idx] + 1) % (1U << 18U); // We perform a deep-copy of the byte buffer to better simulate a real deployment, where there is stress over // the byte buffer pool. - auto pdu_copy = pdcp_pdu.deep_copy(); - if (not pdu_copy.has_value()) { - test_logger.warning("Byte buffer segment pool depleted"); + byte_buffer pdcp_pdu = test_helpers::create_pdcp_pdu( + pdcp_sn_size::size12bits, /* is_srb = */ false, pdcp_sn_list[bearer_idx], PDCP_MAX_HDR_LEN, 0); + auto payload_copy = pdcp_pdu_payload.deep_copy(); + if (not payload_copy.has_value()) { + test_logger.warning("Failed to copy payload for PDCP PDU. Byte buffer segment pool depleted"); + return; + } + if (not pdcp_pdu.append(std::move(payload_copy.value()))) { + test_logger.warning("Failed to append payload to PDCP PDU. Byte buffer segment pool depleted"); return; } if (i == nof_dl_pdus_per_slot - 1 and last_dl_pdu_size != 0) { // If it is last DL PDU. - if (!pdu_copy.value().resize(last_dl_pdu_size)) { + if (!pdcp_pdu.resize(last_dl_pdu_size)) { test_logger.warning("Unable to resize PDU to {} bytes", last_dl_pdu_size); return; } } - f1u_dl_total_bytes.fetch_add(pdu_copy.value().length(), std::memory_order_relaxed); - du_notif->on_new_pdu(nru_dl_message{.t_pdu = std::move(pdu_copy.value())}); + f1u_dl_total_bytes.fetch_add(pdcp_pdu.length(), std::memory_order_relaxed); + du_notif->on_new_pdu(nru_dl_message{.t_pdu = std::move(pdcp_pdu)}); } })) { // keep trying to push new PDUs. @@ -1091,7 +1101,7 @@ class du_high_bench /// Queue of MAC CRC indication message to be sent in their expected receive slot. std::deque pending_crc; - byte_buffer pdcp_pdu; + byte_buffer pdcp_pdu_payload; byte_buffer mac_pdu; // - 8-bit R/LCID MAC subheader. diff --git a/tests/benchmarks/phy/upper/channel_processors/prach_detector_benchmark.cpp b/tests/benchmarks/phy/upper/channel_processors/prach_detector_benchmark.cpp index e8c379a0cd..28e5614ace 100644 --- a/tests/benchmarks/phy/upper/channel_processors/prach_detector_benchmark.cpp +++ b/tests/benchmarks/phy/upper/channel_processors/prach_detector_benchmark.cpp @@ -33,10 +33,11 @@ using namespace srsran; // General test configuration parameters. -static uint64_t nof_repetitions = 1000; -static std::set set_nof_rx_ports = {1, 2, 4}; -static std::set set_format = {prach_format_type::zero, prach_format_type::B4}; -static std::set set_zcz = {0, 1, 14}; +static uint64_t nof_repetitions = 1000; +static std::set set_nof_rx_ports = {1, 2, 4}; +static std::set set_format = {prach_format_type::zero, prach_format_type::B4}; +static std::set set_zcz = {0, 1, 14}; +static std::set set_nof_preambles = {4, 64}; // Global pseudo-random generator. static std::mt19937 rgen(0); @@ -147,31 +148,36 @@ std::vector generate_test_cases() if (((format == prach_format_type::zero) && (zcz == 14)) || ((format == prach_format_type::B4) && (zcz == 1))) { continue; } - // Create new test case. - test_cases.emplace_back(); - - // Select test case. - prach_detector::configuration& config = test_cases.back(); - - // Select RA subcarrier spacing. Set to 30kHz for all short preambles. - prach_subcarrier_spacing ra_scs = prach_subcarrier_spacing::kHz30; - if (is_long_preamble(format)) { - if (format == prach_format_type::three) { - ra_scs = prach_subcarrier_spacing::kHz5; - } else { - ra_scs = prach_subcarrier_spacing::kHz1_25; + + for (unsigned nof_preambles : set_nof_preambles) { + std::uniform_int_distribution start_preamble_index_dist(0, 64 - nof_preambles); + + // Create new test case. + test_cases.emplace_back(); + + // Select test case. + prach_detector::configuration& config = test_cases.back(); + + // Select RA subcarrier spacing. Set to 30kHz for all short preambles. + prach_subcarrier_spacing ra_scs = prach_subcarrier_spacing::kHz30; + if (is_long_preamble(format)) { + if (format == prach_format_type::three) { + ra_scs = prach_subcarrier_spacing::kHz5; + } else { + ra_scs = prach_subcarrier_spacing::kHz1_25; + } } - } - // Fill test case. - config.root_sequence_index = root_sequence_index_dist(rgen); - config.format = format; - config.restricted_set = restricted_set_config::UNRESTRICTED; - config.zero_correlation_zone = zcz; - config.start_preamble_index = 0; - config.nof_preamble_indices = 64; - config.ra_scs = ra_scs; - config.nof_rx_ports = nof_rx_ports; + // Fill test case. + config.root_sequence_index = root_sequence_index_dist(rgen); + config.format = format; + config.restricted_set = restricted_set_config::UNRESTRICTED; + config.zero_correlation_zone = zcz; + config.start_preamble_index = start_preamble_index_dist(rgen); + config.nof_preamble_indices = nof_preambles; + config.ra_scs = ra_scs; + config.nof_rx_ports = nof_rx_ports; + } } } } diff --git a/tests/benchmarks/rlc/rlc_am_rx_benchmark.cpp b/tests/benchmarks/rlc/rlc_am_rx_benchmark.cpp index 461ff85b84..8277d94416 100644 --- a/tests/benchmarks/rlc/rlc_am_rx_benchmark.cpp +++ b/tests/benchmarks/rlc/rlc_am_rx_benchmark.cpp @@ -157,7 +157,7 @@ std::vector generate_pdus(bench_params params, rx_order order) std::vector pdus; rlc_tx = std::make_unique(gnb_du_id_t::min, du_ue_index_t::MIN_DU_UE_INDEX, - srb_id_t::srb0, + drb_id_t::drb1, config, *tester, *tester, @@ -177,7 +177,7 @@ std::vector generate_pdus(bench_params params, rx_order order) int num_pdus = 0; int pdu_size = params.pdu_size; for (int i = 0; i < num_sdus; i++) { - byte_buffer sdu = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, i, num_bytes, i); + byte_buffer sdu = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, i, num_bytes, i); rlc_tx->handle_sdu(std::move(sdu), false); while (rlc_tx->get_buffer_state() > 0) { std::vector pdu_buf; @@ -240,7 +240,7 @@ void benchmark_rx_pdu(const bench_params& params, rx_order order) // Create RLC AM RX entity std::unique_ptr rlc_rx = std::make_unique(gnb_du_id_t::min, du_ue_index_t::MIN_DU_UE_INDEX, - srb_id_t::srb0, + drb_id_t::drb1, config, *tester, timer_factory{timers, ue_worker}, diff --git a/tests/benchmarks/rlc/rlc_handle_status_report.cpp b/tests/benchmarks/rlc/rlc_handle_status_report.cpp index c4ee32bac2..8a48a19fc7 100644 --- a/tests/benchmarks/rlc/rlc_handle_status_report.cpp +++ b/tests/benchmarks/rlc/rlc_handle_status_report.cpp @@ -126,7 +126,7 @@ void benchmark_status_pdu_handling(rlc_am_status_pdu status, const bench_params& auto context = [&rlc, &tester, config, &timers, &pcell_worker, &ue_worker, &pcap]() { rlc = std::make_unique(gnb_du_id_t::min, du_ue_index_t::MIN_DU_UE_INDEX, - srb_id_t::srb0, + drb_id_t::drb1, config, *tester, *tester, @@ -141,7 +141,7 @@ void benchmark_status_pdu_handling(rlc_am_status_pdu status, const bench_params& rlc->set_status_provider(tester.get()); for (int i = 0; i < 2048; i++) { - byte_buffer sdu = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, i, 7, 0); + byte_buffer sdu = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, i, 7, 0); rlc->handle_sdu(std::move(sdu), false); std::array pdu_buf; rlc->pull_pdu(pdu_buf); diff --git a/tests/e2e/tests/steps/stub.py b/tests/e2e/tests/steps/stub.py index b6d6efc16b..3453811011 100644 --- a/tests/e2e/tests/steps/stub.py +++ b/tests/e2e/tests/steps/stub.py @@ -29,6 +29,7 @@ import grpc import pytest +from _pytest.outcomes import Failed from google.protobuf.empty_pb2 import Empty from google.protobuf.text_format import MessageToString from google.protobuf.wrappers_pb2 import StringValue, UInt32Value @@ -222,7 +223,7 @@ def handle_start_error(name: str) -> Generator[None, None, None]: else: raise err from None if raise_failed: - pytest.fail(f"{name} failed to start") + raise Failed(msg=f"{name} failed to start", pytrace=True) from None def _log_attached_ue(future: grpc.Future, ue_stub: UEStub): diff --git a/tests/e2e/tests/test_mode.py b/tests/e2e/tests/test_mode.py index 2d9b8c4bbb..0257a5ae3e 100644 --- a/tests/e2e/tests/test_mode.py +++ b/tests/e2e/tests/test_mode.py @@ -96,7 +96,6 @@ def test_ue( "templates": { "cu": str(Path(__file__).joinpath("../test_mode/config_ue.yml").resolve()), "du": tmp_file.name, - "ru": tmp_file.name, }, }, } diff --git a/tests/e2e/tests/viavi.py b/tests/e2e/tests/viavi.py index 1debe471ff..00d4b40213 100644 --- a/tests/e2e/tests/viavi.py +++ b/tests/e2e/tests/viavi.py @@ -346,7 +346,6 @@ def _test_viavi( "max_pdschs_per_slot": test_declaration.max_pdschs_per_slot, "enable_qos_viavi": test_declaration.enable_qos_viavi, }, - "templates": {"extra": str(Path(__file__).joinpath("../viavi/config.yml").resolve())}, }, } if metrics_server is not None: diff --git a/tests/e2e/tests/viavi/config.yml b/tests/e2e/tests/viavi/config.yml deleted file mode 100644 index 1fc527099e..0000000000 --- a/tests/e2e/tests/viavi/config.yml +++ /dev/null @@ -1,57 +0,0 @@ -# -# Copyright 2021-2024 Software Radio Systems Limited -# -# This file is part of srsRAN -# -# srsRAN is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of -# the License, or (at your option) any later version. -# -# srsRAN is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# A copy of the GNU Affero General Public License can be found in -# the LICENSE file in the top-level directory of this distribution -# and at http://www.gnu.org/licenses/. -# - -cell_cfg: - sib: - t301: 2000 - t311: 3000 - prach: - prach_root_sequence_index: 1 - zero_correlation_zone: 0 - prach_frequency_start: 12 - pucch: - sr_period_ms: 40 - f1_nof_cell_res_sr: 30 - min_k1: 2 - csi: - csi_rs_period: 40 - pdsch: - mcs_table: qam256 - max_alloc_attempts: 8 - olla_target_bler: 0.1 - olla_cqi_inc_step: 0.05 - olla_max_cqi_offset: 10 - ul_common: - max_ul_grants_per_slot: 16 - max_pucchs_per_slot: 14 - pusch: - mcs_table: qam256 - min_k2: 2 - olla_target_bler: 0.1 - olla_snr_inc_step: 0.05 - olla_max_snr_offset: 20 - tdd_ul_dl_cfg: - nof_dl_symbols: 7 - nof_dl_slots: 7 - nof_ul_slots: 2 - -cu_up: - gtpu_queue_size: 32768 - gtpu_reordering_timer: 20 diff --git a/tests/e2e/tests/viavi/test_declaration.yml b/tests/e2e/tests/viavi/test_declaration.yml index ce6d15cc46..e782850be1 100644 --- a/tests/e2e/tests/viavi/test_declaration.yml +++ b/tests/e2e/tests/viavi/test_declaration.yml @@ -155,7 +155,7 @@ tests: - campaign_filename: *campaign_filename test_name: "32UE ideal UDP attach-detach with traffic" test_timeout: *test_timeout - gnb_extra_commands: *gnb_extra_commands + gnb_extra_commands: "log --ngap_level=debug --rrc_level=debug" id: "32UE ideal UDP attach-detach with traffic" max_pdschs_per_slot: 1 max_puschs_per_slot: 1 @@ -211,7 +211,7 @@ tests: - campaign_filename: *campaign_filename test_name: "32UE ideal ping" test_timeout: *test_timeout - gnb_extra_commands: "log --mac_level=info" + gnb_extra_commands: "log --all_level=info" id: "32UE ideal ping" max_pdschs_per_slot: 1 max_puschs_per_slot: 1 @@ -225,7 +225,7 @@ tests: - campaign_filename: *campaign_filename test_name: "32UE ideal ping with traffic" test_timeout: *test_timeout - gnb_extra_commands: "log --mac_level=info" + gnb_extra_commands: *gnb_extra_commands id: "32UE ideal ping with traffic" max_pdschs_per_slot: 1 max_puschs_per_slot: 1 @@ -235,3 +235,17 @@ tests: expected_ul_bitrate: *expected_ul_bitrate_low fail_if_kos: true warning_as_errors: true + + - campaign_filename: *campaign_filename + test_name: "experimental 32UE ideal UDP bidirectional Long" + test_timeout: 21600 # 6 hours + gnb_extra_commands: *gnb_extra_commands + id: "experimental 32UE ideal UDP bidirectional Long" + max_pdschs_per_slot: 1 + max_puschs_per_slot: 1 + enable_qos_viavi: false + # test/fail criteria + expected_dl_bitrate: *expected_dl_bitrate_high + expected_ul_bitrate: *expected_ul_bitrate_high + fail_if_kos: false + warning_as_errors: false diff --git a/tests/integrationtests/du_high/du_high_many_cells_test.cpp b/tests/integrationtests/du_high/du_high_many_cells_test.cpp index 169e1afa54..637e62fa19 100644 --- a/tests/integrationtests/du_high/du_high_many_cells_test.cpp +++ b/tests/integrationtests/du_high/du_high_many_cells_test.cpp @@ -127,7 +127,8 @@ TEST_P(du_high_many_cells_tester, when_ue_created_in_multiple_cells_then_traffic const unsigned nof_pdcp_pdus = 100, pdcp_pdu_size = 128; for (unsigned i = 0; i < nof_pdcp_pdus; ++i) { for (unsigned c = 0; c != GetParam().nof_cells; ++c) { - nru_dl_message f1u_pdu{.t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, i, pdcp_pdu_size, i)}; + nru_dl_message f1u_pdu{ + .t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, i, pdcp_pdu_size, i)}; cu_up_sim.created_du_notifs[c]->on_new_pdu(f1u_pdu); } } diff --git a/tests/integrationtests/du_high/du_high_test.cpp b/tests/integrationtests/du_high/du_high_test.cpp index 02d1726034..2e9482ca27 100644 --- a/tests/integrationtests/du_high/du_high_test.cpp +++ b/tests/integrationtests/du_high/du_high_test.cpp @@ -91,7 +91,8 @@ TEST_F(du_high_tester, when_ue_context_setup_completes_then_drb_is_active) // Forward several DRB PDUs. const unsigned nof_pdcp_pdus = 100, pdcp_pdu_size = 128; for (unsigned i = 0; i < nof_pdcp_pdus; ++i) { - nru_dl_message f1u_pdu{.t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, i, pdcp_pdu_size, i)}; + nru_dl_message f1u_pdu{ + .t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, i, pdcp_pdu_size, i)}; cu_up_sim.created_du_notifs[0]->on_new_pdu(f1u_pdu); } @@ -150,7 +151,8 @@ TEST_F(du_high_tester, when_ue_context_setup_release_starts_then_drb_activity_st const unsigned nof_pdcp_pdus = 100, pdcp_pdu_size = 128; for (unsigned i = 0; i < nof_pdcp_pdus; ++i) { - nru_dl_message f1u_pdu{.t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, i, pdcp_pdu_size, i)}; + nru_dl_message f1u_pdu{ + .t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, i, pdcp_pdu_size, i)}; cu_up_sim.created_du_notifs[0]->on_new_pdu(f1u_pdu); } @@ -231,7 +233,8 @@ TEST_F(du_high_tester, when_ue_context_modification_with_rem_drbs_is_received_th const unsigned nof_pdcp_pdus = 100, pdcp_pdu_size = 128; for (unsigned i = 0; i < nof_pdcp_pdus; ++i) { - nru_dl_message f1u_pdu{.t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, i, pdcp_pdu_size, i)}; + nru_dl_message f1u_pdu{ + .t_pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, i, pdcp_pdu_size, i)}; cu_up_sim.created_du_notifs[0]->on_new_pdu(f1u_pdu); } diff --git a/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test.cpp b/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test.cpp index 1b57b215fe..b2765b9a5d 100644 --- a/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test.cpp +++ b/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test.cpp @@ -48,14 +48,13 @@ using namespace srsran; static constexpr subcarrier_spacing scs = subcarrier_spacing::kHz30; -static constexpr pusch_mcs_table mcs_table = pusch_mcs_table::qam256; static constexpr uint16_t rnti = 0x1234; static constexpr unsigned bwp_size_rb = 273; static constexpr unsigned bwp_start_rb = 0; static constexpr unsigned nof_layers = 1; static constexpr unsigned nof_ofdm_symbols = 14; static const symbol_slot_mask dmrs_symbol_mask = {0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}; -static constexpr unsigned nof_ldpc_iterations = 6; +static constexpr unsigned nof_ldpc_iterations = 10; static constexpr dmrs_type dmrs = dmrs_type::TYPE1; static constexpr unsigned nof_cdm_groups_without_data = 2; static constexpr cyclic_prefix cp = cyclic_prefix::NORMAL; @@ -78,6 +77,8 @@ struct pxsch_bler_params { float sinr_dB; // Number of receive ports. unsigned nof_rx_ports; + // Modulation and code scheme table + pusch_mcs_table mcs_table; // Modulation and code scheme index. sch_mcs_index mcs_index; // Frequency allocation. @@ -173,6 +174,7 @@ class PxschBlerTestFixture : public ::testing::TestWithParam // Extract test parameters. const std::string& channel = GetParam().channel_delay_profile; + pusch_mcs_table mcs_table = GetParam().mcs_table; sch_mcs_index mcs_index = GetParam().mcs_index; prb_interval rb_mapping = GetParam().freq_allocation; @@ -445,15 +447,16 @@ TEST_P(PxschBlerTestFixture, Fading) } static const std::vector test_cases = { - {"TDLA", 60.0, 1, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLA", 60.0, 2, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLA", 60.0, 4, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLB", 60.0, 1, 10, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLB", 60.0, 2, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLB", 60.0, 4, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLC", 60.0, 1, 8, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLC", 60.0, 2, 12, prb_interval{bwp_start_rb, bwp_size_rb}}, - {"TDLC", 60.0, 4, 19, prb_interval{bwp_start_rb, bwp_size_rb}}}; + {"Single-tap", 20.0, 2, pusch_mcs_table::qam64, 28, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLA", 60.0, 1, pusch_mcs_table::qam256, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLA", 60.0, 2, pusch_mcs_table::qam256, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLA", 60.0, 4, pusch_mcs_table::qam256, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLB", 60.0, 1, pusch_mcs_table::qam256, 10, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLB", 60.0, 2, pusch_mcs_table::qam256, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLB", 60.0, 4, pusch_mcs_table::qam256, 27, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLC", 60.0, 1, pusch_mcs_table::qam256, 8, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLC", 60.0, 2, pusch_mcs_table::qam256, 12, prb_interval{bwp_start_rb, bwp_size_rb}}, + {"TDLC", 60.0, 4, pusch_mcs_table::qam256, 19, prb_interval{bwp_start_rb, bwp_size_rb}}}; INSTANTIATE_TEST_SUITE_P(PxschBlertest, PxschBlerTestFixture, ::testing::ValuesIn(test_cases)); diff --git a/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test_channel_emulator.cpp b/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test_channel_emulator.cpp index 7d0f3d10b9..90a9ca5460 100644 --- a/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test_channel_emulator.cpp +++ b/tests/integrationtests/phy/upper/channel_processors/pxsch_bler_test_channel_emulator.cpp @@ -35,6 +35,9 @@ using namespace srsran; unsigned channel_emulator::concurrent_channel_emulator::seed = 0; +/// Single-tap profile. +constexpr static std::array, 12> taps_single = {{{200, 0.0}}}; + /// TDLA fading profile. constexpr static std::array, 12> taps_tdla = {{{0, -15.5}, {10, 0.0}, @@ -94,7 +97,9 @@ channel_emulator::channel_emulator(std::string channel, { // Select fading channel taps. span> taps; - if (channel == "TDLA") { + if (channel == "Single-tap") { + taps = taps_single; + } else if (channel == "TDLA") { taps = taps_tdla; } else if (channel == "TDLB") { taps = taps_tdlb; diff --git a/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp b/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp index eeb6b16d26..e9601e4f1a 100644 --- a/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp +++ b/tests/test_doubles/f1ap/f1ap_test_message_validators.cpp @@ -67,9 +67,13 @@ bool srsran::test_helpers::is_valid_dl_rrc_message_transfer(const f1ap_message& return true; } -const byte_buffer& srsran::test_helpers::get_rrc_container(const f1ap_message& dl_rrc_msg_transfer) +const byte_buffer& srsran::test_helpers::get_rrc_container(const f1ap_message& msg) { - return dl_rrc_msg_transfer.pdu.init_msg().value.dl_rrc_msg_transfer()->rrc_container; + if (msg.pdu.init_msg().proc_code == ASN1_F1AP_ID_UE_CONTEXT_SETUP) { + return msg.pdu.init_msg().value.ue_context_setup_request()->rrc_container; + } + + return msg.pdu.init_msg().value.dl_rrc_msg_transfer()->rrc_container; } bool srsran::test_helpers::is_valid_dl_rrc_message_transfer_with_msg4(const f1ap_message& msg) @@ -111,6 +115,15 @@ bool srsran::test_helpers::is_ul_rrc_msg_transfer_valid(const f1ap_message& msg, return true; } +bool srsran::test_helpers::is_valid_ue_context_setup_request(const f1ap_message& msg) +{ + TRUE_OR_RETURN(msg.pdu.type() == asn1::f1ap::f1ap_pdu_c::types_opts::init_msg); + TRUE_OR_RETURN(msg.pdu.init_msg().proc_code == ASN1_F1AP_ID_UE_CONTEXT_SETUP); + TRUE_OR_RETURN(is_packable(msg)); + + return true; +} + bool srsran::test_helpers::is_ue_context_setup_response_valid(const f1ap_message& msg) { if (not(msg.pdu.type() == asn1::f1ap::f1ap_pdu_c::types_opts::successful_outcome and diff --git a/tests/test_doubles/f1ap/f1ap_test_message_validators.h b/tests/test_doubles/f1ap/f1ap_test_message_validators.h index 26e78ac3af..1cc612a382 100644 --- a/tests/test_doubles/f1ap/f1ap_test_message_validators.h +++ b/tests/test_doubles/f1ap/f1ap_test_message_validators.h @@ -42,12 +42,14 @@ bool is_init_ul_rrc_msg_transfer_valid(const f1ap_message& bool is_valid_dl_rrc_message_transfer(const f1ap_message& msg); -const byte_buffer& get_rrc_container(const f1ap_message& dl_rrc_msg_transfer); +const byte_buffer& get_rrc_container(const f1ap_message& msg); bool is_valid_dl_rrc_message_transfer_with_msg4(const f1ap_message& msg); bool is_ul_rrc_msg_transfer_valid(const f1ap_message& msg, srb_id_t srb_id); +bool is_valid_ue_context_setup_request(const f1ap_message& msg); + bool is_ue_context_setup_response_valid(const f1ap_message& msg); bool is_valid_ue_context_modification_request(const f1ap_message& msg); diff --git a/tests/test_doubles/pdcp/pdcp_pdu_generator.cpp b/tests/test_doubles/pdcp/pdcp_pdu_generator.cpp index 944d26d834..ffa0ae0e10 100644 --- a/tests/test_doubles/pdcp/pdcp_pdu_generator.cpp +++ b/tests/test_doubles/pdcp/pdcp_pdu_generator.cpp @@ -31,9 +31,16 @@ using namespace srsran; -byte_buffer -srsran::test_helpers::create_pdcp_pdu(pdcp_sn_size pdcp_sn_len, uint32_t pdcp_sn, uint32_t sdu_size, uint8_t first_byte) +byte_buffer srsran::test_helpers::create_pdcp_pdu(pdcp_sn_size pdcp_sn_len, + bool is_srb, + uint32_t pdcp_sn, + uint32_t sdu_size, + uint8_t first_byte) { + if (is_srb && pdcp_sn_len != pdcp_sn_size::size12bits) { + report_error("Cannot create SRB PDU: Invalid pdcp_sn_len={}", pdcp_sn_len); + } + uint32_t pdcp_hdr_len = 0; switch (pdcp_sn_len) { case pdcp_sn_size::size12bits: @@ -56,8 +63,8 @@ srsran::test_helpers::create_pdcp_pdu(pdcp_sn_size pdcp_sn_len, uint32_t pdcp_sn bit_encoder encoder{sdu_buf}; bool write_ok; - // D/C field - write_ok = encoder.pack(1, 1); + // D/C field (or R for SRBs) + write_ok = encoder.pack(is_srb ? 0 : 1, 1); switch (pdcp_sn_len) { case pdcp_sn_size::size12bits: diff --git a/tests/test_doubles/pdcp/pdcp_pdu_generator.h b/tests/test_doubles/pdcp/pdcp_pdu_generator.h index 67bbdc2f4b..56ecdd54d9 100644 --- a/tests/test_doubles/pdcp/pdcp_pdu_generator.h +++ b/tests/test_doubles/pdcp/pdcp_pdu_generator.h @@ -40,11 +40,13 @@ namespace test_helpers { /// The minimum sdu_size is 3 for 12-bit PDCP SNs and 4 for 18-bit PDCP SNs (i.e. PDCP-HDR + 1 or more bytes). /// /// \param pdcp_sn_len Size of the PDCP sequence number +/// \param is_srb Determines the bearer type: SRB (true) or DRB (false). /// \param pdcp_sn PDCP sequence number /// \param sdu_size Size of the SDU (including PDCP header) /// \param first_byte Value of the first payload byte after PDCP header /// \return the produced SDU as a byte_buffer -byte_buffer create_pdcp_pdu(pdcp_sn_size pdcp_sn_len, uint32_t pdcp_sn, uint32_t sdu_size, uint8_t first_byte = 0); +byte_buffer +create_pdcp_pdu(pdcp_sn_size pdcp_sn_len, bool is_srb, uint32_t pdcp_sn, uint32_t sdu_size, uint8_t first_byte); } // namespace test_helpers } // namespace srsran diff --git a/tests/unittests/cu_cp/cu_cp_test.cpp b/tests/unittests/cu_cp/cu_cp_test.cpp index e3b808dae4..207af159f6 100644 --- a/tests/unittests/cu_cp/cu_cp_test.cpp +++ b/tests/unittests/cu_cp/cu_cp_test.cpp @@ -275,8 +275,7 @@ TEST_F(cu_cp_test, when_unsupported_inactivity_message_received_then_ue_context_ /* AMF initiated PDU Session Release */ ////////////////////////////////////////////////////////////////////////////////////// -// Bearer Context Release is not sent even if last PDU session is removed. Only NGAP UE context release triggers Bearer -// Context Release. +// When multiple PDU sessions exist and not all of them are released, a BearerContextModification is sent to the CU-UP TEST_F(cu_cp_test, when_pdu_session_resource_release_command_received_then_modification_request_is_sent) { // Test preamble @@ -287,15 +286,16 @@ TEST_F(cu_cp_test, when_pdu_session_resource_release_command_received_then_modif pci_t pci = 1; amf_ue_id_t amf_ue_id = uint_to_amf_ue_id( test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))); - ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); - gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); - gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); + ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); + std::vector psis = {uint_to_pdu_session_id(1), uint_to_pdu_session_id(2)}; + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); // Connect AMF, DU, CU-UP test_preamble_all_connected(du_index, pci); // Attach UE test_preamble_ue_full_attach( - du_index, du_ue_id, cu_ue_id, crnti, amf_ue_id, ran_ue_id, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + du_index, du_ue_id, cu_ue_id, crnti, amf_ue_id, ran_ue_id, psis, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); // Inject PduSessionResourceReleaseCommand cu_cp_obj->get_ngap_message_handler().handle_message( @@ -307,6 +307,41 @@ TEST_F(cu_cp_test, when_pdu_session_resource_release_command_received_then_modif asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_mod_request); } +// When all active PDU sessions are released a BearerContextReleaseCommand is sent to the CU-UP +TEST_F(cu_cp_test, when_all_pdu_sessions_get_released_then_bearer_context_release_command_is_sent) +{ + // Test preamble + du_index_t du_index = uint_to_du_index(0); + gnb_cu_ue_f1ap_id_t cu_ue_id = int_to_gnb_cu_ue_f1ap_id(0); + gnb_du_ue_f1ap_id_t du_ue_id = int_to_gnb_du_ue_f1ap_id(0); + rnti_t crnti = to_rnti(0x4601); + pci_t pci = 1; + amf_ue_id_t amf_ue_id = uint_to_amf_ue_id( + test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))); + ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); + std::vector psis = {uint_to_pdu_session_id(1)}; + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); + + // Connect AMF, DU, CU-UP + test_preamble_all_connected(du_index, pci); + // Attach UE + test_preamble_ue_full_attach( + du_index, du_ue_id, cu_ue_id, crnti, amf_ue_id, ran_ue_id, psis, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + + // Inject PduSessionResourceReleaseCommand + cu_cp_obj->get_ngap_message_handler().handle_message( + generate_valid_pdu_session_resource_release_command(amf_ue_id, ran_ue_id, uint_to_pdu_session_id(1))); + + // Check that the Bearer Context Release was sent to the CU-UP + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.type(), asn1::e1ap::e1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_release_cmd); + + // Check that no UE Context Release Request was sent to the AMF + ASSERT_NE(n2_gw.last_ngap_msgs.back().pdu.type(), asn1::ngap::ngap_pdu_c::types_opts::options::init_msg); +} + ////////////////////////////////////////////////////////////////////////////////////// /* AMF initiated UE Context Release */ ////////////////////////////////////////////////////////////////////////////////////// @@ -497,7 +532,7 @@ TEST_F(cu_cp_test, when_handover_request_received_then_handover_notify_is_sent) // Inject F1AP UE Context Setup Response f1ap_message ue_ctxt_setup_resp = - generate_ue_context_setup_response(int_to_gnb_cu_ue_f1ap_id(0), int_to_gnb_du_ue_f1ap_id(0)); + generate_ue_context_setup_response(int_to_gnb_cu_ue_f1ap_id(0), int_to_gnb_du_ue_f1ap_id(0), to_rnti(0x4601)); f1c_gw.get_du(du_index).on_new_message(ue_ctxt_setup_resp); // Check that the Bearer Context Modification Request Message was sent to the CU-UP diff --git a/tests/unittests/cu_cp/cu_cp_test_environment.cpp b/tests/unittests/cu_cp/cu_cp_test_environment.cpp index d5be97db7a..f916bedad2 100644 --- a/tests/unittests/cu_cp/cu_cp_test_environment.cpp +++ b/tests/unittests/cu_cp/cu_cp_test_environment.cpp @@ -392,32 +392,42 @@ bool cu_cp_test_environment::setup_ue_security(unsigned du_idx, gnb_du_ue_f1ap_i generate_valid_initial_context_setup_request_message(ue_ctx.amf_ue_id.value(), ue_ctx.ran_ue_id.value()); get_amf().push_tx_pdu(init_ctxt_setup_req); - // Wait for F1AP DL RRC Message Transfer (containing Security Mode Command). + // Wait for F1AP UE Context Setup Request (containing Security Mode Command). bool result = this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu); - report_fatal_error_if_not(result, "Failed to received Security Mode Command"); - report_fatal_error_if_not(test_helpers::is_valid_dl_rrc_message_transfer(f1ap_pdu), - "Invalid DL RRC Message Transfer"); + report_fatal_error_if_not(result, "Failed to receive Security Mode Command"); + report_fatal_error_if_not(test_helpers::is_valid_ue_context_setup_request(f1ap_pdu), + "Invalid UE Context Setup Request"); const byte_buffer& rrc_container = test_helpers::get_rrc_container(f1ap_pdu); report_fatal_error_if_not( test_helpers::is_valid_rrc_security_mode_command(test_helpers::extract_dl_dcch_msg(rrc_container)), "Invalid Security Mode command"); + // Inject UE Context Setup Response + f1ap_message ue_ctxt_setup_response = generate_ue_context_setup_response(ue_ctx.cu_ue_id.value(), du_ue_id); + get_du(du_idx).push_ul_pdu(ue_ctxt_setup_response); + // Inject RRC Security Mode Complete f1ap_message ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( ue_ctx.cu_ue_id.value(), du_ue_id, srb_id_t::srb1, make_byte_buffer("00032a00fd5ec7ff").value()); get_du(du_idx).push_ul_pdu(ul_rrc_msg_transfer); + // Wait for DL RRC Message Transfer (containing RRC Reconfiguration, containing NAS Registration Accept) + result = this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu); + report_fatal_error_if_not( + result, "Failed to receive DL RRC Message, containing RRC Reconfiguration, containing NAS Registration Accept"); + report_fatal_error_if_not(test_helpers::is_valid_dl_rrc_message_transfer(f1ap_pdu), + "Invalid DL RRC Message Transfer"); + + // Inject UL RRC Message Transfer (containing RRC Reconfiguration Complete) + ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( + ue_ctx.cu_ue_id.value(), du_ue_id, srb_id_t::srb1, make_byte_buffer("00040c00fbca0d80").value()); + get_du(du_idx).push_ul_pdu(ul_rrc_msg_transfer); + // Wait for Initial Context Setup Response. result = this->wait_for_ngap_tx_pdu(ngap_pdu); report_fatal_error_if_not(result, "Failed to receive Initial Context Setup Response"); report_fatal_error_if_not(test_helpers::is_valid_initial_context_setup_response(ngap_pdu), "Invalid init ctxt setup"); - // Wait for DL RRC Message Transfer (containing NAS Registration Accept) - result = this->wait_for_f1ap_tx_pdu(du_idx, f1ap_pdu); - report_fatal_error_if_not(result, "Failed to receive DL RRC Message, containing NAS Registration Accept"); - report_fatal_error_if_not(test_helpers::is_valid_dl_rrc_message_transfer(f1ap_pdu), - "Invalid DL RRC Message Transfer"); - return true; } @@ -446,7 +456,7 @@ bool cu_cp_test_environment::attach_ue(unsigned du_idx, // Inject Registration Complete and wait UL NAS message. get_du(du_idx).push_ul_pdu(test_helpers::create_ul_rrc_message_transfer( - du_ue_id, *ue_ctx.cu_ue_id, srb_id_t::srb1, make_byte_buffer("00043a053f015362c51680bf00218003fe6db7").value())); + du_ue_id, *ue_ctx.cu_ue_id, srb_id_t::srb1, make_byte_buffer("00053a053f015362c51680bf00218086b09a5b").value())); bool result = this->wait_for_ngap_tx_pdu(ngap_pdu); report_fatal_error_if_not(result, "Failed to receive Registration Complete"); @@ -455,8 +465,8 @@ bool cu_cp_test_environment::attach_ue(unsigned du_idx, du_ue_id, *ue_ctx.cu_ue_id, srb_id_t::srb1, - make_byte_buffer("00053a253f011ffa9203013f0033808018970080e0ffffc9d8bd8013404010880080000840830000000041830000000" - "00000800001800005000006000006800008800900c092838339b939b0b837002c98dcab") + make_byte_buffer("00063a253f011ffa9203013f0033808018970080e0ffffc9d8bd8013404010880080000840830000000041830000000" + "00000800001800005000006000006800008800900c092838339b939b0b83700e03a21bb") .value())); result = this->wait_for_ngap_tx_pdu(ngap_pdu); report_fatal_error_if_not(result, "Failed to receive Registration Complete"); @@ -486,9 +496,9 @@ bool cu_cp_test_environment::attach_ue(unsigned du_idx, du_ue_id, *ue_ctx.cu_ue_id, srb_id_t::srb1, - make_byte_buffer("00064c821930680ce811d1968097e340e1480005824c5c00060fc2c00637fe002e00131401a0000000880058d006007" - "a071e439f0000240400e0300000000100186c0000700809df0000000000000103a0002000012cb2800281c50f000700" - "0f00000004008010240a00126cc3c6") + make_byte_buffer("00074e821930680ce811d1968097e360e1480005824c5c00060fc2c00637fe002e00131401a0000000880058d006007" + "a071e439f0000240400e0300000000100186c0000700809df000000000000030368000800004b2ca000a07143c001c0" + "03c00000010020040902807b0dba95") .value())); result = this->wait_for_e1ap_tx_pdu(0, e1ap_pdu); report_fatal_error_if_not(result, "Failed to receive E1AP Bearer Context Setup"); @@ -517,7 +527,7 @@ bool cu_cp_test_environment::attach_ue(unsigned du_idx, // Inject RRC Reconfiguration Complete and wait for PDU Session Resource Setup Response to be sent to AMF. get_du(du_idx).push_ul_pdu(test_helpers::create_ul_rrc_message_transfer( - du_ue_id, *ue_ctx.cu_ue_id, srb_id_t::srb1, make_byte_buffer("00070e00cc6fcda5").value())); + du_ue_id, *ue_ctx.cu_ue_id, srb_id_t::srb1, make_byte_buffer("00080800e6847bbd").value())); result = this->wait_for_ngap_tx_pdu(ngap_pdu); report_fatal_error_if_not(result, "Failed to receive PDU Session Resource Setup Response"); diff --git a/tests/unittests/cu_cp/cu_cp_test_helpers.cpp b/tests/unittests/cu_cp/cu_cp_test_helpers.cpp index f9bceb9ce6..ae428bb7b2 100644 --- a/tests/unittests/cu_cp/cu_cp_test_helpers.cpp +++ b/tests/unittests/cu_cp/cu_cp_test_helpers.cpp @@ -271,10 +271,19 @@ void cu_cp_test::setup_security(amf_ue_id_t amf_ue_id, ngap_message init_ctxt_setup_req = generate_valid_initial_context_setup_request_message(amf_ue_id, ran_ue_id); cu_cp_obj->get_ngap_message_handler().handle_message(init_ctxt_setup_req); + // Inject UE Context Setup Response + f1ap_message ue_ctxt_setup_response = generate_ue_context_setup_response(cu_ue_id, du_ue_id); + f1c_gw.get_du(du_index).on_new_message(ue_ctxt_setup_response); + // Inject Security Mode Complete f1ap_message ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( cu_ue_id, du_ue_id, srb_id_t::srb1, make_byte_buffer("00032a00fd5ec7ff").value()); f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); + + // Inject RRC Reconfiguration Complete + ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( + cu_ue_id, du_ue_id, srb_id_t::srb1, make_byte_buffer("00040c00fbca0d80").value()); + f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); } void cu_cp_test::test_amf_connection() @@ -313,6 +322,108 @@ void cu_cp_test::test_du_attach(du_index_t du_index, gnb_du_id_t gnb_du_id, nr_c f1c_gw.get_du(du_index).on_new_message(f1setup_msg); } +void cu_cp_test::add_pdu_sessions(std::vector psis, + du_index_t du_index, + gnb_du_ue_f1ap_id_t du_ue_id, + gnb_cu_ue_f1ap_id_t cu_ue_id, + amf_ue_id_t amf_ue_id, + ran_ue_id_t ran_ue_id, + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id, + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id) +{ + bool initial_pdu_session = true; + + for (const auto psi : psis) { + // Inject PDU Session Resource Setup Request + ngap_message pdu_session_resource_setup_request = + generate_valid_pdu_session_resource_setup_request_message(amf_ue_id, ran_ue_id, psi); + cu_cp_obj->get_ngap_message_handler().handle_message(pdu_session_resource_setup_request); + + // check that the UE capability enquiry was sent to the DU + ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::dl_rrc_msg_transfer); + + // Inject UE capability info + f1ap_message ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( + cu_ue_id, + du_ue_id, + srb_id_t::srb1, + make_byte_buffer( + "00074e821930680ce811d1968097e360e1480005824c5c00060fc2c00637fe002e00131401a0000000880058d006007" + "a071e439f0000240400e0300000000100186c0000700809df000000000000030368000800004b2ca000a07143c001c0" + "03c00000010020040902807b0dba95") + .value()); + f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); + + if (initial_pdu_session) { + initial_pdu_session = false; + + // check that the Bearer Context Setup was sent to the CU-UP + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.type(), asn1::e1ap::e1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_setup_request); + + // Inject Bearer Context Setup Response + e1ap_message bearer_context_setup_resp = + generate_bearer_context_setup_response(cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + cu_cp_obj->get_e1_handler() + .get_cu_up(uint_to_cu_up_index(0)) + .get_message_handler() + .handle_message(bearer_context_setup_resp); + } else { + // check that the Bearer Context Modification was sent to the CU-UP + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.type(), asn1::e1ap::e1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_mod_request); + // Inject Bearer Context Modification Response + e1ap_message bearer_context_mod_resp = + generate_bearer_context_modification_response(cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + cu_cp_obj->get_e1_handler() + .get_cu_up(uint_to_cu_up_index(0)) + .get_message_handler() + .handle_message(bearer_context_mod_resp); + } + + // check that the UE Context Modification Request was sent to the DU + ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::ue_context_mod_request); + + // Inject UE Context Modification Response + f1ap_message ue_context_mod_resp = generate_ue_context_modification_response(cu_ue_id, du_ue_id); + f1c_gw.get_du(du_index).on_new_message(ue_context_mod_resp); + + // check that the Bearer Context Modification was sent to the CU-UP + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.type(), asn1::e1ap::e1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_mod_request); + + // Inject Bearer Context Modification Response + e1ap_message bearer_context_mod_resp = + generate_bearer_context_modification_response(cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + cu_cp_obj->get_e1_handler() + .get_cu_up(uint_to_cu_up_index(0)) + .get_message_handler() + .handle_message(bearer_context_mod_resp); + + // check that the RRC Reconfiguration was sent to the DU + ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); + ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, + asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::dl_rrc_msg_transfer); + + // Inject RRC Reconfiguration Complete + ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( + cu_ue_id, du_ue_id, srb_id_t::srb1, make_byte_buffer("00080800e6847bbd").value()); + f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); + + // check that the PDU Session Resource Setup Response was sent to the AMF + ASSERT_EQ(n2_gw.last_ngap_msgs.back().pdu.type(), asn1::ngap::ngap_pdu_c::types_opts::options::successful_outcome); + ASSERT_EQ(n2_gw.last_ngap_msgs.back().pdu.successful_outcome().value.type().value, + asn1::ngap::ngap_elem_procs_o::successful_outcome_c::types_opts::pdu_session_res_setup_resp); + } +} + void cu_cp_test::test_preamble_all_connected(du_index_t du_index, pci_t pci) { test_amf_connection(); @@ -338,21 +449,22 @@ void cu_cp_test::test_preamble_ue_creation(du_index_t du_index, setup_security(amf_ue_id, ran_ue_id, du_index, du_ue_id, cu_ue_id); } -void cu_cp_test::test_preamble_ue_full_attach(du_index_t du_index, - gnb_du_ue_f1ap_id_t du_ue_id, - gnb_cu_ue_f1ap_id_t cu_ue_id, - rnti_t crnti, - amf_ue_id_t amf_ue_id, - ran_ue_id_t ran_ue_id, - gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id, - gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id) +void cu_cp_test::test_preamble_ue_full_attach(du_index_t du_index, + gnb_du_ue_f1ap_id_t du_ue_id, + gnb_cu_ue_f1ap_id_t cu_ue_id, + rnti_t crnti, + amf_ue_id_t amf_ue_id, + ran_ue_id_t ran_ue_id, + std::vector psis_to_setup, + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id, + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id) { // Create UE test_preamble_ue_creation(du_index, du_ue_id, cu_ue_id, crnti, amf_ue_id, ran_ue_id); // Inject Registration Complete f1ap_message ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( - cu_ue_id, du_ue_id, srb_id_t::srb1, make_byte_buffer("00043a053f015362c51680bf00218003fe6db7").value()); + cu_ue_id, du_ue_id, srb_id_t::srb1, make_byte_buffer("00053a053f015362c51680bf00218086b09a5b").value()); f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); // Inject PDU Session Establishment Request @@ -360,8 +472,8 @@ void cu_cp_test::test_preamble_ue_full_attach(du_index_t du_index, cu_ue_id, du_ue_id, srb_id_t::srb1, - make_byte_buffer("00053a253f011ffa9203013f0033808018970080e0ffffc9d8bd8013404010880080000840830000000041830000000" - "00000800001800005000006000006800008800900c092838339b939b0b837002c98dcab") + make_byte_buffer("00063a253f011ffa9203013f0033808018970080e0ffffc9d8bd8013404010880080000840830000000041830000000" + "00000800001800005000006000006800008800900c092838339b939b0b83700e03a21bb") .value()); f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); @@ -372,75 +484,8 @@ void cu_cp_test::test_preamble_ue_full_attach(du_index_t du_index, make_byte_buffer("7e0205545bfc027e0054430f90004f00700065006e00350047005346004732800131235200490100").value()); cu_cp_obj->get_ngap_message_handler().handle_message(dl_nas_transport_msg); - // Inject PDU Session Resource Setup Request - ngap_message pdu_session_resource_setup_request = - generate_valid_pdu_session_resource_setup_request_message(amf_ue_id, ran_ue_id, uint_to_pdu_session_id(1)); - cu_cp_obj->get_ngap_message_handler().handle_message(pdu_session_resource_setup_request); - - // check that the UE capability enquiry was sent to the DU - ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); - ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, - asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::dl_rrc_msg_transfer); - - // Inject UE capability info - ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( - cu_ue_id, - du_ue_id, - srb_id_t::srb1, - make_byte_buffer("00064c821930680ce811d1968097e340e1480005824c5c00060fc2c00637fe002e00131401a0000000880058d006007" - "a071e439f0000240400e0300000000100186c0000700809df0000000000000103a0002000012cb2800281c50f000700" - "0f00000004008010240a00126cc3c6") - .value()); - f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); - - // check that the Bearer Context Setup was sent to the CU-UP - ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.type(), asn1::e1ap::e1ap_pdu_c::types_opts::options::init_msg); - ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, - asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_setup_request); - - // Inject Bearer Context Setup Response - e1ap_message bearer_context_setup_resp = generate_bearer_context_setup_response(cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); - cu_cp_obj->get_e1_handler() - .get_cu_up(uint_to_cu_up_index(0)) - .get_message_handler() - .handle_message(bearer_context_setup_resp); - - // check that the UE Context Modification Request was sent to the DU - ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); - ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, - asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::ue_context_mod_request); - - // Inject UE Context Modification Response - f1ap_message ue_context_mod_resp = generate_ue_context_modification_response(cu_ue_id, du_ue_id); - f1c_gw.get_du(du_index).on_new_message(ue_context_mod_resp); - - // check that the Bearer Context Modification was sent to the CU-UP - ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.type(), asn1::e1ap::e1ap_pdu_c::types_opts::options::init_msg); - ASSERT_EQ(e1ap_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, - asn1::e1ap::e1ap_elem_procs_o::init_msg_c::types_opts::bearer_context_mod_request); - - // Inject Bearer Context Modification Response - e1ap_message bearer_context_mod_resp = - generate_bearer_context_modification_response(cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); - cu_cp_obj->get_e1_handler() - .get_cu_up(uint_to_cu_up_index(0)) - .get_message_handler() - .handle_message(bearer_context_mod_resp); - - // check that the RRC Reconfiguration was sent to the DU - ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.type(), asn1::f1ap::f1ap_pdu_c::types_opts::options::init_msg); - ASSERT_EQ(f1c_gw.last_tx_pdus(0).back().pdu.init_msg().value.type().value, - asn1::f1ap::f1ap_elem_procs_o::init_msg_c::types_opts::dl_rrc_msg_transfer); - - // Inject RRC Reconfiguration Complete - ul_rrc_msg_transfer = generate_ul_rrc_message_transfer( - cu_ue_id, du_ue_id, srb_id_t::srb1, make_byte_buffer("00070e00cc6fcda5").value()); - f1c_gw.get_du(du_index).on_new_message(ul_rrc_msg_transfer); - - // check that the PDU Session Resource Setup Response was sent to the AMF - ASSERT_EQ(n2_gw.last_ngap_msgs.back().pdu.type(), asn1::ngap::ngap_pdu_c::types_opts::options::successful_outcome); - ASSERT_EQ(n2_gw.last_ngap_msgs.back().pdu.successful_outcome().value.type().value, - asn1::ngap::ngap_elem_procs_o::successful_outcome_c::types_opts::pdu_session_res_setup_resp); + add_pdu_sessions( + std::move(psis_to_setup), du_index, du_ue_id, cu_ue_id, amf_ue_id, ran_ue_id, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); } bool cu_cp_test::check_minimal_paging_result() diff --git a/tests/unittests/cu_cp/cu_cp_test_helpers.h b/tests/unittests/cu_cp/cu_cp_test_helpers.h index 761507474f..3539264a0e 100644 --- a/tests/unittests/cu_cp/cu_cp_test_helpers.h +++ b/tests/unittests/cu_cp/cu_cp_test_helpers.h @@ -48,6 +48,15 @@ class cu_cp_test : public ::testing::Test void test_e1ap_attach(); void test_du_attach(du_index_t du_index, gnb_du_id_t gnb_du_id, nr_cell_identity nrcell_id, pci_t pci); + void add_pdu_sessions(std::vector psis, + du_index_t du_index, + gnb_du_ue_f1ap_id_t du_ue_id, + gnb_cu_ue_f1ap_id_t cu_ue_id, + amf_ue_id_t amf_ue_id, + ran_ue_id_t ran_ue_id, + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id, + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id); + void attach_ue(gnb_du_ue_f1ap_id_t du_ue_id, gnb_cu_ue_f1ap_id_t cu_ue_id, rnti_t crnti, du_index_t du_index); void authenticate_ue(amf_ue_id_t amf_ue_id, ran_ue_id_t ran_ue_id, @@ -66,14 +75,15 @@ class cu_cp_test : public ::testing::Test rnti_t crnti, amf_ue_id_t amf_ue_id, ran_ue_id_t ran_ue_id); - void test_preamble_ue_full_attach(du_index_t du_index, - gnb_du_ue_f1ap_id_t du_ue_id, - gnb_cu_ue_f1ap_id_t cu_ue_id, - rnti_t crnti, - amf_ue_id_t amf_ue_id, - ran_ue_id_t ran_ue_id, - gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id, - gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id); + void test_preamble_ue_full_attach(du_index_t du_index, + gnb_du_ue_f1ap_id_t du_ue_id, + gnb_cu_ue_f1ap_id_t cu_ue_id, + rnti_t crnti, + amf_ue_id_t amf_ue_id, + ran_ue_id_t ran_ue_id, + std::vector psis_to_setup, + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id, + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id); bool check_minimal_paging_result(); bool check_paging_result(); diff --git a/tests/unittests/cu_cp/mobility/inter_cu_handover_routine_test.cpp b/tests/unittests/cu_cp/mobility/inter_cu_handover_routine_test.cpp index 2eeb89421c..92656c30f9 100644 --- a/tests/unittests/cu_cp/mobility/inter_cu_handover_routine_test.cpp +++ b/tests/unittests/cu_cp/mobility/inter_cu_handover_routine_test.cpp @@ -43,16 +43,17 @@ class inter_cu_handover_routine_test : public mobility_test pci_t pci = 1; amf_ue_id_t amf_ue_id = uint_to_amf_ue_id( test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))); - ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); - gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); - gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); + ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); + std::vector psis = {uint_to_pdu_session_id(1)}; + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); // Connect AMF, DU, CU-UP. test_preamble_all_connected(du_index, pci); // Attach UE. test_preamble_ue_full_attach( - du_index, du_ue_id, cu_ue_id, crnti, amf_ue_id, ran_ue_id, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + du_index, du_ue_id, cu_ue_id, crnti, amf_ue_id, ran_ue_id, psis, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); // Assert single UE attached to source DU. ASSERT_EQ(get_nof_ues_in_source_du(), 1); @@ -66,7 +67,7 @@ class inter_cu_handover_routine_test : public mobility_test generate_ul_rrc_message_transfer(int_to_gnb_cu_ue_f1ap_id(0), int_to_gnb_du_ue_f1ap_id(0), srb_id_t::srb1, - make_byte_buffer("000800400004015d3c18c0806bae872c411e548b").value()); + make_byte_buffer("000900410004015f741fe0804bf183fc980605b7").value()); test_logger.info("Injecting UL RRC message (RRC Measurement Report)"); f1c_gw.get_du(source_du_index).on_new_message(ul_rrc_msg); } diff --git a/tests/unittests/cu_cp/mobility/inter_du_handover_routine_test.cpp b/tests/unittests/cu_cp/mobility/inter_du_handover_routine_test.cpp index 8add282064..e2ed4a7d25 100644 --- a/tests/unittests/cu_cp/mobility/inter_du_handover_routine_test.cpp +++ b/tests/unittests/cu_cp/mobility/inter_du_handover_routine_test.cpp @@ -44,9 +44,10 @@ class inter_du_handover_routine_test : public mobility_test source_pci = 1; amf_ue_id_t amf_ue_id = uint_to_amf_ue_id( test_rgen::uniform_int(amf_ue_id_to_uint(amf_ue_id_t::min), amf_ue_id_to_uint(amf_ue_id_t::max))); - ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); - gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); - gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); + ran_ue_id_t ran_ue_id = uint_to_ran_ue_id(0); + std::vector psis = {uint_to_pdu_session_id(1)}; + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = int_to_gnb_cu_cp_ue_e1ap_id(0); + gnb_cu_up_ue_e1ap_id_t cu_up_ue_e1ap_id = int_to_gnb_cu_up_ue_e1ap_id(0); // Connect AMF, DU, CU-UP. test_preamble_all_connected(du_index, source_pci); @@ -54,7 +55,7 @@ class inter_du_handover_routine_test : public mobility_test test_du_attach(target_du_index, target_du_id, target_nrcell_id, target_pci); // Attach UE. test_preamble_ue_full_attach( - du_index, du_ue_id, cu_ue_id, source_rnti, amf_ue_id, ran_ue_id, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); + du_index, du_ue_id, cu_ue_id, source_rnti, amf_ue_id, ran_ue_id, psis, cu_cp_ue_e1ap_id, cu_up_ue_e1ap_id); // Assert single UE attached to source DU. ASSERT_EQ(get_nof_ues_in_source_du(), 1); @@ -69,7 +70,7 @@ class inter_du_handover_routine_test : public mobility_test generate_ul_rrc_message_transfer(int_to_gnb_cu_ue_f1ap_id(0), int_to_gnb_du_ue_f1ap_id(0), srb_id_t::srb1, - make_byte_buffer("000800410004015f741fe0804bf183fcaa6e9699").value()); + make_byte_buffer("000900410004015f741fe0804bf183fc980605b7").value()); test_logger.info("Injecting UL RRC message (RRC Measurement Report)"); f1c_gw.get_du(source_du_index).on_new_message(ul_rrc_msg); } @@ -156,7 +157,7 @@ class inter_du_handover_routine_test : public mobility_test f1ap_message rrc_recfg_complete = generate_ul_rrc_message_transfer(int_to_gnb_cu_ue_f1ap_id(0), int_to_gnb_du_ue_f1ap_id(0), srb_id_t::srb1, - make_byte_buffer("8000080035c41efd").value()); + make_byte_buffer("80000a00ddc7574a").value()); f1c_gw.get_du(target_du_index).on_new_message(rrc_recfg_complete); } diff --git a/tests/unittests/cu_cp/routines/pdu_session_resource_release_routine_test.cpp b/tests/unittests/cu_cp/routines/pdu_session_resource_release_routine_test.cpp index 44bee02c6b..9e12aca9e9 100644 --- a/tests/unittests/cu_cp/routines/pdu_session_resource_release_routine_test.cpp +++ b/tests/unittests/cu_cp/routines/pdu_session_resource_release_routine_test.cpp @@ -66,6 +66,11 @@ class pdu_session_resource_release_test : public pdu_session_resource_routine_te return true; } + bool was_bearer_context_release_command_sent() const + { + return e1ap_bearer_ctxt_mng.last_release_command.ue_index != ue_index_t::invalid; + } + void setup_pdu_session(ue_index_t ue_index) { // Setup single PDU session. @@ -162,3 +167,20 @@ TEST_F(pdu_session_resource_release_test, when_all_sub_actions_succeed_then_rele // All released. ASSERT_TRUE(was_pdu_session_resource_release_successful()); } + +TEST_F(pdu_session_resource_release_test, when_only_pdu_session_released_then_bearer_context_release_command_sent) +{ + // Test Preamble. + ue_index_t ue_index = ue_mng.add_ue(du_index_t::min); + setup_pdu_session(ue_index); + + cu_cp_pdu_session_resource_release_command command = generate_pdu_session_resource_release(ue_index); + + // Start PDU SESSION RESOURCE RELEASE routine. + start_procedure(command, {true}, {true}); + + ASSERT_TRUE(was_bearer_context_release_command_sent()); + + // All released. + ASSERT_TRUE(was_pdu_session_resource_release_successful()); +} \ No newline at end of file diff --git a/tests/unittests/cu_cp/test_helpers.h b/tests/unittests/cu_cp/test_helpers.h index a3e32d74ab..b704f9a8f5 100644 --- a/tests/unittests/cu_cp/test_helpers.h +++ b/tests/unittests/cu_cp/test_helpers.h @@ -383,6 +383,8 @@ struct dummy_e1ap_bearer_context_manager : public e1ap_bearer_context_manager { { logger.info("Received a new bearer context release command"); + last_release_command = cmd; + return launch_async([](coro_context>& ctx) mutable { CORO_BEGIN(ctx); CORO_RETURN(); @@ -395,6 +397,8 @@ struct dummy_e1ap_bearer_context_manager : public e1ap_bearer_context_manager { second_e1ap_request.reset(); } + e1ap_bearer_context_release_command last_release_command; + std::optional> first_e1ap_request; std::optional second_e1ap_request; diff --git a/tests/unittests/cu_up/cu_up_test.cpp b/tests/unittests/cu_up/cu_up_test.cpp index 4c770b6e0a..f941f0a007 100644 --- a/tests/unittests/cu_up/cu_up_test.cpp +++ b/tests/unittests/cu_up/cu_up_test.cpp @@ -21,9 +21,9 @@ */ #include "cu_up_test_helpers.h" +#include "lib/cu_up/cu_up_impl.h" #include "lib/e1ap/cu_up/e1ap_cu_up_asn1_helpers.h" #include "srsran/asn1/e1ap/e1ap.h" -#include "srsran/cu_up/cu_up_factory.h" #include "srsran/pdcp/pdcp_sn_util.h" #include "srsran/support/executors/task_worker.h" #include "srsran/support/io/io_broker_factory.h" @@ -146,7 +146,7 @@ class cu_up_test : public ::testing::Test auto cfg_copy = cfg; cfg_copy.ngu_gw = ngu_gw.get(); - cu_up = create_cu_up(cfg_copy); + cu_up = std::make_unique(cfg_copy); } void TearDown() override @@ -157,14 +157,14 @@ class cu_up_test : public ::testing::Test std::unique_ptr app_timers; - dummy_cu_cp_handler e1ap_client; - dummy_inner_f1u_bearer f1u_bearer; - std::unique_ptr f1u_gw; - std::unique_ptr broker; - std::unique_ptr ngu_gw; - std::unique_ptr exec_pool; - std::unique_ptr cu_up; - srslog::basic_logger& test_logger = srslog::fetch_basic_logger("TEST"); + dummy_cu_cp_handler e1ap_client; + dummy_inner_f1u_bearer f1u_bearer; + std::unique_ptr f1u_gw; + std::unique_ptr broker; + std::unique_ptr ngu_gw; + std::unique_ptr exec_pool; + std::unique_ptr cu_up; + srslog::basic_logger& test_logger = srslog::fetch_basic_logger("TEST"); std::unique_ptr worker; std::unique_ptr executor; @@ -183,7 +183,7 @@ class cu_up_test : public ::testing::Test bearer_context_setup, asn1_bearer_context_setup_msg.pdu.init_msg().value.bearer_context_setup_request()); // Setup bearer - cu_up->handle_bearer_context_setup_request(bearer_context_setup); + cu_up->get_cu_up_manager()->handle_bearer_context_setup_request(bearer_context_setup); } }; @@ -192,15 +192,16 @@ class cu_up_test : public ::testing::Test ////////////////////////////////////////////////////////////////////////////////////// /// Test the E1AP connection + TEST_F(cu_up_test, when_e1ap_connection_established_then_e1ap_connected) { init(get_default_cu_up_config()); // Connect E1AP - cu_up->on_e1ap_connection_establish(); + cu_up->get_cu_up_manager()->on_e1ap_connection_establish(); // check that E1AP is in connected state - ASSERT_TRUE(cu_up->e1ap_is_connected()); + ASSERT_TRUE(cu_up->get_cu_up_manager()->e1ap_is_connected()); } ////////////////////////////////////////////////////////////////////////////////////// @@ -247,14 +248,16 @@ TEST_F(cu_up_test, dl_data_flow) close(sock_fd); // check reception of message 1 - nru_dl_message sdu1 = f1u_bearer.wait_tx_sdu(); - std::optional sdu1_pdcp_sn = get_pdcp_sn(sdu1.t_pdu, pdcp_sn_size::size18bits, test_logger); + nru_dl_message sdu1 = f1u_bearer.wait_tx_sdu(); + std::optional sdu1_pdcp_sn = + get_pdcp_sn(sdu1.t_pdu, pdcp_sn_size::size18bits, /* is_srb = */ false, test_logger); ASSERT_TRUE(sdu1_pdcp_sn.has_value()); EXPECT_EQ(sdu1_pdcp_sn.value(), 0); // check reception of message 2 - nru_dl_message sdu2 = f1u_bearer.wait_tx_sdu(); - std::optional sdu2_pdcp_sn = get_pdcp_sn(sdu2.t_pdu, pdcp_sn_size::size18bits, test_logger); + nru_dl_message sdu2 = f1u_bearer.wait_tx_sdu(); + std::optional sdu2_pdcp_sn = + get_pdcp_sn(sdu2.t_pdu, pdcp_sn_size::size18bits, /* is_srb = */ false, test_logger); ASSERT_TRUE(sdu2_pdcp_sn.has_value()); EXPECT_EQ(sdu2_pdcp_sn.value(), 1); @@ -343,3 +346,9 @@ TEST_F(cu_up_test, ul_data_flow) close(sock_fd); } + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/unittests/du_manager/procedures/ue_configuration_test.cpp b/tests/unittests/du_manager/procedures/ue_configuration_test.cpp index 3908fedccc..424ce0ece1 100644 --- a/tests/unittests/du_manager/procedures/ue_configuration_test.cpp +++ b/tests/unittests/du_manager/procedures/ue_configuration_test.cpp @@ -201,8 +201,8 @@ TEST_F(ue_config_tester, when_du_manager_completes_ue_configuration_procedure_th TEST_F(ue_config_tester, when_du_manager_finishes_processing_ue_config_request_then_mac_rlc_f1c_bearers_are_connected) { const static std::array dummy_rlc_header = {0x80, 0x0}; - byte_buffer test_payload = - test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0, test_rgen::uniform_int(3, 100)); + byte_buffer test_payload = test_helpers::create_pdcp_pdu( + pdcp_sn_size::size12bits, /* is_srb = */ true, 0, test_rgen::uniform_int(3, 100), 0); // Run UE Configuration Procedure to completion. configure_ue(create_f1ap_ue_context_update_request(test_ue->ue_index, {srb_id_t::srb2}, {})); @@ -234,8 +234,8 @@ TEST_F(ue_config_tester, when_du_manager_finishes_processing_ue_config_request_t TEST_F(ue_config_tester, when_du_manager_finishes_processing_ue_config_request_then_mac_rlc_f1u_bearers_are_connected) { const static std::array dummy_rlc_header = {0x80, 0x0}; - byte_buffer test_payload = - test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0, test_rgen::uniform_int(3, 100)); + byte_buffer test_payload = test_helpers::create_pdcp_pdu( + pdcp_sn_size::size12bits, /* is_srb = */ false, 0, test_rgen::uniform_int(3, 100), 0); // Run UE Configuration Procedure to completion. configure_ue(create_f1ap_ue_context_update_request(test_ue->ue_index, {}, {drb_id_t::drb1})); diff --git a/tests/unittests/du_manager/serving_cell_config_converter_test.cpp b/tests/unittests/du_manager/serving_cell_config_converter_test.cpp index 208515851f..1f0e512be1 100644 --- a/tests/unittests/du_manager/serving_cell_config_converter_test.cpp +++ b/tests/unittests/du_manager/serving_cell_config_converter_test.cpp @@ -415,7 +415,7 @@ TEST(serving_cell_config_converter_test, test_ue_custom_pucch_cfg_conversion) // >> PUCCH Resource Set 1. dest_pucch_cfg.pucch_res_set.emplace_back(); - dest_pucch_cfg.pucch_res_set.back().pucch_res_set_id = 1; + dest_pucch_cfg.pucch_res_set.back().pucch_res_set_id = srsran::pucch_res_set_idx::set_1; dest_pucch_cfg.pucch_res_set.back().pucch_res_id_list.emplace_back(pucch_res_id_t{1, 1}); // Remove first element. dest_pucch_cfg.pucch_res_set.erase(dest_pucch_cfg.pucch_res_set.begin()); diff --git a/tests/unittests/e1ap/common/test_helpers.h b/tests/unittests/e1ap/common/test_helpers.h index e2eb14ba48..5754498af3 100644 --- a/tests/unittests/e1ap/common/test_helpers.h +++ b/tests/unittests/e1ap/common/test_helpers.h @@ -62,7 +62,7 @@ class dummy_e1ap_cu_up_processor_notifier : public srs_cu_cp::e1ap_cu_up_process uint16_t ue_index = srs_cu_cp::ue_index_to_uint(srs_cu_cp::ue_index_t::min); }; -class dummy_e1ap_cu_up_notifier : public srs_cu_up::e1ap_cu_up_notifier +class dummy_e1ap_cu_up_notifier : public srs_cu_up::e1ap_cu_up_manager_notifier { public: dummy_e1ap_cu_up_notifier() : logger(srslog::fetch_basic_logger("TEST")), task_loop(1024) {} diff --git a/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp b/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp index 0499dc2800..0b1c67b0ff 100644 --- a/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp +++ b/tests/unittests/e1ap/cu_cp/e1ap_cu_cp_ue_context_test.cpp @@ -88,6 +88,28 @@ TEST_F(e1ap_cu_cp_ue_context_test, when_ue_not_added_then_ue_doesnt_exist) ASSERT_EQ(ue_ctxt_list.find_ue(cu_cp_ue_e1ap_id), nullptr); } +TEST_F(e1ap_cu_cp_ue_context_test, when_ue_exists_then_ue_not_added) +{ + ue_index_t ue_index = generate_random_ue_index(); + gnb_cu_cp_ue_e1ap_id_t cu_cp_ue_e1ap_id = generate_random_gnb_cu_cp_ue_e1ap_id(); + + ASSERT_NE(ue_ctxt_list.add_ue(ue_index, cu_cp_ue_e1ap_id), nullptr); + + ASSERT_TRUE(ue_ctxt_list.contains(cu_cp_ue_e1ap_id)); + ASSERT_TRUE(ue_ctxt_list.contains(ue_index)); + + ASSERT_EQ(ue_ctxt_list[cu_cp_ue_e1ap_id].ue_ids.cu_cp_ue_e1ap_id, cu_cp_ue_e1ap_id); + ASSERT_EQ(ue_ctxt_list[cu_cp_ue_e1ap_id].ue_ids.ue_index, ue_index); + ASSERT_EQ(ue_ctxt_list[ue_index].ue_ids.cu_cp_ue_e1ap_id, cu_cp_ue_e1ap_id); + ASSERT_EQ(ue_ctxt_list[ue_index].ue_ids.ue_index, ue_index); + + ASSERT_EQ(ue_ctxt_list.size(), 1U); + + // Try to add UE with the same UE index again + ASSERT_EQ(ue_ctxt_list.add_ue(ue_index, generate_random_gnb_cu_cp_ue_e1ap_id()), nullptr); + ASSERT_EQ(ue_ctxt_list.size(), 1U); +} + TEST_F(e1ap_cu_cp_ue_context_test, when_unsupported_number_of_ues_added_then_ue_not_added) { // Add maximum number of supported UEs diff --git a/tests/unittests/f1ap/common/f1ap_cu_test_messages.cpp b/tests/unittests/f1ap/common/f1ap_cu_test_messages.cpp index 8b9c8b1505..df67d8d673 100644 --- a/tests/unittests/f1ap/common/f1ap_cu_test_messages.cpp +++ b/tests/unittests/f1ap/common/f1ap_cu_test_messages.cpp @@ -130,10 +130,10 @@ f1ap_message srsran::srs_cu_cp::generate_ue_context_setup_request(gnb_cu_ue_f1ap return msg; } -f1ap_message srsran::srs_cu_cp::generate_ue_context_setup_response(gnb_cu_ue_f1ap_id_t cu_ue_id, - gnb_du_ue_f1ap_id_t du_ue_id, - rnti_t crnti, - byte_buffer cell_group_config) +f1ap_message srsran::srs_cu_cp::generate_ue_context_setup_response(gnb_cu_ue_f1ap_id_t cu_ue_id, + gnb_du_ue_f1ap_id_t du_ue_id, + std::optional crnti, + byte_buffer cell_group_config) { f1ap_message ue_context_setup_response = {}; @@ -141,10 +141,14 @@ f1ap_message srsran::srs_cu_cp::generate_ue_context_setup_response(gnb_cu_ue_f1a ue_context_setup_response.pdu.successful_outcome().load_info_obj(ASN1_F1AP_ID_UE_CONTEXT_SETUP); auto& ue_context_setup_resp = ue_context_setup_response.pdu.successful_outcome().value.ue_context_setup_resp(); - ue_context_setup_resp->gnb_cu_ue_f1ap_id = (unsigned)cu_ue_id; - ue_context_setup_resp->gnb_du_ue_f1ap_id = (unsigned)du_ue_id; - ue_context_setup_resp->c_rnti_present = true; - ue_context_setup_resp->c_rnti = (unsigned)crnti; + ue_context_setup_resp->gnb_cu_ue_f1ap_id = (unsigned)cu_ue_id; + ue_context_setup_resp->gnb_du_ue_f1ap_id = (unsigned)du_ue_id; + + if (crnti.has_value()) { + ue_context_setup_resp->c_rnti_present = true; + ue_context_setup_resp->c_rnti = (unsigned)crnti.value(); + } + ue_context_setup_resp->du_to_cu_rrc_info.cell_group_cfg = cell_group_config.copy(); return ue_context_setup_response; diff --git a/tests/unittests/f1ap/common/f1ap_cu_test_messages.h b/tests/unittests/f1ap/common/f1ap_cu_test_messages.h index da3420c683..c1e4ae1df0 100644 --- a/tests/unittests/f1ap/common/f1ap_cu_test_messages.h +++ b/tests/unittests/f1ap/common/f1ap_cu_test_messages.h @@ -26,6 +26,7 @@ #include "srsran/asn1/f1ap/f1ap_ies.h" #include "srsran/f1ap/common/f1ap_ue_id.h" #include "srsran/f1ap/cu_cp/f1ap_cu.h" +#include namespace srsran { namespace srs_cu_cp { @@ -53,10 +54,10 @@ f1ap_message generate_ue_context_setup_request(gnb_cu_ue_f1ap_id_t cu_ue_id, gnb /// \brief Generates dummy F1AP UE CONTEXT SETUP RESPONSE message. f1ap_message generate_ue_context_setup_response( - gnb_cu_ue_f1ap_id_t cu_ue_id, - gnb_du_ue_f1ap_id_t du_ue_id, - rnti_t crnti = to_rnti(0x4601), - byte_buffer cell_group_config = + gnb_cu_ue_f1ap_id_t cu_ue_id, + gnb_du_ue_f1ap_id_t du_ue_id, + std::optional crnti = std::nullopt, + byte_buffer cell_group_config = make_byte_buffer("5c02b091117aec701061e000b1c03544cde4a20c7c080408c008241000100000").value()); /// \brief Generates dummy F1AP UE CONTEXT SETUP FAILURE message. diff --git a/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp b/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp index 786deef0ae..7f328728e3 100644 --- a/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp +++ b/tests/unittests/ngap/ngap_pdu_session_resource_setup_procedure_test.cpp @@ -182,8 +182,6 @@ TEST_F(ngap_pdu_session_resource_setup_procedure_test, when_security_not_enabled ue_index_t ue_index = this->start_procedure(false); auto& ue = test_ues.at(ue_index); - ue.rrc_ue_security_handler.set_security_enabled(false); - // Inject PDU Session Resource Setup Request pdu_session_id_t pdu_session_id = uint_to_pdu_session_id(test_rgen::uniform_int( pdu_session_id_to_uint(pdu_session_id_t::min), pdu_session_id_to_uint(pdu_session_id_t::max))); diff --git a/tests/unittests/ngap/ngap_test_helpers.cpp b/tests/unittests/ngap/ngap_test_helpers.cpp index 6696d1f2e8..a2b476c6c5 100644 --- a/tests/unittests/ngap/ngap_test_helpers.cpp +++ b/tests/unittests/ngap/ngap_test_helpers.cpp @@ -76,8 +76,8 @@ ue_index_t ngap_test::create_ue(rnti_t rnti) test_ues.emplace(ue_index, test_ue(ue_index)); test_ue& new_test_ue = test_ues.at(ue_index); - ue_mng.get_ngap_rrc_ue_adapter(ue_index).connect_rrc_ue( - new_test_ue.rrc_ue_dl_nas_handler, new_test_ue.rrc_ue_security_handler, new_test_ue.rrc_ue_ho_prep_handler); + ue_mng.get_ngap_rrc_ue_adapter(ue_index).connect_rrc_ue(new_test_ue.rrc_ue_dl_nas_handler, + new_test_ue.rrc_ue_ho_prep_handler); // generate and inject valid initial ue message cu_cp_initial_ue_message msg = generate_initial_ue_message(ue_index); @@ -103,8 +103,8 @@ ue_index_t ngap_test::create_ue_without_init_ue_message(rnti_t rnti) test_ues.emplace(ue_index, test_ue(ue_index)); test_ue& new_test_ue = test_ues.at(ue_index); - ue_mng.get_ngap_rrc_ue_adapter(ue_index).connect_rrc_ue( - new_test_ue.rrc_ue_dl_nas_handler, new_test_ue.rrc_ue_security_handler, new_test_ue.rrc_ue_ho_prep_handler); + ue_mng.get_ngap_rrc_ue_adapter(ue_index).connect_rrc_ue(new_test_ue.rrc_ue_dl_nas_handler, + new_test_ue.rrc_ue_ho_prep_handler); return ue_index; } diff --git a/tests/unittests/ngap/ngap_test_helpers.h b/tests/unittests/ngap/ngap_test_helpers.h index 340b6d749b..c544ce0763 100644 --- a/tests/unittests/ngap/ngap_test_helpers.h +++ b/tests/unittests/ngap/ngap_test_helpers.h @@ -48,9 +48,8 @@ class ngap_test : public ::testing::Test std::optional amf_ue_id; std::optional ran_ue_id; - dummy_rrc_dl_nas_message_handler rrc_ue_dl_nas_handler; - dummy_rrc_ue_init_security_context_handler rrc_ue_security_handler; - dummy_rrc_ue_handover_preparation_handler rrc_ue_ho_prep_handler; + dummy_rrc_dl_nas_message_handler rrc_ue_dl_nas_handler; + dummy_rrc_ue_handover_preparation_handler rrc_ue_ho_prep_handler; }; ngap_test(); diff --git a/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp b/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp index 54acaf150c..cf831d29db 100644 --- a/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp +++ b/tests/unittests/ngap/ngap_ue_context_management_procedure_test.cpp @@ -182,8 +182,6 @@ TEST_F(ngap_ue_context_management_procedure_test, when_invalid_initial_context_s auto& ue = test_ues.at(ue_index); - ue.rrc_ue_security_handler.set_security_enabled(false); - // Inject Initial Context Setup Request ngap_message init_context_setup_request = generate_invalid_initial_context_setup_request_message(ue.amf_ue_id.value(), ue.ran_ue_id.value()); diff --git a/tests/unittests/ngap/test_helpers.h b/tests/unittests/ngap/test_helpers.h index e99d744261..9652c4190b 100644 --- a/tests/unittests/ngap/test_helpers.h +++ b/tests/unittests/ngap/test_helpers.h @@ -134,15 +134,6 @@ class dummy_ngap_rrc_ue_notifier : public ngap_rrc_ue_pdu_notifier, public ngap_ logger.info("Received a NAS PDU"); } - async_task on_new_security_context() override - { - logger.info("Received a new security context"); - return launch_async([](coro_context>& ctx) { - CORO_BEGIN(ctx); - CORO_RETURN(true); - }); - } - byte_buffer on_handover_preparation_message_required() override { return ho_preparation_message.copy(); } void set_ho_preparation_message(byte_buffer ho_preparation_message_) @@ -223,10 +214,60 @@ class dummy_ngap_cu_cp_notifier : public ngap_cu_cp_notifier return ue_mng.find_ue(ue_index)->get_security_manager().init_security_context(sec_ctxt); } + async_task> + on_new_initial_context_setup_request(ngap_init_context_setup_request& request) override + { + logger.info("Received a new initial context setup request"); + + last_init_ctxt_setup_request = std::move(request); + + return launch_async( + [this, resp = ngap_init_context_setup_response{}, fail = ngap_init_context_setup_failure{}]( + coro_context>>& + ctx) mutable { + CORO_BEGIN(ctx); + + if (!ue_mng.find_ue(last_init_ctxt_setup_request.ue_index) + ->get_security_manager() + .init_security_context(last_init_ctxt_setup_request.security_context)) { + // Add failed PDU session setup responses + if (last_init_ctxt_setup_request.pdu_session_res_setup_list_cxt_req.has_value()) { + for (const auto& session : last_init_ctxt_setup_request.pdu_session_res_setup_list_cxt_req.value() + .pdu_session_res_setup_items) { + cu_cp_pdu_session_res_setup_failed_item failed_item; + failed_item.pdu_session_id = session.pdu_session_id; + failed_item.unsuccessful_transfer.cause = ngap_cause_radio_network_t::unspecified; + + fail.pdu_session_res_failed_to_setup_items.emplace(failed_item.pdu_session_id, failed_item); + } + } + CORO_EARLY_RETURN(make_unexpected(fail)); + } + + // Add successful PDU session setup responses + if (last_init_ctxt_setup_request.pdu_session_res_setup_list_cxt_req.has_value()) { + for (const auto& session : + last_init_ctxt_setup_request.pdu_session_res_setup_list_cxt_req.value().pdu_session_res_setup_items) { + cu_cp_pdu_session_res_setup_response_item response_item; + response_item.pdu_session_id = session.pdu_session_id; + response_item.pdu_session_resource_setup_response_transfer.dlqos_flow_per_tnl_info.up_tp_layer_info = + up_transport_layer_info{transport_layer_address::create_from_string("127.0.0.1"), + int_to_gtpu_teid(1)}; + response_item.pdu_session_resource_setup_response_transfer.dlqos_flow_per_tnl_info + .associated_qos_flow_list.emplace(uint_to_qos_flow_id(5), + cu_cp_associated_qos_flow{uint_to_qos_flow_id(5)}); + resp.pdu_session_res_setup_response_items.emplace(response_item.pdu_session_id, response_item); + } + } + + CORO_RETURN(resp); + }); + } + async_task on_new_pdu_session_resource_setup_request(cu_cp_pdu_session_resource_setup_request& request) override { - logger.info("Received a new pdu session resource setup request."); + logger.info("Received a new pdu session resource setup request"); last_request = std::move(request); @@ -335,6 +376,7 @@ class dummy_ngap_cu_cp_notifier : public ngap_cu_cp_notifier } ue_index_t last_ue = ue_index_t::invalid; + ngap_init_context_setup_request last_init_ctxt_setup_request; cu_cp_pdu_session_resource_setup_request last_request; cu_cp_pdu_session_resource_modify_request last_modify_request; cu_cp_pdu_session_resource_release_command last_release_command; @@ -368,28 +410,6 @@ class dummy_rrc_dl_nas_message_handler : public rrc_dl_nas_message_handler srslog::basic_logger& logger; }; -class dummy_rrc_ue_init_security_context_handler : public rrc_ue_init_security_context_handler -{ -public: - dummy_rrc_ue_init_security_context_handler() : logger(srslog::fetch_basic_logger("TEST")){}; - - void set_security_enabled(bool enabled) { security_enabled = enabled; } - - async_task handle_init_security_context() override - { - logger.info("Received a new security context"); - - return launch_async([](coro_context>& ctx) mutable { - CORO_BEGIN(ctx); - CORO_RETURN(true); - }); - } - -private: - bool security_enabled = true; - srslog::basic_logger& logger; -}; - class dummy_rrc_ue_handover_preparation_handler : public rrc_ue_handover_preparation_handler { public: diff --git a/tests/unittests/rlc/rlc_pdu_recycler_test.cpp b/tests/unittests/rlc/rlc_pdu_recycler_test.cpp index 525562bee0..18e18df54c 100644 --- a/tests/unittests/rlc/rlc_pdu_recycler_test.cpp +++ b/tests/unittests/rlc/rlc_pdu_recycler_test.cpp @@ -74,7 +74,7 @@ TEST_F(rlc_pdu_recycler_test, recycler_memory_reserved) TEST_F(rlc_pdu_recycler_test, clear_by_executor) { - byte_buffer pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xaa, 3, 0xaa); + byte_buffer pdu = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xaa, 3, 0xaa); EXPECT_TRUE(pdu_recycler->add_discarded_pdu(pdu.deep_copy().value())); // Check the PDU is stored in first recycle bin std::array, 3>& recycle_bins = pdu_recycler->get_recycle_bins(); @@ -100,10 +100,10 @@ TEST_F(rlc_pdu_recycler_test, clear_by_executor) TEST_F(rlc_pdu_recycler_test, clear_multiple_times) { - byte_buffer pdu1 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xaa, 3, 0xaa); - byte_buffer pdu2 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xbb, 3, 0xbb); - byte_buffer pdu3 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xcc, 3, 0xcc); - byte_buffer pdu4 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xdd, 3, 0xdd); + byte_buffer pdu1 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xaa, 3, 0xaa); + byte_buffer pdu2 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xbb, 3, 0xbb); + byte_buffer pdu3 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xcc, 3, 0xcc); + byte_buffer pdu4 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xdd, 3, 0xdd); EXPECT_TRUE(pdu_recycler->add_discarded_pdu(pdu1.deep_copy().value())); // Check the PDU is stored in first recycle bin std::array, 3>& recycle_bins = pdu_recycler->get_recycle_bins(); @@ -177,9 +177,9 @@ TEST_F(rlc_pdu_recycler_test, clear_multiple_times) TEST_F(rlc_pdu_recycler_test, full_recycle_bin) { - byte_buffer pdu1 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xaa, 3, 0xaa); - byte_buffer pdu2 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xbb, 3, 0xbb); - byte_buffer pdu3 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, 0xcc, 3, 0xcc); + byte_buffer pdu1 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xaa, 3, 0xaa); + byte_buffer pdu2 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xbb, 3, 0xbb); + byte_buffer pdu3 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ false, 0xcc, 3, 0xcc); EXPECT_TRUE(pdu_recycler->add_discarded_pdu(pdu1.deep_copy().value())); EXPECT_TRUE(pdu_recycler->add_discarded_pdu(pdu2.deep_copy().value())); diff --git a/tests/unittests/rlc/rlc_rx_am_test.cpp b/tests/unittests/rlc/rlc_rx_am_test.cpp index d41e6b6d2a..7666a1ae74 100644 --- a/tests/unittests/rlc/rlc_rx_am_test.cpp +++ b/tests/unittests/rlc/rlc_rx_am_test.cpp @@ -154,7 +154,7 @@ class rlc_rx_am_test : public ::testing::Test, public ::testing::WithParamInterf ASSERT_GT(segment_size, 0) << "Invalid argument: Cannot create PDUs with zero-sized SDU segments"; sdu = test_helpers::create_pdcp_pdu( - pdcp_sn_size::size12bits, sn, sdu_size, first_byte); // 12-bit PDCP SN allows smaller SDUs + pdcp_sn_size::size12bits, /* is_srb = */ false, sn, sdu_size, first_byte); // 12-bit PDCP SN allows smaller SDUs pdu_list.clear(); byte_buffer_view rest = {sdu}; diff --git a/tests/unittests/rlc/rlc_rx_tm_test.cpp b/tests/unittests/rlc/rlc_rx_tm_test.cpp index cc955bba2e..819122888c 100644 --- a/tests/unittests/rlc/rlc_rx_tm_test.cpp +++ b/tests/unittests/rlc/rlc_rx_tm_test.cpp @@ -95,15 +95,17 @@ TEST_F(rlc_rx_am_test, test_rx) uint32_t count = 0; // write first PDU into lower end - byte_buffer pdu_buf = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); - byte_buffer_slice pdu = {pdu_buf.deep_copy().value()}; + byte_buffer pdu_buf = + test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); + byte_buffer_slice pdu = {pdu_buf.deep_copy().value()}; rlc->handle_pdu(std::move(pdu)); count++; // write second PDU into lower end - byte_buffer pdu_buf2 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); - pdu = {pdu_buf2.deep_copy().value()}; + byte_buffer pdu_buf2 = + test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); + pdu = {pdu_buf2.deep_copy().value()}; rlc->handle_pdu(std::move(pdu)); // read first SDU from tester diff --git a/tests/unittests/rlc/rlc_tx_am_test.cpp b/tests/unittests/rlc/rlc_tx_am_test.cpp index 7f207d7b85..c13f90fe55 100644 --- a/tests/unittests/rlc/rlc_tx_am_test.cpp +++ b/tests/unittests/rlc/rlc_tx_am_test.cpp @@ -125,7 +125,7 @@ class rlc_tx_am_test : public ::testing::Test, public ::testing::WithParamInterf // Create RLC AM TX entity rlc = std::make_unique(gnb_du_id_t::min, du_ue_index_t::MIN_DU_UE_INDEX, - srb_id_t::srb0, + drb_id_t::drb1, config, *tester, *tester, @@ -159,7 +159,7 @@ class rlc_tx_am_test : public ::testing::Test, public ::testing::WithParamInterf // Push "n_pdus" SDUs into RLC auto sdu_bufs = std::vector(n_pdus); for (uint32_t i = 0; i < n_pdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -227,7 +227,7 @@ class rlc_tx_am_test : public ::testing::Test, public ::testing::WithParamInterf // Push "n_sdus" SDUs into RLC auto sdu_bufs = std::vector(n_sdus); for (uint32_t i = 0; i < n_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -363,7 +363,7 @@ TEST_P(rlc_tx_am_test, tx_insufficient_space_new_sdu) const uint32_t fit_size = header_min_size + sdu_size; EXPECT_EQ(rlc->get_buffer_state(), 0); - rlc->handle_sdu(test_helpers::create_pdcp_pdu(config.pdcp_sn_len, 0, sdu_size), false); + rlc->handle_sdu(test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, 0, sdu_size, 0), false); EXPECT_EQ(rlc->get_buffer_state(), sdu_size + header_min_size); pcell_worker.run_pending_tasks(); EXPECT_EQ(tester->bsr, sdu_size + header_min_size); @@ -401,7 +401,7 @@ TEST_P(rlc_tx_am_test, tx_insufficient_space_continued_sdu) const uint32_t min_size_seg = header_min_size + header_so_size + 1; EXPECT_EQ(rlc->get_buffer_state(), 0); - rlc->handle_sdu(test_helpers::create_pdcp_pdu(config.pdcp_sn_len, 0, sdu_size), false); + rlc->handle_sdu(test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, 0, sdu_size, 0), false); pcell_worker.run_pending_tasks(); EXPECT_EQ(rlc->get_buffer_state(), sdu_size + header_min_size); EXPECT_EQ(tester->bsr, sdu_size + header_min_size); @@ -457,7 +457,7 @@ TEST_P(rlc_tx_am_test, sdu_discard) // Push "n_pdus" SDUs into RLC byte_buffer sdu_bufs[n_pdus]; for (uint32_t i = 0; i < n_pdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -569,7 +569,8 @@ TEST_P(rlc_tx_am_test, sdu_discard_with_pdcp_sn_wraparound) // Push "n_pdus" SDUs into RLC byte_buffer sdu_bufs[n_pdus]; for (uint32_t i = 0; i < n_pdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, pdcp_sn_start + i, sdu_size, i); + sdu_bufs[i] = + test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, pdcp_sn_start + i, sdu_size, i); // write SDU into upper end rlc->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -1621,7 +1622,7 @@ TEST_P(rlc_tx_am_test, status_report_priority) const uint32_t pdu_size = header_min_size + sdu_size; EXPECT_EQ(rlc->get_buffer_state(), 0); - rlc->handle_sdu(test_helpers::create_pdcp_pdu(config.pdcp_sn_len, 0, sdu_size), false); + rlc->handle_sdu(test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, 0, sdu_size, 0), false); pcell_worker.run_pending_tasks(); EXPECT_EQ(rlc->get_buffer_state(), pdu_size); EXPECT_EQ(tester->bsr, pdu_size); @@ -1801,7 +1802,7 @@ TEST_P(rlc_tx_am_test, expired_poll_retransmit_timer_sets_polling_bit) // push SDU to SDU queue so that it is not empty uint32_t n_bsr = tester->bsr_count; - byte_buffer sdu_buf = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, 7, sdu_size, 7); + byte_buffer sdu_buf = test_helpers::create_pdcp_pdu(config.pdcp_sn_len, /* is_srb = */ false, 7, sdu_size, 7); rlc->handle_sdu(sdu_buf.deep_copy().value(), false); // keep local copy for later comparison pcell_worker.run_pending_tasks(); EXPECT_EQ(rlc->get_buffer_state(), pdu_size); diff --git a/tests/unittests/rlc/rlc_tx_tm_test.cpp b/tests/unittests/rlc/rlc_tx_tm_test.cpp index 1a8f04e162..c192dbf434 100644 --- a/tests/unittests/rlc/rlc_tx_tm_test.cpp +++ b/tests/unittests/rlc/rlc_tx_tm_test.cpp @@ -118,7 +118,8 @@ TEST_F(rlc_tx_tm_test, test_tx) EXPECT_EQ(rlc->get_buffer_state(), 0); - byte_buffer sdu_buf = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); + byte_buffer sdu_buf = + test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); // write SDU into upper end rlc->handle_sdu(sdu_buf.deep_copy().value(), false); // keep local copy for later comparison @@ -151,7 +152,7 @@ TEST_F(rlc_tx_tm_test, test_tx) // write another SDU into upper end count++; - sdu_buf = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); + sdu_buf = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); rlc->handle_sdu(sdu_buf.deep_copy().value(), false); // keep local copy for later comparison pcell_worker.run_pending_tasks(); @@ -171,7 +172,8 @@ TEST_F(rlc_tx_tm_test, test_tx) // write another SDU into upper end count++; - byte_buffer sdu_buf2 = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); + byte_buffer sdu_buf2 = + test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); // write SDU into upper end rlc->handle_sdu(sdu_buf2.deep_copy().value(), false); // keep local copy for later comparison @@ -210,7 +212,8 @@ TEST_F(rlc_tx_tm_test, discard_sdu_increments_discard_failure_counter) EXPECT_EQ(rlc->get_buffer_state(), 0); - byte_buffer sdu_buf = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); + byte_buffer sdu_buf = + test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); // write SDU into upper end rlc->handle_sdu(sdu_buf.deep_copy().value(), false); // keep local copy for later comparison @@ -247,7 +250,8 @@ TEST_F(rlc_tx_tm_test, test_tx_metrics) EXPECT_EQ(rlc->get_buffer_state(), 0); - byte_buffer sdu_buf = test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, count, sdu_size, count); + byte_buffer sdu_buf = + test_helpers::create_pdcp_pdu(pdcp_sn_size::size12bits, /* is_srb = */ true, count, sdu_size, count); // write SDU into upper end rlc->handle_sdu(sdu_buf.deep_copy().value(), false); // keep local copy for later comparison diff --git a/tests/unittests/rlc/rlc_um_test.cpp b/tests/unittests/rlc/rlc_um_test.cpp index 4864479706..2f6f839548 100644 --- a/tests/unittests/rlc/rlc_um_test.cpp +++ b/tests/unittests/rlc/rlc_um_test.cpp @@ -348,7 +348,7 @@ TEST_P(rlc_um_test, tx_without_segmentation) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i + 13, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i + 13, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -417,7 +417,7 @@ TEST_P(rlc_um_test, tx_with_segmentation) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -502,7 +502,7 @@ TEST_P(rlc_um_test, sdu_discard) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -603,7 +603,8 @@ TEST_P(rlc_um_test, sdu_discard_with_pdcp_sn_wraparound) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, (pdcp_sn_start + i) % pdcp_sn_mod, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu( + config.tx.pdcp_sn_len, /* is_srb = */ false, (pdcp_sn_start + i) % pdcp_sn_mod, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -699,7 +700,7 @@ TEST_P(rlc_um_test, tx_with_segmentation_reverse_rx) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -784,7 +785,7 @@ TEST_P(rlc_um_test, tx_multiple_SDUs_with_segmentation) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -908,7 +909,8 @@ TEST_P(rlc_um_test, reassembly_window_wrap_around) uint32_t rx_sdu_idx = 0; for (uint32_t i = 0; i < num_sdus; i++) { // create and write SDU into upper end - rlc1_tx_upper->handle_sdu(test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i), false); + rlc1_tx_upper->handle_sdu( + test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i), false); pcell_worker.run_pending_tasks(); // check buffer state @@ -939,7 +941,8 @@ TEST_P(rlc_um_test, reassembly_window_wrap_around) while (!tester2.sdu_queue.empty() && rx_sdu_idx < num_sdus) { byte_buffer_chain& rx_sdu = tester2.sdu_queue.front(); EXPECT_EQ(sdu_size, rx_sdu.length()); - EXPECT_TRUE(rx_sdu == test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i)); + EXPECT_TRUE(rx_sdu == + test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i)); tester2.sdu_queue.pop(); rx_sdu_idx++; } @@ -971,7 +974,8 @@ TEST_P(rlc_um_test, lost_PDU_outside_reassembly_window) uint32_t rx_sdu_idx = 0; for (uint32_t i = 0; i < num_sdus; i++) { // create and write SDU into upper end - rlc1_tx_upper->handle_sdu(test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i), false); + rlc1_tx_upper->handle_sdu( + test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i), false); pcell_worker.run_pending_tasks(); // check buffer state @@ -1007,7 +1011,8 @@ TEST_P(rlc_um_test, lost_PDU_outside_reassembly_window) while (!tester2.sdu_queue.empty() && rx_sdu_idx < num_sdus) { byte_buffer_chain& rx_sdu = tester2.sdu_queue.front(); EXPECT_EQ(sdu_size, rx_sdu.length()); - EXPECT_TRUE(rx_sdu == test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i)); + EXPECT_TRUE(rx_sdu == + test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i)); tester2.sdu_queue.pop(); rx_sdu_idx++; } @@ -1040,7 +1045,7 @@ TEST_P(rlc_um_test, lost_segment_outside_reassembly_window) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison @@ -1123,7 +1128,7 @@ TEST_P(rlc_um_test, out_of_order_segments_across_SDUs) // Push SDUs into RLC1 byte_buffer sdu_bufs[num_sdus]; for (uint32_t i = 0; i < num_sdus; i++) { - sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, i, sdu_size, i); + sdu_bufs[i] = test_helpers::create_pdcp_pdu(config.tx.pdcp_sn_len, /* is_srb = */ false, i, sdu_size, i); // write SDU into upper end rlc1_tx_upper->handle_sdu(sdu_bufs[i].deep_copy().value(), false); // keep local copy for later comparison diff --git a/tests/unittests/rrc/CMakeLists.txt b/tests/unittests/rrc/CMakeLists.txt index 16492c7afd..4dfa22d8b3 100644 --- a/tests/unittests/rrc/CMakeLists.txt +++ b/tests/unittests/rrc/CMakeLists.txt @@ -28,7 +28,6 @@ set(SOURCES rrc_asn1_helpers_test.cpp rrc_ue_setup_proc_test.cpp rrc_ue_dl_info_transfer_proc_test.cpp - rrc_ue_smc_proc_test.cpp rrc_ue_reconfig_proc_test.cpp rrc_ue_capability_transfer_proc_test.cpp rrc_ue_reest_proc_test.cpp diff --git a/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp b/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp index f939ae9428..430be4b502 100644 --- a/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp +++ b/tests/unittests/rrc/rrc_ue_capability_transfer_proc_test.cpp @@ -77,12 +77,11 @@ class rrc_ue_capability_transfer_proc_test : public rrc_ue_test_helper, public : std::fill(init_sec_ctx.supported_enc_algos.begin(), init_sec_ctx.supported_enc_algos.end(), true); ue_mng.find_ue(allocated_ue_index)->get_security_manager().init_security_context(init_sec_ctx); - // Trigger SMC - async_task t = get_rrc_ue_security_handler()->handle_init_security_context(); - lazy_task_launcher t_launcher(t); + // Initialize security context. + init_security_context(); - // Receive SMC complete - receive_smc_complete(); + // Enable security + enable_security(); } void TearDown() override diff --git a/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp b/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp deleted file mode 100644 index 2fe086e1ee..0000000000 --- a/tests/unittests/rrc/rrc_ue_smc_proc_test.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "rrc_ue_test_helpers.h" -#include "rrc_ue_test_messages.h" -#include "srsran/support/async/async_test_utils.h" -#include - -using namespace srsran; -using namespace srs_cu_cp; - -/// Fixture class RRC Setup tests preparation -class rrc_ue_smc : public rrc_ue_test_helper, public ::testing::Test -{ -protected: - static void SetUpTestSuite() { srslog::init(); } - - void SetUp() override - { - init(); - - srslog::basic_logger& rrc_logger = srslog::fetch_basic_logger("RRC", false); - rrc_logger.set_level(srslog::basic_levels::debug); - rrc_logger.set_hex_dump_max_size(32); - - srslog::basic_logger& pdcp_logger = srslog::fetch_basic_logger("PDCP", false); - pdcp_logger.set_level(srslog::basic_levels::debug); - pdcp_logger.set_hex_dump_max_size(32); - - receive_setup_request(); - - // check if the RRC setup message was generated - ASSERT_EQ(get_srb0_pdu_type(), asn1::rrc_nr::dl_ccch_msg_type_c::c1_c_::types::rrc_setup); - - // check if SRB1 was created - check_srb1_exists(); - - receive_setup_complete(); - } - - void TearDown() override - { - // flush logger after each test - srslog::flush(); - } -}; - -/// Test the RRC setup with connected AMF -TEST_F(rrc_ue_smc, when_key_provided_smc_generated) -{ - const char* sk_gnb_cstr = "45cbc3f8a81193fd5c5229300d59edf812e998a115ec4e0ce903ba89367e2628"; - const char* k_enc_cstr = "4ea96992c8c7e82977231ad001309062ae9f31ead90a4d0842af6cd25cb44dc4"; - const char* k_int_cstr = "aeeb5e0ae02c6188ecb1625c4a9e022fdfc2a1fc845b44b44443ac9a3bda667c"; - - // Pack hex strings into srsgnb types - security::sec_key sk_gnb = make_sec_key(sk_gnb_cstr); - security::sec_key k_enc = make_sec_key(k_enc_cstr); - security::sec_key k_int = make_sec_key(k_int_cstr); - - // Create expected SRB1 sec config - security::sec_as_config sec_cfg = {}; - sec_cfg.domain = security::sec_domain::rrc; - sec_cfg.integ_algo = security::integrity_algorithm::nia2; - sec_cfg.cipher_algo = security::ciphering_algorithm::nea0; - sec_cfg.k_enc = k_enc; - sec_cfg.k_int = k_int; - - // Initialize security context and capabilities. - security::security_context init_sec_ctx = {}; - init_sec_ctx.k = sk_gnb; - std::fill(init_sec_ctx.supported_int_algos.begin(), init_sec_ctx.supported_int_algos.end(), true); - std::fill(init_sec_ctx.supported_enc_algos.begin(), init_sec_ctx.supported_enc_algos.end(), true); - ue_mng.find_ue(allocated_ue_index)->get_security_manager().init_security_context(init_sec_ctx); - - // Trigger SMC - async_task t = get_rrc_ue_security_handler()->handle_init_security_context(); - lazy_task_launcher t_launcher(t); - - ASSERT_FALSE(t.ready()); - check_smc_pdu(); - - // Receive SMC complete - receive_smc_complete(); - - ASSERT_TRUE(t.ready()); -} - -TEST_F(rrc_ue_smc, when_reply_missing_procedure_timeout) -{ - const char* sk_gnb_cstr = "45cbc3f8a81193fd5c5229300d59edf812e998a115ec4e0ce903ba89367e2628"; - - // Pack hex strings into srsgnb types - security::sec_key sk_gnb = make_sec_key(sk_gnb_cstr); - - // Initialize security context and capabilities. - security::security_context init_sec_ctx = {}; - init_sec_ctx.k = sk_gnb; - std::fill(init_sec_ctx.supported_int_algos.begin(), init_sec_ctx.supported_int_algos.end(), true); - std::fill(init_sec_ctx.supported_enc_algos.begin(), init_sec_ctx.supported_enc_algos.end(), true); - ue_mng.find_ue(allocated_ue_index)->get_security_manager().init_security_context(init_sec_ctx); - - // Trigger SMC - async_task t = get_rrc_ue_security_handler()->handle_init_security_context(); - lazy_task_launcher t_launcher(t); - - ASSERT_FALSE(t.ready()); - check_smc_pdu(); - - // check that UE has been created and was not requested to be released - check_ue_release_not_requested(); - - // tick timer until RRC procedure timer fires - tick_timer(); - - ASSERT_TRUE(t.ready()); -} diff --git a/tests/unittests/rrc/rrc_ue_test_helpers.h b/tests/unittests/rrc/rrc_ue_test_helpers.h index 5483417843..c0541d25ee 100644 --- a/tests/unittests/rrc/rrc_ue_test_helpers.h +++ b/tests/unittests/rrc/rrc_ue_test_helpers.h @@ -171,11 +171,6 @@ class rrc_ue_test_helper .value(); } - rrc_ue_init_security_context_handler* get_rrc_ue_security_handler() - { - return &rrc_ue->get_rrc_ue_init_security_context_handler(); - } - rrc_ue_control_message_handler* get_rrc_ue_control_message_handler() { return &rrc_ue->get_rrc_ue_control_message_handler(); @@ -202,6 +197,12 @@ class rrc_ue_test_helper rrc_ue_notifier.on_new_as_security_context(); } + void enable_security() + { + rrc_ue_security_mode_command_context rrc_smc_ctxt = rrc_ue->get_security_mode_command_context(); + receive_smc_complete(); + } + void create_srb2() { init_security_context(); diff --git a/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp b/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp index 063206a023..653a6965b8 100644 --- a/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp +++ b/tests/unittests/scheduler/scheduler_ue_fallback_mode_test.cpp @@ -123,7 +123,7 @@ class scheduler_con_res_msg4_test : public base_scheduler_conres_test, void enqueue_random_number_of_rach_indications() { rach_indication_message rach_ind{to_du_cell_index(0), next_slot_rx(), {{0, 0, {}}}}; - unsigned nof_preambles = test_rgen::uniform_int(1, 10); + auto nof_preambles = test_rgen::uniform_int(1, 10); for (unsigned i = 0; i != nof_preambles; ++i) { rach_ind.occasions[0].preambles.push_back({i, to_rnti((uint16_t)rnti + 1 + i), phy_time_unit{}}); } @@ -185,12 +185,12 @@ TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_pucch_is // Wait for ConRes + Msg4 PDCCH, PDSCH and PUCCH to be scheduled. ASSERT_TRUE(this->run_slot_until([this]() { - for (const auto& pucch : this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs) { - if (pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_1 and pucch.format_1.harq_ack_nof_bits > 0) { - return true; - } - } - return false; + return std::any_of(this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs.begin(), + this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs.end(), + [rnti = this->rnti](const pucch_info& pucch) { + return pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_1 and + pucch.format_1.harq_ack_nof_bits > 0; + }); })); // Enqueue SRB1 data; with the UE in fallback mode, and after the MSG4 has been delivered, both common and dedicated @@ -237,18 +237,23 @@ TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_pucch_is return true; } } + return false; } // Case of 3 PUCCH grants. else if (this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs.size() == 3) { for (const auto& pucch : this->last_sched_res_list[to_du_cell_index(0)]->ul.pucchs) { if (pucch.crnti == rnti and pucch.format == pucch_format::FORMAT_1) { - if (pucch.format_1.sr_bits == sr_nof_bits::no_sr) { - if (pucch.format_1.harq_ack_nof_bits > 0) { - pucch.resources.second_hop_prbs.empty() ? pucch_res_ptrs.f1_ded_ptr = &pucch - : pucch_res_ptrs.f1_common_ptr = &pucch; + if (pucch.format_1.sr_bits == sr_nof_bits::no_sr and pucch.format_1.harq_ack_nof_bits > 0 and + not pucch.resources.second_hop_prbs.empty()) { + pucch_res_ptrs.f1_common_ptr = &pucch; + } else if (pucch.format_1.sr_bits == sr_nof_bits::one and pucch.format_1.harq_ack_nof_bits > 0) { + // We cannot tell whether it's a f1_ded_sr_ptr or f1_ded_ptr yet only from the bits and format. But this is + // not important for the test. We only need to know that both pointers are set. + if (pucch_res_ptrs.f1_ded_ptr == nullptr and pucch_res_ptrs.f1_ded_sr_ptr == nullptr) { + pucch_res_ptrs.f1_ded_ptr = &pucch; + } else if (pucch_res_ptrs.f1_ded_ptr != nullptr and pucch_res_ptrs.f1_ded_sr_ptr == nullptr) { + pucch_res_ptrs.f1_ded_sr_ptr = &pucch; } - } else { - pucch_res_ptrs.f1_ded_sr_ptr = &pucch; } } if (pucch_res_ptrs.f1_common_ptr != nullptr and pucch_res_ptrs.f1_ded_ptr != nullptr and @@ -256,6 +261,7 @@ TEST_P(scheduler_con_res_msg4_test, while_ue_is_in_fallback_then_common_pucch_is return true; } } + return false; } return false; diff --git a/tests/unittests/scheduler/test_utils/config_generators.h b/tests/unittests/scheduler/test_utils/config_generators.h index 5370ae480e..870b690e89 100644 --- a/tests/unittests/scheduler/test_utils/config_generators.h +++ b/tests/unittests/scheduler/test_utils/config_generators.h @@ -26,6 +26,7 @@ #include "lib/scheduler/config/sched_config_manager.h" #include "srsran/du/du_cell_config_helpers.h" #include "srsran/ran/duplex_mode.h" +#include "srsran/ran/pucch/pucch_info.h" #include "srsran/scheduler/config/csi_helper.h" #include "srsran/scheduler/config/logical_channel_config_factory.h" #include "srsran/scheduler/config/sched_cell_config_helpers.h" @@ -145,13 +146,13 @@ inline uplink_config make_test_ue_uplink_config(const config_helpers::cell_confi auto& pucch_cfg = ul_config.init_ul_bwp.pucch_cfg.value(); // PUCCH Resource Set ID 0. auto& pucch_res_set_0 = pucch_cfg.pucch_res_set.emplace_back(); - pucch_res_set_0.pucch_res_set_id = 0; + pucch_res_set_0.pucch_res_set_id = pucch_res_set_idx::set_0; pucch_res_set_0.pucch_res_id_list.emplace_back(pucch_res_id_t{0, 0}); pucch_res_set_0.pucch_res_id_list.emplace_back(pucch_res_id_t{1, 1}); pucch_res_set_0.pucch_res_id_list.emplace_back(pucch_res_id_t{2, 2}); auto& pucch_res_set_1 = pucch_cfg.pucch_res_set.emplace_back(); - pucch_res_set_1.pucch_res_set_id = 1; + pucch_res_set_1.pucch_res_set_id = pucch_res_set_idx::set_1; pucch_res_set_1.pucch_res_id_list.emplace_back(pucch_res_id_t{3, 3}); pucch_res_set_1.pucch_res_id_list.emplace_back(pucch_res_id_t{4, 4}); pucch_res_set_1.pucch_res_id_list.emplace_back(pucch_res_id_t{5, 5}); @@ -269,6 +270,12 @@ inline uplink_config make_test_ue_uplink_config(const config_helpers::cell_confi config_helpers::generate_k2_candidates(cyclic_prefix::NORMAL, params.tdd_ul_dl_cfg_common.value()); } + // Compute the max UCI payload per format. + pucch_cfg.format_max_payload[pucch_format_to_uint(pucch_format::FORMAT_1)] = 2U; + const auto& res_f2 = std::get(res_basic_f2.format_params); + pucch_cfg.format_max_payload[pucch_format_to_uint(pucch_format::FORMAT_2)] = get_pucch_format2_max_payload( + res_f2.nof_prbs, res_f2.nof_symbols, to_max_code_rate_float(pucch_cfg.format_2_common_param.value().max_c_rate)); + // > SRS config. ul_config.init_ul_bwp.srs_cfg.emplace(config_helpers::make_default_srs_config(params)); diff --git a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_common_harq_test.cpp b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_common_harq_test.cpp index 8281706577..20991fc42f 100644 --- a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_common_harq_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_common_harq_test.cpp @@ -124,7 +124,9 @@ TEST_P(test_pucch_harq_common_output, test_pucch_output_info) ASSERT_TRUE(pucch_res_indicator.has_value()); ASSERT_FALSE(t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs.empty()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected, t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs.back())); + ASSERT_TRUE( + find_pucch_pdu(t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs, + [&expected = pucch_expected](const auto& pdu) { return pucch_info_match(expected, pdu); })); } // Tests whether PUCCH allocator returns the correct values for the DCI. @@ -310,8 +312,9 @@ TEST_F(test_pucch_harq_common_multiple_allocation, test_on_full_grid) ASSERT_TRUE(pucch_res_indicator_1.has_value()); ASSERT_FALSE(t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs.empty()); - const pucch_info& pucch_pdu_test = t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs.back(); ASSERT_EQ(0, pucch_res_indicator_1.value()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_pdu_benchmark, pucch_pdu_test)); + ASSERT_TRUE( + find_pucch_pdu(t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs, + [&expected = pucch_pdu_benchmark](const auto& pdu) { return pucch_info_match(expected, pdu); })); } diff --git a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp index 10aa3da3c8..cd93716eec 100644 --- a/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/pucch_alloc_harq_sr_csi_test.cpp @@ -275,7 +275,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_sr_allocation_only) slot_grid, t_bench.get_main_ue().crnti, t_bench.get_main_ue().get_pcell().cfg()); ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_sr, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f1_sr](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_allocator_ded_resources, test_sr_allocation_when_no_free_sr_resources) @@ -307,8 +309,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_sr_alloc_over_common_harq_grant) t_bench.pucch_alloc.pucch_allocate_sr_opportunity( slot_grid, t_bench.get_main_ue().crnti, t_bench.get_main_ue().get_pcell().cfg()); - ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_sr, slot_grid.result.ul.pucchs[1])); + // The SR won't be allocated if there is an existing PUCCH common grant. It is possible to have both SR and HARQ if + // the SR is scheduled first. + ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); } /////////////// Tests PUCCH allocator for CSI. @@ -326,7 +329,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_csi_alloc_only) // Expect 1 PUCCH PDU. ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_csi, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_csi](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); ASSERT_TRUE(slot_grid.result.ul.pucchs[0].csi_rep_cfg.has_value()); } @@ -345,7 +350,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_csi_alloc_over_sr) // Expect 1 PUCCH PDU. ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_csi, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_csi](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); ASSERT_TRUE(slot_grid.result.ul.pucchs[0].csi_rep_cfg.has_value()); } @@ -391,10 +398,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_csi_alloc_over_common_harq_grant t_bench.pucch_alloc.pucch_allocate_csi_opportunity( slot_grid, t_bench.get_main_ue().crnti, t_bench.get_main_ue().get_pcell().cfg(), csi_part1_bits); - // Expect 2 PUCCH PDU; the second one should be Format 2 for CSI only. - ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_csi, slot_grid.result.ul.pucchs[1])); - ASSERT_TRUE(slot_grid.result.ul.pucchs[1].csi_rep_cfg.has_value()); + // Expect 1 PUCCH PDU, as the CSI over common PUCCH should be scheduled following CSI first, then the PUCCH common, + // not the other way around. + ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); } /////// Test HARQ-ACK allocation on ded. resources - Format 1 /////// @@ -407,9 +413,10 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_allocation_only) ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator); - const unsigned EXPECTED_HARQ_GRANTS = 1; - ASSERT_EQ(EXPECTED_HARQ_GRANTS, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs.back())); + ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f1_harq](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_allocator_ded_resources, test_harq_allocation_over_sr) @@ -423,10 +430,11 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_allocation_over_sr) ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[1])); - // Verify that the SR grants gets updated. + // Verify that the UCI bits grants are correct. ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[0].format_1.sr_bits); + ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[1].format_1.sr_bits); } TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_2bits) @@ -441,7 +449,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_2bits) ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f1_harq](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_2bits_over_sr) @@ -457,10 +467,11 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_2bits_over_sr) ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[1])); - const unsigned EXPECTED_HARQ_BITS_IN_SR = 2; - ASSERT_EQ(EXPECTED_HARQ_BITS_IN_SR, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); + const unsigned EXPECTED_HARQ_BITS = 2; + ASSERT_EQ(EXPECTED_HARQ_BITS, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[0].format_1.sr_bits); + ASSERT_EQ(EXPECTED_HARQ_BITS, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); + ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[1].format_1.sr_bits); } /////// Test HARQ-ACK allocation on ded. resources - Format 1 - Multi UEs /////// @@ -569,14 +580,27 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_3bits_over_sr) ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f2, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f2](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_3bits_over_csi) { + // We don't know a-priori whether CSI and HARQ will be multilplexed within the same resource; we need to consider both + // possibilities, (i) 2 separate PUCCH resources HARQ + CSI, and (ii) 1 PUCCH resource with both HARQ and CSI. pucch_expected_f2.format_2.harq_ack_nof_bits = 3; pucch_expected_f2.format_2.sr_bits = sr_nof_bits::no_sr; - pucch_expected_f2.format_2.csi_part1_bits = 4; + pucch_expected_f2.format_2.csi_part1_bits = 0; + + pucch_expected_csi.format_2.harq_ack_nof_bits = 0; + pucch_expected_csi.format_2.sr_bits = sr_nof_bits::no_sr; + pucch_expected_csi.format_2.csi_part1_bits = 4; + + pucch_info pucch_f2_harq_csi_mplexed = pucch_expected_f2; + pucch_f2_harq_csi_mplexed.format_2.harq_ack_nof_bits = 3; + pucch_f2_harq_csi_mplexed.format_2.sr_bits = sr_nof_bits::no_sr; + pucch_f2_harq_csi_mplexed.format_2.csi_part1_bits = 4; add_csi_grant(); add_harq_grant(); @@ -588,13 +612,25 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_3bits_over_csi) // Expect 1 HARQ and 1 SR. ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); - ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f2, slot_grid.result.ul.pucchs[0])); - ASSERT_TRUE(slot_grid.result.ul.pucchs[0].csi_rep_cfg.has_value()); + ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); + + const auto& pucch_pdus = slot_grid.result.ul.pucchs; + ASSERT_TRUE(find_pucch_pdu(pucch_pdus, [](const auto& pdu) { return pdu.csi_rep_cfg.has_value(); })); + + ASSERT_TRUE( + find_pucch_pdu(pucch_pdus, + [&expected = pucch_expected_f2](const auto& pdu) { return pucch_info_match(expected, pdu); }) or + find_pucch_pdu(pucch_pdus, + [&expected = pucch_expected_csi](const auto& pdu) { return pucch_info_match(expected, pdu); }) or + find_pucch_pdu(pucch_pdus, [&expected = pucch_f2_harq_csi_mplexed](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_3bits_over_sr_and_csi) { + // With SR and with PUCCH Format 1 it is guaranteed that the resources will be multiplexed, as PUCCH Format 1 for SR + // spans over the 14 symbols. pucch_expected_f2.format_2.harq_ack_nof_bits = 3; pucch_expected_f2.format_2.sr_bits = sr_nof_bits::one; pucch_expected_f2.format_2.csi_part1_bits = 4; @@ -611,11 +647,15 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_3bits_over_sr_and_csi ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f2, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f2](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_4bits_over_sr_and_csi) { + // With SR and with PUCCH Format 1 it is guaranteed that the resources will be multiplexed, as PUCCH Format 1 for SR + // spans over the 14 symbols. pucch_expected_f2.format_2.harq_ack_nof_bits = 4; pucch_expected_f2.format_2.sr_bits = sr_nof_bits::one; pucch_expected_f2.format_2.csi_part1_bits = 3; @@ -634,8 +674,10 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_4bits_over_sr_and_csi ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(pucch_res_idx, test_pucch_res_indicator.value()); ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f2, slot_grid.result.ul.pucchs[0])); - ASSERT_TRUE(slot_grid.result.ul.pucchs[0].csi_rep_cfg.has_value()); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f2](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [](const auto& pdu) { return pdu.csi_rep_cfg.has_value(); })); } TEST_F(test_pucch_allocator_ded_resources, test_harq_alloc_4bits_over_sr_and_csi_fails) @@ -672,7 +714,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_without ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[0].format_1.sr_bits); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_f1_harq](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); // PUCCH common resource. ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); @@ -682,6 +726,12 @@ TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_without TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_with_existing_f1_sr) { + pucch_expected_f1_harq.format_1.harq_ack_nof_bits = 1U; + pucch_expected_f1_harq.format_1.sr_bits = sr_nof_bits::one; + + pucch_expected_f1_sr.format_1.harq_ack_nof_bits = 1U; + pucch_expected_f1_sr.format_1.sr_bits = sr_nof_bits::one; + add_sr_grant(); std::optional test_pucch_res_indicator = @@ -694,26 +744,30 @@ TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_with_ex auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + const auto& pucch_pdus = slot_grid.result.ul.pucchs; ASSERT_TRUE(test_pucch_res_indicator.has_value()); ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); - // PUCCH dedicated resource for SR. - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); - ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[0].format_1.sr_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - // PUCCH dedicated resource for HARQ-ACK. - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_f1_harq, slot_grid.result.ul.pucchs[1])); + // All resources are Format 1. + ASSERT_TRUE(std::all_of( + pucch_pdus.begin(), pucch_pdus.end(), [](const auto& pdu) { return pdu.format == pucch_format::FORMAT_1; })); + // We expect 2 PUCCH dedicated resource with HARQ-ACK and SR bits. + ASSERT_TRUE(find_pucch_pdu( + pucch_pdus, [&expected = pucch_expected_f1_harq](const auto& pdu) { return pucch_info_match(expected, pdu); })); + ASSERT_TRUE(find_pucch_pdu( + pucch_pdus, [&expected = pucch_expected_f1_sr](const auto& pdu) { return pucch_info_match(expected, pdu); })); // PUCCH common resource. - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[2].format); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[2].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[2].format_1.sr_bits); - ASSERT_FALSE(slot_grid.result.ul.pucchs[2].resources.second_hop_prbs.empty()); + ASSERT_TRUE(find_pucch_pdu(pucch_pdus, [](const auto& pdu) { + return pdu.format_1.sr_bits == sr_nof_bits::no_sr and pdu.format_1.harq_ack_nof_bits == 1U and + pdu.pdu_context.is_common; + })); } TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_with_existing_f2_csi) { + pucch_expected_f2.format_2.harq_ack_nof_bits = 1U; + pucch_expected_f2.format_2.sr_bits = srsran::sr_nof_bits::no_sr; + pucch_expected_f2.format_2.csi_part1_bits = 4U; + add_csi_grant(); std::optional test_pucch_res_indicator = @@ -726,18 +780,17 @@ TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_resource_with_ex auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; + const auto& pucch_pdus = slot_grid.result.ul.pucchs; ASSERT_TRUE(test_pucch_res_indicator.has_value()); - ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); + ASSERT_EQ(2, pucch_pdus.size()); // PUCCH dedicated resource for HARQ-ACK. - ASSERT_EQ(pucch_format::FORMAT_2, slot_grid.result.ul.pucchs[0].format); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_2.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[0].format_2.sr_bits); - ASSERT_EQ(4, slot_grid.result.ul.pucchs[0].format_2.csi_part1_bits); + ASSERT_TRUE(find_pucch_pdu( + pucch_pdus, [&expected = pucch_expected_f2](const auto& pdu) { return pucch_info_match(expected, pdu); })); // PUCCH common resource. - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); - ASSERT_FALSE(slot_grid.result.ul.pucchs[1].resources.second_hop_prbs.empty()); + ASSERT_TRUE(find_pucch_pdu(pucch_pdus, [](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.format_1.sr_bits == sr_nof_bits::no_sr and + pdu.format_1.harq_ack_nof_bits == 1U and pdu.pdu_context.is_common; + })); } TEST_F(test_pucch_allocator_ded_resources, test_common_plus_ded_res_fails_due_to_no_free_resources) @@ -821,7 +874,9 @@ TEST_F(test_pucch_f2_alloc_several_prbs, test_prb_allocation_csi_only) auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; // Expect 1 PUCCH grant with CSI. ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_csi_only, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_csi_only](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); ASSERT_TRUE(slot_grid.result.ul.pucchs[0].csi_rep_cfg.has_value()); } @@ -837,7 +892,9 @@ TEST_F(test_pucch_f2_alloc_several_prbs, test_prb_allocation_csi_sr) auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; // Expect 1 PUCCH grant with CSI. ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_csi_only, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_csi_only](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); ASSERT_TRUE(slot_grid.result.ul.pucchs[0].csi_rep_cfg.has_value()); } @@ -856,11 +913,23 @@ TEST_F(test_pucch_f2_alloc_several_prbs, test_prb_allocation_harq_only) auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; // Expect 1 PUCCH grant with CSI. ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_harq_only, slot_grid.result.ul.pucchs[0])); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [&expected = pucch_expected_harq_only](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } TEST_F(test_pucch_f2_alloc_several_prbs, test_prb_allocation_harq_csi_only) { + // We don't know a-priori whether CSI and HARQ will be multilplexed within the same resource; we need to consider both + // possibilities, (i) 2 separate PUCCH resources HARQ + CSI, and (ii) 1 PUCCH resource with both HARQ and CSI. + pucch_expected_harq_only.format_2.harq_ack_nof_bits = 5; + pucch_expected_harq_only.format_2.sr_bits = sr_nof_bits::no_sr; + pucch_expected_harq_only.format_2.csi_part1_bits = 0; + + pucch_expected_csi_only.format_2.harq_ack_nof_bits = 0; + pucch_expected_csi_only.format_2.sr_bits = sr_nof_bits::no_sr; + pucch_expected_csi_only.format_2.csi_part1_bits = 4; + pucch_expected_harq_csi.format_2.harq_ack_nof_bits = 5; pucch_expected_harq_csi.format_2.sr_bits = sr_nof_bits::no_sr; pucch_expected_harq_csi.format_2.csi_part1_bits = 4; @@ -872,10 +941,17 @@ TEST_F(test_pucch_f2_alloc_several_prbs, test_prb_allocation_harq_csi_only) add_harq_grant(); add_harq_grant(); - auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; - // Expect 1 PUCCH grant with CSI. - ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_TRUE(assess_ul_pucch_info(pucch_expected_harq_csi, slot_grid.result.ul.pucchs[0])); + const auto& pucch_pdus = t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs; + ASSERT_TRUE(find_pucch_pdu(pucch_pdus, + [&expected = pucch_expected_harq_only](const auto& pdu) { + return pucch_info_match(expected, pdu); + }) or + find_pucch_pdu( + pucch_pdus, + [&expected = pucch_expected_csi_only](const auto& pdu) { return pucch_info_match(expected, pdu); }) or + find_pucch_pdu(pucch_pdus, [&expected = pucch_expected_harq_csi](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } /////// Test HARQ-ACK allocation on ded. resources - Format 2 - Multi UEs /////// @@ -905,7 +981,11 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_f2_alloc_multiple_ues) // Allocate an HARQ-ACK grant with Format 2 for 6 UEs. add_ue_with_format2_harq_grant(); + ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); + add_ue_with_format2_harq_grant(); + ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); + add_ue_with_format2_harq_grant(); add_ue_with_format2_harq_grant(); add_ue_with_format2_harq_grant(); @@ -939,8 +1019,9 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_f2_alloc_last_ue_not_alloca // Allocate an HARQ-ACK grant with Format 2 for UE 0x4601. const std::optional test_pucch_res_indicator = add_format2_harq_grant(); - // 7 PDU expected, as many as the number of UEs; of these, the first 6 are expected to be Format 2, the last one is - // Format 1, as the allocation of the Format 2 for the last UE failed for lack of available PUCCH format 2 resources. + // 7 PDU expected, as many as the number of UEs; of these, the first 6 are expected to be Format 2, the last one + // is Format 1, as the allocation of the Format 2 for the last UE failed for lack of available PUCCH format 2 + // resources. ASSERT_EQ(7, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs.back().format); ASSERT_FALSE(test_pucch_res_indicator.has_value()); @@ -988,28 +1069,6 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_sr_f2_alloc_multiple_ues) ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs.back().format_2.sr_bits); } -TEST_F(test_pucch_allocator_ded_resources, test_harq_csi_f2_alloc_multiple_ues) -{ - auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; - - // Allocate an HARQ-ACK grant with Format 2 for 6 UEs. - add_ue_with_csi_and_harq_f2(); - add_ue_with_csi_and_harq_f2(); - add_ue_with_csi_and_harq_f2(); - add_ue_with_csi_and_harq_f2(); - add_ue_with_csi_and_harq_f2(); - // 5 PDU expected, as many as the number of UEs. - ASSERT_EQ(5, slot_grid.result.ul.pucchs.size()); - - // Allocate an HARQ-ACK grant with Format 2 for UE 0x4601. - add_ue_with_csi_and_harq_f2(); - // 6 PDU expected, as many as the number of UEs. - ASSERT_EQ(6, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_2, slot_grid.result.ul.pucchs.back().format); - ASSERT_EQ(3, slot_grid.result.ul.pucchs.back().format_2.harq_ack_nof_bits); - ASSERT_EQ(4, slot_grid.result.ul.pucchs.back().format_2.csi_part1_bits); -} - /////// Test removal of dedicated PUCCH resources /////// TEST_F(test_pucch_allocator_ded_resources, test_sr_removal) @@ -1024,7 +1083,8 @@ TEST_F(test_pucch_allocator_ded_resources, test_sr_removal) ASSERT_EQ(0, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(0, removed_bits.harq_ack_nof_bits); - ASSERT_EQ(0, removed_bits.csi_part1_bits); + ASSERT_EQ(sr_nof_bits::one, removed_bits.sr_bits); + ASSERT_EQ(0, removed_bits.csi_part1_nof_bits); } TEST_F(test_pucch_allocator_ded_resources, test_csi_removal) @@ -1039,7 +1099,7 @@ TEST_F(test_pucch_allocator_ded_resources, test_csi_removal) ASSERT_EQ(0, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(0, removed_bits.harq_ack_nof_bits); - ASSERT_EQ(4, removed_bits.csi_part1_bits); + ASSERT_EQ(4, removed_bits.csi_part1_nof_bits); } TEST_F(test_pucch_allocator_ded_resources, test_harq_removal) @@ -1054,7 +1114,7 @@ TEST_F(test_pucch_allocator_ded_resources, test_harq_removal) ASSERT_EQ(0, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(1, removed_bits.harq_ack_nof_bits); - ASSERT_EQ(0, removed_bits.csi_part1_bits); + ASSERT_EQ(0, removed_bits.csi_part1_nof_bits); } TEST_F(test_pucch_allocator_ded_resources, test_sr_harq_removal) @@ -1070,7 +1130,7 @@ TEST_F(test_pucch_allocator_ded_resources, test_sr_harq_removal) ASSERT_EQ(0, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(1, removed_bits.harq_ack_nof_bits); - ASSERT_EQ(0, removed_bits.csi_part1_bits); + ASSERT_EQ(0, removed_bits.csi_part1_nof_bits); } TEST_F(test_pucch_allocator_ded_resources, test_csi_harq_removal) @@ -1086,7 +1146,7 @@ TEST_F(test_pucch_allocator_ded_resources, test_csi_harq_removal) ASSERT_EQ(0, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(1, removed_bits.harq_ack_nof_bits); - ASSERT_EQ(4, removed_bits.csi_part1_bits); + ASSERT_EQ(4, removed_bits.csi_part1_nof_bits); } /////// Test allocation over different slots. /////// @@ -1190,29 +1250,36 @@ TEST_F(test_pucch_allocator_ded_resources, test_for_private_fnc_retrieving_exist ASSERT_TRUE(pucch_res_ind_ue1.has_value()); ASSERT_EQ(0, pucch_res_ind_ue1.value()); ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); - ASSERT_EQ(t_bench.get_ue(ue1_idx).crnti, slot_grid.result.ul.pucchs[0].crnti); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue1_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U; + })); std::optional pucch_res_ind_ue0 = t_bench.pucch_alloc.alloc_common_pucch_harq_ack_ue( t_bench.res_grid, t_bench.get_main_ue().crnti, t_bench.k0, k1, t_bench.dci_info); ASSERT_TRUE(pucch_res_ind_ue0.has_value()); ASSERT_EQ(0, pucch_res_ind_ue0.value()); ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); - ASSERT_EQ(t_bench.get_main_ue().crnti, slot_grid.result.ul.pucchs[1].crnti); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_main_ue().crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U and + pdu.pdu_context.is_common; + })); std::optional pucch_res_ind_ue2 = t_bench.pucch_alloc.alloc_ded_pucch_harq_ack_ue( t_bench.res_grid, t_bench.get_ue(ue2_idx).crnti, t_bench.get_ue(ue2_idx).get_pcell().cfg(), t_bench.k0, k1); ASSERT_TRUE(pucch_res_ind_ue2.has_value()); ASSERT_EQ(1, pucch_res_ind_ue2.value()); ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[2].format); - ASSERT_EQ(t_bench.get_ue(ue1_idx).crnti, slot_grid.result.ul.pucchs[0].crnti); - ASSERT_EQ(t_bench.get_main_ue().crnti, slot_grid.result.ul.pucchs[1].crnti); - ASSERT_EQ(t_bench.get_ue(ue2_idx).crnti, slot_grid.result.ul.pucchs[2].crnti); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[2].format_1.harq_ack_nof_bits); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue2_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U; + })); + // Test now that the previous allocations have not been messed up. + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue1_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_main_ue().crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U and + pdu.pdu_context.is_common; + })); // Advance by 1 slot. Allocate: // - 1 PUCCH ded with 1 HARQ-ACK bit to UE 1 (Format 1). @@ -1230,13 +1297,16 @@ TEST_F(test_pucch_allocator_ded_resources, test_for_private_fnc_retrieving_exist ASSERT_TRUE(pucch_res_ind_ue1.has_value()); ASSERT_EQ(0, pucch_res_ind_ue1.value()); ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); - ASSERT_EQ(t_bench.get_ue(ue1_idx).crnti, slot_grid.result.ul.pucchs[0].crnti); - ASSERT_EQ(t_bench.get_main_ue().crnti, slot_grid.result.ul.pucchs[1].crnti); - ASSERT_EQ(t_bench.get_ue(ue2_idx).crnti, slot_grid.result.ul.pucchs[2].crnti); - ASSERT_EQ(2, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[2].format_1.harq_ack_nof_bits); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue1_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 2U; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_main_ue().crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U and + pdu.pdu_context.is_common; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue2_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U; + })); // Advance by 1 slot. Allocate: // - 1 PUCCH ded with 1 HARQ-ACK bit to UE 1 (Convert to Format 2). @@ -1256,15 +1326,16 @@ TEST_F(test_pucch_allocator_ded_resources, test_for_private_fnc_retrieving_exist ASSERT_TRUE(pucch_res_ind_ue1.has_value()); ASSERT_EQ(0, pucch_res_ind_ue1.value()); ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); - ASSERT_EQ(pucch_format::FORMAT_2, slot_grid.result.ul.pucchs[2].format); - ASSERT_EQ(t_bench.get_main_ue().crnti, slot_grid.result.ul.pucchs[0].crnti); - ASSERT_EQ(t_bench.get_ue(ue2_idx).crnti, slot_grid.result.ul.pucchs[1].crnti); - ASSERT_EQ(t_bench.get_ue(ue1_idx).crnti, slot_grid.result.ul.pucchs[2].crnti); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(3, slot_grid.result.ul.pucchs[2].format_1.harq_ack_nof_bits); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue1_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_2 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 3U; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_main_ue().crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U and + pdu.pdu_context.is_common; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue2_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U; + })); // Advance by 1 slot. Allocate: // - 1 PUCCH ded with 1 HARQ-ACK bit to UE 2 (Multiplex on existing Format 1). @@ -1284,15 +1355,16 @@ TEST_F(test_pucch_allocator_ded_resources, test_for_private_fnc_retrieving_exist ASSERT_TRUE(pucch_res_ind_ue2.has_value()); ASSERT_EQ(1, pucch_res_ind_ue2.value()); ASSERT_EQ(3, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[0].format); - ASSERT_EQ(pucch_format::FORMAT_1, slot_grid.result.ul.pucchs[1].format); - ASSERT_EQ(pucch_format::FORMAT_2, slot_grid.result.ul.pucchs[2].format); - ASSERT_EQ(t_bench.get_main_ue().crnti, slot_grid.result.ul.pucchs[0].crnti); - ASSERT_EQ(t_bench.get_ue(ue2_idx).crnti, slot_grid.result.ul.pucchs[1].crnti); - ASSERT_EQ(t_bench.get_ue(ue1_idx).crnti, slot_grid.result.ul.pucchs[2].crnti); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - ASSERT_EQ(2, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(3, slot_grid.result.ul.pucchs[2].format_1.harq_ack_nof_bits); + + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue1_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_2 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 3U; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_main_ue().crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 1U; + })); + ASSERT_TRUE(find_pucch_pdu(slot_grid.result.ul.pucchs, [rnti = t_bench.get_ue(ue2_idx).crnti](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.crnti == rnti and pdu.format_1.harq_ack_nof_bits == 2U; + })); } /////// Test UL grants reached and PUCCH fails. /////// diff --git a/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp b/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp index f2b67c8c5f..32e3db9fd3 100644 --- a/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/pucch_res_manager_test.cpp @@ -72,12 +72,12 @@ class test_pucch_resource_manager : public ::testing::Test if (not format_2) { for (size_t n = 0; n != nof_ues_to_allocate; ++n) { const rnti_t rnti = to_rnti(0x4601 + n); - res_manager.reserve_next_f1_harq_res_available(sl_tx, rnti, pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, rnti, pucch_cfg); } } else { for (size_t n = 0; n != nof_ues_to_allocate; ++n) { const rnti_t rnti = to_rnti(0x4601 + n); - res_manager.reserve_next_f2_harq_res_available(sl_tx, rnti, pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, rnti, pucch_cfg); } } }; @@ -109,131 +109,97 @@ TEST_F(test_pucch_resource_manager, common_res_available_reserve_and_check) } } -// Tests whether PUCCH HARQ grant is allocated with correct PUCCH RESOURCE Indicator; for 1 UE only. -TEST_F(test_pucch_resource_manager, get_next_harq_res_nof_ues_1) +TEST_F(test_pucch_resource_manager, get_available_f1_with_1_ue_only) { const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); ASSERT_EQ(0, record.pucch_res_indicator); ASSERT_EQ(&pucch_cfg.pucch_res_list[0], record.pucch_res); } -TEST_F(test_pucch_resource_manager, get_next_harq_res_nof_ues_2) +TEST_F(test_pucch_resource_manager, get_available_f1_with_2_ues) { allocate_ues(1); const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); ASSERT_EQ(1, record.pucch_res_indicator); ASSERT_EQ(&pucch_cfg.pucch_res_list[1], record.pucch_res); } -TEST_F(test_pucch_resource_manager, get_next_harq_res_nof_ues_3) +TEST_F(test_pucch_resource_manager, get_available_f1_with_3_ues) { allocate_ues(2); const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); ASSERT_EQ(2, record.pucch_res_indicator); ASSERT_EQ(&pucch_cfg.pucch_res_list[2], record.pucch_res); } -// Tests whether PUCCH HARQ grant is allocated with correct PUCCH RESOURCE Indicator; for n UEs. -TEST_F(test_pucch_resource_manager, get_next_harq_res_nof_ues_4) +TEST_F(test_pucch_resource_manager, get_available_f1_with_4_ues) { allocate_ues(3); // Attempt to allocate the PUCCH resource to the 4th UE. const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4604), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4604), pucch_cfg); - // Verify that the 4th UE did not get any resource assigned. ASSERT_EQ(nullptr, record.pucch_res); } -// Tests allocation in different slots. TEST_F(test_pucch_resource_manager, get_next_harq_different_slot) { allocate_ues(1); ++sl_tx; const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); // Expect that pucch_res_indicator = 0 is returned, as the UE 0x4602 is allocated in a different slot to UE 0x4601. ASSERT_EQ(0, record.pucch_res_indicator); ASSERT_EQ(&pucch_cfg.pucch_res_list[0], record.pucch_res); } -// Tests slot indication for PUCCH resource manager. -TEST_F(test_pucch_resource_manager, slot_indication) -{ - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); - - // Increment slot point and invoke slot_indication(), which should reset the previous UE's resource allocation. - ++sl_tx; - res_manager.slot_indication(sl_tx); - - // Slot point pointing at the last slot, that has been cleared by that slot_indication(). - const slot_point old_slot{0, sl_tx.to_uint() - 1}; - const int res_id = res_manager.fetch_f1_pucch_res_indic(old_slot, to_rnti(0x4601), pucch_cfg); - - // Expect that pucch_res_indicator = -1 is returned (due to the slot_indication() resetting the resource records for - // old slots). - ASSERT_EQ(-1, res_id); -} - -// Tests allocation and removal of PUCCH resource format 1 for 1 UE. TEST_F(test_pucch_resource_manager, allocate_and_release_f1) { const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); ASSERT_EQ(0, record.pucch_res_indicator); ASSERT_EQ(&pucch_cfg.pucch_res_list[0], record.pucch_res); // Release the resource and verify the UE does not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); // Re-allocate the resource. const pucch_harq_resource_alloc_record reallocation = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); ASSERT_EQ(0, reallocation.pucch_res_indicator); ASSERT_EQ(record.pucch_res, reallocation.pucch_res); } -// Tests allocation and removal of PUCCH resource format 1 for multiple UEs. -TEST_F(test_pucch_resource_manager, allocate_and_release_multiple_f1ap_ues) +TEST_F(test_pucch_resource_manager, allocate_and_release_multiple_ues) { // Allocate 3 UEs. allocate_ues(3); - // Check whether the UEs get returned the corresponding PUCCH resource indicator. - ASSERT_EQ(0, res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_EQ(2, res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4603), pucch_cfg)); - - // Release the resource and verify that the UEs do not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, to_rnti(0x4603), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4603), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, to_rnti(0x4603), pucch_cfg)); // Re-allocate the resources to UE1 and UE3. const pucch_harq_resource_alloc_record realloc_ue1 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); const pucch_harq_resource_alloc_record realloc_ue3 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); - // Check whether the UEs get returned (again) the corresponding PUCCH resource indicator. ASSERT_EQ(0, realloc_ue1.pucch_res_indicator); ASSERT_EQ(2, realloc_ue3.pucch_res_indicator); } -// Tests whether PUCCH HARQ grant is allocated with correct PUCCH RESOURCE Indicator; for 1 UE only. -TEST_F(test_pucch_resource_manager, allocate_1_ue_res_f2) +TEST_F(test_pucch_resource_manager, allocate_resources_f2_1_ue) { const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); ASSERT_EQ(0, record.pucch_res_indicator); const unsigned res_idx_from_list = @@ -241,12 +207,11 @@ TEST_F(test_pucch_resource_manager, allocate_1_ue_res_f2) ASSERT_EQ(&pucch_cfg.pucch_res_list[res_idx_from_list], record.pucch_res); } -// Tests whether PUCCH HARQ grant is allocated with correct PUCCH RESOURCE Indicator; for n UEs. -TEST_F(test_pucch_resource_manager, allocate_2_ue_res_f2) +TEST_F(test_pucch_resource_manager, allocate_resources_f2_2_ues) { allocate_ues(1, /* format_2= */ true); const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); ASSERT_EQ(1, record.pucch_res_indicator); const unsigned res_idx_from_list = @@ -254,12 +219,11 @@ TEST_F(test_pucch_resource_manager, allocate_2_ue_res_f2) ASSERT_EQ(&pucch_cfg.pucch_res_list[res_idx_from_list], record.pucch_res); } -// Tests whether PUCCH HARQ grant is allocated with correct PUCCH RESOURCE Indicator; for n UEs. -TEST_F(test_pucch_resource_manager, allocate_3_ue_res_f2) +TEST_F(test_pucch_resource_manager, allocate_resources_f2_3_ues) { allocate_ues(2, /* format_2= */ true); const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); ASSERT_EQ(2, record.pucch_res_indicator); const unsigned res_idx_from_list = @@ -267,11 +231,11 @@ TEST_F(test_pucch_resource_manager, allocate_3_ue_res_f2) ASSERT_EQ(&pucch_cfg.pucch_res_list[res_idx_from_list], record.pucch_res); } -TEST_F(test_pucch_resource_manager, allocate_8_ue_res_f2) +TEST_F(test_pucch_resource_manager, allocate_resources_f2_8_ues) { allocate_ues(7, /* format_2= */ true); const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4608), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4608), pucch_cfg); // Expect the 8th UE not to get assigned anything, as there are only 7 resources available. ASSERT_EQ(nullptr, record.pucch_res); @@ -283,14 +247,11 @@ TEST_F(test_pucch_resource_manager, allocate_csi_resource) const pucch_resource* res = res_manager.reserve_csi_resource(sl_tx, to_rnti(0x4601), ue_cell_cfg); ASSERT_EQ(&pucch_cfg.pucch_res_list[expected_csi_res_index], res); - // Check that the Resource hgets actually stored in the resource manager. - ASSERT_EQ(&pucch_cfg.pucch_res_list[expected_csi_res_index], - res_manager.fetch_csi_pucch_res_config(sl_tx, to_rnti(0x4601), ue_cell_cfg)); } TEST_F(test_pucch_resource_manager, release_and_reallocate_csi_resource) { - // There is no allcoated resource, expects false from the release. + // There is no allocated resource, expects false from the release. ASSERT_FALSE(res_manager.release_csi_resource(sl_tx, to_rnti(0x4601), ue_cell_cfg)); const unsigned expected_csi_res_index = 9; @@ -299,7 +260,7 @@ TEST_F(test_pucch_resource_manager, release_and_reallocate_csi_resource) const pucch_resource* res_second_allc_no_release = res_manager.reserve_csi_resource(sl_tx, to_rnti(0x4601), ue_cell_cfg); - ASSERT_EQ(nullptr, res_second_allc_no_release); + ASSERT_EQ(res, res_second_allc_no_release); // This time the release it supposed to return true. ASSERT_TRUE(res_manager.release_csi_resource(sl_tx, to_rnti(0x4601), ue_cell_cfg)); @@ -308,13 +269,12 @@ TEST_F(test_pucch_resource_manager, release_and_reallocate_csi_resource) ASSERT_EQ(&pucch_cfg.pucch_res_list[expected_csi_res_index], res_reallocation); } -// Tests allocation in different slots. TEST_F(test_pucch_resource_manager, get_format2_different_slot) { allocate_ues(1); ++sl_tx; const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4602), pucch_cfg); // Expect that pucch_res_indicator = 0 is returned, as the UE 0x4602 is allocated in a different slot to UE 0x4601. ASSERT_EQ(0, record.pucch_res_indicator); @@ -323,54 +283,43 @@ TEST_F(test_pucch_resource_manager, get_format2_different_slot) ASSERT_EQ(&pucch_cfg.pucch_res_list[res_idx_from_list], record.pucch_res); } -// Tests allocation and release of resources for 1 UE. TEST_F(test_pucch_resource_manager, allocate_and_release_f2) { const pucch_harq_resource_alloc_record record = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); ASSERT_EQ(0, record.pucch_res_indicator); unsigned res_idx_from_list = pucch_cfg.pucch_res_set[1].pucch_res_id_list[record.pucch_res_indicator].cell_res_id; ASSERT_EQ(&pucch_cfg.pucch_res_list[res_idx_from_list], record.pucch_res); // Release the resource and verify the UE does not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); // Re-allocate the resource. const pucch_harq_resource_alloc_record reallocation = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); ASSERT_EQ(0, reallocation.pucch_res_indicator); res_idx_from_list = pucch_cfg.pucch_res_set[1].pucch_res_id_list[record.pucch_res_indicator].cell_res_id; ASSERT_EQ(record.pucch_res, reallocation.pucch_res); } -// Tests allocation and release of resources for multiple UE. TEST_F(test_pucch_resource_manager, allocate_and_release_f2_multiple_ues) { // Allocate 6 UEs. allocate_ues(6, /* format_2*/ true); - // Check whether the UEs get returned the corresponding PUCCH resource indicator. - ASSERT_EQ(0, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_EQ(2, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4603), pucch_cfg)); - ASSERT_EQ(5, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4606), pucch_cfg)); - // Release the resource and verify that the UEs do not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, to_rnti(0x4603), pucch_cfg)); - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, to_rnti(0x4606), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4603), pucch_cfg)); - ASSERT_EQ(-1, res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4606), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, to_rnti(0x4601), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, to_rnti(0x4603), pucch_cfg)); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, to_rnti(0x4606), pucch_cfg)); // Re-allocate the resources to UE1, UE3, UE6. const pucch_harq_resource_alloc_record realloc_ue1 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); const pucch_harq_resource_alloc_record realloc_ue3 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4603), pucch_cfg); const pucch_harq_resource_alloc_record realloc_ue6 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4606), pucch_cfg); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, to_rnti(0x4606), pucch_cfg); // Check whether the UEs get returned (again) the corresponding PUCCH resource indicator. ASSERT_EQ(0, realloc_ue1.pucch_res_indicator); @@ -378,25 +327,6 @@ TEST_F(test_pucch_resource_manager, allocate_and_release_f2_multiple_ues) ASSERT_EQ(5, realloc_ue6.pucch_res_indicator); } -// Tests slot indication for PUCCH resource manager, format 2. -TEST_F(test_pucch_resource_manager, slot_indication_format2) -{ - res_manager.reserve_next_f2_harq_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); - - // Increment slot point and invoke slot_indication(), which should reset the previous UE's resource allocation. - ++sl_tx; - res_manager.slot_indication(sl_tx); - - // Slot point pointing at the last slot, that has been cleared by that slot_indication(). - const slot_point old_slot{0, sl_tx.to_uint() - 1}; - const int res_id = res_manager.fetch_f2_pucch_res_indic(old_slot, to_rnti(0x4601), pucch_cfg); - - // Expect that pucch_res_indicator = -1 is returned (due to the slot_indication() resetting the resource records for - // old slots). - ASSERT_EQ(-1, res_id); -} - -// Tests allocation of SR resource. TEST_F(test_pucch_resource_manager, test_allocation_sr_resource) { const pucch_resource* sr_resource = res_manager.reserve_sr_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); @@ -405,7 +335,6 @@ TEST_F(test_pucch_resource_manager, test_allocation_sr_resource) ASSERT_EQ(&pucch_cfg.pucch_res_list[sr_pucch_res_idx], sr_resource); } -// Tests release of SR resource. TEST_F(test_pucch_resource_manager, test_allocation_release_sr_resource) { res_manager.reserve_sr_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); @@ -420,7 +349,6 @@ TEST_F(test_pucch_resource_manager, test_allocation_release_sr_resource) ASSERT_EQ(&pucch_cfg.pucch_res_list[sr_pucch_res_idx], sr_resource_ue2); } -// Tests SRs for 2 UEs using different PUCCH resource indices. TEST_F(test_pucch_resource_manager, test_allocation_2_sr_resource) { res_manager.reserve_sr_res_available(sl_tx, to_rnti(0x4601), pucch_cfg); @@ -444,16 +372,16 @@ TEST_F(test_pucch_resource_manager, test_allocation_specific_f1) const unsigned res_indicator = 2; // Attempt to allocate PUCCH resource Format 2 with given resource indicator. - ASSERT_TRUE(nullptr != res_manager.reserve_f1_res_by_res_indicator(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); - - // Verify the resource can be retrieved. - ASSERT_EQ(static_cast(res_indicator), res_manager.fetch_f1_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); + ASSERT_TRUE(nullptr != + res_manager.reserve_set_0_res_by_res_indicator(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); // Attempt to allocate another UE to the same resource and verify it gets returned nullptr. - ASSERT_TRUE(nullptr == res_manager.reserve_f1_res_by_res_indicator(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); + ASSERT_TRUE(nullptr == + res_manager.reserve_set_0_res_by_res_indicator(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); // Attempt to allocate a third UE with wrong resource indicator and verify it gets returned nullptr. - ASSERT_TRUE(nullptr == res_manager.reserve_f1_res_by_res_indicator(sl_tx, to_rnti(0x4603), res_indicator, pucch_cfg)); + ASSERT_TRUE(nullptr == + res_manager.reserve_set_0_res_by_res_indicator(sl_tx, to_rnti(0x4603), res_indicator, pucch_cfg)); } TEST_F(test_pucch_resource_manager, test_allocation_specific_f2) @@ -461,16 +389,16 @@ TEST_F(test_pucch_resource_manager, test_allocation_specific_f2) const unsigned res_indicator = 3; // Attempt to allocate PUCCH resource Format 2 with given resource indicator. - ASSERT_TRUE(nullptr != res_manager.reserve_f2_res_by_res_indicator(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); - - // Verify the resource can be retrieved. - ASSERT_EQ(static_cast(res_indicator), res_manager.fetch_f2_pucch_res_indic(sl_tx, to_rnti(0x4601), pucch_cfg)); + ASSERT_TRUE(nullptr != + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, to_rnti(0x4601), res_indicator, pucch_cfg)); // Attempt to allocate another UE to the same resource and verify it gets returned nullptr. - ASSERT_TRUE(nullptr == res_manager.reserve_f2_res_by_res_indicator(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); + ASSERT_TRUE(nullptr == + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, to_rnti(0x4602), res_indicator, pucch_cfg)); // Attempt to allocate a third UE with wrong resource indicator and verify it gets returned nullptr. - ASSERT_TRUE(nullptr == res_manager.reserve_f2_res_by_res_indicator(sl_tx, to_rnti(0x4603), res_indicator, pucch_cfg)); + ASSERT_TRUE(nullptr == + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, to_rnti(0x4603), res_indicator, pucch_cfg)); } //////////// Test the PUCCH resource manager: UEs with different configs //////////// @@ -488,7 +416,7 @@ class test_pucch_res_manager_multiple_cfg : public test_pucch_resource_manager }; - const pucch_config& get_pucch_cfg() const + [[nodiscard]] const pucch_config& get_pucch_cfg() const { return ue_cell_cfg.cfg_dedicated().ul_config.value().init_ul_bwp.pucch_cfg.value(); } @@ -515,8 +443,8 @@ class test_pucch_res_manager_multiple_cfg : public test_pucch_resource_manager default_pucch_cfg.pucch_res_set[1].pucch_res_id_list.clear(); // Ensure the PUCCH resource sets ID are 0 and 1. - default_pucch_cfg.pucch_res_set[0].pucch_res_set_id = 0; - default_pucch_cfg.pucch_res_set[1].pucch_res_set_id = 1; + default_pucch_cfg.pucch_res_set[0].pucch_res_set_id = srsran::pucch_res_set_idx::set_0; + default_pucch_cfg.pucch_res_set[1].pucch_res_set_id = srsran::pucch_res_set_idx::set_1; const unsigned tot_ue_f1_res = nof_ue_pucch_f1_res_harq + nof_ue_pucch_f1_res_sr; const unsigned tot_ue_f2_res = nof_ue_pucch_f2_res_harq + nof_ue_pucch_f2_res_csi; @@ -586,12 +514,12 @@ class test_pucch_res_manager_multiple_cfg : public test_pucch_resource_manager void allocate_f1_specific_ue(unsigned ue_idx) { - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_idx]->cnrti, ues[ue_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_idx]->cnrti, ues[ue_idx]->get_pucch_cfg()); } void allocate_f2_specific_ue(unsigned ue_idx) { - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_idx]->cnrti, ues[ue_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_idx]->cnrti, ues[ue_idx]->get_pucch_cfg()); } unsigned nof_res_per_ue{8}; @@ -606,7 +534,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_f1_only) // UE 0 and 1 will get assigned the same pucch_res_indicator, as they use different PUCCH configs. const unsigned ue_0_idx = 0; const pucch_harq_resource_alloc_record record_ue_0 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_0.pucch_res_indicator); ASSERT_EQ(&ues[ue_0_idx]->get_pucch_cfg().pucch_res_list[0], record_ue_0.pucch_res); @@ -614,7 +542,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_f1_only) const unsigned ue_1_idx = 1; const pucch_harq_resource_alloc_record record_ue_1 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_1.pucch_res_indicator); ASSERT_EQ(&ues[ue_1_idx]->get_pucch_cfg().pucch_res_list[0], record_ue_1.pucch_res); @@ -623,14 +551,14 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_f1_only) // UE 2 and 3 will get assigned the different pucch_res_indicator from UE 0 and 1, as they share the PUCCH configs. const unsigned ue_2_idx = 2; const pucch_harq_resource_alloc_record record_ue_2 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_2_idx]->cnrti, ues[ue_2_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_2_idx]->cnrti, ues[ue_2_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_2.pucch_res_indicator); ASSERT_EQ(&ues[ue_2_idx]->get_pucch_cfg().pucch_res_list[1], record_ue_2.pucch_res); ASSERT_EQ(1, record_ue_2.pucch_res->res_id.cell_res_id); const unsigned ue_3_idx = 3; const pucch_harq_resource_alloc_record record_ue_3 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_3_idx]->cnrti, ues[ue_3_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_3_idx]->cnrti, ues[ue_3_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_3.pucch_res_indicator); ASSERT_EQ(&ues[ue_3_idx]->get_pucch_cfg().pucch_res_list[1], record_ue_3.pucch_res); @@ -644,7 +572,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_f2_only) // UE 0 and 1 will get assigned the same pucch_res_indicator, as they use different PUCCH configs. const unsigned ue_0_idx = 0; const pucch_harq_resource_alloc_record record_ue_0 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_0.pucch_res_indicator); // The first F2 resource is has index 9 within the UE pucch_res_list (after 8+1 PUCCH F1). @@ -653,7 +581,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_f2_only) const unsigned ue_1_idx = 1; const pucch_harq_resource_alloc_record record_ue_1 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_1.pucch_res_indicator); // The first F2 resource is has index 9 within the UE pucch_res_list (after 8+1 PUCCH F1). @@ -663,18 +591,18 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_f2_only) // UE 2 and 3 will get assigned the different pucch_res_indicator from UE 0 and 1, as they share the PUCCH configs. const unsigned ue_2_idx = 2; const pucch_harq_resource_alloc_record record_ue_2 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_2_idx]->cnrti, ues[ue_2_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_2_idx]->cnrti, ues[ue_2_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_2.pucch_res_indicator); - // The second F2 resource is has index 10 within the UE pucch_res_list (first 8+1 PUCCH F1, then 1 F2). + // The second F2 resource has index 10 within the UE pucch_res_list (first 8+1 PUCCH F1, then 1 F2). ASSERT_EQ(&ues[ue_2_idx]->get_pucch_cfg().pucch_res_list[10], record_ue_2.pucch_res); ASSERT_EQ(19, record_ue_2.pucch_res->res_id.cell_res_id); const unsigned ue_3_idx = 3; const pucch_harq_resource_alloc_record record_ue_3 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_3_idx]->cnrti, ues[ue_3_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_3_idx]->cnrti, ues[ue_3_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_3.pucch_res_indicator); - // The second F2 resource is has index 10 within the UE pucch_res_list (first 8+1 PUCCH F1, then 1 F2). + // The second F2 resource has index 10 within the UE pucch_res_list (first 8+1 PUCCH F1, then 1 F2). ASSERT_EQ(&ues[ue_3_idx]->get_pucch_cfg().pucch_res_list[10], record_ue_3.pucch_res); ASSERT_EQ(28, record_ue_3.pucch_res->res_id.cell_res_id); } @@ -686,7 +614,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_fetch_releas // UE 0 and 1 will get assigned the same pucch_res_indicator, as they use different PUCCH configs. const unsigned ue_0_idx = 0; const pucch_harq_resource_alloc_record record_ue_0 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_0.pucch_res_indicator); ASSERT_EQ(&ues[ue_0_idx]->get_pucch_cfg().pucch_res_list[0], record_ue_0.pucch_res); @@ -694,27 +622,14 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_fetch_releas const unsigned ue_1_idx = 1; const pucch_harq_resource_alloc_record record_ue_1 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_1.pucch_res_indicator); ASSERT_EQ(&ues[ue_1_idx]->get_pucch_cfg().pucch_res_list[0], record_ue_1.pucch_res); ASSERT_EQ(9, record_ue_1.pucch_res->res_id.cell_res_id); - const int res_indicator_ue_0 = - res_manager.fetch_f1_pucch_res_indic(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); - ASSERT_EQ(0, res_indicator_ue_0); - - const int res_indicator_ue_1 = - res_manager.fetch_f1_pucch_res_indic(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); - ASSERT_EQ(0, res_indicator_ue_1); - - // Release the resource and verify the UE does not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg())); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg())); - - // Release the resource and verify the UE does not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg())); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg())); } TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_fetch_release_f2) @@ -724,7 +639,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_fetch_releas // UE 0 and 1 will get assigned the same pucch_res_indicator, as they use different PUCCH configs. const unsigned ue_0_idx = 0; const pucch_harq_resource_alloc_record record_ue_0 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_0.pucch_res_indicator); // The first F2 resource is has index 9 within the UE pucch_res_list (after 8+1 PUCCH F1). @@ -733,28 +648,18 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_fetch_releas const unsigned ue_1_idx = 1; const pucch_harq_resource_alloc_record record_ue_1 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); ASSERT_EQ(0, record_ue_1.pucch_res_indicator); // The first F2 resource is has index 9 within the UE pucch_res_list (after 8+1 PUCCH F1). ASSERT_EQ(&ues[ue_1_idx]->get_pucch_cfg().pucch_res_list[9], record_ue_1.pucch_res); ASSERT_EQ(27, record_ue_1.pucch_res->res_id.cell_res_id); - const int res_indicator_ue_0 = - res_manager.fetch_f2_pucch_res_indic(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg()); - ASSERT_EQ(0, res_indicator_ue_0); - - const int res_indicator_ue_1 = - res_manager.fetch_f2_pucch_res_indic(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg()); - ASSERT_EQ(0, res_indicator_ue_1); - // Release the resource and verify the UE does not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg())); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, ues[ue_0_idx]->cnrti, ues[ue_0_idx]->get_pucch_cfg())); // Release the resource and verify the UE does not hold it anymore. - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg())); - ASSERT_EQ(-1, res_manager.fetch_f1_pucch_res_indic(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, ues[ue_1_idx]->cnrti, ues[ue_1_idx]->get_pucch_cfg())); } TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_specific_f2) @@ -764,14 +669,14 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_specific_f2) // UE 0 and 1 will get assigned the same pucch_res_indicator, as they use different PUCCH configs. const unsigned ue_0_idx = 0; const pucch_resource* res_ue_0 = - res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_0_idx]->cnrti, 5, ues[ue_0_idx]->get_pucch_cfg()); + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, ues[ue_0_idx]->cnrti, 5, ues[ue_0_idx]->get_pucch_cfg()); ASSERT_EQ(&ues[ue_0_idx]->get_pucch_cfg().pucch_res_list[14], res_ue_0); ASSERT_EQ(23, res_ue_0->res_id.cell_res_id); const unsigned ue_1_idx = 1; const pucch_resource* res_ue_1 = - res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_1_idx]->cnrti, 5, ues[ue_1_idx]->get_pucch_cfg()); + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, ues[ue_1_idx]->cnrti, 5, ues[ue_1_idx]->get_pucch_cfg()); ASSERT_EQ(&ues[ue_1_idx]->get_pucch_cfg().pucch_res_list[14], res_ue_1); ASSERT_EQ(32, res_ue_1->res_id.cell_res_id); @@ -779,12 +684,12 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_2_ues_2_cfgs_alloc_specific_f2) // Try to allocate the same PUCCH resource (already reserved to UE 0 and 1) and check that the allocation fails. const unsigned ue_2_idx = 2; const pucch_resource* res_ue_2 = - res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_2_idx]->cnrti, 5, ues[ue_2_idx]->get_pucch_cfg()); + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, ues[ue_2_idx]->cnrti, 5, ues[ue_2_idx]->get_pucch_cfg()); ASSERT_EQ(nullptr, res_ue_2); const unsigned ue_3_idx = 3; const pucch_resource* res_ue_3 = - res_manager.reserve_f2_res_by_res_indicator(sl_tx, ues[ue_3_idx]->cnrti, 5, ues[ue_3_idx]->get_pucch_cfg()); + res_manager.reserve_set_1_res_by_res_indicator(sl_tx, ues[ue_3_idx]->cnrti, 5, ues[ue_3_idx]->get_pucch_cfg()); ASSERT_EQ(nullptr, res_ue_3); } @@ -798,7 +703,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_8_ues_2_cfgs_allocate_all_resou allocate_f1_specific_ue(/* ue idx */ 4); pucch_harq_resource_alloc_record record_ue_6 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[6]->cnrti, ues[6]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[6]->cnrti, ues[6]->get_pucch_cfg()); ASSERT_EQ(nullptr, record_ue_6.pucch_res); allocate_f1_specific_ue(/* ue idx */ 1); @@ -806,22 +711,22 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_8_ues_2_cfgs_allocate_all_resou allocate_f1_specific_ue(/* ue idx */ 5); pucch_harq_resource_alloc_record record_ue_7 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[7]->cnrti, ues[7]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[7]->cnrti, ues[7]->get_pucch_cfg()); ASSERT_EQ(nullptr, record_ue_7.pucch_res); // Release one resource and check the next allocation is successful. - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, ues[2]->cnrti, ues[2]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, ues[2]->cnrti, ues[2]->get_pucch_cfg())); const unsigned ue_6_idx = 6; record_ue_6 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_6_idx]->cnrti, ues[ue_6_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_6_idx]->cnrti, ues[ue_6_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_6.pucch_res_indicator); ASSERT_EQ(&ues[ue_6_idx]->get_pucch_cfg().pucch_res_list[1], record_ue_6.pucch_res); ASSERT_EQ(1, record_ue_6.pucch_res->res_id.cell_res_id); - ASSERT_TRUE(res_manager.release_harq_f1_resource(sl_tx, ues[3]->cnrti, ues[3]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_0_resource(sl_tx, ues[3]->cnrti, ues[3]->get_pucch_cfg())); const unsigned ue_7_idx = 7; record_ue_7 = - res_manager.reserve_next_f1_harq_res_available(sl_tx, ues[ue_7_idx]->cnrti, ues[ue_7_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_0_harq_res_available(sl_tx, ues[ue_7_idx]->cnrti, ues[ue_7_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_7.pucch_res_indicator); ASSERT_EQ(&ues[ue_7_idx]->get_pucch_cfg().pucch_res_list[1], record_ue_7.pucch_res); ASSERT_EQ(5, record_ue_7.pucch_res->res_id.cell_res_id); @@ -837,7 +742,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_8_ues_2_cfgs_allocate_all_resou allocate_f2_specific_ue(/* ue idx */ 4); pucch_harq_resource_alloc_record record_ue_6 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[6]->cnrti, ues[6]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[6]->cnrti, ues[6]->get_pucch_cfg()); ASSERT_EQ(nullptr, record_ue_6.pucch_res); allocate_f2_specific_ue(/* ue idx */ 1); @@ -845,14 +750,14 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_8_ues_2_cfgs_allocate_all_resou allocate_f2_specific_ue(/* ue idx */ 5); pucch_harq_resource_alloc_record record_ue_7 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[7]->cnrti, ues[7]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[7]->cnrti, ues[7]->get_pucch_cfg()); ASSERT_EQ(nullptr, record_ue_7.pucch_res); // Release one resource and check the next allocation is successful. - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, ues[2]->cnrti, ues[2]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, ues[2]->cnrti, ues[2]->get_pucch_cfg())); const unsigned ue_6_idx = 6; record_ue_6 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_6_idx]->cnrti, ues[ue_6_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_6_idx]->cnrti, ues[ue_6_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_6.pucch_res_indicator); // The F2 resource corresponding to pucch_res_indicator = 1 has index 5 within the UE pucch_res_list (after 3+1 PUCCH // F1, then 1 F2). @@ -860,10 +765,10 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_8_ues_2_cfgs_allocate_all_resou // The F2 resource corresponding to pucch_res_indicator = 1 has res ID 9. ASSERT_EQ(9, record_ue_6.pucch_res->res_id.cell_res_id); - ASSERT_TRUE(res_manager.release_harq_f2_resource(sl_tx, ues[3]->cnrti, ues[3]->get_pucch_cfg())); + ASSERT_TRUE(res_manager.release_harq_set_1_resource(sl_tx, ues[3]->cnrti, ues[3]->get_pucch_cfg())); const unsigned ue_7_idx = 7; record_ue_7 = - res_manager.reserve_next_f2_harq_res_available(sl_tx, ues[ue_7_idx]->cnrti, ues[ue_7_idx]->get_pucch_cfg()); + res_manager.reserve_next_set_1_harq_res_available(sl_tx, ues[ue_7_idx]->cnrti, ues[ue_7_idx]->get_pucch_cfg()); ASSERT_EQ(1, record_ue_7.pucch_res_indicator); // The F2 resource corresponding to pucch_res_indicator = 1 has index 5 within the UE pucch_res_list (after 3+1 PUCCH // F1, then 1 F2). @@ -895,7 +800,7 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_4_ues_2_cfgs_allocate_sr) const pucch_resource* sr_resource_ue1 = res_manager.reserve_sr_res_available(sl_tx, ues[1]->cnrti, ues[1]->get_pucch_cfg()); ASSERT_EQ(&ues[1]->get_pucch_cfg().pucch_res_list[8], sr_resource_ue1); - ASSERT_EQ(nullptr, res_manager.reserve_sr_res_available(sl_tx, ues[1]->cnrti, ues[1]->get_pucch_cfg())); + ASSERT_EQ(sr_resource_ue1, res_manager.reserve_sr_res_available(sl_tx, ues[1]->cnrti, ues[1]->get_pucch_cfg())); ASSERT_EQ(17, sr_resource_ue1->res_id.cell_res_id); // Release resource and verify it was successful. @@ -917,9 +822,6 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_4_ues_2_cfgs_allocate_csi) ASSERT_EQ(&ues[0]->get_pucch_cfg().pucch_res_list[17], csi_resource); ASSERT_EQ(nullptr, res_manager.reserve_csi_resource(sl_tx, ues[2]->cnrti, ues[2]->ue_cell_cfg)); ASSERT_EQ(26, csi_resource->res_id.cell_res_id); - // Test fetch CSI operation. - ASSERT_EQ(csi_resource, res_manager.fetch_csi_pucch_res_config(sl_tx, ues[0]->cnrti, ues[0]->ue_cell_cfg)); - ASSERT_EQ(nullptr, res_manager.fetch_csi_pucch_res_config(sl_tx, ues[2]->cnrti, ues[2]->ue_cell_cfg)); // Release resource and verify it was successful. ASSERT_TRUE(res_manager.release_csi_resource(sl_tx, ues[0]->cnrti, ues[0]->ue_cell_cfg)); @@ -933,9 +835,6 @@ TEST_F(test_pucch_res_manager_multiple_cfg, test_4_ues_2_cfgs_allocate_csi) ASSERT_EQ(&ues[1]->get_pucch_cfg().pucch_res_list[17], csi_resource_ue1); ASSERT_EQ(nullptr, res_manager.reserve_csi_resource(sl_tx, ues[3]->cnrti, ues[3]->ue_cell_cfg)); ASSERT_EQ(35, csi_resource_ue1->res_id.cell_res_id); - // Test fetch CSI operation. - ASSERT_EQ(csi_resource_ue1, res_manager.fetch_csi_pucch_res_config(sl_tx, ues[1]->cnrti, ues[1]->ue_cell_cfg)); - ASSERT_EQ(nullptr, res_manager.fetch_csi_pucch_res_config(sl_tx, ues[3]->cnrti, ues[3]->ue_cell_cfg)); // Release resource and verify it was successful. ASSERT_TRUE(res_manager.release_csi_resource(sl_tx, ues[1]->cnrti, ues[1]->ue_cell_cfg)); diff --git a/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp b/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp index 62df48ae35..7a622e5402 100644 --- a/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/uci_allocator_test.cpp @@ -72,12 +72,12 @@ class test_uci_allocator : public ::testing::Test void add_harq_grant_on_pucch(unsigned nof_harq_ack_bits = 1) { - t_bench.pucch_alloc.alloc_ded_pucch_harq_ack_ue( - t_bench.res_grid, t_bench.get_main_ue().crnti, t_bench.get_main_ue().get_pcell().cfg(), t_bench.k0, t_bench.k1); - for (auto& pucch : t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs) { - if (pucch.crnti == t_bench.get_main_ue().crnti) { - pucch.format_1.harq_ack_nof_bits = nof_harq_ack_bits; - } + for (unsigned n = 0; n != nof_harq_ack_bits; ++n) { + t_bench.pucch_alloc.alloc_ded_pucch_harq_ack_ue(t_bench.res_grid, + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + t_bench.k0, + t_bench.k1); } } @@ -241,10 +241,11 @@ TEST_F(test_uci_allocator, uci_harq_alloc_over_existing_sr) ASSERT_EQ(0, slot_grid.result.ul.puschs.size()); // 2 PUCCH grants expected. ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[0].format_1.sr_bits); - ASSERT_EQ(1, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); + ASSERT_TRUE( + std::all_of(slot_grid.result.ul.pucchs.begin(), slot_grid.result.ul.pucchs.end(), [](const pucch_info& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.format_1.harq_ack_nof_bits == 1U and + pdu.format_1.sr_bits == sr_nof_bits::one; + })); // Note: no need to check other PUCCH grant values, as this is part of pucch_allocator test. } @@ -259,16 +260,16 @@ TEST_F(test_uci_allocator, uci_harq_alloc_on_existing_pucch_harq_plus_sr) t_bench.k0, k1_candidates); - auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; - // No grants expected on PUSCH. - ASSERT_EQ(0, slot_grid.result.ul.puschs.size()); - // 2 PUCCH grants expected. - ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(2, slot_grid.result.ul.pucchs[0].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::one, slot_grid.result.ul.pucchs[0].format_1.sr_bits); - ASSERT_EQ(2, slot_grid.result.ul.pucchs[1].format_1.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs[1].format_1.sr_bits); + const auto& pusch_pdus = t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.puschs; + const auto& pucch_pdus = t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs; + ASSERT_EQ(0, pusch_pdus.size()); + // 2 PUCCH grants expected, both with HARQ and SR bits. + ASSERT_EQ(2, pucch_pdus.size()); + ASSERT_TRUE(std::all_of(pucch_pdus.begin(), pucch_pdus.end(), [](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_1 and pdu.format_1.harq_ack_nof_bits == 2U and + pdu.format_1.sr_bits == sr_nof_bits::one; + })); // Note: no need to check other PUCCH grant values, as this is part of pucch_allocator test. } @@ -282,15 +283,15 @@ TEST_F(test_uci_allocator, uci_harq_alloc_on_existing_harq_2_bits) t_bench.k0, k1_candidates); - auto& slot_grid = t_bench.res_grid[t_bench.k0 + t_bench.k1]; - - // No grants expected on PUSCH. - ASSERT_EQ(0, slot_grid.result.ul.puschs.size()); + const auto& pusch_pdus = t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.puschs; + const auto& pucch_pdus = t_bench.res_grid[t_bench.k0 + t_bench.k1].result.ul.pucchs; + ASSERT_EQ(0, pusch_pdus.size()); // 1 PUCCH grant expected. - ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); - ASSERT_EQ(pucch_format::FORMAT_2, slot_grid.result.ul.pucchs.front().format); - ASSERT_EQ(3, slot_grid.result.ul.pucchs.back().format_2.harq_ack_nof_bits); - ASSERT_EQ(sr_nof_bits::no_sr, slot_grid.result.ul.pucchs.back().format_2.sr_bits); + ASSERT_EQ(1, pucch_pdus.size()); + ASSERT_TRUE(find_pucch_pdu(pucch_pdus, [](const auto& pdu) { + return pdu.format == pucch_format::FORMAT_2 and pdu.format_2.harq_ack_nof_bits == 3U and + pdu.format_2.sr_bits == sr_nof_bits::no_sr; + })); } /////// UCI allocation on PUSCH /////// @@ -452,7 +453,7 @@ TEST_F(test_uci_allocator, uci_multiplexing_3_bit_harq_sr_csi_on_pusch) auto& slot_grid = t_bench.res_grid[k2]; // 1 PUSCH grant (without UCI) and 2 PUCCH grants expected before multiplexing. - ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); + ASSERT_EQ(2, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(1, slot_grid.result.ul.puschs.size()); ASSERT_FALSE(slot_grid.result.ul.puschs.back().uci.has_value()); @@ -538,6 +539,14 @@ class test_uci_allocator_mimo_4x4 : public ::testing::Test puschs.emplace_back(ul_sched_info{}); puschs.back().pusch_cfg.rnti = t_bench.get_main_ue().crnti; } + + void add_csi_grant(unsigned csi_part1_bits = 4) + { + t_bench.pucch_alloc.pucch_allocate_csi_opportunity(t_bench.res_grid[t_bench.k0 + t_bench.k1], + t_bench.get_main_ue().crnti, + t_bench.get_main_ue().get_pcell().cfg(), + csi_part1_bits); + } }; TEST_F(test_uci_allocator_mimo_4x4, uci_alloc_csi_part2_over_existing_pusch) @@ -559,15 +568,10 @@ TEST_F(test_uci_allocator_mimo_4x4, uci_alloc_csi_part2_over_existing_pusch) TEST_F(test_uci_allocator_mimo_4x4, uci_mplex_csi_part2_over_existing_pusch) { + add_csi_grant(/* CSI bits for MIMO 4x4*/ 11); add_pusch_alloc(t_bench.k0 + k2); auto& slot_grid = t_bench.res_grid[k2]; - // Add manually the PUCCH grant and force the number of CSI bits to 11. - auto& pucch_csi = slot_grid.result.ul.pucchs.emplace_back(); - pucch_csi.crnti = t_bench.get_main_ue().crnti; - pucch_csi.format = srsran::pucch_format::FORMAT_2; - pucch_csi.format_2.csi_part1_bits = 11; - // 1 PUSCH grant (without UCI) and 2 PUCCH grants expected before multiplexing. ASSERT_EQ(1, slot_grid.result.ul.pucchs.size()); ASSERT_EQ(1, slot_grid.result.ul.puschs.size()); diff --git a/tests/unittests/scheduler/uci_and_pucch/uci_scheduling_test.cpp b/tests/unittests/scheduler/uci_and_pucch/uci_scheduling_test.cpp index e75a0b877a..cf29b9a387 100644 --- a/tests/unittests/scheduler/uci_and_pucch/uci_scheduling_test.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/uci_scheduling_test.cpp @@ -125,9 +125,15 @@ TEST_P(uci_sr_scheduler_tester, test_different_periods) // - SR only on slots that are for SR only. // - CSI + SR on slots that are for CSI + SR. if ((t_bench.sl_tx - csi_offset).to_uint() % csi_report_periodicity_to_uint(csi_period) == 0) { - ASSERT_TRUE(assess_ul_pucch_info(pucch_sr_csi_test, t_bench.res_grid[0].result.ul.pucchs.back())); + ASSERT_TRUE( + find_pucch_pdu(t_bench.res_grid[0].result.ul.pucchs, [&expected = pucch_sr_csi_test](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } else { - ASSERT_TRUE(assess_ul_pucch_info(pucch_sr_only_test, t_bench.res_grid[0].result.ul.pucchs.back())); + ASSERT_TRUE( + find_pucch_pdu(t_bench.res_grid[0].result.ul.pucchs, [&expected = pucch_sr_only_test](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } } // Update the slot indicator. @@ -238,9 +244,15 @@ TEST_P(uci_csi_scheduler_tester, test_different_periods) // - CSI only on slots that are for CSI only. // - CSI + SR on slots that are for CSI + SR. if ((t_bench.sl_tx - sr_offset).to_uint() % sr_period == 0) { - ASSERT_TRUE(assess_ul_pucch_info(pucch_csi_and_sr_test, t_bench.res_grid[0].result.ul.pucchs.back())); + ASSERT_TRUE( + find_pucch_pdu(t_bench.res_grid[0].result.ul.pucchs, [&expected = pucch_csi_and_sr_test](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } else { - ASSERT_TRUE(assess_ul_pucch_info(pucch_csi_only_test, t_bench.res_grid[0].result.ul.pucchs.back())); + ASSERT_TRUE( + find_pucch_pdu(t_bench.res_grid[0].result.ul.pucchs, [&expected = pucch_csi_only_test](const auto& pdu) { + return pucch_info_match(expected, pdu); + })); } } // Update the slot indicator. diff --git a/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp b/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp index 96f6dd3940..663f3dec0f 100644 --- a/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp +++ b/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.cpp @@ -70,8 +70,7 @@ pucch_info srsran::build_pucch_info(const bwp_configuration* bwp_cfg, return pucch_test; } -// Verify if the PUCCH scheduler output (or PUCCH PDU) is correct. -bool srsran::assess_ul_pucch_info(const pucch_info& expected, const pucch_info& test) +bool srsran::pucch_info_match(const pucch_info& expected, const pucch_info& test) { bool is_equal = expected.crnti == test.crnti && *expected.bwp_cfg == *test.bwp_cfg && expected.format == test.format; is_equal = is_equal && expected.resources.prbs == test.resources.prbs && @@ -177,6 +176,17 @@ test_bench::test_bench(const test_bench_params& params, } if (params.cfg_for_mimo_4x4) { + auto& pucch_cfg = ue_req.cfg.cells->back().serv_cell_cfg.ul_config->init_ul_bwp.pucch_cfg.value(); + pucch_cfg.format_2_common_param.value().max_c_rate = max_pucch_code_rate::dot_35; + const auto& res_f2 = std::find_if(pucch_cfg.pucch_res_list.begin(), + pucch_cfg.pucch_res_list.end(), + [](const auto& pucch) { return pucch.format == pucch_format::FORMAT_2; }); + srsran_assert(res_f2 != pucch_cfg.pucch_res_list.end(), "PUCCH format 2 not found"); + const auto& res_f2_cfg = std::get(res_f2->format_params); + pucch_cfg.format_max_payload[pucch_format_to_uint(pucch_format::FORMAT_2)] = + get_pucch_format2_max_payload(res_f2_cfg.nof_prbs, + res_f2_cfg.nof_symbols, + to_max_code_rate_float(pucch_cfg.format_2_common_param.value().max_c_rate)); ue_req.cfg.cells->back().serv_cell_cfg.csi_meas_cfg = csi_helper::make_csi_meas_config(csi_helper::csi_builder_params{.nof_ports = 4}); auto& beta_offsets = std::get(ue_req.cfg.cells->back() diff --git a/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.h b/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.h index b767e866cd..14af93dfe7 100644 --- a/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.h +++ b/tests/unittests/scheduler/uci_and_pucch/uci_test_utils.h @@ -67,8 +67,14 @@ pucch_info build_pucch_info(const bwp_configuration* bwp_cfg, unsigned harq_ack_nof_bits, uint8_t time_domain_occ); -// Verify if the PUCCH scheduler output (or PUCCH PDU) is correct. -bool assess_ul_pucch_info(const pucch_info& expected, const pucch_info& test); +bool pucch_info_match(const pucch_info& expected, const pucch_info& test); + +// Wrapper for std::find_if() to find a PUCCH PDU in a vector of PUCCH PDUs. +template +bool find_pucch_pdu(const static_vector& pucch_pdus, const F& func) +{ + return std::find_if(pucch_pdus.begin(), pucch_pdus.end(), func) != pucch_pdus.end(); +} // Makes a default DCI for PUCCH test purposes but some given parameters. inline pdcch_dl_information make_default_dci(unsigned n_cces, const coreset_configuration* coreset_cfg_) diff --git a/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp b/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp index 652e19ad07..5272149547 100644 --- a/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp +++ b/tests/unittests/scheduler/ue_scheduling/ue_grid_allocator_test.cpp @@ -23,6 +23,7 @@ #include "../test_utils/config_generators.h" #include "../test_utils/dummy_test_components.h" #include "lib/scheduler/config/sched_config_manager.h" +#include "lib/scheduler/logging/scheduler_result_logger.h" #include "lib/scheduler/pdcch_scheduling/pdcch_resource_allocator_impl.h" #include "lib/scheduler/pucch_scheduling/pucch_allocator_impl.h" #include "lib/scheduler/uci_scheduling/uci_allocator_impl.h" @@ -32,10 +33,11 @@ #include "srsran/ran/du_types.h" #include "srsran/ran/pdcch/search_space.h" #include +#include using namespace srsran; -class ue_grid_allocator_tester : public ::testing::Test +class ue_grid_allocator_tester : public ::testing::TestWithParam { protected: ue_grid_allocator_tester() : @@ -46,7 +48,10 @@ class ue_grid_allocator_tester : public ::testing::Test return ue_expert_cfg; }()), cell_cfg(*[this]() { - cfg_builder_params.dl_arfcn = 536020; + cfg_builder_params.dl_arfcn = GetParam() == duplex_mode::FDD ? 530000 : 520002; + cfg_builder_params.scs_common = + GetParam() == duplex_mode::FDD ? subcarrier_spacing::kHz15 : subcarrier_spacing::kHz30; + cfg_builder_params.band = band_helper::get_band_from_dl_arfcn(cfg_builder_params.dl_arfcn); cfg_builder_params.channel_bw_mhz = bs_channel_bandwidth_fr1::MHz20; auto* cfg = cfg_mng.add_cell(test_helpers::make_default_sched_cell_configuration_request(cfg_builder_params)); srsran_assert(cfg != nullptr, "Cell configuration failed"); @@ -54,6 +59,9 @@ class ue_grid_allocator_tester : public ::testing::Test }()), current_slot(cfg_builder_params.scs_common, 0) { + logger.set_level(srslog::basic_levels::debug); + srslog::init(); + // Initialize resource grid. res_grid.slot_indication(current_slot); pdcch_alloc.slot_indication(current_slot); @@ -66,10 +74,27 @@ class ue_grid_allocator_tester : public ::testing::Test void run_slot() { ++current_slot; + logger.set_context(current_slot.sfn(), current_slot.slot_index()); + res_grid.slot_indication(current_slot); pdcch_alloc.slot_indication(current_slot); pucch_alloc.slot_indication(current_slot); uci_alloc.slot_indication(current_slot); + ues.slot_indication(current_slot); + + // Log scheduler results. + res_logger.on_scheduler_result(res_grid[0].result); + } + + bool run_until(unique_function condition, unsigned max_slot_count = 1000) + { + for (unsigned count = 0; count != max_slot_count; ++count) { + if (condition()) { + return true; + } + run_slot(); + } + return false; } ue& add_ue(du_ue_index_t ue_index, const std::initializer_list& lcids_to_activate) @@ -110,13 +135,16 @@ class ue_grid_allocator_tester : public ::testing::Test pucch_allocator_impl pucch_alloc{cell_cfg, expert_cfg.max_pucchs_per_slot, expert_cfg.max_ul_grants_per_slot}; uci_allocator_impl uci_alloc{pucch_alloc}; + srslog::basic_logger& logger{srslog::fetch_basic_logger("SCHED")}; + scheduler_result_logger res_logger{false, cell_cfg.pci}; + ue_repository ues; - ue_cell_grid_allocator alloc{expert_cfg, ues, srslog::fetch_basic_logger("SCHED")}; + ue_cell_grid_allocator alloc{expert_cfg, ues, logger}; slot_point current_slot; }; -TEST_F(ue_grid_allocator_tester, +TEST_P(ue_grid_allocator_tester, when_ue_dedicated_ss_is_css_then_allocation_is_within_coreset_start_crb_and_coreset0_end_crb) { static const unsigned nof_bytes_to_schedule = 40U; @@ -142,11 +170,12 @@ TEST_F(ue_grid_allocator_tester, .h_id = to_harq_id(0), .recommended_nof_bytes = nof_bytes_to_schedule}; - ASSERT_EQ(alloc.allocate_dl_grant(grant).status, alloc_status::success); + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); ASSERT_TRUE(crb_lims.contains(res_grid[0].result.dl.ue_grants.back().pdsch_cfg.rbs.type1())); } -TEST_F(ue_grid_allocator_tester, when_using_non_fallback_dci_format_use_mcs_table_set_in_pdsch_cfg) +TEST_P(ue_grid_allocator_tester, when_using_non_fallback_dci_format_use_mcs_table_set_in_pdsch_cfg) { static const unsigned nof_bytes_to_schedule = 40U; @@ -165,12 +194,13 @@ TEST_F(ue_grid_allocator_tester, when_using_non_fallback_dci_format_use_mcs_tabl .h_id = to_harq_id(0), .recommended_nof_bytes = nof_bytes_to_schedule}; - ASSERT_EQ(alloc.allocate_dl_grant(grant).status, alloc_status::success); + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); ASSERT_EQ(res_grid[0].result.dl.ue_grants.back().pdsch_cfg.codewords.back().mcs_table, srsran::pdsch_mcs_table::qam256); } -TEST_F(ue_grid_allocator_tester, remaining_dl_rbs_are_allocated_if_max_pucch_per_slot_is_reached) +TEST_P(ue_grid_allocator_tester, remaining_dl_rbs_are_allocated_if_max_pucch_per_slot_is_reached) { sched_ue_creation_request_message ue_creation_req = test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); @@ -185,23 +215,57 @@ TEST_F(ue_grid_allocator_tester, remaining_dl_rbs_are_allocated_if_max_pucch_per const ue_pdsch_grant grant1{ .user = &u1, .cell_index = to_du_cell_index(0), .h_id = to_harq_id(0), .recommended_nof_bytes = sched_bytes}; - // Successfully allocates RBs corresponding to the grant. - ASSERT_EQ(alloc.allocate_dl_grant(grant1).status, alloc_status::success); - ASSERT_GE(res_grid[0].result.dl.ue_grants.back().pdsch_cfg.codewords.back().tb_size_bytes, sched_bytes); - // Since UE dedicated SearchSpace is a UE specific SearchSpace (Not CSS). Entire BWP CRBs can be used for allocation. - const unsigned total_crbs = cell_cfg.dl_cfg_common.init_dl_bwp.generic_params.crbs.length(); - const unsigned crbs_allocated = res_grid[0].result.dl.ue_grants.back().pdsch_cfg.rbs.type1().length(); - + const unsigned total_crbs = cell_cfg.dl_cfg_common.init_dl_bwp.generic_params.crbs.length(); const ue_pdsch_grant grant2{ .user = &u2, .cell_index = to_du_cell_index(0), .h_id = to_harq_id(0), .recommended_nof_bytes = sched_bytes}; + ASSERT_TRUE(run_until([&]() { + return alloc.allocate_dl_grant(grant1).status == alloc_status::success and + alloc.allocate_dl_grant(grant2).status == alloc_status::success; + })); + ASSERT_TRUE(run_until([&]() { + return find_ue_pdsch(u1.crnti, res_grid[0].result.dl.ue_grants) != nullptr and + find_ue_pdsch(u2.crnti, res_grid[0].result.dl.ue_grants) != nullptr; + })); + // Successfully allocates PDSCH corresponding to the grant. + ASSERT_GE(find_ue_pdsch(u1.crnti, res_grid[0].result.dl.ue_grants)->pdsch_cfg.codewords.back().tb_size_bytes, + sched_bytes); + + // Since UE dedicated SearchSpace is a UE specific SearchSpace (Not CSS). Entire BWP CRBs can be used for allocation. + const unsigned crbs_allocated = + find_ue_pdsch(u1.crnti, res_grid[0].result.dl.ue_grants)->pdsch_cfg.rbs.type1().length(); + // Allocates all remaining RBs to UE2. - ASSERT_EQ(alloc.allocate_dl_grant(grant2).status, alloc_status::success); - ASSERT_EQ(res_grid[0].result.dl.ue_grants.back().pdsch_cfg.rbs.type1().length(), (total_crbs - crbs_allocated)); + ASSERT_EQ(find_ue_pdsch(u2.crnti, res_grid[0].result.dl.ue_grants)->pdsch_cfg.rbs.type1().length(), + (total_crbs - crbs_allocated)); } -TEST_F(ue_grid_allocator_tester, remaining_ul_rbs_are_allocated_if_max_ul_grant_per_slot_is_reached) +TEST_P(ue_grid_allocator_tester, allocates_pdsch_restricted_to_recommended_max_nof_rbs) +{ + sched_ue_creation_request_message ue_creation_req = + test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); + ue_creation_req.ue_index = to_du_ue_index(0); + ue_creation_req.crnti = to_rnti(0x4601); + const ue& u1 = add_ue(ue_creation_req); + + static const unsigned sched_bytes = 2000U; + const unsigned max_nof_rbs_to_schedule = 10U; + + const ue_pdsch_grant grant1{.user = &u1, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = sched_bytes, + .max_nof_rbs = max_nof_rbs_to_schedule}; + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant1).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u1.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); + // Successfully allocates PDSCH corresponding to the grant. + ASSERT_GE(find_ue_pdsch(u1.crnti, res_grid[0].result.dl.ue_grants)->pdsch_cfg.rbs.type1().length(), + grant1.max_nof_rbs); +} + +TEST_P(ue_grid_allocator_tester, remaining_ul_rbs_are_allocated_if_max_ul_grant_per_slot_is_reached) { sched_ue_creation_request_message ue_creation_req = test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); @@ -213,39 +277,59 @@ TEST_F(ue_grid_allocator_tester, remaining_ul_rbs_are_allocated_if_max_ul_grant_ const ue& u2 = add_ue(ue_creation_req); const unsigned recommended_nof_bytes_to_schedule = 200U; - const unsigned max_nof_rbs_to_schedule = 10U; const crb_interval cell_crbs = {cell_cfg.ul_cfg_common.init_ul_bwp.generic_params.crbs.start(), cell_cfg.ul_cfg_common.init_ul_bwp.generic_params.crbs.stop()}; const ue_pusch_grant grant1{.user = &u1, .cell_index = to_du_cell_index(0), .h_id = to_harq_id(0), - .recommended_nof_bytes = recommended_nof_bytes_to_schedule, - .max_nof_rbs = max_nof_rbs_to_schedule}; - - // Successfully allocates RBs corresponding to the grant. - ASSERT_EQ(alloc.allocate_ul_grant(grant1).status, alloc_status::success); - unsigned k2 = cell_cfg.ul_cfg_common.init_ul_bwp.pusch_cfg_common - ->pusch_td_alloc_list[res_grid[0].result.dl.ul_pdcchs.back().dci.c_rnti_f0_1.time_resource] - .k2; + .recommended_nof_bytes = recommended_nof_bytes_to_schedule}; + const ue_pusch_grant grant2{.user = &u2, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = recommended_nof_bytes_to_schedule}; + + ASSERT_TRUE(run_until([&]() { + return alloc.allocate_ul_grant(grant1).status == alloc_status::success and + alloc.allocate_ul_grant(grant2).status == alloc_status::success; + })); + ASSERT_TRUE(run_until([&]() { + return find_ue_pusch(u1.crnti, res_grid[0].result.ul) != nullptr and find_ue_pusch(u2.crnti, res_grid[0].result.ul); + })); + // Successfully allocates PUSCH corresponding to the grant. + ASSERT_GE(find_ue_pusch(u1.crnti, res_grid[0].result.ul)->pusch_cfg.tb_size_bytes, grant1.recommended_nof_bytes); const unsigned remaining_crbs = - cell_crbs.length() - res_grid[k2].result.ul.puschs.back().pusch_cfg.rbs.type1().length(); - const ue_pusch_grant grant2{.user = &u2, + cell_crbs.length() - find_ue_pusch(u1.crnti, res_grid[0].result.ul)->pusch_cfg.rbs.type1().length(); + + // Allocates all remaining RBs to UE2. + ASSERT_EQ(find_ue_pusch(u2.crnti, res_grid[0].result.ul)->pusch_cfg.rbs.type1().length(), remaining_crbs); +} + +TEST_P(ue_grid_allocator_tester, allocates_pusch_restricted_to_recommended_max_nof_rbs) +{ + sched_ue_creation_request_message ue_creation_req = + test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); + ue_creation_req.ue_index = to_du_ue_index(0); + ue_creation_req.crnti = to_rnti(0x4601); + const ue& u1 = add_ue(ue_creation_req); + + const unsigned recommended_nof_bytes_to_schedule = 2000U; + const unsigned max_nof_rbs_to_schedule = 10U; + + const ue_pusch_grant grant1{.user = &u1, .cell_index = to_du_cell_index(0), .h_id = to_harq_id(0), .recommended_nof_bytes = recommended_nof_bytes_to_schedule, .max_nof_rbs = max_nof_rbs_to_schedule}; - // Allocates all remaining RBs to UE2. - ASSERT_EQ(alloc.allocate_ul_grant(grant2).status, alloc_status::success); - k2 = cell_cfg.ul_cfg_common.init_ul_bwp.pusch_cfg_common - ->pusch_td_alloc_list[res_grid[0].result.dl.ul_pdcchs.back().dci.c_rnti_f0_1.time_resource] - .k2; - ASSERT_EQ(res_grid[k2].result.ul.puschs.back().pusch_cfg.rbs.type1().length(), remaining_crbs); + ASSERT_TRUE(run_until([&]() { return alloc.allocate_ul_grant(grant1).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pusch(u1.crnti, res_grid[0].result.ul) != nullptr; })); + // Successfully allocates PUSCH corresponding to the grant. + ASSERT_EQ(find_ue_pusch(u1.crnti, res_grid[0].result.ul)->pusch_cfg.rbs.type1().length(), grant1.max_nof_rbs); } -TEST_F(ue_grid_allocator_tester, no_two_pdschs_are_allocated_in_same_slot_for_a_ue) +TEST_P(ue_grid_allocator_tester, no_two_pdschs_are_allocated_in_same_slot_for_a_ue) { static const unsigned nof_bytes_to_schedule = 400U; @@ -255,12 +339,10 @@ TEST_F(ue_grid_allocator_tester, no_two_pdschs_are_allocated_in_same_slot_for_a_ const ue& u = add_ue(ue_creation_req); // First PDSCH grant for the UE. - const ue_pdsch_grant grant{.user = &u, - .cell_index = to_du_cell_index(0), - .h_id = to_harq_id(0), - .recommended_nof_bytes = nof_bytes_to_schedule}; - - ASSERT_EQ(alloc.allocate_dl_grant(grant).status, alloc_status::success); + const ue_pdsch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; // Second PDSCH grant for the UE. const ue_pdsch_grant grant2{.user = &u, @@ -268,11 +350,17 @@ TEST_F(ue_grid_allocator_tester, no_two_pdschs_are_allocated_in_same_slot_for_a_ .h_id = to_harq_id(1), .recommended_nof_bytes = nof_bytes_to_schedule}; - // Second PDSCH grant should not be allocated. - ASSERT_NE(alloc.allocate_dl_grant(grant2).status, alloc_status::success); + ASSERT_TRUE(run_until([&]() { + return alloc.allocate_dl_grant(grant1).status == alloc_status::success or + alloc.allocate_dl_grant(grant2).status == alloc_status::success; + })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); + + // Only one PDSCH per slot per UE. + ASSERT_EQ(res_grid[0].result.dl.ue_grants.size(), 1); } -TEST_F(ue_grid_allocator_tester, no_two_puschs_are_allocated_in_same_slot_for_a_ue) +TEST_P(ue_grid_allocator_tester, no_two_puschs_are_allocated_in_same_slot_for_a_ue) { static const unsigned nof_bytes_to_schedule = 400U; @@ -282,12 +370,10 @@ TEST_F(ue_grid_allocator_tester, no_two_puschs_are_allocated_in_same_slot_for_a_ const ue& u = add_ue(ue_creation_req); // First PUSCH grant for the UE. - const ue_pusch_grant grant{.user = &u, - .cell_index = to_du_cell_index(0), - .h_id = to_harq_id(0), - .recommended_nof_bytes = nof_bytes_to_schedule}; - - ASSERT_EQ(alloc.allocate_ul_grant(grant).status, alloc_status::success); + const ue_pusch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; // Second PUSCH grant for the UE. const ue_pusch_grant grant2{.user = &u, @@ -295,11 +381,17 @@ TEST_F(ue_grid_allocator_tester, no_two_puschs_are_allocated_in_same_slot_for_a_ .h_id = to_harq_id(1), .recommended_nof_bytes = nof_bytes_to_schedule}; - // Second PUSCH grant should not be allocated. - ASSERT_NE(alloc.allocate_ul_grant(grant2).status, alloc_status::success); + ASSERT_TRUE(run_until([&]() { + return alloc.allocate_ul_grant(grant1).status == alloc_status::success or + alloc.allocate_ul_grant(grant2).status == alloc_status::success; + })); + ASSERT_TRUE(run_until([&]() { return find_ue_pusch(u.crnti, res_grid[0].result.ul) != nullptr; })); + + // Only one PUSCH per slot per UE. + ASSERT_EQ(res_grid[0].result.ul.puschs.size(), 1); } -TEST_F(ue_grid_allocator_tester, consecutive_puschs_for_a_ue_are_allocated_in_increasing_order_of_time) +TEST_P(ue_grid_allocator_tester, consecutive_puschs_for_a_ue_are_allocated_in_increasing_order_of_time) { static const unsigned nof_bytes_to_schedule = 400U; @@ -308,50 +400,62 @@ TEST_F(ue_grid_allocator_tester, consecutive_puschs_for_a_ue_are_allocated_in_in const ue& u = add_ue(ue_creation_req); - slot_point last_pusch_alloc_slot; - // First PUSCH grant for the UE. - const ue_pusch_grant grant{.user = &u, - .cell_index = to_du_cell_index(0), - .h_id = to_harq_id(0), - .recommended_nof_bytes = nof_bytes_to_schedule}; + const ue_pusch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_ul_grant(grant1).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pusch(u.crnti, res_grid[0].result.ul) != nullptr; })); + slot_point last_pusch_alloc_slot = current_slot; - ASSERT_EQ(alloc.allocate_ul_grant(grant).status, alloc_status::success); - unsigned k2 = cell_cfg.ul_cfg_common.init_ul_bwp.pusch_cfg_common - ->pusch_td_alloc_list[res_grid[0].result.dl.ul_pdcchs.back().dci.c_rnti_f0_1.time_resource] - .k2; - last_pusch_alloc_slot = current_slot + k2; + run_slot(); - // Second PUSCH grant in the same slot for the UE. + // Second PUSCH grant for the UE. const ue_pusch_grant grant2{.user = &u, .cell_index = to_du_cell_index(0), .h_id = to_harq_id(1), .recommended_nof_bytes = nof_bytes_to_schedule}; - const auto outcome = alloc.allocate_ul_grant(grant2); - if (outcome.status == alloc_status::success) { - k2 = cell_cfg.ul_cfg_common.init_ul_bwp.pusch_cfg_common - ->pusch_td_alloc_list[res_grid[0].result.dl.ul_pdcchs.back().dci.c_rnti_f0_1.time_resource] - .k2; - ASSERT_GT(current_slot + k2, last_pusch_alloc_slot); - last_pusch_alloc_slot = current_slot + k2; - } + ASSERT_TRUE(run_until([&]() { return alloc.allocate_ul_grant(grant2).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pusch(u.crnti, res_grid[0].result.ul) != nullptr; })); + ASSERT_GT(current_slot, last_pusch_alloc_slot); +} + +TEST_P(ue_grid_allocator_tester, consecutive_pdschs_for_a_ue_are_allocated_in_increasing_order_of_time) +{ + static const unsigned nof_bytes_to_schedule = 400U; + + sched_ue_creation_request_message ue_creation_req = + test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); + + const ue& u = add_ue(ue_creation_req); + + // First PDSCH grant for the UE. + const ue_pdsch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant1).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); + slot_point last_pdsch_slot = current_slot; run_slot(); - // Third PUSCH grant in the next slot for the UE. - const ue_pusch_grant grant3{.user = &u, + // Second PDSCH grant in the same slot for the UE. + const ue_pdsch_grant grant2{.user = &u, .cell_index = to_du_cell_index(0), - .h_id = to_harq_id(2), + .h_id = to_harq_id(1), .recommended_nof_bytes = nof_bytes_to_schedule}; - ASSERT_EQ(alloc.allocate_ul_grant(grant3).status, alloc_status::success); - k2 = cell_cfg.ul_cfg_common.init_ul_bwp.pusch_cfg_common - ->pusch_td_alloc_list[res_grid[0].result.dl.ul_pdcchs.back().dci.c_rnti_f0_1.time_resource] - .k2; - ASSERT_GT(current_slot + k2, last_pusch_alloc_slot); + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant2).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); + ASSERT_GE(current_slot, last_pdsch_slot); } -TEST_F(ue_grid_allocator_tester, +TEST_P(ue_grid_allocator_tester, ack_slot_of_consecutive_pdschs_for_a_ue_must_be_greater_than_or_equal_to_last_ack_slot_allocated) { static const unsigned nof_bytes_to_schedule = 400U; @@ -361,21 +465,17 @@ TEST_F(ue_grid_allocator_tester, const ue& u = add_ue(ue_creation_req); - slot_point last_pdsch_ack_slot; - // First PDSCH grant for the UE. - const ue_pdsch_grant grant{.user = &u, - .cell_index = to_du_cell_index(0), - .h_id = to_harq_id(0), - .recommended_nof_bytes = nof_bytes_to_schedule}; + const ue_pdsch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; - ASSERT_EQ(alloc.allocate_dl_grant(grant).status, alloc_status::success); - const search_space_info* ss_info = - u.get_pcell().cfg().find_search_space(res_grid[0].result.dl.dl_pdcchs.back().ctx.context.ss_id); - unsigned k1 = - ss_info - ->get_k1_candidates()[*res_grid[0].result.dl.dl_pdcchs.back().dci.c_rnti_f1_1.pdsch_harq_fb_timing_indicator]; - last_pdsch_ack_slot = current_slot + k1; + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant1).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); + slot_point last_pdsch_ack_slot = current_slot + find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants)->context.k1; + + run_slot(); // Second PDSCH grant in the same slot for the UE. const ue_pdsch_grant grant2{.user = &u, @@ -383,26 +483,91 @@ TEST_F(ue_grid_allocator_tester, .h_id = to_harq_id(1), .recommended_nof_bytes = nof_bytes_to_schedule}; - const auto outcome = alloc.allocate_dl_grant(grant2); - if (outcome.status == srsran::alloc_status::success) { - ss_info = u.get_pcell().cfg().find_search_space(res_grid[0].result.dl.dl_pdcchs.back().ctx.context.ss_id); - k1 = ss_info->get_k1_candidates() - [*res_grid[0].result.dl.dl_pdcchs.back().dci.c_rnti_f1_1.pdsch_harq_fb_timing_indicator]; - ASSERT_GE(current_slot + k1, last_pdsch_ack_slot); - last_pdsch_ack_slot = current_slot + k1; + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant2).status == alloc_status::success; })); + ASSERT_TRUE(run_until([&]() { return find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants) != nullptr; })); + ASSERT_GE(current_slot + find_ue_pdsch(u.crnti, res_grid[0].result.dl.ue_grants)->context.k1, last_pdsch_ack_slot); +} + +TEST_P(ue_grid_allocator_tester, successfully_allocated_pdsch_even_with_large_gap_to_last_pdsch_slot_allocated) +{ + static const unsigned nof_bytes_to_schedule = 8U; + const unsigned nof_slot_until_pdsch_is_allocated_threshold = SCHEDULER_MAX_K0; + + sched_ue_creation_request_message ue_creation_req = + test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); + + const ue& u = add_ue(ue_creation_req); + + // Ensure current slot is the middle of 1024 SFNs. i.e. current slot=511.0 + while (current_slot.sfn() != NOF_SFNS / 2) { + run_slot(); + } + + // First PDSCH grant for the UE. + const ue_pdsch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant1).status == alloc_status::success; })); + + // Ensure next PDSCH to be allocated slot is after wrap around of 1024 SFNs (large gap to last allocated PDSCH slot) + // and current slot value is less than last allocated PDSCH slot. e.g. next PDSCH to be allocated slot=SFN 2, slot 2 + // after wrap around of 1024 SFNs. + for (unsigned i = 0; i < current_slot.nof_slots_per_system_frame() / 2 + current_slot.nof_slots_per_frame(); ++i) { + run_slot(); + } + + // Next PDSCH grant to be allocated. + const ue_pdsch_grant grant2{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(1), + .recommended_nof_bytes = nof_bytes_to_schedule}; + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_dl_grant(grant2).status == alloc_status::success; }, + nof_slot_until_pdsch_is_allocated_threshold)); +} + +TEST_P(ue_grid_allocator_tester, successfully_allocated_pusch_even_with_large_gap_to_last_pusch_slot_allocated) +{ + static const unsigned nof_bytes_to_schedule = 400U; + const unsigned nof_slot_until_pusch_is_allocated_threshold = SCHEDULER_MAX_K2; + + sched_ue_creation_request_message ue_creation_req = + test_helpers::create_default_sched_ue_creation_request(this->cfg_builder_params); + + const ue& u = add_ue(ue_creation_req); + + // Ensure current slot is the middle of 1024 SFNs. i.e. current slot=511.0 + while (current_slot.sfn() != NOF_SFNS / 2) { + run_slot(); } - run_slot(); + // First PUSCH grant for the UE. + const ue_pusch_grant grant1{.user = &u, + .cell_index = to_du_cell_index(0), + .h_id = to_harq_id(0), + .recommended_nof_bytes = nof_bytes_to_schedule}; + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_ul_grant(grant1).status == alloc_status::success; })); - // Third PDSCH grant in the next slot for the UE. - const ue_pdsch_grant grant3{.user = &u, + // Ensure next PUSCH to be allocated slot is after wrap around of 1024 SFNs (large gap to last allocated PUSCH slot) + // and current slot value is less than last allocated PUSCH slot. e.g. next PUSCH to be allocated slot=SFN 2, slot 2 + // after wrap around of 1024 SFNs. + for (unsigned i = 0; i < current_slot.nof_slots_per_system_frame() / 2 + current_slot.nof_slots_per_frame(); ++i) { + run_slot(); + } + + // Second PUSCH grant for the UE. + const ue_pusch_grant grant2{.user = &u, .cell_index = to_du_cell_index(0), - .h_id = to_harq_id(2), + .h_id = to_harq_id(1), .recommended_nof_bytes = nof_bytes_to_schedule}; - ASSERT_EQ(alloc.allocate_dl_grant(grant3).status, alloc_status::success); - ss_info = u.get_pcell().cfg().find_search_space(res_grid[0].result.dl.dl_pdcchs.back().ctx.context.ss_id); - k1 = - ss_info - ->get_k1_candidates()[*res_grid[0].result.dl.dl_pdcchs.back().dci.c_rnti_f1_1.pdsch_harq_fb_timing_indicator]; - ASSERT_GE(current_slot + k1, last_pdsch_ack_slot); + + ASSERT_TRUE(run_until([&]() { return alloc.allocate_ul_grant(grant2).status == alloc_status::success; }, + nof_slot_until_pusch_is_allocated_threshold)); } + +INSTANTIATE_TEST_SUITE_P(ue_grid_allocator_test, + ue_grid_allocator_tester, + testing::Values(duplex_mode::FDD, duplex_mode::TDD)); diff --git a/tests/unittests/srsvec/srsvec_convert_test.cpp b/tests/unittests/srsvec/srsvec_convert_test.cpp index 71937d80e8..95e9c27c77 100644 --- a/tests/unittests/srsvec/srsvec_convert_test.cpp +++ b/tests/unittests/srsvec/srsvec_convert_test.cpp @@ -211,6 +211,74 @@ TEST_P(SrsvecConvertFixture, SrsvecConvertTestComplexComplex16Special) } } +TEST_P(SrsvecConvertFixture, SrsvecConvertTestFloatFloat16Random) +{ + std::uniform_real_distribution dist(-1.0, 1.0); + + srsvec::aligned_vec in(size); + std::generate(in.begin(), in.end(), [&dist]() { return dist(rgen); }); + + // Convert from single precision to brain float. + srsvec::aligned_vec data_bf16(size); + srsvec::convert(data_bf16, in); + + // Assert conversion to BF16. + for (size_t i = 0; i != size; ++i) { + ASSERT_EQ(data_bf16[i], to_bf16(in[i])); + } + + // Convert back to single precision float. + srsvec::aligned_vec out(size); + srsvec::convert(out, data_bf16); + + // Assert conversion from BF16. + for (size_t i = 0; i != size; ++i) { + float tolerance = std::abs(in[i]) / 256.0F; + ASSERT_LT(std::abs(in[i] - out[i]), tolerance); + } +} + +TEST_P(SrsvecConvertFixture, SrsvecConvertTestInt16Float16Random) +{ + std::uniform_real_distribution dist(-1.0, 1.0); + + float int16_scale = (1 << 15) - 1; + + srsvec::aligned_vec in(size); + std::generate(in.begin(), in.end(), [&dist]() { return dist(rgen); }); + + // Convert from single precision to int16. + srsvec::aligned_vec in_int16(size); + srsvec::convert(in, int16_scale, in_int16); + + // Convert from int16 to brain float. + srsvec::aligned_vec data_bf16(size); + srsvec::convert(data_bf16, in_int16, int16_scale); + + // Assert conversion to BF16. + for (size_t i = 0; i != size; ++i) { + ASSERT_EQ(data_bf16[i], to_bf16(in_int16[i], int16_scale)); + } + + // Convert from brain float back to int16. + srsvec::aligned_vec out_int16(size); + srsvec::convert(out_int16, data_bf16, int16_scale); + + // Assert conversion from BF16. + for (size_t i = 0; i != size; ++i) { + ASSERT_EQ(out_int16[i], to_int16(data_bf16[i], int16_scale)); + } + + // Convert int16 to float and compare with original data. + srsvec::aligned_vec out(size); + srsvec::convert(out_int16, int16_scale, out); + + for (size_t i = 0; i != size; ++i) { + float tolerance = std::abs(in[i]) / 256.0F + 1 / int16_scale; + ASSERT_LT(std::abs(in[i] - out[i]), tolerance); + } +} + INSTANTIATE_TEST_SUITE_P(SrsvecConvertTest, SrsvecConvertFixture, ::testing::Values(1, 5, 7, 19, 23, 257, 1234)); } // namespace \ No newline at end of file