diff --git a/ci-scripts/attenuatorctl.py b/ci-scripts/attenuatorctl.py new file mode 100644 index 0000000000..e6f8c01837 --- /dev/null +++ b/ci-scripts/attenuatorctl.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import itertools +import usb.core +import usb.util +import sys +import time + +logging.basicConfig( + level=logging.DEBUG, + stream=sys.stdout, + format="%(message)s" +) + +# helper class parsing command line options for channels and attenuators +class ValidateChAtt(argparse.Action): + def _parse_channels(s): + if len(s.split("-")) == 2: + s, e = [int(x) for x in s.split("-")] + chs = [x for x in range(s, e+1)] + if len(chs) == 0: + raise Exception(f"empty range") + elif len(s.split(",")) > 1: + chs = [int(x) for x in s.split(",")] + elif s.isdigit(): + chs = [int(s)] + else: + raise Exception(f"could not parse channel expression") + for c in chs: + if not 1 <= c <= 4: + raise Exception(f"channel number must be within [1,4], but have {c}") + return chs + + def __call__(self, parse, args, values, option_string=None): + ch, att = values + try: + chs = ValidateChAtt._parse_channels(ch) + except Exception as e: + parse.exit(1, f"while parsing channel expression {ch}: {e}\n") + if not att.isdigit(): + parse.exit(1, f'expected number for attenuation, but got {att}\n') + if not 0 <= int(att) <= 63: + parse.exit(1, f'attenuation must be within [0,63], but is {att}\n') + opts = getattr(args, self.dest) or {} + for c in chs: + opts[c] = int(att) + setattr(args, self.dest, opts) + +def _parse_args() -> argparse.Namespace: + epilog = ''' +[CHs] can have the following forms: + (1) single channel number w (within [1-4]) + (2) a range of channels in the form x-y (x <= y) + (3) a list of channels in the form a,b,c +[ATT] must be within [0,63] (dB) + +--set and --reach are mutually exclusive + +Examples: +- reset all channels to 0: -s 1-4 0 +- set channels 3 and 4 to attenuation 60: -s 3,4 60 +- perform attenuation sweep during 10s, reaching ch. 1+2 to 60: -r 1,2 60 -d 10 +''' + parser = argparse.ArgumentParser(description='Mini-Circuit RC*DAT attenuator controller', + epilog=epilog, + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument('--info', '-i', action='store_true', default=False, + help='Get Infos for all connected Mini-Circuits RC*DAT',) + parser.add_argument('--set', '-s', nargs=2, metavar=("[CHs]","[ATT]"), + action=ValidateChAtt, help="Set a fixed attenuation ATT on channels CHs") + parser.add_argument('--reach', '-r', nargs=2, metavar=("[CHs]","[ATT]"), + action=ValidateChAtt, help="Perform an \"attenuation sweep\": reach attenuation ATT on channels CHs") + parser.add_argument('--duration', '-d', action='store', type=float, default=5.0, + help='DURATION for attenuation sweep to --reach given attenuation (Default: 5)',) + parser.add_argument('--progress', '-p', action='store_true', default=False, + help='If provided, will show progress during attenuation sweep',) + return parser.parse_args() + +def _exec(dev, cmd): + dev.write(1, cmd) + ans = dev.read(0x81,64) + # take from ans(wer) while valid bytes + sn = itertools.takewhile(lambda x: x < 255 and x > 0, ans) + # turn bytes into characters and accumulate into string + return "".join(map(chr, sn)) + +def devId(dev): + return f"{dev.idVendor:04x}:{dev.idProduct:04x}" + +def _info(dev): + ident = devId(dev) + SerialN = _exec(dev, "*:SN?") + ModelN = _exec(dev, "*:MN?") + Fw = _exec(dev, "*:FIRMWARE?") + + logging.info(f'{ident}: model is {ModelN}') + logging.info(f'{ident}: serial number is {SerialN}') + logging.info(f'{ident}: FW version is {Fw}') + +def _get_attenuation(dev): + ident = devId(dev) + att = _exec(dev, "*:ATT?") + # remove leading "*" and split by channels + return [float(n) for n in att[1:].split(" ")] + +def _set_attenuation(dev, ch_att): + ident = devId(dev) + for ch, att in ch_att: + #logging.debug(f"{ident}: Set attenuation channel {ch}: {att} dB") + att = round(4 * att) / 4 # round to closest 0.25dB + resp = _exec(dev, f"*:CHAN:{ch}:SETATT:{att};" ) + +def _continuous_set_attenuation(dev, ch_att_end, duration, show_progress=False, step_duration=0.25): + ident = devId(dev) + + current = _get_attenuation(dev) + steps = duration / step_duration + ch_att_step = [(ch, (att_end - current[ch - 1]) / steps) for ch, att_end in ch_att_end] + logging.info(f"{ident}: sweep attenuation during {duration} s in {steps} steps: increments {ch_att_step}") + for i in range(1, int(steps)+1): + next_att = [(ch, current[ch - 1] + i * att_step) for ch, att_step in ch_att_step] + _set_attenuation(dev, next_att) + if show_progress: + logging.debug(f"{ident}: attenuation for channel(s): {next_att}") + time.sleep(step_duration) + _set_attenuation(dev, ch_att_end) + +def _get_usb_devs(idVendor, idProduct): + devs = [] + for dev in usb.core.find(idVendor=idVendor, idProduct=idProduct, find_all=True): + for configuration in dev: + for interface in configuration: + ifnum = interface.bInterfaceNumber + if not dev.is_kernel_driver_active(ifnum): + continue + try: + dev.detach_kernel_driver(ifnum) + except e: + pass + devs.append(dev) + return devs + +if __name__ == '__main__': + # Parse the arguments + args = _parse_args() + if args.set and args.reach: + logging.error("--set and --reach are mutually exclusive") + sys.exit(1) + + # 20ce:0023 is Mini-Circuit RC*DAT device + devs = _get_usb_devs(idVendor=0x20ce, idProduct=0x0023) + if len(devs) == 0: + logging.error('no Mini-Circuits RC*DAT device detected') + sys.exit(-1) + if len(devs) > 1: + logging.error('more than one Mini-Circuits RC*DAT device detected') + sys.exit(-1) + dev = devs[0] + + if args.info: + _info(dev) + + if args.set: + _set_attenuation(dev, [(k,v) for k,v in args.set.items()]) + + if args.reach: + _continuous_set_attenuation(dev, [(k,v) for k,v in args.reach.items()], args.duration, args.progress) + + ident = devId(dev) + for i, a in enumerate(_get_attenuation(dev)): + logging.info(f"{ident}: Attenuation channel {i+1}: {a} dB") + sys.exit(0) diff --git a/ci-scripts/ci_infra.yaml b/ci-scripts/ci_infra.yaml index 85dbd10180..837f1edb82 100644 --- a/ci-scripts/ci_infra.yaml +++ b/ci-scripts/ci_infra.yaml @@ -85,7 +85,7 @@ oc-cn5g-20897: Undeploy: "! scripts/oc-cn5g-undeploy.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72" LogCollect: "! scripts/oc-cn5g-logcollect.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72 %%log_dir%%" -oc-cn5g-00102-ho: +oc-cn5g-00103-ho: Host: groot NetworkScript: echo "inet 172.21.6.111" RunIperf3Server: False diff --git a/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf b/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf index 8159eff571..f16a94b472 100644 --- a/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf +++ b/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf @@ -1,4 +1,4 @@ -Active_gNBs = ( "oai-cu"); +Active_gNBs = ( "CU-OAI"); # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity = "none"; @@ -10,7 +10,7 @@ gNBs = # cell_type = "CELL_MACRO_GNB"; - gNB_name = "oai-cu"; + gNB_name = "CU-OAI"; // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1; diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf index eb52e68566..df225e84c2 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf @@ -1,4 +1,4 @@ -Active_gNBs = ( "oai-du0"); +Active_gNBs = ( "DU0-OAI"); # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity = "none"; @@ -8,7 +8,7 @@ gNBs = ////////// Identification parameters: gNB_ID = 0xe88; gNB_DU_ID = 0xe00; - gNB_name = "oai-du0"; + gNB_name = "DU0-OAI"; // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1 ; @@ -107,7 +107,7 @@ gNBs = pucchGroupHopping = 0; hoppingId = 40; p0_nominal = -90; -# ssb_PositionsInBurs_BitmapPR + ssb_PositionsInBurst_Bitmap = 1; # ssb_periodicityServingCell @@ -159,9 +159,7 @@ MACRLCs = ( tr_n_preference = "f1"; local_n_address = "172.21.19.98"; remote_n_address = "172.21.19.98"; - local_n_portc = 500; local_n_portd = 2154; - remote_n_portc = 501; remote_n_portd = 2153; pusch_TargetSNRx10 = 200; pucch_TargetSNRx10 = 230; @@ -176,7 +174,6 @@ L1s = ( prach_dtx_threshold = 100; pucch0_dtx_threshold = 10; max_ldpc_iterations = 20; - sl_ahead = 3; ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0 } ); @@ -188,6 +185,7 @@ RUs = ( nb_rx = 1 att_tx = 0; att_rx = 0; + sl_ahead = 3; bands = [77]; max_pdschReferenceSignalPower = -27; max_rxgain = 114; diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf index 3ba7e17b46..14ec8b1e82 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf @@ -1,4 +1,4 @@ -Active_gNBs = ( "oai-du1"); +Active_gNBs = ( "DU1-OAI"); # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity = "none"; @@ -8,7 +8,7 @@ gNBs = ////////// Identification parameters: gNB_ID = 0xe88; gNB_DU_ID = 0xe01; - gNB_name = "oai-du1"; + gNB_name = "DU1-OAI"; // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1 ; @@ -107,8 +107,8 @@ gNBs = pucchGroupHopping = 0; hoppingId = 40; p0_nominal = -90; -# ssb_PositionsInBurs_BitmapPR - ssb_PositionsInBurst_Bitmap = 1; + + ssb_PositionsInBurst_Bitmap = 2; # ssb_periodicityServingCell # 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1 @@ -159,9 +159,7 @@ MACRLCs = ( tr_n_preference = "f1"; local_n_address = "172.21.19.99"; remote_n_address = "172.21.19.98"; - local_n_portc = 500; local_n_portd = 2154; - remote_n_portc = 501; remote_n_portd = 2153; pusch_TargetSNRx10 = 200; pucch_TargetSNRx10 = 230; @@ -176,7 +174,6 @@ L1s = ( prach_dtx_threshold = 100; pucch0_dtx_threshold = 10; max_ldpc_iterations = 20; - sl_ahead = 3; ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0 } ); @@ -188,6 +185,7 @@ RUs = ( nb_rx = 1 att_tx = 0; att_rx = 0; + sl_ahead = 3; bands = [77]; max_pdschReferenceSignalPower = -27; max_rxgain = 114; diff --git a/ci-scripts/main.py b/ci-scripts/main.py index 3e7be396cc..ac4ac2c3be 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -321,11 +321,15 @@ def ExecuteActionWithParam(action, ctx): # Change all execution targets to localhost node = 'localhost' command = test.findtext('command') + # Allow referencing repository workspace path in XML via %%workspace%% + command = command.replace("%%workspace%%", CONTAINERS.eNBSourceCodePath) success = cls_oaicitest.Custom_Command(HTML, node, command) elif action == 'Custom_Script': node = test.findtext('node') script = test.findtext('script') + # Allow referencing repository workspace path in XML via %%workspace%% + script = script.replace("%%workspace%%", CONTAINERS.eNBSourceCodePath) success = cls_oaicitest.Custom_Script(HTML, node, script) elif action == 'Pull_Cluster_Image': diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index 814346b5f3..77b2312409 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -51,6 +51,8 @@ 000024 000025 000026 + 000027 + 000028 @@ -58,7 +60,7 @@ DeployCoreNetwork Initialize 5G Core - oc-cn5g-00102-ho + oc-cn5g-00103-ho @@ -88,127 +90,134 @@ - Deploy_Object - Deploy gNB-CU in a container - ci-scripts/yaml_files/5g_sa_f1_b210_ho - groot - oai_cu + Custom_Command + Set attenuator to 0 for DU0 and 17 for DU1 + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 17 -d 5 Deploy_Object - Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container + Deploy gNB-CU in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho - oai_du0 groot + oai-cu + Deploy_Object + Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container + ci-scripts/yaml_files/5g_sa_f1_b210_ho + oai-du0 + groot + + + + Deploy_Object + Deploy gNB-DU1 (TDD/Band77/20MHz/B210) in a container + ci-scripts/yaml_files/5g_sa_f1_b210_ho + rocket + oai-du1 + + + Attach_UE Attach Quectel raspix - + Ping Ping: 100pings in 20sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 100 -i 0.2 5 - - Deploy_Object - Deploy gNB-DU1 (TDD/Band77/20MHz/B210) in a container - ci-scripts/yaml_files/5g_sa_f1_b210_ho - rocket - oai_du1 - - - + IdleSleep Sleep 5 - + Custom_Command - Trigger Handover - groot - echo ci trigger_f1_ho | ncat 127.0.0.1 9090 + Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 - + Ping Ping: 50pings in 10sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 50 -i 0.2 5 - + Custom_Command UE connected to DU1? groot echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" - - Custom_Command - Trigger Handover - groot - echo ci trigger_f1_ho | ncat 127.0.0.1 9090 + + Custom_Command + Attenuate DU1 by 17dB and remove attenuation on DU0 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 17 -d 5 - + Ping Ping: 50pings in 10sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 50 -i 0.2 5 - + Custom_Command UE connected to DU0? groot echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584" - - Custom_Command - Trigger Handover - groot - echo ci trigger_f1_ho | ncat 127.0.0.1 9090 + + Custom_Command + Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 - + Ping Ping: 50pings in 10sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 50 -i 0.2 5 - + Custom_Command UE connected to DU1? groot echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" - + Detach_UE true Detach Quectel raspix - + Undeploy_Object true Undeploy CU-DU0 @@ -216,7 +225,7 @@ ci-scripts/yaml_files/5g_sa_f1_b210_ho - + Undeploy_Object true Undeploy DU1 @@ -224,7 +233,15 @@ ci-scripts/yaml_files/5g_sa_f1_b210_ho - + + Custom_Command + Reset all channels on the attenuator to 0 + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1-4 0 + true + + + Clean_Test_Server_Images true Clean Test Images on Test Server @@ -232,7 +249,7 @@ oai-gnb - + Clean_Test_Server_Images true Clean Test Images on Test Server @@ -240,11 +257,11 @@ oai-gnb - + UndeployCoreNetwork true Terminate 5G Core - oc-cn5g-00102-ho + oc-cn5g-00103-ho diff --git a/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml b/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml index 24a3055701..c1c3db1607 100644 --- a/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml +++ b/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml @@ -1,5 +1,5 @@ services: - oai_cu: + oai-cu: image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} container_name: oai-cu cap_drop: @@ -25,7 +25,7 @@ services: timeout: 5s retries: 5 - oai_du0: + oai-du0: image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} container_name: oai-du0 cap_drop: @@ -57,7 +57,7 @@ services: timeout: 5s retries: 5 - oai_du1: + oai-du1: image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} container_name: oai-du1 cap_drop: diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index e7564b8678..8674103a1a 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -24,6 +24,9 @@ | gracehopper1-oai | Gracehopper1 | gNB (n78, PNF/Nvidia CUBB + VNF) | Foxconn RU, _Nvidia Aerial integration_ | | gracehopper3-oai | Gracehopper3 | build | -- | | acamas | Acamas | 4G and 5G rfsimulator| -- | +| groot | Groot | gNB (n77) | B210 (30AD30F) | +| rocket | Rocket | gNB (n77) | B210 (31F8010) | +| raspix | Raspix | COTS UE | Quectel RM520N | Note: The available resources, and their current usage, is indicated here: - [Lockable resources of jenkins-oai](https://jenkins-oai.eurecom.fr/lockable-resources/): @@ -226,6 +229,11 @@ information on how the images are built. - cacofonix + FHI72 + Metanoia (gNB), up2 (Quectel RM520N UE), OAI CN5G - OpenShift cluster for CN deployment - FHI 7.2 testing with 100 MHz bandwidth, 2 layers in DL +- [RAN-SA-Handover-CN5G](https://jenkins-oai.eurecom.fr/job/RAN-SA-Handover-CN5G/) + ~5G-NR + - groot (CU+DU0) + B210, rocket (DU1) + B210, raspix (Quectel RM520N UE), OAI CN5G + - OpenShift cluster for CN deployment + - Attenuator (mini circuits RC4DAT-6G-60) - controlled from rocket ### RAN-CI-NSA-Trigger diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index edf6f19164..246df60626 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -257,6 +257,11 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config nc->absoluteFrequencySSB); return false; } + // Check neighbour band + if (get_dl_band(cell) != nc->band) { + LOG_W(NR_RRC, "Cell %ld in neighbour config: Band mismatch (%d vs %d)\n", cell->nr_cellid, get_dl_band(cell), nc->band); + return false; + } if (get_ssb_scs(cell) != nc->subcarrierSpacing) { LOG_W(NR_RRC, "Cell %ld in neighbour config: SCS mismatch (%d vs %d)\n",