From 65beb8f324e2ee79911226e600ca1bcbd6c19dbf Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Tue, 31 Mar 2020 12:03:19 +0200 Subject: amarisoftUE: adding basic support for Amarisoft UE Change-Id: Idda0d3a040663969dd71781814198b47fff7daf3 --- example/scenarios/amarisoftue-rftype@.conf | 4 + src/osmo_gsm_tester/amarisoft_enb.py | 5 + src/osmo_gsm_tester/amarisoft_ue.py | 273 +++++++++++++++++++++ src/osmo_gsm_tester/resource.py | 3 +- .../templates/amarisoft_lteue.cfg.tmpl | 70 ++++++ .../templates/amarisoft_rf_driver.cfg.tmpl | 10 +- 6 files changed, 359 insertions(+), 6 deletions(-) create mode 100644 example/scenarios/amarisoftue-rftype@.conf create mode 100644 src/osmo_gsm_tester/amarisoft_ue.py create mode 100644 src/osmo_gsm_tester/templates/amarisoft_lteue.cfg.tmpl diff --git a/example/scenarios/amarisoftue-rftype@.conf b/example/scenarios/amarisoftue-rftype@.conf new file mode 100644 index 0000000..5bf4cfe --- /dev/null +++ b/example/scenarios/amarisoftue-rftype@.conf @@ -0,0 +1,4 @@ +resources: + modem: + - type: amarisoftue + rf_dev_type: ${param1} diff --git a/src/osmo_gsm_tester/amarisoft_enb.py b/src/osmo_gsm_tester/amarisoft_enb.py index 374393a..e6e214f 100644 --- a/src/osmo_gsm_tester/amarisoft_enb.py +++ b/src/osmo_gsm_tester/amarisoft_enb.py @@ -201,6 +201,11 @@ class AmarisoftENB(enb.eNodeB): logfile = self.log_file if self.setup_runs_locally() else self.remote_log_file config.overlay(values, dict(enb=dict(log_filename=logfile))) + # rf driver is shared between amarisoft enb and ue, so it has a + # different cfg namespace 'trx'. Copy needed values over there: + config.overlay(values, dict(trx=dict(rf_dev_type=values['enb'].get('rf_dev_type', None), + rf_dev_args=values['enb'].get('rf_dev_args', None)))) + self.gen_conf_file(self.config_file, AmarisoftENB.CFGFILE, values) self.gen_conf_file(self.config_sib1_file, AmarisoftENB.CFGFILE_SIB1, values) self.gen_conf_file(self.config_sib23_file, AmarisoftENB.CFGFILE_SIB23, values) diff --git a/src/osmo_gsm_tester/amarisoft_ue.py b/src/osmo_gsm_tester/amarisoft_ue.py new file mode 100644 index 0000000..31e814e --- /dev/null +++ b/src/osmo_gsm_tester/amarisoft_ue.py @@ -0,0 +1,273 @@ +# osmo_gsm_tester: specifics for running an SRS UE process +# +# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH +# +# Author: Pau Espin Pedrol +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import pprint + +from . import log, util, config, template, process, remote +from .run_node import RunNode +from .event_loop import MainLoop +from .ms import MS + +def rf_type_valid(rf_type_str): + return rf_type_str in ('uhd', 'zmq') + +#reference: srsLTE.git srslte_symbol_sz() +def num_prb2symbol_sz(num_prb): + if num_prb <= 6: + return 128 + if num_prb <= 15: + return 256 + if num_prb <= 25: + return 384 + if num_prb <= 50: + return 768 + if num_prb <= 75: + return 1024 + if num_prb <= 110: + return 1536 + raise log.Error('invalid num_prb %r', num_prb) + +def num_prb2base_srate(num_prb): + return num_prb2symbol_sz(num_prb) * 15 * 1000 + +class AmarisoftUE(MS): + + REMOTE_DIR = '/osmo-gsm-tester-amarisoftue' + BINFILE = 'lteue' + CFGFILE = 'amarisoft_lteue.cfg' + CFGFILE_RF = 'amarisoft_rf_driver.cfg' + LOGFILE = 'lteue.log' + + def __init__(self, suite_run, conf): + self._addr = conf.get('addr', None) + if self._addr is None: + raise log.Error('addr not set') + super().__init__('amarisoftue_%s' % self._addr, conf) + self.enb = None + self.run_dir = None + self.inst = None + self._bin_prefix = None + self.config_file = None + self.config_rf_file = None + self.log_file = None + self.process = None + self.rem_host = None + self.remote_inst = None + self.remote_config_file = None + self.remote_config_rf_file = None + self.remote_log_file = None + self.suite_run = suite_run + self.remote_user = conf.get('remote_user', None) + if not rf_type_valid(conf.get('rf_dev_type', None)): + raise log.Error('Invalid rf_dev_type=%s' % conf.get('rf_dev_type', None)) + + def bin_prefix(self): + if self._bin_prefix is None: + self._bin_prefix = os.getenv('AMARISOFT_PATH_UE', None) + if self._bin_prefix == None: + self._bin_prefix = self.suite_run.trial.get_inst('amarisoftue') + return self._bin_prefix + + def cleanup(self): + if self.process is None: + return + if self.setup_runs_locally(): + return + # copy back files (may not exist, for instance if there was an early error of process): + try: + self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file) + except Exception as e: + self.log(repr(e)) + + def setup_runs_locally(self): + return self.remote_user is None + + def netns(self): + return "amarisoftue1" + + def stop(self): + self.suite_run.stop_process(self.process) + + def connect(self, enb): + self.log('Starting amarisoftue') + self.enb = enb + self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name())) + self.configure() + if self.setup_runs_locally(): + self.start_locally() + else: + self.start_remotely() + + # send t+Enter to enable console trace + self.dbg('Enabling console trace') + self.process.stdin_write('t\n') + + def start_remotely(self): + remote_binary = self.remote_inst.child('', AmarisoftUE.BINFILE) + # setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead. + self.log('Setting RPATH for ltetue') + # amarisoftue binary needs patchelf >= 0.9+52 to avoid failing during patch. OS#4389, patchelf-GH#192. + self.rem_host.set_remote_env({'PATCHELF_BIN': '/opt/bin/patchelf-v0.10' }) + self.rem_host.change_elf_rpath(remote_binary, str(self.remote_inst)) + + # amarisoftue requires CAP_SYS_ADMIN to cjump to net network namespace: netns(CLONE_NEWNET): + # amarisoftue requires CAP_NET_ADMIN to create tunnel devices: ioctl(TUNSETIFF): + self.log('Applying CAP_SYS_ADMIN+CAP_NET_ADMIN capability to ltetue') + self.rem_host.setcap_netsys_admin(remote_binary) + + self.log('Creating netns %s' % self.netns()) + self.rem_host.create_netns(self.netns()) + + args = (remote_binary, self.remote_config_file) + self.process = self.rem_host.RemoteProcess(AmarisoftUE.BINFILE, args) + self.suite_run.remember_to_stop(self.process) + self.process.launch() + + def start_locally(self): + binary = self.inst.child('', AmarisoftUE.BINFILE) + env = {} + + # setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead. + self.log('Setting RPATH for lteue') + util.change_elf_rpath(binary, util.prepend_library_path(self.inst), self.run_dir.new_dir('patchelf')) + + # amarisoftue requires CAP_SYS_ADMIN to cjump to net network namespace: netns(CLONE_NEWNET): + # amarisoftue requires CAP_NET_ADMIN to create tunnel devices: ioctl(TUNSETIFF): + self.log('Applying CAP_SYS_ADMIN+CAP_NET_ADMIN capability to lteue') + util.setcap_netsys_admin(binary, self.run_dir.new_dir('setcap_netsys_admin')) + + self.log('Creating netns %s' % self.netns()) + util.create_netns(self.netns(), self.run_dir.new_dir('create_netns')) + + args = (binary, os.path.abspath(self.config_file)) + self.dbg(run_dir=self.run_dir, binary=binary, env=env) + self.process = process.Process(self.name(), self.run_dir, args, env=env) + self.suite_run.remember_to_stop(self.process) + self.process.launch() + + def gen_conf_file(self, path, filename, values): + self.dbg('AmarisoftUE ' + filename + ':\n' + pprint.pformat(values)) + with open(path, 'w') as f: + r = template.render(filename, values) + self.dbg(r) + f.write(r) + + def configure(self): + self.inst = util.Dir(os.path.abspath(self.bin_prefix())) + if not self.inst.isfile('', AmarisoftUE.BINFILE): + raise log.Error('No %s binary in' % AmarisoftUE.BINFILE, self.inst) + + self.config_file = self.run_dir.child(AmarisoftUE.CFGFILE) + self.config_rf_file = self.run_dir.child(AmarisoftUE.CFGFILE_RF) + self.log_file = self.run_dir.child(AmarisoftUE.LOGFILE) + + if not self.setup_runs_locally(): + self.rem_host = remote.RemoteHost(self.run_dir, self.remote_user, self._addr) + remote_prefix_dir = util.Dir(AmarisoftUE.REMOTE_DIR) + self.remote_inst = util.Dir(remote_prefix_dir.child(os.path.basename(str(self.inst)))) + remote_run_dir = util.Dir(remote_prefix_dir.child(AmarisoftUE.BINFILE)) + + self.remote_config_file = remote_run_dir.child(AmarisoftUE.CFGFILE) + self.remote_config_rf_file = remote_run_dir.child(AmarisoftUE.CFGFILE_RF) + self.remote_log_file = remote_run_dir.child(AmarisoftUE.LOGFILE) + + values = dict(ue=config.get_defaults('amarisoft')) + config.overlay(values, dict(ue=config.get_defaults('amarisoftue'))) + config.overlay(values, dict(ue=self.suite_run.config().get('amarisoft', {}))) + config.overlay(values, dict(ue=self.suite_run.config().get('modem', {}))) + config.overlay(values, dict(ue=self._conf)) + config.overlay(values, dict(ue=dict(num_antennas = self.enb.num_ports()))) + + logfile = self.log_file if self.setup_runs_locally() else self.remote_log_file + config.overlay(values, dict(ue=dict(log_filename=logfile))) + + # We need to set some specific variables programatically here to match IP addresses: + if self._conf.get('rf_dev_type') == 'zmq': + base_srate = num_prb2base_srate(self.enb.num_prb()) + rf_dev_args = 'tx_port0=tcp://' + self.addr() + ':2001' \ + + ',tx_port1=tcp://' + self.addr() + ':2003' \ + + ',rx_port0=tcp://' + self.enb.addr() + ':2000' \ + + ',rx_port1=tcp://' + self.enb.addr() + ':2002' \ + + ',tx_freq=2510e6,rx_freq=2630e6,tx_freq2=2530e6,rx_freq2=2650e6' \ + + ',id=ue,base_srate='+ str(base_srate) + config.overlay(values, dict(ue=dict(sample_rate = base_srate / (1000*1000), + rf_dev_args = rf_dev_args))) + + # Set UHD frame size as a function of the cell bandwidth on B2XX + if self._conf.get('rf_dev_type') == 'UHD' and values['ue'].get('rf_dev_args', None) is not None: + if 'b200' in values['ue'].get('rf_dev_args'): + rf_dev_args = values['ue'].get('rf_dev_args', '') + rf_dev_args += ',' if rf_dev_args != '' and not rf_dev_args.endswith(',') else '' + + if self.enb.num_prb() < 25: + rf_dev_args += 'send_frame_size=512,recv_frame_size=512' + elif self.enb.num_prb() == 25: + rf_dev_args += 'send_frame_size=1024,recv_frame_size=1024' + elif self.enb.num_prb() > 50: + rf_dev_args += 'num_recv_frames=64,num_send_frames=64' + + # For 15 and 20 MHz, further reduce over the wire format to sc12 + if self.enb.num_prb() >= 75: + rf_dev_args += ',otw_format=sc12' + + config.overlay(values, dict(ue=dict(rf_dev_args=rf_dev_args))) + + # rf driver is shared between amarisoft enb and ue, so it has a + # different cfg namespace 'trx'. Copy needed values over there: + config.overlay(values, dict(trx=dict(rf_dev_type=values['ue'].get('rf_dev_type', None), + rf_dev_args=values['ue'].get('rf_dev_args', None)))) + + self.gen_conf_file(self.config_file, AmarisoftUE.CFGFILE, values) + self.gen_conf_file(self.config_rf_file, AmarisoftUE.CFGFILE_RF, values) + + if not self.setup_runs_locally(): + self.rem_host.recreate_remote_dir(self.remote_inst) + self.rem_host.scp('scp-inst-to-remote', str(self.inst), remote_prefix_dir) + self.rem_host.recreate_remote_dir(remote_run_dir) + self.rem_host.scp('scp-cfg-to-remote', self.config_file, self.remote_config_file) + self.rem_host.scp('scp-cfg-rf-to-remote', self.config_rf_file, self.remote_config_rf_file) + + def is_connected(self, mcc_mnc=None): + return 'Network attach successful.' in (self.process.get_stdout() or '') + + def is_attached(self): + return self.is_connected() + + def running(self): + return not self.process.terminated() + + def addr(self): + return self._addr + + def run_node(self): + return RunNode(RunNode.T_REM_SSH, self._addr, self.remote_user, self._addr) + + def run_netns_wait(self, name, popen_args): + if self.setup_runs_locally(): + proc = process.NetNSProcess(name, self.run_dir.new_dir(name), self.netns(), popen_args, env={}) + else: + proc = self.rem_host.RemoteNetNSProcess(name, self.netns(), popen_args, env={}) + proc.launch_sync() + return proc + + def verify_metric(self, value, operation='avg', metric='dl_brate', criterion='gt'): + return 'metrics not yet implemented with Amarisoft UE' + +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py index bff8282..ea1b543 100644 --- a/src/osmo_gsm_tester/resource.py +++ b/src/osmo_gsm_tester/resource.py @@ -29,7 +29,7 @@ from . import schema from . import bts_sysmo, bts_osmotrx, bts_osmovirtual, bts_octphy, bts_nanobts, bts_oc2g from . import modem from . import ms_osmo_mobile -from . import srs_ue, srs_enb, amarisoft_enb, srs_epc, amarisoft_epc +from . import srs_ue, amarisoft_ue, srs_enb, amarisoft_enb, srs_epc, amarisoft_epc from .util import is_dict, is_list @@ -159,6 +159,7 @@ KNOWN_MS_TYPES = { 'ofono': modem.Modem, 'osmo-mobile': ms_osmo_mobile.MSOsmoMobile, 'srsue': srs_ue.srsUE, + 'amarisoftue': amarisoft_ue.AmarisoftUE, } diff --git a/src/osmo_gsm_tester/templates/amarisoft_lteue.cfg.tmpl b/src/osmo_gsm_tester/templates/amarisoft_lteue.cfg.tmpl new file mode 100644 index 0000000..fb34917 --- /dev/null +++ b/src/osmo_gsm_tester/templates/amarisoft_lteue.cfg.tmpl @@ -0,0 +1,70 @@ +/* UE simulator configuration file version 2018-10-18 + * Copyright (C) 2015-2018 Amarisoft + */ +{ + +%if ue.license_server_addr != '0.0.0.0': + license_server: { + server_addr: "${ue.license_server_addr}", + name: "amarisoft", + }, +%endif + +% if ue.rf_dev_type == 'zmq': + /* Force sampling rate (if uncommented) */ + sample_rate: ${ue.sample_rate}, +%endif + + +// log_options: "all.level=debug,all.max_size=32", + log_options: "all.level=error,all.max_size=0,nas.level=debug,nas.max_size=1,rrc.level=debug,rrc.max_size=1", + log_filename: "${ue.log_filename}", + + /* Enable remote API and Web interface */ + com_addr: "${ue.addr}:9002", + + include "amarisoft_rf_driver.cfg", + + bandwidth: 5, + + /* If true, allow the simulation of several UEs at the same time and + allow dynamic UE creation from remote API */ + multi_ue: false, + + cells: [ + { + dl_earfcn: 3350, /* 2850 MHz (band 7) */ +// dl_earfcn: 40620, /* 3500 MHz (band 41) */ + n_antenna_dl: 1, /* number of downlink antennas */ + n_antenna_ul: 1, + + /* must be provided if multi_ue = true */ + //global_timing_advance: 1, + } + ], + + ue_list: [ + { + /* UE capabilities */ + as_release: 8, + ue_category: 4, + + /* USIM data */ + imsi: "${ue.imsi}", + K: "${ue.ki}", + + /* If enabled, will try to use external SIM card using libpcsclite */ + //external_sim: true, + + /* Enable it to create a TUN interface for each UE PDN */ + //tun_setup_script: "ue-ifup", + } + ], + + /* If case your system have a high SNR and you are running high number of + * UEs, enable this option to optimize PDCCH decoding and save CPU + */ + pdcch_decode_opt: false, + pdcch_decode_opt_threshold: 0.1, + +} diff --git a/src/osmo_gsm_tester/templates/amarisoft_rf_driver.cfg.tmpl b/src/osmo_gsm_tester/templates/amarisoft_rf_driver.cfg.tmpl index 7f9d929..1e385d0 100644 --- a/src/osmo_gsm_tester/templates/amarisoft_rf_driver.cfg.tmpl +++ b/src/osmo_gsm_tester/templates/amarisoft_rf_driver.cfg.tmpl @@ -1,10 +1,10 @@ rf_driver: { - name: "${enb.rf_dev_type}", - sync: "${'1' if enb.rf_dev_type == 'zmq' else 'none'}", + name: "${trx.rf_dev_type}", + sync: "${'1' if trx.rf_dev_type == 'zmq' else 'none'}", /* Use this for b2x0 devices. Bandwidth >= 10 Mhz */ - args: "${enb.rf_dev_args}", -% if enb.rf_dev_type == 'zmq': + args: "${trx.rf_dev_args}", +% if trx.rf_dev_type == 'zmq': dl_sample_bits: 16, ul_sample_bits: 16, % endif @@ -12,7 +12,7 @@ rf_driver: { tx_gain: 89.0, /* TX gain (in dB) B2x0: 0 to 89.8 dB */ rx_gain: 60.0, /* RX gain (in dB) B2x0: 0 to 73 dB */ -% if enb.rf_dev_type == 'zmq': +% if trx.rf_dev_type == 'zmq': tx_time_offset: 0, % else: tx_time_offset: -150, -- cgit v1.2.3