aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore82
-rw-r--r--.gitreview3
-rw-r--r--.mailmap12
-rw-r--r--COPYING661
-rw-r--r--Makefile.am23
-rw-r--r--README.md126
-rw-r--r--configure.ac350
-rw-r--r--contrib/Makefile.am1
-rwxr-xr-xcontrib/dtx_check.gawk89
-rwxr-xr-xcontrib/dump_docs.py40
-rw-r--r--contrib/eeprom_reader.c91
-rwxr-xr-xcontrib/jenkins_bts_model.sh49
-rwxr-xr-xcontrib/jenkins_bts_trx.sh24
-rw-r--r--contrib/jenkins_common.sh47
-rwxr-xr-xcontrib/jenkins_lc15.sh25
-rwxr-xr-xcontrib/jenkins_oc2g.sh25
-rwxr-xr-xcontrib/jenkins_oct.sh26
-rwxr-xr-xcontrib/jenkins_oct_and_bts_trx.sh27
-rwxr-xr-xcontrib/jenkins_sysmobts.sh33
-rwxr-xr-xcontrib/l1fwd.init31
-rwxr-xr-xcontrib/respawn-only.sh13
-rwxr-xr-xcontrib/respawn.sh18
-rw-r--r--contrib/screenrc-l1fwd3
-rw-r--r--contrib/screenrc-sysmobts5
-rwxr-xr-xcontrib/si_check.gawk91
-rwxr-xr-xcontrib/superfemto.sh110
-rwxr-xr-xcontrib/sysmobts.init29
-rw-r--r--contrib/systemd/Makefile.am18
-rw-r--r--contrib/systemd/lc15bts-mgr.service29
-rw-r--r--contrib/systemd/oc2gbts-mgr.service29
-rw-r--r--contrib/systemd/osmo-bts-lc15.service21
-rw-r--r--contrib/systemd/osmo-bts-oc2g.service21
-rw-r--r--contrib/systemd/osmo-bts-sysmo.service21
-rw-r--r--contrib/systemd/osmo-bts-trx.service15
-rw-r--r--contrib/systemd/osmo-bts-virtual.service15
-rw-r--r--contrib/systemd/sysmobts-mgr.service12
-rw-r--r--debian/changelog755
-rw-r--r--debian/compat1
-rw-r--r--debian/control50
-rw-r--r--debian/copyright81
-rw-r--r--debian/osmo-bts-trx.install5
-rw-r--r--debian/osmo-bts-virtual.install6
-rwxr-xr-xdebian/rules28
-rw-r--r--debian/source/format1
-rw-r--r--doc/Makefile.am3
-rw-r--r--doc/control_interface.txt61
-rw-r--r--doc/examples/Makefile.am46
-rw-r--r--doc/examples/litecell15/lc15bts-mgr.cfg43
-rw-r--r--doc/examples/litecell15/osmo-bts-lc15.cfg43
-rw-r--r--doc/examples/oc2g/oc2gbts-mgr.cfg33
-rw-r--r--doc/examples/oc2g/osmo-bts.cfg38
-rw-r--r--doc/examples/octphy/osmo-bts-octphy.cfg31
-rw-r--r--doc/examples/octphy/osmo-bts-trx2dsp1.cfg34
-rw-r--r--doc/examples/sysmo/osmo-bts-sysmo.cfg29
-rw-r--r--doc/examples/sysmo/sysmobts-mgr.cfg23
-rw-r--r--doc/examples/trx/osmo-bts-trx-calypso.cfg38
-rw-r--r--doc/examples/trx/osmo-bts-trx.cfg34
-rw-r--r--doc/examples/virtual/openbsc-virtual.cfg151
-rw-r--r--doc/examples/virtual/osmo-bts-virtual.cfg61
-rw-r--r--doc/phy_link.txt57
-rw-r--r--doc/startup.txt42
-rwxr-xr-xgit-version-gen151
-rw-r--r--include/Makefile.am1
-rw-r--r--include/osmo-bts/Makefile.am5
-rw-r--r--include/osmo-bts/abis.h29
-rw-r--r--include/osmo-bts/amr.h18
-rw-r--r--include/osmo-bts/bts.h68
-rw-r--r--include/osmo-bts/bts_model.h65
-rw-r--r--include/osmo-bts/cbch.h16
-rw-r--r--include/osmo-bts/control_if.h5
-rw-r--r--include/osmo-bts/dtx_dl_amr_fsm.h44
-rw-r--r--include/osmo-bts/gsm_data.h58
-rw-r--r--include/osmo-bts/gsm_data_shared.h854
-rw-r--r--include/osmo-bts/handover.h12
-rw-r--r--include/osmo-bts/l1sap.h103
-rw-r--r--include/osmo-bts/logging.h40
-rw-r--r--include/osmo-bts/measurement.h19
-rw-r--r--include/osmo-bts/msg_utils.h48
-rw-r--r--include/osmo-bts/oml.h50
-rw-r--r--include/osmo-bts/paging.h52
-rw-r--r--include/osmo-bts/pcu_if.h24
-rw-r--r--include/osmo-bts/pcuif_proto.h195
-rw-r--r--include/osmo-bts/phy_link.h175
-rw-r--r--include/osmo-bts/power_control.h7
-rw-r--r--include/osmo-bts/rsl.h46
-rw-r--r--include/osmo-bts/scheduler.h227
-rw-r--r--include/osmo-bts/scheduler_backend.h94
-rw-r--r--include/osmo-bts/signal.h19
-rw-r--r--include/osmo-bts/tx_power.h78
-rw-r--r--include/osmo-bts/vty.h32
-rw-r--r--src/Makefile.am22
-rw-r--r--src/common/Makefile.am17
-rw-r--r--src/common/abis.c295
-rw-r--r--src/common/amr.c170
-rw-r--r--src/common/bts.c759
-rw-r--r--src/common/bts_ctrl_commands.c93
-rw-r--r--src/common/bts_ctrl_lookup.c115
-rw-r--r--src/common/cbch.c198
-rw-r--r--src/common/dtx_dl_amr_fsm.c477
-rw-r--r--src/common/gsm_data_shared.c807
-rw-r--r--src/common/handover.c164
-rw-r--r--src/common/l1sap.c1549
-rw-r--r--src/common/lchan.c49
-rw-r--r--src/common/load_indication.c94
-rw-r--r--src/common/logging.c150
-rw-r--r--src/common/main.c358
-rw-r--r--src/common/measurement.c723
-rw-r--r--src/common/msg_utils.c603
-rw-r--r--src/common/oml.c1495
-rw-r--r--src/common/paging.c630
-rw-r--r--src/common/pcu_sock.c982
-rw-r--r--src/common/phy_link.c163
-rw-r--r--src/common/power_control.c89
-rw-r--r--src/common/rsl.c2996
-rw-r--r--src/common/scheduler.c1025
-rw-r--r--src/common/scheduler_mframe.c1040
-rw-r--r--src/common/sysinfo.c177
-rw-r--r--src/common/tx_power.c306
-rw-r--r--src/common/vty.c1651
-rw-r--r--src/osmo-bts-litecell15/Makefile.am38
-rw-r--r--src/osmo-bts-litecell15/calib_file.c456
-rw-r--r--src/osmo-bts-litecell15/hw_misc.c88
-rw-r--r--src/osmo-bts-litecell15/hw_misc.h13
-rw-r--r--src/osmo-bts-litecell15/l1_if.c1586
-rw-r--r--src/osmo-bts-litecell15/l1_if.h134
-rw-r--r--src/osmo-bts-litecell15/l1_transp.h14
-rw-r--r--src/osmo-bts-litecell15/l1_transp_hw.c326
-rw-r--r--src/osmo-bts-litecell15/lc15bts.c332
-rw-r--r--src/osmo-bts-litecell15/lc15bts.h64
-rw-r--r--src/osmo-bts-litecell15/lc15bts_vty.c417
-rw-r--r--src/osmo-bts-litecell15/main.c207
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_bid.c162
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_bid.h52
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_bts.c131
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_bts.h21
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_clock.c260
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_clock.h16
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_led.c333
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_led.h22
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr.c366
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr.h422
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c292
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c195
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c378
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c1074
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_misc.c383
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_misc.h18
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_nl.c123
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_nl.h27
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_par.c232
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_par.h44
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_power.c210
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_power.h38
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_swd.c178
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_swd.h7
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_temp.c74
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_temp.h28
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_util.c164
-rw-r--r--src/osmo-bts-litecell15/oml.c1941
-rw-r--r--src/osmo-bts-litecell15/oml_router.c132
-rw-r--r--src/osmo-bts-litecell15/oml_router.h13
-rw-r--r--src/osmo-bts-litecell15/tch.c533
-rw-r--r--src/osmo-bts-litecell15/utils.c118
-rw-r--r--src/osmo-bts-litecell15/utils.h13
-rw-r--r--src/osmo-bts-oc2g/Makefile.am38
-rw-r--r--src/osmo-bts-oc2g/calib_file.c469
-rw-r--r--src/osmo-bts-oc2g/hw_info.ver_major0
-rw-r--r--src/osmo-bts-oc2g/hw_misc.c88
-rw-r--r--src/osmo-bts-oc2g/hw_misc.h13
-rw-r--r--src/osmo-bts-oc2g/l1_if.c1755
-rw-r--r--src/osmo-bts-oc2g/l1_if.h145
-rw-r--r--src/osmo-bts-oc2g/l1_transp.h14
-rw-r--r--src/osmo-bts-oc2g/l1_transp_hw.c326
-rw-r--r--src/osmo-bts-oc2g/main.c242
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.c175
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.h47
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.c129
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.h21
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.c263
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.h16
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.c332
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.h22
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.c353
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.h398
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c760
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c208
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c980
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c984
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.c381
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.h17
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.c123
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.h27
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.c249
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.h43
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.c177
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.h36
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.c178
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.h7
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.c71
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.h26
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_util.c158
-rw-r--r--src/osmo-bts-oc2g/oc2gbts.c361
-rw-r--r--src/osmo-bts-oc2g/oc2gbts.h92
-rw-r--r--src/osmo-bts-oc2g/oc2gbts_vty.c733
-rw-r--r--src/osmo-bts-oc2g/oml.c2088
-rw-r--r--src/osmo-bts-oc2g/oml_router.c132
-rw-r--r--src/osmo-bts-oc2g/oml_router.h13
-rw-r--r--src/osmo-bts-oc2g/tch.c545
-rw-r--r--src/osmo-bts-oc2g/utils.c118
-rw-r--r--src/osmo-bts-oc2g/utils.h13
-rw-r--r--src/osmo-bts-octphy/Makefile.am12
-rw-r--r--src/osmo-bts-octphy/l1_if.c1806
-rw-r--r--src/osmo-bts-octphy/l1_if.h117
-rw-r--r--src/osmo-bts-octphy/l1_oml.c1825
-rw-r--r--src/osmo-bts-octphy/l1_oml.h18
-rw-r--r--src/osmo-bts-octphy/l1_tch.c283
-rw-r--r--src/osmo-bts-octphy/l1_utils.c141
-rw-r--r--src/osmo-bts-octphy/l1_utils.h9
-rw-r--r--src/osmo-bts-octphy/main.c101
-rw-r--r--src/osmo-bts-octphy/octphy_hw_api.c404
-rw-r--r--src/osmo-bts-octphy/octphy_hw_api.h84
-rw-r--r--src/osmo-bts-octphy/octphy_vty.c466
-rw-r--r--src/osmo-bts-octphy/octpkt.c158
-rw-r--r--src/osmo-bts-octphy/octpkt.h22
-rw-r--r--src/osmo-bts-omldummy/Makefile.am8
-rw-r--r--src/osmo-bts-omldummy/bts_model.c222
-rw-r--r--src/osmo-bts-omldummy/main.c53
-rwxr-xr-xsrc/osmo-bts-omldummy/respawn.sh6
-rw-r--r--src/osmo-bts-sysmo/Makefile.am43
-rw-r--r--src/osmo-bts-sysmo/calib_file.c475
-rw-r--r--src/osmo-bts-sysmo/calib_fixup.c101
-rw-r--r--src/osmo-bts-sysmo/eeprom.c1804
-rw-r--r--src/osmo-bts-sysmo/eeprom.h304
-rw-r--r--src/osmo-bts-sysmo/femtobts.c370
-rw-r--r--src/osmo-bts-sysmo/femtobts.h110
-rw-r--r--src/osmo-bts-sysmo/hw_misc.c113
-rw-r--r--src/osmo-bts-sysmo/hw_misc.h12
-rw-r--r--src/osmo-bts-sysmo/l1_fwd.h5
-rw-r--r--src/osmo-bts-sysmo/l1_fwd_main.c236
-rw-r--r--src/osmo-bts-sysmo/l1_if.c1899
-rw-r--r--src/osmo-bts-sysmo/l1_if.h171
-rw-r--r--src/osmo-bts-sysmo/l1_transp.h14
-rw-r--r--src/osmo-bts-sysmo/l1_transp_fwd.c152
-rw-r--r--src/osmo-bts-sysmo/l1_transp_hw.c329
-rw-r--r--src/osmo-bts-sysmo/main.c199
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-calib.c537
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-layer1.c800
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-layer1.h45
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_eeprom.h44
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr.c336
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr.h122
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c384
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c547
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c186
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c321
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c531
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_misc.c275
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_misc.h65
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_nl.c120
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_nl.h24
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_par.c382
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_par.h38
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_util.c256
-rw-r--r--src/osmo-bts-sysmo/oml.c1963
-rw-r--r--src/osmo-bts-sysmo/oml_router.c129
-rw-r--r--src/osmo-bts-sysmo/oml_router.h13
-rw-r--r--src/osmo-bts-sysmo/sysmobts_ctrl.c274
-rw-r--r--src/osmo-bts-sysmo/sysmobts_vty.c542
-rw-r--r--src/osmo-bts-sysmo/tch.c684
-rw-r--r--src/osmo-bts-sysmo/utils.c116
-rw-r--r--src/osmo-bts-sysmo/utils.h12
-rw-r--r--src/osmo-bts-trx/Makefile.am10
-rw-r--r--src/osmo-bts-trx/l1_if.c782
-rw-r--r--src/osmo-bts-trx/l1_if.h82
-rw-r--r--src/osmo-bts-trx/loops.c340
-rw-r--r--src/osmo-bts-trx/loops.h27
-rw-r--r--src/osmo-bts-trx/main.c151
-rw-r--r--src/osmo-bts-trx/scheduler_trx.c1635
-rw-r--r--src/osmo-bts-trx/trx_if.c838
-rw-r--r--src/osmo-bts-trx/trx_if.h35
-rw-r--r--src/osmo-bts-trx/trx_vty.c606
-rw-r--r--src/osmo-bts-virtual/Makefile.am10
-rw-r--r--src/osmo-bts-virtual/bts_model.c176
-rw-r--r--src/osmo-bts-virtual/l1_if.c461
-rw-r--r--src/osmo-bts-virtual/l1_if.h20
-rw-r--r--src/osmo-bts-virtual/main.c144
-rw-r--r--src/osmo-bts-virtual/osmo_mcast_sock.c113
-rw-r--r--src/osmo-bts-virtual/osmo_mcast_sock.h29
-rw-r--r--src/osmo-bts-virtual/scheduler_virtbts.c619
-rw-r--r--src/osmo-bts-virtual/virtual_um.c100
-rw-r--r--src/osmo-bts-virtual/virtual_um.h31
-rw-r--r--src/osmo-bts-virtual/virtualbts_vty.c185
-rw-r--r--tests/Makefile.am45
-rw-r--r--tests/agch/Makefile.am8
-rw-r--r--tests/agch/agch_test.c240
-rw-r--r--tests/agch/agch_test.ok23
-rw-r--r--tests/cipher/Makefile.am8
-rw-r--r--tests/cipher/cipher_test.c85
-rw-r--r--tests/cipher/cipher_test.ok1
-rw-r--r--tests/handover/Makefile.am8
-rw-r--r--tests/handover/handover_test.c283
-rw-r--r--tests/handover/handover_test.ok1
-rw-r--r--tests/meas/Makefile.am9
-rw-r--r--tests/meas/meas_test.c675
-rw-r--r--tests/meas/meas_test.ok853
-rw-r--r--tests/meas/meas_testcases.h578
-rw-r--r--tests/meas/sysmobts_fr_samples.h2601
-rw-r--r--tests/misc/Makefile.am11
-rw-r--r--tests/misc/misc_test.c199
-rw-r--r--tests/misc/misc_test.ok6
-rw-r--r--tests/paging/Makefile.am8
-rw-r--r--tests/paging/paging_test.c187
-rw-r--r--tests/paging/paging_test.ok24
-rw-r--r--tests/power/Makefile.am9
-rw-r--r--tests/power/power_test.c88
-rw-r--r--tests/power/power_test.ok7
-rw-r--r--tests/stubs.c59
-rw-r--r--tests/sysmobts/Makefile.am17
-rw-r--r--tests/sysmobts/sysmobts_test.c210
-rw-r--r--tests/sysmobts/sysmobts_test.ok19
-rw-r--r--tests/testsuite.at51
-rw-r--r--tests/tx_power/Makefile.am8
-rw-r--r--tests/tx_power/tx_power_test.c247
-rw-r--r--tests/tx_power/tx_power_test.err38
-rw-r--r--tests/tx_power/tx_power_test.ok23
325 files changed, 82523 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..d81bd450
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,82 @@
+*.o
+*.a
+Makefile.in
+Makefile
+.deps
+
+btsconfig.h
+btsconfig.h.in
+
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+config.guess
+config.sub
+configure
+compile
+depcomp
+install-sh
+missing
+stamp-h1
+libtool
+ltmain.sh
+core
+core.*
+
+# git-version-gen magic
+.tarball-version
+.version
+
+src/osmo-bts-sysmo/sysmobts-calib
+src/osmo-bts-sysmo/l1fwd-proxy
+src/osmo-bts-sysmo/osmo-bts-sysmo
+src/osmo-bts-sysmo/osmo-bts-sysmo-remote
+src/osmo-bts-sysmo/sysmobts-mgr
+src/osmo-bts-sysmo/sysmobts-util
+
+src/osmo-bts-litecell15/lc15bts-mgr
+src/osmo-bts-litecell15/lc15bts-util
+src/osmo-bts-litecell15/misc/.dirstamp
+src/osmo-bts-litecell15/osmo-bts-lc15
+
+src/osmo-bts-trx/osmo-bts-trx
+
+src/osmo-bts-octphy/osmo-bts-octphy
+
+src/osmo-bts-virtual/osmo-bts-virtual
+src/osmo-bts-omldummy/osmo-bts-omldummy
+
+tests/atconfig
+tests/package.m4
+tests/agch/agch_test
+tests/paging/paging_test
+tests/cipher/cipher_test
+tests/sysmobts/sysmobts_test
+tests/meas/meas_test
+tests/misc/misc_test
+tests/handover/handover_test
+tests/tx_power/tx_power_test
+tests/testsuite
+tests/testsuite.log
+
+# Possible generated file
+doc/vty_reference.xml
+
+# Backups, vi, merges
+*~
+*.sw?
+*.orig
+*.sav
+
+# debian
+.tarball-version
+debian/autoreconf.after
+debian/autoreconf.before
+debian/files
+debian/*.debhelper.log
+debian/*.substvars
+debian/osmo-bts-trx-dbg/
+debian/osmo-bts-trx/
+debian/tmp/
+/tests/power/power_test
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 00000000..2fcadd2c
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,3 @@
+[gerrit]
+host=gerrit.osmocom.org
+project=osmo-bts
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..cda40579
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,12 @@
+Harald Welte <laforge@gnumonks.org>
+Harald Welte <laforge@gnumonks.org> <laflocal@hanuman.gnumonks.org>
+Harald Welte <laforge@gnumonks.org> <laflocal@goeller.de.gnumonks.org>
+Holger Hans Peter Freyther <holger@moiji-mobile.com> <zecke@selfish.org>
+Holger Hans Peter Freyther <holger@moiji-mobile.com> <ich@tamarin.(none)>
+Holger Hans Peter Freyther <holgre@moiji-mobile.com> <holger@freyther.de>
+Andreas Eversberg <jolly@eversberg.eu>
+Andreas Eversberg <jolly@eversberg.eu> <Andreas.Eversberg@versatel.de>
+Andreas Eversberg <jolly@eversberg.eu> <root@nuedel.(none)>
+Pablo Neira Ayuso <pablo@soleta.eu> <pablo@gnumonks.org>
+Max Suraev <msuraev@sysmocom.de>
+Tom Tsou <tom.tsou@ettus.com> <tom@tsou.cc>
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..dba13ed2
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program 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.
+
+ 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 00000000..5b49bb71
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,23 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+SUBDIRS = include src tests doc contrib
+
+
+# package the contrib and doc
+EXTRA_DIST = \
+ contrib/dump_docs.py contrib/screenrc-l1fwd \
+ contrib/l1fwd.init contrib/screenrc-sysmobts contrib/respawn.sh \
+ git-version-gen .version \
+ README.md
+
+DISTCHECK_CONFIGURE_FLAGS = \
+ --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+@RELMAKE@
+
+BUILT_SOURCES = $(top_srcdir)/.version
+
+$(top_srcdir)/.version:
+ echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+ echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..38b4bd9f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,126 @@
+osmo-bts - Osmocom BTS Implementation
+====================================
+
+This repository contains a C-language implementation of a GSM Base
+Transceiver Station (BTS). It is part of the
+[Osmocom](https://osmocom.org/) Open Source Mobile Communications
+project.
+
+This code implements Layer 2 and higher of a more or less conventional GSM BTS
+(Base Transceiver Station) - however, using an Abis/IP interface, rather than
+the old-fashioned E1/T1.
+
+Specifically, this includes
+ * BTS-side implementation of TS 08.58 (RSL) and TS 12.21 (OML)
+ * BTS-side implementation of LAPDm (using libosmocore/libosmogsm)
+ * A somewhat separated interface between those higher layer parts and the
+ Layer1 interface.
+
+Several kinds of BTS hardware are supported:
+ * sysmocom sysmoBTS
+ * Octasic octphy
+ * Nutaq litecell 1.5
+ * software-defined radio based osmo-bts-trx (e.g. USRP B210, UmTRX)
+
+Homepage
+--------
+
+The official homepage of the project is
+https://osmocom.org/projects/osmobts/wiki
+
+GIT Repository
+--------------
+
+You can clone from the official osmo-bts.git repository using
+
+ git clone git://git.osmocom.org/osmo-bts.git
+
+There is a cgit interface at http://git.osmocom.org/osmo-bts/
+
+Documentation
+-------------
+
+We provide a
+[User Manual](http://ftp.osmocom.org/docs/latest/osmobts-usermanual.pdf)
+as well as a
+[VTY Reference Manual](http://ftp.osmocom.org/docs/latest/osmobsc-vty-reference.pdf)
+and a
+[Abis refrence MAnual](http://ftp.osmocom.org/docs/latest/osmobts-abis.pdf)
+describing the OsmoBTS specific A-bis dialect.
+
+Mailing List
+------------
+
+Discussions related to osmo-bts are happening on the
+openbsc@lists.osmocom.org mailing list, please see
+https://lists.osmocom.org/mailman/listinfo/openbsc for subscription
+options and the list archive.
+
+Please observe the [Osmocom Mailing List
+Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules)
+when posting.
+
+Contributing
+------------
+
+Our coding standards are described at
+https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards
+
+We us a gerrit based patch submission/review process for managing
+contributions. Please see
+https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit for
+more details
+
+The current patch queue for osmo-bts can be seen at
+https://gerrit.osmocom.org/#/q/project:osmo-bts+status:open
+
+Known Limitations
+=================
+
+As of March 17, 2017, the following known limitations exist in this
+implementation:
+
+Common Core
+-----------
+
+ * No Extended BCCH support
+ * System Information limited to 1,2,2bis,2ter,2quater,3,4,5,6,9,13
+ * No RATSCCH in AMR
+ * Will reject TS 12.21 STARTING TIME in SET BTS ATTR / SET CHAN ATTR
+ * No support for frequency hopping
+ * No reporting of interference levels as part of TS 08.58 RF RES IND
+ * No error reporting in case PAGING COMMAND fails due to queue overflow
+ * No use of TS 08.58 BS Power and MS Power parameters
+ * No support of TS 08.58 MultiRate Control
+ * No support of TS 08.58 Supported Codec Types
+ * No support of Bter frame / ENHANCED MEASUREMENT REPORT
+
+osmo-bts-sysmo
+--------------
+
+ * No CSD / ECSD support (not planned)
+ * GSM-R frequency band supported, but no NCH/ASCI/SoLSA
+ * All timeslots on one TRX have to use same training sequence (TSC)
+ * No multi-TRX support yet, though hardware+L1 support stacking
+ * Makes no use of 12.21 Intave Parameters and Interference
+ Level Boundaries
+ * MphConfig.CNF can be returned to the wrong callback. E.g. with Tx Power
+ and ciphering. The dispatch should take a look at the hLayer3.
+
+osmo-bts-octphy
+---------------
+
+ * No support of EFR, HR voice codec (lack of PHY support?)
+ * No re-transmission of PHY primitives in case of time-out
+ * Link Quality / Measurement processing incomplete
+ * impossible to modify encryption parameters using RSL MODE MODIFY
+ * no clear indication of nominal transmit power, various power related
+ computations are likely off
+ * no OML attribute validation during bts_model_check_oml()
+
+osmo-bts-trx
+------------
+
+ * TCH/F_PDCH cannel not working as voice (https://osmocom.org/issues/1865)
+ * No BER value delivered to OsmoPCU (https://osmocom.org/issues/1855)
+ * No 11bit RACH support (https://osmocom.org/issues/1854)
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..9a8d58f1
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,350 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([osmo-bts],
+ m4_esyscmd([./git-version-gen .tarball-version]),
+ [openbsc@lists.osmocom.org])
+
+dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
+AC_CONFIG_AUX_DIR([.])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl include release helper
+RELMAKE='-include osmo-release.mk'
+AC_SUBST([RELMAKE])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT
+
+dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
+AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
+if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
+ AC_MSG_WARN([You need to install pkg-config])
+fi
+PKG_PROG_PKG_CONFIG([0.20])
+
+dnl checks for header files
+AC_HEADER_STDC
+
+dnl Checks for typedefs, structures and compiler characteristics
+
+AC_ARG_ENABLE(sanitize,
+ [AS_HELP_STRING([--enable-sanitize], [Compile with address sanitizer enabled], )],
+ [sanitize=$enableval], [sanitize="no"])
+if test x"$sanitize" = x"yes"
+then
+ CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
+ CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
+fi
+
+AC_ARG_ENABLE(werror,
+ [AS_HELP_STRING(
+ [--enable-werror],
+ [Turn all compiler warnings into errors, with exceptions:
+ a) deprecation (allow upstream to mark deprecation without breaking builds);
+ b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
+ ]
+ )],
+ [werror=$enableval], [werror="no"])
+if test x"$werror" = x"yes"
+then
+ WERROR_FLAGS="-Werror"
+ WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
+ WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
+ CFLAGS="$CFLAGS $WERROR_FLAGS"
+ CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
+fi
+
+dnl checks for libraries
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 0.12.0)
+PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.0)
+PKG_CHECK_MODULES(LIBOSMOTRAU, libosmotrau >= 0.5.0)
+
+AC_MSG_CHECKING([whether to enable support for sysmobts calibration tool])
+AC_ARG_ENABLE(sysmobts-calib,
+ AC_HELP_STRING([--enable-sysmobts-calib],
+ [enable code for sysmobts calibration tool [default=no]]),
+ [enable_sysmobts_calib="yes"],[enable_sysmobts_calib="no"])
+AC_MSG_RESULT([$enable_sysmobts_calib])
+AM_CONDITIONAL(ENABLE_SYSMOBTS_CALIB, test "x$enable_sysmobts_calib" = "xyes")
+
+AC_MSG_CHECKING([whether to enable support for sysmoBTS L1/PHY support])
+AC_ARG_ENABLE(sysmocom-bts,
+ AC_HELP_STRING([--enable-sysmocom-bts],
+ [enable code for sysmoBTS L1/PHY [default=no]]),
+ [enable_sysmocom_bts="yes"],[enable_sysmocom_bts="no"])
+AC_ARG_WITH([sysmobts], [AS_HELP_STRING([--with-sysmobts=INCLUDE_DIR], [Location of the sysmobts API header files, implies --enable-sysmocom-bts])],
+ [sysmobts_incdir="$withval"],[sysmobts_incdir="$incdir"])
+if test "x$sysmobts_incdir" != "x"; then
+ # --with-sysmobts was passed, imply enable_sysmocom_bts
+ enable_sysmocom_bts="yes"
+fi
+AC_SUBST([SYSMOBTS_INCDIR], -I$sysmobts_incdir)
+AC_MSG_RESULT([$enable_sysmocom_bts])
+AM_CONDITIONAL(ENABLE_SYSMOBTS, test "x$enable_sysmocom_bts" = "xyes")
+if test "$enable_sysmocom_bts" = "yes"; then
+ oldCPPFLAGS=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $SYSMOBTS_INCDIR -I$srcdir/include"
+ AC_CHECK_HEADER([sysmocom/femtobts/superfemto.h],[],
+ [AC_MSG_ERROR([sysmocom/femtobts/superfemto.h can not be found in $sysmobts_incdir])],
+ [#include <sysmocom/femtobts/superfemto.h>])
+
+ # Check for the sbts2050_header.h that was added after the 3.6 release
+ AC_CHECK_HEADER([sysmocom/femtobts/sbts2050_header.h], [sysmo_uc_header="yes"], [])
+ if test "$sysmo_uc_header" = "yes" ; then
+ AC_DEFINE(BUILD_SBTS2050, 1, [Define if we want to build SBTS2050])
+ fi
+
+ PKG_CHECK_MODULES(LIBGPS, libgps)
+ CPPFLAGS=$oldCPPFLAGS
+fi
+AM_CONDITIONAL(BUILD_SBTS2050, test "x$sysmo_uc_header" = "xyes")
+
+AC_MSG_CHECKING([whether to enable support for osmo-trx based L1/PHY support])
+AC_ARG_ENABLE(trx,
+ AC_HELP_STRING([--enable-trx],
+ [enable code for osmo-trx L1/PHY [default=no]]),
+ [enable_trx="yes"],[enable_trx="no"])
+AC_MSG_RESULT([$enable_trx])
+AM_CONDITIONAL(ENABLE_TRX, test "x$enable_trx" = "xyes")
+
+AC_MSG_CHECKING([whether to enable support for Octasic OCTPHY-2G])
+AC_ARG_ENABLE(octphy,
+ AC_HELP_STRING([--enable-octphy],
+ [enable code for Octasic OCTPHY-2G [default=no]]),
+ [enable_octphy="yes"],[enable_octphy="no"])
+AC_ARG_WITH([octsdr-2g], [AS_HELP_STRING([--with-octsdr-2g], [Location of the OCTSDR-2G API header files])],
+ [octsdr2g_incdir="$withval"],[octsdr2g_incdir="`cd $srcdir; pwd`/src/osmo-bts-octphy/"])
+AC_SUBST([OCTSDR2G_INCDIR], -I$octsdr2g_incdir)
+AC_MSG_RESULT([$enable_octphy])
+AM_CONDITIONAL(ENABLE_OCTPHY, test "x$enable_octphy" = "xyes")
+if test "$enable_octphy" = "yes" ; then
+ oldCPPFLAGS=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $OCTSDR2G_INCDIR -I$srcdir/include"
+
+ AC_CHECK_HEADER([octphy/octvc1/gsm/octvc1_gsm_default.h],[],
+ [AC_MSG_ERROR([octphy/octvc1/gsm/octvc1_gsm_default.h can not be found in $octsdr2g_incdir])],
+ [#include <octphy/octvc1/gsm/octvc1_gsm_default.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_GSM_TRX_CONFIG.usCentreArfcn],
+ AC_DEFINE([OCTPHY_MULTI_TRX],
+ [1],
+ [Define to 1 if your octphy header files support multi-trx]),
+ [],
+ [#include <octphy/octvc1/gsm/octvc1_gsm_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_RF_PORT_RX_STATS.Frequency],
+ AC_DEFINE([OCTPHY_USE_FREQUENCY],
+ [1],
+ [Define to 1 if your octphy header files support tOCTVC1_RADIO_FREQUENCY_VALUE type]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP.TxConfig],
+ AC_DEFINE([OCTPHY_USE_TX_CONFIG],
+ [1],
+ [Define to 1 if your octphy header files support tOCTVC1_HW_RF_PORT_ANTENNA_TX_CONFIG type]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP.RxConfig],
+ AC_DEFINE([OCTPHY_USE_RX_CONFIG],
+ [1],
+ [Define to 1 if your octphy header files support tOCTVC1_HW_RF_PORT_ANTENNA_RX_CONFIG type]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_GSM_RF_CONFIG.ulTxAntennaId],
+ AC_DEFINE([OCTPHY_USE_ANTENNA_ID],
+ [1],
+ [Define to 1 if your octphy header files support antenna ids in tOCTVC1_GSM_RF_CONFIG]),
+ [],
+ [#include <octphy/octvc1/gsm/octvc1_gsm_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP.ulClkSourceSelection],
+ AC_DEFINE([OCTPHY_USE_CLK_SOURCE_SELECTION],
+ [1],
+ [Define to 1 if your octphy header files supports ulClkSourceSelection in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.lClockError],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR],
+ [1],
+ [Define to 1 if your octphy header files supports lClockError in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.lDroppedCycles],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES],
+ [1],
+ [Define to 1 if your octphy header files supports lDroppedCycles in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulPllFreqHz],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ],
+ [1],
+ [Define to 1 if your octphy header files supports ulPllFreqHz in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulPllFractionalFreqHz],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ],
+ [1],
+ [Define to 1 if your octphy header files supports ulPllFractionalFreqHz in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSlipCnt],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT],
+ [1],
+ [Define to 1 if your octphy header files supports ulSlipCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSyncLosseCnt],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT],
+ [1],
+ [Define to 1 if your octphy header files supports ulSyncLosseCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSyncLossCnt],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT],
+ [1],
+ [Define to 1 if your octphy header files supports ulSyncLossCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSourceState],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE],
+ [1],
+ [Define to 1 if your octphy header files supports ulSourceState in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulDacState],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE],
+ [1],
+ [Define to 1 if your octphy header files supports ulDacState in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulDriftElapseTimeUs],
+ AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US],
+ [1],
+ [Define to 1 if your octphy header files supports ulDriftElapseTimeUs in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]),
+ [],
+ [#include <octphy/octvc1/hw/octvc1_hw_api.h>])
+
+ AC_CHECK_MEMBER([tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD.ulOversample16xEnableFlag],
+ AC_DEFINE([OCTPHY_USE_16X_OVERSAMPLING],
+ [1],
+ [Define to 1 if your octphy header files support 16x oversampling]),
+ [],
+ [#include <octphy/octvc1/gsm/octvc1_gsm_api.h>])
+
+ CPPFLAGS=$oldCPPFLAGS
+fi
+
+AC_MSG_CHECKING([whether to enable NuRAN Wireless Litecell 1.5 hardware support])
+AC_ARG_ENABLE(litecell15,
+ AC_HELP_STRING([--enable-litecell15],
+ [enable code for NuRAN Wireless Litecell15 bts [default=no]]),
+ [enable_litecell15="yes"],[enable_litecell15="no"])
+AC_ARG_WITH([litecell15], [AS_HELP_STRING([--with-litecell15=INCLUDE_DIR], [Location of the litecell 1.5 API header files])],
+ [litecell15_incdir="$withval"],[litecell15_incdir="$incdir"])
+AC_SUBST([LITECELL15_INCDIR], -I$litecell15_incdir)
+AC_MSG_RESULT([$enable_litecell15])
+AM_CONDITIONAL(ENABLE_LC15BTS, test "x$enable_litecell15" = "xyes")
+if test "$enable_litecell15" = "yes"; then
+ oldCPPFLAGS=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LITECELL15_INCDIR -I$srcdir/include"
+ AC_CHECK_HEADER([nrw/litecell15/litecell15.h],[],
+ [AC_MSG_ERROR([nrw/litecell15/litecell15.h can not be found in $litecell15_incdir])],
+ [#include <nrw/litecell15/litecell15.h>])
+ PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd)
+ CPPFLAGS=$oldCPPFLAGS
+fi
+
+AC_MSG_CHECKING([whether to enable NuRAN Wireless OC-2G hardware support])
+AC_ARG_ENABLE(oc2g,
+ AC_HELP_STRING([--enable-oc2g],
+ [enable code for NuRAN Wireless OC-2G bts [default=no]]),
+ [enable_oc2g="yes"],[enable_oc2g="no"])
+AC_ARG_WITH([oc2g], [AS_HELP_STRING([--with-oc2g=INCLUDE_DIR], [Location of the OC-2G API header files])],
+ [oc2g_incdir="$withval"],[oc2g_incdir="$incdir"])
+AC_SUBST([OC2G_INCDIR], -I$oc2g_incdir)
+AC_MSG_RESULT([$enable_oc2g])
+AM_CONDITIONAL(ENABLE_OC2GBTS, test "x$enable_oc2g" = "xyes")
+if test "$enable_oc2g" = "yes"; then
+ oldCPPFLAGS=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $OC2G_INCDIR -I$srcdir/include"
+ AC_CHECK_HEADER([nrw/oc2g/oc2g.h],[],
+ [AC_MSG_ERROR([nrw/oc2g/oc2g.h can not be found in $oc2g_incdir])],
+ [#include <nrw/oc2g/oc2g.h>])
+ PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd)
+ PKG_CHECK_MODULES(LIBGPS, libgps)
+ CPPFLAGS=$oldCPPFLAGS
+fi
+
+# https://www.freedesktop.org/software/systemd/man/daemon.html
+AC_ARG_WITH([systemdsystemunitdir],
+ [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
+ [with_systemdsystemunitdir=auto])
+AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
+ def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
+
+ AS_IF([test "x$def_systemdsystemunitdir" = "x"],
+ [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
+ [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
+ with_systemdsystemunitdir=no],
+ [with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
+ [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
+AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
+
+AC_MSG_RESULT([CFLAGS="$CFLAGS"])
+AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
+
+AM_CONFIG_HEADER(btsconfig.h)
+
+AC_OUTPUT(
+ src/Makefile
+ src/common/Makefile
+ src/osmo-bts-virtual/Makefile
+ src/osmo-bts-omldummy/Makefile
+ src/osmo-bts-sysmo/Makefile
+ src/osmo-bts-litecell15/Makefile
+ src/osmo-bts-oc2g/Makefile
+ src/osmo-bts-trx/Makefile
+ src/osmo-bts-octphy/Makefile
+ include/Makefile
+ include/osmo-bts/Makefile
+ tests/Makefile
+ tests/paging/Makefile
+ tests/agch/Makefile
+ tests/cipher/Makefile
+ tests/sysmobts/Makefile
+ tests/misc/Makefile
+ tests/handover/Makefile
+ tests/tx_power/Makefile
+ tests/power/Makefile
+ tests/meas/Makefile
+ doc/Makefile
+ doc/examples/Makefile
+ contrib/Makefile
+ contrib/systemd/Makefile
+ Makefile)
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 00000000..3439c97b
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = systemd
diff --git a/contrib/dtx_check.gawk b/contrib/dtx_check.gawk
new file mode 100755
index 00000000..9a3ddcf6
--- /dev/null
+++ b/contrib/dtx_check.gawk
@@ -0,0 +1,89 @@
+#!/usr/bin/gawk -f
+
+# Expected input format: FN TYPE
+
+BEGIN {
+ DELTA = 0
+ ERR = 0
+ FORCE = 0
+ FN = 0
+ SILENCE = 0
+ TYPE = ""
+ CHK = ""
+ U_MAX = 8 * 20 + 120 / 26
+ U_MIN = 8 * 20 - 120 / 26
+ F_MAX = 3 * 20 + 120 / 26
+ F_MIN = 3 * 20 - 120 / 26
+}
+
+{
+ if (NR > 2) { # we have data from previous record to compare to
+ DELTA = ($1 - FN) * 120 / 26
+ CHK = "OK"
+ if ("FACCH" == $2 && "ONSET" == TYPE) { # ONSET due to FACCH is NOT a talkspurt
+ SILENCE = 1
+ }
+ if (("UPDATE" == TYPE || "FIRST" == TYPE) && ("FACCH" == $2 || "SPEECH" == $2)) { # check for missing ONSET:
+ CHK = "FAIL: missing ONSET (" $2 ") after " TYPE "."
+ ERR++
+ }
+ if ("SID_P1" == $2) {
+ CHK = "FAIL: regular AMR payload with FT SID and STI=0 (should be either pyaload Update or STI=1)."
+ ERR++
+ }
+ if ("FORCED_FIRST" == $2 || "FORCED_NODATA" == $2 || "FORCED_F_P2" == $2 || "FORCED_F_INH" == $2 || "FORCED_U_INH" == $2) {
+ CHK = "FAIL: event " $2 " inserted by DSP."
+ FORCE++
+ ERR++
+ }
+ if ("FIRST_P2" != $2 && "FIRST_P1" == TYPE) {
+ CHK = "FAIL: " TYPE " followed by " $2 " instead of P2."
+ ERR++
+ }
+ if ("FIRST" == $2 && "FIRST" == TYPE) {
+ CHK = "FAIL: multiple SID FIRST in a row."
+ ERR++
+ }
+ if ("OK" == CHK && "ONSET" != $2) { # check inter-SID distances:
+ if ("UPDATE" == TYPE) {
+ if (DELTA > U_MAX) {
+ CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too big " DELTA "ms > " U_MAX "ms."
+ ERR++
+ }
+ if ("UPDATE" == $2 && DELTA < U_MIN) {
+ CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too small " DELTA "ms < " U_MIN "ms."
+ ERR++
+ }
+ }
+ if ("FIRST" == TYPE) {
+ if (DELTA > F_MAX) {
+ CHK = "FAIL: delta (" $1 - FN "fn) from previous SID FIRST (@" FN ") too big " DELTA "ms > " F_MAX "ms."
+ ERR++
+ }
+ if ("UPDATE" == $2 && DELTA < F_MIN) {
+ CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too small " DELTA "ms < " F_MIN "ms."
+ ERR++
+ }
+ }
+ }
+ if ("FACCH" == TYPE && "FIRST" != $2 && "FACCH" != $2 && 1 == SILENCE) { # check FACCH handling
+ CHK = "FAIL: incorrect silence resume with " $2 " after FACCH."
+ ERR++
+ }
+ }
+ if ("SPEECH" == $2 || "ONSET" == $2) { # talkspurt
+ SILENCE = 0
+ }
+ if ("UPDATE" == $2 || "FIRST" == $2) { # silence
+ SILENCE = 1
+ }
+ print $1, $2, CHK
+ if ($2 != "EMPTY") { # skip over EMPTY records
+ TYPE = $2
+ FN = $1
+ }
+}
+
+END {
+ print "Check completed: found " ERR " errors (" FORCE " events inserted by DSP) in " NR " records."
+}
diff --git a/contrib/dump_docs.py b/contrib/dump_docs.py
new file mode 100755
index 00000000..59f2a61d
--- /dev/null
+++ b/contrib/dump_docs.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+"""
+Start the process and dump the documentation to the doc dir
+"""
+
+import socket, subprocess, time,os
+
+env = os.environ
+env['L1FWD_BTS_HOST'] = '127.0.0.1'
+
+bts_proc = subprocess.Popen(["./src/osmo-bts-sysmo/sysmobts-remote",
+ "-c", "./doc/examples/sysmo/osmo-bts.cfg"], env = env,
+ stdin=None, stdout=None)
+time.sleep(1)
+
+try:
+ sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sck.setblocking(1)
+ sck.connect(("localhost", 4241))
+ sck.recv(4096)
+
+ # Now send the command
+ sck.send("show online-help\r")
+ xml = ""
+ while True:
+ data = sck.recv(4096)
+ xml = "%s%s" % (xml, data)
+ if data.endswith('\r\nOsmoBTS> '):
+ break
+
+ # Now write everything until the end to the file
+ out = open('doc/vty_reference.xml', 'w')
+ out.write(xml[18:-11])
+ out.close()
+finally:
+ # Clean-up
+ bts_proc.kill()
+ bts_proc.wait()
+
diff --git a/contrib/eeprom_reader.c b/contrib/eeprom_reader.c
new file mode 100644
index 00000000..d0d41363
--- /dev/null
+++ b/contrib/eeprom_reader.c
@@ -0,0 +1,91 @@
+/* GPLv3+ to read sysmobts-v2 revD or later EEPROM from userspace */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <sys/ioctl.h>
+
+#include <errno.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+
+
+/* Can read a 16bit at24 eeprom with 8192 byte in storage (24c64) */
+static int dump_eeprom(int fd, int out)
+{
+#define STEP 8192
+#define SIZE 8192
+ uint8_t buf[STEP + 2];
+ int rc = 0;
+ int i;
+
+ for (i = 0; i < SIZE; i += STEP) {
+ /* write the address */
+ buf[0] = i >> 8;
+ buf[1] = i;
+ rc = write(fd, buf, 2);
+ if (rc != 2) {
+ fprintf(stderr, "writing address failed: %d/%d/%s\n", rc, errno, strerror(errno));
+ return 1;
+ }
+
+ /* execute step amount of reads */
+ rc = read(fd, buf, STEP);
+ if (rc != STEP) {
+ fprintf(stderr, "Failed to read: %d/%d/%s\n", rc, errno, strerror(errno));
+ return 1;
+ }
+
+ write(out, buf, STEP);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int i2c_fd, out_fd;
+ char *filename = "/dev/i2c-1";
+ char *out_file = "eeprom.out";
+ int addr = 0x50;
+ int rc;
+
+ i2c_fd = open(filename, O_RDWR);
+ if (i2c_fd < 0) {
+ fprintf(stderr, "Failed to open i2c device %d/%d/%s\n",
+ i2c_fd, errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ /* force using that address it is already bound with a driver */
+ rc = ioctl(i2c_fd, I2C_SLAVE_FORCE, addr);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to claim i2c device %d/%d/%s\n",
+ rc, errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if (argc >= 2)
+ out_file = argv[1];
+ out_fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (out_fd < 0) {
+ fprintf(stderr, "Failed to open out device %s %d/%d/%s\n",
+ out_file, rc, errno, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if (dump_eeprom(i2c_fd, out_fd) != 0) {
+ unlink(out_file);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/contrib/jenkins_bts_model.sh b/contrib/jenkins_bts_model.sh
new file mode 100755
index 00000000..9aa943fa
--- /dev/null
+++ b/contrib/jenkins_bts_model.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+# this is a dispatcher script which will call the bts-model-specific
+# script based on the bts model specified as command line argument
+
+bts_model="$1"
+
+if [ "x$bts_model" = "x" ]; then
+ echo "Error: You have to specify the BTS model as first argument, e.g. $0 sysmo"
+ exit 2
+fi
+
+if [ ! -d "./contrib" ]; then
+ echo "Run ./contrib/jenkins_bts_model.sh from the root of the osmo-bts tree"
+ exit 1
+fi
+
+set -x -e
+
+case "$bts_model" in
+
+ sysmo)
+ ./contrib/jenkins_sysmobts.sh
+ ;;
+
+ oct)
+ ./contrib/jenkins_oct.sh
+ ;;
+
+ lc15)
+ ./contrib/jenkins_lc15.sh
+ ;;
+
+ oc2g)
+ ./contrib/jenkins_oc2g.sh
+ ;;
+
+ trx)
+ ./contrib/jenkins_bts_trx.sh
+ ;;
+
+ oct+trx)
+ ./contrib/jenkins_oct_and_bts_trx.sh
+ ;;
+
+ *)
+ set +x
+ echo "Unknown BTS model '$bts_model'"
+ ;;
+esac
diff --git a/contrib/jenkins_bts_trx.sh b/contrib/jenkins_bts_trx.sh
new file mode 100755
index 00000000..54efa56f
--- /dev/null
+++ b/contrib/jenkins_bts_trx.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-trx
+
+# shellcheck source=contrib/jenkins_common.sh
+. $(dirname "$0")/jenkins_common.sh
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+
+osmo-build-dep.sh libosmo-abis
+
+cd "$deps"
+
+configure_flags="\
+ --enable-sanitize \
+ --enable-werror \
+ --enable-trx \
+ "
+
+build_bts "osmo-bts-trx" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/jenkins_common.sh b/contrib/jenkins_common.sh
new file mode 100644
index 00000000..bdb12d55
--- /dev/null
+++ b/contrib/jenkins_common.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# this is a common helper script that is shared among all BTS model
+# specific helper scripts like jenkins_sysmobts.sh. You shouldn't call
+# this directly, but rather indirectly via the bts-specific scripts
+
+if ! [ -x "$(command -v osmo-deps.sh)" ]; then
+ echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !"
+ exit 2
+fi
+
+set -ex
+
+base="$PWD"
+deps="$base/deps"
+inst="$deps/install"
+
+export deps inst
+
+osmo-clean-workspace.sh
+
+mkdir -p "$deps"
+
+verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
+
+# generic project build function, usage:
+# build "PROJECT-NAME" "CONFIGURE OPTIONS"
+build_bts() {
+ set +x
+ echo
+ echo
+ echo
+ echo " =============================== $1 ==============================="
+ echo
+ set -x
+
+ cd $deps
+ osmo-deps.sh libosmocore
+ cd $base
+ shift
+ conf_flags="$*"
+ autoreconf --install --force
+ ./configure $conf_flags
+ $MAKE $PARALLEL_MAKE
+ $MAKE check || cat-testlogs.sh
+ DISTCHECK_CONFIGURE_FLAGS="$conf_flags" $MAKE distcheck || cat-testlogs.sh
+}
diff --git a/contrib/jenkins_lc15.sh b/contrib/jenkins_lc15.sh
new file mode 100755
index 00000000..c7d62c96
--- /dev/null
+++ b/contrib/jenkins_lc15.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-lc15
+
+# shellcheck source=contrib/jenkins_common.sh
+. $(dirname "$0")/jenkins_common.sh
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmo-abis
+
+cd "$deps"
+osmo-layer1-headers.sh lc15 "$FIRMWARE_VERSION"
+
+configure_flags="\
+ --enable-sanitize \
+ --with-litecell15=$deps/layer1-headers/inc/ \
+ --enable-litecell15 \
+ "
+
+build_bts "osmo-bts-lc15" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/jenkins_oc2g.sh b/contrib/jenkins_oc2g.sh
new file mode 100755
index 00000000..b8badce6
--- /dev/null
+++ b/contrib/jenkins_oc2g.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-oc2g
+
+# shellcheck source=contrib/jenkins_common.sh
+. $(dirname "$0")/jenkins_common.sh
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmo-abis
+
+cd "$deps"
+osmo-layer1-headers.sh oc2g "$FIRMWARE_VERSION"
+
+configure_flags="\
+ --enable-sanitize \
+ --with-oc2g=$deps/layer1-headers/inc/ \
+ --enable-oc2g \
+ "
+
+build_bts "osmo-bts-oc2g" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/jenkins_oct.sh b/contrib/jenkins_oct.sh
new file mode 100755
index 00000000..bd57dd18
--- /dev/null
+++ b/contrib/jenkins_oct.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-octphy
+
+# shellcheck source=contrib/jenkins_common.sh
+. $(dirname "$0")/jenkins_common.sh
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmo-abis
+
+cd "$deps"
+osmo-layer1-headers.sh oct "$FIRMWARE_VERSION"
+
+configure_flags="\
+ --enable-sanitize \
+ --enable-werror \
+ --with-octsdr-2g=$deps/layer1-headers/ \
+ --enable-octphy \
+ "
+
+build_bts "osmo-bts-octphy" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/jenkins_oct_and_bts_trx.sh b/contrib/jenkins_oct_and_bts_trx.sh
new file mode 100755
index 00000000..67f67aa4
--- /dev/null
+++ b/contrib/jenkins_oct_and_bts_trx.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-octphy + osmo-bts-trx
+
+# shellcheck source=contrib/jenkins_common.sh
+. $(dirname "$0")/jenkins_common.sh
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+
+osmo-build-dep.sh libosmo-abis
+
+cd "$deps"
+
+osmo-layer1-headers.sh oct "$FIRMWARE_VERSION"
+
+configure_flags="\
+ --enable-werror \
+ --with-octsdr-2g=$deps/layer1-headers/ \
+ --enable-octphy \
+ --enable-trx \
+ "
+
+build_bts "osmo-bts-octphy+trx" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/jenkins_sysmobts.sh b/contrib/jenkins_sysmobts.sh
new file mode 100755
index 00000000..d0d05ae6
--- /dev/null
+++ b/contrib/jenkins_sysmobts.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-sysmo
+
+# shellcheck source=contrib/jenkins_common.sh
+. $(dirname "$0")/jenkins_common.sh
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+
+osmo-build-dep.sh libosmo-abis
+
+cd "$deps"
+osmo-layer1-headers.sh sysmo "$FIRMWARE_VERSION"
+mkdir -p "$inst/include/sysmocom/femtobts"
+ln -s $deps/layer1-headers/include/* "$inst/include/sysmocom/femtobts/"
+
+configure_flags="\
+ --enable-sanitize \
+ --enable-werror \
+ --enable-sysmocom-bts \
+ --with-sysmobts=$inst/include/ \
+ "
+
+# This will not work for the femtobts
+if [ $FIRMWARE_VERSION != "femtobts_v2.7" ]; then
+ configure_flags="$configure_flags --enable-sysmobts-calib"
+fi
+
+build_bts "osmo-bts-sysmo" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/l1fwd.init b/contrib/l1fwd.init
new file mode 100755
index 00000000..b228580e
--- /dev/null
+++ b/contrib/l1fwd.init
@@ -0,0 +1,31 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: l1fwd
+# Required-Start:
+# Required-Stop: $local_fs
+# Default-Start: 5
+# Default-Stop: 0 6
+# Short-Description: Start screen session with l1fwd software
+# Description:
+### END INIT INFO
+
+. /etc/default/rcS
+
+case "$1" in
+ start)
+ /usr/bin/screen -d -m -c /etc/osmocom/screenrc-l1fwd
+ ;;
+ stop)
+ echo "This script doesn't support stop"
+ exit 1
+ ;;
+ restart|reload|force-reload)
+ exit 0
+ ;;
+ show)
+ ;;
+ *)
+ echo "Usage: sysmobts {start|stop|show|reload|restart}" >&2
+ exit 1
+ ;;
+esac
diff --git a/contrib/respawn-only.sh b/contrib/respawn-only.sh
new file mode 100755
index 00000000..478abd66
--- /dev/null
+++ b/contrib/respawn-only.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+PID=$$
+echo "-1000" > /proc/$PID/oom_score_adj
+
+trap "{ kill 0; kill -2 0; }" EXIT
+
+while [ -f $1 ]; do
+ (echo "0" > /proc/self/oom_score_adj && exec nice -n -20 $*) &
+ LAST_PID=$!
+ wait $LAST_PID
+ sleep 10s
+done
diff --git a/contrib/respawn.sh b/contrib/respawn.sh
new file mode 100755
index 00000000..196edadc
--- /dev/null
+++ b/contrib/respawn.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+PID=$$
+echo "-1000" > /proc/$PID/oom_score_adj
+
+trap "kill 0" EXIT
+
+while [ -e /etc/passwd ]; do
+ cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0
+ sleep 2s
+ cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0
+ sleep 1s
+ echo "0" > /sys/class/leds/activity_led/brightness
+ (echo "0" > /proc/self/oom_score_adj && exec nice -n -20 $*) &
+ LAST_PID=$!
+ wait $LAST_PID
+ sleep 10s
+done
diff --git a/contrib/screenrc-l1fwd b/contrib/screenrc-l1fwd
new file mode 100644
index 00000000..4256a381
--- /dev/null
+++ b/contrib/screenrc-l1fwd
@@ -0,0 +1,3 @@
+chdir /tmp
+screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/l1fwd-proxy
+detach
diff --git a/contrib/screenrc-sysmobts b/contrib/screenrc-sysmobts
new file mode 100644
index 00000000..9c810d9f
--- /dev/null
+++ b/contrib/screenrc-sysmobts
@@ -0,0 +1,5 @@
+chdir /tmp
+screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/osmo-bts-sysmo -c /etc/osmocom/osmo-bts.cfg -r 1 -M
+screen -t PCU 1 /etc/osmocom/respawn-only.sh /usr/bin/osmo-pcu -c /etc/osmocom/osmo-pcu.cfg -e
+screen -t MGR 2 /etc/osmocom/respawn-only.sh /usr/bin/sysmobts-mgr -n -c /etc/osmocom/sysmobts-mgr.cfg
+detach
diff --git a/contrib/si_check.gawk b/contrib/si_check.gawk
new file mode 100755
index 00000000..0a54ed46
--- /dev/null
+++ b/contrib/si_check.gawk
@@ -0,0 +1,91 @@
+#!/usr/bin/gawk -f
+
+# Usage example:
+# tshark -2 -t r -E 'header=n' -E 'separator=,' -E 'quote=n' -T fields -e gsmtap.frame_nr -e gsmtap.ts -e gsmtap.arfcn -e _ws.col.Info -Y 'gsmtap' -r test.pcapng.gz | grep Information | env ARFCN=878 ./si_check.gawk
+# read summary on number of bis/ter messages and adjust BT_BOTH and BT_NONE environment variables accordingly
+
+BEGIN {
+ FS = ","
+ FAILED = 0
+ IGNORE = 0
+ BIS = 0
+ TER = 0
+ QUA = 0
+ BT_BOTH = ENVIRON["BOTH"]
+ BT_NONE = ENVIRON["NONE"]
+ TC_INDEX = 0
+ TC4[4] = 0
+}
+
+{ # expected .csv input as follows: gsmtap.frame_nr,gsmtap.ts,gsmtap.arfcn,_ws.col.Info
+ if ("ARFCN" in ENVIRON) { # ARFCN filtering is enabled
+ if (ENVIRON["ARFCN"] != $3) { # ignore other ARFCNs
+ IGNORE++
+ next
+ }
+ }
+ type = get_si_type($4)
+ tc = get_tc($1)
+ result = "FAIL"
+
+ if (1 == check_si_tc(tc, type)) { result = "OK" }
+ else { FAILED++ }
+
+ if (4 == tc) {
+ TC4[TC_INDEX] = type
+ TC_INDEX = int((TC_INDEX + 1) % 4)
+ if (0 == check_tc4c(type) && "OK" == result) {
+ result = "FAIL"
+ FAILED++
+ }
+ }
+ if (type == "2bis") { BIS++ }
+ if (type == "2ter") { TER++ }
+ if (type == "2quater") { QUA++ }
+ # for (i in TC4) print TC4[i] # debugging
+ printf "ARFCN=%d FN=%d TS=%d TC=%d TYPE=%s %s\n", $3, $1, $2, tc, type, result
+}
+
+END {
+ printf "check completed: total %d, failed %d, ignored %d, ok %d\nSI2bis = %d, SI2ter = %d, SI2quater = %d\n", NR, FAILED, IGNORE, NR - FAILED - IGNORE, BIS, TER, QUA
+ if ((BIS > 0 || TER > 0) && BT_NONE) { printf "please re-run with correct environment: unset 'NONE' variable\n" }
+ if ((BIS > 0 && TER > 0) && !BT_BOTH) { printf "please re-run with correct environment: set 'BOTH' variable\n" }
+}
+
+func get_si_type(s, x) { # we rely on format of Info column in wireshark output - if it's changed we're screwed
+ return x[split(s, x, " ")]
+}
+
+func get_tc(f) { # N. B: all numbers in awk are float
+ return int(int(f / 51) % 8)
+}
+
+func check_tc4c(si, count) { # check for "once in 4 consecutive occurrences" rule
+ count = 0
+ if ("2quater" != si || "2ter" != si) { return 1 } # rules is not applicable to other types
+ if (BT_NONE && "2quater" == si) { return 0 } # should be on TC=5 instead
+ if (!BT_BOTH && "2ter" == si) { return 0 } # should be on TC=5 instead
+ if (0 in TC4 && 1 in TC4 && 2 in TC4 && 3 in TC4) { # only check if we have 4 consecutive occurrences already
+ if (si == TC4[0]) { count++ }
+ if (si == TC4[1]) { count++ }
+ if (si == TC4[2]) { count++ }
+ if (si == TC4[3]) { count++ }
+ if (0 == count) { return 0 }
+ }
+ return 1
+}
+
+func check_si_tc(tc, si) { # check that SI scheduling on BCCH Norm is matching rules from 3GPP TS 05.02 § 6.3.1.3
+ switch (si) {
+ case "1": return (0 == tc) ? 1 : 0
+ case "2": return (1 == tc) ? 1 : 0
+ case "2bis": return (5 == tc) ? 1 : 0
+ case "13": return (4 == tc) ? 1 : 0
+ case "9": return (4 == tc) ? 1 : 0
+ case "2ter": if (BT_BOTH) { return (4 == tc) ? 1 : 0 } else { return (5 == tc) ? 1 : 0 }
+ case "2quater": if (BT_NONE) { return (5 == tc) ? 1 : 0 } else { return (4 == tc) ? 1 : 0 }
+ case "3": return (2 == tc || 6 == tc) ? 1 : 0
+ case "4": return (3 == tc || 7 == tc) ? 1 : 0
+ }
+ return 0
+}
diff --git a/contrib/superfemto.sh b/contrib/superfemto.sh
new file mode 100755
index 00000000..19dc4107
--- /dev/null
+++ b/contrib/superfemto.sh
@@ -0,0 +1,110 @@
+#!/bin/sh
+
+# Split common DSP call log file (produced by superfemto-compatible firmware) into 4 separate call leg files (MO/MT & DL/UL) with events in format "FN EVENT_TYPE":
+# MO Mobile Originated
+# MT Mobile Terminated
+# DL DownLink (BTS -> L1)
+# UL UpLink (L1 -> BTS)
+
+if [ -z $1 ]; then
+ echo "expecting DSP log file name as parameter"
+ exit 1
+fi
+
+# MO handle appear 1st in the logs
+MO=$(grep 'h=' $1 | head -n 1 | cut -f2 -d',' | cut -f2 -d= | cut -f1 -d']')
+
+# direction markers:
+DLST="_CodeBurst"
+ULST="_DecodeAndIdentify"
+
+# DL sed filters:
+D_EMP='s/ Empty frame request!/EMPTY/i'
+D_FAC='s/ Coding a FACCH\/. frame !!/FACCH/i'
+D_FST='s/ Coding a RTP SID First frame !!/FIRST/i'
+D_FS1='s/ Coding a SID First P1 frame !!/FIRST_P1/i'
+D_FS2='s/ Coding a SID First P2 frame !!/FIRST_P2/i'
+D_RP1='s/ Coding a RTP SID P1 frame !!/SID_P1/i'
+D_UPD='s/ Coding a RTP SID Update frame !!/UPDATE/i'
+D_SPE='s/ Coding a RTP Speech frame !!/SPEECH/i'
+D_ONS='s/ Coding a Onset frame !!/ONSET/i'
+D_FO1='s/ A speech frame is following a NoData or SID First without an Onset./FORCED_FIRST/i'
+D_FO2='s/ A speech frame is following a NoData without an Onset./FORCED_NODATA/i'
+D_FP2='s/ A speech frame is following a NoData or SID_FIRST_P2 without an Onset./FORCED_F_P2/i'
+D_FIN='s/ A speech frame is following a SID_FIRST without inhibit. A SID_FIRST_INH will be inserted./FORCED_F_INH/i'
+D_UIN='s/ A speech frame is following a SID_UPDATE without inhibit. A SID_UPDATE_INH will be inserted./FORCED_U_INH/i'
+
+# UL sed filters:
+U_NOD='s/ It is a No Data frame !!/NODATA/i'
+U_ONS='s/ It is an ONSET frame !!/ONSET/i'
+U_UPD='s/ It is a SID UPDATE frame !!/UPDATE/i'
+U_FST='s/ It is a SID FIRST frame !!/FIRST/i'
+U_FP1='s/ It is a SID-First P1 frame !!/FIRST_P1/i'
+U_FP2='s/ It is a SID-First P2 frame !!/FIRST_P2/i'
+U_SPE='s/ It is a SPEECH frame *!!/SPEECH/i'
+U_UIN='s/ It is a SID update InH frame !!/UPD_INH/i'
+U_FIN='s/ It is a SID-First InH frame !!/FST_INH/i'
+U_RAT='s/ It is a RATSCCH data frame !!/RATSCCH/i'
+
+DL () { # filter downlink-related entries
+ grep $DLST $1 > $1.DL.tmp
+}
+
+UL () { # uplink does not require special fix
+ grep $ULST $1 > $1.UL.tmp.fix
+}
+
+DL $1
+UL $1
+
+FIX() { # add MO/MT marker from preceding line to inserted ONSETs so filtering works as expected
+ cat $1.DL.tmp | awk 'BEGIN{ FS=" h="; H="" } { if (NF > 1) { H = $2; print $1 "h=" $2 } else { print $1 ", h=" H } }' > $1.DL.tmp.fix
+}
+
+FIX $1
+
+MO() { # filter MO call DL or UL logs
+ grep "h=$MO" $1.tmp.fix > $1.MO.raw
+}
+
+MT() { # filter MT call DL or UL logs
+ grep -v "h=$MO" $1.tmp.fix > $1.MT.raw
+}
+
+MO $1.DL
+MT $1.DL
+MO $1.UL
+MT $1.UL
+
+PREP() { # prepare logs for reformatting
+ cat $1.raw | cut -f2 -d')' | cut -f1 -d',' | cut -f2 -d'>' | sed 's/\[u32Fn/fn/' | sed 's/\[ u32Fn/fn/' | sed 's/fn = /fn=/' | sed 's/fn=//' | sed 's/\[Fn=//' | sed 's/ An Onset will be inserted.//' > $1.tmp1
+}
+
+PREP "$1.DL.MT"
+PREP "$1.DL.MO"
+PREP "$1.UL.MT"
+PREP "$1.UL.MO"
+
+RD() { # reformat DL logs for consistency checks
+ cat $1.tmp1 | sed "$D_FST" | sed "$D_SPE" | sed "$D_FS1" | sed "$D_FS2" | sed "$D_UIN" | sed "$D_FIN" | sed "$D_UPD" | sed "$D_INH" | sed "$D_RP1" | sed "$D_ONS" | sed "$D_EMP" | sed "$D_FAC" | sed "$D_FO1" | sed "$D_FO2" | sed "$D_FP2" > $1.tmp2
+}
+
+RU() { # reformat UL logs for consistency checks
+ cat $1.tmp1 | sed "$U_FST" | sed "$U_SPE" | sed "$U_FP1" | sed "$U_FP2" | sed "$U_UPD" | sed "$U_ONS" | sed "$U_NOD" | sed "$U_UIN" | sed "$U_FIN" | sed "$U_RAT" > $1.tmp2
+}
+
+RD "$1.DL.MT"
+RD "$1.DL.MO"
+RU "$1.UL.MT"
+RU "$1.UL.MO"
+
+SW() { # swap fields
+ cat $1.tmp2 | awk '{ print $2, $1 }' > $1
+}
+
+SW "$1.DL.MT"
+SW "$1.DL.MO"
+SW "$1.UL.MT"
+SW "$1.UL.MO"
+
+rm $1.*.tmp*
diff --git a/contrib/sysmobts.init b/contrib/sysmobts.init
new file mode 100755
index 00000000..2b9d2814
--- /dev/null
+++ b/contrib/sysmobts.init
@@ -0,0 +1,29 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: sysmobts
+# Required-Start:
+# Required-Stop: $local_fs
+# Default-Start: 5
+# Default-Stop: 0 6
+# Short-Description: Start screen session with sysmobts software
+# Description:
+### END INIT INFO
+
+case "$1" in
+ start)
+ /usr/bin/screen -d -m -c /etc/osmocom/screenrc-sysmobts -S sysmobts
+ ;;
+ stop)
+ /usr/bin/screen -d -r sysmobts -X quit
+ exit 1
+ ;;
+ restart|reload|force-reload)
+ exit 0
+ ;;
+ show)
+ ;;
+ *)
+ echo "Usage: sysmobts {start|stop|show|reload|restart}" >&2
+ exit 1
+ ;;
+esac
diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am
new file mode 100644
index 00000000..16463087
--- /dev/null
+++ b/contrib/systemd/Makefile.am
@@ -0,0 +1,18 @@
+if HAVE_SYSTEMD
+SYSTEMD_SERVICES = osmo-bts-virtual.service
+
+if ENABLE_SYSMOBTS
+SYSTEMD_SERVICES += osmo-bts-sysmo.service sysmobts-mgr.service
+endif
+
+if ENABLE_TRX
+SYSTEMD_SERVICES += osmo-bts-trx.service
+endif
+
+if ENABLE_LC15BTS
+SYSTEMD_SERVICES += osmo-bts-lc15.service lc15bts-mgr.service
+endif
+
+EXTRA_DIST = $(SYSTEMD_SERVICES)
+systemdsystemunit_DATA = $(SYSTEMD_SERVICES)
+endif # HAVE_SYSTEMD
diff --git a/contrib/systemd/lc15bts-mgr.service b/contrib/systemd/lc15bts-mgr.service
new file mode 100644
index 00000000..bf788e61
--- /dev/null
+++ b/contrib/systemd/lc15bts-mgr.service
@@ -0,0 +1,29 @@
+[Unit]
+Description=osmo-bts manager for LC15 / sysmoBTS 2100
+After=lc15-sysdev-remap.service
+Wants=lc15-sysdev-remap.service
+
+[Service]
+Type=simple
+NotifyAccess=all
+WatchdogSec=21780s
+Restart=always
+RestartSec=2
+
+# Make sure directories and symbolic link exist
+ExecStartPre=/bin/sh -c 'test -d /mnt/storage/var/run/lc15bts-mgr || mkdir -p /mnt/storage/var/run/lc15bts-mgr ; test -d /var/run/lc15bts-mgr || ln -sf /mnt/storage/var/run/lc15bts-mgr/ /var/run'
+# Make sure BTS operation hour exist
+ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/lc15bts-mgr/hours-running || echo 0 > /mnt/storage/var/run/lc15bts-mgr/hours-running'
+# Shutdown all PA correctly
+ExecStartPre=/bin/sh -c 'echo disabled > /var/lc15/pa-state/pa0/state; echo disabled > /var/lc15/pa-state/pa1/state'
+ExecStartPre=/bin/sh -c 'echo 0 > /var/lc15/pa-supply/max_microvolts; echo 0 > /var/lc15/pa-supply/min_microvolts'
+
+ExecStart=/usr/bin/lc15bts-mgr -s -c /etc/osmocom/lc15bts-mgr.cfg
+
+# Shutdown all PA correctly
+ExecStopPost=/bin/sh -c 'echo disabled > /var/lc15/pa-state/pa0/state; echo disabled > /var/lc15/pa-state/pa1/state'
+ExecStopPost=/bin/sh -c 'echo 0 > /var/lc15/pa-supply/max_microvolts; echo 0 > /var/lc15/pa-supply/min_microvolts'
+
+[Install]
+WantedBy=multi-user.target
+Alias=osmo-bts-mgr.service
diff --git a/contrib/systemd/oc2gbts-mgr.service b/contrib/systemd/oc2gbts-mgr.service
new file mode 100644
index 00000000..ed915b33
--- /dev/null
+++ b/contrib/systemd/oc2gbts-mgr.service
@@ -0,0 +1,29 @@
+[Unit]
+Description=osmo-bts manager for OC-2G
+After=oc2g-sysdev-remap.service
+Wants=oc2g-sysdev-remap.service
+
+[Service]
+Type=simple
+NotifyAccess=all
+WatchdogSec=21780s
+Restart=always
+RestartSec=2
+
+# Make sure directories and symbolic link exist
+ExecStartPre=/bin/sh -c 'test -d /mnt/storage/var/run/oc2gbts-mgr || mkdir -p /mnt/storage/var/run/oc2gbts-mgr ; test -d /var/run/oc2gbts-mgr || ln -sf /mnt/storage/var/run/oc2gbts-mgr/ /var/run'
+# Make sure BTS operation hour exist
+ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/oc2gbts-mgr/hours-running || echo 0 > /mnt/storage/var/run/oc2gbts-mgr/hours-running'
+# Shutdown all PA correctly
+ExecStartPre=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;'
+#ExecStartPre=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts'
+
+ExecStart=/usr/bin/oc2gbts-mgr -s -c /etc/osmocom/oc2gbts-mgr.cfg
+
+# Shutdown all PA correctly
+ExecStopPost=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;'
+#ExecStopPost=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts'
+
+[Install]
+WantedBy=multi-user.target
+Alias=osmo-bts-mgr.service
diff --git a/contrib/systemd/osmo-bts-lc15.service b/contrib/systemd/osmo-bts-lc15.service
new file mode 100644
index 00000000..90e7fc29
--- /dev/null
+++ b/contrib/systemd/osmo-bts-lc15.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=osmo-bts for LC15 / sysmoBTS 2100
+
+[Service]
+Type=simple
+ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness'
+ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr1/brightness'
+ExecStart=/usr/bin/osmo-bts-lc15 -t 2 -s -c /etc/osmocom/osmo-bts-lc15.cfg -M
+ExecStopPost=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness'
+ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/usr1/brightness'
+Restart=always
+RestartSec=2
+RestartPreventExitStatus=1
+
+# The msg queues must be read fast enough
+CPUSchedulingPolicy=rr
+CPUSchedulingPriority=1
+
+[Install]
+WantedBy=multi-user.target
+Alias=osmo-bts.service
diff --git a/contrib/systemd/osmo-bts-oc2g.service b/contrib/systemd/osmo-bts-oc2g.service
new file mode 100644
index 00000000..2f2d8378
--- /dev/null
+++ b/contrib/systemd/osmo-bts-oc2g.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=osmo-bts for OC-2G
+
+[Service]
+Type=simple
+ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness'
+ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr1/brightness'
+ExecStart=/usr/bin/osmo-bts-oc2g -s -c /etc/osmocom/osmo-bts.cfg -M
+ExecStopPost=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness'
+ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/usr1/brightness'
+Restart=always
+RestartSec=2
+RestartPreventExitStatus=1
+
+# The msg queues must be read fast enough
+CPUSchedulingPolicy=rr
+CPUSchedulingPriority=1
+
+[Install]
+WantedBy=multi-user.target
+Alias=osmo-bts.service
diff --git a/contrib/systemd/osmo-bts-sysmo.service b/contrib/systemd/osmo-bts-sysmo.service
new file mode 100644
index 00000000..92558172
--- /dev/null
+++ b/contrib/systemd/osmo-bts-sysmo.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=osmo-bts for sysmocom sysmoBTS
+
+[Service]
+Type=simple
+ExecStartPre=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness'
+ExecStart=/usr/bin/osmo-bts-sysmo -s -c /etc/osmocom/osmo-bts-sysmo.cfg -M
+ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness'
+ExecStopPost=/bin/sh -c 'cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 ; sleep 3s; cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0; sleep 1s'
+Restart=always
+RestartSec=2
+RestartPreventExitStatus=1
+
+# The msg queues must be read fast enough
+CPUSchedulingPolicy=rr
+CPUSchedulingPriority=1
+
+[Install]
+WantedBy=multi-user.target
+Alias=sysmobts.service
+Alias=osmo-bts.service
diff --git a/contrib/systemd/osmo-bts-trx.service b/contrib/systemd/osmo-bts-trx.service
new file mode 100644
index 00000000..97c2b070
--- /dev/null
+++ b/contrib/systemd/osmo-bts-trx.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Osmocom osmo-bts for osmo-trx
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/osmo-bts-trx -s -c /etc/osmocom/osmo-bts-trx.cfg
+Restart=always
+RestartSec=2
+
+# Let it process messages quickly enough
+CPUSchedulingPolicy=rr
+CPUSchedulingPriority=1
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/osmo-bts-virtual.service b/contrib/systemd/osmo-bts-virtual.service
new file mode 100644
index 00000000..16332669
--- /dev/null
+++ b/contrib/systemd/osmo-bts-virtual.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Osmocom GSM BTS for virtual Um layer based on GSMTAP/UDP
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/osmo-bts-virtual -s -c /etc/osmocom/osmo-bts-virtual.cfg
+Restart=always
+RestartSec=2
+
+# Let it process messages quickly enough
+CPUSchedulingPolicy=rr
+CPUSchedulingPriority=1
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/sysmobts-mgr.service b/contrib/systemd/sysmobts-mgr.service
new file mode 100644
index 00000000..4346991d
--- /dev/null
+++ b/contrib/systemd/sysmobts-mgr.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=osmo-bts manager for sysmoBTS
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/sysmobts-mgr -ns -c /etc/osmocom/sysmobts-mgr.cfg
+Restart=always
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
+Alias=osmo-bts-mgr.service
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 00000000..09e2a831
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,755 @@
+osmo-bts (0.8.1) unstable; urgency=medium
+
+ [ Neels Hofmeyr ]
+ * cosmetic: dyn TS: clarify rsl_tx_rf_rel_ack() with a switch
+ * dyn TS: fix TCH/F_TCH/H_PDCH: properly record release of PDCH TS
+ * dyn TS: rx_rf_chan_rel: properly mark PDCH rel when no PCU, clarify
+ * dyn TS: clear TCH state upon reconnecting as PDCH
+ * cosmetic: dyn TS: clarify chan_nr composition
+ * ignore RSL RF CHAN REL for inactive lchans
+ * fix RSL Chan Activ Nack messages
+ * ip.access dyn ts: properly NACK a PDCH ACT on a still active lchan
+ * add/improve various logging around dyn ts
+ * dyn TS: be less strict on chan_nr, to allow arbitrary pchan switches
+
+ [ Stefan Sperling ]
+ * send a State Changed Event Report when rf is locked/unlocked
+
+ [ Harald Welte ]
+ * rsl: log errors when parsing of encryption information fails
+ * rsl: Make channel activation fail if encryption algorithm not supported
+ * rsl: Properly NACK CHAN_ACKT / MODE_MODIFY
+ * rsl: If CHAN ACT or MODE MODIF fails, send respective NACK
+ * osmo-bts-trx: Enable A5/3 cipher support
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 15 May 2018 14:08:47 +0200
+
+osmo-bts (0.8.0) unstable; urgency=medium
+
+ [ Neels Hofmeyr ]
+ * vty: skip installing cmds now always installed by default
+ * jenkins_common.sh: fix build_bts distcheck for more than one conf_flag
+ * fix build: tests/sysmobts: add missing -I$(SYSMOBTS_INCDIR)
+ * fix handover: handle_ph_ra_ind(): evaluate ra_ind before msgb_trim()
+ * implement support for 3-digit MNC with leading zeros
+ * configure: add --enable-werror
+ * use osmo_init_logging2() with proper talloc ctx
+
+ [ Pau Espin Pedrol ]
+ * lc15: Fix cfg indentation
+ * l1sap: Fix abort on big RTP packet received
+ * bts-trx: trx_ctrl_cmd: Simplify var assignment logic
+ * bts-trx: Avoid enqueueing consecutive duplicate messages to TRX
+ * Fix malformed Resource Indication packet
+ * debian/control: Remove uneeded dep libosmo-netif-dev
+ * jenkins.sh: Disable building doxygen for deps
+ * oml.c: Fix use of htons instead of ntohs
+ * bts-trx: trx_if.c: Log timedout+retransmitted CMD
+ * bts-trx: trx_if.c: trx_ctrl_read_cb: Move error handling to end of func
+ * bts-trx: trx_if.c: Improve parsing of received RSP messages from TRX
+ * bts-trx: Detect duplicated responses for retransmitted commands
+ * gsm_pchan2chan_nr: move warning to pragma message and track issue
+ * Remove unused variables
+ * bts-trx: scheduler_trx.c: Fix missing header
+ * l1sap.c: l1sap_tch_rts_ind: Remove unused variables
+ * octphy: octpkt.c: Remove unused static functions
+ * vty.c: Remove warning message
+ * virtual: l1_if.c: Remove unneeded warning message
+ * main.c: bts_main: fix typo in error message
+ * l1sap: Validate incoming RTP payload, drop bw-efficient AMR
+ * l1sap: Avoid assumption that l1sap is at head of msgb
+ * contrib: jenkins_bts_model: Fix bashism expr
+ * Include missing headers for osmo_init_logging2
+ * common/sysinfo.c: Fix no return on on-void function
+ * gsm_data_shared.h: Remove unused enum gsm_paging_event
+ * scheduler_trx: Fix signed integer overflow in clock calculations
+
+ [ Harald Welte ]
+ * trx: Better be safe than sorry before calling strlen
+ * trx: Avoid NULL+1 dereference in trx_ctrl_read_cb()
+ * trx: Don't call osmo_fr_check_sid with negative 'rc'
+ * trx: Don't assume phy_instance_by_num() returns non-NULL
+ * l1sap: fix wrong return value of is_fill_frame()
+ * measurement.c: Fix various typos in comments
+ * Comments on individual members of struct gsm_abis_mo
+ * scheduler: Harmonize log line format; Always print TS name + decoded FN
+ * scheduler_trx: L1P is for PH (data), L1M for MPH (control)
+ * l1sap: Fix log subsystem: Use DRTP for RTP related bits, L1C for MPH
+ * measurment.c: Introduce INFO category for DMEAS logging
+ * osmo-bts-octphy: Remove bogus warning about BS_AG_BLKS_RES
+ * rsl.c: Log RTP socket related errors as DRTP, not DRSL
+ * Put useful information in RTCP SDES.
+ * osmo-bts-trx: Fix reported frame number during PRIM_INFO_MEAS
+ * DTX: avoid illegal character contained in DTX FSM allocation which causes BTS crash
+ * gsm_lchan: remove unused member fields
+ * Add 'show (bts|trx|ts|lchan)' commands
+ * Print much more information during 'show lchan'
+ * vty: don't print "Bound IP / Port" if it isn't bound [yet]
+ * osmo-bts: Add talloc context introspection via VTY
+ * sysmo: Fix compiler warnings in eeprom.c
+ * sysmo+lc15: Add missign include for readv/writev
+ * trx: make l1if_fill_meas_res() static
+ * RSL: Properly reject RSL CHAN_NR IE for incompatible PCHAN
+ * RSL: Ensure we don't accept DCHAN messages for CCHAN
+ * osmo-bts-virtual: Shut down gracefully on socket creation failure
+ * osmo-bts-virtual: Generate PRIM_INFO_MEAS (with bogus values)
+ * Introduce + use LOG/DEBUGP with frame number prefixing/printing
+ * osmo-bts-virtual: Make use of LOGL1S() macro for context
+ * osmo-bts-virtual: Make sure PRIM_INFO_MEAS have non-zero frame number
+ * scheduler.c: Factor out find_sched_mframe_idx() function
+ * scheduler: add trx_sched_is_sacch_fn() function
+ * Revert "measurement: fix measurement computation"
+ * measurement.c: Hand Frame Number into measurement computation
+ * l1sap: Pass is_sub from L1 primitive into the Uplink Measurement
+ * osmo-bts-trx: Add missing frame number to l1if_process_meas_res()
+ * scheduler.c: Print message when burst substitution happens
+ * load_indication: Fix start of load indication timer
+ * RSL: Implement DELETE INDICATION on AGCH overflow
+ * RSL: Send ERROR REPORT on too short/truncated messages + wrong discriminator
+ * BTS: add rate_ctr about CCCH (paging, agch, pch)
+ * paging: Drop + Log paging requests for non-existant paging groups
+ * paging.c: Fix encoding of optional Mobile ID RR PAGING TYPE 1 / 2
+ * rsl: Improve ERROR REPORTing
+ * paging: Fix encoding of PAGING TYPE 3 Rest Octets
+ * RSL IPA DLCX: Avoid null-pointer dereference
+ * RSL: Fix encoding of ConnectionID in IPA_DLCX_ACK
+ * RSL IPA DLCX: Avoid another null-pointer dereference
+ * measurement.c: Fix sdcch4_meas_rep_fn102 / sdcch8_meas_rep_fn102
+ * counters: split rach:sent into rach:drop, rach:ho, rach:cs and rach:ps
+ * octphy: Remove code duplication for BER / RSSI conversion
+ * {sysmo,lc15}: Correctly report BER to L1SAP in INFO_MEAS_IND
+ * {sysmo,lc15}: Fix RACH reporting in combined CBCH case
+ * split scheduler_mframe.c from scheduler.c
+ * measurement: Compute RX{LEV,QUAL}-SUB for SDCCH and non-AMR TCH
+ * measurement.c: Don't silently copy "FULL" measurements to "SUB"
+ * scheduler: Add missing \n at end of LOG statement
+ * Move rach_busy counting above L1SAP
+ * RACH decoding: Use BER threshold for RACH ghost detection
+ * trx/scheduler: Use integer math for TOA (Timing of Arrival)
+ * measurement.c: higher-precision TA/TOA math
+ * L1SAP: Increase resolution of reported burst timing
+ * measurement: Keep average of high-accurate ToA value in lchan
+ * Add high-accuracy ToA value to Uplink Measurement Reports
+ * pcu_sock: Discard messages that are too short
+ * pcu_sock: Don't overflow the timeslot array
+ * pcu_sock: Log an error message and discard PCU primitives for BTS != 0
+ * pcu_sock: LOG + drop DATA.req from PCU for non-PDCH timeslot
+ * pcu_sock: LOG + drop PCU DATA.req for inactive lchan
+ * sysinfo.c: SI1 is optional; Send SI2 at TC=0 if no SI1 exists
+ * sysmobts: Compatibility with older firmware versions
+ * cosmetic: Document some SI scheduling related function API
+ * sysinfo: Fix scheduling of downlink SACCH information
+ * gsm_data_shared: Remove unused definitions/members/functions
+ * cosmetic: Move agch_queue to sub-structure of gsm_bts_role_bts
+ * Get rid of 'struct gsm_bts_role_bts'
+ * virtual: Correctly set+report BTS variant in OML attributes
+ * Add 'osmo-bts-omldummy' to bring up only OML without RSL
+ * fix inverted logic bug in omldummy patch
+ * omldummy: Suppress RSL transmission errors
+ * debian: Split osmo-bts-virtual from osmo-bts-trx
+ * fox chan_nr_is_dchan() for RSL_CHAN_OSMO_PDCH
+ * rsl_tx_dyn_pdch_ack: Add missing FRAME_NR information element
+ * fix activation of osmocom-style dynamic PDCH as TCH/F or TCH/H
+
+ [ Philipp Maier ]
+ * octphy: override firmware version check
+ * cosmetic: meas_test: fix section comment
+ * cosmetic: tests/Makefile.am: remove excess whitespace
+ * cosmetic: tests/power: remove unused var "ret"
+ * cosmetic: tests/agch: remove unused var "static_ilv"
+ * octphy: l1_oml: check returncode of trx_by_l1h()
+ * meas_test: fix header file references
+ * rsl: fix double-free in rsl_rx_mode_modif()
+ * fix nullpointer deref in rsl_tx_mode_modif_nack()
+ * rsl: do not allow MODE MODIFY request with unsupp. codec/rate
+ * gsm_data_shared: extend bts feature list with speech codecs
+ * octphy: ensure all BTS models set features
+ * vty: display bts features in vty command show bts
+ * bts: use feature list instead of speech codec table
+ * octphy: replace #warning with #pragma message
+ * ipac: fix log output
+ * rsl: remove unused variable
+ * l1_tch: remove dead code
+ * cosmetic: remove dead code
+ * cosmetic: remove unused variable
+ * cosmetic: remove unused variable in osmo-bts-omldummy/main.c
+ * octphy: integrate octasics latest header release
+ * osmo-bts-trx: perform error concealment for FR frames
+
+ [ Max ]
+ * Remove leftover comments and checks
+ * Log filenames on L1 errors
+ * Add --enable-sanitize configure option
+ * Use existing function to obtain TSC
+ * Remove BSC-specific parts
+ * Print FN delta on L1 errors
+ * Move sysmobts-calib into osmo-bts-sysmo
+ * Allow specifying sysmocom headers explicitly
+ * fix build: tests/misc: missing libosmo-abis and -trau flags
+ * Enable optional static builds
+ * Remove unneeded LIBOSMOCORE_CFLAGS from tests
+ * sysmobts: use proper includes for sbts2050 test
+ * Move -I inside *INCDIR variable
+ * sysmobts: remove weird default header location
+ * sysmobts: move header check to appropriate place
+ * CI: drop unused OsmoPCU dependency
+ * Enable sanitize for CI tests
+ * Add helper to get BCC from BSIC
+ * osmo-bts-trx: init nbits to know value
+ * osmo-bts-trx: ignore frame offset error on startup
+
+ [ Vadim Yanitskiy ]
+ * doc/examples: add CalypsoBTS configuration example
+ * common/pcu_sock.c: fix double field assignment
+ * scheduler_trx.c: remove ToA (Time of Arrival) hack
+ * common/l1sap.c: increase the BTS_CTR_RACH_DROP in RACH BER check
+ * common/l1sap.c: increment valid RACH counter after all checks
+ * common/l1sap.c: clean up noise / ghost RACH filtering
+ * common/l1sap.c: perform noise / ghost filtering for handover RACH
+ * common/l1sap.c: limit the minimal ToA for RACH bursts
+ * common/vty.c: remove unused variables
+ * common/main.c: track talloc NULL contexts by default
+
+ [ Alexander Huemer ]
+ * cosmetic: Makefile.am whitespace
+ * various Makefile.am: add missing CFLAGS
+ * gitignore: add missing entries
+
+ [ Stefan Sperling ]
+ * Cosmetic fixes for power ramping code.
+ * respond with NACK for non-hopping BTS with multiple ARFCN
+ * cosmetic: fix typos in src/common/oml.c
+ * return NACK codes instead of errno values from oml_tx_attr_resp()
+
+ [ Alexander Couzens ]
+ * pcuif_proto: correct indention of gsm_pcu_if_data
+ * pcu_if: move definition PCU_SOCK_DEFAULT into pcuif_proto.h
+ * pcuif_proto: add version 8 features
+
+ [ Keith ]
+ * osmo-bts-sysmo eeprom.c Restore ability to read/write EEPROM
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Thu, 03 May 2018 17:02:19 +0200
+
+osmo-bts (0.7.0) unstable; urgency=medium
+
+ [ Max ]
+ * Use value string check from osmo-ci
+ * Support sending SI13 to PCU
+ * Support removing SI13 from PCU
+ * trx: avoid deactivating lchan on LCHAN_REL_ACT_REACT
+ * Check readv() return value to prevent crash
+ * OML: print actual type of report sent to BSC
+ * Replace dead code
+ * vty: print version and description for each phy
+ * Remove build dependency on legacy OpenBSC
+ * Fix multiple SI2q reception
+ * jenkins: remove openbsc dependency
+ * sysmo: use clock calibration source wrapper
+ * sysmo: don't override clock source with defaults
+ * Fix race condition in attribute reporting
+ * Move power loop to generic tests
+ * Make power test more verbose
+
+ [ Neels Hofmeyr ]
+ * vty: mgr: sysmobts, lc15: install default commands for ACT_NORM_NODE
+ * osmo-bts-trx: vty: various fixes of 'write file' and doc
+ * jenkins: use osmo-clean-workspace.sh before and after build
+
+ [ Pau Espin Pedrol ]
+ * l1sap: Improve log msg when frame diff >1
+ * vty: Print string for Administrative state
+
+ [ Harald Welte ]
+ * Fix Downlink AMR FSM name to avoid illegal space character
+ * update dependencies to latest libosmo-*
+ * configure.ac: Fix Mailing list address
+
+ -- Harald Welte <laforge@gnumonks.org> Sat, 28 Oct 2017 20:53:21 +0200
+
+osmo-bts (0.6.0) unstable; urgency=medium
+
+ [ Holger Hans Peter Freyther ]
+ * Initial release.
+ * misc: Ignore files generated by a debian packaging build
+ * jenkins: Add the build script from jenkins here
+ * jenkins: Add the build script from jenkins here
+ * sysmobts: Add the barebox boot state reservation
+ * sysmobts: Fix eeprom padding before gpg key
+ * ci/spatch: Remove the "static" analysis handling
+ * oct: Attempt to enable the Octphy for the osmo-bts-oct build
+ * debian: Use the header files installed by openbsc-dev
+ * build: Do not require more headers from OpenBSC
+ * sysmobts: Make reservation for mode/netmask/ip and suc
+ * sysmobts: Store a simple network config in the EEPROM as well
+
+ [ Max ]
+ * Ensure TRX invariant
+ * Use libosmocore function for uplink measurements
+ * Fix debug output
+ * Fix RTP timestamps in case of DTX
+ * Add DTXd support for sysmoBTS and LC15
+ * Use libosmocodec for AMR RTP
+ * octphy: Use the app. info. defaults as base
+ * Fix debug output
+ * DTXd: store/repeat last SID
+ * DTXd: store/repeat last SID
+ * DTXu: mark beginning of speech burst in RTP
+ * Fix OML activation
+ * TRX: Add vty command to power on/off transceiver
+ * TRX: add configuration example
+ * Add .gitreview
+ * DTX: add support for AMR/HR
+ * Move copy-pasted code into common part
+ * Use libosmocodec functions for AMR
+ * Use error values instead of number for RSL error
+ * Clarify logging message
+ * Make get_lchan_by_chan_nr globally available
+ * DTXu: move copy-pasted code to common part
+ * Remove duplicated nibble shift code
+ * TRX: add Uplink DTX support for FR/HR
+ * Mark array as static const
+ * sysmobts: dump PRACH and PTCCH parameters
+ * Activate PTCCH UL
+ * Fix dsp tracing at phy config
+ * octphy: fix build
+ * Fill measurements data for L1SAP
+ * sysmo: ts_connect: log channel combination name instead of number
+ * DTX: fix last SID saving
+ * DTX: fix SID repeat scheduling
+ * DTX: fix SID logic
+ * lc15, sysmo: Use SID_FIRST_P1 to initiate DTX
+ * DTX: check Marker bit to send ONSET to L1
+ * DTX: remove misleading comment
+ * LC15: Clarify msgb ownership / fix memory leaks
+ * DTX: move scheduling check inside repeat_last_sid
+ * DTX: further AMR SID cache fixes (lc15, sysmo)
+ * DTX: move ONSET detection into separate function
+ * DTX: send AMR voice alongside with ONSET
+ * DTX: fix conversion from fn to ms
+ * Move copy-pasted array into shared header
+ * DTX DL: use FSM for AMR
+ * TRX: fix building with latest DTX changes
+ * DTX: fix array size calculation
+ * DTX AMR - fix buffer length check
+ * Replace magic number with define
+ * Fix lc15 build
+ * Extend RTP RX callback parameters
+ * DTX HR - fix array size calculation
+ * Fix DTX DL AMR SIDscheduling logic
+ * Add tools to check DTX operation
+ * DTX DL: split ONSET state handling
+ * Remove obsolete define
+ * DTX DL: add AMR HR support to scheduling check
+ * DTX fix ONSET handling
+ * dtx_check.gawk: Fix false-positives in DTX check
+ * Fix tests linking with libosmocodec
+ * DTX DL: tighten check for enabled operation
+ * DTX: wrap FSM signal dispatching
+ * Add libosmocodec for octphy build
+ * dtx_check.gawk: add check for repetitive SID FIRST
+ * Remove duplicated code
+ * Replace link_id constant with define
+ * DTX DL AMR: rewrite FSM recursion
+ * Remove duplicated code
+ * Fix AGCH/PCH proportional allocation
+ * TRX: prevent segfault upon phy init
+ * DTX: add explicit check if DTX enabled
+ * Save RTP metadata in Control Buffer
+ * osmo-bts-trx: fix lchan deactivation
+ * DTX: fix TS adjustment for ONSET
+ * Optionally use adaptive RTP jitter buffering
+ * Integrate Debian packaging changes
+ * DTX AMR HR: fix inhibition
+ * Add copyright for .deb packages
+ * Move code to libosmocore
+ * Log socket path on error
+ * Add Abis OML failure event reporting
+ * Alarm on various errors
+ * Remove obsolete define TLVP_PRES_LEN
+ * scheduler: log lchan on which prim error occured
+ * deb: use gsm_data_shared.* from openbsc-dev
+ * OML: internalize failure reporting
+ * Add ctrl command to send OML alert
+ * Fix typo in TCH/H interleaving table
+ * Use oml-alert CTRL command for temp report
+ * Remove code duplication
+ * Handle ctrl cmd allocation failures
+ * Check for suitable lchan type when detecting HO
+ * osmo-bts-trx: fix scheduling of broken frames
+ * Sync protocol with OsmoPCU
+ * vty: reduce code duplication
+ * Handle TXT indication from OsmoPCU
+ * Add MS TO to RSL measurements
+ * Signal to BSC when PCU disconnects
+ * Prepare for extended SI2quater support
+ * Set BTS variant while initializing BTS model
+ * Prepare for BTS attribute reporting via OML
+ * osmo-bts-trx: use libosmocoding
+ * Remove redundant test
+ * Implement basic Get Attribute responder
+ * Add version to phy_instance
+ * OML: fix Coverity-reported issues
+ * Re-add version to phy_instance
+ * Use systemd template specifiers
+ * Place *-mgr config examples according to BTS model
+ * lc15: add example systemd service file
+ * Extend Get Attribute responder
+ * Set and report BTS features
+ * Cleanup SI scheduling
+ * RSL: receive and send multiple SI2q messages
+ * RSL: check for abnormal SI2q values
+ * lc15bts-mgr: use extended config file example
+ * Move parameter file opening into separate function
+ * Move common steps into common jenkins helper
+ * lc15: add jenkins helper
+ * Use generic L1 headers helper
+ * Copy sysmobts.service to osmo-bts-sysmo
+ * OML: move BTS number check into separate function
+ * lc15: make jenkins helper executable
+ * lc15: fix jenkins build
+ * Add missing include for abis.h header file
+ * RSL: receive and send multiple SI2q messages
+ * Use release helper from libosmocore
+ * si2q: do not consider count update as error
+ * Cleanup example config files
+ * Fix .deb build
+ * Unify *.service files
+ * lc15: cleanup board parameters reading
+ * lc15-mgr: update parameter read/write
+ * lc15: fix BTS revision and hw options
+ * lc15: make default config usable
+ * lc15: port lc15bts-mgr changes
+ * lc15bts-mgr: separate service file
+ * lc15: port lc15bts-mgr dependency changes
+ * Simplify jenkins build scripts
+ * OML: use fom_hdr while handling attr. request
+ * osmo-bts-trx: fix 'osmotrx legacy-setbsic'
+ * osmo-bts-trx: remove global variables from loops
+
+ [ Daniel Laszlo Sitzer ]
+ * octphy: Update outdated config param name in error message.
+
+ [ Jason DSouza ]
+ * Close TRX session before opening new one
+
+ [ Minh-Quang Nguyen ]
+ * l1sap.h: fix wrong L1SAP_FN2PTCCHBLOCK calculation according to TS 45.002 Table 6
+ * common/abis.c: fix 100% CPU usage after disconnecting OML/RSL link (Bug #1703)
+ * LC15: Bring back DSP trace argument
+ * LC15: Hardware changes
+ * LC15: TRX nominal TX power can be used from EEPROM or from BTS configuration
+ * rsl: Fix dropping of LAPDm UA message.
+ * LC15: properly handle BS-AG-BLKS-RES as received from BSC
+
+ [ Neels Hofmeyr ]
+ * sysmo: add L3 handle to l1prim messages
+ * pcu_sock: add pcu_connected() to query PCU availability
+ * tests/stubs.c: remove unused stubs
+ * fix typo in error message ('at lEast')
+ * oml, Set Chan Attr: treat unknown PCHAN types as error
+ * dyn PDCH: rsl rx dchan: also log ip.access message names
+ * doc: add ladder diagram on dynamic PDCH, add msc-README
+ * add missing DSUM entry to bts_log_info_cat
+ * fix compiler warning: printf format for sizeof()
+ * fix compiler warning: add missing case (PHY_LINK_CONNECTING)
+ * fix two compiler warnings: add two opaque struct declarations
+ * dyn PDCH: add bts_model_ts_connect() and _disconnect() stubs
+ * dyn PDCH: conf_lchans_for_pchan(): handle TCH/F_PDCH
+ * dyn PDCH: pcu_tx_info_ind(): handle TCH/F_PDCH in PDCH mode
+ * dyn PDCH: chan_nr_by_sapi(): handle TCH/F_PDCH according to ts->flags
+ * dyn PDCH: implement main dyn PDCH logic in common/
+ * dyn PDCH: sysmo-bts/oml.c: add ts_connect_as(), absorbing ts_connect() guts
+ * dyn PDCH: sysmo: handle TCH/F_PDCH init like TCH/F
+ * dyn PDCH: complete for sysmo-bts: implement bts_model_ts_*()
+ * error log: two minor clarifications
+ * debug log: log lchan state transitions
+ * debug log: log TS pchan type on connect
+ * fix lc15 build: put src/common/libbts.a left of -losmogsm
+ * lc15: add L3 handle to l1prim messages
+ * dyn PDCH: lc15: chan_nr_by_sapi(): handle TCH/F_PDCH according to ts->flags
+ * dyn PDCH: lc15: add ts_connect_as(), absorbing ts_connect() guts
+ * dyn PDCH: lc15: handle TCH/F_PDCH init like TCH/F
+ * dyn PDCH: lc15: complete for litecell15-bts: implement bts_model_ts_*()
+ * dyn PDCH: safeguard: exit if nothing pending in dyn_pdch_ts_disconnected()
+ * vty: install orphaned trx nominal power command
+ * fix compiler warnings: include bts_model.h in phy_link.c
+ * fix compiler warning: remove useless 'static' storage class for struct decl
+ * fix compiler warning: remove unused variable 'i' in calib_verify()
+ * log: osmo-bts-trx: change access burst logs to DEBUG level
+ * log: osmo-bts-trx: change PDTCH block logs to DEBUG level
+ * osmo-bts-trx: init OML only once by sending AVSTATE_OK with OPSTATE_ENABLED
+ * doc: move dyn_pdch.msc to osmo-gsm-manuals.git
+ * error log: rsl.c: typo x2
+ * info log: l1sap.c: add '0x' to hex output
+ * fix compiler warning: msg_utils.c: fn_chk() constify arg
+ * fix compiler warning: msg_utils.c: fn_chk() constify arg
+ * info log: l1sap.c: add '0x' to hex output
+ * error log: rsl.c: typo x2
+ * dyn PDCH: code dup: use conf_lchans_as_pchan()
+ * prepare dyn TS: split/replace conf_lchans_for_pchan()
+ * code dup: join [rsl_]lchan_lookup() from libbsc and osmo-bts
+ * dyn TS: common TCH/F_TCH/H_PDCH implementation
+ * sysmo/oml.c: rename ts_connect() to ts_opstart()
+ * dyn TS: implement SysmoBTS specifics
+ * lc15/oml.c: rename ts_connect() to ts_opstart()
+ * dyn TS: implement litecell15 specifics
+ * comment typo: common/l1sap.c
+ * log typo: trx_sched_set_pchan()
+ * dyn TS: sysmo,lc15: chan_nr_by_sapi(): add missing assertion
+ * fix comment in common/l1sap.c, function name changed
+ * dyn TS, dyn PDCH: common/l1sap.c: properly notice PDCH
+ * dyn PDCH: trx l1_if.c: factor out trx_set_ts_as_pchan() from trx_set_ts()
+ * dyn PDCH: complete for trx: implement bts_model_ts_[dis]connect()
+ * dyn PDCH: trx l1_if.c: drop fixme, add comment
+ * dyn TS: complete for TRX
+ * dyn TS: measurement.c: replace fixme with comment
+ * sysmo,lc15: ts_connect_as(): log error also for pchan_as == TCH/F_PDCH
+ * sysmo: fix dyn TS: Revert "Activate PTCCH UL" [in sysmobts]
+ * log: l1sap: add 0x to hex output of chan_nr, 5 times
+ * dyn TS: measurement: use correct nr of subslots, rm code dup
+ * dyn TS: sysmo,lc15: ph_data_req: fix PDCH mode detection
+ * Fix ip.access style dyn PDCH, broken in 37af36e85eca546595081246aec010fa7f6fd0be
+ * common/rsl: move decision whether to chan act ack/nack to common function
+ * octphy: fix build: Revert "octphy: fix for multiple trx with more than 1 dsp"
+ * octphy: fix build: Revert "octphy: add support for multiple trx ids"
+ * octphy: fix build with OCTSDR-OPENBSC-02.07.00-B708: name changed
+ * dyn TS: if PCU is not connected, allow operation as TCH
+ * log: sysmo,lc15: tweak log about sapi_cmds queue
+ * log causing rx event for lchan_lookup errors
+ * heed VTY 'line vty'/'bind' command
+ * sysmobts_mgr, lc15bts_mgr: fix tall context for telnet vty
+ * build: be robust against install-sh files above the root dir
+ * configure: check for pkg-config presence
+ * jenkins.sh: use osmo-build-dep.sh, log test failures
+ * msgb ctx: use new msgb_talloc_ctx_init() in various main()s
+ * jenkins-oct.sh: fix build: typo in deps path
+ * fix 'osmo-bts-* --version' segfault
+ * osmo-bts-trx: remove obsolete include of netif/rtp.h
+ * add jenkins_bts_trx.sh
+ * add jenkins_oct_and_bts_trx.sh
+ * jenkins: add jenkins_bts_model.sh
+ * bursts test: test_pdtch: pre-init result mem
+ * fix: dyn ts: uplink measurement report
+ * fix missing ~ in bit logic for lchan->si.valid in rsl_rx_sacch_inf_mod()
+ * SACCH: fix sending of SI with an enum value > 7
+ * SACCH SI: assert that SI enum vals fit in bit mask
+ * all models: fix vty write: bts_model_config_write_phy
+ * jenkins: add value_string termination check
+ * Revert "Add version to phy_instance"
+ * Revert "RSL: check for abnormal SI2q values"
+ * Revert "RSL: receive and send multiple SI2q messages"
+
+ [ Harald Welte ]
+ * sysmobts: screnrc/systemd-service: Use osmo-bts-sysmo instead of sysmobts
+ * Add .mailmap for mapping mail addresses in shortlog
+ * vty: Ensure to not use negative (error) sapi value
+ * sysmobts: Add correct nominal transmit power for sysmoBTS 1020
+ * sysmobts_eeprom.h: Fix/extend model number definitions
+ * Revert "sysmobts: Add correct nominal transmit power for sysmoBTS 1020"
+ * tx_power: Change PA calibration tables to use delta vales
+ * Add new unit-test for transmit power computation code
+ * sysmobts: fully support trx_power_params
+ * README: Add general project information and convert to markdown
+ * README: update some of the limitations
+ * sysmobts: Don't start with 0dBm TRX output power before ramping
+ * Remove unusued left-over gsm0503_conv.c
+ * scheduler_trx.c: Avoid code duplication for BER10k computation
+ * scheduler_trx: Avoid copy+pasting determining CMR from FN
+ * rx_tchh_fn(): Avoid copy+pasting formula to determine odd-ness of fn
+ * Consistently check for minimum attribute/TLV length in RSL and OML
+ * l1sap.c: Add spec reference to link timeout implementation
+ * osmo-bts-trx: Remove duplicate parsing of NM_ATT_CONN_FAIL_CRIT
+ * vty: Remove command for manual channel activation/deactivation
+ * l1_if: Add inline functions to check dsp/fgpa version at runtime
+ * sysmobts: Re-order the bit-endianness of every HR codec parameter
+ * OML Add osmocom-specific way to deactivate radio link timeout
+ * measurement: Remove dead code
+ * l1sap.c: Factor out function to limit message queue
+ * osmo-bts-sysmo/l1_if.c: PH-DATA.ind belongs to L1P, not L1C
+ * l1sap: if lchan is in loopback, don't accept incoming RTP
+ * TRX: Use timerfd and CLOCK_MONOTONIC for GSM frame timer (Closes: #2325)
+ * Add loopback support for PDTCH
+ * TRX: trx_if: Improve code description / comments
+ * trx_if: Improve error handling
+ * TRX: Rename trx_if_data() -> trx_if_send_burst()
+ * TRX: merge/simplify l1_if and trx_if code
+ * TRX: don't free l1h in trx_phy_inst_close()
+ * l1sap: Don't enqueue PTCCH blocks for loopback
+ * TRX: permit transmission of all-zero loopback frames
+ * jenkins helpers: some minimal documentation/comments + print errors
+ * VIRT-PHY: Initial check-in of a new virtual BTS
+ * VIRT-PHY: Fix handling of default values for vty configuration
+ * VIRT-PHY: Use IPv4 multicast groups for private / local scope
+ * VIRT-PHY: cause BTS to terminate in case of recv()/send() on udp socket returns 0
+ * Ensure we don't send dummy UI frames on BCCH for TC=5
+ * virt: Don't print NOTICE log message if ARFCN doesn't match
+ * VIRT-PHY: Report virtual RACH bursts with plausible burst type
+ * scheduler: Fix wrong log subsystem: L1C is L1 *control* not user data
+ * VIRT-PHY: Print NOTICE log message from unimplemented stubs
+ * TRX / VIRT-PHY: Make check for BCCH/CCCH more specific
+ * L1SAP: Print chan_nr and link_id always as hex
+ * VIRT-BTS: Support for GPRS
+ * L1SAP: Use RSL_CHAN_OSMO_PDCH across L1SAP
+ * GSMTAP: Don't log fill frames via GSMTAP
+ * TRX: Remove bogus extern global variable declarations
+ * l1sap/osmo-bts-sysmo: Improve logging
+ * TRX: Remove global variables, move SETBSIC/SETTSC handling into phy_link
+ * Fix build after recent gsm_bts_alloc() change
+ * Treat SIGTERM just like SIGINT in our programs
+
+ [ Tom Tsou ]
+ * trx: Add EGPRS tables, sequences, and mappings
+ * trx: Add EGPRS coding and decoding procedures
+ * trx: Enable EGPRS handling through burst lengths
+ * trx: Fix coverity BER calculation NULL dereference
+
+ [ Vadim Yanitskiy ]
+ * pcu_sock: use osmo_sock_unix_init() from libosmocore
+ * osmo-bts-trx/l1_if.c: use channel combination III for TCH/H
+ * scheduler_trx.c: strip unused variable
+
+ [ Mike McTernan ]
+ * osmo-bts-trx: Fix PCS1900 operation
+ * osmo-bts-trx: log decoder bit errors as DEBUG, not NOTICE
+
+ [ bhargava ]
+ * Change interface in osmo-bts for 11 bit RACH
+ * Update parameters in osmo-bts-sysmo for 11bit RACH
+ * 11bit RACH support for osmo-bts-litecell15
+ * Initialize parameters in osmo-trx for 11bit RACH
+
+ [ Philipp ]
+ * octphy: Fixing missing payload type in ph. chan. activation
+ * octphy: Fixing band selection for ARFCN 0
+ * octphy: reintroducing multi-trx support
+ * octopy: fixing renamed constant
+ * octphy: prevent mismatch between dsp-firmware and octphy headers
+ * rsl: improving the log output
+ * octphy: multi-trx support: fix AC_CHECK order
+ * RSL: drop obsolete NULL check
+ * RSL: add assertions to check args of public API
+ * OML: fix possible segfault: add NULL check in oml_ipa_set_attr()
+ * CTRL: make the CTRL-Interface IP address configurable
+ * l1sap: Fix expired rach slot counting
+ * l1sap: fix missing 'else's causing wrong rach frame expiry counts
+ * octphy: set tx attenuation via VTY
+ * octphy: Improve OML ADM state handling
+
+ [ Yves Godin ]
+ * DTX: fix 1st RTP packet drop
+
+ [ Alexander Chemeris ]
+ * l1sap: Fix use-after-free in loopback mode.
+ * vty: Add commands to manually activate/deactivate a channel.
+ * trx: Add "maxdlynb" VTY command to control max TA for Normal Bursts.
+ * rsl: Output RTP stats before closing the socket.
+ * osmo-bts-trx: Fix MS power control loop.
+ * osmo-bts-trx: Remove an unused variable. Resolves a compiler warning.
+ * osmo-bts-trx: Increase a maximum allowed MS power reduction step from 2dB to 4dB.
+ * Fix static build of osmo-bts-trx and osmo-bts-virtual.
+
+ [ Jean-Francois Dionne ]
+ * DTX: don't always perform AMR HR specific check
+ * DTX: fix SID-FIRST detection
+ * lc15,sysmobts l1_if: fix memleak in handle_mph_time_ind()
+ * sysmo,lc15: fix memory leak at each call placed
+ * DTX: fix "unexpected burst" error
+ * Fix AMR HR DTX FSM logic.
+ * Fix SACCH channel release indication not sent to BSC after location update.
+ * Fix RTP duration adjustment not done when speech resumes in DTX mode.
+
+ [ Ruben Undheim ]
+ * Fix some spelling errors
+
+ [ Holger Freyther ]
+ * Revert "deb: use gsm_data_shared.* from openbsc-dev"
+
+ [ Philipp Maier ]
+ * octphy VTY: fix vty write output for octphy's phy section
+ * octphy: Fix VTY commands
+ * l1sap: fix rach reason (ra) parsing
+ * l1sap: fix PTCCH detection
+ * octphy: fix usage of wrong define constant
+ * octphy: add CBCH support
+ * l1sap: improve log output
+ * octphy: print log message for multi-trx support
+ * octphy: display hint in case of wrongly configured transceiver number
+ * octphy: add conditional compilation to support latest octasic header release
+ * octphy: set tx/rx antenne IDs via VTY
+ * bts: revert trx shutdown order
+ * octphy: activate CBCH after all physical channels are activated
+ * octphy: align frame number for new firmware versions
+ * octphy: ensure that 11 bit rach flag is not set
+ * measurement: fix measurement reporting period
+ * measurement: make lchan_meas_check_compute() available to l1sap.c
+ * measurement: Compute measurement results on measurement idication
+ * measurement: exclude idle channels from uplink measurement
+ * octphy: integrate channel measurement handling
+ * octphy: remove old event control code
+ * osmo-bts-sysmo: Include frame number in MEAS IND
+ * measurement: fix measurement computation
+ * octphy: fix segfault
+ * Revert "measurement: exclude idle channels from uplink measurement"
+ * sysmobts: normalize frame number in measurement indication
+ * measurement: Improve log output
+ * measurement: improve log output
+ * octphy: improve log output
+ * octphy: initalize l1msg and only when needed
+ * octphy: initalize nmsg only when needed
+ * octphy: remove log output
+ * Revert "sysmobts: normalize frame number in measurement indication"
+ * osmo-bts-trx: fix missing frame number in MEAS IND
+ * osmo-bts-litecell15: Fix missing frame number in MEAS IND
+ * Revert "osmo-bts-sysmo: Include frame number in MEAS IND"
+ * octphy: complete value strings (octphy_cid_vals)
+ * octphy: do not send empty frames to phy
+ * osmo-bts-sysmo: Include frame number in MEAS IND
+ * measurement: fix measurment report
+ * octphy: remap frame number in MEAS_IND
+ * octphy: implement support for dynamic timeslots
+
+ [ Ivan Klyuchnikov ]
+ * osmo-trx-bts: Fix incorrect setting of RXGAIN and POWER parameters on second channel (TRX1) of osmo-trx
+ * osmo-trx-bts: Fix osmo-bts-trx crash on startup during reading phy instance parameters from config file
+ * osmo-trx-bts: Fix incorrect bts shutdown procedure in case of abis connection closure
+ * osmo-trx-bts: Fix incorrect bts shutdown procedure in case of clock loss from osmo-trx
+
+ [ Ivan Kluchnikov ]
+ * oml: Fix incorrect usage of const variable abis_nm_att_tlvdef_ipa
+
+ [ Pau Espin Pedrol ]
+ * phy_link: Fix typo in state being printed
+ * trx: Allow BTS and TRX to be on different IPs
+ * trx: Save osmotrx base-port vty properties
+ * sysmo/tch.c: Clean up use of empty buffer
+ * litecell15/tch.c: Clean up use of empty buffer
+ * Use L1P instead of L1C for TCH logging and allocation
+ * Fix annoying trailing whitespace
+ * sysmo, litecell15: Make sure all TCH events are triggered
+ * sysmo: Remove non longer valid -p option from help
+ * Allow passing low link quality buffers to upper layers
+ * l1sap.c: Avoid sending RTP frame with empty payload
+ * l1sap.c: fn_ms_adj: Add err logging and always return GSM_RTP_DURATION
+ * Move dump_gsmtime to libosmocore as osmo_dump_gsmtime
+ * Use osmo_dump_gsmtime to log fn across different layers
+ * lc15bts-mgr.cfg: Set default vswr to a value inside valid range
+ * litecell15: Register in vty limits for paX_pwr
+ * lc15: Tweak led colors used in service file
+ * lc-15, sysmo: l1_if: print name on PH-DATA.ind unknwon sapi
+ * lc15bts-mgr.service: Prepare dirs and sysctls for the process
+ * osmo-bts-trx: Enable osmotrx tx-attenuation oml by default
+ * osmo-bts-trx: Relax validation to allow TRX data bursts without padding
+
+ [ Sebastian Stumpf ]
+ * VIRT-PHY: Added example configurations for openbsc and osmobts.
+ * VIRT-PHY: Fixed timeslot in gsmtap-msg on downlink which was always 0.
+ * VIRT-PHY: Added test option for fast hyperframe repeat.
+
+ -- Max <msuraev@sysmocom.de> Fri, 25 Aug 2017 15:16:56 +0200
+
+osmo-bts (0.5.0) unstable; urgency=medium
+
+ * Initial release.
+
+ -- Holger Hans Peter Freyther <holger@moiji-mobile.com> Fri, 01 Apr 2016 16:13:40 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 00000000..ec635144
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 00000000..0377d9fd
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,50 @@
+Source: osmo-bts
+Maintainer: Holger Hans Peter Freyther <holger@moiji-mobile.com>
+Section: net
+Priority: optional
+Build-Depends: debhelper (>= 9),
+ pkg-config,
+ dh-autoreconf,
+ dh-systemd (>= 1.5),
+ autotools-dev,
+ pkg-config,
+ libosmocore-dev,
+ libosmo-abis-dev,
+ libgps-dev,
+ txt2man
+Standards-Version: 3.9.8
+Vcs-Browser: http://git.osmocom.org/osmo-bts/
+Vcs-Git: git://git.osmocom.org/osmo-bts
+Homepage: https://projects.osmocom.org/projects/osmobts
+
+Package: osmo-bts-trx
+Architecture: any
+Conflicts: osmo-bts
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: osmo-bts-trx GSM BTS with osmo-trx
+ osmo-bts-trx to be used with the osmo-trx application
+
+Package: osmo-bts-trx-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmo-bts-trx (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the osmo-bts-trx
+ Make debugging possible
+
+Package: osmo-bts-virtual
+Architecture: any
+Conflicts: osmo-bts
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Virtual Osmocom GSM BTS (no RF hardware; GSMTAP/UDP)
+ This version of OsmoBTS doesn't use actual GSM PHY/Hardware/RF, but
+ utilizes GSMTAP-over-UDP frames for the Um interface. This is useful
+ in fully virtualized setups e.g. in combination with OsmocomBB virt_phy.
+
+Package: osmo-bts-virtual-dbg
+Architecture: any
+Section: debug
+Priority: extra
+Depends: osmo-bts-virtual (= ${binary:Version}), ${misc:Depends}
+Description: Debug symbols for the osmo-bts-virtual
+ Make debugging possible
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 00000000..302d1d94
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,81 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: osmo-bts
+Source: http://cgit.osmocom.org/osmo-bts/
+
+Files: *
+Copyright: 2008-2014 Harald Welte <laforge@gnumonks.org>
+ 2009,2011,2013 Andreas Eversberg <jolly@eversberg.eu>
+ 2010,2011 On-Waves
+ 2012-2015 Holger Hans Peter Freyther
+ 2014 sysmocom s.f.m.c. Gmbh
+ 2015 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+License: AGPL-3+
+
+Files: src/osmo-bts-sysmo/eeprom.c
+ src/osmo-bts-sysmo/eeprom.h
+Copyright: 2012 Nutaq
+License: MIT
+Comment: Yves Godin is the author
+
+Files: src/common/pcu_sock.c
+Copyright: 2008-2010 Harald Welte <laforge@gnumonks.org>
+ 2009-2012 Andreas Eversberg <jolly@eversberg.eu>
+ 2012 Holger Hans Peter Freyther
+License: GPL-2+
+
+Files: debian/*
+Copyright: 2015-2016 Ruben Undheim <ruben.undheim@gmail.com>
+License: AGPL-3+
+
+
+License: AGPL-3+
+ This program 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.
+ .
+ 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 Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+License: GPL-2+
+ This package 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 2 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 <http://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
+
+
+License: MIT
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is furnished to do
+ so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/debian/osmo-bts-trx.install b/debian/osmo-bts-trx.install
new file mode 100644
index 00000000..004a7ed0
--- /dev/null
+++ b/debian/osmo-bts-trx.install
@@ -0,0 +1,5 @@
+etc/osmocom/osmo-bts-trx.cfg
+lib/systemd/system/osmo-bts-trx.service
+usr/bin/osmo-bts-trx
+usr/share/doc/osmo-bts/examples/osmo-bts-trx/osmo-bts-trx.cfg
+usr/share/doc/osmo-bts/examples/osmo-bts-trx/osmo-bts-trx-calypso.cfg
diff --git a/debian/osmo-bts-virtual.install b/debian/osmo-bts-virtual.install
new file mode 100644
index 00000000..f4d988f4
--- /dev/null
+++ b/debian/osmo-bts-virtual.install
@@ -0,0 +1,6 @@
+etc/osmocom/osmo-bts-virtual.cfg
+lib/systemd/system/osmo-bts-virtual.service
+usr/bin/osmo-bts-virtual
+usr/bin/osmo-bts-omldummy
+usr/share/doc/osmo-bts/examples/osmo-bts-virtual/osmo-bts-virtual.cfg
+usr/share/doc/osmo-bts/examples/osmo-bts-virtual/openbsc-virtual.cfg
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 00000000..27de11b8
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,28 @@
+#!/usr/bin/make -f
+
+DEBIAN := $(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2)
+DEBVERS := $(shell echo '$(DEBIAN)' | cut -d- -f1)
+VERSION := $(shell echo '$(DEBVERS)' | sed -e 's/[+-].*//' -e 's/~//g')
+
+#export DH_VERBOSE=1
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+
+%:
+ dh $@ --with=systemd --with autoreconf --fail-missing
+
+override_dh_strip:
+ dh_strip --package=osmo-bts-virtual --dbg-package=osmo-bts-virtual-dbg
+ dh_strip --package=osmo-bts-trx --dbg-package=osmo-bts-trx-dbg
+
+override_dh_auto_configure:
+ dh_auto_configure -- --enable-trx --with-systemdsystemunitdir=/lib/systemd/system
+
+override_dh_clean:
+ dh_clean
+ $(RM) tests/package.m4
+ $(RM) tests/testsuite
+
+# Print test results in case of a failure
+override_dh_auto_test:
+ dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 00000000..89ae9db8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 00000000..1d42b0aa
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = \
+ examples \
+ $(NULL)
diff --git a/doc/control_interface.txt b/doc/control_interface.txt
new file mode 100644
index 00000000..5ad97172
--- /dev/null
+++ b/doc/control_interface.txt
@@ -0,0 +1,61 @@
+The osmo-bts control interface is currently supporting the following operations:
+
+h2. generic
+
+h3. trx.0.thermal-attenuation
+
+The idea of this paramter is to attenuate the system output power as part of
+thermal management. In some cases the PA might be passing a critical level,
+so an external control process can use this attribute to reduce the system
+output power.
+
+Please note that all values in the context of transmit power calculation
+are integers in milli-dB (1/10000 bel), so the below example is setting
+the attenuation at 3 dB:
+
+<pre>
+bsc_control.py -d localhost -p 4238 -s trx.0.thermal-attenuation 3000
+Got message: SET_REPLY 1 trx.0.thermal-attenuation 3000
+</pre>
+
+<pre>
+bsc_control.py -d localhost -p 4238 -g trx.0.thermal-attenuation
+Got message: GET_REPLY 1 trx.0.thermal-attenuation 3000
+</pre>
+
+
+h2. sysmobts specific
+
+h3. trx.0.clock-info
+
+obtain information on the current clock status:
+
+<pre>
+bsc_control.py -d localhost -p 4238 -g trx.0.clock-info
+Got message: GET_REPLY 1 trx.0.clock-info -100,ocxo,0,0,gps
+</pre>
+
+which is to be interpreted as:
+* current clock correction value is -100 ppb
+* current clock source is OCXO
+* deviation between clock source and calibration source is 0 ppb
+* resolution of clock error measurement is 0 ppt (0 means no result yet)
+* current calibration source is GPS
+
+When this attribute is set, any value passed on is discarded, but the clock
+calibration process is re-started.
+
+
+h3. trx.0.clock-correction
+
+This attribute can get and set the current clock correction value:
+
+<pre>
+bsc_control.py -d localhost -p 4238 -g trx.0.clock-correction
+Got message: GET_REPLY 1 trx.0.clock-correction -100
+</pre>
+
+<pre>
+bsc_control.py -d localhost -p 4238 -s trx.0.clock-correction -- -99
+Got message: SET_REPLY 1 trx.0.clock-correction success
+</pre>
diff --git a/doc/examples/Makefile.am b/doc/examples/Makefile.am
new file mode 100644
index 00000000..04f82798
--- /dev/null
+++ b/doc/examples/Makefile.am
@@ -0,0 +1,46 @@
+OSMOCONF_FILES = virtual/osmo-bts-virtual.cfg
+
+doc_virtualdir = $(docdir)/examples/osmo-bts-virtual
+doc_virtual_DATA = \
+ virtual/osmo-bts-virtual.cfg \
+ virtual/openbsc-virtual.cfg
+EXTRA_DIST = $(doc_virtual_DATA)
+
+if ENABLE_SYSMOBTS
+doc_sysmodir = $(docdir)/examples/osmo-bts-sysmo
+doc_sysmo_DATA = \
+ sysmo/osmo-bts-sysmo.cfg \
+ sysmo/sysmobts-mgr.cfg
+EXTRA_DIST += $(doc_sysmo_DATA)
+OSMOCONF_FILES += sysmo/osmo-bts-sysmo.cfg sysmo/sysmobts-mgr.cfg
+endif
+
+if ENABLE_TRX
+doc_trxdir = $(docdir)/examples/osmo-bts-trx
+doc_trx_DATA = \
+ trx/osmo-bts-trx.cfg \
+ trx/osmo-bts-trx-calypso.cfg
+EXTRA_DIST += $(doc_trx_DATA)
+OSMOCONF_FILES += trx/osmo-bts-trx.cfg
+endif
+
+if ENABLE_OCTPHY
+doc_octphydir = $(docdir)/examples/osmo-bts-octphy
+doc_octphy_DATA = \
+ octphy/osmo-bts-trx2dsp1.cfg \
+ octphy/osmo-bts-octphy.cfg
+EXTRA_DIST += $(doc_octphy_DATA)
+OSMOCONF_FILES += octphy/osmo-bts-octphy.cfg
+endif
+
+if ENABLE_LC15BTS
+doc_lc15dir = $(docdir)/examples/osmo-bts-lc15
+doc_lc15_DATA = \
+ litecell15/osmo-bts-lc15.cfg \
+ litecell15/lc15bts-mgr.cfg
+EXTRA_DIST += $(doc_lc15_DATA)
+OSMOCONF_FILES += litecell15/osmo-bts-lc15.cfg litecell15/lc15bts-mgr.cfg
+endif
+
+osmoconfdir = $(sysconfdir)/osmocom
+osmoconf_DATA = $(OSMOCONF_FILES)
diff --git a/doc/examples/litecell15/lc15bts-mgr.cfg b/doc/examples/litecell15/lc15bts-mgr.cfg
new file mode 100644
index 00000000..a92a3fd6
--- /dev/null
+++ b/doc/examples/litecell15/lc15bts-mgr.cfg
@@ -0,0 +1,43 @@
+!
+! lc15bts-mgr (0.3.0.284-a7c2-dirty) configuration saved from vty
+!!
+!
+log stderr
+ logging filter all 1
+ logging color 1
+ logging print category 0
+ logging timestamp 0
+ logging level temp info
+ logging level fw info
+ logging level find info
+ logging level calib info
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+!
+line vty
+ no login
+!
+lc15bts-mgr
+ limits supply_volt
+ threshold warning min 17500
+ threshold critical min 19000
+ limits tx0_vswr
+ threshold warning max 1000
+ limits tx1_vswr
+ threshold warning max 1000
+ limits supply_pwr
+ threshold warning max 110
+ threshold critical max 120
+ limits pa0_pwr
+ threshold warning max 50
+ threshold critical max 60
+ limits pa1_pwr
+ threshold warning max 50
+ threshold critical max 60
diff --git a/doc/examples/litecell15/osmo-bts-lc15.cfg b/doc/examples/litecell15/osmo-bts-lc15.cfg
new file mode 100644
index 00000000..907d83a2
--- /dev/null
+++ b/doc/examples/litecell15/osmo-bts-lc15.cfg
@@ -0,0 +1,43 @@
+!
+! OsmoBTS (0.0.1.100-0455-dirty) configuration saved from vty
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl info
+ logging level oml info
+ logging level rll notice
+ logging level rr notice
+ logging level meas notice
+ logging level pag info
+ logging level l1c info
+ logging level l1p info
+ logging level dsp debug
+ logging level abis notice
+ logging level rtp notice
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+!
+line vty
+ no login
+!
+phy 0
+ instance 0
+ trx-calibration-path /mnt/rom/factory/calib
+phy 1
+ instance 0
+ trx-calibration-path /mnt/rom/factory/calib
+bts 0
+ band 900
+ ipa unit-id 1500 0
+ oml remote-ip 192.168.234.185
+ trx 0
+ phy 0 instance 0
+ trx 1
+ phy 1 instance 0
diff --git a/doc/examples/oc2g/oc2gbts-mgr.cfg b/doc/examples/oc2g/oc2gbts-mgr.cfg
new file mode 100644
index 00000000..8248f60d
--- /dev/null
+++ b/doc/examples/oc2g/oc2gbts-mgr.cfg
@@ -0,0 +1,33 @@
+!
+! oc2gbts-mgr (0.3.0.284-a7c2-dirty) configuration saved from vty
+!!
+!
+log stderr
+ logging filter all 1
+ logging color 1
+ logging print category 0
+ logging timestamp 0
+ logging level temp info
+ logging level fw info
+ logging level find info
+ logging level calib info
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+!
+line vty
+ no login
+!
+oc2gbts-mgr
+ limits supply_volt
+ threshold warning min 17500
+ threshold critical min 19000
+ limits supply_pwr
+ threshold warning max 110
+ threshold critical max 120
diff --git a/doc/examples/oc2g/osmo-bts.cfg b/doc/examples/oc2g/osmo-bts.cfg
new file mode 100644
index 00000000..f985f3bc
--- /dev/null
+++ b/doc/examples/oc2g/osmo-bts.cfg
@@ -0,0 +1,38 @@
+!
+! OsmoBTS (0.0.1.100-0455-dirty) configuration saved from vty
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl info
+ logging level oml info
+ logging level rll notice
+ logging level rr notice
+ logging level meas notice
+ logging level pag info
+ logging level l1c info
+ logging level l1p info
+ logging level dsp debug
+ logging level abis notice
+ logging level rtp notice
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+!
+line vty
+ no login
+!
+phy 0
+ instance 0
+ trx-calibration-path /mnt/rom/factory/calib
+bts 0
+ band 900
+ ipa unit-id 1500 0
+ oml remote-ip 10.42.0.1
+ trx 0
+ phy 0 instance 0
diff --git a/doc/examples/octphy/osmo-bts-octphy.cfg b/doc/examples/octphy/osmo-bts-octphy.cfg
new file mode 100644
index 00000000..d6d03b5d
--- /dev/null
+++ b/doc/examples/octphy/osmo-bts-octphy.cfg
@@ -0,0 +1,31 @@
+!
+! OsmoBTS () configuration saved from vty
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl info
+ logging level oml info
+ logging level rll notice
+ logging level rr notice
+ logging level meas notice
+ logging level pag info
+ logging level l1c info
+ logging level l1p info
+ logging level dsp info
+ logging level abis notice
+!
+line vty
+ no login
+!
+phy 0
+ octphy hw-addr 00:0C:90:2e:80:1e
+ octphy net-device eth0.2342
+ instance 0
+bts 0
+ band 1800
+ ipa unit-id 1234 0
+ oml remote-ip 127.0.0.1
+ trx 0
+ phy 0 instance 0
diff --git a/doc/examples/octphy/osmo-bts-trx2dsp1.cfg b/doc/examples/octphy/osmo-bts-trx2dsp1.cfg
new file mode 100644
index 00000000..bf590f7d
--- /dev/null
+++ b/doc/examples/octphy/osmo-bts-trx2dsp1.cfg
@@ -0,0 +1,34 @@
+!
+! OsmoBTS () configuration saved from vty
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl info
+ logging level oml info
+ logging level rll notice
+ logging level rr notice
+ logging level meas notice
+ logging level pag info
+ logging level l1c info
+ logging level l1p info
+ logging level dsp info
+ logging level abis notice
+!
+line vty
+ no login
+!
+phy 0
+ octphy hw-addr 00:0c:de:ad:fa:ce
+ octphy net-device eth2
+ instance 0
+ instance 1
+bts 0
+ band 1800
+ ipa unit-id 1234 0
+ oml remote-ip 127.0.0.1
+ trx 0
+ phy 0 instance 0
+ trx 1
+ phy 0 instance 1
diff --git a/doc/examples/sysmo/osmo-bts-sysmo.cfg b/doc/examples/sysmo/osmo-bts-sysmo.cfg
new file mode 100644
index 00000000..6ff043d8
--- /dev/null
+++ b/doc/examples/sysmo/osmo-bts-sysmo.cfg
@@ -0,0 +1,29 @@
+!
+! OsmoBTS () configuration saved from vty
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl info
+ logging level oml info
+ logging level rll notice
+ logging level rr notice
+ logging level meas notice
+ logging level pag info
+ logging level l1c info
+ logging level l1p info
+ logging level dsp info
+ logging level abis notice
+!
+line vty
+ no login
+!
+phy 0
+ instance 0
+bts 0
+ band 1800
+ ipa unit-id 666 0
+ oml remote-ip 10.1.2.3
+ trx 0
+ phy 0 instance 0
diff --git a/doc/examples/sysmo/sysmobts-mgr.cfg b/doc/examples/sysmo/sysmobts-mgr.cfg
new file mode 100644
index 00000000..f891ada7
--- /dev/null
+++ b/doc/examples/sysmo/sysmobts-mgr.cfg
@@ -0,0 +1,23 @@
+!
+! SysmoMgr (0.3.0.141-33e5) configuration saved from vty
+!!
+!
+log stderr
+ logging filter all 1
+ logging color 1
+ logging timestamp 0
+ logging level temp info
+ logging level fw info
+ logging level find info
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+!
+line vty
+ no login
+!
+sysmobts-mgr
diff --git a/doc/examples/trx/osmo-bts-trx-calypso.cfg b/doc/examples/trx/osmo-bts-trx-calypso.cfg
new file mode 100644
index 00000000..6b52fd2a
--- /dev/null
+++ b/doc/examples/trx/osmo-bts-trx-calypso.cfg
@@ -0,0 +1,38 @@
+!
+! OsmoBTS configuration example for CalypsoBTS
+! http://osmocom.org/projects/baseband/wiki/CalypsoBTS
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl notice
+ logging level oml notice
+ logging level rll notice
+ logging level rr notice
+ logging level meas error
+ logging level pag error
+ logging level l1c error
+ logging level l1p error
+ logging level dsp error
+ logging level abis error
+!
+line vty
+ no login
+!
+phy 0
+ instance 0
+ osmotrx rx-gain 1
+ osmotrx ip local 127.0.0.1
+ osmotrx ip remote 127.0.0.1
+ osmotrx timing-advance-loop
+ osmotrx ms-power-loop -65
+ osmotrx legacy-setbsic
+bts 0
+ oml remote-ip 127.0.0.1
+ ipa unit-id 1801 0
+ gsmtap-sapi pdtch
+ gsmtap-sapi ccch
+ band 900
+ trx 0
+ phy 0 instance 0
diff --git a/doc/examples/trx/osmo-bts-trx.cfg b/doc/examples/trx/osmo-bts-trx.cfg
new file mode 100644
index 00000000..83426979
--- /dev/null
+++ b/doc/examples/trx/osmo-bts-trx.cfg
@@ -0,0 +1,34 @@
+!
+! OsmoBTS () configuration saved from vty
+!!
+!
+log stderr
+ logging color 1
+ logging timestamp 0
+ logging level rsl notice
+ logging level oml notice
+ logging level rll notice
+ logging level rr notice
+ logging level meas error
+ logging level pag error
+ logging level l1c error
+ logging level l1p error
+ logging level dsp error
+ logging level abis error
+!
+line vty
+ no login
+!
+phy 0
+ instance 0
+ osmotrx rx-gain 1
+ osmotrx ip local 127.0.0.1
+ osmotrx ip remote 127.0.0.1
+bts 0
+ band 1800
+ ipa unit-id 6969 0
+ oml remote-ip 192.168.122.1
+ gsmtap-sapi ccch
+ gsmtap-sapi pdtch
+ trx 0
+ phy 0 instance 0
diff --git a/doc/examples/virtual/openbsc-virtual.cfg b/doc/examples/virtual/openbsc-virtual.cfg
new file mode 100644
index 00000000..be79d589
--- /dev/null
+++ b/doc/examples/virtual/openbsc-virtual.cfg
@@ -0,0 +1,151 @@
+!
+! OpenBSC (0.15.0.629-34f0-dirty) configuration saved from vty
+!!
+!
+log stderr
+ logging filter all 1
+ logging color 0
+ logging print category 1
+ logging timestamp 1
+ logging level all info
+ logging level rll notice
+ logging level cc notice
+ logging level mm debug
+ logging level rr notice
+ logging level rsl notice
+ logging level nm info
+ logging level mncc notice
+ logging level pag notice
+ logging level meas notice
+ logging level sccp notice
+ logging level msc notice
+ logging level mgcp notice
+ logging level ho notice
+ logging level db notice
+ logging level ref notice
+ logging level gprs debug
+ logging level ns info
+ logging level bssgp debug
+ logging level llc debug
+ logging level sndcp debug
+ logging level nat notice
+ logging level ctrl notice
+ logging level smpp debug
+ logging level filter debug
+ logging level ranap debug
+ logging level sua debug
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+ logging level lstats notice
+ logging level lgsup notice
+ logging level loap notice
+!
+stats interval 5
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver ipa
+ e1_line 0 port 0
+ no e1_line 0 keepalive
+network
+ network country code 262
+ mobile network code 42
+ short name OpenBSC
+ long name OpenBSC
+ auth policy accept-all
+ authorized-regexp .*
+ location updating reject cause 13
+ encryption a5 0
+ neci 1
+ paging any use tch 0
+ rrlp mode ms-based
+ mm info 1
+ handover 0
+ handover window rxlev averaging 10
+ handover window rxqual averaging 1
+ handover window rxlev neighbor averaging 10
+ handover power budget interval 6
+ handover power budget hysteresis 3
+ handover maximum distance 9999
+ timer t3101 10
+ timer t3103 0
+ timer t3105 0
+ timer t3107 0
+ timer t3109 4
+ timer t3111 0
+ timer t3113 60
+ timer t3115 0
+ timer t3117 0
+ timer t3119 0
+ timer t3122 10
+ timer t3141 0
+ subscriber-keep-in-ram 0
+ bts 0
+ type sysmobts
+ band DCS1800
+ cell_identity 6969
+ location_area_code 1
+ base_station_id_code 63
+ ms max power 0
+ cell reselection hysteresis 4
+ rxlev access min 0
+ periodic location update 30
+ radio-link-timeout 32
+ channel allocator descending
+ rach tx integer 9
+ rach max transmission 7
+ channel-descrption attach 1
+ channel-descrption bs-pa-mfrms 5
+ channel-descrption bs-ag-blks-res 1
+ ip.access unit_id 6969 0
+ oml ip.access stream_id 255 line 0
+ neighbor-list mode automatic
+ codec-support fr
+ gprs mode none
+ no force-combined-si
+ trx 0
+ rf_locked 0
+ arfcn 666
+ nominal power 0
+ max_power_red 0
+ rsl e1 tei 0
+ timeslot 0
+ phys_chan_config CCCH+SDCCH4
+ hopping enabled 0
+ timeslot 1
+ phys_chan_config SDCCH8
+ hopping enabled 0
+ timeslot 2
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 3
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 4
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 5
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 6
+ phys_chan_config TCH/F
+ hopping enabled 0
+ timeslot 7
+ phys_chan_config TCH/F
+ hopping enabled 0
+mncc-int
+ default-codec tch-f fr
+ default-codec tch-h hr
+nitb
+ subscriber-create-on-demand
+ subscriber-create-on-demand random 1 24
+ assign-tmsi
diff --git a/doc/examples/virtual/osmo-bts-virtual.cfg b/doc/examples/virtual/osmo-bts-virtual.cfg
new file mode 100644
index 00000000..dbdc22fa
--- /dev/null
+++ b/doc/examples/virtual/osmo-bts-virtual.cfg
@@ -0,0 +1,61 @@
+!
+! OsmoBTS (0.4.0.216-bc49-dirty) configuration saved from vty
+!!
+!
+log stderr
+ logging filter all 0
+ logging color 0
+ logging print category 1
+ logging timestamp 0
+ logging level rsl info
+ logging level oml info
+ logging level rll notice
+ logging level rr notice
+ logging level meas notice
+ logging level pag info
+ logging level l1c info
+ logging level l1p info
+ logging level dsp error
+ logging level pcu notice
+ logging level ho debug
+ logging level trx notice
+ logging level loop notice
+ logging level abis debug
+ logging level rtp notice
+ logging level sum error
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+ logging level lstats error
+!
+line vty
+ no login
+!
+e1_input
+ e1_line 0 driver ipa
+ e1_line 0 port 0
+ no e1_line 0 keepalive
+phy 0
+ instance 0
+bts 0
+ band DCS1800
+ ipa unit-id 6969 0
+ oml remote-ip 127.0.0.1
+ rtp jitter-buffer 100
+ paging queue-size 200
+ paging lifetime 0
+ uplink-power-target -75
+ min-qual-rach 50
+ min-qual-norm -5
+ trx 0
+ power-ramp max-initial 23000 mdBm
+ power-ramp step-size 2000 mdB
+ power-ramp step-interval 1
+ ms-power-control dsp
+ phy 0 instance 0
diff --git a/doc/phy_link.txt b/doc/phy_link.txt
new file mode 100644
index 00000000..c49e328f
--- /dev/null
+++ b/doc/phy_link.txt
@@ -0,0 +1,57 @@
+
+== OsmoBTS PHY interface abstraction
+
+The OsmoBTS PHY interface serves as an abstraction layer between given
+PHY hardware and the actual logical transceivers (TRXs) of a BTS inside
+the OsmoBTS code base.
+
+
+=== PHY link
+
+A PHY link is a physical connection / link towards a given PHY. This
+might be, for example,
+
+* a set of file descriptors to device nodes in the /dev/ directory
+ (sysmobts, litecell15)
+* a packet socket for sending raw Ethernet frames to an OCTPHY
+* a set of UDP sockets for interacting with OsmoTRX
+
+Each PHY interface has a set of attribute/parameters and a list of 1 to
+n PHY instances.
+
+PHY links are numbered 0..n globally inside OsmoBTS.
+
+Each PHY link is configured via the VTY using its individual top-level
+vty node. Given the different bts-model / phy specific properties, the
+VTY configuration options (if any) of the PHY instance differ between
+BTS models.
+
+The PHY links and instances must be configured above the BTS/TRX nodes
+in the configuration file. If the file is saved via the VTY, the code
+automatically ensures this.
+
+
+=== PHY instance
+
+A PHY instance is an instance of a PHY, accessed via a PHY link.
+
+In the case of osmo-bts-sysmo and osmo-bts-trx, there is only one
+instance in every PHY link. This is due to the fact that the API inside
+that PHY link does not permit for distinguishing multiple different
+logical TRXs.
+
+Other PHY implementations like the OCTPHY however do support addressing
+multiple PHY instances via a single PHY link.
+
+PHY instances are numbered 0..n inside each PHY link.
+
+Each PHY instance is configured via the VTY as a separate node beneath each
+PHY link. Given the different bts-model / phy specific properties, the
+VTY configuration options (if any) of the PHY instance differ between
+BTS models.
+
+
+=== Mapping PHY instances to TRXs
+
+Each TRX node in the VTY must use the 'phy N instance M' command in
+order to specify which PHY instance is allocated to this specific TRX.
diff --git a/doc/startup.txt b/doc/startup.txt
new file mode 100644
index 00000000..50766e48
--- /dev/null
+++ b/doc/startup.txt
@@ -0,0 +1,42 @@
+
+== start-up / sequencing during OsmoBTS start
+
+The start-up procedure of OsmoBTS can be described as follows:
+
+|===
+| bts-specific | main() |
+| common | bts_main() | initialization of talloc contexts
+| common | osmo_init_logging2() | initialization of logging
+| common | handle_options() | common option parsing
+| bts-specific | bts_model_handle_options() | model-specific option parsing
+| common | gsm_bts_alloc() | allocation of BTS/TRX/TS data structures
+| common | vty_init() | Initialziation of VTY core, libosmo-abis and osmo-bts VTY
+| common | main() | Setting of scheduler RR priority (if configured)
+| common | main() | Initialization of GSMTAP (if configured)
+| common | bts_init() | configuration of defaults in bts/trx/s object
+| bts-specific | bts_model_init | ?
+| common | abis_init() | Initialization of libosmo-abis
+| common | vty_read_config_file() | Reading of configuration file
+| bts-specific | bts_model_phy_link_set_defaults() | Called for every PHY link created
+| bts-specific | bts_model_phy_instance_set_defaults() | Called for every PHY Instance created
+| common | bts_controlif_setup() | Initialization of Control Interface
+| bts-specific | bts_model_ctrl_cmds_install()
+| common | telnet_init() | Initialization of telnet interface
+| common | pcu_sock_init() | Initializaiton of PCU socket
+| common | main() | Installation of signal handlers
+| common | abis_open() | Start of the A-bis connection to BSC
+| common | phy_links_open() | Iterate over list of configured PHY links
+| bts-specific | bts_model_phy_link_open() | Open each of the configured PHY links
+| common | write_pid_file() | Generate the pid file
+| common | osmo_daemonize() | Fork as daemon in background (if configured)
+| common | bts_main() | Run main loop until global variable quit >= 2
+| bts-specific | bts_model_oml_estab() | Called by core once OML link is established
+| bts-specific | bts_model_check_oml() | called each time OML sets some attributes on a MO, checks if attributes are valid
+| bts-specific | bts_model_apply_oml() | called each time OML sets some attributes on a MO, stores attribute contents in data structures
+| bts-specific | bts_model_opstart() | for NM_OC_BTS, NM_OC_SITE_MANAGER, NM_OC_GPRS_NSE, NM_OC_GPRS_CELL, NMO_OC_GPRS_NSVC
+| bts-specific | bts_model_opstart() | for NM_OC_RADIO_CARRIER for each trx
+| bts-specific | bts_model_opstart() | for NM_OC_BASEB_TRANSC for each trx
+| bts-specific | bts_model_opstart() | for NM_OC_CHANNEL for each timeslot on each trx
+| bts-specific | bts_model_change_power() | change transmit power for each trx (power ramp-up/ramp-down
+
+| bts-specific | bts_model_abis_close() | called when either one of the RSL links or the OML link are down
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 00000000..42cf3d2b
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+# produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+# presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+# a checked-out repository. Created with contents that were learned at
+# the last time autoconf was run, and used by git-version-gen. Must not
+# be present in either $(srcdir) or $(builddir) for git-version-gen to
+# give accurate answers during normal development with a checked out tree,
+# but must be present in a tarball when there is no version control system.
+# Therefore, it cannot be used in any dependencies. GNUmakefile has
+# hooks to force a reconfigure at distribution time to get the value
+# correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+# tarball. Usable in dependencies, particularly for files that don't
+# want to depend on config.h but do want to track version changes.
+# Delete this file prior to any autoconf run where you want to rebuild
+# files to pick up a version string change; and leave it stale to
+# minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+# [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+# echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+# echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+ 1) ;;
+ *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+ v=`cat $tarball_version_file` || exit 1
+ case $v in
+ *$nl*) v= ;; # reject multi-line output
+ [0-9]*) ;;
+ *) v= ;;
+ esac
+ test -z "$v" \
+ && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+ : # use $v
+elif
+ v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+ || git describe --abbrev=4 HEAD 2>/dev/null` \
+ && case $v in
+ [0-9]*) ;;
+ v[0-9]*) ;;
+ *) (exit 1) ;;
+ esac
+then
+ # Is this a new git that lists number of commits since the last
+ # tag or the previous older version that did not?
+ # Newer: v6.10-77-g0f8faeb
+ # Older: v6.10-g0f8faeb
+ case $v in
+ *-*-*) : git describe is okay three part flavor ;;
+ *-*)
+ : git describe is older two part flavor
+ # Recreate the number of commits and rewrite such that the
+ # result is the same as if we were using the newer version
+ # of git describe.
+ vtag=`echo "$v" | sed 's/-.*//'`
+ numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+ v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+ ;;
+ esac
+
+ # Change the first '-' to a '.', so version-comparing tools work properly.
+ # Remove the "g" in git describe's output string, to save a byte.
+ v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+ v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+ '') ;;
+ *) # Append the suffix only if there isn't one already.
+ case $v in
+ *-dirty) ;;
+ *) v="$v-dirty" ;;
+ esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 00000000..7585a65f
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = osmo-bts
diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am
new file mode 100644
index 00000000..a15ce3d2
--- /dev/null
+++ b/include/osmo-bts/Makefile.am
@@ -0,0 +1,5 @@
+noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h gsm_data_shared.h logging.h measurement.h \
+ oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \
+ handover.h msg_utils.h tx_power.h control_if.h cbch.h l1sap.h \
+ power_control.h scheduler.h scheduler_backend.h phy_link.h \
+ dtx_dl_amr_fsm.h
diff --git a/include/osmo-bts/abis.h b/include/osmo-bts/abis.h
new file mode 100644
index 00000000..62407ece
--- /dev/null
+++ b/include/osmo-bts/abis.h
@@ -0,0 +1,29 @@
+#ifndef _ABIS_H
+#define _ABIS_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <osmo-bts/gsm_data.h>
+
+#define OML_RETRY_TIMER 5
+#define OML_PING_TIMER 20
+
+enum {
+ LINK_STATE_IDLE = 0,
+ LINK_STATE_RETRYING,
+ LINK_STATE_CONNECTING,
+ LINK_STATE_CONNECT,
+};
+
+void abis_init(struct gsm_bts *bts);
+struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host,
+ char *model_name);
+
+
+int abis_oml_sendmsg(struct msgb *msg);
+int abis_bts_rsl_sendmsg(struct msgb *msg);
+
+uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link);
+
+#endif /* _ABIS_H */
diff --git a/include/osmo-bts/amr.h b/include/osmo-bts/amr.h
new file mode 100644
index 00000000..f3132874
--- /dev/null
+++ b/include/osmo-bts/amr.h
@@ -0,0 +1,18 @@
+#ifndef _OSMO_BTS_AMR_H
+#define _OSMO_BTS_AMR_H
+
+#include <osmo-bts/gsm_data.h>
+
+#define AMR_TOC_QBIT 0x04
+#define AMR_CMR_NONE 0xF
+
+void amr_log_mr_conf(int ss, int logl, const char *pfx,
+ struct amr_multirate_conf *amr_mrc);
+
+int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc,
+ const uint8_t *mr_conf, unsigned int len);
+void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi, uint8_t cmr);
+unsigned int amr_get_initial_mode(struct gsm_lchan *lchan);
+
+#endif /* _OSMO_BTS_AMR_H */
diff --git a/include/osmo-bts/bts.h b/include/osmo-bts/bts.h
new file mode 100644
index 00000000..d7c4bbf3
--- /dev/null
+++ b/include/osmo-bts/bts.h
@@ -0,0 +1,68 @@
+#ifndef _BTS_H
+#define _BTS_H
+
+#include <osmocom/core/rate_ctr.h>
+#include <osmo-bts/gsm_data.h>
+
+enum bts_global_status {
+ BTS_STATUS_RF_ACTIVE,
+ BTS_STATUS_RF_MUTE,
+ BTS_STATUS_LAST,
+};
+
+enum {
+ BTS_CTR_PAGING_RCVD,
+ BTS_CTR_PAGING_DROP,
+ BTS_CTR_PAGING_SENT,
+ BTS_CTR_RACH_RCVD,
+ BTS_CTR_RACH_DROP,
+ BTS_CTR_RACH_HO,
+ BTS_CTR_RACH_CS,
+ BTS_CTR_RACH_PS,
+ BTS_CTR_AGCH_RCVD,
+ BTS_CTR_AGCH_SENT,
+ BTS_CTR_AGCH_DELETED,
+};
+
+extern void *tall_bts_ctx;
+
+int bts_init(struct gsm_bts *bts);
+int bts_trx_init(struct gsm_bts_trx *trx);
+void bts_shutdown(struct gsm_bts *bts, const char *reason);
+
+struct gsm_bts *create_bts(uint8_t num_trx, char *id);
+int create_ms(struct gsm_bts_trx *trx, int maskc, uint8_t *maskv_tx,
+ uint8_t *maskv_rx);
+void destroy_bts(struct gsm_bts *bts);
+int work_bts(struct gsm_bts *bts);
+int bts_link_estab(struct gsm_bts *bts);
+int trx_link_estab(struct gsm_bts_trx *trx);
+int trx_set_available(struct gsm_bts_trx *trx, int avail);
+void bts_new_si(void *arg);
+void bts_setup_slot(struct gsm_bts_trx_ts *slot, uint8_t comb);
+
+int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg);
+struct msgb *bts_agch_dequeue(struct gsm_bts *bts);
+int bts_agch_max_queue_length(int T, int bcch_conf);
+int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt,
+ int is_ag_res);
+
+uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time);
+uint8_t *lchan_sacch_get(struct gsm_lchan *lchan);
+int lchan_init_lapdm(struct gsm_lchan *lchan);
+
+void load_timer_start(struct gsm_bts *bts);
+uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg);
+void bts_update_status(enum bts_global_status which, int on);
+
+int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx);
+
+struct gsm_time *get_time(struct gsm_bts *bts);
+
+int bts_main(int argc, char **argv);
+
+int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan,
+ enum gsm48_chan_mode cm);
+
+#endif /* _BTS_H */
+
diff --git a/include/osmo-bts/bts_model.h b/include/osmo-bts/bts_model.h
new file mode 100644
index 00000000..be0480c1
--- /dev/null
+++ b/include/osmo-bts/bts_model.h
@@ -0,0 +1,65 @@
+#ifndef BTS_MODEL_H
+#define BTS_MODEL_H
+
+#include <stdint.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/gsm_data.h>
+
+struct phy_link;
+struct phy_instance;
+
+/* BTS model specific functions needed by the common code */
+
+int bts_model_init(struct gsm_bts *bts);
+int bts_model_trx_init(struct gsm_bts_trx *trx);
+
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj);
+
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int obj_kind, void *obj);
+
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj);
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state);
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx);
+int bts_model_trx_close(struct gsm_bts_trx *trx);
+
+int bts_model_vty_init(struct gsm_bts *bts);
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts);
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx);
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink);
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst);
+
+int bts_model_oml_estab(struct gsm_bts *bts);
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm);
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan);
+
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan);
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan);
+
+void bts_model_abis_close(struct gsm_bts *bts);
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts);
+
+int bts_model_handle_options(int argc, char **argv);
+void bts_model_print_help();
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink);
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst);
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts);
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan);
+
+#endif
diff --git a/include/osmo-bts/cbch.h b/include/osmo-bts/cbch.h
new file mode 100644
index 00000000..b4ac409f
--- /dev/null
+++ b/include/osmo-bts/cbch.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts.h>
+
+/* incoming SMS broadcast command from RSL */
+int bts_process_smscb_cmd(struct gsm_bts *bts,
+ struct rsl_ie_cb_cmd_type cmd_type,
+ uint8_t msg_len, const uint8_t *msg);
+
+/* call-back from bts model specific code when it wants to obtain a CBCH
+ * block for a given gsm_time. outbuf must have 23 bytes of space. */
+int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time);
diff --git a/include/osmo-bts/control_if.h b/include/osmo-bts/control_if.h
new file mode 100644
index 00000000..490c87af
--- /dev/null
+++ b/include/osmo-bts/control_if.h
@@ -0,0 +1,5 @@
+#pragma once
+
+int bts_ctrl_cmds_install(struct gsm_bts *bts);
+struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts,
+ const char *bind_addr, uint16_t port);
diff --git a/include/osmo-bts/dtx_dl_amr_fsm.h b/include/osmo-bts/dtx_dl_amr_fsm.h
new file mode 100644
index 00000000..c66ac7d6
--- /dev/null
+++ b/include/osmo-bts/dtx_dl_amr_fsm.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+
+/* DTX DL AMR FSM */
+
+#define X(s) (1 << (s))
+
+enum dtx_dl_amr_fsm_states {
+ ST_VOICE,
+ ST_SID_F1,
+ ST_SID_F2,
+ ST_F1_INH_V,
+ ST_F1_INH_F,
+ ST_U_INH_V,
+ ST_U_INH_F,
+ ST_U_NOINH,
+ ST_F1_INH_V_REC,
+ ST_F1_INH_F_REC,
+ ST_U_INH_V_REC,
+ ST_U_INH_F_REC,
+ ST_SID_U,
+ ST_ONSET_V,
+ ST_ONSET_F,
+ ST_ONSET_V_REC,
+ ST_ONSET_F_REC,
+ ST_FACCH,
+};
+
+enum dtx_dl_amr_fsm_events {
+ E_VOICE,
+ E_ONSET,
+ E_FACCH,
+ E_COMPL,
+ E_FIRST,
+ E_INHIB,
+ E_SID_F,
+ E_SID_U,
+};
+
+extern const struct value_string dtx_dl_amr_fsm_event_names[];
+extern struct osmo_fsm dtx_dl_amr_fsm;
diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h
new file mode 100644
index 00000000..9e62cdf0
--- /dev/null
+++ b/include/osmo-bts/gsm_data.h
@@ -0,0 +1,58 @@
+#ifndef _GSM_DATA_H
+#define _GSM_DATA_H
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/gsm/gsm23003.h>
+
+#include <osmo-bts/paging.h>
+#include <osmo-bts/tx_power.h>
+
+#define GSM_FR_BITS 260
+#define GSM_EFR_BITS 244
+
+#define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */
+#define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */
+#define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */
+
+#define GSM_SUPERFRAME (26*51) /* 1326 TDMA frames */
+#define GSM_HYPERFRAME (2048*GSM_SUPERFRAME) /* GSM_HYPERFRAME frames */
+
+#define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT 41
+#define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE 999999
+#define GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT 41
+#define GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT 91
+
+struct gsm_network {
+ struct llist_head bts_list;
+ unsigned int num_bts;
+ struct osmo_plmn_id plmn;
+ struct pcu_sock_state *pcu_state;
+};
+
+enum lchan_ciph_state {
+ LCHAN_CIPH_NONE,
+ LCHAN_CIPH_RX_REQ,
+ LCHAN_CIPH_RX_CONF,
+ LCHAN_CIPH_RXTX_REQ,
+ LCHAN_CIPH_RX_CONF_TX_REQ,
+ LCHAN_CIPH_RXTX_CONF,
+};
+
+#include <osmo-bts/gsm_data_shared.h>
+
+void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state);
+int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan);
+
+/* cipher code */
+#define CIPHER_A5(x) (1 << (x-1))
+
+int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher);
+
+bool ts_is_pdch(const struct gsm_bts_trx_ts *ts);
+
+int bts_model_check_cm_mode(enum gsm_phys_chan_config pchan, enum gsm48_chan_mode cm);
+
+#endif /* _GSM_DATA_H */
diff --git a/include/osmo-bts/gsm_data_shared.h b/include/osmo-bts/gsm_data_shared.h
new file mode 100644
index 00000000..56ab5b19
--- /dev/null
+++ b/include/osmo-bts/gsm_data_shared.h
@@ -0,0 +1,854 @@
+#ifndef _GSM_DATA_SHAREDH
+#define _GSM_DATA_SHAREDH
+
+#include <regex.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/codec/ecu.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/rxlev_stat.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <osmocom/gsm/meas_rep.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/lapdm.h>
+
+/* 16 is the max. number of SI2quater messages according to 3GPP TS 44.018 Table 10.5.2.33b.1:
+ 4-bit index is used (2#1111 = 10#15) */
+#define SI2Q_MAX_NUM 16
+/* length in bits (for single SI2quater message) */
+#define SI2Q_MAX_LEN 160
+#define SI2Q_MIN_LEN 18
+
+/* Channel Request reason */
+enum gsm_chreq_reason_t {
+ GSM_CHREQ_REASON_EMERG,
+ GSM_CHREQ_REASON_PAG,
+ GSM_CHREQ_REASON_CALL,
+ GSM_CHREQ_REASON_LOCATION_UPD,
+ GSM_CHREQ_REASON_OTHER,
+ GSM_CHREQ_REASON_PDCH,
+};
+
+/* lchans 0..3 are SDCCH in combined channel configuration,
+ use 4 as magic number for BCCH hack - see osmo-bts-../oml.c:opstart_compl() */
+#define CCCH_LCHAN 4
+
+#define TRX_NR_TS 8
+#define TS_MAX_LCHAN 8
+
+#define HARDCODED_ARFCN 123
+#define HARDCODED_BSIC 0x3f /* NCC = 7 / BCC = 7 */
+
+/* for multi-drop config */
+#define HARDCODED_BTS0_TS 1
+#define HARDCODED_BTS1_TS 6
+#define HARDCODED_BTS2_TS 11
+
+#define MAX_VERSION_LENGTH 64
+
+#define MAX_BTS_FEATURES 128
+
+enum gsm_hooks {
+ GSM_HOOK_NM_SWLOAD,
+ GSM_HOOK_RR_PAGING,
+ GSM_HOOK_RR_SECURITY,
+};
+
+enum bts_gprs_mode {
+ BTS_GPRS_NONE = 0,
+ BTS_GPRS_GPRS = 1,
+ BTS_GPRS_EGPRS = 2,
+};
+
+struct gsm_lchan;
+struct osmo_rtp_socket;
+struct pcu_sock_state;
+struct smscb_msg;
+
+/* Network Management State */
+struct gsm_nm_state {
+ uint8_t operational;
+ uint8_t administrative;
+ uint8_t availability;
+};
+
+struct gsm_abis_mo {
+ /* A-bis OML Object Class */
+ uint8_t obj_class;
+ /* is there still some procedure pending? */
+ uint8_t procedure_pending;
+ /* A-bis OML Object Instance */
+ struct abis_om_obj_inst obj_inst;
+ /* human-readable name */
+ const char *name;
+ /* NM State */
+ struct gsm_nm_state nm_state;
+ /* Attributes configured in this MO */
+ struct tlv_parsed *nm_attr;
+ /* BTS to which this MO belongs */
+ struct gsm_bts *bts;
+};
+
+#define MAX_A5_KEY_LEN (128/8)
+#define A38_XOR_MIN_KEY_LEN 12
+#define A38_XOR_MAX_KEY_LEN 16
+#define A38_COMP128_KEY_LEN 16
+#define RSL_ENC_ALG_A5(x) (x+1)
+#define MAX_EARFCN_LIST 32
+
+/* is the data link established? who established it? */
+#define LCHAN_SAPI_UNUSED 0
+#define LCHAN_SAPI_MS 1
+#define LCHAN_SAPI_NET 2
+#define LCHAN_SAPI_REL 3
+
+/* state of a logical channel */
+enum gsm_lchan_state {
+ LCHAN_S_NONE, /* channel is not active */
+ LCHAN_S_ACT_REQ, /* channel activation requested */
+ LCHAN_S_ACTIVE, /* channel is active and operational */
+ LCHAN_S_REL_REQ, /* channel release has been requested */
+ LCHAN_S_REL_ERR, /* channel is in an error state */
+ LCHAN_S_BROKEN, /* channel is somehow unusable */
+ LCHAN_S_INACTIVE, /* channel is set inactive */
+};
+
+/* BTS ONLY */
+#define MAX_NUM_UL_MEAS 104
+#define LC_UL_M_F_L1_VALID (1 << 0)
+#define LC_UL_M_F_RES_VALID (1 << 1)
+#define LC_UL_M_F_OSMO_EXT_VALID (1 << 2)
+
+struct bts_ul_meas {
+ /* BER in units of 0.01%: 10.000 == 100% ber, 0 == 0% ber */
+ uint16_t ber10k;
+ /* timing advance offset (in 1/256 bits) */
+ int16_t ta_offs_256bits;
+ /* C/I ratio in dB */
+ float c_i;
+ /* flags */
+ uint8_t is_sub:1;
+ /* RSSI in dBm * -1 */
+ uint8_t inv_rssi;
+};
+
+struct bts_codec_conf {
+ uint8_t hr;
+ uint8_t efr;
+ uint8_t amr;
+};
+
+struct amr_mode {
+ uint8_t mode;
+ uint8_t threshold;
+ uint8_t hysteresis;
+};
+
+struct amr_multirate_conf {
+ uint8_t gsm48_ie[2];
+ struct amr_mode ms_mode[4];
+ struct amr_mode bts_mode[4];
+ uint8_t num_modes;
+};
+/* /BTS ONLY */
+
+enum lchan_csd_mode {
+ LCHAN_CSD_M_NT,
+ LCHAN_CSD_M_T_1200_75,
+ LCHAN_CSD_M_T_600,
+ LCHAN_CSD_M_T_1200,
+ LCHAN_CSD_M_T_2400,
+ LCHAN_CSD_M_T_9600,
+ LCHAN_CSD_M_T_14400,
+ LCHAN_CSD_M_T_29000,
+ LCHAN_CSD_M_T_32000,
+};
+
+/* State of the SAPIs in the lchan */
+enum lchan_sapi_state {
+ LCHAN_SAPI_S_NONE,
+ LCHAN_SAPI_S_REQ,
+ LCHAN_SAPI_S_ASSIGNED,
+ LCHAN_SAPI_S_REL,
+ LCHAN_SAPI_S_ERROR,
+};
+
+struct gsm_lchan {
+ /* The TS that we're part of */
+ struct gsm_bts_trx_ts *ts;
+ /* The logical subslot number in the TS */
+ uint8_t nr;
+ /* The logical channel type */
+ enum gsm_chan_t type;
+ /* RSL channel mode */
+ enum rsl_cmod_spd rsl_cmode;
+ /* If TCH, traffic channel mode */
+ enum gsm48_chan_mode tch_mode;
+ enum lchan_csd_mode csd_mode;
+ /* State */
+ enum gsm_lchan_state state;
+ const char *broken_reason;
+ /* Power levels for MS and BTS */
+ uint8_t bs_power;
+ uint8_t ms_power;
+ /* Encryption information */
+ struct {
+ uint8_t alg_id;
+ uint8_t key_len;
+ uint8_t key[MAX_A5_KEY_LEN];
+ } encr;
+
+ /* AMR bits */
+ uint8_t mr_bts_lv[7];
+
+ /* Established data link layer services */
+ int sacch_deact;
+
+ struct {
+ uint32_t bound_ip;
+ uint32_t connect_ip;
+ uint16_t bound_port;
+ uint16_t connect_port;
+ uint16_t conn_id;
+ uint8_t rtp_payload;
+ uint8_t rtp_payload2;
+ uint8_t speech_mode;
+ struct osmo_rtp_socket *rtp_socket;
+ } abis_ip;
+
+ uint8_t rqd_ta;
+
+ char *name;
+
+ /* Number of different GsmL1_Sapi_t used in osmo_bts_sysmo is 23.
+ * Currently we don't share these headers so this is a magic number. */
+ struct llist_head sapi_cmds;
+ uint8_t sapis_dl[23];
+ uint8_t sapis_ul[23];
+ struct lapdm_channel lapdm_ch;
+ struct llist_head dl_tch_queue;
+ struct {
+ /* bitmask of all SI that are present/valid in si_buf */
+ uint32_t valid;
+ /* bitmask of all SI that do not mirror the BTS-global SI values */
+ uint32_t overridden;
+ uint32_t last;
+ /* buffers where we put the pre-computed SI:
+ SI2Q_MAX_NUM is the max number of SI2quater messages (see 3GPP TS 44.018) */
+ sysinfo_buf_t buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM];
+ } si;
+ struct {
+ uint8_t flags;
+ /* RSL measurment result number, 0 at lchan_act */
+ uint8_t res_nr;
+ /* current Tx power level of the BTS */
+ uint8_t bts_tx_pwr;
+ /* number of measurements stored in array below */
+ uint8_t num_ul_meas;
+ struct bts_ul_meas uplink[MAX_NUM_UL_MEAS];
+ /* last L1 header from the MS */
+ uint8_t l1_info[2];
+ struct gsm_meas_rep_unidir ul_res;
+ int16_t ms_toa256;
+ /* Frame number of the last measurement indication receceived */
+ uint32_t last_fn;
+ /* Osmocom extended measurement results, see LC_UL_M_F_EXTD_VALID */
+ struct {
+ /* minimum value of toa256 during measurement period */
+ int16_t toa256_min;
+ /* maximum value of toa256 during measurement period */
+ int16_t toa256_max;
+ /* standard deviation of toa256 value during measurement period */
+ uint16_t toa256_std_dev;
+ } ext;
+ } meas;
+ struct {
+ struct amr_multirate_conf amr_mr;
+ struct {
+ struct osmo_fsm_inst *dl_amr_fsm;
+ /* TCH cache */
+ uint8_t cache[20];
+ /* FACCH cache */
+ uint8_t facch[GSM_MACBLOCK_LEN];
+ uint8_t len;
+ uint32_t fn;
+ bool is_update;
+ /* set for each SID frame to detect talkspurt for codecs
+ without explicit ONSET event */
+ bool ul_sid;
+ /* indicates if DTXd was active during DL measurement
+ period */
+ bool dl_active;
+ /* last UL SPEECH resume flag */
+ bool is_speech_resume;
+ } dtx;
+ uint8_t last_cmr;
+ uint32_t last_fn;
+ } tch;
+
+ /* 3GPP TS 48.058 § 9.3.37: [0; 255] ok, -1 means invalid*/
+ int16_t ms_t_offs;
+ /* 3GPP TS 45.010 § 1.2 round trip propagation delay (in symbols) or -1 */
+ int16_t p_offs;
+
+ /* BTS-side ciphering state (rx only, bi-directional, ...) */
+ uint8_t ciph_state;
+ uint8_t ciph_ns;
+ uint8_t loopback;
+ struct {
+ uint8_t active;
+ uint8_t ref;
+ /* T3105: PHYS INF retransmission */
+ struct osmo_timer_list t3105;
+ /* counts up to Ny1 */
+ unsigned int phys_info_count;
+ } ho;
+ /* S counter for link loss */
+ int s;
+ /* Kind of the release/activation. E.g. RSL or PCU */
+ int rel_act_kind;
+ /* RTP header Marker bit to indicate beginning of speech after pause */
+ bool rtp_tx_marker;
+ /* power handling */
+ struct {
+ uint8_t current;
+ uint8_t fixed;
+ } ms_power_ctrl;
+
+ struct msgb *pending_rel_ind_msg;
+
+ /* ECU (Error Concealment Unit) state */
+ union {
+ struct osmo_ecu_fr_state fr;
+ } ecu_state;
+};
+
+static inline uint8_t lchan_get_ta(const struct gsm_lchan *lchan)
+{
+ return lchan->meas.l1_info[1];
+}
+
+extern const struct value_string lchan_ciph_state_names[];
+static inline const char *lchan_ciph_state_name(uint8_t state) {
+ return get_value_string(lchan_ciph_state_names, state);
+}
+
+enum {
+ TS_F_PDCH_ACTIVE = 0x1000,
+ TS_F_PDCH_ACT_PENDING = 0x2000,
+ TS_F_PDCH_DEACT_PENDING = 0x4000,
+ TS_F_PDCH_PENDING_MASK = 0x6000 /*<
+ TS_F_PDCH_ACT_PENDING | TS_F_PDCH_DEACT_PENDING */
+} gsm_bts_trx_ts_flags;
+
+/* One Timeslot in a TRX */
+struct gsm_bts_trx_ts {
+ struct gsm_bts_trx *trx;
+ /* number of this timeslot at the TRX */
+ uint8_t nr;
+
+ enum gsm_phys_chan_config pchan;
+
+ struct {
+ enum gsm_phys_chan_config pchan_is;
+ enum gsm_phys_chan_config pchan_want;
+ struct msgb *pending_chan_activ;
+ } dyn;
+
+ unsigned int flags;
+ struct gsm_abis_mo mo;
+ struct tlv_parsed nm_attr;
+ uint8_t nm_chan_comb;
+ int tsc; /* -1 == use BTS TSC */
+
+ struct {
+ /* Parameters below are configured by VTY */
+ int enabled;
+ uint8_t maio;
+ uint8_t hsn;
+ struct bitvec arfcns;
+ uint8_t arfcns_data[1024/8];
+ /* This is the pre-computed MA for channel assignments */
+ struct bitvec ma;
+ uint8_t ma_len; /* part of ma_data that is used */
+ uint8_t ma_data[8]; /* 10.5.2.21: max 8 bytes value part */
+ } hopping;
+
+ struct gsm_lchan lchan[TS_MAX_LCHAN];
+};
+
+/* One TRX in a BTS */
+struct gsm_bts_trx {
+ /* list header in bts->trx_list */
+ struct llist_head list;
+
+ struct gsm_bts *bts;
+ /* number of this TRX in the BTS */
+ uint8_t nr;
+ /* human readable name / description */
+ char *description;
+ /* how do we talk RSL with this TRX? */
+ uint8_t rsl_tei;
+ struct e1inp_sign_link *rsl_link;
+
+ /* Some BTS (specifically Ericsson RBS) have a per-TRX OML Link */
+ struct e1inp_sign_link *oml_link;
+
+ struct gsm_abis_mo mo;
+ struct tlv_parsed nm_attr;
+ struct {
+ struct gsm_abis_mo mo;
+ } bb_transc;
+
+ uint16_t arfcn;
+ int nominal_power; /* in dBm */
+ unsigned int max_power_red; /* in actual dB */
+ uint8_t max_power_backoff_8psk; /* in actual dB OC-2G only */
+ uint8_t c0_idle_power_red; /* in actual dB OC-2G only */
+
+
+ struct trx_power_params power_params;
+ int ms_power_control;
+
+ struct {
+ void *l1h;
+ } role_bts;
+
+ union {
+ struct {
+ unsigned int test_state;
+ uint8_t test_nr;
+ struct rxlev_stats rxlev_stat;
+ } ipaccess;
+ };
+ struct gsm_bts_trx_ts ts[TRX_NR_TS];
+};
+
+#define GSM_BTS_SI2Q(bts, i) (struct gsm48_system_information_type_2quater *)((bts)->si_buf[SYSINFO_TYPE_2quater][i])
+#define GSM_BTS_HAS_SI(bts, i) ((bts)->si_valid & (1 << i))
+#define GSM_BTS_SI(bts, i) (void *)((bts)->si_buf[i][0])
+#define GSM_LCHAN_SI(lchan, i) (void *)((lchan)->si.buf[i][0])
+
+enum gsm_bts_type_variant {
+ BTS_UNKNOWN,
+ BTS_OSMO_LITECELL15,
+ BTS_OSMO_OC2G,
+ BTS_OSMO_OCTPHY,
+ BTS_OSMO_SYSMO,
+ BTS_OSMO_TRX,
+ BTS_OSMO_VIRTUAL,
+ BTS_OSMO_OMLDUMMY,
+ _NUM_BTS_VARIANT
+};
+
+/* Used by OML layer for BTS Attribute reporting */
+enum bts_attribute {
+ BTS_TYPE_VARIANT,
+ BTS_SUB_MODEL,
+ TRX_PHY_VERSION,
+};
+
+struct vty;
+
+/* N. B: always add new features to the end of the list (right before _NUM_BTS_FEAT) to avoid breaking compatibility
+ with BTS compiled against earlier version of this header. Also make sure that the description strings
+ gsm_bts_features_descs[] in gsm_data_shared.c are also updated accordingly! */
+enum gsm_bts_features {
+ BTS_FEAT_HSCSD,
+ BTS_FEAT_GPRS,
+ BTS_FEAT_EGPRS,
+ BTS_FEAT_ECSD,
+ BTS_FEAT_HOPPING,
+ BTS_FEAT_MULTI_TSC,
+ BTS_FEAT_OML_ALERTS,
+ BTS_FEAT_AGCH_PCH_PROP,
+ BTS_FEAT_CBCH,
+ BTS_FEAT_SPEECH_F_V1,
+ BTS_FEAT_SPEECH_H_V1,
+ BTS_FEAT_SPEECH_F_EFR,
+ BTS_FEAT_SPEECH_F_AMR,
+ BTS_FEAT_SPEECH_H_AMR,
+ _NUM_BTS_FEAT
+};
+
+extern const struct value_string gsm_bts_features_descs[];
+
+struct gsm_bts_gprs_nsvc {
+ struct gsm_bts *bts;
+ /* data read via VTY config file, to configure the BTS
+ * via OML from BSC */
+ int id;
+ uint16_t nsvci;
+ uint16_t local_port; /* on the BTS */
+ uint16_t remote_port; /* on the SGSN */
+ uint32_t remote_ip; /* on the SGSN */
+
+ struct gsm_abis_mo mo;
+};
+
+enum gprs_rlc_par {
+ RLC_T3142,
+ RLC_T3169,
+ RLC_T3191,
+ RLC_T3193,
+ RLC_T3195,
+ RLC_N3101,
+ RLC_N3103,
+ RLC_N3105,
+ CV_COUNTDOWN,
+ T_DL_TBF_EXT, /* ms */
+ T_UL_TBF_EXT, /* ms */
+ _NUM_RLC_PAR
+};
+
+enum gprs_cs {
+ GPRS_CS1,
+ GPRS_CS2,
+ GPRS_CS3,
+ GPRS_CS4,
+ GPRS_MCS1,
+ GPRS_MCS2,
+ GPRS_MCS3,
+ GPRS_MCS4,
+ GPRS_MCS5,
+ GPRS_MCS6,
+ GPRS_MCS7,
+ GPRS_MCS8,
+ GPRS_MCS9,
+ _NUM_GRPS_CS
+};
+
+struct gprs_rlc_cfg {
+ uint16_t parameter[_NUM_RLC_PAR];
+ struct {
+ uint16_t repeat_time; /* ms */
+ uint8_t repeat_count;
+ } paging;
+ uint32_t cs_mask; /* bitmask of gprs_cs */
+ uint8_t initial_cs;
+ uint8_t initial_mcs;
+};
+
+/* The amount of time within which a sudden disconnect of a newly established
+ * OML connection will cause a special warning to be logged. */
+#define OSMO_BTS_OML_CONN_EARLY_DISCONNECT 10 /* in seconds */
+
+/* One BTS */
+struct gsm_bts {
+ /* list header in net->bts_list */
+ struct llist_head list;
+
+ /* Geographical location of the BTS */
+ struct llist_head loc_list;
+
+ /* number of ths BTS in network */
+ uint8_t nr;
+ /* human readable name / description */
+ char *description;
+ /* Cell Identity */
+ uint16_t cell_identity;
+ /* location area code of this BTS */
+ uint16_t location_area_code;
+ /* Base Station Identification Code (BSIC), lower 3 bits is BCC,
+ * which is used as TSC for the CCCH */
+ uint8_t bsic;
+ /* type of BTS */
+ enum gsm_bts_type_variant variant;
+ enum gsm_band band;
+ char version[MAX_VERSION_LENGTH];
+ char sub_model[MAX_VERSION_LENGTH];
+
+ /* features of a given BTS set/reported via OML */
+ struct bitvec features;
+ uint8_t _features_data[MAX_BTS_FEATURES/8];
+
+ /* Connected PCU version (if any) */
+ char pcu_version[MAX_VERSION_LENGTH];
+
+ /* maximum Tx power that the MS is permitted to use in this cell */
+ int ms_max_power;
+
+ /* how do we talk OML with this TRX? */
+ uint8_t oml_tei;
+ struct e1inp_sign_link *oml_link;
+ struct timespec oml_conn_established_timestamp;
+
+ /* Abis network management O&M handle */
+ struct abis_nm_h *nmh;
+
+ struct gsm_abis_mo mo;
+
+ /* number of this BTS on given E1 link */
+ uint8_t bts_nr;
+
+ /* DTX features of this BTS */
+ enum gsm48_dtx_mode dtxu;
+ bool dtxd;
+
+ /* CCCH is on C0 */
+ struct gsm_bts_trx *c0;
+
+ struct {
+ struct gsm_abis_mo mo;
+ } site_mgr;
+
+ /* bitmask of all SI that are present/valid in si_buf */
+ uint32_t si_valid;
+ /* 3GPP TS 44.018 Table 10.5.2.33b.1 INDEX and COUNT for SI2quater */
+ uint8_t si2q_index; /* distinguish individual SI2quater messages */
+ uint8_t si2q_count; /* si2q_index for the last (highest indexed) individual SI2quater message */
+ /* buffers where we put the pre-computed SI */
+ sysinfo_buf_t si_buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM];
+ /* offsets used while generating SI2quater */
+ size_t e_offset;
+ size_t u_offset;
+
+ /* ip.accesss Unit ID's have Site/BTS/TRX layout */
+ union {
+ struct {
+ uint16_t site_id;
+ uint16_t bts_id;
+ uint32_t flags;
+ uint32_t rsl_ip;
+ } ip_access;
+ };
+
+ /* Not entirely sure how ip.access specific this is */
+ struct {
+ uint8_t supports_egprs_11bit_rach;
+ enum bts_gprs_mode mode;
+ struct {
+ struct gsm_abis_mo mo;
+ uint16_t nsei;
+ uint8_t timer[7];
+ } nse;
+ struct {
+ struct gsm_abis_mo mo;
+ uint16_t bvci;
+ uint8_t timer[11];
+ struct gprs_rlc_cfg rlc_cfg;
+ } cell;
+ struct gsm_bts_gprs_nsvc nsvc[2];
+ uint8_t rac;
+ uint8_t net_ctrl_ord;
+ bool ctrl_ack_type_use_block;
+ } gprs;
+
+ /* RACH NM values */
+ int rach_b_thresh;
+ int rach_ldavg_slots;
+
+ /* transceivers */
+ int num_trx;
+ struct llist_head trx_list;
+
+ /* SI related items */
+ int force_combined_si;
+ int bcch_change_mark;
+
+ struct rate_ctr_group *ctrs;
+ bool supp_meas_toa256;
+
+ struct {
+ /* Interference Boundaries for OML */
+ int16_t boundary[6];
+ uint8_t intave;
+ } interference;
+ unsigned int t200_ms[7];
+ unsigned int t3105_ms;
+ struct {
+ uint8_t overload_period;
+ struct {
+ /* Input parameters from OML */
+ uint8_t load_ind_thresh; /* percent */
+ uint8_t load_ind_period; /* seconds */
+ /* Internal data */
+ struct osmo_timer_list timer;
+ unsigned int pch_total;
+ unsigned int pch_used;
+ } ccch;
+ struct {
+ /* Input parameters from OML */
+ int16_t busy_thresh; /* in dBm */
+ uint16_t averaging_slots;
+ /* Internal data */
+ unsigned int total; /* total nr */
+ unsigned int busy; /* above busy_thresh */
+ unsigned int access; /* access bursts */
+ } rach;
+ } load;
+ uint8_t ny1;
+ uint8_t max_ta;
+
+ /* AGCH queuing */
+ struct {
+ struct llist_head queue;
+ int length;
+ int max_length;
+
+ int thresh_level; /* Cleanup threshold in percent of max len */
+ int low_level; /* Low water mark in percent of max len */
+ int high_level; /* High water mark in percent of max len */
+
+ /* TODO: Use a rate counter group instead */
+ uint64_t dropped_msgs;
+ uint64_t merged_msgs;
+ uint64_t rejected_msgs;
+ uint64_t agch_msgs;
+ uint64_t pch_msgs;
+ } agch_queue;
+
+ struct paging_state *paging_state;
+ char *bsc_oml_host;
+ struct llist_head oml_queue;
+ unsigned int rtp_jitter_buf_ms;
+ bool rtp_jitter_adaptive;
+
+ uint16_t rtp_port_range_start;
+ uint16_t rtp_port_range_end;
+ uint16_t rtp_port_range_next;
+
+ struct {
+ uint8_t ciphers; /* flags A5/1==0x1, A5/2==0x2, A5/3==0x4 */
+ } support;
+ struct {
+ uint8_t tc4_ctr;
+ } si;
+ struct gsm_time gsm_time;
+ /* Radio Link Timeout counter. -1 disables timeout for
+ * lab/measurement purpose */
+ int radio_link_timeout;
+
+ int ul_power_target; /* Uplink Rx power target */
+
+ /* used by the sysmoBTS to adjust band */
+ uint8_t auto_band;
+
+ struct {
+ struct llist_head queue; /* list of struct smscb_msg */
+ struct smscb_msg *cur_msg; /* current SMS-CB */
+ } smscb_state;
+
+ float min_qual_rach; /* minimum quality for RACH bursts */
+ float min_qual_norm; /* minimum quality for normal daata */
+ uint16_t max_ber10k_rach; /* Maximum permitted RACH BER in 0.01% */
+
+ struct {
+ char *sock_path;
+ } pcu;
+
+ struct {
+ uint32_t last_fn;
+ struct timeval tv_clock;
+ struct osmo_timer_list fn_timer;
+ } vbts;
+#ifdef ENABLE_OC2GBTS
+ /* specific to Open Cellular 2G BTS */
+ struct {
+ uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */
+ struct llist_head ceased_alarm_list; /* ceased alarm list*/
+ unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */
+ } oc2g;
+#endif
+};
+
+
+struct gsm_bts *gsm_bts_alloc(void *talloc_ctx, uint8_t bts_num);
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num);
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts);
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num);
+
+enum bts_attribute str2btsattr(const char *s);
+const char *btsatttr2str(enum bts_attribute v);
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg);
+const char *btsvariant2str(enum gsm_bts_type_variant v);
+
+extern const struct value_string gsm_chreq_descs[];
+const struct value_string gsm_pchant_names[13];
+const struct value_string gsm_pchant_descs[13];
+const char *gsm_pchan_name(enum gsm_phys_chan_config c);
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name);
+const char *gsm_lchant_name(enum gsm_chan_t c);
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c);
+char *gsm_trx_name(const struct gsm_bts_trx *trx);
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts);
+char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts);
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan);
+const char *gsm_lchans_name(enum gsm_lchan_state s);
+
+static inline char *gsm_lchan_name(const struct gsm_lchan *lchan)
+{
+ return lchan->name;
+}
+
+static inline int gsm_bts_set_feature(struct gsm_bts *bts, enum gsm_bts_features feat)
+{
+ OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+ return bitvec_set_bit_pos(&bts->features, feat, 1);
+}
+
+static inline bool gsm_bts_has_feature(const struct gsm_bts *bts, enum gsm_bts_features feat)
+{
+ OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+ return bitvec_get_bit_pos(&bts->features, feat);
+}
+
+void gsm_abis_mo_reset(struct gsm_abis_mo *mo);
+
+struct gsm_abis_mo *
+gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst);
+
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst);
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst);
+
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr);
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan);
+uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan,
+ enum gsm_phys_chan_config as_pchan);
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts);
+
+/*
+ * help with parsing regexps
+ */
+int gsm_parse_reg(void *ctx, regex_t *reg, char **str,
+ int argc, const char **argv) __attribute__ ((warn_unused_result));
+
+#define BSIC2BCC(bsic) ((bsic) & 0x3)
+
+static inline uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts)
+{
+ if (ts->tsc != -1)
+ return ts->tsc;
+ else
+ return ts->trx->bts->bsic & 7;
+}
+
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ int *rc);
+
+enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts);
+uint8_t ts_subslots(struct gsm_bts_trx_ts *ts);
+bool ts_is_tch(struct gsm_bts_trx_ts *ts);
+const char *gsm_trx_unit_id(struct gsm_bts_trx *trx);
+
+#endif
diff --git a/include/osmo-bts/handover.h b/include/osmo-bts/handover.h
new file mode 100644
index 00000000..35d5c690
--- /dev/null
+++ b/include/osmo-bts/handover.h
@@ -0,0 +1,12 @@
+#pragma once
+
+enum {
+ HANDOVER_NONE = 0,
+ HANDOVER_ENABLED,
+ HANDOVER_WAIT_FRAME,
+};
+
+void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay);
+void handover_frame(struct gsm_lchan *lchan);
+void handover_reset(struct gsm_lchan *lchan);
+
diff --git a/include/osmo-bts/l1sap.h b/include/osmo-bts/l1sap.h
new file mode 100644
index 00000000..3cf0ea58
--- /dev/null
+++ b/include/osmo-bts/l1sap.h
@@ -0,0 +1,103 @@
+#ifndef L1SAP_H
+#define L1SAP_H
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+/* lchan link ID */
+#define LID_SACCH 0x40
+#define LID_DEDIC 0x00
+
+/* timeslot and subslot from chan_nr */
+#define L1SAP_CHAN2TS(chan_nr) (chan_nr & 7)
+#define L1SAP_CHAN2SS_TCHH(chan_nr) ((chan_nr >> 3) & 1)
+#define L1SAP_CHAN2SS_SDCCH4(chan_nr) ((chan_nr >> 3) & 3)
+#define L1SAP_CHAN2SS_SDCCH8(chan_nr) ((chan_nr >> 3) & 7)
+#define L1SAP_CHAN2SS_BCCH(chan_nr) (CCCH_LCHAN)
+
+/* logical channel from chan_nr + link_id */
+#define L1SAP_IS_LINK_SACCH(link_id) ((link_id & 0xC0) == LID_SACCH)
+#define L1SAP_IS_CHAN_TCHF(chan_nr) ((chan_nr & 0xf8) == 0x08)
+#define L1SAP_IS_CHAN_TCHH(chan_nr) ((chan_nr & 0xf0) == 0x10)
+#define L1SAP_IS_CHAN_SDCCH4(chan_nr) ((chan_nr & 0xe0) == 0x20)
+#define L1SAP_IS_CHAN_SDCCH8(chan_nr) ((chan_nr & 0xc0) == 0x40)
+#define L1SAP_IS_CHAN_BCCH(chan_nr) ((chan_nr & 0xf8) == 0x80)
+#define L1SAP_IS_CHAN_RACH(chan_nr) ((chan_nr & 0xf8) == 0x88)
+#define L1SAP_IS_CHAN_AGCH_PCH(chan_nr) ((chan_nr & 0xf8) == 0x90)
+#define L1SAP_IS_CHAN_PDCH(chan_nr) ((chan_nr & 0xf8) == 0xc0)
+#define L1SAP_IS_CHAN_CBCH(chan_nr) ((chan_nr & 0xf8) == 0xc8)
+
+/* rach type from ra */
+#define L1SAP_IS_PACKET_RACH(ra) ((ra & 0xf0) == 0x70 && (ra & 0x0f) != 0x0f)
+
+/* CCCH block from frame number */
+unsigned int l1sap_fn2ccch_block(uint32_t fn);
+
+/* PTCH layout from frame number */
+#define L1SAP_FN2MACBLOCK(fn) ((fn % 52) / 4)
+#define L1SAP_FN2PTCCHBLOCK(fn) ((fn / 104) & 3)
+
+/* Calculate PTCCH occurrence, See also 3GPP TS 05.02, Clause 7, Table 6 of 9 */
+#define L1SAP_IS_PTCCH(fn) (((fn % 52) == 12) || ((fn % 52) == 38))
+
+
+static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = {
+ 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
+ 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
+ 0x2B, 0x2B, 0x2B
+};
+
+/* subslot from any chan_nr */
+static inline uint8_t l1sap_chan2ss(uint8_t chan_nr)
+{
+ if (L1SAP_IS_CHAN_BCCH(chan_nr))
+ return L1SAP_CHAN2SS_BCCH(chan_nr);
+ if (L1SAP_IS_CHAN_SDCCH8(chan_nr))
+ return L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ if (L1SAP_IS_CHAN_SDCCH4(chan_nr))
+ return L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ if (L1SAP_IS_CHAN_TCHH(chan_nr))
+ return L1SAP_CHAN2SS_TCHH(chan_nr);
+ return 0;
+}
+
+struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx,
+ unsigned int chan_nr);
+
+/* allocate a msgb containing a osmo_phsap_prim + optional l2 data */
+struct msgb *l1sap_msgb_alloc(unsigned int l2_len);
+
+/* any L1 prim received from bts model */
+int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
+/* pcu (socket interface) sends us a data request primitive */
+int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len);
+
+/* call-back function for incoming RTP */
+void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
+ unsigned int rtp_pl_len, uint16_t seq_number,
+ uint32_t timestamp, bool marker);
+
+/* channel control */
+int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp);
+int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr);
+int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr);
+int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr);
+
+extern const struct value_string gsmtap_sapi_names[];
+extern struct gsmtap_inst *gsmtap;
+extern uint32_t gsmtap_sapi_mask;
+extern uint8_t gsmtap_sapi_acch;
+
+int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg,
+ struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn,
+ uint16_t ber10k, int16_t lqual_cb);
+
+#define msgb_l1sap_prim(msg) ((struct osmo_phsap_prim *)(msg)->l1h)
+
+int bts_check_for_first_ciphrd(struct gsm_lchan *lchan,
+ uint8_t *data, int len);
+
+int is_ccch_for_agch(struct gsm_bts_trx *trx, uint32_t fn);
+
+#endif /* L1SAP_H */
diff --git a/include/osmo-bts/logging.h b/include/osmo-bts/logging.h
new file mode 100644
index 00000000..852c3836
--- /dev/null
+++ b/include/osmo-bts/logging.h
@@ -0,0 +1,40 @@
+#ifndef _LOGGING_H
+#define _LOGGING_H
+
+#define DEBUG
+#include <osmocom/core/logging.h>
+
+enum {
+ DRSL,
+ DOML,
+ DRLL,
+ DRR,
+ DMEAS,
+ DPAG,
+ DL1C,
+ DL1P,
+ DDSP,
+ DPCU,
+ DHO,
+ DTRX,
+ DLOOP,
+ DABIS,
+ DRTP,
+ DSUM,
+};
+
+extern const struct log_info bts_log_info;
+
+/* LOGP with gsm_time prefix */
+#define LOGPGT(ss, lvl, gt, fmt, args...) \
+ LOGP(ss, lvl, "%s " fmt, osmo_dump_gsmtime(gt), ## args)
+#define DEBUGPGT(ss, gt, fmt, args...) \
+ LOGP(ss, LOGL_DEBUG, "%s " fmt, osmo_dump_gsmtime(gt), ## args)
+
+/* LOGP with frame number prefix */
+#define LOGPFN(ss, lvl, fn, fmt, args...) \
+ LOGP(ss, lvl, "%s " fmt, gsm_fn_as_gsmtime_str(fn), ## args)
+#define DEBUGPFN(ss, fn, fmt, args...) \
+ LOGP(ss, LOGL_DEBUG, "%s " fmt, gsm_fn_as_gsmtime_str(fn), ## args)
+
+#endif /* _LOGGING_H */
diff --git a/include/osmo-bts/measurement.h b/include/osmo-bts/measurement.h
new file mode 100644
index 00000000..4f04ffa2
--- /dev/null
+++ b/include/osmo-bts/measurement.h
@@ -0,0 +1,19 @@
+#ifndef OSMO_BTS_MEAS_H
+#define OSMO_BTS_MEAS_H
+
+#define MEAS_MAX_TIMING_ADVANCE 63
+#define MEAS_MIN_TIMING_ADVANCE 0
+
+int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn);
+
+int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn);
+
+int lchan_meas_process_measurement(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn);
+
+void lchan_meas_reset(struct gsm_lchan *lchan);
+
+bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn, bool is_amr_sid_update);
+
+int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn);
+
+#endif
diff --git a/include/osmo-bts/msg_utils.h b/include/osmo-bts/msg_utils.h
new file mode 100644
index 00000000..7ddbe88f
--- /dev/null
+++ b/include/osmo-bts/msg_utils.h
@@ -0,0 +1,48 @@
+/*
+ * Routines to check the structurally integrity of messages
+ */
+
+#pragma once
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <osmocom/codec/codec.h>
+
+#include <stdbool.h>
+
+struct msgb;
+
+/* Access 1st part of msgb control buffer */
+#define rtpmsg_marker_bit(x) ((x)->cb[0])
+
+/* Access 2nd part of msgb control buffer */
+#define rtpmsg_seq(x) ((x)->cb[1])
+
+/* Access 3rd part of msgb control buffer */
+#define rtpmsg_ts(x) ((x)->cb[2])
+
+/**
+ * Classification of OML message. ETSI for plain GSM 12.21
+ * messages and IPA/Osmo for manufacturer messages.
+ */
+enum {
+ OML_MSG_TYPE_ETSI,
+ OML_MSG_TYPE_IPA,
+ OML_MSG_TYPE_OSMO,
+};
+
+void lchan_set_marker(bool t, struct gsm_lchan *lchan);
+bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan);
+void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e);
+bool dtx_recursion(const struct gsm_lchan *lchan);
+void dtx_int_signal(struct gsm_lchan *lchan);
+bool dtx_is_first_p1(const struct gsm_lchan *lchan);
+void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload,
+ size_t length, uint32_t fn, int update);
+int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
+ size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload,
+ bool marker, uint8_t *len, uint8_t *ft_out);
+uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn);
+int msg_verify_ipa_structure(struct msgb *msg);
+int msg_verify_oml_structure(struct msgb *msg);
diff --git a/include/osmo-bts/oml.h b/include/osmo-bts/oml.h
new file mode 100644
index 00000000..139464ec
--- /dev/null
+++ b/include/osmo-bts/oml.h
@@ -0,0 +1,50 @@
+#ifndef _OML_H
+#define _OML_H
+
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+struct gsm_bts;
+struct gsm_abis_mo;
+struct msgb;
+struct gsm_lchan;
+
+
+int oml_init(struct gsm_abis_mo *mo);
+int down_oml(struct gsm_bts *bts, struct msgb *msg);
+
+struct msgb *oml_msgb_alloc(void);
+int oml_send_msg(struct msgb *msg, int is_mauf);
+int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type);
+int oml_mo_opstart_ack(struct gsm_abis_mo *mo);
+int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause);
+int oml_mo_statechg_ack(struct gsm_abis_mo *mo);
+int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause);
+
+/* Change the state and send STATE CHG REP */
+int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state);
+
+/* First initialization of MO, does _not_ generate state changes */
+void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state);
+
+/* Update admin state and send ACK/NACK */
+int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
+ int success);
+
+/* Transmit STATE CHG REP even if there was no state change */
+int oml_tx_state_changed(struct gsm_abis_mo *mo);
+
+int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo);
+
+int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause);
+
+int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type,
+ uint8_t cause);
+
+/* Configure LAPDm T200 timers for this lchan according to OML */
+int oml_set_lchan_t200(struct gsm_lchan *lchan);
+extern const unsigned int oml_default_t200_ms[7];
+
+/* Transmit failure event report */
+void oml_fail_rep(uint16_t cause_value, const char *fmt, ...);
+
+#endif // _OML_H */
diff --git a/include/osmo-bts/paging.h b/include/osmo-bts/paging.h
new file mode 100644
index 00000000..7fc0bf05
--- /dev/null
+++ b/include/osmo-bts/paging.h
@@ -0,0 +1,52 @@
+#ifndef OSMO_BTS_PAGING_H
+#define OSMO_BTS_PAGING_H
+
+#include <stdint.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+struct paging_state;
+struct gsm_bts;
+
+/* initialize paging code */
+struct paging_state *paging_init(struct gsm_bts *bts,
+ unsigned int num_paging_max,
+ unsigned int paging_lifetime);
+
+/* (re) configure paging code */
+void paging_config(struct paging_state *ps,
+ unsigned int num_paging_max,
+ unsigned int paging_lifetime);
+
+void paging_reset(struct paging_state *ps);
+
+/* The max number of paging entries */
+unsigned int paging_get_queue_max(struct paging_state *ps);
+void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max);
+
+/* The lifetime of a paging entry */
+unsigned int paging_get_lifetime(struct paging_state *ps);
+void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime);
+
+/* update with new SYSTEM INFORMATION parameters */
+int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc);
+
+/* Add an identity to the paging queue */
+int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
+ const uint8_t *identity_lv, uint8_t chan_needed);
+
+/* Add an IMM.ASS message to the paging queue */
+int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data,
+ uint8_t len);
+
+/* generate paging message for given gsm time */
+int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt,
+ int *is_empty);
+
+
+/* inspection methods below */
+int paging_group_queue_empty(struct paging_state *ps, uint8_t group);
+int paging_queue_length(struct paging_state *ps);
+int paging_buffer_space(struct paging_state *ps);
+
+#endif
diff --git a/include/osmo-bts/pcu_if.h b/include/osmo-bts/pcu_if.h
new file mode 100644
index 00000000..98efb570
--- /dev/null
+++ b/include/osmo-bts/pcu_if.h
@@ -0,0 +1,24 @@
+#ifndef _PCU_IF_H
+#define _PCU_IF_H
+
+extern int pcu_direct;
+
+int pcu_tx_info_ind(void);
+int pcu_tx_si13(const struct gsm_bts *bts, bool enable);
+int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr);
+int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len,
+ int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual);
+int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
+ uint8_t is_11bit, enum ph_burst_type burst_type);
+int pcu_tx_time_ind(uint32_t fn);
+int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed);
+int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len);
+
+int pcu_sock_init(const char *path);
+void pcu_sock_exit(void);
+
+bool pcu_connected(void);
+
+#endif /* _PCU_IF_H */
diff --git a/include/osmo-bts/pcuif_proto.h b/include/osmo-bts/pcuif_proto.h
new file mode 100644
index 00000000..b06077c3
--- /dev/null
+++ b/include/osmo-bts/pcuif_proto.h
@@ -0,0 +1,195 @@
+#ifndef _PCUIF_PROTO_H
+#define _PCUIF_PROTO_H
+
+#include <osmocom/gsm/l1sap.h>
+
+#define PCU_SOCK_DEFAULT "/tmp/pcu_bts"
+
+#define PCU_IF_VERSION 0x09
+#define TXT_MAX_LEN 128
+
+/* msg_type */
+#define PCU_IF_MSG_DATA_REQ 0x00 /* send data to given channel */
+#define PCU_IF_MSG_DATA_CNF 0x01 /* confirm (e.g. transmission on PCH) */
+#define PCU_IF_MSG_DATA_IND 0x02 /* receive data from given channel */
+#define PCU_IF_MSG_RTS_REQ 0x10 /* ready to send request */
+#define PCU_IF_MSG_DATA_CNF_DT 0x11 /* confirm (with direct tlli) */
+#define PCU_IF_MSG_RACH_IND 0x22 /* receive RACH */
+#define PCU_IF_MSG_INFO_IND 0x32 /* retrieve BTS info */
+#define PCU_IF_MSG_ACT_REQ 0x40 /* activate/deactivate PDCH */
+#define PCU_IF_MSG_TIME_IND 0x52 /* GSM time indication */
+#define PCU_IF_MSG_PAG_REQ 0x60 /* paging request */
+#define PCU_IF_MSG_TXT_IND 0x70 /* Text indication for BTS */
+
+/* sapi */
+#define PCU_IF_SAPI_RACH 0x01 /* channel request on CCCH */
+#define PCU_IF_SAPI_AGCH 0x02 /* assignment on AGCH */
+#define PCU_IF_SAPI_PCH 0x03 /* paging/assignment on PCH */
+#define PCU_IF_SAPI_BCCH 0x04 /* SI on BCCH */
+#define PCU_IF_SAPI_PDTCH 0x05 /* packet data/control/ccch block */
+#define PCU_IF_SAPI_PRACH 0x06 /* packet random access channel */
+#define PCU_IF_SAPI_PTCCH 0x07 /* packet TA control channel */
+#define PCU_IF_SAPI_AGCH_DT 0x08 /* assignment on AGCH but with additional TLLI */
+
+/* flags */
+#define PCU_IF_FLAG_ACTIVE (1 << 0)/* BTS is active */
+#define PCU_IF_FLAG_SYSMO (1 << 1)/* access PDCH of sysmoBTS directly */
+#define PCU_IF_FLAG_CS1 (1 << 16)
+#define PCU_IF_FLAG_CS2 (1 << 17)
+#define PCU_IF_FLAG_CS3 (1 << 18)
+#define PCU_IF_FLAG_CS4 (1 << 19)
+#define PCU_IF_FLAG_MCS1 (1 << 20)
+#define PCU_IF_FLAG_MCS2 (1 << 21)
+#define PCU_IF_FLAG_MCS3 (1 << 22)
+#define PCU_IF_FLAG_MCS4 (1 << 23)
+#define PCU_IF_FLAG_MCS5 (1 << 24)
+#define PCU_IF_FLAG_MCS6 (1 << 25)
+#define PCU_IF_FLAG_MCS7 (1 << 26)
+#define PCU_IF_FLAG_MCS8 (1 << 27)
+#define PCU_IF_FLAG_MCS9 (1 << 28)
+
+enum gsm_pcu_if_text_type {
+ PCU_VERSION,
+ PCU_OML_ALERT,
+};
+
+struct gsm_pcu_if_txt_ind {
+ uint8_t type; /* gsm_pcu_if_text_type */
+ char text[TXT_MAX_LEN]; /* Text to be transmitted to BTS */
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_data {
+ uint8_t sapi;
+ uint8_t len;
+ uint8_t data[162];
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t block_nr;
+ int8_t rssi;
+ uint16_t ber10k; /* !< \brief BER in units of 0.01% */
+ int16_t ta_offs_qbits; /* !< \brief Burst TA Offset in quarter bits */
+ int16_t lqual_cb; /* !< \brief Link quality in centiBel */
+} __attribute__ ((packed));
+
+/* data confirmation with direct tlli (instead of raw mac block with tlli) */
+struct gsm_pcu_if_data_cnf_dt {
+ uint8_t sapi;
+ uint32_t tlli;
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t block_nr;
+ int8_t rssi;
+ uint16_t ber10k; /* !< \brief BER in units of 0.01% */
+ int16_t ta_offs_qbits; /* !< \brief Burst TA Offset in quarter bits */
+ int16_t lqual_cb; /* !< \brief Link quality in centiBel */
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_rts_req {
+ uint8_t sapi;
+ uint8_t spare[3];
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t block_nr;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_rach_ind {
+ uint8_t sapi;
+ uint16_t ra;
+ int16_t qta;
+ uint32_t fn;
+ uint16_t arfcn;
+ uint8_t is_11bit;
+ uint8_t burst_type;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_info_trx {
+ uint16_t arfcn;
+ uint8_t pdch_mask; /* PDCH channels per TS */
+ uint8_t spare;
+ uint8_t tsc[8]; /* TSC per channel */
+ uint32_t hlayer1;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_info_ind {
+ uint32_t version;
+ uint32_t flags;
+ struct gsm_pcu_if_info_trx trx[8]; /* TRX infos per BTS */
+ uint8_t bsic;
+ /* RAI */
+ uint16_t mcc, mnc;
+ uint8_t mnc_3_digits;
+ uint16_t lac, rac;
+ /* NSE */
+ uint16_t nsei;
+ uint8_t nse_timer[7];
+ uint8_t cell_timer[11];
+ /* cell */
+ uint16_t cell_id;
+ uint16_t repeat_time;
+ uint8_t repeat_count;
+ uint16_t bvci;
+ uint8_t t3142;
+ uint8_t t3169;
+ uint8_t t3191;
+ uint8_t t3193_10ms;
+ uint8_t t3195;
+ uint8_t n3101;
+ uint8_t n3103;
+ uint8_t n3105;
+ uint8_t cv_countdown;
+ uint16_t dl_tbf_ext;
+ uint16_t ul_tbf_ext;
+ uint8_t initial_cs;
+ uint8_t initial_mcs;
+ /* NSVC */
+ uint16_t nsvci[2];
+ uint16_t local_port[2];
+ uint16_t remote_port[2];
+ uint32_t remote_ip[2];
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_act_req {
+ uint8_t activate;
+ uint8_t trx_nr;
+ uint8_t ts_nr;
+ uint8_t spare;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_time_ind {
+ uint32_t fn;
+} __attribute__ ((packed));
+
+struct gsm_pcu_if_pag_req {
+ uint8_t sapi;
+ uint8_t chan_needed;
+ uint8_t identity_lv[9];
+} __attribute__ ((packed));
+
+struct gsm_pcu_if {
+ /* context based information */
+ uint8_t msg_type; /* message type */
+ uint8_t bts_nr; /* bts number */
+ uint8_t spare[2];
+
+ union {
+ struct gsm_pcu_if_data data_req;
+ struct gsm_pcu_if_data data_cnf;
+ struct gsm_pcu_if_data_cnf_dt data_cnf_dt;
+ struct gsm_pcu_if_data data_ind;
+ struct gsm_pcu_if_rts_req rts_req;
+ struct gsm_pcu_if_rach_ind rach_ind;
+ struct gsm_pcu_if_txt_ind txt_ind;
+ struct gsm_pcu_if_info_ind info_ind;
+ struct gsm_pcu_if_act_req act_req;
+ struct gsm_pcu_if_time_ind time_ind;
+ struct gsm_pcu_if_pag_req pag_req;
+ } u;
+} __attribute__ ((packed));
+
+#endif /* _PCUIF_PROTO_H */
diff --git a/include/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h
new file mode 100644
index 00000000..2472c051
--- /dev/null
+++ b/include/osmo-bts/phy_link.h
@@ -0,0 +1,175 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <osmo-bts/scheduler.h>
+
+#include <linux/if_packet.h>
+#include "btsconfig.h"
+
+struct gsm_bts_trx;
+struct virt_um_inst;
+
+enum phy_link_type {
+ PHY_LINK_T_NONE,
+ PHY_LINK_T_SYSMOBTS,
+ PHY_LINK_T_OSMOTRX,
+ PHY_LINK_T_VIRTUAL,
+};
+
+enum phy_link_state {
+ PHY_LINK_SHUTDOWN,
+ PHY_LINK_CONNECTING,
+ PHY_LINK_CONNECTED,
+};
+
+/* A PHY link represents the connection to a given PHYsical layer
+ * implementation. That PHY link contains 1...N PHY instances, one for
+ * each TRX */
+struct phy_link {
+ struct llist_head list;
+ int num;
+ enum phy_link_type type;
+ enum phy_link_state state;
+ struct llist_head instances;
+ char *description;
+ union {
+ struct {
+ } sysmobts;
+ struct {
+ char *local_ip;
+ char *remote_ip;
+ uint16_t base_port_local;
+ uint16_t base_port_remote;
+ struct osmo_fd trx_ofd_clk;
+ bool trx_ta_loop;
+ bool trx_ms_power_loop;
+ int8_t trx_target_rssi;
+ uint32_t clock_advance;
+ uint32_t rts_advance;
+ bool use_legacy_setbsic;
+ } osmotrx;
+ struct {
+ char *mcast_dev; /* Network device for multicast */
+ char *bts_mcast_group; /* BTS are listening to this group */
+ uint16_t bts_mcast_port;
+ char *ms_mcast_group; /* MS are listening to this group */
+ uint16_t ms_mcast_port;
+ struct virt_um_inst *virt_um;
+ } virt;
+ struct {
+ /* MAC address of the PHY */
+ struct sockaddr_ll phy_addr;
+ /* Network device name */
+ char *netdev_name;
+
+ /* configuration */
+ uint32_t rf_port_index;
+#if OCTPHY_USE_ANTENNA_ID == 1
+ uint32_t rx_ant_id;
+ uint32_t tx_ant_id;
+#endif
+ uint32_t rx_gain_db;
+ bool tx_atten_flag;
+ uint32_t tx_atten_db;
+ bool over_sample_16x;
+#if OCTPHY_MULTI_TRX == 1
+ /* arfcn used by TRX with id 0 */
+ uint16_t center_arfcn;
+#endif
+
+ struct octphy_hdl *hdl;
+ } octphy;
+ } u;
+};
+
+struct phy_instance {
+ /* liked inside phy_link.linstances */
+ struct llist_head list;
+ int num;
+ char *description;
+ char version[MAX_VERSION_LENGTH];
+ /* pointer to the PHY link to which we belong */
+ struct phy_link *phy_link;
+
+ /* back-pointer to the TRX to which we're associated */
+ struct gsm_bts_trx *trx;
+
+ union {
+ struct {
+ /* configuration */
+ uint8_t clk_use_eeprom;
+ uint32_t dsp_trace_f;
+ int clk_cal;
+ uint8_t clk_src;
+ char *calib_path;
+
+ struct femtol1_hdl *hdl;
+ } sysmobts;
+ struct {
+ struct trx_l1h *hdl;
+ bool sw_act_reported;
+ } osmotrx;
+ struct {
+ struct l1sched_trx sched;
+ } virt;
+ struct {
+ /* logical transceiver number within one PHY */
+ uint32_t trx_id;
+ /* trx lock state variable */
+ int trx_locked;
+ } octphy;
+ struct {
+ /* configuration */
+ uint32_t dsp_trace_f;
+ char *calib_path;
+ int minTxPower;
+ int maxTxPower;
+ struct lc15l1_hdl *hdl;
+ uint8_t max_cell_size; /* 0:166 qbits*/
+ uint8_t diversity_mode; /* 0: SISO A, 1: SISO B, 2: MRC */
+ uint8_t pedestal_mode; /* 0: unused TS is OFF, 1: unused TS is in minimum Tx power */
+ uint8_t dsp_alive_period; /* DSP alive timer period */
+ uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */
+ uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */
+ } lc15;
+ struct {
+ /* configuration */
+ uint32_t dsp_trace_f;
+ char *calib_path;
+ int minTxPower;
+ int maxTxPower;
+ struct oc2gl1_hdl *hdl;
+ uint8_t max_cell_size; /* 0:166 qbits*/
+ uint8_t pedestal_mode; /* 0: unused TS is OFF, 1: unused TS is in minimum Tx power */
+ uint8_t dsp_alive_period; /* DSP alive timer period */
+ uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */
+ uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */
+ uint8_t tx_c0_idle_pwr_red; /* C0 idle slot Tx power reduction level in dB */
+ } oc2g;
+ } u;
+};
+
+struct phy_link *phy_link_by_num(int num);
+struct phy_link *phy_link_create(void *ctx, int num);
+void phy_link_destroy(struct phy_link *plink);
+void phy_link_state_set(struct phy_link *plink, enum phy_link_state state);
+int phy_links_open(void);
+
+struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num);
+struct phy_instance *phy_instance_create(struct phy_link *plink, int num);
+void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx);
+void phy_instance_destroy(struct phy_instance *pinst);
+const char *phy_instance_name(struct phy_instance *pinst);
+
+void phy_user_statechg_notif(struct phy_instance *pinst, enum phy_link_state link_state);
+
+static inline struct phy_instance *trx_phy_instance(struct gsm_bts_trx *trx)
+{
+ OSMO_ASSERT(trx);
+ return trx->role_bts.l1h;
+}
+
+int bts_model_phy_link_open(struct phy_link *plink);
diff --git a/include/osmo-bts/power_control.h b/include/osmo-bts/power_control.h
new file mode 100644
index 00000000..43d4b591
--- /dev/null
+++ b/include/osmo-bts/power_control.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmo-bts/gsm_data.h>
+
+int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
+ const uint8_t ms_power, const int rxLevel);
diff --git a/include/osmo-bts/rsl.h b/include/osmo-bts/rsl.h
new file mode 100644
index 00000000..0361841d
--- /dev/null
+++ b/include/osmo-bts/rsl.h
@@ -0,0 +1,46 @@
+#ifndef _RSL_H
+#define _RSL_H
+
+/**
+ * What kind of release/activation is done? A silent one for
+ * the PDCH or one triggered through RSL?
+ */
+enum {
+ LCHAN_REL_ACT_RSL,
+ LCHAN_REL_ACT_PCU,
+ LCHAN_REL_ACT_OML,
+ LCHAN_REL_ACT_REACT, /* remove once auto-activation hack is removed from opstart_compl() */
+};
+
+#define LCHAN_FN_DUMMY 0xFFFFFFFF
+#define LCHAN_FN_WAIT 0xFFFFFFFE
+
+int msgb_queue_flush(struct llist_head *list);
+
+int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg);
+int rsl_tx_rf_res(struct gsm_bts_trx *trx);
+int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime,
+ uint8_t ra, uint8_t acc_delay);
+int rsl_tx_est_ind(struct gsm_lchan *lchan, uint8_t link_id, uint8_t *data, int len);
+
+int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause);
+int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause);
+int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan);
+int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay);
+
+int lchan_deactivate(struct gsm_lchan *lchan);
+
+/* call-back for LAPDm code, called when it wants to send msgs UP */
+int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx);
+
+int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause);
+int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail);
+int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total,
+ uint16_t busy, uint16_t access);
+int rsl_tx_delete_ind(struct gsm_bts *bts, const uint8_t *ia, uint8_t ia_len);
+
+void cb_ts_disconnected(struct gsm_bts_trx_ts *ts);
+void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc);
+void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc);
+
+#endif // _RSL_H */
diff --git a/include/osmo-bts/scheduler.h b/include/osmo-bts/scheduler.h
new file mode 100644
index 00000000..f9d99629
--- /dev/null
+++ b/include/osmo-bts/scheduler.h
@@ -0,0 +1,227 @@
+#ifndef TRX_SCHEDULER_H
+#define TRX_SCHEDULER_H
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+
+/* These types define the different channels on a multiframe.
+ * Each channel has queues and can be activated individually.
+ */
+enum trx_chan_type {
+ TRXC_IDLE = 0,
+ TRXC_FCCH,
+ TRXC_SCH,
+ TRXC_BCCH,
+ TRXC_RACH,
+ TRXC_CCCH,
+ TRXC_TCHF,
+ TRXC_TCHH_0,
+ TRXC_TCHH_1,
+ TRXC_SDCCH4_0,
+ TRXC_SDCCH4_1,
+ TRXC_SDCCH4_2,
+ TRXC_SDCCH4_3,
+ TRXC_SDCCH8_0,
+ TRXC_SDCCH8_1,
+ TRXC_SDCCH8_2,
+ TRXC_SDCCH8_3,
+ TRXC_SDCCH8_4,
+ TRXC_SDCCH8_5,
+ TRXC_SDCCH8_6,
+ TRXC_SDCCH8_7,
+ TRXC_SACCHTF,
+ TRXC_SACCHTH_0,
+ TRXC_SACCHTH_1,
+ TRXC_SACCH4_0,
+ TRXC_SACCH4_1,
+ TRXC_SACCH4_2,
+ TRXC_SACCH4_3,
+ TRXC_SACCH8_0,
+ TRXC_SACCH8_1,
+ TRXC_SACCH8_2,
+ TRXC_SACCH8_3,
+ TRXC_SACCH8_4,
+ TRXC_SACCH8_5,
+ TRXC_SACCH8_6,
+ TRXC_SACCH8_7,
+ TRXC_PDTCH,
+ TRXC_PTCCH,
+ TRXC_CBCH,
+ _TRX_CHAN_MAX
+};
+
+extern const struct value_string trx_chan_type_names[];
+
+#define GSM_BURST_LEN 148
+#define GPRS_BURST_LEN GSM_BURST_LEN
+#define EGPRS_BURST_LEN 444
+
+enum trx_burst_type {
+ TRX_BURST_GMSK,
+ TRX_BURST_8PSK,
+};
+
+/* States each channel on a multiframe */
+struct l1sched_chan_state {
+ /* scheduler */
+ uint8_t active; /* Channel is active */
+ ubit_t *dl_bursts; /* burst buffer for TX */
+ enum trx_burst_type dl_burst_type; /* GMSK or 8PSK burst type */
+ sbit_t *ul_bursts; /* burst buffer for RX */
+ uint32_t ul_first_fn; /* fn of first burst */
+ uint8_t ul_mask; /* mask of received bursts */
+
+ /* RSSI / TOA */
+ uint8_t rssi_num; /* number of RSSI values */
+ float rssi_sum; /* sum of RSSI values */
+ uint8_t toa_num; /* number of TOA values */
+ int32_t toa256_sum; /* sum of TOA values (1/256 symbol) */
+
+ /* loss detection */
+ uint8_t lost_frames; /* how many L2 frames were lost */
+ uint32_t last_tdma_fn; /* last processed TDMA frame number */
+ uint32_t proc_tdma_fs; /* how many TDMA frames were processed */
+ uint32_t lost_tdma_fs; /* how many TDMA frames were lost */
+
+ /* mode */
+ uint8_t rsl_cmode, tch_mode; /* mode for TCH channels */
+
+ /* AMR */
+ uint8_t codec[4]; /* 4 possible codecs for amr */
+ int codecs; /* number of possible codecs */
+ float ber_sum; /* sum of bit error rates */
+ int ber_num; /* number of bit error rates */
+ uint8_t ul_ft; /* current uplink FT index */
+ uint8_t dl_ft; /* current downlink FT index */
+ uint8_t ul_cmr; /* current uplink CMR index */
+ uint8_t dl_cmr; /* current downlink CMR index */
+ uint8_t amr_loop; /* if AMR loop is enabled */
+
+ /* TCH/H */
+ uint8_t dl_ongoing_facch; /* FACCH/H on downlink */
+ uint8_t ul_ongoing_facch; /* FACCH/H on uplink */
+
+ /* encryption */
+ int ul_encr_algo; /* A5/x encry algo downlink */
+ int dl_encr_algo; /* A5/x encry algo uplink */
+ int ul_encr_key_len;
+ int dl_encr_key_len;
+ uint8_t ul_encr_key[MAX_A5_KEY_LEN];
+ uint8_t dl_encr_key[MAX_A5_KEY_LEN];
+
+ /* measurements */
+ struct {
+ uint8_t clock; /* cyclic clock counter */
+ int8_t rssi[32]; /* last RSSI values */
+ int rssi_count; /* received RSSI values */
+ int rssi_valid_count; /* number of stored value */
+ int rssi_got_burst; /* any burst received so far */
+ int32_t toa256_sum; /* sum of TOA values (1/256 symbol) */
+ int toa_num; /* number of TOA value */
+ } meas;
+
+ /* handover */
+ uint8_t ho_rach_detect; /* if rach detection is on */
+};
+
+struct l1sched_ts {
+ uint8_t mf_index; /* selected multiframe index */
+ uint8_t mf_period; /* period of multiframe */
+ const struct trx_sched_frame *mf_frames; /* pointer to frame layout */
+
+ struct llist_head dl_prims; /* Queue primitives for TX */
+
+ /* Channel states for all logical channels */
+ struct l1sched_chan_state chan_state[_TRX_CHAN_MAX];
+};
+
+struct l1sched_trx {
+ struct gsm_bts_trx *trx;
+ struct l1sched_ts ts[TRX_NR_TS];
+};
+
+struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn);
+
+/*! \brief how many frame numbers in advance we should send bursts to PHY */
+extern uint32_t trx_clock_advance;
+/*! \brief advance RTS.ind to L2 by that many clocks */
+extern uint32_t trx_rts_advance;
+/*! \brief last frame number as received from PHY */
+extern uint32_t transceiver_last_fn;
+
+
+/*! \brief Initialize the scheduler data structures */
+int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx);
+
+/*! \brief De-initialize the scheduler data structures */
+void trx_sched_exit(struct l1sched_trx *l1t);
+
+/*! \brief Handle a PH-DATA.req from L2 down to L1 */
+int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap);
+
+/*! \brief Handle a PH-TCH.req from L2 down to L1 */
+int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap);
+
+/*! \brief PHY informs us of new (current) GSM frame number */
+int trx_sched_clock(struct gsm_bts *bts, uint32_t fn);
+
+/*! \brief handle an UL burst received by PHY */
+int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa);
+
+/*! \brief set multiframe scheduler to given physical channel config */
+int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn,
+ enum gsm_phys_chan_config pchan);
+
+/*! \brief set all matching logical channels active/inactive */
+int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id,
+ int active);
+
+/*! \brief set mode of all matching logical channels to given mode(s) */
+int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmode,
+ uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1,
+ uint8_t codec2, uint8_t codec3, uint8_t initial_codec,
+ uint8_t handover);
+
+/*! \brief set ciphering on given logical channels */
+int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink,
+ int algo, uint8_t *key, int key_len);
+
+/* \brief close all logical channels and reset timeslots */
+void trx_sched_reset(struct l1sched_trx *l1t);
+
+
+/* frame structures */
+struct trx_sched_frame {
+ /*! \brief downlink TRX channel type */
+ enum trx_chan_type dl_chan;
+ /*! \brief downlink block ID */
+ uint8_t dl_bid;
+ /*! \brief uplink TRX channel type */
+ enum trx_chan_type ul_chan;
+ /*! \brief uplink block ID */
+ uint8_t ul_bid;
+};
+
+/* multiframe structure */
+struct trx_sched_multiframe {
+ /*! \brief physical channel config (channel combination) */
+ enum gsm_phys_chan_config pchan;
+ /*! \brief applies to which timeslots? */
+ uint8_t slotmask;
+ /*! \brief repeats how many frames */
+ uint8_t period;
+ /*! \brief pointer to scheduling structure */
+ const struct trx_sched_frame *frames;
+ /*! \brief human-readable name */
+ const char *name;
+};
+
+int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn);
+
+/*! Determine if given frame number contains SACCH (true) or other (false) burst */
+bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink);
+extern const struct trx_sched_multiframe trx_sched_multiframes[];
+
+#endif /* TRX_SCHEDULER_H */
diff --git a/include/osmo-bts/scheduler_backend.h b/include/osmo-bts/scheduler_backend.h
new file mode 100644
index 00000000..dbd93195
--- /dev/null
+++ b/include/osmo-bts/scheduler_backend.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#define LOGL1S(subsys, level, l1t, tn, chan, fn, fmt, args ...) \
+ LOGP(subsys, level, "%s %s %s: " fmt, \
+ gsm_fn_as_gsmtime_str(fn), \
+ gsm_ts_name(&(l1t)->trx->ts[tn]), \
+ chan >=0 ? trx_chan_desc[chan].name : "", ## args)
+
+typedef int trx_sched_rts_func(struct l1sched_trx *l1t, uint8_t tn,
+ uint32_t fn, enum trx_chan_type chan);
+
+typedef ubit_t *trx_sched_dl_func(struct l1sched_trx *l1t, uint8_t tn,
+ uint32_t fn, enum trx_chan_type chan,
+ uint8_t bid, uint16_t *nbits);
+
+typedef int trx_sched_ul_func(struct l1sched_trx *l1t, uint8_t tn,
+ uint32_t fn, enum trx_chan_type chan,
+ uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256);
+
+struct trx_chan_desc {
+ /*! \brief Is this on a PDCH (PS) ? */
+ int pdch;
+ /*! \brief TRX Channel Type */
+ enum trx_chan_type chan;
+ /*! \brief Channel Number (like in RSL) */
+ uint8_t chan_nr;
+ /*! \brief Link ID (like in RSL) */
+ uint8_t link_id;
+ /*! \brief Human-readable name */
+ const char *name;
+ /*! \brief function to call when we want to generate RTS.req to L2 */
+ trx_sched_rts_func *rts_fn;
+ /*! \brief function to call when DATA.req received from L2 */
+ trx_sched_dl_func *dl_fn;
+ /*! \brief function to call when burst received from PHY */
+ trx_sched_ul_func *ul_fn;
+ /*! \brief is this channel automatically active at start? */
+ int auto_active;
+};
+extern const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX];
+
+extern const ubit_t _sched_tsc[8][26];
+extern const ubit_t _sched_egprs_tsc[8][78];
+const ubit_t _sched_fcch_burst[148];
+const ubit_t _sched_sch_train[64];
+
+struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn,
+ enum trx_chan_type chan);
+
+int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t *l2,
+ uint8_t l2_len, float rssi,
+ int16_t ta_offs_256bits, int16_t link_qual_cb,
+ uint16_t ber10k,
+ enum osmo_ph_pres_info_type presence_info);
+
+int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len);
+
+ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits);
+int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256);
+int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256);
+int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256);
+int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256);
+int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256);
+
+const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn,
+ uint32_t fn, uint16_t *nbits);
+int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn);
+void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate);
diff --git a/include/osmo-bts/signal.h b/include/osmo-bts/signal.h
new file mode 100644
index 00000000..01d4099b
--- /dev/null
+++ b/include/osmo-bts/signal.h
@@ -0,0 +1,19 @@
+#ifndef OSMO_BTS_SIGNAL_H
+#define OSMO_BTS_SIGNAL_H
+
+#include <osmocom/core/signal.h>
+
+enum sig_subsys {
+ SS_GLOBAL,
+ SS_FAIL,
+};
+
+enum signals_global {
+ S_NEW_SYSINFO,
+ S_NEW_OP_STATE,
+ S_NEW_NSE_ATTR,
+ S_NEW_CELL_ATTR,
+ S_NEW_NSVC_ATTR,
+};
+
+#endif
diff --git a/include/osmo-bts/tx_power.h b/include/osmo-bts/tx_power.h
new file mode 100644
index 00000000..21887c7c
--- /dev/null
+++ b/include/osmo-bts/tx_power.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/timer.h>
+
+/* our unit is 'milli dB" or "milli dBm", i.e. 1/1000 of a dB(m) */
+#define to_mdB(x) (x * 1000)
+
+/* PA calibration table */
+struct pa_calibration {
+ int delta_mdB[1024]; /* gain delta at given ARFCN */
+ /* FIXME: thermal calibration */
+};
+
+/* representation of a RF power amplifier */
+struct power_amp {
+ /* nominal gain of the PA */
+ int nominal_gain_mdB;
+ /* table with calibrated actual gain for each ARFCN */
+ struct pa_calibration calib;
+};
+
+/* Transmit power related parameters of a transceiver */
+struct trx_power_params {
+ /* specified maximum output of TRX at full power, has to be
+ * initialized by BTS model at startup*/
+ int trx_p_max_out_mdBm;
+
+ /* intended current total system output power */
+ int p_total_tgt_mdBm;
+
+ /* actual current total system output power, filled in by tx_power code */
+ int p_total_cur_mdBm;
+
+ /* current temporary attenuation due to thermal management,
+ * set by thermal management code via control interface */
+ int thermal_attenuation_mdB;
+
+ /* external gain (+) or attenuation (-) added by the user, configured
+ * by the user via VTY */
+ int user_gain_mdB;
+
+ /* calibration table of internal PA */
+ struct power_amp pa;
+
+ /* calibration table of user PA */
+ struct power_amp user_pa;
+
+ /* power ramping related data */
+ struct {
+ /* maximum initial Pout including all PAs */
+ int max_initial_pout_mdBm;
+ /* temporary attenuation due to power ramping */
+ int attenuation_mdB;
+ unsigned int step_size_mdB;
+ unsigned int step_interval_sec;
+ struct osmo_timer_list step_timer;
+ } ramp;
+};
+
+int get_p_max_out_mdBm(struct gsm_bts_trx *trx);
+
+int get_p_nominal_mdBm(struct gsm_bts_trx *trx);
+
+int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie);
+int get_p_target_mdBm_lchan(struct gsm_lchan *lchan);
+
+int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie);
+int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan);
+
+int get_p_trxout_actual_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie);
+int get_p_trxout_actual_mdBm_lchan(struct gsm_lchan *lchan);
+
+int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass);
+
+void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm);
+
+int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx);
diff --git a/include/osmo-bts/vty.h b/include/osmo-bts/vty.h
new file mode 100644
index 00000000..d27acb5f
--- /dev/null
+++ b/include/osmo-bts/vty.h
@@ -0,0 +1,32 @@
+#ifndef OSMOBTS_VTY_H
+#define OSMOBTS_VTY_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+enum bts_vty_node {
+ /* PHY_NODE must come before BTS node to ensure the phy
+ * instances are created at the time the TRX nodes want to refer
+ * to them */
+ PHY_NODE = _LAST_OSMOVTY_NODE + 1,
+ PHY_INST_NODE,
+ BTS_NODE,
+ TRX_NODE,
+};
+
+extern struct cmd_element ournode_exit_cmd;
+extern struct cmd_element ournode_end_cmd;
+
+extern struct cmd_element cfg_bts_auto_band_cmd;
+extern struct cmd_element cfg_bts_no_auto_band_cmd;
+
+struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr);
+
+int bts_vty_go_parent(struct vty *vty);
+int bts_vty_is_config_node(struct vty *vty, int node);
+
+int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat);
+
+extern struct vty_app_info bts_vty_info;
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..70e4d968
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,22 @@
+SUBDIRS = common osmo-bts-virtual osmo-bts-omldummy
+
+if ENABLE_SYSMOBTS
+SUBDIRS += osmo-bts-sysmo
+endif
+
+if ENABLE_TRX
+SUBDIRS += osmo-bts-trx
+endif
+
+if ENABLE_OCTPHY
+SUBDIRS += osmo-bts-octphy
+endif
+
+if ENABLE_LC15BTS
+SUBDIRS += osmo-bts-litecell15
+endif
+
+if ENABLE_OC2GBTS
+SUBDIRS += osmo-bts-oc2g
+endif
+
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
new file mode 100644
index 00000000..113ff2f4
--- /dev/null
+++ b/src/common/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOCODEC_LIBS)
+
+if ENABLE_LC15BTS
+AM_CFLAGS += -DENABLE_LC15BTS
+endif
+
+noinst_LIBRARIES = libbts.a libl1sched.a
+libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \
+ rsl.c vty.c paging.c measurement.c amr.c lchan.c \
+ load_indication.c pcu_sock.c handover.c msg_utils.c \
+ tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \
+ l1sap.c cbch.c power_control.c main.c phy_link.c \
+ dtx_dl_amr_fsm.c scheduler_mframe.c
+
+libl1sched_a_SOURCES = scheduler.c
diff --git a/src/common/abis.c b/src/common/abis.c
new file mode 100644
index 00000000..84a3a047
--- /dev/null
+++ b/src/common/abis.c
@@ -0,0 +1,295 @@
+/* Abis/IP interface routines utilizing libosmo-abis (Pablo) */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "btsconfig.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/macaddr.h>
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts_model.h>
+
+static struct gsm_bts *g_bts;
+
+int abis_oml_sendmsg(struct msgb *msg)
+{
+ struct gsm_bts *bts = msg->trx->bts;
+
+ if (!bts->oml_link) {
+ llist_add_tail(&msg->list, &bts->oml_queue);
+ return 0;
+ } else {
+ /* osmo-bts uses msg->trx internally, but libosmo-abis uses
+ * the signalling link at msg->dst */
+ msg->dst = bts->oml_link;
+ return abis_sendmsg(msg);
+ }
+}
+
+static void drain_oml_queue(struct gsm_bts *bts)
+{
+ struct msgb *msg, *msg2;
+
+ llist_for_each_entry_safe(msg, msg2, &bts->oml_queue, list) {
+ /* osmo-bts uses msg->trx internally, but libosmo-abis uses
+ * the signalling link at msg->dst */
+ llist_del(&msg->list);
+ msg->dst = bts->oml_link;
+ abis_sendmsg(msg);
+ }
+}
+
+int abis_bts_rsl_sendmsg(struct msgb *msg)
+{
+ OSMO_ASSERT(msg->trx);
+
+ if (msg->trx->bts->variant == BTS_OSMO_OMLDUMMY) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ /* osmo-bts uses msg->trx internally, but libosmo-abis uses
+ * the signalling link at msg->dst */
+ msg->dst = msg->trx->rsl_link;
+ return abis_sendmsg(msg);
+}
+
+static struct e1inp_sign_link *sign_link_up(void *unit, struct e1inp_line *line,
+ enum e1inp_sign_type type)
+{
+ struct e1inp_sign_link *sign_link = NULL;
+ struct gsm_bts_trx *trx;
+ int trx_nr;
+
+ switch (type) {
+ case E1INP_SIGN_OML:
+ LOGP(DABIS, LOGL_INFO, "OML Signalling link up\n");
+ e1inp_ts_config_sign(&line->ts[E1INP_SIGN_OML-1], line);
+ sign_link = g_bts->oml_link =
+ e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML-1],
+ E1INP_SIGN_OML, NULL, 255, 0);
+ if (clock_gettime(CLOCK_MONOTONIC, &g_bts->oml_conn_established_timestamp) != 0)
+ memset(&g_bts->oml_conn_established_timestamp, 0,
+ sizeof(g_bts->oml_conn_established_timestamp));
+ drain_oml_queue(g_bts);
+ sign_link->trx = g_bts->c0;
+ bts_link_estab(g_bts);
+ break;
+ default:
+ trx_nr = type - E1INP_SIGN_RSL;
+ LOGP(DABIS, LOGL_INFO, "RSL Signalling link for TRX%d up\n",
+ trx_nr);
+ trx = gsm_bts_trx_num(g_bts, trx_nr);
+ if (!trx) {
+ LOGP(DABIS, LOGL_ERROR, "TRX%d does not exist!\n",
+ trx_nr);
+ break;
+ }
+ e1inp_ts_config_sign(&line->ts[type-1], line);
+ sign_link = trx->rsl_link =
+ e1inp_sign_link_create(&line->ts[type-1],
+ E1INP_SIGN_RSL, NULL, 0, 0);
+ sign_link->trx = trx;
+ trx_link_estab(trx);
+ break;
+ }
+
+ return sign_link;
+}
+
+static void sign_link_down(struct e1inp_line *line)
+{
+ struct gsm_bts_trx *trx;
+ LOGP(DABIS, LOGL_ERROR, "Signalling link down\n");
+
+ /* First remove the OML signalling link */
+ if (g_bts->oml_link) {
+ struct timespec now;
+
+ e1inp_sign_link_destroy(g_bts->oml_link);
+
+ /* Log a special notice if the OML connection was dropped relatively quickly. */
+ if (g_bts->oml_conn_established_timestamp.tv_sec != 0 && clock_gettime(CLOCK_MONOTONIC, &now) == 0 &&
+ g_bts->oml_conn_established_timestamp.tv_sec + OSMO_BTS_OML_CONN_EARLY_DISCONNECT >= now.tv_sec) {
+ LOGP(DABIS, LOGL_FATAL, "OML link was closed early within %" PRIu64 " seconds. "
+ "If this situation persists, please check your BTS and BSC configuration files for errors. "
+ "A common error is a mismatch between unit_id configuration parameters of BTS and BSC.\n",
+ (uint64_t)(now.tv_sec - g_bts->oml_conn_established_timestamp.tv_sec));
+ }
+ }
+ g_bts->oml_link = NULL;
+ memset(&g_bts->oml_conn_established_timestamp, 0, sizeof(g_bts->oml_conn_established_timestamp));
+
+ /* Then iterate over the RSL signalling links */
+ llist_for_each_entry(trx, &g_bts->trx_list, list) {
+ if (trx->rsl_link) {
+ e1inp_sign_link_destroy(trx->rsl_link);
+ trx->rsl_link = NULL;
+ }
+ }
+
+ bts_model_abis_close(g_bts);
+}
+
+
+/* callback for incoming mesages from A-bis/IP */
+static int sign_link_cb(struct msgb *msg)
+{
+ struct e1inp_sign_link *link = msg->dst;
+
+ /* osmo-bts code assumes msg->trx is set, but libosmo-abis works
+ * with the sign_link stored in msg->dst, so we have to convert
+ * here */
+ msg->trx = link->trx;
+
+ switch (link->type) {
+ case E1INP_SIGN_OML:
+ down_oml(link->trx->bts, msg);
+ break;
+ case E1INP_SIGN_RSL:
+ down_rsl(link->trx, msg);
+ break;
+ default:
+ msgb_free(msg);
+ break;
+ }
+
+ return 0;
+}
+
+uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link)
+{
+ int fd = link->ts->driver.ipaccess.fd.fd;
+ struct sockaddr_in sin;
+ socklen_t slen = sizeof(sin);
+ int rc;
+
+ rc = getpeername(fd, (struct sockaddr *)&sin, &slen);
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "Cannot determine remote IP Addr: %s\n",
+ strerror(errno));
+ return 0;
+ }
+
+ /* we assume that the soket is AF_INET. As Abis/IP contains
+ * lots of hard-coded IPv4 addresses, this safe */
+ OSMO_ASSERT(sin.sin_family == AF_INET);
+
+ return ntohl(sin.sin_addr.s_addr);
+}
+
+
+static int inp_s_cbfn(unsigned int subsys, unsigned int signal,
+ void *hdlr_data, void *signal_data)
+{
+ if (subsys != SS_L_INPUT)
+ return 0;
+
+ struct input_signal_data *isd = signal_data;
+ DEBUGP(DABIS, "Input Signal %s received for link_type=%s\n",
+ get_value_string(e1inp_signal_names, signal), e1inp_signtype_name(isd->link_type));
+
+ return 0;
+}
+
+
+static struct ipaccess_unit bts_dev_info = {
+ .unit_name = "sysmoBTS",
+ .equipvers = "", /* FIXME: read this from hw */
+ .swversion = PACKAGE_VERSION,
+ .location1 = "",
+ .location2 = "",
+ .serno = "",
+};
+
+static struct e1inp_line_ops line_ops = {
+ .cfg = {
+ .ipa = {
+ .role = E1INP_LINE_R_BTS,
+ .dev = &bts_dev_info,
+ },
+ },
+ .sign_link_up = sign_link_up,
+ .sign_link_down = sign_link_down,
+ .sign_link = sign_link_cb,
+};
+
+void abis_init(struct gsm_bts *bts)
+{
+ g_bts = bts;
+
+ oml_init(&bts->mo);
+ libosmo_abis_init(NULL);
+
+ osmo_signal_register_handler(SS_L_INPUT, &inp_s_cbfn, bts);
+}
+
+struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host,
+ char *model_name)
+{
+ struct e1inp_line *line;
+
+ /* patch in various data from VTY and othe sources */
+ line_ops.cfg.ipa.addr = dst_host;
+ osmo_get_macaddr(bts_dev_info.mac_addr, "eth0");
+ bts_dev_info.site_id = bts->ip_access.site_id;
+ bts_dev_info.bts_id = bts->ip_access.bts_id;
+ bts_dev_info.unit_name = model_name;
+ if (bts->description)
+ bts_dev_info.unit_name = bts->description;
+ bts_dev_info.location2 = model_name;
+
+ line = e1inp_line_find(0);
+ if (!line)
+ line = e1inp_line_create(0, "ipa");
+ if (!line)
+ return NULL;
+ e1inp_line_bind_ops(line, &line_ops);
+
+ /* This will open the OML connection now */
+ if (e1inp_line_update(line) < 0)
+ return NULL;
+
+ return line;
+}
diff --git a/src/common/amr.c b/src/common/amr.c
new file mode 100644
index 00000000..05d1aaac
--- /dev/null
+++ b/src/common/amr.c
@@ -0,0 +1,170 @@
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/logging.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/amr.h>
+
+void amr_log_mr_conf(int ss, int logl, const char *pfx,
+ struct amr_multirate_conf *amr_mrc)
+{
+ int i;
+
+ LOGP(ss, logl, "%s AMR MR Conf: num_modes=%u",
+ pfx, amr_mrc->num_modes);
+
+ for (i = 0; i < amr_mrc->num_modes; i++)
+ LOGPC(ss, logl, ", mode[%u] = %u/%u/%u",
+ i, amr_mrc->bts_mode[i].mode,
+ amr_mrc->bts_mode[i].threshold,
+ amr_mrc->bts_mode[i].hysteresis);
+ LOGPC(ss, logl, "\n");
+}
+
+static inline int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi)
+{
+ unsigned int i;
+ for (i = 0; i < amr_mrc->num_modes; i++) {
+ if (amr_mrc->bts_mode[i].mode == cmi)
+ return i;
+ }
+ return -EINVAL;
+}
+
+static inline uint8_t set_cmr_mode_idx(const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmr)
+{
+ int rc;
+
+ /* Codec Mode Request is in upper 4 bits of RTP payload header,
+ * and we simply copy the CMR into the CMC */
+ if (cmr == 0xF) {
+ /* FIXME: we need some state about the last codec mode */
+ return 0;
+ }
+
+ rc = get_amr_mode_idx(amr_mrc, cmr);
+ if (rc < 0) {
+ /* FIXME: we need some state about the last codec mode */
+ LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr);
+ return 0;
+ }
+ return rc;
+}
+
+static inline uint8_t set_cmi_mode_idx(const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi)
+{
+ int rc = get_amr_mode_idx(amr_mrc, cmi);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n",
+ cmi);
+ return 0;
+ }
+ return rc;
+}
+
+void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi, uint8_t cmr)
+{
+ data[0] = set_cmi_mode_idx(amr_mrc, cmi);
+ data[1] = set_cmr_mode_idx(amr_mrc, cmr);
+}
+
+/* parse a GSM 04.08 MultiRate Config IE (10.5.2.21aa) in a more
+ * comfortable internal data structure */
+int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc,
+ const uint8_t *mr_conf, unsigned int len)
+{
+ uint8_t mr_version = mr_conf[0] >> 5;
+ uint8_t num_codecs = 0;
+ int i, j = 0;
+
+ if (mr_version != 1) {
+ LOGP(DRSL, LOGL_ERROR, "AMR Multirate Version %u unknown\n",
+ mr_version);
+ goto ret_einval;
+ }
+
+ /* check number of active codecs */
+ for (i = 0; i < 8; i++) {
+ if (mr_conf[1] & (1 << i))
+ num_codecs++;
+ }
+
+ /* check for minimum length */
+ if (num_codecs == 0 ||
+ (num_codecs == 1 && len < 2) ||
+ (num_codecs == 2 && len < 4) ||
+ (num_codecs == 3 && len < 5) ||
+ (num_codecs == 4 && len < 6) ||
+ (num_codecs > 4)) {
+ LOGP(DRSL, LOGL_ERROR, "AMR Multirate with %u modes len=%u "
+ "not possible\n", num_codecs, len);
+ goto ret_einval;
+ }
+
+ /* copy the first two octets of the IE */
+ amr_mrc->gsm48_ie[0] = mr_conf[0];
+ amr_mrc->gsm48_ie[1] = mr_conf[1];
+
+ amr_mrc->num_modes = num_codecs;
+
+ for (i = 0; i < 8; i++) {
+ if (mr_conf[1] & (1 << i)) {
+ amr_mrc->bts_mode[j++].mode = i;
+ }
+ }
+
+ if (num_codecs >= 2) {
+ amr_mrc->bts_mode[0].threshold = mr_conf[1] & 0x3F;
+ amr_mrc->bts_mode[0].hysteresis = mr_conf[2] >> 4;
+ }
+ if (num_codecs >= 3) {
+ amr_mrc->bts_mode[1].threshold =
+ ((mr_conf[2] & 0xF) << 2) | (mr_conf[3] >> 6);
+ amr_mrc->bts_mode[1].hysteresis = (mr_conf[3] >> 2) & 0xF;
+ }
+ if (num_codecs >= 4) {
+ amr_mrc->bts_mode[2].threshold =
+ ((mr_conf[3] & 0x3) << 4) | (mr_conf[4] >> 4);
+ amr_mrc->bts_mode[2].hysteresis = mr_conf[4] & 0xF;
+ }
+
+ return num_codecs;
+
+ret_einval:
+ return -EINVAL;
+}
+
+
+/*! \brief determine AMR initial codec mode for given logical channel
+ * \returns integer between 0..3 for AMR codce mode 1..4 */
+unsigned int amr_get_initial_mode(struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+
+ if (mr_conf->icmi) {
+ /* initial mode given, coding in TS 05.09 3.4.1 */
+ return mr_conf->smod;
+ } else {
+ /* implicit rule according to TS 05.09 Chapter 3.4.3 */
+ switch (amr_mrc->num_modes) {
+ case 2:
+ case 3:
+ /* return the most robust */
+ return 0;
+ case 4:
+ /* return the second-most robust */
+ return 1;
+ case 1:
+ default:
+ /* return the only mode we have */
+ return 0;
+ }
+ }
+}
diff --git a/src/common/bts.c b/src/common/bts.c
new file mode 100644
index 00000000..abbaeb46
--- /dev/null
+++ b/src/common/bts.c
@@ -0,0 +1,759 @@
+/* BTS support code common to all supported BTS models */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */
+#define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */
+
+static void bts_update_agch_max_queue_length(struct gsm_bts *bts);
+
+struct gsm_network bts_gsmnet = {
+ .bts_list = { &bts_gsmnet.bts_list, &bts_gsmnet.bts_list },
+ .num_bts = 0,
+};
+
+void *tall_bts_ctx;
+
+/* Table 3.1 TS 04.08: Values of parameter S */
+static const uint8_t tx_integer[] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50,
+};
+
+static const uint8_t s_values[][2] = {
+ { 55, 41 }, { 76, 52 }, { 109, 58 }, { 163, 86 }, { 217, 115 },
+};
+
+static int bts_signal_cbfn(unsigned int subsys, unsigned int signal,
+ void *hdlr_data, void *signal_data)
+{
+ if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) {
+ struct gsm_bts *bts = signal_data;
+
+ bts_update_agch_max_queue_length(bts);
+ }
+ return 0;
+}
+
+static const struct rate_ctr_desc bts_ctr_desc[] = {
+ [BTS_CTR_PAGING_RCVD] = {"paging:rcvd", "Received paging requests (Abis)"},
+ [BTS_CTR_PAGING_DROP] = {"paging:drop", "Dropped paging requests (Abis)"},
+ [BTS_CTR_PAGING_SENT] = {"paging:sent", "Sent paging requests (Um)"},
+
+ [BTS_CTR_RACH_RCVD] = {"rach:rcvd", "Received RACH requests (Um)"},
+ [BTS_CTR_RACH_DROP] = {"rach:drop", "Dropped RACH requests (Um)"},
+ [BTS_CTR_RACH_HO] = {"rach:handover", "Received RACH requests (Handover)"},
+ [BTS_CTR_RACH_CS] = {"rach:cs", "Received RACH requests (CS/Abis)"},
+ [BTS_CTR_RACH_PS] = {"rach:ps", "Received RACH requests (PS/PCU)"},
+
+ [BTS_CTR_AGCH_RCVD] = {"agch:rcvd", "Received AGCH requests (Abis)"},
+ [BTS_CTR_AGCH_SENT] = {"agch:sent", "Sent AGCH requests (Abis)"},
+ [BTS_CTR_AGCH_DELETED] = {"agch:delete", "Sent AGCH DELETE IND (Abis)"},
+};
+static const struct rate_ctr_group_desc bts_ctrg_desc = {
+ "bts",
+ "base transceiver station",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(bts_ctr_desc),
+ bts_ctr_desc
+};
+
+/* Initialize the BTS data structures, called before config
+ * file reading */
+int bts_init(struct gsm_bts *bts)
+{
+ int rc, i;
+ static int initialized = 0;
+ void *tall_rtp_ctx;
+
+ /* add to list of BTSs */
+ llist_add_tail(&bts->list, &bts_gsmnet.bts_list);
+
+ bts->band = GSM_BAND_1800;
+
+ INIT_LLIST_HEAD(&bts->agch_queue.queue);
+ bts->agch_queue.length = 0;
+
+ bts->ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr);
+
+ /* enable management with default levels,
+ * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to
+ * disable this feature.
+ */
+ bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT;
+ bts->agch_queue.high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT;
+ bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT;
+
+ /* configurable via VTY */
+ bts->paging_state = paging_init(bts, 200, 0);
+ bts->ul_power_target = -75; /* dBm default */
+ bts->rtp_jitter_adaptive = false;
+ bts->rtp_port_range_start = 16384;
+ bts->rtp_port_range_end = 17407;
+ bts->rtp_port_range_next = bts->rtp_port_range_start;
+
+ /* configurable via OML */
+ bts->load.ccch.load_ind_period = 112;
+ load_timer_start(bts);
+ bts->rtp_jitter_buf_ms = 100;
+ bts->max_ta = 63;
+ bts->ny1 = 4;
+ bts->t3105_ms = 300;
+ bts->min_qual_rach = MIN_QUAL_RACH;
+ bts->min_qual_norm = MIN_QUAL_NORM;
+ bts->max_ber10k_rach = 1707; /* 7 of 41 bits is Eb/N0 of 0 dB = 0.1707 */
+ bts->pcu.sock_path = talloc_strdup(bts, PCU_SOCK_DEFAULT);
+ for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++)
+ bts->t200_ms[i] = oml_default_t200_ms[i];
+
+ /* default RADIO_LINK_TIMEOUT */
+ bts->radio_link_timeout = 32;
+
+ /* Start with the site manager */
+ oml_mo_state_init(&bts->site_mgr.mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* set BTS to dependency */
+ oml_mo_state_init(&bts->mo, -1, NM_AVSTATE_DEPENDENCY);
+ oml_mo_state_init(&bts->gprs.nse.mo, -1, NM_AVSTATE_DEPENDENCY);
+ oml_mo_state_init(&bts->gprs.cell.mo, -1, NM_AVSTATE_DEPENDENCY);
+ oml_mo_state_init(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_DEPENDENCY);
+ oml_mo_state_init(&bts->gprs.nsvc[1].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+
+ /* allocate a talloc pool for ORTP to ensure it doesn't have to go back
+ * to the libc malloc all the time */
+ tall_rtp_ctx = talloc_pool(tall_bts_ctx, 262144);
+ osmo_rtp_init(tall_rtp_ctx);
+
+ rc = bts_model_init(bts);
+ if (rc < 0) {
+ llist_del(&bts->list);
+ return rc;
+ }
+
+ /* TRX0 was allocated early during gsm_bts_alloc, not later through VTY */
+ bts_trx_init(bts->c0);
+ bts_gsmnet.num_bts++;
+
+ if (!initialized) {
+ osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL);
+ initialized = 1;
+ }
+
+ INIT_LLIST_HEAD(&bts->smscb_state.queue);
+ INIT_LLIST_HEAD(&bts->oml_queue);
+
+ /* register DTX DL FSM */
+ rc = osmo_fsm_register(&dtx_dl_amr_fsm);
+ OSMO_ASSERT(rc == 0);
+
+ return rc;
+}
+
+/* Initialize the TRX data structures, called before config
+ * file reading */
+int bts_trx_init(struct gsm_bts_trx *trx)
+{
+ /* initialize bts data structure */
+ struct trx_power_params *tpp = &trx->power_params;
+ int rc, i;
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ int k;
+
+ for (k = 0; k < ARRAY_SIZE(ts->lchan); k++) {
+ struct gsm_lchan *lchan = &ts->lchan[k];
+ INIT_LLIST_HEAD(&lchan->dl_tch_queue);
+ }
+ }
+ /* Default values for the power adjustments */
+ tpp->ramp.max_initial_pout_mdBm = to_mdB(0);
+ tpp->ramp.step_size_mdB = to_mdB(2);
+ tpp->ramp.step_interval_sec = 1;
+
+ rc = bts_model_trx_init(trx);
+ if (rc < 0) {
+ llist_del(&trx->list);
+ return rc;
+ }
+ return 0;
+}
+
+static void shutdown_timer_cb(void *data)
+{
+ fprintf(stderr, "Shutdown timer expired\n");
+ exit(42);
+}
+
+static struct osmo_timer_list shutdown_timer = {
+ .cb = &shutdown_timer_cb,
+};
+
+void bts_shutdown(struct gsm_bts *bts, const char *reason)
+{
+ struct gsm_bts_trx *trx;
+
+ if (osmo_timer_pending(&shutdown_timer)) {
+ LOGP(DOML, LOGL_NOTICE,
+ "BTS is already being shutdown.\n");
+ return;
+ }
+
+ LOGP(DOML, LOGL_NOTICE, "Shutting down BTS %u, Reason %s\n",
+ bts->nr, reason);
+
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
+ bts_model_trx_deact_rf(trx);
+ bts_model_trx_close(trx);
+ }
+
+ /* shedule a timer to make sure select loop logic can run again
+ * to dispatch any pending primitives */
+ osmo_timer_schedule(&shutdown_timer, 3, 0);
+}
+
+/* main link is established, send status report */
+int bts_link_estab(struct gsm_bts *bts)
+{
+ int i, j;
+
+ LOGP(DSUM, LOGL_INFO, "Main link established, sending Status'.\n");
+
+ /* BTS and SITE MGR are EANBLED, BTS is DEPENDENCY */
+ oml_tx_state_changed(&bts->site_mgr.mo);
+ oml_tx_state_changed(&bts->mo);
+
+ /* those should all be in DEPENDENCY */
+ oml_tx_state_changed(&bts->gprs.nse.mo);
+ oml_tx_state_changed(&bts->gprs.cell.mo);
+ oml_tx_state_changed(&bts->gprs.nsvc[0].mo);
+ oml_tx_state_changed(&bts->gprs.nsvc[1].mo);
+
+ /* All other objects start off-line until the BTS Model code says otherwise */
+ for (i = 0; i < bts->num_trx; i++) {
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i);
+
+ oml_tx_state_changed(&trx->mo);
+ oml_tx_state_changed(&trx->bb_transc.mo);
+
+ for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[j];
+
+ oml_tx_state_changed(&ts->mo);
+ }
+ }
+
+ return bts_model_oml_estab(bts);
+}
+
+/* RSL link is established, send status report */
+int trx_link_estab(struct gsm_bts_trx *trx)
+{
+ struct e1inp_sign_link *link = trx->rsl_link;
+ uint8_t radio_state = link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED;
+ int rc;
+
+ LOGP(DSUM, LOGL_INFO, "RSL link (TRX %02x) state changed to %s, sending Status'.\n",
+ trx->nr, link ? "up" : "down");
+
+ oml_mo_state_chg(&trx->mo, radio_state, NM_AVSTATE_OK);
+
+ if (link)
+ rc = rsl_tx_rf_res(trx);
+ else
+ rc = bts_model_trx_deact_rf(trx);
+ if (rc < 0)
+ oml_fail_rep(OSMO_EVT_MAJ_RSL_FAIL,
+ link ? "Failed to establish RSL link (%d)" :
+ "Failed to deactivate RF (%d)", rc);
+ return 0;
+}
+
+/* set the availability of the TRX (used by PHY driver) */
+int trx_set_available(struct gsm_bts_trx *trx, int avail)
+{
+ int tn;
+
+ LOGP(DSUM, LOGL_INFO, "TRX(%d): Setting available = %d\n",
+ trx->nr, avail);
+ if (avail) {
+ int op_state = trx->rsl_link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED;
+ oml_mo_state_chg(&trx->mo, op_state, NM_AVSTATE_OK);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, op_state, NM_AVSTATE_OK);
+ } else {
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_NOT_INSTALLED);
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+ }
+ return 0;
+}
+
+int lchan_init_lapdm(struct gsm_lchan *lchan)
+{
+ struct lapdm_channel *lc = &lchan->lapdm_ch;
+
+ lapdm_channel_init(lc, LAPDM_MODE_BTS);
+ lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY);
+ lapdm_channel_set_l1(lc, NULL, lchan);
+ lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan);
+ oml_set_lchan_t200(lchan);
+
+ return 0;
+}
+
+#define CCCH_RACH_RATIO_COMBINED256 (256*1/9)
+#define CCCH_RACH_RATIO_SEPARATE256 (256*10/55)
+
+int bts_agch_max_queue_length(int T, int bcch_conf)
+{
+ int S, ccch_rach_ratio256, i;
+ int T_group = 0;
+ int is_ccch_comb = 0;
+
+ if (bcch_conf == RSL_BCCH_CCCH_CONF_1_C)
+ is_ccch_comb = 1;
+
+ /*
+ * The calculation is based on the ratio of the number RACH slots and
+ * CCCH blocks per time:
+ * Lmax = (T + 2*S) / R_RACH * R_CCCH
+ * where
+ * T3126_min = (T + 2*S) / R_RACH, as defined in GSM 04.08, 11.1.1
+ * R_RACH is the RACH slot rate (e.g. RACHs per multiframe)
+ * R_CCCH is the CCCH block rate (same time base like R_RACH)
+ * S and T are defined in GSM 04.08, 3.3.1.1.2
+ * The ratio is mainly influenced by the downlink only channels
+ * (BCCH, FCCH, SCH, CBCH) that can not be used for CCCH.
+ * An estimation with an error of < 10% is used:
+ * ~ 1/9 if CCCH is combined with SDCCH, and
+ * ~ 1/5.5 otherwise.
+ */
+ ccch_rach_ratio256 = is_ccch_comb ?
+ CCCH_RACH_RATIO_COMBINED256 :
+ CCCH_RACH_RATIO_SEPARATE256;
+
+ for (i = 0; i < ARRAY_SIZE(tx_integer); i++) {
+ if (tx_integer[i] == T) {
+ T_group = i % 5;
+ break;
+ }
+ }
+ S = s_values[T_group][is_ccch_comb];
+
+ return (T + 2 * S) * ccch_rach_ratio256 / 256;
+}
+
+static void bts_update_agch_max_queue_length(struct gsm_bts *bts)
+{
+ struct gsm48_system_information_type_3 *si3;
+ int old_max_length = bts->agch_queue.max_length;
+
+ if (!(bts->si_valid & (1<<SYSINFO_TYPE_3)))
+ return;
+
+ si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+
+ bts->agch_queue.max_length =
+ bts_agch_max_queue_length(si3->rach_control.tx_integer,
+ si3->control_channel_desc.ccch_conf);
+
+ if (bts->agch_queue.max_length != old_max_length)
+ LOGP(DRSL, LOGL_INFO, "Updated AGCH max queue length to %d\n",
+ bts->agch_queue.max_length);
+}
+
+#define REQ_REFS_PER_IMM_ASS_REJ 4
+static int store_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej,
+ struct gsm48_req_ref *req_refs,
+ uint8_t *wait_inds,
+ int count)
+{
+ switch (count) {
+ case 0:
+ /* TODO: Warning ? */
+ return 0;
+ default:
+ count = 4;
+ rej->req_ref4 = req_refs[3];
+ rej->wait_ind4 = wait_inds[3];
+ /* fall through */
+ case 3:
+ rej->req_ref3 = req_refs[2];
+ rej->wait_ind3 = wait_inds[2];
+ /* fall through */
+ case 2:
+ rej->req_ref2 = req_refs[1];
+ rej->wait_ind2 = wait_inds[1];
+ /* fall through */
+ case 1:
+ rej->req_ref1 = req_refs[0];
+ rej->wait_ind1 = wait_inds[0];
+ break;
+ }
+
+ switch (count) {
+ case 1:
+ rej->req_ref2 = req_refs[0];
+ rej->wait_ind2 = wait_inds[0];
+ /* fall through */
+ case 2:
+ rej->req_ref3 = req_refs[0];
+ rej->wait_ind3 = wait_inds[0];
+ /* fall through */
+ case 3:
+ rej->req_ref4 = req_refs[0];
+ rej->wait_ind4 = wait_inds[0];
+ /* fall through */
+ default:
+ break;
+ }
+
+ return count;
+}
+
+static int extract_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej,
+ struct gsm48_req_ref *req_refs,
+ uint8_t *wait_inds)
+{
+ int count = 0;
+ req_refs[count] = rej->req_ref1;
+ wait_inds[count] = rej->wait_ind1;
+ count++;
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) {
+ req_refs[count] = rej->req_ref2;
+ wait_inds[count] = rej->wait_ind2;
+ count++;
+ }
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3)) &&
+ memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) {
+ req_refs[count] = rej->req_ref3;
+ wait_inds[count] = rej->wait_ind3;
+ count++;
+ }
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4)) &&
+ memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4)) &&
+ memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) {
+ req_refs[count] = rej->req_ref4;
+ wait_inds[count] = rej->wait_ind4;
+ count++;
+ }
+
+ return count;
+}
+
+static int try_merge_imm_ass_rej(struct gsm48_imm_ass_rej *old_rej,
+ struct gsm48_imm_ass_rej *new_rej)
+{
+ struct gsm48_req_ref req_refs[2 * REQ_REFS_PER_IMM_ASS_REJ];
+ uint8_t wait_inds[2 * REQ_REFS_PER_IMM_ASS_REJ];
+ int count = 0;
+ int stored = 0;
+
+ if (new_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ)
+ return 0;
+ if (old_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ)
+ return 0;
+
+ /* GSM 08.58, 5.7
+ * -> The BTS may combine serveral IMM.ASS.REJ messages
+ * -> Identical request refs in one message may be squeezed
+ *
+ * GSM 04.08, 9.1.20.2
+ * -> Request ref and wait ind are duplicated to fill the message
+ */
+
+ /* Extract all entries */
+ count = extract_imm_ass_rej_refs(old_rej,
+ &req_refs[count], &wait_inds[count]);
+ if (count == REQ_REFS_PER_IMM_ASS_REJ)
+ return 0;
+
+ count += extract_imm_ass_rej_refs(new_rej,
+ &req_refs[count], &wait_inds[count]);
+
+ /* Store entries into old message */
+ stored = store_imm_ass_rej_refs(old_rej,
+ &req_refs[stored], &wait_inds[stored],
+ count);
+ count -= stored;
+ if (count == 0)
+ return 1;
+
+ /* Store remaining entries into new message */
+ stored += store_imm_ass_rej_refs(new_rej,
+ &req_refs[stored], &wait_inds[stored],
+ count);
+ return 0;
+}
+
+int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg)
+{
+ int hard_limit = 100;
+ struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg);
+
+ if (bts->agch_queue.length > hard_limit) {
+ LOGP(DSUM, LOGL_ERROR,
+ "AGCH: too many messages in queue, "
+ "refusing message type %s, length = %d/%d\n",
+ gsm48_rr_msg_name(((struct gsm48_imm_ass *)msgb_l3(msg))->msg_type),
+ bts->agch_queue.length, bts->agch_queue.max_length);
+
+ bts->agch_queue.rejected_msgs++;
+ return -ENOMEM;
+ }
+
+ if (bts->agch_queue.length > 0) {
+ struct msgb *last_msg =
+ llist_entry(bts->agch_queue.queue.prev, struct msgb, list);
+ struct gsm48_imm_ass_rej *last_imm_ass_rej = msgb_l3(last_msg);
+
+ if (try_merge_imm_ass_rej(last_imm_ass_rej, imm_ass_cmd)) {
+ bts->agch_queue.merged_msgs++;
+ msgb_free(msg);
+ return 0;
+ }
+ }
+
+ msgb_enqueue(&bts->agch_queue.queue, msg);
+ bts->agch_queue.length++;
+
+ return 0;
+}
+
+struct msgb *bts_agch_dequeue(struct gsm_bts *bts)
+{
+ struct msgb *msg = msgb_dequeue(&bts->agch_queue.queue);
+ if (!msg)
+ return NULL;
+
+ bts->agch_queue.length--;
+ return msg;
+}
+
+/*
+ * Remove lower prio messages if the queue has grown too long.
+ *
+ * \return 0 iff the number of messages in the queue would fit into the AGCH
+ * reserved part of the CCCH.
+ */
+static void compact_agch_queue(struct gsm_bts *bts)
+{
+ struct msgb *msg, *msg2;
+ int max_len, slope, offs;
+ int level_low = bts->agch_queue.low_level;
+ int level_high = bts->agch_queue.high_level;
+ int level_thres = bts->agch_queue.thresh_level;
+
+ max_len = bts->agch_queue.max_length;
+
+ if (max_len == 0)
+ max_len = 1;
+
+ if (bts->agch_queue.length < max_len * level_thres / 100)
+ return;
+
+ /* p^
+ * 1+ /'''''
+ * | /
+ * | /
+ * 0+---/--+----+--> Q length
+ * low high max_len
+ */
+
+ offs = max_len * level_low / 100;
+ if (level_high > level_low)
+ slope = 0x10000 * 100 / (level_high - level_low);
+ else
+ slope = 0x10000 * max_len; /* p_drop >= 1 if len > offs */
+
+ llist_for_each_entry_safe(msg, msg2, &bts->agch_queue.queue, list) {
+ struct gsm48_imm_ass *imm_ass_cmd = msgb_l3(msg);
+ int p_drop;
+
+ p_drop = (bts->agch_queue.length - offs) * slope / max_len;
+
+ if ((random() & 0xffff) >= p_drop)
+ return;
+
+ llist_del(&msg->list);
+ bts->agch_queue.length--;
+ rsl_tx_delete_ind(bts, (uint8_t *)imm_ass_cmd, msgb_l3len(msg));
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_DELETED);
+ msgb_free(msg);
+
+ bts->agch_queue.dropped_msgs++;
+ }
+ return;
+}
+
+int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt,
+ int is_ag_res)
+{
+ struct msgb *msg = NULL;
+ int rc = 0;
+ int is_empty = 1;
+
+ /* Do queue house keeping.
+ * This needs to be done every time a CCCH message is requested, since
+ * the queue max length is calculated based on the CCCH block rate and
+ * PCH messages also reduce the drain of the AGCH queue.
+ */
+ compact_agch_queue(bts);
+
+ /* Check for paging messages first if this is PCH */
+ if (!is_ag_res)
+ rc = paging_gen_msg(bts->paging_state, out_buf, gt, &is_empty);
+
+ /* Check whether the block may be overwritten */
+ if (!is_empty)
+ return rc;
+
+ msg = bts_agch_dequeue(bts);
+ if (!msg)
+ return rc;
+
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_SENT);
+
+ /* Copy AGCH message */
+ memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg));
+ rc = msgb_l3len(msg);
+ msgb_free(msg);
+
+ if (is_ag_res)
+ bts->agch_queue.agch_msgs++;
+ else
+ bts->agch_queue.pch_msgs++;
+
+ return rc;
+}
+
+int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher)
+{
+ int sup;
+
+ if (rsl_cipher < 1 || rsl_cipher > 8)
+ return -ENOTSUP;
+
+ /* No encryption is always supported */
+ if (rsl_cipher == 1)
+ return 1;
+
+ sup = (1 << (rsl_cipher - 2)) & bts->support.ciphers;
+ return sup > 0;
+}
+
+int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx)
+{
+ return trx->ms_power_control == 1;
+}
+
+struct gsm_time *get_time(struct gsm_bts *bts)
+{
+ return &bts->gsm_time;
+}
+
+int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan,
+ enum gsm48_chan_mode cm)
+{
+ enum gsm_bts_features feature = _NUM_BTS_FEAT;
+
+ /* Before the requested pchan/cm combination can be checked, we need to
+ * convert it to a feature identifier we can check */
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ switch(cm) {
+ case GSM48_CMODE_SPEECH_V1:
+ feature = BTS_FEAT_SPEECH_F_V1;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ feature = BTS_FEAT_SPEECH_F_EFR;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ feature = BTS_FEAT_SPEECH_F_AMR;
+ break;
+ default:
+ /* Invalid speech codec type => Not supported! */
+ return 0;
+ }
+ break;
+
+ case GSM_PCHAN_TCH_H:
+ switch(cm) {
+ case GSM48_CMODE_SPEECH_V1:
+ feature = BTS_FEAT_SPEECH_H_V1;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ feature = BTS_FEAT_SPEECH_H_AMR;
+ break;
+ default:
+ /* Invalid speech codec type => Not supported! */
+ return 0;
+ }
+ break;
+
+ default:
+ LOGP(DRSL, LOGL_ERROR, "BTS %u: unhandled pchan %s when checking mode %s\n",
+ bts->nr, gsm_pchan_name(pchan), gsm48_chan_mode_name(cm));
+ return 0;
+ }
+
+ /* Check if the feature is supported by this BTS */
+ if (gsm_bts_has_feature(bts, feature))
+ return 1;
+
+ return 0;
+}
diff --git a/src/common/bts_ctrl_commands.c b/src/common/bts_ctrl_commands.c
new file mode 100644
index 00000000..4efb4ee3
--- /dev/null
+++ b/src/common/bts_ctrl_commands.c
@@ -0,0 +1,93 @@
+/* Control Interface for osmo-bts */
+
+/* (C) 2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/tx_power.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts.h>
+
+CTRL_CMD_DEFINE(therm_att, "thermal-attenuation");
+static int get_therm_att(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct trx_power_params *tpp = &trx->power_params;
+
+ cmd->reply = talloc_asprintf(cmd, "%d", tpp->thermal_attenuation_mdB);
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_therm_att(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct trx_power_params *tpp = &trx->power_params;
+ int val = atoi(cmd->value);
+
+ printf("set_therm_att(trx=%p, tpp=%p)\n", trx, tpp);
+
+ tpp->thermal_attenuation_mdB = val;
+
+ power_ramp_start(trx, tpp->p_total_cur_mdBm, 0);
+
+ return get_therm_att(cmd, data);
+}
+
+static int verify_therm_att(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ int val = atoi(value);
+
+ /* permit between 0 to 40 dB attenuation */
+ if (val < 0 || val > to_mdB(40))
+ return 1;
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(oml_alert, "oml-alert");
+static int set_oml_alert(struct ctrl_cmd *cmd, void *data)
+{
+ /* Note: we expect signal dispatch to be synchronous */
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, cmd->value);
+
+ cmd->reply = "OK";
+
+ return CTRL_CMD_REPLY;
+}
+
+int bts_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_therm_att);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oml_alert);
+
+ return rc;
+}
diff --git a/src/common/bts_ctrl_lookup.c b/src/common/bts_ctrl_lookup.c
new file mode 100644
index 00000000..f0157e9a
--- /dev/null
+++ b/src/common/bts_ctrl_lookup.c
@@ -0,0 +1,115 @@
+/* Control Interface for osmo-bts */
+
+/* (C) 2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/control_if.h>
+
+extern vector ctrl_node_vec;
+
+/*! \brief control interface lookup function for bsc/bts gsm_data
+ * \param[in] data Private data passed to controlif_setup()
+ * \param[in] vline Vector of the line holding the command string
+ * \param[out] node_type type (CTRL_NODE_) that was determined
+ * \param[out] node_data private dta of node that was determined
+ * \param i Current index into vline, up to which it is parsed
+ */
+static int bts_ctrl_node_lookup(void *data, vector vline, int *node_type,
+ void **node_data, int *i)
+{
+ struct gsm_bts *bts = data;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+ char *token = vector_slot(vline, *i);
+ long num;
+
+ /* TODO: We need to make sure that the following chars are digits
+ * and/or use strtol to check if number conversion was successful
+ * Right now something like net.bts_stats will not work */
+ if (!strcmp(token, "trx")) {
+ if (*node_type != CTRL_NODE_ROOT || !*node_data)
+ goto err_missing;
+ bts = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ trx = gsm_bts_trx_num(bts, num);
+ if (!trx)
+ goto err_missing;
+ *node_data = trx;
+ *node_type = CTRL_NODE_TRX;
+ } else if (!strcmp(token, "ts")) {
+ if (*node_type != CTRL_NODE_TRX || !*node_data)
+ goto err_missing;
+ trx = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ if ((num >= 0) && (num < TRX_NR_TS))
+ ts = &trx->ts[num];
+ if (!ts)
+ goto err_missing;
+ *node_data = ts;
+ *node_type = CTRL_NODE_TS;
+ } else
+ return 0;
+
+ return 1;
+err_missing:
+ return -ENODEV;
+err_index:
+ return -ERANGE;
+}
+
+struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts,
+ const char *bind_addr, uint16_t port)
+{
+ struct ctrl_handle *hdl;
+ int rc = 0;
+
+ hdl = ctrl_interface_setup_dynip(bts, bind_addr, port,
+ bts_ctrl_node_lookup);
+ if (!hdl)
+ return NULL;
+
+ rc = bts_ctrl_cmds_install(bts);
+ if (rc) {
+ /* FIXME: close control interface */
+ return NULL;
+ }
+
+ rc = bts_model_ctrl_cmds_install(bts);
+ if (rc) {
+ /* FIXME: cleanup generic control commands */
+ /* FIXME: close control interface */
+ return NULL;
+ }
+
+ return hdl;
+}
diff --git a/src/common/cbch.c b/src/common/cbch.c
new file mode 100644
index 00000000..c628cb5a
--- /dev/null
+++ b/src/common/cbch.c
@@ -0,0 +1,198 @@
+/* Cell Broadcast routines */
+
+/* (C) 2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_04_12.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/cbch.h>
+#include <osmo-bts/logging.h>
+
+struct smscb_msg {
+ struct llist_head list; /* list in smscb_state.queue */
+
+ uint8_t msg[GSM412_MSG_LEN]; /* message buffer */
+ uint8_t next_seg; /* next segment number */
+ uint8_t num_segs; /* total number of segments */
+};
+
+static int get_smscb_null_block(uint8_t *out)
+{
+ struct gsm412_block_type *block_type = (struct gsm412_block_type *) out;
+
+ block_type->spare = 0;
+ block_type->lpd = 1;
+ block_type->seq_nr = GSM412_SEQ_NULL_MSG;
+ block_type->lb = 0;
+ memset(out+1, GSM_MACBLOCK_PADDING, GSM412_BLOCK_LEN);
+
+ return 0;
+}
+
+/* get the next block of the current CB message */
+static int get_smscb_block(struct gsm_bts *bts, uint8_t *out)
+{
+ int to_copy;
+ struct gsm412_block_type *block_type;
+ struct smscb_msg *msg = bts->smscb_state.cur_msg;
+
+ if (!msg) {
+ /* No message: Send NULL mesage */
+ return get_smscb_null_block(out);
+ }
+ OSMO_ASSERT(msg->next_seg < 4);
+
+ block_type = (struct gsm412_block_type *) out++;
+
+ /* LPD is always 01 */
+ block_type->spare = 0;
+ block_type->lpd = 1;
+
+ /* determine how much data to copy */
+ to_copy = GSM412_MSG_LEN - (msg->next_seg * GSM412_BLOCK_LEN);
+ if (to_copy > GSM412_BLOCK_LEN)
+ to_copy = GSM412_BLOCK_LEN;
+ OSMO_ASSERT(to_copy >= 0);
+
+ /* copy data and increment index */
+ memcpy(out, &msg->msg[msg->next_seg * GSM412_BLOCK_LEN], to_copy);
+
+ /* set + increment sequence number */
+ block_type->seq_nr = msg->next_seg++;
+
+ /* determine if this is the last block */
+ if (block_type->seq_nr + 1 == msg->num_segs)
+ block_type->lb = 1;
+ else
+ block_type->lb = 0;
+
+ if (block_type->lb == 1) {
+ /* remove/release the message memory */
+ talloc_free(bts->smscb_state.cur_msg);
+ bts->smscb_state.cur_msg = NULL;
+ }
+
+ return block_type->lb;
+}
+
+static const uint8_t last_block_rsl2um[4] = {
+ [RSL_CB_CMD_LASTBLOCK_4] = 4,
+ [RSL_CB_CMD_LASTBLOCK_1] = 1,
+ [RSL_CB_CMD_LASTBLOCK_2] = 2,
+ [RSL_CB_CMD_LASTBLOCK_3] = 3,
+};
+
+
+/* incoming SMS broadcast command from RSL */
+int bts_process_smscb_cmd(struct gsm_bts *bts,
+ struct rsl_ie_cb_cmd_type cmd_type,
+ uint8_t msg_len, const uint8_t *msg)
+{
+ struct smscb_msg *scm;
+
+ if (msg_len > sizeof(scm->msg)) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "Cannot process SMSCB of %u bytes (max %zu)\n",
+ msg_len, sizeof(scm->msg));
+ return -EINVAL;
+ }
+
+ scm = talloc_zero_size(bts, sizeof(*scm));
+ if (!scm)
+ return -1;
+
+ /* initialize entire message with default padding */
+ memset(scm->msg, GSM_MACBLOCK_PADDING, sizeof(scm->msg));
+ /* next segment is first segment */
+ scm->next_seg = 0;
+
+ switch (cmd_type.command) {
+ case RSL_CB_CMD_TYPE_NORMAL:
+ case RSL_CB_CMD_TYPE_SCHEDULE:
+ case RSL_CB_CMD_TYPE_NULL:
+ scm->num_segs = last_block_rsl2um[cmd_type.last_block&3];
+ memcpy(scm->msg, msg, msg_len);
+ /* def_bcast is ignored */
+ break;
+ case RSL_CB_CMD_TYPE_DEFAULT:
+ /* use def_bcast, ignore command */
+ /* def_bcast == 0: normal mess */
+ break;
+ }
+
+ llist_add_tail(&scm->list, &bts->smscb_state.queue);
+ /* FIXME: limit queue size and optionally send CBCH LOAD Information (overflow) via RSL */
+
+ return 0;
+}
+
+static struct smscb_msg *select_next_smscb(struct gsm_bts *bts)
+{
+ struct smscb_msg *msg;
+
+ msg = llist_first_entry_or_null(&bts->smscb_state.queue, struct smscb_msg, list);
+ if (!msg) {
+ /* FIXME: send CBCH LOAD Information (underflow) via RSL */
+ return NULL;
+ }
+
+ llist_del(&msg->list);
+
+ return msg;
+}
+
+/* call-back from bts model specific code when it wants to obtain a CBCH
+ * block for a given gsm_time. outbuf must have 23 bytes of space. */
+int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time)
+{
+ uint32_t fn = gsm_gsmtime2fn(g_time);
+ /* According to 05.02 Section 6.5.4 */
+ uint32_t tb = (fn / 51) % 8;
+ int rc = 0;
+
+ /* The multiframes used for the basic cell broadcast channel
+ * shall be those in * which TB = 0,1,2 and 3. The multiframes
+ * used for the extended cell broadcast channel shall be those
+ * in which TB = 4, 5, 6 and 7 */
+
+ /* The SMSCB header shall be sent in the multiframe in which TB
+ * = 0 for the basic, and TB = 4 for the extended cell
+ * broadcast channel. */
+
+ switch (tb) {
+ case 0:
+ /* select a new SMSCB message */
+ bts->smscb_state.cur_msg = select_next_smscb(bts);
+ rc = get_smscb_block(bts, outbuf);
+ break;
+ case 1: case 2: case 3:
+ rc = get_smscb_block(bts, outbuf);
+ break;
+ case 4: case 5: case 6: case 7:
+ /* always send NULL frame in extended CBCH for now */
+ rc = get_smscb_null_block(outbuf);
+ break;
+ }
+
+ return rc;
+}
diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c
new file mode 100644
index 00000000..38e22c95
--- /dev/null
+++ b/src/common/dtx_dl_amr_fsm.c
@@ -0,0 +1,477 @@
+/* DTX DL AMR FSM */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/logging.h>
+
+void dtx_fsm_voice(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_VOICE:
+ case E_FACCH:
+ break;
+ case E_SID_F:
+ osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0);
+ break;
+ case E_SID_U:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ case E_INHIB:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Inexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_sid_f1(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_SID_F:
+/* FIXME: what shall we do if we get SID-FIRST _again_ (twice in a row)?
+ Was observed during testing, let's just ignore it for now */
+ break;
+ case E_SID_U:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_F, 0, 0);
+ break;
+ case E_FIRST:
+ osmo_fsm_inst_state_chg(fi, ST_SID_F2, 0, 0);
+ break;
+ case E_ONSET:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_sid_f2(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+ break;
+ case E_ONSET:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_V_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_F_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_V_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_F_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_VOICE:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_VOICE:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_noinh(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+ break;
+ case E_VOICE:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_SID_U, 0, 0);
+ break;
+ case E_SID_U:
+ case E_SID_F:
+/* FIXME: what shall we do if we get SID-FIRST _after_ sending SID-UPDATE?
+ Was observed during testing, let's just ignore it for now */
+ break;
+ case E_ONSET:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_sid_upd(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_F, 0, 0);
+ break;
+ case E_VOICE:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ case E_INHIB:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_V, 0, 0);
+ break;
+ case E_SID_U:
+ case E_SID_F:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_F_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_facch(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_SID_U:
+ case E_SID_F:
+ case E_FACCH:
+ break;
+ case E_VOICE:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+static struct osmo_fsm_state dtx_dl_amr_fsm_states[] = {
+ /* default state for non-DTX and DTX when SPEECH is in progress */
+ [ST_VOICE] = {
+ .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_VOICE) | X(E_FACCH) | X(E_INHIB),
+ .out_state_mask = X(ST_SID_F1) | X(ST_U_NOINH) | X(ST_F1_INH_V),
+ .name = "Voice",
+ .action = dtx_fsm_voice,
+ },
+ /* SID-FIRST or SID-FIRST-P1 in case of AMR HR:
+ start of silence period (might be interrupted in case of AMR HR) */
+ [ST_SID_F1]= {
+ .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_FACCH) | X(E_FIRST) | X(E_ONSET),
+ .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_SID_F2) | X(ST_ONSET_V),
+ .name = "SID-FIRST (P1)",
+ .action = dtx_fsm_sid_f1,
+ },
+ /* SID-FIRST P2 (only for AMR HR):
+ actual start of silence period in case of AMR HR */
+ [ST_SID_F2]= {
+ .in_event_mask = X(E_COMPL) | X(E_FACCH) | X(E_ONSET),
+ .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_ONSET_V),
+ .name = "SID-FIRST (P2)",
+ .action = dtx_fsm_sid_f2,
+ },
+ /* SID-FIRST Inhibited: incoming SPEECH (only for AMR HR) */
+ [ST_F1_INH_V]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_F1_INH_V_REC),
+ .name = "SID-FIRST (Inh, SPEECH)",
+ .action = dtx_fsm_f1_inh_v,
+ },
+ /* SID-FIRST Inhibited: incoming FACCH frame (only for AMR HR) */
+ [ST_F1_INH_F]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_F1_INH_F_REC),
+ .name = "SID-FIRST (Inh, FACCH)",
+ .action = dtx_fsm_f1_inh_f,
+ },
+ /* SID-UPDATE Inhibited: incoming SPEECH (only for AMR HR) */
+ [ST_U_INH_V]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_U_INH_V_REC),
+ .name = "SID-UPDATE (Inh, SPEECH)",
+ .action = dtx_fsm_u_inh_v,
+ },
+ /* SID-UPDATE Inhibited: incoming FACCH frame (only for AMR HR) */
+ [ST_U_INH_F]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_U_INH_F_REC),
+ .name = "SID-UPDATE (Inh, FACCH)",
+ .action = dtx_fsm_u_inh_f,
+ },
+ /* SID-UPDATE: Inhibited not allowed (only for AMR HR) */
+ [ST_U_NOINH]= {
+ .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F) | X(E_ONSET),
+ .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_SID_U) | X(ST_ONSET_V),
+ .name = "SID-UPDATE (NoInh)",
+ .action = dtx_fsm_u_noinh,
+ },
+ /* SID-FIRST Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the voice that caused it */
+ [ST_F1_INH_V_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_VOICE),
+ .out_state_mask = X(ST_VOICE),
+ .name = "SID-FIRST (Inh, SPEECH, Rec)",
+ .action = dtx_fsm_f1_inh_v_rec,
+ },
+ /* SID-FIRST Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the data that caused it */
+ [ST_F1_INH_F_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_FACCH),
+ .out_state_mask = X(ST_FACCH),
+ .name = "SID-FIRST (Inh, FACCH, Rec)",
+ .action = dtx_fsm_f1_inh_f_rec,
+ },
+ /* SID-UPDATE Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the voice that caused it */
+ [ST_U_INH_V_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_VOICE),
+ .out_state_mask = X(ST_VOICE),
+ .name = "SID-UPDATE (Inh, SPEECH, Rec)",
+ .action = dtx_fsm_u_inh_v_rec,
+ },
+ /* SID-UPDATE Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the data that caused it */
+ [ST_U_INH_F_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_FACCH),
+ .out_state_mask = X(ST_FACCH),
+ .name = "SID-UPDATE (Inh, FACCH, Rec)",
+ .action = dtx_fsm_u_inh_f_rec,
+ },
+ /* Silence period with periodic comfort noise data updates */
+ [ST_SID_U]= {
+ .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_INHIB) | X(E_SID_U) | X(E_SID_F),
+ .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_U_INH_V) | X(ST_U_INH_F) | X(ST_U_NOINH),
+ .name = "SID-UPDATE (AMR/HR)",
+ .action = dtx_fsm_sid_upd,
+ },
+ /* ONSET - end of silent period due to incoming SPEECH frame */
+ [ST_ONSET_V]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_ONSET_V_REC),
+ .name = "ONSET (SPEECH)",
+ .action = dtx_fsm_onset_v,
+ },
+ /* ONSET - end of silent period due to incoming FACCH frame */
+ [ST_ONSET_F]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_ONSET_F_REC),
+ .name = "ONSET (FACCH)",
+ .action = dtx_fsm_onset_f,
+ },
+ /* ONSET recursion in progress:
+ ONSET itself was already sent, now have to send the voice that caused it */
+ [ST_ONSET_V_REC]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_VOICE),
+ .name = "ONSET (SPEECH, Rec)",
+ .action = dtx_fsm_onset_v_rec,
+ },
+ /* ONSET recursion in progress:
+ ONSET itself was already sent, now have to send the data that caused it */
+ [ST_ONSET_F_REC]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_FACCH),
+ .name = "ONSET (FACCH, Rec)",
+ .action = dtx_fsm_onset_f_rec,
+ },
+ /* FACCH sending state */
+ [ST_FACCH]= {
+ .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F),
+ .out_state_mask = X(ST_VOICE) | X(ST_SID_F1),
+ .name = "FACCH",
+ .action = dtx_fsm_facch,
+ },
+};
+
+const struct value_string dtx_dl_amr_fsm_event_names[] = {
+ { E_VOICE, "Voice" },
+ { E_ONSET, "ONSET" },
+ { E_FACCH, "FACCH" },
+ { E_COMPL, "Complete" },
+ { E_FIRST, "FIRST P1->P2" },
+ { E_INHIB, "Inhibit" },
+ { E_SID_F, "SID-FIRST" },
+ { E_SID_U, "SID-UPDATE" },
+ { 0, NULL }
+};
+
+struct osmo_fsm dtx_dl_amr_fsm = {
+ .name = "DTX_DL_AMR_FSM",
+ .states = dtx_dl_amr_fsm_states,
+ .num_states = ARRAY_SIZE(dtx_dl_amr_fsm_states),
+ .event_names = dtx_dl_amr_fsm_event_names,
+ .log_subsys = DL1C,
+};
diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c
new file mode 100644
index 00000000..2d9af783
--- /dev/null
+++ b/src/common/gsm_data_shared.c
@@ -0,0 +1,807 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/statistics.h>
+
+#include <osmo-bts/gsm_data.h>
+
+void gsm_abis_mo_reset(struct gsm_abis_mo *mo)
+{
+ mo->nm_state.operational = NM_OPSTATE_NULL;
+ mo->nm_state.availability = NM_AVSTATE_POWER_OFF;
+}
+
+static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
+ uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3)
+{
+ mo->bts = bts;
+ mo->obj_class = obj_class;
+ mo->obj_inst.bts_nr = p1;
+ mo->obj_inst.trx_nr = p2;
+ mo->obj_inst.ts_nr = p3;
+ gsm_abis_mo_reset(mo);
+}
+
+const struct value_string bts_attribute_names[] = {
+ OSMO_VALUE_STRING(BTS_TYPE_VARIANT),
+ OSMO_VALUE_STRING(BTS_SUB_MODEL),
+ OSMO_VALUE_STRING(TRX_PHY_VERSION),
+ { 0, NULL }
+};
+
+enum bts_attribute str2btsattr(const char *s)
+{
+ return get_string_value(bts_attribute_names, s);
+}
+
+const char *btsatttr2str(enum bts_attribute v)
+{
+ return get_value_string(bts_attribute_names, v);
+}
+
+const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
+ { BTS_UNKNOWN, "unknown" },
+ { BTS_OSMO_LITECELL15, "osmo-bts-lc15" },
+ { BTS_OSMO_OC2G, "osmo-bts-oc2g" },
+ { BTS_OSMO_OCTPHY, "osmo-bts-octphy" },
+ { BTS_OSMO_SYSMO, "osmo-bts-sysmo" },
+ { BTS_OSMO_TRX, "omso-bts-trx" },
+ { BTS_OSMO_VIRTUAL, "omso-bts-virtual" },
+ { BTS_OSMO_OMLDUMMY, "omso-bts-omldummy" },
+ { 0, NULL }
+};
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg)
+{
+ return get_string_value(osmo_bts_variant_names, arg);
+}
+
+const char *btsvariant2str(enum gsm_bts_type_variant v)
+{
+ return get_value_string(osmo_bts_variant_names, v);
+}
+
+const struct value_string gsm_bts_features_descs[] = {
+ { BTS_FEAT_HSCSD, "HSCSD" },
+ { BTS_FEAT_GPRS, "GPRS" },
+ { BTS_FEAT_EGPRS, "EGPRS" },
+ { BTS_FEAT_ECSD, "ECSD" },
+ { BTS_FEAT_HOPPING, "Frequency Hopping" },
+ { BTS_FEAT_MULTI_TSC, "Multi-TSC" },
+ { BTS_FEAT_OML_ALERTS, "OML Alerts" },
+ { BTS_FEAT_AGCH_PCH_PROP, "AGCH/PCH proportional allocation" },
+ { BTS_FEAT_CBCH, "CBCH" },
+ { BTS_FEAT_SPEECH_F_V1, "Fullrate speech V1" },
+ { BTS_FEAT_SPEECH_H_V1, "Halfrate speech V1" },
+ { BTS_FEAT_SPEECH_F_EFR, "Fullrate speech EFR" },
+ { BTS_FEAT_SPEECH_F_AMR, "Fullrate speech AMR" },
+ { BTS_FEAT_SPEECH_H_AMR, "Halfrate speech AMR" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_chreq_descs[] = {
+ { GSM_CHREQ_REASON_EMERG, "emergency call" },
+ { GSM_CHREQ_REASON_PAG, "answer to paging" },
+ { GSM_CHREQ_REASON_CALL, "call re-establishment" },
+ { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" },
+ { GSM_CHREQ_REASON_PDCH, "one phase packet access" },
+ { GSM_CHREQ_REASON_OTHER, "other" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_pchant_names[13] = {
+ { GSM_PCHAN_NONE, "NONE" },
+ { GSM_PCHAN_CCCH, "CCCH" },
+ { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" },
+ { GSM_PCHAN_TCH_F, "TCH/F" },
+ { GSM_PCHAN_TCH_H, "TCH/H" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+ { GSM_PCHAN_PDCH, "PDCH" },
+ { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" },
+ { GSM_PCHAN_UNKNOWN, "UNKNOWN" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_pchant_descs[13] = {
+ { GSM_PCHAN_NONE, "Physical Channel not configured" },
+ { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" },
+ { GSM_PCHAN_CCCH_SDCCH4,
+ "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" },
+ { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" },
+ { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" },
+ { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" },
+ { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" },
+ { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" },
+ { 0, NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+ return get_value_string(gsm_pchant_names, c);
+}
+
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
+{
+ return get_string_value(gsm_pchant_names, name);
+}
+
+/* TODO: move to libosmocore, next to gsm_chan_t_names? */
+const char *gsm_lchant_name(enum gsm_chan_t c)
+{
+ return get_value_string(gsm_chan_t_names, c);
+}
+
+static const struct value_string lchan_s_names[] = {
+ { LCHAN_S_NONE, "NONE" },
+ { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" },
+ { LCHAN_S_ACTIVE, "ACTIVE" },
+ { LCHAN_S_INACTIVE, "INACTIVE" },
+ { LCHAN_S_REL_REQ, "RELEASE REQUESTED" },
+ { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" },
+ { LCHAN_S_BROKEN, "BROKEN UNUSABLE" },
+ { 0, NULL }
+};
+
+const char *gsm_lchans_name(enum gsm_lchan_state s)
+{
+ return get_value_string(lchan_s_names, s);
+}
+
+static const struct value_string chreq_names[] = {
+ { GSM_CHREQ_REASON_EMERG, "EMERGENCY" },
+ { GSM_CHREQ_REASON_PAG, "PAGING" },
+ { GSM_CHREQ_REASON_CALL, "CALL" },
+ { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" },
+ { GSM_CHREQ_REASON_OTHER, "OTHER" },
+ { 0, NULL }
+};
+
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c)
+{
+ return get_value_string(chreq_names, c);
+}
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num)
+{
+ struct gsm_bts *bts;
+
+ if (num >= net->num_bts)
+ return NULL;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (bts->nr == num)
+ return bts;
+ }
+
+ return NULL;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
+ int k;
+
+ if (!trx)
+ return NULL;
+
+ trx->bts = bts;
+ trx->nr = bts->num_trx++;
+ trx->mo.nm_state.administrative = NM_STATE_UNLOCKED;
+
+ gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER,
+ bts->nr, trx->nr, 0xff);
+ gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC,
+ bts->nr, trx->nr, 0xff);
+
+ for (k = 0; k < TRX_NR_TS; k++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[k];
+ int l;
+
+ ts->trx = trx;
+ ts->nr = k;
+ ts->pchan = GSM_PCHAN_NONE;
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ ts->tsc = -1;
+
+ gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
+ bts->nr, trx->nr, ts->nr);
+
+ ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
+ ts->hopping.arfcns.data = ts->hopping.arfcns_data;
+ ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
+ ts->hopping.ma.data = ts->hopping.ma_data;
+
+ for (l = 0; l < TS_MAX_LCHAN; l++) {
+ struct gsm_lchan *lchan;
+ char *name;
+ lchan = &ts->lchan[l];
+
+ lchan->ts = ts;
+ lchan->nr = l;
+ lchan->type = GSM_LCHAN_NONE;
+
+ name = gsm_lchan_name_compute(lchan);
+ lchan->name = talloc_strdup(trx, name);
+ INIT_LLIST_HEAD(&lchan->sapi_cmds);
+ }
+ }
+
+ if (trx->nr != 0)
+ trx->nominal_power = bts->c0->nominal_power;
+
+ llist_add_tail(&trx->list, &bts->trx_list);
+
+ return trx;
+}
+
+
+static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+static const uint8_t bts_cell_timer_default[] =
+ { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 };
+static const struct gprs_rlc_cfg rlc_cfg_default = {
+ .parameter = {
+ [RLC_T3142] = 20,
+ [RLC_T3169] = 5,
+ [RLC_T3191] = 5,
+ [RLC_T3193] = 160, /* 10ms */
+ [RLC_T3195] = 5,
+ [RLC_N3101] = 10,
+ [RLC_N3103] = 4,
+ [RLC_N3105] = 8,
+ [CV_COUNTDOWN] = 15,
+ [T_DL_TBF_EXT] = 250 * 10, /* ms */
+ [T_UL_TBF_EXT] = 250 * 10, /* ms */
+ },
+ .paging = {
+ .repeat_time = 5 * 50, /* ms */
+ .repeat_count = 3,
+ },
+ .cs_mask = 0x1fff,
+ .initial_cs = 2,
+ .initial_mcs = 6,
+};
+
+struct gsm_bts *gsm_bts_alloc(void *ctx, uint8_t bts_num)
+{
+ struct gsm_bts *bts = talloc_zero(ctx, struct gsm_bts);
+ int i;
+
+ if (!bts)
+ return NULL;
+
+ bts->nr = bts_num;
+ bts->num_trx = 0;
+ INIT_LLIST_HEAD(&bts->trx_list);
+ bts->ms_max_power = 15; /* dBm */
+
+ gsm_mo_init(&bts->mo, bts, NM_OC_BTS,
+ bts->nr, 0xff, 0xff);
+ gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER,
+ 0xff, 0xff, 0xff);
+
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+ bts->gprs.nsvc[i].bts = bts;
+ bts->gprs.nsvc[i].id = i;
+ gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC,
+ bts->nr, i, 0xff);
+ }
+ memcpy(&bts->gprs.nse.timer, bts_nse_timer_default,
+ sizeof(bts->gprs.nse.timer));
+ gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE,
+ bts->nr, 0xff, 0xff);
+ memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
+ sizeof(bts->gprs.cell.timer));
+ gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL,
+ bts->nr, 0xff, 0xff);
+ memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default,
+ sizeof(bts->gprs.cell.rlc_cfg));
+
+ /* create our primary TRX. It will be initialized during bts_init() */
+ bts->c0 = gsm_bts_trx_alloc(bts);
+ if (!bts->c0) {
+ talloc_free(bts);
+ return NULL;
+ }
+ bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4;
+
+ bts->rach_b_thresh = -1;
+ bts->rach_ldavg_slots = -1;
+ bts->features.data = &bts->_features_data[0];
+ bts->features.data_len = sizeof(bts->_features_data);
+
+ /* si handling */
+ bts->bcch_change_mark = 1;
+
+ return bts;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num)
+{
+ struct gsm_bts_trx *trx;
+
+ if (num >= bts->num_trx)
+ return NULL;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->nr == num)
+ return trx;
+ }
+
+ return NULL;
+}
+
+static char ts2str[255];
+
+char *gsm_trx_name(const struct gsm_bts_trx *trx)
+{
+ if (!trx)
+ snprintf(ts2str, sizeof(ts2str), "(trx=NULL)");
+ else
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
+ trx->bts->nr, trx->nr);
+
+ return ts2str;
+}
+
+
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts)
+{
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+ return ts2str;
+}
+
+/*! Log timeslot number with full pchan information */
+char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(ts->dyn.pchan_is));
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s"
+ " switching %s -> %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(ts->dyn.pchan_is),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F");
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s"
+ " switching %s -> %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+ : "TCH/F");
+ break;
+ default:
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan));
+ break;
+ }
+
+ return ts2str;
+}
+
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
+
+ return ts2str;
+}
+
+/* obtain the MO structure for a given object instance */
+struct gsm_abis_mo *
+gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_abis_mo *mo = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ mo = &bts->mo;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->mo;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bb_transc.mo;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ mo = &trx->ts[obj_inst->ts_nr].mo;
+ break;
+ case NM_OC_SITE_MANAGER:
+ mo = &bts->site_mgr.mo;
+ break;
+ case NM_OC_GPRS_NSE:
+ mo = &bts->gprs.nse.mo;
+ break;
+ case NM_OC_GPRS_CELL:
+ mo = &bts->gprs.cell.mo;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo;
+ break;
+ }
+ return mo;
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_abis_mo *mo;
+
+ mo = gsm_objclass2mo(bts, obj_class, obj_inst);
+ if (!mo)
+ return NULL;
+
+ return &mo->nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ void *obj = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ obj = bts;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ obj = trx;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ obj = &trx->bb_transc;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ obj = &trx->ts[obj_inst->ts_nr];
+ break;
+ case NM_OC_SITE_MANAGER:
+ obj = &bts->site_mgr;
+ break;
+ case NM_OC_GPRS_NSE:
+ obj = &bts->gprs.nse;
+ break;
+ case NM_OC_GPRS_CELL:
+ obj = &bts->gprs.cell;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+ break;
+ }
+ return obj;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr)
+{
+ uint8_t cbits, chan_nr;
+
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_PDCH:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = RSL_CHAN_OSMO_PDCH >> 3;
+ break;
+ case GSM_PCHAN_TCH_H:
+ OSMO_ASSERT(lchan_nr < 2);
+ cbits = 0x02;
+ cbits += lchan_nr;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /*
+ * As a special hack for BCCH, lchan_nr == 4 may be passed
+ * here. This should never be sent in an RSL message.
+ * See osmo-bts-xxx/oml.c:opstart_compl().
+ */
+ if (lchan_nr == CCCH_LCHAN)
+ cbits = 0x10; /* BCCH */
+ else {
+ OSMO_ASSERT(lchan_nr < 4);
+ cbits = 0x04;
+ cbits += lchan_nr;
+ }
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ OSMO_ASSERT(lchan_nr < 8);
+ cbits = 0x08;
+ cbits += lchan_nr;
+ break;
+ case GSM_PCHAN_CCCH:
+ default:
+ /* OSMO_ASSERT(lchan_nr == 0);
+ * FIXME: On octphy and litecell, we hit above assertion (see
+ * Max's comment at https://gerrit.osmocom.org/589 ); disabled
+ * for BTS until this is clarified; remove the #ifdef when it
+ * is fixed. Tracked in OS#2906.
+ */
+#pragma message "fix caller that passes lchan_nr != 0"
+ cbits = 0x10;
+ break;
+ }
+
+ chan_nr = (cbits << 3) | (ts_nr & 0x7);
+
+ return chan_nr;
+}
+
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ /* Return chan_nr reflecting the current TS pchan, either a standard TCH kind, or the
+ * nonstandard value reflecting PDCH for Osmocom style dyn TS. */
+ return gsm_lchan_as_pchan2chan_nr(lchan,
+ lchan->ts->dyn.pchan_is);
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* For ip.access style dyn TS, we always want to use the chan_nr as if it was TCH/F.
+ * We're using custom PDCH ACT and DEACT messages that use the usual chan_nr values. */
+ return gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F);
+ default:
+ return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr);
+ }
+}
+
+uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan,
+ enum gsm_phys_chan_config as_pchan)
+{
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && as_pchan == GSM_PCHAN_PDCH)
+ return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK);
+ return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr);
+}
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts)
+{
+ struct gsm_lchan *lchan = NULL;
+ struct gsm_bts_trx *trx = bts->c0;
+
+ if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ lchan = &trx->ts[0].lchan[2];
+ else {
+ int i;
+ for (i = 0; i < 8; i++) {
+ if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) {
+ lchan = &trx->ts[i].lchan[2];
+ break;
+ }
+ }
+ }
+
+ return lchan;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ int *rc)
+{
+ uint8_t ts_nr = chan_nr & 0x07;
+ uint8_t cbits = chan_nr >> 3;
+ uint8_t lch_idx;
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ bool ok = true;
+
+ if (rc)
+ *rc = -EINVAL;
+
+ if (cbits == 0x01) {
+ lch_idx = 0; /* TCH/F */
+ if (ts->pchan != GSM_PCHAN_TCH_F &&
+ ts->pchan != GSM_PCHAN_PDCH &&
+ ts->pchan != GSM_PCHAN_TCH_F_PDCH &&
+ ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ ok = false;
+ } else if ((cbits & 0x1e) == 0x02) {
+ lch_idx = cbits & 0x1; /* TCH/H */
+ if (ts->pchan != GSM_PCHAN_TCH_H &&
+ ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ ok = false;
+ } else if ((cbits & 0x1c) == 0x04) {
+ lch_idx = cbits & 0x3; /* SDCCH/4 */
+ if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ ok = false;
+ } else if ((cbits & 0x18) == 0x08) {
+ lch_idx = cbits & 0x7; /* SDCCH/8 */
+ if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C &&
+ ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH)
+ ok = false;
+ } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+ lch_idx = 0;
+ if (ts->pchan != GSM_PCHAN_CCCH &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ ok = false;
+ /* FIXME: we should not return first sdcch4 !!! */
+ } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) {
+ lch_idx = 0;
+ if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ ok = false;
+ } else
+ return NULL;
+
+ if (rc && ok)
+ *rc = 0;
+
+ return &ts->lchan[lch_idx];
+}
+
+static const uint8_t subslots_per_pchan[] = {
+ [GSM_PCHAN_NONE] = 0,
+ [GSM_PCHAN_CCCH] = 0,
+ [GSM_PCHAN_PDCH] = 0,
+ [GSM_PCHAN_CCCH_SDCCH4] = 4,
+ [GSM_PCHAN_TCH_F] = 1,
+ [GSM_PCHAN_TCH_H] = 2,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = 8,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8,
+ /*
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+ * part of this, those TS are handled according to their dynamic state.
+ */
+};
+
+/*! Return the actual pchan type, also heeding dynamic TS. */
+enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ else
+ return GSM_PCHAN_TCH_F;
+ default:
+ return ts->pchan;
+ }
+}
+
+/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of
+ * logical channels available in the timeslot. */
+uint8_t ts_subslots(struct gsm_bts_trx_ts *ts)
+{
+ return subslots_per_pchan[ts_pchan(ts)];
+}
+
+static bool pchan_is_tch(enum gsm_phys_chan_config pchan)
+{
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool ts_is_tch(struct gsm_bts_trx_ts *ts)
+{
+ return pchan_is_tch(ts_pchan(ts));
+}
+
+const char *gsm_trx_unit_id(struct gsm_bts_trx *trx)
+{
+ static char buf[23];
+
+ snprintf(buf, sizeof(buf), "%u/%u/%u", trx->bts->ip_access.site_id,
+ trx->bts->ip_access.bts_id, trx->nr);
+ return buf;
+}
+
+const struct value_string lchan_ciph_state_names[] = {
+ { LCHAN_CIPH_NONE, "NONE" },
+ { LCHAN_CIPH_RX_REQ, "RX_REQ" },
+ { LCHAN_CIPH_RX_CONF, "RX_CONF" },
+ { LCHAN_CIPH_RXTX_REQ, "RXTX_REQ" },
+ { LCHAN_CIPH_RX_CONF_TX_REQ, "RX_CONF_TX_REQ" },
+ { LCHAN_CIPH_RXTX_CONF, "RXTX_CONF" },
+ { 0, NULL }
+};
diff --git a/src/common/handover.c b/src/common/handover.c
new file mode 100644
index 00000000..54b131ff
--- /dev/null
+++ b/src/common/handover.c
@@ -0,0 +1,164 @@
+/* Paging message encoding + queue management */
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ * Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+/* Transmit a handover related PHYS INFO on given lchan */
+static int ho_tx_phys_info(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(1024, 128, "PHYS INFO");
+ struct gsm48_hdr *gh;
+
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DHO, LOGL_INFO,
+ "%s Sending PHYSICAL INFORMATION to MS.\n",
+ gsm_lchan_name(lchan));
+
+ /* Build RSL UNITDATA REQUEST message with 04.08 PHYS INFO */
+ msg->l3h = msg->data;
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_HANDO_INFO;
+ msgb_put_u8(msg, lchan->rqd_ta);
+
+ rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan),
+ 0x00, 0);
+
+ lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+ return 0;
+}
+
+/* timer call-back for T3105 (handover PHYS INFO re-transmit) */
+static void ho_t3105_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ LOGP(DHO, LOGL_INFO, "%s T3105 timeout (%d resends left)\n",
+ gsm_lchan_name(lchan), bts->ny1 - lchan->ho.phys_info_count);
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DHO, LOGL_NOTICE,
+ "%s is in not active. It is in state %s. Ignoring\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state));
+ return;
+ }
+
+ if (lchan->ho.phys_info_count >= bts->ny1) {
+ /* HO Abort */
+ LOGP(DHO, LOGL_NOTICE, "%s NY1 reached, sending CONNection "
+ "FAILure to BSC.\n", gsm_lchan_name(lchan));
+ rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL);
+ return;
+ }
+
+ ho_tx_phys_info(lchan);
+ lchan->ho.phys_info_count++;
+ osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000);
+}
+
+/* received random access on dedicated channel */
+void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* Ignore invalid handover ref */
+ if (lchan->ho.ref != ra) {
+ LOGP(DHO, LOGL_INFO, "%s RACH on dedicated channel received, but "
+ "ra=0x%02x != expected ref=0x%02x. (This is no bug)\n",
+ gsm_lchan_name(lchan), ra, lchan->ho.ref);
+ return;
+ }
+
+ /* Ignore handover on channels other than DCCH and SACCH */
+ if (lchan->type != GSM_LCHAN_SDCCH && lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F) {
+ LOGP(DHO, LOGL_ERROR, "%s handover RACH received on %s?!\n",
+ gsm_lchan_name(lchan), gsm_lchant_name(lchan->type));
+ return;
+ }
+
+ LOGP(DHO, LOGL_NOTICE,
+ "%s RACH on dedicated channel type %s received with TA=%u, ref=%u\n",
+ gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), acc_delay, ra);
+
+ /* Set timing advance */
+ lchan->rqd_ta = acc_delay;
+
+ /* Stop handover detection, wait for valid frame */
+ lchan->ho.active = HANDOVER_WAIT_FRAME;
+ if (l1sap_chan_modify(lchan->ts->trx, gsm_lchan2chan_nr(lchan)) != 0) {
+ LOGP(DHO, LOGL_ERROR,
+ "%s failed to modify channel after handover\n",
+ gsm_lchan_name(lchan));
+ rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL);
+ return;
+ }
+
+ /* Send HANDover DETect to BSC */
+ rsl_tx_hando_det(lchan, &lchan->rqd_ta);
+
+ /* Send PHYS INFO */
+ lchan->ho.phys_info_count = 1;
+ ho_tx_phys_info(lchan);
+
+ /* Start T3105 */
+ LOGP(DHO, LOGL_DEBUG,
+ "%s Starting T3105 with %u ms\n",
+ gsm_lchan_name(lchan), bts->t3105_ms);
+ lchan->ho.t3105.cb = ho_t3105_cb;
+ lchan->ho.t3105.data = lchan;
+ osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000);
+}
+
+/* received frist valid data frame on dedicated channel */
+void handover_frame(struct gsm_lchan *lchan)
+{
+ LOGP(DHO, LOGL_INFO,
+ "%s First valid frame detected\n", gsm_lchan_name(lchan));
+ handover_reset(lchan);
+}
+
+/* release handover state */
+void handover_reset(struct gsm_lchan *lchan)
+{
+ /* Stop T3105 */
+ osmo_timer_del(&lchan->ho.t3105);
+
+ /* Handover process is done */
+ lchan->ho.active = HANDOVER_NONE;
+}
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
new file mode 100644
index 00000000..dba08dfb
--- /dev/null
+++ b/src/common/l1sap.c
@@ -0,0 +1,1549 @@
+/* L1 SAP primitives */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/l1sap.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/power_control.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/cbch.h>
+
+
+#define CB_FCCH -1
+#define CB_SCH -2
+#define CB_BCCH -3
+#define CB_IDLE -4
+
+/* according to TS 05.02 Clause 7 Table 3 of 9 an Figure 8a */
+static const int ccch_block_table[51] = {
+ CB_FCCH, CB_SCH,/* 0..1 */
+ CB_BCCH, CB_BCCH, CB_BCCH, CB_BCCH, /* 2..5: BCCH */
+ 0, 0, 0, 0, /* 6..9: B0 */
+ CB_FCCH, CB_SCH,/* 10..11 */
+ 1, 1, 1, 1, /* 12..15: B1 */
+ 2, 2, 2, 2, /* 16..19: B2 */
+ CB_FCCH, CB_SCH,/* 20..21 */
+ 3, 3, 3, 3, /* 22..25: B3 */
+ 4, 4, 4, 4, /* 26..29: B4 */
+ CB_FCCH, CB_SCH,/* 30..31 */
+ 5, 5, 5, 5, /* 32..35: B5 */
+ 6, 6, 6, 6, /* 36..39: B6 */
+ CB_FCCH, CB_SCH,/* 40..41 */
+ 7, 7, 7, 7, /* 42..45: B7 */
+ 8, 8, 8, 8, /* 46..49: B8 */
+ -4 /* 50: Idle */
+};
+
+/* determine the CCCH block number based on the frame number */
+unsigned int l1sap_fn2ccch_block(uint32_t fn)
+{
+ int rc = ccch_block_table[fn%51];
+ /* if FN is negative, we were called for something that's not CCCH! */
+ OSMO_ASSERT(rc >= 0);
+ return rc;
+}
+
+struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx,
+ unsigned int chan_nr)
+{
+ unsigned int tn, ss;
+
+ tn = L1SAP_CHAN2TS(chan_nr);
+ OSMO_ASSERT(tn < ARRAY_SIZE(trx->ts));
+
+ if (L1SAP_IS_CHAN_CBCH(chan_nr))
+ ss = 2; /* CBCH is always on sub-slot 2 */
+ else
+ ss = l1sap_chan2ss(chan_nr);
+ OSMO_ASSERT(ss < ARRAY_SIZE(trx->ts[tn].lchan));
+
+ return &trx->ts[tn].lchan[ss];
+}
+
+static struct gsm_lchan *
+get_active_lchan_by_chan_nr(struct gsm_bts_trx *trx, unsigned int chan_nr)
+{
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ if (lchan && lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DL1P, LOGL_NOTICE, "%s: assuming active lchan, but "
+ "state is %s\n", gsm_lchan_name(lchan),
+ gsm_lchans_name(lchan->state));
+ return NULL;
+ }
+ return lchan;
+}
+
+static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
+static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan)
+{
+ uint32_t samples_passed, r;
+
+ if (lchan->tch.last_fn != LCHAN_FN_DUMMY) {
+ /* 12/13 frames usable for audio in TCH,
+ 160 samples per RTP packet,
+ 1 RTP packet per 4 frames */
+ samples_passed = (fn - lchan->tch.last_fn) * 12 * 160 / (13 * 4);
+ /* round number of samples to the nearest multiple of
+ GSM_RTP_DURATION */
+ r = samples_passed + GSM_RTP_DURATION / 2;
+ r -= r % GSM_RTP_DURATION;
+
+ if (r != GSM_RTP_DURATION)
+ LOGP(DRTP, LOGL_ERROR, "RTP clock out of sync with lower layer:"
+ " %"PRIu32" vs %d (%"PRIu32"->%"PRIu32")\n",
+ r, GSM_RTP_DURATION, lchan->tch.last_fn, fn);
+ }
+ return GSM_RTP_DURATION;
+}
+
+/*! limit number of queue entries to %u; drops any surplus messages */
+static void queue_limit_to(const char *prefix, struct llist_head *queue, unsigned int limit)
+{
+ unsigned int count = llist_count(queue);
+
+ if (count > limit)
+ LOGP(DL1P, LOGL_NOTICE, "%s: freeing %d queued frames\n", prefix, count-limit);
+ while (count > limit) {
+ struct msgb *tmp = msgb_dequeue(queue);
+ msgb_free(tmp);
+ count--;
+ }
+}
+
+/* allocate a msgb containing a osmo_phsap_prim + optional l2 data
+ * in order to wrap femtobts header arround l2 data, there must be enough space
+ * in front and behind data pointer */
+struct msgb *l1sap_msgb_alloc(unsigned int l2_len)
+{
+ int headroom = 128;
+ int size = headroom + sizeof(struct osmo_phsap_prim) + l2_len;
+ struct msgb *msg = msgb_alloc_headroom(size, headroom, "l1sap_prim");
+
+ if (!msg)
+ return NULL;
+
+ msg->l1h = msgb_put(msg, sizeof(struct osmo_phsap_prim));
+
+ return msg;
+}
+
+int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg,
+ struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn,
+ uint16_t ber10k, int16_t lqual_cb)
+{
+ struct osmo_phsap_prim *l1sap;
+
+ LOGP(DL1P, LOGL_DEBUG, "%s Rx -> RTP: %s\n",
+ gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len));
+
+ rmsg->l2h = rmsg->data;
+ rmsg->l1h = msgb_push(rmsg, sizeof(*l1sap));
+ l1sap = msgb_l1sap_prim(rmsg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION,
+ rmsg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ l1sap->u.tch.ber10k = ber10k;
+ l1sap->u.tch.lqual_cb = lqual_cb;
+
+ return l1sap_up(trx, l1sap);
+}
+
+static int l1sap_tx_ciph_req(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ uint8_t downlink, uint8_t uplink)
+{
+ struct osmo_phsap_prim l1sap_ciph;
+
+ osmo_prim_init(&l1sap_ciph.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_REQUEST, NULL);
+ l1sap_ciph.u.info.type = PRIM_INFO_ACT_CIPH;
+ l1sap_ciph.u.info.u.ciph_req.chan_nr = chan_nr;
+ l1sap_ciph.u.info.u.ciph_req.downlink = downlink;
+ l1sap_ciph.u.info.u.ciph_req.uplink = uplink;
+
+ return l1sap_down(trx, &l1sap_ciph);
+}
+
+
+/* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable
+ * uni-directional de-cryption on the uplink. We need this ugly layering
+ * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD)
+ * to this point in L1 */
+static int check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan,
+ uint8_t chan_nr)
+{
+ uint8_t n_s;
+
+ /* only do this if we are in the right state */
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_NONE:
+ case LCHAN_CIPH_RX_REQ:
+ break;
+ default:
+ return 0;
+ }
+
+ /* First byte (Address Field) of LAPDm header) */
+ if (msg->data[0] != 0x03)
+ return 0;
+ /* First byte (protocol discriminator) of RR */
+ if ((msg->data[3] & 0xF) != GSM48_PDISC_RR)
+ return 0;
+ /* 2nd byte (msg type) of RR */
+ if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD)
+ return 0;
+
+ /* Remember N(S) + 1 to find the first ciphered frame */
+ n_s = (msg->data[1] >> 1) & 0x7;
+ lchan->ciph_ns = (n_s + 1) % 8;
+
+ l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 0, 1);
+
+ return 1;
+}
+
+/* public helpers for the test */
+int bts_check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan,
+ uint8_t chan_nr)
+{
+ return check_for_ciph_cmd(msg, lchan, chan_nr);
+}
+
+struct gsmtap_inst *gsmtap = NULL;
+uint32_t gsmtap_sapi_mask = 0;
+uint8_t gsmtap_sapi_acch = 0;
+
+const struct value_string gsmtap_sapi_names[] = {
+ { GSMTAP_CHANNEL_BCCH, "BCCH" },
+ { GSMTAP_CHANNEL_CCCH, "CCCH" },
+ { GSMTAP_CHANNEL_RACH, "RACH" },
+ { GSMTAP_CHANNEL_AGCH, "AGCH" },
+ { GSMTAP_CHANNEL_PCH, "PCH" },
+ { GSMTAP_CHANNEL_SDCCH, "SDCCH" },
+ { GSMTAP_CHANNEL_TCH_F, "TCH/F" },
+ { GSMTAP_CHANNEL_TCH_H, "TCH/H" },
+ { GSMTAP_CHANNEL_PACCH, "PACCH" },
+ { GSMTAP_CHANNEL_PDCH, "PDTCH" },
+ { GSMTAP_CHANNEL_PTCCH, "PTCCH" },
+ { GSMTAP_CHANNEL_CBCH51,"CBCH" },
+ { GSMTAP_CHANNEL_ACCH, "SACCH" },
+ { 0, NULL }
+};
+
+/* send primitive as gsmtap */
+static int gsmtap_ph_data(struct osmo_phsap_prim *l1sap, uint8_t *chan_type,
+ uint8_t *ss, uint32_t fn, uint8_t **data, unsigned int *len,
+ uint8_t num_agch)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ uint8_t chan_nr, link_id;
+
+ *data = msgb_l2(msg);
+ *len = msgb_l2len(msg);
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+
+ if (L1SAP_IS_CHAN_TCHF(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_TCH_F;
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ *ss = L1SAP_CHAN2SS_TCHH(chan_nr);
+ *chan_type = GSMTAP_CHANNEL_TCH_H;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ *chan_type = GSMTAP_CHANNEL_SDCCH;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ *chan_type = GSMTAP_CHANNEL_SDCCH;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_BCCH;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ if (l1sap_fn2ccch_block(fn) >= num_agch)
+ *chan_type = GSMTAP_CHANNEL_PCH;
+ else
+ *chan_type = GSMTAP_CHANNEL_AGCH;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_CBCH51;
+ } else if (L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_PDTCH;
+ }
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ *chan_type |= GSMTAP_CHANNEL_ACCH;
+
+ return 0;
+}
+
+static int gsmtap_pdch(struct osmo_phsap_prim *l1sap, uint8_t *chan_type,
+ uint8_t *ss, uint32_t fn, uint8_t **data, unsigned int *len)
+{
+ struct msgb *msg = l1sap->oph.msg;
+
+ *data = msgb_l2(msg);
+ *len = msgb_l2len(msg);
+
+ if (L1SAP_IS_PTCCH(fn)) {
+ *chan_type = GSMTAP_CHANNEL_PTCCH;
+ *ss = L1SAP_FN2PTCCHBLOCK(fn);
+ if (l1sap->oph.primitive == PRIM_OP_INDICATION) {
+ OSMO_ASSERT(len > 0);
+ if ((*data[0]) == 7)
+ return -EINVAL;
+ (*data)++;
+ (*len)--;
+ }
+ } else
+ *chan_type = GSMTAP_CHANNEL_PACCH;
+
+ return 0;
+}
+
+static int gsmtap_ph_rach(struct osmo_phsap_prim *l1sap, uint8_t *chan_type,
+ uint8_t *tn, uint8_t *ss, uint32_t *fn, uint8_t **data, unsigned int *len)
+{
+ uint8_t chan_nr;
+
+ *chan_type = GSMTAP_CHANNEL_RACH;
+ *fn = l1sap->u.rach_ind.fn;
+ *tn = L1SAP_CHAN2TS(l1sap->u.rach_ind.chan_nr);
+ chan_nr = l1sap->u.rach_ind.chan_nr;
+ if (L1SAP_IS_CHAN_TCHH(chan_nr))
+ *ss = L1SAP_CHAN2SS_TCHH(chan_nr);
+ else if (L1SAP_IS_CHAN_SDCCH4(chan_nr))
+ *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ else if (L1SAP_IS_CHAN_SDCCH8(chan_nr))
+ *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ *data = (uint8_t *)&l1sap->u.rach_ind.ra;
+ *len = 1;
+
+ return 0;
+}
+
+/* Paging Request 1 with "no identity" content, i.e. empty/dummy paging */
+static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = {
+ 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b };
+
+static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int len)
+{
+ switch (chan_type) {
+ case GSMTAP_CHANNEL_AGCH:
+ if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN))
+ return true;
+ break;
+ case GSMTAP_CHANNEL_PCH:
+ if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN))
+ return true;
+ break;
+ /* don't use 'default' case here as the above only conditionally return true */
+ }
+ return false;
+}
+
+static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t *data;
+ unsigned int len;
+ uint8_t chan_type = 0, tn = 0, ss = 0;
+ uint32_t fn;
+ uint16_t uplink = GSMTAP_ARFCN_F_UPLINK;
+ int rc;
+
+ if (!gsmtap)
+ return 0;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ uplink = 0;
+ /* fall through */
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
+ fn = l1sap->u.data.fn;
+ tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr);
+ if (ts_is_pdch(&trx->ts[tn]))
+ rc = gsmtap_pdch(l1sap, &chan_type, &ss, fn, &data,
+ &len);
+ else
+ rc = gsmtap_ph_data(l1sap, &chan_type, &ss, fn, &data,
+ &len, num_agch(trx, "GSMTAP"));
+ break;
+ case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+ rc = gsmtap_ph_rach(l1sap, &chan_type, &tn, &ss, &fn, &data,
+ &len);
+ break;
+ default:
+ rc = -ENOTSUP;
+ }
+
+ if (rc)
+ return rc;
+
+ if (len == 0)
+ return 0;
+ if ((chan_type & GSMTAP_CHANNEL_ACCH)) {
+ if (!gsmtap_sapi_acch)
+ return 0;
+ } else {
+ if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask))
+ return 0;
+ }
+
+ /* don't log fill frames via GSMTAP; they serve no purpose other than
+ * to clog up your logs */
+ if (is_fill_frame(chan_type, data, len))
+ return 0;
+
+ gsmtap_send(gsmtap, trx->arfcn | uplink, tn, chan_type, ss, fn, 0, 0,
+ data, len);
+
+ return 0;
+}
+
+/* Calculate the number of RACH slots that expire in a certain GSM frame
+ * See also 3GPP TS 05.02 Clause 7 Table 5 of 9 */
+static unsigned int calc_exprd_rach_frames(struct gsm_bts *bts, uint32_t fn)
+{
+ int rach_frames_expired = 0;
+ uint8_t ccch_conf;
+ struct gsm48_system_information_type_3 *si3;
+ unsigned int blockno;
+
+ si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ ccch_conf = si3->control_channel_desc.ccch_conf;
+
+ if (ccch_conf == RSL_BCCH_CCCH_CONF_1_C) {
+ /* It is possible to combine a CCCH with an SDCCH4, in this
+ * case the CCCH will have to share the available frames with
+ * the other channel, this results in a limited number of
+ * available rach slots */
+ blockno = fn % 51;
+ if (blockno == 4 || blockno == 5
+ || (blockno >= 15 && blockno <= 36) || blockno == 45
+ || blockno == 46)
+ rach_frames_expired = 1;
+ } else {
+ /* It is possible to have multiple CCCH channels on
+ * different physical channels (large cells), this
+ * also multiplies the available/expired RACH channels.
+ * See also TS 04.08, Chapter 10.5.2.11, table 10.29 */
+ if (ccch_conf == RSL_BCCH_CCCH_CONF_2_NC)
+ rach_frames_expired = 2;
+ else if (ccch_conf == RSL_BCCH_CCCH_CONF_3_NC)
+ rach_frames_expired = 3;
+ else if (ccch_conf == RSL_BCCH_CCCH_CONF_4_NC)
+ rach_frames_expired = 4;
+ else
+ rach_frames_expired = 1;
+ }
+
+ /* Each Frame has room for 4 RACH slots, since RACH
+ * slots are short enough to fit into a single radio
+ * burst, so we need to multiply the final result by 4 */
+ return rach_frames_expired * 4;
+}
+
+/* time information received from bts model */
+static int l1sap_info_time_ind(struct gsm_bts *bts,
+ struct osmo_phsap_prim *l1sap,
+ struct info_time_ind_param *info_time_ind)
+{
+ int frames_expired;
+
+ DEBUGPFN(DL1P, info_time_ind->fn, "Rx MPH_INFO time ind\n");
+
+ /* Calculate and check frame difference */
+ frames_expired = info_time_ind->fn - bts->gsm_time.fn;
+ if (frames_expired > 1) {
+ if (bts->gsm_time.fn)
+ LOGPFN(DL1P, LOGL_ERROR, info_time_ind->fn,
+ "Invalid condition detected: Frame difference is %"PRIu32"-%"PRIu32"=%d > 1!\n",
+ info_time_ind->fn, bts->gsm_time.fn, frames_expired);
+ }
+
+ /* Update our data structures with the current GSM time */
+ gsm_fn2gsmtime(&bts->gsm_time, info_time_ind->fn);
+
+ /* Update time on PCU interface */
+ pcu_tx_time_ind(info_time_ind->fn);
+
+ /* increment number of RACH slots that have passed by since the
+ * last time indication */
+ bts->load.rach.total +=
+ calc_exprd_rach_frames(bts, info_time_ind->fn) * frames_expired;
+
+ return 0;
+}
+
+static inline void set_ms_to_data(struct gsm_lchan *lchan, int16_t data, bool set_ms_to)
+{
+ if (!lchan)
+ return;
+
+ if (data + 63 > 255) { /* According to 3GPP TS 48.058 §9.3.37 Timing Offset field cannot exceed 255 */
+ LOGP(DL1P, LOGL_ERROR, "Attempting to set invalid Timing Offset value %d (MS TO = %u)!\n",
+ data, set_ms_to);
+ return;
+ }
+
+ if (set_ms_to) {
+ lchan->ms_t_offs = data + 63;
+ lchan->p_offs = -1;
+ } else {
+ lchan->p_offs = data + 63;
+ lchan->ms_t_offs = -1;
+ }
+}
+
+/* measurement information received from bts model */
+static int l1sap_info_meas_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap,
+ struct info_meas_ind_param *info_meas_ind)
+{
+ struct bts_ul_meas ulm;
+ struct gsm_lchan *lchan;
+
+ lchan = get_active_lchan_by_chan_nr(trx, info_meas_ind->chan_nr);
+ if (!lchan) {
+ LOGPFN(DL1P, LOGL_ERROR, info_meas_ind->fn,
+ "No lchan for MPH INFO MEAS IND (chan_nr=%s)\n", rsl_chan_nr_str(info_meas_ind->chan_nr));
+ return 0;
+ }
+
+ DEBUGPFN(DL1P, info_meas_ind->fn,
+ "%s MPH_INFO meas ind, ta_offs_256bits=%d, ber10k=%d, inv_rssi=%u\n",
+ gsm_lchan_name(lchan), info_meas_ind->ta_offs_256bits,
+ info_meas_ind->ber10k, info_meas_ind->inv_rssi);
+
+ /* in the GPRS case we are not interested in measurement
+ * processing. The PCU will take care of it */
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ return 0;
+
+ memset(&ulm, 0, sizeof(ulm));
+ ulm.ta_offs_256bits = info_meas_ind->ta_offs_256bits;
+ ulm.ber10k = info_meas_ind->ber10k;
+ ulm.inv_rssi = info_meas_ind->inv_rssi;
+ ulm.is_sub = info_meas_ind->is_sub;
+
+ /* we assume that symbol period is 1 bit: */
+ set_ms_to_data(lchan, info_meas_ind->ta_offs_256bits / 256, true);
+
+ lchan_meas_process_measurement(lchan, &ulm, info_meas_ind->fn);
+
+ return 0;
+}
+
+/* any L1 MPH_INFO indication prim recevied from bts model */
+static int l1sap_mph_info_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
+{
+ int rc = 0;
+
+ switch (info->type) {
+ case PRIM_INFO_TIME:
+ if (trx != trx->bts->c0) {
+ LOGPFN(DL1P, LOGL_NOTICE, info->u.time_ind.fn,
+ "BTS model is sending us PRIM_INFO_TIME for TRX %u, please fix it\n",
+ trx->nr);
+ rc = -1;
+ } else
+ rc = l1sap_info_time_ind(trx->bts, l1sap,
+ &info->u.time_ind);
+ break;
+ case PRIM_INFO_MEAS:
+ rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n",
+ info->type);
+ break;
+ }
+
+ return rc;
+}
+
+/* activation confirm received from bts model */
+static int l1sap_info_act_cnf(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap,
+ struct info_act_cnf_param *info_act_cnf)
+{
+ struct gsm_lchan *lchan;
+
+ LOGP(DL1C, LOGL_INFO, "activate confirm chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr);
+
+ lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr);
+
+ rsl_tx_chan_act_acknack(lchan, info_act_cnf->cause);
+
+ /* During PDCH ACT, this is where we know that the PCU is done
+ * activating a PDCH, and PDCH switchover is complete. See
+ * rsl_rx_dyn_pdch() */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && (lchan->ts->flags & TS_F_PDCH_ACT_PENDING))
+ ipacc_dyn_pdch_complete(lchan->ts,
+ info_act_cnf->cause? -EIO : 0);
+
+ return 0;
+}
+
+/* activation confirm received from bts model */
+static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap,
+ struct info_act_cnf_param *info_act_cnf)
+{
+ struct gsm_lchan *lchan;
+
+ LOGP(DL1C, LOGL_INFO, "deactivate confirm chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr);
+
+ lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr);
+
+ rsl_tx_rf_rel_ack(lchan);
+
+ /* During PDCH DEACT, this marks the deactivation of the PDTCH as
+ * requested by the PCU. Next up, we disconnect the TS completely and
+ * call back to cb_ts_disconnected(). See rsl_rx_dyn_pdch(). */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && (lchan->ts->flags & TS_F_PDCH_DEACT_PENDING))
+ bts_model_ts_disconnect(lchan->ts);
+
+ return 0;
+}
+
+/* any L1 MPH_INFO confirm prim recevied from bts model */
+static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
+{
+ int rc = 0;
+
+ switch (info->type) {
+ case PRIM_INFO_ACTIVATE:
+ rc = l1sap_info_act_cnf(trx, l1sap, &info->u.act_cnf);
+ break;
+ case PRIM_INFO_DEACTIVATE:
+ rc = l1sap_info_rel_cnf(trx, l1sap, &info->u.act_cnf);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH_INFO cnf type %d\n",
+ info->type);
+ break;
+ }
+
+ return rc;
+}
+
+/*! handling for PDTCH loopback mode, used for BER testing
+ * \param[in] lchan logical channel on which we operate
+ * \param[in] rts_ind PH-RTS.ind from PHY which we process
+ * \param[out] msg Message buffer to which we write data
+ *
+ * The function will fill \a msg, from which the caller can then
+ * subsequently build a PH-DATA.req */
+static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan,
+ const struct ph_data_param *rts_ind,
+ struct msgb *msg, const struct gsm_time *tm)
+{
+ struct msgb *loop_msg;
+ uint8_t *p;
+
+ /* de-queue response message (loopback) */
+ loop_msg = msgb_dequeue(&lchan->dl_tch_queue);
+ if (!loop_msg) {
+ LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: no looped PDTCH message, sending empty\n",
+ gsm_lchan_name(lchan));
+ /* empty downlink message */
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ memset(p, 0, GSM_MACBLOCK_LEN);
+ } else {
+ LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: looped PDTCH message of %u bytes\n",
+ gsm_lchan_name(lchan), msgb_l2len(loop_msg));
+ /* copy over data from queued response message */
+ p = msgb_put(msg, msgb_l2len(loop_msg));
+ memcpy(p, msgb_l2(loop_msg), msgb_l2len(loop_msg));
+ msgb_free(loop_msg);
+ }
+ return 0;
+}
+
+/* Check if given CCCH frame number is for a PCH or for an AGCH (this function is
+ * only used internally, it is public to call it from unit-tests) */
+int is_ccch_for_agch(struct gsm_bts_trx *trx, uint32_t fn) {
+ /* Note: The number of available access grant channels is set by the
+ * parameter BS_AG_BLKS_RES via system information type 3. This SI is
+ * transfered to osmo-bts via RSL */
+ return l1sap_fn2ccch_block(fn) < num_agch(trx, "PH-RTS-IND");
+}
+
+/* PH-RTS-IND prim received from bts model */
+static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct ph_data_param *rts_ind)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ uint8_t chan_nr, link_id;
+ uint8_t tn;
+ uint32_t fn;
+ uint8_t *p, *si;
+ struct lapdm_entity *le;
+ struct osmo_phsap_prim pp;
+ bool dtxd_facch = false;
+ int rc;
+ int is_ag_res;
+
+ chan_nr = rts_ind->chan_nr;
+ link_id = rts_ind->link_id;
+ fn = rts_ind->fn;
+ tn = L1SAP_CHAN2TS(chan_nr);
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind chan_nr=%s link_id=0x%02xd\n", rsl_chan_nr_str(chan_nr), link_id);
+
+ /* reuse PH-RTS.ind for PH-DATA.req */
+ if (!msg) {
+ LOGPGT(DL1P, LOGL_FATAL, &g_time, "RTS without msg to be reused. Please fix!\n");
+ abort();
+ }
+ msgb_trim(msg, sizeof(*l1sap));
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST,
+ msg);
+ msg->l2h = msg->l1h + sizeof(*l1sap);
+
+ if (ts_is_pdch(&trx->ts[tn])) {
+ lchan = get_active_lchan_by_chan_nr(trx, chan_nr);
+ if (lchan && lchan->loopback) {
+ if (!L1SAP_IS_PTCCH(rts_ind->fn))
+ lchan_pdtch_ph_rts_ind_loop(lchan, rts_ind, msg, &g_time);
+ /* continue below like for SACCH/FACCH/... */
+ } else {
+ /* forward RTS.ind to PCU */
+ if (L1SAP_IS_PTCCH(rts_ind->fn)) {
+ pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */,
+ L1SAP_FN2PTCCHBLOCK(fn));
+ } else {
+ pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */,
+ L1SAP_FN2MACBLOCK(fn));
+ }
+ /* return early, PCU takes care of rest */
+ return 0;
+ }
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ /* get them from bts->si_buf[] */
+ si = bts_sysinfo_get(trx->bts, &g_time);
+ if (si)
+ memcpy(p, si, GSM_MACBLOCK_LEN);
+ else
+ memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ bts_cbch_get(trx->bts, p, &g_time);
+ } else if (!(chan_nr & 0x80)) { /* only TCH/F, TCH/H, SDCCH/4 and SDCCH/8 have C5 bit cleared */
+ lchan = get_active_lchan_by_chan_nr(trx, chan_nr);
+ if (!lchan) {
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%s)\n",
+ rsl_chan_nr_str(chan_nr));
+ return 0;
+ }
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ /* L1-header, if not set/modified by layer 1 */
+ p[0] = lchan->ms_power_ctrl.current;
+ p[1] = lchan->rqd_ta;
+ le = &lchan->lapdm_ch.lapdm_acch;
+ } else {
+ if (lchan->ts->trx->bts->dtxd)
+ dtxd_facch = true;
+ le = &lchan->lapdm_ch.lapdm_dcch;
+ }
+ rc = lapdm_phsap_dequeue_prim(le, &pp);
+ if (rc < 0) {
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ /* No SACCH data from LAPDM pending, send SACCH filling */
+ uint8_t *si = lchan_sacch_get(lchan);
+ if (si) {
+ /* The +2 is empty space where the DSP inserts the L1 hdr */
+ memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2);
+ } else
+ memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2);
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr) || L1SAP_IS_CHAN_SDCCH8(chan_nr) ||
+ (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN && !lchan->ts->trx->bts->dtxd)) {
+ /*
+ * SDCCH or TCH in signalling mode without DTX.
+ *
+ * Send fill frame according to GSM 05.08, section 8.3: "On the SDCCH and on the
+ * half rate speech traffic channel in signalling only mode DTX is not allowed.
+ * In these cases and during signalling on the TCH when DTX is not used, the same
+ * L2 fill frame shall be transmitted in case there is nothing else to transmit."
+ */
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+ } /* else the message remains empty, so TCH frames are sent */
+ } else {
+ /* The +2 is empty space where the DSP inserts the L1 hdr */
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ memcpy(p + 2, pp.oph.msg->data + 2, GSM_MACBLOCK_LEN - 2);
+ else {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN);
+ /* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */
+ check_for_ciph_cmd(pp.oph.msg, lchan, chan_nr);
+ if (dtxd_facch)
+ dtx_dispatch(lchan, E_FACCH);
+ }
+ msgb_free(pp.oph.msg);
+ }
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ is_ag_res = is_ccch_for_agch(trx, fn);
+ rc = bts_ccch_copy_msg(trx->bts, p, &g_time, is_ag_res);
+ if (rc <= 0)
+ memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+ }
+
+ DEBUGPGT(DL1P, &g_time, "Tx PH-DATA.req chan_nr=%s link_id=0x%02x\n", rsl_chan_nr_str(chan_nr), link_id);
+
+ l1sap_down(trx, l1sap);
+
+ /* don't free, because we forwarded data */
+ return 1;
+}
+
+static bool rtppayload_is_octet_aligned(const uint8_t *rtp_pl, uint8_t payload_len)
+{
+ /*
+ * Logic: If 1st bit padding is not zero, packet is either:
+ * - bandwidth-efficient AMR payload.
+ * - malformed packet.
+ * However, Bandwidth-efficient AMR 4,75 frame last in payload(F=0, FT=0)
+ * with 4th,5ht,6th AMR payload to 0 matches padding==0.
+ * Furthermore, both AMR 4,75 bw-efficient and octet alignment are 14 bytes long (AMR 4,75 encodes 95b):
+ * bw-efficient: 95b, + 4b hdr + 6b ToC = 105b, + padding = 112b = 14B.
+ * octet-aligned: 1B hdr + 1B ToC + 95b = 111b, + padding = 112b = 14B.
+ * We cannot use other fields to match since they are inside the AMR
+ * payload bits which are unknown.
+ * As a result, this function may return false positive (true) for some AMR
+ * 4,75 AMR frames, but given the length, CMR and FT read is the same as a
+ * consequence, the damage in here is harmless other than being unable to
+ * decode the audio at the other side.
+ */
+ #define AMR_PADDING1(rtp_pl) (rtp_pl[0] & 0x0f)
+ #define AMR_PADDING2(rtp_pl) (rtp_pl[1] & 0x03)
+
+ if(payload_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl))
+ return false;
+
+ return true;
+}
+
+static bool rtppayload_is_valid(struct gsm_lchan *lchan, struct msgb *resp_msg)
+{
+ /* Avoid sending bw-efficient AMR to lower layers, most bts models
+ * don't support it. */
+ if(lchan->tch_mode == GSM48_CMODE_SPEECH_AMR &&
+ !rtppayload_is_octet_aligned(resp_msg->data, resp_msg->len)) {
+ LOGP(DL1P, LOGL_NOTICE,
+ "%s RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n",
+ gsm_lchan_name(lchan), osmo_hexdump(resp_msg->data, resp_msg->len));
+ return false;
+ }
+ return true;
+}
+
+/* TCH-RTS-IND prim recevied from bts model */
+static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct ph_tch_param *rts_ind)
+{
+ struct msgb *resp_msg;
+ struct osmo_phsap_prim *resp_l1sap, empty_l1sap;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ uint8_t chan_nr, marker = 0;
+ uint32_t fn;
+ int rc;
+
+ chan_nr = rts_ind->chan_nr;
+ fn = rts_ind->fn;
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx TCH-RTS.ind chan_nr=%s\n", rsl_chan_nr_str(chan_nr));
+
+ lchan = get_active_lchan_by_chan_nr(trx, chan_nr);
+ if (!lchan) {
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%s)\n", rsl_chan_nr_str(chan_nr));
+ return 0;
+ }
+
+ if (!lchan->loopback && lchan->abis_ip.rtp_socket) {
+ osmo_rtp_socket_poll(lchan->abis_ip.rtp_socket);
+ /* FIXME: we _assume_ that we never miss TDMA
+ * frames and that we always get to this point
+ * for every to-be-transmitted voice frame. A
+ * better solution would be to compute
+ * rx_user_ts based on how many TDMA frames have
+ * elapsed since the last call */
+ lchan->abis_ip.rtp_socket->rx_user_ts += GSM_RTP_DURATION;
+ }
+ /* get a msgb from the dl_tx_queue */
+ resp_msg = msgb_dequeue(&lchan->dl_tch_queue);
+ if (!resp_msg) {
+ DEBUGPGT(DL1P, &g_time, "%s DL TCH Tx queue underrun\n", gsm_lchan_name(lchan));
+ resp_l1sap = &empty_l1sap;
+ } else if(!rtppayload_is_valid(lchan, resp_msg)) {
+ msgb_free(resp_msg);
+ resp_msg = NULL;
+ resp_l1sap = &empty_l1sap;
+ } else {
+ /* Obtain RTP header Marker bit from control buffer */
+ marker = rtpmsg_marker_bit(resp_msg);
+
+ resp_msg->l2h = resp_msg->data;
+ msgb_push(resp_msg, sizeof(*resp_l1sap));
+ resp_msg->l1h = resp_msg->data;
+ resp_l1sap = msgb_l1sap_prim(resp_msg);
+ }
+
+ /* check for pending REL_IND */
+ if (lchan->pending_rel_ind_msg) {
+ LOGPGT(DRSL, LOGL_INFO, &g_time, "%s Forward REL_IND to L3\n", gsm_lchan_name(lchan));
+ /* Forward it to L3 */
+ rc = abis_bts_rsl_sendmsg(lchan->pending_rel_ind_msg);
+ lchan->pending_rel_ind_msg = NULL;
+ if (rc < 0)
+ return rc;
+ }
+
+ memset(resp_l1sap, 0, sizeof(*resp_l1sap));
+ osmo_prim_init(&resp_l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_REQUEST,
+ resp_msg);
+ resp_l1sap->u.tch.chan_nr = chan_nr;
+ resp_l1sap->u.tch.fn = fn;
+ resp_l1sap->u.tch.marker = marker;
+
+ DEBUGPGT(DL1P, &g_time, "Tx TCH.req chan_nr=%s\n", rsl_chan_nr_str(chan_nr));
+
+ l1sap_down(trx, resp_l1sap);
+
+ return 0;
+}
+
+/* process radio link timeout counter S. Follows TS 05.08 Section 5.2
+ * "MS Procedure" as the "BSS Procedure [...] shall be determined by the
+ * network operator." */
+static void radio_link_timeout(struct gsm_lchan *lchan, int bad_frame)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* Bypass radio link timeout if set to -1 */
+ if (bts->radio_link_timeout < 0)
+ return;
+
+ /* if link loss criterion already reached */
+ if (lchan->s == 0) {
+ DEBUGP(DMEAS, "%s radio link counter S already 0.\n",
+ gsm_lchan_name(lchan));
+ return;
+ }
+
+ if (bad_frame) {
+ /* count down radio link counter S */
+ lchan->s--;
+ DEBUGP(DMEAS, "%s counting down radio link counter S=%d\n",
+ gsm_lchan_name(lchan), lchan->s);
+ if (lchan->s == 0)
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ return;
+ }
+
+ if (lchan->s < bts->radio_link_timeout) {
+ /* count up radio link counter S */
+ lchan->s += 2;
+ if (lchan->s > bts->radio_link_timeout)
+ lchan->s = bts->radio_link_timeout;
+ DEBUGP(DMEAS, "%s counting up radio link counter S=%d\n",
+ gsm_lchan_name(lchan), lchan->s);
+ }
+}
+
+static inline int check_for_first_ciphrd(struct gsm_lchan *lchan,
+ uint8_t *data, int len)
+{
+ uint8_t n_r;
+
+ /* if this is the first valid message after enabling Rx
+ * decryption, we have to enable Tx encryption */
+ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF)
+ return 0;
+
+ /* HACK: check if it's an I frame, in order to
+ * ignore some still buffered/queued UI frames received
+ * before decryption was enabled */
+ if (data[0] != 0x01)
+ return 0;
+
+ if ((data[1] & 0x01) != 0)
+ return 0;
+
+ n_r = data[1] >> 5;
+ if (lchan->ciph_ns != n_r)
+ return 0;
+
+ return 1;
+}
+
+/* public helper for the test */
+int bts_check_for_first_ciphrd(struct gsm_lchan *lchan,
+ uint8_t *data, int len)
+{
+ return check_for_first_ciphrd(lchan, data, len);
+}
+
+/* DATA received from bts model */
+static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct ph_data_param *data_ind)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ struct lapdm_entity *le;
+ uint8_t *data = msg->l2h;
+ int len = msgb_l2len(msg);
+ uint8_t chan_nr, link_id;
+ uint8_t tn;
+ uint32_t fn;
+ int8_t rssi;
+ enum osmo_ph_pres_info_type pr_info = data_ind->pdch_presence_info;
+
+ rssi = data_ind->rssi;
+ chan_nr = data_ind->chan_nr;
+ link_id = data_ind->link_id;
+ fn = data_ind->fn;
+ tn = L1SAP_CHAN2TS(chan_nr);
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind chan_nr=%s link_id=0x%02x len=%d\n",
+ rsl_chan_nr_str(chan_nr), link_id, len);
+
+ if (ts_is_pdch(&trx->ts[tn])) {
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (!lchan)
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=%s\n", rsl_chan_nr_str(chan_nr));
+ if (lchan && lchan->loopback && !L1SAP_IS_PTCCH(fn)) {
+ /* we are in loopback mode (for BER testing)
+ * mode and need to enqeue the frame to be
+ * returned in downlink */
+ queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1);
+ msgb_enqueue(&lchan->dl_tch_queue, msg);
+
+ /* Return 1 to signal that we're still using msg
+ * and it should not be freed */
+ return 1;
+ }
+
+ /* don't send bad frames to PCU */
+ if (len == 0)
+ return -EINVAL;
+ if (L1SAP_IS_PTCCH(fn)) {
+ pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PTCCH, fn,
+ 0 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn),
+ data, len, rssi, data_ind->ber10k,
+ data_ind->ta_offs_256bits/64,
+ data_ind->lqual_cb);
+ } else {
+ /* drop incomplete UL block */
+ if (pr_info != PRES_INFO_BOTH)
+ return 0;
+ /* PDTCH / PACCH frame handling */
+ pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PDTCH, fn, 0 /* ARFCN */,
+ L1SAP_FN2MACBLOCK(fn), data, len, rssi, data_ind->ber10k,
+ data_ind->ta_offs_256bits/64, data_ind->lqual_cb);
+ }
+ return 0;
+ }
+
+ lchan = get_active_lchan_by_chan_nr(trx, chan_nr);
+ if (!lchan) {
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=%s\n", rsl_chan_nr_str(chan_nr));
+ return 0;
+ }
+
+ /* bad frame */
+ if (len == 0) {
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ radio_link_timeout(lchan, 1);
+ return -EINVAL;
+ }
+
+ /* report first valid received frame to handover process */
+ if (lchan->ho.active == HANDOVER_WAIT_FRAME)
+ handover_frame(lchan);
+
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ radio_link_timeout(lchan, 0);
+ le = &lchan->lapdm_ch.lapdm_acch;
+ /* save the SACCH L1 header in the lchan struct for RSL MEAS RES */
+ if (len < 2) {
+ LOGPGT(DL1P, LOGL_NOTICE, &g_time, "SACCH with size %u<2 !?!\n", len);
+ return -EINVAL;
+ }
+ /* Some brilliant engineer decided that the ordering of
+ * fields on the Um interface is different from the
+ * order of fields in RLS. See TS 04.04 (Chapter 7.2)
+ * vs. TS 08.58 (Chapter 9.3.10). */
+ lchan->meas.l1_info[0] = data[0] << 3;
+ lchan->meas.l1_info[0] |= ((data[0] >> 5) & 1) << 2;
+ lchan->meas.l1_info[1] = data[1];
+ lchan->meas.flags |= LC_UL_M_F_L1_VALID;
+
+ lchan_ms_pwr_ctrl(lchan, data[0] & 0x1f, data_ind->rssi);
+ } else
+ le = &lchan->lapdm_ch.lapdm_dcch;
+
+ if (check_for_first_ciphrd(lchan, data, len))
+ l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 1, 0);
+
+ /* SDCCH, SACCH and FACCH all go to LAPDm */
+ msgb_pull(msg, (msg->l2h - msg->data));
+ msg->l1h = NULL;
+ lapdm_phsap_up(&l1sap->oph, le);
+
+ /* don't free, because we forwarded data */
+ return 1;
+}
+
+/* TCH received from bts model */
+static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap,
+ struct ph_tch_param *tch_ind)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct msgb *msg = l1sap->oph.msg;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ uint8_t chan_nr;
+ uint32_t fn;
+
+ chan_nr = tch_ind->chan_nr;
+ fn = tch_ind->fn;
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ LOGPGT(DL1P, LOGL_INFO, &g_time, "Rx TCH.ind chan_nr=%s\n", rsl_chan_nr_str(chan_nr));
+
+ lchan = get_active_lchan_by_chan_nr(trx, chan_nr);
+ if (!lchan) {
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for TCH.ind (chan_nr=%s)\n", rsl_chan_nr_str(chan_nr));
+ return 0;
+ }
+
+ msgb_pull(msg, sizeof(*l1sap));
+
+ /* Low level layers always call us when TCH content is expected, even if
+ * the content is not available due to decoding issues. Content not
+ * available is expected as empty payload. We also check if quality is
+ * good enough. */
+ if (msg->len && tch_ind->lqual_cb / 10 >= bts->min_qual_norm) {
+ /* hand msg to RTP code for transmission */
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket,
+ msg->data, msg->len, fn_ms_adj(fn, lchan), lchan->rtp_tx_marker);
+ /* if loopback is enabled, also queue received RTP data */
+ if (lchan->loopback) {
+ /* make sure the queue doesn't get too long */
+ queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1);
+ /* add new frame to queue */
+ msgb_enqueue(&lchan->dl_tch_queue, msg);
+ /* Return 1 to signal that we're still using msg and it should not be freed */
+ return 1;
+ }
+ /* Only clear the marker bit once we have sent a RTP packet with it */
+ lchan->rtp_tx_marker = false;
+ } else {
+ DEBUGPGT(DRTP, &g_time, "Skipping RTP frame with lost payload (chan_nr=0x%02x)\n",
+ chan_nr);
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_skipped_frame(lchan->abis_ip.rtp_socket, fn_ms_adj(fn, lchan));
+ lchan->rtp_tx_marker = true;
+ }
+
+ lchan->tch.last_fn = fn;
+ return 0;
+}
+
+#define RACH_MIN_TOA256 -2 * 256
+
+static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts)
+{
+ int16_t toa256 = rach_ind->acc_delay_256bits;
+
+ /* Check for RACH exceeding BER threshold (ghost RACH) */
+ if (rach_ind->ber10k > bts->max_ber10k_rach) {
+ LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: "
+ "BER10k(%u) > BER10k_MAX(%u)\n",
+ rach_ind->ber10k, bts->max_ber10k_rach);
+ return false;
+ }
+
+ /**
+ * Make sure that ToA (Timing of Arrival) is acceptable.
+ * We allow early arrival up to 2 symbols, and delay
+ * according to maximal allowed Timing Advance value.
+ */
+ if (toa256 < RACH_MIN_TOA256 || toa256 > bts->max_ta * 256) {
+ LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: "
+ "ToA(%d) exceeds the allowed range (%d..%d)\n",
+ toa256, RACH_MIN_TOA256, bts->max_ta * 256);
+ return false;
+ }
+
+ return true;
+}
+
+/* Special case where handover RACH is detected */
+static int l1sap_handover_rach(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind)
+{
+ /* Filter out noise / interference / ghosts */
+ if (!rach_pass_filter(rach_ind, trx->bts)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP);
+ return 0;
+ }
+
+ handover_rach(get_lchan_by_chan_nr(trx, rach_ind->chan_nr),
+ rach_ind->ra, rach_ind->acc_delay);
+
+ /* must return 0, so in case of msg at l1sap, it will be freed */
+ return 0;
+}
+
+/* RACH received from bts model */
+static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct lapdm_channel *lc;
+
+ DEBUGPFN(DL1P, rach_ind->fn, "Rx PH-RA.ind");
+
+ /* check for handover access burst on dedicated channels */
+ if (!L1SAP_IS_CHAN_RACH(rach_ind->chan_nr)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_HO);
+ return l1sap_handover_rach(trx, l1sap, rach_ind);
+ }
+
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_RCVD);
+
+ /* increment number of busy RACH slots, if required */
+ if (rach_ind->rssi >= bts->load.rach.busy_thresh)
+ bts->load.rach.busy++;
+
+ /* Filter out noise / interference / ghosts */
+ if (!rach_pass_filter(rach_ind, bts)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP);
+ return 0;
+ }
+
+ /* increment number of RACH slots with valid non-handover RACH burst */
+ bts->load.rach.access++;
+
+ lc = &trx->ts[0].lchan[CCCH_LCHAN].lapdm_ch;
+
+ /* According to 3GPP TS 48.058 § 9.3.17 Access Delay is expressed same way as TA (number of symbols) */
+ set_ms_to_data(get_lchan_by_chan_nr(trx, rach_ind->chan_nr),
+ rach_ind->acc_delay, false);
+
+ /* check for packet access */
+ if ((trx == bts->c0 && L1SAP_IS_PACKET_RACH(rach_ind->ra)) ||
+ (trx == bts->c0 && rach_ind->is_11bit)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_PS);
+
+ LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for packet access (toa=%d, ra=%d)\n",
+ rach_ind->acc_delay, rach_ind->ra);
+
+ pcu_tx_rach_ind(bts, rach_ind->acc_delay << 2,
+ rach_ind->ra, rach_ind->fn,
+ rach_ind->is_11bit, rach_ind->burst_type);
+ return 0;
+ }
+
+ LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for RR access (toa=%d, ra=%d)\n",
+ rach_ind->acc_delay, rach_ind->ra);
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_CS);
+ lapdm_phsap_up(&l1sap->oph, &lc->lapdm_dcch);
+
+ return 0;
+}
+
+/* Process any L1 prim received from bts model.
+ *
+ * This function takes ownership of the msgb.
+ * If l1sap contains a msgb, it assumes that msgb->l2h was set by lower layer.
+ */
+int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_INDICATION):
+ rc = l1sap_mph_info_ind(trx, l1sap, &l1sap->u.info);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_CONFIRM):
+ rc = l1sap_mph_info_cnf(trx, l1sap, &l1sap->u.info);
+ break;
+ case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION):
+ rc = l1sap_ph_rts_ind(trx, l1sap, &l1sap->u.data);
+ break;
+ case OSMO_PRIM(PRIM_TCH_RTS, PRIM_OP_INDICATION):
+ rc = l1sap_tch_rts_ind(trx, l1sap, &l1sap->u.tch);
+ break;
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
+ to_gsmtap(trx, l1sap);
+ rc = l1sap_ph_data_ind(trx, l1sap, &l1sap->u.data);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_INDICATION):
+ rc = l1sap_tch_ind(trx, l1sap, &l1sap->u.tch);
+ break;
+ case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+ to_gsmtap(trx, l1sap);
+ rc = l1sap_ph_rach_ind(trx, l1sap, &l1sap->u.rach_ind);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ oml_fail_rep(OSMO_EVT_MAJ_UKWN_MSG, "unknown prim %d op %d",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ break;
+ }
+
+ /* Special return value '1' means: do not free */
+ if (rc != 1)
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* any L1 prim sent to bts model */
+static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ if (OSMO_PRIM_HDR(&l1sap->oph) ==
+ OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST))
+ to_gsmtap(trx, l1sap);
+
+ return bts_model_l1sap_down(trx, l1sap);
+}
+
+/* pcu (socket interface) sends us a data request primitive */
+int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len)
+{
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+ struct gsm_time g_time;
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGP(DL1P, "TX packet data %s is_ptcch=%d trx=%d ts=%d "
+ "block_nr=%d, arfcn=%d, len=%d\n", osmo_dump_gsmtime(&g_time),
+ is_ptcch, ts->trx->nr, ts->nr, block_nr, arfcn, len);
+
+ msg = l1sap_msgb_alloc(len);
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST,
+ msg);
+ l1sap->u.data.chan_nr = RSL_CHAN_OSMO_PDCH | ts->nr;
+ l1sap->u.data.link_id = 0x00;
+ l1sap->u.data.fn = fn;
+ msg->l2h = msgb_put(msg, len);
+ memcpy(msg->l2h, data, len);
+
+ return l1sap_down(ts->trx, l1sap);
+}
+
+/*! \brief call-back function for incoming RTP */
+void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
+ unsigned int rtp_pl_len, uint16_t seq_number,
+ uint32_t timestamp, bool marker)
+{
+ struct gsm_lchan *lchan = rs->priv;
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+
+ /* if we're in loopback mode, we don't accept frames from the
+ * RTP socket anymore */
+ if (lchan->loopback)
+ return;
+
+ msg = l1sap_msgb_alloc(rtp_pl_len);
+ if (!msg)
+ return;
+ memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len);
+ msgb_pull(msg, sizeof(*l1sap));
+
+ /* Store RTP header Marker bit in control buffer */
+ rtpmsg_marker_bit(msg) = marker;
+ /* Store RTP header Sequence Number in control buffer */
+ rtpmsg_seq(msg) = seq_number;
+ /* Store RTP header Timestamp in control buffer */
+ rtpmsg_ts(msg) = timestamp;
+
+ /* make sure the queue doesn't get too long */
+ queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1);
+
+ msgb_enqueue(&lchan->dl_tch_queue, msg);
+}
+
+static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ enum osmo_mph_info_type type, uint8_t sacch_only)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_req.chan_nr = chan_nr;
+ l1sap.u.info.u.act_req.sacch_only = sacch_only;
+
+ return l1sap_down(trx, &l1sap);
+}
+
+int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp)
+{
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ struct gsm48_chan_desc *cd;
+ int rc;
+
+ LOGP(DL1C, LOGL_INFO, "activating channel chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(chan_nr), trx->nr);
+
+ /* osmo-pcu calls this without a valid 'tp' parameter, so we
+ * need to make sure ew don't crash here */
+ if (tp && TLVP_PRESENT(tp, GSM48_IE_CHANDESC_2) &&
+ TLVP_LEN(tp, GSM48_IE_CHANDESC_2) >= sizeof(*cd)) {
+ cd = (struct gsm48_chan_desc *)
+ TLVP_VAL(tp, GSM48_IE_CHANDESC_2);
+
+ /* our L1 only supports one global TSC for all channels
+ * one one TRX, so we need to make sure not to activate
+ * channels with a different TSC!! */
+ if (cd->h0.tsc != (lchan->ts->trx->bts->bsic & 7)) {
+ LOGP(DL1C, LOGL_ERROR, "lchan TSC %u != BSIC-TSC %u\n",
+ cd->h0.tsc, lchan->ts->trx->bts->bsic & 7);
+ return -RSL_ERR_SERV_OPT_UNIMPL;
+ }
+ }
+
+ lchan->sacch_deact = 0;
+ lchan->s = lchan->ts->trx->bts->radio_link_timeout;
+
+ rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0);
+ if (rc)
+ return -RSL_ERR_EQUIPMENT_FAIL;
+
+ /* Init DTX DL FSM if necessary */
+ if (trx->bts->dtxd && lchan->type != GSM_LCHAN_SDCCH) {
+ char name[32];
+ snprintf(name, sizeof(name), "bts%u-trx%u-ts%u-ss%u", lchan->ts->trx->bts->nr,
+ lchan->ts->trx->nr, lchan->ts->nr, lchan->nr);
+ lchan->tch.dtx.dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm,
+ tall_bts_ctx,
+ lchan,
+ LOGL_DEBUG,
+ name);
+ if (!lchan->tch.dtx.dl_amr_fsm) {
+ l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0);
+ return -RSL_ERR_EQUIPMENT_FAIL;
+ }
+ }
+ return 0;
+}
+
+int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ LOGP(DL1C, LOGL_INFO, "deactivating channel chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(chan_nr), trx->nr);
+
+ if (lchan->tch.dtx.dl_amr_fsm) {
+ osmo_fsm_inst_free(lchan->tch.dtx.dl_amr_fsm);
+ lchan->tch.dtx.dl_amr_fsm = NULL;
+ }
+
+ return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
+ 0);
+}
+
+int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ LOGP(DL1C, LOGL_INFO, "deactivating sacch chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(chan_nr), trx->nr);
+
+ lchan->sacch_deact = 1;
+
+ return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
+ 1);
+}
+
+int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+ LOGP(DL1C, LOGL_INFO, "modifying channel chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(chan_nr), trx->nr);
+
+ return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0);
+}
diff --git a/src/common/lchan.c b/src/common/lchan.c
new file mode 100644
index 00000000..9e98166d
--- /dev/null
+++ b/src/common/lchan.c
@@ -0,0 +1,49 @@
+/* OsmoBTS lchan interface */
+
+/* (C) 2012 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state)
+{
+ DEBUGP(DL1C, "%s state %s -> %s\n",
+ gsm_lchan_name(lchan),
+ gsm_lchans_name(lchan->state),
+ gsm_lchans_name(state));
+ lchan->state = state;
+}
+
+bool ts_is_pdch(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_PDCH:
+ return true;
+ case GSM_PCHAN_TCH_F_PDCH:
+ return (ts->flags & TS_F_PDCH_ACTIVE)
+ && !(ts->flags & TS_F_PDCH_PENDING_MASK);
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && ts->dyn.pchan_want == ts->dyn.pchan_is;
+ default:
+ return false;
+ }
+}
diff --git a/src/common/load_indication.c b/src/common/load_indication.c
new file mode 100644
index 00000000..e91f6d49
--- /dev/null
+++ b/src/common/load_indication.c
@@ -0,0 +1,94 @@
+/* Support for generating RSL Load Indication */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/paging.h>
+
+static void reset_load_counters(struct gsm_bts *bts)
+{
+ /* re-set the counters */
+ bts->load.ccch.pch_used = bts->load.ccch.pch_total = 0;
+}
+
+static void load_timer_cb(void *data)
+{
+ struct gsm_bts *bts = data;
+ unsigned int pch_percent, rach_percent;
+
+ /* compute percentages */
+ if (bts->load.ccch.pch_total == 0)
+ pch_percent = 0;
+ else
+ pch_percent = (bts->load.ccch.pch_used * 100) /
+ bts->load.ccch.pch_total;
+
+ if (pch_percent >= bts->load.ccch.load_ind_thresh) {
+ /* send RSL load indication message to BSC */
+ uint16_t buffer_space = paging_buffer_space(bts->paging_state);
+ rsl_tx_ccch_load_ind_pch(bts, buffer_space);
+ } else {
+ /* This is an extenstion of TS 08.58. We don't only
+ * send load indications if the load is above threshold,
+ * but we also explicitly indicate that we are below
+ * threshold by using the magic value 0xffff */
+ rsl_tx_ccch_load_ind_pch(bts, 0xffff);
+ }
+
+ if (bts->load.rach.total == 0)
+ rach_percent = 0;
+ else
+ rach_percent = (bts->load.rach.busy * 100) /
+ bts->load.rach.total;
+
+ if (rach_percent >= bts->load.ccch.load_ind_thresh) {
+ /* send RSL load indication message to BSC */
+ rsl_tx_ccch_load_ind_rach(bts, bts->load.rach.total,
+ bts->load.rach.busy,
+ bts->load.rach.access);
+ }
+
+ reset_load_counters(bts);
+
+ /* re-schedule the timer */
+ osmo_timer_schedule(&bts->load.ccch.timer,
+ bts->load.ccch.load_ind_period, 0);
+}
+
+void load_timer_start(struct gsm_bts *bts)
+{
+ if (!bts->load.ccch.timer.data) {
+ bts->load.ccch.timer.data = bts;
+ bts->load.ccch.timer.cb = load_timer_cb;
+ reset_load_counters(bts);
+ }
+ osmo_timer_schedule(&bts->load.ccch.timer, bts->load.ccch.load_ind_period, 0);
+}
+
+void load_timer_stop(struct gsm_bts *bts)
+{
+ osmo_timer_del(&bts->load.ccch.timer);
+}
diff --git a/src/common/logging.c b/src/common/logging.c
new file mode 100644
index 00000000..3315a019
--- /dev/null
+++ b/src/common/logging.c
@@ -0,0 +1,150 @@
+/* libosmocore logging support */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <errno.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+
+static struct log_info_cat bts_log_info_cat[] = {
+ [DRSL] = {
+ .name = "DRSL",
+ .description = "A-bis Radio Siganlling Link (RSL)",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DOML] = {
+ .name = "DOML",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DRLL] = {
+ .name = "DRLL",
+ .description = "A-bis Radio Link Layer (RLL)",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRR] = {
+ .name = "DRR",
+ .description = "Layer3 Radio Resource (RR)",
+ .color = "\033[1;34m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMEAS] = {
+ .name = "DMEAS",
+ .description = "Radio Measurement Processing",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DPAG] = {
+ .name = "DPAG",
+ .description = "Paging Subsystem",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DL1C] = {
+ .name = "DL1C",
+ .description = "Layer 1 Control (MPH)",
+ .loglevel = LOGL_INFO,
+ .enabled = 1,
+ },
+ [DL1P] = {
+ .name = "DL1P",
+ .description = "Layer 1 Primitives (PH)",
+ .loglevel = LOGL_INFO,
+ .enabled = 0,
+ },
+ [DDSP] = {
+ .name = "DDSP",
+ .description = "DSP Trace Messages",
+ .loglevel = LOGL_DEBUG,
+ .enabled = 1,
+ },
+ [DABIS] = {
+ .name = "DABIS",
+ .description = "A-bis Intput Subsystem",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRTP] = {
+ .name = "DRTP",
+ .description = "Realtime Transfer Protocol",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DPCU] = {
+ .name = "DPCU",
+ .description = "PCU interface",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DHO] = {
+ .name = "DHO",
+ .description = "Handover",
+ .color = "\033[0;37m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DTRX] = {
+ .name = "DTRX",
+ .description = "TRX interface",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DLOOP] = {
+ .name = "DLOOP",
+ .description = "Control loops",
+ .color = "\033[0;34m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+#if 0
+ [DNS] = {
+ .name = "DNS",
+ .description = "GPRS Network Service (NS)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DBSSGP] = {
+ .name = "DBSSGP",
+ .description = "GPRS BSS Gateway Protocol (BSSGP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DLLC] = {
+ .name = "DLLC",
+ .description = "GPRS Logical Link Control Protocol (LLC)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+#endif
+ [DSUM] = {
+ .name = "DSUM",
+ .description = "DSUM",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+};
+
+const struct log_info bts_log_info = {
+ .cat = bts_log_info_cat,
+ .num_cat = ARRAY_SIZE(bts_log_info_cat),
+};
diff --git a/src/common/main.c b/src/common/main.c
new file mode 100644
index 00000000..f90a4d4d
--- /dev/null
+++ b/src/common/main.c
@@ -0,0 +1,358 @@
+/* Main program for Osmocom BTS */
+
+/* (C) 2011-2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/control_if.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmo-bts/oml.h>
+
+int quit = 0;
+static const char *config_file = "osmo-bts.cfg";
+static int daemonize = 0;
+static int rt_prio = -1;
+static char *gsmtap_ip = 0;
+extern int g_vty_port_num;
+
+static void print_help()
+{
+ printf( "Some useful options:\n"
+ " -h --help this text\n"
+ " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n"
+ " -D --daemonize For the process into a background daemon\n"
+ " -c --config-file Specify the filename of the config file\n"
+ " -s --disable-color Don't use colors in stderr log output\n"
+ " -T --timestamp Prefix every log line with a timestamp\n"
+ " -V --version Print version information and exit\n"
+ " -e --log-level Set a global log-level\n"
+ " -r --realtime PRIO Use SCHED_RR with the specified priority\n"
+ " -i --gsmtap-ip The destination IP used for GSMTAP.\n"
+ );
+ bts_model_print_help();
+}
+
+/* FIXME: finally get some option parsing code into libosmocore */
+static void handle_options(int argc, char **argv)
+{
+ char *argv_out[argc];
+ int argc_out = 0;
+
+ argv_out[argc_out++] = argv[0];
+
+ /* disable generation of error messages on encountering unknown
+ * options */
+ opterr = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* FIXME: all those are generic Osmocom app options */
+ { "help", 0, 0, 'h' },
+ { "debug", 1, 0, 'd' },
+ { "daemonize", 0, 0, 'D' },
+ { "config-file", 1, 0, 'c' },
+ { "disable-color", 0, 0, 's' },
+ { "timestamp", 0, 0, 'T' },
+ { "version", 0, 0, 'V' },
+ { "log-level", 1, 0, 'e' },
+ /* FIXME: generic BTS app options */
+ { "gsmtap-ip", 1, 0, 'i' },
+ { "trx-num", 1, 0, 't' },
+ { "realtime", 1, 0, 'r' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "-hc:d:Dc:sTVe:i:t:r:",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'r':
+ rt_prio = atoi(optarg);
+ break;
+ case 'i':
+ gsmtap_ip = optarg;
+ break;
+ case 't':
+ fprintf(stderr, "Parameter -t is deprecated and does nothing, "
+ "TRX num is calculated from VTY\n");
+ break;
+ case '?':
+ case 1:
+ /* prepare argv[] for bts_model */
+ argv_out[argc_out++] = argv[optind-1];
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* re-set opt-ind for new parsig round */
+ optind = 1;
+ /* enable error-checking for the following getopt call */
+ opterr = 1;
+ if (bts_model_handle_options(argc_out, argv_out)) {
+ print_help();
+ exit(1);
+ }
+}
+
+static struct gsm_bts *bts;
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ if (!quit) {
+ oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP,
+ "BTS: SIGINT received -> shutdown");
+ bts_shutdown(bts, "SIGINT");
+ }
+ quit++;
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP,
+ "BTS: signal %d (%s) received", signal,
+ strsignal(signal));
+ talloc_report_full(tall_bts_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static int write_pid_file(char *procname)
+{
+ FILE *outf;
+ char tmp[PATH_MAX+1];
+
+ snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname);
+ tmp[PATH_MAX-1] = '\0';
+
+ outf = fopen(tmp, "w");
+ if (!outf)
+ return -1;
+
+ fprintf(outf, "%d\n", getpid());
+
+ fclose(outf);
+
+ return 0;
+}
+
+int bts_main(int argc, char **argv)
+{
+ struct gsm_bts_trx *trx;
+ struct e1inp_line *line;
+ int rc;
+
+ printf("((*))\n |\n / \\ OsmoBTS\n");
+
+ /* Track the use of talloc NULL memory contexts */
+ talloc_enable_null_tracking();
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 100*1024);
+ bts_vty_info.tall_ctx = tall_bts_ctx;
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+ vty_init(&bts_vty_info);
+ ctrl_vty_init(tall_bts_ctx);
+ rate_ctr_init(tall_bts_ctx);
+
+ handle_options(argc, argv);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (!bts) {
+ fprintf(stderr, "Failed to create BTS structure\n");
+ exit(1);
+ }
+
+ e1inp_vty_init();
+ bts_vty_init(bts, &bts_log_info);
+
+ /* enable realtime priority for us */
+ if (rt_prio != -1) {
+ struct sched_param param;
+
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = rt_prio;
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc != 0) {
+ fprintf(stderr, "Setting SCHED_RR priority(%d) failed: %s\n",
+ param.sched_priority, strerror(errno));
+ exit(1);
+ }
+ }
+
+ if (gsmtap_ip) {
+ gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1);
+ if (!gsmtap) {
+ fprintf(stderr, "Failed during gsmtap_init()\n");
+ exit(1);
+ }
+ gsmtap_source_add_sink(gsmtap);
+ }
+
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to open bts\n");
+ exit(1);
+ }
+
+ abis_init(bts);
+
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ config_file);
+ exit(1);
+ }
+
+ if (!phy_link_by_num(0)) {
+ fprintf(stderr, "You need to configure at least phy0\n");
+ exit(1);
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (!trx->role_bts.l1h) {
+ fprintf(stderr, "TRX %u has no associated PHY instance\n",
+ trx->nr);
+ exit(1);
+ }
+ }
+
+ write_pid_file("osmo-bts");
+
+ bts_controlif_setup(bts, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_BTS);
+
+ rc = telnet_init_dynif(tall_bts_ctx, NULL, vty_get_bind_addr(),
+ g_vty_port_num);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ if (pcu_sock_init(bts->pcu.sock_path)) {
+ fprintf(stderr, "PCU L1 socket failed\n");
+ exit(1);
+ }
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ //signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ if (!bts->bsc_oml_host) {
+ fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n");
+ exit(1);
+ }
+
+ line = abis_open(bts, bts->bsc_oml_host, "sysmoBTS");
+ if (!line) {
+ fprintf(stderr, "unable to connect to BSC\n");
+ exit(2);
+ }
+
+ rc = phy_links_open();
+ if (rc < 0) {
+ fprintf(stderr, "unable to open PHY link(s)\n");
+ exit(2);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (quit < 2) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/common/measurement.c b/src/common/measurement.c
new file mode 100644
index 00000000..c2001dae
--- /dev/null
+++ b/src/common/measurement.c
@@ -0,0 +1,723 @@
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/rsl.h>
+
+/* Tables as per TS 45.008 Section 8.3 */
+static const uint8_t ts45008_83_tch_f[] = { 52, 53, 54, 55, 56, 57, 58, 59 };
+static const uint8_t ts45008_83_tch_hs0[] = { 0, 2, 4, 6, 52, 54, 56, 58 };
+static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 };
+
+/* In cases where we less measurements than we expect we must assume that we
+ * just did not receive the block because it was lost due to bad channel
+ * conditions. We set up a dummy measurement result here that reflects the
+ * worst possible result. In our* calculation we will use this dummy to replace
+ * the missing measurements */
+#define MEASUREMENT_DUMMY_BER 10000 /* 100% BER */
+#define MEASUREMENT_DUMMY_IRSSI 109 /* noise floor in -dBm */
+static const struct bts_ul_meas measurement_dummy = (struct bts_ul_meas) {
+ .ber10k = MEASUREMENT_DUMMY_BER,
+ .ta_offs_256bits = 0,
+ .c_i = 0,
+ .is_sub = 0,
+ .inv_rssi = MEASUREMENT_DUMMY_IRSSI
+};
+
+/* find out if an array contains a given key as element */
+#define ARRAY_CONTAINS(arr, val) array_contains(arr, ARRAY_SIZE(arr), val)
+static bool array_contains(const uint8_t *arr, unsigned int len, uint8_t val) {
+ int i;
+ for (i = 0; i < len; i++) {
+ if (arr[i] == val)
+ return true;
+ }
+ return false;
+}
+
+/* Decide if a given frame number is part of the "-SUB" measurements (true) or not (false)
+ * (this function is only used internally, it is public to call it from unit-tests) */
+bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn, bool is_amr_sid_update)
+{
+ uint32_t fn104 = fn % 104;
+
+ /* See TS 45.008 Sections 8.3 and 8.4 for a detailed descriptions of the rules
+ * implemented here. We only implement the logic for Voice, not CSD */
+
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
+ return true;
+ if (ARRAY_CONTAINS(ts45008_83_tch_f, fn104))
+ return true;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
+ return true;
+ if (is_amr_sid_update)
+ return true;
+ break;
+ default:
+ LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
+ gsm_lchan_name(lchan), lchan->tch_mode);
+ break;
+ }
+ break;
+ case GSM_LCHAN_TCH_H:
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
+ return true;
+ switch (lchan->nr) {
+ case 0:
+ if (ARRAY_CONTAINS(ts45008_83_tch_hs0, fn104))
+ return true;
+ break;
+ case 1:
+ if (ARRAY_CONTAINS(ts45008_83_tch_hs1, fn104))
+ return true;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ if (trx_sched_is_sacch_fn(lchan->ts, fn, true))
+ return true;
+ if (is_amr_sid_update)
+ return true;
+ break;
+ case GSM48_CMODE_SIGN:
+ /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are
+ * SUB */
+ return true;
+ default:
+ LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
+ gsm_lchan_name(lchan), lchan->tch_mode);
+ break;
+ }
+ break;
+ case GSM_LCHAN_SDCCH:
+ /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are SUB */
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+/* Measurement reporting period and mapping of SACCH message block for TCHF
+ * and TCHH chan As per in 3GPP TS 45.008, section 8.4.1.
+ *
+ * Timeslot number (TN) TDMA frame number (FN) modulo 104
+ * Half rate, Half rate, Reporting SACCH
+ * Full Rate subch.0 subch.1 period Message block
+ * 0 0 and 1 0 to 103 12, 38, 64, 90
+ * 1 0 and 1 13 to 12 25, 51, 77, 103
+ * 2 2 and 3 26 to 25 38, 64, 90, 12
+ * 3 2 and 3 39 to 38 51, 77, 103, 25
+ * 4 4 and 5 52 to 51 64, 90, 12, 38
+ * 5 4 and 5 65 to 64 77, 103, 25, 51
+ * 6 6 and 7 78 to 77 90, 12, 38, 64
+ * 7 6 and 7 91 to 90 103, 25, 51, 77
+ *
+ * Note: The array index of the following three lookup tables refes to a
+ * timeslot number. */
+
+static const uint8_t tchf_meas_rep_fn104_by_ts[] = {
+ [0] = 90,
+ [1] = 103,
+ [2] = 12,
+ [3] = 25,
+ [4] = 38,
+ [5] = 51,
+ [6] = 64,
+ [7] = 77,
+};
+static const uint8_t tchh0_meas_rep_fn104_by_ts[] = {
+ [0] = 90,
+ [1] = 90,
+ [2] = 12,
+ [3] = 12,
+ [4] = 38,
+ [5] = 38,
+ [6] = 64,
+ [7] = 64,
+};
+static const uint8_t tchh1_meas_rep_fn104_by_ts[] = {
+ [0] = 103,
+ [1] = 103,
+ [2] = 25,
+ [3] = 25,
+ [4] = 51,
+ [5] = 51,
+ [6] = 77,
+ [7] = 77,
+};
+
+/* Measurement reporting period for SDCCH8 and SDCCH4 chan
+ * As per in 3GPP TS 45.008, section 8.4.2.
+ *
+ * Logical Chan TDMA frame number
+ * (FN) modulo 102
+ *
+ * SDCCH/8 12 to 11
+ * SDCCH/4 37 to 36
+ *
+ *
+ * Note: The array index of the following three lookup tables refes to a
+ * subslot number. */
+
+/* FN of the first burst whose block completes before reaching fn%102=11 */
+static const uint8_t sdcch8_meas_rep_fn102_by_ss[] = {
+ [0] = 66, /* 15(SDCCH), 47(SACCH), 66(SDCCH) */
+ [1] = 70, /* 19(SDCCH), 51(SACCH), 70(SDCCH) */
+ [2] = 74, /* 23(SDCCH), 55(SACCH), 74(SDCCH) */
+ [3] = 78, /* 27(SDCCH), 59(SACCH), 78(SDCCH) */
+ [4] = 98, /* 31(SDCCH), 98(SACCH), 82(SDCCH) */
+ [5] = 0, /* 35(SDCCH), 0(SACCH), 86(SDCCH) */
+ [6] = 4, /* 39(SDCCH), 4(SACCH), 90(SDCCH) */
+ [7] = 8, /* 43(SDCCH), 8(SACCH), 94(SDCCH) */
+};
+
+/* FN of the first burst whose block completes before reaching fn%102=37 */
+static const uint8_t sdcch4_meas_rep_fn102_by_ss[] = {
+ [0] = 88, /* 37(SDCCH), 57(SACCH), 88(SDCCH) */
+ [1] = 92, /* 41(SDCCH), 61(SACCH), 92(SDCCH) */
+ [2] = 6, /* 6(SACCH), 47(SDCCH), 98(SDCCH) */
+ [3] = 10 /* 10(SACCH), 0(SDCCH), 51(SDCCH) */
+};
+
+/* Note: The reporting of the measurement results is done via the SACCH channel.
+ * The measurement interval is not aligned with the interval in which the
+ * SACCH is transmitted. When we receive the measurement indication with the
+ * SACCH block, the corresponding measurement interval will already have ended
+ * and we will get the results late, but on spot with the beginning of the
+ * next measurement interval.
+ *
+ * For example: We get a measurement indication on FN%104=38 in TS=2. Then we
+ * will have to look at 3GPP TS 45.008, section 8.4.1 (or 3GPP TS 05.02 Clause 7
+ * Table 1 of 9) what value we need to feed into the lookup tables in order to
+ * detect the measurement period ending. In this example the "real" ending
+ * was on FN%104=12. This is the value we have to look for in
+ * tchf_meas_rep_fn104_by_ts to know that a measurement period has just ended. */
+
+/* See also 3GPP TS 05.02 Clause 7 Table 1 of 9:
+ * Mapping of logical channels onto physical channels (see subclauses 6.3, 6.4, 6.5) */
+static uint8_t translate_tch_meas_rep_fn104(uint8_t fn_mod)
+{
+ switch (fn_mod) {
+ case 25:
+ return 103;
+ case 38:
+ return 12;
+ case 51:
+ return 25;
+ case 64:
+ return 38;
+ case 77:
+ return 51;
+ case 90:
+ return 64;
+ case 103:
+ return 77;
+ case 12:
+ return 90;
+ }
+
+ /* Invalid / not of interest */
+ return 0;
+}
+
+/* determine if a measurement period ends at the given frame number
+ * (this function is only used internally, it is public to call it from
+ * unit-tests) */
+int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn)
+{
+ unsigned int fn_mod = -1;
+ const uint8_t *tbl;
+ int rc = 0;
+ enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);
+
+ if (lchan->ts->nr >= 8)
+ return -EINVAL;
+ if (pchan >= _GSM_PCHAN_MAX)
+ return -EINVAL;
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ fn_mod = translate_tch_meas_rep_fn104(fn % 104);
+ if (tchf_meas_rep_fn104_by_ts[lchan->ts->nr] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_TCH_H:
+ fn_mod = translate_tch_meas_rep_fn104(fn % 104);
+ if (lchan->nr == 0)
+ tbl = tchh0_meas_rep_fn104_by_ts;
+ else
+ tbl = tchh1_meas_rep_fn104_by_ts;
+ if (tbl[lchan->ts->nr] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ fn_mod = fn % 102;
+ if (sdcch8_meas_rep_fn102_by_ss[lchan->nr] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ fn_mod = fn % 102;
+ if (sdcch4_meas_rep_fn102_by_ss[lchan->nr] == fn_mod)
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ if (rc == 1) {
+ DEBUGP(DMEAS,
+ "%s meas period end fn:%u, fn_mod:%i, status:%d, pchan:%s\n",
+ gsm_lchan_name(lchan), fn, fn_mod, rc, gsm_pchan_name(pchan));
+ }
+
+ return rc;
+}
+
+/* determine the measurement interval modulus by a given lchan */
+static uint8_t modulus_by_lchan(struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ return 104;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ return 102;
+ break;
+ default:
+ /* Invalid */
+ return 1;
+ break;
+ }
+}
+
+/* receive a L1 uplink measurement from L1 (this function is only used
+ * internally, it is public to call it from unit-tests) */
+int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn)
+{
+ uint32_t fn_mod = fn % modulus_by_lchan(lchan);
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGPFN(DMEAS, LOGL_NOTICE, fn,
+ "%s measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state),
+ lchan->meas.num_ul_meas, fn_mod);
+ }
+
+ if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) {
+ LOGPFN(DMEAS, LOGL_NOTICE, fn,
+ "%s no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n",
+ gsm_lchan_name(lchan), lchan->meas.num_ul_meas, fn_mod);
+ return -ENOSPC;
+ }
+
+ /* We expect the lower layers to mark AMR SID_UPDATE frames already as such.
+ * In this function, we only deal with the comon logic as per the TS 45.008 tables */
+ if (!ulm->is_sub)
+ ulm->is_sub = ts45008_83_is_sub(lchan, fn, false);
+
+ DEBUGPFN(DMEAS, fn, "%s adding measurement (is_sub=%u), num_ul_meas=%d, fn_mod=%u\n",
+ gsm_lchan_name(lchan), ulm->is_sub, lchan->meas.num_ul_meas, fn_mod);
+
+ memcpy(&lchan->meas.uplink[lchan->meas.num_ul_meas++], ulm,
+ sizeof(*ulm));
+
+ lchan->meas.last_fn = fn;
+
+ return 0;
+}
+
+/* input: BER in steps of .01%, i.e. percent/100 */
+static uint8_t ber10k_to_rxqual(uint32_t ber10k)
+{
+ /* Eight levels of Rx quality are defined and are mapped to the
+ * equivalent BER before channel decoding, as per in 3GPP TS 45.008,
+ * secton 8.2.4.
+ *
+ * RxQual: BER Range:
+ * RXQUAL_0 BER < 0,2 % Assumed value = 0,14 %
+ * RXQUAL_1 0,2 % < BER < 0,4 % Assumed value = 0,28 %
+ * RXQUAL_2 0,4 % < BER < 0,8 % Assumed value = 0,57 %
+ * RXQUAL_3 0,8 % < BER < 1,6 % Assumed value = 1,13 %
+ * RXQUAL_4 1,6 % < BER < 3,2 % Assumed value = 2,26 %
+ * RXQUAL_5 3,2 % < BER < 6,4 % Assumed value = 4,53 %
+ * RXQUAL_6 6,4 % < BER < 12,8 % Assumed value = 9,05 %
+ * RXQUAL_7 12,8 % < BER Assumed value = 18,10 % */
+
+ if (ber10k < 20)
+ return 0;
+ if (ber10k < 40)
+ return 1;
+ if (ber10k < 80)
+ return 2;
+ if (ber10k < 160)
+ return 3;
+ if (ber10k < 320)
+ return 4;
+ if (ber10k < 640)
+ return 5;
+ if (ber10k < 1280)
+ return 6;
+ return 7;
+}
+
+/* Get the number of measurements that we expect for a specific lchan.
+ * (This is a static number that is defined by the specific slot layout of
+ * the channel) */
+static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ /* 24 for TCH + 1 for SACCH */
+ return 25;
+ case GSM_PCHAN_TCH_H:
+ /* 24 half-blocks for TCH + 1 for SACCH */
+ return 25;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* 2 for SDCCH + 1 for SACCH */
+ return 3;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /* 2 for SDCCH + 1 for SACCH */
+ return 3;
+ default:
+ return lchan->meas.num_ul_meas;
+ }
+}
+
+/* In DTX a subset of blocks must always be transmitted
+ * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX) */
+static unsigned int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);
+
+ /* AMR is using a more elaborated model with a dymanic amount of
+ * DTX blocks so this function is not applicable to determine the
+ * amount of expected SUB Measurements when AMR is used */
+ OSMO_ASSERT(lchan->tch_mode != GSM48_CMODE_SPEECH_AMR)
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ /* 1 block SDCCH, 2 blocks TCH */
+ return 3;
+ case GSM_PCHAN_TCH_H:
+ /* 1 block SDCCH, 4 half-blocks TCH */
+ return 5;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* no DTX here, all blocks must be present! */
+ return 3;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /* no DTX here, all blocks must be present! */
+ return 3;
+ default:
+ return 0;
+ }
+}
+
+/* if we clip the TOA value to 12 bits, i.e. toa256=3200,
+ * -> the maximum deviation can be 2*3200 = 6400
+ * -> the maximum squared deviation can be 6400^2 = 40960000
+ * -> the maximum sum of squared deviations can be 104*40960000 = 4259840000
+ * and hence fit into uint32_t
+ * -> once the value is divided by 104, it's again below 40960000
+ * leaving 6 MSBs of freedom, i.e. we could extend by 64, resulting in 2621440000
+ * -> as a result, the standard deviation could be communicated with up to six bits
+ * of fractional fixed-point number.
+ */
+
+/* compute Osmocom extended measurements for the given lchan */
+static void lchan_meas_compute_extended(struct gsm_lchan *lchan)
+{
+ unsigned int num_ul_meas;
+ unsigned int num_ul_meas_excess = 0;
+ unsigned int num_ul_meas_expect;
+
+ /* we assume that lchan_meas_check_compute() has already computed the mean value
+ * and we can compute the min/max/variance/stddev from this */
+ int i;
+
+ /* each measurement is an int32_t, so the squared difference value must fit in 32bits */
+ /* the sum of the squared values (each up to 32bit) can very easily exceed 32 bits */
+ u_int64_t sq_diff_sum = 0;
+
+ /* In case we do not have any measurement values collected there is no
+ * computation possible. We just skip the whole computation here, the
+ * lchan->meas.flags will not get the LC_UL_M_F_OSMO_EXT_VALID flag set
+ * so no extended measurement results will be reported back via RSL.
+ * this is ok, since we have nothing to report anyway and apart of that
+ * we also just lost the signal (otherwise we would have at least some
+ * measurements). */
+ if (!lchan->meas.num_ul_meas)
+ return;
+
+ /* initialize min/max values with their counterpart */
+ lchan->meas.ext.toa256_min = INT16_MAX;
+ lchan->meas.ext.toa256_max = INT16_MIN;
+
+ /* Determine the number of measurement values we need to take into the
+ * computation. In this case we only compute over the measurements we
+ * have indeed received. Since this computation is about timing
+ * information it does not make sense to approach missing measurement
+ * samples the TOA with 0. This would bend the average towards 0. What
+ * counts is the average TOA of the properly received blocks so that
+ * the TA logic can make a proper decision. */
+ num_ul_meas_expect = lchan_meas_num_expected(lchan);
+ if (lchan->meas.num_ul_meas > num_ul_meas_expect) {
+ num_ul_meas = num_ul_meas_expect;
+ num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect;
+ }
+ else
+ num_ul_meas = lchan->meas.num_ul_meas;
+
+ /* all computations are done on the relative arrival time of the burst, relative to the
+ * beginning of its slot. This is of course excluding the TA value that the MS has already
+ * compensated/pre-empted its transmission */
+
+ /* step 1: compute the sum of the squared difference of each value to mean */
+ for (i = 0; i < num_ul_meas; i++) {
+ const struct bts_ul_meas *m;
+
+ OSMO_ASSERT(i < lchan->meas.num_ul_meas);
+ m = &lchan->meas.uplink[i+num_ul_meas_excess];
+
+ int32_t diff = (int32_t)m->ta_offs_256bits - (int32_t)lchan->meas.ms_toa256;
+ /* diff can now be any value of +65535 to -65535, so we can safely square it,
+ * but only in unsigned math. As squaring looses the sign, we can simply drop
+ * it before squaring, too. */
+ uint32_t diff_abs = labs(diff);
+ uint32_t diff_squared = diff_abs * diff_abs;
+ sq_diff_sum += diff_squared;
+
+ /* also use this loop iteration to compute min/max values */
+ if (m->ta_offs_256bits > lchan->meas.ext.toa256_max)
+ lchan->meas.ext.toa256_max = m->ta_offs_256bits;
+ if (m->ta_offs_256bits < lchan->meas.ext.toa256_min)
+ lchan->meas.ext.toa256_min = m->ta_offs_256bits;
+ }
+ /* step 2: compute the variance (mean of sum of squared differences) */
+ sq_diff_sum = sq_diff_sum / num_ul_meas;
+ /* as the individual summed values can each not exceed 2^32, and we're
+ * dividing by the number of summands, the resulting value can also not exceed 2^32 */
+ OSMO_ASSERT(sq_diff_sum <= UINT32_MAX);
+ /* step 3: compute the standard deviation from the variance */
+ lchan->meas.ext.toa256_std_dev = osmo_isqrt32(sq_diff_sum);
+ lchan->meas.flags |= LC_UL_M_F_OSMO_EXT_VALID;
+}
+
+int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
+{
+ struct gsm_meas_rep_unidir *mru;
+ uint32_t ber_full_sum = 0;
+ uint32_t irssi_full_sum = 0;
+ uint32_t ber_sub_sum = 0;
+ uint32_t irssi_sub_sum = 0;
+ int32_t ta256b_sum = 0;
+ unsigned int num_meas_sub = 0;
+ unsigned int num_meas_sub_actual = 0;
+ unsigned int num_meas_sub_subst = 0;
+ unsigned int num_meas_sub_expect;
+ unsigned int num_ul_meas;
+ unsigned int num_ul_meas_actual = 0;
+ unsigned int num_ul_meas_subst = 0;
+ unsigned int num_ul_meas_expect;
+ unsigned int num_ul_meas_excess = 0;
+ int i;
+
+ /* if measurement period is not complete, abort */
+ if (!is_meas_complete(lchan, fn))
+ return 0;
+
+ LOGP(DMEAS, LOGL_DEBUG, "%s Calculating measurement results for physical channel:%s\n",
+ gsm_lchan_name(lchan), gsm_pchan_name(ts_pchan(lchan->ts)));
+
+ /* Note: Some phys will send no measurement indication at all
+ * when a block is lost. Also in DTX mode blocks are left out
+ * intentionally to save energy. It is not necessarly an error
+ * when we get less measurements as we expect. */
+ num_ul_meas_expect = lchan_meas_num_expected(lchan);
+
+ if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR)
+ num_meas_sub_expect = lchan_meas_sub_num_expected(lchan);
+ else {
+ /* FIXME: the amount of SUB Measurements is a dynamic parameter
+ * in AMR and can not be determined by using a lookup table.
+ * See also: OS#2978 */
+ num_meas_sub_expect = 0;
+ }
+
+ if (lchan->meas.num_ul_meas > num_ul_meas_expect)
+ num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect;
+ num_ul_meas = num_ul_meas_expect;
+
+ LOGP(DMEAS, LOGL_DEBUG, "%s received %u UL measurements, expected %u\n", gsm_lchan_name(lchan),
+ lchan->meas.num_ul_meas, num_ul_meas_expect);
+ if (num_ul_meas_excess)
+ LOGP(DMEAS, LOGL_DEBUG, "%s received %u excess UL measurements\n", gsm_lchan_name(lchan),
+ num_ul_meas_excess);
+
+ /* Measurement computation step 1: add up */
+ for (i = 0; i < num_ul_meas; i++) {
+ const struct bts_ul_meas *m;
+ bool is_sub = false;
+
+ /* Note: We will always compute over a full measurement,
+ * interval even when not enough measurement samples are in
+ * the buffer. As soon as we run out of measurement values
+ * we continue the calculation using dummy values. This works
+ * well for the BER, since there we can safely assume 100%
+ * since a missing measurement means that the data (block)
+ * is lost as well (some phys do not give us measurement
+ * reports for lost blocks or blocks that are spaced out for
+ * DTX). However, for RSSI and TA this does not work since
+ * there we would distort the calculation if we would replace
+ * them with a made up number. This means for those values we
+ * only compute over the data we have actually received. */
+
+ if (i < lchan->meas.num_ul_meas) {
+ m = &lchan->meas.uplink[i + num_ul_meas_excess];
+ if (m->is_sub) {
+ irssi_sub_sum += m->inv_rssi;
+ num_meas_sub_actual++;
+ is_sub = true;
+ }
+ irssi_full_sum += m->inv_rssi;
+ ta256b_sum += m->ta_offs_256bits;
+
+ num_ul_meas_actual++;
+ } else {
+ m = &measurement_dummy;
+ if (num_ul_meas_expect - i <= num_meas_sub_expect - num_meas_sub) {
+ num_meas_sub_subst++;
+ is_sub = true;
+ }
+
+ num_ul_meas_subst++;
+ }
+
+ ber_full_sum += m->ber10k;
+ if (is_sub) {
+ num_meas_sub++;
+ ber_sub_sum += m->ber10k;
+ }
+ }
+
+ LOGP(DMEAS, LOGL_DEBUG, "%s received UL measurements contain %u SUB measurements, expected %u\n",
+ gsm_lchan_name(lchan), num_meas_sub_actual, num_meas_sub_expect);
+ LOGP(DMEAS, LOGL_DEBUG, "%s replaced %u measurements with dummy values, from which %u were SUB measurements\n",
+ gsm_lchan_name(lchan), num_ul_meas_subst, num_meas_sub_subst);
+
+ if (num_meas_sub != num_meas_sub_expect) {
+ LOGP(DMEAS, LOGL_ERROR, "%s Incorrect number of SUB measurements detected! (%u vs exp %u)\n",
+ gsm_lchan_name(lchan), num_meas_sub, num_meas_sub_expect);
+ /* Normally the logic above should make sure that there is
+ * always the exact amount of SUB measurements taken into
+ * account. If not then the logic that decides tags the received
+ * measurements as is_sub works incorrectly. Since the logic
+ * above only adds missing measurements during the calculation
+ * it can not remove excess SUB measurements or add missing SUB
+ * measurements when there is no more room in the interval. */
+ }
+
+ /* Measurement computation step 2: divide */
+ ber_full_sum = ber_full_sum / num_ul_meas;
+
+ if (!irssi_full_sum)
+ ber_full_sum = MEASUREMENT_DUMMY_IRSSI;
+ else
+ irssi_full_sum = irssi_full_sum / num_ul_meas_actual;
+
+ if (!num_ul_meas_actual)
+ ta256b_sum = lchan->meas.ms_toa256;
+ else
+ ta256b_sum = ta256b_sum / num_ul_meas_actual;
+
+ if (!num_meas_sub)
+ ber_sub_sum = MEASUREMENT_DUMMY_BER;
+ else
+ ber_sub_sum = ber_sub_sum / num_meas_sub;
+
+ if (!num_meas_sub_actual)
+ irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI;
+ else
+ irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual;
+
+ LOGP(DMEAS, LOGL_INFO, "%s Computed TA256(% 4d) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), "
+ "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan),
+ ta256b_sum, ber_full_sum / 100,
+ ber_full_sum % 100, irssi_full_sum, ber_sub_sum / 100, ber_sub_sum % 100, irssi_sub_sum);
+
+ /* store results */
+ mru = &lchan->meas.ul_res;
+ mru->full.rx_lev = dbm2rxlev((int)irssi_full_sum * -1);
+ mru->sub.rx_lev = dbm2rxlev((int)irssi_sub_sum * -1);
+ mru->full.rx_qual = ber10k_to_rxqual(ber_full_sum);
+ mru->sub.rx_qual = ber10k_to_rxqual(ber_sub_sum);
+ lchan->meas.ms_toa256 = ta256b_sum;
+
+ LOGP(DMEAS, LOGL_INFO, "%s UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u),"
+ "RXQUAL_FULL(%u), RXQUAL_SUB(%u), num_meas_sub(%u), num_ul_meas(%u) \n",
+ gsm_lchan_name(lchan),
+ mru->full.rx_lev, mru->sub.rx_lev, mru->full.rx_qual, mru->sub.rx_qual, num_meas_sub, num_ul_meas_expect);
+
+ lchan->meas.flags |= LC_UL_M_F_RES_VALID;
+
+ lchan_meas_compute_extended(lchan);
+
+ lchan->meas.num_ul_meas = 0;
+
+ /* return 1 to indicte that the computation has been done and the next
+ * interval begins. */
+ return 1;
+}
+
+/* Process a single uplink measurement sample. This function is called from
+ * l1sap.c every time a measurement indication is received. It collects the
+ * measurement samples and automatically detects the end of the measurement
+ * interval. */
+int lchan_meas_process_measurement(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn)
+{
+ lchan_new_ul_meas(lchan, ulm, fn);
+ return lchan_meas_check_compute(lchan, fn);
+}
+
+/* Reset all measurement related struct members to their initial values. This
+ * function will be called every time an lchan is activated to ensure the
+ * measurement process starts with a defined state. */
+void lchan_meas_reset(struct gsm_lchan *lchan)
+{
+ memset(&lchan->meas, 0, sizeof(lchan->meas));
+ lchan->meas.last_fn = LCHAN_FN_DUMMY;
+}
diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c
new file mode 100644
index 00000000..f936c983
--- /dev/null
+++ b/src/common/msg_utils.c
@@ -0,0 +1,603 @@
+/* (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/rsl.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+
+#define STI_BIT_MASK 16
+
+static int check_fom(struct abis_om_hdr *omh, size_t len)
+{
+ if (omh->length != len) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect OM hdr length value %d %zu\n",
+ omh->length, len);
+ return -1;
+ }
+
+ if (len < sizeof(struct abis_om_fom_hdr)) {
+ LOGP(DL1C, LOGL_ERROR, "FOM header insufficient space %zu %zu\n",
+ len, sizeof(struct abis_om_fom_hdr));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_manuf(struct msgb *msg, struct abis_om_hdr *omh, size_t msg_size)
+{
+ int type;
+ size_t size;
+
+ if (msg_size < 1) {
+ LOGP(DL1C, LOGL_ERROR, "No ManId Length Indicator %zu\n",
+ msg_size);
+ return -1;
+ }
+
+ if (omh->data[0] >= msg_size - 1) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Insufficient message space for this ManId Length %d %zu\n",
+ omh->data[0], msg_size - 1);
+ return -1;
+ }
+
+ if (omh->data[0] == sizeof(abis_nm_ipa_magic) &&
+ strncmp(abis_nm_ipa_magic, (const char *)omh->data + 1,
+ sizeof(abis_nm_ipa_magic)) == 0) {
+ type = OML_MSG_TYPE_IPA;
+ size = sizeof(abis_nm_ipa_magic) + 1;
+ } else if (omh->data[0] == sizeof(abis_nm_osmo_magic) &&
+ strncmp(abis_nm_osmo_magic, (const char *) omh->data + 1,
+ sizeof(abis_nm_osmo_magic)) == 0) {
+ type = OML_MSG_TYPE_OSMO;
+ size = sizeof(abis_nm_osmo_magic) + 1;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Manuf Label Unknown\n");
+ return -1;
+ }
+
+ /* we have verified that the vendor string fits */
+ msg->l3h = omh->data + size;
+ if (check_fom(omh, msgb_l3len(msg)) != 0)
+ return -1;
+ return type;
+}
+
+/* check that DTX is in the middle of silence */
+static inline bool dtx_is_update(const struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return false;
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH)
+ return true;
+ return false;
+}
+
+/* check that DTX is in the beginning of silence for AMR HR */
+bool dtx_is_first_p1(const struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return false;
+ if ((lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1))
+ return true;
+ return false;
+}
+
+/* update lchan SID status */
+void lchan_set_marker(bool t, struct gsm_lchan *lchan)
+{
+ if (t)
+ lchan->tch.dtx.ul_sid = true;
+ else if (lchan->tch.dtx.ul_sid) {
+ lchan->tch.dtx.ul_sid = false;
+ lchan->rtp_tx_marker = true;
+ }
+}
+
+/*! \brief Store the last SID frame in lchan context
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] l1_payload buffer with SID data
+ * \param[in] length length of l1_payload
+ * \param[in] fn Frame Number for which we check scheduling
+ * \param[in] update 0 if SID_FIRST, 1 if SID_UPDATE, -1 if not AMR SID
+ */
+void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload,
+ size_t length, uint32_t fn, int update)
+{
+ size_t amr = (update < 0) ? 0 : 2,
+ copy_len = OSMO_MIN(length,
+ ARRAY_SIZE(lchan->tch.dtx.cache) - amr);
+
+ lchan->tch.dtx.len = copy_len + amr;
+ /* SID FIRST is special because it's both sent and cached: */
+ if (update == 0) {
+ lchan->tch.dtx.is_update = false; /* Mark SID FIRST explicitly */
+ /* for non-AMR case - always update FN for incoming SID FIRST */
+ if (!amr || !dtx_is_update(lchan))
+ lchan->tch.dtx.fn = fn;
+ /* for AMR case - do not update FN if SID FIRST arrives in a
+ middle of silence: this should not be happening according to
+ the spec */
+ }
+
+ memcpy(lchan->tch.dtx.cache + amr, l1_payload, copy_len);
+}
+
+/*! \brief Check current state of DTX DL AMR FSM and dispatch necessary events
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] rtp_pl buffer with RTP data
+ * \param[in] rtp_pl_len length of rtp_pl
+ * \param[in] fn Frame Number for which we check scheduling
+ * \param[in] l1_payload buffer where CMR and CMI prefix should be added
+ * \param[in] marker RTP Marker bit
+ * \param[out] len Length of expected L1 payload
+ * \param[out] ft_out Frame Type to be populated after decoding
+ * \returns 0 in case of success; negative on error
+ */
+int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
+ size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload,
+ bool marker, uint8_t *len, uint8_t *ft_out)
+{
+ uint8_t cmr;
+ enum osmo_amr_type ft;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+ int rc;
+
+ if (dtx_dl_amr_enabled(lchan)) {
+ if (lchan->type == GSM_LCHAN_TCH_H && !rtp_pl) {
+ /* we're called by gen_empty_tch_msg() to handle states
+ specific to AMR HR DTX */
+ switch (lchan->tch.dtx.dl_amr_fsm->state) {
+ case ST_SID_F2:
+ *len = 3; /* SID-FIRST P1 -> P2 completion */
+ memcpy(l1_payload, lchan->tch.dtx.cache, 2);
+ rc = 0;
+ dtx_dispatch(lchan, E_COMPL);
+ break;
+ case ST_SID_U:
+ rc = -EBADMSG;
+ dtx_dispatch(lchan, E_SID_U);
+ break;
+ default:
+ rc = -EBADMSG;
+ }
+ return rc;
+ }
+ }
+
+ if (!rtp_pl_len)
+ return -EBADMSG;
+
+ rc = osmo_amr_rtp_dec(rtp_pl, rtp_pl_len, &cmr, &cmi, &ft, &bfi, &sti);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "failed to decode AMR RTP (length %zu, "
+ "%p)\n", rtp_pl_len, rtp_pl);
+ return rc;
+ }
+
+ /* only needed for old sysmo firmware: */
+ *ft_out = ft;
+
+ /* CMI in downlink tells the L1 encoder which encoding function
+ * it will use, so we have to use the frame type */
+ if (osmo_amr_is_speech(ft))
+ cmi = ft;
+
+ /* populate L1 payload with CMR/CMI - might be ignored by caller: */
+ amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi, cmr);
+
+ /* populate DTX cache with CMR/CMI - overwrite cache which will be
+ either updated or invalidated by caller anyway: */
+ amr_set_mode_pref(lchan->tch.dtx.cache, &lchan->tch.amr_mr, cmi, cmr);
+ *len = 3 + rtp_pl_len;
+
+ /* DTX DL is not enabled, move along */
+ if (!lchan->ts->trx->bts->dtxd)
+ return 0;
+
+ if (osmo_amr_is_speech(ft)) {
+ /* AMR HR - SID-FIRST_P1 Inhibition */
+ if (marker && lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_INHIB, (void *)lchan);
+
+ /* AMR HR - SID-UPDATE Inhibition */
+ if (marker && lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_INHIB, (void *)lchan);
+
+ /* AMR FR & HR - generic */
+ if (marker && (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1 ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2 ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH))
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_ONSET, (void *)lchan);
+
+ if (lchan->tch.dtx.dl_amr_fsm->state != ST_VOICE)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_VOICE, (void *)lchan);
+
+ return 0;
+ }
+
+ if (ft == AMR_SID) {
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) {
+ /* SID FIRST/UPDATE scheduling logic relies on SID FIRST
+ being sent first hence we have to force caching of SID
+ as FIRST regardless of actually decoded type */
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, false);
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ sti ? E_SID_U : E_SID_F,
+ (void *)lchan);
+ } else if (lchan->tch.dtx.dl_amr_fsm->state != ST_FACCH)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, sti);
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_COMPL, (void *)lchan);
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ sti ? E_SID_U : E_SID_F,
+ (void *)lchan);
+ }
+
+ if (ft != AMR_NO_DATA) {
+ LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft);
+ return -ENOTSUP;
+ }
+
+ if (marker)
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, E_VOICE,
+ (void *)lchan);
+ *len = 0;
+ return 0;
+}
+
+/* STI is located in payload byte 6, cache contains 2 byte prefix (CMR/CMI)
+ * STI set = SID UPDATE, STI unset = SID FIRST
+ */
+static inline void dtx_sti_set(struct gsm_lchan *lchan)
+{
+ lchan->tch.dtx.cache[6 + 2] |= STI_BIT_MASK;
+}
+
+static inline void dtx_sti_unset(struct gsm_lchan *lchan)
+{
+ lchan->tch.dtx.cache[6 + 2] &= ~STI_BIT_MASK;
+}
+
+/*! \brief Check if enough time has passed since last SID (if any) to repeat it
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] fn Frame Number for which we check scheduling
+ * \returns true if transmission can be omitted, false otherwise
+ */
+static inline bool dtx_amr_sid_optional(struct gsm_lchan *lchan, uint32_t fn)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return true;
+
+ /* Compute approx. time delta x26 based on Fn duration */
+ uint32_t dx26 = 120 * (fn - lchan->tch.dtx.fn);
+
+ /* We're resuming after FACCH interruption */
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
+ /* force STI bit to 0 so cache is treated as SID FIRST */
+ dtx_sti_unset(lchan);
+ lchan->tch.dtx.is_update = false;
+ /* check that this FN has not been used for FACCH message
+ already: we rely here on the order of RTS arrival from L1 - we
+ expect that PH-DATA.req ALWAYS comes before PH-TCH.req for the
+ same FN */
+ if(lchan->type == GSM_LCHAN_TCH_H) {
+ if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY &&
+ lchan->tch.dtx.fn != LCHAN_FN_WAIT) {
+ /* FACCH interruption is over */
+ dtx_dispatch(lchan, E_COMPL);
+ return false;
+ } else if(lchan->tch.dtx.fn == LCHAN_FN_DUMMY) {
+ lchan->tch.dtx.fn = LCHAN_FN_WAIT;
+ } else
+ lchan->tch.dtx.fn = fn;
+ } else if(lchan->type == GSM_LCHAN_TCH_F) {
+ if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY) {
+ /* FACCH interruption is over */
+ dtx_dispatch(lchan, E_COMPL);
+ return false;
+ } else
+ lchan->tch.dtx.fn = fn;
+ }
+ /* this FN was already used for FACCH or ONSET message so we just
+ prepare things for next one */
+ return true;
+ }
+
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE)
+ return true;
+
+ /* according to 3GPP TS 26.093 A.5.1.1:
+ (*26) to avoid float math, add 1 FN tolerance (-120) */
+ if (lchan->tch.dtx.is_update) { /* SID UPDATE: every 8th RTP frame */
+ if (dx26 < GSM_RTP_FRAME_DURATION_MS * 8 * 26 - 120)
+ return true;
+ return false;
+ }
+ /* 3rd frame after SID FIRST should be SID UPDATE */
+ if (dx26 < GSM_RTP_FRAME_DURATION_MS * 3 * 26 - 120)
+ return true;
+ return false;
+}
+
+static inline bool fn_chk(const uint8_t *t, uint32_t fn, uint8_t len)
+{
+ uint8_t i;
+ for (i = 0; i < len; i++)
+ if (fn % 104 == t[i])
+ return false;
+ return true;
+}
+
+/*! \brief Check if TX scheduling is optional for a given FN in case of DTX
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] fn Frame Number for which we check scheduling
+ * \returns true if transmission can be omitted, false otherwise
+ */
+static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn)
+{
+ /* According to 3GPP TS 45.008 § 8.3: */
+ static const uint8_t f[] = { 52, 53, 54, 55, 56, 57, 58, 59 },
+ h0[] = { 0, 2, 4, 6, 52, 54, 56, 58 },
+ h1[] = { 14, 16, 18, 20, 66, 68, 70, 72 };
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1) {
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ return fn_chk(f, fn, ARRAY_SIZE(f));
+ else
+ return fn_chk(lchan->nr ? h1 : h0, fn,
+ lchan->nr ? ARRAY_SIZE(h1) :
+ ARRAY_SIZE(h0));
+ }
+ return false;
+}
+
+/*! \brief Check if DTX DL AMR is enabled for a given lchan (it have proper type,
+ * FSM is allocated etc.)
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \returns true if DTX DL AMR is enabled, false otherwise
+ */
+bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan)
+{
+ if (lchan->ts->trx->bts->dtxd &&
+ lchan->tch.dtx.dl_amr_fsm &&
+ lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ return true;
+ return false;
+}
+
+/*! \brief Check if DTX DL AMR FSM state is recursive: requires secondary
+ * response to a single RTS request from L1.
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \returns true if DTX DL AMR FSM state is recursive, false otherwise
+ */
+bool dtx_recursion(const struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return false;
+
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V_REC)
+ return true;
+
+ return false;
+}
+
+/*! \brief Send signal to FSM: with proper check if DIX is enabled for this lchan
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] e DTX DL AMR FSM Event
+ */
+void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e)
+{
+ if (dtx_dl_amr_enabled(lchan))
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, e,
+ (void *)lchan);
+}
+
+/*! \brief Send internal signal to FSM: check that DTX is enabled for this chan,
+ * check that current FSM and lchan states are permitting such signal.
+ * Note: this should be the only way to dispatch E_COMPL to FSM from
+ * BTS code.
+ * \param[in] lchan Logical channel on which we check scheduling
+ */
+void dtx_int_signal(struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return;
+
+ if (dtx_is_first_p1(lchan) || dtx_recursion(lchan))
+ dtx_dispatch(lchan, E_COMPL);
+}
+
+/*! \brief Repeat last SID if possible in case of DTX
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] dst Buffer to copy last SID into
+ * \returns Number of bytes copied + 1 (to accommodate for extra byte with
+ * payload type), 0 if there's nothing to copy
+ */
+uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn)
+{
+ /* FIXME: add EFR support */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)
+ return 0;
+
+ if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) {
+ if (dtx_sched_optional(lchan, fn))
+ return 0;
+ } else
+ if (dtx_amr_sid_optional(lchan, fn))
+ return 0;
+
+ if (lchan->tch.dtx.len) {
+ if (dtx_dl_amr_enabled(lchan)) {
+ if ((lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2) ||
+ (lchan->type == GSM_LCHAN_TCH_F &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1)) {
+ /* advance FSM in case we've just sent SID FIRST
+ to restore silence after FACCH interruption */
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_SID_U, (void *)lchan);
+ dtx_sti_unset(lchan);
+ } else if (dtx_is_update(lchan)) {
+ /* enforce SID UPDATE for next repetition: it
+ might have been altered by FACCH handling */
+ dtx_sti_set(lchan);
+ if (lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state ==
+ ST_U_NOINH)
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_COMPL,
+ (void *)lchan);
+ lchan->tch.dtx.is_update = true;
+ }
+ }
+ memcpy(dst, lchan->tch.dtx.cache, lchan->tch.dtx.len);
+ lchan->tch.dtx.fn = fn;
+ return lchan->tch.dtx.len + 1;
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "Have to send %s frame on TCH but SID buffer "
+ "is empty - sent nothing\n",
+ get_value_string(gsm48_chan_mode_names, lchan->tch_mode));
+
+ return 0;
+}
+
+/**
+ * Return 0 in case the IPA structure is okay and in this
+ * case the l2h will be set to the beginning of the data.
+ */
+int msg_verify_ipa_structure(struct msgb *msg)
+{
+ struct ipaccess_head *hh;
+
+ if (msgb_l1len(msg) < sizeof(struct ipaccess_head)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Ipa header insufficient space %d %zu\n",
+ msgb_l1len(msg), sizeof(struct ipaccess_head));
+ return -1;
+ }
+
+ hh = (struct ipaccess_head *) msg->l1h;
+
+ if (ntohs(hh->len) != msgb_l1len(msg) - sizeof(struct ipaccess_head)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Incorrect ipa header msg size %d %zu\n",
+ ntohs(hh->len), msgb_l1len(msg) - sizeof(struct ipaccess_head));
+ return -1;
+ }
+
+ if (hh->proto == IPAC_PROTO_OSMO) {
+ struct ipaccess_head_ext *hh_ext = (struct ipaccess_head_ext *) hh->data;
+ if (ntohs(hh->len) < sizeof(*hh_ext)) {
+ LOGP(DL1C, LOGL_ERROR, "IPA length shorter than OSMO header\n");
+ return -1;
+ }
+ msg->l2h = hh_ext->data;
+ } else
+ msg->l2h = hh->data;
+
+ return 0;
+}
+
+/**
+ * \brief Verify the structure of the OML message and set l3h
+ *
+ * This function verifies that the data in \param in msg is a proper
+ * OML message. This code assumes that msg->l2h points to the
+ * beginning of the OML message. In the successful case the msg->l3h
+ * will be set and will point to the FOM header. The value is undefined
+ * in all other cases.
+ *
+ * \param msg The message to analyze starting from msg->l2h.
+ * \return In case the structure is correct a positive number will be
+ * returned and msg->l3h will point to the FOM. The number is a
+ * classification of the vendor type of the message.
+ */
+int msg_verify_oml_structure(struct msgb *msg)
+{
+ struct abis_om_hdr *omh;
+
+ if (msgb_l2len(msg) < sizeof(*omh)) {
+ LOGP(DL1C, LOGL_ERROR, "Om header insufficient space %d %zu\n",
+ msgb_l2len(msg), sizeof(*omh));
+ return -1;
+ }
+
+ omh = (struct abis_om_hdr *) msg->l2h;
+ if (omh->mdisc != ABIS_OM_MDISC_FOM &&
+ omh->mdisc != ABIS_OM_MDISC_MANUF) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect om mdisc value %x\n",
+ omh->mdisc);
+ return -1;
+ }
+
+ if (omh->placement != ABIS_OM_PLACEMENT_ONLY) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect om placement value %x %x\n",
+ omh->placement, ABIS_OM_PLACEMENT_ONLY);
+ return -1;
+ }
+
+ if (omh->sequence != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect om sequence value %d\n",
+ omh->sequence);
+ return -1;
+ }
+
+ if (omh->mdisc == ABIS_OM_MDISC_MANUF)
+ return check_manuf(msg, omh, msgb_l2len(msg) - sizeof(*omh));
+
+ msg->l3h = omh->data;
+ if (check_fom(omh, msgb_l3len(msg)) != 0)
+ return -1;
+ return OML_MSG_TYPE_ETSI;
+}
diff --git a/src/common/oml.c b/src/common/oml.c
new file mode 100644
index 00000000..41debc1b
--- /dev/null
+++ b/src/common/oml.c
@@ -0,0 +1,1495 @@
+/* GSM TS 12.21 O&M / OML, BTS side */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * Operation and Maintenance Messages
+ */
+
+#include "btsconfig.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipaccess.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/phy_link.h>
+
+static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg);
+
+static struct tlv_definition abis_nm_att_tlvdef_ipa_local = {};
+
+/*
+ * support
+ */
+
+static int oml_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len)
+{
+ return tlv_parse(tp, &abis_nm_att_tlvdef_ipa_local, buf, len, 0, 0);
+}
+
+struct msgb *oml_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(1024, 128, "OML");
+}
+
+/* 3GPP TS 12.21 § 8.8.2 */
+static int oml_tx_failure_event_rep(struct gsm_abis_mo *mo, uint16_t cause_value,
+ const char *fmt, ...)
+{
+ struct msgb *nmsg;
+ va_list ap;
+
+ LOGP(DOML, LOGL_NOTICE, "Sending %s to BSC: ", get_value_string(abis_mm_event_cause_names, cause_value));
+ va_start(ap, fmt);
+ osmo_vlogp(DOML, LOGL_NOTICE, __FILE__, __LINE__, 1, fmt, ap);
+ nmsg = abis_nm_fail_evt_vrep(NM_EVT_PROC_FAIL, NM_SEVER_CRITICAL,
+ NM_PCAUSE_T_MANUF, cause_value, fmt, ap);
+ va_end(ap);
+ LOGPC(DOML, LOGL_NOTICE, "\n");
+
+ if (!nmsg)
+ return -ENOMEM;
+
+ return oml_mo_send_msg(mo, nmsg, NM_MT_FAILURE_EVENT_REP);
+}
+
+void oml_fail_rep(uint16_t cause_value, const char *fmt, ...)
+{
+ va_list ap;
+ char *rep;
+
+ va_start(ap, fmt);
+ rep = talloc_asprintf(tall_bts_ctx, fmt, ap);
+ va_end(ap);
+
+ osmo_signal_dispatch(SS_FAIL, cause_value, rep);
+ /* signal dispatch is synchronous so all the signal handlers are
+ finished already: we're free to free */
+ talloc_free(rep);
+}
+
+int oml_send_msg(struct msgb *msg, int is_manuf)
+{
+ struct abis_om_hdr *omh;
+
+ if (is_manuf) {
+ /* length byte, string + 0 termination */
+ uint8_t *manuf = msgb_push(msg, 1 + sizeof(abis_nm_ipa_magic));
+ manuf[0] = strlen(abis_nm_ipa_magic)+1;
+ memcpy(manuf+1, abis_nm_ipa_magic, strlen(abis_nm_ipa_magic));
+ }
+
+ /* Push the main OML header and send it off */
+ omh = (struct abis_om_hdr *) msgb_push(msg, sizeof(*omh));
+ if (is_manuf)
+ omh->mdisc = ABIS_OM_MDISC_MANUF;
+ else
+ omh->mdisc = ABIS_OM_MDISC_FOM;
+ omh->placement = ABIS_OM_PLACEMENT_ONLY;
+ omh->sequence = 0;
+ omh->length = msgb_l3len(msg);
+
+ msg->l2h = (uint8_t *)omh;
+
+ return abis_oml_sendmsg(msg);
+}
+
+int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type)
+{
+ struct abis_om_fom_hdr *foh;
+
+ msg->l3h = msgb_push(msg, sizeof(*foh));
+ foh = (struct abis_om_fom_hdr *) msg->l3h;
+ foh->msg_type = msg_type;
+ foh->obj_class = mo->obj_class;
+ memcpy(&foh->obj_inst, &mo->obj_inst, sizeof(foh->obj_inst));
+
+ /* FIXME: This assumption may not always be correct */
+ msg->trx = mo->bts->c0;
+
+ return oml_send_msg(msg, 0);
+}
+
+/* FIXME: move to gsm_data_shared */
+static char mo_buf[128];
+char *gsm_abis_mo_name(const struct gsm_abis_mo *mo)
+{
+ snprintf(mo_buf, sizeof(mo_buf), "OC=%s INST=(%02x,%02x,%02x)",
+ get_value_string(abis_nm_obj_class_names, mo->obj_class),
+ mo->obj_inst.bts_nr, mo->obj_inst.trx_nr, mo->obj_inst.ts_nr);
+ return mo_buf;
+}
+
+static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts)
+{
+ abis_nm_put_sw_file(msg, "osmobts", PACKAGE_VERSION, true);
+ abis_nm_put_sw_file(msg, btsatttr2str(BTS_TYPE_VARIANT), btsvariant2str(bts->variant), true);
+
+ if (strlen(bts->sub_model))
+ abis_nm_put_sw_file(msg, btsatttr2str(BTS_SUB_MODEL), bts->sub_model, true);
+}
+
+/* Add BTS features as 3GPP TS 52.021 §9.4.30 Manufacturer Id */
+static inline void add_bts_feat(struct msgb *msg, const struct gsm_bts *bts)
+{
+ msgb_tl16v_put(msg, NM_ATT_MANUF_ID, _NUM_BTS_FEAT/8 + 1, bts->_features_data);
+}
+
+static inline void add_trx_attr(struct msgb *msg, struct gsm_bts_trx *trx)
+{
+ const struct phy_instance *pinst = trx_phy_instance(trx);
+
+ abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), pinst && strlen(pinst->version) ? pinst->version : "Unknown",
+ true);
+}
+
+/* The number of attributes in §9.4.26 List of Required Attributes is 2 bytes,
+ but the Count of not-reported attributes from §9.4.64 is 1 byte */
+static inline uint8_t pack_num_unreported_attr(uint16_t attrs)
+{
+ if (attrs > 255) {
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes, Count of not-reported attributes is too big: %u\n",
+ attrs);
+ return 255;
+ }
+ return attrs; /* Return number of unhandled attributes */
+}
+
+/* copy all the attributes accumulated in msg to out and return the total length of out buffer */
+static inline int cleanup_attr_msg(uint8_t *out, int out_offset, struct msgb *msg)
+{
+ int len = 0;
+
+ out[0] = pack_num_unreported_attr(out_offset - 1);
+
+ if (msg) {
+ memcpy(out + out_offset, msgb_data(msg), msg->len);
+ len = msg->len;
+ msgb_free(msg);
+ }
+
+ return len + out_offset + 1;
+}
+
+static inline int handle_attrs_trx(uint8_t *out, struct gsm_bts_trx *trx, const uint8_t *attr, uint16_t attr_len)
+{
+ uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */
+ struct msgb *attr_buf = oml_msgb_alloc();
+
+ if (!attr_buf)
+ return -NM_NACK_CANT_PERFORM;
+
+ for (i = 0; i < attr_len; i++) {
+ bool processed = false;
+ switch (attr[i]) {
+ case NM_ATT_SW_CONFIG:
+ if (trx) {
+ add_trx_attr(attr_buf, trx);
+ processed = true;
+ } else
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unhandled due to missing TRX.\n",
+ i, get_value_string(abis_nm_att_names, attr[i]));
+ break;
+ default:
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by TRX.\n", i,
+ get_value_string(abis_nm_att_names, attr[i]));
+ }
+ /* assemble values of supported attributes and list of unsupported ones */
+ if (!processed) {
+ out[attr_out_index] = attr[i];
+ attr_out_index++;
+ }
+ }
+
+ return cleanup_attr_msg(out, attr_out_index, attr_buf);
+}
+
+static inline int handle_attrs_bts(uint8_t *out, const struct gsm_bts *bts, const uint8_t *attr, uint16_t attr_len)
+{
+ uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */
+ struct msgb *attr_buf = oml_msgb_alloc();
+
+ if (!attr_buf)
+ return -NM_NACK_CANT_PERFORM;
+
+ for (i = 0; i < attr_len; i++) {
+ switch (attr[i]) {
+ case NM_ATT_SW_CONFIG:
+ add_bts_attrs(attr_buf, bts);
+ break;
+ case NM_ATT_MANUF_ID:
+ add_bts_feat(attr_buf, bts);
+ break;
+ default:
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by BTS.\n", i,
+ get_value_string(abis_nm_att_names, attr[i]));
+ out[attr_out_index] = attr[i]; /* assemble values of supported attributes and list of unsupported ones */
+ attr_out_index++;
+ }
+ }
+
+ return cleanup_attr_msg(out, attr_out_index, attr_buf);
+}
+
+/* send 3GPP TS 52.021 §8.11.2 Get Attribute Response */
+static int oml_tx_attr_resp(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, const uint8_t *attr,
+ uint16_t attr_len)
+{
+ struct msgb *nmsg = oml_msgb_alloc();
+ uint8_t resp[MAX_VERSION_LENGTH * attr_len * 2]; /* heuristic for Attribute Response Info space requirements */
+ int len;
+
+ LOGP(DOML, LOGL_INFO, "%s Tx Get Attribute Response\n",
+ get_value_string(abis_nm_obj_class_names, foh->obj_class));
+
+ if (!nmsg)
+ return -NM_NACK_CANT_PERFORM;
+
+ switch (foh->obj_class) {
+ case NM_OC_BTS:
+ len = handle_attrs_bts(resp, bts, attr, attr_len);
+ break;
+ case NM_OC_BASEB_TRANSC:
+ len = handle_attrs_trx(resp, gsm_bts_trx_num(bts, foh->obj_inst.trx_nr), attr, attr_len);
+ break;
+ default:
+ LOGP(DOML, LOGL_ERROR, "Unsupported MO class %s in Get Attribute Response\n",
+ get_value_string(abis_nm_obj_class_names, foh->obj_class));
+ len = -NM_NACK_RES_NOTIMPL;
+ }
+
+ if (len < 0) {
+ LOGP(DOML, LOGL_ERROR, "Tx Get Attribute Response FAILED with %d\n", len);
+ msgb_free(nmsg);
+ return len;
+ }
+
+ /* §9.4.64 Get Attribute Response Info */
+ msgb_tl16v_put(nmsg, NM_ATT_GET_ARI, len, resp);
+
+ len = oml_mo_send_msg(&bts->mo, nmsg, NM_MT_GET_ATTR_RESP);
+ return (len < 0) ? -NM_NACK_CANT_PERFORM : len;
+}
+
+/* 8.8.1 sending State Changed Event Report */
+int oml_tx_state_changed(struct gsm_abis_mo *mo)
+{
+ struct msgb *nmsg;
+
+ LOGP(DOML, LOGL_INFO, "%s Tx STATE CHG REP\n", gsm_abis_mo_name(mo));
+
+ nmsg = oml_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+
+ /* 9.4.38 Operational State */
+ msgb_tv_put(nmsg, NM_ATT_OPER_STATE, mo->nm_state.operational);
+
+ /* 9.4.7 Availability Status */
+ msgb_tl16v_put(nmsg, NM_ATT_AVAIL_STATUS, 1, &mo->nm_state.availability);
+
+ /* 9.4.4 Administrative Status -- not in spec but also sent by nanobts */
+ msgb_tv_put(nmsg, NM_ATT_ADM_STATE, mo->nm_state.administrative);
+
+ return oml_mo_send_msg(mo, nmsg, NM_MT_STATECHG_EVENT_REP);
+}
+
+/* First initialization of MO, does _not_ generate state changes */
+void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state)
+{
+ mo->nm_state.availability = avail_state;
+ mo->nm_state.operational = op_state;
+}
+
+int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state)
+{
+ int rc = 0;
+
+ if ((op_state != -1 && mo->nm_state.operational != op_state) ||
+ (avail_state != -1 && mo->nm_state.availability != avail_state)) {
+ if (avail_state != -1) {
+ LOGP(DOML, LOGL_INFO, "%s AVAIL STATE %s -> %s\n",
+ gsm_abis_mo_name(mo),
+ abis_nm_avail_name(mo->nm_state.availability),
+ abis_nm_avail_name(avail_state));
+ mo->nm_state.availability = avail_state;
+ }
+ if (op_state != -1) {
+ LOGP(DOML, LOGL_INFO, "%s OPER STATE %s -> %s\n",
+ gsm_abis_mo_name(mo),
+ abis_nm_opstate_name(mo->nm_state.operational),
+ abis_nm_opstate_name(op_state));
+ mo->nm_state.operational = op_state;
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, NULL);
+ }
+
+ /* send state change report */
+ rc = oml_tx_state_changed(mo);
+ }
+ return rc;
+}
+
+int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type,
+ uint8_t cause)
+{
+ struct msgb *msg;
+ uint8_t new_msg_type;
+
+ msg = oml_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ if (cause) {
+ new_msg_type = orig_msg_type + 2;
+ msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause);
+ } else {
+ new_msg_type = orig_msg_type + 1;
+ }
+
+ return oml_mo_send_msg(mo, msg, new_msg_type);
+}
+
+int oml_mo_statechg_ack(struct gsm_abis_mo *mo)
+{
+ struct msgb *msg;
+ int rc = 0;
+
+ msg = oml_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ msgb_tv_put(msg, NM_ATT_ADM_STATE, mo->nm_state.administrative);
+
+ rc = oml_mo_send_msg(mo, msg, NM_MT_CHG_ADM_STATE_ACK);
+ if (rc != 0)
+ return rc;
+
+ /* Emulate behaviour of ipaccess nanobts: Send a 'State Changed Event Report' as well. */
+ return oml_tx_state_changed(mo);
+}
+
+int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause)
+{
+ return oml_mo_fom_ack_nack(mo, NM_MT_CHG_ADM_STATE, nack_cause);
+}
+
+int oml_mo_opstart_ack(struct gsm_abis_mo *mo)
+{
+ return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, 0);
+}
+
+int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause)
+{
+ return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, nack_cause);
+}
+
+int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause)
+{
+ struct abis_om_hdr *old_oh = msgb_l2(old_msg);
+ struct abis_om_fom_hdr *old_foh = msgb_l3(old_msg);
+ struct msgb *msg;
+ struct abis_om_fom_hdr *foh;
+ int is_manuf = 0;
+
+ msg = oml_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ /* make sure to respond with MANUF if request was MANUF */
+ if (old_oh->mdisc == ABIS_OM_MDISC_MANUF)
+ is_manuf = 1;
+
+ msg->trx = old_msg->trx;
+
+ /* copy over old FOM-Header and later only change the msg_type */
+ msg->l3h = msgb_push(msg, sizeof(*foh));
+ foh = (struct abis_om_fom_hdr *) msg->l3h;
+ memcpy(foh, old_foh, sizeof(*foh));
+
+ /* alter message type */
+ if (cause) {
+ LOGP(DOML, LOGL_NOTICE, "Sending FOM NACK with cause %s.\n",
+ abis_nm_nack_cause_name(cause));
+ foh->msg_type += 2; /* nack */
+ /* add cause */
+ msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause);
+ } else {
+ LOGP(DOML, LOGL_DEBUG, "Sending FOM ACK.\n");
+ foh->msg_type++; /* ack */
+ }
+
+ return oml_send_msg(msg, is_manuf);
+}
+
+/*
+ * Formatted O&M messages
+ */
+
+/* 8.3.7 sending SW Activated Report */
+int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo)
+{
+ struct msgb *nmsg;
+
+ LOGP(DOML, LOGL_INFO, "%s Tx SW ACT REP\n", gsm_abis_mo_name(mo));
+
+ nmsg = oml_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+
+ msgb_put(nmsg, sizeof(struct abis_om_fom_hdr));
+ return oml_mo_send_msg(mo, nmsg, NM_MT_SW_ACTIVATED_REP);
+}
+
+/* The defaults below correspond to the libosmocore default of 1s for
+ * DCCH and 2s for ACCH. The BSC should override this via OML anyway. */
+const unsigned int oml_default_t200_ms[7] = {
+ [T200_SDCCH] = 1000,
+ [T200_FACCH_F] = 1000,
+ [T200_FACCH_H] = 1000,
+ [T200_SACCH_TCH_SAPI0] = 2000,
+ [T200_SACCH_SDCCH] = 2000,
+ [T200_SDCCH_SAPI3] = 1000,
+ [T200_SACCH_TCH_SAPI3] = 2000,
+};
+
+static void dl_set_t200(struct lapdm_datalink *dl, unsigned int t200_msec)
+{
+ dl->dl.t200_sec = t200_msec / 1000;
+ dl->dl.t200_usec = (t200_msec % 1000) * 1000;
+}
+
+/* Configure LAPDm T200 timers for this lchan according to OML */
+int oml_set_lchan_t200(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct lapdm_channel *lc = &lchan->lapdm_ch;
+ unsigned int t200_dcch, t200_dcch_sapi3, t200_acch, t200_acch_sapi3;
+
+ /* set T200 for main and associated channel */
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ t200_dcch = bts->t200_ms[T200_SDCCH];
+ t200_dcch_sapi3 = bts->t200_ms[T200_SDCCH_SAPI3];
+ t200_acch = bts->t200_ms[T200_SACCH_SDCCH];
+ t200_acch_sapi3 = bts->t200_ms[T200_SACCH_SDCCH];
+ break;
+ case GSM_LCHAN_TCH_F:
+ t200_dcch = bts->t200_ms[T200_FACCH_F];
+ t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_F];
+ t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0];
+ t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3];
+ break;
+ case GSM_LCHAN_TCH_H:
+ t200_dcch = bts->t200_ms[T200_FACCH_H];
+ t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_H];
+ t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0];
+ t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3];
+ break;
+ default:
+ return -1;
+ }
+
+ DEBUGP(DLLAPD, "%s: Setting T200 D0=%u, D3=%u, S0=%u, S3=%u"
+ "(all in ms)\n", gsm_lchan_name(lchan), t200_dcch,
+ t200_dcch_sapi3, t200_acch, t200_acch_sapi3);
+
+ dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI0], t200_dcch);
+ dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI3], t200_dcch_sapi3);
+ dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI0], t200_acch);
+ dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI3], t200_acch_sapi3);
+
+ return 0;
+}
+
+/* 3GPP TS 52.021 §8.11.1 Get Attributes has been received */
+static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp;
+ int rc;
+
+ if (!foh || !bts)
+ return -EINVAL;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx GET ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute parsing failure");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ if (!TLVP_PRES_LEN(&tp, NM_ATT_LIST_REQ_ATTR, 1)) {
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes message without Attribute List?!\n");
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute without Attribute List");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ rc = oml_tx_attr_resp(bts, foh, TLVP_VAL(&tp, NM_ATT_LIST_REQ_ATTR), TLVP_LEN(&tp, NM_ATT_LIST_REQ_ATTR));
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "responding to O&M Get Attributes message with NACK 0%x\n", -rc);
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ return 0;
+}
+
+/* 8.6.1 Set BTS Attributes has been received */
+static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp, *tp_merged;
+ int rc, i;
+ const uint8_t *payload;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx SET BTS ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for Attribute not supported");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ /* Test for globally unsupported stuff here */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) {
+ uint16_t arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN));
+ if (arfcn > 1024) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_WARN_SW_WARN,
+ "Given ARFCN %u is not supported",
+ arfcn);
+ LOGP(DOML, LOGL_NOTICE, "Given ARFCN %d is not supported.\n", arfcn);
+ return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL);
+ }
+ }
+ /* 9.4.52 Starting Time */
+ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "NM_ATT_START_TIME Attribute not "
+ "supported");
+ return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ }
+
+ /* merge existing BTS attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(bts->mo.nm_attr, bts);
+ osmo_tlvp_merge(tp_merged, &tp);
+
+ /* Ask BTS driver to validate new merged attributes */
+ rc = bts_model_check_oml(bts, foh->msg_type, bts->mo.nm_attr, tp_merged, bts);
+ if (rc < 0) {
+ talloc_free(tp_merged);
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ /* Success: replace old BTS attributes with new */
+ talloc_free(bts->mo.nm_attr);
+ bts->mo.nm_attr = tp_merged;
+
+ /* ... and actually still parse them */
+
+ /* 9.4.25 Interference Level Boundaries */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_INTERF_BOUND, 6)) {
+ payload = TLVP_VAL(&tp, NM_ATT_INTERF_BOUND);
+ for (i = 0; i < 6; i++) {
+ int16_t boundary = *payload;
+ bts->interference.boundary[i] = -1 * boundary;
+ }
+ }
+ /* 9.4.24 Intave Parameter */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_INTAVE_PARAM, 1))
+ bts->interference.intave = *TLVP_VAL(&tp, NM_ATT_INTAVE_PARAM);
+
+ /* 9.4.14 Connection Failure Criterion */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_CONN_FAIL_CRIT, 1)) {
+ const uint8_t *val = TLVP_VAL(&tp, NM_ATT_CONN_FAIL_CRIT);
+
+ switch (val[0]) {
+ case 0xFF: /* Osmocom specific Extension of TS 12.21 */
+ LOGP(DOML, LOGL_NOTICE, "WARNING: Radio Link Timeout "
+ "explicitly disabled, only use this for lab testing!\n");
+ bts->radio_link_timeout = -1;
+ break;
+ case 0x01: /* Based on uplink SACCH (radio link timeout) */
+ if (TLVP_LEN(&tp, NM_ATT_CONN_FAIL_CRIT) >= 2 &&
+ val[1] >= 4 && val[1] <= 64) {
+ bts->radio_link_timeout = val[1];
+ break;
+ }
+ /* fall-through */
+ case 0x02: /* Based on RXLEV/RXQUAL measurements */
+ default:
+ LOGP(DOML, LOGL_NOTICE, "Given Conn. Failure Criterion "
+ "not supported. Please use criterion 0x01 with "
+ "RADIO_LINK_TIMEOUT value of 4..64\n");
+ return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
+ }
+ }
+
+ /* 9.4.53 T200 */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_ms))) {
+ payload = TLVP_VAL(&tp, NM_ATT_T200);
+ for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) {
+ uint32_t t200_ms = payload[i] * abis_nm_t200_ms[i];
+#if 0
+ bts->t200_ms[i] = t200_ms;
+ DEBUGP(DOML, "T200[%u]: OML=%u, mult=%u => %u ms\n",
+ i, payload[i], abis_nm_t200_mult[i],
+ bts->t200_ms[i]);
+#else
+ /* we'd rather use the 1s/2s (long) defaults by
+ * libosmocore, as we appear to have some bug(s)
+ * related to handling T200 expiration in
+ * libosmogsm lapd(m) code? */
+ LOGP(DOML, LOGL_NOTICE, "Ignoring T200[%u] (%u ms) "
+ "as sent by BSC due to suspected LAPDm bug!\n",
+ i, t200_ms);
+#endif
+ }
+ }
+
+ /* 9.4.31 Maximum Timing Advance */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_MAX_TA, 1))
+ bts->max_ta = *TLVP_VAL(&tp, NM_ATT_MAX_TA);
+
+ /* 9.4.39 Overload Period */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_OVERL_PERIOD, 1))
+ bts->load.overload_period = *TLVP_VAL(&tp, NM_ATT_OVERL_PERIOD);
+
+ /* 9.4.12 CCCH Load Threshold */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_T, 1))
+ bts->load.ccch.load_ind_thresh = *TLVP_VAL(&tp, NM_ATT_CCCH_L_T);
+
+ /* 9.4.11 CCCH Load Indication Period */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_I_P, 1)) {
+ bts->load.ccch.load_ind_period = *TLVP_VAL(&tp, NM_ATT_CCCH_L_I_P);
+ load_timer_start(bts);
+ }
+
+ /* 9.4.44 RACH Busy Threshold */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_RACH_B_THRESH, 1)) {
+ int16_t thresh = *TLVP_VAL(&tp, NM_ATT_RACH_B_THRESH);
+ bts->load.rach.busy_thresh = -1 * thresh;
+ }
+
+ /* 9.4.45 RACH Load Averaging Slots */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_LDAVG_SLOTS, 2)) {
+ bts->load.rach.averaging_slots =
+ ntohs(tlvp_val16_unal(&tp, NM_ATT_LDAVG_SLOTS));
+ }
+
+ /* 9.4.10 BTS Air Timer */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_BTS_AIR_TIMER, 1)) {
+ uint8_t t3105 = *TLVP_VAL(&tp, NM_ATT_BTS_AIR_TIMER);
+ if (t3105 == 0) {
+ LOGP(DOML, LOGL_NOTICE,
+ "T3105 must have a value != 0.\n");
+ return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
+ }
+ bts->t3105_ms = t3105 * 10;
+ }
+
+ /* 9.4.37 NY1 */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1))
+ bts->ny1 = *TLVP_VAL(&tp, NM_ATT_NY1);
+
+ /* 9.4.8 BCCH ARFCN */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2))
+ bts->c0->arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN));
+
+ /* 9.4.9 BSIC */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_BSIC, 1))
+ bts->bsic = *TLVP_VAL(&tp, NM_ATT_BSIC);
+
+ /* call into BTS driver to apply new attributes to hardware */
+ return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_BTS, bts);
+}
+
+/* 8.6.2 Set Radio Attributes has been received */
+static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp, *tp_merged;
+ int rc;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx SET RADIO CARRIER ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for Set Radio Attribute not"
+ " supported");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ /* merge existing BTS attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(trx->mo.nm_attr, trx->bts);
+ osmo_tlvp_merge(tp_merged, &tp);
+
+ /* Ask BTS driver to validate new merged attributes */
+ rc = bts_model_check_oml(trx->bts, foh->msg_type, trx->mo.nm_attr, tp_merged, trx);
+ if (rc < 0) {
+ talloc_free(tp_merged);
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ /* Success: replace old BTS attributes with new */
+ talloc_free(trx->mo.nm_attr);
+ trx->mo.nm_attr = tp_merged;
+
+ /* ... and actually still parse them */
+
+ /* 9.4.47 RF Max Power Reduction */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_RF_MAXPOWR_R, 1)) {
+ trx->max_power_red = *TLVP_VAL(&tp, NM_ATT_RF_MAXPOWR_R) * 2;
+ LOGP(DOML, LOGL_INFO, "Set RF Max Power Reduction = %d dBm\n",
+ trx->max_power_red);
+ }
+ /* 9.4.5 ARFCN List */
+#if 0
+ if (TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) {
+ uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t _value;
+ uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t arfcn;
+ int i;
+ for (i = 0; i < length; i++) {
+ memcpy(&_value, value, 2);
+ arfcn = ntohs(_value);
+ value += 2;
+ if (arfcn > 1024)
+ return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL);
+ trx->arfcn_list[i] = arfcn;
+ LOGP(DOML, LOGL_INFO, " ARFCN list = %d\n", trx->arfcn_list[i]);
+ }
+ trx->arfcn_num = length;
+ } else
+ trx->arfcn_num = 0;
+#else
+ if (trx != trx->bts->c0 && TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) {
+ const uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t _value;
+ uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t arfcn;
+ if (length != 2) {
+ LOGP(DOML, LOGL_ERROR, "Expecting only one ARFCN, "
+ "because hopping not supported\n");
+ return oml_fom_ack_nack(msg, NM_NACK_MSGINCONSIST_PHYSCFG);
+ }
+ memcpy(&_value, value, 2);
+ arfcn = ntohs(_value);
+ value += 2;
+ if (arfcn > 1024) {
+ oml_tx_failure_event_rep(&trx->bts->mo,
+ OSMO_EVT_WARN_SW_WARN,
+ "Given ARFCN %u is unsupported",
+ arfcn);
+ LOGP(DOML, LOGL_NOTICE,
+ "Given ARFCN %u is unsupported.\n", arfcn);
+ return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL);
+ }
+ trx->arfcn = arfcn;
+ }
+#endif
+ /* call into BTS driver to apply new attributes to hardware */
+ return bts_model_apply_oml(trx->bts, msg, tp_merged, NM_OC_RADIO_CARRIER, trx);
+}
+
+static int conf_lchans(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan = ts->pchan;
+
+ /* RSL_MT_IPAC_PDCH_ACT style dyn PDCH */
+ if (pchan == GSM_PCHAN_TCH_F_PDCH)
+ pchan = ts->flags & TS_F_PDCH_ACTIVE? GSM_PCHAN_PDCH
+ : GSM_PCHAN_TCH_F;
+
+ /* Osmocom RSL CHAN ACT style dyn TS */
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ pchan = ts->dyn.pchan_is;
+
+ /* If the dyn TS doesn't have a pchan yet, do nothing. */
+ if (pchan == GSM_PCHAN_NONE)
+ return 0;
+ }
+
+ return conf_lchans_as_pchan(ts, pchan);
+}
+
+int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan)
+{
+ struct gsm_lchan *lchan;
+ unsigned int i;
+
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /* fallthrough */
+ case GSM_PCHAN_CCCH_SDCCH4:
+ for (i = 0; i < 4; i++) {
+ lchan = &ts->lchan[i];
+ if (pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH
+ && i == 2) {
+ lchan->type = GSM_LCHAN_CBCH;
+ } else {
+ lchan->type = GSM_LCHAN_SDCCH;
+ }
+ }
+ /* fallthrough */
+ case GSM_PCHAN_CCCH:
+ lchan = &ts->lchan[CCCH_LCHAN];
+ lchan->type = GSM_LCHAN_CCCH;
+ break;
+ case GSM_PCHAN_TCH_F:
+ lchan = &ts->lchan[0];
+ lchan->type = GSM_LCHAN_TCH_F;
+ break;
+ case GSM_PCHAN_TCH_H:
+ for (i = 0; i < 2; i++) {
+ lchan = &ts->lchan[i];
+ lchan->type = GSM_LCHAN_TCH_H;
+ }
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* fallthrough */
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ for (i = 0; i < 8; i++) {
+ lchan = &ts->lchan[i];
+ if (pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH
+ && i == 2) {
+ lchan->type = GSM_LCHAN_CBCH;
+ } else {
+ lchan->type = GSM_LCHAN_SDCCH;
+ }
+ }
+ break;
+ case GSM_PCHAN_PDCH:
+ lchan = &ts->lchan[0];
+ lchan->type = GSM_LCHAN_PDTCH;
+ break;
+ default:
+ LOGP(DOML, LOGL_ERROR, "Unknown/unhandled PCHAN type: %u %s\n",
+ ts->pchan, gsm_pchan_name(ts->pchan));
+ return -NM_NACK_PARAM_RANGE;
+ }
+ return 0;
+}
+
+/* 8.6.3 Set Channel Attributes has been received */
+static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts *bts = ts->trx->bts;
+ struct tlv_parsed tp, *tp_merged;
+ int rc;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx SET CHAN ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&ts->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for Set Channel Attribute "
+ "not supported");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ /* 9.4.21 HSN... */
+ /* 9.4.27 MAIO */
+ if (TLVP_PRESENT(&tp, NM_ATT_HSN) || TLVP_PRESENT(&tp, NM_ATT_MAIO)) {
+ LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Frequency hopping not supported.\n");
+ return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ }
+
+ /* 9.4.52 Starting Time */
+ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) {
+ LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Starting time not supported.\n");
+ return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ }
+
+ /* merge existing BTS attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(ts->mo.nm_attr, bts);
+ osmo_tlvp_merge(tp_merged, &tp);
+
+ /* Call into BTS driver to check attribute values */
+ rc = bts_model_check_oml(bts, foh->msg_type, ts->mo.nm_attr, tp_merged, ts);
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid attribute value, rc=%d\n", rc);
+ talloc_free(tp_merged);
+ /* Send NACK */
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ /* Success: replace old BTS attributes with new */
+ talloc_free(ts->mo.nm_attr);
+ ts->mo.nm_attr = tp_merged;
+
+ /* 9.4.13 Channel Combination */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_CHAN_COMB, 1)) {
+ uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB);
+ ts->pchan = abis_nm_pchan4chcomb(comb);
+ rc = conf_lchans(ts);
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid Chan Comb 0x%x"
+ " (pchan=%s, conf_lchans()->%d)\n",
+ comb, gsm_pchan_name(ts->pchan), rc);
+ talloc_free(tp_merged);
+ /* Send NACK */
+ return oml_fom_ack_nack(msg, -rc);
+ }
+ }
+
+ /* 9.4.5 ARFCN List */
+
+ /* 9.4.60 TSC */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_TSC, 1)) {
+ ts->tsc = *TLVP_VAL(&tp, NM_ATT_TSC);
+ } else {
+ /* If there is no TSC specified, use the BCC */
+ ts->tsc = BSIC2BCC(bts->bsic);
+ }
+ LOGP(DOML, LOGL_INFO, "%s SET CHAN ATTR (TSC=%u pchan=%s)\n",
+ gsm_abis_mo_name(&ts->mo), ts->tsc, gsm_pchan_name(ts->pchan));
+
+ /* call into BTS driver to apply new attributes to hardware */
+ return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_CHANNEL, ts);
+}
+
+/* 8.9.2 Opstart has been received */
+static int oml_rx_opstart(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_abis_mo *mo;
+ void *obj;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx OPSTART\n");
+
+ /* Step 1: Resolve MO by obj_class/obj_inst */
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+ if (!mo || !obj)
+ return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+
+ /* Step 2: Do some global dependency/consistency checking */
+ if (mo->nm_state.operational == NM_OPSTATE_ENABLED) {
+ DEBUGP(DOML, "... automatic ACK, OP state already was Enabled\n");
+ return oml_mo_opstart_ack(mo);
+ }
+
+ /* Step 3: Ask BTS driver to apply the opstart */
+ return bts_model_opstart(bts, mo, obj);
+}
+
+static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp;
+ struct gsm_abis_mo *mo;
+ uint8_t adm_state;
+ void *obj;
+ int rc;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx CHG ADM STATE\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: error during TLV parse\n");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
+ LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: no ADM state attribute\n");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+
+ /* Step 1: Resolve MO by obj_class/obj_inst */
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+ if (!mo || !obj)
+ return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+
+ /* Step 2: Do some global dependency/consistency checking */
+ if (mo->nm_state.administrative == adm_state)
+ LOGP(DOML, LOGL_NOTICE,
+ "ADM state already was %s\n",
+ get_value_string(abis_nm_adm_state_names, adm_state));
+
+ /* Step 3: Ask BTS driver to apply the state chg */
+ return bts_model_chg_adm_state(bts, mo, obj, adm_state);
+}
+
+/* Check and report if the BTS number received via OML is incorrect:
+ according to 3GPP TS 52.021 §9.3 BTS number is used to distinguish between different BTS of the same Site Manager.
+ As we always have only single BTS per Site Manager (in case of Abis/IP with each BTS having dedicated OML connection
+ to BSC), the only valid values are 0 and 0xFF (means all BTS' of a given Site Manager). */
+static inline bool report_bts_number_incorrect(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, bool is_formatted)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_abis_mo *mo = &bts->mo;
+ const char *form = is_formatted ?
+ "Unexpected BTS %d in formatted O&M %s (exp. 0 or 0xFF)" :
+ "Unexpected BTS %d in manufacturer O&M %s (exp. 0 or 0xFF)";
+
+ if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) {
+ LOGP(DOML, LOGL_ERROR, form, foh->obj_inst.bts_nr, get_value_string(abis_nm_msgtype_names,
+ foh->msg_type));
+ LOGPC(DOML, LOGL_ERROR, "\n");
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (trx) {
+ trx->mo.obj_inst.bts_nr = 0;
+ trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr;
+ trx->mo.obj_inst.ts_nr = 0xff;
+ mo = &trx->mo;
+ }
+ oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UKWN_MSG, form, foh->obj_inst.bts_nr,
+ get_value_string(abis_nm_msgtype_names, foh->msg_type));
+
+ return true;
+ }
+
+ return false;
+}
+
+static int down_fom(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx;
+ int ret;
+
+ if (msgb_l2len(msg) < sizeof(*foh)) {
+ LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n");
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (trx) {
+ trx->mo.obj_inst.bts_nr = 0;
+ trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr;
+ trx->mo.obj_inst.ts_nr = 0xff;
+ oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG,
+ "Formatted O&M message too short");
+ }
+ return -EIO;
+ }
+
+ if (report_bts_number_incorrect(bts, foh, true))
+ return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN);
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_BTS_ATTR:
+ ret = oml_rx_set_bts_attr(bts, msg);
+ break;
+ case NM_MT_SET_RADIO_ATTR:
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (!trx)
+ return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN);
+ ret = oml_rx_set_radio_attr(trx, msg);
+ break;
+ case NM_MT_SET_CHAN_ATTR:
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (!trx)
+ return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN);
+ if (foh->obj_inst.ts_nr >= ARRAY_SIZE(trx->ts))
+ return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+ ret = oml_rx_set_chan_attr(&trx->ts[foh->obj_inst.ts_nr], msg);
+ break;
+ case NM_MT_OPSTART:
+ ret = oml_rx_opstart(bts, msg);
+ break;
+ case NM_MT_CHG_ADM_STATE:
+ ret = oml_rx_chg_adm_state(bts, msg);
+ break;
+ case NM_MT_IPACC_SET_ATTR:
+ ret = oml_ipa_set_attr(bts, msg);
+ break;
+ case NM_MT_GET_ATTR:
+ ret = oml_rx_get_attr(bts, msg);
+ break;
+ default:
+ LOGP(DOML, LOGL_INFO, "unknown Formatted O&M msg_type 0x%02x\n",
+ foh->msg_type);
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (trx) {
+ trx->mo.obj_inst.bts_nr = 0;
+ trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr;
+ trx->mo.obj_inst.ts_nr = 0xff;
+ oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG,
+ "unknown Formatted O&M "
+ "msg_type 0x%02x",
+ foh->msg_type);
+ } else
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UKWN_MSG,
+ "unknown Formatted O&M "
+ "msg_type 0x%02x",
+ foh->msg_type);
+ ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL);
+ }
+
+ return ret;
+}
+
+/*
+ * manufacturer related messages
+ */
+
+static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp)
+{
+ struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.nse);
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSEI, 2))
+ bts->gprs.nse.nsei =
+ ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSEI));
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_CFG, 7)) {
+ memcpy(&bts->gprs.nse.timer,
+ TLVP_VAL(tp, NM_ATT_IPACC_NS_CFG), 7);
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BSSGP_CFG, 11)) {
+ memcpy(&bts->gprs.cell.timer,
+ TLVP_VAL(tp, NM_ATT_IPACC_BSSGP_CFG), 11);
+ }
+
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSE_ATTR, bts);
+
+ return 0;
+}
+
+static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp)
+{
+ struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.cell);
+ struct gprs_rlc_cfg *rlcc = &bts->gprs.cell.rlc_cfg;
+ const uint8_t *cur;
+ uint16_t _cur_s;
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RAC, 1))
+ bts->gprs.rac = *TLVP_VAL(tp, NM_ATT_IPACC_RAC);
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_GPRS_PAGING_CFG, 2)) {
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_GPRS_PAGING_CFG);
+ rlcc->paging.repeat_time = cur[0] * 50;
+ rlcc->paging.repeat_count = cur[1];
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BVCI, 2))
+ bts->gprs.cell.bvci =
+ ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_BVCI));
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG, 9)) {
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG);
+ rlcc->parameter[RLC_T3142] = cur[0];
+ rlcc->parameter[RLC_T3169] = cur[1];
+ rlcc->parameter[RLC_T3191] = cur[2];
+ rlcc->parameter[RLC_T3193] = cur[3];
+ rlcc->parameter[RLC_T3195] = cur[4];
+ rlcc->parameter[RLC_N3101] = cur[5];
+ rlcc->parameter[RLC_N3103] = cur[6];
+ rlcc->parameter[RLC_N3105] = cur[7];
+ rlcc->parameter[CV_COUNTDOWN] = cur[8];
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_CODING_SCHEMES, 2)) {
+ int i;
+ rlcc->cs_mask = 0;
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_CODING_SCHEMES);
+
+ for (i = 0; i < 4; i++) {
+ if (cur[0] & (1 << i))
+ rlcc->cs_mask |= (1 << (GPRS_CS1+i));
+ }
+ if (cur[0] & 0x80)
+ rlcc->cs_mask |= (1 << GPRS_MCS9);
+ for (i = 0; i < 8; i++) {
+ if (cur[1] & (1 << i))
+ rlcc->cs_mask |= (1 << (GPRS_MCS1+i));
+ }
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_2, 5)) {
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_2);
+ memcpy(&_cur_s, cur, 2);
+ rlcc->parameter[T_DL_TBF_EXT] = ntohs(_cur_s) * 10;
+ cur += 2;
+ memcpy(&_cur_s, cur, 2);
+ rlcc->parameter[T_UL_TBF_EXT] = ntohs(_cur_s) * 10;
+ cur += 2;
+ rlcc->initial_cs = *cur;
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_3, 1)) {
+ rlcc->initial_mcs = *TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_3);
+ }
+
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_CELL_ATTR, bts);
+
+ return 0;
+}
+
+static int oml_ipa_mo_set_attr_nsvc(struct gsm_bts_gprs_nsvc *nsvc,
+ struct tlv_parsed *tp)
+{
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSVCI, 2))
+ nsvc->nsvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSVCI));
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_LINK_CFG, 8)) {
+ const uint8_t *cur = TLVP_VAL(tp, NM_ATT_IPACC_NS_LINK_CFG);
+ uint16_t _cur_s;
+ uint32_t _cur_l;
+
+ memcpy(&_cur_s, cur, 2);
+ nsvc->remote_port = ntohs(_cur_s);
+ cur += 2;
+ memcpy(&_cur_l, cur, 4);
+ nsvc->remote_ip = ntohl(_cur_l);
+ cur += 4;
+ memcpy(&_cur_s, cur, 2);
+ nsvc->local_port = ntohs(_cur_s);
+ }
+
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc);
+
+ return 0;
+}
+
+static int oml_ipa_mo_set_attr(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, struct tlv_parsed *tp)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_GPRS_NSE:
+ rc = oml_ipa_mo_set_attr_nse(obj, tp);
+ break;
+ case NM_OC_GPRS_CELL:
+ rc = oml_ipa_mo_set_attr_cell(obj, tp);
+ break;
+ case NM_OC_GPRS_NSVC:
+ rc = oml_ipa_mo_set_attr_nsvc(obj, tp);
+ break;
+ default:
+ rc = NM_NACK_OBJINST_UNKN;
+ }
+
+ return rc;
+}
+
+static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_abis_mo *mo;
+ struct tlv_parsed tp;
+ void *obj;
+ int rc;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx IPA SET ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ if (!mo)
+ return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+ oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for IPAC Set Attribute not "
+ "supported\n");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ /* Resolve MO by obj_class/obj_inst */
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+ if (!mo || !obj)
+ return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+
+ rc = oml_ipa_mo_set_attr(bts, mo, obj, &tp);
+
+ return oml_fom_ack_nack(msg, rc);
+}
+
+static int rx_oml_ipa_rsl_connect(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct tlv_parsed *tp)
+{
+ struct e1inp_sign_link *oml_link = trx->bts->oml_link;
+ uint16_t port = IPA_TCP_PORT_RSL;
+ uint32_t ip = get_signlink_remote_ip(oml_link);
+ struct in_addr in;
+ int rc;
+
+ uint8_t stream_id = 0;
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP, 4)) {
+ ip = ntohl(tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP));
+ }
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP_PORT, 2)) {
+ port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT));
+ }
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_STREAM_ID, 1)) {
+ stream_id = *TLVP_VAL(tp, NM_ATT_IPACC_STREAM_ID);
+ }
+
+ in.s_addr = htonl(ip);
+ LOGP(DOML, LOGL_INFO, "Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ inet_ntoa(in), port, stream_id);
+
+ if (trx->bts->variant == BTS_OSMO_OMLDUMMY) {
+ rc = 0;
+ LOGP(DOML, LOGL_NOTICE, "Not connecting RSL in OML-DUMMY!\n");
+ } else
+ rc = e1inp_ipa_bts_rsl_connect_n(oml_link->ts->line, inet_ntoa(in), port, trx->nr);
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "Error in abis_open(RSL): %d\n", rc);
+ return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
+ }
+
+ return oml_fom_ack_nack(msg, 0);
+}
+
+static int down_mom(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ struct abis_om_fom_hdr *foh;
+ struct gsm_bts_trx *trx;
+ uint8_t idstrlen = oh->data[0];
+ struct tlv_parsed tp;
+ int ret;
+
+ if (msgb_l2len(msg) < sizeof(*foh)) {
+ LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n");
+ return -EIO;
+ }
+
+ if (strncmp((char *)&oh->data[1], abis_nm_ipa_magic, idstrlen)) {
+ LOGP(DOML, LOGL_ERROR, "Manufacturer OML message != ipaccess not supported\n");
+ return -EINVAL;
+ }
+
+ msg->l3h = oh->data + 1 + idstrlen;
+ foh = (struct abis_om_fom_hdr *) msg->l3h;
+
+ if (report_bts_number_incorrect(bts, foh, false))
+ return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN);
+
+ ret = oml_tlv_parse(&tp, foh->data, oh->length - sizeof(*foh));
+ if (ret < 0) {
+ LOGP(DOML, LOGL_ERROR, "TLV parse error %d\n", ret);
+ return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN);
+ }
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx IPACCESS(0x%02x): ", foh->msg_type);
+
+ switch (foh->msg_type) {
+ case NM_MT_IPACC_RSL_CONNECT:
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ ret = rx_oml_ipa_rsl_connect(trx, msg, &tp);
+ break;
+ case NM_MT_IPACC_SET_ATTR:
+ ret = oml_ipa_set_attr(bts, msg);
+ break;
+ default:
+ LOGP(DOML, LOGL_INFO, "Manufacturer Formatted O&M msg_type 0x%02x\n",
+ foh->msg_type);
+ ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL);
+ }
+
+ return ret;
+}
+
+/* incoming OML message from BSC */
+int down_oml(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < 1) {
+ LOGP(DOML, LOGL_NOTICE, "OML message too short\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+ switch (oh->mdisc) {
+ case ABIS_OM_MDISC_FOM:
+ if (msgb_l2len(msg) < sizeof(*oh)) {
+ LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n");
+ ret = -EIO;
+ break;
+ }
+ ret = down_fom(bts, msg);
+ break;
+ case ABIS_OM_MDISC_MANUF:
+ if (msgb_l2len(msg) < sizeof(*oh)) {
+ LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n");
+ ret = -EIO;
+ break;
+ }
+ ret = down_mom(bts, msg);
+ break;
+ default:
+ LOGP(DOML, LOGL_NOTICE, "unknown OML msg_discr 0x%02x\n",
+ oh->mdisc);
+ ret = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int handle_fail_sig(unsigned int subsys, unsigned int signal, void *handle,
+ void *signal_data)
+{
+ if (signal_data)
+ oml_tx_failure_event_rep(handle, signal, "%s", signal_data);
+ else
+ oml_tx_failure_event_rep(handle, signal, "");
+
+ return 0;
+}
+
+int oml_init(struct gsm_abis_mo *mo)
+{
+ DEBUGP(DOML, "Initializing OML attribute definitions\n");
+ tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef_ipa);
+ tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef);
+ osmo_signal_register_handler(SS_FAIL, handle_fail_sig, mo);
+
+ return 0;
+}
diff --git a/src/common/paging.c b/src/common/paging.c
new file mode 100644
index 00000000..aa604e72
--- /dev/null
+++ b/src/common/paging.c
@@ -0,0 +1,630 @@
+/* Paging message encoding + queue management */
+
+/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* TODO:
+ * eMLPP priprity
+ * add P1/P2/P3 rest octets
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/pcu_if.h>
+
+#define MAX_PAGING_BLOCKS_CCCH 9
+#define MAX_BS_PA_MFRMS 9
+
+enum paging_record_type {
+ PAGING_RECORD_PAGING,
+ PAGING_RECORD_IMM_ASS
+};
+
+struct paging_record {
+ struct llist_head list;
+ enum paging_record_type type;
+ union {
+ struct {
+ time_t expiration_time;
+ uint8_t chan_needed;
+ uint8_t identity_lv[9];
+ } paging;
+ struct {
+ uint8_t msg[GSM_MACBLOCK_LEN];
+ } imm_ass;
+ } u;
+};
+
+struct paging_state {
+ struct gsm_bts *bts;
+
+ /* parameters taken / interpreted from BCCH/CCCH configuration */
+ struct gsm48_control_channel_descr chan_desc;
+
+ /* configured otherwise */
+ unsigned int paging_lifetime; /* in seconds */
+ unsigned int num_paging_max;
+
+ /* total number of currently active paging records in queue */
+ unsigned int num_paging;
+ struct llist_head paging_queue[MAX_PAGING_BLOCKS_CCCH*MAX_BS_PA_MFRMS];
+};
+
+unsigned int paging_get_lifetime(struct paging_state *ps)
+{
+ return ps->paging_lifetime;
+}
+
+unsigned int paging_get_queue_max(struct paging_state *ps)
+{
+ return ps->num_paging_max;
+}
+
+void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime)
+{
+ ps->paging_lifetime = lifetime;
+}
+
+void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max)
+{
+ ps->num_paging_max = queue_max;
+}
+
+static int tmsi_mi_to_uint(uint32_t *out, const uint8_t *tmsi_lv)
+{
+ if (tmsi_lv[0] < 5)
+ return -EINVAL;
+ if ((tmsi_lv[1] & 7) != GSM_MI_TYPE_TMSI)
+ return -EINVAL;
+
+ *out = *((uint32_t *)(tmsi_lv+2));
+
+ return 0;
+}
+
+/* paging block numbers in a simple non-combined CCCH */
+static const uint8_t block_by_tdma51[51] = {
+ 255, 255, /* FCCH, SCH */
+ 255, 255, 255, 255, /* BCCH */
+ 0, 0, 0, 0, /* B0(6..9) */
+ 255, 255, /* FCCH, SCH */
+ 1, 1, 1, 1, /* B1(12..15) */
+ 2, 2, 2, 2, /* B2(16..19) */
+ 255, 255, /* FCCH, SCH */
+ 3, 3, 3, 3, /* B3(22..25) */
+ 4, 4, 4, 4, /* B3(26..29) */
+ 255, 255, /* FCCH, SCH */
+ 5, 5, 5, 5, /* B3(32..35) */
+ 6, 6, 6, 6, /* B3(36..39) */
+ 255, 255, /* FCCH, SCH */
+ 7, 7, 7, 7, /* B3(42..45) */
+ 8, 8, 8, 8, /* B3(46..49) */
+ 255, /* empty */
+};
+
+/* get the paging block number _within_ current 51 multiframe */
+static int get_pag_idx_n(struct paging_state *ps, struct gsm_time *gt)
+{
+ int blk_n = block_by_tdma51[gt->t3];
+ int blk_idx;
+
+ if (blk_n == 255)
+ return -EINVAL;
+
+ blk_idx = blk_n - ps->chan_desc.bs_ag_blks_res;
+ if (blk_idx < 0)
+ return -EINVAL;
+
+ return blk_idx;
+}
+
+/* get paging block index over multiple 51 multiframes */
+static int get_pag_subch_nr(struct paging_state *ps, struct gsm_time *gt)
+{
+ int pag_idx = get_pag_idx_n(ps, gt);
+ unsigned int n_pag_blks_51 = gsm0502_get_n_pag_blocks(&ps->chan_desc);
+ unsigned int mfrm_part;
+
+ if (pag_idx < 0)
+ return pag_idx;
+
+ mfrm_part = ((gt->fn / 51) % (ps->chan_desc.bs_pa_mfrms+2)) * n_pag_blks_51;
+
+ return pag_idx + mfrm_part;
+}
+
+int paging_buffer_space(struct paging_state *ps)
+{
+ if (ps->num_paging >= ps->num_paging_max)
+ return 0;
+ else
+ return ps->num_paging_max - ps->num_paging;
+}
+
+/* Add an identity to the paging queue */
+int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
+ const uint8_t *identity_lv, uint8_t chan_needed)
+{
+ struct llist_head *group_q = &ps->paging_queue[paging_group];
+ int blocks = gsm48_number_of_paging_subchannels(&ps->chan_desc);
+ struct paging_record *pr;
+
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_RCVD);
+
+ if (paging_group >= blocks) {
+ LOGP(DPAG, LOGL_ERROR, "BSC Send PAGING for group %u, but number of paging "
+ "sub-channels is only %u\n", paging_group, blocks);
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP);
+ return -EINVAL;
+ }
+
+ if (ps->num_paging >= ps->num_paging_max) {
+ LOGP(DPAG, LOGL_NOTICE, "Dropping paging, queue full (%u)\n",
+ ps->num_paging);
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP);
+ return -ENOSPC;
+ }
+
+ /* Check if we already have this identity */
+ llist_for_each_entry(pr, group_q, list) {
+ if (pr->type != PAGING_RECORD_PAGING)
+ continue;
+ if (identity_lv[0] == pr->u.paging.identity_lv[0] &&
+ !memcmp(identity_lv+1, pr->u.paging.identity_lv+1,
+ identity_lv[0])) {
+ LOGP(DPAG, LOGL_INFO, "Ignoring duplicate paging\n");
+ pr->u.paging.expiration_time =
+ time(NULL) + ps->paging_lifetime;
+ return -EEXIST;
+ }
+ }
+
+ pr = talloc_zero(ps, struct paging_record);
+ if (!pr)
+ return -ENOMEM;
+ pr->type = PAGING_RECORD_PAGING;
+
+ if (*identity_lv + 1 > sizeof(pr->u.paging.identity_lv)) {
+ talloc_free(pr);
+ return -E2BIG;
+ }
+
+ LOGP(DPAG, LOGL_INFO, "Add paging to queue (group=%u, queue_len=%u)\n",
+ paging_group, ps->num_paging+1);
+
+ pr->u.paging.expiration_time = time(NULL) + ps->paging_lifetime;
+ pr->u.paging.chan_needed = chan_needed;
+ memcpy(&pr->u.paging.identity_lv, identity_lv, identity_lv[0]+1);
+
+ /* enqueue the new identity to the HEAD of the queue,
+ * to ensure it will be paged quickly at least once. */
+ llist_add(&pr->list, group_q);
+ ps->num_paging++;
+
+ return 0;
+}
+
+/* Add an IMM.ASS message to the paging queue */
+int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data,
+ uint8_t len)
+{
+ struct llist_head *group_q;
+ struct paging_record *pr;
+ uint16_t imsi, paging_group;
+
+ if (len != GSM_MACBLOCK_LEN + 3) {
+ LOGP(DPAG, LOGL_ERROR, "IMM.ASS invalid length %d\n", len);
+ return -EINVAL;
+ }
+ len -= 3;
+
+ imsi = 100 * ((*(data++)) - '0');
+ imsi += 10 * ((*(data++)) - '0');
+ imsi += (*(data++)) - '0';
+ paging_group = gsm0502_calc_paging_group(&ps->chan_desc, imsi);
+
+ group_q = &ps->paging_queue[paging_group];
+
+ pr = talloc_zero(ps, struct paging_record);
+ if (!pr)
+ return -ENOMEM;
+ pr->type = PAGING_RECORD_IMM_ASS;
+
+ LOGP(DPAG, LOGL_INFO, "Add IMM.ASS to queue (group=%u)\n",
+ paging_group);
+ memcpy(pr->u.imm_ass.msg, data, GSM_MACBLOCK_LEN);
+
+ /* enqueue the new message to the HEAD of the queue */
+ llist_add(&pr->list, group_q);
+
+ return 0;
+}
+
+#define L2_PLEN(len) (((len - 1) << 2) | 0x01)
+
+static int fill_paging_type_1(uint8_t *out_buf, const uint8_t *identity1_lv,
+ uint8_t chan1, const uint8_t *identity2_lv,
+ uint8_t chan2)
+{
+ struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf;
+ uint8_t *cur;
+
+ memset(out_buf, 0, sizeof(*pt1));
+
+ pt1->proto_discr = GSM48_PDISC_RR;
+ pt1->msg_type = GSM48_MT_RR_PAG_REQ_1;
+ pt1->pag_mode = GSM48_PM_NORMAL;
+ pt1->cneed1 = chan1 & 3;
+ pt1->cneed2 = chan2 & 3;
+ cur = lv_put(pt1->data, identity1_lv[0], identity1_lv+1);
+ if (identity2_lv)
+ cur = tlv_put(cur, GSM48_IE_MOBILE_ID, identity2_lv[0], identity2_lv+1);
+
+ pt1->l2_plen = L2_PLEN(cur - out_buf);
+
+ return cur - out_buf;
+}
+
+static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv,
+ uint8_t cneed1, const uint8_t *tmsi2_lv,
+ uint8_t cneed2, const uint8_t *identity3_lv)
+{
+ struct gsm48_paging2 *pt2 = (struct gsm48_paging2 *) out_buf;
+ uint8_t *cur;
+
+ memset(out_buf, 0, sizeof(*pt2));
+
+ pt2->proto_discr = GSM48_PDISC_RR;
+ pt2->msg_type = GSM48_MT_RR_PAG_REQ_2;
+ pt2->pag_mode = GSM48_PM_NORMAL;
+ pt2->cneed1 = cneed1;
+ pt2->cneed2 = cneed2;
+ tmsi_mi_to_uint(&pt2->tmsi1, tmsi1_lv);
+ tmsi_mi_to_uint(&pt2->tmsi2, tmsi2_lv);
+ cur = out_buf + sizeof(*pt2);
+
+ if (identity3_lv)
+ cur = tlv_put(pt2->data, GSM48_IE_MOBILE_ID, identity3_lv[0], identity3_lv+1);
+
+ pt2->l2_plen = L2_PLEN(cur - out_buf);
+
+ return cur - out_buf;
+}
+
+static int fill_paging_type_3(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t cneed1,
+ const uint8_t *tmsi2_lv, uint8_t cneed2,
+ const uint8_t *tmsi3_lv, uint8_t cneed3,
+ const uint8_t *tmsi4_lv, uint8_t cneed4)
+{
+ struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf;
+
+ memset(out_buf, 0, sizeof(*pt3));
+
+ pt3->proto_discr = GSM48_PDISC_RR;
+ pt3->msg_type = GSM48_MT_RR_PAG_REQ_3;
+ pt3->pag_mode = GSM48_PM_NORMAL;
+ pt3->cneed1 = cneed1;
+ pt3->cneed2 = cneed2;
+ tmsi_mi_to_uint(&pt3->tmsi1, tmsi1_lv);
+ tmsi_mi_to_uint(&pt3->tmsi2, tmsi2_lv);
+ tmsi_mi_to_uint(&pt3->tmsi3, tmsi3_lv);
+ tmsi_mi_to_uint(&pt3->tmsi4, tmsi4_lv);
+
+ /* The structure definition in libosmocore is wrong. It includes as last
+ * byte some invalid definition of chneed3/chneed4, so we must do this by hand
+ * here and cannot rely on sizeof(*pt3) */
+ out_buf[20] = (0x23 & ~0xf8) | 0x80 | (cneed3 & 3) << 5 | (cneed4 & 3) << 3;
+
+ return 21;
+}
+
+static const uint8_t empty_id_lv[] = { 0x01, 0xF0 };
+
+static struct paging_record *dequeue_pr(struct llist_head *group_q)
+{
+ struct paging_record *pr;
+
+ pr = llist_entry(group_q->next, struct paging_record, list);
+ llist_del(&pr->list);
+
+ return pr;
+}
+
+static int pr_is_imsi(struct paging_record *pr)
+{
+ if ((pr->u.paging.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI)
+ return 1;
+ else
+ return 0;
+}
+
+static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n)
+{
+ int i, j;
+ struct paging_record *t;
+
+ if (n < 2)
+ return;
+
+ /* simple bubble sort */
+ for (i = n-2; i >= 0; i--) {
+ for (j=0; j<=i ; j++) {
+ if (pr_is_imsi(pr[j]) > pr_is_imsi(pr[j+1])) {
+ t = pr[j];
+ pr[j] = pr[j+1];
+ pr[j+1] = t;
+ }
+ }
+ }
+}
+
+/* generate paging message for given gsm time */
+int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt,
+ int *is_empty)
+{
+ struct llist_head *group_q;
+ int group;
+ int len;
+
+ *is_empty = 0;
+ ps->bts->load.ccch.pch_total += 1;
+
+ group = get_pag_subch_nr(ps, gt);
+ if (group < 0) {
+ LOGP(DPAG, LOGL_ERROR,
+ "Paging called for GSM wrong time: FN %d/%d/%d/%d.\n",
+ gt->fn, gt->t1, gt->t2, gt->t3);
+ return -1;
+ }
+
+ group_q = &ps->paging_queue[group];
+
+ /* There is nobody to be paged, send Type1 with two empty ID */
+ if (llist_empty(group_q)) {
+ //DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n");
+ len = fill_paging_type_1(out_buf, empty_id_lv, 0,
+ NULL, 0);
+ *is_empty = 1;
+ } else {
+ struct paging_record *pr[4];
+ unsigned int num_pr = 0, imm_ass = 0;
+ time_t now = time(NULL);
+ unsigned int i, num_imsi = 0;
+
+ ps->bts->load.ccch.pch_used += 1;
+
+ /* get (if we have) up to four paging records */
+ for (i = 0; i < ARRAY_SIZE(pr); i++) {
+ if (llist_empty(group_q))
+ break;
+ pr[i] = dequeue_pr(group_q);
+
+ /* check for IMM.ASS */
+ if (pr[i]->type == PAGING_RECORD_IMM_ASS) {
+ imm_ass = 1;
+ break;
+ }
+
+ num_pr++;
+
+ /* count how many IMSIs are among them */
+ if (pr_is_imsi(pr[i]))
+ num_imsi++;
+ }
+
+ /* if we have an IMMEDIATE ASSIGNMENT */
+ if (imm_ass) {
+ /* re-add paging records */
+ for (i = 0; i < num_pr; i++)
+ llist_add(&pr[i]->list, group_q);
+
+ /* get message and free record */
+ memcpy(out_buf, pr[num_pr]->u.imm_ass.msg,
+ GSM_MACBLOCK_LEN);
+ pcu_tx_pch_data_cnf(gt->fn, pr[num_pr]->u.imm_ass.msg,
+ GSM_MACBLOCK_LEN);
+ talloc_free(pr[num_pr]);
+ return GSM_MACBLOCK_LEN;
+ }
+
+ /* make sure the TMSIs are ahead of the IMSIs in the array */
+ sort_pr_tmsi_imsi(pr, num_pr);
+
+ if (num_pr == 4 && num_imsi == 0) {
+ /* No IMSI: easy case, can use TYPE 3 */
+ DEBUGP(DPAG, "Tx PAGING TYPE 3 (4 TMSI)\n");
+ len = fill_paging_type_3(out_buf,
+ pr[0]->u.paging.identity_lv,
+ pr[0]->u.paging.chan_needed,
+ pr[1]->u.paging.identity_lv,
+ pr[1]->u.paging.chan_needed,
+ pr[2]->u.paging.identity_lv,
+ pr[2]->u.paging.chan_needed,
+ pr[3]->u.paging.identity_lv,
+ pr[3]->u.paging.chan_needed);
+ } else if (num_pr >= 3 && num_imsi <= 1) {
+ /* 3 or 4, of which only up to 1 is IMSI */
+ DEBUGP(DPAG, "Tx PAGING TYPE 2 (2 TMSI,1 xMSI)\n");
+ len = fill_paging_type_2(out_buf,
+ pr[0]->u.paging.identity_lv,
+ pr[0]->u.paging.chan_needed,
+ pr[1]->u.paging.identity_lv,
+ pr[1]->u.paging.chan_needed,
+ pr[2]->u.paging.identity_lv);
+ if (num_pr == 4) {
+ /* re-add #4 for next time */
+ llist_add(&pr[3]->list, group_q);
+ pr[3] = NULL;
+ }
+ } else if (num_pr == 1) {
+ DEBUGP(DPAG, "Tx PAGING TYPE 1 (1 xMSI,1 empty)\n");
+ len = fill_paging_type_1(out_buf,
+ pr[0]->u.paging.identity_lv,
+ pr[0]->u.paging.chan_needed,
+ NULL, 0);
+ } else {
+ /* 2 (any type) or
+ * 3 or 4, of which only 2 will be sent */
+ DEBUGP(DPAG, "Tx PAGING TYPE 1 (2 xMSI)\n");
+ len = fill_paging_type_1(out_buf,
+ pr[0]->u.paging.identity_lv,
+ pr[0]->u.paging.chan_needed,
+ pr[1]->u.paging.identity_lv,
+ pr[1]->u.paging.chan_needed);
+ if (num_pr >= 3) {
+ /* re-add #4 for next time */
+ llist_add(&pr[2]->list, group_q);
+ pr[2] = NULL;
+ }
+ if (num_pr == 4) {
+ /* re-add #4 for next time */
+ llist_add(&pr[3]->list, group_q);
+ pr[3] = NULL;
+ }
+ }
+
+ for (i = 0; i < num_pr; i++) {
+ /* skip those that we might have re-added above */
+ if (pr[i] == NULL)
+ continue;
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_SENT);
+ /* check if we can expire the paging record,
+ * or if we need to re-queue it */
+ if (pr[i]->u.paging.expiration_time <= now) {
+ talloc_free(pr[i]);
+ ps->num_paging--;
+ LOGP(DPAG, LOGL_INFO, "Removed paging record, queue_len=%u\n",
+ ps->num_paging);
+ } else
+ llist_add_tail(&pr[i]->list, group_q);
+ }
+ }
+ memset(out_buf+len, 0x2B, GSM_MACBLOCK_LEN-len);
+ return len;
+}
+
+int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc)
+{
+ LOGP(DPAG, LOGL_INFO, "Paging SI update\n");
+
+ ps->chan_desc = *chan_desc;
+
+ /* FIXME: do we need to re-sort the old paging_records? */
+
+ return 0;
+}
+
+static int paging_signal_cbfn(unsigned int subsys, unsigned int signal, void *hdlr_data,
+ void *signal_data)
+{
+ if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) {
+ struct gsm_bts *bts = signal_data;
+ struct paging_state *ps = bts->paging_state;
+ struct gsm48_system_information_type_3 *si3 = (void *) bts->si_buf[SYSINFO_TYPE_3];
+
+ paging_si_update(ps, &si3->control_channel_desc);
+ }
+ return 0;
+}
+
+static int initialized = 0;
+
+struct paging_state *paging_init(struct gsm_bts *bts,
+ unsigned int num_paging_max,
+ unsigned int paging_lifetime)
+{
+ struct paging_state *ps;
+ unsigned int i;
+
+ ps = talloc_zero(bts, struct paging_state);
+ if (!ps)
+ return NULL;
+
+ ps->bts = bts;
+ ps->paging_lifetime = paging_lifetime;
+ ps->num_paging_max = num_paging_max;
+
+ for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++)
+ INIT_LLIST_HEAD(&ps->paging_queue[i]);
+
+ if (!initialized) {
+ osmo_signal_register_handler(SS_GLOBAL, paging_signal_cbfn, NULL);
+ initialized = 1;
+ }
+ return ps;
+}
+
+void paging_config(struct paging_state *ps,
+ unsigned int num_paging_max,
+ unsigned int paging_lifetime)
+{
+ ps->num_paging_max = num_paging_max;
+ ps->paging_lifetime = paging_lifetime;
+}
+
+void paging_reset(struct paging_state *ps)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) {
+ struct llist_head *queue = &ps->paging_queue[i];
+ struct paging_record *pr, *pr2;
+ llist_for_each_entry_safe(pr, pr2, queue, list) {
+ llist_del(&pr->list);
+ talloc_free(pr);
+ ps->num_paging--;
+ }
+ }
+
+ if (ps->num_paging != 0)
+ LOGP(DPAG, LOGL_NOTICE, "num_paging != 0 after flushing all records?!?\n");
+
+ ps->num_paging = 0;
+}
+
+/**
+ * \brief Helper for the unit tests
+ */
+int paging_group_queue_empty(struct paging_state *ps, uint8_t grp)
+{
+ if (grp >= ARRAY_SIZE(ps->paging_queue))
+ return 1;
+ return llist_empty(&ps->paging_queue[grp]);
+}
+
+int paging_queue_length(struct paging_state *ps)
+{
+ return ps->num_paging;
+}
diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c
new file mode 100644
index 00000000..60e0f7a7
--- /dev/null
+++ b/src/common/pcu_sock.c
@@ -0,0 +1,982 @@
+/* pcu_sock.c: Connect from PCU via unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2012 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <inttypes.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/l1sap.h>
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
+
+extern struct gsm_network bts_gsmnet;
+int pcu_direct = 0;
+static int avail_lai = 0, avail_nse = 0, avail_cell = 0, avail_nsvc[2] = {0, 0};
+
+static const char *sapi_string[] = {
+ [PCU_IF_SAPI_RACH] = "RACH",
+ [PCU_IF_SAPI_AGCH] = "AGCH",
+ [PCU_IF_SAPI_PCH] = "PCH",
+ [PCU_IF_SAPI_BCCH] = "BCCH",
+ [PCU_IF_SAPI_PDTCH] = "PDTCH",
+ [PCU_IF_SAPI_PRACH] = "PRACH",
+ [PCU_IF_SAPI_PTCCH] = "PTCCH",
+};
+
+static int pcu_sock_send(struct gsm_network *net, struct msgb *msg);
+
+/*
+ * PCU messages
+ */
+
+struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+
+ msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx");
+ if (!msg)
+ return NULL;
+ msgb_put(msg, sizeof(struct gsm_pcu_if));
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ pcu_prim->msg_type = msg_type;
+ pcu_prim->bts_nr = bts_nr;
+
+ return msg;
+}
+
+static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
+ if (ts->pchan == GSM_PCHAN_PDCH)
+ return true;
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+ /* When we're busy deactivating the PDCH, we first set
+ * DEACT_PENDING, tell the PCU about it and wait for a
+ * response. So DEACT_PENDING means "no PDCH" to the PCU.
+ * Similarly, when we're activating PDCH, we set the
+ * ACT_PENDING and wait for an activation response from the
+ * PCU, so ACT_PENDING means "is PDCH". */
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return !(ts->flags & TS_F_PDCH_DEACT_PENDING);
+ else
+ return (ts->flags & TS_F_PDCH_ACT_PENDING);
+ }
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ /*
+ * When we're busy de-/activating the PDCH, we first set
+ * ts->dyn.pchan_want, tell the PCU about it and wait for a
+ * response. So only care about dyn.pchan_want here.
+ */
+ return ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+ }
+ return false;
+}
+
+int pcu_tx_info_ind(void)
+{
+ struct gsm_network *net = &bts_gsmnet;
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_info_ind *info_ind;
+ struct gsm_bts *bts;
+ struct gprs_rlc_cfg *rlcc;
+ struct gsm_bts_gprs_nsvc *nsvc;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ int i, j;
+
+ LOGP(DPCU, LOGL_INFO, "Sending info\n");
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+ rlcc = &bts->gprs.cell.rlc_cfg;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ info_ind = &pcu_prim->u.info_ind;
+ info_ind->version = PCU_IF_VERSION;
+
+ if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) {
+ info_ind->flags |= PCU_IF_FLAG_ACTIVE;
+ LOGP(DPCU, LOGL_INFO, "BTS is up\n");
+ } else
+ LOGP(DPCU, LOGL_INFO, "BTS is down\n");
+
+ if (pcu_direct)
+ info_ind->flags |= PCU_IF_FLAG_SYSMO;
+
+ /* RAI */
+ info_ind->mcc = net->plmn.mcc;
+ info_ind->mnc = net->plmn.mnc;
+ info_ind->mnc_3_digits = net->plmn.mnc_3_digits;
+ info_ind->lac = bts->location_area_code;
+ info_ind->rac = bts->gprs.rac;
+
+ /* NSE */
+ info_ind->nsei = bts->gprs.nse.nsei;
+ memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7);
+ memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11);
+
+ /* cell attributes */
+ info_ind->cell_id = bts->cell_identity;
+ info_ind->repeat_time = rlcc->paging.repeat_time;
+ info_ind->repeat_count = rlcc->paging.repeat_count;
+ info_ind->bvci = bts->gprs.cell.bvci;
+ info_ind->t3142 = rlcc->parameter[RLC_T3142];
+ info_ind->t3169 = rlcc->parameter[RLC_T3169];
+ info_ind->t3191 = rlcc->parameter[RLC_T3191];
+ info_ind->t3193_10ms = rlcc->parameter[RLC_T3193];
+ info_ind->t3195 = rlcc->parameter[RLC_T3195];
+ info_ind->n3101 = rlcc->parameter[RLC_N3101];
+ info_ind->n3103 = rlcc->parameter[RLC_N3103];
+ info_ind->n3105 = rlcc->parameter[RLC_N3105];
+ info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN];
+ if (rlcc->cs_mask & (1 << GPRS_CS1))
+ info_ind->flags |= PCU_IF_FLAG_CS1;
+ if (rlcc->cs_mask & (1 << GPRS_CS2))
+ info_ind->flags |= PCU_IF_FLAG_CS2;
+ if (rlcc->cs_mask & (1 << GPRS_CS3))
+ info_ind->flags |= PCU_IF_FLAG_CS3;
+ if (rlcc->cs_mask & (1 << GPRS_CS4))
+ info_ind->flags |= PCU_IF_FLAG_CS4;
+ if (rlcc->cs_mask & (1 << GPRS_MCS1))
+ info_ind->flags |= PCU_IF_FLAG_MCS1;
+ if (rlcc->cs_mask & (1 << GPRS_MCS2))
+ info_ind->flags |= PCU_IF_FLAG_MCS2;
+ if (rlcc->cs_mask & (1 << GPRS_MCS3))
+ info_ind->flags |= PCU_IF_FLAG_MCS3;
+ if (rlcc->cs_mask & (1 << GPRS_MCS4))
+ info_ind->flags |= PCU_IF_FLAG_MCS4;
+ if (rlcc->cs_mask & (1 << GPRS_MCS5))
+ info_ind->flags |= PCU_IF_FLAG_MCS5;
+ if (rlcc->cs_mask & (1 << GPRS_MCS6))
+ info_ind->flags |= PCU_IF_FLAG_MCS6;
+ if (rlcc->cs_mask & (1 << GPRS_MCS7))
+ info_ind->flags |= PCU_IF_FLAG_MCS7;
+ if (rlcc->cs_mask & (1 << GPRS_MCS8))
+ info_ind->flags |= PCU_IF_FLAG_MCS8;
+ if (rlcc->cs_mask & (1 << GPRS_MCS9))
+ info_ind->flags |= PCU_IF_FLAG_MCS9;
+#warning "isn't dl_tbf_ext wrong?: * 10 and no ntohs"
+ info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT];
+#warning "isn't ul_tbf_ext wrong?: * 10 and no ntohs"
+ info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT];
+ info_ind->initial_cs = rlcc->initial_cs;
+ info_ind->initial_mcs = rlcc->initial_mcs;
+
+ /* NSVC */
+ for (i = 0; i < 2; i++) {
+ nsvc = &bts->gprs.nsvc[i];
+ info_ind->nsvci[i] = nsvc->nsvci;
+ info_ind->local_port[i] = nsvc->local_port;
+ info_ind->remote_port[i] = nsvc->remote_port;
+ info_ind->remote_ip[i] = nsvc->remote_ip;
+ }
+
+ for (i = 0; i < 8; i++) {
+ trx = gsm_bts_trx_num(bts, i);
+ if (!trx)
+ break;
+ info_ind->trx[i].pdch_mask = 0;
+ info_ind->trx[i].arfcn = trx->arfcn;
+ info_ind->trx[i].hlayer1 = trx_get_hlayer1(trx);
+ for (j = 0; j < 8; j++) {
+ ts = &trx->ts[j];
+ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+ && ts_should_be_pdch(ts)) {
+ info_ind->trx[i].pdch_mask |= (1 << j);
+ info_ind->trx[i].tsc[j] = gsm_ts_tsc(ts);
+
+ LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: "
+ "available (tsc=%d arfcn=%d)\n",
+ trx->nr, ts->nr,
+ info_ind->trx[i].tsc[j],
+ info_ind->trx[i].arfcn);
+ }
+ }
+ }
+
+ return pcu_sock_send(net, msg);
+}
+
+static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal,
+ void *hdlr_data, void *signal_data)
+{
+ struct gsm_network *net = &bts_gsmnet;
+ struct gsm_bts_gprs_nsvc *nsvc;
+ struct gsm_bts *bts;
+ struct gsm48_system_information_type_3 *si3;
+ int id;
+
+ if (subsys != SS_GLOBAL)
+ return -EINVAL;
+
+ switch(signal) {
+ case S_NEW_SYSINFO:
+ bts = signal_data;
+ if (!(bts->si_valid & (1 << SYSINFO_TYPE_3)))
+ break;
+ si3 = (struct gsm48_system_information_type_3 *)
+ bts->si_buf[SYSINFO_TYPE_3];
+ osmo_plmn_from_bcd(si3->lai.digits, &net->plmn);
+ bts->location_area_code = ntohs(si3->lai.lac);
+ bts->cell_identity = si3->cell_identity;
+ avail_lai = 1;
+ break;
+ case S_NEW_NSE_ATTR:
+ bts = signal_data;
+ avail_nse = 1;
+ break;
+ case S_NEW_CELL_ATTR:
+ bts = signal_data;
+ avail_cell = 1;
+ break;
+ case S_NEW_NSVC_ATTR:
+ nsvc = signal_data;
+ id = nsvc->id;
+ if (id < 0 || id > 1)
+ return -EINVAL;
+ avail_nsvc[id] = 1;
+ break;
+ case S_NEW_OP_STATE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* If all infos have been received, of if one info is updated after
+ * all infos have been received, transmit info update. */
+ if (avail_lai && avail_nse && avail_cell && avail_nsvc[0])
+ pcu_tx_info_ind();
+ return 0;
+}
+
+
+int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_rts_req *rts_req;
+ struct gsm_bts *bts = ts->trx->bts;
+
+ LOGP(DPCU, LOGL_DEBUG, "Sending rts request: is_ptcch=%d arfcn=%d "
+ "block=%d\n", is_ptcch, arfcn, block_nr);
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_RTS_REQ, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ rts_req = &pcu_prim->u.rts_req;
+
+ rts_req->sapi = (is_ptcch) ? PCU_IF_SAPI_PTCCH : PCU_IF_SAPI_PDTCH;
+ rts_req->fn = fn;
+ rts_req->arfcn = arfcn;
+ rts_req->trx_nr = ts->trx->nr;
+ rts_req->ts_nr = ts->nr;
+ rts_req->block_nr = block_nr;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len,
+ int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_data *data_ind;
+ struct gsm_bts *bts = ts->trx->bts;
+
+ LOGP(DPCU, LOGL_DEBUG, "Sending data indication: sapi=%s arfcn=%d block=%d data=%s\n",
+ sapi_string[sapi], arfcn, block_nr, osmo_hexdump(data, len));
+
+ if (lqual / 10 < bts->min_qual_norm) {
+ LOGP(DPCU, LOGL_DEBUG, "Link quality %"PRId16" is below threshold %f, dropping packet\n",
+ lqual, bts->min_qual_norm);
+ return 0;
+ }
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ data_ind = &pcu_prim->u.data_ind;
+
+ data_ind->sapi = sapi;
+ data_ind->rssi = rssi;
+ data_ind->fn = fn;
+ data_ind->arfcn = arfcn;
+ data_ind->trx_nr = ts->trx->nr;
+ data_ind->ts_nr = ts->nr;
+ data_ind->block_nr = block_nr;
+ data_ind->ber10k = ber10k;
+ data_ind->ta_offs_qbits = bto;
+ data_ind->lqual_cb = lqual;
+ memcpy(data_ind->data, data, len);
+ data_ind->len = len;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
+ uint8_t is_11bit, enum ph_burst_type burst_type)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_rach_ind *rach_ind;
+
+ LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, "
+ "fn=%d\n", qta, ra, fn);
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ rach_ind = &pcu_prim->u.rach_ind;
+
+ rach_ind->sapi = PCU_IF_SAPI_RACH;
+ rach_ind->ra = ra;
+ rach_ind->qta = qta;
+ rach_ind->fn = fn;
+ rach_ind->is_11bit = is_11bit;
+ rach_ind->burst_type = burst_type;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_time_ind(uint32_t fn)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_time_ind *time_ind;
+ uint8_t fn13 = fn % 13;
+
+ /* omit frame numbers not starting at a MAC block */
+ if (fn13 != 0 && fn13 != 4 && fn13 != 8)
+ return 0;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_TIME_IND, 0);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ time_ind = &pcu_prim->u.time_ind;
+
+ time_ind->fn = fn;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed)
+{
+ struct pcu_sock_state *state = bts_gsmnet.pcu_state;
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_pag_req *pag_req;
+
+ /* check if identity does not fit: length > sizeof(lv) - 1 */
+ if (identity_lv[0] >= sizeof(pag_req->identity_lv)) {
+ LOGP(DPCU, LOGL_ERROR, "Paging identity too large (%d)\n",
+ identity_lv[0]);
+ return -EINVAL;
+ }
+
+ /* socket not created */
+ if (!state) {
+ LOGP(DPCU, LOGL_DEBUG, "PCU socket not created, ignoring "
+ "paging message\n");
+ return 0;
+ }
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_PAG_REQ, 0);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ pag_req = &pcu_prim->u.pag_req;
+
+ pag_req->chan_needed = chan_needed;
+ memcpy(pag_req->identity_lv, identity_lv, identity_lv[0] + 1);
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len)
+{
+ struct gsm_network *net = &bts_gsmnet;
+ struct gsm_bts *bts;
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_data *data_cnf;
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+
+ LOGP(DPCU, LOGL_INFO, "Sending PCH confirm\n");
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ data_cnf = &pcu_prim->u.data_cnf;
+
+ data_cnf->sapi = PCU_IF_SAPI_PCH;
+ data_cnf->fn = fn;
+ memcpy(data_cnf->data, data, len);
+ data_cnf->len = len;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
+ struct gsm_pcu_if_data *data_req)
+{
+ uint8_t is_ptcch;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct msgb *msg;
+ int rc = 0;
+
+ LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d "
+ "block=%d data=%s\n", sapi_string[data_req->sapi],
+ data_req->arfcn, data_req->block_nr,
+ osmo_hexdump(data_req->data, data_req->len));
+
+ switch (data_req->sapi) {
+ case PCU_IF_SAPI_PCH:
+ paging_add_imm_ass(bts->paging_state, data_req->data, data_req->len);
+ break;
+ case PCU_IF_SAPI_AGCH:
+ msg = msgb_alloc(data_req->len, "pcu_agch");
+ if (!msg) {
+ rc = -ENOMEM;
+ break;
+ }
+ msg->l3h = msgb_put(msg, data_req->len);
+ memcpy(msg->l3h, data_req->data, data_req->len);
+ if (bts_agch_enqueue(bts, msg) < 0) {
+ msgb_free(msg);
+ rc = -EIO;
+ }
+ break;
+ case PCU_IF_SAPI_PDTCH:
+ case PCU_IF_SAPI_PTCCH:
+ trx = gsm_bts_trx_num(bts, data_req->trx_nr);
+ if (!trx) {
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "not existing TRX %d\n", data_req->trx_nr);
+ rc = -EINVAL;
+ break;
+ }
+ if (data_req->ts_nr >= ARRAY_SIZE(trx->ts)) {
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "not existing TS %u\n", data_req->ts_nr);
+ rc = -EINVAL;
+ break;
+ }
+ ts = &trx->ts[data_req->ts_nr];
+ if (!ts_should_be_pdch(ts)) {
+ LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for non-PDCH TS\n",
+ gsm_ts_name(ts));
+ rc = -EINVAL;
+ break;
+ }
+ if (ts->lchan[0].state != LCHAN_S_ACTIVE) {
+ LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for inactive lchan\n",
+ gsm_ts_name(ts));
+ rc = -EINVAL;
+ break;
+ }
+ is_ptcch = (data_req->sapi == PCU_IF_SAPI_PTCCH);
+ rc = l1sap_pdch_req(ts, is_ptcch, data_req->fn, data_req->arfcn,
+ data_req->block_nr, data_req->data, data_req->len);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "unsupported sapi %d\n", data_req->sapi);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int pcu_rx_pag_req(struct gsm_bts *bts, uint8_t msg_type,
+ struct gsm_pcu_if_pag_req *pag_req)
+{
+ int rc = 0;
+
+ OSMO_ASSERT(msg_type == PCU_IF_MSG_PAG_REQ);
+
+ /* FIXME: Add function to schedule paging request.
+ * At present, osmo-pcu sends paging requests in PCU_IF_MSG_DATA_REQ
+ * messages which are processed by pcu_rx_data_req().
+ * This code path is not triggered in practice. */
+ LOGP(DPCU, LOGL_NOTICE, "Paging request received: chan_needed=%d length=%d "
+ "(dropping message because support for PCU_IF_MSG_PAG_REQ is not yet implemented)\n",
+ pag_req->chan_needed, pag_req->identity_lv[0]);
+
+ return rc;
+}
+
+int pcu_tx_si13(const struct gsm_bts *bts, bool enable)
+{
+ /* the SI is per-BTS so it doesn't matter which TRX we use */
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 0);
+
+ /* The low-level data like FN, ARFCN etc will be ignored but we have to set lqual high enough to bypass
+ the check at lower levels */
+ int rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, GSM_BTS_SI(bts, SYSINFO_TYPE_13),
+ enable ? GSM_MACBLOCK_LEN : 0, 0, 0, 0, INT16_MAX);
+ if (rc < 0)
+ LOGP(DPCU, LOGL_NOTICE, "Failed to send SI13 to PCU: %d\n", rc);
+
+ return rc;
+}
+
+static int pcu_rx_txt_ind(struct gsm_bts *bts,
+ struct gsm_pcu_if_txt_ind *txt)
+{
+ switch (txt->type) {
+ case PCU_VERSION:
+ LOGP(DPCU, LOGL_INFO, "OsmoPCU version %s connected\n",
+ txt->text);
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, txt->text);
+ osmo_strlcpy(bts->pcu_version, txt->text, MAX_VERSION_LENGTH);
+
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13))
+ return pcu_tx_si13(bts, true);
+
+ LOGP(DPCU, LOGL_INFO, "SI13 is not available on PCU connection\n");
+ break;
+ case PCU_OML_ALERT:
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, txt->text);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Unknown TXT_IND type %u received\n",
+ txt->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pcu_rx_act_req(struct gsm_bts *bts,
+ struct gsm_pcu_if_act_req *act_req)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_lchan *lchan;
+
+ LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TX=%d\n",
+ (act_req->activate) ? "Activate" : "Deactivate",
+ act_req->trx_nr, act_req->ts_nr);
+
+ trx = gsm_bts_trx_num(bts, act_req->trx_nr);
+ if (!trx || act_req->ts_nr >= 8)
+ return -EINVAL;
+
+ lchan = trx->ts[act_req->ts_nr].lchan;
+ lchan->rel_act_kind = LCHAN_REL_ACT_PCU;
+ if (lchan->type != GSM_LCHAN_PDTCH) {
+ LOGP(DPCU, LOGL_ERROR,
+ "%s request, but lchan is not of type PDTCH (is %s)\n",
+ (act_req->activate) ? "Activate" : "Deactivate",
+ gsm_lchant_name(lchan->type));
+ return -EINVAL;
+ }
+ if (act_req->activate)
+ l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan), NULL);
+ else
+ l1sap_chan_rel(trx, gsm_lchan2chan_nr(lchan));
+
+ return 0;
+}
+
+static int pcu_rx(struct gsm_network *net, uint8_t msg_type,
+ struct gsm_pcu_if *pcu_prim)
+{
+ int rc = 0;
+ struct gsm_bts *bts;
+
+ /* FIXME: allow multiple BTS */
+ if (pcu_prim->bts_nr != 0) {
+ LOGP(DPCU, LOGL_ERROR, "Received PCU Prim for non-existent BTS %u\n", pcu_prim->bts_nr);
+ return -EINVAL;
+ }
+ bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+
+ switch (msg_type) {
+ case PCU_IF_MSG_DATA_REQ:
+ rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req);
+ break;
+ case PCU_IF_MSG_PAG_REQ:
+ rc = pcu_rx_pag_req(bts, msg_type, &pcu_prim->u.pag_req);
+ break;
+ case PCU_IF_MSG_ACT_REQ:
+ rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req);
+ break;
+ case PCU_IF_MSG_TXT_IND:
+ rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n",
+ msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/*
+ * PCU socket interface
+ */
+
+struct pcu_sock_state {
+ struct gsm_network *net;
+ struct osmo_fd listen_bfd; /* fd for listen socket */
+ struct osmo_fd conn_bfd; /* fd for connection to lcr */
+ struct llist_head upqueue; /* queue for sending messages */
+};
+
+static int pcu_sock_send(struct gsm_network *net, struct msgb *msg)
+{
+ struct pcu_sock_state *state = net->pcu_state;
+ struct osmo_fd *conn_bfd;
+ struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data;
+
+ if (!state) {
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ LOGP(DPCU, LOGL_INFO, "PCU socket not created, "
+ "dropping message\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ conn_bfd = &state->conn_bfd;
+ if (conn_bfd->fd <= 0) {
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, "
+ "dropping message\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msgb_enqueue(&state->upqueue, msg);
+ conn_bfd->when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void pcu_sock_close(struct pcu_sock_state *state)
+{
+ struct osmo_fd *bfd = &state->conn_bfd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ int i, j;
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list);
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, NULL);
+ bts->pcu_version[0] = '\0';
+
+ close(bfd->fd);
+ bfd->fd = -1;
+ osmo_fd_unregister(bfd);
+
+ /* re-enable the generation of ACCEPT for new connections */
+ state->listen_bfd.when |= BSC_FD_READ;
+
+#if 0
+ /* remove si13, ... */
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_13);
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+#endif
+
+ /* release PDCH */
+ for (i = 0; i < 8; i++) {
+ trx = gsm_bts_trx_num(bts, i);
+ if (!trx)
+ break;
+ for (j = 0; j < 8; j++) {
+ ts = &trx->ts[j];
+ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+ && ts->pchan == GSM_PCHAN_PDCH) {
+ ts->lchan[0].rel_act_kind = LCHAN_REL_ACT_PCU;
+ l1sap_chan_rel(trx,
+ gsm_lchan2chan_nr(&ts->lchan[0]));
+ }
+ }
+ }
+
+ /* flush the queue */
+ while (!llist_empty(&state->upqueue)) {
+ struct msgb *msg = msgb_dequeue(&state->upqueue);
+ msgb_free(msg);
+ }
+}
+
+static int pcu_sock_read(struct osmo_fd *bfd)
+{
+ struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+ struct gsm_pcu_if *pcu_prim;
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx");
+ if (!msg)
+ return -ENOMEM;
+
+ pcu_prim = (struct gsm_pcu_if *) msg->tail;
+
+ rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+ if (rc == 0)
+ goto close;
+
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ goto close;
+ }
+
+ if (rc < sizeof(*pcu_prim)) {
+ LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive size "
+ "is %lu, discarding\n", rc, sizeof(*pcu_prim));
+ return 0;
+ }
+
+ rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim);
+
+ /* as we always synchronously process the message in pcu_rx() and
+ * its callbacks, we can free the message here. */
+ msgb_free(msg);
+
+ return rc;
+
+close:
+ msgb_free(msg);
+ pcu_sock_close(state);
+ return -1;
+}
+
+static int pcu_sock_write(struct osmo_fd *bfd)
+{
+ struct pcu_sock_state *state = bfd->data;
+ int rc;
+
+ while (!llist_empty(&state->upqueue)) {
+ struct msgb *msg, *msg2;
+ struct gsm_pcu_if *pcu_prim;
+
+ /* peek at the beginning of the queue */
+ msg = llist_entry(state->upqueue.next, struct msgb, list);
+ pcu_prim = (struct gsm_pcu_if *)msg->data;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+ if (!msgb_length(msg)) {
+ LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO "
+ "bytes!\n", pcu_prim->msg_type);
+ goto dontsend;
+ }
+
+ /* try to send it over the socket */
+ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc == 0)
+ goto close;
+ if (rc < 0) {
+ if (errno == EAGAIN) {
+ bfd->when |= BSC_FD_WRITE;
+ break;
+ }
+ goto close;
+ }
+
+dontsend:
+ /* _after_ we send it, we can deueue */
+ msg2 = msgb_dequeue(&state->upqueue);
+ assert(msg == msg2);
+ msgb_free(msg);
+ }
+ return 0;
+
+close:
+ pcu_sock_close(state);
+
+ return -1;
+}
+
+static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+ int rc = 0;
+
+ if (flags & BSC_FD_READ)
+ rc = pcu_sock_read(bfd);
+ if (rc < 0)
+ return rc;
+
+ if (flags & BSC_FD_WRITE)
+ rc = pcu_sock_write(bfd);
+
+ return rc;
+}
+
+/* accept connection comming from PCU */
+static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags)
+{
+ struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+ struct osmo_fd *conn_bfd = &state->conn_bfd;
+ struct sockaddr_un un_addr;
+ socklen_t len;
+ int rc;
+
+ len = sizeof(un_addr);
+ rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+ if (rc < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n");
+ return -1;
+ }
+
+ if (conn_bfd->fd >= 0) {
+ LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have "
+ "another active connection ?!?\n");
+ /* We already have one PCU connected, this is all we support */
+ state->listen_bfd.when &= ~BSC_FD_READ;
+ close(rc);
+ return 0;
+ }
+
+ conn_bfd->fd = rc;
+ conn_bfd->when = BSC_FD_READ;
+ conn_bfd->cb = pcu_sock_cb;
+ conn_bfd->data = state;
+
+ if (osmo_fd_register(conn_bfd) != 0) {
+ LOGP(DPCU, LOGL_ERROR, "Failed to register new connection "
+ "fd\n");
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+ return -1;
+ }
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n");
+
+ /* send current info */
+ pcu_tx_info_ind();
+
+ return 0;
+}
+
+int pcu_sock_init(const char *path)
+{
+ struct pcu_sock_state *state;
+ struct osmo_fd *bfd;
+ int rc;
+
+ state = talloc_zero(NULL, struct pcu_sock_state);
+ if (!state)
+ return -ENOMEM;
+
+ INIT_LLIST_HEAD(&state->upqueue);
+ state->net = &bts_gsmnet;
+ state->conn_bfd.fd = -1;
+
+ bfd = &state->listen_bfd;
+
+ bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path,
+ OSMO_SOCK_F_BIND);
+ if (bfd->fd < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Could not create %s unix socket: %s\n",
+ path, strerror(errno));
+ talloc_free(state);
+ return -1;
+ }
+
+ bfd->when = BSC_FD_READ;
+ bfd->cb = pcu_sock_accept;
+ bfd->data = state;
+
+ rc = osmo_fd_register(bfd);
+ if (rc < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n",
+ rc);
+ close(bfd->fd);
+ talloc_free(state);
+ return rc;
+ }
+
+ osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL);
+
+ bts_gsmnet.pcu_state = state;
+
+ LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path);
+
+ return 0;
+}
+
+void pcu_sock_exit(void)
+{
+ struct pcu_sock_state *state = bts_gsmnet.pcu_state;
+ struct osmo_fd *bfd, *conn_bfd;
+
+ if (!state)
+ return;
+
+ osmo_signal_unregister_handler(SS_GLOBAL, pcu_if_signal_cb, NULL);
+ conn_bfd = &state->conn_bfd;
+ if (conn_bfd->fd > 0)
+ pcu_sock_close(state);
+ bfd = &state->listen_bfd;
+ close(bfd->fd);
+ osmo_fd_unregister(bfd);
+ talloc_free(state);
+ bts_gsmnet.pcu_state = NULL;
+}
+
+bool pcu_connected(void) {
+ struct gsm_network *net = &bts_gsmnet;
+ struct pcu_sock_state *state = net->pcu_state;
+
+ if (!state)
+ return false;
+ if (state->conn_bfd.fd <= 0)
+ return false;
+ return true;
+}
diff --git a/src/common/phy_link.c b/src/common/phy_link.c
new file mode 100644
index 00000000..588fcc91
--- /dev/null
+++ b/src/common/phy_link.c
@@ -0,0 +1,163 @@
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+
+static LLIST_HEAD(g_phy_links);
+
+struct phy_link *phy_link_by_num(int num)
+{
+ struct phy_link *plink;
+
+ llist_for_each_entry(plink, &g_phy_links, list) {
+ if (plink->num == num)
+ return plink;
+ }
+
+ return NULL;
+}
+
+struct phy_link *phy_link_create(void *ctx, int num)
+{
+ struct phy_link *plink;
+
+ if (phy_link_by_num(num))
+ return NULL;
+
+ plink = talloc_zero(ctx, struct phy_link);
+ plink->num = num;
+ plink->state = PHY_LINK_SHUTDOWN;
+ INIT_LLIST_HEAD(&plink->instances);
+ llist_add_tail(&plink->list, &g_phy_links);
+
+ bts_model_phy_link_set_defaults(plink);
+
+ return plink;
+}
+
+const struct value_string phy_link_state_vals[] = {
+ { PHY_LINK_SHUTDOWN, "shutdown" },
+ { PHY_LINK_CONNECTING, "connecting" },
+ { PHY_LINK_CONNECTED, "connected" },
+ { 0, NULL }
+};
+
+void phy_link_state_set(struct phy_link *plink, enum phy_link_state state)
+{
+ struct phy_instance *pinst;
+
+ LOGP(DL1C, LOGL_INFO, "PHY link state change %s -> %s\n",
+ get_value_string(phy_link_state_vals, plink->state),
+ get_value_string(phy_link_state_vals, state));
+
+ /* notify all TRX associated with this phy */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ struct gsm_bts_trx *trx = pinst->trx;
+ if (!trx)
+ continue;
+
+ switch (state) {
+ case PHY_LINK_CONNECTED:
+ LOGP(DL1C, LOGL_INFO, "trx_set_avail(1)\n");
+ trx_set_available(trx, 1);
+ break;
+ case PHY_LINK_SHUTDOWN:
+ LOGP(DL1C, LOGL_INFO, "trx_set_avail(0)\n");
+ trx_set_available(trx, 0);
+ break;
+ case PHY_LINK_CONNECTING:
+ /* nothing to do */
+ break;
+ }
+ }
+
+ plink->state = state;
+}
+
+struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num)
+{
+ struct phy_instance *pinst;
+
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->num == num)
+ return pinst;
+ }
+ return NULL;
+}
+
+struct phy_instance *phy_instance_create(struct phy_link *plink, int num)
+{
+ struct phy_instance *pinst;
+
+ if (phy_instance_by_num(plink, num))
+ return NULL;
+
+ pinst = talloc_zero(plink, struct phy_instance);
+ pinst->num = num;
+ pinst->phy_link = plink;
+ llist_add_tail(&pinst->list, &plink->instances);
+
+ bts_model_phy_instance_set_defaults(pinst);
+
+ return pinst;
+}
+
+void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx)
+{
+ trx->role_bts.l1h = pinst;
+ pinst->trx = trx;
+}
+
+void phy_instance_destroy(struct phy_instance *pinst)
+{
+ /* remove from list of instances in the link */
+ llist_del(&pinst->list);
+
+ /* remove reverse link from TRX */
+ OSMO_ASSERT(pinst->trx->role_bts.l1h == pinst);
+ pinst->trx->role_bts.l1h = NULL;
+ pinst->trx = NULL;
+
+ talloc_free(pinst);
+}
+
+void phy_link_destroy(struct phy_link *plink)
+{
+ struct phy_instance *pinst, *pinst2;
+
+ llist_for_each_entry_safe(pinst, pinst2, &plink->instances, list)
+ phy_instance_destroy(pinst);
+
+ talloc_free(plink);
+}
+
+int phy_links_open(void)
+{
+ struct phy_link *plink;
+
+ llist_for_each_entry(plink, &g_phy_links, list) {
+ int rc;
+
+ rc = bts_model_phy_link_open(plink);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+const char *phy_instance_name(struct phy_instance *pinst)
+{
+ static char buf[32];
+
+ snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num,
+ pinst->num);
+ return buf;
+}
diff --git a/src/common/power_control.c b/src/common/power_control.c
new file mode 100644
index 00000000..b1728705
--- /dev/null
+++ b/src/common/power_control.c
@@ -0,0 +1,89 @@
+/* MS Power Control Loop L1 */
+
+/* (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+
+/*
+ * Check if manual power control is needed
+ * Check if fixed power was selected
+ * Check if the MS is already using our level if not
+ * the value is bogus..
+ * TODO: Add a timeout.. e.g. if the ms is not capable of reaching
+ * the value we have set.
+ */
+int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
+ const uint8_t ms_power, const int rxLevel)
+{
+ int rx;
+ int cur_dBm, new_dBm, new_pwr;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ const enum gsm_band band = bts->band;
+
+ if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ return 0;
+ if (lchan->ms_power_ctrl.fixed)
+ return 0;
+
+ /* The phone hasn't reached the power level yet */
+ if (lchan->ms_power_ctrl.current != ms_power)
+ return 0;
+
+ /* What is the difference between what we want and received? */
+ rx = bts->ul_power_target - rxLevel;
+
+ cur_dBm = ms_pwr_dbm(band, ms_power);
+ new_dBm = cur_dBm + rx;
+
+ /* Clamp negative values and do it depending on the band */
+ if (new_dBm < 0)
+ new_dBm = 0;
+
+ switch (band) {
+ case GSM_BAND_1800:
+ /* If MS_TX_PWR_MAX_CCH is set the values 29,
+ * 30, 31 are not used. Avoid specifying a dBm
+ * that would lead to these power levels. The
+ * phone might not be able to reach them. */
+ if (new_dBm > 30)
+ new_dBm = 30;
+ break;
+ default:
+ break;
+ }
+
+ new_pwr = ms_pwr_ctl_lvl(band, new_dBm);
+ if (lchan->ms_power_ctrl.current != new_pwr) {
+ lchan->ms_power_ctrl.current = new_pwr;
+ bts_model_adjst_ms_pwr(lchan);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/common/rsl.c b/src/common/rsl.c
new file mode 100644
index 00000000..507e8aaf
--- /dev/null
+++ b/src/common/rsl.c
@@ -0,0 +1,2996 @@
+/* GSM TS 08.58 RSL, BTS Side */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011-2017 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "btsconfig.h" /* for PACKAGE_VERSION */
+
+#include <stdio.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/cbch.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcuif_proto.h>
+
+//#define FAKE_CIPH_MODE_COMPL
+
+
+static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uint8_t *chan_nr,
+ const uint8_t *link_id, const struct msgb *orig_msg);
+
+/* list of RSL SI types that can occur on the SACCH */
+static const unsigned int rsl_sacch_sitypes[] = {
+ RSL_SYSTEM_INFO_5,
+ RSL_SYSTEM_INFO_6,
+ RSL_SYSTEM_INFO_5bis,
+ RSL_SYSTEM_INFO_5ter,
+ RSL_EXT_MEAS_ORDER,
+ RSL_MEAS_INFO,
+};
+
+/* FIXME: move this to libosmocore */
+int osmo_in_array(unsigned int search, const unsigned int *arr, unsigned int size)
+{
+ unsigned int i;
+ for (i = 0; i < size; i++) {
+ if (arr[i] == search)
+ return 1;
+ }
+ return 0;
+}
+#define OSMO_IN_ARRAY(search, arr) osmo_in_array(search, arr, ARRAY_SIZE(arr))
+
+int msgb_queue_flush(struct llist_head *list)
+{
+ struct msgb *msg, *msg2;
+ int count = 0;
+
+ llist_for_each_entry_safe(msg, msg2, list, list) {
+ msgb_free(msg);
+ count++;
+ }
+
+ return count;
+}
+
+/* FIXME: move this to libosmocore */
+void gsm48_gen_starting_time(uint8_t *out, struct gsm_time *gtime)
+{
+ uint8_t t1p = gtime->t1 % 32;
+ out[0] = (t1p << 3) | (gtime->t3 >> 3);
+ out[1] = (gtime->t3 << 5) | gtime->t2;
+}
+
+/* compute lchan->rsl_cmode and lchan->tch_mode from RSL CHAN MODE IE */
+static void lchan_tchmode_from_cmode(struct gsm_lchan *lchan,
+ struct rsl_ie_chan_mode *cm)
+{
+ lchan->rsl_cmode = cm->spd_ind;
+ lchan->ts->trx->bts->dtxd = (cm->dtx_dtu & RSL_CMOD_DTXd) ? true : false;
+
+ switch (cm->chan_rate) {
+ case RSL_CMOD_SP_GSM1:
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ break;
+ case RSL_CMOD_SP_GSM2:
+ lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+ break;
+ case RSL_CMOD_SP_GSM3:
+ lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
+ break;
+ case RSL_CMOD_SP_NT_14k5:
+ lchan->tch_mode = GSM48_CMODE_DATA_14k5;
+ break;
+ case RSL_CMOD_SP_NT_12k0:
+ lchan->tch_mode = GSM48_CMODE_DATA_12k0;
+ break;
+ case RSL_CMOD_SP_NT_6k0:
+ lchan->tch_mode = GSM48_CMODE_DATA_6k0;
+ break;
+ }
+}
+
+
+/*
+ * support
+ */
+
+/* Is this channel number for a dedicated channel (true) or not (false) */
+static bool chan_nr_is_dchan(uint8_t chan_nr)
+{
+ /* See TS 48.058 9.3.1 + Osmocom extension for RSL_CHAN_OSMO_PDCH */
+ if ((chan_nr & 0xc0) == 0x80)
+ return false;
+ else
+ return true;
+}
+
+static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ const char *log_name)
+{
+ int rc;
+ struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc);
+
+ if (!lchan) {
+ LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n", log_name,
+ chan_nr);
+ return NULL;
+ }
+
+ if (rc < 0) {
+ LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n",
+ gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr);
+ return NULL;
+ }
+ return lchan;
+}
+
+static struct msgb *rsl_msgb_alloc(int hdr_size)
+{
+ struct msgb *nmsg;
+
+ hdr_size += sizeof(struct ipaccess_head);
+
+ nmsg = msgb_alloc_headroom(600+hdr_size, hdr_size, "RSL");
+ if (!nmsg)
+ return NULL;
+ nmsg->l3h = nmsg->data;
+ return nmsg;
+}
+
+static void rsl_trx_push_hdr(struct msgb *msg, uint8_t msg_type)
+{
+ struct abis_rsl_common_hdr *th;
+
+ th = (struct abis_rsl_common_hdr *) msgb_push(msg, sizeof(*th));
+ th->msg_discr = ABIS_RSL_MDISC_TRX;
+ th->msg_type = msg_type;
+}
+
+static void rsl_cch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr)
+{
+ struct abis_rsl_cchan_hdr *cch;
+
+ cch = (struct abis_rsl_cchan_hdr *) msgb_push(msg, sizeof(*cch));
+ cch->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+ cch->c.msg_type = msg_type;
+ cch->ie_chan = RSL_IE_CHAN_NR;
+ cch->chan_nr = chan_nr;
+}
+
+static void rsl_dch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr)
+{
+ struct abis_rsl_dchan_hdr *dch;
+
+ dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch));
+ dch->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dch->c.msg_type = msg_type;
+ dch->ie_chan = RSL_IE_CHAN_NR;
+ dch->chan_nr = chan_nr;
+}
+
+static void rsl_ipa_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr)
+{
+ struct abis_rsl_dchan_hdr *dch;
+
+ dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch));
+ dch->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+ dch->c.msg_type = msg_type;
+ dch->ie_chan = RSL_IE_CHAN_NR;
+ dch->chan_nr = chan_nr;
+}
+
+/*
+ * TRX related messages
+ */
+
+/* 8.6.4 sending ERROR REPORT */
+static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uint8_t *chan_nr,
+ const uint8_t *link_id, const struct msgb *orig_msg)
+{
+ unsigned int len = sizeof(struct abis_rsl_common_hdr);
+ struct msgb *nmsg;
+
+ LOGP(DRSL, LOGL_NOTICE, "Tx RSL Error Report: cause = 0x%02x\n", cause);
+
+ if (orig_msg)
+ len += 2 + 3+msgb_l2len(orig_msg); /* chan_nr + TLV(orig_msg) */
+ if (chan_nr)
+ len += 2;
+ if (link_id)
+ len += 2;
+
+ nmsg = rsl_msgb_alloc(len);
+ if (!nmsg)
+ return -ENOMEM;
+ msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause);
+ if (orig_msg && msgb_l2len(orig_msg) >= sizeof(struct abis_rsl_common_hdr)) {
+ struct abis_rsl_common_hdr *ch = (struct abis_rsl_common_hdr *) msgb_l2(orig_msg);
+ msgb_tv_put(nmsg, RSL_IE_MSG_ID, ch->msg_type);
+ }
+ if (chan_nr)
+ msgb_tv_put(nmsg, RSL_IE_CHAN_NR, *chan_nr);
+ if (link_id)
+ msgb_tv_put(nmsg, RSL_IE_LINK_IDENT, *link_id);
+ if (orig_msg)
+ msgb_tlv_put(nmsg, RSL_IE_ERR_MSG, msgb_l2len(orig_msg), msgb_l2(orig_msg));
+
+ rsl_trx_push_hdr(nmsg, RSL_MT_ERROR_REPORT);
+ nmsg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/* 8.6.1 sending RF RESOURCE INDICATION */
+int rsl_tx_rf_res(struct gsm_bts_trx *trx)
+{
+ struct msgb *nmsg;
+
+ LOGP(DRSL, LOGL_INFO, "Tx RSL RF RESource INDication\n");
+
+ nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_common_hdr));
+ if (!nmsg)
+ return -ENOMEM;
+ // FIXME: add interference levels of TRX
+ msgb_tlv_put(nmsg, RSL_IE_RESOURCE_INFO, 0, NULL);
+ rsl_trx_push_hdr(nmsg, RSL_MT_RF_RES_IND);
+ nmsg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/*
+ * common channel releated messages
+ */
+
+/* 8.5.1 BCCH INFOrmation is received */
+static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct tlv_parsed tp;
+ uint8_t rsl_si, count;
+ enum osmo_sysinfo_type osmo_si;
+ struct gsm48_system_information_type_2quater *si2q;
+ struct bitvec bv;
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.30 System Info Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE);
+ if (OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes))
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI 0x%02x not supported.\n", rsl_si);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+ /* 9.3.39 Full BCCH Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_FULL_BCCH_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_FULL_BCCH_INFO);
+ if (len > sizeof(sysinfo_buf_t)) {
+ LOGP(DRSL, LOGL_ERROR, "Truncating received Full BCCH Info (%u -> %zu) for SI%s\n",
+ len, sizeof(sysinfo_buf_t), get_value_string(osmo_sitype_strs, osmo_si));
+ len = sizeof(sysinfo_buf_t);
+ }
+
+ LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s, %u bytes)\n",
+ get_value_string(osmo_sitype_strs, osmo_si), len);
+
+ if (SYSINFO_TYPE_2quater == osmo_si) {
+ si2q = (struct gsm48_system_information_type_2quater *) TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO);
+ bv.data = si2q->rest_octets;
+ bv.data_len = GSM_MACBLOCK_LEN;
+ bv.cur_bit = 3;
+ bts->si2q_index = (uint8_t) bitvec_get_uint(&bv, 4);
+
+ count = (uint8_t) bitvec_get_uint(&bv, 4);
+ if (bts->si2q_count && bts->si2q_count != count) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI2quater count updated: %u -> %d\n",
+ bts->si2q_count, count);
+ }
+
+ bts->si2q_count = count;
+ if (bts->si2q_index > bts->si2q_count) {
+ LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with index %u > count %u\n",
+ bts->si2q_index, bts->si2q_count);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+
+ if (bts->si2q_index > SI2Q_MAX_NUM || bts->si2q_count > SI2Q_MAX_NUM) {
+ LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with impossible parameters: index %u, count %u"
+ "should be <= %u\n", bts->si2q_index, bts->si2q_count, SI2Q_MAX_NUM);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+
+ memset(GSM_BTS_SI2Q(bts, bts->si2q_index), GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t));
+ memcpy(GSM_BTS_SI2Q(bts, bts->si2q_index), TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len);
+ } else {
+ memset(bts->si_buf[osmo_si], GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t));
+ memcpy(bts->si_buf[osmo_si], TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len);
+ }
+
+ bts->si_valid |= (1 << osmo_si);
+
+ if (SYSINFO_TYPE_3 == osmo_si && trx->nr == 0 &&
+ num_agch(trx, "RSL") != 1) {
+ lchan_deactivate(&trx->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ /* will be reactivated by sapi_deactivate_cb() */
+ trx->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_REACT;
+ }
+
+ if (SYSINFO_TYPE_13 == osmo_si)
+ pcu_tx_si13(trx->bts, true);
+
+ } else if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+ if (len > sizeof(sysinfo_buf_t))
+ len = sizeof(sysinfo_buf_t);
+ bts->si_valid |= (1 << osmo_si);
+ memset(bts->si_buf[osmo_si], 0x2b, sizeof(sysinfo_buf_t));
+ memcpy(bts->si_buf[osmo_si],
+ TLVP_VAL(&tp, RSL_IE_L3_INFO), len);
+ LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s)\n",
+ get_value_string(osmo_sitype_strs, osmo_si));
+ } else {
+ bts->si_valid &= ~(1 << osmo_si);
+ LOGP(DRSL, LOGL_INFO, " RX RSL Disabling BCCH INFO (SI%s)\n",
+ get_value_string(osmo_sitype_strs, osmo_si));
+ if (SYSINFO_TYPE_13 == osmo_si)
+ pcu_tx_si13(trx->bts, false);
+ }
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+
+ return 0;
+}
+
+/* 8.5.2 CCCH Load Indication (PCH) */
+int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+ rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_PCH_AGCH);
+ msgb_tv16_put(msg, RSL_IE_PAGING_LOAD, paging_avail);
+ msg->trx = bts->c0;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.2 CCCH Load Indication (RACH) */
+int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total,
+ uint16_t busy, uint16_t access)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+ rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_RACH);
+ /* tag and length */
+ msgb_tv_put(msg, RSL_IE_RACH_LOAD, 6);
+ /* content of the IE */
+ msgb_put_u16(msg, total);
+ msgb_put_u16(msg, busy);
+ msgb_put_u16(msg, access);
+
+ msg->trx = bts->c0;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.4 DELETE INDICATION */
+int rsl_tx_delete_ind(struct gsm_bts *bts, const uint8_t *ia, uint8_t ia_len)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+ rsl_cch_push_hdr(msg, RSL_MT_DELETE_IND, RSL_CHAN_PCH_AGCH);
+ msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, ia_len, ia);
+ msg->trx = bts->c0;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.5 PAGING COMMAND */
+static int rsl_rx_paging_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ struct gsm_bts *bts = trx->bts;
+ uint8_t chan_needed = 0, paging_group;
+ const uint8_t *identity_lv;
+ int rc;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_PAGING_GROUP) ||
+ !TLVP_PRESENT(&tp, RSL_IE_MS_IDENTITY))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ paging_group = *TLVP_VAL(&tp, RSL_IE_PAGING_GROUP);
+ identity_lv = TLVP_VAL(&tp, RSL_IE_MS_IDENTITY)-1;
+
+ if (TLVP_PRES_LEN(&tp, RSL_IE_CHAN_NEEDED, 1))
+ chan_needed = *TLVP_VAL(&tp, RSL_IE_CHAN_NEEDED);
+
+ rc = paging_add_identity(bts->paging_state, paging_group, identity_lv, chan_needed);
+ if (rc < 0) {
+ /* FIXME: notfiy the BSC on other errors? */
+ if (rc == -ENOSPC)
+ oml_fail_rep(OSMO_EVT_MIN_PAG_TAB_FULL,
+ "BTS paging table is full");
+ }
+
+ pcu_tx_pag_req(identity_lv, chan_needed);
+
+ return 0;
+}
+
+/* 8.5.8 SMS BROADCAST COMMAND */
+static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ struct rsl_ie_cb_cmd_type *cb_cmd_type;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_CB_CMD_TYPE) ||
+ !TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ cb_cmd_type = (struct rsl_ie_cb_cmd_type *)
+ TLVP_VAL(&tp, RSL_IE_CB_CMD_TYPE);
+
+ return bts_process_smscb_cmd(trx->bts, *cb_cmd_type,
+ TLVP_LEN(&tp, RSL_IE_SMSCB_MSG),
+ TLVP_VAL(&tp, RSL_IE_SMSCB_MSG));
+}
+
+/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given output buffer.
+ * \param[out] buf Output buffer, must be caller-allocated and hold at least len + 2 or sizeof(sysinfo_buf_t) bytes
+ * \param[out] valid pointer to bit-mask of 'valid' System information types
+ * \param[in] current input data (L3 without L2/L1 header)
+ * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*)
+ * \param[in] len length of \a current in octets */
+static inline void lapdm_ui_prefix(uint8_t *buf, uint32_t *valid, const uint8_t *current, uint8_t osmo_si, uint16_t len)
+{
+ /* We have to pre-fix with the two-byte LAPDM UI header */
+ if (len > sizeof(sysinfo_buf_t) - 2) {
+ LOGP(DRSL, LOGL_ERROR, "Truncating received SI%s (%u -> %zu) to prepend LAPDM UI header (2 bytes)\n",
+ get_value_string(osmo_sitype_strs, osmo_si), len, sizeof(sysinfo_buf_t) - 2);
+ len = sizeof(sysinfo_buf_t) - 2;
+ }
+
+ (*valid) |= (1 << osmo_si);
+ buf[0] = 0x03; /* C/R + EA */
+ buf[1] = 0x03; /* UI frame */
+
+ memset(buf + 2, GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t) - 2);
+ memcpy(buf + 2, current, len);
+}
+
+/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given BTS SACCH buffer
+ * \param[out] bts BTS in whose System Information State we shall store
+ * \param[in] current input data (L3 without L2/L1 header)
+ * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*)
+ * \param[in] len length of \a current in octets */
+static inline void lapdm_ui_prefix_bts(struct gsm_bts *bts, const uint8_t *current, uint8_t osmo_si, uint16_t len)
+{
+ lapdm_ui_prefix(GSM_BTS_SI(bts, osmo_si), &bts->si_valid, current, osmo_si, len);
+}
+
+/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given lchan SACCH buffer
+ * \param[out] lchan Logical Channel in whose System Information State we shall store
+ * \param[in] current input data (L3 without L2/L1 header)
+ * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*)
+ * \param[in] len length of \a current in octets */
+static inline void lapdm_ui_prefix_lchan(struct gsm_lchan *lchan, const uint8_t *current, uint8_t osmo_si, uint16_t len)
+{
+ lapdm_ui_prefix(GSM_LCHAN_SI(lchan, osmo_si), &lchan->si.valid, current, osmo_si, len);
+}
+
+/* 8.6.2 SACCH FILLING */
+static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct tlv_parsed tp;
+ uint8_t rsl_si;
+ enum osmo_sysinfo_type osmo_si;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.30 System Info Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE);
+ if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes))
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+ if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+ struct gsm_bts_trx *t;
+
+ lapdm_ui_prefix_bts(bts, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len);
+
+ /* Propagate SI change to all lchans which adhere to BTS-global default. */
+ llist_for_each_entry(t, &bts->trx_list, list) {
+ int i, j;
+ for (i = 0; i < ARRAY_SIZE(t->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &t->ts[i];
+ for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+ if (lchan->state == LCHAN_S_NONE || (lchan->si.overridden & (1 << osmo_si)))
+ continue;
+ lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len);
+ }
+ }
+ }
+
+ LOGP(DRSL, LOGL_INFO, " Rx RSL SACCH FILLING (SI%s, %u bytes)\n",
+ get_value_string(osmo_sitype_strs, osmo_si), len);
+ } else {
+ struct gsm_bts_trx *t;
+
+ bts->si_valid &= ~(1 << osmo_si);
+
+ /* Propagate SI change to all lchans which adhere to BTS-global default. */
+ llist_for_each_entry(t, &bts->trx_list, list) {
+ int i, j;
+ for (i = 0; i < ARRAY_SIZE(t->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &t->ts[i];
+ for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+ if (lchan->state == LCHAN_S_NONE || (lchan->si.overridden & (1 << osmo_si)))
+ continue;
+ lchan->si.valid &= ~(1 << osmo_si);
+ }
+ }
+ }
+ LOGP(DRSL, LOGL_INFO, " Rx RSL Disabling SACCH FILLING (SI%s)\n",
+ get_value_string(osmo_sitype_strs, osmo_si));
+ }
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+
+ return 0;
+
+}
+
+/* 8.5.6 IMMEDIATE ASSIGN COMMAND is received */
+static int rsl_rx_imm_ass(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct tlv_parsed tp;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_FULL_IMM_ASS_INFO))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_RCVD);
+
+ /* cut down msg to the 04.08 RR part */
+ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO);
+ msg->data = msg->l3h;
+ msg->l2h = NULL;
+ msg->len = TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO);
+
+ /* put into the AGCH queue of the BTS */
+ if (bts_agch_enqueue(trx->bts, msg) < 0) {
+ /* if there is no space in the queue: send DELETE IND */
+ rsl_tx_delete_ind(trx->bts, TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO),
+ TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO));
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_DELETED);
+ msgb_free(msg);
+ }
+
+ /* return 1 means: don't msgb_free() the msg */
+ return 1;
+}
+
+/*
+ * dedicated channel related messages
+ */
+
+/* Send an RF CHANnel RELease ACKnowledge with the given chan_nr. This chan_nr may mismatch the current
+ * lchan state, if we received a CHANnel RELease for an already released channel, and we're just acking
+ * what we got without taking any action. */
+static int tx_rf_rel_ack(struct gsm_lchan *lchan, uint8_t chan_nr)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ rsl_dch_push_hdr(msg, RSL_MT_RF_CHAN_REL_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.4.19 sending RF CHANnel RELease ACKnowledge */
+int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan)
+{
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ bool send_rel_ack;
+
+ switch (lchan->rel_act_kind) {
+ case LCHAN_REL_ACT_RSL:
+ send_rel_ack = true;
+ break;
+
+ case LCHAN_REL_ACT_PCU:
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (lchan->ts->dyn.pchan_is != GSM_PCHAN_PDCH) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s (ss=%d) PDCH release: not in PDCH mode\n",
+ gsm_ts_and_pchan_name(lchan->ts), lchan->nr);
+ /* well, what to do about it ... carry on and hope it's fine. */
+ }
+ /* remember the fact that the TS is now released */
+ lchan->ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ /* Continue to ack the release below. (This is a non-standard rel ack invented
+ * specifically for GSM_PCHAN_TCH_F_TCH_H_PDCH). */
+ send_rel_ack = true;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* GSM_PCHAN_TCH_F_PDCH, does not require a rel ack. The caller
+ * l1sap_info_rel_cnf() will continue with bts_model_ts_disconnect(). */
+ send_rel_ack = false;
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR, "%s PCU rel ack for unexpected lchan kind\n",
+ gsm_lchan_name(lchan));
+ /* Release certainly was not requested by the BSC via RSL, so don't ack. */
+ send_rel_ack = false;
+ break;
+ }
+ break;
+
+ default:
+ /* A rel that was not requested by the BSC via RSL, hence not sending a rel ack to the
+ * BSC. */
+ send_rel_ack = false;
+ break;
+ }
+
+ if (!send_rel_ack) {
+ LOGP(DRSL, LOGL_NOTICE, "%s not sending REL ACK\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN REL ACK\n",
+ gsm_ts_and_pchan_name(lchan->ts), lchan->nr,
+ gsm_lchant_name(lchan->type));
+
+ /*
+ * Free the LAPDm resources now that the BTS
+ * has released all the resources.
+ */
+ lapdm_channel_exit(&lchan->lapdm_ch);
+
+ return tx_rf_rel_ack(lchan, chan_nr);
+}
+
+/* 8.4.2 sending CHANnel ACTIVation ACKnowledge */
+static int rsl_tx_chan_act_ack(struct gsm_lchan *lchan)
+{
+ struct gsm_time *gtime = get_time(lchan->ts->trx->bts);
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t ie[2];
+
+ LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN ACT ACK\n",
+ gsm_ts_and_pchan_name(lchan->ts), lchan->nr,
+ gsm_lchant_name(lchan->type));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ gsm48_gen_starting_time(ie, gtime);
+ msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie);
+ rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ /* since activation was successful, do some lchan initialization */
+ lchan_meas_reset(lchan);
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.4.7 sending HANDOver DETection */
+int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_INFO, "Sending HANDOver DETect\n");
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ /* 9.3.17 Access Delay */
+ if (ho_delay)
+ msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, *ho_delay);
+
+ rsl_dch_push_hdr(msg, RSL_MT_HANDO_DET, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.4.3 sending CHANnel ACTIVation Negative ACK */
+static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+
+ if (lchan)
+ LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan));
+ else
+ LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr);
+ LOGPC(DRSL, LOGL_NOTICE, "Sending Channel Activated NACK: cause = 0x%02x\n", cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_NACK, chan_nr);
+ msg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+static int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause) {
+ return _rsl_tx_chan_act_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan);
+}
+
+/* Send an RSL Channel Activation Ack if cause is zero, a Nack otherwise. */
+int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause)
+{
+ if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) {
+ LOGP(DRSL, LOGL_NOTICE, "%s not sending CHAN ACT %s\n",
+ gsm_lchan_name(lchan), cause ? "NACK" : "ACK");
+ return 0;
+ }
+
+ if (cause)
+ return rsl_tx_chan_act_nack(lchan, cause);
+ return rsl_tx_chan_act_ack(lchan);
+}
+
+/* 8.4.4 sending CONNection FAILure */
+int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s Sending Connection Failure: cause = 0x%02x\n",
+ gsm_lchan_name(lchan), cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg, RSL_MT_CONN_FAIL, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.3 sending CHANnel ReQuireD */
+int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime,
+ uint8_t ra, uint8_t acc_delay)
+{
+ struct msgb *nmsg;
+ uint8_t payload[3];
+
+ LOGP(DRSL, LOGL_NOTICE, "Sending Channel Required\n");
+
+ nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!nmsg)
+ return -ENOMEM;
+
+ /* 9.3.19 Request Reference */
+ payload[0] = ra;
+ gsm48_gen_starting_time(payload+1, gtime);
+ msgb_tv_fixed_put(nmsg, RSL_IE_REQ_REFERENCE, 3, payload);
+
+ /* 9.3.17 Access Delay */
+ msgb_tv_put(nmsg, RSL_IE_ACCESS_DELAY, acc_delay);
+
+ rsl_cch_push_hdr(nmsg, RSL_MT_CHAN_RQD, 0x88); // FIXME
+ nmsg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/* copy the SACCH related sysinfo from BTS global buffer to lchan specific buffer */
+static void copy_sacch_si_to_lchan(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rsl_sacch_sitypes); i++) {
+ uint8_t rsl_si = rsl_sacch_sitypes[i];
+ int osmo_si = osmo_rsl2sitype(rsl_si);
+ uint32_t osmo_si_shifted = (1 << osmo_si);
+ osmo_static_assert(_MAX_SYSINFO_TYPE <= sizeof(osmo_si_shifted) * 8,
+ si_enum_vals_fit_in_bit_mask);
+
+ if (osmo_si == SYSINFO_TYPE_NONE)
+ continue;
+ if (!(bts->si_valid & osmo_si_shifted)) {
+ lchan->si.valid &= ~osmo_si_shifted;
+ continue;
+ }
+ lchan->si.valid |= osmo_si_shifted;
+ memcpy(GSM_LCHAN_SI(lchan, osmo_si), GSM_BTS_SI(bts, osmo_si), sizeof(sysinfo_buf_t));
+ }
+}
+
+
+static int encr_info2lchan(struct gsm_lchan *lchan,
+ const uint8_t *val, uint8_t len)
+{
+ int rc;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ const char *ciph_name = get_value_string(gsm0808_chosen_enc_alg_names, *val);
+
+ /* check if the encryption algorithm sent by BSC is supported! */
+ rc = bts_supports_cipher(bts, *val);
+ if (rc != 1) {
+ LOGP(DRSL, LOGL_ERROR, "%s: BTS doesn't support cipher %s\n",
+ gsm_lchan_name(lchan), ciph_name);
+ return -EINVAL;
+ }
+
+ /* length can be '1' in case of no ciphering */
+ if (len < 1) {
+ LOGP(DRSL, LOGL_ERROR, "%s: Encryption Info cannot have len=%d\n",
+ gsm_lchan_name(lchan), len);
+ return -EINVAL;
+ }
+
+ lchan->encr.alg_id = *val++;
+ lchan->encr.key_len = len -1;
+ if (lchan->encr.key_len > sizeof(lchan->encr.key))
+ lchan->encr.key_len = sizeof(lchan->encr.key);
+ memcpy(lchan->encr.key, val, lchan->encr.key_len);
+ DEBUGP(DRSL, "%s: Setting lchan cipher algorithm %s\n",
+ gsm_lchan_name(lchan), ciph_name);
+
+ return 0;
+}
+
+/* Make sure no state from TCH use remains. */
+static void clear_lchan_for_pdch_activ(struct gsm_lchan *lchan)
+{
+ /* These values don't apply to PDCH, just clear them. Particularly the encryption must be
+ * cleared, or we would enable encryption on PDCH with parameters remaining from the TCH. */
+ lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0);
+ lchan->ms_power_ctrl.current = lchan->ms_power;
+ lchan->ms_power_ctrl.fixed = 0;
+ lchan->rsl_cmode = 0;
+ lchan->tch_mode = 0;
+ memset(&lchan->encr, 0, sizeof(lchan->encr));
+ memset(&lchan->ho, 0, sizeof(lchan->ho));
+ lchan->bs_power = 0;
+ lchan->ms_power = 0;
+ memset(&lchan->ms_power_ctrl, 0, sizeof(lchan->ms_power_ctrl));
+ lchan->rqd_ta = 0;
+ copy_sacch_si_to_lchan(lchan);
+ memset(&lchan->tch, 0, sizeof(lchan->tch));
+}
+
+/*!
+ * Store the CHAN_ACTIV msg, connect the L1 timeslot in the proper type and
+ * then invoke rsl_rx_chan_activ() with msg.
+ */
+static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg)
+{
+ DEBUGP(DRSL, "%s dyn_ts_l1_reconnect\n", gsm_ts_and_pchan_name(ts));
+
+ switch (ts->dyn.pchan_want) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ break;
+ case GSM_PCHAN_PDCH:
+ /* Only the first lchan matters for PDCH */
+ clear_lchan_for_pdch_activ(ts->lchan);
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Cannot reconnect as pchan %s\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ return -EINVAL;
+ }
+
+ /* We will feed this back to rsl_rx_chan_activ() later */
+ ts->dyn.pending_chan_activ = msg;
+
+ /* Disconnect, continue connecting from cb_ts_disconnected(). */
+ DEBUGP(DRSL, "%s Disconnect\n", gsm_ts_and_pchan_name(ts));
+ return bts_model_ts_disconnect(ts);
+}
+
+static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr)
+{
+ uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK;
+ switch (cbits) {
+ case RSL_CHAN_Bm_ACCHs:
+ return GSM_PCHAN_TCH_F;
+ case RSL_CHAN_Lm_ACCHs:
+ case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1):
+ return GSM_PCHAN_TCH_H;
+ case RSL_CHAN_OSMO_PDCH:
+ return GSM_PCHAN_PDCH;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "chan nr 0x%x not covered by dyn_pchan_from_chan_nr()\n",
+ chan_nr);
+ return GSM_PCHAN_UNKNOWN;
+ }
+}
+
+/* 8.4.1 CHANnel ACTIVation is received */
+static int rsl_rx_chan_activ(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+ struct rsl_ie_chan_mode *cm;
+ struct tlv_parsed tp;
+ uint8_t type;
+ int rc;
+
+ if (lchan->state != LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s: error: lchan is not available, but in state: %s.\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state));
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL);
+ }
+
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ ts->dyn.pchan_want = dyn_pchan_from_chan_nr(dch->chan_nr);
+ DEBUGP(DRSL, "%s rx chan activ\n", gsm_ts_and_pchan_name(ts));
+
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ /*
+ * The phy has the timeslot connected in a different
+ * mode than this activation needs it to be.
+ * Re-connect, then come back to rsl_rx_chan_activ().
+ */
+ rc = dyn_ts_l1_reconnect(ts, msg);
+ if (rc)
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_NORMAL_UNSPEC);
+ /* indicate that the msgb should not be freed. */
+ return 1;
+ }
+ }
+
+ LOGP(DRSL, LOGL_DEBUG, "%s: rx Channel Activation in state: %s.\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state));
+
+ /* Initialize channel defaults */
+ lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0);
+ lchan->ms_power_ctrl.current = lchan->ms_power;
+ lchan->ms_power_ctrl.fixed = 0;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.3 Activation Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) {
+ LOGP(DRSL, LOGL_NOTICE, "missing Activation Type\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR);
+ }
+ type = *TLVP_VAL(&tp, RSL_IE_ACT_TYPE);
+
+ /* 9.3.6 Channel Mode */
+ if (type != RSL_ACT_OSMO_PDCH) {
+ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
+ LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR);
+ }
+ cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE);
+ lchan_tchmode_from_cmode(lchan, cm);
+ }
+
+ /* 9.3.7 Encryption Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO);
+
+ if (encr_info2lchan(lchan, val, len) < 0) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_ENCR_UNIMPL);
+ }
+ } else
+ memset(&lchan->encr, 0, sizeof(lchan->encr));
+
+ /* 9.3.9 Handover Reference */
+ if ((type == RSL_ACT_INTER_ASYNC ||
+ type == RSL_ACT_INTER_SYNC) &&
+ TLVP_PRES_LEN(&tp, RSL_IE_HANDO_REF, 1)) {
+ lchan->ho.active = HANDOVER_ENABLED;
+ lchan->ho.ref = *TLVP_VAL(&tp, RSL_IE_HANDO_REF);
+ }
+
+ /* 9.3.4 BS Power */
+ if (TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1))
+ lchan->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+ /* 9.3.13 MS Power */
+ if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) {
+ lchan->ms_power = *TLVP_VAL(&tp, RSL_IE_MS_POWER);
+ lchan->ms_power_ctrl.current = lchan->ms_power;
+ lchan->ms_power_ctrl.fixed = 0;
+ }
+ /* 9.3.24 Timing Advance */
+ if (TLVP_PRES_LEN(&tp, RSL_IE_TIMING_ADVANCE, 1))
+ lchan->rqd_ta = *TLVP_VAL(&tp, RSL_IE_TIMING_ADVANCE);
+
+ /* 9.3.32 BS Power Parameters */
+ /* 9.3.31 MS Power Parameters */
+ /* 9.3.16 Physical Context */
+
+ /* 9.3.29 SACCH Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_SACCH_INFO)) {
+ uint8_t tot_len = TLVP_LEN(&tp, RSL_IE_SACCH_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_SACCH_INFO);
+ const uint8_t *cur = val;
+ uint8_t num_msgs = *cur++;
+ unsigned int i;
+ for (i = 0; i < num_msgs; i++) {
+ uint8_t rsl_si = *cur++;
+ uint8_t si_len = *cur++;
+ uint8_t osmo_si;
+
+ if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT,
+ &dch->chan_nr, NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT);
+ }
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si);
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr,
+ NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT);
+ }
+
+ lapdm_ui_prefix_lchan(lchan, cur, osmo_si, si_len);
+
+ cur += si_len;
+ if (cur >= val + tot_len) {
+ LOGP(DRSL, LOGL_ERROR, "Error parsing SACCH INFO IE\n");
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr,
+ NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT);
+ }
+ }
+ } else {
+ /* use standard SACCH filling of the BTS */
+ copy_sacch_si_to_lchan(lchan);
+ }
+ /* 9.3.52 MultiRate Configuration */
+ if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) {
+ if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) {
+ LOGP(DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n");
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT);
+ }
+ memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1,
+ TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1);
+ amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG),
+ TLVP_LEN(&tp, RSL_IE_MR_CONFIG));
+ amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan),
+ &lchan->tch.amr_mr);
+ lchan->tch.last_cmr = AMR_CMR_NONE;
+ }
+ /* 9.3.53 MultiRate Control */
+ /* 9.3.54 Supported Codec Types */
+
+ LOGP(DRSL, LOGL_INFO, "%s: chan_nr=%s type=0x%02x mode=%s\n",
+ gsm_lchan_name(lchan), rsl_chan_nr_str(dch->chan_nr), type,
+ gsm48_chan_mode_name(lchan->tch_mode));
+
+ /* Connecting PDCH on dyn TS goes via PCU instead. */
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && ts->dyn.pchan_want == GSM_PCHAN_PDCH) {
+ /*
+ * We ack the activation to the BSC right away, regardless of
+ * the PCU succeeding or not; if a dynamic timeslot fails to go
+ * to PDCH mode for any reason, the BSC should still be able to
+ * switch it back to TCH modes and should not put the time slot
+ * in an error state. So for operating dynamic TS, the BSC
+ * would not take any action if the PDCH mode failed, e.g.
+ * because the PCU is not yet running. Even if alerting the
+ * core network of broken GPRS service is desired, this only
+ * makes sense when the PCU has not shown up for some time.
+ * It's easiest to not forward activation delays to the BSC: if
+ * the BSC tells us to do PDCH, we do our best, and keep the
+ * details on the BTS and PCU level. This is kind of analogous
+ * to how plain PDCH TS operate. Directly call
+ * rsl_tx_chan_act_ack() instead of rsl_tx_chan_act_acknack()
+ * because we don't want/need to decide whether to drop due to
+ * lchan->rel_act_kind.
+ */
+ rc = rsl_tx_chan_act_ack(lchan);
+ if (rc < 0)
+ LOGP(DRSL, LOGL_ERROR, "%s Cannot send act ack: %d\n",
+ gsm_ts_and_pchan_name(ts), rc);
+
+ /*
+ * pcu_tx_info_ind() will pick up the ts->dyn.pchan_want. If
+ * the PCU is not connected yet, ignore for now; the PCU will
+ * catch up (and send the RSL ack) once it connects.
+ */
+ if (pcu_connected()) {
+ DEBUGP(DRSL, "%s Activate via PCU\n", gsm_ts_and_pchan_name(ts));
+ rc = pcu_tx_info_ind();
+ }
+ else {
+ DEBUGP(DRSL, "%s Activate via PCU when PCU connects\n",
+ gsm_ts_and_pchan_name(ts));
+ rc = 0;
+ }
+ if (rc) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_NORMAL_UNSPEC, &dch->chan_nr, NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_NORMAL_UNSPEC);
+ }
+ return 0;
+ }
+
+ /* Remember to send an RSL ACK once the lchan is active */
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+
+ /* actually activate the channel in the BTS */
+ rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr, &tp);
+ if (rc < 0)
+ return rsl_tx_chan_act_acknack(lchan, -rc);
+
+ return 0;
+}
+
+static int dyn_ts_pdch_release(struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ LOGP(DRSL, LOGL_ERROR, "%s: PDCH release requested but already"
+ " in switchover\n", gsm_ts_and_pchan_name(ts));
+ return -EINVAL;
+ }
+
+ /*
+ * Indicate PDCH Disconnect in dyn_pdch.want, let pcu_tx_info_ind()
+ * pick it up and wait for PCU to disable the channel.
+ */
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+
+ if (!pcu_connected()) {
+ /* PCU not connected yet. Just record the new type and done,
+ * the PCU will pick it up once connected. */
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ return 1;
+ }
+
+ return pcu_tx_info_ind();
+}
+
+/* 8.4.14 RF CHANnel RELease is received */
+static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr)
+{
+ int rc;
+
+ if (lchan->state == LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s ss=%d state=%s Rx RSL RF Channel Release, but is already inactive;"
+ " just ACKing the release\n",
+ gsm_ts_and_pchan_name(lchan->ts), lchan->nr,
+ gsm_lchans_name(lchan->state));
+ /* Just ack the release and ignore. Make sure to reflect the same chan_nr we received,
+ * not necessarily reflecting the current lchan state. */
+ return tx_rf_rel_ack(lchan, chan_nr);
+ }
+
+ if (lchan->abis_ip.rtp_socket) {
+ rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC);
+ osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO,
+ "Closing RTP socket on Channel Release ");
+ osmo_rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ msgb_queue_flush(&lchan->dl_tch_queue);
+ }
+
+ /* release handover state */
+ handover_reset(lchan);
+
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+
+ /* Dynamic channel in PDCH mode is released via PCU */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH) {
+ rc = dyn_ts_pdch_release(lchan);
+ if (rc == 1) {
+ /* If the PCU is not connected, continue to rel ack right away. */
+ lchan->rel_act_kind = LCHAN_REL_ACT_PCU;
+ return rsl_tx_rf_rel_ack(lchan);
+ }
+ /* Waiting for PDCH release */
+ return rc;
+ }
+
+ l1sap_chan_rel(lchan->ts->trx, chan_nr);
+
+ lapdm_channel_exit(&lchan->lapdm_ch);
+
+ return 0;
+}
+
+#ifdef FAKE_CIPH_MODE_COMPL
+/* ugly hack to send a fake CIPH MODE COMPLETE back to the BSC */
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48.h>
+static int tx_ciph_mod_compl_hack(struct gsm_lchan *lchan, uint8_t link_id,
+ const char *imeisv)
+{
+ struct msgb *fake_msg;
+ struct gsm48_hdr *g48h;
+ uint8_t mid_buf[11];
+ int rc;
+
+ fake_msg = rsl_msgb_alloc(128);
+ if (!fake_msg)
+ return -ENOMEM;
+
+ /* generate 04.08 RR message */
+ g48h = (struct gsm48_hdr *) msgb_put(fake_msg, sizeof(*g48h));
+ g48h->proto_discr = GSM48_PDISC_RR;
+ g48h->msg_type = GSM48_MT_RR_CIPH_M_COMPL;
+
+ /* add IMEISV, if requested */
+ if (imeisv) {
+ rc = gsm48_generate_mid_from_imsi(mid_buf, imeisv);
+ if (rc > 0) {
+ mid_buf[2] = (mid_buf[2] & 0xf8) | GSM_MI_TYPE_IMEISV;
+ memcpy(msgb_put(fake_msg, rc), mid_buf, rc);
+ }
+ }
+
+ rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr(lchan),
+ link_id, 1);
+
+ fake_msg->lchan = lchan;
+ fake_msg->trx = lchan->ts->trx;
+
+ /* send it back to the BTS */
+ return abis_bts_rsl_sendmsg(fake_msg);
+}
+
+struct ciph_mod_compl {
+ struct osmo_timer_list timer;
+ struct gsm_lchan *lchan;
+ int send_imeisv;
+ uint8_t link_id;
+};
+
+static void cmc_timer_cb(void *data)
+{
+ struct ciph_mod_compl *cmc = data;
+ const char *imeisv = NULL;
+
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s Sending FAKE CIPHERING MODE COMPLETE to BSC (Alg %u)\n",
+ gsm_lchan_name(cmc->lchan), cmc->lchan->encr.alg_id);
+
+ if (cmc->send_imeisv)
+ imeisv = "0123456789012345";
+
+ /* We have no clue whatsoever that this lchan still exists! */
+ tx_ciph_mod_compl_hack(cmc->lchan, cmc->link_id, imeisv);
+
+ talloc_free(cmc);
+}
+#endif
+
+
+/* 8.4.6 ENCRYPTION COMMAND */
+static int rsl_rx_encr_cmd(struct msgb *msg)
+{
+ struct gsm_lchan *lchan = msg->lchan;
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ struct tlv_parsed tp;
+ uint8_t link_id;
+
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ }
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO) ||
+ !TLVP_PRESENT(&tp, RSL_IE_L3_INFO) ||
+ !TLVP_PRESENT(&tp, RSL_IE_LINK_IDENT)) {
+ return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
+ }
+
+ /* 9.3.7 Encryption Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO);
+
+ if (encr_info2lchan(lchan, val, len) < 0) {
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr,
+ NULL, msg);
+ }
+ }
+
+ /* 9.3.2 Link Identifier */
+ link_id = *TLVP_VAL(&tp, RSL_IE_LINK_IDENT);
+
+ /* we have to set msg->l3h as rsl_rll_push_l3 will use it to
+ * determine the length field of the L3_INFO IE */
+ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+
+ /* pop the RSL dchan header, but keep L3 TLV */
+ msgb_pull(msg, msg->l3h - msg->data);
+
+ /* push a fake RLL DATA REQ header */
+ rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, dch->chan_nr, link_id, 1);
+
+
+#ifdef FAKE_CIPH_MODE_COMPL
+ if (lchan->encr.alg_id != RSL_ENC_ALG_A5(0)) {
+ struct ciph_mod_compl *cmc;
+ struct gsm48_hdr *g48h = (struct gsm48_hdr *) msg->l3h;
+
+ cmc = talloc_zero(NULL, struct ciph_mod_compl);
+ if (g48h->data[0] & 0x10)
+ cmc->send_imeisv = 1;
+ cmc->lchan = lchan;
+ cmc->link_id = link_id;
+ cmc->timer.cb = cmc_timer_cb;
+ cmc->timer.data = cmc;
+ osmo_timer_schedule(&cmc->timer, 1, 0);
+
+ /* FIXME: send fake CM SERVICE ACCEPT to MS */
+
+ return 0;
+ } else
+#endif
+ {
+ LOGP(DRSL, LOGL_INFO, "%s Fwd RSL ENCR CMD (Alg %u) to LAPDm\n",
+ gsm_lchan_name(lchan), lchan->encr.alg_id);
+ /* hand it into RSLms for transmission of L3_INFO to the MS */
+ lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+ /* return 1 to make sure the msgb is not free'd */
+ return 1;
+ }
+}
+
+/* 8.4.11 MODE MODIFY NEGATIVE ACKNOWLEDGE */
+static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+
+ if (lchan)
+ LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan));
+ else
+ LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr);
+ LOGPC(DRSL, LOGL_NOTICE, "Tx MODE MODIFY NACK (cause = 0x%02x)\n", cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ msg->len = 0;
+ msg->data = msg->tail = msg->l3h;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_NACK, chan_nr);
+ msg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause)
+{
+ return _rsl_tx_mode_modif_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan);
+}
+
+
+/* 8.4.10 MODE MODIFY ACK */
+static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_INFO, "%s Tx MODE MODIF ACK\n", gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.4.9 MODE MODIFY */
+static int rsl_rx_mode_modif(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ struct gsm_lchan *lchan = msg->lchan;
+ struct rsl_ie_chan_mode *cm;
+ struct tlv_parsed tp;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.6 Channel Mode */
+ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
+ LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n");
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_MAND_IE_ERROR);
+ }
+ cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE);
+ lchan_tchmode_from_cmode(lchan, cm);
+
+ if (bts_supports_cm(lchan->ts->trx->bts, ts_pchan(lchan->ts), lchan->tch_mode) != 1) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s %s: invalid mode: %s (wrong BSC configuration?)\n",
+ gsm_ts_and_pchan_name(lchan->ts), gsm_lchan_name(lchan),
+ gsm48_chan_mode_name(lchan->tch_mode));
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_SERV_OPT_UNAVAIL);
+ }
+
+ /* 9.3.7 Encryption Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO);
+
+ if (encr_info2lchan(lchan, val, len) < 0) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_ENCR_UNIMPL);
+ }
+ }
+
+ /* 9.3.45 Main channel reference */
+
+ /* 9.3.52 MultiRate Configuration */
+ if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) {
+ if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) {
+ LOGP(DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n");
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_IE_CONTENT);;
+ }
+ memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1,
+ TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1);
+ amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG),
+ TLVP_LEN(&tp, RSL_IE_MR_CONFIG));
+ amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan),
+ &lchan->tch.amr_mr);
+ lchan->tch.last_cmr = AMR_CMR_NONE;
+ }
+ /* 9.3.53 MultiRate Control */
+ /* 9.3.54 Supported Codec Types */
+
+ l1sap_chan_modify(lchan->ts->trx, dch->chan_nr);
+
+ /* FIXME: delay this until L1 says OK? */
+ rsl_tx_mode_modif_ack(lchan);
+
+ return 0;
+}
+
+/* 8.4.15 MS POWER CONTROL */
+static int rsl_rx_ms_pwr_ctrl(struct msgb *msg)
+{
+ struct gsm_lchan *lchan = msg->lchan;
+ struct tlv_parsed tp;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) {
+ uint8_t pwr = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F;
+ lchan->ms_power_ctrl.fixed = 1;
+ lchan->ms_power_ctrl.current = pwr;
+
+ LOGP(DRSL, LOGL_NOTICE, "%s forcing power to %d\n",
+ gsm_lchan_name(lchan), lchan->ms_power_ctrl.current);
+ bts_model_adjst_ms_pwr(lchan);
+ }
+
+ return 0;
+}
+
+/* 8.4.20 SACCH INFO MODify */
+static int rsl_rx_sacch_inf_mod(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct tlv_parsed tp;
+ uint8_t rsl_si, osmo_si;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (TLVP_PRESENT(&tp, RSL_IE_STARTNG_TIME)) {
+ LOGP(DRSL, LOGL_NOTICE, "Starting time not supported\n");
+ return rsl_tx_error_report(msg->trx, RSL_ERR_SERV_OPT_UNIMPL, &dch->chan_nr, NULL, msg);
+ }
+
+ /* 9.3.30 System Info Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
+ return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
+
+ rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE);
+ if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes))
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, "%s Rx SACCH SI 0x%02x not supported.\n",
+ gsm_lchan_name(lchan), rsl_si);
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ }
+ if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+
+ lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len);
+ if (memcmp(GSM_BTS_SI(bts, osmo_si), TLVP_VAL(&tp, RSL_IE_L3_INFO), sizeof(sysinfo_buf_t) != 0))
+ lchan->si.overridden |= (1 << osmo_si);
+ else
+ lchan->si.overridden &= ~(1 << osmo_si);
+
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL SACCH FILLING (SI%s)\n",
+ gsm_lchan_name(lchan),
+ get_value_string(osmo_sitype_strs, osmo_si));
+ } else {
+ lchan->si.valid &= ~(1 << osmo_si);
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL Disabling SACCH FILLING (SI%s)\n",
+ gsm_lchan_name(lchan),
+ get_value_string(osmo_sitype_strs, osmo_si));
+ }
+
+ return 0;
+}
+
+/*
+ * ip.access related messages
+ */
+static void rsl_add_rtp_stats(struct gsm_lchan *lchan, struct msgb *msg)
+{
+ struct ipa_stats {
+ uint32_t packets_sent;
+ uint32_t octets_sent;
+ uint32_t packets_recv;
+ uint32_t octets_recv;
+ uint32_t packets_lost;
+ uint32_t arrival_jitter;
+ uint32_t avg_tx_delay;
+ } __attribute__((packed));
+
+ struct ipa_stats stats;
+
+ memset(&stats, 0, sizeof(stats));
+
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_socket_stats(lchan->abis_ip.rtp_socket,
+ &stats.packets_sent, &stats.octets_sent,
+ &stats.packets_recv, &stats.octets_recv,
+ &stats.packets_lost, &stats.arrival_jitter);
+ /* convert to network byte order */
+ stats.packets_sent = htonl(stats.packets_sent);
+ stats.octets_sent = htonl(stats.octets_sent);
+ stats.packets_recv = htonl(stats.packets_recv);
+ stats.octets_recv = htonl(stats.octets_recv);
+ stats.packets_lost = htonl(stats.packets_lost);
+
+ msgb_tlv_put(msg, RSL_IE_IPAC_CONN_STAT, sizeof(stats), (uint8_t *) &stats);
+}
+
+int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause)
+{
+ struct msgb *nmsg;
+
+ LOGP(DRSL, LOGL_NOTICE, "%s Sending RTP delete indication: cause = %s\n",
+ gsm_lchan_name(lchan), rsl_err_name(cause));
+
+ nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!nmsg)
+ return -ENOMEM;
+
+ msgb_tv16_put(nmsg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id));
+ rsl_add_rtp_stats(lchan, nmsg);
+ msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause);
+ rsl_ipa_push_hdr(nmsg, RSL_MT_IPAC_DLCX_IND, gsm_lchan2chan_nr(lchan));
+
+ nmsg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/* transmit an CRCX ACK for the lchan */
+static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2,
+ uint8_t orig_msgt)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ const char *name;
+ struct in_addr ia;
+
+ if (orig_msgt == RSL_MT_IPAC_CRCX)
+ name = "CRCX";
+ else
+ name = "MDCX";
+
+ ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+ LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_%s_ACK (local %s:%u, ",
+ gsm_lchan_name(lchan), name,
+ inet_ntoa(ia), lchan->abis_ip.bound_port);
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ LOGPC(DRSL, LOGL_INFO, "remote %s:%u)\n",
+ inet_ntoa(ia), lchan->abis_ip.connect_port);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+
+ /* Connection ID */
+ msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id));
+
+ /* locally bound IP */
+ msgb_v_put(msg, RSL_IE_IPAC_LOCAL_IP);
+ msgb_put_u32(msg, lchan->abis_ip.bound_ip);
+
+ /* locally bound port */
+ msgb_tv16_put(msg, RSL_IE_IPAC_LOCAL_PORT,
+ lchan->abis_ip.bound_port);
+
+ if (inc_pt2) {
+ /* RTP Payload Type 2 */
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2,
+ lchan->abis_ip.rtp_payload2);
+ }
+
+ /* push the header in front */
+ rsl_ipa_push_hdr(msg, orig_msgt + 1, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+static int rsl_tx_ipac_dlcx_ack(struct gsm_lchan *lchan, int inc_conn_id)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_ACK\n",
+ gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (inc_conn_id) {
+ msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+ rsl_add_rtp_stats(lchan, msg);
+ }
+
+ rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+static int rsl_tx_ipac_dlcx_nack(struct gsm_lchan *lchan, int inc_conn_id,
+ uint8_t cause)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_NACK\n",
+ gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (inc_conn_id)
+ msgb_tv_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+
+ rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_NACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+
+}
+
+
+/* transmit an CRCX NACK for the lchan */
+static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause,
+ int inc_ipport, uint8_t orig_msgtype)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ /* FIXME: allocate new msgb and copy old over */
+ LOGP(DRSL, LOGL_NOTICE, "%s RSL Tx IPAC_BIND_NACK\n",
+ gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (inc_ipport) {
+ /* remote IP */
+ msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
+ msgb_put_u32(msg, lchan->abis_ip.connect_ip);
+
+ /* remote port */
+ msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT,
+ htons(lchan->abis_ip.connect_port));
+ }
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+
+ /* push the header in front */
+ rsl_ipa_push_hdr(msg, orig_msgtype + 2, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+static char *get_rsl_local_ip(struct gsm_bts_trx *trx)
+{
+ struct e1inp_ts *ts = trx->rsl_link->ts;
+ struct sockaddr_storage ss;
+ socklen_t sa_len = sizeof(ss);
+ static char hostbuf[256];
+ int rc;
+
+ rc = getsockname(ts->driver.ipaccess.fd.fd, (struct sockaddr *) &ss,
+ &sa_len);
+ if (rc < 0)
+ return NULL;
+
+ rc = getnameinfo((struct sockaddr *)&ss, sa_len,
+ hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NUMERICHOST);
+ if (rc < 0)
+ return NULL;
+
+ return hostbuf;
+}
+
+static int bind_rtp(struct gsm_bts *bts, struct osmo_rtp_socket *rs, const char *ip)
+{
+ int rc;
+ unsigned int i;
+ unsigned int tries;
+
+ tries = (bts->rtp_port_range_end - bts->rtp_port_range_start) / 2;
+ for (i = 0; i < tries; i++) {
+
+ if (bts->rtp_port_range_next >= bts->rtp_port_range_end)
+ bts->rtp_port_range_next = bts->rtp_port_range_start;
+
+ rc = osmo_rtp_socket_bind(rs, ip, bts->rtp_port_range_next);
+
+ bts->rtp_port_range_next += 2;
+
+ if (rc == 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+static int rsl_rx_ipac_XXcx(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ struct tlv_parsed tp;
+ struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ const uint8_t *payload_type, *speech_mode, *payload_type2;
+ uint32_t connect_ip = 0;
+ uint16_t connect_port = 0;
+ int rc, inc_ip_port = 0, port;
+ char *name;
+ struct in_addr ia;
+ struct in_addr addr;
+
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX)
+ name = "CRCX";
+ else
+ name = "MDCX";
+
+ /* check the kind of channel and reject */
+ if (lchan->type != GSM_LCHAN_TCH_F && lchan->type != GSM_LCHAN_TCH_H)
+ return tx_ipac_XXcx_nack(lchan, 0x52,
+ 0, dch->c.msg_type);
+
+ rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rc < 0)
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR,
+ 0, dch->c.msg_type);
+
+ LOGP(DRSL, LOGL_DEBUG, "%s IPAC_%s: ", gsm_lchan_name(lchan), name);
+ if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_IP, 4)) {
+ connect_ip = tlvp_val32_unal(&tp, RSL_IE_IPAC_REMOTE_IP);
+ addr.s_addr = connect_ip;
+ LOGPC(DRSL, LOGL_DEBUG, "connect_ip=%s ", inet_ntoa(addr));
+ }
+
+ if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_PORT, 2)) {
+ connect_port = tlvp_val16_unal(&tp, RSL_IE_IPAC_REMOTE_PORT);
+ LOGPC(DRSL, LOGL_DEBUG, "connect_port=%u ",
+ ntohs(connect_port));
+ }
+
+ speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE);
+ if (speech_mode)
+ LOGPC(DRSL, LOGL_DEBUG, "speech_mode=%u ", *speech_mode);
+
+ payload_type = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD);
+ if (payload_type)
+ LOGPC(DRSL, LOGL_DEBUG, "payload_type=%u ", *payload_type);
+
+ LOGPC(DRSL, LOGL_DEBUG, "\n");
+
+ payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2);
+
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX && connect_ip && connect_port)
+ inc_ip_port = 1;
+
+ if (payload_type && payload_type2) {
+ LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC %s, "
+ "RTP_PT and RTP_PT2 in same msg !?!\n",
+ gsm_lchan_name(lchan), name);
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR,
+ inc_ip_port, dch->c.msg_type);
+ }
+
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX) {
+ char cname[32];
+ char *ipstr = NULL;
+ if (lchan->abis_ip.rtp_socket) {
+ LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC CRCX, "
+ "but we already have socket!\n",
+ gsm_lchan_name(lchan));
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
+ /* FIXME: select default value depending on speech_mode */
+ //if (!payload_type)
+ lchan->tch.last_fn = LCHAN_FN_DUMMY;
+ lchan->abis_ip.rtp_socket = osmo_rtp_socket_create(lchan->ts->trx,
+ OSMO_RTP_F_POLL);
+ if (!lchan->abis_ip.rtp_socket) {
+ LOGP(DRTP, LOGL_ERROR,
+ "%s IPAC Failed to create RTP/RTCP sockets\n",
+ gsm_lchan_name(lchan));
+ oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT,
+ "%s IPAC Failed to create RTP/RTCP sockets",
+ gsm_lchan_name(lchan));
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
+ rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket,
+ bts->rtp_jitter_adaptive ?
+ OSMO_RTP_P_JIT_ADAP :
+ OSMO_RTP_P_JITBUF,
+ bts->rtp_jitter_buf_ms);
+ if (rc < 0)
+ LOGP(DRTP, LOGL_ERROR,
+ "%s IPAC Failed to set RTP socket parameters: %s\n",
+ gsm_lchan_name(lchan), strerror(-rc));
+ else
+ LOGP(DRTP, LOGL_INFO,
+ "%s IPAC set RTP socket parameters: %d\n",
+ gsm_lchan_name(lchan), rc);
+ lchan->abis_ip.rtp_socket->priv = lchan;
+ lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb;
+
+ if (connect_ip && connect_port) {
+ /* if CRCX specifies a remote IP, we can bind()
+ * here to 0.0.0.0 and wait for the connect()
+ * below, after which the kernel will have
+ * selected the local IP address. */
+ ipstr = "0.0.0.0";
+ } else {
+ /* if CRCX does not specify a remote IP, we will
+ * not do any connect() below, and thus the
+ * local socket will remain bound to 0.0.0.0 -
+ * which however we cannot legitimately report
+ * back to the BSC in the CRCX_ACK */
+ ipstr = get_rsl_local_ip(lchan->ts->trx);
+ }
+ rc = bind_rtp(bts, lchan->abis_ip.rtp_socket, ipstr);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "%s IPAC Failed to bind RTP/RTCP sockets\n",
+ gsm_lchan_name(lchan));
+ oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT,
+ "%s IPAC Failed to bind RTP/RTCP sockets",
+ gsm_lchan_name(lchan));
+ osmo_rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ msgb_queue_flush(&lchan->dl_tch_queue);
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
+ /* Ensure RTCP SDES contains some useful information */
+ snprintf(cname, sizeof(cname), "bts@%s", ipstr);
+ osmo_rtp_set_source_desc(lchan->abis_ip.rtp_socket, cname,
+ gsm_lchan_name(lchan), NULL, NULL,
+ gsm_trx_unit_id(lchan->ts->trx),
+ "OsmoBTS-" PACKAGE_VERSION, NULL);
+ /* FIXME: multiplex connection, BSC proxy */
+ } else {
+ /* MDCX */
+ if (!lchan->abis_ip.rtp_socket) {
+ LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC MDCX, "
+ "but we have no RTP socket!\n",
+ gsm_lchan_name(lchan));
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
+ }
+
+
+ /* Special rule: If connect_ip == 0.0.0.0, use RSL IP
+ * address */
+ if (connect_ip == 0) {
+ struct e1inp_sign_link *sign_link =
+ lchan->ts->trx->rsl_link;
+
+ ia.s_addr = htonl(get_signlink_remote_ip(sign_link));
+ } else
+ ia.s_addr = connect_ip;
+ rc = osmo_rtp_socket_connect(lchan->abis_ip.rtp_socket,
+ inet_ntoa(ia), ntohs(connect_port));
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "%s Failed to connect RTP/RTCP sockets\n",
+ gsm_lchan_name(lchan));
+ osmo_rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ msgb_queue_flush(&lchan->dl_tch_queue);
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
+ /* save IP address and port number */
+ lchan->abis_ip.connect_ip = ntohl(ia.s_addr);
+ lchan->abis_ip.connect_port = ntohs(connect_port);
+
+ rc = osmo_rtp_get_bound_ip_port(lchan->abis_ip.rtp_socket,
+ &lchan->abis_ip.bound_ip,
+ &port);
+ if (rc < 0)
+ LOGP(DRTP, LOGL_ERROR, "%s IPAC cannot obtain "
+ "locally bound IP/port: %d\n",
+ gsm_lchan_name(lchan), rc);
+ lchan->abis_ip.bound_port = port;
+
+ /* Everything has succeeded, we can store new values in lchan */
+ if (payload_type) {
+ lchan->abis_ip.rtp_payload = *payload_type;
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket,
+ *payload_type);
+ }
+ if (payload_type2) {
+ lchan->abis_ip.rtp_payload2 = *payload_type2;
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket,
+ *payload_type2);
+ }
+ if (speech_mode)
+ lchan->abis_ip.speech_mode = *speech_mode;
+
+ /* FIXME: CSD, jitterbuffer, compression */
+
+ return rsl_tx_ipac_XXcx_ack(lchan, payload_type2 ? 1 : 0,
+ dch->c.msg_type);
+}
+
+static int rsl_rx_ipac_dlcx(struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ struct gsm_lchan *lchan = msg->lchan;
+ int rc, inc_conn_id = 0;
+
+ rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rc < 0)
+ return rsl_tx_ipac_dlcx_nack(lchan, 0, RSL_ERR_MAND_IE_ERROR);
+
+ if (TLVP_PRESENT(&tp, RSL_IE_IPAC_CONN_ID))
+ inc_conn_id = 1;
+
+ rc = rsl_tx_ipac_dlcx_ack(lchan, inc_conn_id);
+ if (lchan->abis_ip.rtp_socket) {
+ osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO,
+ "Closing RTP socket on DLCX ");
+ osmo_rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ msgb_queue_flush(&lchan->dl_tch_queue);
+ }
+ return rc;
+}
+
+/*
+ * dynamic TCH/F_PDCH related messages, originally ip.access specific but
+ * reused for other BTS models (sysmo-bts, ...)
+ */
+
+/* PDCH ACT/DEACT ACKNOWLEDGE */
+static int rsl_tx_dyn_pdch_ack(struct gsm_lchan *lchan, bool pdch_act)
+{
+ struct gsm_time *gtime = get_time(lchan->ts->trx->bts);
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ struct msgb *msg;
+ uint8_t ie[2];
+
+ LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s ACK\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT");
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (pdch_act) {
+ gsm48_gen_starting_time(ie, gtime);
+ msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie);
+ }
+ rsl_dch_push_hdr(msg,
+ pdch_act? RSL_MT_IPAC_PDCH_ACT_ACK
+ : RSL_MT_IPAC_PDCH_DEACT_ACK,
+ chan_nr);
+ msg->lchan = lchan;
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* PDCH ACT/DEACT NEGATIVE ACKNOWLEDGE */
+static int rsl_tx_dyn_pdch_nack(struct gsm_lchan *lchan, bool pdch_act,
+ uint8_t cause)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s NACK (cause = 0x%02x)\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ msg->len = 0;
+ msg->data = msg->tail = msg->l3h;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg,
+ pdch_act? RSL_MT_IPAC_PDCH_ACT_NACK
+ : RSL_MT_IPAC_PDCH_DEACT_NACK,
+ chan_nr);
+ msg->lchan = lchan;
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/*
+ * Starting point for dynamic PDCH switching. See osmo-gsm-manuals.git for a
+ * diagram of what will happen here. The implementation is as follows:
+ *
+ * PDCH ACT == TCH/F -> PDCH:
+ * 1. call bts_model_ts_disconnect() to disconnect TCH/F;
+ * 2. cb_ts_disconnected() is called when done;
+ * 3. call bts_model_ts_connect() to connect as PDTCH;
+ * 4. cb_ts_connected(rc) is called when done;
+ * 5. instruct the PCU to enable PDTCH;
+ * 6. the PCU will call back with an activation request;
+ * 7. l1sap_info_act_cnf() will call ipacc_dyn_pdch_complete() when SAPI
+ * activations are done;
+ * 8. send a PDCH ACT ACK.
+ *
+ * PDCH DEACT == PDCH -> TCH/F:
+ * 1. instruct the PCU to disable PDTCH;
+ * 2. the PCU will call back with a deactivation request;
+ * 3. l1sap_info_rel_cnf() will call bts_model_ts_disconnect() when SAPI
+ * deactivations are done;
+ * 4. cb_ts_disconnected() is called when done;
+ * 5. call bts_model_ts_connect() to connect as TCH/F;
+ * 6. cb_ts_connected(rc) is called when done;
+ * 7. directly call ipacc_dyn_pdch_complete(), since no further action required
+ * for TCH/F;
+ * 8. send a PDCH DEACT ACK.
+ *
+ * When an error happens along the way, a PDCH DE/ACT NACK is sent.
+ * TODO: may need to be made more waterproof in all stages, to send a NACK and
+ * clear the PDCH pending flags from ts->flags.
+ */
+static void rsl_rx_dyn_pdch(struct msgb *msg, bool pdch_act)
+{
+ int rc;
+ struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+ bool is_pdch_act = (ts->flags & TS_F_PDCH_ACTIVE);
+
+ if (ts->flags & TS_F_PDCH_PENDING_MASK) {
+ /* Only one of the pending flags should ever be set at the same
+ * time, but just log both in case both should be set. */
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Request to PDCH %s, but PDCH%s%s is still pending\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? " ACT" : "",
+ (ts->flags & TS_F_PDCH_DEACT_PENDING)? " DEACT" : "");
+ rsl_tx_dyn_pdch_nack(lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC);
+ return;
+ }
+
+ if (lchan->state != LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s Request to PDCH %s, but lchan is still in state %s\n",
+ gsm_ts_and_pchan_name(ts), pdch_act? "ACT" : "DEACT",
+ gsm_lchans_name(lchan->state));
+ }
+
+ ts->flags |= pdch_act? TS_F_PDCH_ACT_PENDING
+ : TS_F_PDCH_DEACT_PENDING;
+
+ /* ensure that this is indeed a dynamic-PDCH channel */
+ if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Attempt to PDCH %s on TS that is not a TCH/F_PDCH (is %s)\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT",
+ gsm_pchan_name(ts->pchan));
+ ipacc_dyn_pdch_complete(ts, -EINVAL);
+ return;
+ }
+
+ if (is_pdch_act == pdch_act) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "%s Request to PDCH %s, but is already so\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT");
+ ipacc_dyn_pdch_complete(ts, 0);
+ return;
+ }
+
+ if (pdch_act) {
+ /* Clear TCH state. Only first lchan matters for PDCH */
+ clear_lchan_for_pdch_activ(ts->lchan);
+
+ /* First, disconnect the TCH channel, to connect PDTCH later */
+ rc = bts_model_ts_disconnect(ts);
+ } else {
+ /* First, deactivate PDTCH through the PCU, to connect TCH
+ * later.
+ * pcu_tx_info_ind() will pick up TS_F_PDCH_DEACT_PENDING and
+ * trigger a deactivation.
+ * Except when the PCU is not connected yet, then trigger
+ * disconnect immediately from here. The PCU will catch up when
+ * it connects. */
+ /* TODO: timeout on channel connect / disconnect request from PCU? */
+ if (pcu_connected())
+ rc = pcu_tx_info_ind();
+ else
+ rc = bts_model_ts_disconnect(ts);
+ }
+
+ /* Error? then NACK right now. */
+ if (rc)
+ ipacc_dyn_pdch_complete(ts, rc);
+}
+
+static void ipacc_dyn_pdch_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ int rc;
+ enum gsm_phys_chan_config as_pchan;
+
+ if (ts->flags & TS_F_PDCH_DEACT_PENDING) {
+ LOGP(DRSL, LOGL_DEBUG,
+ "%s PDCH DEACT operation: channel disconnected, will reconnect as TCH\n",
+ gsm_lchan_name(ts->lchan));
+ as_pchan = GSM_PCHAN_TCH_F;
+ } else if (ts->flags & TS_F_PDCH_ACT_PENDING) {
+ LOGP(DRSL, LOGL_DEBUG,
+ "%s PDCH ACT operation: channel disconnected, will reconnect as PDTCH\n",
+ gsm_lchan_name(ts->lchan));
+ as_pchan = GSM_PCHAN_PDCH;
+ } else
+ /* No reconnect pending. */
+ return;
+
+ rc = conf_lchans_as_pchan(ts, as_pchan);
+ if (rc)
+ goto error_nack;
+
+ bts_model_ts_connect(ts, as_pchan);
+ return;
+
+error_nack:
+ /* Error? then NACK right now. */
+ if (rc)
+ ipacc_dyn_pdch_complete(ts, rc);
+}
+
+static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ DEBUGP(DRSL, "%s Disconnected\n", gsm_ts_and_pchan_name(ts));
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+
+ switch (ts->dyn.pchan_want) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_PDCH:
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Dyn TS disconnected, but invalid desired pchan: %s\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(ts->dyn.pchan_want));
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* TODO: how would this recover? */
+ return;
+ }
+
+ conf_lchans_as_pchan(ts, ts->dyn.pchan_want);
+ DEBUGP(DRSL, "%s Connect\n", gsm_ts_and_pchan_name(ts));
+ bts_model_ts_connect(ts, ts->dyn.pchan_want);
+}
+
+void cb_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ OSMO_ASSERT(ts);
+
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ return ipacc_dyn_pdch_ts_disconnected(ts);
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return osmo_dyn_ts_disconnected(ts);
+ default:
+ return;
+ }
+}
+
+static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts, int rc)
+{
+ if (rc) {
+ LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT IPA operation failed (%d) in bts model\n",
+ gsm_lchan_name(ts->lchan), rc);
+ ipacc_dyn_pdch_complete(ts, rc);
+ return;
+ }
+
+ if (ts->flags & TS_F_PDCH_DEACT_PENDING) {
+ if (ts->lchan[0].type != GSM_LCHAN_TCH_F)
+ LOGP(DRSL, LOGL_ERROR, "%s PDCH DEACT error:"
+ " timeslot connected, so expecting"
+ " lchan type TCH/F, but is %s\n",
+ gsm_lchan_name(ts->lchan),
+ gsm_lchant_name(ts->lchan[0].type));
+
+ LOGP(DRSL, LOGL_DEBUG, "%s PDCH DEACT operation:"
+ " timeslot connected as TCH/F\n",
+ gsm_lchan_name(ts->lchan));
+
+ /* During PDCH DEACT, we're done right after the TCH/F came
+ * back up. */
+ ipacc_dyn_pdch_complete(ts, 0);
+
+ } else if (ts->flags & TS_F_PDCH_ACT_PENDING) {
+ if (ts->lchan[0].type != GSM_LCHAN_PDTCH)
+ LOGP(DRSL, LOGL_ERROR, "%s PDCH ACT error:"
+ " timeslot connected, so expecting"
+ " lchan type PDTCH, but is %s\n",
+ gsm_lchan_name(ts->lchan),
+ gsm_lchant_name(ts->lchan[0].type));
+
+ LOGP(DRSL, LOGL_DEBUG, "%s PDCH ACT operation:"
+ " timeslot connected as PDTCH\n",
+ gsm_lchan_name(ts->lchan));
+
+ /* The PDTCH is connected, now tell the PCU about it. Except
+ * when the PCU is not connected (yet), then there's nothing
+ * left to do now. The PCU will catch up when it connects. */
+ if (!pcu_connected()) {
+ ipacc_dyn_pdch_complete(ts, 0);
+ return;
+ }
+
+ /* The PCU will request to activate the PDTCH SAPIs, which,
+ * when done, will call back to ipacc_dyn_pdch_complete(). */
+ /* TODO: timeout on channel connect / disconnect request from PCU? */
+ rc = pcu_tx_info_ind();
+
+ /* Error? then NACK right now. */
+ if (rc)
+ ipacc_dyn_pdch_complete(ts, rc);
+ }
+}
+
+static void osmo_dyn_ts_connected(struct gsm_bts_trx_ts *ts, int rc)
+{
+ struct msgb *msg = ts->dyn.pending_chan_activ;
+ ts->dyn.pending_chan_activ = NULL;
+
+ if (rc) {
+ LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT OSMO operation failed (%d) in bts model\n",
+ gsm_lchan_name(ts->lchan), rc);
+ ipacc_dyn_pdch_complete(ts, rc);
+ return;
+ }
+
+ if (!msg) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s TS re-connected, but no chan activ msg pending\n",
+ gsm_ts_and_pchan_name(ts));
+ return;
+ }
+
+ ts->dyn.pchan_is = ts->dyn.pchan_want;
+ DEBUGP(DRSL, "%s Connected\n", gsm_ts_and_pchan_name(ts));
+
+ /* continue where we left off before re-connecting the TS. */
+ rc = rsl_rx_chan_activ(msg);
+ if (rc != 1)
+ msgb_free(msg);
+}
+
+void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc)
+{
+ OSMO_ASSERT(ts);
+
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ return ipacc_dyn_pdch_ts_connected(ts, rc);
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return osmo_dyn_ts_connected(ts, rc);
+ default:
+ return;
+ }
+}
+
+void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc)
+{
+ bool pdch_act;
+ OSMO_ASSERT(ts);
+
+ pdch_act = ts->flags & TS_F_PDCH_ACT_PENDING;
+
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == TS_F_PDCH_PENDING_MASK)
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Internal Error: both PDCH ACT and PDCH DEACT pending\n",
+ gsm_lchan_name(ts->lchan));
+
+ ts->flags &= ~TS_F_PDCH_PENDING_MASK;
+
+ if (rc != 0) {
+ LOGP(DRSL, LOGL_ERROR,
+ "PDCH %s on dynamic TCH/F_PDCH returned error %d\n",
+ pdch_act? "ACT" : "DEACT", rc);
+ rsl_tx_dyn_pdch_nack(ts->lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC);
+ return;
+ }
+
+ if (pdch_act)
+ ts->flags |= TS_F_PDCH_ACTIVE;
+ else
+ ts->flags &= ~TS_F_PDCH_ACTIVE;
+ DEBUGP(DRSL, "%s %s switched to %s mode (ts->flags == %x)\n",
+ gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan),
+ pdch_act? "PDCH" : "TCH/F", ts->flags);
+
+ rc = rsl_tx_dyn_pdch_ack(ts->lchan, pdch_act);
+ if (rc)
+ LOGP(DRSL, LOGL_ERROR,
+ "Failed to transmit PDCH %s ACK, rc %d\n",
+ pdch_act? "ACT" : "DEACT", rc);
+}
+
+/* handle a message with an RSL CHAN_NR that is incompatible/unknown */
+static int rsl_reject_unknown_lchan(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rh = msgb_l2(msg);
+ struct abis_rsl_dchan_hdr *dch;
+ int rc;
+
+ /* Handle GSM 08.58 7 Error Handling for the given input. This method will
+ * send either a CHANNEL ACTIVATION NACK, MODE MODIFY NACK or ERROR REPORT
+ * depending on the input of the method. */
+
+ /* TS 48.058 Section 7 explains how to do error handling */
+ switch (rh->msg_discr & 0xfe) {
+ case ABIS_RSL_MDISC_DED_CHAN:
+ dch = msgb_l2(msg);
+ switch (dch->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV:
+ rc = _rsl_tx_chan_act_nack(msg->trx, dch->chan_nr,
+ RSL_ERR_MAND_IE_ERROR, NULL);
+ break;
+ case RSL_MT_MODE_MODIFY_REQ:
+ rc = _rsl_tx_mode_modif_nack(msg->trx, dch->chan_nr,
+ RSL_ERR_MAND_IE_ERROR, NULL);
+ break;
+ default:
+ rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+ break;
+ }
+ break;
+ case ABIS_RSL_MDISC_RLL:
+ /* fall-through */
+ case ABIS_RSL_MDISC_COM_CHAN:
+ /* fall-through */
+ case ABIS_RSL_MDISC_TRX:
+ /* fall-through */
+ case ABIS_RSL_MDISC_IPACCESS:
+ /* fall-through */
+ default:
+ /* ERROR REPORT */
+ rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+/*
+ * selecting message
+ */
+
+static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_rll_hdr *rh = msgb_l2(msg);
+ struct gsm_lchan *lchan;
+
+ if (msgb_l2len(msg) < sizeof(*rh)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL Radio Link Layer message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, &rh->chan_nr, &rh->link_id, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)rh + sizeof(*rh);
+
+ if (!chan_nr_is_dchan(rh->chan_nr))
+ return rsl_reject_unknown_lchan(msg);
+
+ lchan = lchan_lookup(trx, rh->chan_nr, "RSL rx RLL: ");
+ if (!lchan) {
+ LOGP(DRLL, LOGL_NOTICE, "Rx RLL %s for unknown lchan\n",
+ rsl_msg_name(rh->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ DEBUGP(DRLL, "%s Rx RLL %s Abis -> LAPDm\n", gsm_lchan_name(lchan),
+ rsl_msg_name(rh->c.msg_type));
+
+ /* exception: RLL messages are _NOT_ freed as they are now
+ * owned by LAPDm which might have queued them */
+ return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+}
+
+static inline int rsl_link_id_is_sacch(uint8_t link_id)
+{
+ if (link_id >> 6 == 1)
+ return 1;
+ else
+ return 0;
+}
+
+static int rslms_is_meas_rep(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rh = msgb_l2(msg);
+ struct abis_rsl_rll_hdr *rllh;
+ struct gsm48_hdr *gh;
+
+ if ((rh->msg_discr & 0xfe) != ABIS_RSL_MDISC_RLL)
+ return 0;
+
+ if (rh->msg_type != RSL_MT_UNIT_DATA_IND)
+ return 0;
+
+ rllh = msgb_l2(msg);
+ if (rsl_link_id_is_sacch(rllh->link_id) == 0)
+ return 0;
+
+ gh = msgb_l3(msg);
+ if (gh->proto_discr != GSM48_PDISC_RR)
+ return 0;
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_MEAS_REP:
+ case GSM48_MT_RR_EXT_MEAS_REP:
+ return 1;
+ default:
+ break;
+ }
+
+ /* FIXME: this does not cover the Bter frame format and the associated
+ * short RR protocol descriptor for ENHANCED MEASUREMENT REPORT */
+
+ return 0;
+}
+
+static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, const struct lapdm_entity *le)
+{
+ return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - le->ta);
+}
+
+static inline bool ms_to_valid(const struct gsm_lchan *lchan)
+{
+ return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0);
+}
+
+struct osmo_bts_supp_meas_info {
+ int16_t toa256_mean;
+ int16_t toa256_min;
+ int16_t toa256_max;
+ uint16_t toa256_std_dev;
+} __attribute__((packed));
+
+/* 8.4.8 MEASUREMENT RESult */
+static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, const struct lapdm_entity *le)
+{
+ struct msgb *msg;
+ uint8_t meas_res[16];
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ int res_valid = lchan->meas.flags & LC_UL_M_F_RES_VALID;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ LOGP(DRSL, LOGL_DEBUG,
+ "%s chan_num:%u Tx MEAS RES valid(%d), flags(%02x)\n",
+ gsm_lchan_name(lchan), chan_nr, res_valid, lchan->meas.flags);
+
+ if (!res_valid)
+ return -EINPROGRESS;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DRSL, LOGL_DEBUG,
+ "%s Send Meas RES: NUM:%u, RXLEV_FULL:%u, RXLEV_SUB:%u, RXQUAL_FULL:%u, RXQUAL_SUB:%u, MS_PWR:%u, UL_TA:%u, L3_LEN:%d, TimingOff:%u\n",
+ gsm_lchan_name(lchan),
+ lchan->meas.res_nr,
+ lchan->meas.ul_res.full.rx_lev,
+ lchan->meas.ul_res.sub.rx_lev,
+ lchan->meas.ul_res.full.rx_qual,
+ lchan->meas.ul_res.sub.rx_qual,
+ lchan->meas.l1_info[0],
+ lchan->meas.l1_info[1], l3_len, ms_to2rsl(lchan, le) - MEAS_MAX_TIMING_ADVANCE);
+
+ msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++);
+ size_t ie_len = gsm0858_rsl_ul_meas_enc(&lchan->meas.ul_res,
+ lchan->tch.dtx.dl_active,
+ meas_res);
+ lchan->tch.dtx.dl_active = false;
+ if (ie_len >= 3) {
+ if (bts->supp_meas_toa256 && lchan->meas.flags & LC_UL_M_F_OSMO_EXT_VALID) {
+ struct osmo_bts_supp_meas_info *smi;
+ smi = (struct osmo_bts_supp_meas_info *) &meas_res[ie_len];
+ ie_len += sizeof(struct osmo_bts_supp_meas_info);
+ /* append signed 16bit value containing MS timing offset in 1/256th symbols
+ * in the vendor-specific "Supplementary Measurement Information" part of
+ * the uplink measurements IE. The lchan->meas.ext members are the current
+ * offset *relative* to the TA which the MS has already applied. As we want
+ * to know the total propagation time between MS and BTS, we need to add
+ * the actual TA value applied by the MS plus the respective toa256 value in
+ * 1/256 symbol periods. */
+ int16_t ta256 = lchan_get_ta(lchan) * 256;
+ smi->toa256_mean = htons(ta256 + lchan->meas.ms_toa256);
+ smi->toa256_min = htons(ta256 + lchan->meas.ext.toa256_min);
+ smi->toa256_max = htons(ta256 + lchan->meas.ext.toa256_max);
+ smi->toa256_std_dev = htons(lchan->meas.ext.toa256_std_dev);
+ lchan->meas.flags &= ~LC_UL_M_F_OSMO_EXT_VALID;
+ }
+ msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, ie_len, meas_res);
+ lchan->meas.flags &= ~LC_UL_M_F_RES_VALID;
+ }
+ msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->meas.bts_tx_pwr);
+ if (lchan->meas.flags & LC_UL_M_F_L1_VALID) {
+ msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, 2, lchan->meas.l1_info);
+ lchan->meas.flags &= ~LC_UL_M_F_L1_VALID;
+ }
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3);
+ if (ms_to_valid(lchan)) {
+ msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, ms_to2rsl(lchan, le));
+ lchan->ms_t_offs = -1;
+ lchan->p_offs = -1;
+ }
+
+ rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* call-back for LAPDm code, called when it wants to send msgs UP */
+int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx)
+{
+ struct gsm_lchan *lchan = ctx;
+ struct abis_rsl_common_hdr *rh;
+
+ OSMO_ASSERT(msg);
+ rh = msgb_l2(msg);
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DRSL, LOGL_ERROR, "%s(%s) is not active. Dropping message (len=%u): %s\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state),
+ msgb_l2len(msg), msgb_hexdump_l2(msg));
+ msgb_free(msg);
+ return 0;
+ }
+
+ msg->trx = lchan->ts->trx;
+ msg->lchan = lchan;
+
+ /* check if this is a measurement report from SACCH which needs special
+ * processing before forwarding */
+ if (rslms_is_meas_rep(msg)) {
+ int rc;
+
+ LOGP(DRSL, LOGL_INFO, "%s Handing RLL msg %s from LAPDm to MEAS REP\n",
+ gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type));
+
+ /* REL_IND handling */
+ if (rh->msg_type == RSL_MT_REL_IND &&
+ (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)) {
+ LOGP(DRSL, LOGL_INFO, "%s Scheduling %s to L3 in next associated TCH-RTS.ind\n",
+ gsm_lchan_name(lchan),
+ rsl_msg_name(rh->msg_type));
+
+ if(lchan->pending_rel_ind_msg) {
+ LOGP(DRSL, LOGL_INFO, "Dropping pending release indication message\n");
+ msgb_free(lchan->pending_rel_ind_msg);
+ }
+
+ lchan->pending_rel_ind_msg = msg;
+ return 0;
+ }
+
+ rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg), le);
+ msgb_free(msg);
+ return rc;
+ } else {
+ LOGP(DRSL, LOGL_INFO, "%s Fwd RLL msg %s from LAPDm to A-bis\n",
+ gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type));
+
+ return abis_bts_rsl_sendmsg(msg);
+ }
+}
+
+static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_cchan_hdr *cch = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*cch)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL Common Channel Management message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)cch + sizeof(*cch);
+
+ /* normally we don't permit dedicated channels here ... */
+ if (chan_nr_is_dchan(cch->chan_nr)) {
+ /* ... however, CBCH is on a SDCCH, so we must permit it */
+ if (cch->c.msg_type != RSL_MT_SMS_BC_CMD && cch->c.msg_type != RSL_MT_SMS_BC_REQ)
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ msg->lchan = lchan_lookup(trx, cch->chan_nr, "RSL rx CCHAN: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n",
+ rsl_msg_name(cch->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan),
+ rsl_msg_name(cch->c.msg_type));
+
+ switch (cch->c.msg_type) {
+ case RSL_MT_BCCH_INFO:
+ ret = rsl_rx_bcch_info(trx, msg);
+ break;
+ case RSL_MT_IMMEDIATE_ASSIGN_CMD:
+ ret = rsl_rx_imm_ass(trx, msg);
+ break;
+ case RSL_MT_PAGING_CMD:
+ ret = rsl_rx_paging_cmd(trx, msg);
+ break;
+ case RSL_MT_SMS_BC_CMD:
+ ret = rsl_rx_sms_bcast_cmd(trx, msg);
+ break;
+ case RSL_MT_SMS_BC_REQ:
+ case RSL_MT_NOT_CMD:
+ LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL cchan msg_type %s\n",
+ rsl_msg_name(cch->c.msg_type));
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n",
+ cch->c.msg_type);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int rsl_rx_dchan(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*dch)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL Dedicated Channel Management message too short\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)dch + sizeof(*dch);
+
+ if (!chan_nr_is_dchan(dch->chan_nr))
+ return rsl_reject_unknown_lchan(msg);
+
+ msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx DCHAN: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n",
+ rsl_or_ipac_msg_name(dch->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ LOGP(DRSL, LOGL_INFO, "%s ss=%d Rx RSL %s\n",
+ gsm_ts_and_pchan_name(msg->lchan->ts), msg->lchan->nr,
+ rsl_or_ipac_msg_name(dch->c.msg_type));
+
+ switch (dch->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV:
+ ret = rsl_rx_chan_activ(msg);
+ break;
+ case RSL_MT_RF_CHAN_REL:
+ ret = rsl_rx_rf_chan_rel(msg->lchan, dch->chan_nr);
+ break;
+ case RSL_MT_SACCH_INFO_MODIFY:
+ ret = rsl_rx_sacch_inf_mod(msg);
+ break;
+ case RSL_MT_DEACTIVATE_SACCH:
+ ret = l1sap_chan_deact_sacch(trx, dch->chan_nr);
+ break;
+ case RSL_MT_ENCR_CMD:
+ ret = rsl_rx_encr_cmd(msg);
+ break;
+ case RSL_MT_MODE_MODIFY_REQ:
+ ret = rsl_rx_mode_modif(msg);
+ break;
+ case RSL_MT_MS_POWER_CONTROL:
+ ret = rsl_rx_ms_pwr_ctrl(msg);
+ break;
+ case RSL_MT_IPAC_PDCH_ACT:
+ case RSL_MT_IPAC_PDCH_DEACT:
+ rsl_rx_dyn_pdch(msg, dch->c.msg_type == RSL_MT_IPAC_PDCH_ACT);
+ ret = 0;
+ break;
+ case RSL_MT_PHY_CONTEXT_REQ:
+ case RSL_MT_PREPROC_CONFIG:
+ case RSL_MT_RTD_REP:
+ case RSL_MT_PRE_HANDO_NOTIF:
+ case RSL_MT_MR_CODEC_MOD_REQ:
+ case RSL_MT_TFO_MOD_REQ:
+ LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL dchan msg_type %s\n",
+ rsl_msg_name(dch->c.msg_type));
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "undefined RSL dchan msg_type 0x%02x\n",
+ dch->c.msg_type);
+ ret = -EINVAL;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int rsl_rx_trx(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *th = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*th)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL TRX message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)th + sizeof(*th);
+
+ switch (th->msg_type) {
+ case RSL_MT_SACCH_FILL:
+ ret = rsl_rx_sacch_fill(trx, msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "undefined RSL TRX msg_type 0x%02x\n",
+ th->msg_type);
+ ret = -EINVAL;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*dch)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL ip.access message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)dch + sizeof(*dch);
+
+ if (!chan_nr_is_dchan(dch->chan_nr))
+ return rsl_reject_unknown_lchan(msg);
+
+ msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n",
+ rsl_msg_name(dch->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan),
+ rsl_ipac_msg_name(dch->c.msg_type));
+
+ switch (dch->c.msg_type) {
+ case RSL_MT_IPAC_CRCX:
+ case RSL_MT_IPAC_MDCX:
+ ret = rsl_rx_ipac_XXcx(msg);
+ break;
+ case RSL_MT_IPAC_DLCX:
+ ret = rsl_rx_ipac_dlcx(msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "unsupported RSL ip.access msg_type 0x%02x\n",
+ dch->c.msg_type);
+ ret = -EINVAL;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+ return ret;
+}
+
+int lchan_deactivate(struct gsm_lchan *lchan)
+{
+ OSMO_ASSERT(lchan);
+
+ lchan->ciph_state = 0;
+ return bts_model_lchan_deactivate(lchan);
+}
+
+int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rslh;
+ int ret = 0;
+
+ OSMO_ASSERT(trx);
+ OSMO_ASSERT(msg);
+
+ rslh = msgb_l2(msg);
+
+ if (msgb_l2len(msg) < sizeof(*rslh)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ switch (rslh->msg_discr & 0xfe) {
+ case ABIS_RSL_MDISC_RLL:
+ ret = rsl_rx_rll(trx, msg);
+ /* exception: RLL messages are _NOT_ freed as they are now
+ * owned by LAPDm which might have queued them */
+ break;
+ case ABIS_RSL_MDISC_COM_CHAN:
+ ret = rsl_rx_cchan(trx, msg);
+ break;
+ case ABIS_RSL_MDISC_DED_CHAN:
+ ret = rsl_rx_dchan(trx, msg);
+ break;
+ case ABIS_RSL_MDISC_TRX:
+ ret = rsl_rx_trx(trx, msg);
+ break;
+ case ABIS_RSL_MDISC_IPACCESS:
+ ret = rsl_rx_ipaccess(trx, msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "unknown RSL msg_discr 0x%02x\n",
+ rslh->msg_discr);
+ rsl_tx_error_report(trx, RSL_ERR_MSG_DISCR, NULL, NULL, msg);
+ msgb_free(msg);
+ ret = -EINVAL;
+ }
+
+ /* we don't free here, as rsl_rx{cchan,dchan,trx,ipaccess,rll} are
+ * responsible for owning the msg */
+
+ return ret;
+}
diff --git a/src/common/scheduler.c b/src/common/scheduler.c
new file mode 100644
index 00000000..f705ddf7
--- /dev/null
+++ b/src/common/scheduler.c
@@ -0,0 +1,1025 @@
+/* Scheduler for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/a5.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+extern void *tall_bts_ctx;
+
+static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan);
+static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan);
+static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan);
+/*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */
+static const ubit_t dummy_burst[GSM_BURST_LEN] = {
+ 0,0,0,
+ 1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0,
+ 0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0,
+ 0,1,0,1,1,1,0,0,0,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,
+ 0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,0,0,1,
+ 0,0,1,0,1,1,1,1,1,0,1,0,1,0,
+ 0,0,0,
+};
+
+/*! \brief FCCH Burst (TS 05.02 Chapter 5.2.4) */
+const ubit_t _sched_fcch_burst[GSM_BURST_LEN] = {
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+};
+
+/*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */
+const ubit_t _sched_tsc[8][26] = {
+ { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, },
+ { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, },
+ { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, },
+ { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, },
+ { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, },
+ { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, },
+ { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, },
+ { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, },
+};
+
+const ubit_t _sched_egprs_tsc[8][78] = {
+ { 1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,
+ 1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,
+ 1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, },
+ { 1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,
+ 1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1,1,
+ 1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, },
+ { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,
+ 1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0,
+ 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1, },
+ { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,
+ 1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,
+ 0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1, },
+ { 1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,
+ 1,0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1, },
+ { 1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,
+ 1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
+ 0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1, },
+ { 0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,
+ 1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1,
+ 1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1, },
+ { 0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,
+ 1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,
+ 0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1, },
+};
+
+/*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */
+const ubit_t _sched_sch_train[64] = {
+ 1,0,1,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,1,
+ 0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,0,1,1,
+};
+
+/*
+ * subchannel description structure
+ */
+
+const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
+ /* is_pdch chan_type chan_nr link_id name rts_fn dl_fn ul_fn auto_active */
+ { 0, TRXC_IDLE, 0, LID_DEDIC, "IDLE", NULL, tx_idle_fn, NULL, 1 },
+ { 0, TRXC_FCCH, 0, LID_DEDIC, "FCCH", NULL, tx_fcch_fn, NULL, 1 },
+ { 0, TRXC_SCH, 0, LID_DEDIC, "SCH", NULL, tx_sch_fn, NULL, 1 },
+ { 0, TRXC_BCCH, 0x80, LID_DEDIC, "BCCH", rts_data_fn, tx_data_fn, NULL, 1 },
+ { 0, TRXC_RACH, 0x88, LID_DEDIC, "RACH", NULL, NULL, rx_rach_fn, 1 },
+ { 0, TRXC_CCCH, 0x90, LID_DEDIC, "CCCH", rts_data_fn, tx_data_fn, NULL, 1 },
+ { 0, TRXC_TCHF, 0x08, LID_DEDIC, "TCH/F", rts_tchf_fn, tx_tchf_fn, rx_tchf_fn, 0 },
+ { 0, TRXC_TCHH_0, 0x10, LID_DEDIC, "TCH/H(0)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 },
+ { 0, TRXC_TCHH_1, 0x18, LID_DEDIC, "TCH/H(1)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 },
+ { 0, TRXC_SDCCH4_0, 0x20, LID_DEDIC, "SDCCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH4_1, 0x28, LID_DEDIC, "SDCCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH4_2, 0x30, LID_DEDIC, "SDCCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH4_3, 0x38, LID_DEDIC, "SDCCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_0, 0x40, LID_DEDIC, "SDCCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_1, 0x48, LID_DEDIC, "SDCCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_2, 0x50, LID_DEDIC, "SDCCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_3, 0x58, LID_DEDIC, "SDCCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_4, 0x60, LID_DEDIC, "SDCCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_5, 0x68, LID_DEDIC, "SDCCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_6, 0x70, LID_DEDIC, "SDCCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_7, 0x78, LID_DEDIC, "SDCCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCHTF, 0x08, LID_SACCH, "SACCH/TF", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCHTH_0, 0x10, LID_SACCH, "SACCH/TH(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCHTH_1, 0x18, LID_SACCH, "SACCH/TH(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_0, 0x20, LID_SACCH, "SACCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_1, 0x28, LID_SACCH, "SACCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_2, 0x30, LID_SACCH, "SACCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_3, 0x38, LID_SACCH, "SACCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_0, 0x40, LID_SACCH, "SACCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_1, 0x48, LID_SACCH, "SACCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_2, 0x50, LID_SACCH, "SACCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_3, 0x58, LID_SACCH, "SACCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_4, 0x60, LID_SACCH, "SACCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_5, 0x68, LID_SACCH, "SACCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_6, 0x70, LID_SACCH, "SACCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_7, 0x78, LID_SACCH, "SACCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 1, TRXC_PDTCH, 0xc0, LID_DEDIC, "PDTCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 },
+ { 1, TRXC_PTCCH, 0xc0, LID_DEDIC, "PTCCH", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_CBCH, 0xc8, LID_DEDIC, "CBCH", rts_data_fn, tx_data_fn, NULL, 1 },
+};
+
+const struct value_string trx_chan_type_names[] = {
+ OSMO_VALUE_STRING(TRXC_IDLE),
+ OSMO_VALUE_STRING(TRXC_FCCH),
+ OSMO_VALUE_STRING(TRXC_SCH),
+ OSMO_VALUE_STRING(TRXC_BCCH),
+ OSMO_VALUE_STRING(TRXC_RACH),
+ OSMO_VALUE_STRING(TRXC_CCCH),
+ OSMO_VALUE_STRING(TRXC_TCHF),
+ OSMO_VALUE_STRING(TRXC_TCHH_0),
+ OSMO_VALUE_STRING(TRXC_TCHH_1),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_0),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_1),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_2),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_3),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_0),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_1),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_2),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_3),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_4),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_5),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_6),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_7),
+ OSMO_VALUE_STRING(TRXC_SACCHTF),
+ OSMO_VALUE_STRING(TRXC_SACCHTH_0),
+ OSMO_VALUE_STRING(TRXC_SACCHTH_1),
+ OSMO_VALUE_STRING(TRXC_SACCH4_0),
+ OSMO_VALUE_STRING(TRXC_SACCH4_1),
+ OSMO_VALUE_STRING(TRXC_SACCH4_2),
+ OSMO_VALUE_STRING(TRXC_SACCH4_3),
+ OSMO_VALUE_STRING(TRXC_SACCH8_0),
+ OSMO_VALUE_STRING(TRXC_SACCH8_1),
+ OSMO_VALUE_STRING(TRXC_SACCH8_2),
+ OSMO_VALUE_STRING(TRXC_SACCH8_3),
+ OSMO_VALUE_STRING(TRXC_SACCH8_4),
+ OSMO_VALUE_STRING(TRXC_SACCH8_5),
+ OSMO_VALUE_STRING(TRXC_SACCH8_6),
+ OSMO_VALUE_STRING(TRXC_SACCH8_7),
+ OSMO_VALUE_STRING(TRXC_PDTCH),
+ OSMO_VALUE_STRING(TRXC_PTCCH),
+ OSMO_VALUE_STRING(TRXC_CBCH),
+ OSMO_VALUE_STRING(_TRX_CHAN_MAX),
+ { 0, NULL }
+};
+
+/*
+ * init / exit
+ */
+
+int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx)
+{
+ uint8_t tn;
+ unsigned int i;
+
+ if (!trx)
+ return -EINVAL;
+
+ l1t->trx = trx;
+
+ LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr);
+
+ for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ l1ts->mf_index = 0;
+ INIT_LLIST_HEAD(&l1ts->dl_prims);
+ for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) {
+ struct l1sched_chan_state *chan_state;
+ chan_state = &l1ts->chan_state[i];
+ chan_state->active = 0;
+ }
+ }
+
+ return 0;
+}
+
+void trx_sched_exit(struct l1sched_trx *l1t)
+{
+ struct gsm_bts_trx_ts *ts;
+ uint8_t tn;
+ int i;
+
+ LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1t->trx->nr);
+
+ for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ msgb_queue_flush(&l1ts->dl_prims);
+ for (i = 0; i < _TRX_CHAN_MAX; i++) {
+ struct l1sched_chan_state *chan_state;
+ chan_state = &l1ts->chan_state[i];
+ if (chan_state->dl_bursts) {
+ talloc_free(chan_state->dl_bursts);
+ chan_state->dl_bursts = NULL;
+ }
+ if (chan_state->ul_bursts) {
+ talloc_free(chan_state->ul_bursts);
+ chan_state->ul_bursts = NULL;
+ }
+ }
+ /* clear lchan channel states */
+ ts = &l1t->trx->ts[tn];
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++)
+ lchan_set_state(&ts->lchan[i], LCHAN_S_NONE);
+ }
+}
+
+/* close all logical channels and reset timeslots */
+void trx_sched_reset(struct l1sched_trx *l1t)
+{
+ trx_sched_exit(l1t);
+ trx_sched_init(l1t, l1t->trx);
+}
+
+struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn,
+ enum trx_chan_type chan)
+{
+ struct msgb *msg, *msg2;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t prim_fn;
+ uint8_t chan_nr, link_id;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ /* get prim of current fn from queue */
+ llist_for_each_entry_safe(msg, msg2, &l1ts->dl_prims, list) {
+ l1sap = msgb_l1sap_prim(msg);
+ if (l1sap->oph.operation != PRIM_OP_REQUEST) {
+wrong_type:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong type.\n");
+free_msg:
+ /* unlink and free message */
+ llist_del(&msg->list);
+ msgb_free(msg);
+ return NULL;
+ }
+ switch (l1sap->oph.primitive) {
+ case PRIM_PH_DATA:
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ prim_fn = ((l1sap->u.data.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME);
+ break;
+ case PRIM_TCH:
+ chan_nr = l1sap->u.tch.chan_nr;
+ link_id = 0;
+ prim_fn = ((l1sap->u.tch.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME);
+ break;
+ default:
+ goto wrong_type;
+ }
+ if (prim_fn > 100) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Prim %u is out of range (100), or channel %s with "
+ "type %s is already disabled. If this happens in "
+ "conjunction with PCU, increase 'rts-advance' by 5.\n",
+ prim_fn, get_lchan_by_chan_nr(l1t->trx, chan_nr)->name,
+ get_value_string(trx_chan_type_names, chan));
+ /* unlink and free message */
+ llist_del(&msg->list);
+ msgb_free(msg);
+ continue;
+ }
+ if (prim_fn > 0)
+ continue;
+
+ goto found_msg;
+ }
+
+ return NULL;
+
+found_msg:
+ if ((chan_nr ^ (trx_chan_desc[chan].chan_nr | tn))
+ || ((link_id & 0xc0) ^ trx_chan_desc[chan].link_id)) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong chan_nr=0x%02x link_id=%02x, "
+ "expecting chan_nr=0x%02x link_id=%02x.\n", chan_nr, link_id,
+ trx_chan_desc[chan].chan_nr | tn, trx_chan_desc[chan].link_id);
+ goto free_msg;
+ }
+
+ /* unlink and return message */
+ llist_del(&msg->list);
+ return msg;
+}
+
+int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t *l2,
+ uint8_t l2_len, float rssi,
+ int16_t ta_offs_256bits, int16_t link_qual_cb,
+ uint16_t ber10k,
+ enum osmo_ph_pres_info_type presence_info)
+{
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+ uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ /* compose primitive */
+ msg = l1sap_msgb_alloc(l2_len);
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.link_id = trx_chan_desc[chan].link_id;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = (int8_t) (rssi);
+ l1sap->u.data.ber10k = ber10k;
+ l1sap->u.data.ta_offs_256bits = ta_offs_256bits;
+ l1sap->u.data.lqual_cb = link_qual_cb;
+ l1sap->u.data.pdch_presence_info = presence_info;
+ msg->l2h = msgb_put(msg, l2_len);
+ if (l2_len)
+ memcpy(msg->l2h, l2, l2_len);
+
+ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id))
+ l1ts->chan_state[chan].lost_frames = 0;
+
+ /* forward primitive */
+ l1sap_up(l1t->trx, l1sap);
+
+ return 0;
+}
+
+int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len)
+{
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+ struct gsm_bts_trx *trx = l1t->trx;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
+
+ /* compose primitive */
+ msg = l1sap_msgb_alloc(tch_len);
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ msg->l2h = msgb_put(msg, tch_len);
+ if (tch_len)
+ memcpy(msg->l2h, tch, tch_len);
+
+ if (l1ts->chan_state[chan].lost_frames)
+ l1ts->chan_state[chan].lost_frames--;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, -1, l1sap->u.data.fn,
+ "%s Rx -> RTP: %s\n",
+ gsm_lchan_name(lchan), osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)));
+ /* forward primitive */
+ l1sap_up(l1t->trx, l1sap);
+
+ return 0;
+}
+
+
+
+/*
+ * data request (from upper layer)
+ */
+
+int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t tn = l1sap->u.data.chan_nr & 7;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.data.fn,
+ "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x\n",
+ l1sap->u.data.chan_nr, l1sap->u.data.link_id);
+
+ if (!l1sap->oph.msg)
+ abort();
+
+ /* ignore empty frame */
+ if (!msgb_l2len(l1sap->oph.msg)) {
+ msgb_free(l1sap->oph.msg);
+ return 0;
+ }
+
+ msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg);
+
+ return 0;
+}
+
+int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t tn = l1sap->u.tch.chan_nr & 7;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.tch.fn, "TCH.req: chan_nr=0x%02x\n",
+ l1sap->u.tch.chan_nr);
+
+ if (!l1sap->oph.msg)
+ abort();
+
+ /* ignore empty frame */
+ if (!msgb_l2len(l1sap->oph.msg)) {
+ msgb_free(l1sap->oph.msg);
+ return 0;
+ }
+
+ msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg);
+
+ return 0;
+}
+
+
+/*
+ * ready-to-send indication (to upper layer)
+ */
+
+/* RTS for data frame */
+static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan)
+{
+ uint8_t chan_nr, link_id;
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+
+ /* get data for RTS indication */
+ chan_nr = trx_chan_desc[chan].chan_nr | tn;
+ link_id = trx_chan_desc[chan].link_id;
+
+ if (!chan_nr) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "RTS func with non-existing chan_nr %d\n", chan_nr);
+ return -ENODEV;
+ }
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn,
+ "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id);
+
+ /* generate prim */
+ msg = l1sap_msgb_alloc(200);
+ if (!msg)
+ return -ENOMEM;
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.fn = fn;
+
+ return l1sap_up(l1t->trx, l1sap);
+}
+
+static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, int facch)
+{
+ uint8_t chan_nr, link_id;
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ int rc = 0;
+
+ /* get data for RTS indication */
+ chan_nr = trx_chan_desc[chan].chan_nr | tn;
+ link_id = trx_chan_desc[chan].link_id;
+
+ if (!chan_nr) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "RTS func with non-existing chan_nr %d\n", chan_nr);
+ return -ENODEV;
+ }
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr);
+
+ /* only send, if FACCH is selected */
+ if (facch) {
+ /* generate prim */
+ msg = l1sap_msgb_alloc(200);
+ if (!msg)
+ return -ENOMEM;
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.fn = fn;
+
+ rc = l1sap_up(l1t->trx, l1sap);
+ }
+
+ /* dont send, if TCH is in signalling only mode */
+ if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) {
+ /* generate prim */
+ msg = l1sap_msgb_alloc(200);
+ if (!msg)
+ return -ENOMEM;
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+
+ return l1sap_up(l1t->trx, l1sap);
+ }
+
+ return rc;
+}
+
+/* RTS for full rate traffic frame */
+static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan)
+{
+ /* TCH/F may include FACCH on every 4th burst */
+ return rts_tch_common(l1t, tn, fn, chan, 1);
+}
+
+
+/* RTS for half rate traffic frame */
+static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan)
+{
+ /* the FN 4/5, 13/14, 21/22 defines that FACCH may be included. */
+ return rts_tch_common(l1t, tn, fn, chan, ((fn % 26) >> 2) & 1);
+}
+
+/* set multiframe scheduler to given pchan */
+int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn,
+ enum gsm_phys_chan_config pchan)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ int i;
+
+ i = find_sched_mframe_idx(pchan, tn);
+ if (i < 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe "
+ "trx=%d ts=%d\n", l1t->trx->nr, tn);
+ return -ENOTSUP;
+ }
+ l1ts->mf_index = i;
+ l1ts->mf_period = trx_sched_multiframes[i].period;
+ l1ts->mf_frames = trx_sched_multiframes[i].frames;
+ LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with %s trx=%d ts=%d\n",
+ trx_sched_multiframes[i].name, l1t->trx->nr, tn);
+ return 0;
+}
+
+/* setting all logical channels given attributes to active/inactive */
+int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id,
+ int active)
+{
+ uint8_t tn = L1SAP_CHAN2TS(chan_nr);
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ uint8_t ss = l1sap_chan2ss(chan_nr);
+ int i;
+ int rc = -EINVAL;
+
+ /* look for all matching chan_nr/link_id */
+ for (i = 0; i < _TRX_CHAN_MAX; i++) {
+ struct l1sched_chan_state *chan_state;
+ chan_state = &l1ts->chan_state[i];
+ /* skip if pchan type does not match pdch flag */
+ if ((trx_sched_multiframes[l1ts->mf_index].pchan
+ == GSM_PCHAN_PDCH)
+ != trx_chan_desc[i].pdch)
+ continue;
+ if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)
+ && trx_chan_desc[i].link_id == link_id) {
+ rc = 0;
+ if (chan_state->active == active)
+ continue;
+ LOGP(DL1C, LOGL_NOTICE, "%s %s on trx=%d ts=%d\n",
+ (active) ? "Activating" : "Deactivating",
+ trx_chan_desc[i].name, l1t->trx->nr, tn);
+ if (active)
+ memset(chan_state, 0, sizeof(*chan_state));
+ chan_state->active = active;
+ /* free burst memory, to cleanly start with burst 0 */
+ if (chan_state->dl_bursts) {
+ talloc_free(chan_state->dl_bursts);
+ chan_state->dl_bursts = NULL;
+ }
+ if (chan_state->ul_bursts) {
+ talloc_free(chan_state->ul_bursts);
+ chan_state->ul_bursts = NULL;
+ }
+ if (!active)
+ chan_state->ho_rach_detect = 0;
+ }
+ }
+
+ /* disable handover detection (on deactivation) */
+ if (!active)
+ _sched_act_rach_det(l1t, tn, ss, 0);
+
+ return rc;
+}
+
+/* setting all logical channels given attributes to active/inactive */
+int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmode,
+ uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1,
+ uint8_t codec2, uint8_t codec3, uint8_t initial_id, uint8_t handover)
+{
+ uint8_t tn = L1SAP_CHAN2TS(chan_nr);
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ uint8_t ss = l1sap_chan2ss(chan_nr);
+ int i;
+ int rc = -EINVAL;
+ struct l1sched_chan_state *chan_state;
+
+ /* no mode for PDCH */
+ if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH)
+ return 0;
+
+ /* look for all matching chan_nr/link_id */
+ for (i = 0; i < _TRX_CHAN_MAX; i++) {
+ if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)
+ && trx_chan_desc[i].link_id == 0x00) {
+ chan_state = &l1ts->chan_state[i];
+ LOGP(DL1C, LOGL_NOTICE, "Set mode %u, %u, handover %u "
+ "on %s of trx=%d ts=%d\n", rsl_cmode, tch_mode,
+ handover, trx_chan_desc[i].name, l1t->trx->nr,
+ tn);
+ chan_state->rsl_cmode = rsl_cmode;
+ chan_state->tch_mode = tch_mode;
+ chan_state->ho_rach_detect = handover;
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+ && tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ chan_state->codecs = codecs;
+ chan_state->codec[0] = codec0;
+ chan_state->codec[1] = codec1;
+ chan_state->codec[2] = codec2;
+ chan_state->codec[3] = codec3;
+ chan_state->ul_ft = initial_id;
+ chan_state->dl_ft = initial_id;
+ chan_state->ul_cmr = initial_id;
+ chan_state->dl_cmr = initial_id;
+ chan_state->ber_sum = 0;
+ chan_state->ber_num = 0;
+ }
+ rc = 0;
+ }
+ }
+
+ /* command rach detection
+ * always enable handover, even if state is still set (due to loss
+ * of transceiver link).
+ * disable handover, if state is still set, since we might not know
+ * the actual state of transceiver (due to loss of link) */
+ _sched_act_rach_det(l1t, tn, ss, handover);
+
+ return rc;
+}
+
+/* setting cipher on logical channels */
+int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink,
+ int algo, uint8_t *key, int key_len)
+{
+ uint8_t tn = L1SAP_CHAN2TS(chan_nr);
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ int i;
+ int rc = -EINVAL;
+ struct l1sched_chan_state *chan_state;
+
+ /* no cipher for PDCH */
+ if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH)
+ return 0;
+
+ /* no algorithm given means a5/0 */
+ if (algo <= 0)
+ algo = 0;
+ else if (key_len != 8) {
+ LOGP(DL1C, LOGL_ERROR, "Algo A5/%d not supported with given "
+ "key len=%d\n", algo, key_len);
+ return -ENOTSUP;
+ }
+
+ /* look for all matching chan_nr */
+ for (i = 0; i < _TRX_CHAN_MAX; i++) {
+ /* skip if pchan type */
+ if (trx_chan_desc[i].pdch)
+ continue;
+ if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)) {
+ chan_state = &l1ts->chan_state[i];
+ LOGP(DL1C, LOGL_NOTICE, "Set a5/%d %s for %s on trx=%d "
+ "ts=%d\n", algo,
+ (downlink) ? "downlink" : "uplink",
+ trx_chan_desc[i].name, l1t->trx->nr, tn);
+ if (downlink) {
+ chan_state->dl_encr_algo = algo;
+ memcpy(chan_state->dl_encr_key, key, key_len);
+ chan_state->dl_encr_key_len = key_len;
+ } else {
+ chan_state->ul_encr_algo = algo;
+ memcpy(chan_state->ul_encr_key, key, key_len);
+ chan_state->ul_encr_key_len = key_len;
+ }
+ rc = 0;
+ }
+ }
+
+ return rc;
+}
+
+/* process ready-to-send */
+int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ const struct trx_sched_frame *frame;
+ uint8_t offset, period, bid;
+ trx_sched_rts_func *func;
+ enum trx_chan_type chan;
+
+ /* no multiframe set */
+ if (!l1ts->mf_index)
+ return 0;
+
+ /* get frame from multiframe */
+ period = l1ts->mf_period;
+ offset = fn % period;
+ frame = l1ts->mf_frames + offset;
+
+ chan = frame->dl_chan;
+ bid = frame->dl_bid;
+ func = trx_chan_desc[frame->dl_chan].rts_fn;
+
+ /* only on bid == 0 */
+ if (bid != 0)
+ return 0;
+
+ /* no RTS function */
+ if (!func)
+ return 0;
+
+ /* check if channel is active */
+ if (!trx_chan_desc[chan].auto_active
+ && !l1ts->chan_state[chan].active)
+ return -EINVAL;
+
+ return func(l1t, tn, fn, frame->dl_chan);
+}
+
+/* process downlink burst */
+const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn,
+ uint32_t fn, uint16_t *nbits)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *l1cs;
+ const struct trx_sched_frame *frame;
+ uint8_t offset, period, bid;
+ trx_sched_dl_func *func;
+ enum trx_chan_type chan;
+ ubit_t *bits = NULL;
+
+ if (!l1ts->mf_index)
+ goto no_data;
+
+ /* get frame from multiframe */
+ period = l1ts->mf_period;
+ offset = fn % period;
+ frame = l1ts->mf_frames + offset;
+
+ chan = frame->dl_chan;
+ bid = frame->dl_bid;
+ func = trx_chan_desc[chan].dl_fn;
+
+ l1cs = &l1ts->chan_state[chan];
+
+ /* check if channel is active */
+ if (!trx_chan_desc[chan].auto_active && !l1cs->active) {
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+ goto no_data;
+ }
+
+ /* get burst from function */
+ bits = func(l1t, tn, fn, chan, bid, nbits);
+
+ /* encrypt */
+ if (bits && l1cs->dl_encr_algo) {
+ ubit_t ks[114];
+ int i;
+
+ osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL);
+ for (i = 0; i < 57; i++) {
+ bits[i + 3] ^= ks[i];
+ bits[i + 88] ^= ks[i + 57];
+ }
+ }
+
+no_data:
+ /* in case of C0, we need a dummy burst to maintain RF power */
+ if (bits == NULL && l1t->trx == l1t->trx->bts->c0) {
+#if 0
+ if (chan != TRXC_IDLE) // hack
+ LOGP(DL1C, LOGL_DEBUG, "No burst data for %s fn=%u ts=%u "
+ "burst=%d on C0, so filling with dummy burst\n",
+ trx_chan_desc[chan].name, fn, tn, bid);
+#endif
+ bits = (ubit_t *) dummy_burst;
+ }
+
+ return bits;
+}
+
+#define TDMA_FN_SUM(a, b) \
+ ((a + GSM_HYPERFRAME + b) % GSM_HYPERFRAME)
+
+#define TDMA_FN_SUB(a, b) \
+ ((a + GSM_HYPERFRAME - b) % GSM_HYPERFRAME)
+
+static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t,
+ struct l1sched_chan_state *l1cs, uint8_t tn, uint32_t fn)
+{
+ const struct trx_sched_frame *frame_head;
+ const struct trx_sched_frame *frame;
+ struct l1sched_ts *l1ts;
+ uint32_t elapsed_fs;
+ uint8_t offset, i;
+ uint32_t fn_i;
+
+ /**
+ * When a channel is just activated, the MS needs some time
+ * to synchronize and start burst transmission,
+ * so let's wait until the first UL burst...
+ */
+ if (l1cs->proc_tdma_fs == 0)
+ return 0;
+
+ /* Get current TDMA frame info */
+ l1ts = l1sched_trx_get_ts(l1t, tn);
+ offset = fn % l1ts->mf_period;
+ frame_head = l1ts->mf_frames + offset;
+
+ /* Not applicable for some logical channels */
+ switch (frame_head->ul_chan) {
+ case TRXC_IDLE:
+ case TRXC_RACH:
+ case TRXC_PDTCH:
+ case TRXC_PTCCH:
+ return 0;
+ default:
+ /* No applicable if we are waiting for handover RACH */
+ if (l1cs->ho_rach_detect)
+ return 0;
+ }
+
+ /* How many frames elapsed since the last one? */
+ elapsed_fs = TDMA_FN_SUB(fn, l1cs->last_tdma_fn);
+ if (elapsed_fs > l1ts->mf_period) { /* Too many! */
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, frame_head->ul_chan, fn,
+ "Too many (>%u) contiguous TDMA frames=%u elapsed "
+ "since the last processed fn=%u\n", l1ts->mf_period,
+ elapsed_fs, l1cs->last_tdma_fn);
+ /* FIXME: how should this affect the measurements? */
+ return -EINVAL;
+ }
+
+ /**
+ * There are several TDMA frames between the last processed
+ * frame and currently received one. Let's walk through this
+ * path and count potentially lost frames, i.e. for which
+ * we didn't receive the corresponsing UL bursts.
+ *
+ * Start counting from the last_fn + 1.
+ */
+ for (i = 1; i < elapsed_fs; i++) {
+ fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i);
+ offset = fn_i % l1ts->mf_period;
+ frame = l1ts->mf_frames + offset;
+
+ if (frame->ul_chan == frame_head->ul_chan)
+ l1cs->lost_tdma_fs++;
+ }
+
+ if (l1cs->lost_tdma_fs > 0) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame_head->ul_chan, fn,
+ "At least %u TDMA frames were lost since the last "
+ "processed fn=%u\n", l1cs->lost_tdma_fs, l1cs->last_tdma_fn);
+
+ /**
+ * HACK: substitute lost bursts by zero-filled ones
+ *
+ * Instead of doing this, it makes sense to use the
+ * amount of lost frames in measurement calculations.
+ */
+ static sbit_t zero_burst[GSM_BURST_LEN] = { 0 };
+ trx_sched_ul_func *func;
+
+ for (i = 1; i < elapsed_fs; i++) {
+ fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i);
+ offset = fn_i % l1ts->mf_period;
+ frame = l1ts->mf_frames + offset;
+ func = trx_chan_desc[frame->ul_chan].ul_fn;
+
+ if (frame->ul_chan != frame_head->ul_chan)
+ continue;
+
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame->ul_chan, fn,
+ "Substituting lost TDMA frame=%u by all-zero "
+ "dummy burst\n", fn_i);
+
+ func(l1t, tn, fn_i, frame->ul_chan, frame->ul_bid,
+ zero_burst, GSM_BURST_LEN, -128, 0);
+
+ l1cs->lost_tdma_fs--;
+ }
+ }
+
+ return 0;
+}
+
+/* process uplink burst */
+int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *l1cs;
+ const struct trx_sched_frame *frame;
+ uint8_t offset, period, bid;
+ trx_sched_ul_func *func;
+ enum trx_chan_type chan;
+
+ if (!l1ts->mf_index)
+ return -EINVAL;
+
+ /* get frame from multiframe */
+ period = l1ts->mf_period;
+ offset = fn % period;
+ frame = l1ts->mf_frames + offset;
+
+ chan = frame->ul_chan;
+ bid = frame->ul_bid;
+ l1cs = &l1ts->chan_state[chan];
+ func = trx_chan_desc[chan].ul_fn;
+
+ /* check if channel is active */
+ if (!trx_chan_desc[chan].auto_active && !l1cs->active)
+ return -EINVAL;
+
+ /* omit bursts which have no handler, like IDLE bursts */
+ if (!func)
+ return -EINVAL;
+
+ /* calculate how many TDMA frames were potentially lost */
+ trx_sched_calc_frame_loss(l1t, l1cs, tn, fn);
+
+ /* update TDMA frame counters */
+ l1cs->last_tdma_fn = fn;
+ l1cs->proc_tdma_fs++;
+
+ /* decrypt */
+ if (bits && l1cs->ul_encr_algo) {
+ ubit_t ks[114];
+ int i;
+
+ osmo_a5(l1cs->ul_encr_algo, l1cs->ul_encr_key, fn, NULL, ks);
+ for (i = 0; i < 57; i++) {
+ if (ks[i])
+ bits[i + 3] = - bits[i + 3];
+ if (ks[i + 57])
+ bits[i + 88] = - bits[i + 88];
+ }
+ }
+
+ /* put burst to function */
+ func(l1t, tn, fn, chan, bid, bits, nbits, rssi, toa256);
+
+ return 0;
+}
+
+struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn)
+{
+ OSMO_ASSERT(tn < ARRAY_SIZE(l1t->ts));
+ return &l1t->ts[tn];
+}
diff --git a/src/common/scheduler_mframe.c b/src/common/scheduler_mframe.c
new file mode 100644
index 00000000..b969407c
--- /dev/null
+++ b/src/common/scheduler_mframe.c
@@ -0,0 +1,1040 @@
+/* Scheduler for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015-2018 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+
+
+/*
+ * multiframe structure
+ */
+
+static const struct trx_sched_frame frame_bcch[51] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_BCCH, 0, TRXC_RACH, 0 }, { TRXC_BCCH, 1, TRXC_RACH, 0 }, { TRXC_BCCH, 2, TRXC_RACH, 0 }, { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_IDLE, 0, TRXC_RACH, 0 },
+};
+
+static const struct trx_sched_frame frame_bcch_sdcch4[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_3, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 },
+ { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 },
+ { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 },
+
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_1, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 },
+ { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 },
+ { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 },
+};
+
+static const struct trx_sched_frame frame_bcch_sdcch4_cbch[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_IDLE, 0 },
+ { TRXC_CCCH, 1, TRXC_IDLE, 0 },
+ { TRXC_CCCH, 2, TRXC_IDLE, 0 },
+ { TRXC_CCCH, 3, TRXC_IDLE, 0 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_3, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 1, TRXC_RACH, 0 },
+ { TRXC_CBCH, 2, TRXC_RACH, 0 },
+ { TRXC_CBCH, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 1, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_1, 2, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_1, 3, TRXC_IDLE, 0 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_1, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 1, TRXC_RACH, 0 },
+ { TRXC_CBCH, 2, TRXC_RACH, 0 },
+ { TRXC_CBCH, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_1, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_1, 3 },
+ { TRXC_IDLE, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_3, 3, TRXC_IDLE, 0 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_sdcch8[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 },
+ { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 },
+ { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 },
+ { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 },
+ { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 },
+
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 },
+ { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 },
+ { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 },
+ { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 },
+ { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 },
+};
+
+static const struct trx_sched_frame frame_sdcch8_cbch[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 },
+ { TRXC_CBCH, 0, TRXC_SACCH8_7, 0 },
+ { TRXC_CBCH, 1, TRXC_SACCH8_7, 1 },
+ { TRXC_CBCH, 2, TRXC_SACCH8_7, 2 },
+ { TRXC_CBCH, 3, TRXC_SACCH8_7, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_IDLE, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_IDLE, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_IDLE, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_IDLE, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 },
+
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_IDLE, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_IDLE, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_IDLE, 3 },
+ { TRXC_CBCH, 0, TRXC_SACCH8_3, 0 },
+ { TRXC_CBCH, 1, TRXC_SACCH8_3, 1 },
+ { TRXC_CBCH, 2, TRXC_SACCH8_3, 2 },
+ { TRXC_CBCH, 3, TRXC_SACCH8_3, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts0[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts1[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts2[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts3[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts4[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts5[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts6[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts7[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts01[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts23[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts45[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts67[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+};
+
+static const struct trx_sched_frame frame_pdch[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 0, TRXC_PTCCH, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 1, TRXC_PTCCH, 1 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 2, TRXC_PTCCH, 2 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 3, TRXC_PTCCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+const struct trx_sched_multiframe trx_sched_multiframes[] = {
+ { GSM_PCHAN_NONE, 0xff, 0, NULL, "NONE"},
+ { GSM_PCHAN_CCCH, 0xff, 51, frame_bcch, "BCCH+CCCH" },
+ { GSM_PCHAN_CCCH_SDCCH4, 0xff, 102, frame_bcch_sdcch4, "BCCH+CCCH+SDCCH/4+SACCH/4" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, 0xff, 102, frame_bcch_sdcch4_cbch, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, 0xff, 102, frame_sdcch8, "SDCCH/8+SACCH/8" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH,0xff, 102, frame_sdcch8_cbch, "SDCCH/8+SACCH/8+CBCH" },
+ { GSM_PCHAN_TCH_F, 0x01, 104, frame_tchf_ts0, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x02, 104, frame_tchf_ts1, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x04, 104, frame_tchf_ts2, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x08, 104, frame_tchf_ts3, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x10, 104, frame_tchf_ts4, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x20, 104, frame_tchf_ts5, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x40, 104, frame_tchf_ts6, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x80, 104, frame_tchf_ts7, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_H, 0x03, 104, frame_tchh_ts01, "TCH/H+SACCH" },
+ { GSM_PCHAN_TCH_H, 0x0c, 104, frame_tchh_ts23, "TCH/H+SACCH" },
+ { GSM_PCHAN_TCH_H, 0x30, 104, frame_tchh_ts45, "TCH/H+SACCH" },
+ { GSM_PCHAN_TCH_H, 0xc0, 104, frame_tchh_ts67, "TCH/H+SACCH" },
+ { GSM_PCHAN_PDCH, 0xff, 104, frame_pdch, "PDCH" },
+};
+
+
+/*
+ * scheduler functions
+ */
+
+int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(trx_sched_multiframes); i++) {
+ if (trx_sched_multiframes[i].pchan == pchan
+ && (trx_sched_multiframes[i].slotmask & (1 << tn))) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/* Determine if given frame number contains SACCH (true) or other (false) burst */
+bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink)
+{
+ int i;
+ const struct trx_sched_multiframe *sched;
+ const struct trx_sched_frame *frame;
+ enum trx_chan_type ch_type;
+
+ i = find_sched_mframe_idx(ts->pchan, ts->nr);
+ if (i < 0)
+ return -EINVAL;
+ sched = &trx_sched_multiframes[i];
+ frame = &sched->frames[fn % sched->period];
+ if (uplink)
+ ch_type = frame->ul_chan;
+ else
+ ch_type = frame->dl_chan;
+
+ switch (ch_type) {
+ case TRXC_SACCH4_0:
+ case TRXC_SACCH4_1:
+ case TRXC_SACCH4_2:
+ case TRXC_SACCH4_3:
+ case TRXC_SACCH8_0:
+ case TRXC_SACCH8_1:
+ case TRXC_SACCH8_2:
+ case TRXC_SACCH8_3:
+ case TRXC_SACCH8_4:
+ case TRXC_SACCH8_5:
+ case TRXC_SACCH8_6:
+ case TRXC_SACCH8_7:
+ case TRXC_SACCHTF:
+ case TRXC_SACCHTH_0:
+ case TRXC_SACCHTH_1:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/src/common/sysinfo.c b/src/common/sysinfo.c
new file mode 100644
index 00000000..5c66e086
--- /dev/null
+++ b/src/common/sysinfo.c
@@ -0,0 +1,177 @@
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/sysinfo.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+/* properly increment SI2q index and return SI2q data for scheduling */
+static inline uint8_t *get_si2q_inc_index(struct gsm_bts *bts)
+{
+ uint8_t i = bts->si2q_index;
+ /* si2q_count is the max si2q_index value, not the number of messages */
+ bts->si2q_index = (bts->si2q_index + 1) % (bts->si2q_count + 1);
+
+ return (uint8_t *)GSM_BTS_SI2Q(bts, i);
+}
+
+/* Apply the rules from 05.02 6.3.1.3 Mapping of BCCH Data */
+uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time)
+{
+ unsigned int tc4_cnt = 0;
+ unsigned int tc4_sub[4];
+
+ /* System information type 2 bis or 2 ter messages are sent if
+ * needed, as determined by the system operator. If only one of
+ * them is needed, it is sent when TC = 5. If both are needed,
+ * 2bis is sent when TC = 5 and 2ter is sent at least once
+ * within any of 4 consecutive occurrences of TC = 4. */
+ /* System information type 2 quater is sent if needed, as
+ * determined by the system operator. If sent on BCCH Norm, it
+ * shall be sent when TC = 5 if neither of 2bis and 2ter are
+ * used, otherwise it shall be sent at least once within any of
+ * 4 consecutive occurrences of TC = 4. If sent on BCCH Ext, it
+ * is sent at least once within any of 4 consecutive occurrences
+ * of TC = 5. */
+ /* System Information type 9 is sent in those blocks with
+ * TC = 4 which are specified in system information type 3 as
+ * defined in 3GPP TS 04.08. */
+ /* System Information Type 13 need only be sent if GPRS support
+ * is indicated in one or more of System Information Type 3 or 4
+ * or 7 or 8 messages. These messages also indicate if the
+ * message is sent on the BCCH Norm or if the message is
+ * transmitted on the BCCH Ext. In the case that the message is
+ * sent on the BCCH Norm, it is sent at least once within any of
+ * 4 consecutive occurrences of TC = 4. */
+
+ /* We only implement BCCH Norm at this time */
+ switch (g_time->tc) {
+ case 0:
+ /* System Information Type 1 need only be sent if
+ * frequency hopping is in use or when the NCH is
+ * present in a cell. If the MS finds another message
+ * when TC = 0, it can assume that System Information
+ * Type 1 is not in use. */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_1))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_1);
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ case 1:
+ /* A SI 2 message will be sent at least every time TC = 1. */
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ case 2:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ case 3:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_4);
+ case 4:
+ /* iterate over 2ter, 2quater, 9, 13 */
+ /* determine how many SI we need to send on TC=4,
+ * and which of them we send when */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) {
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_2ter;
+ tc4_cnt += 1;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) &&
+ (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) || GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))) {
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater;
+ tc4_cnt += 1;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) {
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_13;
+ tc4_cnt += 1;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_9)) {
+ /* FIXME: check SI3 scheduling info! */
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_9;
+ tc4_cnt += 1;
+ }
+ /* simply send SI2 if we have nothing else to send */
+ if (tc4_cnt == 0)
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ else {
+ /* increment static counter by one, modulo count */
+ bts->si.tc4_ctr = (bts->si.tc4_ctr + 1) % tc4_cnt;
+
+ if (tc4_sub[bts->si.tc4_ctr] == SYSINFO_TYPE_2quater)
+ return get_si2q_inc_index(bts);
+
+ return GSM_BTS_SI(bts, tc4_sub[bts->si.tc4_ctr]);
+ }
+ case 5:
+ /* 2bis, 2ter, 2quater */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis);
+
+ else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2ter);
+
+ else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis);
+
+ else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) &&
+ !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))
+ return get_si2q_inc_index(bts);
+
+ /* simply send SI2 if we have nothing else to send */
+ else
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ break;
+ case 6:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ case 7:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_4);
+ }
+
+ /* this should never bve reached. We must transmit a BCCH
+ * message on the normal BCCH in all cases. */
+ OSMO_ASSERT(0);
+ return 0;
+}
+
+uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg)
+{
+ struct gsm_bts *b = trx->bts;
+ struct gsm48_system_information_type_3 *si3;
+ if (GSM_BTS_HAS_SI(b, SYSINFO_TYPE_3)) {
+ si3 = GSM_BTS_SI(b, SYSINFO_TYPE_3);
+ return si3->control_channel_desc.bs_ag_blks_res;
+ }
+ LOGP(DL1P, LOGL_ERROR, "%s: Unable to determine actual BS_AG_BLKS_RES "
+ "value as SI3 is not available yet, fallback to 1\n", arg);
+ return 1;
+}
+
+/* obtain the next to-be transmitted dowlink SACCH frame (L2 hdr + L3); returns pointer to lchan->si buffer */
+uint8_t *lchan_sacch_get(struct gsm_lchan *lchan)
+{
+ uint32_t tmp, i;
+
+ for (i = 0; i < _MAX_SYSINFO_TYPE; i++) {
+ tmp = (lchan->si.last + 1 + i) % _MAX_SYSINFO_TYPE;
+ if (!(lchan->si.valid & (1 << tmp)))
+ continue;
+ lchan->si.last = tmp;
+ return GSM_LCHAN_SI(lchan, tmp);
+ }
+ LOGP(DL1P, LOGL_NOTICE, "%s SACCH no SI available\n", gsm_lchan_name(lchan));
+ return NULL;
+}
diff --git a/src/common/tx_power.c b/src/common/tx_power.c
new file mode 100644
index 00000000..e418cec5
--- /dev/null
+++ b/src/common/tx_power.c
@@ -0,0 +1,306 @@
+/* Transmit Power computation */
+
+/* (C) 2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/tx_power.h>
+
+static int get_pa_drive_level_mdBm(const struct power_amp *pa,
+ int desired_p_out_mdBm, unsigned int arfcn)
+{
+ if (arfcn >= ARRAY_SIZE(pa->calib.delta_mdB))
+ return INT_MIN;
+
+ /* FIXME: temperature compensation */
+
+ return desired_p_out_mdBm - pa->nominal_gain_mdB - pa->calib.delta_mdB[arfcn];
+}
+
+/* maximum output power of the system */
+int get_p_max_out_mdBm(struct gsm_bts_trx *trx)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ /* Add user gain, internal and external PA gain to TRX output power */
+ return tpp->trx_p_max_out_mdBm + tpp->user_gain_mdB +
+ tpp->pa.nominal_gain_mdB + tpp->user_pa.nominal_gain_mdB;
+}
+
+/* nominal output power, i.e. OML-reduced maximum output power */
+int get_p_nominal_mdBm(struct gsm_bts_trx *trx)
+{
+ /* P_max_out subtracted by OML maximum power reduction IE */
+ return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red);
+}
+
+/* calculate the target total output power required, reduced by both
+ * OML and RSL, but ignoring the attenuation required for power ramping and
+ * thermal management */
+int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie)
+{
+ /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */
+ return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2);
+}
+int get_p_target_mdBm_lchan(struct gsm_lchan *lchan)
+{
+ return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power);
+}
+
+/* calculate the actual total output power required, taking into account the
+ * attenuation required for power ramping but not thermal management */
+int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ /* P_target subtracted by ramp attenuation */
+ return p_target_mdBm - tpp->ramp.attenuation_mdB;
+}
+
+/* calculate the effective total output power required, taking into account the
+ * attenuation required for power ramping and thermal management */
+int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ /* P_target subtracted by ramp attenuation */
+ return p_target_mdBm - tpp->ramp.attenuation_mdB - tpp->thermal_attenuation_mdB;
+}
+
+/* calculate effect TRX output power required, taking into account the
+ * attenuations required for power ramping and thermal management */
+int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm;
+ unsigned int arfcn = trx->arfcn;
+
+ /* P_actual subtracted by any bulk gain added by the user */
+ p_actual_mdBm = get_p_eff_mdBm(trx, p_target_mdBm) - tpp->user_gain_mdB;
+
+ /* determine input drive level required at input to user PA */
+ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_actual_mdBm, arfcn);
+
+ /* determine input drive level required at input to internal PA */
+ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn);
+
+ /* internal PA input drive level is TRX output power */
+ return pa_drvlvl_mdBm;
+}
+
+/* calculate target TRX output power required, ignoring the
+ * attenuations required for power ramping but not thermal management */
+int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_target_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm;
+ unsigned int arfcn = trx->arfcn;
+
+ /* P_target subtracted by any bulk gain added by the user */
+ p_target_mdBm = get_p_target_mdBm(trx, bs_power_ie) - tpp->user_gain_mdB;
+
+ /* determine input drive level required at input to user PA */
+ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_target_mdBm, arfcn);
+
+ /* determine input drive level required at input to internal PA */
+ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn);
+
+ /* internal PA input drive level is TRX output power */
+ return pa_drvlvl_mdBm;
+}
+int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan)
+{
+ return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power);
+}
+
+
+/* output power ramping code */
+
+/* The idea here is to avoid a hard switch from 0 to 100, but to actually
+ * slowly and gradually ramp up or down the power. This is needed on the
+ * one hand side to avoid very fast dynamic load changes towards the PA power
+ * supply, but is also needed in order to avoid a DoS by too many subscriber
+ * attempting to register at the same time. Rather, grow the cell slowly in
+ * radius than start with the full radius at once. */
+
+static int we_are_ramping_up(struct gsm_bts_trx *trx)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm)
+ return 1;
+ else
+ return 0;
+}
+
+static void power_ramp_do_step(struct gsm_bts_trx *trx, int first);
+
+/* timer call-back for the ramp timer */
+static void power_ramp_timer_cb(void *_trx)
+{
+ struct gsm_bts_trx *trx = _trx;
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_trxout_eff_mdBm;
+
+ /* compute new actual total output power (= minus ramp attenuation) */
+ tpp->p_total_cur_mdBm = get_p_actual_mdBm(trx, tpp->p_total_tgt_mdBm);
+
+ /* compute new effective (= minus ramp and thermal attenuation) TRX output required */
+ p_trxout_eff_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm);
+
+ LOGP(DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, "
+ "ramp_att=%d, therm_att=%d, user_gain=%d)\n",
+ tpp->p_total_cur_mdBm, tpp->p_total_tgt_mdBm,
+ tpp->ramp.attenuation_mdB, tpp->thermal_attenuation_mdB,
+ tpp->user_gain_mdB);
+
+ LOGP(DL1C, LOGL_INFO,
+ "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm);
+
+ /* Instruct L1 to apply new effective TRX output power required */
+ bts_model_change_power(trx, p_trxout_eff_mdBm);
+}
+
+/* BTS model call-back once one a call to bts_model_change_power()
+ * completes, indicating actual L1 transmit power */
+void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_trxout_should_mdBm;
+
+ p_trxout_should_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm);
+
+ /* for now we simply write an error message, but in the future
+ * we might use the value (again) as part of our math? */
+ if (p_trxout_cur_mdBm != p_trxout_should_mdBm) {
+ LOGP(DL1C, LOGL_ERROR, "bts_model notifies us of %u mdBm TRX "
+ "output power. However, it should be %u mdBm!\n",
+ p_trxout_cur_mdBm, p_trxout_should_mdBm);
+ }
+
+ /* and do another step... */
+ power_ramp_do_step(trx, 0);
+}
+
+static void power_ramp_do_step(struct gsm_bts_trx *trx, int first)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ /* we had finished in last loop iteration */
+ if (!first && tpp->ramp.attenuation_mdB == 0)
+ return;
+
+ if (we_are_ramping_up(trx)) {
+ /* ramp up power -> ramp down attenuation */
+ tpp->ramp.attenuation_mdB -= tpp->ramp.step_size_mdB;
+ if (tpp->ramp.attenuation_mdB <= 0) {
+ /* we are done */
+ tpp->ramp.attenuation_mdB = 0;
+ }
+ } else {
+ /* ramp down power -> ramp up attenuation */
+ tpp->ramp.attenuation_mdB += tpp->ramp.step_size_mdB;
+ if (tpp->ramp.attenuation_mdB >= 0) {
+ /* we are done */
+ tpp->ramp.attenuation_mdB = 0;
+ }
+ }
+
+ /* schedule timer for the next step */
+ tpp->ramp.step_timer.data = trx;
+ tpp->ramp.step_timer.cb = power_ramp_timer_cb;
+ osmo_timer_schedule(&tpp->ramp.step_timer, tpp->ramp.step_interval_sec, 0);
+}
+
+
+int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ /* The input to this function is the actual desired output power, i.e.
+ * the maximum total system power subtracted by OML as well as RSL
+ * reductions */
+
+ LOGP(DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d)\n",
+ tpp->p_total_cur_mdBm, p_total_tgt_mdBm);
+
+ if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) {
+ LOGP(DL1C, LOGL_ERROR, "Asked to ramp power up to "
+ "%d mdBm, which exceeds P_max_out (%d)\n",
+ p_total_tgt_mdBm, get_p_nominal_mdBm(trx));
+ return -ERANGE;
+ }
+
+ /* Cancel any pending request */
+ osmo_timer_del(&tpp->ramp.step_timer);
+
+ /* set the new target */
+ tpp->p_total_tgt_mdBm = p_total_tgt_mdBm;
+
+ if (we_are_ramping_up(trx)) {
+ if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) {
+ LOGP(DL1C, LOGL_INFO,
+ "target_power(%d) is below max.initial power\n",
+ tpp->p_total_tgt_mdBm);
+ /* new setting is below the maximum initial output
+ * power, so we can directly jump to this level */
+ tpp->p_total_cur_mdBm = tpp->p_total_tgt_mdBm;
+ tpp->ramp.attenuation_mdB = 0;
+ power_ramp_timer_cb(trx);
+ } else {
+ /* We need to step it up. Start from the current value */
+ /* Set attenuation to cause no power change right now */
+ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
+
+ /* start with the first step */
+ power_ramp_do_step(trx, 1);
+ }
+ } else {
+ /* Set ramp attenuation to negative value, and increase that by
+ * steps until it reaches 0 */
+ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
+
+ /* start with the first step */
+ power_ramp_do_step(trx, 1);
+ }
+
+ return 0;
+}
+
+/* determine the initial transceiver output power at start-up time */
+int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int pout_mdBm;
+
+ /* this is the maximum initial output on the antenna connector
+ * towards the antenna */
+ pout_mdBm = tpp->ramp.max_initial_pout_mdBm;
+
+ /* use this as input to compute transceiver board power
+ * (reflecting gains in internal/external amplifiers */
+ return get_p_trxout_eff_mdBm(trx, pout_mdBm);
+}
diff --git a/src/common/vty.c b/src/common/vty.c
new file mode 100644
index 00000000..bb88c578
--- /dev/null
+++ b/src/common/vty.c
@@ -0,0 +1,1651 @@
+/* OsmoBTS VTY interface */
+
+/* (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "btsconfig.h"
+
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/l1sap.h>
+
+#define VTY_STR "Configure the VTY\n"
+
+#define BTS_NR_STR "BTS Number\n"
+#define TRX_NR_STR "TRX Number\n"
+#define TS_NR_STR "Timeslot Number\n"
+#define LCHAN_NR_STR "Logical Channel Number\n"
+#define BTS_TRX_STR BTS_NR_STR TRX_NR_STR
+#define BTS_TRX_TS_STR BTS_TRX_STR TS_NR_STR
+#define BTS_TRX_TS_LCHAN_STR BTS_TRX_TS_STR LCHAN_NR_STR
+
+int g_vty_port_num = OSMO_VTY_PORT_BTS;
+
+struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr)
+{
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ struct phy_instance *pinst;
+
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY link number %d%s",
+ phy_nr, VTY_NEWLINE);
+ return NULL;
+ }
+
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "Cannot find PHY instance number %d%s",
+ inst_nr, VTY_NEWLINE);
+ return NULL;
+ }
+ return pinst;
+}
+
+int bts_vty_go_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case PHY_INST_NODE:
+ vty->node = PHY_NODE;
+ {
+ struct phy_instance *pinst = vty->index;
+ vty->index = pinst->phy_link;
+ }
+ break;
+ case TRX_NODE:
+ vty->node = BTS_NODE;
+ {
+ struct gsm_bts_trx *trx = vty->index;
+ vty->index = trx->bts;
+ }
+ break;
+ case PHY_NODE:
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ return vty->node;
+}
+
+int bts_vty_is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case TRX_NODE:
+ case BTS_NODE:
+ case PHY_NODE:
+ case PHY_INST_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+gDEFUN(ournode_exit, ournode_exit_cmd, "exit",
+ "Exit current node, go down to provious node")
+{
+ switch (vty->node) {
+ case PHY_INST_NODE:
+ vty->node = PHY_NODE;
+ {
+ struct phy_instance *pinst = vty->index;
+ vty->index = pinst->phy_link;
+ }
+ break;
+ case PHY_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ break;
+ case TRX_NODE:
+ vty->node = BTS_NODE;
+ {
+ struct gsm_bts_trx *trx = vty->index;
+ vty->index = trx->bts;
+ }
+ break;
+ default:
+ break;
+ }
+ return CMD_SUCCESS;
+}
+
+gDEFUN(ournode_end, ournode_end_cmd, "end",
+ "End current mode and change to enable mode")
+{
+ switch (vty->node) {
+ default:
+ vty_config_unlock(vty);
+ vty->node = ENABLE_NODE;
+ vty->index = NULL;
+ vty->index_sub = NULL;
+ break;
+ }
+ return CMD_SUCCESS;
+}
+
+static const char osmobts_copyright[] =
+ "Copyright (C) 2010, 2011 by Harald Welte, Andreas Eversberg and On-Waves\r\n"
+ "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+struct vty_app_info bts_vty_info = {
+ .name = "OsmoBTS",
+ .version = PACKAGE_VERSION,
+ .copyright = osmobts_copyright,
+ .go_parent_cb = bts_vty_go_parent,
+ .is_config_node = bts_vty_is_config_node,
+};
+
+extern struct gsm_network bts_gsmnet;
+
+struct gsm_network *gsmnet_from_vty(struct vty *v)
+{
+ return &bts_gsmnet;
+}
+
+static struct cmd_node bts_node = {
+ BTS_NODE,
+ "%s(bts)# ",
+ 1,
+};
+
+static struct cmd_node trx_node = {
+ TRX_NODE,
+ "%s(trx)# ",
+ 1,
+};
+
+gDEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd,
+ "auto-band",
+ "Automatically select band for ARFCN based on configured band\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->auto_band = 1;
+ return CMD_SUCCESS;
+}
+
+gDEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd,
+ "no auto-band",
+ NO_STR "Automatically select band for ARFCN based on configured band\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->auto_band = 0;
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
+ "trx <0-254>",
+ "Select a TRX to configure\n" "TRX number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx *trx;
+
+
+ if (trx_nr > bts->num_trx) {
+ vty_out(vty, "%% The next unused TRX number is %u%s",
+ bts->num_trx, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (trx_nr == bts->num_trx) {
+ /* Allocate a new TRX
+ * Remark: TRX0 was already created during gsm_bts_alloc() and
+ * initialized in bts_init(), not here.
+ */
+ trx = gsm_bts_trx_alloc(bts);
+ if (trx)
+ bts_trx_init(trx);
+ } else
+ trx = gsm_bts_trx_num(bts, trx_nr);
+
+ if (!trx) {
+ vty_out(vty, "%% Unable to allocate TRX %u%s",
+ trx_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->index = trx;
+ vty->index_sub = &trx->description;
+ vty->node = TRX_NODE;
+
+ return CMD_SUCCESS;
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ const char *sapi_buf;
+ int i;
+
+ vty_out(vty, "bts %u%s", bts->nr, VTY_NEWLINE);
+ if (bts->description)
+ vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE);
+ vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
+ if (bts->auto_band)
+ vty_out(vty, " auto-band%s", VTY_NEWLINE);
+ vty_out(vty, " ipa unit-id %u %u%s",
+ bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+ vty_out(vty, " oml remote-ip %s%s", bts->bsc_oml_host, VTY_NEWLINE);
+ vty_out(vty, " rtp jitter-buffer %u", bts->rtp_jitter_buf_ms);
+ if (bts->rtp_jitter_adaptive)
+ vty_out(vty, " adaptive");
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " rtp port-range %u %u%s", bts->rtp_port_range_start,
+ bts->rtp_port_range_end, VTY_NEWLINE);
+ vty_out(vty, " paging queue-size %u%s", paging_get_queue_max(bts->paging_state),
+ VTY_NEWLINE);
+ vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(bts->paging_state),
+ VTY_NEWLINE);
+ vty_out(vty, " uplink-power-target %d%s", bts->ul_power_target, VTY_NEWLINE);
+ if (bts->agch_queue.thresh_level != GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT
+ || bts->agch_queue.low_level != GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT
+ || bts->agch_queue.high_level != GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT)
+ vty_out(vty, " agch-queue-mgmt threshold %d low %d high %d%s",
+ bts->agch_queue.thresh_level, bts->agch_queue.low_level,
+ bts->agch_queue.high_level, VTY_NEWLINE);
+
+ for (i = 0; i < 32; i++) {
+ if (gsmtap_sapi_mask & (1 << i)) {
+ sapi_buf = osmo_str_tolower(get_value_string(gsmtap_sapi_names, i));
+ vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE);
+ }
+ }
+ if (gsmtap_sapi_acch) {
+ sapi_buf = osmo_str_tolower(get_value_string(gsmtap_sapi_names, GSMTAP_CHANNEL_ACCH));
+ vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE);
+ }
+ vty_out(vty, " min-qual-rach %.0f%s", bts->min_qual_rach * 10.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " min-qual-norm %.0f%s", bts->min_qual_norm * 10.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " max-ber10k-rach %u%s", bts->max_ber10k_rach,
+ VTY_NEWLINE);
+ if (strcmp(bts->pcu.sock_path, PCU_SOCK_DEFAULT))
+ vty_out(vty, " pcu-socket %s%s", bts->pcu.sock_path, VTY_NEWLINE);
+ if (bts->supp_meas_toa256)
+ vty_out(vty, " supp-meas-info toa256%s", VTY_NEWLINE);
+
+ bts_model_config_write_bts(vty, bts);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct trx_power_params *tpp = &trx->power_params;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
+
+ if (trx->power_params.user_gain_mdB)
+ vty_out(vty, " user-gain %u mdB%s",
+ tpp->user_gain_mdB, VTY_NEWLINE);
+ vty_out(vty, " power-ramp max-initial %d mdBm%s",
+ tpp->ramp.max_initial_pout_mdBm, VTY_NEWLINE);
+ vty_out(vty, " power-ramp step-size %d mdB%s",
+ tpp->ramp.step_size_mdB, VTY_NEWLINE);
+ vty_out(vty, " power-ramp step-interval %d%s",
+ tpp->ramp.step_interval_sec, VTY_NEWLINE);
+ vty_out(vty, " ms-power-control %s%s",
+ trx->ms_power_control == 0 ? "dsp" : "osmo",
+ VTY_NEWLINE);
+ vty_out(vty, " phy %u instance %u%s", pinst->phy_link->num,
+ pinst->num, VTY_NEWLINE);
+
+ bts_model_config_write_trx(vty, trx);
+ }
+}
+
+static int config_write_bts(struct vty *vty)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list)
+ config_write_bts_single(vty, bts);
+
+ return CMD_SUCCESS;
+}
+
+static void config_write_phy_single(struct vty *vty, struct phy_link *plink)
+{
+ int i;
+
+ vty_out(vty, "phy %u%s", plink->num, VTY_NEWLINE);
+ bts_model_config_write_phy(vty, plink);
+
+ for (i = 0; i < 255; i++) {
+ struct phy_instance *pinst = phy_instance_by_num(plink, i);
+ if (!pinst)
+ break;
+ vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE);
+ bts_model_config_write_phy_inst(vty, pinst);
+ }
+}
+
+static int config_write_phy(struct vty *vty)
+{
+ int i;
+
+ for (i = 0; i < 255; i++) {
+ struct phy_link *plink = phy_link_by_num(i);
+ if (!plink)
+ break;
+ config_write_phy_single(vty, plink);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_vty_telnet_port, cfg_vty_telnet_port_cmd,
+ "vty telnet-port <0-65535>",
+ VTY_STR "Set the VTY telnet port\n"
+ "TCP Port number\n")
+{
+ g_vty_port_num = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+ cfg_bts_cmd,
+ "bts BTS_NR",
+ "Select a BTS to configure\n"
+ "BTS Number\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts;
+
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "%% Unknown BTS number %u (num %u)%s",
+ bts_nr, gsmnet->num_bts, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else
+ bts = gsm_bts_num(gsmnet, bts_nr);
+
+ vty->index = bts;
+ vty->index_sub = &bts->description;
+ vty->node = BTS_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_unit_id,
+ cfg_bts_unit_id_cmd,
+ "ipa unit-id <0-65534> <0-255>",
+ "ip.access RSL commands\n"
+ "Set the Unit ID of this BTS\n"
+ "Site ID\n" "Unit ID\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int site_id = atoi(argv[0]);
+ int bts_id = atoi(argv[1]);
+
+ bts->ip_access.site_id = site_id;
+ bts->ip_access.bts_id = bts_id;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_band,
+ cfg_bts_band_cmd,
+ "band (450|GSM450|480|GSM480|750|GSM750|810|GSM810|850|GSM850|900|GSM900|1800|DCS1800|1900|PCS1900)",
+ "Set the frequency band of this BTS\n"
+ "Alias for GSM450\n450Mhz\n"
+ "Alias for GSM480\n480Mhz\n"
+ "Alias for GSM750\n750Mhz\n"
+ "Alias for GSM810\n810Mhz\n"
+ "Alias for GSM850\n850Mhz\n"
+ "Alias for GSM900\n900Mhz\n"
+ "Alias for DCS1800\n1800Mhz\n"
+ "Alias for PCS1900\n1900Mhz\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int band = gsm_band_parse(argv[0]);
+
+ if (band < 0) {
+ vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+ band, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->band = band;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_oml_ip,
+ cfg_bts_oml_ip_cmd,
+ "oml remote-ip A.B.C.D",
+ "OML Parameters\n" "OML IP Address\n" "OML IP Address\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->bsc_oml_host)
+ talloc_free(bts->bsc_oml_host);
+
+ bts->bsc_oml_host = talloc_strdup(bts, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define RTP_STR "RTP parameters\n"
+
+DEFUN_DEPRECATED(cfg_bts_rtp_bind_ip,
+ cfg_bts_rtp_bind_ip_cmd,
+ "rtp bind-ip A.B.C.D",
+ RTP_STR "RTP local bind IP Address\n" "RTP local bind IP Address\n")
+{
+ vty_out(vty, "%% rtp bind-ip is now deprecated%s", VTY_NEWLINE);
+
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_bts_rtp_jitbuf,
+ cfg_bts_rtp_jitbuf_cmd,
+ "rtp jitter-buffer <0-10000> [adaptive]",
+ RTP_STR "RTP jitter buffer\n" "jitter buffer in ms\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->rtp_jitter_buf_ms = atoi(argv[0]);
+ if (argc > 1)
+ bts->rtp_jitter_adaptive = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rtp_port_range,
+ cfg_bts_rtp_port_range_cmd,
+ "rtp port-range <1-65534> <1-65534>",
+ RTP_STR "Range of local ports to use for RTP/RTCP traffic\n")
+{
+ struct gsm_bts *bts = vty->index;
+ unsigned int start;
+ unsigned int end;
+
+ start = atoi(argv[0]);
+ end = atoi(argv[1]);
+
+ if (end < start) {
+ vty_out(vty, "range end port (%u) must be greater than the range start port (%u)!%s",
+ end, start, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (start & 1) {
+ vty_out(vty, "range must begin at an even port number! (%u not even)%s",
+ start, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if ((end & 1) == 0) {
+ vty_out(vty, "range must end at an odd port number! (%u not odd)%s",
+ end, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->rtp_port_range_start = start;
+ bts->rtp_port_range_end = end;
+ bts->rtp_port_range_next = bts->rtp_port_range_start;
+
+ return CMD_SUCCESS;
+}
+
+#define PAG_STR "Paging related parameters\n"
+
+DEFUN(cfg_bts_paging_queue_size,
+ cfg_bts_paging_queue_size_cmd,
+ "paging queue-size <1-1024>",
+ PAG_STR "Maximum length of BTS-internal paging queue\n"
+ "Maximum length of BTS-internal paging queue\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ paging_set_queue_max(bts->paging_state, atoi(argv[0]));
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_paging_lifetime,
+ cfg_bts_paging_lifetime_cmd,
+ "paging lifetime <0-60>",
+ PAG_STR "Maximum lifetime of a paging record\n"
+ "Maximum lifetime of a paging record (secods)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ paging_set_lifetime(bts->paging_state, atoi(argv[0]));
+
+ return CMD_SUCCESS;
+}
+
+#define AGCH_QUEUE_STR "AGCH queue mgmt\n"
+
+DEFUN(cfg_bts_agch_queue_mgmt_params,
+ cfg_bts_agch_queue_mgmt_params_cmd,
+ "agch-queue-mgmt threshold <0-100> low <0-100> high <0-100000>",
+ AGCH_QUEUE_STR
+ "Threshold to start cleanup\nin %% of the maximum queue length\n"
+ "Low water mark for cleanup\nin %% of the maximum queue length\n"
+ "High water mark for cleanup\nin %% of the maximum queue length\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->agch_queue.thresh_level = atoi(argv[0]);
+ bts->agch_queue.low_level = atoi(argv[1]);
+ bts->agch_queue.high_level = atoi(argv[2]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_agch_queue_mgmt_default,
+ cfg_bts_agch_queue_mgmt_default_cmd,
+ "agch-queue-mgmt default",
+ AGCH_QUEUE_STR
+ "Reset clean parameters to default values\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT;
+ bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT;
+ bts->agch_queue.high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd,
+ "uplink-power-target <-110-0>",
+ "Set the nominal target Rx Level for uplink power control loop\n"
+ "Target uplink Rx level in dBm\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->ul_power_target = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd,
+ "min-qual-rach <-100-100>",
+ "Set the minimum quality level of RACH burst to be accpeted\n"
+ "C/I level in tenth of dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->min_qual_rach = strtof(argv[0], NULL) / 10.0f;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd,
+ "min-qual-norm <-100-100>",
+ "Set the minimum quality level of normal burst to be accpeted\n"
+ "C/I level in tenth of dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->min_qual_norm = strtof(argv[0], NULL) / 10.0f;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd,
+ "max-ber10k-rach <0-10000>",
+ "Set the maximum BER for valid RACH requests\n"
+ "BER in 1/10000 units (0=no BER; 100=1% BER)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->max_ber10k_rach = strtoul(argv[0], NULL, 10);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd,
+ "pcu-socket PATH",
+ "Configure the PCU socket file/path name\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->pcu.sock_path) {
+ /* FIXME: close the interface? */
+ talloc_free(bts->pcu.sock_path);
+ }
+ bts->pcu.sock_path = talloc_strdup(bts, argv[0]);
+ /* FIXME: re-open the interface? */
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd,
+ "supp-meas-info toa256",
+ "Configure the RSL Supplementary Measurement Info\n"
+ "Report the TOA in 1/256th symbol periods\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->supp_meas_toa256 = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd,
+ "no supp-meas-info toa256",
+ NO_STR "Configure the RSL Supplementary Measurement Info\n"
+ "Report the TOA in 1/256th symbol periods\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->supp_meas_toa256 = false;
+ return CMD_SUCCESS;
+}
+
+
+#define DB_DBM_STR \
+ "Unit is dB (decibels)\n" \
+ "Unit is mdB (milli-decibels, or rather 1/10000 bel)\n"
+
+static int parse_mdbm(const char *valstr, const char *unit)
+{
+ int val = atoi(valstr);
+
+ if (!strcmp(unit, "dB") || !strcmp(unit, "dBm"))
+ return val * 1000;
+ else
+ return val;
+}
+
+DEFUN(cfg_trx_user_gain,
+ cfg_trx_user_gain_cmd,
+ "user-gain <-100000-100000> (dB|mdB)",
+ "Inform BTS about additional, user-provided gain or attenuation at TRX output\n"
+ "Value of user-provided external gain(+)/attenuation(-)\n" DB_DBM_STR)
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.user_gain_mdB = parse_mdbm(argv[0], argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+#define PR_STR "Power-Ramp settings"
+DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd,
+ "power-ramp max-initial <0-100000> (dBm|mdBm)",
+ PR_STR "Maximum initial power\n"
+ "Value\n" DB_DBM_STR)
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.ramp.max_initial_pout_mdBm =
+ parse_mdbm(argv[0], argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_pr_step_size, cfg_trx_pr_step_size_cmd,
+ "power-ramp step-size <1-100000> (dB|mdB)",
+ PR_STR "Power increase by step\n"
+ "Step size\n" DB_DBM_STR)
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.ramp.step_size_mdB =
+ parse_mdbm(argv[0], argv[1]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd,
+ "power-ramp step-interval <1-100>",
+ PR_STR "Power increase by step\n"
+ "Step time in seconds\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.ramp.step_interval_sec = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_ms_power_control, cfg_trx_ms_power_control_cmd,
+ "ms-power-control (dsp|osmo)",
+ "Mobile Station Power Level Control (change requires restart)\n"
+ "Handled by DSP\n" "Handled by OsmoBTS\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->ms_power_control = argv[0][0] == 'd' ? 0 : 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_phy, cfg_trx_phy_cmd,
+ "phy <0-255> instance <0-255>",
+ "Configure PHY Link+Instance for this TRX\n"
+ "PHY Link number\n" "PHY instance\n" "PHY Instance number")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct phy_link *plink = phy_link_by_num(atoi(argv[0]));
+ struct phy_instance *pinst;
+
+ if (!plink) {
+ vty_out(vty, "phy%s does not exist%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst = phy_instance_by_num(plink, atoi(argv[1]));
+ if (!pinst) {
+ vty_out(vty, "phy%s instance %s does not exit%s",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ trx->role_bts.l1h = pinst;
+ pinst->trx = trx;
+
+ return CMD_SUCCESS;
+}
+
+/* ======================================================================
+ * SHOW
+ * ======================================================================*/
+
+static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+{
+ vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s",
+ abis_nm_opstate_name(nms->operational),
+ get_value_string(abis_nm_adm_state_names, nms->administrative),
+ abis_nm_avail_name(nms->availability), VTY_NEWLINE);
+}
+
+static unsigned int llist_length(struct llist_head *list)
+{
+ unsigned int len = 0;
+ struct llist_head *pos;
+
+ llist_for_each(pos, list)
+ len++;
+
+ return len;
+}
+
+static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
+{
+ unsigned int i;
+ bool no_features = true;
+ vty_out(vty, " Features:%s", VTY_NEWLINE);
+
+ for (i = 0; i < _NUM_BTS_FEAT; i++) {
+ if (gsm_bts_has_feature(bts, i)) {
+ vty_out(vty, " %03u ", i);
+ vty_out(vty, "%-40s%s", get_value_string(gsm_bts_features_descs, i), VTY_NEWLINE);
+ no_features = false;
+ }
+ }
+
+ if (no_features)
+ vty_out(vty, " (not available)%s", VTY_NEWLINE);
+}
+
+static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+ "BSIC %u and %u TRX%s",
+ bts->nr, "FIXME", gsm_band_name(bts->band),
+ bts->cell_identity,
+ bts->location_area_code, bts->bsic,
+ bts->num_trx, VTY_NEWLINE);
+ vty_out(vty, " Description: %s%s",
+ bts->description ? bts->description : "(null)", VTY_NEWLINE);
+ vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s",
+ bts->ip_access.site_id, bts->ip_access.bts_id,
+ bts->oml_tei, VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &bts->mo.nm_state);
+ vty_out(vty, " Site Mgr NM State: ");
+ net_dump_nmstate(vty, &bts->site_mgr.mo.nm_state);
+ if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH))
+ vty_out(vty, " PCU version %s connected%s",
+ bts->pcu_version, VTY_NEWLINE);
+ vty_out(vty, " Paging: Queue size %u, occupied %u, lifetime %us%s",
+ paging_get_queue_max(bts->paging_state), paging_queue_length(bts->paging_state),
+ paging_get_lifetime(bts->paging_state), VTY_NEWLINE);
+ vty_out(vty, " AGCH: Queue limit %u, occupied %d, "
+ "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", "
+ "ag-res %"PRIu64", non-res %"PRIu64"%s",
+ bts->agch_queue.max_length, bts->agch_queue.length,
+ bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs,
+ bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs,
+ bts->agch_queue.pch_msgs,
+ VTY_NEWLINE);
+ vty_out(vty, " CBCH backlog queue length: %u%s",
+ llist_length(&bts->smscb_state.queue), VTY_NEWLINE);
+ vty_out(vty, " Paging: queue length %d, buffer space %d%s",
+ paging_queue_length(bts->paging_state), paging_buffer_space(bts->paging_state),
+ VTY_NEWLINE);
+ vty_out(vty, " OML Link state: %s.%s",
+ bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ vty_out(vty, " TRX %u%s", trx->nr, VTY_NEWLINE);
+ if (pinst) {
+ vty_out(vty, " phy %d %s", pinst->num, pinst->version);
+ if (pinst->description)
+ vty_out(vty, " (%s)", pinst->description);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
+ }
+
+ bts_dump_vty_features(vty, bts);
+ vty_out_rate_ctr_group(vty, " ", bts->ctrs);
+}
+
+
+DEFUN(show_bts, show_bts_cmd, "show bts <0-255>",
+ SHOW_STR "Display information about a BTS\n"
+ BTS_NR_STR)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int bts_nr;
+
+ if (argc != 0) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+ return CMD_SUCCESS;
+ }
+ /* print all BTS's */
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+ bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+
+ return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+ trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, "Description: %s%s",
+ trx->description ? trx->description : "(null)", VTY_NEWLINE);
+ vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, "
+ "resulting BS power: %d dBm%s",
+ trx->nominal_power, trx->max_power_red,
+ trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &trx->mo.nm_state);
+ vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE);
+ vty_out(vty, " Baseband Transceiver NM State: ");
+ net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state);
+ vty_out(vty, " IPA stream ID: 0x%02x%s", trx->rsl_tei, VTY_NEWLINE);
+}
+
+static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts)
+{
+ uint8_t trx_nr;
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++)
+ trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr));
+}
+
+DEFUN(show_trx,
+ show_trx_cmd,
+ "show trx [<0-255>] [<0-255>]",
+ SHOW_STR "Display information about a TRX\n"
+ BTS_TRX_STR)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ int bts_nr, trx_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr));
+ return CMD_SUCCESS;
+ }
+ if (bts) {
+ /* print all TRX in this BTS */
+ print_all_trx(vty, bts);
+ return CMD_SUCCESS;
+ }
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+ print_all_trx(vty, gsm_bts_num(net, bts_nr));
+
+ return CMD_SUCCESS;
+}
+
+
+static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s, TSC %u",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts));
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
+ vty_out(vty, " (%s mode)",
+ ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F");
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &ts->mo.nm_state);
+}
+
+DEFUN(show_ts,
+ show_ts_cmd,
+ "show timeslot [<0-255>] [<0-255>] [<0-7>]",
+ SHOW_STR "Display information about a TS\n"
+ BTS_TRX_TS_STR)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+ int bts_nr, trx_nr, ts_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ }
+ if (argc >= 3) {
+ ts_nr = atoi(argv[2]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS '%s'%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* Fully Specified: print and exit */
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ return CMD_SUCCESS;
+ }
+
+ if (bts && trx) {
+ /* Iterate over all TS in this TRX */
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ } else if (bts) {
+ /* Iterate over all TRX in this BTS, TS in each TRX */
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ }
+ } else {
+ /* Iterate over all BTS, TRX in each BTS, TS in each TRX */
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ }
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* FIXME: move this to libosmogsm */
+static const struct value_string gsm48_cmode_names[] = {
+ { GSM48_CMODE_SIGN, "signalling" },
+ { GSM48_CMODE_SPEECH_V1, "FR or HR" },
+ { GSM48_CMODE_SPEECH_EFR, "EFR" },
+ { GSM48_CMODE_SPEECH_AMR, "AMR" },
+ { GSM48_CMODE_DATA_14k5, "CSD(14k5)" },
+ { GSM48_CMODE_DATA_12k0, "CSD(12k0)" },
+ { GSM48_CMODE_DATA_6k0, "CSD(6k0)" },
+ { GSM48_CMODE_DATA_3k6, "CSD(3k6)" },
+ { 0, NULL }
+};
+
+/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots.
+ * Don't do anything if the ts is not dynamic. */
+static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+ vty_out(vty, " as %s",
+ gsm_pchan_name(ts->dyn.pchan_is));
+ else
+ vty_out(vty, " switching %s -> %s",
+ gsm_pchan_name(ts->dyn.pchan_is),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+ vty_out(vty, " as %s",
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F");
+ else
+ vty_out(vty, " switching %s -> %s",
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+ : "TCH/F");
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+}
+
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ struct in_addr ia;
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
+ /* show dyn TS details, if applicable */
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ vty_out(vty, " Osmocom Dyn TS:");
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ vty_out(vty, " IPACC Dyn PDCH TS:");
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+ vty_out(vty, " State: %s%s%s%s",
+ gsm_lchans_name(lchan->state),
+ lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "",
+ lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "",
+ VTY_NEWLINE);
+ vty_out(vty, " BS Power: %d dBm, MS Power: %u dBm%s",
+ lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
+ - lchan->bs_power*2,
+ ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+ VTY_NEWLINE);
+ vty_out(vty, " Channel Mode / Codec: %s%s",
+ get_value_string(gsm48_cmode_names, lchan->tch_mode),
+ VTY_NEWLINE);
+
+ if (lchan->abis_ip.bound_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+ vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s",
+ inet_ntoa(ia), lchan->abis_ip.bound_port,
+ lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
+ VTY_NEWLINE);
+ }
+ if (lchan->abis_ip.connect_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02u%s",
+ inet_ntoa(ia), lchan->abis_ip.connect_port,
+ lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode,
+ VTY_NEWLINE);
+ }
+#define LAPDM_ESTABLISHED(link, sapi_idx) \
+ (link).datalink[sapi_idx].dl.state == LAPD_STATE_MF_EST
+ vty_out(vty, " LAPDm SAPIs: DCCH %c%c, SACCH %c%c%s",
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI0) ? '0' : '-',
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI3) ? '3' : '-',
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI0) ? '0' : '-',
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI3) ? '3' : '-',
+ VTY_NEWLINE);
+#undef LAPDM_ESTABLISHED
+ vty_out(vty, " Valid System Information: 0x%08x%s",
+ lchan->si.valid, VTY_NEWLINE);
+ /* TODO: L1 SAPI (but those are BTS speific :( */
+ /* TODO: AMR bits */
+ vty_out(vty, " MS Timing Offset: %d, propagation delay: %d symbols %s",
+ lchan->ms_t_offs, lchan->p_offs, VTY_NEWLINE);
+ if (lchan->encr.alg_id) {
+ vty_out(vty, " Ciphering A5/%u State: %s, N(S)=%u%s",
+ lchan->encr.alg_id-1, lchan_ciph_state_name(lchan->ciph_state),
+ lchan->ciph_ns, VTY_NEWLINE);
+ }
+ if (lchan->loopback)
+ vty_out(vty, " RTP/PDCH Loopback Enabled%s", VTY_NEWLINE);
+ vty_out(vty, " Radio Link Failure Counter 'S': %d%s", lchan->s, VTY_NEWLINE);
+ /* TODO: MS Power Control */
+}
+
+static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ const struct gsm_meas_rep_unidir *mru = &lchan->meas.ul_res;
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ gsm_pchan_name(lchan->ts->pchan));
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, ", Lchan %u, Type %s, State %s - "
+ "RXL-FULL-ul: %4d dBm%s",
+ lchan->nr,
+ gsm_lchant_name(lchan->type), gsm_lchans_name(lchan->state),
+ rxlev2dbm(mru->full.rx_lev),
+ VTY_NEWLINE);
+}
+
+static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int lchan_nr;
+ for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) {
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+ if (lchan->state == LCHAN_S_NONE)
+ continue;
+ dump_cb(vty, lchan);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int ts_nr;
+
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ dump_lchan_trx_ts(ts, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int trx_nr;
+
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr);
+ dump_lchan_trx(trx, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int lchan_summary(struct vty *vty, int argc, const char **argv,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int bts_nr, trx_nr, ts_nr, lchan_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+
+ if (argc == 1)
+ return dump_lchan_bts(bts, vty, dump_cb);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX %s%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+
+ if (argc == 2)
+ return dump_lchan_trx(trx, vty, dump_cb);
+ }
+ if (argc >= 3) {
+ ts_nr = atoi(argv[2]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS %s%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ts = &trx->ts[ts_nr];
+
+ if (argc == 3)
+ return dump_lchan_trx_ts(ts, vty, dump_cb);
+ }
+ if (argc >= 4) {
+ lchan_nr = atoi(argv[3]);
+ if (lchan_nr >= TS_MAX_LCHAN) {
+ vty_out(vty, "%% can't find LCHAN %s%s", argv[3],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan = &ts->lchan[lchan_nr];
+ dump_cb(vty, lchan);
+ return CMD_SUCCESS;
+ }
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ dump_lchan_bts(bts, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_lchan,
+ show_lchan_cmd,
+ "show lchan [<0-255>] [<0-255>] [<0-7>] [<0-7>]",
+ SHOW_STR "Display information about a logical channel\n"
+ BTS_TRX_TS_LCHAN_STR)
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+ show_lchan_summary_cmd,
+ "show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]",
+ SHOW_STR "Display information about a logical channel\n"
+ "Short summary\n"
+ BTS_TRX_TS_LCHAN_STR)
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
+static struct gsm_lchan *resolve_lchan(struct gsm_network *net,
+ const char **argv, int idx)
+{
+ int bts_nr = atoi(argv[idx+0]);
+ int trx_nr = atoi(argv[idx+1]);
+ int ts_nr = atoi(argv[idx+2]);
+ int lchan_nr = atoi(argv[idx+3]);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+
+ bts = gsm_bts_num(net, bts_nr);
+ if (!bts)
+ return NULL;
+
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ if (!trx)
+ return NULL;
+
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+#define BTS_T_T_L_STR \
+ "BTS related commands\n" \
+ "BTS number\n" \
+ "TRX related commands\n" \
+ "TRX number\n" \
+ "timeslot related commands\n" \
+ "timeslot number\n" \
+ "logical channel commands\n" \
+ "logical channel number\n"
+
+DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd,
+ "HIDDEN", "HIDDEN")
+{
+ int sapi;
+
+ sapi = get_string_value(gsmtap_sapi_names, argv[0]);
+ OSMO_ASSERT(sapi >= 0);
+
+ if (sapi == GSMTAP_CHANNEL_ACCH)
+ gsmtap_sapi_acch = 1;
+ else
+ gsmtap_sapi_mask |= (1 << sapi);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd,
+ "HIDDEN", "HIDDEN")
+{
+ int sapi;
+
+ sapi = get_string_value(gsmtap_sapi_names, argv[0]);
+ OSMO_ASSERT(sapi >= 0);
+
+ if (sapi == GSMTAP_CHANNEL_ACCH)
+ gsmtap_sapi_acch = 0;
+ else
+ gsmtap_sapi_mask &= ~(1 << sapi);
+
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node phy_node = {
+ PHY_NODE,
+ "%s(phy)# ",
+ 1,
+};
+
+static struct cmd_node phy_inst_node = {
+ PHY_INST_NODE,
+ "%s(phy-inst)# ",
+ 1,
+};
+
+DEFUN(cfg_phy, cfg_phy_cmd,
+ "phy <0-255>",
+ "Select a PHY to configure\n" "PHY number\n")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_link *plink;
+
+ plink = phy_link_by_num(phy_nr);
+ if (!plink)
+ plink = phy_link_create(tall_bts_ctx, phy_nr);
+ if (!plink)
+ return CMD_WARNING;
+
+ vty->index = plink;
+ vty->index_sub = &plink->description;
+ vty->node = PHY_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_inst, cfg_phy_inst_cmd,
+ "instance <0-255>",
+ "Select a PHY instance to configure\n" "PHY Instance number\n")
+{
+ int inst_nr = atoi(argv[0]);
+ struct phy_link *plink = vty->index;
+ struct phy_instance *pinst;
+
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ pinst = phy_instance_create(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "Unable to create phy%u instance %u%s",
+ plink->num, inst_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ vty->index = pinst;
+ vty->index_sub = &pinst->description;
+ vty->node = PHY_INST_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd,
+ "no instance <0-255>"
+ NO_STR "Select a PHY instance to remove\n", "PHY Instance number\n")
+{
+ int inst_nr = atoi(argv[0]);
+ struct phy_link *plink = vty->index;
+ struct phy_instance *pinst;
+
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ phy_instance_destroy(pinst);
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_phy_type, cfg_phy_type_cmd,
+ "type (sysmobts|osmo-trx|virtual)",
+ "configure the type of the PHY\n"
+ "sysmocom sysmoBTS PHY\n"
+ "OsmoTRX based PHY\n"
+ "Virtual PHY (GSMTAP based)\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "sysmobts"))
+ plink->type = PHY_LINK_T_SYSMOBTS;
+ else if (!strcmp(argv[0], "osmo-trx"))
+ plink->type = PHY_LINK_T_OSMOTRX;
+ else if (!strcmp(argv[0], "virtual"))
+ plink->type = PHY_LINK_T_VIRTUAL;
+}
+#endif
+
+DEFUN(bts_t_t_l_jitter_buf,
+ bts_t_t_l_jitter_buf_cmd,
+ "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> rtp jitter-buffer <0-10000>",
+ BTS_T_T_L_STR "RTP settings\n"
+ "Jitter buffer\n" "Size of jitter buffer in (ms)\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_lchan *lchan;
+ int jitbuf_ms = atoi(argv[4]), rc;
+
+ lchan = resolve_lchan(net, argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!lchan->abis_ip.rtp_socket) {
+ vty_out(vty, "%% this channel has no active RTP stream%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket,
+ lchan->ts->trx->bts->rtp_jitter_adaptive ?
+ OSMO_RTP_P_JIT_ADAP : OSMO_RTP_P_JITBUF,
+ jitbuf_ms);
+ if (rc < 0)
+ vty_out(vty, "%% error setting jitter parameters: %s%s",
+ strerror(-rc), VTY_NEWLINE);
+ else
+ vty_out(vty, "%% jitter parameters set: %d%s", rc, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(bts_t_t_l_loopback,
+ bts_t_t_l_loopback_cmd,
+ "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback",
+ BTS_T_T_L_STR "Set loopback\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_lchan *lchan;
+
+ lchan = resolve_lchan(net, argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan->loopback = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_bts_t_t_l_loopback,
+ no_bts_t_t_l_loopback_cmd,
+ "no bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback",
+ NO_STR BTS_T_T_L_STR "Set loopback\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_lchan *lchan;
+
+ lchan = resolve_lchan(net, argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat)
+{
+ cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+ "gsmtap-sapi (",
+ "|",")", VTY_DO_LOWER);
+ cfg_trx_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+ "GSMTAP SAPI\n",
+ "\n", "", 0);
+
+ cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+ "no gsmtap-sapi (",
+ "|",")", VTY_DO_LOWER);
+ cfg_trx_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+ NO_STR "GSMTAP SAPI\n",
+ "\n", "", 0);
+
+ install_element_ve(&show_bts_cmd);
+ install_element_ve(&show_trx_cmd);
+ install_element_ve(&show_ts_cmd);
+ install_element_ve(&show_lchan_cmd);
+ install_element_ve(&show_lchan_summary_cmd);
+
+ logging_vty_add_cmds(cat);
+ osmo_talloc_vty_add_cmds();
+
+ install_node(&bts_node, config_write_bts);
+ install_element(CONFIG_NODE, &cfg_bts_cmd);
+ install_element(CONFIG_NODE, &cfg_vty_telnet_port_cmd);
+ install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_oml_ip_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_bind_ip_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_jitbuf_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_port_range_cmd);
+ install_element(BTS_NODE, &cfg_bts_band_cmd);
+ install_element(BTS_NODE, &cfg_description_cmd);
+ install_element(BTS_NODE, &cfg_no_description_cmd);
+ install_element(BTS_NODE, &cfg_bts_paging_queue_size_cmd);
+ install_element(BTS_NODE, &cfg_bts_paging_lifetime_cmd);
+ install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_default_cmd);
+ install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_params_cmd);
+ install_element(BTS_NODE, &cfg_bts_ul_power_target_cmd);
+ install_element(BTS_NODE, &cfg_bts_min_qual_rach_cmd);
+ install_element(BTS_NODE, &cfg_bts_min_qual_norm_cmd);
+ install_element(BTS_NODE, &cfg_bts_max_ber_rach_cmd);
+ install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd);
+ install_element(BTS_NODE, &cfg_bts_supp_meas_toa256_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_supp_meas_toa256_cmd);
+
+ install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd);
+ install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd);
+
+ /* add and link to TRX config node */
+ install_element(BTS_NODE, &cfg_bts_trx_cmd);
+ install_node(&trx_node, config_write_dummy);
+
+ install_element(TRX_NODE, &cfg_trx_user_gain_cmd);
+ install_element(TRX_NODE, &cfg_trx_pr_max_initial_cmd);
+ install_element(TRX_NODE, &cfg_trx_pr_step_size_cmd);
+ install_element(TRX_NODE, &cfg_trx_pr_step_interval_cmd);
+ install_element(TRX_NODE, &cfg_trx_ms_power_control_cmd);
+ install_element(TRX_NODE, &cfg_trx_phy_cmd);
+
+ install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd);
+ install_element(ENABLE_NODE, &bts_t_t_l_loopback_cmd);
+ install_element(ENABLE_NODE, &no_bts_t_t_l_loopback_cmd);
+
+ install_element(CONFIG_NODE, &cfg_phy_cmd);
+ install_node(&phy_node, config_write_phy);
+ install_element(PHY_NODE, &cfg_phy_inst_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_inst_cmd);
+
+ install_node(&phy_inst_node, config_write_dummy);
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/Makefile.am b/src/osmo-bts-litecell15/Makefile.am
new file mode 100644
index 00000000..0cc124ab
--- /dev/null
+++ b/src/osmo-bts-litecell15/Makefile.am
@@ -0,0 +1,38 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LITECELL15_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(LIBSYSTEMD_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS)
+
+AM_CFLAGS += -DENABLE_LC15BTS
+
+EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h misc/lc15bts_led.h \
+ misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \
+ misc/lc15bts_bid.h misc/lc15bts_nl.h misc/lc15bts_bts.h misc/lc15bts_swd.h \
+ hw_misc.h l1_if.h l1_transp.h lc15bts.h oml_router.h utils.h
+
+bin_PROGRAMS = osmo-bts-lc15 lc15bts-mgr lc15bts-util
+
+COMMON_SOURCES = main.c lc15bts.c l1_if.c oml.c lc15bts_vty.c tch.c hw_misc.c calib_file.c \
+ utils.c misc/lc15bts_par.c misc/lc15bts_bid.c oml_router.c
+
+osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+osmo_bts_lc15_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+lc15bts_mgr_SOURCES = \
+ misc/lc15bts_mgr.c misc/lc15bts_misc.c \
+ misc/lc15bts_par.c misc/lc15bts_nl.c \
+ misc/lc15bts_temp.c misc/lc15bts_power.c \
+ misc/lc15bts_clock.c misc/lc15bts_bid.c \
+ misc/lc15bts_mgr_vty.c \
+ misc/lc15bts_mgr_nl.c \
+ misc/lc15bts_mgr_temp.c \
+ misc/lc15bts_mgr_calib.c \
+ misc/lc15bts_led.c \
+ misc/lc15bts_bts.c \
+ misc/lc15bts_swd.c
+
+lc15bts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD)
+
+lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c
+lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-litecell15/calib_file.c b/src/osmo-bts-litecell15/calib_file.c
new file mode 100644
index 00000000..b7049df1
--- /dev/null
+++ b/src/osmo-bts-litecell15/calib_file.c
@@ -0,0 +1,456 @@
+/* NuRAN Wireless Litecell 1.5 BTS L1 calibration file routines*/
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+
+#include "l1_if.h"
+#include "lc15bts.h"
+#include "utils.h"
+
+/* Maximum calibration data chunk size */
+#define MAX_CALIB_TBL_SIZE 65536
+/* Calibration header version */
+#define CALIB_HDR_V1 0x01
+
+struct calib_file_desc {
+ const char *fname;
+ int rx;
+ int trx;
+ int rxpath;
+};
+
+static const struct calib_file_desc calib_files[] = {
+ {
+ .fname = "calib_rx0a.conf",
+ .rx = 1,
+ .trx = 0,
+ .rxpath = 0,
+ }, {
+ .fname = "calib_rx0b.conf",
+ .rx = 1,
+ .trx = 0,
+ .rxpath = 1,
+ }, {
+ .fname = "calib_rx1a.conf",
+ .rx = 1,
+ .trx = 1,
+ .rxpath = 0,
+ }, {
+ .fname = "calib_rx1b.conf",
+ .rx = 1,
+ .trx = 1,
+ .rxpath = 1,
+ }, {
+ .fname = "calib_tx0.conf",
+ .rx = 0,
+ .trx = 0,
+ }, {
+ .fname = "calib_tx1.conf",
+ .rx = 0,
+ .trx = 1,
+ },
+};
+
+struct calTbl_t
+{
+ union
+ {
+ struct
+ {
+ uint8_t u8Version; /* Header version (1) */
+ uint8_t u8Parity; /* Parity byte (xor) */
+ uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */
+ uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */
+ uint32_t u32Len; /* Table length in bytes including the header */
+ struct
+ {
+ uint32_t u32DescOfst; /* Description section offset */
+ uint32_t u32DateOfst; /* Date section offset */
+ uint32_t u32StationOfst; /* Calibration test station section offset */
+ uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */
+ uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */
+ uint32_t u32DataOfst; /* Calibration data section offset */
+ } toc;
+ } v1;
+ } hdr;
+
+ uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32];
+};
+
+
+static int calib_file_send(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc);
+static int calib_verify(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc);
+
+/* determine next calibration file index based on supported bands */
+static int get_next_calib_file_idx(struct lc15l1_hdl *fl1h, int last_idx)
+{
+ struct phy_link *plink = fl1h->phy_inst->phy_link;
+ int i;
+
+ for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
+ if (calib_files[i].trx == plink->num)
+ return i;
+ }
+ return -1;
+}
+
+static int calib_file_open(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.lc15.calib_path;
+ char fname[PATH_MAX];
+
+ if (st->fp) {
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n");
+ fclose(st->fp);
+ st->fp = NULL;
+ }
+
+ fname[0] = '\0';
+ snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname);
+ fname[sizeof(fname)-1] = '\0';
+
+ st->fp = fopen(fname, "rb");
+ if (!st->fp) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to open '%s' for calibration data.\n", fname);
+ return -1;
+ }
+ return 0;
+}
+
+static int calib_file_close(struct lc15l1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+
+ if (st->fp) {
+ fclose(st->fp);
+ st->fp = NULL;
+ }
+ return 0;
+}
+
+/* iteratively download the calibration data into the L1 */
+
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data);
+
+/* send a chunk of calibration tabledata for a single specified file */
+static int calib_file_send_next_chunk(struct lc15l1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+ Litecell15_Prim_t *prim;
+ struct msgb *msg;
+ size_t n;
+
+ msg = sysp_msgb_alloc();
+ prim = msgb_sysprim(msg);
+
+ prim->id = Litecell15_PrimId_SetCalibTblReq;
+ prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp);
+ n = fread(prim->u.setCalibTblReq.u8Data, 1,
+ sizeof(prim->u.setCalibTblReq.u8Data), st->fp);
+ prim->u.setCalibTblReq.length = n;
+
+
+ if (n == 0) {
+ /* The table data has been completely sent and acknowledged */
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n",
+ calib_files[st->last_file_idx].fname);
+
+ calib_file_close(fl1h);
+
+ msgb_free(msg);
+
+ /* Send the next one if any */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0) {
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
+}
+
+/* send the calibration table for a single specified file */
+static int calib_file_send(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ int rc;
+
+ rc = calib_file_open(fl1h, desc);
+ if (rc < 0) {
+ /* still, we'd like to continue trying to load
+ * calibration for all other bands */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ rc = calib_verify(fl1h, desc);
+ if ( rc < 0 ) {
+ LOGP(DL1C, LOGL_ERROR, "Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc);
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ return 0;
+
+ }
+
+ LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname);
+
+ return calib_file_send_next_chunk(fl1h);
+}
+
+/* completion callback after every SetCalibTbl is confirmed */
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct calib_send_state *st = &fl1h->st;
+ Litecell15_Prim_t *prim = msgb_sysprim(l1_msg);
+
+ if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n");
+
+ msgb_free(l1_msg);
+
+ calib_file_close(fl1h);
+
+ /* Skip this one and try the next one */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0) {
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ /* Keep sending the calibration file data */
+ return calib_file_send_next_chunk(fl1h);
+}
+
+int calib_load(struct lc15l1_hdl *fl1h)
+{
+ int rc;
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.lc15.calib_path;
+
+ if (!calib_path) {
+ LOGP(DL1C, LOGL_ERROR, "Calibration file path not specified\n");
+ return -1;
+ }
+
+ rc = get_next_calib_file_idx(fl1h, -1);
+ if (rc < 0) {
+ return -1;
+ }
+ st->last_file_idx = rc;
+
+ return calib_file_send(fl1h, &calib_files[st->last_file_idx]);
+}
+
+
+static int calib_verify(struct lc15l1_hdl *fl1h, const struct calib_file_desc *desc)
+{
+ int rc, sz;
+ struct calib_send_state *st = &fl1h->st;
+ struct phy_link *plink = fl1h->phy_inst->phy_link;
+ char *rbuf;
+ struct calTbl_t *calTbl;
+ char calChkSum ;
+
+ /* calculate file size in bytes */
+ fseek(st->fp, 0L, SEEK_END);
+ sz = ftell(st->fp);
+
+ /* rewind read poiner */
+ fseek(st->fp, 0L, SEEK_SET);
+
+ /* read file */
+ rbuf = (char *) malloc( sizeof(char) * sz );
+
+ rc = fread(rbuf, 1, sizeof(char) * sz, st->fp);
+ if ( rc != sz) {
+
+ LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname);
+ free(rbuf);
+
+ /* close file */
+ rc = calib_file_close(fl1h);
+ if (rc < 0 ) {
+ LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname);
+ return rc;
+ }
+
+ return -2;
+ }
+
+ calTbl = (struct calTbl_t*) rbuf;
+ /* calculate file checksum */
+ calChkSum = 0;
+ while ( sz-- ) {
+ calChkSum ^= rbuf[sz];
+ }
+
+ /* validate Tx calibration parity */
+ if ( calChkSum ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum);
+ return -4;
+ }
+
+ /* validate Tx calibration header */
+ if ( calTbl->hdr.v1.u8Version != CALIB_HDR_V1 ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version);
+ return -5;
+ }
+
+ /* validate calibration description */
+ if ( calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname);
+ return -6;
+ }
+
+ /* validate calibration date */
+ if ( calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname);
+ return -7;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n",
+ desc->fname,
+ calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst);
+
+ /* validate calibration station */
+ if ( calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname);
+ return -8;
+ }
+
+ /* validate FPGA FW version */
+ if ( calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFFFFFFF ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname);
+ return -9;
+ }
+
+ /* validate DSP FW version */
+ if ( calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname);
+ return -10;
+ }
+
+ /* validate Tx calibration data offset */
+ if ( calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF ) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname);
+ return -11;
+ }
+
+ if ( !desc->rx ) {
+
+ /* parse min/max Tx power */
+ fl1h->phy_inst->u.lc15.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)];
+ fl1h->phy_inst->u.lc15.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)];
+
+ /* override nominal Tx power of given TRX if needed */
+ if ( fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.lc15.maxTxPower) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
+ plink->num,
+ fl1h->phy_inst->u.lc15.maxTxPower,
+ fl1h->phy_inst->trx->nominal_power);
+
+ fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.maxTxPower;
+ }
+
+ if ( fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.lc15.minTxPower) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
+ plink->num,
+ fl1h->phy_inst->u.lc15.minTxPower,
+ fl1h->phy_inst->trx->nominal_power);
+
+ fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower;
+ }
+
+ if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.lc15.maxTxPower) ) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
+ plink->num,
+ to_mdB(fl1h->phy_inst->u.lc15.maxTxPower),
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm);
+
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.maxTxPower);
+ }
+
+ if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.lc15.minTxPower) ) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
+ plink->num,
+ to_mdB(fl1h->phy_inst->u.lc15.minTxPower),
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm);
+
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.minTxPower);
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n",
+ desc->fname,
+ fl1h->phy_inst->u.lc15.minTxPower,
+ fl1h->phy_inst->u.lc15.maxTxPower );
+ }
+
+ /* rewind read pointer for subsequence tasks */
+ fseek(st->fp, 0L, SEEK_SET);
+ free(rbuf);
+
+ return 0;
+}
+
diff --git a/src/osmo-bts-litecell15/hw_misc.c b/src/osmo-bts-litecell15/hw_misc.c
new file mode 100644
index 00000000..9f070bba
--- /dev/null
+++ b/src/osmo-bts-litecell15/hw_misc.c
@@ -0,0 +1,88 @@
+/* Misc HW routines for NuRAN Wireless Litecell 1.5 BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+
+#include "hw_misc.h"
+
+int lc15bts_led_set(enum lc15bts_led_color c)
+{
+ int fd, rc;
+ uint8_t cmd[2];
+
+ switch (c) {
+ case LED_OFF:
+ cmd[0] = 0;
+ cmd[1] = 0;
+ break;
+ case LED_RED:
+ cmd[0] = 1;
+ cmd[1] = 0;
+ break;
+ case LED_GREEN:
+ cmd[0] = 0;
+ cmd[1] = 1;
+ break;
+ case LED_ORANGE:
+ cmd[0] = 1;
+ cmd[1] = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fd = open("/var/lc15/leds/led0/brightness", O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ rc = write(fd, cmd[0] ? "1" : "0", 2);
+ if (rc != 2) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ fd = open("/var/lc15/leds/led1/brightness", O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ rc = write(fd, cmd[1] ? "1" : "0", 2);
+ if (rc != 2) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/hw_misc.h b/src/osmo-bts-litecell15/hw_misc.h
new file mode 100644
index 00000000..59ed04b7
--- /dev/null
+++ b/src/osmo-bts-litecell15/hw_misc.h
@@ -0,0 +1,13 @@
+#ifndef _HW_MISC_H
+#define _HW_MISC_H
+
+enum lc15bts_led_color {
+ LED_OFF,
+ LED_RED,
+ LED_GREEN,
+ LED_ORANGE,
+};
+
+int lc15bts_led_set(enum lc15bts_led_color c);
+
+#endif
diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-litecell15/l1_if.c
new file mode 100644
index 00000000..99852e39
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_if.c
@@ -0,0 +1,1586 @@
+/* Interface handler for NuRAN Wireless Litecell 1.5 L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "hw_misc.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_bid.h"
+#include "utils.h"
+
+extern unsigned int dsp_trace;
+
+struct wait_l1_conf {
+ struct llist_head list; /* internal linked list */
+ struct osmo_timer_list timer; /* timer for L1 timeout */
+ unsigned int conf_prim_id; /* primitive we expect in response */
+ HANDLE conf_hLayer3; /* layer 3 handle we expect in response */
+ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */
+ l1if_compl_cb *cb;
+ void *cb_data;
+};
+
+static void release_wlc(struct wait_l1_conf *wlc)
+{
+ osmo_timer_del(&wlc->timer);
+ talloc_free(wlc);
+}
+
+static void l1if_req_timeout(void *data)
+{
+ struct wait_l1_conf *wlc = data;
+
+ if (wlc->is_sys_prim)
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n",
+ get_value_string(lc15bts_sysprim_names, wlc->conf_prim_id));
+ else
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(lc15bts_l1prim_names, wlc->conf_prim_id));
+ exit(23);
+}
+
+static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitReq:
+ return prim->u.mphInitReq.hLayer3;
+ case GsmL1_PrimId_MphCloseReq:
+ return prim->u.mphCloseReq.hLayer3;
+ case GsmL1_PrimId_MphConnectReq:
+ return prim->u.mphConnectReq.hLayer3;
+ case GsmL1_PrimId_MphDisconnectReq:
+ return prim->u.mphDisconnectReq.hLayer3;
+ case GsmL1_PrimId_MphActivateReq:
+ return prim->u.mphActivateReq.hLayer3;
+ case GsmL1_PrimId_MphDeactivateReq:
+ return prim->u.mphDeactivateReq.hLayer3;
+ case GsmL1_PrimId_MphConfigReq:
+ return prim->u.mphConfigReq.hLayer3;
+ case GsmL1_PrimId_MphMeasureReq:
+ return prim->u.mphMeasureReq.hLayer3;
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.hLayer3;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.hLayer3;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.hLayer3;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.hLayer3;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.hLayer3;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.hLayer3;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.hLayer3;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.hLayer3;
+ case GsmL1_PrimId_MphTimeInd:
+ case GsmL1_PrimId_MphSyncInd:
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ case GsmL1_PrimId_PhDataReq:
+ case GsmL1_PrimId_PhConnectInd:
+ case GsmL1_PrimId_PhReadyToSendInd:
+ case GsmL1_PrimId_PhDataInd:
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id);
+ break;
+ }
+ return 0;
+}
+
+static int _l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ int is_system_prim, l1if_compl_cb *cb, void *data)
+{
+ struct wait_l1_conf *wlc;
+ struct osmo_wqueue *wqueue;
+ unsigned int timeout_secs;
+
+ /* allocate new wsc and store reference to mutex and conf_id */
+ wlc = talloc_zero(fl1h, struct wait_l1_conf);
+ wlc->cb = cb;
+ wlc->cb_data = data;
+
+ /* Make sure we actually have received a REQUEST type primitive */
+ if (is_system_prim == 0) {
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+
+ LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id));
+
+ if (lc15bts_get_l1prim_type(l1p->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 0;
+ wlc->conf_prim_id = lc15bts_get_l1prim_conf(l1p->id);
+ wlc->conf_hLayer3 = l1p_get_hLayer3(l1p);
+ wqueue = &fl1h->write_q[MQ_L1_WRITE];
+ timeout_secs = 30;
+ } else {
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id));
+
+ if (lc15bts_get_sysprim_type(sysp->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 1;
+ wlc->conf_prim_id = lc15bts_get_sysprim_conf(sysp->id);
+ wqueue = &fl1h->write_q[MQ_SYS_WRITE];
+ timeout_secs = 30;
+ }
+
+ /* enqueue the message in the queue and add wsc to list */
+ if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
+ /* So we will get a timeout but the log message might help */
+ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n",
+ is_system_prim ? "system primitive" : "gsm");
+ msgb_free(msg);
+ }
+ llist_add(&wlc->list, &fl1h->wlc_list);
+
+ /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */
+ wlc->timer.data = wlc;
+ wlc->timer.cb = l1if_req_timeout;
+ osmo_timer_schedule(&wlc->timer, timeout_secs, 0);
+
+ return 0;
+}
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 1, cb, data);
+}
+
+int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 0, cb, data);
+}
+
+/* allocate a msgb containing a GsmL1_Prim_t */
+struct msgb *l1p_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
+
+ return msg;
+}
+
+/* allocate a msgb containing a Litecell15_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(Litecell15_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(Litecell15_Prim_t));
+
+ return msg;
+}
+
+static GsmL1_PhDataReq_t *
+data_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = rts_ind->hLayer1;
+ data_req->u8Tn = rts_ind->u8Tn;
+ data_req->u32Fn = rts_ind->u32Fn;
+ data_req->sapi = rts_ind->sapi;
+ data_req->subCh = rts_ind->subCh;
+ data_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return data_req;
+}
+
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = rts_ind->hLayer1;
+ empty_req->u8Tn = rts_ind->u8Tn;
+ empty_req->u32Fn = rts_ind->u32Fn;
+ empty_req->sapi = rts_ind->sapi;
+ empty_req->subCh = rts_ind->subCh;
+ empty_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return empty_req;
+}
+
+/* fill PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+data_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr, uint8_t len)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ data_req->u8Tn = tn;
+ data_req->u32Fn = fn;
+ data_req->sapi = sapi;
+ data_req->subCh = sub_ch;
+ data_req->u8BlockNbr = block_nr;
+
+ data_req->msgUnitParam.u8Size = len;
+
+ return data_req;
+}
+
+/* fill PH-EMPTY_FRAME.req from l1sap primitive */
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi,
+ uint8_t subch, uint8_t block_nr)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ empty_req->u8Tn = tn;
+ empty_req->u32Fn = fn;
+ empty_req->sapi = sapi;
+ empty_req->subCh = subch;
+ empty_req->u8BlockNbr = block_nr;
+
+ return empty_req;
+}
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+ struct msgb *l1msg = l1p_msgb_alloc();
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+
+ if (!msg) {
+ LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n");
+ abort();
+ }
+
+ len = msgb_l2len(msg);
+
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ u32Fn = l1sap->u.data.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ subCh = 0x1f;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ sapi = GsmL1_Sapi_Sacch;
+ if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (ts_is_pdch(&trx->ts[u8Tn])) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = GsmL1_Sapi_Ptcch;
+ u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn);
+ } else {
+ sapi = GsmL1_Sapi_Pdtch;
+ u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn);
+ }
+ } else {
+ sapi = GsmL1_Sapi_FacchF;
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_FacchH;
+ u8BlockNbr = (u32Fn % 26) >> 3;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Bcch;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Cbch;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ u8BlockNbr = l1sap_fn2ccch_block(u32Fn);
+ if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ"))
+ sapi = GsmL1_Sapi_Pch;
+ else
+ sapi = GsmL1_Sapi_Agch;
+ } else {
+ LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d "
+ "chan_nr %d link_id %d\n", l1sap->oph.primitive,
+ l1sap->oph.operation, chan_nr, link_id);
+ msgb_free(l1msg);
+ return -EINVAL;
+ }
+
+ /* convert l1sap message to GsmL1 primitive, keep payload */
+ if (len) {
+ /* data request */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len);
+ if (use_cache)
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ lchan->tch.dtx.facch, msgb_l2len(msg));
+ else if (dtx_dl_amr_enabled(lchan) &&
+ ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) {
+ if (sapi == GsmL1_Sapi_FacchF) {
+ sapi = GsmL1_Sapi_TchF;
+ }
+ if (sapi == GsmL1_Sapi_FacchH) {
+ sapi = GsmL1_Sapi_TchH;
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) {
+ /* FACCH interruption of DTX silence */
+ /* cache FACCH data */
+ memcpy(lchan->tch.dtx.facch, msg->l2h,
+ msgb_l2len(msg));
+ /* prepare ONSET or INH message */
+ if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_Onset;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidUpdateInH;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidFirstInH;
+ /* ignored CMR/CMI pair */
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0;
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0;
+ /* update length */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi,
+ subCh, u8BlockNbr, 3);
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ } else if (dtx_dl_amr_enabled(lchan) &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ else {
+ OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer));
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h,
+ msgb_l2len(msg));
+ }
+ LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n",
+ osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ l1p->u.phDataReq.msgUnitParam.u8Size));
+ } else {
+ /* empty frame */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) {
+ LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(l1msg);
+ } else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan))
+ ph_data_req(trx, msg, l1sap, true);
+ return 0;
+}
+
+static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi;
+ uint8_t chan_nr;
+ GsmL1_Prim_t *l1p;
+ struct msgb *nmsg = NULL;
+ int rc = -1;
+
+ chan_nr = l1sap->u.tch.chan_nr;
+ u32Fn = l1sap->u.tch.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_TchH;
+ } else {
+ subCh = 0x1f;
+ sapi = GsmL1_Sapi_TchF;
+ }
+
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ /* create new message and fill data */
+ if (msg) {
+ msgb_pull(msg, sizeof(*l1sap));
+ /* create new message */
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ l1p = msgb_l1prim(nmsg);
+ rc = l1if_tch_encode(lchan,
+ l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ &l1p->u.phDataReq.msgUnitParam.u8Size,
+ msg->data, msg->len, u32Fn, use_cache,
+ l1sap->u.tch.marker);
+ if (rc < 0) {
+ /* no data encoded for L1: smth will be generated below */
+ msgb_free(nmsg);
+ nmsg = NULL;
+ }
+ }
+
+ /* no message/data, we might generate an empty traffic msg or re-send
+ cached SID in case of DTX */
+ if (!nmsg)
+ nmsg = gen_empty_tch_msg(lchan, u32Fn);
+
+ /* no traffic message, we generate an empty msg */
+ if (!nmsg) {
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ }
+
+ l1p = msgb_l1prim(nmsg);
+
+ /* if we provide data, or if data is already in nmsg */
+ if (l1p->u.phDataReq.msgUnitParam.u8Size) {
+ /* data request */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh,
+ u8BlockNbr,
+ l1p->u.phDataReq.msgUnitParam.u8Size);
+ } else {
+ /* empty frame */
+ if (trx->bts->dtxd && trx != trx->bts->c0)
+ lchan->tch.dtx.dl_active = true;
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+ /* send message to DSP's queue */
+ osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg);
+ if (dtx_is_first_p1(lchan))
+ dtx_dispatch(lchan, E_FIRST);
+ else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */
+ return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);
+
+ return 0;
+}
+
+static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+ uint8_t chan_nr;
+ struct gsm_lchan *lchan;
+ int rc = 0;
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink) {
+ l1if_set_ciphering(fl1, lchan, 0);
+ lchan->ciph_state = LCHAN_CIPH_RX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink) {
+ l1if_set_ciphering(fl1, lchan, 1);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink
+ && l1sap->u.info.u.ciph_req.uplink)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
+ l1if_rsl_chan_act(lchan);
+ else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ if (lchan->ho.active == HANDOVER_WAIT_FRAME)
+ l1if_rsl_chan_mod(lchan);
+ else
+ l1if_rsl_mode_modify(lchan);
+ } else if (l1sap->u.info.u.act_req.sacch_only)
+ l1if_rsl_deact_sacch(lchan);
+ else
+ l1if_rsl_chan_rel(lchan);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ /* called functions MUST NOT take ownership of msgb, as it is
+ * free()d below */
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ rc = ph_data_req(trx, msg, l1sap, false);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ rc = mph_info_req(trx, msg, l1sap);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_mph_time_ind(struct lc15l1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind,
+ struct msgb *msg)
+{
+ struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct osmo_phsap_prim l1sap;
+ uint32_t fn;
+
+ /* increment the primitive count for the alive timer */
+ fl1->alive_prim_cnt++;
+
+ /* ignore every time indication, except for c0 */
+ if (trx != bts->c0) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ fn = time_ind->u32Fn;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_TIME;
+ l1sap.u.info.u.time_ind.fn = fn;
+
+ msgb_free(msg);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ return GSM_PCHAN_TCH_F;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ default:
+ return ts->pchan;
+ }
+}
+
+static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
+ GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ enum gsm_phys_chan_config pchan = pick_pchan(ts);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ switch (sapi) {
+ case GsmL1_Sapi_Bcch:
+ cbits = 0x10;
+ break;
+ case GsmL1_Sapi_Cbch:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ break;
+ case GsmL1_Sapi_Sacch:
+ switch(pchan) {
+ case GSM_PCHAN_TCH_F:
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ cbits = 0x02 + subCh;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Sdcch:
+ switch(pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Agch:
+ case GsmL1_Sapi_Pch:
+ cbits = 0x12;
+ break;
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_TchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_TchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_FacchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_FacchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ if (!L1SAP_IS_PTCCH(u32Fn)) {
+ LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
+ "number other than 12, got it at %u (%u). "
+ "Please fix!\n", u32Fn % 52, u32Fn);
+ abort();
+ }
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ /* not reached due to default case above */
+ return (cbits << 3) | u8Tn;
+}
+
+static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1,
+ GsmL1_PhReadyToSendInd_t *rts_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct msgb *resp_msg;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ struct gsm_time g_time;
+ uint32_t t3p;
+ int rc;
+ struct osmo_phsap_prim *l1sap;
+ uint8_t chan_nr, link_id;
+ uint32_t fn;
+
+ /* check if primitive should be handled by common part */
+ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi,
+ rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn);
+ if (chan_nr) {
+ fn = rts_ind->u32Fn;
+ if (rts_ind->sapi == GsmL1_Sapi_Sacch)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+ /* recycle the msgb and use it for the L1 primitive,
+ * which means that we (or our caller) must not free it */
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ if (rts_ind->sapi == GsmL1_Sapi_TchF
+ || rts_ind->sapi == GsmL1_Sapi_TchH) {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ } else {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ }
+
+ return l1sap_up(trx, l1sap);
+ }
+
+ gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
+ get_value_string(lc15bts_l1sapi_names, rts_ind->sapi));
+
+ /* in all other cases, we need to allocate a new PH-DATA.ind
+ * primitive msgb and start to fill it */
+ resp_msg = l1p_msgb_alloc();
+ data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+ msu_param = &data_req->msgUnitParam;
+
+ /* set default size */
+ msu_param->u8Size = GSM_MACBLOCK_LEN;
+
+ switch (rts_ind->sapi) {
+ case GsmL1_Sapi_Sch:
+ /* compute T3prime */
+ t3p = (g_time.t3 - 1) / 10;
+ /* fill SCH burst with data */
+ msu_param->u8Size = 4;
+ msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9);
+ msu_param->u8Buffer[1] = (g_time.t1 >> 1);
+ msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
+ msu_param->u8Buffer[3] = (t3p & 1);
+ break;
+ case GsmL1_Sapi_Prach:
+ goto empty_frame;
+ break;
+ default:
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ break;
+ }
+tx:
+
+ /* transmit */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) {
+ LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(resp_msg);
+ }
+
+ /* free the msgb, as we have not handed it to l1sap and thus
+ * need to release its memory */
+ msgb_free(l1p_msg);
+ return 0;
+
+empty_frame:
+ /* in case we decide to send an empty frame... */
+ empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+
+ goto tx;
+}
+
+static void dump_meas_res(int ll, GsmL1_MeasParam_t *m)
+{
+ LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, "
+ "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality,
+ m->fBer, m->i16BurstTiming);
+}
+
+static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ GsmL1_MeasParam_t *m, uint32_t fn)
+{
+ struct osmo_phsap_prim l1sap;
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_MEAS;
+ l1sap.u.info.u.meas_ind.chan_nr = chan_nr;
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64;
+ l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000);
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1);
+ l1sap.u.info.u.meas_ind.fn = fn;
+
+ /* l1sap wants to take msgb ownership. However, as there is no
+ * msg, it will msgb_free(l1sap.oph.msg == NULL) */
+ return l1sap_up(trx, &l1sap);
+}
+
+static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
+ uint8_t chan_nr, link_id;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ struct gsm_time g_time;
+ uint8_t *data, len;
+ int rc = 0;
+ int8_t rssi;
+
+ chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi,
+ data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn);
+ fn = data_ind->u32Fn;
+ link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC;
+ gsm_fn2gsmtime(&g_time, fn);
+
+ if (!chan_nr) {
+ LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n",
+ get_value_string(lc15bts_l1sapi_names, data_ind->sapi), data_ind->sapi);
+ msgb_free(l1p_msg);
+ return ENOTSUP;
+ }
+
+ process_meas_res(trx, chan_nr, &data_ind->measParam, fn);
+
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+ get_value_string(lc15bts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2,
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size));
+ dump_meas_res(LOGL_DEBUG, &data_ind->measParam);
+
+ /* check for TCH */
+ if (data_ind->sapi == GsmL1_Sapi_TchF
+ || data_ind->sapi == GsmL1_Sapi_TchH) {
+ /* TCH speech frame handling */
+ rc = l1if_tch_rx(trx, chan_nr, l1p_msg);
+ msgb_free(l1p_msg);
+ return rc;
+ }
+
+ /* get rssi */
+ rssi = (int8_t) (data_ind->measParam.fRssi);
+ /* get data pointer and length */
+ data = data_ind->msgUnitParam.u8Buffer;
+ len = data_ind->msgUnitParam.u8Size;
+ /* pull lower header part before data */
+ msgb_pull(l1p_msg, data - l1p_msg->data);
+ /* trim remaining data to it's size, to get rid of upper header part */
+ rc = msgb_trim(l1p_msg, len);
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1p_msg->l2h = l1p_msg->data;
+ /* push new l1 header */
+ l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap));
+ /* fill header */
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = rssi;
+ if (!pcu_direct) {
+ l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000;
+ l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64;
+ l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10;
+ }
+ return l1sap_up(trx, l1sap);
+}
+
+static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_lchan *lchan;
+ struct osmo_phsap_prim *l1sap;
+ int rc;
+ struct ph_rach_ind_param rach_ind_param;
+
+ /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */
+ if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) {
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+
+ if ((ra_ind->msgUnitParam.u8Size != 1) &&
+ (ra_ind->msgUnitParam.u8Size != 2)) {
+ LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
+ rach_ind_param = (struct ph_rach_ind_param) {
+ /* .chan_nr set below */
+ /* .ra set below */
+ .acc_delay = 0,
+ .fn = ra_ind->u32Fn,
+ /* .is_11bit set below */
+ /* .burst_type set below */
+ .rssi = (int8_t) ra_ind->measParam.fRssi,
+ .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0),
+ .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64,
+ };
+
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2);
+ if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ rach_ind_param.chan_nr = 0x88;
+ else
+ rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);
+
+ if (ra_ind->msgUnitParam.u8Size == 2) {
+ uint16_t temp;
+ uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0];
+ ra = ra << 3;
+ temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7);
+ ra = ra | temp;
+ rach_ind_param.is_11bit = 1;
+ rach_ind_param.ra = ra;
+ } else {
+ rach_ind_param.is_11bit = 0;
+ rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0];
+ }
+
+ /* the old legacy full-bits acc_delay cannot express negative values */
+ if (ra_ind->measParam.i16BurstTiming > 0)
+ rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+ /* mapping of the burst type, the values are specific to
+ * osmo-bts-litecell15 */
+ switch (ra_ind->burstType) {
+ case GsmL1_BurstType_Access_0:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_0;
+ break;
+ case GsmL1_BurstType_Access_1:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_1;
+ break;
+ case GsmL1_BurstType_Access_2:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_2;
+ break;
+ default:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_NONE;
+ break;
+ }
+
+ /* msgb_trim() invalidates ra_ind, make that abundantly clear: */
+ ra_ind = NULL;
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ l1p_msg);
+ l1sap->u.rach_ind = rach_ind_param;
+
+ return l1sap_up(trx, l1sap);
+}
+
+/* handle any random indication from the L1 */
+static int l1if_handle_ind(struct lc15l1_hdl *fl1, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ int rc = 0;
+
+ /* all the below called functions must take ownership of the msgb */
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg);
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd,
+ msg);
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg);
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg);
+ break;
+ default:
+ msgb_free(msg);
+ }
+
+ return rc;
+}
+
+static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc)
+{
+ if (wlc->is_sys_prim != 0)
+ return 0;
+ if (l1p->id != wlc->conf_prim_id)
+ return 0;
+ if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3)
+ return 0;
+ return 1;
+}
+
+int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ /* silent, don't clog the log file */
+ break;
+ default:
+ LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id), wq);
+ }
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (is_prim_compat(l1p, wlc)) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id));
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ /* the limitation here is that we cannot have multiple callers
+ * sending the same primitive */
+ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+ unsigned int i;
+
+ if (sysp->id == Litecell15_PrimId_ActivateRfCnf)
+ on = 1;
+
+ if (on)
+ status = sysp->u.activateRfCnf.status;
+ else
+ status = sysp->u.deactivateRfCnf.status;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
+ get_value_string(lc15bts_l1status_names, status));
+
+
+ if (on) {
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-ACT failure");
+ } else
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 1);
+
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+ } else {
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+/* activate or de-activate the entire RF-Frontend */
+int l1if_activate_rf(struct lc15l1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ if (on) {
+ sysp->id = Litecell15_PrimId_ActivateRfReq;
+ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;
+
+ sysp->u.activateRfReq.u8UnusedTsMode = 0;
+ sysp->u.activateRfReq.u8McCorrMode = 0;
+
+ /* maximum cell size in quarter-bits, 90 == 12.456 km */
+ sysp->u.activateRfReq.u8MaxCellSize = 90;
+ } else {
+ sysp->id = Litecell15_PrimId_DeactivateRfReq;
+ }
+
+ return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL);
+}
+
+static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
+ struct gsm_lchan *lchan = &ts->lchan[i];
+
+ if (!is_muted)
+ continue;
+
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+
+ /* skip channels that might be active for another reason */
+ if (lchan->type == GSM_LCHAN_CCCH)
+ continue;
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ continue;
+
+ if (lchan->s <= 0)
+ continue;
+
+ lchan->s = 0;
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ }
+}
+
+static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0);
+ } else {
+ int i;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1);
+
+ osmo_static_assert(
+ ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute),
+ ts_array_size);
+
+ for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i)
+ mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+/* mute/unmute RF time slots */
+int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n",
+ mute[0], mute[1], mute[2], mute[3],
+ mute[4], mute[5], mute[6], mute[7]
+ );
+
+ sysp->id = Litecell15_PrimId_MuteRfReq;
+ memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute));
+ /* save for later use */
+ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute));
+
+ return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL);
+}
+
+/* call-back on arrival of DSP+FPGA version + band capability */
+static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ Litecell15_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ int rc;
+
+ fl1h->hw_info.dsp_version[0] = sic->dspVersion.major;
+ fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor;
+ fl1h->hw_info.dsp_version[2] = sic->dspVersion.build;
+
+ fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major;
+ fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor;
+ fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build;
+
+ LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n",
+ sic->dspVersion.major, sic->dspVersion.minor,
+ sic->dspVersion.build, sic->fpgaVersion.major,
+ sic->fpgaVersion.minor, sic->fpgaVersion.build);
+
+ if (!(fl1h->hw_info.band_support & trx->bts->band))
+ LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
+ gsm_band_name(trx->bts->band));
+
+ /* Request the activation */
+ l1if_activate_rf(fl1h, 1);
+
+ /* load calibration tables */
+ rc = calib_load(fl1h);
+ if (rc < 0)
+ LOGP(DL1C, LOGL_ERROR, "Operating without calibration; "
+ "unable to load tables!\n");
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* request DSP+FPGA code versions */
+static int l1if_get_info(struct lc15l1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = Litecell15_PrimId_SystemInfoReq;
+
+ return l1if_req_compl(hdl, msg, info_compl_cb, NULL);
+}
+
+static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status = sysp->u.layer1ResetCnf.status;
+
+ LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n",
+ get_value_string(lc15bts_l1status_names, status));
+
+ msgb_free(resp);
+
+ /* If we're coming out of reset .. */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_shutdown(trx->bts, "L1-RESET failure");
+ }
+
+ /* as we cannot get the current DSP trace flags, we simply
+ * set them to zero (or whatever dsp_trace_f has been initialized to */
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f);
+
+ /* obtain version information on DSP/FPGA and band capabilities */
+ l1if_get_info(fl1h);
+
+ return 0;
+}
+
+int l1if_reset(struct lc15l1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = Litecell15_PrimId_Layer1ResetReq;
+
+ return l1if_req_compl(hdl, msg, reset_compl_cb, NULL);
+}
+
+/* set the trace flags within the DSP */
+int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
+ flags);
+
+ sysp->id = Litecell15_PrimId_SetTraceFlagsReq;
+ sysp->u.setTraceFlagsReq.u32Tf = flags;
+
+ hdl->dsp_trace_f = flags;
+
+ /* There is no confirmation we could wait for */
+ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int get_hwinfo(struct lc15l1_hdl *fl1h)
+{
+ int rc;
+
+ rc = lc15bts_rev_get();
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS revision: %d\n", rc);
+ return rc;
+ }
+ fl1h->hw_info.ver_major = rc;
+
+ rc = lc15bts_model_get();
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS model: %d\n", rc);
+ return rc;
+ }
+ fl1h->hw_info.ver_minor = rc;
+
+ rc = lc15bts_option_get(LC15BTS_OPTION_BAND);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS_OPTION_BAND: %d\n", rc);
+ return rc;
+ }
+
+ switch (rc) {
+ case LC15BTS_BAND_850:
+ fl1h->hw_info.band_support = GSM_BAND_850;
+ break;
+ case LC15BTS_BAND_900:
+ fl1h->hw_info.band_support = GSM_BAND_900;
+ break;
+ case LC15BTS_BAND_1800:
+ fl1h->hw_info.band_support = GSM_BAND_1800;
+ break;
+ case LC15BTS_BAND_1900:
+ fl1h->hw_info.band_support = GSM_BAND_1900;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "Unexpected LC15BTS_BAND value: %d\n", rc);
+ return -1;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "BTS hw support band %s\n", gsm_band_name(fl1h->hw_info.band_support));
+
+ return 0;
+}
+
+struct lc15l1_hdl *l1if_open(struct phy_instance *pinst)
+{
+ struct lc15l1_hdl *fl1h;
+ int rc;
+
+ LOGP(DL1C, LOGL_INFO, "Litecell 1.5 BTS L1IF compiled against API headers "
+ "v%u.%u.%u\n", LITECELL15_API_VERSION >> 16,
+ (LITECELL15_API_VERSION >> 8) & 0xff,
+ LITECELL15_API_VERSION & 0xff);
+
+ fl1h = talloc_zero(pinst, struct lc15l1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->phy_inst = pinst;
+ fl1h->dsp_trace_f = pinst->u.lc15.dsp_trace_f;
+
+ get_hwinfo(fl1h);
+
+ rc = l1if_transport_open(MQ_SYS_WRITE, fl1h);
+ if (rc < 0) {
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ rc = l1if_transport_open(MQ_L1_WRITE, fl1h);
+ if (rc < 0) {
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ return fl1h;
+}
+
+int l1if_close(struct lc15l1_hdl *fl1h)
+{
+ l1if_transport_close(MQ_L1_WRITE, fl1h);
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ return 0;
+}
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst = phy_instance_by_num(plink, 0);
+
+ OSMO_ASSERT(pinst);
+
+ if (!pinst->trx) {
+ LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d "
+ "because no TRX is associated with it\n", plink->num, pinst->num);
+ return 0;
+ }
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ pinst->u.lc15.hdl = l1if_open(pinst);
+ if (!pinst->u.lc15.hdl) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n");
+ return -EIO;
+ }
+
+
+ struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl;
+ fl1h->dsp_trace_f = dsp_trace;
+
+ l1if_reset(pinst->u.lc15.hdl);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-litecell15/l1_if.h
new file mode 100644
index 00000000..aac26075
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_if.h
@@ -0,0 +1,134 @@
+#ifndef _L1_IF_H
+#define _L1_IF_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/phy_link.h>
+
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include <stdbool.h>
+
+enum {
+ MQ_SYS_READ,
+ MQ_L1_READ,
+ MQ_TCH_READ,
+ MQ_PDTCH_READ,
+ _NUM_MQ_READ
+};
+
+enum {
+ MQ_SYS_WRITE,
+ MQ_L1_WRITE,
+ MQ_TCH_WRITE,
+ MQ_PDTCH_WRITE,
+ _NUM_MQ_WRITE
+};
+
+struct calib_send_state {
+ FILE *fp;
+ const char *path;
+ int last_file_idx;
+};
+
+struct lc15l1_hdl {
+ struct gsm_time gsm_time;
+ HANDLE hLayer1; /* handle to the L1 instance in the DSP */
+ uint32_t dsp_trace_f; /* currently operational DSP trace flags */
+ struct llist_head wlc_list;
+
+ struct phy_instance *phy_inst;
+
+ struct osmo_timer_list alive_timer;
+ unsigned int alive_prim_cnt;
+
+ struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
+ struct osmo_wqueue write_q[_NUM_MQ_WRITE];
+
+ struct {
+ /* from DSP/FPGA after L1 Init */
+ uint8_t dsp_version[3];
+ uint8_t fpga_version[3];
+ uint32_t band_support;
+ uint8_t ver_major;
+ uint8_t ver_minor;
+ } hw_info;
+
+ struct calib_send_state st;
+
+ uint8_t last_rf_mute[8];
+};
+
+#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg) ((Litecell15_Prim_t *)(msg)->l1h)
+
+typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+
+struct lc15l1_hdl *l1if_open(struct phy_instance *pinst);
+int l1if_close(struct lc15l1_hdl *hdl);
+int l1if_reset(struct lc15l1_hdl *hdl);
+int l1if_activate_rf(struct lc15l1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power);
+int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
+
+struct msgb *l1p_msgb_alloc(void);
+struct msgb *sysp_msgb_alloc(void);
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
+struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
+
+/* tch.c */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker);
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
+int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
+
+/* ciphering */
+int l1if_set_ciphering(struct lc15l1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink);
+
+/* channel control */
+int l1if_rsl_chan_act(struct gsm_lchan *lchan);
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
+
+/* calibration loading */
+int calib_load(struct lc15l1_hdl *fl1h);
+
+/* public helpers for test */
+int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan);
+int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target,
+ const uint8_t ms_power, const float rxLevel);
+
+static inline struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ OSMO_ASSERT(pinst);
+ return pinst->u.lc15.hdl;
+}
+
+static inline struct gsm_bts_trx *lc15l1_hdl_trx(struct lc15l1_hdl *fl1h)
+{
+ OSMO_ASSERT(fl1h->phy_inst);
+ return fl1h->phy_inst->trx;
+}
+
+#endif /* _L1_IF_H */
diff --git a/src/osmo-bts-litecell15/l1_transp.h b/src/osmo-bts-litecell15/l1_transp.h
new file mode 100644
index 00000000..7d6772e8
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_transp.h
@@ -0,0 +1,14 @@
+#ifndef _L1_TRANSP_H
+#define _L1_TRANSP_H
+
+#include <osmocom/core/msgb.h>
+
+/* functions a transport calls on arrival of primitive from BTS */
+int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg);
+
+/* functions exported by a transport */
+int l1if_transport_open(int q, struct lc15l1_hdl *fl1h);
+int l1if_transport_close(int q, struct lc15l1_hdl *fl1h);
+
+#endif /* _L1_TRANSP_H */
diff --git a/src/osmo-bts-litecell15/l1_transp_hw.c b/src/osmo-bts-litecell15/l1_transp_hw.c
new file mode 100644
index 00000000..c8972be5
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_transp_hw.c
@@ -0,0 +1,326 @@
+/* Interface handler for Nuran Wireless Litecell 1.5 L1 (real hardware) */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/litecell15_dsp2arm_trx"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/litecell15_arm2dsp_trx"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx"
+
+#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx"
+#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx"
+#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx"
+#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx"
+
+static const char *rd_devnames[] = {
+ [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
+ [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
+ [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
+ [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
+};
+
+static const char *wr_devnames[] = {
+ [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
+ [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
+ [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
+ [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
+};
+
+/*
+ * Make sure that all structs we read fit into the LC15BTS_PRIM_SIZE
+ */
+osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, l1_prim)
+osmo_static_assert(sizeof(Litecell15_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, super_prim)
+
+static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct osmo_wqueue *queue;
+
+ queue = container_of(fd, struct osmo_wqueue, bfd);
+
+ if (what & BSC_FD_READ)
+ queue->read_cb(fd);
+
+ if (what & BSC_FD_EXCEPT)
+ queue->except_cb(fd);
+
+ if (what & BSC_FD_WRITE) {
+ struct iovec iov[5];
+ struct msgb *msg, *tmp;
+ int written, count = 0;
+
+ fd->when &= ~BSC_FD_WRITE;
+
+ llist_for_each_entry(msg, &queue->msg_queue, list) {
+ /* more writes than we have */
+ if (count >= ARRAY_SIZE(iov))
+ break;
+
+ iov[count].iov_base = msg->l1h;
+ iov[count].iov_len = msgb_l1len(msg);
+ count += 1;
+ }
+
+ /* TODO: check if all lengths are the same. */
+
+
+ /* Nothing scheduled? This should not happen. */
+ if (count == 0) {
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ written = writev(fd->fd, iov, count);
+ if (written < 0) {
+ /* nothing written?! */
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ /* now delete the written entries */
+ written = written / iov[0].iov_len;
+ count = 0;
+ llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) {
+ queue->current_length -= 1;
+
+ llist_del(&msg->list);
+ msgb_free(msg);
+
+ count += 1;
+ if (count >= written)
+ break;
+ }
+
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ }
+
+ return 0;
+}
+
+static int prim_size_for_queue(int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return sizeof(Litecell15_Prim_t);
+ case MQ_L1_WRITE:
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+ return sizeof(GsmL1_Prim_t);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+}
+
+/* callback when there's something to read from the l1 msg_queue */
+static int read_dispatch_one(struct lc15l1_hdl *fl1h, struct msgb *msg, int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return l1if_handle_sysprim(fl1h, msg);
+ case MQ_L1_WRITE:
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+ return l1if_handle_l1prim(queue, fl1h, msg);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+};
+
+static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int i, rc;
+
+ const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr);
+ uint32_t count;
+
+ struct iovec iov[3];
+ struct msgb *msg[ARRAY_SIZE(iov)];
+
+ for (i = 0; i < ARRAY_SIZE(iov); ++i) {
+ msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd");
+ msg[i]->l1h = msg[i]->data;
+
+ iov[i].iov_base = msg[i]->l1h;
+ iov[i].iov_len = msgb_tailroom(msg[i]);
+ }
+
+ rc = readv(ofd->fd, iov, ARRAY_SIZE(iov));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno));
+ /* N. B: we do not abort to let the cycle below cleanup allocated memory properly,
+ the return value is ignored by the caller anyway.
+ TODO: use libexplain's explain_readv() to provide detailed error description */
+ count = 0;
+ } else
+ count = rc / prim_size;
+
+ for (i = 0; i < count; ++i) {
+ msgb_put(msg[i], prim_size);
+ read_dispatch_one(ofd->data, msg[i], ofd->priv_nr);
+ }
+
+ for (i = count; i < ARRAY_SIZE(iov); ++i)
+ msgb_free(msg[i]);
+
+ return 1;
+}
+
+/* callback when we can write to one of the l1 msg_queue devices */
+static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msg->len) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msg->len);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int l1if_transport_open(int q, struct lc15l1_hdl *hdl)
+{
+ struct phy_link *plink = hdl->phy_inst->phy_link;
+ int rc;
+ char buf[PATH_MAX];
+
+ /* Step 1: Open all msg_queue file descriptors */
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_wqueue *wq = &hdl->write_q[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num);
+ buf[sizeof(buf)-1] = '\0';
+
+ rc = open(buf, O_RDONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
+ buf, strerror(errno));
+ return rc;
+ }
+ read_ofd->fd = rc;
+ read_ofd->priv_nr = q;
+ read_ofd->data = hdl;
+ read_ofd->cb = l1if_fd_cb;
+ read_ofd->when = BSC_FD_READ;
+ rc = osmo_fd_register(read_ofd);
+ if (rc < 0) {
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+ return rc;
+ }
+
+ snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num);
+ buf[sizeof(buf)-1] = '\0';
+
+ rc = open(buf, O_WRONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
+ buf, strerror(errno));
+ goto out_read;
+ }
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = l1fd_write_cb;
+ write_ofd->cb = wqueue_vector_cb;
+ write_ofd->fd = rc;
+ write_ofd->priv_nr = q;
+ write_ofd->data = hdl;
+ write_ofd->when = BSC_FD_WRITE;
+ rc = osmo_fd_register(write_ofd);
+ if (rc < 0) {
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+ goto out_read;
+ }
+
+ return 0;
+
+out_read:
+ close(hdl->read_ofd[q].fd);
+ osmo_fd_unregister(&hdl->read_ofd[q]);
+
+ return rc;
+}
+
+int l1if_transport_close(int q, struct lc15l1_hdl *hdl)
+{
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ osmo_fd_unregister(read_ofd);
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+
+ osmo_fd_unregister(write_ofd);
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-litecell15/lc15bts.c
new file mode 100644
index 00000000..172a7e45
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts.c
@@ -0,0 +1,332 @@
+/* NuRAN Wireless Litecell 1.5 L1 API related definitions */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ * sysmobts.c
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1dbg.h>
+
+#include "lc15bts.h"
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id)
+{
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ;
+ case GsmL1_PrimId_PhDataReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphTimeInd: return L1P_T_IND;
+ case GsmL1_PrimId_MphSyncInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhConnectInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhDataInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhRaInd: return L1P_T_IND;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1] = {
+ { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
+ { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
+ { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
+ { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
+ { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
+ { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
+ { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
+ { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
+ { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
+ { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
+ { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
+ { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
+ { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
+ { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
+ { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
+ { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
+ { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
+ { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
+ { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
+ { GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
+ { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
+ { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
+ { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
+ { GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
+ { 0, NULL }
+};
+
+GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id)
+{
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf;
+ case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf;
+ case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf;
+ case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf;
+ case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf;
+ case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf;
+ case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf;
+ case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf;
+ default: return -1; // Weak
+ }
+}
+
+enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id)
+{
+ switch (id) {
+ case Litecell15_PrimId_SystemInfoReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SystemInfoCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SystemFailureInd: return L1P_T_IND;
+ case Litecell15_PrimId_ActivateRfReq: return L1P_T_REQ;
+ case Litecell15_PrimId_ActivateRfCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_DeactivateRfReq: return L1P_T_REQ;
+ case Litecell15_PrimId_DeactivateRfCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
+ case Litecell15_PrimId_Layer1ResetReq: return L1P_T_REQ;
+ case Litecell15_PrimId_Layer1ResetCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetCalibTblReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SetCalibTblCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_MuteRfReq: return L1P_T_REQ;
+ case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = {
+ { Litecell15_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
+ { Litecell15_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
+ { Litecell15_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
+ { Litecell15_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
+ { Litecell15_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
+ { Litecell15_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
+ { Litecell15_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
+ { Litecell15_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
+ { Litecell15_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
+ { Litecell15_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
+ { Litecell15_PrimId_SetCalibTblReq, "SET-CALIB.req" },
+ { Litecell15_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" },
+ { Litecell15_PrimId_MuteRfReq, "MUTE-RF.req" },
+ { Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
+ { Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
+ { Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
+ { 0, NULL }
+};
+
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id)
+{
+ switch (id) {
+ case Litecell15_PrimId_SystemInfoReq: return Litecell15_PrimId_SystemInfoCnf;
+ case Litecell15_PrimId_ActivateRfReq: return Litecell15_PrimId_ActivateRfCnf;
+ case Litecell15_PrimId_DeactivateRfReq: return Litecell15_PrimId_DeactivateRfCnf;
+ case Litecell15_PrimId_Layer1ResetReq: return Litecell15_PrimId_Layer1ResetCnf;
+ case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf;
+ case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf;
+ case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf;
+ default: return -1; // Weak
+ }
+}
+
+const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Idle, "IDLE" },
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1] = {
+ { GsmL1_Status_Success, "Success" },
+ { GsmL1_Status_Generic, "Generic error" },
+ { GsmL1_Status_NoMemory, "Not enough memory" },
+ { GsmL1_Status_Timeout, "Timeout" },
+ { GsmL1_Status_InvalidParam, "Invalid parameter" },
+ { GsmL1_Status_Busy, "Resource busy" },
+ { GsmL1_Status_NoRessource, "No more resources" },
+ { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
+ { GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
+ { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
+ { GsmL1_Status_BadCrc, "Bad CRC" },
+ { GsmL1_Status_BadUsf, "Bad USF" },
+ { GsmL1_Status_InvalidCPS, "Invalid CPS field" },
+ { GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
+ { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
+ { GsmL1_Status_CriticalError, "Critical error" },
+ { GsmL1_Status_OverheatError, "Overheat error" },
+ { GsmL1_Status_DeviceError, "Device error" },
+ { GsmL1_Status_FacchError, "FACCH / TCH order error" },
+ { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
+ { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
+ { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
+ { GsmL1_Status_NotSynchronized, "Not synchronized" },
+ { GsmL1_Status_Unsupported, "Unsupported feature" },
+ { GsmL1_Status_ClockError, "System clock error" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_tracef_names[29] = {
+ { DBG_DEBUG, "DEBUG" },
+ { DBG_L1WARNING, "L1_WARNING" },
+ { DBG_ERROR, "ERROR" },
+ { DBG_L1RXMSG, "L1_RX_MSG" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
+ { DBG_L1TXMSG, "L1_TX_MSG" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
+ { DBG_MPHCNF, "MPH_CNF" },
+ { DBG_MPHIND, "MPH_IND" },
+ { DBG_MPHREQ, "MPH_REQ" },
+ { DBG_PHIND, "PH_IND" },
+ { DBG_PHREQ, "PH_REQ" },
+ { DBG_PHYRF, "PHY_RF" },
+ { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
+ { DBG_MODE, "MODE" },
+ { DBG_TDMAINFO, "TDMA_INFO" },
+ { DBG_BADCRC, "BAD_CRC" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "DEVICE_MSG" },
+ { DBG_RACHINFO, "RACH_INFO" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "MEMORY" },
+ { DBG_PROFILING, "PROFILING" },
+ { DBG_TESTCOMMENT, "TEST_COMMENT" },
+ { DBG_TEST, "TEST" },
+ { DBG_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_tracef_docs[29] = {
+ { DBG_DEBUG, "Debug Region" },
+ { DBG_L1WARNING, "L1 Warning Region" },
+ { DBG_ERROR, "Error Region" },
+ { DBG_L1RXMSG, "L1_RX_MSG Region" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
+ { DBG_L1TXMSG, "L1_TX_MSG Region" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
+ { DBG_MPHCNF, "MphConfirmation Region" },
+ { DBG_MPHIND, "MphIndication Region" },
+ { DBG_MPHREQ, "MphRequest Region" },
+ { DBG_PHIND, "PhIndication Region" },
+ { DBG_PHREQ, "PhRequest Region" },
+ { DBG_PHYRF, "PhyRF Region" },
+ { DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
+ { DBG_MODE, "Mode Region" },
+ { DBG_TDMAINFO, "TDMA Info Region" },
+ { DBG_BADCRC, "Bad CRC Region" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "Device Message Region" },
+ { DBG_RACHINFO, "RACH Info" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "Memory Region" },
+ { DBG_PROFILING, "Profiling Region" },
+ { DBG_TESTCOMMENT, "Test Comments" },
+ { DBG_TEST, "Test Region" },
+ { DBG_STATUS, "Status Region" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_tch_pl_names[] = {
+ { GsmL1_TchPlType_NA, "N/A" },
+ { GsmL1_TchPlType_Fr, "FR" },
+ { GsmL1_TchPlType_Hr, "HR" },
+ { GsmL1_TchPlType_Efr, "EFR" },
+ { GsmL1_TchPlType_Amr, "AMR(IF2)" },
+ { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
+ { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
+ { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
+ { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
+ { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
+ { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
+ { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
+ { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
+ { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_dir_names[] = {
+ { GsmL1_Dir_TxDownlink, "TxDL" },
+ { GsmL1_Dir_TxUplink, "TxUL" },
+ { GsmL1_Dir_RxUplink, "RxUL" },
+ { GsmL1_Dir_RxDownlink, "RxDL" },
+ { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_chcomb_names[] = {
+ { GsmL1_LogChComb_0, "dummy" },
+ { GsmL1_LogChComb_I, "tch_f" },
+ { GsmL1_LogChComb_II, "tch_h" },
+ { GsmL1_LogChComb_IV, "ccch" },
+ { GsmL1_LogChComb_V, "ccch_sdcch4" },
+ { GsmL1_LogChComb_VII, "sdcch8" },
+ { GsmL1_LogChComb_XIII, "pdtch" },
+ { 0, NULL }
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
+ [PDCH_CS_1] = 23,
+ [PDCH_CS_2] = 34,
+ [PDCH_CS_3] = 40,
+ [PDCH_CS_4] = 54,
+ [PDCH_MCS_1] = 27,
+ [PDCH_MCS_2] = 33,
+ [PDCH_MCS_3] = 42,
+ [PDCH_MCS_4] = 49,
+ [PDCH_MCS_5] = 60,
+ [PDCH_MCS_6] = 78,
+ [PDCH_MCS_7] = 118,
+ [PDCH_MCS_8] = 142,
+ [PDCH_MCS_9] = 154
+};
diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h
new file mode 100644
index 00000000..4c40db0f
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts.h
@@ -0,0 +1,64 @@
+#ifndef LC15BTS_H
+#define LC15BTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define LC15BTS_PRIM_SIZE \
+ (OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
+
+enum l1prim_type {
+ L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
+ L1P_T_REQ,
+ L1P_T_CONF,
+ L1P_T_IND,
+};
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id);
+const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1];
+GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id);
+
+enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id);
+const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1];
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id);
+
+const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string lc15bts_tracef_names[29];
+const struct value_string lc15bts_tracef_docs[29];
+
+const struct value_string lc15bts_tch_pl_names[15];
+
+const struct value_string lc15bts_clksrc_names[10];
+
+const struct value_string lc15bts_dir_names[6];
+
+enum pdch_cs {
+ PDCH_CS_1,
+ PDCH_CS_2,
+ PDCH_CS_3,
+ PDCH_CS_4,
+ PDCH_MCS_1,
+ PDCH_MCS_2,
+ PDCH_MCS_3,
+ PDCH_MCS_4,
+ PDCH_MCS_5,
+ PDCH_MCS_6,
+ PDCH_MCS_7,
+ PDCH_MCS_8,
+ PDCH_MCS_9,
+ _NUM_PDCH_CS
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+#endif /* LC15BTS_H */
diff --git a/src/osmo-bts-litecell15/lc15bts_vty.c b/src/osmo-bts-litecell15/lc15bts_vty.c
new file mode 100644
index 00000000..d0edc886
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts_vty.c
@@ -0,0 +1,417 @@
+/* VTY interface for NuRAN Wireless Litecell 1.5 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012,2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/rsl.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+extern int lchan_activate(struct gsm_lchan *lchan);
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+#define DSP_TRACE_F_STR "DSP Trace Flag\n"
+
+static struct gsm_bts *vty_bts;
+
+/* configuration */
+
+DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd,
+ "trx-calibration-path PATH",
+ "Set the path name to TRX calibration data\n" "Path name\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ if (pinst->u.lc15.calib_path)
+ talloc_free(pinst->u.lc15.calib_path);
+
+ pinst->u.lc15.calib_path = talloc_strdup(pinst, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
+ "HIDDEN", TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ pinst->u.lc15.dsp_trace_f |= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
+ "HIDDEN", NO_STR TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ pinst->u.lc15.dsp_trace_f &= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+
+/* runtime */
+
+DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
+ "show trx <0-0> dsp-trace-flags",
+ SHOW_TRX_STR "Display the current setting of the DSP trace flags")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct lc15l1_hdl *fl1h;
+ int i;
+
+ if (!trx)
+ return CMD_WARNING;
+
+ fl1h = trx_lc15l1_hdl(trx);
+
+ vty_out(vty, "Litecell15 L1 DSP trace flags:%s", VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(lc15bts_tracef_names); i++) {
+ const char *endis;
+
+ if (lc15bts_tracef_names[i].value == 0 &&
+ lc15bts_tracef_names[i].str == NULL)
+ break;
+
+ if (fl1h->dsp_trace_f & lc15bts_tracef_names[i].value)
+ endis = "enabled";
+ else
+ endis = "disabled";
+
+ vty_out(vty, "DSP Trace %-15s %s%s",
+ lc15bts_tracef_names[i].str, endis,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct lc15l1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.lc15.hdl;
+ flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct lc15l1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.lc15.hdl;
+ flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sys_info, show_sys_info_cmd,
+ "show phy <0-1> instance <0-0> system-information",
+ SHOW_TRX_STR "Display information about system\n")
+{
+ int phy_nr = atoi(argv[0]);
+ int inst_nr = atoi(argv[1]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ struct phy_instance *pinst;
+ struct lc15l1_hdl *fl1h;
+ int i;
+
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY link %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY instance %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ fl1h = pinst->u.lc15.hdl;
+
+ vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
+ fl1h->hw_info.dsp_version[0],
+ fl1h->hw_info.dsp_version[1],
+ fl1h->hw_info.dsp_version[2],
+ fl1h->hw_info.fpga_version[0],
+ fl1h->hw_info.fpga_version[1],
+ fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
+
+ vty_out(vty, "GSM Band Support: ");
+ for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
+ if (fl1h->hw_info.band_support & (1 << i))
+ vty_out(vty, "%s ", gsm_band_name(1 << i));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.minTxPower, VTY_NEWLINE);
+ vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.maxTxPower, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(activate_lchan, activate_lchan_cmd,
+ "trx <0-0> <0-7> (activate|deactivate) <0-7>",
+ TRX_STR
+ "Timeslot number\n"
+ "Activate Logical Channel\n"
+ "Deactivate Logical Channel\n"
+ "Logical Channel Number\n" )
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[3]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ if (!strcmp(argv[2], "activate"))
+ lchan_activate(lchan);
+ else
+ lchan_deactivate(lchan);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_tx_power, set_tx_power_cmd,
+ "trx nr <0-1> tx-power <-110-100>",
+ TRX_STR
+ "TRX number \n"
+ "Set transmit power (override BSC)\n"
+ "Transmit power in dBm\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int power = atoi(argv[1]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+
+ power_ramp_start(trx, to_mdB(power), 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(loopback, loopback_cmd,
+ "trx <0-0> <0-7> loopback <0-1>",
+ TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_loopback, no_loopback_cmd,
+ "no trx <0-0> <0-7> loopback <0-1>",
+ NO_STR TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
+ "nominal-tx-power <0-40>",
+ "Set the nominal transmit output power in dBm\n"
+ "Nominal transmit output power level in dBm\n")
+{
+ int nominal_power = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+
+ if (( nominal_power > 40 ) || ( nominal_power < 0 )) {
+ vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s",
+ nominal_power, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ trx->nominal_power = nominal_power;
+ trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power);
+
+ return CMD_SUCCESS;
+}
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE);
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ if (pinst->u.lc15.dsp_trace_f & (1 << i)) {
+ const char *name;
+ name = get_value_string(lc15bts_tracef_names, (1 << i));
+ vty_out(vty, " dsp-trace-flag %s%s", name,
+ VTY_NEWLINE);
+ }
+ }
+ if (pinst->u.lc15.calib_path)
+ vty_out(vty, " trx-calibration-path %s%s",
+ pinst->u.lc15.calib_path, VTY_NEWLINE);
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ /* runtime-patch the command strings with debug levels */
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names,
+ "phy <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs,
+ TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names,
+ "no phy <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs,
+ NO_STR TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ lc15bts_tracef_names,
+ "dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ lc15bts_tracef_docs,
+ DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ lc15bts_tracef_names,
+ "no dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ lc15bts_tracef_docs,
+ NO_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ install_element_ve(&show_dsp_trace_f_cmd);
+ install_element_ve(&show_sys_info_cmd);
+ install_element_ve(&dsp_trace_f_cmd);
+ install_element_ve(&no_dsp_trace_f_cmd);
+
+ install_element(ENABLE_NODE, &activate_lchan_cmd);
+ install_element(ENABLE_NODE, &set_tx_power_cmd);
+
+ install_element(ENABLE_NODE, &loopback_cmd);
+ install_element(ENABLE_NODE, &no_loopback_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+
+ install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd);
+
+ return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/main.c b/src/osmo-bts-litecell15/main.c
new file mode 100644
index 00000000..e4f9a567
--- /dev/null
+++ b/src/osmo-bts-litecell15/main.c
@@ -0,0 +1,207 @@
+/* Main program for NuRAN Wireless Litecell 1.5 BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+
+/*NTQD: Change how rx_nr is handle in multi-trx*/
+#define LC15BTS_RF_LOCK_PATH "/var/lock/bts_rf_lock"
+
+#include "utils.h"
+#include "l1_if.h"
+#include "hw_misc.h"
+#include "oml_router.h"
+#include "misc/lc15bts_bid.h"
+
+unsigned int dsp_trace = 0x00000000;
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ struct stat st;
+ static struct osmo_fd accept_fd, read_fd;
+ int rc;
+
+ bts->variant = BTS_OSMO_LITECELL15;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
+ if (rc < 0) {
+ fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
+ OML_ROUTER_PATH, rc);
+ exit(1);
+ }
+
+ if (stat(LC15BTS_RF_LOCK_PATH, &st) == 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
+ exit(23);
+ }
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_EGPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ trx->nominal_power = 40;
+ trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power);
+ return 0;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+void bts_update_status(enum bts_global_status which, int on)
+{
+ static uint64_t states = 0;
+ uint64_t old_states = states;
+ int led_rf_active_on;
+
+ if (on)
+ states |= (1ULL << which);
+ else
+ states &= ~(1ULL << which);
+
+ led_rf_active_on =
+ (states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
+ !(states & (1ULL << BTS_STATUS_RF_MUTE));
+
+ LOGP(DL1C, LOGL_INFO,
+ "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
+ which, on,
+ (long long)old_states, (long long)states,
+ led_rf_active_on);
+
+ lc15bts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF);
+}
+
+void bts_model_print_help()
+{
+ printf( " -w --hw-version Print the targeted HW Version\n"
+ " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n"
+ " -p --dsp-trace Set DSP trace flags\n"
+ );
+}
+
+static void print_hwversion()
+{
+ printf(get_hwversion_desc());
+ printf("\n");
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "dsp-trace", 1, 0, 'p' },
+ { "hw-version", 0, 0, 'w' },
+ { "pcu-direct", 0, 0, 'M' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "p:wM",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'p':
+ dsp_trace = strtoul(optarg, NULL, 16);
+ break;
+ case 'M':
+ pcu_direct = 1;
+ break;
+ case 'w':
+ print_hwversion();
+ exit(0);
+ break;
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.c b/src/osmo-bts-litecell15/misc/lc15bts_bid.c
new file mode 100644
index 00000000..9284b62e
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.c
@@ -0,0 +1,162 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#include "lc15bts_bid.h"
+
+#define BOARD_REV_SYSFS "/var/lc15/platform/revision"
+#define BOARD_OPT_SYSFS "/var/lc15/platform/option"
+
+static const int option_type_mask[_NUM_OPTION_TYPES] = {
+ [LC15BTS_OPTION_OCXO] = 0x07,
+ [LC15BTS_OPTION_FPGA] = 0x03,
+ [LC15BTS_OPTION_PA] = 0x01,
+ [LC15BTS_OPTION_BAND] = 0x03,
+ [LC15BTS_OPTION_TX_ISO_BYP] = 0x01,
+ [LC15BTS_OPTION_RX_DUP_BYP] = 0x01,
+ [LC15BTS_OPTION_RX_PB_BYP] = 0x01,
+ [LC15BTS_OPTION_RX_DIV] = 0x01,
+ [LC15BTS_OPTION_RX1A] = 0x01,
+ [LC15BTS_OPTION_RX1B] = 0x01,
+ [LC15BTS_OPTION_RX2A] = 0x01,
+ [LC15BTS_OPTION_RX2B] = 0x01,
+ [LC15BTS_OPTION_DDR_32B] = 0x01,
+ [LC15BTS_OPTION_DDR_ECC] = 0x01,
+ [LC15BTS_OPTION_LOG_DET] = 0x01,
+ [LC15BTS_OPTION_DUAL_LOG_DET] = 0x01,
+};
+
+static const int option_type_shift[_NUM_OPTION_TYPES] = {
+ [LC15BTS_OPTION_OCXO] = 0,
+ [LC15BTS_OPTION_FPGA] = 3,
+ [LC15BTS_OPTION_PA] = 5,
+ [LC15BTS_OPTION_BAND] = 6,
+ [LC15BTS_OPTION_TX_ISO_BYP] = 8,
+ [LC15BTS_OPTION_RX_DUP_BYP] = 9,
+ [LC15BTS_OPTION_RX_PB_BYP] = 10,
+ [LC15BTS_OPTION_RX_DIV] = 11,
+ [LC15BTS_OPTION_RX1A] = 12,
+ [LC15BTS_OPTION_RX1B] = 13,
+ [LC15BTS_OPTION_RX2A] = 14,
+ [LC15BTS_OPTION_RX2B] = 15,
+ [LC15BTS_OPTION_DDR_32B] = 16,
+ [LC15BTS_OPTION_DDR_ECC] = 17,
+ [LC15BTS_OPTION_LOG_DET] = 18,
+ [LC15BTS_OPTION_DUAL_LOG_DET] = 19,
+};
+
+
+static int board_rev = -1;
+static int board_option = -1;
+
+static inline bool read_board(const char *src, const char *spec, void *dst)
+{
+ FILE *fp = fopen(src, "r");
+ if (!fp) {
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", src, strerror(errno));
+ return false;
+ }
+
+ if (fscanf(fp, spec, dst) != 1) {
+ fclose(fp);
+ fprintf(stderr, "Failed to read %s due to '%s' error\n", src, strerror(errno));
+ return false;
+ }
+ fclose(fp);
+ return true;
+}
+
+int lc15bts_rev_get(void)
+{
+ char rev;
+
+ if (board_rev != -1) {
+ return board_rev;
+ }
+
+ if (!read_board(BOARD_REV_SYSFS, "%c", &rev))
+ return -1;
+
+ board_rev = rev;
+ return board_rev;
+}
+
+int lc15bts_model_get(void)
+{
+ int opt;
+
+ if (board_option != -1)
+ return board_option;
+
+ if (!read_board(BOARD_OPT_SYSFS, "%X", &opt))
+ return -1;
+
+ board_option = opt;
+ return board_option;
+}
+
+int lc15bts_option_get(enum lc15bts_option_type type)
+{
+ int rc;
+ int option;
+
+ if (type >= _NUM_OPTION_TYPES) {
+ return -EINVAL;
+ }
+
+ if (board_option == -1) {
+ rc = lc15bts_model_get();
+ if (rc < 0) return rc;
+ }
+
+ option = (board_option >> option_type_shift[type])
+ & option_type_mask[type];
+
+ return option;
+}
+
+const char* get_hwversion_desc()
+{
+ int rev;
+ int model;
+ size_t len;
+ static char model_name[64] = {0, };
+ len = snprintf(model_name, sizeof(model_name), "NuRAN Litecell 1.5 BTS");
+
+ rev = lc15bts_rev_get();
+ if (rev >= 0) {
+ len += snprintf(model_name + len, sizeof(model_name) - len,
+ " Rev %c", (char)rev);
+ }
+
+ model = lc15bts_model_get();
+ if (model >= 0) {
+ snprintf(model_name + len, sizeof(model_name) - len,
+ "%s (%05X)", model_name, model);
+ }
+ return model_name;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.h b/src/osmo-bts-litecell15/misc/lc15bts_bid.h
new file mode 100644
index 00000000..a71fdd7e
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.h
@@ -0,0 +1,52 @@
+#ifndef _LC15BTS_BOARD_H
+#define _LC15BTS_BOARD_H
+
+#include <stdint.h>
+
+enum lc15bts_option_type {
+ LC15BTS_OPTION_OCXO,
+ LC15BTS_OPTION_FPGA,
+ LC15BTS_OPTION_PA,
+ LC15BTS_OPTION_BAND,
+ LC15BTS_OPTION_TX_ISO_BYP,
+ LC15BTS_OPTION_RX_DUP_BYP,
+ LC15BTS_OPTION_RX_PB_BYP,
+ LC15BTS_OPTION_RX_DIV,
+ LC15BTS_OPTION_RX1A,
+ LC15BTS_OPTION_RX1B,
+ LC15BTS_OPTION_RX2A,
+ LC15BTS_OPTION_RX2B,
+ LC15BTS_OPTION_DDR_32B,
+ LC15BTS_OPTION_DDR_ECC,
+ LC15BTS_OPTION_LOG_DET,
+ LC15BTS_OPTION_DUAL_LOG_DET,
+ _NUM_OPTION_TYPES
+};
+
+enum lc15bts_ocxo_type {
+ LC15BTS_OCXO_BILAY_NVG45AV2072,
+ LC15BTS_OCXO_TAITIEN_NJ26M003,
+ _NUM_OCXO_TYPES
+};
+
+enum lc15bts_fpga_type {
+ LC15BTS_FPGA_35T,
+ LC15BTS_FPGA_50T,
+ LC15BTS_FPGA_75T,
+ LC15BTS_FPGA_100T,
+ _NUM_FPGA_TYPES
+};
+
+enum lc15bts_gsm_band {
+ LC15BTS_BAND_850,
+ LC15BTS_BAND_900,
+ LC15BTS_BAND_1800,
+ LC15BTS_BAND_1900,
+};
+
+int lc15bts_rev_get(void);
+int lc15bts_model_get(void);
+int lc15bts_option_get(enum lc15bts_option_type type);
+const char* get_hwversion_desc();
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.c b/src/osmo-bts-litecell15/misc/lc15bts_bts.c
new file mode 100644
index 00000000..0343e930
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_bts.c
@@ -0,0 +1,131 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include "lc15bts_mgr.h"
+#include "lc15bts_bts.h"
+
+static int check_eth_status(char *dev_name)
+{
+ int fd, rc;
+ struct ifreq ifr;
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (fd < 0)
+ return fd;
+
+ memset(&ifr, 0, sizeof(ifr));
+ memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name));
+ rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
+ close(fd);
+
+ if (rc < 0)
+ return rc;
+
+ if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING))
+ return 0;
+
+ return 1;
+}
+
+void check_bts_led_pattern(uint8_t *led)
+{
+ FILE *fp;
+ char str[64] = "\0";
+ int rc;
+
+ /* check for existing of BTS state file */
+ if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) {
+ led[BLINK_PATTERN_INIT] = 1;
+ return;
+ }
+
+ /* check Ethernet interface status */
+ rc = check_eth_status("eth0");
+ if (rc > 0) {
+ LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n");
+ led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1;
+ fclose(fp);
+ return;
+ }
+
+ /* check for BTS is still alive */
+ if (system("pidof osmo-bts-lc15 > /dev/null")) {
+ LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n");
+ led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
+ fclose(fp);
+ return;
+ }
+
+ /* check for BTS state */
+ while (fgets(str, 64, fp) != NULL) {
+ LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP");
+ if (strstr(str, "ABIS DOWN") != NULL)
+ led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
+ }
+ fclose(fp);
+
+ return;
+}
+
+int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led)
+{
+ if(mgr->alarms.temp_high == 1)
+ led[BLINK_PATTERN_TEMP_HIGH] = 1;
+
+ if(mgr->alarms.temp_max == 1)
+ led[BLINK_PATTERN_TEMP_MAX] = 1;
+
+ if(mgr->alarms.supply_low == 1)
+ led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1;
+
+ if(mgr->alarms.supply_min == 1)
+ led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1;
+
+ if(mgr->alarms.vswr_high == 1)
+ led[BLINK_PATTERN_VSWR_HIGH] = 1;
+
+ if(mgr->alarms.vswr_max == 1)
+ led[BLINK_PATTERN_VSWR_MAX] = 1;
+
+ if(mgr->alarms.supply_pwr_high == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1;
+
+ if(mgr->alarms.supply_pwr_max == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1;
+
+ if(mgr->alarms.supply_pwr_max2 == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_MAX2] = 1;
+
+ if(mgr->alarms.pa_pwr_high == 1)
+ led[BLINK_PATTERN_PA_PWR_HIGH] = 1;
+
+ if(mgr->alarms.pa_pwr_max == 1)
+ led[BLINK_PATTERN_PA_PWR_MAX] = 1;
+
+ if(mgr->alarms.gps_fix_lost == 1)
+ led[BLINK_PATTERN_GPS_FIX_LOST] = 1;
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.h b/src/osmo-bts-litecell15/misc/lc15bts_bts.h
new file mode 100644
index 00000000..3918b870
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_bts.h
@@ -0,0 +1,21 @@
+#ifndef _LC15BTS_BTS_H_
+#define _LC15BTS_BTS_H_
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <osmo-bts/logging.h>
+
+/* public function prototypes */
+void check_bts_led_pattern(uint8_t *led);
+int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.c b/src/osmo-bts-litecell15/misc/lc15bts_clock.c
new file mode 100644
index 00000000..71701496
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.c
@@ -0,0 +1,260 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "lc15bts_clock.h"
+
+#define CLKERR_ERR_SYSFS "/var/lc15/clkerr/clkerr1_average"
+#define CLKERR_ACC_SYSFS "/var/lc15/clkerr/clkerr1_average_accuracy"
+#define CLKERR_INT_SYSFS "/var/lc15/clkerr/clkerr1_average_interval"
+#define CLKERR_FLT_SYSFS "/var/lc15/clkerr/clkerr1_fault"
+#define CLKERR_RFS_SYSFS "/var/lc15/clkerr/refresh"
+#define CLKERR_RST_SYSFS "/var/lc15/clkerr/reset"
+
+#define OCXODAC_VAL_SYSFS "/var/lc15/ocxo/voltage"
+#define OCXODAC_ROM_SYSFS "/var/lc15/ocxo/eeprom"
+
+/* clock error */
+static int clkerr_fd_err = -1;
+static int clkerr_fd_accuracy = -1;
+static int clkerr_fd_interval = -1;
+static int clkerr_fd_fault = -1;
+static int clkerr_fd_refresh = -1;
+static int clkerr_fd_reset = -1;
+
+/* ocxo dac */
+static int ocxodac_fd_value = -1;
+static int ocxodac_fd_save = -1;
+
+
+static int sysfs_read_val(int fd, int *val)
+{
+ int rc;
+ char szVal[32] = {0};
+
+ lseek( fd, 0, SEEK_SET );
+
+ rc = read(fd, szVal, sizeof(szVal) - 1);
+ if (rc < 0) {
+ return -errno;
+ }
+
+ rc = sscanf(szVal, "%d", val);
+ if (rc != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sysfs_write_val(int fd, int val)
+{
+ int n, rc;
+ char szVal[32] = {0};
+
+ n = sprintf(szVal, "%d", val);
+
+ lseek(fd, 0, SEEK_SET);
+ rc = write(fd, szVal, n+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int sysfs_write_str(int fd, const char *str)
+{
+ int rc;
+
+ lseek( fd, 0, SEEK_SET );
+ rc = write(fd, str, strlen(str)+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+
+int lc15bts_clock_err_open(void)
+{
+ if (clkerr_fd_err < 0) {
+ clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY);
+ if (clkerr_fd_err < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_err;
+ }
+ }
+
+ if (clkerr_fd_accuracy < 0) {
+ clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY);
+ if (clkerr_fd_accuracy < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_accuracy;
+ }
+ }
+
+ if (clkerr_fd_interval < 0) {
+ clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY);
+ if (clkerr_fd_interval < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_interval;
+ }
+ }
+
+ if (clkerr_fd_fault < 0) {
+ clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY);
+ if (clkerr_fd_fault < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_fault;
+ }
+ }
+
+ if (clkerr_fd_refresh < 0) {
+ clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY);
+ if (clkerr_fd_refresh < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_refresh;
+ }
+ }
+
+ if (clkerr_fd_reset < 0) {
+ clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY);
+ if (clkerr_fd_reset < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_reset;
+ }
+ }
+ return 0;
+}
+
+void lc15bts_clock_err_close(void)
+{
+ if (clkerr_fd_err >= 0) {
+ close(clkerr_fd_err);
+ clkerr_fd_err = -1;
+ }
+
+ if (clkerr_fd_accuracy >= 0) {
+ close(clkerr_fd_accuracy);
+ clkerr_fd_accuracy = -1;
+ }
+
+ if (clkerr_fd_interval >= 0) {
+ close(clkerr_fd_interval);
+ clkerr_fd_interval = -1;
+ }
+
+ if (clkerr_fd_fault >= 0) {
+ close(clkerr_fd_fault);
+ clkerr_fd_fault = -1;
+ }
+
+ if (clkerr_fd_refresh >= 0) {
+ close(clkerr_fd_refresh);
+ clkerr_fd_refresh = -1;
+ }
+
+ if (clkerr_fd_reset >= 0) {
+ close(clkerr_fd_reset);
+ clkerr_fd_reset = -1;
+ }
+}
+
+int lc15bts_clock_err_reset(void)
+{
+ return sysfs_write_val(clkerr_fd_reset, 1);
+}
+
+int lc15bts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec)
+{
+ int rc;
+
+ rc = sysfs_write_str(clkerr_fd_refresh, "once");
+ if (rc < 0) {
+ return -1;
+ }
+
+ rc = sysfs_read_val(clkerr_fd_fault, fault);
+ rc |= sysfs_read_val(clkerr_fd_err, error_ppt);
+ rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq);
+ rc |= sysfs_read_val(clkerr_fd_interval, interval_sec);
+ if (rc) {
+ return -1;
+ }
+ return 0;
+}
+
+
+int lc15bts_clock_dac_open(void)
+{
+ if (ocxodac_fd_value < 0) {
+ ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR);
+ if (ocxodac_fd_value < 0) {
+ lc15bts_clock_dac_close();
+ return ocxodac_fd_value;
+ }
+ }
+
+ if (ocxodac_fd_save < 0) {
+ ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY);
+ if (ocxodac_fd_save < 0) {
+ lc15bts_clock_dac_close();
+ return ocxodac_fd_save;
+ }
+ }
+ return 0;
+}
+
+void lc15bts_clock_dac_close(void)
+{
+ if (ocxodac_fd_value >= 0) {
+ close(ocxodac_fd_value);
+ ocxodac_fd_value = -1;
+ }
+
+ if (ocxodac_fd_save >= 0) {
+ close(ocxodac_fd_save);
+ ocxodac_fd_save = -1;
+ }
+}
+
+int lc15bts_clock_dac_get(int *dac_value)
+{
+ return sysfs_read_val(ocxodac_fd_value, dac_value);
+}
+
+int lc15bts_clock_dac_set(int dac_value)
+{
+ return sysfs_write_val(ocxodac_fd_value, dac_value);
+}
+
+int lc15bts_clock_dac_save(void)
+{
+ return sysfs_write_val(ocxodac_fd_save, 1);
+}
+
+
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.h b/src/osmo-bts-litecell15/misc/lc15bts_clock.h
new file mode 100644
index 00000000..d9673598
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.h
@@ -0,0 +1,16 @@
+#ifndef _LC15BTS_CLOCK_H
+#define _LC15BTS_CLOCK_H
+
+int lc15bts_clock_err_open(void);
+void lc15bts_clock_err_close(void);
+int lc15bts_clock_err_reset(void);
+int lc15bts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec);
+
+int lc15bts_clock_dac_open(void);
+void lc15bts_clock_dac_close(void);
+int lc15bts_clock_dac_get(int *dac_value);
+int lc15bts_clock_dac_set(int dac_value);
+int lc15bts_clock_dac_save(void);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.c b/src/osmo-bts-litecell15/misc/lc15bts_led.c
new file mode 100644
index 00000000..a93d3fb0
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_led.c
@@ -0,0 +1,333 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "lc15bts_led.h"
+#include "lc15bts_bts.h"
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+
+static struct lc15bts_led led_entries[] = {
+ {
+ .name = "led0",
+ .fullname = "led red",
+ .path = "/var/lc15/leds/led0/brightness"
+ },
+ {
+ .name = "led1",
+ .fullname = "led green",
+ .path = "/var/lc15/leds/led1/brightness"
+ }
+};
+
+static const struct value_string lc15bts_led_strs[] = {
+ { LC15BTS_LED_RED, "LED red" },
+ { LC15BTS_LED_GREEN, "LED green" },
+ { LC15BTS_LED_ORANGE, "LED orange" },
+ { LC15BTS_LED_OFF, "LED off" },
+ { 0, NULL }
+};
+
+static uint8_t led_priority[] = {
+ BLINK_PATTERN_INIT,
+ BLINK_PATTERN_INT_PROC_MALFUNC,
+ BLINK_PATTERN_SUPPLY_PWR_MAX,
+ BLINK_PATTERN_PA_PWR_MAX,
+ BLINK_PATTERN_VSWR_MAX,
+ BLINK_PATTERN_SUPPLY_VOLT_MIN,
+ BLINK_PATTERN_TEMP_MAX,
+ BLINK_PATTERN_SUPPLY_PWR_MAX2,
+ BLINK_PATTERN_EXT_LINK_MALFUNC,
+ BLINK_PATTERN_SUPPLY_VOLT_LOW,
+ BLINK_PATTERN_TEMP_HIGH,
+ BLINK_PATTERN_VSWR_HIGH,
+ BLINK_PATTERN_SUPPLY_PWR_HIGH,
+ BLINK_PATTERN_PA_PWR_HIGH,
+ BLINK_PATTERN_GPS_FIX_LOST,
+ BLINK_PATTERN_NORMAL
+};
+
+
+char *blink_pattern_command[] = BLINK_PATTERN_COMMAND;
+
+static int lc15bts_led_write(char *path, char *str)
+{
+ int fd;
+
+ if ((fd = open(path, O_WRONLY)) == -1)
+ {
+ return 0;
+ }
+
+ write(fd, str, strlen(str)+1);
+ close(fd);
+ return 1;
+}
+
+static void led_set_red()
+{
+ lc15bts_led_write(led_entries[0].path, "1");
+ lc15bts_led_write(led_entries[1].path, "0");
+}
+
+static void led_set_green()
+{
+ lc15bts_led_write(led_entries[0].path, "0");
+ lc15bts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_orange()
+{
+ lc15bts_led_write(led_entries[0].path, "1");
+ lc15bts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_off()
+{
+ lc15bts_led_write(led_entries[0].path, "0");
+ lc15bts_led_write(led_entries[1].path, "0");
+}
+
+static void led_sleep( struct lc15bts_mgr_instance *mgr, struct lc15bts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) {
+ /* Cancel any pending timer */
+ osmo_timer_del(&led_timer->timer);
+ /* Start LED timer */
+ led_timer->timer.cb = led_timer_cb;
+ led_timer->timer.data = mgr;
+ mgr->lc15bts_leds.active_timer = led_timer->idx;
+ osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec);
+ LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n",
+ get_value_string(lc15bts_led_strs, led_timer->idx),
+ led_timer->param.sleep_sec,
+ led_timer->param.sleep_usec);
+
+ switch (led_timer->idx) {
+ case LC15BTS_LED_RED:
+ led_set_red();
+ break;
+ case LC15BTS_LED_GREEN:
+ led_set_green();
+ break;
+ case LC15BTS_LED_ORANGE:
+ led_set_orange();
+ break;
+ case LC15BTS_LED_OFF:
+ led_set_off();
+ break;
+ default:
+ led_set_off();
+ }
+}
+
+static void led_sleep_cb(void *_data) {
+ struct lc15bts_mgr_instance *mgr = _data;
+ struct lc15bts_led_timer_list *led_list;
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->lc15bts_leds.list))
+ return;
+
+ llist_for_each_entry(led_list, &mgr->lc15bts_leds.list, list) {
+ if (led_list->led_timer.idx == mgr->lc15bts_leds.active_timer) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n",
+ get_value_string(lc15bts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ /* Delete current timer */
+ osmo_timer_del(&led_list->led_timer.timer);
+ /* Rotate the timer list */
+ llist_move_tail(&led_list->list, &mgr->lc15bts_leds.list);
+ break;
+ }
+ }
+
+ /* Execute next timer */
+ led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n",
+ get_value_string(lc15bts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->lc15bts_leds.list));
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+static void delete_led_timer_entries(struct lc15bts_mgr_instance *mgr)
+{
+ struct lc15bts_led_timer_list *led_list, *led_list2;
+
+ if (llist_empty(&mgr->lc15bts_leds.list))
+ return;
+
+ llist_for_each_entry_safe(led_list, led_list2, &mgr->lc15bts_leds.list, list) {
+ /* Delete the timer in list */
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n",
+ get_value_string(lc15bts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ /* Delete current timer */
+ osmo_timer_del(&led_list->led_timer.timer);
+ llist_del(&led_list->list);
+ talloc_free(led_list);
+ }
+ }
+ return;
+}
+
+static int add_led_timer_entry(struct lc15bts_mgr_instance *mgr, char *cmdstr)
+{
+ double sec, int_sec, frac_sec;
+ struct lc15bts_sleep_time led_param;
+
+ led_param.sleep_sec = 0;
+ led_param.sleep_usec = 0;
+
+ if (strstr(cmdstr, "set red") != NULL)
+ mgr->lc15bts_leds.led_idx = LC15BTS_LED_RED;
+ else if (strstr(cmdstr, "set green") != NULL)
+ mgr->lc15bts_leds.led_idx = LC15BTS_LED_GREEN;
+ else if (strstr(cmdstr, "set orange") != NULL)
+ mgr->lc15bts_leds.led_idx = LC15BTS_LED_ORANGE;
+ else if (strstr(cmdstr, "set off") != NULL)
+ mgr->lc15bts_leds.led_idx = LC15BTS_LED_OFF;
+ else if (strstr(cmdstr, "sleep") != NULL) {
+ sec = atof(cmdstr + 6);
+ /* split time into integer and fractional of seconds */
+ frac_sec = modf(sec, &int_sec) * 1000000.0;
+ led_param.sleep_sec = (int)int_sec;
+ led_param.sleep_usec = (int)frac_sec;
+
+ if ((mgr->lc15bts_leds.led_idx >= LC15BTS_LED_RED) && (mgr->lc15bts_leds.led_idx < _LC15BTS_LED_MAX)) {
+ struct lc15bts_led_timer_list *led_list;
+
+ /* allocate timer entry */
+ led_list = talloc_zero(tall_mgr_ctx, struct lc15bts_led_timer_list);
+ if (led_list) {
+ led_list->led_timer.idx = mgr->lc15bts_leds.led_idx;
+ led_list->led_timer.param.sleep_sec = led_param.sleep_sec;
+ led_list->led_timer.param.sleep_usec = led_param.sleep_usec;
+ llist_add_tail(&led_list->list, &mgr->lc15bts_leds.list);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n",
+ get_value_string(lc15bts_led_strs, mgr->lc15bts_leds.led_idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->lc15bts_leds.list));
+ }
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int parse_led_pattern(char *pattern, struct lc15bts_mgr_instance *mgr)
+{
+ char str[1024];
+ char *pstr;
+ char *sep;
+ int rc = 0;
+
+ strcpy(str, pattern);
+ pstr = str;
+ while ((sep = strsep(&pstr, ";")) != NULL) {
+ rc = add_led_timer_entry(mgr, sep);
+ if (rc < 0) {
+ break;
+ }
+
+ }
+ return rc;
+}
+
+/*** led interface ***/
+
+void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id)
+{
+ int rc;
+ struct lc15bts_led_timer_list *led_list;
+
+ if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) {
+ LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n",
+ pattern_id,
+ BLINK_PATTERN_POWER_ON,
+ BLINK_PATTERN_MAX_ITEM - 1);
+ return;
+ }
+ if (pattern_id == mgr->lc15bts_leds.last_pattern_id)
+ return;
+
+ mgr->lc15bts_leds.last_pattern_id = pattern_id;
+
+ LOGP(DTEMP, LOGL_NOTICE, "blink pattern command : %d\n", pattern_id);
+ LOGP(DTEMP, LOGL_NOTICE, "%s\n", blink_pattern_command[pattern_id]);
+
+ /* Empty existing LED timer in the list */
+ delete_led_timer_entries(mgr);
+
+ /* parse LED pattern */
+ rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n");
+ return;
+ }
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->lc15bts_leds.list))
+ return;
+
+ /* Start the first LED timer in the list */
+ led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n",
+ get_value_string(lc15bts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+void select_led_pattern(struct lc15bts_mgr_instance *mgr)
+{
+ int i;
+ uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0};
+
+ /* set normal LED pattern at first */
+ led[BLINK_PATTERN_NORMAL] = 1;
+
+ /* check on-board sensors for new LED pattern */
+ check_sensor_led_pattern(mgr, led);
+
+ /* check BTS status for new LED pattern */
+ check_bts_led_pattern(led);
+
+ /* check by priority */
+ for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) {
+ if(led[led_priority[i]] == 1) {
+ led_set(mgr, led_priority[i]);
+ break;
+ }
+ }
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.h b/src/osmo-bts-litecell15/misc/lc15bts_led.h
new file mode 100644
index 00000000..b6d9d28b
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_led.h
@@ -0,0 +1,22 @@
+#ifndef _LC15BTS_LED_H
+#define _LC15BTS_LED_H
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/stat.h>
+
+#include <osmo-bts/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include "lc15bts_mgr.h"
+
+/* public function prototypes */
+void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id);
+
+void select_led_pattern(struct lc15bts_mgr_instance *mgr);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c
new file mode 100644
index 00000000..dbdcc9f0
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c
@@ -0,0 +1,366 @@
+/* Main program for NuRAN Wireless Litecell 1.5 BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_bid.h"
+#include "misc/lc15bts_power.h"
+#include "misc/lc15bts_swd.h"
+
+#include "lc15bts_led.h"
+
+static int no_rom_write = 0;
+static int daemonize = 0;
+void *tall_mgr_ctx;
+
+/* every 6 hours means 365*4 = 1460 rom writes per year (max) */
+#define SENSOR_TIMER_SECS (6 * 3600)
+
+/* every 1 hours means 365*24 = 8760 rom writes per year (max) */
+#define HOURS_TIMER_SECS (1 * 3600)
+
+
+/* the initial state */
+static struct lc15bts_mgr_instance manager = {
+ .config_file = "lc15bts-mgr.cfg",
+ .temp = {
+ .supply_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .soc_temp_limit = {
+ .thresh_warn_max = 95,
+ .thresh_crit_max = 100,
+ .thresh_warn_min = -40,
+ },
+ .fpga_temp_limit = {
+ .thresh_warn_max = 95,
+ .thresh_crit_max = 100,
+ .thresh_warn_min = -40,
+ },
+ .rmsdet_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .ocxo_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .tx0_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -20,
+ },
+ .tx1_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -20,
+ },
+ .pa0_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .pa1_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ }
+ },
+ .volt = {
+ .supply_volt_limit = {
+ .thresh_warn_max = 30000,
+ .thresh_crit_max = 30500,
+ .thresh_warn_min = 19000,
+ .thresh_crit_min = 17500,
+ }
+ },
+ .pwr = {
+ .supply_pwr_limit = {
+ .thresh_warn_max = 110,
+ .thresh_crit_max = 120,
+ },
+ .pa0_pwr_limit = {
+ .thresh_warn_max = 50,
+ .thresh_crit_max = 60,
+ },
+ .pa1_pwr_limit = {
+ .thresh_warn_max = 50,
+ .thresh_crit_max = 60,
+ }
+ },
+ .vswr = {
+ .tx0_vswr_limit = {
+ .thresh_warn_max = 3000,
+ .thresh_crit_max = 5000,
+ },
+ .tx1_vswr_limit = {
+ .thresh_warn_max = 3000,
+ .thresh_crit_max = 5000,
+ }
+ },
+ .gps = {
+ .gps_fix_limit = {
+ .thresh_warn_max = 7,
+ }
+ },
+ .state = {
+ .action_norm = SENSOR_ACT_NORM_PA0_ON | SENSOR_ACT_NORM_PA1_ON,
+ .action_warn = 0,
+ .action_crit = 0,
+ .action_comb = 0,
+ .state = STATE_NORMAL,
+ }
+};
+
+static struct osmo_timer_list sensor_timer;
+static void check_sensor_timer_cb(void *unused)
+{
+ lc15bts_check_temp(no_rom_write);
+ lc15bts_check_power(no_rom_write);
+ lc15bts_check_vswr(no_rom_write);
+ osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0);
+ /* TODO checks if lc15bts_check_temp/lc15bts_check_power/lc15bts_check_vswr went ok */
+ lc15bts_swd_event(&manager, SWD_CHECK_SENSOR);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ lc15bts_update_hours(no_rom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+ /* TODO: validates if lc15bts_update_hours went correctly */
+ lc15bts_swd_event(&manager, SWD_UPDATE_HOURS);
+}
+
+static void print_help(void)
+{
+ printf("lc15bts-mgr [-nsD] [-d cat]\n");
+ printf(" -n Do not write to ROM\n");
+ printf(" -s Disable color\n");
+ printf(" -d CAT enable debugging\n");
+ printf(" -D daemonize\n");
+ printf(" -c Specify the filename of the config file\n");
+}
+
+static int parse_options(int argc, char **argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
+ switch (opt) {
+ case 'n':
+ no_rom_write = 1;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ manager.config_file = optarg;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ lc15bts_check_temp(no_rom_write);
+ lc15bts_check_power(no_rom_write);
+ lc15bts_check_vswr(no_rom_write);
+ lc15bts_update_hours(no_rom_write);
+ exit(0);
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_mgr_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct log_info_cat mgr_log_info_cat[] = {
+ [DTEMP] = {
+ .name = "DTEMP",
+ .description = "Temperature monitoring",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFW] = {
+ .name = "DFW",
+ .description = "Firmware management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFIND] = {
+ .name = "DFIND",
+ .description = "ipaccess-find handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DCALIB] = {
+ .name = "DCALIB",
+ .description = "Calibration handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DSWD] = {
+ .name = "DSWD",
+ .description = "Software Watchdog",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+static const struct log_info mgr_log_info = {
+ .cat = mgr_log_info_cat,
+ .num_cat = ARRAY_SIZE(mgr_log_info_cat),
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
+ msgb_talloc_ctx_init(tall_mgr_ctx, 0);
+
+ osmo_init_logging2(tall_mgr_ctx, &mgr_log_info);
+
+ osmo_init_ignore_signals();
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ lc15bts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = lc15bts_mgr_parse_config(&manager);
+ if (rc < 0) {
+ LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
+ exit(1);
+ }
+
+ rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ INIT_LLIST_HEAD(&manager.lc15bts_leds.list);
+ INIT_LLIST_HEAD(&manager.alarms.list);
+
+ /* Initialize the service watchdog notification for SWD_LAST event(s) */
+ if (lc15bts_swd_init(&manager, (int)(SWD_LAST)) != 0)
+ exit(3);
+
+ /* start temperature check timer */
+ sensor_timer.cb = check_sensor_timer_cb;
+ check_sensor_timer_cb(NULL);
+
+ /* start operational hours timer */
+ hours_timer.cb = hours_timer_cb;
+ hours_timer_cb(NULL);
+
+ /* Enable the PAs */
+ rc = lc15bts_power_set(LC15BTS_POWER_PA0, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+
+ rc = lc15bts_power_set(LC15BTS_POWER_PA1, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+
+ /* handle broadcast messages for ipaccess-find */
+ if (lc15bts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the sensor control */
+ lc15bts_mgr_sensor_init(&manager);
+
+ if (lc15bts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ lc15bts_swd_event(&manager, SWD_MAINLOOP);
+ }
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h
new file mode 100644
index 00000000..4bfbdbc9
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h
@@ -0,0 +1,422 @@
+#ifndef _LC15BTS_MGR_H
+#define _LC15BTS_MGR_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <stdint.h>
+
+#define LC15BTS_SENSOR_TIMER_DURATION 60
+#define LC15BTS_PREVENT_TIMER_DURATION 15 * 60
+#define LC15BTS_PREVENT_TIMER_SHORT_DURATION 5 * 60
+#define LC15BTS_PREVENT_TIMER_NONE 0
+#define LC15BTS_PREVENT_RETRY INT_MAX - 1
+
+enum BLINK_PATTERN {
+ BLINK_PATTERN_POWER_ON = 0, //hardware set
+ BLINK_PATTERN_INIT,
+ BLINK_PATTERN_NORMAL,
+ BLINK_PATTERN_EXT_LINK_MALFUNC,
+ BLINK_PATTERN_INT_PROC_MALFUNC,
+ BLINK_PATTERN_SUPPLY_VOLT_LOW,
+ BLINK_PATTERN_SUPPLY_VOLT_MIN,
+ BLINK_PATTERN_VSWR_HIGH,
+ BLINK_PATTERN_VSWR_MAX,
+ BLINK_PATTERN_TEMP_HIGH,
+ BLINK_PATTERN_TEMP_MAX,
+ BLINK_PATTERN_SUPPLY_PWR_HIGH,
+ BLINK_PATTERN_SUPPLY_PWR_MAX,
+ BLINK_PATTERN_SUPPLY_PWR_MAX2,
+ BLINK_PATTERN_PA_PWR_HIGH,
+ BLINK_PATTERN_PA_PWR_MAX,
+ BLINK_PATTERN_GPS_FIX_LOST,
+ BLINK_PATTERN_MAX_ITEM
+};
+
+#define BLINK_PATTERN_COMMAND {\
+ "set red; sleep 5.0",\
+ "set orange; sleep 5.0",\
+ "set green; sleep 2.5; set off; sleep 2.5",\
+ "set red; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 2.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\
+ "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set orange; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+}
+
+enum {
+ DTEMP,
+ DFW,
+ DFIND,
+ DCALIB,
+ DSWD,
+};
+
+// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ...
+enum {
+#if 0
+ SENSOR_ACT_PWR_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_PA0_OFF = 0x2,
+ SENSOR_ACT_PA1_OFF = 0x4,
+ SENSOR_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ SENSOR_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_NORM_PA0_ON = 0x2,
+ SENSOR_ACT_NORM_PA1_ON = 0x4,
+ SENSOR_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum lc15bts_sensor_state {
+ STATE_NORMAL, /* Everything is fine */
+ STATE_WARNING_HYST, /* Go back to normal next? */
+ STATE_WARNING, /* We are above the warning threshold */
+ STATE_CRITICAL, /* We have an issue. Wait for below warning */
+};
+
+enum lc15bts_leds_name {
+ LC15BTS_LED_RED = 0,
+ LC15BTS_LED_GREEN,
+ LC15BTS_LED_ORANGE,
+ LC15BTS_LED_OFF,
+ _LC15BTS_LED_MAX
+};
+
+struct lc15bts_led{
+ char *name;
+ char *fullname;
+ char *path;
+};
+
+/**
+ * Temperature Limits. We separate from a threshold
+ * that will generate a warning and one that is so
+ * severe that an action will be taken.
+ */
+struct lc15bts_temp_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+};
+
+struct lc15bts_volt_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+ int thresh_crit_min;
+};
+
+struct lc15bts_pwr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct lc15bts_vswr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct lc15bts_gps_fix_limit {
+ int thresh_warn_max;
+};
+
+struct lc15bts_sleep_time {
+ int sleep_sec;
+ int sleep_usec;
+};
+
+struct lc15bts_led_timer {
+ uint8_t idx;
+ struct osmo_timer_list timer;
+ struct lc15bts_sleep_time param;
+};
+
+struct lc15bts_led_timer_list {
+ struct llist_head list;
+ struct lc15bts_led_timer led_timer;
+};
+
+struct lc15bts_preventive_list {
+ struct llist_head list;
+ struct lc15bts_sleep_time param;
+ int action_flag;
+};
+
+enum mgr_vty_node {
+ MGR_NODE = _LAST_OSMOVTY_NODE + 1,
+
+ ACT_NORM_NODE,
+ ACT_WARN_NODE,
+ ACT_CRIT_NODE,
+ LIMIT_SUPPLY_TEMP_NODE,
+ LIMIT_SOC_NODE,
+ LIMIT_FPGA_NODE,
+ LIMIT_RMSDET_NODE,
+ LIMIT_OCXO_NODE,
+ LIMIT_TX0_TEMP_NODE,
+ LIMIT_TX1_TEMP_NODE,
+ LIMIT_PA0_TEMP_NODE,
+ LIMIT_PA1_TEMP_NODE,
+ LIMIT_SUPPLY_VOLT_NODE,
+ LIMIT_TX0_VSWR_NODE,
+ LIMIT_TX1_VSWR_NODE,
+ LIMIT_SUPPLY_PWR_NODE,
+ LIMIT_PA0_PWR_NODE,
+ LIMIT_PA1_PWR_NODE,
+ LIMIT_GPS_FIX_NODE,
+};
+
+enum mgr_vty_limit_type {
+ MGR_LIMIT_TYPE_TEMP = 0,
+ MGR_LIMIT_TYPE_VOLT,
+ MGR_LIMIT_TYPE_VSWR,
+ MGR_LIMIT_TYPE_PWR,
+ _MGR_LIMIT_TYPE_MAX,
+};
+
+struct lc15bts_mgr_instance {
+ const char *config_file;
+
+ struct {
+ struct lc15bts_temp_limit supply_temp_limit;
+ struct lc15bts_temp_limit soc_temp_limit;
+ struct lc15bts_temp_limit fpga_temp_limit;
+ struct lc15bts_temp_limit rmsdet_temp_limit;
+ struct lc15bts_temp_limit ocxo_temp_limit;
+ struct lc15bts_temp_limit tx0_temp_limit;
+ struct lc15bts_temp_limit tx1_temp_limit;
+ struct lc15bts_temp_limit pa0_temp_limit;
+ struct lc15bts_temp_limit pa1_temp_limit;
+ } temp;
+
+ struct {
+ struct lc15bts_volt_limit supply_volt_limit;
+ } volt;
+
+ struct {
+ struct lc15bts_pwr_limit supply_pwr_limit;
+ struct lc15bts_pwr_limit pa0_pwr_limit;
+ struct lc15bts_pwr_limit pa1_pwr_limit;
+ } pwr;
+
+ struct {
+ struct lc15bts_vswr_limit tx0_vswr_limit;
+ struct lc15bts_vswr_limit tx1_vswr_limit;
+ int tx0_last_vswr;
+ int tx1_last_vswr;
+ } vswr;
+
+ struct {
+ struct lc15bts_gps_fix_limit gps_fix_limit;
+ time_t last_update;
+ } gps;
+
+ struct {
+ int action_norm;
+ int action_warn;
+ int action_crit;
+ int action_comb;
+
+ enum lc15bts_sensor_state state;
+ } state;
+
+ struct {
+ int state;
+ int calib_from_loop;
+ struct osmo_timer_list calib_timeout;
+ } calib;
+
+ struct {
+ int state;
+ int swd_from_loop;
+ unsigned long long int swd_events;
+ unsigned long long int swd_events_cache;
+ unsigned long long int swd_eventmasks;
+ int num_events;
+ struct osmo_timer_list swd_timeout;
+ } swd;
+
+ struct {
+ uint8_t led_idx;
+ uint8_t last_pattern_id;
+ uint8_t active_timer;
+ struct llist_head list;
+ } lc15bts_leds;
+
+ struct {
+ int is_up;
+ uint32_t last_seqno;
+ struct osmo_timer_list recon_timer;
+ struct ipa_client_conn *bts_conn;
+ uint32_t crit_flags;
+ uint32_t warn_flags;
+ } lc15bts_ctrl;
+
+ struct lc15bts_alarms {
+ int temp_high;
+ int temp_max;
+ int supply_low;
+ int supply_min;
+ int vswr_high;
+ int vswr_max;
+ int supply_pwr_high;
+ int supply_pwr_max;
+ int supply_pwr_max2;
+ int pa_pwr_high;
+ int pa_pwr_max;
+ int gps_fix_lost;
+ struct llist_head list;
+ struct osmo_timer_list preventive_timer;
+ int preventive_duration;
+ int preventive_retry;
+ } alarms;
+
+};
+
+enum lc15bts_mgr_fail_evt_rep_crit_sig {
+ /* Critical alarms */
+ S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0),
+ S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1),
+ S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2),
+ S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3),
+ S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4),
+ S_MGR_TEMP_TRX0_CRIT_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_TRX1_CRIT_MAX_ALARM = (1 << 6),
+ S_MGR_TEMP_PA0_CRIT_MAX_ALARM = (1 << 7),
+ S_MGR_TEMP_PA1_CRIT_MAX_ALARM = (1 << 8),
+ S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 9),
+ S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 10),
+ S_MGR_VSWR0_CRIT_MAX_ALARM = (1 << 11),
+ S_MGR_VSWR1_CRIT_MAX_ALARM = (1 << 12),
+ S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 13),
+ S_MGR_PWR_PA0_CRIT_MAX_ALARM = (1 << 14),
+ S_MGR_PWR_PA1_CRIT_MAX_ALARM = (1 << 15),
+ _S_MGR_CRIT_ALARM_MAX,
+};
+
+enum lc15bts_mgr_fail_evt_rep_warn_sig {
+ /* Warning alarms */
+ S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0),
+ S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 2),
+ S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 3),
+ S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 4),
+ S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 5),
+ S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 6),
+ S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 7),
+ S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 8),
+ S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 9),
+ S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 10),
+ S_MGR_TEMP_TRX0_WARN_MIN_ALARM = (1 << 11),
+ S_MGR_TEMP_TRX0_WARN_MAX_ALARM = (1 << 12),
+ S_MGR_TEMP_TRX1_WARN_MIN_ALARM = (1 << 13),
+ S_MGR_TEMP_TRX1_WARN_MAX_ALARM = (1 << 14),
+ S_MGR_TEMP_PA0_WARN_MIN_ALARM = (1 << 15),
+ S_MGR_TEMP_PA0_WARN_MAX_ALARM = (1 << 16),
+ S_MGR_TEMP_PA1_WARN_MIN_ALARM = (1 << 17),
+ S_MGR_TEMP_PA1_WARN_MAX_ALARM = (1 << 18),
+ S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 19),
+ S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 20),
+ S_MGR_VSWR0_WARN_MAX_ALARM = (1 << 21),
+ S_MGR_VSWR1_WARN_MAX_ALARM = (1 << 22),
+ S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 23),
+ S_MGR_PWR_PA0_WARN_MAX_ALARM = (1 << 24),
+ S_MGR_PWR_PA1_WARN_MAX_ALARM = (1 << 25),
+ S_MGR_GPS_FIX_WARN_ALARM = (1 << 26),
+ _S_MGR_WARN_ALARM_MAX,
+};
+
+enum lc15bts_mgr_failure_event_causes {
+ /* Critical causes */
+ NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100,
+ NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101,
+ NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102,
+ NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103,
+ NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104,
+ NM_EVT_CAUSE_CRIT_TEMP_TRX0_MAX_FAIL = 0x4105,
+ NM_EVT_CAUSE_CRIT_TEMP_TRX1_MAX_FAIL = 0x4106,
+ NM_EVT_CAUSE_CRIT_TEMP_PA0_MAX_FAIL = 0x4107,
+ NM_EVT_CAUSE_CRIT_TEMP_PA1_MAX_FAIL = 0x4108,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4109,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x410A,
+ NM_EVT_CAUSE_CRIT_VSWR0_MAX_FAIL = 0x410B,
+ NM_EVT_CAUSE_CRIT_VSWR1_MAX_FAIL = 0x410C,
+ NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410D,
+ NM_EVT_CAUSE_CRIT_PWR_PA0_MAX_FAIL = 0x410E,
+ NM_EVT_CAUSE_CRIT_PWR_PA1_MAX_FAIL = 0x410F,
+ /* Warning causes */
+ NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400,
+ NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401,
+ NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402,
+ NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403,
+ NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404,
+ NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405,
+ NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406,
+ NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407,
+ NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408,
+ NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409,
+ NM_EVT_CAUSE_WARN_TEMP_TRX0_LOW_FAIL = 0x440A,
+ NM_EVT_CAUSE_WARN_TEMP_TRX0_HIGH_FAIL = 0x440B,
+ NM_EVT_CAUSE_WARN_TEMP_TRX1_LOW_FAIL = 0x440C,
+ NM_EVT_CAUSE_WARN_TEMP_TRX1_HIGH_FAIL = 0x440D,
+ NM_EVT_CAUSE_WARN_TEMP_PA0_LOW_FAIL = 0x440E,
+ NM_EVT_CAUSE_WARN_TEMP_PA0_HIGH_FAIL = 0x440F,
+ NM_EVT_CAUSE_WARN_TEMP_PA1_LOW_FAIL = 0x4410,
+ NM_EVT_CAUSE_WARN_TEMP_PA1_HIGH_FAIL = 0x4411,
+ NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x4412,
+ NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x4413,
+ NM_EVT_CAUSE_WARN_VSWR0_HIGH_FAIL = 0x4414,
+ NM_EVT_CAUSE_WANR_VSWR1_HIGH_FAIL = 0x4415,
+ NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4416,
+ NM_EVT_CAUSE_WARN_PWR_PA0_HIGH_FAIL = 0x4417,
+ NM_EVT_CAUSE_WARN_PWR_PA1_HIGH_FAIL = 0x4418,
+ NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4419,
+};
+
+/* This defines the list of notification events for systemd service watchdog.
+ all these events must be notified in a certain service defined timeslot
+ or the service (this app) would be restarted (only if related systemd service
+ unit file has WatchdogSec!=0).
+ WARNING: swd events must begin with event 0. Last events must be
+ SWD_LAST (max 64 events in this list).
+*/
+enum mgr_swd_events {
+ SWD_MAINLOOP = 0,
+ SWD_CHECK_SENSOR,
+ SWD_UPDATE_HOURS,
+ SWD_CHECK_TEMP_SENSOR,
+ SWD_CHECK_LED_CTRL,
+ SWD_CHECK_CALIB,
+ SWD_CHECK_BTS_CONNECTION,
+ SWD_LAST
+};
+
+int lc15bts_mgr_vty_init(void);
+int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *mgr);
+int lc15bts_mgr_nl_init(void);
+int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr);
+const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state);
+
+int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr);
+int lc15bts_mgr_control_init(struct lc15bts_mgr_instance *mgr);
+int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr);
+void lc15bts_mgr_dispatch_alarm(struct lc15bts_mgr_instance *mgr, const int cause, const char *key, const char *text);
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c
new file mode 100644
index 00000000..badb5455
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c
@@ -0,0 +1,292 @@
+/* OCXO calibration control for Litecell 1.5 BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_calib.c
+ * (C) 2014,2015 by Holger Hans Peter Freyther
+ * (C) 2014 by Harald Welte for the IPA code from the oml router
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_clock.h"
+#include "misc/lc15bts_swd.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_led.h"
+#include "osmo-bts/msg_utils.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipa.h>
+
+#include <time.h>
+
+static void calib_adjust(struct lc15bts_mgr_instance *mgr);
+static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int reason);
+static void calib_loop_run(void *_data);
+
+static int ocxodac_saved_value = -1;
+
+enum calib_state {
+ CALIB_INITIAL,
+ CALIB_IN_PROGRESS,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPSFIX,
+ CALIB_FAIL_CLKERR,
+ CALIB_FAIL_OCXODAC,
+ CALIB_SUCCESS,
+};
+
+static void calib_start(struct lc15bts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = lc15bts_clock_err_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ rc = lc15bts_clock_dac_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ calib_adjust(mgr);
+}
+
+static void calib_adjust(struct lc15bts_mgr_instance *mgr)
+{
+ int rc;
+ int fault;
+ int error_ppt;
+ int accuracy_ppq;
+ int interval_sec;
+ int dac_value;
+ int new_dac_value;
+ int dac_correction;
+ time_t now;
+ time_t last_gps_fix;
+
+ rc = lc15bts_clock_err_get(&fault, &error_ppt,
+ &accuracy_ppq, &interval_sec);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get clock error measurement %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ /* get current time */
+ now = time(NULL);
+
+ /* first time after start of manager program */
+ if (mgr->gps.last_update == 0)
+ mgr->gps.last_update = now;
+
+ /* read last GPS 3D fix from storage */
+ rc = lc15bts_par_get_gps_fix(&last_gps_fix);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc);
+ last_gps_fix = 0;
+ }
+
+ if (fault) {
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ /* We got GPS 3D fix */
+ LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, last=%lld, now=%lld\n",
+ mgr->lc15bts_ctrl.warn_flags,
+ (long long)last_gps_fix,
+ (long long)now);
+
+ rc = lc15bts_clock_dac_get(&dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ /* Set OCXO initial dac value */
+ if (ocxodac_saved_value < 0)
+ ocxodac_saved_value = dac_value;
+
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
+ error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
+
+ /* Need integration time to correct */
+ if (interval_sec) {
+ /* 1 unit of correction equal about 0.5 - 1 PPB correction */
+ dac_correction = (int)(-error_ppt * 0.0015);
+ new_dac_value = dac_value + dac_correction;
+
+ if (new_dac_value > 4095)
+ new_dac_value = 4095;
+ else if (new_dac_value < 0)
+ new_dac_value = 0;
+
+ /* We have a fix, make sure the measured error is
+ meaningful (10 times the accuracy) */
+ if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) {
+
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Going to apply %d as new clock setting.\n",
+ new_dac_value);
+
+ rc = lc15bts_clock_dac_set(new_dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to set OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+ rc = lc15bts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+ }
+ /* New conditions to store DAC value:
+ * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ)
+ * - Error less or equal than 2PPB (or 2000PPT)
+ * - Solution different than the last one */
+ else if (accuracy_ppq <= 10000) {
+ if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) {
+ LOGP(DCALIB, LOGL_NOTICE, "Saving OCXO DAC value to memory... val = %d\n", dac_value);
+ rc = lc15bts_clock_dac_save();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to save OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ } else {
+ ocxodac_saved_value = dac_value;
+ }
+ }
+
+ rc = lc15bts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ }
+ }
+ } else {
+ LOGP(DCALIB, LOGL_NOTICE, "Skipping this iteration, no integration time\n");
+ }
+
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+}
+
+static void calib_close(struct lc15bts_mgr_instance *mgr)
+{
+ lc15bts_clock_err_close();
+ lc15bts_clock_dac_close();
+}
+
+static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->calib.calib_from_loop) {
+ /*
+ * In case of success calibrate in two hours again
+ * and in case of a failure in some minutes.
+ *
+ * TODO NTQ: Select timeout based on last error and accuracy
+ */
+ int timeout = 60;
+ //int timeout = 2 * 60 * 60;
+ //if (outcome != CALIB_SUCESS) }
+ // timeout = 5 * 60;
+ //}
+
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
+ /* TODO: do we want to notify if we got a calibration error, like no gps fix? */
+ lc15bts_swd_event(mgr, SWD_CHECK_CALIB);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ calib_close(mgr);
+}
+
+static int calib_run(struct lc15bts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -1;
+ }
+
+ mgr->calib.calib_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->calib.state = CALIB_IN_PROGRESS;
+ calib_start(mgr);
+ return 0;
+}
+
+static void calib_loop_run(void *_data)
+{
+ int rc;
+ struct lc15bts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0) {
+ calib_state_reset(mgr, CALIB_FAIL_START);
+ }
+}
+
+int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr)
+{
+ mgr->calib.state = CALIB_INITIAL;
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0);
+
+ return 0;
+}
+
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c
new file mode 100644
index 00000000..3a617dd7
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c
@@ -0,0 +1,195 @@
+/* NetworkListen for NuRAN Litecell 1.5 BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_nl.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_nl.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_bid.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define ETH0_ADDR_SYSFS "/var/lc15/net/eth0/address"
+
+static struct osmo_fd nl_fd;
+
+/*
+ * The TLV structure in IPA messages in UDP packages is a bit
+ * weird. First the header appears to have an extra NULL byte
+ * and second the L16 of the L16TV needs to include +1 for the
+ * tag. The default msgb/tlv and libosmo-abis routines do not
+ * provide this.
+ */
+
+static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
+ hh->len = htons(msg->len - sizeof(*hh) - 1);
+ hh->proto = proto;
+}
+
+static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+ const uint8_t *val)
+{
+ uint8_t *buf = msgb_put(msg, len + 2 + 1);
+
+ *buf++ = (len + 1) >> 8;
+ *buf++ = (len + 1) & 0xff;
+ *buf++ = tag;
+ memcpy(buf, val, len);
+}
+
+/*
+ * We don't look at the content of the request yet and lie
+ * about most of the responses.
+ */
+static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
+ uint8_t *data, size_t len)
+{
+ static int fetched_info = 0;
+ static char mac_str[20] = {0, };
+ static char model_name[64] = {0, };
+ static char ser_str[20] = {0, };
+
+ struct sockaddr_in loc_addr;
+ int rc;
+ char loc_ip[INET_ADDRSTRLEN];
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
+ if (!msg) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
+ return;
+ }
+
+ if (!fetched_info) {
+ int fd_eth;
+ int serno;
+
+ /* fetch the MAC */
+ fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY);
+ if (fd_eth >= 0) {
+ read(fd_eth, mac_str, sizeof(mac_str)-1);
+ mac_str[sizeof(mac_str)-1] = '\0';
+ close(fd_eth);
+ }
+
+ /* fetch the serial number */
+ lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ strncpy(model_name, get_hwversion_desc(), sizeof(model_name)-1);
+ fetched_info = 1;
+ }
+
+ if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
+ return;
+ }
+
+ msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
+
+ /* append MAC addr */
+ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
+
+ /* append ip address */
+ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
+ quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
+
+ /* append the serial number */
+ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
+
+ /* abuse some flags */
+ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
+
+ /* ip.access nanoBTS would reply to port==3006 */
+ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
+ rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
+ if (rc != msg->len)
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to send with rc(%d) errno(%d)\n", rc, errno);
+}
+
+static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
+{
+ uint8_t data[2048];
+ char src[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {};
+ socklen_t len = sizeof(addr);
+ int rc;
+
+ rc = recvfrom(fd->fd, data, sizeof(data), 0,
+ (struct sockaddr *) &addr, &len);
+ if (rc <= 0) {
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to read from socket errno(%d)\n", errno);
+ return -1;
+ }
+
+ LOGP(DFIND, LOGL_DEBUG,
+ "Received request from: %s size %d\n",
+ inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
+
+ if (rc < 6)
+ return 0;
+
+ if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
+ return 0;
+
+ respond_to(&addr, fd, data + 6, rc - 6);
+ return 0;
+}
+
+int lc15bts_mgr_nl_init(void)
+{
+ int rc;
+
+ nl_fd.cb = ipaccess_bcast;
+ rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ "0.0.0.0", 3006, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("Socket creation");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c
new file mode 100644
index 00000000..9665e1db
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c
@@ -0,0 +1,378 @@
+/* Temperature control for NuRAN Litecell 1.5 BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_temp.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <inttypes.h>
+#include "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_temp.h"
+#include "misc/lc15bts_power.h"
+#include "misc/lc15bts_led.h"
+#include "misc/lc15bts_swd.h"
+#include "limits.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+
+struct lc15bts_mgr_instance *s_mgr;
+static struct osmo_timer_list sensor_ctrl_timer;
+
+static const struct value_string state_names[] = {
+ { STATE_NORMAL, "NORMAL" },
+ { STATE_WARNING_HYST, "WARNING (HYST)" },
+ { STATE_WARNING, "WARNING" },
+ { STATE_CRITICAL, "CRITICAL" },
+ { 0, NULL }
+};
+
+const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum lc15bts_sensor_state current_state, int critical, int warning)
+{
+ int next_state = -1;
+ switch (current_state) {
+ case STATE_NORMAL:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ break;
+ case STATE_WARNING_HYST:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ else
+ next_state = STATE_NORMAL;
+ break;
+ case STATE_WARNING:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (!warning)
+ next_state = STATE_WARNING_HYST;
+ break;
+ case STATE_CRITICAL:
+ if (!critical && !warning)
+ next_state = STATE_WARNING;
+ break;
+ };
+
+ return next_state;
+}
+
+static void handle_normal_actions(int actions)
+{
+ /* switch on the PA */
+ if (actions & SENSOR_ACT_NORM_PA0_ON) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA0, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA #0\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the PA #0 as normal action.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_NORM_PA1_ON) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA1, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA #1\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the PA #1 as normal action.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch on the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl start osmo-bts.service");
+ }
+}
+
+static void handle_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & SENSOR_ACT_PA1_OFF) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA1, 0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA #1. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA #1 due temperature.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_PA0_OFF) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA0, 0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA #0. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA #0 due temperature.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_BTS_SRV_OFF) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch off the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl stop osmo-bts.service");
+ }
+}
+
+/**
+ * Go back to normal! Depending on the configuration execute the normal
+ * actions that could (start to) undo everything we did in the other
+ * states. What is still missing is the power increase/decrease depending
+ * on the state. E.g. starting from WARNING_HYST we might want to slowly
+ * ramp up the output power again.
+ */
+static void execute_normal_act(struct lc15bts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n");
+ handle_normal_actions(manager->state.action_norm);
+}
+
+static void execute_warning_act(struct lc15bts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n");
+ handle_actions(manager->state.action_warn);
+}
+
+static void execute_critical_act(struct lc15bts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_actions(manager->state.action_crit);
+}
+
+static void lc15bts_mgr_sensor_handle(struct lc15bts_mgr_instance *manager,
+ int critical, int warning)
+{
+ int new_state = next_state(manager->state.state, critical, warning);
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+
+ LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n",
+ get_value_string(state_names, manager->state.state),
+ get_value_string(state_names, new_state));
+ manager->state.state = new_state;
+ switch (manager->state.state) {
+ case STATE_NORMAL:
+ execute_normal_act(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ break;
+ };
+}
+
+static void sensor_ctrl_check(struct lc15bts_mgr_instance *mgr)
+{
+ int rc;
+ int temp = 0;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+
+ LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n");
+
+ /* Read the current supply temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the supply temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.supply_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.supply_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "Supply temperature is: %d\n", temp);
+ }
+
+ /* Read the current SoC temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the SoC temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.soc_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.soc_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "SoC temperature is: %d\n", temp);
+ }
+
+ /* Read the current fpga temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the fpga temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "FPGA temperature is: %d\n", temp);
+ }
+
+ /* Read the current RMS detector temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the RMS detector temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "RMS detector temperature is: %d\n", temp);
+ }
+
+ /* Read the current OCXO temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the OCXO temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "OCXO temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX #0 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the TX #0 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.tx0_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.tx0_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "TX #0 temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX #1 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the TX #1 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.tx1_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.tx1_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "TX #1 temperature is: %d\n", temp);
+ }
+
+ /* Read the current PA #0 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the PA #0 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.pa0_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.pa0_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "PA #0 temperature is: %d\n", temp);
+ }
+
+ /* Read the current PA #1 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the PA #1 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.pa1_temp_limit.thresh_warn_max)
+ warn_thresh_passed = 1;
+ if (temp > mgr->temp.pa1_temp_limit.thresh_crit_max)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "PA #1 temperature is: %d\n", temp);
+ }
+
+ lc15bts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed);
+}
+
+static void sensor_ctrl_check_cb(void *_data)
+{
+ struct lc15bts_mgr_instance *mgr = _data;
+ sensor_ctrl_check(mgr);
+ /* Check every minute? XXX make it configurable! */
+ osmo_timer_schedule(&sensor_ctrl_timer, LC15BTS_SENSOR_TIMER_DURATION, 0);
+ LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n");
+ /* TODO: do we want to notify if some sensors could not be read? */
+ lc15bts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR);
+}
+
+int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr)
+{
+ s_mgr = mgr;
+ sensor_ctrl_timer.cb = sensor_ctrl_check_cb;
+ sensor_ctrl_timer.data = s_mgr;
+ sensor_ctrl_check_cb(s_mgr);
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c
new file mode 100644
index 00000000..80751fb0
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c
@@ -0,0 +1,1074 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_vty.c
+ * (C) 2014 by sysmocom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@sysmocom.de>
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "lc15bts_misc.h"
+#include "lc15bts_mgr.h"
+#include "lc15bts_temp.h"
+#include "lc15bts_power.h"
+#include "lc15bts_led.h"
+#include "btsconfig.h"
+
+static struct lc15bts_mgr_instance *s_mgr;
+
+static const char copyright[] =
+ "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
+ "(C) 2014 by Holger Hans Peter Freyther\r\n"
+ "(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n"
+ "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static int go_to_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case MGR_NODE:
+ vty->node = CONFIG_NODE;
+ break;
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_SUPPLY_TEMP_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_RMSDET_NODE:
+ case LIMIT_OCXO_NODE:
+ case LIMIT_TX0_TEMP_NODE:
+ case LIMIT_TX1_TEMP_NODE:
+ case LIMIT_PA0_TEMP_NODE:
+ case LIMIT_PA1_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_TX0_VSWR_NODE:
+ case LIMIT_TX1_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA0_PWR_NODE:
+ case LIMIT_PA1_PWR_NODE:
+ vty->node = MGR_NODE;
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ return vty->node;
+}
+
+static int is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case MGR_NODE:
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_SUPPLY_TEMP_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_RMSDET_NODE:
+ case LIMIT_OCXO_NODE:
+ case LIMIT_TX0_TEMP_NODE:
+ case LIMIT_TX1_TEMP_NODE:
+ case LIMIT_PA0_TEMP_NODE:
+ case LIMIT_PA1_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_TX0_VSWR_NODE:
+ case LIMIT_TX1_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA0_PWR_NODE:
+ case LIMIT_PA1_PWR_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "lc15bts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure lc15bts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(lc15bts-mgr)# ",
+ 1,
+};
+
+static struct cmd_node act_norm_node = {
+ ACT_NORM_NODE,
+ "%s(actions-normal)# ",
+ 1,
+};
+
+static struct cmd_node act_warn_node = {
+ ACT_WARN_NODE,
+ "%s(actions-warn)# ",
+ 1,
+};
+
+static struct cmd_node act_crit_node = {
+ ACT_CRIT_NODE,
+ "%s(actions-critical)# ",
+ 1,
+};
+
+static struct cmd_node limit_supply_temp_node = {
+ LIMIT_SUPPLY_TEMP_NODE,
+ "%s(limit-supply-temp)# ",
+ 1,
+};
+
+static struct cmd_node limit_soc_node = {
+ LIMIT_SOC_NODE,
+ "%s(limit-soc)# ",
+ 1,
+};
+
+static struct cmd_node limit_fpga_node = {
+ LIMIT_FPGA_NODE,
+ "%s(limit-fpga)# ",
+ 1,
+};
+
+static struct cmd_node limit_rmsdet_node = {
+ LIMIT_RMSDET_NODE,
+ "%s(limit-rmsdet)# ",
+ 1,
+};
+
+static struct cmd_node limit_ocxo_node = {
+ LIMIT_OCXO_NODE,
+ "%s(limit-ocxo)# ",
+ 1,
+};
+
+static struct cmd_node limit_tx0_temp_node = {
+ LIMIT_TX0_TEMP_NODE,
+ "%s(limit-tx0-temp)# ",
+ 1,
+};
+static struct cmd_node limit_tx1_temp_node = {
+ LIMIT_TX1_TEMP_NODE,
+ "%s(limit-tx1-temp)# ",
+ 1,
+};
+static struct cmd_node limit_pa0_temp_node = {
+ LIMIT_PA0_TEMP_NODE,
+ "%s(limit-pa0-temp)# ",
+ 1,
+};
+static struct cmd_node limit_pa1_temp_node = {
+ LIMIT_PA1_TEMP_NODE,
+ "%s(limit-pa1-temp)# ",
+ 1,
+};
+static struct cmd_node limit_supply_volt_node = {
+ LIMIT_SUPPLY_VOLT_NODE,
+ "%s(limit-supply-volt)# ",
+ 1,
+};
+static struct cmd_node limit_tx0_vswr_node = {
+ LIMIT_TX0_VSWR_NODE,
+ "%s(limit-tx0-vswr)# ",
+ 1,
+};
+static struct cmd_node limit_tx1_vswr_node = {
+ LIMIT_TX1_VSWR_NODE,
+ "%s(limit-tx1-vswr)# ",
+ 1,
+};
+static struct cmd_node limit_supply_pwr_node = {
+ LIMIT_SUPPLY_PWR_NODE,
+ "%s(limit-supply-pwr)# ",
+ 1,
+};
+static struct cmd_node limit_pa0_pwr_node = {
+ LIMIT_PA0_PWR_NODE,
+ "%s(limit-pa0-pwr)# ",
+ 1,
+};
+static struct cmd_node limit_pa1_pwr_node = {
+ LIMIT_PA1_PWR_NODE,
+ "%s(limit-pa1-pwr)# ",
+ 1,
+};
+
+static struct cmd_node limit_gps_fix_node = {
+ LIMIT_GPS_FIX_NODE,
+ "%s(limit-gps-fix)# ",
+ 1,
+};
+
+DEFUN(cfg_mgr, cfg_mgr_cmd,
+ "lc15bts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_volt_limit(struct vty *vty, const char *name,
+ struct lc15bts_volt_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning min %d%s",
+ limit->thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " threshold critical min %d%s",
+ limit->thresh_crit_min, VTY_NEWLINE);
+}
+
+static void write_vswr_limit(struct vty *vty, const char *name,
+ struct lc15bts_vswr_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning max %d%s",
+ limit->thresh_warn_max, VTY_NEWLINE);
+}
+
+static void write_pwr_limit(struct vty *vty, const char *name,
+ struct lc15bts_pwr_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning max %d%s",
+ limit->thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " threshold critical max %d%s",
+ limit->thresh_crit_max, VTY_NEWLINE);
+}
+
+static void write_norm_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa0-on%s",
+ (actions & SENSOR_ACT_NORM_PA0_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %spa1-on%s",
+ (actions & SENSOR_ACT_NORM_PA1_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-on%s",
+ (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
+}
+
+static void write_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa0-off%s",
+ (actions & SENSOR_ACT_PA0_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %spa1-off%s",
+ (actions & SENSOR_ACT_PA1_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-off%s",
+ (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
+}
+
+static int config_write_mgr(struct vty *vty)
+{
+ vty_out(vty, "lc15bts-mgr%s", VTY_NEWLINE);
+
+ write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit);
+ write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit);
+ write_vswr_limit(vty, "limits tx0_vswr", &s_mgr->vswr.tx0_vswr_limit);
+ write_vswr_limit(vty, "limits tx1_vswr", &s_mgr->vswr.tx1_vswr_limit);
+
+ write_norm_action(vty, "actions normal", s_mgr->state.action_norm);
+ write_action(vty, "actions warn", s_mgr->state.action_warn);
+ write_action(vty, "actions critical", s_mgr->state.action_crit);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->temp.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit)
+CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit)
+CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit)
+CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit)
+CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit)
+CFG_LIMIT_TEMP(tx0_temp, "TX0 TEMP\n", LIMIT_TX0_TEMP_NODE, tx0_temp_limit)
+CFG_LIMIT_TEMP(tx1_temp, "TX1 TEMP\n", LIMIT_TX1_TEMP_NODE, tx1_temp_limit)
+CFG_LIMIT_TEMP(pa0_temp, "PA0 TEMP\n", LIMIT_PA0_TEMP_NODE, pa0_temp_limit)
+CFG_LIMIT_TEMP(pa1_temp, "PA1 TEMP\n", LIMIT_PA1_TEMP_NODE, pa1_temp_limit)
+#undef CFG_LIMIT_TEMP
+
+#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->volt.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit)
+#undef CFG_LIMIT_VOLT
+
+#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->vswr.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_VSWR(tx0_vswr, "TX0 VSWR\n", LIMIT_TX0_VSWR_NODE, tx0_vswr_limit)
+CFG_LIMIT_VSWR(tx1_vswr, "TX1 VSWR\n", LIMIT_TX1_VSWR_NODE, tx1_vswr_limit)
+#undef CFG_LIMIT_VSWR
+
+#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->pwr.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit)
+CFG_LIMIT_PWR(pa0_pwr, "PA0 PWR\n", LIMIT_PA0_PWR_NODE, pa0_pwr_limit)
+CFG_LIMIT_PWR(pa1_pwr, "PA1 PWR\n", LIMIT_PA1_PWR_NODE, pa1_pwr_limit)
+#undef CFG_LIMIT_PWR
+
+#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->gps.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit)
+#undef CFG_LIMIT_GPS_FIX
+
+DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd,
+ "threshold warning min <0-48000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct lc15bts_volt_limit *limit = vty->index;
+ limit->thresh_warn_min = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd,
+ "threshold critical min <0-48000>",
+ "Threshold to reach\n" "Critical level\n" "Range\n")
+{
+ struct lc15bts_volt_limit *limit = vty->index;
+ limit->thresh_crit_min = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd,
+ "threshold warning max <1000-200000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct lc15bts_vswr_limit *limit = vty->index;
+ limit->thresh_warn_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd,
+ "threshold critical max <1000-200000>",
+ "Threshold to reach\n" "Critical level\n" "Range\n")
+{
+ struct lc15bts_vswr_limit *limit = vty->index;
+ limit->thresh_crit_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd,
+ "threshold warning max <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct lc15bts_pwr_limit *limit = vty->index;
+ limit->thresh_warn_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd,
+ "threshold critical max <0-200>",
+ "Threshold to reach\n" "Critical level\n" "Range\n")
+{
+ struct lc15bts_pwr_limit *limit = vty->index;
+ limit->thresh_crit_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define CFG_ACTION(name, expl, switch_to, variable) \
+DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
+ "actions " #name, \
+ "Configure Actions\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->state.variable; \
+ return CMD_SUCCESS; \
+}
+CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
+CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
+CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
+#undef CFG_ACTION
+
+DEFUN(cfg_action_pa0_on, cfg_action_pa0_on_cmd,
+ "pa0-on",
+ "Switch the Power Amplifier #0 on\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_PA0_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa0_on, cfg_no_action_pa0_on_cmd,
+ "no pa0-on",
+ NO_STR "Switch the Power Amplifieri #0 on\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_PA0_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa1_on, cfg_action_pa1_on_cmd,
+ "pa1-on",
+ "Switch the Power Amplifier #1 on\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_PA1_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa1_on, cfg_no_action_pa1_on_cmd,
+ "no pa1-on",
+ NO_STR "Switch the Power Amplifieri #1 on\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_PA1_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
+ "bts-service-on",
+ "Start the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
+ "no bts-service-on",
+ NO_STR "Start the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa0_off, cfg_action_pa0_off_cmd,
+ "pa0-off",
+ "Switch the Power Amplifier #0 off\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_PA0_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa0_off, cfg_no_action_pa0_off_cmd,
+ "no pa0-off",
+ NO_STR "Do not switch off the Power Amplifier #0\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_PA0_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa1_off, cfg_action_pa1_off_cmd,
+ "pa1-off",
+ "Switch the Power Amplifier #1 off\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_PA1_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa1_off, cfg_no_action_pa1_off_cmd,
+ "no pa1-off",
+ NO_STR "Do not switch off the Power Amplifier #1\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_PA1_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
+ "no bts-service-off",
+ NO_STR "Stop the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mgr, show_mgr_cmd, "show manager",
+ SHOW_STR "Display information about the manager")
+{
+ int temp, volt, current, power, vswr;
+ vty_out(vty, "Warning alarm flags: 0x%08x%s",
+ s_mgr->lc15bts_ctrl.warn_flags, VTY_NEWLINE);
+ vty_out(vty, "Critical alarm flags: 0x%08x%s",
+ s_mgr->lc15bts_ctrl.crit_flags, VTY_NEWLINE);
+ vty_out(vty, "Preventive action retried: %d%s",
+ s_mgr->alarms.preventive_retry, VTY_NEWLINE);
+ vty_out(vty, "Temperature control state: %s%s",
+ lc15bts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp);
+ vty_out(vty, " Main Supply : %4.2f Celcius%s",
+ temp/ 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp);
+ vty_out(vty, " SoC : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp);
+ vty_out(vty, " FPGA : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp);
+ vty_out(vty, " RMSDet : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp);
+ vty_out(vty, " OCXO : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp);
+ vty_out(vty, " TX 0 : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp);
+ vty_out(vty, " TX 1 : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp);
+ vty_out(vty, " Power Amp #0: %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp);
+ vty_out(vty, " Power Amp #1: %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_VOLTAGE, &volt);
+ lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_CURRENT, &current);
+ lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_POWER, &power);
+ vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA0,
+ LC15BTS_POWER_VOLTAGE, &volt);
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA0,
+ LC15BTS_POWER_CURRENT, &current);
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA0,
+ LC15BTS_POWER_POWER, &power);
+ vty_out(vty, " Power Amp #0: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ lc15bts_power_get(LC15BTS_POWER_PA0) ? "ON " : "OFF",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
+ LC15BTS_POWER_VOLTAGE, &volt);
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
+ LC15BTS_POWER_CURRENT, &current);
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
+ LC15BTS_POWER_POWER, &power);
+ vty_out(vty, " Power Amp #1: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ lc15bts_power_get(LC15BTS_POWER_PA1) ? "ON " : "OFF",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, "VSWR Status%s", VTY_NEWLINE);
+ lc15bts_vswr_get(LC15BTS_VSWR_TX0, &vswr);
+ vty_out(vty, " VSWR TX 0: %f %s",
+ vswr / 1000.0f,
+ VTY_NEWLINE);
+ lc15bts_vswr_get(LC15BTS_VSWR_TX1, &vswr);
+ vty_out(vty, " VSWR TX 1: %f %s",
+ vswr / 1000.0f,
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_thresh, show_thresh_cmd, "show thresholds",
+ SHOW_STR "Display information about the thresholds")
+{
+ vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE);
+ vty_out(vty, " Main supply%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " SoC%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " FPGA%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " RMSDet%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " OCXO%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " TX0%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " TX1%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " PA0%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " PA1%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, "Power limits%s", VTY_NEWLINE);
+ vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE);
+ vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " PA0 power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " PA1 power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, "VSWR limits%s", VTY_NEWLINE);
+ vty_out(vty, " TX0%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " TX1%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(calibrate_clock, calibrate_clock_cmd,
+ "calibrate clock",
+ "Calibration commands\n"
+ "Calibrate clock against GPS PPS\n")
+{
+ if (lc15bts_mgr_calib_run(s_mgr) < 0) {
+ vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_led_pattern, set_led_pattern_cmd,
+ "set led pattern <0-255>",
+ "Set LED pattern\n"
+ "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n")
+{
+ int pattern_id = atoi(argv[0]);
+
+ if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) {
+ vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ led_set(s_mgr, pattern_id);
+ return CMD_SUCCESS;
+}
+
+DEFUN(force_mgr_state, force_mgr_state_cmd,
+ "force manager state <0-255>",
+ "Force BTS manager state\n"
+ "Force BTS manager state for debugging purpose only\n")
+{
+ int state = atoi(argv[0]);
+
+ if ((state < 0) || (state > STATE_CRITICAL)) {
+ vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ s_mgr->state.state = state;
+ return CMD_SUCCESS;
+}
+
+#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \
+ "limit temp " #name " " #criticity " " #min_max " <-200-200>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->temp.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_min, warning, min)
+#undef LIMIT_TEMP
+
+#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \
+ "limit " #name " " #criticity " " #min_max " <0-48000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->volt.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min)
+#undef LIMIT_VOLT
+
+#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \
+ DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \
+ "limit power " #name " " #criticity " " #min_max " <0-200>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->pwr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max)
+LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_crit_max, critical, max)
+LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_PWR
+
+#define LIMIT_VSWR(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_vswr_##name##_##variable, limit_vswr_##name##_##variable##_cmd, \
+ "limit vswr " #name " " #criticity " " #min_max " <1000-200000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->vswr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_warn_max, warning, max)
+LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_crit_max, critical, max)
+LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_warn_max, warning, max)
+LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_VSWR
+
+#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \
+DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \
+ "limit gpsfix " #criticity " " #min_max " <0-365>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->gps.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max)
+#undef LIMIT_GPSFIX
+
+static void register_limit(int limit, uint32_t unit)
+{
+ switch (unit) {
+ case MGR_LIMIT_TYPE_VOLT:
+ install_element(limit, &cfg_thresh_volt_warn_min_cmd);
+ install_element(limit, &cfg_thresh_volt_crit_min_cmd);
+ break;
+ case MGR_LIMIT_TYPE_VSWR:
+ install_element(limit, &cfg_thresh_vswr_warn_max_cmd);
+ install_element(limit, &cfg_thresh_vswr_crit_max_cmd);
+ break;
+ case MGR_LIMIT_TYPE_PWR:
+ install_element(limit, &cfg_thresh_pwr_warn_max_cmd);
+ install_element(limit, &cfg_thresh_pwr_crit_max_cmd);
+ break;
+ default:
+ break;
+ }
+}
+
+static void register_normal_action(int act)
+{
+ install_element(act, &cfg_action_pa0_on_cmd);
+ install_element(act, &cfg_no_action_pa0_on_cmd);
+ install_element(act, &cfg_action_pa1_on_cmd);
+ install_element(act, &cfg_no_action_pa1_on_cmd);
+ install_element(act, &cfg_action_bts_srv_on_cmd);
+ install_element(act, &cfg_no_action_bts_srv_on_cmd);
+}
+
+static void register_action(int act)
+{
+ install_element(act, &cfg_action_pa0_off_cmd);
+ install_element(act, &cfg_no_action_pa0_off_cmd);
+ install_element(act, &cfg_action_pa1_off_cmd);
+ install_element(act, &cfg_no_action_pa1_off_cmd);
+ install_element(act, &cfg_action_bts_srv_off_cmd);
+ install_element(act, &cfg_no_action_bts_srv_off_cmd);
+}
+
+static void register_hidden_commands()
+{
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx0_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx1_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa0_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa1_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_min_cmd);
+
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd);
+
+ install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_crit_max_cmd);
+
+ install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_crit_max_cmd);
+
+ install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd);
+}
+
+int lc15bts_mgr_vty_init(void)
+{
+ vty_init(&vty_info);
+
+ install_element_ve(&show_mgr_cmd);
+ install_element_ve(&show_thresh_cmd);
+
+ install_element(ENABLE_NODE, &calibrate_clock_cmd);
+
+ install_node(&mgr_node, config_write_mgr);
+ install_element(CONFIG_NODE, &cfg_mgr_cmd);
+
+ /* install the limit nodes */
+ install_node(&limit_supply_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_temp_cmd);
+
+ install_node(&limit_soc_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_soc_temp_cmd);
+
+ install_node(&limit_fpga_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd);
+
+ install_node(&limit_rmsdet_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd);
+
+ install_node(&limit_ocxo_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd);
+
+ install_node(&limit_tx0_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx0_temp_cmd);
+
+ install_node(&limit_tx1_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx1_temp_cmd);
+
+ install_node(&limit_pa0_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa0_temp_cmd);
+
+ install_node(&limit_pa1_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa1_temp_cmd);
+
+ install_node(&limit_supply_volt_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_volt_cmd);
+ register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT);
+
+ install_node(&limit_tx0_vswr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx0_vswr_cmd);
+ register_limit(LIMIT_TX0_VSWR_NODE, MGR_LIMIT_TYPE_VSWR);
+
+ install_node(&limit_tx1_vswr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx1_vswr_cmd);
+ register_limit(LIMIT_TX1_VSWR_NODE, MGR_LIMIT_TYPE_VSWR);
+
+ install_node(&limit_supply_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd);
+ register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR);
+
+ install_node(&limit_pa0_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa0_pwr_cmd);
+ register_limit(LIMIT_PA0_PWR_NODE, MGR_LIMIT_TYPE_PWR);
+
+ install_node(&limit_pa1_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa1_pwr_cmd);
+ register_limit(LIMIT_PA1_PWR_NODE, MGR_LIMIT_TYPE_PWR);
+
+ install_node(&limit_gps_fix_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_gps_fix_cmd);
+
+ /* install the normal node */
+ install_node(&act_norm_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_normal_cmd);
+ register_normal_action(ACT_NORM_NODE);
+
+ /* install the warning and critical node */
+ install_node(&act_warn_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_warn_cmd);
+ register_action(ACT_WARN_NODE);
+
+ install_node(&act_crit_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_critical_cmd);
+ register_action(ACT_CRIT_NODE);
+
+ /* install LED pattern command for debugging purpose */
+ install_element_ve(&set_led_pattern_cmd);
+ install_element_ve(&force_mgr_state_cmd);
+
+ register_hidden_commands();
+
+ return 0;
+}
+
+int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *manager)
+{
+ int rc;
+
+ s_mgr = manager;
+ rc = vty_read_config_file(s_mgr->config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ s_mgr->config_file);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.c b/src/osmo-bts-litecell15/misc/lc15bts_misc.c
new file mode 100644
index 00000000..2cedc5d8
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.c
@@ -0,0 +1,383 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include "lc15bts_mgr.h"
+#include "btsconfig.h"
+#include "lc15bts_misc.h"
+#include "lc15bts_par.h"
+#include "lc15bts_mgr.h"
+#include "lc15bts_temp.h"
+#include "lc15bts_power.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum lc15bts_temp_sensor sensor;
+ enum lc15bts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "supply_temp",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_SUPPLY,
+ .ee_par = LC15BTS_PAR_TEMP_SUPPLY_MAX,
+ }, {
+ .name = "soc_temp",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_SOC,
+ .ee_par = LC15BTS_PAR_TEMP_SOC_MAX,
+ }, {
+ .name = "fpga_temp",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_FPGA,
+ .ee_par = LC15BTS_PAR_TEMP_FPGA_MAX,
+
+ }, {
+ .name = "rmsdet_temp",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_RMSDET,
+ .ee_par = LC15BTS_PAR_TEMP_RMSDET_MAX,
+ }, {
+ .name = "ocxo_temp",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_OCXO,
+ .ee_par = LC15BTS_PAR_TEMP_OCXO_MAX,
+ }, {
+ .name = "tx0_temp",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_TX0,
+ .ee_par = LC15BTS_PAR_TEMP_TX0_MAX,
+ }, {
+ .name = "tx1_temp",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_TX1,
+ .ee_par = LC15BTS_PAR_TEMP_TX1_MAX,
+ }, {
+ .name = "pa0_temp",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_PA0,
+ .ee_par = LC15BTS_PAR_TEMP_PA0_MAX,
+ }, {
+ .name = "pa1_temp",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_PA1,
+ .ee_par = LC15BTS_PAR_TEMP_PA1_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum lc15bts_power_source sensor_source;
+ enum lc15bts_power_type sensor_type;
+ enum lc15bts_par ee_par;
+} power_data[] = {
+ {
+ .name = "supply_volt",
+ .has_max = 1,
+ .sensor_source = LC15BTS_POWER_SUPPLY,
+ .sensor_type = LC15BTS_POWER_VOLTAGE,
+ .ee_par = LC15BTS_PAR_VOLT_SUPPLY_MAX,
+ }, {
+ .name = "supply_pwr",
+ .has_max = 1,
+ .sensor_source = LC15BTS_POWER_SUPPLY,
+ .sensor_type = LC15BTS_POWER_POWER,
+ .ee_par = LC15BTS_PAR_PWR_SUPPLY_MAX,
+ }, {
+ .name = "pa0_pwr",
+ .has_max = 1,
+ .sensor_source = LC15BTS_POWER_PA0,
+ .sensor_type = LC15BTS_POWER_POWER,
+ .ee_par = LC15BTS_PAR_PWR_PA0_MAX,
+ }, {
+ .name = "pa1_pwr",
+ .has_max = 1,
+ .sensor_source = LC15BTS_POWER_PA1,
+ .sensor_type = LC15BTS_POWER_POWER,
+ .ee_par = LC15BTS_PAR_PWR_PA1_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum lc15bts_vswr_sensor sensor;
+ enum lc15bts_par ee_par;
+} vswr_data[] = {
+ {
+ .name = "tx0_vswr",
+ .has_max = 0,
+ .sensor = LC15BTS_VSWR_TX0,
+ .ee_par = LC15BTS_PAR_VSWR_TX0_MAX,
+ }, {
+ .name = "tx1_vswr",
+ .has_max = 0,
+ .sensor = LC15BTS_VSWR_TX1,
+ .ee_par = LC15BTS_PAR_VSWR_TX1_MAX,
+ }
+};
+
+static const struct value_string power_unit_strs[] = {
+ { LC15BTS_POWER_POWER, "W" },
+ { LC15BTS_POWER_VOLTAGE, "V" },
+ { 0, NULL }
+};
+
+void lc15bts_check_temp(int no_rom_write)
+{
+ int temp_old[ARRAY_SIZE(temp_data)];
+ int temp_cur[ARRAY_SIZE(temp_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
+ int ret;
+ rc = lc15bts_par_get_int(tall_mgr_ctx, temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+
+ lc15bts_temp_get(temp_data[i].sensor, &temp_cur[i]);
+ if (temp_cur[i] < 0 && temp_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d): unexpected value %d\n",
+ temp_data[i].sensor, temp_cur[i]);
+ continue;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
+ temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
+
+ if (temp_cur[i] > temp_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "temperature: %d.%d C\n", temp_data[i].name,
+ temp_cur[i]/1000, temp_old[i]%1000);
+
+ if (!no_rom_write) {
+ rc = lc15bts_par_set_int(tall_mgr_ctx, temp_data[i].ee_par, temp_cur[i]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max temp %d (%s)\n", temp_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+void lc15bts_check_power(int no_rom_write)
+{
+ int power_old[ARRAY_SIZE(power_data)];
+ int power_cur[ARRAY_SIZE(power_data)];
+ int i, rc;
+ int div_ratio;
+
+ for (i = 0; i < ARRAY_SIZE(power_data); i++) {
+ int ret;
+ rc = lc15bts_par_get_int(tall_mgr_ctx, power_data[i].ee_par, &ret);
+ switch(power_data[i].sensor_type) {
+ case LC15BTS_POWER_VOLTAGE:
+ div_ratio = 1000;
+ break;
+ case LC15BTS_POWER_POWER:
+ div_ratio = 1000000;
+ break;
+ default:
+ div_ratio = 1000;
+ }
+ power_old[i] = ret * div_ratio;
+
+ lc15bts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]);
+ if (power_cur[i] < 0 && power_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source,
+ power_data[i].sensor_type);
+ continue;
+ }
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n",
+ power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio,
+ get_value_string(power_unit_strs, power_data[i].sensor_type));
+
+ if (power_cur[i] > power_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "power: %d.%d %s\n", power_data[i].name,
+ power_cur[i]/div_ratio, power_cur[i]%div_ratio,
+ get_value_string(power_unit_strs, power_data[i].sensor_type));
+
+ if (!no_rom_write) {
+ rc = lc15bts_par_set_int(tall_mgr_ctx, power_data[i].ee_par, power_cur[i]/div_ratio);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max power %d (%s)\n", power_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+void lc15bts_check_vswr(int no_rom_write)
+{
+ int vswr_old[ARRAY_SIZE(vswr_data)];
+ int vswr_cur[ARRAY_SIZE(vswr_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(vswr_data); i++) {
+ int ret;
+ rc = lc15bts_par_get_int(tall_mgr_ctx, vswr_data[i].ee_par, &ret);
+ vswr_old[i] = ret * 1000;
+
+ lc15bts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]);
+ if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor);
+ continue;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n",
+ vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000);
+
+ if (vswr_cur[i] > vswr_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "vswr: %d.%d C\n", vswr_data[i].name,
+ vswr_cur[i]/1000, vswr_old[i]%1000);
+
+ if (!no_rom_write) {
+ rc = lc15bts_par_set_int(tall_mgr_ctx, vswr_data[i].ee_par, vswr_cur[i]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max vswr %d (%s)\n", vswr_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+/*********************************************************************
+ * Hours handling
+ *********************************************************************/
+static time_t last_update;
+
+int lc15bts_update_hours(int no_rom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ /* number of hours to increase */
+ op_hrs += (now-last_update)/3600;
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ if (!no_rom_write) {
+ rc = lc15bts_par_set_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+static const char *fw_sysfs[_NUM_FW] = {
+ [LC15BTS_FW_DSP0] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+ [LC15BTS_FW_DSP1] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+};
+
+int lc15bts_firmware_reload(enum lc15bts_firmware_type type)
+{
+ int fd;
+ int rc;
+
+ switch (type) {
+ case LC15BTS_FW_DSP0:
+ case LC15BTS_FW_DSP1:
+ fd = open(fw_sysfs[type], O_WRONLY);
+ if (fd < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
+ fw_sysfs[type], strerror(errno));
+ close(fd);
+ return fd;
+ }
+ rc = write(fd, "restart", 8);
+ if (rc < 8) {
+ LOGP(DFW, LOGL_ERROR, "short write during "
+ "fw write to %s\n", fw_sysfs[type]);
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.h b/src/osmo-bts-litecell15/misc/lc15bts_misc.h
new file mode 100644
index 00000000..79e9e686
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.h
@@ -0,0 +1,18 @@
+#ifndef _LC15BTS_MISC_H
+#define _LC15BTS_MISC_H
+
+#include <stdint.h>
+
+void lc15bts_check_temp(int no_rom_write);
+void lc15bts_check_power(int no_rom_write);
+void lc15bts_check_vswr(int no_rom_write);
+
+int lc15bts_update_hours(int no_rom_write);
+
+enum lc15bts_firmware_type {
+ LC15BTS_FW_DSP0,
+ LC15BTS_FW_DSP1,
+ _NUM_FW
+};
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_nl.c
new file mode 100644
index 00000000..39f64aae
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_nl.c
@@ -0,0 +1,123 @@
+/* Helper for netlink */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_nl.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+
+#include <sys/socket.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/**
+ * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source
+ * address will be used when sending a message this function can be used.
+ * It will ask the routing code of the kernel for the PREFSRC
+ */
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source)
+{
+ int fd, rc;
+ struct rtmsg *r;
+ struct rtattr *rta;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (fd < 0) {
+ perror("nl socket");
+ return -1;
+ }
+
+ /* Send a rtmsg and ask for a response */
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.n.nlmsg_seq = 1;
+
+ /* Prepare the routing request */
+ req.r.rtm_family = AF_INET;
+
+ /* set the dest */
+ rta = NLMSG_TAIL(&req.n);
+ rta->rta_type = RTA_DST;
+ rta->rta_len = RTA_LENGTH(sizeof(*dest));
+ memcpy(RTA_DATA(rta), dest, sizeof(*dest));
+
+ /* update sizes for dest */
+ req.r.rtm_dst_len = sizeof(*dest) * 8;
+ req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
+
+ rc = send(fd, &req, req.n.nlmsg_len, 0);
+ if (rc != req.n.nlmsg_len) {
+ perror("short write");
+ close(fd);
+ return -2;
+ }
+
+
+ /* now receive a response and parse it */
+ rc = recv(fd, &req, sizeof(req), 0);
+ if (rc <= 0) {
+ perror("short read");
+ close(fd);
+ return -3;
+ }
+
+ if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) {
+ close(fd);
+ return -4;
+ }
+
+ r = NLMSG_DATA(&req.n);
+ rc -= NLMSG_LENGTH(sizeof(*r));
+ rta = RTM_RTA(r);
+ while (RTA_OK(rta, rc)) {
+ if (rta->rta_type != RTA_PREFSRC) {
+ rta = RTA_NEXT(rta, rc);
+ continue;
+ }
+
+ /* we are done */
+ memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+ return -5;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.h b/src/osmo-bts-litecell15/misc/lc15bts_nl.h
new file mode 100644
index 00000000..340cf117
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_nl.h
@@ -0,0 +1,27 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_nl.h
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+struct in_addr;
+
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source);
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.c b/src/osmo-bts-litecell15/misc/lc15bts_par.c
new file mode 100644
index 00000000..af9d030f
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_par.c
@@ -0,0 +1,232 @@
+/* lc15bts - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_par.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+
+#include "lc15bts_par.h"
+
+const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1] = {
+ { LC15BTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
+ { LC15BTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
+ { LC15BTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
+ { LC15BTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" },
+ { LC15BTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" },
+ { LC15BTS_PAR_TEMP_TX0_MAX, "temp-tx0-max" },
+ { LC15BTS_PAR_TEMP_TX1_MAX, "temp-tx1-max" },
+ { LC15BTS_PAR_TEMP_PA0_MAX, "temp-pa0-max" },
+ { LC15BTS_PAR_TEMP_PA1_MAX, "temp-pa1-max" },
+ { LC15BTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" },
+ { LC15BTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" },
+ { LC15BTS_PAR_PWR_PA0_MAX, "pwr-pa0-max" },
+ { LC15BTS_PAR_PWR_PA1_MAX, "pwr-pa1-max" },
+ { LC15BTS_PAR_VSWR_TX0_MAX, "vswr-tx0-max" },
+ { LC15BTS_PAR_VSWR_TX1_MAX, "vswr-tx1-max" },
+ { LC15BTS_PAR_GPS_FIX, "gps-fix" },
+ { LC15BTS_PAR_SERNR, "serial-nr" },
+ { LC15BTS_PAR_HOURS, "hours-running" },
+ { LC15BTS_PAR_BOOTS, "boot-count" },
+ { LC15BTS_PAR_KEY, "key" },
+ { 0, NULL }
+};
+
+int lc15bts_par_is_int(enum lc15bts_par par)
+{
+ switch (par) {
+ case LC15BTS_PAR_TEMP_SUPPLY_MAX:
+ case LC15BTS_PAR_TEMP_SOC_MAX:
+ case LC15BTS_PAR_TEMP_FPGA_MAX:
+ case LC15BTS_PAR_TEMP_RMSDET_MAX:
+ case LC15BTS_PAR_TEMP_OCXO_MAX:
+ case LC15BTS_PAR_TEMP_TX0_MAX:
+ case LC15BTS_PAR_TEMP_TX1_MAX:
+ case LC15BTS_PAR_TEMP_PA0_MAX:
+ case LC15BTS_PAR_TEMP_PA1_MAX:
+ case LC15BTS_PAR_VOLT_SUPPLY_MAX:
+ case LC15BTS_PAR_VSWR_TX0_MAX:
+ case LC15BTS_PAR_VSWR_TX1_MAX:
+ case LC15BTS_PAR_SERNR:
+ case LC15BTS_PAR_HOURS:
+ case LC15BTS_PAR_BOOTS:
+ case LC15BTS_PAR_PWR_SUPPLY_MAX:
+ case LC15BTS_PAR_PWR_PA0_MAX:
+ case LC15BTS_PAR_PWR_PA1_MAX:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+FILE *lc15bts_par_get_path(void *ctx, enum lc15bts_par par, const char* mode)
+{
+ char *fpath;
+ FILE *fp;
+
+ if (par >= _NUM_LC15BTS_PAR)
+ return NULL;
+
+ fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par));
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, mode);
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ return fp;
+}
+
+int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret)
+{
+ FILE *fp = lc15bts_par_get_path(ctx, par, "r");
+ int rc;
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+ return 0;
+}
+
+int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val)
+{
+ FILE *fp = lc15bts_par_get_path(ctx, par, "w");
+ int rc;
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+
+ fsync(fileno(fp));
+ fclose(fp);
+ return 0;
+}
+
+int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size)
+{
+ FILE *fp = lc15bts_par_get_path(ctx, par, "rb");
+ int rc;
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fread(buf, 1, size, fp);
+
+ fclose(fp);
+
+ return rc;
+}
+
+int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size)
+{
+ FILE *fp = lc15bts_par_get_path(ctx, par, "wb");
+ int rc;
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fwrite(buf, 1, size, fp);
+
+ fsync(fileno(fp));
+ fclose(fp);
+
+ return rc;
+}
+
+int lc15bts_par_get_gps_fix(time_t *ret)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%lld", (long long *)ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+int lc15bts_par_set_gps_fix(time_t val)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%lld", (long long)val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fileno(fp));
+ fclose(fp);
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.h b/src/osmo-bts-litecell15/misc/lc15bts_par.h
new file mode 100644
index 00000000..217ae5f2
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_par.h
@@ -0,0 +1,44 @@
+#ifndef _LC15BTS_PAR_H
+#define _LC15BTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+#define FACTORY_ROM_PATH "/mnt/rom/factory"
+#define USER_ROM_PATH "/mnt/storage/var/run/lc15bts-mgr"
+
+enum lc15bts_par {
+ LC15BTS_PAR_TEMP_SUPPLY_MAX,
+ LC15BTS_PAR_TEMP_SOC_MAX,
+ LC15BTS_PAR_TEMP_FPGA_MAX,
+ LC15BTS_PAR_TEMP_RMSDET_MAX,
+ LC15BTS_PAR_TEMP_OCXO_MAX,
+ LC15BTS_PAR_TEMP_TX0_MAX,
+ LC15BTS_PAR_TEMP_TX1_MAX,
+ LC15BTS_PAR_TEMP_PA0_MAX,
+ LC15BTS_PAR_TEMP_PA1_MAX,
+ LC15BTS_PAR_VOLT_SUPPLY_MAX,
+ LC15BTS_PAR_PWR_SUPPLY_MAX,
+ LC15BTS_PAR_PWR_PA0_MAX,
+ LC15BTS_PAR_PWR_PA1_MAX,
+ LC15BTS_PAR_VSWR_TX0_MAX,
+ LC15BTS_PAR_VSWR_TX1_MAX,
+ LC15BTS_PAR_GPS_FIX,
+ LC15BTS_PAR_SERNR,
+ LC15BTS_PAR_HOURS,
+ LC15BTS_PAR_BOOTS,
+ LC15BTS_PAR_KEY,
+ _NUM_LC15BTS_PAR
+};
+
+extern const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1];
+
+int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret);
+int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val);
+int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size);
+int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size);
+
+int lc15bts_par_is_int(enum lc15bts_par par);
+int lc15bts_par_get_gps_fix(time_t *ret);
+int lc15bts_par_set_gps_fix(time_t val);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.c b/src/osmo-bts-litecell15/misc/lc15bts_power.c
new file mode 100644
index 00000000..1a37d8e6
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_power.c
@@ -0,0 +1,210 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "lc15bts_power.h"
+
+#define LC15BTS_PA_VOLTAGE 24000000
+
+#define PA_SUPPLY_MIN_SYSFS "/var/lc15/pa-supply/min_microvolts"
+#define PA_SUPPLY_MAX_SYSFS "/var/lc15/pa-supply/max_microvolts"
+
+static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
+ [LC15BTS_POWER_PA0] = "/var/lc15/pa-state/pa0/state",
+ [LC15BTS_POWER_PA1] = "/var/lc15/pa-state/pa1/state",
+};
+
+static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
+ [LC15BTS_POWER_SUPPLY] = "/var/lc15/pwr-sense/main-supply/",
+ [LC15BTS_POWER_PA0] = "/var/lc15/pwr-sense/pa0/",
+ [LC15BTS_POWER_PA1] = "/var/lc15/pwr-sense/pa1/",
+};
+
+static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
+ [LC15BTS_POWER_POWER] = "power",
+ [LC15BTS_POWER_VOLTAGE] = "voltage",
+ [LC15BTS_POWER_CURRENT] = "current",
+};
+
+int lc15bts_power_sensor_get(
+ enum lc15bts_power_source source,
+ enum lc15bts_power_type type,
+ int *power)
+{
+ char buf[PATH_MAX];
+ char pwrstr[10];
+ int fd, rc;
+
+ if (source >= _NUM_POWER_SOURCES)
+ return -EINVAL;
+
+ if (type >= _NUM_POWER_TYPES)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, pwrstr, sizeof(pwrstr));
+ pwrstr[sizeof(pwrstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *power = atoi(pwrstr);
+ return 0;
+}
+
+
+int lc15bts_power_set(
+ enum lc15bts_power_source source,
+ int en)
+{
+ int fd;
+ int rc;
+
+ if ((source != LC15BTS_POWER_PA0)
+ && (source != LC15BTS_POWER_PA1) ) {
+ return -EINVAL;
+ }
+
+ fd = open(PA_SUPPLY_MAX_SYSFS, O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ rc = write(fd, "32000000", 9);
+ close( fd );
+
+ if (rc != 9) {
+ return -1;
+ }
+
+ fd = open(PA_SUPPLY_MIN_SYSFS, O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+
+ /* TODO NTQ: Make the voltage configurable */
+ rc = write(fd, "24000000", 9);
+ close( fd );
+
+ if (rc != 9) {
+ return -1;
+ }
+
+ fd = open(power_enable_devs[source], O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ rc = write(fd, en?"1":"0", 2);
+ close( fd );
+
+ if (rc != 2) {
+ return -1;
+ }
+
+ if (en) usleep(50*1000);
+
+ return 0;
+}
+
+int lc15bts_power_get(
+ enum lc15bts_power_source source)
+{
+ int fd;
+ int rc;
+ int retVal = 0;
+ char enstr[10];
+
+ fd = open(power_enable_devs[source], O_RDONLY);
+ if (fd < 0) {
+ return fd;
+ }
+
+ rc = read(fd, enstr, sizeof(enstr));
+ enstr[rc-1] = '\0';
+
+ close(fd);
+
+ if (rc < 0) {
+ return rc;
+ }
+ if (rc == 0) {
+ return -EIO;
+ }
+
+ rc = strcmp(enstr, "enabled");
+ if(rc == 0) {
+ retVal = 1;
+ }
+
+ return retVal;
+}
+
+static const char *vswr_devs[_NUM_VSWR_SENSORS] = {
+ [LC15BTS_VSWR_TX0] = "/var/lc15/vswr/tx0/vswr",
+ [LC15BTS_VSWR_TX1] = "/var/lc15/vswr/tx1/vswr",
+};
+
+int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr)
+{
+ char buf[PATH_MAX];
+ char vswrstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, vswrstr, sizeof(vswrstr));
+ vswrstr[sizeof(vswrstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *vswr = atoi(vswrstr);
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.h b/src/osmo-bts-litecell15/misc/lc15bts_power.h
new file mode 100644
index 00000000..b48cfdcd
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_power.h
@@ -0,0 +1,38 @@
+#ifndef _LC15BTS_POWER_H
+#define _LC15BTS_POWER_H
+
+enum lc15bts_power_source {
+ LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_PA0,
+ LC15BTS_POWER_PA1,
+ _NUM_POWER_SOURCES
+};
+
+enum lc15bts_power_type {
+ LC15BTS_POWER_POWER,
+ LC15BTS_POWER_VOLTAGE,
+ LC15BTS_POWER_CURRENT,
+ _NUM_POWER_TYPES
+};
+
+int lc15bts_power_sensor_get(
+ enum lc15bts_power_source source,
+ enum lc15bts_power_type type,
+ int *volt);
+
+int lc15bts_power_set(
+ enum lc15bts_power_source source,
+ int en);
+
+int lc15bts_power_get(
+ enum lc15bts_power_source source);
+
+enum lc15bts_vswr_sensor {
+ LC15BTS_VSWR_TX0,
+ LC15BTS_VSWR_TX1,
+ _NUM_VSWR_SENSORS
+};
+
+int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.c b/src/osmo-bts-litecell15/misc/lc15bts_swd.c
new file mode 100644
index 00000000..59c7b616
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_swd.c
@@ -0,0 +1,178 @@
+/* Systemd service wd notification for Litecell 1.5 BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_swd.h"
+#include <osmocom/core/logging.h>
+
+/* Needed for service watchdog notification */
+#include <systemd/sd-daemon.h>
+
+/* This is the period used to verify if all events have been registered to be allowed
+ to notify the systemd service watchdog
+*/
+#define SWD_PERIOD 30
+
+static void swd_start(struct lc15bts_mgr_instance *mgr);
+static void swd_process(struct lc15bts_mgr_instance *mgr);
+static void swd_close(struct lc15bts_mgr_instance *mgr);
+static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int reason);
+static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop);
+static void swd_loop_run(void *_data);
+
+enum swd_state {
+ SWD_INITIAL,
+ SWD_IN_PROGRESS,
+};
+
+enum swd_result {
+ SWD_FAIL_START,
+ SWD_FAIL_NOTIFY,
+ SWD_SUCCESS,
+};
+
+static void swd_start(struct lc15bts_mgr_instance *mgr)
+{
+ swd_process(mgr);
+}
+
+static void swd_process(struct lc15bts_mgr_instance *mgr)
+{
+ int rc = 0, notify = 0;
+
+ /* Did we get all needed conditions ? */
+ if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) {
+ /* Ping systemd service wd if enabled */
+ rc = sd_notify(0, "WATCHDOG=1");
+ LOGP(DSWD, LOGL_NOTICE, "Watchdog notification attempt\n");
+ notify = 1;
+ }
+ else {
+ LOGP(DSWD, LOGL_NOTICE, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks);
+ }
+
+ if (rc < 0) {
+ LOGP(DSWD, LOGL_ERROR,
+ "Failed to notify system service watchdog: %d\n", rc);
+ swd_state_reset(mgr, SWD_FAIL_NOTIFY);
+ return;
+ }
+ else {
+ /* Did we notified the watchdog? */
+ if (notify) {
+ mgr->swd.swd_events = 0;
+ /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */
+ if (mgr->swd.swd_events != 0)
+ mgr->swd.swd_events = 0;
+ }
+ }
+
+ swd_state_reset(mgr, SWD_SUCCESS);
+ return;
+}
+
+static void swd_close(struct lc15bts_mgr_instance *mgr)
+{
+}
+
+static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->swd.swd_from_loop) {
+ mgr->swd.swd_timeout.data = mgr;
+ mgr->swd.swd_timeout.cb = swd_loop_run;
+ osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0);
+ }
+
+ mgr->swd.state = SWD_INITIAL;
+ swd_close(mgr);
+}
+
+static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->swd.state != SWD_INITIAL) {
+ LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n");
+ return -1;
+ }
+
+ mgr->swd.swd_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->swd.state = SWD_IN_PROGRESS;
+ swd_start(mgr);
+ return 0;
+}
+
+static void swd_loop_run(void *_data)
+{
+ int rc;
+ struct lc15bts_mgr_instance *mgr = _data;
+
+ LOGP(DSWD, LOGL_NOTICE, "Going to check for watchdog notification.\n");
+ rc = swd_run(mgr, 1);
+ if (rc != 0) {
+ swd_state_reset(mgr, SWD_FAIL_START);
+ }
+}
+
+/* 'swd_num_events' configures the number of events to be monitored before notifying the
+ systemd service watchdog. It must be in the range of [1,64]. Events are notified
+ through the function 'lc15bts_swd_event'
+*/
+int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events)
+{
+ /* Checks for a valid number of events to validate */
+ if (swd_num_events < 1 || swd_num_events > 64)
+ return(-1);
+
+ mgr->swd.state = SWD_INITIAL;
+ mgr->swd.swd_timeout.data = mgr;
+ mgr->swd.swd_timeout.cb = swd_loop_run;
+ osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0);
+
+ if (swd_num_events == 64){
+ mgr->swd.swd_eventmasks = 0xffffffffffffffffULL;
+ }
+ else {
+ mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1);
+ }
+ mgr->swd.swd_events = 0;
+ mgr->swd.num_events = swd_num_events;
+
+ return 0;
+}
+
+/* Notifies that the specified event 'swd_event' happened correctly;
+ the value must be in the range of [0,'swd_num_events'[ (see lc15bts_swd_init).
+ For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63.
+ WARNING: if this function can be used from multiple threads at the same time,
+ it must be protected with a kind of mutex to avoid loosing event notification.
+*/
+int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event)
+{
+ /* Checks for a valid specified event (smaller than max possible) */
+ if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events)
+ return(-1);
+
+ mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event));
+
+ /* !!! Uncomment following line to debug events notification */
+ LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event));
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.h b/src/osmo-bts-litecell15/misc/lc15bts_swd.h
new file mode 100644
index 00000000..b78a2c2a
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_swd.h
@@ -0,0 +1,7 @@
+#ifndef _LC15BTS_SWD_H
+#define _LC15BTS_SWD_H
+
+int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events);
+int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_temp.c
new file mode 100644
index 00000000..45602dcc
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.c
@@ -0,0 +1,74 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <osmocom/core/utils.h>
+
+#include "lc15bts_temp.h"
+
+static const char *temp_devs[_NUM_TEMP_SENSORS] = {
+ [LC15BTS_TEMP_SUPPLY] = "/var/lc15/temp/main-supply/temp",
+ [LC15BTS_TEMP_SOC] = "/var/lc15/temp/cpu/temp",
+ [LC15BTS_TEMP_FPGA] = "/var/lc15/temp/fpga/temp",
+ [LC15BTS_TEMP_RMSDET] = "/var/lc15/temp/rmsdet/temp",
+ [LC15BTS_TEMP_OCXO] = "/var/lc15/temp/ocxo/temp",
+ [LC15BTS_TEMP_TX0] = "/var/lc15/temp/tx0/temp",
+ [LC15BTS_TEMP_TX1] = "/var/lc15/temp/tx1/temp",
+ [LC15BTS_TEMP_PA0] = "/var/lc15/temp/pa0/temp",
+ [LC15BTS_TEMP_PA1] = "/var/lc15/temp/pa1/temp",
+};
+
+int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp)
+{
+ char buf[PATH_MAX];
+ char tempstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, tempstr, sizeof(tempstr));
+ tempstr[sizeof(tempstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *temp = atoi(tempstr);
+ return 0;
+}
+
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.h b/src/osmo-bts-litecell15/misc/lc15bts_temp.h
new file mode 100644
index 00000000..35d81f1b
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.h
@@ -0,0 +1,28 @@
+#ifndef _LC15BTS_TEMP_H
+#define _LC15BTS_TEMP_H
+
+enum lc15bts_temp_sensor {
+ LC15BTS_TEMP_SUPPLY,
+ LC15BTS_TEMP_SOC,
+ LC15BTS_TEMP_FPGA,
+ LC15BTS_TEMP_RMSDET,
+ LC15BTS_TEMP_OCXO,
+ LC15BTS_TEMP_TX0,
+ LC15BTS_TEMP_TX1,
+ LC15BTS_TEMP_PA0,
+ LC15BTS_TEMP_PA1,
+ _NUM_TEMP_SENSORS
+};
+
+enum lc15bts_temp_type {
+ LC15BTS_TEMP_INPUT,
+ LC15BTS_TEMP_LOWEST,
+ LC15BTS_TEMP_HIGHEST,
+ LC15BTS_TEMP_FAULT,
+ _NUM_TEMP_TYPES
+};
+
+int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp);
+
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_util.c b/src/osmo-bts-litecell15/misc/lc15bts_util.c
new file mode 100644
index 00000000..430ce0f7
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_util.c
@@ -0,0 +1,164 @@
+/* lc15bts-util - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+
+#include "lc15bts_par.h"
+
+void *tall_util_ctx;
+
+enum act {
+ ACT_GET,
+ ACT_SET,
+};
+
+static enum act action;
+static char *write_arg;
+static int void_warranty;
+
+static void print_help()
+{
+ const struct value_string *par = lc15bts_par_names;
+
+ printf("lc15bts-util [--void-warranty -r | -w value] param_name\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!lc15bts_par_is_int(par->value))
+ continue;
+ printf(" %s\n", par->str);
+ }
+}
+
+static int parse_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "read", 0, 0, 'r' },
+ { "void-warranty", 0, 0, 1000},
+ { "write", 1, 0, 'w' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "rw:h",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'r':
+ action = ACT_GET;
+ break;
+ case 'w':
+ action = ACT_SET;
+ write_arg = optarg;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ break;
+ case 1000:
+ printf("Will void warranty on write.\n");
+ void_warranty = 1;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *parname;
+ enum lc15bts_par par;
+ int rc, val;
+
+ tall_util_ctx = talloc_named_const(NULL, 1, "lc15 utils");
+ msgb_talloc_ctx_init(tall_util_ctx, 0);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ if (optind >= argc) {
+ fprintf(stderr, "You must specify the parameter name\n");
+ exit(2);
+ }
+ parname = argv[optind];
+
+ rc = get_string_value(lc15bts_par_names, parname);
+ if (rc < 0) {
+ fprintf(stderr, "`%s' is not a valid parameter\n", parname);
+ exit(2);
+ } else
+ par = rc;
+
+ switch (action) {
+ case ACT_GET:
+ rc = lc15bts_par_get_int(tall_util_ctx, par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("%d\n", val);
+ break;
+ case ACT_SET:
+ rc = lc15bts_par_get_int(tall_util_ctx, par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
+ fprintf(stderr, "Parameter is already set!\r\n");
+ goto err;
+ }
+ rc = lc15bts_par_set_int(tall_util_ctx, par, atoi(write_arg));
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("Success setting %s=%d\n", parname,
+ atoi(write_arg));
+ break;
+ default:
+ fprintf(stderr, "Unsupported action\n");
+ goto err;
+ }
+
+ exit(0);
+
+err:
+ exit(1);
+}
+
diff --git a/src/osmo-bts-litecell15/oml.c b/src/osmo-bts-litecell15/oml.c
new file mode 100644
index 00000000..f084f1bf
--- /dev/null
+++ b/src/osmo-bts-litecell15/oml.c
@@ -0,0 +1,1941 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013-2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+#include <nrw/litecell15/litecell15.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "lc15bts.h"
+#include "utils.h"
+
+static int mph_info_chan_confirm(struct gsm_lchan *lchan,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(lchan->ts->trx, &l1sap);
+}
+
+enum sapi_cmd_type {
+ SAPI_CMD_ACTIVATE,
+ SAPI_CMD_CONFIG_CIPHERING,
+ SAPI_CMD_CONFIG_LOGCH_PARAM,
+ SAPI_CMD_SACCH_REL_MARKER,
+ SAPI_CMD_REL_MARKER,
+ SAPI_CMD_DEACTIVATE,
+};
+
+struct sapi_cmd {
+ struct llist_head entry;
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+ enum sapi_cmd_type type;
+ int (*callback)(struct gsm_lchan *lchan, int status);
+};
+
+static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = GsmL1_LogChComb_0,
+ [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV,
+ [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I,
+ [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII,
+ [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0,
+ /*
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+ * part of this, only "real" pchan values will be looked up here.
+ * See the callers of ts_connect_as().
+ */
+};
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);
+
+static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct lc15l1_hdl *gl1,
+ uint32_t hLayer3_uint32)
+{
+ HANDLE hLayer3;
+ prim->id = id;
+
+ osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit);
+ hLayer3 = (void*)hLayer3_uint32;
+
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq:
+ //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ prim->u.mphInitReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseReq:
+ prim->u.mphCloseReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphCloseReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectReq:
+ prim->u.mphConnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectReq:
+ prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDisconnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateReq:
+ prim->u.mphActivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphActivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateReq:
+ prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDeactivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigReq:
+ prim->u.mphConfigReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConfigReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureReq:
+ prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphMeasureReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphInitCnf:
+ prim->u.mphInitCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseCnf:
+ prim->u.mphCloseCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectCnf:
+ prim->u.mphConnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ prim->u.mphDisconnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateCnf:
+ prim->u.mphActivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ prim->u.mphDeactivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigCnf:
+ prim->u.mphConfigCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureCnf:
+ prim->u.mphMeasureCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphTimeInd:
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ break;
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhDataReq:
+ prim->u.phDataReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id);
+ break;
+ }
+ return &prim->u;
+}
+
+static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx)
+{
+ struct gsm_bts *bts = trx->bts;
+
+ osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit);
+ osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit);
+
+ return bts->nr << 24
+ | trx->nr << 16;
+}
+
+static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts)
+{
+ osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit);
+
+ return l1p_handle_for_trx(ts->trx)
+ | ts->nr << 8;
+}
+
+
+static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan)
+{
+ osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit);
+
+ return l1p_handle_for_ts(lchan->ts)
+ | lchan->nr;
+}
+
+GsmL1_Status_t prim_status(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.status;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.status;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.status;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.status;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.status;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.status;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.status;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.status;
+ default:
+ break;
+ }
+ return GsmL1_Status_Success;
+}
+
+#if 0
+static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data)
+{
+ struct msgb *resp_msg = data;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+
+ if (prim_status(l1p) != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id),
+ get_value_string(lc15bts_l1status_names, cc->status));
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ return abis_nm_sendmsg(msg);
+}
+#endif
+
+int lchan_activate(struct gsm_lchan *lchan);
+
+static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_Status_t status = prim_status(l1p);
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id),
+ get_value_string(lc15bts_l1status_names, status));
+ msgb_free(l1_msg);
+ return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM);
+ }
+
+ msgb_free(l1_msg);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
+ mo->obj_inst.ts_nr == 0) {
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
+ DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n");
+ mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ if (cbch) {
+ cbch->rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(cbch);
+ }
+ }
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(mo);
+}
+
+static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_abis_mo *mo;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+
+ mo = &trx->ts[cnf->u8Tn].mo;
+ return opstart_compl(mo, l1_msg);
+}
+
+static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-MUTE failure");
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf;
+
+ LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n",
+ get_value_string(lc15bts_l1status_names, ic->status));
+
+ /* store layer1 handle */
+ if (ic->status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n",
+ get_value_string(lc15bts_l1status_names, ic->status));
+ bts_shutdown(trx->bts, "MPH-INIT failure");
+ }
+
+ fl1h->hLayer1 = ic->hLayer1;
+
+ /* If the TRX was already locked the MphInit would have undone it */
+ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
+ trx_rf_lock(trx, 1, trx_mute_on_init_cb);
+
+ /* Begin to ramp up the power */
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+
+ return opstart_compl(&trx->mo, l1_msg);
+}
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids,
+ unsigned int num_attr_ids)
+{
+ unsigned int i;
+
+ if (!mo->nm_attr)
+ return 0;
+
+ for (i = 0; i < num_attr_ids; i++) {
+ if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct msgb *msg;
+ GsmL1_MphInitReq_t *mi_req;
+ GsmL1_DeviceParam_t *dev_par;
+ int lc15_band;
+
+ if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
+ ARRAY_SIZE(trx_rqd_attr))) {
+ /* HACK: spec says we need to decline, but openbsc
+ * doesn't deal with this very well */
+ return oml_mo_opstart_ack(&trx->mo);
+ //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
+ }
+
+ lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn);
+ if (lc15_band < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n",
+ gsm_band_name(trx->bts->band));
+ }
+
+ msg = l1p_msgb_alloc();
+ mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h,
+ l1p_handle_for_trx(trx));
+ dev_par = &mi_req->deviceParam;
+ dev_par->devType = GsmL1_DevType_TxdRxu;
+ dev_par->freqBand = lc15_band;
+ dev_par->u16Arfcn = trx->arfcn;
+ dev_par->u16BcchArfcn = trx->bts->c0->arfcn;
+ dev_par->u8NbTsc = trx->bts->bsic & 7;
+ dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx)
+ ? 0.0 : trx->bts->ul_power_target;
+
+ dev_par->fTxPowerLevel = 0.0;
+ LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, "
+ "TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc,
+ dev_par->fRxPowerLevel, dev_par->fTxPowerLevel);
+
+ /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */
+ return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
+}
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ return fl1h->hLayer1;
+}
+
+static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ msgb_free(l1_msg);
+ return 0;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct msgb *msg;
+
+ msg = l1p_msgb_alloc();
+ prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h,
+ l1p_handle_for_trx(trx));
+ LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr);
+
+ return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+}
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ uint8_t mute[8];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mute); ++i)
+ mute[i] = locked ? 1 : 0;
+
+ return l1if_mute_rf(fl1h, mute, cb);
+}
+
+int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
+ int success)
+{
+ if (success) {
+ int i;
+ int is_locked = 1;
+
+ for (i = 0; i < 8; ++i)
+ if (!mute_state[i])
+ is_locked = 0;
+
+ mo->nm_state.administrative =
+ is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_ack(mo);
+ } else {
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+ }
+}
+
+static int ts_connect_as(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan,
+ l1if_compl_cb *cb, void *data)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx);
+ GsmL1_MphConnectReq_t *cr;
+
+ if (pchan == GSM_PCHAN_TCH_F_PDCH
+ || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Requested TS connect as %s,"
+ " expected a specific pchan instead\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan));
+ return -EINVAL;
+ }
+
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+ cr->logChComb = pchan_to_logChComb[pchan];
+
+ return l1if_gsm_req_compl(fl1h, msg, cb, NULL);
+}
+
+static int ts_opstart(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan = ts->pchan;
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* First connect as NONE, until first RSL CHAN ACT. */
+ pchan = GSM_PCHAN_NONE;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* First connect as TCH/F, expecting PDCH ACT. */
+ pchan = GSM_PCHAN_TCH_F;
+ break;
+ default:
+ /* simply use ts->pchan */
+ break;
+ }
+ return ts_connect_as(ts, pchan, opstart_compl_cb, NULL);
+}
+
+GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
+{
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return GsmL1_Sapi_TchF;
+ case GSM_LCHAN_TCH_H:
+ return GsmL1_Sapi_TchH;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+ return GsmL1_Sapi_Idle;
+}
+
+GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = lchan->ts->pchan;
+
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ pchan = lchan->ts->dyn.pchan_want;
+
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ if (lchan->type == GSM_LCHAN_CCCH)
+ return GsmL1_SubCh_NA;
+ /* fall-through */
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ return lchan->nr;
+ case GSM_PCHAN_NONE:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_UNKNOWN:
+ default:
+ /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ return GsmL1_SubCh_NA;
+ }
+
+ return GsmL1_SubCh_NA;
+}
+
+struct sapi_dir {
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+};
+
+static const struct sapi_dir ccch_sapis[] = {
+ { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchf_sapis[] = {
+ { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchh_sapis[] = {
+ { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir sdcch_sapis[] = {
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir cbch_sapis[] = {
+ { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink },
+ /* Does the CBCH really have a SACCH in Downlink? */
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+};
+
+static const struct sapi_dir pdtch_sapis[] = {
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink },
+#if 0
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink },
+#endif
+};
+
+static const struct sapi_dir ho_sapis[] = {
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+struct lchan_sapis {
+ const struct sapi_dir *sapis;
+ unsigned int num_sapis;
+};
+
+static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
+ [GSM_LCHAN_SDCCH] = {
+ .sapis = sdcch_sapis,
+ .num_sapis = ARRAY_SIZE(sdcch_sapis),
+ },
+ [GSM_LCHAN_TCH_F] = {
+ .sapis = tchf_sapis,
+ .num_sapis = ARRAY_SIZE(tchf_sapis),
+ },
+ [GSM_LCHAN_TCH_H] = {
+ .sapis = tchh_sapis,
+ .num_sapis = ARRAY_SIZE(tchh_sapis),
+ },
+ [GSM_LCHAN_CCCH] = {
+ .sapis = ccch_sapis,
+ .num_sapis = ARRAY_SIZE(ccch_sapis),
+ },
+ [GSM_LCHAN_PDTCH] = {
+ .sapis = pdtch_sapis,
+ .num_sapis = ARRAY_SIZE(pdtch_sapis),
+ },
+ [GSM_LCHAN_CBCH] = {
+ .sapis = cbch_sapis,
+ .num_sapis = ARRAY_SIZE(cbch_sapis),
+ },
+};
+
+static const struct lchan_sapis sapis_for_ho = {
+ .sapis = ho_sapis,
+ .num_sapis = ARRAY_SIZE(ho_sapis),
+};
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir);
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan);
+
+/**
+ * Execute the first SAPI command of the queue. In case of the markers
+ * this method is re-entrant so we need to make sure to remove a command
+ * from the list before calling a function that will queue a command.
+ *
+ * \return 0 in case no Gsm Request was sent, 1 otherwise
+ */
+static int sapi_queue_exeute(struct gsm_lchan *lchan)
+{
+ int res;
+ struct sapi_cmd *cmd;
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+
+ switch (cmd->type) {
+ case SAPI_CMD_ACTIVATE:
+ mph_send_activate_req(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_CIPHERING:
+ mph_send_config_ciphering(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_LOGCH_PARAM:
+ mph_send_config_logchpar(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_SACCH_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_TxDownlink);
+ res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_RxUplink);
+ break;
+ case SAPI_CMD_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = lchan_deactivate_sapis(lchan);
+ break;
+ case SAPI_CMD_DEACTIVATE:
+ mph_send_deactivate_req(lchan, cmd);
+ res = 1;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE,
+ "Unimplemented command type %d\n", cmd->type);
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = 0;
+ abort();
+ break;
+ }
+
+ return res;
+}
+
+static void sapi_queue_send(struct gsm_lchan *lchan)
+{
+ int res;
+
+ do {
+ res = sapi_queue_exeute(lchan);
+ } while (res == 0 && !llist_empty(&lchan->sapi_cmds));
+}
+
+static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
+{
+ int end;
+ struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
+ struct sapi_cmd, entry);
+ llist_del(&cmd->entry);
+ end = llist_empty(&lchan->sapi_cmds);
+
+ if (cmd->callback)
+ cmd->callback(lchan, status);
+ talloc_free(cmd);
+
+ if (end || llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_DEBUG,
+ "%s End of SAPI cmd queue encountered.%s\n",
+ gsm_lchan_name(lchan),
+ llist_empty(&lchan->sapi_cmds)
+ ? " Queue is now empty."
+ : " More pending.");
+ return;
+ }
+
+ sapi_queue_send(lchan);
+}
+
+/**
+ * Queue and possible execute a SAPI command. Return 1 in case the command was
+ * already executed and 0 in case if it was only put into the queue
+ */
+static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ int start = llist_empty(&lchan->sapi_cmds);
+ llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
+
+ if (!start)
+ return 0;
+
+ sapi_queue_send(lchan);
+ return 1;
+}
+
+static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_ASSIGNED;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(lc15bts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_ACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan)
+{
+ return 0xBB
+ | (lchan->nr << 8)
+ | (lchan->ts->nr << 16)
+ | (lchan->ts->trx->nr << 24);
+}
+
+/* obtain a ptr to the lapdm_channel for a given hLayer */
+struct gsm_lchan *
+l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2)
+{
+ uint8_t magic = hLayer2 & 0xff;
+ uint8_t ts_nr = (hLayer2 >> 16) & 0xff;
+ uint8_t lchan_nr = (hLayer2 >> 8)& 0xff;
+ struct gsm_bts_trx_ts *ts;
+
+ if (magic != 0xBB)
+ return NULL;
+
+ /* FIXME: if we actually run on the BTS, the 32bit field is large
+ * enough to simply put a pointer inside. */
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+/* we regularly check if the DSP L1 is still sending us primitives.
+ * if not, we simply stop the BTS program (and be re-spawned) */
+static void alive_timer_cb(void *data)
+{
+ struct lc15l1_hdl *fl1h = data;
+
+ if (fl1h->alive_prim_cnt == 0) {
+ LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n");
+ exit(23);
+ }
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+}
+
+static void clear_amr_params(GsmL1_LogChParam_t *lch_par)
+{
+ int j;
+ /* common for the SIGN, V1 and EFR: */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA;
+ lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset;
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+}
+
+static void set_payload_format(GsmL1_LogChParam_t *lch_par)
+{
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp;
+}
+
+static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+ int j;
+
+ LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
+ gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ /* we have to set some TCH payload type even if we don't
+ * know yet what codec we will use later on */
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Efr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Amr;
+ set_payload_format(lch_par);
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */
+ lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
+
+ /* initialize to clean state */
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+
+ j = 0;
+ if (mr_conf->m4_75)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_15)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_90)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m6_70)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_40)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_95)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m10_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+ if (mr_conf->m12_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2;
+ break;
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ int sapi = cmd->sapi;
+ int dir = cmd->dir;
+ GsmL1_MphActivateReq_t *act_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ lch_par = &act_req->logChPrm;
+ act_req->u8Tn = lchan->ts->nr;
+ act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ act_req->dir = dir;
+ act_req->sapi = sapi;
+ act_req->hLayer2 = (HANDLE *)l1if_lchan_to_hLayer(lchan);
+ act_req->hLayer3 = act_req->hLayer2;
+
+ switch (act_req->sapi) {
+ case GsmL1_Sapi_Rach:
+ lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Agch:
+ lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name);
+ break;
+ case GsmL1_Sapi_TchH:
+ case GsmL1_Sapi_TchF:
+ lchan2lch_par(lch_par, lchan);
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Prach:
+ lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Sacch:
+ /*
+ * For the SACCH we need to set the u8MsPowerLevel when
+ * doing manual MS power control.
+ */
+ if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+ /* fall through */
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ default:
+ break;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2,
+ get_value_string(lc15bts_l1sapi_names, act_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, act_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
+}
+
+static void sapi_clear_queue(struct llist_head *queue)
+{
+ struct sapi_cmd *next, *tmp;
+
+ llist_for_each_entry_safe(next, tmp, queue, entry) {
+ llist_del(&next->entry);
+ talloc_free(next);
+ }
+}
+
+static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+
+ /* FIXME: Error handling */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s act failed mark broken due status: %d\n",
+ gsm_lchan_name(lchan), status);
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
+
+ /* set the initial ciphering parameters for both directions */
+ l1if_set_ciphering(fl1h, lchan, 1);
+ l1if_set_ciphering(fl1h, lchan, 0);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ return 0;
+}
+
+static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_ACTIVATE;
+ cmd->callback = sapi_activate_cb;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_activate(struct gsm_lchan *lchan)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Trying to activate lchan, but commands in queue\n",
+ gsm_lchan_name(lchan));
+
+ /* override the regular SAPIs if this is the first hand-over
+ * related activation of the LCHAN */
+ if (lchan->ho.active == HANDOVER_ENABLED)
+ s4l = &sapis_for_ho;
+
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+
+ if (sapi == GsmL1_Sapi_Sch) {
+ /* once we activate the SCH, we should get MPH-TIME.ind */
+ fl1h->alive_timer.cb = alive_timer_cb;
+ fl1h->alive_timer.data = fl1h;
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+ }
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+#warning "FIXME: Should this be in sapi_activate_cb?"
+ lchan_init_lapdm(lchan);
+
+ return 0;
+}
+
+const struct value_string lc15bts_l1cfgt_names[] = {
+ { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" },
+ { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" },
+ { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" },
+ { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
+ { 0, NULL }
+};
+
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi)
+{
+ int i;
+
+ switch (sapi) {
+ case GsmL1_Sapi_Rach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic);
+ break;
+ case GsmL1_Sapi_Agch:
+ LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ",
+ lch_par->agch.u8NbrOfAgch);
+ break;
+ case GsmL1_Sapi_Sacch:
+ LOGPC(DL1C, logl, "MS Power Level 0x%02x",
+ lch_par->sacch.u8MsPowerLevel);
+ break;
+ case GsmL1_Sapi_TchF:
+ case GsmL1_Sapi_TchH:
+ LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (",
+ lch_par->tch.amrCmiPhase,
+ lch_par->tch.amrInitCodecMode);
+ for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) {
+ LOGPC(DL1C, logl, "%x ",
+ lch_par->tch.amrActiveCodecSet[i]);
+ }
+ break;
+ /* FIXME: PRACH / PTCCH */
+ default:
+ break;
+ }
+ LOGPC(DL1C, logl, ")\n");
+}
+
+static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_trx_name(trx),
+ get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n",
+ cc->cfgParams.setTxPowerLevel.fTxPowerLevel);
+
+ power_trx_change_compl(trx,
+ (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000));
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId));
+
+ switch (cc->cfgParamId) {
+ case GsmL1_ConfigParamId_SetLogChParams:
+ dump_lch_par(LOGL_INFO,
+ &cc->cfgParams.setLogChParams.logChParams,
+ cc->cfgParams.setLogChParams.sapi);
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetCipheringParams:
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_RX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ break;
+ case LCHAN_CIPH_RX_CONF_TX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ break;
+ case LCHAN_CIPH_RXTX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ break;
+ case LCHAN_CIPH_NONE:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ default:
+ LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
+ break;
+ }
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetNbTsc:
+ default:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ }
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams;
+ conf_req->cfgParams.setLogChParams.sapi = cmd->sapi;
+ conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr;
+ conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ conf_req->cfgParams.setLogChParams.dir = cmd->dir;
+ conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
+ lchan2lch_par(lch_par, lchan);
+
+ /* Update the MS Power Level */
+ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+
+ /* FIXME: update encryption */
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names,
+ conf_req->cfgParams.setLogChParams.sapi));
+ LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ",
+ conf_req->cfgParams.setLogChParams.u8Tn,
+ conf_req->cfgParams.setLogChParams.subCh,
+ conf_req->cfgParams.setLogChParams.dir);
+ dump_lch_par(LOGL_INFO,
+ &conf_req->cfgParams.setLogChParams.logChParams,
+ conf_req->cfgParams.setLogChParams.sapi);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->sapi = sapi;
+ cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
+ queue_sapi_command(lchan, cmd);
+}
+
+static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
+{
+ enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan));
+ return 0;
+}
+
+int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel;
+ conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
+}
+
+const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
+ [0] = GsmL1_CipherId_A50,
+ [1] = GsmL1_CipherId_A50,
+ [2] = GsmL1_CipherId_A51,
+ [3] = GsmL1_CipherId_A52,
+ [4] = GsmL1_CipherId_A53,
+};
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ struct GsmL1_MphConfigReq_t *cfgr;
+
+ cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+
+ cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams;
+ cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr;
+ cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ cfgr->cfgParams.setCipheringParams.dir = cmd->dir;
+ cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph))
+ return -EINVAL;
+ cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id];
+
+ LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n",
+ gsm_lchan_name(lchan),
+ cfgr->cfgParams.setCipheringParams.cipherId,
+ get_value_string(lc15bts_dir_names,
+ cfgr->cfgParams.setCipheringParams.dir));
+
+ memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
+ lchan->encr.key, lchan->encr.key_len);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_CIPHERING;
+ queue_sapi_command(lchan, cmd);
+}
+
+int l1if_set_ciphering(struct lc15l1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink)
+{
+ int dir;
+
+ /* ignore the request when the channel is not active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ if (dir_downlink)
+ dir = GsmL1_Dir_TxDownlink;
+ else
+ dir = GsmL1_Dir_RxUplink;
+
+ enqueue_sapi_ciphering_cmd(lchan, dir);
+
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch);
+ return 0;
+}
+
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink);
+ tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink);
+
+ /* FIXME: update encryption */
+
+ return 0;
+}
+
+static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf;
+
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_NONE;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(lc15bts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got de-activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_DEACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+ return 0;
+}
+
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphDeactivateReq_t *deact_req;
+
+ deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ deact_req->u8Tn = lchan->ts->nr;
+ deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ deact_req->dir = cmd->dir;
+ deact_req->sapi = cmd->sapi;
+ deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names, deact_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, deact_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
+}
+
+static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
+{
+ /* FIXME: Error handling. There is no NACK... */
+ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ /* Don't send an REL ACK on SACCH deactivate */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+
+ /* Reactivate CCCH due to SI3 update in RSL */
+ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+ lchan_activate(lchan);
+ }
+ return 0;
+}
+
+static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_DEACTIVATE;
+ cmd->callback = sapi_deactivate_cb;
+ return queue_sapi_command(lchan, cmd);
+}
+
+/*
+ * Release the SAPI if it was allocated. E.g. the SACCH might be already
+ * deactivated or during a hand-over the TCH was not allocated yet.
+ */
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ /* check if we should schedule a release */
+ if (dir & GsmL1_Dir_TxDownlink) {
+ if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
+ } else if (dir & GsmL1_Dir_RxUplink) {
+ if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
+ }
+
+ /* now schedule the command and maybe dispatch it */
+ return enqueue_sapi_deact_cmd(lchan, sapi, dir);
+}
+
+static int release_sapis_for_ho(struct gsm_lchan *lchan)
+{
+ int res = 0;
+ int i;
+
+ const struct lchan_sapis *s4l = &sapis_for_ho;
+
+ for (i = s4l->num_sapis-1; i >= 0; i--)
+ res |= check_sapi_release(lchan,
+ s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ return res;
+}
+
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ int i, res;
+
+ res = 0;
+
+ /* The order matters.. the Facch needs to be released first */
+ for (i = s4l->num_sapis-1; i >= 0; i--) {
+ /* Stop the alive timer once we deactivate the SCH */
+ if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch)
+ osmo_timer_del(&fl1h->alive_timer);
+
+ /* Release if it was allocated */
+ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ }
+
+ /* always attempt to disable the RACH burst */
+ res |= release_sapis_for_ho(lchan);
+
+ /* nothing was queued */
+ if (res == 0) {
+ LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ }
+
+ return res;
+}
+
+static void enqueue_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to release all active SAPIs */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ enqueue_rel_marker(lchan);
+ return 0;
+}
+
+static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to check if the SACCH is allocated */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_SACCH_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ enqueue_sacch_rel_marker(lchan);
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ /* FIXME: more checks if the attributes are valid */
+
+ switch (msg_type) {
+ case NM_MT_SET_CHAN_ATTR:
+ /* our L1 only supports one global TSC for all channels
+ * one one TRX, so we need to make sure not to activate
+ * channels with a different TSC!! */
+ if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) &&
+ *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) {
+ LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n",
+ *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7);
+ return -NM_NACK_PARAM_RANGE;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ if (kind == NM_OC_RADIO_CARRIER) {
+ struct gsm_bts_trx *trx = obj;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ /* Did we go through MphInit yet? If yes fire and forget */
+ if (fl1h->hLayer1)
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ }
+
+ /* FIXME: we actaully need to send a ACK or NACK for the OML message */
+ return oml_fom_ack_nack(msg, 0);
+}
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ rc = ts_opstart(obj);
+ break;
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
+ rc = oml_mo_opstart_ack(mo);
+ if (mo->obj_class == NM_OC_BTS) {
+ oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK);
+ }
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ int rc = -EINVAL;
+ int granted = 0;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+
+ if (mo->procedure_pending) {
+ LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
+ "pending procedure on RC %d\n",
+ ((struct gsm_bts_trx *)obj)->nr);
+ return 0;
+ }
+ mo->procedure_pending = 1;
+ switch (adm_state) {
+ case NM_STATE_LOCKED:
+ rc = trx_rf_lock(obj, 1, NULL);
+ break;
+ case NM_STATE_UNLOCKED:
+ rc = trx_rf_lock(obj, 0, NULL);
+ break;
+ default:
+ granted = 1;
+ break;
+ }
+
+ if (!granted && rc == 0)
+ /* in progress, will send ack/nack after completion */
+ return 0;
+
+ mo->procedure_pending = 0;
+
+ break;
+ default:
+ /* blindly accept all state changes */
+ granted = 1;
+ break;
+ }
+
+ if (granted) {
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+ } else
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+
+}
+
+int l1if_rsl_chan_act(struct gsm_lchan *lchan)
+{
+ //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE);
+ //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE);
+ lchan_activate(lchan);
+ return 0;
+}
+
+/**
+ * Modify the given lchan in the handover scenario. This is a lot like
+ * second channel activation but with some additional activation.
+ */
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan)
+{
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ if (lchan->ho.active == HANDOVER_NONE)
+ return -1;
+
+ LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n",
+ gsm_lchan_name(lchan));
+
+ /* Give up listening to RACH bursts */
+ release_sapis_for_ho(lchan);
+
+ /* Activate the normal SAPIs */
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+ return 0;
+}
+
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
+{
+ /* A duplicate RF Release Request, ignore it */
+ if (lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ lchan_deactivate(lchan);
+ return 0;
+}
+
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+ /* Only de-activate the SACCH if the lchan is active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return 0;
+ return bts_model_lchan_deactivate_sacch(lchan);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+
+ return l1if_activate_rf(fl1, 0);
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ return l1if_set_txpower(trx_lc15l1_hdl(trx), ((float) p_trxout_mdBm)/1000.0);
+}
+
+static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n",
+ gsm_lchan_name(ts->lchan));
+
+ cb_ts_disconnected(ts);
+
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx);
+ GsmL1_MphDisconnectReq_t *cr;
+
+ DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+
+ return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL);
+}
+
+static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n",
+ gsm_lchan_name(ts->lchan),
+ gsm_pchan_name(ts->pchan),
+ ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
+ ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
+ ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : "");
+
+ cb_ts_connected(ts, 0);
+
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+
+ rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL);
+ if (rc)
+ cb_ts_connected(ts, rc);
+}
diff --git a/src/osmo-bts-litecell15/oml_router.c b/src/osmo-bts-litecell15/oml_router.c
new file mode 100644
index 00000000..198d5e30
--- /dev/null
+++ b/src/osmo-bts-litecell15/oml_router.c
@@ -0,0 +1,132 @@
+/* Beginnings of an OML router */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "oml_router.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = oml_msgb_alloc();
+ if (!msg) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n");
+ return -1;
+ }
+
+ rc = recv(fd->fd, msg->tail, msg->data_len, 0);
+ if (rc <= 0) {
+ close(fd->fd);
+ osmo_fd_unregister(fd);
+ fd->fd = -1;
+ goto err;
+ }
+
+ msg->l1h = msgb_put(msg, rc);
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid IPA message rc(%d)\n", rc);
+ goto err;
+ }
+
+ rc = msg_verify_oml_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid OML message rc(%d)\n", rc);
+ goto err;
+ }
+
+ /* todo dispatch message */
+
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what)
+{
+ int fd;
+ struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data;
+
+ /* Accept only one connection at a time. De-register it */
+ if (read_fd->fd > -1) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "New OML router connection. Closing old one.\n");
+ close(read_fd->fd);
+ osmo_fd_unregister(read_fd);
+ read_fd->fd = -1;
+ }
+
+ fd = accept(accept_fd->fd, NULL, NULL);
+ if (fd < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n",
+ strerror(errno));
+ return -1;
+ }
+
+ read_fd->fd = fd;
+ if (osmo_fd_register(read_fd) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n");
+ close(fd);
+ read_fd->fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+int oml_router_init(struct gsm_bts *bts, const char *path,
+ struct osmo_fd *accept_fd, struct osmo_fd *read_fd)
+{
+ int rc;
+
+ memset(accept_fd, 0, sizeof(*accept_fd));
+ memset(read_fd, 0, sizeof(*read_fd));
+
+ accept_fd->cb = oml_router_accept_cb;
+ accept_fd->data = read_fd;
+
+ read_fd->cb = oml_router_read_cb;
+ read_fd->data = bts;
+ read_fd->when = BSC_FD_READ;
+ read_fd->fd = -1;
+
+ rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0,
+ path,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+ return rc;
+}
diff --git a/src/osmo-bts-litecell15/oml_router.h b/src/osmo-bts-litecell15/oml_router.h
new file mode 100644
index 00000000..8c08baaa
--- /dev/null
+++ b/src/osmo-bts-litecell15/oml_router.h
@@ -0,0 +1,13 @@
+#pragma once
+
+struct gsm_bts;
+struct osmo_fd;
+
+/**
+ * The default path lc15bts will listen for incoming
+ * registrations for OML routing and sending.
+ */
+#define OML_ROUTER_PATH "/var/run/lc15bts_oml_router"
+
+
+int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read);
diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-litecell15/tch.c
new file mode 100644
index 00000000..5eae7538
--- /dev/null
+++ b/src/osmo-bts-litecell15/tch.c
@@ -0,0 +1,533 @@
+/* Traffic channel support for NuRAN Wireless Litecell 1.5 BTS L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+
+static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_FR_BYTES);
+ memcpy(cur, l1_payload, GSM_FR_BYTES);
+
+ lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+ /* new L1 can deliver bits like we need them */
+ memcpy(l1_payload, rtp_payload, GSM_FR_BYTES);
+ return GSM_FR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
+ uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_EFR_BYTES);
+ memcpy(cur, l1_payload, GSM_EFR_BYTES);
+ enum osmo_amr_type ft;
+ enum osmo_amr_quality bfi;
+ uint8_t cmr;
+ int8_t sti, cmi;
+ osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti);
+ lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan);
+
+ return msg;
+}
+
+static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+ memcpy(l1_payload, rtp_payload, payload_len);
+
+ return payload_len;
+}
+
+static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return NULL;
+ }
+
+ cur = msgb_put(msg, GSM_HR_BYTES);
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+
+ lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return 0;
+ }
+
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+
+ return GSM_HR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t amr_if2_len = payload_len - 2;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ cur = msgb_put(msg, amr_if2_len);
+ memcpy(cur, l1_payload+2, amr_if2_len);
+
+ /*
+ * Audiocode's MGW doesn't like receiving CMRs that are not
+ * the same as the previous one. This means we need to patch
+ * the content here.
+ */
+ if ((cur[0] & 0xF0) == 0xF0)
+ cur[0]= lchan->tch.last_cmr << 4;
+ else
+ lchan->tch.last_cmr = cur[0] >> 4;
+
+ return msg;
+}
+
+/*! \brief convert AMR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ uint8_t payload_len, uint8_t ft)
+{
+ memcpy(l1_payload, rtp_payload, payload_len);
+ return payload_len;
+}
+
+#define RTP_MSGB_ALLOC_SIZE 512
+
+/*! \brief function for incoming RTP via TCH.req
+ * \param[in] rtp_pl buffer containing RTP payload
+ * \param[in] rtp_pl_len length of \a rtp_pl
+ * \param[in] use_cache Use cached payload instead of parsing RTP
+ * \param[in] marker RTP header Marker bit (indicates speech onset)
+ * \returns 0 if encoding result can be sent further to L1 without extra actions
+ * positive value if data is ready AND extra actions are required
+ * negative value otherwise (no data for L1 encoded)
+ *
+ * This function prepares a msgb with a L1 PH-DATA.req primitive and
+ * queues it into lchan->dl_tch_queue.
+ *
+ * Note that the actual L1 primitive header is not fully initialized
+ * yet, as things like the frame number, etc. are unknown at the time we
+ * pre-fill the primtive.
+ */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker)
+{
+ uint8_t *payload_type;
+ uint8_t *l1_payload, ft;
+ int rc = 0;
+ bool is_sid = false;
+
+ DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(rtp_pl, rtp_pl_len));
+
+ payload_type = &data[0];
+ l1_payload = &data[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ *payload_type = GsmL1_TchPlType_Fr;
+ rc = rtppayload_to_l1_fr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len);
+ } else{
+ *payload_type = GsmL1_TchPlType_Hr;
+ rc = rtppayload_to_l1_hr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
+ }
+ if (is_sid)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
+ rtp_pl_len);
+ /* FIXME: detect and save EFR SID */
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ if (use_cache) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache,
+ lchan->tch.dtx.len, ft);
+ *len = lchan->tch.dtx.len + 1;
+ return 0;
+ }
+
+ rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
+ l1_payload, marker, len, &ft);
+ if (rc < 0)
+ return rc;
+ if (!dtx_dl_amr_enabled(lchan)) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ }
+
+ /* DTX DL-specific logic below: */
+ switch (lchan->tch.dtx.dl_amr_fsm->state) {
+ case ST_ONSET_V:
+ *payload_type = GsmL1_TchPlType_Amr_Onset;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ *len = 3;
+ return 1;
+ case ST_VOICE:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F1:
+ if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
+ rtp_pl_len, ft);
+ return 0;
+ }
+ /* AMR FR */
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F2:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_F1_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_U_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_SID_U:
+ case ST_U_NOINH:
+ return -EAGAIN;
+ case ST_FACCH:
+ return -EBADMSG;
+ default:
+ LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
+ "%d\n", lchan->tch.dtx.dl_amr_fsm->state);
+ return -EINVAL;
+ }
+ break;
+ default:
+ /* we don't support CSD modes */
+ rc = -1;
+ break;
+ }
+
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
+ gsm_lchan_name(lchan));
+ return -EBADMSG;
+ }
+
+ *len = rc + 1;
+
+ DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(data, *len));
+ return 0;
+}
+
+static int is_recv_only(uint8_t speech_mode)
+{
+ return (speech_mode & 0xF0) == (1 << 4);
+}
+
+/*! \brief receive a traffic L1 primitive for a given lchan */
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
+ GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
+ uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 };
+ struct msgb *rmsg = NULL;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
+
+ if (is_recv_only(lchan->abis_ip.speech_mode))
+ return -EAGAIN;
+
+ if (data_ind->msgUnitParam.u8Size < 1) {
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr);
+ /* Push empty payload to upper layers */
+ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP");
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+ }
+
+ payload_type = data_ind->msgUnitParam.u8Buffer[0];
+ payload = data_ind->msgUnitParam.u8Buffer + 1;
+ payload_len = data_ind->msgUnitParam.u8Size - 1;
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ case GsmL1_TchPlType_Efr:
+ if (lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Hr:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr_Onset:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ /* according to 3GPP TS 26.093 ONSET frames precede the first
+ speech frame of a speech burst - set the marker for next RTP
+ frame */
+ lchan->rtp_tx_marker = true;
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP2:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidUpdateInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ default:
+ LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_tch_pl_names, payload_type));
+ break;
+ }
+
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Hr:
+ rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Efr:
+ rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Amr:
+ rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ memcpy(sid_first, payload, payload_len);
+ int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD);
+ if (len < 0)
+ return 0;
+ rmsg = l1_to_rtppayload_amr(sid_first, len, lchan);
+ break;
+ }
+
+ if (rmsg)
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+
+ return 0;
+
+err_payload_match:
+ LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type));
+ return -EINVAL;
+}
+
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn)
+{
+ struct msgb *msg;
+ GsmL1_Prim_t *l1p;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ uint8_t *payload_type;
+ uint8_t *l1_payload;
+ int rc;
+
+ msg = l1p_msgb_alloc();
+ if (!msg)
+ return NULL;
+
+ l1p = msgb_l1prim(msg);
+ data_req = &l1p->u.phDataReq;
+ msu_param = &data_req->msgUnitParam;
+ payload_type = &msu_param->u8Buffer[0];
+ l1_payload = &msu_param->u8Buffer[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_AMR:
+ if (lchan->type == GSM_LCHAN_TCH_H &&
+ dtx_dl_amr_enabled(lchan)) {
+ /* we have to explicitly handle sending SID FIRST P2 for
+ AMR HR in here */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
+ rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
+ false, &(msu_param->u8Size),
+ NULL);
+ if (rc == 0)
+ return msg;
+ }
+ *payload_type = GsmL1_TchPlType_Amr;
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ *payload_type = GsmL1_TchPlType_Fr;
+ else
+ *payload_type = GsmL1_TchPlType_Hr;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ break;
+ default:
+ msgb_free(msg);
+ return NULL;
+ }
+
+ rc = repeat_last_sid(lchan, l1_payload, fn);
+ if (!rc) {
+ msgb_free(msg);
+ return NULL;
+ }
+ msu_param->u8Size = rc;
+
+ return msg;
+}
diff --git a/src/osmo-bts-litecell15/utils.c b/src/osmo-bts-litecell15/utils.c
new file mode 100644
index 00000000..8d980ba8
--- /dev/null
+++ b/src/osmo-bts-litecell15/utils.c
@@ -0,0 +1,118 @@
+/* Helper utilities that are used in OMLs */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "utils.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+
+int band_lc152osmo(GsmL1_FreqBand_t band)
+{
+ switch (band) {
+ case GsmL1_FreqBand_850:
+ return GSM_BAND_850;
+ case GsmL1_FreqBand_900:
+ return GSM_BAND_900;
+ case GsmL1_FreqBand_1800:
+ return GSM_BAND_1800;
+ case GsmL1_FreqBand_1900:
+ return GSM_BAND_1900;
+ default:
+ return -1;
+ }
+}
+
+static int band_osmo2lc15(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ /* check if the TRX hardware actually supports the given band */
+ if (!(fl1h->hw_info.band_support & osmo_band))
+ return -1;
+
+ /* if yes, convert from osmcoom style band definition to L1 band */
+ switch (osmo_band) {
+ case GSM_BAND_850:
+ return GsmL1_FreqBand_850;
+ case GSM_BAND_900:
+ return GsmL1_FreqBand_900;
+ case GSM_BAND_1800:
+ return GsmL1_FreqBand_1800;
+ case GSM_BAND_1900:
+ return GsmL1_FreqBand_1900;
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Select the band that matches the ARFCN. In general the ARFCNs
+ * for GSM1800 and GSM1900 overlap and one needs to specify the
+ * rightband. When moving between GSM900/GSM1800 and GSM850/1900
+ * modifying the BTS configuration is a bit annoying. The auto-band
+ * configuration allows to ease with this transition.
+ */
+int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn)
+{
+ enum gsm_band band;
+ struct gsm_bts *bts = trx->bts;
+ int rc;
+
+ if (!bts->auto_band)
+ return band_osmo2lc15(trx, bts->band);
+
+ /*
+ * We need to check what will happen now.
+ */
+ rc = gsm_arfcn2band_rc(arfcn, &band);
+ if (rc) /* wrong ARFCN, give up */
+ return -1;
+
+ /* if we are already on the right band return */
+ if (band == bts->band)
+ return band_osmo2lc15(trx, bts->band);
+
+ /* Check if it is GSM1800/GSM1900 */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return band_osmo2lc15(trx, bts->band);
+
+ /*
+ * Now to the actual autobauding. We just want DCS/DCS and
+ * PCS/PCS for PCS we check for 850/1800 though
+ */
+ if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800)
+ || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900)
+ || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900))
+ return band_osmo2lc15(trx, band);
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
+ return band_osmo2lc15(trx, GSM_BAND_1900);
+
+ /* give up */
+ return -1;
+}
diff --git a/src/osmo-bts-litecell15/utils.h b/src/osmo-bts-litecell15/utils.h
new file mode 100644
index 00000000..a2a22348
--- /dev/null
+++ b/src/osmo-bts-litecell15/utils.h
@@ -0,0 +1,13 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdint.h>
+#include "lc15bts.h"
+
+struct gsm_bts_trx;
+
+int band_lc152osmo(GsmL1_FreqBand_t band);
+
+int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn);
+
+#endif
diff --git a/src/osmo-bts-oc2g/Makefile.am b/src/osmo-bts-oc2g/Makefile.am
new file mode 100644
index 00000000..7188626f
--- /dev/null
+++ b/src/osmo-bts-oc2g/Makefile.am
@@ -0,0 +1,38 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OC2G_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) $(LIBSYSTEMD_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS)
+
+AM_CFLAGS += -DENABLE_OC2GBTS
+
+EXTRA_DIST = misc/oc2gbts_mgr.h misc/oc2gbts_misc.h misc/oc2gbts_par.h misc/oc2gbts_led.h \
+ misc/oc2gbts_temp.h misc/oc2gbts_power.h misc/oc2gbts_clock.h \
+ misc/oc2gbts_bid.h misc/oc2gbts_nl.h \
+ hw_misc.h l1_if.h l1_transp.h oc2gbts.h oml_router.h utils.h
+
+bin_PROGRAMS = osmo-bts-oc2g oc2gbts-mgr oc2gbts-util
+
+COMMON_SOURCES = main.c oc2gbts.c l1_if.c oml.c oc2gbts_vty.c tch.c hw_misc.c calib_file.c \
+ utils.c misc/oc2gbts_par.c misc/oc2gbts_bid.c oml_router.c
+
+osmo_bts_oc2g_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+osmo_bts_oc2g_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+oc2gbts_mgr_SOURCES = \
+ misc/oc2gbts_mgr.c misc/oc2gbts_misc.c \
+ misc/oc2gbts_par.c misc/oc2gbts_nl.c \
+ misc/oc2gbts_temp.c misc/oc2gbts_power.c \
+ misc/oc2gbts_clock.c misc/oc2gbts_bid.c \
+ misc/oc2gbts_mgr_vty.c \
+ misc/oc2gbts_mgr_nl.c \
+ misc/oc2gbts_mgr_temp.c \
+ misc/oc2gbts_mgr_calib.c \
+ misc/oc2gbts_led.c \
+ misc/oc2gbts_bts.c \
+ misc/oc2gbts_swd.c
+
+oc2gbts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD)
+
+oc2gbts_util_SOURCES = misc/oc2gbts_util.c misc/oc2gbts_par.c
+oc2gbts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-oc2g/calib_file.c b/src/osmo-bts-oc2g/calib_file.c
new file mode 100644
index 00000000..49768480
--- /dev/null
+++ b/src/osmo-bts-oc2g/calib_file.c
@@ -0,0 +1,469 @@
+/* NuRAN Wireless OC-2G BTS L1 calibration file routines*/
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1const.h>
+
+#include "l1_if.h"
+#include "oc2gbts.h"
+#include "utils.h"
+#include "osmo-bts/oml.h"
+
+/* Maximum calibration data chunk size */
+#define MAX_CALIB_TBL_SIZE 65536
+/* Calibration header version */
+#define CALIB_HDR_V1 0x01
+
+struct calib_file_desc {
+ const char *fname;
+ int rx;
+ int trx;
+ int rxpath;
+};
+
+static const struct calib_file_desc calib_files[] = {
+ {
+ .fname = "calib_rx0.conf",
+ .rx = 1,
+ .trx = 0,
+ .rxpath = 0,
+ }, {
+ .fname = "calib_tx0.conf",
+ .rx = 0,
+ .trx = 0,
+ },
+};
+
+struct calTbl_t
+{
+ union
+ {
+ struct
+ {
+ uint8_t u8Version; /* Header version (1) */
+ uint8_t u8Parity; /* Parity byte (xor) */
+ uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */
+ uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */
+ uint32_t u32Len; /* Table length in bytes including the header */
+ struct
+ {
+ uint32_t u32DescOfst; /* Description section offset */
+ uint32_t u32DateOfst; /* Date section offset */
+ uint32_t u32StationOfst; /* Calibration test station section offset */
+ uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */
+ uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */
+ uint32_t u32DataOfst; /* Calibration data section offset */
+ } toc;
+ } v1;
+ } hdr;
+
+ uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32];
+};
+
+
+static int calib_file_send(struct oc2gl1_hdl *fl1h,
+ const struct calib_file_desc *desc);
+static int calib_verify(struct oc2gl1_hdl *fl1h,
+ const struct calib_file_desc *desc);
+
+/* determine next calibration file index based on supported bands */
+static int get_next_calib_file_idx(struct oc2gl1_hdl *fl1h, int last_idx)
+{
+ struct phy_link *plink = fl1h->phy_inst->phy_link;
+ int i;
+
+ for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
+ if (calib_files[i].trx == plink->num)
+ return i;
+ }
+ return -1;
+}
+
+static int calib_file_open(struct oc2gl1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.oc2g.calib_path;
+ char fname[PATH_MAX];
+
+ if (st->fp) {
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n");
+ fclose(st->fp);
+ st->fp = NULL;
+ }
+
+ fname[0] = '\0';
+ snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname);
+ fname[sizeof(fname)-1] = '\0';
+
+ st->fp = fopen(fname, "rb");
+ if (!st->fp) {
+ LOGP(DL1C, LOGL_NOTICE, "Failed to open '%s' for calibration data.\n", fname);
+
+ /* TODO (oramadan): Fix OML alarms
+ if( fl1h->phy_inst->trx ){
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ alarm_sig_data.add_text = (char*)&fname[0];
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_OPEN_CALIB_ALARM, &alarm_sig_data);
+ }
+ */
+ return -1;
+ }
+ return 0;
+}
+
+static int calib_file_close(struct oc2gl1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+
+ if (st->fp) {
+ fclose(st->fp);
+ st->fp = NULL;
+ }
+ return 0;
+}
+
+/* iteratively download the calibration data into the L1 */
+
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data);
+
+/* send a chunk of calibration tabledata for a single specified file */
+static int calib_file_send_next_chunk(struct oc2gl1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+ Oc2g_Prim_t *prim;
+ struct msgb *msg;
+ size_t n;
+
+ msg = sysp_msgb_alloc();
+ prim = msgb_sysprim(msg);
+
+ prim->id = Oc2g_PrimId_SetCalibTblReq;
+ prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp);
+ n = fread(prim->u.setCalibTblReq.u8Data, 1,
+ sizeof(prim->u.setCalibTblReq.u8Data), st->fp);
+ prim->u.setCalibTblReq.length = n;
+
+
+ if (n == 0) {
+ /* The table data has been completely sent and acknowledged */
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n",
+ calib_files[st->last_file_idx].fname);
+
+ calib_file_close(fl1h);
+
+ msgb_free(msg);
+
+ /* Send the next one if any */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0) {
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
+}
+
+/* send the calibration table for a single specified file */
+static int calib_file_send(struct oc2gl1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ int rc;
+
+ rc = calib_file_open(fl1h, desc);
+ if (rc < 0) {
+ /* still, we'd like to continue trying to load
+ * calibration for all other bands */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ rc = calib_verify(fl1h, desc);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_NOTICE,"Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc);
+
+ /* TODO (oramadan): Fix OML alarms
+ if (fl1h->phy_inst->trx) {
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ alarm_sig_data.add_text = (char*)&desc->fname[0];
+ memcpy(alarm_sig_data.spare, &rc, sizeof(int));
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_VERIFY_CALIB_ALARM, &alarm_sig_data);
+ }
+ */
+
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ return 0;
+
+ }
+
+ LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname);
+
+ return calib_file_send_next_chunk(fl1h);
+}
+
+/* completion callback after every SetCalibTbl is confirmed */
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ struct calib_send_state *st = &fl1h->st;
+ Oc2g_Prim_t *prim = msgb_sysprim(l1_msg);
+
+ if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n");
+
+ msgb_free(l1_msg);
+
+ calib_file_close(fl1h);
+
+ /* Skip this one and try the next one */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0) {
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ /* Keep sending the calibration file data */
+ return calib_file_send_next_chunk(fl1h);
+}
+
+int calib_load(struct oc2gl1_hdl *fl1h)
+{
+ int rc;
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.oc2g.calib_path;
+
+ if (!calib_path) {
+ LOGP(DL1C, LOGL_NOTICE, "Calibration file path not specified\n");
+
+ /* TODO (oramadan): Fix OML alarms
+ if( fl1h->phy_inst->trx ){
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_NO_CALIB_PATH_ALARM, &alarm_sig_data);
+ }
+ */
+ return -1;
+ }
+
+ rc = get_next_calib_file_idx(fl1h, -1);
+ if (rc < 0) {
+ return -1;
+ }
+ st->last_file_idx = rc;
+
+ return calib_file_send(fl1h, &calib_files[st->last_file_idx]);
+}
+
+
+static int calib_verify(struct oc2gl1_hdl *fl1h, const struct calib_file_desc *desc)
+{
+ int rc, sz;
+ struct calib_send_state *st = &fl1h->st;
+ struct phy_link *plink = fl1h->phy_inst->phy_link;
+ char *rbuf;
+ struct calTbl_t *calTbl;
+ char calChkSum ;
+
+
+ /* calculate file size in bytes */
+ fseek(st->fp, 0L, SEEK_END);
+ sz = ftell(st->fp);
+
+ /* rewind read poiner */
+ fseek(st->fp, 0L, SEEK_SET);
+
+ /* read file */
+ rbuf = (char *) malloc( sizeof(char) * sz );
+
+ rc = fread(rbuf, 1, sizeof(char) * sz, st->fp);
+ if (rc != sz) {
+ LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname);
+ free(rbuf);
+
+ /* close file */
+ rc = calib_file_close(fl1h);
+ if (rc < 0 ) {
+ LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname);
+ return rc;
+ }
+
+ return -2;
+ }
+
+
+ calTbl = (struct calTbl_t*) rbuf;
+ /* calculate file checksum */
+ calChkSum = 0;
+ while (sz--) {
+ calChkSum ^= rbuf[sz];
+ }
+
+ /* validate Tx calibration parity */
+ if (calChkSum) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum);
+ return -4;
+ }
+
+ /* validate Tx calibration header */
+ if (calTbl->hdr.v1.u8Version != CALIB_HDR_V1) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version);
+ return -5;
+ }
+
+ /* validate calibration description */
+ if (calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname);
+ return -6;
+ }
+
+ /* validate calibration date */
+ if (calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname);
+ return -7;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n",
+ desc->fname,
+ calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst);
+
+ /* validate calibration station */
+ if (calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname);
+ return -8;
+ }
+
+ /* validate FPGA FW version */
+ if (calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFF) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname);
+ return -9;
+ }
+
+ /* validate DSP FW version */
+ if (calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname);
+ return -10;
+ }
+
+ /* validate Tx calibration data offset */
+ if (calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF) {
+ LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname);
+ return -11;
+ }
+
+ if (!desc->rx) {
+
+ /* parse min/max Tx power */
+ fl1h->phy_inst->u.oc2g.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)];
+ fl1h->phy_inst->u.oc2g.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)];
+
+ /* override nominal Tx power of given TRX if needed */
+ if (fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.oc2g.maxTxPower) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
+ plink->num,
+ fl1h->phy_inst->u.oc2g.maxTxPower,
+ fl1h->phy_inst->trx->nominal_power);
+
+ fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.oc2g.maxTxPower;
+ }
+
+ if (fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.oc2g.minTxPower) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
+ plink->num,
+ fl1h->phy_inst->u.oc2g.minTxPower,
+ fl1h->phy_inst->trx->nominal_power);
+
+ fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower;
+ }
+
+ if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower) ) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
+ plink->num,
+ to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower),
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm);
+
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower);
+ }
+
+ if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.oc2g.minTxPower) ) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
+ plink->num,
+ to_mdB(fl1h->phy_inst->u.oc2g.minTxPower),
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm);
+
+ fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.minTxPower);
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n",
+ desc->fname,
+ fl1h->phy_inst->u.oc2g.minTxPower,
+ fl1h->phy_inst->u.oc2g.maxTxPower );
+ }
+
+ /* rewind read pointer for subsequence tasks */
+ fseek(st->fp, 0L, SEEK_SET);
+ free(rbuf);
+
+ return 0;
+}
+
diff --git a/src/osmo-bts-oc2g/hw_info.ver_major b/src/osmo-bts-oc2g/hw_info.ver_major
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/osmo-bts-oc2g/hw_info.ver_major
diff --git a/src/osmo-bts-oc2g/hw_misc.c b/src/osmo-bts-oc2g/hw_misc.c
new file mode 100644
index 00000000..31daf078
--- /dev/null
+++ b/src/osmo-bts-oc2g/hw_misc.c
@@ -0,0 +1,88 @@
+/* Misc HW routines for NuRAN Wireless OC-2G BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+
+#include "hw_misc.h"
+
+int oc2gbts_led_set(enum oc2gbts_led_color c)
+{
+ int fd, rc;
+ uint8_t cmd[2];
+
+ switch (c) {
+ case LED_OFF:
+ cmd[0] = 0;
+ cmd[1] = 0;
+ break;
+ case LED_RED:
+ cmd[0] = 1;
+ cmd[1] = 0;
+ break;
+ case LED_GREEN:
+ cmd[0] = 0;
+ cmd[1] = 1;
+ break;
+ case LED_ORANGE:
+ cmd[0] = 1;
+ cmd[1] = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fd = open("/var/oc2g/leds/led0/brightness", O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ rc = write(fd, cmd[0] ? "1" : "0", 2);
+ if (rc != 2) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ fd = open("/var/oc2g/leds/led1/brightness", O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ rc = write(fd, cmd[1] ? "1" : "0", 2);
+ if (rc != 2) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/hw_misc.h b/src/osmo-bts-oc2g/hw_misc.h
new file mode 100644
index 00000000..b8f3332a
--- /dev/null
+++ b/src/osmo-bts-oc2g/hw_misc.h
@@ -0,0 +1,13 @@
+#ifndef _HW_MISC_H
+#define _HW_MISC_H
+
+enum oc2gbts_led_color {
+ LED_OFF,
+ LED_RED,
+ LED_GREEN,
+ LED_ORANGE,
+};
+
+int oc2gbts_led_set(enum oc2gbts_led_color c);
+
+#endif
diff --git a/src/osmo-bts-oc2g/l1_if.c b/src/osmo-bts-oc2g/l1_if.c
new file mode 100644
index 00000000..d8fdd968
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_if.c
@@ -0,0 +1,1755 @@
+/* Interface handler for NuRAN Wireless OC-2G L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "hw_misc.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+#include "utils.h"
+
+extern unsigned int dsp_trace;
+
+struct wait_l1_conf {
+ struct llist_head list; /* internal linked list */
+ struct osmo_timer_list timer; /* timer for L1 timeout */
+ unsigned int conf_prim_id; /* primitive we expect in response */
+ HANDLE conf_hLayer3; /* layer 3 handle we expect in response */
+ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */
+ l1if_compl_cb *cb;
+ void *cb_data;
+};
+
+static void release_wlc(struct wait_l1_conf *wlc)
+{
+ osmo_timer_del(&wlc->timer);
+ talloc_free(wlc);
+}
+
+static void l1if_req_timeout(void *data)
+{
+ struct wait_l1_conf *wlc = data;
+
+ if (wlc->is_sys_prim)
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n",
+ get_value_string(oc2gbts_sysprim_names, wlc->conf_prim_id));
+ else
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(oc2gbts_l1prim_names, wlc->conf_prim_id));
+ exit(23);
+}
+
+static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitReq:
+ return prim->u.mphInitReq.hLayer3;
+ case GsmL1_PrimId_MphCloseReq:
+ return prim->u.mphCloseReq.hLayer3;
+ case GsmL1_PrimId_MphConnectReq:
+ return prim->u.mphConnectReq.hLayer3;
+ case GsmL1_PrimId_MphDisconnectReq:
+ return prim->u.mphDisconnectReq.hLayer3;
+ case GsmL1_PrimId_MphActivateReq:
+ return prim->u.mphActivateReq.hLayer3;
+ case GsmL1_PrimId_MphDeactivateReq:
+ return prim->u.mphDeactivateReq.hLayer3;
+ case GsmL1_PrimId_MphConfigReq:
+ return prim->u.mphConfigReq.hLayer3;
+ case GsmL1_PrimId_MphMeasureReq:
+ return prim->u.mphMeasureReq.hLayer3;
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.hLayer3;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.hLayer3;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.hLayer3;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.hLayer3;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.hLayer3;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.hLayer3;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.hLayer3;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.hLayer3;
+ case GsmL1_PrimId_MphTimeInd:
+ case GsmL1_PrimId_MphSyncInd:
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ case GsmL1_PrimId_PhDataReq:
+ case GsmL1_PrimId_PhConnectInd:
+ case GsmL1_PrimId_PhReadyToSendInd:
+ case GsmL1_PrimId_PhDataInd:
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id);
+ break;
+ }
+ return 0;
+}
+
+static int _l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
+ int is_system_prim, l1if_compl_cb *cb, void *data)
+{
+ struct wait_l1_conf *wlc;
+ struct osmo_wqueue *wqueue;
+ unsigned int timeout_secs;
+
+ /* allocate new wsc and store reference to mutex and conf_id */
+ wlc = talloc_zero(fl1h, struct wait_l1_conf);
+ wlc->cb = cb;
+ wlc->cb_data = data;
+
+ /* Make sure we actually have received a REQUEST type primitive */
+ if (is_system_prim == 0) {
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+
+ LOGP(DL1P, LOGL_DEBUG, "Tx L1 prim %s\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id));
+
+ if (oc2gbts_get_l1prim_type(l1p->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 0;
+ wlc->conf_prim_id = oc2gbts_get_l1prim_conf(l1p->id);
+ wlc->conf_hLayer3 = l1p_get_hLayer3(l1p);
+ wqueue = &fl1h->write_q[MQ_L1_WRITE];
+ timeout_secs = 30;
+ } else {
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id));
+
+ if (oc2gbts_get_sysprim_type(sysp->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 1;
+ wlc->conf_prim_id = oc2gbts_get_sysprim_conf(sysp->id);
+ wqueue = &fl1h->write_q[MQ_SYS_WRITE];
+ timeout_secs = 30;
+ }
+
+ /* enqueue the message in the queue and add wsc to list */
+ if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
+ /* So we will get a timeout but the log message might help */
+ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n",
+ is_system_prim ? "system primitive" : "gsm");
+ msgb_free(msg);
+ }
+ llist_add(&wlc->list, &fl1h->wlc_list);
+
+ /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */
+ wlc->timer.data = wlc;
+ wlc->timer.cb = l1if_req_timeout;
+ osmo_timer_schedule(&wlc->timer, timeout_secs, 0);
+
+ return 0;
+}
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 1, cb, data);
+}
+
+int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 0, cb, data);
+}
+
+/* allocate a msgb containing a GsmL1_Prim_t */
+struct msgb *l1p_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
+
+ return msg;
+}
+
+/* allocate a msgb containing a Oc2g_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(Oc2g_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(Oc2g_Prim_t));
+
+ return msg;
+}
+
+static GsmL1_PhDataReq_t *
+data_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = rts_ind->hLayer1;
+ data_req->u8Tn = rts_ind->u8Tn;
+ data_req->u32Fn = rts_ind->u32Fn;
+ data_req->sapi = rts_ind->sapi;
+ data_req->subCh = rts_ind->subCh;
+ data_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return data_req;
+}
+
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = rts_ind->hLayer1;
+ empty_req->u8Tn = rts_ind->u8Tn;
+ empty_req->u32Fn = rts_ind->u32Fn;
+ empty_req->sapi = rts_ind->sapi;
+ empty_req->subCh = rts_ind->subCh;
+ empty_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return empty_req;
+}
+
+/* fill PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+data_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr, uint8_t len)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ data_req->u8Tn = tn;
+ data_req->u32Fn = fn;
+ data_req->sapi = sapi;
+ data_req->subCh = sub_ch;
+ data_req->u8BlockNbr = block_nr;
+
+ data_req->msgUnitParam.u8Size = len;
+
+ return data_req;
+}
+
+/* fill PH-EMPTY_FRAME.req from l1sap primitive */
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi,
+ uint8_t subch, uint8_t block_nr)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ empty_req->u8Tn = tn;
+ empty_req->u32Fn = fn;
+ empty_req->sapi = sapi;
+ empty_req->subCh = subch;
+ empty_req->u8BlockNbr = block_nr;
+
+ return empty_req;
+}
+
+/* fill frame PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+fill_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+ GsmL1_MsgUnitParam_t *msu_param;
+ uint8_t *l1_payload;
+
+ msu_param = &data_req->msgUnitParam;
+ l1_payload = &msu_param->u8Buffer[0];
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ memset(l1_payload, 0x2B, GSM_MACBLOCK_LEN);
+ /* address field */
+ l1_payload[0] = 0x03;
+ /* control field */
+ l1_payload[1] = 0x03;
+ /* length field */
+ l1_payload[2] = 0x01;
+
+ /* copy fields from PH-RTS.ind */
+ data_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ data_req->u8Tn = tn;
+ data_req->u32Fn = fn;
+ data_req->sapi = sapi;
+ data_req->subCh = sub_ch;
+ data_req->u8BlockNbr = block_nr;
+ data_req->msgUnitParam.u8Size = GSM_MACBLOCK_LEN;
+
+
+ LOGP(DL1C, LOGL_DEBUG, "Send fill frame on in none DTX mode Tn=%d, Fn=%d, SAPI=%d, SubCh=%d, BlockNr=%d dump=%s\n",
+ tn,
+ fn,
+ sapi,
+ sub_ch,
+ block_nr,
+ osmo_hexdump(data_req->msgUnitParam.u8Buffer, data_req->msgUnitParam.u8Size));
+
+ return data_req;
+}
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache)
+{
+ struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx);
+ struct msgb *l1msg = l1p_msgb_alloc();
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+
+ if (!msg) {
+ LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n");
+ abort();
+ }
+
+ len = msgb_l2len(msg);
+
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ u32Fn = l1sap->u.data.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ subCh = 0x1f;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ sapi = GsmL1_Sapi_Sacch;
+ if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (ts_is_pdch(&trx->ts[u8Tn])) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = GsmL1_Sapi_Ptcch;
+ u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn);
+ } else {
+ sapi = GsmL1_Sapi_Pdtch;
+ u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn);
+ }
+ } else {
+ sapi = GsmL1_Sapi_FacchF;
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_FacchH;
+ u8BlockNbr = (u32Fn % 26) >> 3;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Bcch;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Cbch;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ u8BlockNbr = l1sap_fn2ccch_block(u32Fn);
+ if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ"))
+ sapi = GsmL1_Sapi_Pch;
+ else
+ sapi = GsmL1_Sapi_Agch;
+ } else {
+ LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d "
+ "chan_nr %d link_id %d\n", l1sap->oph.primitive,
+ l1sap->oph.operation, chan_nr, link_id);
+ msgb_free(l1msg);
+ return -EINVAL;
+ }
+
+ /* convert l1sap message to GsmL1 primitive, keep payload */
+ if (len) {
+ /* data request */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len);
+ if (use_cache)
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ lchan->tch.dtx.facch, msgb_l2len(msg));
+ else if (dtx_dl_amr_enabled(lchan) &&
+ ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) {
+ if (sapi == GsmL1_Sapi_FacchF) {
+ sapi = GsmL1_Sapi_TchF;
+ }
+ if (sapi == GsmL1_Sapi_FacchH) {
+ sapi = GsmL1_Sapi_TchH;
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) {
+ /* FACCH interruption of DTX silence */
+ /* cache FACCH data */
+ memcpy(lchan->tch.dtx.facch, msg->l2h,
+ msgb_l2len(msg));
+ /* prepare ONSET or INH message */
+ if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_Onset;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidUpdateInH;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidFirstInH;
+ /* ignored CMR/CMI pair */
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0;
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0;
+ /* update length */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi,
+ subCh, u8BlockNbr, 3);
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ } else if (dtx_dl_amr_enabled(lchan) &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ else {
+ OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer));
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h,
+ msgb_l2len(msg));
+ }
+ LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n",
+ osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ l1p->u.phDataReq.msgUnitParam.u8Size));
+ } else {
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN)
+ /* fill frame */
+ fill_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ else {
+ if (lchan->ts->trx->bts->dtxd)
+ /* empty frame */
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ else
+ /* fill frame */
+ fill_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+ }
+
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) {
+ LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(l1msg);
+ } else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan))
+ ph_data_req(trx, msg, l1sap, true);
+ return 0;
+}
+
+static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
+{
+ struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx);
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi;
+ uint8_t chan_nr;
+ GsmL1_Prim_t *l1p;
+ struct msgb *nmsg = NULL;
+ int rc = -1;
+
+ chan_nr = l1sap->u.tch.chan_nr;
+ u32Fn = l1sap->u.tch.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_TchH;
+ } else {
+ subCh = 0x1f;
+ sapi = GsmL1_Sapi_TchF;
+ }
+
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ /* create new message and fill data */
+ if (msg) {
+ msgb_pull(msg, sizeof(*l1sap));
+ /* create new message */
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ l1p = msgb_l1prim(nmsg);
+ rc = l1if_tch_encode(lchan,
+ l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ &l1p->u.phDataReq.msgUnitParam.u8Size,
+ msg->data, msg->len, u32Fn, use_cache,
+ l1sap->u.tch.marker);
+ if (rc < 0) {
+ /* no data encoded for L1: smth will be generated below */
+ msgb_free(nmsg);
+ nmsg = NULL;
+ }
+ }
+
+ /* no message/data, we might generate an empty traffic msg or re-send
+ cached SID in case of DTX */
+ if (!nmsg)
+ nmsg = gen_empty_tch_msg(lchan, u32Fn);
+
+ /* no traffic message, we generate an empty msg */
+ if (!nmsg) {
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ }
+
+ l1p = msgb_l1prim(nmsg);
+
+ /* if we provide data, or if data is already in nmsg */
+ if (l1p->u.phDataReq.msgUnitParam.u8Size) {
+ /* data request */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh,
+ u8BlockNbr,
+ l1p->u.phDataReq.msgUnitParam.u8Size);
+ } else {
+ /* empty frame */
+ if (trx->bts->dtxd && trx != trx->bts->c0)
+ lchan->tch.dtx.dl_active = true;
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+ /* send message to DSP's queue */
+ osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg);
+ if (dtx_is_first_p1(lchan))
+ dtx_dispatch(lchan, E_FIRST);
+ else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */
+ return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);
+
+ return 0;
+}
+
+static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx);
+ uint8_t chan_nr;
+ struct gsm_lchan *lchan;
+ int rc = 0;
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink) {
+ l1if_set_ciphering(fl1, lchan, 0);
+ lchan->ciph_state = LCHAN_CIPH_RX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink) {
+ l1if_set_ciphering(fl1, lchan, 1);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink
+ && l1sap->u.info.u.ciph_req.uplink)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
+ l1if_rsl_chan_act(lchan);
+ else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ if (lchan->ho.active == HANDOVER_WAIT_FRAME)
+ l1if_rsl_chan_mod(lchan);
+ else
+ l1if_rsl_mode_modify(lchan);
+ } else if (l1sap->u.info.u.act_req.sacch_only)
+ l1if_rsl_deact_sacch(lchan);
+ else
+ l1if_rsl_chan_rel(lchan);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ /* called functions MUST NOT take ownership of msgb, as it is
+ * free()d below */
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ rc = ph_data_req(trx, msg, l1sap, false);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ rc = mph_info_req(trx, msg, l1sap);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_mph_time_ind(struct oc2gl1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind,
+ struct msgb *msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct osmo_phsap_prim l1sap;
+ uint32_t fn;
+
+ /* increment the primitive count for the alive timer */
+ fl1->alive_prim_cnt++;
+
+ /* ignore every time indication, except for c0 */
+ if (trx != bts->c0) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ fn = time_ind->u32Fn;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_TIME;
+ l1sap.u.info.u.time_ind.fn = fn;
+
+ msgb_free(msg);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ return GSM_PCHAN_TCH_F;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ default:
+ return ts->pchan;
+ }
+}
+
+static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
+ GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ enum gsm_phys_chan_config pchan = pick_pchan(ts);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ switch (sapi) {
+ case GsmL1_Sapi_Bcch:
+ cbits = 0x10;
+ break;
+ case GsmL1_Sapi_Cbch:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ break;
+ case GsmL1_Sapi_Sacch:
+ switch(pchan) {
+ case GSM_PCHAN_TCH_F:
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ cbits = 0x02 + subCh;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Sdcch:
+ switch(pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Agch:
+ case GsmL1_Sapi_Pch:
+ cbits = 0x12;
+ break;
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_TchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_TchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_FacchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_FacchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ if (!L1SAP_IS_PTCCH(u32Fn)) {
+ LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
+ "number other than 12, got it at %u (%u). "
+ "Please fix!\n", u32Fn % 52, u32Fn);
+ abort();
+ }
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ /* not reached due to default case above */
+ return (cbits << 3) | u8Tn;
+}
+
+static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1,
+ GsmL1_PhReadyToSendInd_t *rts_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct msgb *resp_msg;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ struct gsm_time g_time;
+ uint32_t t3p;
+ int rc;
+ struct osmo_phsap_prim *l1sap;
+ uint8_t chan_nr, link_id;
+ uint32_t fn;
+
+ /* check if primitive should be handled by common part */
+ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi,
+ rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn);
+ if (chan_nr) {
+ fn = rts_ind->u32Fn;
+ if (rts_ind->sapi == GsmL1_Sapi_Sacch)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+ /* recycle the msgb and use it for the L1 primitive,
+ * which means that we (or our caller) must not free it */
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ if (rts_ind->sapi == GsmL1_Sapi_TchF
+ || rts_ind->sapi == GsmL1_Sapi_TchH) {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ } else {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ }
+
+ return l1sap_up(trx, l1sap);
+ }
+
+ gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
+ get_value_string(oc2gbts_l1sapi_names, rts_ind->sapi));
+
+ /* in all other cases, we need to allocate a new PH-DATA.ind
+ * primitive msgb and start to fill it */
+ resp_msg = l1p_msgb_alloc();
+ data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+ msu_param = &data_req->msgUnitParam;
+
+ /* set default size */
+ msu_param->u8Size = GSM_MACBLOCK_LEN;
+
+ switch (rts_ind->sapi) {
+ case GsmL1_Sapi_Sch:
+ /* compute T3prime */
+ t3p = (g_time.t3 - 1) / 10;
+ /* fill SCH burst with data */
+ msu_param->u8Size = 4;
+ msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9);
+ msu_param->u8Buffer[1] = (g_time.t1 >> 1);
+ msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
+ msu_param->u8Buffer[3] = (t3p & 1);
+ break;
+ case GsmL1_Sapi_Prach:
+ goto empty_frame;
+ break;
+ case GsmL1_Sapi_Cbch:
+ /* get them from bts->si_buf[] */
+ bts_cbch_get(bts, msu_param->u8Buffer, &g_time);
+ break;
+ default:
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ break;
+ }
+tx:
+
+ /* transmit */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) {
+ LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(resp_msg);
+ }
+
+ /* free the msgb, as we have not handed it to l1sap and thus
+ * need to release its memory */
+ msgb_free(l1p_msg);
+ return 0;
+
+empty_frame:
+ /* in case we decide to send an empty frame... */
+ empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+
+ goto tx;
+}
+
+static void dump_meas_res(int ll, GsmL1_MeasParam_t *m)
+{
+ LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, "
+ "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality,
+ m->fBer, m->i16BurstTiming);
+}
+
+static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ GsmL1_MeasParam_t *m, uint32_t fn)
+{
+ struct osmo_phsap_prim l1sap;
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_MEAS;
+ l1sap.u.info.u.meas_ind.chan_nr = chan_nr;
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64;
+ l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000);
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1);
+ l1sap.u.info.u.meas_ind.fn = fn;
+
+ /* l1sap wants to take msgb ownership. However, as there is no
+ * msg, it will msgb_free(l1sap.oph.msg == NULL) */
+ return l1sap_up(trx, &l1sap);
+}
+
+static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1);
+ uint8_t chan_nr, link_id;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ struct gsm_time g_time;
+ uint8_t *data, len;
+ int rc = 0;
+ int8_t rssi;
+
+ chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi,
+ data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn);
+ fn = data_ind->u32Fn;
+ link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC;
+ gsm_fn2gsmtime(&g_time, fn);
+
+ if (!chan_nr) {
+ LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n",
+ get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), data_ind->sapi);
+ msgb_free(l1p_msg);
+ return ENOTSUP;
+ }
+
+ process_meas_res(trx, chan_nr, &data_ind->measParam, fn);
+
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+ get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2,
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size));
+ dump_meas_res(LOGL_DEBUG, &data_ind->measParam);
+
+ /* check for TCH */
+ if (data_ind->sapi == GsmL1_Sapi_TchF
+ || data_ind->sapi == GsmL1_Sapi_TchH) {
+ /* TCH speech frame handling */
+ rc = l1if_tch_rx(trx, chan_nr, l1p_msg);
+ msgb_free(l1p_msg);
+ return rc;
+ }
+
+ /* get rssi */
+ rssi = (int8_t) (data_ind->measParam.fRssi);
+ /* get data pointer and length */
+ data = data_ind->msgUnitParam.u8Buffer;
+ len = data_ind->msgUnitParam.u8Size;
+ /* pull lower header part before data */
+ msgb_pull(l1p_msg, data - l1p_msg->data);
+ /* trim remaining data to it's size, to get rid of upper header part */
+ rc = msgb_trim(l1p_msg, len);
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1p_msg->l2h = l1p_msg->data;
+ /* push new l1 header */
+ l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap));
+ /* fill header */
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = rssi;
+ if (!pcu_direct) {
+ l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000;
+ l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64;
+ l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10;
+ }
+ return l1sap_up(trx, l1sap);
+}
+
+static int handle_ph_ra_ind(struct oc2gl1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_lchan *lchan;
+ struct osmo_phsap_prim *l1sap;
+ int rc;
+ struct ph_rach_ind_param rach_ind_param;
+
+ /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */
+ if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) {
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+
+ if ((ra_ind->msgUnitParam.u8Size != 1) &&
+ (ra_ind->msgUnitParam.u8Size != 2)) {
+ LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
+ rach_ind_param = (struct ph_rach_ind_param) {
+ /* .chan_nr set below */
+ /* .ra set below */
+ .acc_delay = 0,
+ .fn = ra_ind->u32Fn,
+ /* .is_11bit set below */
+ /* .burst_type set below */
+ .rssi = (int8_t) ra_ind->measParam.fRssi,
+ .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0),
+ .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64,
+ };
+
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2);
+ if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ rach_ind_param.chan_nr = 0x88;
+ else
+ rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);
+
+ if (ra_ind->msgUnitParam.u8Size == 2) {
+ uint16_t temp;
+ uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0];
+ ra = ra << 3;
+ temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7);
+ ra = ra | temp;
+ rach_ind_param.is_11bit = 1;
+ rach_ind_param.ra = ra;
+ } else {
+ rach_ind_param.is_11bit = 0;
+ rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0];
+ }
+
+ /* the old legacy full-bits acc_delay cannot express negative values */
+ if (ra_ind->measParam.i16BurstTiming > 0)
+ rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+ /* mapping of the burst type, the values are specific to
+ * osmo-bts-oc2g */
+ switch (ra_ind->burstType) {
+ case GsmL1_BurstType_Access_0:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_0;
+ break;
+ case GsmL1_BurstType_Access_1:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_1;
+ break;
+ case GsmL1_BurstType_Access_2:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_2;
+ break;
+ default:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_NONE;
+ break;
+ }
+
+ /* msgb_trim() invalidates ra_ind, make that abundantly clear: */
+ ra_ind = NULL;
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ l1p_msg);
+ l1sap->u.rach_ind = rach_ind_param;
+
+ return l1sap_up(trx, l1sap);
+}
+
+/* handle any random indication from the L1 */
+static int l1if_handle_ind(struct oc2gl1_hdl *fl1, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ int rc = 0;
+
+ /* all the below called functions must take ownership of the msgb */
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg);
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd,
+ msg);
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg);
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg);
+ break;
+ default:
+ msgb_free(msg);
+ }
+
+ return rc;
+}
+
+static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc)
+{
+ if (wlc->is_sys_prim != 0)
+ return 0;
+ if (l1p->id != wlc->conf_prim_id)
+ return 0;
+ if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3)
+ return 0;
+ return 1;
+}
+
+int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ /* silent, don't clog the log file */
+ break;
+ default:
+ LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id), wq);
+ }
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (is_prim_compat(l1p, wlc)) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(oc2gl1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id));
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ /* the limitation here is that we cannot have multiple callers
+ * sending the same primitive */
+ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(oc2gl1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+ unsigned int i;
+ struct gsm_bts *bts = trx->bts;
+
+ if (sysp->id == Oc2g_PrimId_ActivateRfCnf)
+ on = 1;
+
+ if (on)
+ status = sysp->u.activateRfCnf.status;
+ else
+ status = sysp->u.deactivateRfCnf.status;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
+ get_value_string(oc2gbts_l1status_names, status));
+
+
+ if (on) {
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
+ get_value_string(oc2gbts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-ACT failure");
+ } else
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 1);
+
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+ } else {
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+/* activate or de-activate the entire RF-Frontend */
+int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+ struct phy_instance *pinst = hdl->phy_inst;
+
+ if (on) {
+ sysp->id = Oc2g_PrimId_ActivateRfReq;
+ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;
+
+ sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.oc2g.pedestal_mode;
+
+ /* maximum cell size in quarter-bits, 90 == 12.456 km */
+ sysp->u.activateRfReq.u8MaxCellSize = pinst->u.oc2g.max_cell_size;
+
+ /* auto tx power adjustment mode 0:none, 1: automatic*/
+ sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.oc2g.tx_pwr_adj_mode;
+
+ } else {
+ sysp->id = Oc2g_PrimId_DeactivateRfReq;
+ }
+
+ return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL);
+}
+
+static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
+ struct gsm_lchan *lchan = &ts->lchan[i];
+
+ if (!is_muted)
+ continue;
+
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+
+ /* skip channels that might be active for another reason */
+ if (lchan->type == GSM_LCHAN_CCCH)
+ continue;
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ continue;
+
+ if (lchan->s <= 0)
+ continue;
+
+ lchan->s = 0;
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ }
+}
+
+static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n",
+ get_value_string(oc2gbts_l1status_names, status));
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0);
+ } else {
+ int i;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n",
+ get_value_string(oc2gbts_l1status_names, status));
+ bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1);
+
+ osmo_static_assert(
+ ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute),
+ ts_array_size);
+
+ for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i)
+ mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+/* mute/unmute RF time slots */
+int l1if_mute_rf(struct oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n",
+ mute[0], mute[1], mute[2], mute[3],
+ mute[4], mute[5], mute[6], mute[7]
+ );
+
+ sysp->id = Oc2g_PrimId_MuteRfReq;
+ memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute));
+ /* save for later use */
+ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute));
+
+ return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL);
+}
+
+/* call-back on arrival of DSP+FPGA version + band capability */
+static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ int rc;
+
+ fl1h->hw_info.dsp_version[0] = sic->dspVersion.major;
+ fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor;
+ fl1h->hw_info.dsp_version[2] = sic->dspVersion.build;
+
+ fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major;
+ fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor;
+ fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build;
+
+ LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n",
+ sic->dspVersion.major, sic->dspVersion.minor,
+ sic->dspVersion.build, sic->fpgaVersion.major,
+ sic->fpgaVersion.minor, sic->fpgaVersion.build);
+
+ LOGP(DL1C, LOGL_INFO, "Band support %s", gsm_band_name(fl1h->hw_info.band_support));
+
+ if (!(fl1h->hw_info.band_support & trx->bts->band))
+ LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
+ gsm_band_name(trx->bts->band));
+
+ /* Request the activation */
+ l1if_activate_rf(fl1h, 1);
+
+ /* load calibration tables */
+ rc = calib_load(fl1h);
+ if (rc < 0)
+ LOGP(DL1C, LOGL_ERROR, "Operating without calibration; "
+ "unable to load tables!\n");
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* request DSP+FPGA code versions */
+static int l1if_get_info(struct oc2gl1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = Oc2g_PrimId_SystemInfoReq;
+
+ return l1if_req_compl(hdl, msg, info_compl_cb, NULL);
+}
+
+static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status = sysp->u.layer1ResetCnf.status;
+
+ LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n",
+ get_value_string(oc2gbts_l1status_names, status));
+
+ msgb_free(resp);
+
+ /* If we're coming out of reset .. */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n",
+ get_value_string(oc2gbts_l1status_names, status));
+ bts_shutdown(trx->bts, "L1-RESET failure");
+ }
+
+ /* as we cannot get the current DSP trace flags, we simply
+ * set them to zero (or whatever dsp_trace_f has been initialized to */
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f);
+
+ /* obtain version information on DSP/FPGA and band capabilities */
+ l1if_get_info(fl1h);
+
+ return 0;
+}
+
+int l1if_reset(struct oc2gl1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = Oc2g_PrimId_Layer1ResetReq;
+
+ return l1if_req_compl(hdl, msg, reset_compl_cb, NULL);
+}
+
+/* set the trace flags within the DSP */
+int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
+ flags);
+
+ sysp->id = Oc2g_PrimId_SetTraceFlagsReq;
+ sysp->u.setTraceFlagsReq.u32Tf = flags;
+
+ hdl->dsp_trace_f = flags;
+
+ /* There is no confirmation we could wait for */
+ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int get_hwinfo(struct oc2gl1_hdl *fl1h)
+{
+ int rc = 0;
+ char rev_maj = 0, rev_min = 0;
+
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.ver_major = (uint8_t)rev_maj;
+ fl1h->hw_info.ver_minor = (uint8_t)rev_min;
+
+ rc = oc2gbts_model_get();
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.options = (uint32_t)rc;
+
+ rc = oc2gbts_option_get(OC2GBTS_OPTION_BAND);
+ if (rc < 0)
+ return rc;
+
+ switch (rc) {
+ case OC2GBTS_BAND_850:
+ fl1h->hw_info.band_support = GSM_BAND_850;
+ break;
+ case OC2GBTS_BAND_900:
+ fl1h->hw_info.band_support = GSM_BAND_900;
+ break;
+ case OC2GBTS_BAND_1800:
+ fl1h->hw_info.band_support = GSM_BAND_1800;
+ break;
+ case OC2GBTS_BAND_1900:
+ fl1h->hw_info.band_support = GSM_BAND_1900;
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst)
+{
+ struct oc2gl1_hdl *fl1h;
+ int rc;
+
+ LOGP(DL1C, LOGL_INFO, "OC-2G BTS L1IF compiled against API headers "
+ "v%u.%u.%u\n", OC2G_API_VERSION >> 16,
+ (OC2G_API_VERSION >> 8) & 0xff,
+ OC2G_API_VERSION & 0xff);
+
+ fl1h = talloc_zero(pinst, struct oc2gl1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->phy_inst = pinst;
+ fl1h->dsp_trace_f = pinst->u.oc2g.dsp_trace_f;
+
+ get_hwinfo(fl1h);
+
+ rc = l1if_transport_open(MQ_SYS_WRITE, fl1h);
+ if (rc < 0) {
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ rc = l1if_transport_open(MQ_L1_WRITE, fl1h);
+ if (rc < 0) {
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ return fl1h;
+}
+
+int l1if_close(struct oc2gl1_hdl *fl1h)
+{
+ l1if_transport_close(MQ_L1_WRITE, fl1h);
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ return 0;
+}
+
+/* TODO(oramadan) MERGE */
+#ifdef MERGE_ME
+static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_IsAliveCnf_t *sac = &sysp->u.isAliveCnf;
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+
+ fl1h->hw_alive.dsp_alive_cnt++;
+ LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id), sac->status, trx->nr);
+
+ msgb_free(resp);
+}
+
+static int dsp_alive_timer_cb(void *data)
+{
+ struct oc2gl1_hdl *fl1h = data;
+ struct gsm_bts_trx *trx = fl1h->phy_inst->trx;
+ struct msgb *msg = sysp_msgb_alloc();
+ int rc;
+ struct oml_alarm_list *alarm_sent;
+
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_IsAliveReq;
+
+ if (fl1h->hw_alive.dsp_alive_cnt == 0) {
+ /* check for the alarm has already sent or not */
+ llist_for_each_entry(alarm_sent, &fl1h->alarm_list, list) {
+ llist_del(&alarm_sent->list);
+ if (alarm_sent->alarm_signal != S_NM_OML_BTS_DSP_ALIVE_ALARM)
+ continue;
+
+ LOGP(DL1C, LOGL_ERROR, "Alarm %d has removed from sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr);
+ exit(23);
+ }
+
+ LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n",
+ get_value_string(oc2gbts_sysprim_names, sys_prim->id + 1), trx->nr);
+
+ if( fl1h->phy_inst->trx ){
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ memcpy(alarm_sig_data.spare, &sys_prim->id, sizeof(unsigned int));
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_DSP_ALIVE_ALARM, &alarm_sig_data);
+ if (!alarm_sig_data.rc) {
+ /* allocate new list of sent alarms */
+ alarm_sent = talloc_zero(fl1h, struct oml_alarm_list);
+ if (!alarm_sent)
+ return -EIO;
+
+ alarm_sent->alarm_signal = S_NM_OML_BTS_DSP_ALIVE_ALARM;
+ /* add alarm to sent list */
+ llist_add(&alarm_sent->list, &fl1h->alarm_list);
+ LOGP(DL1C, LOGL_ERROR, "Alarm %d has added to sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr);
+ }
+ }
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n",
+ get_value_string(oc2gbts_sysprim_names, sys_prim->id), trx->nr);
+
+ rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id));
+ return -EIO;
+ }
+
+ /* restart timer */
+ fl1h->hw_alive.dsp_alive_cnt = 0;
+ osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0);
+
+ return 0;
+}
+#endif
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst = phy_instance_by_num(plink, 0);
+
+ OSMO_ASSERT(pinst);
+
+ if (!pinst->trx) {
+ LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d "
+ "because no TRX is associated with it\n", plink->num, pinst->num);
+ return 0;
+ }
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ pinst->u.oc2g.hdl = l1if_open(pinst);
+ if (!pinst->u.oc2g.hdl) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n");
+ return -EIO;
+ }
+
+ /* Set default PHY parameters */
+ if (!pinst->u.oc2g.max_cell_size)
+ pinst->u.oc2g.max_cell_size = OC2G_BTS_MAX_CELL_SIZE_DEFAULT;
+
+ if (!pinst->u.oc2g.pedestal_mode)
+ pinst->u.oc2g.pedestal_mode = OC2G_BTS_PEDESTAL_MODE_DEFAULT;
+
+ if (!pinst->u.oc2g.dsp_alive_period)
+ pinst->u.oc2g.dsp_alive_period = OC2G_BTS_DSP_ALIVE_TMR_DEFAULT;
+
+ if (!pinst->u.oc2g.tx_pwr_adj_mode)
+ pinst->u.oc2g.tx_pwr_adj_mode = OC2G_BTS_TX_PWR_ADJ_DEFAULT;
+
+ if (!pinst->u.oc2g.tx_pwr_red_8psk)
+ pinst->u.oc2g.tx_pwr_red_8psk = OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT;
+
+ if (!pinst->u.oc2g.tx_c0_idle_pwr_red)
+ pinst->u.oc2g.tx_c0_idle_pwr_red = OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT;
+
+ struct oc2gl1_hdl *fl1h = pinst->u.oc2g.hdl;
+ fl1h->dsp_trace_f = dsp_trace;
+
+ l1if_reset(pinst->u.oc2g.hdl);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ /* TODO (oramadan) MERGE)
+ / * Send first IS_ALIVE primitive * /
+ struct msgb *msg = sysp_msgb_alloc();
+ int rc;
+
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_IsAliveReq;
+
+ rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id));
+ return -EIO;
+ }
+
+ /* initialize DSP heart beat alive timer * /
+ fl1h->hw_alive.dsp_alive_timer.cb = dsp_alive_timer_cb;
+ fl1h->hw_alive.dsp_alive_timer.data = fl1h;
+ fl1h->hw_alive.dsp_alive_cnt = 0;
+ fl1h->hw_alive.dsp_alive_period = pinst->u.oc2g.dsp_alive_period;
+ osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); */
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/l1_if.h b/src/osmo-bts-oc2g/l1_if.h
new file mode 100644
index 00000000..38699e01
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_if.h
@@ -0,0 +1,145 @@
+#ifndef _L1_IF_H
+#define _L1_IF_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/phy_link.h>
+
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include <stdbool.h>
+
+enum {
+ MQ_SYS_READ,
+ MQ_L1_READ,
+ MQ_TCH_READ,
+ MQ_PDTCH_READ,
+ _NUM_MQ_READ
+};
+
+enum {
+ MQ_SYS_WRITE,
+ MQ_L1_WRITE,
+ MQ_TCH_WRITE,
+ MQ_PDTCH_WRITE,
+ _NUM_MQ_WRITE
+};
+
+struct calib_send_state {
+ FILE *fp;
+ const char *path;
+ int last_file_idx;
+};
+
+struct oc2gl1_hdl {
+ struct gsm_time gsm_time;
+ HANDLE hLayer1; /* handle to the L1 instance in the DSP */
+ uint32_t dsp_trace_f; /* currently operational DSP trace flags */
+ struct llist_head wlc_list;
+ struct llist_head alarm_list; /* list of sent alarms */
+
+ struct phy_instance *phy_inst;
+
+ struct osmo_timer_list alive_timer;
+ unsigned int alive_prim_cnt;
+
+ struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
+ struct osmo_wqueue write_q[_NUM_MQ_WRITE];
+
+ struct {
+ /* from DSP/FPGA after L1 Init */
+ uint8_t dsp_version[3];
+ uint8_t fpga_version[3];
+ uint32_t band_support;
+ uint8_t ver_major;
+ uint8_t ver_minor;
+ uint32_t options;
+ } hw_info;
+
+ struct calib_send_state st;
+
+ uint8_t last_rf_mute[8];
+
+ struct {
+ struct osmo_timer_list dsp_alive_timer;
+ unsigned int dsp_alive_cnt;
+ uint8_t dsp_alive_period;
+ } hw_alive;
+};
+
+#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg) ((Oc2g_Prim_t *)(msg)->l1h)
+
+typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+
+struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst);
+int l1if_close(struct oc2gl1_hdl *hdl);
+int l1if_reset(struct oc2gl1_hdl *hdl);
+int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power);
+int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff);
+int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red);
+int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size);
+int l1if_mute_rf(struct oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
+
+struct msgb *l1p_msgb_alloc(void);
+struct msgb *sysp_msgb_alloc(void);
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
+struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
+
+/* tch.c */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker);
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
+int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
+
+/* ciphering */
+int l1if_set_ciphering(struct oc2gl1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink);
+
+/* channel control */
+int l1if_rsl_chan_act(struct gsm_lchan *lchan);
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
+
+/* calibration loading */
+int calib_load(struct oc2gl1_hdl *fl1h);
+
+/* public helpers for test */
+int bts_check_for_ciph_cmd(struct oc2gl1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan);
+int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target,
+ const uint8_t ms_power, const float rxLevel);
+
+static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ OSMO_ASSERT(pinst);
+ return pinst->u.oc2g.hdl;
+}
+
+static inline struct gsm_bts_trx *oc2gl1_hdl_trx(struct oc2gl1_hdl *fl1h)
+{
+ OSMO_ASSERT(fl1h->phy_inst);
+ return fl1h->phy_inst->trx;
+}
+
+#endif /* _L1_IF_H */
diff --git a/src/osmo-bts-oc2g/l1_transp.h b/src/osmo-bts-oc2g/l1_transp.h
new file mode 100644
index 00000000..5af79dcb
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_transp.h
@@ -0,0 +1,14 @@
+#ifndef _L1_TRANSP_H
+#define _L1_TRANSP_H
+
+#include <osmocom/core/msgb.h>
+
+/* functions a transport calls on arrival of primitive from BTS */
+int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg);
+
+/* functions exported by a transport */
+int l1if_transport_open(int q, struct oc2gl1_hdl *fl1h);
+int l1if_transport_close(int q, struct oc2gl1_hdl *fl1h);
+
+#endif /* _L1_TRANSP_H */
diff --git a/src/osmo-bts-oc2g/l1_transp_hw.c b/src/osmo-bts-oc2g/l1_transp_hw.c
new file mode 100644
index 00000000..e1d46581
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_transp_hw.c
@@ -0,0 +1,326 @@
+/* Interface handler for Nuran Wireless OC-2G L1 (real hardware) */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/oc2g_dsp2arm_trx"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/oc2g_arm2dsp_trx"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx"
+
+#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx"
+#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx"
+#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx"
+#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx"
+
+static const char *rd_devnames[] = {
+ [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
+ [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
+ [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
+ [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
+};
+
+static const char *wr_devnames[] = {
+ [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
+ [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
+ [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
+ [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
+};
+
+/*
+ * Make sure that all structs we read fit into the OC2GBTS_PRIM_SIZE
+ */
+osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, l1_prim)
+osmo_static_assert(sizeof(Oc2g_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, super_prim)
+
+static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct osmo_wqueue *queue;
+
+ queue = container_of(fd, struct osmo_wqueue, bfd);
+
+ if (what & BSC_FD_READ)
+ queue->read_cb(fd);
+
+ if (what & BSC_FD_EXCEPT)
+ queue->except_cb(fd);
+
+ if (what & BSC_FD_WRITE) {
+ struct iovec iov[5];
+ struct msgb *msg, *tmp;
+ int written, count = 0;
+
+ fd->when &= ~BSC_FD_WRITE;
+
+ llist_for_each_entry(msg, &queue->msg_queue, list) {
+ /* more writes than we have */
+ if (count >= ARRAY_SIZE(iov))
+ break;
+
+ iov[count].iov_base = msg->l1h;
+ iov[count].iov_len = msgb_l1len(msg);
+ count += 1;
+ }
+
+ /* TODO: check if all lengths are the same. */
+
+
+ /* Nothing scheduled? This should not happen. */
+ if (count == 0) {
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ written = writev(fd->fd, iov, count);
+ if (written < 0) {
+ /* nothing written?! */
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ /* now delete the written entries */
+ written = written / iov[0].iov_len;
+ count = 0;
+ llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) {
+ queue->current_length -= 1;
+
+ llist_del(&msg->list);
+ msgb_free(msg);
+
+ count += 1;
+ if (count >= written)
+ break;
+ }
+
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ }
+
+ return 0;
+}
+
+static int prim_size_for_queue(int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return sizeof(Oc2g_Prim_t);
+ case MQ_L1_WRITE:
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+ return sizeof(GsmL1_Prim_t);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+}
+
+/* callback when there's something to read from the l1 msg_queue */
+static int read_dispatch_one(struct oc2gl1_hdl *fl1h, struct msgb *msg, int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return l1if_handle_sysprim(fl1h, msg);
+ case MQ_L1_WRITE:
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+ return l1if_handle_l1prim(queue, fl1h, msg);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+};
+
+static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int i, rc;
+
+ const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr);
+ uint32_t count;
+
+ struct iovec iov[3];
+ struct msgb *msg[ARRAY_SIZE(iov)];
+
+ for (i = 0; i < ARRAY_SIZE(iov); ++i) {
+ msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd");
+ msg[i]->l1h = msg[i]->data;
+
+ iov[i].iov_base = msg[i]->l1h;
+ iov[i].iov_len = msgb_tailroom(msg[i]);
+ }
+
+ rc = readv(ofd->fd, iov, ARRAY_SIZE(iov));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno));
+ /* N. B: we do not abort to let the cycle below cleanup allocated memory properly,
+ the return value is ignored by the caller anyway.
+ TODO: use libexplain's explain_readv() to provide detailed error description */
+ count = 0;
+ } else
+ count = rc / prim_size;
+
+ for (i = 0; i < count; ++i) {
+ msgb_put(msg[i], prim_size);
+ read_dispatch_one(ofd->data, msg[i], ofd->priv_nr);
+ }
+
+ for (i = count; i < ARRAY_SIZE(iov); ++i)
+ msgb_free(msg[i]);
+
+ return 1;
+}
+
+/* callback when we can write to one of the l1 msg_queue devices */
+static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msg->len) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msg->len);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int l1if_transport_open(int q, struct oc2gl1_hdl *hdl)
+{
+ struct phy_link *plink = hdl->phy_inst->phy_link;
+ int rc;
+ char buf[PATH_MAX];
+
+ /* Step 1: Open all msg_queue file descriptors */
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_wqueue *wq = &hdl->write_q[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num);
+ buf[sizeof(buf)-1] = '\0';
+
+ rc = open(buf, O_RDONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
+ buf, strerror(errno));
+ return rc;
+ }
+ read_ofd->fd = rc;
+ read_ofd->priv_nr = q;
+ read_ofd->data = hdl;
+ read_ofd->cb = l1if_fd_cb;
+ read_ofd->when = BSC_FD_READ;
+ rc = osmo_fd_register(read_ofd);
+ if (rc < 0) {
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+ return rc;
+ }
+
+ snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num);
+ buf[sizeof(buf)-1] = '\0';
+
+ rc = open(buf, O_WRONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
+ buf, strerror(errno));
+ goto out_read;
+ }
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = l1fd_write_cb;
+ write_ofd->cb = wqueue_vector_cb;
+ write_ofd->fd = rc;
+ write_ofd->priv_nr = q;
+ write_ofd->data = hdl;
+ write_ofd->when = BSC_FD_WRITE;
+ rc = osmo_fd_register(write_ofd);
+ if (rc < 0) {
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+ goto out_read;
+ }
+
+ return 0;
+
+out_read:
+ close(hdl->read_ofd[q].fd);
+ osmo_fd_unregister(&hdl->read_ofd[q]);
+
+ return rc;
+}
+
+int l1if_transport_close(int q, struct oc2gl1_hdl *hdl)
+{
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ osmo_fd_unregister(read_ofd);
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+
+ osmo_fd_unregister(write_ofd);
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/main.c b/src/osmo-bts-oc2g/main.c
new file mode 100644
index 00000000..574bc723
--- /dev/null
+++ b/src/osmo-bts-oc2g/main.c
@@ -0,0 +1,242 @@
+/* Main program for NuRAN Wireless OC-2G BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+
+static int write_status_file(char *status_file, char *status_str)
+{
+ FILE *outf;
+ char tmp[PATH_MAX+1];
+
+ snprintf(tmp, sizeof(tmp)-1, "/var/run/osmo-bts/%s", status_file);
+ tmp[PATH_MAX-1] = '\0';
+
+ outf = fopen(tmp, "w");
+ if (!outf)
+ return -1;
+
+ fprintf(outf, "%s\n", status_str);
+
+ fclose(outf);
+
+ return 0;
+}
+
+/*NTQD: Change how rx_nr is handle in multi-trx*/
+#define OC2GBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock"
+
+#include "utils.h"
+#include "l1_if.h"
+#include "hw_misc.h"
+#include "oml_router.h"
+#include "misc/oc2gbts_bid.h"
+
+unsigned int dsp_trace = 0x00000000;
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ struct stat st;
+ static struct osmo_fd accept_fd, read_fd;
+ int rc;
+
+ bts->variant = BTS_OSMO_OC2G;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ /* specific default values for OC2G platform */
+
+ /* TODO(oramadan) MERGE
+ bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT;
+ /* RTP drift threshold default * /
+ bts->oc2g.rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT;
+ */
+
+ rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
+ if (rc < 0) {
+ fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
+ OML_ROUTER_PATH, rc);
+ exit(1);
+ }
+
+ if (stat(OC2GBTS_RF_LOCK_PATH, &st) == 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
+ exit(23);
+ }
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_EGPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ trx->nominal_power = 40;
+ trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power);
+ return 0;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ /* update status file */
+ write_status_file("state", "");
+
+ return 0;
+}
+
+void bts_update_status(enum bts_global_status which, int on)
+{
+ static uint64_t states = 0;
+ uint64_t old_states = states;
+ int led_rf_active_on;
+
+ if (on)
+ states |= (1ULL << which);
+ else
+ states &= ~(1ULL << which);
+
+ led_rf_active_on =
+ (states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
+ !(states & (1ULL << BTS_STATUS_RF_MUTE));
+
+ LOGP(DL1C, LOGL_INFO,
+ "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
+ which, on,
+ (long long)old_states, (long long)states,
+ led_rf_active_on);
+
+ oc2gbts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF);
+}
+
+void bts_model_print_help()
+{
+ printf( " -w --hw-version Print the targeted HW Version\n"
+ " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n"
+ " -p --dsp-trace Set DSP trace flags\n"
+ );
+}
+
+static void print_hwversion()
+{
+ printf(get_hwversion_desc());
+ printf("\n");
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "dsp-trace", 1, 0, 'p' },
+ { "hw-version", 0, 0, 'w' },
+ { "pcu-direct", 0, 0, 'M' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "p:wM",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'p':
+ dsp_trace = strtoul(optarg, NULL, 16);
+ break;
+ case 'M':
+ pcu_direct = 1;
+ break;
+ case 'w':
+ print_hwversion();
+ exit(0);
+ break;
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* write to status file */
+ write_status_file("state", "ABIS DOWN");
+
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+int main(int argc, char **argv)
+{
+ /* create status file with initial state */
+ write_status_file("state", "ABIS DOWN");
+
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
new file mode 100644
index 00000000..6eaa9c69
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
@@ -0,0 +1,175 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "oc2gbts_bid.h"
+
+#define BOARD_REV_MAJ_SYSFS "/var/oc2g/platform/rev/major"
+#define BOARD_REV_MIN_SYSFS "/var/oc2g/platform/rev/minor"
+#define BOARD_OPT_SYSFS "/var/oc2g/platform/option"
+
+static const int option_type_mask[_NUM_OPTION_TYPES] = {
+ [OC2GBTS_OPTION_OCXO] = 0x07,
+ [OC2GBTS_OPTION_FPGA] = 0x03,
+ [OC2GBTS_OPTION_PA] = 0x01,
+ [OC2GBTS_OPTION_BAND] = 0x03,
+ [OC2GBTS_OPTION_TX_ATT] = 0x01,
+ [OC2GBTS_OPTION_TX_FIL] = 0x01,
+ [OC2GBTS_OPTION_RX_ATT] = 0x01,
+ [OC2GBTS_OPTION_RMS_FWD] = 0x01,
+ [OC2GBTS_OPTION_RMS_REFL] = 0x01,
+ [OC2GBTS_OPTION_DDR_32B] = 0x01,
+ [OC2GBTS_OPTION_DDR_ECC] = 0x01,
+ [OC2GBTS_OPTION_PA_TEMP] = 0x01,
+};
+
+static const int option_type_shift[_NUM_OPTION_TYPES] = {
+ [OC2GBTS_OPTION_OCXO] = 0,
+ [OC2GBTS_OPTION_FPGA] = 3,
+ [OC2GBTS_OPTION_PA] = 5,
+ [OC2GBTS_OPTION_BAND] = 6,
+ [OC2GBTS_OPTION_TX_ATT] = 8,
+ [OC2GBTS_OPTION_TX_FIL] = 9,
+ [OC2GBTS_OPTION_RX_ATT] = 10,
+ [OC2GBTS_OPTION_RMS_FWD] = 11,
+ [OC2GBTS_OPTION_RMS_REFL] = 12,
+ [OC2GBTS_OPTION_DDR_32B] = 13,
+ [OC2GBTS_OPTION_DDR_ECC] = 14,
+ [OC2GBTS_OPTION_PA_TEMP] = 15,
+};
+
+
+static char board_rev_maj = -1;
+static char board_rev_min = -1;
+static int board_option = -1;
+
+void oc2gbts_rev_get(char *rev_maj, char *rev_min)
+{
+ FILE *fp;
+ char rev;
+
+ *rev_maj = 0;
+ *rev_min = 0;
+
+ if (board_rev_maj != -1 && board_rev_min != -1) {
+ *rev_maj = board_rev_maj;
+ *rev_min = board_rev_min;
+ }
+
+ fp = fopen(BOARD_REV_MAJ_SYSFS, "r");
+ if (fp == NULL) return;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ *rev_maj = board_rev_maj = rev;
+
+ fp = fopen(BOARD_REV_MIN_SYSFS, "r");
+ if (fp == NULL) return;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ *rev_min = board_rev_min = rev;
+}
+
+const char* get_hwversion_desc()
+{
+ int rev;
+ int model;
+ size_t len;
+ static char model_name[64] = {0, };
+ len = snprintf(model_name, sizeof(model_name), "NuRAN OC-2G BTS");
+
+ char rev_maj = 0, rev_min = 0;
+
+ int rc = 0;
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ if (rc < 0)
+ return rc;
+ if (rev >= 0) {
+ len += snprintf(model_name + len, sizeof(model_name) - len,
+ " Rev %d.%d", (uint8_t)rev_maj, (uint8_t)rev_min);
+ }
+
+ model = oc2gbts_model_get();
+ if (model >= 0) {
+ snprintf(model_name + len, sizeof(model_name) - len,
+ "%s (%05X)", model_name, model);
+ }
+ return model_name;
+}
+
+int oc2gbts_model_get(void)
+{
+ FILE *fp;
+ int opt;
+
+
+ if (board_option == -1) {
+ fp = fopen(BOARD_OPT_SYSFS, "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ if (fscanf(fp, "%X", &opt) != 1) {
+ fclose( fp );
+ return -1;
+ }
+ fclose(fp);
+
+ board_option = opt;
+ }
+ return board_option;
+}
+
+int oc2gbts_option_get(enum oc2gbts_option_type type)
+{
+ int rc;
+ int option;
+
+ if (type >= _NUM_OPTION_TYPES) {
+ return -EINVAL;
+ }
+
+ if (board_option == -1) {
+ rc = oc2gbts_model_get();
+ if (rc < 0) return rc;
+ }
+
+ option = (board_option >> option_type_shift[type])
+ & option_type_mask[type];
+
+ return option;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.h b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h
new file mode 100644
index 00000000..00cf3899
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h
@@ -0,0 +1,47 @@
+#ifndef _OC2GBTS_BOARD_H
+#define _OC2GBTS_BOARD_H
+
+#include <stdint.h>
+
+enum oc2gbts_option_type {
+ OC2GBTS_OPTION_OCXO,
+ OC2GBTS_OPTION_FPGA,
+ OC2GBTS_OPTION_PA,
+ OC2GBTS_OPTION_BAND,
+ OC2GBTS_OPTION_TX_ATT,
+ OC2GBTS_OPTION_TX_FIL,
+ OC2GBTS_OPTION_RX_ATT,
+ OC2GBTS_OPTION_RMS_FWD,
+ OC2GBTS_OPTION_RMS_REFL,
+ OC2GBTS_OPTION_DDR_32B,
+ OC2GBTS_OPTION_DDR_ECC,
+ OC2GBTS_OPTION_PA_TEMP,
+ _NUM_OPTION_TYPES
+};
+
+enum oc2gbts_ocxo_type {
+ OC2GBTS_OCXO_BILAY_NVG45AV2072,
+ OC2GBTS_OCXO_TAITIEN_NJ26M003,
+ _NUM_OCXO_TYPES
+};
+
+enum oc2gbts_fpga_type {
+ OC2GBTS_FPGA_35T,
+ OC2GBTS_FPGA_50T,
+ OC2GBTS_FPGA_75T,
+ OC2GBTS_FPGA_100T,
+ _NUM_FPGA_TYPES
+};
+
+enum oc2gbts_gsm_band {
+ OC2GBTS_BAND_850,
+ OC2GBTS_BAND_900,
+ OC2GBTS_BAND_1800,
+ OC2GBTS_BAND_1900,
+};
+
+void oc2gbts_rev_get(char *rev_maj, char *rev_min);
+int oc2gbts_model_get(void);
+int oc2gbts_option_get(enum oc2gbts_option_type type);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
new file mode 100644
index 00000000..b3dae76e
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
@@ -0,0 +1,129 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include "oc2gbts_mgr.h"
+#include "oc2gbts_bts.h"
+
+static int check_eth_status(char *dev_name)
+{
+ int fd, rc;
+ struct ifreq ifr;
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (fd < 0)
+ return fd;
+
+ memset(&ifr, 0, sizeof(ifr));
+ memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name));
+ rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
+ close(fd);
+
+ if (rc < 0)
+ return rc;
+
+ if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING))
+ return 0;
+
+ return 1;
+}
+
+void check_bts_led_pattern(uint8_t *led)
+{
+ FILE *fp;
+ char str[64] = "\0";
+ int rc;
+
+ /* check for existing of BTS state file */
+ if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) {
+ led[BLINK_PATTERN_INIT] = 1;
+ return;
+ }
+
+ /* check Ethernet interface status */
+ rc = check_eth_status("eth0");
+ if (rc > 0) {
+ LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n");
+ led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1;
+ fclose(fp);
+ return;
+ }
+
+ /* check for BTS is still alive */
+ if (system("pidof osmo-bts-oc2g > /dev/null")) {
+ LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n");
+ led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
+ fclose(fp);
+ return;
+ }
+
+ /* check for BTS state */
+ while (fgets(str, 64, fp) != NULL) {
+ LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP");
+ if (strstr(str, "ABIS DOWN") != NULL)
+ led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
+ }
+ fclose(fp);
+
+ return;
+}
+
+int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led)
+{
+ if(mgr->alarms.temp_high == 1)
+ led[BLINK_PATTERN_TEMP_HIGH] = 1;
+
+ if(mgr->alarms.temp_max == 1)
+ led[BLINK_PATTERN_TEMP_MAX] = 1;
+
+ if(mgr->alarms.supply_low == 1)
+ led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1;
+
+ if(mgr->alarms.supply_min == 1)
+ led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1;
+
+ if(mgr->alarms.vswr_high == 1)
+ led[BLINK_PATTERN_VSWR_HIGH] = 1;
+
+ if(mgr->alarms.vswr_max == 1)
+ led[BLINK_PATTERN_VSWR_MAX] = 1;
+
+ if(mgr->alarms.supply_pwr_high == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1;
+
+ if(mgr->alarms.supply_pwr_max == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1;
+
+ if(mgr->alarms.pa_pwr_high == 1)
+ led[BLINK_PATTERN_PA_PWR_HIGH] = 1;
+
+ if(mgr->alarms.pa_pwr_max == 1)
+ led[BLINK_PATTERN_PA_PWR_MAX] = 1;
+
+ if(mgr->alarms.gps_fix_lost == 1)
+ led[BLINK_PATTERN_GPS_FIX_LOST] = 1;
+
+ return 0;
+}
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.h b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h
new file mode 100644
index 00000000..b003bdcd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h
@@ -0,0 +1,21 @@
+#ifndef _OC2GBTS_BTS_H_
+#define _OC2GBTS_BTS_H_
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <osmo-bts/logging.h>
+
+/* public function prototypes */
+void check_bts_led_pattern(uint8_t *led);
+int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
new file mode 100644
index 00000000..acbbbc5c
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
@@ -0,0 +1,263 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "oc2gbts_clock.h"
+
+#define CLKERR_ERR_SYSFS "/var/oc2g/clkerr/clkerr1_average"
+#define CLKERR_ACC_SYSFS "/var/oc2g/clkerr/clkerr1_average_accuracy"
+#define CLKERR_INT_SYSFS "/var/oc2g/clkerr/clkerr1_average_interval"
+#define CLKERR_FLT_SYSFS "/var/oc2g/clkerr/clkerr1_fault"
+#define CLKERR_RFS_SYSFS "/var/oc2g/clkerr/refresh"
+#define CLKERR_RST_SYSFS "/var/oc2g/clkerr/reset"
+
+#define OCXODAC_VAL_SYSFS "/var/oc2g/ocxo/voltage"
+#define OCXODAC_ROM_SYSFS "/var/oc2g/ocxo/eeprom"
+
+/* clock error */
+static int clkerr_fd_err = -1;
+static int clkerr_fd_accuracy = -1;
+static int clkerr_fd_interval = -1;
+static int clkerr_fd_fault = -1;
+static int clkerr_fd_refresh = -1;
+static int clkerr_fd_reset = -1;
+
+/* ocxo dac */
+static int ocxodac_fd_value = -1;
+static int ocxodac_fd_save = -1;
+
+
+static int sysfs_read_val(int fd, int *val)
+{
+ int rc;
+ char szVal[32] = {0};
+
+ lseek( fd, 0, SEEK_SET );
+
+ rc = read(fd, szVal, sizeof(szVal) - 1);
+ if (rc < 0) {
+ return -errno;
+ }
+
+ rc = sscanf(szVal, "%d", val);
+ if (rc != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sysfs_write_val(int fd, int val)
+{
+ int n, rc;
+ char szVal[32] = {0};
+
+ n = sprintf(szVal, "%d", val);
+
+ lseek(fd, 0, SEEK_SET);
+ rc = write(fd, szVal, n+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int sysfs_write_str(int fd, const char *str)
+{
+ int rc;
+
+ lseek( fd, 0, SEEK_SET );
+ rc = write(fd, str, strlen(str)+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+
+int oc2gbts_clock_err_open(void)
+{
+ int rc;
+ int fault;
+
+ if (clkerr_fd_err < 0) {
+ clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY);
+ if (clkerr_fd_err < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_err;
+ }
+ }
+
+ if (clkerr_fd_accuracy < 0) {
+ clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY);
+ if (clkerr_fd_accuracy < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_accuracy;
+ }
+ }
+
+ if (clkerr_fd_interval < 0) {
+ clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY);
+ if (clkerr_fd_interval < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_interval;
+ }
+ }
+
+ if (clkerr_fd_fault < 0) {
+ clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY);
+ if (clkerr_fd_fault < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_fault;
+ }
+ }
+
+ if (clkerr_fd_refresh < 0) {
+ clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY);
+ if (clkerr_fd_refresh < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_refresh;
+ }
+ }
+
+ if (clkerr_fd_reset < 0) {
+ clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY);
+ if (clkerr_fd_reset < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_reset;
+ }
+ }
+ return 0;
+}
+
+void oc2gbts_clock_err_close(void)
+{
+ if (clkerr_fd_err >= 0) {
+ close(clkerr_fd_err);
+ clkerr_fd_err = -1;
+ }
+
+ if (clkerr_fd_accuracy >= 0) {
+ close(clkerr_fd_accuracy);
+ clkerr_fd_accuracy = -1;
+ }
+
+ if (clkerr_fd_interval >= 0) {
+ close(clkerr_fd_interval);
+ clkerr_fd_interval = -1;
+ }
+
+ if (clkerr_fd_fault >= 0) {
+ close(clkerr_fd_fault);
+ clkerr_fd_fault = -1;
+ }
+
+ if (clkerr_fd_refresh >= 0) {
+ close(clkerr_fd_refresh);
+ clkerr_fd_refresh = -1;
+ }
+
+ if (clkerr_fd_reset >= 0) {
+ close(clkerr_fd_reset);
+ clkerr_fd_reset = -1;
+ }
+}
+
+int oc2gbts_clock_err_reset(void)
+{
+ return sysfs_write_val(clkerr_fd_reset, 1);
+}
+
+int oc2gbts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec)
+{
+ int rc;
+
+ rc = sysfs_write_str(clkerr_fd_refresh, "once");
+ if (rc < 0) {
+ return -1;
+ }
+
+ rc = sysfs_read_val(clkerr_fd_fault, fault);
+ rc |= sysfs_read_val(clkerr_fd_err, error_ppt);
+ rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq);
+ rc |= sysfs_read_val(clkerr_fd_interval, interval_sec);
+ if (rc) {
+ return -1;
+ }
+ return 0;
+}
+
+
+int oc2gbts_clock_dac_open(void)
+{
+ if (ocxodac_fd_value < 0) {
+ ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR);
+ if (ocxodac_fd_value < 0) {
+ oc2gbts_clock_dac_close();
+ return ocxodac_fd_value;
+ }
+ }
+
+ if (ocxodac_fd_save < 0) {
+ ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY);
+ if (ocxodac_fd_save < 0) {
+ oc2gbts_clock_dac_close();
+ return ocxodac_fd_save;
+ }
+ }
+ return 0;
+}
+
+void oc2gbts_clock_dac_close(void)
+{
+ if (ocxodac_fd_value >= 0) {
+ close(ocxodac_fd_value);
+ ocxodac_fd_value = -1;
+ }
+
+ if (ocxodac_fd_save >= 0) {
+ close(ocxodac_fd_save);
+ ocxodac_fd_save = -1;
+ }
+}
+
+int oc2gbts_clock_dac_get(int *dac_value)
+{
+ return sysfs_read_val(ocxodac_fd_value, dac_value);
+}
+
+int oc2gbts_clock_dac_set(int dac_value)
+{
+ return sysfs_write_val(ocxodac_fd_value, dac_value);
+}
+
+int oc2gbts_clock_dac_save(void)
+{
+ return sysfs_write_val(ocxodac_fd_save, 1);
+}
+
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.h b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h
new file mode 100644
index 00000000..1444b5cf
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h
@@ -0,0 +1,16 @@
+#ifndef _OC2GBTS_CLOCK_H
+#define _OC2GBTS_CLOCK_H
+
+int oc2gbts_clock_err_open(void);
+void oc2gbts_clock_err_close(void);
+int oc2gbts_clock_err_reset(void);
+int oc2gbts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec);
+
+int oc2gbts_clock_dac_open(void);
+void oc2gbts_clock_dac_close(void);
+int oc2gbts_clock_dac_get(int *dac_value);
+int oc2gbts_clock_dac_set(int dac_value);
+int oc2gbts_clock_dac_save(void);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
new file mode 100644
index 00000000..b8758b8e
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
@@ -0,0 +1,332 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "oc2gbts_led.h"
+#include "oc2gbts_bts.h"
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+
+static struct oc2gbts_led led_entries[] = {
+ {
+ .name = "led0",
+ .fullname = "led red",
+ .path = "/var/oc2g/leds/led0/brightness"
+ },
+ {
+ .name = "led1",
+ .fullname = "led green",
+ .path = "/var/oc2g/leds/led1/brightness"
+ }
+};
+
+static const struct value_string oc2gbts_led_strs[] = {
+ { OC2GBTS_LED_RED, "LED red" },
+ { OC2GBTS_LED_GREEN, "LED green" },
+ { OC2GBTS_LED_ORANGE, "LED orange" },
+ { OC2GBTS_LED_OFF, "LED off" },
+ { 0, NULL }
+};
+
+static uint8_t led_priority[] = {
+ BLINK_PATTERN_INIT,
+ BLINK_PATTERN_INT_PROC_MALFUNC,
+ BLINK_PATTERN_SUPPLY_PWR_MAX,
+ BLINK_PATTERN_PA_PWR_MAX,
+ BLINK_PATTERN_VSWR_MAX,
+ BLINK_PATTERN_SUPPLY_VOLT_MIN,
+ BLINK_PATTERN_TEMP_MAX,
+ BLINK_PATTERN_EXT_LINK_MALFUNC,
+ BLINK_PATTERN_SUPPLY_VOLT_LOW,
+ BLINK_PATTERN_TEMP_HIGH,
+ BLINK_PATTERN_VSWR_HIGH,
+ BLINK_PATTERN_SUPPLY_PWR_HIGH,
+ BLINK_PATTERN_PA_PWR_HIGH,
+ BLINK_PATTERN_GPS_FIX_LOST,
+ BLINK_PATTERN_NORMAL
+};
+
+
+char *blink_pattern_command[] = BLINK_PATTERN_COMMAND;
+
+static int oc2gbts_led_write(char *path, char *str)
+{
+ int fd;
+
+ if ((fd = open(path, O_WRONLY)) == -1)
+ {
+ return 0;
+ }
+
+ write(fd, str, strlen(str)+1);
+ close(fd);
+ return 1;
+}
+
+static void led_set_red()
+{
+ oc2gbts_led_write(led_entries[0].path, "1");
+ oc2gbts_led_write(led_entries[1].path, "0");
+}
+
+static void led_set_green()
+{
+ oc2gbts_led_write(led_entries[0].path, "0");
+ oc2gbts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_orange()
+{
+ oc2gbts_led_write(led_entries[0].path, "1");
+ oc2gbts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_off()
+{
+ oc2gbts_led_write(led_entries[0].path, "0");
+ oc2gbts_led_write(led_entries[1].path, "0");
+}
+
+static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) {
+ /* Cancel any pending timer */
+ osmo_timer_del(&led_timer->timer);
+ /* Start LED timer */
+ led_timer->timer.cb = led_timer_cb;
+ led_timer->timer.data = mgr;
+ mgr->oc2gbts_leds.active_timer = led_timer->idx;
+ osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec);
+ LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_timer->idx),
+ led_timer->param.sleep_sec,
+ led_timer->param.sleep_usec);
+
+ switch (led_timer->idx) {
+ case OC2GBTS_LED_RED:
+ led_set_red();
+ break;
+ case OC2GBTS_LED_GREEN:
+ led_set_green();
+ break;
+ case OC2GBTS_LED_ORANGE:
+ led_set_orange();
+ break;
+ case OC2GBTS_LED_OFF:
+ led_set_off();
+ break;
+ default:
+ led_set_off();
+ }
+}
+
+static void led_sleep_cb(void *_data) {
+ struct oc2gbts_mgr_instance *mgr = _data;
+ struct oc2gbts_led_timer_list *led_list;
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) {
+ if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ /* Delete current timer */
+ osmo_timer_del(&led_list->led_timer.timer);
+ /* Rotate the timer list */
+ llist_move_tail(led_list, &mgr->oc2gbts_leds.list);
+ break;
+ }
+ }
+
+ /* Execute next timer */
+ led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->oc2gbts_leds.list));
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr)
+{
+ struct oc2gbts_led_timer_list *led_list, *led_list2;
+
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_leds.list, list) {
+ /* Delete the timer in list */
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ /* Delete current timer */
+ osmo_timer_del(&led_list->led_timer.timer);
+ llist_del(&led_list->list);
+ talloc_free(led_list);
+ }
+ }
+ return;
+}
+
+static int add_led_timer_entry(struct oc2gbts_mgr_instance *mgr, char *cmdstr)
+{
+ double sec, int_sec, frac_sec;
+ struct oc2gbts_sleep_time led_param;
+
+ led_param.sleep_sec = 0;
+ led_param.sleep_usec = 0;
+
+ if (strstr(cmdstr, "set red") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED;
+ else if (strstr(cmdstr, "set green") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN;
+ else if (strstr(cmdstr, "set orange") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE;
+ else if (strstr(cmdstr, "set off") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_OFF;
+ else if (strstr(cmdstr, "sleep") != NULL) {
+ sec = atof(cmdstr + 6);
+ /* split time into integer and fractional of seconds */
+ frac_sec = modf(sec, &int_sec) * 1000000.0;
+ led_param.sleep_sec = (int)int_sec;
+ led_param.sleep_usec = (int)frac_sec;
+
+ if ((mgr->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) {
+ struct oc2gbts_led_timer_list *led_list;
+
+ /* allocate timer entry */
+ led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list);
+ if (led_list) {
+ led_list->led_timer.idx = mgr->oc2gbts_leds.led_idx;
+ led_list->led_timer.param.sleep_sec = led_param.sleep_sec;
+ led_list->led_timer.param.sleep_usec = led_param.sleep_usec;
+ llist_add_tail(&led_list->list, &mgr->oc2gbts_leds.list);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n",
+ get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->oc2gbts_leds.list));
+ }
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int parse_led_pattern(char *pattern, struct oc2gbts_mgr_instance *mgr)
+{
+ char str[1024];
+ char *pstr;
+ char *sep;
+ int rc = 0;
+
+ strcpy(str, pattern);
+ pstr = str;
+ while ((sep = strsep(&pstr, ";")) != NULL) {
+ rc = add_led_timer_entry(mgr, sep);
+ if (rc < 0) {
+ break;
+ }
+
+ }
+ return rc;
+}
+
+/*** led interface ***/
+
+void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id)
+{
+ int rc;
+ struct oc2gbts_led_timer_list *led_list;
+
+ if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) {
+ LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n",
+ pattern_id,
+ BLINK_PATTERN_POWER_ON,
+ BLINK_PATTERN_MAX_ITEM - 1);
+ return;
+ }
+ if (pattern_id == mgr->oc2gbts_leds.last_pattern_id)
+ return;
+
+ mgr->oc2gbts_leds.last_pattern_id = pattern_id;
+
+ LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id);
+ LOGP(DTEMP, LOGL_INFO, "%s\n", blink_pattern_command[pattern_id]);
+
+ /* Empty existing LED timer in the list */
+ delete_led_timer_entries(mgr);
+
+ /* parse LED pattern */
+ rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n");
+ return;
+ }
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ /* Start the first LED timer in the list */
+ led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+void select_led_pattern(struct oc2gbts_mgr_instance *mgr)
+{
+ int i;
+ uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0};
+
+ /* set normal LED pattern at first */
+ led[BLINK_PATTERN_NORMAL] = 1;
+
+ /* check on-board sensors for new LED pattern */
+ check_sensor_led_pattern(mgr, led);
+
+ /* check BTS status for new LED pattern */
+ check_bts_led_pattern(led);
+
+ /* check by priority */
+ for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) {
+ if(led[led_priority[i]] == 1) {
+ led_set(mgr, led_priority[i]);
+ break;
+ }
+ }
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.h b/src/osmo-bts-oc2g/misc/oc2gbts_led.h
new file mode 100644
index 00000000..cb627e3c
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.h
@@ -0,0 +1,22 @@
+#ifndef _OC2GBTS_LED_H
+#define _OC2GBTS_LED_H
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/stat.h>
+
+#include <osmo-bts/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include "oc2gbts_mgr.h"
+
+/* public function prototypes */
+void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id);
+
+void select_led_pattern(struct oc2gbts_mgr_instance *mgr);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
new file mode 100644
index 00000000..45ecc65d
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
@@ -0,0 +1,353 @@
+/* Main program for NuRAN Wireless OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+#include "misc/oc2gbts_power.h"
+#include "misc/oc2gbts_swd.h"
+
+#include "oc2gbts_led.h"
+
+static int no_rom_write = 0;
+static int daemonize = 0;
+void *tall_mgr_ctx;
+
+/* every 6 hours means 365*4 = 1460 rom writes per year (max) */
+#define SENSOR_TIMER_SECS (6 * 3600)
+
+/* every 1 hours means 365*24 = 8760 rom writes per year (max) */
+#define HOURS_TIMER_SECS (1 * 3600)
+
+/* the initial state */
+static struct oc2gbts_mgr_instance manager = {
+ .config_file = "oc2gbts-mgr.cfg",
+ .temp = {
+ .supply_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .soc_temp_limit = {
+ .thresh_warn_max = 95,
+ .thresh_crit_max = 100,
+ .thresh_warn_min = -40,
+ },
+ .fpga_temp_limit = {
+ .thresh_warn_max = 95,
+ .thresh_crit_max = 100,
+ .thresh_warn_min = -40,
+ },
+ .rmsdet_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .ocxo_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .tx_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -20,
+ },
+ .pa_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ }
+ },
+ .volt = {
+ .supply_volt_limit = {
+ .thresh_warn_max = 30000,
+ .thresh_crit_max = 30500,
+ .thresh_warn_min = 19000,
+ .thresh_crit_min = 17500,
+ }
+ },
+ .pwr = {
+ .supply_pwr_limit = {
+ .thresh_warn_max = 30,
+ .thresh_crit_max = 40,
+ },
+ .pa_pwr_limit = {
+ .thresh_warn_max = 20,
+ .thresh_crit_max = 30,
+ }
+ },
+ .vswr = {
+ .vswr_limit = {
+ .thresh_warn_max = 3000,
+ .thresh_crit_max = 5000,
+ }
+ },
+ .gps = {
+ .gps_fix_limit = {
+ .thresh_warn_max = 7,
+ }
+ },
+ .state = {
+ .action_norm = SENSOR_ACT_NORM_PA_ON,
+ .action_warn = 0,
+ .action_crit = 0,
+ .action_comb = 0,
+ .state = STATE_NORMAL,
+ }
+};
+
+static struct osmo_timer_list sensor_timer;
+static void check_sensor_timer_cb(void *unused)
+{
+ oc2gbts_check_temp(no_rom_write);
+ oc2gbts_check_power(no_rom_write);
+ oc2gbts_check_vswr(no_rom_write);
+ osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0);
+ /* TODO checks if oc2gbts_check_temp/oc2gbts_check_power/oc2gbts_check_vswr went ok */
+ oc2gbts_swd_event(&manager, SWD_CHECK_SENSOR);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ oc2gbts_update_hours(no_rom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+ /* TODO: validates if oc2gbts_update_hours went correctly */
+ oc2gbts_swd_event(&manager, SWD_UPDATE_HOURS);
+}
+
+static void print_help(void)
+{
+ printf("oc2gbts-mgr [-nsD] [-d cat]\n");
+ printf(" -n Do not write to ROM\n");
+ printf(" -s Disable color\n");
+ printf(" -d CAT enable debugging\n");
+ printf(" -D daemonize\n");
+ printf(" -c Specify the filename of the config file\n");
+}
+
+static int parse_options(int argc, char **argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
+ switch (opt) {
+ case 'n':
+ no_rom_write = 1;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ manager.config_file = optarg;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ oc2gbts_check_temp(no_rom_write);
+ oc2gbts_check_power(no_rom_write);
+ oc2gbts_check_vswr(no_rom_write);
+ oc2gbts_update_hours(no_rom_write);
+ exit(0);
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_mgr_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct log_info_cat mgr_log_info_cat[] = {
+ [DTEMP] = {
+ .name = "DTEMP",
+ .description = "Temperature monitoring",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFW] = {
+ .name = "DFW",
+ .description = "Firmware management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFIND] = {
+ .name = "DFIND",
+ .description = "ipaccess-find handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DCALIB] = {
+ .name = "DCALIB",
+ .description = "Calibration handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DSWD] = {
+ .name = "DSWD",
+ .description = "Software Watchdog",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+static const struct log_info mgr_log_info = {
+ .cat = mgr_log_info_cat,
+ .num_cat = ARRAY_SIZE(mgr_log_info_cat),
+};
+
+static int mgr_log_init(void)
+{
+ osmo_init_logging(&mgr_log_info);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ void *tall_msgb_ctx;
+ int rc;
+ pthread_t tid;
+
+ tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
+ tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb");
+ msgb_set_talloc_ctx(tall_msgb_ctx);
+
+ mgr_log_init();
+
+ osmo_init_ignore_signals();
+ signal(SIGINT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ oc2gbts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = oc2gbts_mgr_parse_config(&manager);
+ if (rc < 0) {
+ LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
+ exit(1);
+ }
+
+ rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ INIT_LLIST_HEAD(&manager.oc2gbts_leds.list);
+ INIT_LLIST_HEAD(&manager.alarms.list);
+
+ /* Initialize the service watchdog notification for SWD_LAST event(s) */
+ if (oc2gbts_swd_init(&manager, (int)(SWD_LAST)) != 0)
+ exit(3);
+
+ /* start temperature check timer */
+ sensor_timer.cb = check_sensor_timer_cb;
+ check_sensor_timer_cb(NULL);
+
+ /* start operational hours timer */
+ hours_timer.cb = hours_timer_cb;
+ hours_timer_cb(NULL);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ /* Enable the PAs */
+ rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+ }
+ /* handle broadcast messages for ipaccess-find */
+ if (oc2gbts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the sensor control */
+ oc2gbts_mgr_sensor_init(&manager);
+
+ if (oc2gbts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (oc2gbts_mgr_control_init(&manager))
+ exit(3);
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ oc2gbts_swd_event(&manager, SWD_MAINLOOP);
+ }
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h
new file mode 100644
index 00000000..1f50fb6b
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h
@@ -0,0 +1,398 @@
+#ifndef _OC2GBTS_MGR_H
+#define _OC2GBTS_MGR_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <stdint.h>
+#include <gps.h>
+
+#define OC2GBTS_SENSOR_TIMER_DURATION 60
+#define OC2GBTS_PREVENT_TIMER_DURATION 15 * 60
+#define OC2GBTS_PREVENT_TIMER_SHORT_DURATION 5 * 60
+#define OC2GBTS_PREVENT_TIMER_NONE 0
+#define OC2GBTS_PREVENT_RETRY INT_MAX - 1
+
+enum BLINK_PATTERN {
+ BLINK_PATTERN_POWER_ON = 0, //hardware set
+ BLINK_PATTERN_INIT,
+ BLINK_PATTERN_NORMAL,
+ BLINK_PATTERN_EXT_LINK_MALFUNC,
+ BLINK_PATTERN_INT_PROC_MALFUNC,
+ BLINK_PATTERN_SUPPLY_VOLT_LOW,
+ BLINK_PATTERN_SUPPLY_VOLT_MIN,
+ BLINK_PATTERN_VSWR_HIGH,
+ BLINK_PATTERN_VSWR_MAX,
+ BLINK_PATTERN_TEMP_HIGH,
+ BLINK_PATTERN_TEMP_MAX,
+ BLINK_PATTERN_SUPPLY_PWR_HIGH,
+ BLINK_PATTERN_SUPPLY_PWR_MAX,
+ BLINK_PATTERN_PA_PWR_HIGH,
+ BLINK_PATTERN_PA_PWR_MAX,
+ BLINK_PATTERN_GPS_FIX_LOST,
+ BLINK_PATTERN_MAX_ITEM
+};
+
+#define BLINK_PATTERN_COMMAND {\
+ "set red; sleep 5.0",\
+ "set orange; sleep 5.0",\
+ "set green; sleep 2.5; set off; sleep 2.5",\
+ "set red; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 2.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\
+ "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+}
+
+enum {
+ DTEMP,
+ DFW,
+ DFIND,
+ DCALIB,
+ DSWD,
+};
+
+// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ...
+enum {
+#if 0
+ SENSOR_ACT_PWR_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_PA_OFF = 0x2,
+ SENSOR_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ SENSOR_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_NORM_PA_ON = 0x2,
+ SENSOR_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum oc2gbts_sensor_state {
+ STATE_NORMAL, /* Everything is fine */
+ STATE_WARNING_HYST, /* Go back to normal next? */
+ STATE_WARNING, /* We are above the warning threshold */
+ STATE_CRITICAL, /* We have an issue. Wait for below warning */
+};
+
+enum oc2gbts_leds_name {
+ OC2GBTS_LED_RED = 0,
+ OC2GBTS_LED_GREEN,
+ OC2GBTS_LED_ORANGE,
+ OC2GBTS_LED_OFF,
+ _OC2GBTS_LED_MAX
+};
+
+struct oc2gbts_led{
+ char *name;
+ char *fullname;
+ char *path;
+};
+
+/**
+ * Temperature Limits. We separate from a threshold
+ * that will generate a warning and one that is so
+ * severe that an action will be taken.
+ */
+struct oc2gbts_temp_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+};
+
+struct oc2gbts_volt_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+ int thresh_crit_min;
+};
+
+struct oc2gbts_pwr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct oc2gbts_vswr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct oc2gbts_gps_fix_limit {
+ int thresh_warn_max;
+};
+
+struct oc2gbts_sleep_time {
+ int sleep_sec;
+ int sleep_usec;
+};
+
+struct oc2gbts_led_timer {
+ uint8_t idx;
+ struct osmo_timer_list timer;
+ struct oc2gbts_sleep_time param;
+};
+
+struct oc2gbts_led_timer_list {
+ struct llist_head list;
+ struct oc2gbts_led_timer led_timer;
+};
+
+struct oc2gbts_preventive_list {
+ struct llist_head list;
+ struct oc2gbts_sleep_time param;
+ int action_flag;
+};
+
+enum mgr_vty_node {
+ MGR_NODE = _LAST_OSMOVTY_NODE + 1,
+
+ ACT_NORM_NODE,
+ ACT_WARN_NODE,
+ ACT_CRIT_NODE,
+ LIMIT_SUPPLY_TEMP_NODE,
+ LIMIT_SOC_NODE,
+ LIMIT_FPGA_NODE,
+ LIMIT_RMSDET_NODE,
+ LIMIT_OCXO_NODE,
+ LIMIT_TX_TEMP_NODE,
+ LIMIT_PA_TEMP_NODE,
+ LIMIT_SUPPLY_VOLT_NODE,
+ LIMIT_VSWR_NODE,
+ LIMIT_SUPPLY_PWR_NODE,
+ LIMIT_PA_PWR_NODE,
+ LIMIT_GPS_FIX_NODE,
+};
+
+enum mgr_vty_limit_type {
+ MGR_LIMIT_TYPE_TEMP = 0,
+ MGR_LIMIT_TYPE_VOLT,
+ MGR_LIMIT_TYPE_VSWR,
+ MGR_LIMIT_TYPE_PWR,
+ _MGR_LIMIT_TYPE_MAX,
+};
+
+struct oc2gbts_mgr_instance {
+ const char *config_file;
+
+ struct {
+ struct oc2gbts_temp_limit supply_temp_limit;
+ struct oc2gbts_temp_limit soc_temp_limit;
+ struct oc2gbts_temp_limit fpga_temp_limit;
+ struct oc2gbts_temp_limit rmsdet_temp_limit;
+ struct oc2gbts_temp_limit ocxo_temp_limit;
+ struct oc2gbts_temp_limit tx_temp_limit;
+ struct oc2gbts_temp_limit pa_temp_limit;
+ } temp;
+
+ struct {
+ struct oc2gbts_volt_limit supply_volt_limit;
+ } volt;
+
+ struct {
+ struct oc2gbts_pwr_limit supply_pwr_limit;
+ struct oc2gbts_pwr_limit pa_pwr_limit;
+ } pwr;
+
+ struct {
+ struct oc2gbts_vswr_limit vswr_limit;
+ int last_vswr;
+ } vswr;
+
+ struct {
+ struct oc2gbts_gps_fix_limit gps_fix_limit;
+ int last_update;
+ time_t last_gps_fix;
+ time_t gps_fix_now;
+ int gps_open;
+ struct osmo_fd gpsfd;
+ struct gps_data_t gpsdata;
+ struct osmo_timer_list fix_timeout;
+ } gps;
+
+ struct {
+ int action_norm;
+ int action_warn;
+ int action_crit;
+ int action_comb;
+
+ enum oc2gbts_sensor_state state;
+ } state;
+
+ struct {
+ int state;
+ int calib_from_loop;
+ struct osmo_timer_list calib_timeout;
+ } calib;
+
+ struct {
+ int state;
+ int swd_from_loop;
+ unsigned long long int swd_events;
+ unsigned long long int swd_events_cache;
+ unsigned long long int swd_eventmasks;
+ int num_events;
+ struct osmo_timer_list swd_timeout;
+ } swd;
+
+ struct {
+ uint8_t led_idx;
+ uint8_t last_pattern_id;
+ uint8_t active_timer;
+ struct llist_head list;
+ } oc2gbts_leds;
+
+ struct {
+ int is_up;
+ uint32_t last_seqno;
+ struct osmo_timer_list recon_timer;
+ struct ipa_client_conn *bts_conn;
+ uint32_t crit_flags;
+ uint32_t warn_flags;
+ } oc2gbts_ctrl;
+
+ struct oc2gbts_alarms {
+ int temp_high;
+ int temp_max;
+ int supply_low;
+ int supply_min;
+ int vswr_high;
+ int vswr_max;
+ int supply_pwr_high;
+ int supply_pwr_max;
+ int pa_pwr_high;
+ int pa_pwr_max;
+ int gps_fix_lost;
+ struct llist_head list;
+ struct osmo_timer_list preventive_timer;
+ int preventive_duration;
+ int preventive_retry;
+ } alarms;
+
+};
+
+enum oc2gbts_mgr_fail_evt_rep_crit_sig {
+ /* Critical alarms */
+ S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0),
+ S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1),
+ S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2),
+ S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3),
+ S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4),
+ S_MGR_TEMP_TRX_CRIT_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_PA_CRIT_MAX_ALARM = (1 << 6),
+ S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 7),
+ S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 8),
+ S_MGR_VSWR_CRIT_MAX_ALARM = (1 << 9),
+ S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 10),
+ S_MGR_PWR_PA_CRIT_MAX_ALARM = (1 << 11),
+ _S_MGR_CRIT_ALARM_MAX,
+};
+
+enum oc2gbts_mgr_fail_evt_rep_warn_sig {
+ /* Warning alarms */
+ S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0),
+ S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 1),
+ S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 2),
+ S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 3),
+ S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 4),
+ S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 6),
+ S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 7),
+ S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 8),
+ S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 9),
+ S_MGR_TEMP_TRX_WARN_MIN_ALARM = (1 << 10),
+ S_MGR_TEMP_TRX_WARN_MAX_ALARM = (1 << 11),
+ S_MGR_TEMP_PA_WARN_MIN_ALARM = (1 << 12),
+ S_MGR_TEMP_PA_WARN_MAX_ALARM = (1 << 13),
+ S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 14),
+ S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 15),
+ S_MGR_VSWR_WARN_MAX_ALARM = (1 << 16),
+ S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 17),
+ S_MGR_PWR_PA_WARN_MAX_ALARM = (1 << 18),
+ S_MGR_GPS_FIX_WARN_ALARM = (1 << 19),
+ _S_MGR_WARN_ALARM_MAX,
+};
+
+enum oc2gbts_mgr_failure_event_causes {
+ /* Critical causes */
+ NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100,
+ NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101,
+ NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102,
+ NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103,
+ NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104,
+ NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL = 0x4105,
+ NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL = 0x4106,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4107,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x4108,
+ NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL = 0x4109,
+ NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410A,
+ NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL = 0x410B,
+ /* Warning causes */
+ NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400,
+ NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401,
+ NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402,
+ NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403,
+ NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404,
+ NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405,
+ NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406,
+ NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407,
+ NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408,
+ NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409,
+ NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL = 0x440A,
+ NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL = 0x440B,
+ NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL = 0x440C,
+ NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL = 0x440D,
+ NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x440E,
+ NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x440F,
+ NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL = 0x4410,
+ NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4411,
+ NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL = 0x4412,
+ NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4413,
+};
+
+/* This defines the list of notification events for systemd service watchdog.
+ all these events must be notified in a certain service defined timeslot
+ or the service (this app) would be restarted (only if related systemd service
+ unit file has WatchdogSec!=0).
+ WARNING: swd events must begin with event 0. Last events must be
+ SWD_LAST (max 64 events in this list).
+*/
+enum mgr_swd_events {
+ SWD_MAINLOOP = 0,
+ SWD_CHECK_SENSOR,
+ SWD_UPDATE_HOURS,
+ SWD_CHECK_TEMP_SENSOR,
+ SWD_CHECK_LED_CTRL,
+ SWD_CHECK_CALIB,
+ SWD_CHECK_BTS_CONNECTION,
+ SWD_LAST
+};
+
+int oc2gbts_mgr_vty_init(void);
+int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_nl_init(void);
+int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr);
+const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state);
+
+int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr);
+void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text);
+void handle_alert_actions(struct oc2gbts_mgr_instance *mgr);
+void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr);
+void handle_warn_actions(struct oc2gbts_mgr_instance *mgr);
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
new file mode 100644
index 00000000..104d2792
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
@@ -0,0 +1,760 @@
+/* OCXO calibration control for OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_calib.c
+ * (C) 2014,2015 by Holger Hans Peter Freyther
+ * (C) 2014 by Harald Welte for the IPA code from the oml router
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_clock.h"
+#include "misc/oc2gbts_swd.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_led.h"
+#include "osmo-bts/msg_utils.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipa.h>
+
+#include <time.h>
+#include <sys/sysinfo.h>
+#include <errno.h>
+
+static void calib_adjust(struct oc2gbts_mgr_instance *mgr);
+static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
+static void calib_loop_run(void *_data);
+
+static int ocxodac_saved_value = -1;
+
+enum calib_state {
+ CALIB_INITIAL,
+ CALIB_IN_PROGRESS,
+ CALIB_GPS_WAIT_FOR_FIX,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPSFIX,
+ CALIB_FAIL_CLKERR,
+ CALIB_FAIL_OCXODAC,
+ CALIB_SUCCESS,
+};
+
+static inline int compat_gps_read(struct gps_data_t *data)
+{
+/* API break in gpsd 6bba8b329fc7687b15863d30471d5af402467802 */
+#if GPSD_API_MAJOR_VERSION >= 7 && GPSD_API_MINOR_VERSION >= 0
+ return gps_read(data, NULL, 0);
+#else
+ return gps_read(data);
+#endif
+}
+
+static int oc2gbts_par_get_uptime(void *ctx, int *ret)
+{
+ char *fpath;
+ FILE *fp;
+ int rc;
+
+ fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, "r");
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+static int oc2gbts_par_set_uptime(void *ctx, int val)
+{
+ char *fpath;
+ FILE *fp;
+ int rc;
+
+ fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, "w");
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+
+ return 0;
+}
+
+static void mgr_gps_close(struct oc2gbts_mgr_instance *mgr)
+{
+ if (!mgr->gps.gps_open)
+ return;
+
+ osmo_timer_del(&mgr->gps.fix_timeout);
+
+ osmo_fd_unregister(&mgr->gps.gpsfd);
+ gps_close(&mgr->gps.gpsdata);
+ memset(&mgr->gps.gpsdata, 0, sizeof(mgr->gps.gpsdata));
+ mgr->gps.gps_open = 0;
+}
+
+static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr)
+{
+ struct gps_data_t *data = &mgr->gps.gpsdata;
+
+ /* No 3D fix yet */
+ if (data->fix.mode < MODE_3D) {
+ LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
+ data->fix.mode);
+ return;
+ }
+
+ /* Satellite used checking */
+ if (data->satellites_used < 1) {
+ LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
+ data->satellites_used);
+ return;
+ }
+
+ mgr->gps.gps_fix_now = (time_t) data->fix.time;
+ LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n",
+ data->satellites_used, mgr->gps.gps_fix_now);
+ osmo_timer_del(&mgr->gps.fix_timeout);
+ mgr_gps_close(mgr);
+}
+
+static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = fd->data;
+
+ rc = compat_gps_read(&mgr->gps.gpsdata);
+ if (rc == -1) {
+ LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return -1;
+ }
+
+ if (rc > 0)
+ mgr_gps_checkfix(mgr);
+ return 0;
+}
+
+static void mgr_gps_fix_timeout(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPS fix.\n");
+ mgr_gps_close(mgr);
+}
+
+static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ if (mgr->gps.gps_open)
+ return;
+
+ rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->gps.gpsdata);
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ mgr->gps.gps_open = 1;
+ gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL);
+
+ mgr->gps.gpsfd.data = mgr;
+ mgr->gps.gpsfd.cb = mgr_gps_read;
+ mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT;
+ mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd;
+ if (osmo_fd_register(&mgr->gps.gpsfd) < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ }
+
+ mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
+ mgr->gps.fix_timeout.data = mgr;
+ mgr->gps.fix_timeout.cb = mgr_gps_fix_timeout;
+ osmo_timer_schedule(&mgr->gps.fix_timeout, 60, 0);
+ LOGP(DCALIB, LOGL_INFO, "Opened the GPSD connection waiting for fix: %d\n",
+ mgr->gps.gpsfd.fd);
+}
+
+/* OC2G CTRL interface related functions */
+static void send_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
+{
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+ ipa_client_conn_send(mgr->oc2gbts_ctrl.bts_conn, msg);
+}
+
+static void send_set_ctrl_cmd_int(struct oc2gbts_mgr_instance *mgr, const char *key, const int val)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d",
+ mgr->oc2gbts_ctrl.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_set_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, const char *key, const int val, const char *text)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d, %s",
+ mgr->oc2gbts_ctrl.last_seqno++, key, val, text);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void calib_start(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = oc2gbts_clock_err_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ rc = oc2gbts_clock_dac_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ calib_adjust(mgr);
+}
+static int get_uptime(int *uptime)
+{
+ struct sysinfo s_info;
+ int rc;
+ rc = sysinfo(&s_info);
+ if(!rc)
+ *uptime = s_info.uptime /(60 * 60);
+
+ return rc;
+}
+
+static void calib_adjust(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+ int fault;
+ int error_ppt;
+ int accuracy_ppq;
+ int interval_sec;
+ int dac_value;
+ int new_dac_value;
+ int dac_correction;
+ int now = 0;
+
+ /* Get GPS time via GPSD */
+ mgr_gps_open(mgr);
+
+ rc = oc2gbts_clock_err_get(&fault, &error_ppt,
+ &accuracy_ppq, &interval_sec);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get clock error measurement %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ /* get current up time */
+ rc = get_uptime(&now);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read up time hours: %d (%s)\n", rc, strerror(errno));
+
+ /* read last up time */
+ rc = oc2gbts_par_get_uptime(tall_mgr_ctx, &mgr->gps.last_update);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc);
+
+ if (fault) {
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix, warn_flags=0x%08x, last=%d, now=%d\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
+ if (now >= mgr->gps.last_update + mgr->gps.gps_fix_limit.thresh_warn_max * 24) {
+ if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
+
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last verification, warn_flags=0x%08x, last=%d, now=%d\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
+
+ /* schedule LED pattern for GPS fix lost */
+ mgr->alarms.gps_fix_lost = 1;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ }
+ } else {
+ /* read from last GPS 3D fix timestamp */
+ rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix timestamp can not read (%d)\n", rc);
+
+ if (difftime(mgr->gps.gps_fix_now, mgr->gps.last_gps_fix) > mgr->gps.gps_fix_limit.thresh_warn_max * 24 * 60 * 60) {
+ if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
+
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last known GPS fix, warn_flags=0x%08x, gps_last=%ld, gps_now=%ld\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
+
+ /* schedule LED pattern for GPS fix lost */
+ mgr->alarms.gps_fix_lost = 1;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ }
+ }
+ }
+
+ rc = oc2gbts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ if (!interval_sec) {
+ LOGP(DCALIB, LOGL_INFO, "Skipping this iteration, no integration time\n");
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+ }
+
+ /* We got GPS 3D fix */
+ LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, uptime_last=%d, uptime_now=%d, gps_last=%ld, gps_now=%ld\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
+
+ if (mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM) {
+ /* Store GPS fix as soon as we send ceased alarm */
+ LOGP(DCALIB, LOGL_NOTICE, "Store GPS fix as soon as we send ceased alarm last=%ld, now=%ld\n",
+ mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
+
+ /* Store last up time */
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ mgr->gps.last_update = now;
+
+ /* schedule LED pattern for GPS fix resume */
+ mgr->alarms.gps_fix_lost = 0;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ /* send ceased alarm if possible */
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-ceased", "GPS 3D fix has been lost");
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_GPS_FIX_WARN_ALARM;
+
+ }
+ /* Store GPS fix at every hour */
+ if (now > mgr->gps.last_update) {
+ /* Store GPS fix every 60 minutes */
+ LOGP(DCALIB, LOGL_INFO, "Store GPS fix every hour last=%ld, now=%ld\n",
+ mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
+
+ /* Store last up time every 60 minutes */
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ /* update last uptime */
+ mgr->gps.last_update = now;
+ }
+
+ rc = oc2gbts_clock_dac_get(&dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ /* Set OCXO initial dac value */
+ if (ocxodac_saved_value < 0)
+ ocxodac_saved_value = dac_value;
+
+ LOGP(DCALIB, LOGL_INFO,
+ "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
+ error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
+
+ /* 1 unit of correction equal about 0.5 - 1 PPB correction */
+ dac_correction = (int)(-error_ppt * 0.0015);
+ new_dac_value = dac_value + dac_correction;
+
+ if (new_dac_value > 4095)
+ new_dac_value = 4095;
+ else if (new_dac_value < 0)
+ new_dac_value = 0;
+
+ /* We have a fix, make sure the measured error is
+ meaningful (10 times the accuracy) */
+ if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) {
+
+ LOGP(DCALIB, LOGL_INFO,
+ "Going to apply %d as new clock setting.\n",
+ new_dac_value);
+
+ rc = oc2gbts_clock_dac_set(new_dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to set OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+ rc = oc2gbts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+ }
+ /* New conditions to store DAC value:
+ * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ)
+ * - Error less or equal than 2PPB (or 2000PPT)
+ * - Solution different than the last one */
+ else if (accuracy_ppq <= 10000) {
+ if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) {
+ LOGP(DCALIB, LOGL_INFO, "Saving OCXO DAC value to memory... val = %d\n", dac_value);
+ rc = oc2gbts_clock_dac_save();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to save OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ } else {
+ ocxodac_saved_value = dac_value;
+ }
+ }
+
+ rc = oc2gbts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ }
+ }
+
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+}
+
+static void calib_close(struct oc2gbts_mgr_instance *mgr)
+{
+ oc2gbts_clock_err_close();
+ oc2gbts_clock_dac_close();
+}
+
+static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->calib.calib_from_loop) {
+ /*
+ * In case of success calibrate in two hours again
+ * and in case of a failure in some minutes.
+ *
+ * TODO NTQ: Select timeout based on last error and accuracy
+ */
+ int timeout = 60;
+ //int timeout = 2 * 60 * 60;
+ //if (outcome != CALIB_SUCESS) }
+ // timeout = 5 * 60;
+ //}
+
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
+ /* TODO: do we want to notify if we got a calibration error, like no gps fix? */
+ oc2gbts_swd_event(mgr, SWD_CHECK_CALIB);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ calib_close(mgr);
+}
+
+static int calib_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -1;
+ }
+
+ /* Validates if we have a bts connection */
+ if (mgr->oc2gbts_ctrl.is_up) {
+ LOGP(DCALIB, LOGL_DEBUG, "Bts connection is up.\n");
+ oc2gbts_swd_event(mgr, SWD_CHECK_BTS_CONNECTION);
+ }
+
+ mgr->calib.calib_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->calib.state = CALIB_IN_PROGRESS;
+ calib_start(mgr);
+ return 0;
+}
+
+static void calib_loop_run(void *_data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_INFO, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0) {
+ calib_state_reset(mgr, CALIB_FAIL_START);
+ }
+}
+
+int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+static void schedule_bts_connect(struct oc2gbts_mgr_instance *mgr)
+{
+ DEBUGP(DLCTRL, "Scheduling BTS connect\n");
+ osmo_timer_schedule(&mgr->oc2gbts_ctrl.recon_timer, 1, 0);
+}
+
+/* link to BSC has gone up or down */
+static void bts_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct oc2gbts_mgr_instance *mgr = link->data;
+
+ LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
+
+ if (up) {
+ mgr->oc2gbts_ctrl.is_up = 1;
+ mgr->oc2gbts_ctrl.last_seqno = 0;
+ /* handle any pending alarm */
+ handle_alert_actions(mgr);
+ handle_warn_actions(mgr);
+ } else {
+ mgr->oc2gbts_ctrl.is_up = 0;
+ schedule_bts_connect(mgr);
+ }
+
+}
+
+/* BTS re-connect timer call-back */
+static void bts_recon_timer_cb(void *data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = data;
+
+ /* update LED pattern */
+ select_led_pattern(mgr);
+
+ /* The connection failures are to be expected during boot */
+ mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE;
+ rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn);
+ if (rc < 0) {
+ LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
+ schedule_bts_connect(mgr);
+ }
+}
+
+static void oc2gbts_handle_ctrl(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
+ int cause = atoi(cmd->reply);
+
+ if (!cmd) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
+ return;
+ }
+
+ switch (cmd->type) {
+ case CTRL_TYPE_GET_REPLY:
+ LOGP(DCALIB, LOGL_INFO, "Got GET_REPLY from BTS cause=0x%x\n", cause);
+ break;
+ case CTRL_TYPE_SET_REPLY:
+ LOGP(DCALIB, LOGL_INFO, "Got SET_REPLY from BTS cause=0x%x\n", cause);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled CTRL response: %d. Resetting state\n",
+ cmd->type);
+ break;
+ }
+
+ talloc_free(cmd);
+ return;
+}
+
+static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+{
+ int rc;
+ struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg);
+ struct ipaccess_head_ext *hh_ext;
+
+ LOGP(DLCTRL, LOGL_DEBUG, "Received data from BTS: %s\n",
+ osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ /* regular message handling */
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Invalid IPA message from BTS (rc=%d)\n", rc);
+ goto err;
+ }
+
+ switch (hh->proto) {
+ case IPAC_PROTO_OSMO:
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ switch (hh_ext->proto) {
+ case IPAC_PROTO_EXT_CTRL:
+ oc2gbts_handle_ctrl(link->data, msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled osmo ID %u from BTS\n", hh_ext->proto);
+ };
+ msgb_free(msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled stream ID %u from BTS\n", hh->proto);
+ msgb_free(msg);
+ break;
+ }
+ return 0;
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ /* initialize last uptime */
+ mgr->gps.last_update = 0;
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, mgr->gps.last_update);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ /* get last GPS 3D fix timestamp */
+ mgr->gps.last_gps_fix = 0;
+ rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to get last GPS 3D fix timestamp from storage. Create it anyway %d\n", rc);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.last_gps_fix);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store initial GPS fix to storage %d\n", rc);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0);
+
+ return rc;
+}
+
+int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr)
+{
+ mgr->oc2gbts_ctrl.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
+ "127.0.0.1", OSMO_CTRL_PORT_BTS,
+ bts_updown_cb, bts_read_cb,
+ NULL, mgr);
+ if (!mgr->oc2gbts_ctrl.bts_conn) {
+ LOGP(DLCTRL, LOGL_ERROR, "Failed to create IPA connection to BTS\n");
+ return -1;
+ }
+
+ mgr->oc2gbts_ctrl.recon_timer.cb = bts_recon_timer_cb;
+ mgr->oc2gbts_ctrl.recon_timer.data = mgr;
+ schedule_bts_connect(mgr);
+
+ return 0;
+}
+
+void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text)
+{
+ /* Make sure the control link is ready before sending alarm */
+ if (mgr->oc2gbts_ctrl.bts_conn->state != IPA_CLIENT_LINK_STATE_CONNECTED) {
+ LOGP(DLCTRL, LOGL_NOTICE, "MGR losts connection to BTS.\n");
+ LOGP(DLCTRL, LOGL_NOTICE, "MGR drops an alert cause=0x%x, text=%s to BTS\n", cause, text);
+ return;
+ }
+
+ LOGP(DLCTRL, LOGL_DEBUG, "MGR sends an alert cause=0x%x, text=%s to BTS\n", cause, text);
+ send_set_ctrl_cmd(mgr, key, cause, text);
+ return;
+}
+
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
new file mode 100644
index 00000000..db67caf2
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
@@ -0,0 +1,208 @@
+/* NetworkListen for NuRAN OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_nl.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_nl.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define ETH0_ADDR_SYSFS "/var/oc2g/net/eth0/address"
+
+static struct osmo_fd nl_fd;
+
+/*
+ * The TLV structure in IPA messages in UDP packages is a bit
+ * weird. First the header appears to have an extra NULL byte
+ * and second the L16 of the L16TV needs to include +1 for the
+ * tag. The default msgb/tlv and libosmo-abis routines do not
+ * provide this.
+ */
+
+static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
+ hh->len = htons(msg->len - sizeof(*hh) - 1);
+ hh->proto = proto;
+}
+
+static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+ const uint8_t *val)
+{
+ uint8_t *buf = msgb_put(msg, len + 2 + 1);
+
+ *buf++ = (len + 1) >> 8;
+ *buf++ = (len + 1) & 0xff;
+ *buf++ = tag;
+ memcpy(buf, val, len);
+}
+
+/*
+ * We don't look at the content of the request yet and lie
+ * about most of the responses.
+ */
+static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
+ uint8_t *data, size_t len)
+{
+ static int fetched_info = 0;
+ static char mac_str[20] = {0, };
+ static char model_name[64] = {0, };
+ static char ser_str[20] = {0, };
+
+ struct sockaddr_in loc_addr;
+ int rc;
+ char loc_ip[INET_ADDRSTRLEN];
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
+ if (!msg) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
+ return;
+ }
+
+ if (!fetched_info) {
+ int fd_eth;
+ int serno;
+ int model;
+ char rev_maj, rev_min;
+
+ /* fetch the MAC */
+ fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY);
+ if (fd_eth >= 0) {
+ read(fd_eth, mac_str, sizeof(mac_str)-1);
+ mac_str[sizeof(mac_str)-1] = '\0';
+ close(fd_eth);
+ }
+
+ /* fetch the serial number */
+ oc2gbts_par_get_int(OC2GBTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ /* fetch the model and trx number */
+ snprintf(model_name, sizeof(model_name), "OC-2G BTS");
+
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ snprintf(model_name, sizeof(model_name), "%s Rev %c.%c",
+ model_name, rev_maj, rev_min);
+
+ model = oc2gbts_model_get();
+ if (model >= 0) {
+ snprintf(model_name, sizeof(model_name), "%s (%05X)",
+ model_name, model);
+ }
+ fetched_info = 1;
+ }
+
+ if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
+ return;
+ }
+
+ msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
+
+ /* append MAC addr */
+ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
+
+ /* append ip address */
+ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
+ quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
+
+ /* append the serial number */
+ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
+
+ /* abuse some flags */
+ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
+
+ /* ip.access nanoBTS would reply to port==3006 */
+ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
+ rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
+ if (rc != msg->len)
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to send with rc(%d) errno(%d)\n", rc, errno);
+}
+
+static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
+{
+ uint8_t data[2048];
+ char src[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {};
+ socklen_t len = sizeof(addr);
+ int rc;
+
+ rc = recvfrom(fd->fd, data, sizeof(data), 0,
+ (struct sockaddr *) &addr, &len);
+ if (rc <= 0) {
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to read from socket errno(%d)\n", errno);
+ return -1;
+ }
+
+ LOGP(DFIND, LOGL_DEBUG,
+ "Received request from: %s size %d\n",
+ inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
+
+ if (rc < 6)
+ return 0;
+
+ if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
+ return 0;
+
+ respond_to(&addr, fd, data + 6, rc - 6);
+ return 0;
+}
+
+int oc2gbts_mgr_nl_init(void)
+{
+ int rc;
+
+ nl_fd.cb = ipaccess_bcast;
+ rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ "0.0.0.0", 3006, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("Socket creation");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
new file mode 100644
index 00000000..f9efd9cd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
@@ -0,0 +1,980 @@
+/* Temperature control for NuRAN OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_temp.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <inttypes.h>
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_temp.h"
+#include "misc/oc2gbts_power.h"
+#include "misc/oc2gbts_led.h"
+#include "misc/oc2gbts_swd.h"
+#include "misc/oc2gbts_bid.h"
+#include "limits.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+
+struct oc2gbts_mgr_instance *s_mgr;
+static struct osmo_timer_list sensor_ctrl_timer;
+
+static const struct value_string state_names[] = {
+ { STATE_NORMAL, "NORMAL" },
+ { STATE_WARNING_HYST, "WARNING (HYST)" },
+ { STATE_WARNING, "WARNING" },
+ { STATE_CRITICAL, "CRITICAL" },
+ { 0, NULL }
+};
+
+/* private function prototype */
+static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr);
+
+const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum oc2gbts_sensor_state current_state, int critical, int warning)
+{
+ int next_state = -1;
+ switch (current_state) {
+ case STATE_NORMAL:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ break;
+ case STATE_WARNING_HYST:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ else
+ next_state = STATE_NORMAL;
+ break;
+ case STATE_WARNING:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (!warning)
+ next_state = STATE_WARNING_HYST;
+ break;
+ case STATE_CRITICAL:
+ if (!critical && !warning)
+ next_state = STATE_WARNING;
+ break;
+ };
+
+ return next_state;
+}
+
+static void handle_normal_actions(int actions)
+{
+ /* switch on the PA */
+ if (actions & SENSOR_ACT_NORM_PA_ON) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA\n");
+ } else {
+ LOGP(DTEMP, LOGL_INFO,
+ "Switched on the PA as normal action.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_INFO,
+ "Going to switch on the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl start osmo-bts.service");
+ }
+}
+
+static void handle_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & SENSOR_ACT_PA_OFF) {
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA due temperature.\n");
+ }
+ }
+ }
+
+ if (actions & SENSOR_ACT_BTS_SRV_OFF) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch off the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl stop osmo-bts.service");
+ }
+}
+
+void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_ceased_actions in state %s, warn_flags=0x%x, crit_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.warn_flags,
+ mgr->oc2gbts_ctrl.crit_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ /* clear warning flag without sending ceased alarm */
+ if (mgr->oc2gbts_ctrl.warn_flags & cause)
+ mgr->oc2gbts_ctrl.warn_flags &= ~cause;
+
+ /* clear warning flag with sending ceased alarm */
+ if (mgr->oc2gbts_ctrl.crit_flags & cause) {
+ /* clear associated flag */
+ mgr->oc2gbts_ctrl.crit_flags &= ~cause;
+ /* dispatch ceased alarm */
+ switch (cause) {
+ case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Main power supply temperature is too high");
+ break;
+ case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-ceased", "SoC temperature is too high");
+ break;
+ case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-ceased", "FPGA temperature is too high");
+ break;
+ case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-ceased", "RMS detector temperature is too high");
+ break;
+ case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-ceased", "OCXO temperature is too high");
+ break;
+ case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-ceased", "TRX temperature is too high");
+ break;
+ case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-ceased", "PA temperature is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply voltage is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-ceased", "Power supply voltage is too low");
+ break;
+ case S_MGR_VSWR_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-ceased", "VSWR is too high");
+ break;
+ case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply consumption is too high");
+ break;
+ case S_MGR_PWR_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-ceased", "PA power consumption is too high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+void handle_alert_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_alert_actions in state %s, crit_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.crit_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ if (mgr->oc2gbts_ctrl.crit_flags & cause) {
+ switch(cause) {
+ case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Main power supply temperature is too high");
+ break;
+ case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-alert", "SoC temperature is too high");
+ break;
+ case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-alert", "FPGA temperature is too high");
+ break;
+ case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-alert", "RMS detector temperature is too high");
+ break;
+ case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-alert", "OCXO temperature is too high");
+ break;
+ case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-alert", "TRX temperature is too high");
+ break;
+ case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-alert", "PA temperature is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply voltage is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-alert", "Power supply voltage is too low");
+ break;
+ case S_MGR_VSWR_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-alert", "VSWR is too high");
+ break;
+ case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply consumption is too high");
+ break;
+ case S_MGR_PWR_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-alert", "PA power consumption is too high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+void handle_warn_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_warn_actions in state %s, warn_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.warn_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ if (mgr->oc2gbts_ctrl.warn_flags & cause) {
+ switch(cause) {
+ case S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Main power supply temperature is high");
+ break;
+ case S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Main power supply temperature is low");
+ break;
+ case S_MGR_TEMP_SOC_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL, "oc2g-oml-alert", "SoC temperature is high");
+ break;
+ case S_MGR_TEMP_SOC_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL, "oc2g-oml-alert", "SoC temperature is low");
+ break;
+ case S_MGR_TEMP_FPGA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL, "oc2g-oml-alert", "FPGA temperature is high");
+ break;
+ case S_MGR_TEMP_FPGA_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL, "oc2g-oml-alert", "FPGA temperature is low");
+ break;
+ case S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL, "oc2g-oml-alert", "RMS detector temperature is high");
+ break;
+ case S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL, "oc2g-oml-alert", "RMS detector temperature is low");
+ break;
+ case S_MGR_TEMP_OCXO_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL, "oc2g-oml-alert", "OCXO temperature is high");
+ break;
+ case S_MGR_TEMP_OCXO_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL, "oc2g-oml-alert", "OCXO temperature is low");
+ break;
+ case S_MGR_TEMP_TRX_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL, "oc2g-oml-alert", "TRX temperature is high");
+ break;
+ case S_MGR_TEMP_TRX_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL, "oc2g-oml-alert", "TRX temperature is low");
+ break;
+ case S_MGR_TEMP_PA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL, "oc2g-oml-alert", "PA temperature is high");
+ break;
+ case S_MGR_TEMP_PA_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL, "oc2g-oml-alert", "PA temperature is low");
+ break;
+ case S_MGR_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply voltage is high");
+ break;
+ case S_MGR_SUPPLY_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Power supply voltage is low");
+ break;
+ case S_MGR_VSWR_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL, "oc2g-oml-alert", "VSWR is high");
+ break;
+ case S_MGR_PWR_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply consumption is high");
+ break;
+ case S_MGR_PWR_PA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL, "oc2g-oml-alert", "PA power consumption is high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+/**
+ * Go back to normal! Depending on the configuration execute the normal
+ * actions that could (start to) undo everything we did in the other
+ * states. What is still missing is the power increase/decrease depending
+ * on the state. E.g. starting from WARNING_HYST we might want to slowly
+ * ramp up the output power again.
+ */
+static void execute_normal_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n");
+ handle_ceased_actions(manager);
+ handle_normal_actions(manager->state.action_norm);
+}
+
+static void execute_warning_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n");
+ handle_warn_actions(manager);
+ handle_actions(manager->state.action_warn);
+}
+
+/* Preventive timer call-back */
+static void preventive_timer_cb(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ /* Delete current preventive timer if possible */
+ osmo_timer_del(&mgr->alarms.preventive_timer);
+
+ LOGP(DTEMP, LOGL_DEBUG, "Preventive timer expired in %d sec, retry=%d\n",
+ mgr->alarms.preventive_duration,
+ mgr->alarms.preventive_retry);
+
+ /* Turn on PA and clear action flag */
+ if (mgr->state.action_comb & SENSOR_ACT_PA_OFF) {
+ mgr->state.action_comb &= ~SENSOR_ACT_PA_OFF;
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1))
+ LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n");
+ else
+ LOGP(DTEMP, LOGL_DEBUG, "Re-enable PA after preventive timer expired in %d sec\n",
+ mgr->alarms.preventive_duration);
+ }
+ }
+
+ /* restart check sensor timer */
+ osmo_timer_del(&sensor_ctrl_timer);
+ osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
+
+ return;
+
+}
+
+static void execute_preventive_act(struct oc2gbts_mgr_instance *manager)
+{
+ struct oc2gbts_preventive_list *prevent_list, *prevent_list2;
+
+ /* update LED pattern */
+ select_led_pattern(manager);
+
+ /* do nothing if the preventive action list is empty */
+ if (llist_empty(&manager->alarms.list))
+ return;
+
+ llist_for_each_entry_safe(prevent_list, prevent_list2, &manager->alarms.list, list) {
+ /* Delete the timer in list and perform action*/
+ if (prevent_list) {
+ /* Delete current preventive timer if possible */
+ osmo_timer_del(&manager->alarms.preventive_timer);
+
+ /* Start/restart preventive timer */
+ if (prevent_list->param.sleep_sec) {
+ manager->alarms.preventive_timer.cb = preventive_timer_cb;
+ manager->alarms.preventive_timer.data = manager;
+ osmo_timer_schedule(&manager->alarms.preventive_timer, prevent_list->param.sleep_sec, 0);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Preventive timer scheduled for %d sec, preventive flags=0x%x\n",
+ prevent_list->param.sleep_sec,
+ prevent_list->action_flag);
+ }
+ /* Update active flags */
+ manager->state.action_comb |= prevent_list->action_flag;
+
+ /* Turn off PA */
+ if (manager->state.action_comb & SENSOR_ACT_PA_OFF) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0))
+ LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA\n");
+ }
+
+ /* Delete this preventive entry */
+ llist_del(&prevent_list->list);
+ talloc_free(prevent_list);
+ LOGP(DTEMP, LOGL_DEBUG,"Deleted preventive entry from list, entries left=%d\n",
+ llist_count(&manager->alarms.list));
+
+ /* stay in last state is preventive active has exceed maximum number of retries */
+ if (manager->alarms.preventive_retry > OC2GBTS_PREVENT_RETRY)
+ LOGP(DTEMP, LOGL_NOTICE, "Maximum number of preventive active exceed\n");
+ else
+ /* increase retry counter */
+ manager->alarms.preventive_retry++;
+ }
+ }
+ return;
+}
+
+static void execute_critical_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_alert_actions(manager);
+ handle_actions(manager->state.action_crit);
+
+}
+
+static void oc2gbts_mgr_sensor_handle(struct oc2gbts_mgr_instance *manager,
+ int critical, int warning)
+{
+ int new_state = next_state(manager->state.state, critical, warning);
+
+ /* run preventive action if it is possible */
+ execute_preventive_act(manager);
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+ LOGP(DTEMP, LOGL_INFO, "Moving from state %s to %s.\n",
+ get_value_string(state_names, manager->state.state),
+ get_value_string(state_names, new_state));
+ manager->state.state = new_state;
+ switch (manager->state.state) {
+ case STATE_NORMAL:
+ execute_normal_act(manager);
+ /* reset alarms */
+ manager->alarms.temp_high = 0;
+ manager->alarms.temp_max = 0;
+ manager->alarms.vswr_high = 0;
+ manager->alarms.vswr_max = 0;
+ manager->alarms.supply_low = 0;
+ manager->alarms.supply_min = 0;
+ manager->alarms.supply_pwr_high = 0;
+ manager->alarms.supply_pwr_max = 0;
+ manager->alarms.pa_pwr_max = 0;
+ manager->alarms.pa_pwr_high = 0;
+ manager->state.action_comb = 0;
+ manager->alarms.preventive_retry = 0;
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ };
+}
+
+static void schedule_preventive_action(struct oc2gbts_mgr_instance *mgr, int action, int duration)
+{
+ struct oc2gbts_preventive_list *prevent_list;
+
+ /* add to pending list */
+ prevent_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_preventive_list);
+ if (prevent_list) {
+ prevent_list->action_flag = action;
+ prevent_list->param.sleep_sec = duration;
+ prevent_list->param.sleep_usec = 0;
+ llist_add_tail(&prevent_list->list, &mgr->alarms.list);
+ LOGP(DTEMP, LOGL_DEBUG,"Added preventive action to list, duration=%d sec, total entries=%d\n",
+ prevent_list->param.sleep_sec,
+ llist_count(&mgr->alarms.list));
+ }
+ return;
+}
+
+static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+ int temp, volt, vswr, power = 0;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+ int action = 0;
+
+ LOGP(DTEMP, LOGL_INFO, "Going to check the temperature.\n");
+
+ /* Read the current supply temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the supply temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.supply_temp_limit.thresh_warn_min){
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is under %d\n", mgr->temp.supply_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Supply temperature is: %d\n", temp);
+ }
+
+ /* Read the current SoC temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the SoC temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.soc_temp_limit.thresh_warn_min){
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is under %d\n", mgr->temp.soc_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SOC_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SOC_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "SoC temperature is: %d\n", temp);
+ }
+
+ /* Read the current fpga temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the fpga temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.fpga_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is under %d\n", mgr->temp.fpga_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_FPGA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "FPGA temperature is: %d\n", temp);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ /* Read the current RMS detector temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the RMS detector temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.rmsdet_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is under %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "RMS detector temperature is: %d\n", temp);
+ }
+ }
+
+ /* Read the current OCXO temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the OCXO temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.ocxo_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is under %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_OCXO_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "OCXO temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the TX temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.tx_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.tx_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is under %d\n", mgr->temp.tx_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.tx_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_TRX_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_TRX_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "TX temperature is: %d\n", temp);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ /* Read the current PA temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the PA temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.pa_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.pa_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is under %d\n", mgr->temp.pa_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.pa_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_PA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "PA temperature is: %d\n", temp);
+ }
+ }
+
+ /* Read the current main supply voltage */
+ if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_VOLTAGE, &volt);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the main supply voltage. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ if (volt > mgr->volt.supply_volt_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MAX_ALARM;
+ }
+ if (volt < mgr->volt.supply_volt_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->alarms.supply_low = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MIN_ALARM;
+ }
+ if (volt > mgr->volt.supply_volt_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MAX_ALARM;
+ }
+
+ if (volt < mgr->volt.supply_volt_limit.thresh_crit_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_crit_min);
+ crit_thresh_passed = 1;
+ mgr->alarms.supply_min = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MIN_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MIN_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_NONE);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Main supply voltage is: %d\n", volt);
+ }
+ }
+
+ /* Read the main supply power consumption */
+ if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_POWER, &power);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the power supply current. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ power /= 1000000;
+ if (power > mgr->pwr.supply_pwr_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.supply_pwr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
+ }
+ if (power > mgr->pwr.supply_pwr_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
+
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ mgr->alarms.supply_pwr_max = 1;
+ /* schedule to turn off PA */
+ action = SENSOR_ACT_PA_OFF;
+ /* repeat same alarm to BSC */
+ handle_alert_actions(mgr);
+ }
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Main supply current power consumption is: %d\n", power);
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ /* Read the current PA power consumption */
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, OC2GBTS_POWER_POWER, &power);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the PA power. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ power /= 1000000;
+ if (power > mgr->pwr.pa_pwr_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.pa_pwr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_PA_WARN_MAX_ALARM;
+ }
+ if (power > mgr->pwr.pa_pwr_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.pa_pwr_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_PA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "PA power consumption is: %d\n", power);
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_PA_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ /* Read the current VSWR of powered ON PA*/
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the VSWR. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ if ((vswr > mgr->vswr.vswr_limit.thresh_warn_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_warn_max)) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.vswr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_VSWR_WARN_MAX_ALARM;
+ }
+ if ((vswr > mgr->vswr.vswr_limit.thresh_crit_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_crit_max)) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.vswr_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_VSWR_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_VSWR_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "VSWR is: current = %d, last = %d\n", vswr, mgr->vswr.last_vswr);
+
+ /* update last VSWR */
+ mgr->vswr.last_vswr = vswr;
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_VSWR_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+ }
+
+ select_led_pattern(mgr);
+ oc2gbts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed);
+}
+
+static void sensor_ctrl_check_cb(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+ sensor_ctrl_check(mgr);
+ /* Check every minute? XXX make it configurable! */
+ osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
+ LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n");
+ /* TODO: do we want to notify if some sensors could not be read? */
+ oc2gbts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR);
+}
+
+int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc = 0;
+
+ /* always enable PA GPIO for OC-2G */
+ if (!oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
+ if (!rc)
+ LOGP(DTEMP, LOGL_ERROR, "Failed to set GPIO for internal PA\n");
+ }
+
+ s_mgr = mgr;
+ sensor_ctrl_timer.cb = sensor_ctrl_check_cb;
+ sensor_ctrl_timer.data = s_mgr;
+ sensor_ctrl_check_cb(s_mgr);
+ return rc;
+}
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
new file mode 100644
index 00000000..ef527394
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
@@ -0,0 +1,984 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_vty.c
+ * (C) 2014 by oc2gcom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@oc2gcom.de>
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "oc2gbts_misc.h"
+#include "oc2gbts_mgr.h"
+#include "oc2gbts_temp.h"
+#include "oc2gbts_power.h"
+#include "oc2gbts_bid.h"
+#include "oc2gbts_led.h"
+#include "btsconfig.h"
+
+static struct oc2gbts_mgr_instance *s_mgr;
+
+static const char copyright[] =
+ "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
+ "(C) 2014 by Holger Hans Peter Freyther\r\n"
+ "(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n"
+ "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static int go_to_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case MGR_NODE:
+ vty->node = CONFIG_NODE;
+ break;
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_SUPPLY_TEMP_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_RMSDET_NODE:
+ case LIMIT_OCXO_NODE:
+ case LIMIT_TX_TEMP_NODE:
+ case LIMIT_PA_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA_PWR_NODE:
+ vty->node = MGR_NODE;
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ return vty->node;
+}
+
+static int is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case MGR_NODE:
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_SUPPLY_TEMP_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_RMSDET_NODE:
+ case LIMIT_OCXO_NODE:
+ case LIMIT_TX_TEMP_NODE:
+ case LIMIT_PA_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA_PWR_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "oc2gbts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure oc2gbts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(oc2gbts-mgr)# ",
+ 1,
+};
+
+static struct cmd_node act_norm_node = {
+ ACT_NORM_NODE,
+ "%s(actions-normal)# ",
+ 1,
+};
+
+static struct cmd_node act_warn_node = {
+ ACT_WARN_NODE,
+ "%s(actions-warn)# ",
+ 1,
+};
+
+static struct cmd_node act_crit_node = {
+ ACT_CRIT_NODE,
+ "%s(actions-critical)# ",
+ 1,
+};
+
+static struct cmd_node limit_supply_temp_node = {
+ LIMIT_SUPPLY_TEMP_NODE,
+ "%s(limit-supply-temp)# ",
+ 1,
+};
+
+static struct cmd_node limit_soc_node = {
+ LIMIT_SOC_NODE,
+ "%s(limit-soc)# ",
+ 1,
+};
+
+static struct cmd_node limit_fpga_node = {
+ LIMIT_FPGA_NODE,
+ "%s(limit-fpga)# ",
+ 1,
+};
+
+static struct cmd_node limit_rmsdet_node = {
+ LIMIT_RMSDET_NODE,
+ "%s(limit-rmsdet)# ",
+ 1,
+};
+
+static struct cmd_node limit_ocxo_node = {
+ LIMIT_OCXO_NODE,
+ "%s(limit-ocxo)# ",
+ 1,
+};
+
+static struct cmd_node limit_tx_temp_node = {
+ LIMIT_TX_TEMP_NODE,
+ "%s(limit-tx-temp)# ",
+ 1,
+};
+static struct cmd_node limit_pa_temp_node = {
+ LIMIT_PA_TEMP_NODE,
+ "%s(limit-pa-temp)# ",
+ 1,
+};
+static struct cmd_node limit_supply_volt_node = {
+ LIMIT_SUPPLY_VOLT_NODE,
+ "%s(limit-supply-volt)# ",
+ 1,
+};
+static struct cmd_node limit_vswr_node = {
+ LIMIT_VSWR_NODE,
+ "%s(limit-vswr)# ",
+ 1,
+};
+static struct cmd_node limit_supply_pwr_node = {
+ LIMIT_SUPPLY_PWR_NODE,
+ "%s(limit-supply-pwr)# ",
+ 1,
+};
+static struct cmd_node limit_pa_pwr_node = {
+ LIMIT_PA_PWR_NODE,
+ "%s(limit-pa-pwr)# ",
+ 1,
+};
+
+static struct cmd_node limit_gps_fix_node = {
+ LIMIT_GPS_FIX_NODE,
+ "%s(limit-gps-fix)# ",
+ 1,
+};
+
+DEFUN(cfg_mgr, cfg_mgr_cmd,
+ "oc2gbts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_volt_limit(struct vty *vty, const char *name,
+ struct oc2gbts_volt_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning min %d%s",
+ limit->thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " threshold critical min %d%s",
+ limit->thresh_crit_min, VTY_NEWLINE);
+}
+
+static void write_vswr_limit(struct vty *vty, const char *name,
+ struct oc2gbts_vswr_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning max %d%s",
+ limit->thresh_warn_max, VTY_NEWLINE);
+}
+
+static void write_pwr_limit(struct vty *vty, const char *name,
+ struct oc2gbts_pwr_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning max %d%s",
+ limit->thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " threshold critical max %d%s",
+ limit->thresh_crit_max, VTY_NEWLINE);
+}
+
+static void write_norm_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa-on%s",
+ (actions & SENSOR_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-on%s",
+ (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
+}
+
+static void write_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa-off%s",
+ (actions & SENSOR_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-off%s",
+ (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
+}
+
+static int config_write_mgr(struct vty *vty)
+{
+ vty_out(vty, "oc2gbts-mgr%s", VTY_NEWLINE);
+
+ write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit);
+ write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit);
+ write_vswr_limit(vty, "limits vswr", &s_mgr->vswr.vswr_limit);
+
+ write_norm_action(vty, "actions normal", s_mgr->state.action_norm);
+ write_action(vty, "actions warn", s_mgr->state.action_warn);
+ write_action(vty, "actions critical", s_mgr->state.action_crit);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->temp.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit)
+CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit)
+CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit)
+CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit)
+CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit)
+CFG_LIMIT_TEMP(tx_temp, "TX TEMP\n", LIMIT_TX_TEMP_NODE, tx_temp_limit)
+CFG_LIMIT_TEMP(pa_temp, "PA TEMP\n", LIMIT_PA_TEMP_NODE, pa_temp_limit)
+#undef CFG_LIMIT_TEMP
+
+#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->volt.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit)
+#undef CFG_LIMIT_VOLT
+
+#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->vswr.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_VSWR(vswr, "VSWR\n", LIMIT_VSWR_NODE, vswr_limit)
+#undef CFG_LIMIT_VSWR
+
+#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->pwr.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit)
+CFG_LIMIT_PWR(pa_pwr, "PA PWR\n", LIMIT_PA_PWR_NODE, pa_pwr_limit)
+#undef CFG_LIMIT_PWR
+
+#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->gps.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit)
+#undef CFG_LIMIT_GPS_FIX
+
+DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd,
+ "threshold warning min <0-48000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_volt_limit *limit = vty->index;
+ limit->thresh_warn_min = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd,
+ "threshold critical min <0-48000>",
+ "Threshold to reach\n" "Critical level\n" "Range\n")
+{
+ struct oc2gbts_volt_limit *limit = vty->index;
+ limit->thresh_crit_min = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd,
+ "threshold warning max <1000-200000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_vswr_limit *limit = vty->index;
+ limit->thresh_warn_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd,
+ "threshold critical max <1000-200000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_vswr_limit *limit = vty->index;
+ limit->thresh_crit_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd,
+ "threshold warning max <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_pwr_limit *limit = vty->index;
+ limit->thresh_warn_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd,
+ "threshold critical max <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_pwr_limit *limit = vty->index;
+ limit->thresh_crit_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define CFG_ACTION(name, expl, switch_to, variable) \
+DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
+ "actions " #name, \
+ "Configure Actions\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->state.variable; \
+ return CMD_SUCCESS; \
+}
+CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
+CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
+CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
+#undef CFG_ACTION
+
+DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd,
+ "pa-on",
+ "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd,
+ "no pa-on",
+ NO_STR "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
+ "bts-service-on",
+ "Start the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
+ "no bts-service-on",
+ NO_STR "Start the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd,
+ "pa-off",
+ "Switch the Power Amplifier off\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd,
+ "no pa-off",
+ NO_STR "Do not switch off the Power Amplifier\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
+ "no bts-service-off",
+ NO_STR "Stop the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mgr, show_mgr_cmd, "show manager",
+ SHOW_STR "Display information about the manager")
+{
+ int temp, volt, current, power, vswr;
+ vty_out(vty, "Warning alarm flags: 0x%08x%s",
+ s_mgr->oc2gbts_ctrl.warn_flags, VTY_NEWLINE);
+ vty_out(vty, "Critical alarm flags: 0x%08x%s",
+ s_mgr->oc2gbts_ctrl.crit_flags, VTY_NEWLINE);
+ vty_out(vty, "Preventive action retried: %d%s",
+ s_mgr->alarms.preventive_retry, VTY_NEWLINE);
+ vty_out(vty, "Temperature control state: %s%s",
+ oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
+ vty_out(vty, " Main Supply : %4.2f Celcius%s",
+ temp/ 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
+ vty_out(vty, " SoC : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
+ vty_out(vty, " FPGA : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
+ vty_out(vty, " RMSDet : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ }
+ oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
+ vty_out(vty, " OCXO : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
+ vty_out(vty, " TX : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
+ vty_out(vty, " Power Amp : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ }
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_VOLTAGE, &volt);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_CURRENT, &current);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_POWER, &power);
+ vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_VOLTAGE, &volt);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_CURRENT, &current);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_POWER, &power);
+ vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ oc2gbts_power_get(OC2GBTS_POWER_PA) ? "ON " : "OFF",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ }
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, "VSWR Status%s", VTY_NEWLINE);
+ oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
+ vty_out(vty, " VSWR : %f %s",
+ vswr / 1000.0f,
+ VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_thresh, show_thresh_cmd, "show thresholds",
+ SHOW_STR "Display information about the thresholds")
+{
+ vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE);
+ vty_out(vty, " Main supply%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " SoC%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " FPGA%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, " RMSDet%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ }
+ vty_out(vty, " OCXO%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " TX%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ vty_out(vty, " PA%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ }
+ vty_out(vty, "Power limits%s", VTY_NEWLINE);
+ vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE);
+ vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ vty_out(vty, " PA power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ }
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, "VSWR limits%s", VTY_NEWLINE);
+ vty_out(vty, " TX%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->vswr.vswr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->vswr.vswr_limit.thresh_warn_max, VTY_NEWLINE);
+ }
+ vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(calibrate_clock, calibrate_clock_cmd,
+ "calibrate clock",
+ "Calibration commands\n"
+ "Calibrate clock against GPS PPS\n")
+{
+ if (oc2gbts_mgr_calib_run(s_mgr) < 0) {
+ vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_led_pattern, set_led_pattern_cmd,
+ "set led pattern <0-255>",
+ "Set LED pattern\n"
+ "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n")
+{
+ int pattern_id = atoi(argv[0]);
+
+ if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) {
+ vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ led_set(s_mgr, pattern_id);
+ return CMD_SUCCESS;
+}
+
+DEFUN(force_mgr_state, force_mgr_state_cmd,
+ "force manager state <0-255>",
+ "Force BTS manager state\n"
+ "Force BTS manager state for debugging purpose only\n")
+{
+ int state = atoi(argv[0]);
+
+ if ((state < 0) || (state > STATE_CRITICAL)) {
+ vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ s_mgr->state.state = state;
+ return CMD_SUCCESS;
+}
+
+#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \
+ "limit temp " #name " " #criticity " " #min_max " <-200-200>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->temp.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_min, warning, min)
+#undef LIMIT_TEMP
+
+#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \
+ "limit " #name " " #criticity " " #min_max " <0-48000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->volt.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min)
+#undef LIMIT_VOLT
+
+#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \
+ "limit power " #name " " #criticity " " #min_max " <0-200>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->pwr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max)
+LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_PWR
+
+#define LIMIT_VSWR(limit, expl, variable, criticity, min_max) \
+DEFUN(limit_vswr_##variable, limit_vswr_##variable##_cmd, \
+ "limit vswr " #criticity " " #min_max " <1000-200000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->vswr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_warn_max, warning, max)
+LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_VSWR
+
+#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \
+DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \
+ "limit gpsfix " #criticity " " #min_max " <0-365>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->gps.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max)
+#undef LIMIT_GPSFIX
+
+static void register_limit(int limit, uint32_t unit)
+{
+ switch (unit) {
+ case MGR_LIMIT_TYPE_VOLT:
+ install_element(limit, &cfg_thresh_volt_warn_min_cmd);
+ install_element(limit, &cfg_thresh_volt_crit_min_cmd);
+ break;
+ case MGR_LIMIT_TYPE_VSWR:
+ install_element(limit, &cfg_thresh_vswr_warn_max_cmd);
+ install_element(limit, &cfg_thresh_vswr_crit_max_cmd);
+ break;
+ case MGR_LIMIT_TYPE_PWR:
+ install_element(limit, &cfg_thresh_pwr_warn_max_cmd);
+ install_element(limit, &cfg_thresh_pwr_crit_max_cmd);
+ break;
+ default:
+ break;
+ }
+}
+
+static void register_normal_action(int act)
+{
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(act, &cfg_action_pa_on_cmd);
+ install_element(act, &cfg_no_action_pa_on_cmd);
+ }
+ install_element(act, &cfg_action_bts_srv_on_cmd);
+ install_element(act, &cfg_no_action_bts_srv_on_cmd);
+}
+
+static void register_action(int act)
+{
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(act, &cfg_action_pa_off_cmd);
+ install_element(act, &cfg_no_action_pa_off_cmd);
+ }
+ install_element(act, &cfg_action_bts_srv_off_cmd);
+ install_element(act, &cfg_no_action_bts_srv_off_cmd);
+}
+
+static void register_hidden_commands()
+{
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_min_cmd);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_min_cmd);
+ }
+
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd);
+
+ install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(ENABLE_NODE, &limit_pwr_pa_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa_thresh_crit_max_cmd);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_element(ENABLE_NODE, &limit_vswr_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_vswr_thresh_crit_max_cmd);
+ }
+
+ install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd);
+}
+
+int oc2gbts_mgr_vty_init(void)
+{
+ vty_init(&vty_info);
+
+ install_element_ve(&show_mgr_cmd);
+ install_element_ve(&show_thresh_cmd);
+
+ install_element(ENABLE_NODE, &calibrate_clock_cmd);
+
+ install_node(&mgr_node, config_write_mgr);
+ install_element(CONFIG_NODE, &cfg_mgr_cmd);
+ vty_install_default(MGR_NODE);
+
+ /* install the limit nodes */
+ install_node(&limit_supply_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_temp_cmd);
+ vty_install_default(LIMIT_SUPPLY_TEMP_NODE);
+
+ install_node(&limit_soc_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_soc_temp_cmd);
+ vty_install_default(LIMIT_SOC_NODE);
+
+ install_node(&limit_fpga_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd);
+ vty_install_default(LIMIT_FPGA_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_node(&limit_rmsdet_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd);
+ vty_install_default(LIMIT_RMSDET_NODE);
+ }
+
+ install_node(&limit_ocxo_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd);
+ vty_install_default(LIMIT_OCXO_NODE);
+
+ install_node(&limit_tx_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx_temp_cmd);
+ vty_install_default(LIMIT_TX_TEMP_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ install_node(&limit_pa_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_temp_cmd);
+ vty_install_default(LIMIT_PA_TEMP_NODE);
+ }
+
+ install_node(&limit_supply_volt_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_volt_cmd);
+ register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT);
+ vty_install_default(LIMIT_SUPPLY_VOLT_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_node(&limit_vswr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_vswr_cmd);
+ register_limit(LIMIT_VSWR_NODE, MGR_LIMIT_TYPE_VSWR);
+ vty_install_default(LIMIT_VSWR_NODE);
+ }
+
+ install_node(&limit_supply_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd);
+ register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR);
+ vty_install_default(LIMIT_SUPPLY_PWR_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_node(&limit_pa_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_pwr_cmd);
+ vty_install_default(LIMIT_PA_PWR_NODE);
+ }
+
+ install_node(&limit_gps_fix_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_gps_fix_cmd);
+ vty_install_default(LIMIT_GPS_FIX_NODE);
+
+ /* install the normal node */
+ install_node(&act_norm_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_normal_cmd);
+ register_normal_action(ACT_NORM_NODE);
+
+ /* install the warning and critical node */
+ install_node(&act_warn_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_warn_cmd);
+ register_action(ACT_WARN_NODE);
+ vty_install_default(ACT_WARN_NODE);
+
+ install_node(&act_crit_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_critical_cmd);
+ register_action(ACT_CRIT_NODE);
+ vty_install_default(ACT_CRIT_NODE);
+
+ /* install LED pattern command for debugging purpose */
+ install_element_ve(&set_led_pattern_cmd);
+ install_element_ve(&force_mgr_state_cmd);
+
+ register_hidden_commands();
+
+ return 0;
+}
+
+int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *manager)
+{
+ int rc;
+
+ s_mgr = manager;
+ rc = vty_read_config_file(s_mgr->config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ s_mgr->config_file);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
new file mode 100644
index 00000000..bacf07bd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
@@ -0,0 +1,381 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include "oc2gbts_mgr.h"
+#include "btsconfig.h"
+#include "oc2gbts_misc.h"
+#include "oc2gbts_par.h"
+#include "oc2gbts_temp.h"
+#include "oc2gbts_power.h"
+#include "oc2gbts_bid.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_temp_sensor sensor;
+ enum oc2gbts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "supply_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_SUPPLY,
+ .ee_par = OC2GBTS_PAR_TEMP_SUPPLY_MAX,
+ }, {
+ .name = "soc_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_SOC,
+ .ee_par = OC2GBTS_PAR_TEMP_SOC_MAX,
+ }, {
+ .name = "fpga_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_FPGA,
+ .ee_par = OC2GBTS_PAR_TEMP_FPGA_MAX,
+
+ }, {
+ .name = "rmsdet_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_RMSDET,
+ .ee_par = OC2GBTS_PAR_TEMP_RMSDET_MAX,
+ }, {
+ .name = "ocxo_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_OCXO,
+ .ee_par = OC2GBTS_PAR_TEMP_OCXO_MAX,
+ }, {
+ .name = "tx_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_TX,
+ .ee_par = OC2GBTS_PAR_TEMP_TX_MAX,
+ }, {
+ .name = "pa_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_PA,
+ .ee_par = OC2GBTS_PAR_TEMP_PA_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_power_source sensor_source;
+ enum oc2gbts_power_type sensor_type;
+ enum oc2gbts_par ee_par;
+} power_data[] = {
+ {
+ .name = "supply_volt",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_SUPPLY,
+ .sensor_type = OC2GBTS_POWER_VOLTAGE,
+ .ee_par = OC2GBTS_PAR_VOLT_SUPPLY_MAX,
+ }, {
+ .name = "supply_pwr",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_SUPPLY,
+ .sensor_type = OC2GBTS_POWER_POWER,
+ .ee_par = OC2GBTS_PAR_PWR_SUPPLY_MAX,
+ }, {
+ .name = "pa_pwr",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_PA,
+ .sensor_type = OC2GBTS_POWER_POWER,
+ .ee_par = OC2GBTS_PAR_PWR_PA_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_vswr_sensor sensor;
+ enum oc2gbts_par ee_par;
+} vswr_data[] = {
+ {
+ .name = "vswr",
+ .has_max = 0,
+ .sensor = OC2GBTS_VSWR,
+ .ee_par = OC2GBTS_PAR_VSWR_MAX,
+ }
+};
+
+static const struct value_string power_unit_strs[] = {
+ { OC2GBTS_POWER_POWER, "W" },
+ { OC2GBTS_POWER_VOLTAGE, "V" },
+ { 0, NULL }
+};
+
+void oc2gbts_check_temp(int no_rom_write)
+{
+ int temp_old[ARRAY_SIZE(temp_data)];
+ int temp_cur[ARRAY_SIZE(temp_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
+ int ret = -99;
+
+ if (temp_data[i].sensor == OC2GBTS_TEMP_PA &&
+ !oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP))
+ continue;
+
+ rc = oc2gbts_par_get_int(temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+
+ oc2gbts_temp_get(temp_data[i].sensor, &temp_cur[i]);
+ if (temp_cur[i] < 0 && temp_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d)\n", temp_data[i].sensor);
+ continue;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
+ temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
+
+ if (temp_cur[i] > temp_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "temperature: %d.%d C\n", temp_data[i].name,
+ temp_cur[i]/1000, temp_old[i]%1000);
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(temp_data[i].ee_par,
+ temp_cur[i]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max temp %d (%s)\n", temp_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+void oc2gbts_check_power(int no_rom_write)
+{
+ int power_old[ARRAY_SIZE(power_data)];
+ int power_cur[ARRAY_SIZE(power_data)];
+ int i, rc;
+ int div_ratio;
+
+ for (i = 0; i < ARRAY_SIZE(power_data); i++) {
+ int ret = 0;
+
+ if (power_data[i].sensor_source == OC2GBTS_POWER_PA &&
+ !oc2gbts_option_get(OC2GBTS_OPTION_PA))
+ continue;
+
+ rc = oc2gbts_par_get_int(power_data[i].ee_par, &ret);
+ switch(power_data[i].sensor_type) {
+ case OC2GBTS_POWER_VOLTAGE:
+ div_ratio = 1000;
+ break;
+ case OC2GBTS_POWER_POWER:
+ div_ratio = 1000000;
+ break;
+ default:
+ div_ratio = 1000;
+ }
+ power_old[i] = ret * div_ratio;
+
+ oc2gbts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]);
+ if (power_cur[i] < 0 && power_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, power_data[i].sensor_type);
+ continue;
+ }
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n",
+ power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio,
+ get_value_string(power_unit_strs, power_data[i].sensor_type));
+
+ if (power_cur[i] > power_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "power: %d.%d %s\n", power_data[i].name,
+ power_cur[i]/div_ratio, power_cur[i]%div_ratio,
+ get_value_string(power_unit_strs, power_data[i].sensor_type));
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(power_data[i].ee_par,
+ power_cur[i]/div_ratio);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max power %d (%s)\n", power_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+void oc2gbts_check_vswr(int no_rom_write)
+{
+ int vswr_old[ARRAY_SIZE(vswr_data)];
+ int vswr_cur[ARRAY_SIZE(vswr_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(vswr_data); i++) {
+ int ret = 0;
+
+ if (vswr_data[i].sensor == OC2GBTS_VSWR &&
+ (!oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ !oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)))
+ continue;
+
+ rc = oc2gbts_par_get_int(vswr_data[i].ee_par, &ret);
+ vswr_old[i] = ret * 1000;
+
+ oc2gbts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]);
+ if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor);
+ continue;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n",
+ vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000);
+
+ if (vswr_cur[i] > vswr_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "vswr: %d.%d C\n", vswr_data[i].name,
+ vswr_cur[i]/1000, vswr_old[i]%1000);
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(vswr_data[i].ee_par,
+ vswr_cur[i]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max vswr %d (%s)\n", vswr_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+/*********************************************************************
+ * Hours handling
+ *********************************************************************/
+static time_t last_update;
+
+int oc2gbts_update_hours(int no_rom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs = 0;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ /* create a new file anyway */
+ if (!no_rom_write)
+ rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
+
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ /* number of hours to increase */
+ op_hrs += (now-last_update)/3600;
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+static const char *fw_sysfs[_NUM_FW] = {
+ [OC2GBTS_FW_DSP] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+};
+
+int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type)
+{
+ int fd;
+ int rc;
+
+ switch (type) {
+ case OC2GBTS_FW_DSP:
+ fd = open(fw_sysfs[type], O_WRONLY);
+ if (fd < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
+ fw_sysfs[type], strerror(errno));
+ close(fd);
+ return fd;
+ }
+ rc = write(fd, "restart", 8);
+ if (rc < 8) {
+ LOGP(DFW, LOGL_ERROR, "short write during "
+ "fw write to %s\n", fw_sysfs[type]);
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.h b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h
new file mode 100644
index 00000000..78315679
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h
@@ -0,0 +1,17 @@
+#ifndef _OC2GBTS_MISC_H
+#define _OC2GBTS_MISC_H
+
+#include <stdint.h>
+
+void oc2gbts_check_temp(int no_rom_write);
+void oc2gbts_check_power(int no_rom_write);
+void oc2gbts_check_vswr(int no_rom_write);
+
+int oc2gbts_update_hours(int no_rom_write);
+
+enum oc2gbts_firmware_type {
+ OC2GBTS_FW_DSP,
+ _NUM_FW
+};
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
new file mode 100644
index 00000000..39f64aae
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
@@ -0,0 +1,123 @@
+/* Helper for netlink */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_nl.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+
+#include <sys/socket.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/**
+ * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source
+ * address will be used when sending a message this function can be used.
+ * It will ask the routing code of the kernel for the PREFSRC
+ */
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source)
+{
+ int fd, rc;
+ struct rtmsg *r;
+ struct rtattr *rta;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (fd < 0) {
+ perror("nl socket");
+ return -1;
+ }
+
+ /* Send a rtmsg and ask for a response */
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.n.nlmsg_seq = 1;
+
+ /* Prepare the routing request */
+ req.r.rtm_family = AF_INET;
+
+ /* set the dest */
+ rta = NLMSG_TAIL(&req.n);
+ rta->rta_type = RTA_DST;
+ rta->rta_len = RTA_LENGTH(sizeof(*dest));
+ memcpy(RTA_DATA(rta), dest, sizeof(*dest));
+
+ /* update sizes for dest */
+ req.r.rtm_dst_len = sizeof(*dest) * 8;
+ req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
+
+ rc = send(fd, &req, req.n.nlmsg_len, 0);
+ if (rc != req.n.nlmsg_len) {
+ perror("short write");
+ close(fd);
+ return -2;
+ }
+
+
+ /* now receive a response and parse it */
+ rc = recv(fd, &req, sizeof(req), 0);
+ if (rc <= 0) {
+ perror("short read");
+ close(fd);
+ return -3;
+ }
+
+ if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) {
+ close(fd);
+ return -4;
+ }
+
+ r = NLMSG_DATA(&req.n);
+ rc -= NLMSG_LENGTH(sizeof(*r));
+ rta = RTM_RTA(r);
+ while (RTA_OK(rta, rc)) {
+ if (rta->rta_type != RTA_PREFSRC) {
+ rta = RTA_NEXT(rta, rc);
+ continue;
+ }
+
+ /* we are done */
+ memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+ return -5;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
new file mode 100644
index 00000000..340cf117
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
@@ -0,0 +1,27 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_nl.h
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+struct in_addr;
+
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source);
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
new file mode 100644
index 00000000..f3550243
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
@@ -0,0 +1,249 @@
+/* oc2gbts - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_par.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+
+#include "oc2gbts_par.h"
+
+const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1] = {
+ { OC2GBTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
+ { OC2GBTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
+ { OC2GBTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
+ { OC2GBTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" },
+ { OC2GBTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" },
+ { OC2GBTS_PAR_TEMP_TX_MAX, "temp-tx-max" },
+ { OC2GBTS_PAR_TEMP_PA_MAX, "temp-pa-max" },
+ { OC2GBTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" },
+ { OC2GBTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" },
+ { OC2GBTS_PAR_PWR_PA_MAX, "pwr-pa-max" },
+ { OC2GBTS_PAR_VSWR_MAX, "vswr-max" },
+ { OC2GBTS_PAR_GPS_FIX, "gps-fix" },
+ { OC2GBTS_PAR_SERNR, "serial-nr" },
+ { OC2GBTS_PAR_HOURS, "hours-running" },
+ { OC2GBTS_PAR_BOOTS, "boot-count" },
+ { OC2GBTS_PAR_KEY, "key" },
+ { 0, NULL }
+};
+
+int oc2gbts_par_is_int(enum oc2gbts_par par)
+{
+ switch (par) {
+ case OC2GBTS_PAR_TEMP_SUPPLY_MAX:
+ case OC2GBTS_PAR_TEMP_SOC_MAX:
+ case OC2GBTS_PAR_TEMP_FPGA_MAX:
+ case OC2GBTS_PAR_TEMP_RMSDET_MAX:
+ case OC2GBTS_PAR_TEMP_OCXO_MAX:
+ case OC2GBTS_PAR_TEMP_TX_MAX:
+ case OC2GBTS_PAR_TEMP_PA_MAX:
+ case OC2GBTS_PAR_VOLT_SUPPLY_MAX:
+ case OC2GBTS_PAR_VSWR_MAX:
+ case OC2GBTS_PAR_SERNR:
+ case OC2GBTS_PAR_HOURS:
+ case OC2GBTS_PAR_BOOTS:
+ case OC2GBTS_PAR_PWR_SUPPLY_MAX:
+ case OC2GBTS_PAR_PWR_PA_MAX:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+FILE *oc2gbts_par_get_path(void *ctx, enum oc2gbts_par par, const char* mode)
+{
+ char *fpath;
+ FILE *fp;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return NULL;
+
+ fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, mode);
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ return fp;
+}
+
+int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+ return 0;
+}
+
+int oc2gbts_par_set_int(enum oc2gbts_par par, int val)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+ return 0;
+}
+
+int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "rb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fread(buf, 1, size, fp);
+
+ fclose(fp);
+
+ return rc;
+}
+
+int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "wb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fwrite(buf, 1, size, fp);
+
+ fsync(fp);
+ fclose(fp);
+
+ return rc;
+}
+
+int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret)
+{
+ FILE *fp;
+ int rc;
+
+ fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%ld", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+int oc2gbts_par_set_gps_fix(void *ctx, time_t val)
+{
+ FILE *fp;
+ int rc;
+
+ fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%ld", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.h b/src/osmo-bts-oc2g/misc/oc2gbts_par.h
new file mode 100644
index 00000000..588a3c32
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.h
@@ -0,0 +1,43 @@
+#ifndef _OC2GBTS_PAR_H
+#define _OC2GBTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+#define FACTORY_ROM_PATH "/mnt/rom/factory"
+#define USER_ROM_PATH "/var/run/oc2gbts-mgr"
+#define UPTIME_TMP_PATH "/tmp/uptime"
+
+enum oc2gbts_par {
+ OC2GBTS_PAR_TEMP_SUPPLY_MAX,
+ OC2GBTS_PAR_TEMP_SOC_MAX,
+ OC2GBTS_PAR_TEMP_FPGA_MAX,
+ OC2GBTS_PAR_TEMP_RMSDET_MAX,
+ OC2GBTS_PAR_TEMP_OCXO_MAX,
+ OC2GBTS_PAR_TEMP_TX_MAX,
+ OC2GBTS_PAR_TEMP_PA_MAX,
+ OC2GBTS_PAR_VOLT_SUPPLY_MAX,
+ OC2GBTS_PAR_PWR_SUPPLY_MAX,
+ OC2GBTS_PAR_PWR_PA_MAX,
+ OC2GBTS_PAR_VSWR_MAX,
+ OC2GBTS_PAR_GPS_FIX,
+ OC2GBTS_PAR_SERNR,
+ OC2GBTS_PAR_HOURS,
+ OC2GBTS_PAR_BOOTS,
+ OC2GBTS_PAR_KEY,
+ _NUM_OC2GBTS_PAR
+};
+
+extern const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1];
+
+int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret);
+int oc2gbts_par_set_int(enum oc2gbts_par par, int val);
+int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
+ unsigned int size);
+int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
+ unsigned int size);
+
+int oc2gbts_par_is_int(enum oc2gbts_par par);
+int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret);
+int oc2gbts_par_set_gps_fix(void *ctx, time_t val);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
new file mode 100644
index 00000000..4e2fc95a
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
@@ -0,0 +1,177 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "oc2gbts_power.h"
+
+static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
+ [OC2GBTS_POWER_PA] = "/var/oc2g/pa-state/pa0/state",
+};
+
+static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
+ [OC2GBTS_POWER_SUPPLY] = "/var/oc2g/pwr-sense/main-supply/",
+ [OC2GBTS_POWER_PA] = "/var/oc2g/pwr-sense/pa0/",
+};
+
+static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
+ [OC2GBTS_POWER_POWER] = "power",
+ [OC2GBTS_POWER_VOLTAGE] = "voltage",
+ [OC2GBTS_POWER_CURRENT] = "current",
+};
+
+int oc2gbts_power_sensor_get(
+ enum oc2gbts_power_source source,
+ enum oc2gbts_power_type type,
+ int *power)
+{
+ char buf[PATH_MAX];
+ char pwrstr[10];
+ int fd, rc;
+
+ if (source >= _NUM_POWER_SOURCES)
+ return -EINVAL;
+
+ if (type >= _NUM_POWER_TYPES)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, pwrstr, sizeof(pwrstr));
+ pwrstr[sizeof(pwrstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *power = atoi(pwrstr);
+ return 0;
+}
+
+
+int oc2gbts_power_set(
+ enum oc2gbts_power_source source,
+ int en)
+{
+ int fd;
+ int rc;
+
+ if (source != OC2GBTS_POWER_PA) {
+ return -EINVAL;
+ }
+
+ fd = open(power_enable_devs[source], O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ rc = write(fd, en?"1":"0", 2);
+ close( fd );
+
+ if (rc != 2) {
+ return -1;
+ }
+
+ if (en) usleep(50*1000);
+
+ return 0;
+}
+
+int oc2gbts_power_get(
+ enum oc2gbts_power_source source)
+{
+ int fd;
+ int rc;
+ int retVal = 0;
+ char enstr[10];
+
+ fd = open(power_enable_devs[source], O_RDONLY);
+ if (fd < 0) {
+ return fd;
+ }
+
+ rc = read(fd, enstr, sizeof(enstr));
+ enstr[rc-1] = '\0';
+
+ close(fd);
+
+ if (rc < 0) {
+ return rc;
+ }
+ if (rc == 0) {
+ return -EIO;
+ }
+
+ rc = strcmp(enstr, "enabled");
+ if(rc == 0) {
+ retVal = 1;
+ }
+
+ return retVal;
+}
+
+static const char *vswr_devs[_NUM_VSWR_SENSORS] = {
+ [OC2GBTS_VSWR] = "/var/oc2g/vswr/tx0/vswr",
+};
+
+int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr)
+{
+ char buf[PATH_MAX];
+ char vswrstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, vswrstr, sizeof(vswrstr));
+ vswrstr[sizeof(vswrstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *vswr = atoi(vswrstr);
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.h b/src/osmo-bts-oc2g/misc/oc2gbts_power.h
new file mode 100644
index 00000000..3229f243
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.h
@@ -0,0 +1,36 @@
+#ifndef _OC2GBTS_POWER_H
+#define _OC2GBTS_POWER_H
+
+enum oc2gbts_power_source {
+ OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_PA,
+ _NUM_POWER_SOURCES
+};
+
+enum oc2gbts_power_type {
+ OC2GBTS_POWER_POWER,
+ OC2GBTS_POWER_VOLTAGE,
+ OC2GBTS_POWER_CURRENT,
+ _NUM_POWER_TYPES
+};
+
+int oc2gbts_power_sensor_get(
+ enum oc2gbts_power_source source,
+ enum oc2gbts_power_type type,
+ int *volt);
+
+int oc2gbts_power_set(
+ enum oc2gbts_power_source source,
+ int en);
+
+int oc2gbts_power_get(
+ enum oc2gbts_power_source source);
+
+enum oc2gbts_vswr_sensor {
+ OC2GBTS_VSWR,
+ _NUM_VSWR_SENSORS
+};
+
+int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
new file mode 100644
index 00000000..59b795ac
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
@@ -0,0 +1,178 @@
+/* Systemd service wd notification for OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_swd.h"
+#include <osmocom/core/logging.h>
+
+/* Needed for service watchdog notification */
+#include <systemd/sd-daemon.h>
+
+/* This is the period used to verify if all events have been registered to be allowed
+ to notify the systemd service watchdog
+*/
+#define SWD_PERIOD 30
+
+static void swd_start(struct oc2gbts_mgr_instance *mgr);
+static void swd_process(struct oc2gbts_mgr_instance *mgr);
+static void swd_close(struct oc2gbts_mgr_instance *mgr);
+static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
+static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop);
+static void swd_loop_run(void *_data);
+
+enum swd_state {
+ SWD_INITIAL,
+ SWD_IN_PROGRESS,
+};
+
+enum swd_result {
+ SWD_FAIL_START,
+ SWD_FAIL_NOTIFY,
+ SWD_SUCCESS,
+};
+
+static void swd_start(struct oc2gbts_mgr_instance *mgr)
+{
+ swd_process(mgr);
+}
+
+static void swd_process(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc = 0, notify = 0;
+
+ /* Did we get all needed conditions ? */
+ if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) {
+ /* Ping systemd service wd if enabled */
+ rc = sd_notify(0, "WATCHDOG=1");
+ LOGP(DSWD, LOGL_INFO, "Watchdog notification attempt\n");
+ notify = 1;
+ }
+ else {
+ LOGP(DSWD, LOGL_INFO, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks);
+ }
+
+ if (rc < 0) {
+ LOGP(DSWD, LOGL_ERROR,
+ "Failed to notify system service watchdog: %d\n", rc);
+ swd_state_reset(mgr, SWD_FAIL_NOTIFY);
+ return;
+ }
+ else {
+ /* Did we notified the watchdog? */
+ if (notify) {
+ mgr->swd.swd_events = 0;
+ /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */
+ if (mgr->swd.swd_events != 0)
+ mgr->swd.swd_events = 0;
+ }
+ }
+
+ swd_state_reset(mgr, SWD_SUCCESS);
+ return;
+}
+
+static void swd_close(struct oc2gbts_mgr_instance *mgr)
+{
+}
+
+static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->swd.swd_from_loop) {
+ mgr->swd.swd_timeout.data = mgr;
+ mgr->swd.swd_timeout.cb = swd_loop_run;
+ osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0);
+ }
+
+ mgr->swd.state = SWD_INITIAL;
+ swd_close(mgr);
+}
+
+static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->swd.state != SWD_INITIAL) {
+ LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n");
+ return -1;
+ }
+
+ mgr->swd.swd_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->swd.state = SWD_IN_PROGRESS;
+ swd_start(mgr);
+ return 0;
+}
+
+static void swd_loop_run(void *_data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DSWD, LOGL_INFO, "Going to check for watchdog notification.\n");
+ rc = swd_run(mgr, 1);
+ if (rc != 0) {
+ swd_state_reset(mgr, SWD_FAIL_START);
+ }
+}
+
+/* 'swd_num_events' configures the number of events to be monitored before notifying the
+ systemd service watchdog. It must be in the range of [1,64]. Events are notified
+ through the function 'oc2gbts_swd_event'
+*/
+int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events)
+{
+ /* Checks for a valid number of events to validate */
+ if (swd_num_events < 1 || swd_num_events > 64)
+ return(-1);
+
+ mgr->swd.state = SWD_INITIAL;
+ mgr->swd.swd_timeout.data = mgr;
+ mgr->swd.swd_timeout.cb = swd_loop_run;
+ osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0);
+
+ if (swd_num_events == 64){
+ mgr->swd.swd_eventmasks = 0xffffffffffffffffULL;
+ }
+ else {
+ mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1);
+ }
+ mgr->swd.swd_events = 0;
+ mgr->swd.num_events = swd_num_events;
+
+ return 0;
+}
+
+/* Notifies that the specified event 'swd_event' happened correctly;
+ the value must be in the range of [0,'swd_num_events'[ (see oc2gbts_swd_init).
+ For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63.
+ WARNING: if this function can be used from multiple threads at the same time,
+ it must be protected with a kind of mutex to avoid loosing event notification.
+*/
+int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event)
+{
+ /* Checks for a valid specified event (smaller than max possible) */
+ if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events)
+ return(-1);
+
+ mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event));
+
+ /* !!! Uncomment following line to debug events notification */
+ LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event));
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.h b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h
new file mode 100644
index 00000000..313712ac
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h
@@ -0,0 +1,7 @@
+#ifndef _OC2GBTS_SWD_H
+#define _OC2GBTS_SWD_H
+
+int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events);
+int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
new file mode 100644
index 00000000..8425dda3
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
@@ -0,0 +1,71 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <osmocom/core/utils.h>
+
+#include "oc2gbts_temp.h"
+
+static const char *temp_devs[_NUM_TEMP_SENSORS] = {
+ [OC2GBTS_TEMP_SUPPLY] = "/var/oc2g/temp/main-supply/temp",
+ [OC2GBTS_TEMP_SOC] = "/var/oc2g/temp/cpu/temp",
+ [OC2GBTS_TEMP_FPGA] = "/var/oc2g/temp/fpga/temp",
+ [OC2GBTS_TEMP_RMSDET] = "/var/oc2g/temp/rmsdet/temp",
+ [OC2GBTS_TEMP_OCXO] = "/var/oc2g/temp/ocxo/temp",
+ [OC2GBTS_TEMP_TX] = "/var/oc2g/temp/tx0/temp",
+ [OC2GBTS_TEMP_PA] = "/var/oc2g/temp/pa0/temp",
+};
+
+int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp)
+{
+ char buf[PATH_MAX];
+ char tempstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, tempstr, sizeof(tempstr));
+ tempstr[sizeof(tempstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *temp = atoi(tempstr);
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.h b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h
new file mode 100644
index 00000000..6d5dfca8
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h
@@ -0,0 +1,26 @@
+#ifndef _OC2GBTS_TEMP_H
+#define _OC2GBTS_TEMP_H
+
+enum oc2gbts_temp_sensor {
+ OC2GBTS_TEMP_SUPPLY,
+ OC2GBTS_TEMP_SOC,
+ OC2GBTS_TEMP_FPGA,
+ OC2GBTS_TEMP_RMSDET,
+ OC2GBTS_TEMP_OCXO,
+ OC2GBTS_TEMP_TX,
+ OC2GBTS_TEMP_PA,
+ _NUM_TEMP_SENSORS
+};
+
+enum oc2gbts_temp_type {
+ OC2GBTS_TEMP_INPUT,
+ OC2GBTS_TEMP_LOWEST,
+ OC2GBTS_TEMP_HIGHEST,
+ OC2GBTS_TEMP_FAULT,
+ _NUM_TEMP_TYPES
+};
+
+int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp);
+
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
new file mode 100644
index 00000000..b71f0383
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
@@ -0,0 +1,158 @@
+/* oc2gbts-util - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+
+#include "oc2gbts_par.h"
+
+enum act {
+ ACT_GET,
+ ACT_SET,
+};
+
+static enum act action;
+static char *write_arg;
+static int void_warranty;
+
+static void print_help()
+{
+ const struct value_string *par = oc2gbts_par_names;
+
+ printf("oc2gbts-util [--void-warranty -r | -w value] param_name\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!oc2gbts_par_is_int(par->value))
+ continue;
+ printf(" %s\n", par->str);
+ }
+}
+
+static int parse_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "read", 0, 0, 'r' },
+ { "void-warranty", 0, 0, 1000},
+ { "write", 1, 0, 'w' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "rw:h",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'r':
+ action = ACT_GET;
+ break;
+ case 'w':
+ action = ACT_SET;
+ write_arg = optarg;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ break;
+ case 1000:
+ printf("Will void warranty on write.\n");
+ void_warranty = 1;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *parname;
+ enum oc2gbts_par par;
+ int rc, val;
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ if (optind >= argc) {
+ fprintf(stderr, "You must specify the parameter name\n");
+ exit(2);
+ }
+ parname = argv[optind];
+
+ rc = get_string_value(oc2gbts_par_names, parname);
+ if (rc < 0) {
+ fprintf(stderr, "`%s' is not a valid parameter\n", parname);
+ exit(2);
+ } else
+ par = rc;
+
+ switch (action) {
+ case ACT_GET:
+ rc = oc2gbts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("%d\n", val);
+ break;
+ case ACT_SET:
+ rc = oc2gbts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
+ fprintf(stderr, "Parameter is already set!\r\n");
+ goto err;
+ }
+ rc = oc2gbts_par_set_int(par, atoi(write_arg));
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("Success setting %s=%d\n", parname,
+ atoi(write_arg));
+ break;
+ default:
+ fprintf(stderr, "Unsupported action\n");
+ goto err;
+ }
+
+ exit(0);
+
+err:
+ exit(1);
+}
+
diff --git a/src/osmo-bts-oc2g/oc2gbts.c b/src/osmo-bts-oc2g/oc2gbts.c
new file mode 100644
index 00000000..012d705c
--- /dev/null
+++ b/src/osmo-bts-oc2g/oc2gbts.c
@@ -0,0 +1,361 @@
+/* NuRAN Wireless OC-2G L1 API related definitions */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ * sysmobts.c
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1dbg.h>
+
+#include "oc2gbts.h"
+
+enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id)
+{
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ;
+ case GsmL1_PrimId_PhDataReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphTimeInd: return L1P_T_IND;
+ case GsmL1_PrimId_MphSyncInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhConnectInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhDataInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhRaInd: return L1P_T_IND;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1] = {
+ { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
+ { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
+ { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
+ { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
+ { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
+ { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
+ { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
+ { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
+ { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
+ { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
+ { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
+ { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
+ { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
+ { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
+ { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
+ { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
+ { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
+ { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
+ { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
+ { GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
+ { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
+ { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
+ { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
+ { GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
+ { 0, NULL }
+};
+
+GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id)
+{
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf;
+ case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf;
+ case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf;
+ case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf;
+ case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf;
+ case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf;
+ case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf;
+ case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf;
+ default: return -1; // Weak
+ }
+}
+
+enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id)
+{
+ switch (id) {
+ case Oc2g_PrimId_SystemInfoReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SystemInfoCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SystemFailureInd: return L1P_T_IND;
+ case Oc2g_PrimId_ActivateRfReq: return L1P_T_REQ;
+ case Oc2g_PrimId_ActivateRfCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_DeactivateRfReq: return L1P_T_REQ;
+ case Oc2g_PrimId_DeactivateRfCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
+ case Oc2g_PrimId_Layer1ResetReq: return L1P_T_REQ;
+ case Oc2g_PrimId_Layer1ResetCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetCalibTblReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetCalibTblCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_MuteRfReq: return L1P_T_REQ;
+ case Oc2g_PrimId_MuteRfCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetRxAttenReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetRxAttenCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_IsAliveReq: return L1P_T_REQ;
+ case Oc2g_PrimId_IsAliveCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetMaxCellSizeReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1] = {
+ { Oc2g_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
+ { Oc2g_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
+ { Oc2g_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
+ { Oc2g_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
+ { Oc2g_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
+ { Oc2g_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
+ { Oc2g_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
+ { Oc2g_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
+ { Oc2g_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
+ { Oc2g_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
+ { Oc2g_PrimId_SetCalibTblReq, "SET-CALIB.req" },
+ { Oc2g_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" },
+ { Oc2g_PrimId_MuteRfReq, "MUTE-RF.req" },
+ { Oc2g_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
+ { Oc2g_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
+ { Oc2g_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
+ { Oc2g_PrimId_IsAliveReq, "IS-ALIVE.req" },
+ { Oc2g_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" },
+ { Oc2g_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" },
+ { Oc2g_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" },
+ { Oc2g_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" },
+ { Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" },
+ { 0, NULL }
+};
+
+Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id)
+{
+ switch (id) {
+ case Oc2g_PrimId_SystemInfoReq: return Oc2g_PrimId_SystemInfoCnf;
+ case Oc2g_PrimId_ActivateRfReq: return Oc2g_PrimId_ActivateRfCnf;
+ case Oc2g_PrimId_DeactivateRfReq: return Oc2g_PrimId_DeactivateRfCnf;
+ case Oc2g_PrimId_Layer1ResetReq: return Oc2g_PrimId_Layer1ResetCnf;
+ case Oc2g_PrimId_SetCalibTblReq: return Oc2g_PrimId_SetCalibTblCnf;
+ case Oc2g_PrimId_MuteRfReq: return Oc2g_PrimId_MuteRfCnf;
+ case Oc2g_PrimId_SetRxAttenReq: return Oc2g_PrimId_SetRxAttenCnf;
+ case Oc2g_PrimId_IsAliveReq: return Oc2g_PrimId_IsAliveCnf;
+ case Oc2g_PrimId_SetMaxCellSizeReq: return Oc2g_PrimId_SetMaxCellSizeCnf;
+ case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf;
+ default: return -1; // Weak
+ }
+}
+
+const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Idle, "IDLE" },
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1] = {
+ { GsmL1_Status_Success, "Success" },
+ { GsmL1_Status_Generic, "Generic error" },
+ { GsmL1_Status_NoMemory, "Not enough memory" },
+ { GsmL1_Status_Timeout, "Timeout" },
+ { GsmL1_Status_InvalidParam, "Invalid parameter" },
+ { GsmL1_Status_Busy, "Resource busy" },
+ { GsmL1_Status_NoRessource, "No more resources" },
+ { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
+ { GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
+ { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
+ { GsmL1_Status_BadCrc, "Bad CRC" },
+ { GsmL1_Status_BadUsf, "Bad USF" },
+ { GsmL1_Status_InvalidCPS, "Invalid CPS field" },
+ { GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
+ { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
+ { GsmL1_Status_CriticalError, "Critical error" },
+ { GsmL1_Status_OverheatError, "Overheat error" },
+ { GsmL1_Status_DeviceError, "Device error" },
+ { GsmL1_Status_FacchError, "FACCH / TCH order error" },
+ { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
+ { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
+ { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
+ { GsmL1_Status_NotSynchronized, "Not synchronized" },
+ { GsmL1_Status_Unsupported, "Unsupported feature" },
+ { GsmL1_Status_ClockError, "System clock error" },
+ { 0, NULL }
+};
+
+const struct value_string oc2gbts_tracef_names[29] = {
+ { DBG_DEBUG, "DEBUG" },
+ { DBG_L1WARNING, "L1_WARNING" },
+ { DBG_ERROR, "ERROR" },
+ { DBG_L1RXMSG, "L1_RX_MSG" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
+ { DBG_L1TXMSG, "L1_TX_MSG" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
+ { DBG_MPHCNF, "MPH_CNF" },
+ { DBG_MPHIND, "MPH_IND" },
+ { DBG_MPHREQ, "MPH_REQ" },
+ { DBG_PHIND, "PH_IND" },
+ { DBG_PHREQ, "PH_REQ" },
+ { DBG_PHYRF, "PHY_RF" },
+ { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
+ { DBG_MODE, "MODE" },
+ { DBG_TDMAINFO, "TDMA_INFO" },
+ { DBG_BADCRC, "BAD_CRC" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "DEVICE_MSG" },
+ { DBG_RACHINFO, "RACH_INFO" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "MEMORY" },
+ { DBG_PROFILING, "PROFILING" },
+ { DBG_TESTCOMMENT, "TEST_COMMENT" },
+ { DBG_TEST, "TEST" },
+ { DBG_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string oc2gbts_tracef_docs[29] = {
+ { DBG_DEBUG, "Debug Region" },
+ { DBG_L1WARNING, "L1 Warning Region" },
+ { DBG_ERROR, "Error Region" },
+ { DBG_L1RXMSG, "L1_RX_MSG Region" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
+ { DBG_L1TXMSG, "L1_TX_MSG Region" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
+ { DBG_MPHCNF, "MphConfirmation Region" },
+ { DBG_MPHIND, "MphIndication Region" },
+ { DBG_MPHREQ, "MphRequest Region" },
+ { DBG_PHIND, "PhIndication Region" },
+ { DBG_PHREQ, "PhRequest Region" },
+ { DBG_PHYRF, "PhyRF Region" },
+ { DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
+ { DBG_MODE, "Mode Region" },
+ { DBG_TDMAINFO, "TDMA Info Region" },
+ { DBG_BADCRC, "Bad CRC Region" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "Device Message Region" },
+ { DBG_RACHINFO, "RACH Info" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "Memory Region" },
+ { DBG_PROFILING, "Profiling Region" },
+ { DBG_TESTCOMMENT, "Test Comments" },
+ { DBG_TEST, "Test Region" },
+ { DBG_STATUS, "Status Region" },
+ { 0, NULL }
+};
+
+const struct value_string oc2gbts_tch_pl_names[] = {
+ { GsmL1_TchPlType_NA, "N/A" },
+ { GsmL1_TchPlType_Fr, "FR" },
+ { GsmL1_TchPlType_Hr, "HR" },
+ { GsmL1_TchPlType_Efr, "EFR" },
+ { GsmL1_TchPlType_Amr, "AMR(IF2)" },
+ { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
+ { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
+ { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
+ { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
+ { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
+ { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
+ { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
+ { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
+ { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
+ { 0, NULL }
+};
+
+const struct value_string oc2gbts_dir_names[] = {
+ { GsmL1_Dir_TxDownlink, "TxDL" },
+ { GsmL1_Dir_TxUplink, "TxUL" },
+ { GsmL1_Dir_RxUplink, "RxUL" },
+ { GsmL1_Dir_RxDownlink, "RxDL" },
+ { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
+ { 0, NULL }
+};
+
+const struct value_string oc2gbts_chcomb_names[] = {
+ { GsmL1_LogChComb_0, "dummy" },
+ { GsmL1_LogChComb_I, "tch_f" },
+ { GsmL1_LogChComb_II, "tch_h" },
+ { GsmL1_LogChComb_IV, "ccch" },
+ { GsmL1_LogChComb_V, "ccch_sdcch4" },
+ { GsmL1_LogChComb_VII, "sdcch8" },
+ { GsmL1_LogChComb_XIII, "pdtch" },
+ { 0, NULL }
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
+ [PDCH_CS_1] = 23,
+ [PDCH_CS_2] = 34,
+ [PDCH_CS_3] = 40,
+ [PDCH_CS_4] = 54,
+ [PDCH_MCS_1] = 27,
+ [PDCH_MCS_2] = 33,
+ [PDCH_MCS_3] = 42,
+ [PDCH_MCS_4] = 49,
+ [PDCH_MCS_5] = 60,
+ [PDCH_MCS_6] = 78,
+ [PDCH_MCS_7] = 118,
+ [PDCH_MCS_8] = 142,
+ [PDCH_MCS_9] = 154
+};
+
+const struct value_string oc2gbts_rsl_ho_causes[] = {
+ { IPAC_HO_RQD_CAUSE_L_RXLEV_UL_H, "L_RXLEV_UL_H" },
+ { IPAC_HO_RQD_CAUSE_L_RXLEV_DL_H, "L_RXLEV_DL_H" },
+ { IPAC_HO_RQD_CAUSE_L_RXQUAL_UL_H, "L_RXQUAL_UL_H" },
+ { IPAC_HO_RQD_CAUSE_L_RXQUAL_DL_H, "L_RXQUAL_DL_H" },
+ { IPAC_HO_RQD_CAUSE_RXLEV_UL_IH, "RXLEV_UL_IH" },
+ { IPAC_HO_RQD_CAUSE_RXLEV_DL_IH, "RXLEV_DL_IH" },
+ { IPAC_HO_RQD_CAUSE_MAX_MS_RANGE, "MAX_MS_RANGE" },
+ { IPAC_HO_RQD_CAUSE_POWER_BUDGET, "POWER_BUDGET" },
+ { IPAC_HO_RQD_CAUSE_ENQUIRY, "ENQUIRY" },
+ { IPAC_HO_RQD_CAUSE_ENQUIRY_FAILED, "ENQUIRY_FAILED" },
+ { 0, NULL }
+};
diff --git a/src/osmo-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h
new file mode 100644
index 00000000..9eb87452
--- /dev/null
+++ b/src/osmo-bts-oc2g/oc2gbts.h
@@ -0,0 +1,92 @@
+#ifndef OC2GBTS_H
+#define OC2GBTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1const.h>
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or Oc2g_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define OC2GBTS_PRIM_SIZE \
+ (OSMO_MAX(sizeof(Oc2g_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
+
+enum l1prim_type {
+ L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
+ L1P_T_REQ,
+ L1P_T_CONF,
+ L1P_T_IND,
+};
+
+enum oc2g_pedestal_mode{
+ OC2G_PEDESTAL_OFF = 0,
+ OC2G_PEDESTAL_ON,
+};
+
+enum oc2g_led_control_mode{
+ OC2G_LED_CONTROL_BTS = 0,
+ OC2G_LED_CONTROL_EXT,
+};
+
+enum oc2g_auto_pwr_adjust_mode{
+ OC2G_TX_PWR_ADJ_NONE = 0,
+ OC2G_TX_PWR_ADJ_AUTO,
+};
+
+enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id);
+const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1];
+GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id);
+
+enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id);
+const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1];
+Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id);
+
+const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string oc2gbts_tracef_names[29];
+const struct value_string oc2gbts_tracef_docs[29];
+
+const struct value_string oc2gbts_tch_pl_names[15];
+
+const struct value_string oc2gbts_clksrc_names[10];
+
+const struct value_string oc2gbts_dir_names[6];
+
+const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX];
+
+enum pdch_cs {
+ PDCH_CS_1,
+ PDCH_CS_2,
+ PDCH_CS_3,
+ PDCH_CS_4,
+ PDCH_MCS_1,
+ PDCH_MCS_2,
+ PDCH_MCS_3,
+ PDCH_MCS_4,
+ PDCH_MCS_5,
+ PDCH_MCS_6,
+ PDCH_MCS_7,
+ PDCH_MCS_8,
+ PDCH_MCS_9,
+ _NUM_PDCH_CS
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+/* OC2G default parameters */
+#define OC2G_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */
+#define OC2G_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */
+#define OC2G_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */
+#define OC2G_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */
+#define OC2G_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */
+#define OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */
+#define OC2G_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */
+#define OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */
+
+#endif /* OC2GBTS_H */
diff --git a/src/osmo-bts-oc2g/oc2gbts_vty.c b/src/osmo-bts-oc2g/oc2gbts_vty.c
new file mode 100644
index 00000000..4f7c45a1
--- /dev/null
+++ b/src/osmo-bts-oc2g/oc2gbts_vty.c
@@ -0,0 +1,733 @@
+/* VTY interface for NuRAN Wireless OC-2G */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012,2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/rsl.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+extern int lchan_activate(struct gsm_lchan *lchan);
+extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan);
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+#define DSP_TRACE_F_STR "DSP Trace Flag\n"
+
+static struct gsm_bts *vty_bts;
+
+static const struct value_string oc2g_pedestal_mode_strs[] = {
+ { OC2G_PEDESTAL_OFF, "off" },
+ { OC2G_PEDESTAL_ON, "on" },
+ { 0, NULL }
+};
+
+static const struct value_string oc2g_led_mode_strs[] = {
+ { OC2G_LED_CONTROL_BTS, "bts" },
+ { OC2G_LED_CONTROL_EXT, "external" },
+ { 0, NULL }
+};
+
+static const struct value_string oc2g_auto_adj_pwr_strs[] = {
+ { OC2G_TX_PWR_ADJ_NONE, "none" },
+ { OC2G_TX_PWR_ADJ_AUTO, "auto" },
+ { 0, NULL }
+};
+
+/* configuration */
+
+DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd,
+ "trx-calibration-path PATH",
+ "Set the path name to TRX calibration data\n" "Path name\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ if (pinst->u.oc2g.calib_path)
+ talloc_free(pinst->u.oc2g.calib_path);
+
+ pinst->u.oc2g.calib_path = talloc_strdup(pinst, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
+ "HIDDEN", TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(oc2gbts_tracef_names, argv[1]);
+ pinst->u.oc2g.dsp_trace_f |= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
+ "HIDDEN", NO_STR TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(oc2gbts_tracef_names, argv[1]);
+ pinst->u.oc2g.dsp_trace_f &= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+
+/* runtime */
+
+DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
+ "show trx <0-0> dsp-trace-flags",
+ SHOW_TRX_STR "Display the current setting of the DSP trace flags")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct oc2gl1_hdl *fl1h;
+ int i;
+
+ if (!trx)
+ return CMD_WARNING;
+
+ fl1h = trx_oc2gl1_hdl(trx);
+
+ vty_out(vty, "Oc2g L1 DSP trace flags:%s", VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(oc2gbts_tracef_names); i++) {
+ const char *endis;
+
+ if (oc2gbts_tracef_names[i].value == 0 &&
+ oc2gbts_tracef_names[i].str == NULL)
+ break;
+
+ if (fl1h->dsp_trace_f & oc2gbts_tracef_names[i].value)
+ endis = "enabled";
+ else
+ endis = "disabled";
+
+ vty_out(vty, "DSP Trace %-15s %s%s",
+ oc2gbts_tracef_names[i].str, endis,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct oc2gl1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.oc2g.hdl;
+ flag = get_string_value(oc2gbts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct oc2gl1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.oc2g.hdl;
+ flag = get_string_value(oc2gbts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sys_info, show_sys_info_cmd,
+ "show phy <0-0> instance <0-0> system-information",
+ SHOW_TRX_STR "Display information about system\n")
+{
+ int phy_nr = atoi(argv[0]);
+ int inst_nr = atoi(argv[1]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ struct phy_instance *pinst;
+ struct oc2gl1_hdl *fl1h;
+ int i;
+
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY link %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY instance %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ fl1h = pinst->u.oc2g.hdl;
+
+ vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
+ fl1h->hw_info.dsp_version[0],
+ fl1h->hw_info.dsp_version[1],
+ fl1h->hw_info.dsp_version[2],
+ fl1h->hw_info.fpga_version[0],
+ fl1h->hw_info.fpga_version[1],
+ fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
+
+ vty_out(vty, "GSM Band Support: ");
+ for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
+ if (fl1h->hw_info.band_support & (1 << i))
+ vty_out(vty, "%s ", gsm_band_name(1 << i));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.minTxPower, VTY_NEWLINE);
+ vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.maxTxPower, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(activate_lchan, activate_lchan_cmd,
+ "trx <0-0> <0-7> (activate|deactivate) <0-7>",
+ TRX_STR
+ "Timeslot number\n"
+ "Activate Logical Channel\n"
+ "Deactivate Logical Channel\n"
+ "Logical Channel Number\n" )
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[3]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ if (!strcmp(argv[2], "activate"))
+ lchan_activate(lchan);
+ else
+ lchan_deactivate(lchan);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_tx_power, set_tx_power_cmd,
+ "trx nr <0-1> tx-power <-110-100>",
+ TRX_STR
+ "TRX number \n"
+ "Set transmit power (override BSC)\n"
+ "Transmit power in dBm\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int power = atoi(argv[1]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+
+ power_ramp_start(trx, to_mdB(power), 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(loopback, loopback_cmd,
+ "trx <0-0> <0-7> loopback <0-1>",
+ TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_loopback, no_loopback_cmd,
+ "no trx <0-0> <0-7> loopback <0-1>",
+ NO_STR TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
+ "nominal-tx-power <0-40>",
+ "Set the nominal transmit output power in dBm\n"
+ "Nominal transmit output power level in dBm\n")
+{
+ int nominal_power = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+
+ if (( nominal_power > 40 ) || ( nominal_power < 0 )) {
+ vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s",
+ nominal_power, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ trx->nominal_power = nominal_power;
+ trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd,
+ "max-cell-size <0-166>",
+ "Set the maximum cell size in qbits\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int cell_size = (uint8_t)atoi(argv[0]);
+
+ if (( cell_size > 166 ) || ( cell_size < 0 )) {
+ vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s",
+ cell_size, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.max_cell_size = (uint8_t)cell_size;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd,
+ "pedestal-mode (on|off)",
+ "Set unused time-slot transmission in pedestal mode\n"
+ "Transmission pedestal mode can be (off, on)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = get_string_value(oc2g_pedestal_mode_strs, argv[0]);
+
+ if((val < OC2G_PEDESTAL_OFF) || (val > OC2G_PEDESTAL_ON)) {
+ vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.pedestal_mode = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd,
+ "dsp-alive-period <0-60>",
+ "Set DSP alive timer period in second\n")
+{
+ struct phy_instance *pinst = vty->index;
+ uint8_t period = (uint8_t)atoi(argv[0]);
+
+ if (( period > 60 ) || ( period < 0 )) {
+ vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s",
+ period, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.dsp_alive_period = period;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd,
+ "pwr-adj-mode (none|auto)",
+ "Set output power adjustment mode\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = get_string_value(oc2g_auto_adj_pwr_strs, argv[0]);
+
+ if((val < OC2G_TX_PWR_ADJ_NONE) || (val > OC2G_TX_PWR_ADJ_AUTO)) {
+ vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd,
+ "tx-red-pwr-8psk <0-40>",
+ "Set reduction output power for 8-PSK scheme in dB unit\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = atoi(argv[0]);
+
+ if ((val > 40) || (val < 0)) {
+ vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s",
+ val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.tx_pwr_red_8psk = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd,
+ "c0-idle-red-pwr <0-40>",
+ "Set reduction output power for C0 idle slot in dB unit\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = atoi(argv[0]);
+
+ if ((val > 40) || (val < 0)) {
+ vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s",
+ val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.tx_c0_idle_pwr_red = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int trx_nr, ts_nr, lchan_nr;
+ uint8_t ho_cause;
+ uint8_t old_ho_cause;
+
+ /* get BTS pointer */
+ bts = gsm_bts_num(net, 0);
+ if (!bts) {
+ vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* get TRX pointer */
+ if (argc >= 1) {
+ trx_nr = atoi(argv[0]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ }
+ /* get TS pointer */
+ if (argc >= 2) {
+ ts_nr = atoi(argv[1]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS %s%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ts = &trx->ts[ts_nr];
+ }
+ /* get lchan pointer */
+ if (argc >= 3) {
+ lchan_nr = atoi(argv[2]);
+ if (lchan_nr >= TS_MAX_LCHAN) {
+ vty_out(vty, "%% can't find LCHAN %s%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan = &ts->lchan[lchan_nr];
+ }
+
+ /* get HO cause */
+ if (argc >= 4) {
+ ho_cause = get_string_value(oc2gbts_rsl_ho_causes, argv[3]);
+ if (ho_cause >= IPAC_HO_RQD_CAUSE_MAX) {
+ vty_out(vty, "%% can't find valid HO cause %s%s", argv[3],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ vty_out(vty, "%% HO cause is not provided %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* TODO(oramadan) MERGE
+ /* store recorded HO causes * /
+ old_ho_cause = lchan->meas_preproc.rec_ho_causes;
+
+ /* Apply new HO causes * /
+ lchan->meas_preproc.rec_ho_causes = 1 << (ho_cause - 1);
+
+ /* Send measuremnt report to BSC * /
+ rsl_tx_preproc_meas_res(lchan);
+
+ /* restore HO cause * /
+ lchan->meas_preproc.rec_ho_causes = old_ho_cause;
+ */
+
+ return CMD_SUCCESS;
+}
+
+/* TODO(oramadan) MERGE
+DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd,
+ "rtp-drift-threshold <0-10000>",
+ "RTP parameters\n"
+ "RTP timestamp drift threshold in ms\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ btsb->oc2g.rtp_drift_thres_ms = (unsigned int) atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+*/
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+ /* TODO(oramadan) MERGE
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ vty_out(vty, " led-control-mode %s%s",
+ get_value_string(oc2g_led_mode_strs, btsb->oc2g.led_ctrl_mode), VTY_NEWLINE);
+
+ vty_out(vty, " rtp-drift-threshold %d%s",
+ btsb->oc2g.rtp_drift_thres_ms, VTY_NEWLINE);
+ */
+
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE);
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ if (pinst->u.oc2g.dsp_trace_f & (1 << i)) {
+ const char *name;
+ name = get_value_string(oc2gbts_tracef_names, (1 << i));
+ vty_out(vty, " dsp-trace-flag %s%s", name,
+ VTY_NEWLINE);
+ }
+ }
+ if (pinst->u.oc2g.calib_path)
+ vty_out(vty, " trx-calibration-path %s%s",
+ pinst->u.oc2g.calib_path, VTY_NEWLINE);
+
+ vty_out(vty, " max-cell-size %d%s",
+ pinst->u.oc2g.max_cell_size, VTY_NEWLINE);
+
+ vty_out(vty, " pedestal-mode %s%s",
+ get_value_string(oc2g_pedestal_mode_strs, pinst->u.oc2g.pedestal_mode) , VTY_NEWLINE);
+
+ vty_out(vty, " dsp-alive-period %d%s",
+ pinst->u.oc2g.dsp_alive_period, VTY_NEWLINE);
+
+ vty_out(vty, " pwr-adj-mode %s%s",
+ get_value_string(oc2g_auto_adj_pwr_strs, pinst->u.oc2g.tx_pwr_adj_mode), VTY_NEWLINE);
+
+ vty_out(vty, " tx-red-pwr-8psk %d%s",
+ pinst->u.oc2g.tx_pwr_red_8psk, VTY_NEWLINE);
+
+ vty_out(vty, " c0-idle-red-pwr %d%s",
+ pinst->u.oc2g.tx_c0_idle_pwr_red, VTY_NEWLINE);
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ /* runtime-patch the command strings with debug levels */
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names,
+ "phy <0-1> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs,
+ TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names,
+ "no phy <0-1> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs,
+ NO_STR TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_names,
+ "dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_docs,
+ DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_names,
+ "no dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_docs,
+ NO_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts,
+ oc2gbts_rsl_ho_causes,
+ "trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (",
+ "|",")", VTY_DO_LOWER);
+
+ install_element_ve(&show_dsp_trace_f_cmd);
+ install_element_ve(&show_sys_info_cmd);
+ install_element_ve(&dsp_trace_f_cmd);
+ install_element_ve(&no_dsp_trace_f_cmd);
+
+ install_element(ENABLE_NODE, &activate_lchan_cmd);
+ install_element(ENABLE_NODE, &set_tx_power_cmd);
+
+ install_element(ENABLE_NODE, &loopback_cmd);
+ install_element(ENABLE_NODE, &no_loopback_cmd);
+
+ install_element(ENABLE_NODE, &trigger_ho_cause_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+ /* TODO(oramadan) MERGE
+ install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd);
+ */
+
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+
+ install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd);
+
+ return 0;
+}
+
+/* TODO(oramadan) MERGE
+/* OC2G BTS control interface * /
+CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_alert, "oc2g-oml-alert");
+static int set_oc2g_oml_alert(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int cause = atoi(cmd->value);
+ char *saveptr = NULL;
+
+ alarm_sig_data.mo = &bts->mo;
+ cause = atoi(strtok_r(cmd->value, ",", &saveptr));
+ alarm_sig_data.event_serverity = (cause >> 8) & 0x0F;
+ alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr);
+ memcpy(alarm_sig_data.spare, &cause, sizeof(int));
+ LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text);
+
+ /* dispatch OML alarm signal * /
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_ALARM, &alarm_sig_data);
+
+ /* return with same alarm cause to MGR rather than OK string* /
+ cmd->reply = talloc_asprintf(cmd, "%d", cause);
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_ceased, "oc2g-oml-ceased");
+static int set_oc2g_oml_ceased(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int cause = atoi(cmd->value);
+ char *saveptr = NULL;
+
+ alarm_sig_data.mo = &bts->mo;
+ cause = atoi(strtok_r(cmd->value, ",", &saveptr));
+ alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr);
+ memcpy(alarm_sig_data.spare, &cause, sizeof(int));
+ LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR ceased alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text);
+
+ /* dispatch OML alarm signal * /
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_CEASED_ALARM, &alarm_sig_data);
+ */
+
+ /* return with same alarm cause to MGR rather than OK string* /
+ cmd->reply = talloc_asprintf(cmd, "%d", cause);
+ return CTRL_CMD_REPLY;
+}
+*/
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ int rc = 0;
+
+
+ /* TODO(oramadan) MERGE
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_alert);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_ceased);
+ */
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/oml.c b/src/osmo-bts-oc2g/oml.c
new file mode 100644
index 00000000..6cf7e1d5
--- /dev/null
+++ b/src/osmo-bts-oc2g/oml.c
@@ -0,0 +1,2088 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013-2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+#include <nrw/oc2g/oc2g.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "oc2gbts.h"
+#include "utils.h"
+
+static int mph_info_chan_confirm(struct gsm_lchan *lchan,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(lchan->ts->trx, &l1sap);
+}
+
+enum sapi_cmd_type {
+ SAPI_CMD_ACTIVATE,
+ SAPI_CMD_CONFIG_CIPHERING,
+ SAPI_CMD_CONFIG_LOGCH_PARAM,
+ SAPI_CMD_SACCH_REL_MARKER,
+ SAPI_CMD_REL_MARKER,
+ SAPI_CMD_DEACTIVATE,
+};
+
+struct sapi_cmd {
+ struct llist_head entry;
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+ enum sapi_cmd_type type;
+ int (*callback)(struct gsm_lchan *lchan, int status);
+};
+
+static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = GsmL1_LogChComb_0,
+ [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV,
+ [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I,
+ [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII,
+ [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0,
+ /*
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+ * part of this, only "real" pchan values will be looked up here.
+ * See the callers of ts_connect_as().
+ */
+};
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);
+
+static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct oc2gl1_hdl *gl1,
+ uint32_t hLayer3_uint32)
+{
+ HANDLE hLayer3;
+ prim->id = id;
+
+ osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit);
+ hLayer3 = (void*)hLayer3_uint32;
+
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq:
+ //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ prim->u.mphInitReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseReq:
+ prim->u.mphCloseReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphCloseReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectReq:
+ prim->u.mphConnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectReq:
+ prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDisconnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateReq:
+ prim->u.mphActivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphActivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateReq:
+ prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDeactivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigReq:
+ prim->u.mphConfigReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConfigReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureReq:
+ prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphMeasureReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphInitCnf:
+ prim->u.mphInitCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseCnf:
+ prim->u.mphCloseCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectCnf:
+ prim->u.mphConnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ prim->u.mphDisconnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateCnf:
+ prim->u.mphActivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ prim->u.mphDeactivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigCnf:
+ prim->u.mphConfigCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureCnf:
+ prim->u.mphMeasureCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphTimeInd:
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ break;
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhDataReq:
+ prim->u.phDataReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id);
+ break;
+ }
+ return &prim->u;
+}
+
+static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx)
+{
+ struct gsm_bts *bts = trx->bts;
+
+ osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit);
+ osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit);
+
+ return bts->nr << 24
+ | trx->nr << 16;
+}
+
+static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts)
+{
+ osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit);
+
+ return l1p_handle_for_trx(ts->trx)
+ | ts->nr << 8;
+}
+
+
+static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan)
+{
+ osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit);
+
+ return l1p_handle_for_ts(lchan->ts)
+ | lchan->nr;
+}
+
+GsmL1_Status_t prim_status(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.status;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.status;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.status;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.status;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.status;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.status;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.status;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.status;
+ default:
+ break;
+ }
+ return GsmL1_Status_Success;
+}
+
+#if 0
+static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data)
+{
+ struct msgb *resp_msg = data;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+
+ if (prim_status(l1p) != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id),
+ get_value_string(oc2gbts_l1status_names, cc->status));
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ return abis_nm_sendmsg(msg);
+}
+#endif
+
+int lchan_activate(struct gsm_lchan *lchan);
+
+static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_Status_t status = prim_status(l1p);
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id),
+ get_value_string(oc2gbts_l1status_names, status));
+ msgb_free(l1_msg);
+ return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM);
+ }
+
+ msgb_free(l1_msg);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
+ mo->obj_inst.ts_nr == 0) {
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
+ DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n");
+ mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ if (cbch) {
+ cbch->rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(cbch);
+ }
+ }
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(mo);
+}
+
+static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_abis_mo *mo;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+
+ mo = &trx->ts[cnf->u8Tn].mo;
+ return opstart_compl(mo, l1_msg);
+}
+
+static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n",
+ get_value_string(oc2gbts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-MUTE failure");
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf;
+
+ LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n",
+ get_value_string(oc2gbts_l1status_names, ic->status));
+
+ /* store layer1 handle */
+ if (ic->status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n",
+ get_value_string(oc2gbts_l1status_names, ic->status));
+ bts_shutdown(trx->bts, "MPH-INIT failure");
+ }
+
+ fl1h->hLayer1 = ic->hLayer1;
+
+ /* If the TRX was already locked the MphInit would have undone it */
+ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
+ trx_rf_lock(trx, 1, trx_mute_on_init_cb);
+
+ /* apply initial values for Tx power backoff for 8-PSK * /
+ trx->max_power_backoff_8psk = fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk;
+ l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
+ LOGP(DL1C, LOGL_INFO, "%s Applied initial 8-PSK Tx power backoff of %d dB\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
+
+ /* apply initial values for Tx C0 idle slot power reduction * /
+ trx->c0_idle_power_red = fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red;
+ l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
+ LOGP(DL1C, LOGL_INFO, "%s Applied initial C0 idle slot power reduction of %d dB\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); */
+
+ /* Begin to ramp up the power */
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+
+ return opstart_compl(&trx->mo, l1_msg);
+}
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids,
+ unsigned int num_attr_ids)
+{
+ unsigned int i;
+
+ if (!mo->nm_attr)
+ return 0;
+
+ for (i = 0; i < num_attr_ids; i++) {
+ if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ struct msgb *msg;
+ GsmL1_MphInitReq_t *mi_req;
+ GsmL1_DeviceParam_t *dev_par;
+ int rc, oc2g_band;
+
+ if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
+ ARRAY_SIZE(trx_rqd_attr))) {
+ /* HACK: spec says we need to decline, but openbsc
+ * doesn't deal with this very well */
+ return oml_mo_opstart_ack(&trx->mo);
+ //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
+ }
+
+ /* Update TRX band */
+ rc = gsm_arfcn2band_rc(trx->arfcn, &trx->bts->band);
+ if (rc) {
+ /* FIXME: abort initialization? */
+ LOGP(DL1C, LOGL_ERROR, "Could not pick GSM band "
+ "for ARFCN %u\n", trx->arfcn);
+ trx->bts->band = 0x00;
+ }
+
+ oc2g_band = oc2gbts_select_oc2g_band(trx, trx->arfcn);
+ if (oc2g_band < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n",
+ gsm_band_name(trx->bts->band));
+ }
+
+ msg = l1p_msgb_alloc();
+ mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h,
+ l1p_handle_for_trx(trx));
+ dev_par = &mi_req->deviceParam;
+ dev_par->devType = GsmL1_DevType_TxdRxu;
+ dev_par->freqBand = oc2g_band;
+ dev_par->u16Arfcn = trx->arfcn;
+ dev_par->u16BcchArfcn = trx->bts->c0->arfcn;
+ dev_par->u8NbTsc = trx->bts->bsic & 7;
+ dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx)
+ ? 0.0 : trx->bts->ul_power_target;
+
+ dev_par->fTxPowerLevel = 0.0;
+ LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, "
+ "TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc,
+ dev_par->fRxPowerLevel, dev_par->fTxPowerLevel);
+
+ /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */
+ return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
+}
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+
+ return fl1h->hLayer1;
+}
+
+static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ msgb_free(l1_msg);
+ return 0;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ struct msgb *msg;
+
+ msg = l1p_msgb_alloc();
+ prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h,
+ l1p_handle_for_trx(trx));
+ LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr);
+
+ return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+}
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ uint8_t mute[8];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mute); ++i)
+ mute[i] = locked ? 1 : 0;
+
+ return l1if_mute_rf(fl1h, mute, cb);
+}
+
+int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
+ int success)
+{
+ if (success) {
+ int i;
+ int is_locked = 1;
+
+ for (i = 0; i < 8; ++i)
+ if (!mute_state[i])
+ is_locked = 0;
+
+ mo->nm_state.administrative =
+ is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_ack(mo);
+ } else {
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+ }
+}
+
+static int ts_connect_as(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan,
+ l1if_compl_cb *cb, void *data)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx);
+ GsmL1_MphConnectReq_t *cr;
+
+ if (pchan == GSM_PCHAN_TCH_F_PDCH
+ || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Requested TS connect as %s,"
+ " expected a specific pchan instead\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan));
+ return -EINVAL;
+ }
+
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+ cr->logChComb = pchan_to_logChComb[pchan];
+
+ return l1if_gsm_req_compl(fl1h, msg, cb, NULL);
+}
+
+static int ts_opstart(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan = ts->pchan;
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* First connect as NONE, until first RSL CHAN ACT. */
+ pchan = GSM_PCHAN_NONE;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* First connect as TCH/F, expecting PDCH ACT. */
+ pchan = GSM_PCHAN_TCH_F;
+ break;
+ default:
+ /* simply use ts->pchan */
+ break;
+ }
+ return ts_connect_as(ts, pchan, opstart_compl_cb, NULL);
+}
+
+GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
+{
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return GsmL1_Sapi_TchF;
+ case GSM_LCHAN_TCH_H:
+ return GsmL1_Sapi_TchH;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+ return GsmL1_Sapi_Idle;
+}
+
+GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = lchan->ts->pchan;
+
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ pchan = lchan->ts->dyn.pchan_want;
+
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ if (lchan->type == GSM_LCHAN_CCCH)
+ return GsmL1_SubCh_NA;
+ /* fall-through */
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ return lchan->nr;
+ case GSM_PCHAN_NONE:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_UNKNOWN:
+ default:
+ /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ return GsmL1_SubCh_NA;
+ }
+
+ return GsmL1_SubCh_NA;
+}
+
+struct sapi_dir {
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+};
+
+static const struct sapi_dir ccch_sapis[] = {
+ { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchf_sapis[] = {
+ { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchh_sapis[] = {
+ { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir sdcch_sapis[] = {
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir cbch_sapis[] = {
+ { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink },
+ /* Does the CBCH really have a SACCH in Downlink? */
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+};
+
+static const struct sapi_dir pdtch_sapis[] = {
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink },
+#if 0
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink },
+#endif
+};
+
+static const struct sapi_dir ho_sapis[] = {
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+struct lchan_sapis {
+ const struct sapi_dir *sapis;
+ unsigned int num_sapis;
+};
+
+static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
+ [GSM_LCHAN_SDCCH] = {
+ .sapis = sdcch_sapis,
+ .num_sapis = ARRAY_SIZE(sdcch_sapis),
+ },
+ [GSM_LCHAN_TCH_F] = {
+ .sapis = tchf_sapis,
+ .num_sapis = ARRAY_SIZE(tchf_sapis),
+ },
+ [GSM_LCHAN_TCH_H] = {
+ .sapis = tchh_sapis,
+ .num_sapis = ARRAY_SIZE(tchh_sapis),
+ },
+ [GSM_LCHAN_CCCH] = {
+ .sapis = ccch_sapis,
+ .num_sapis = ARRAY_SIZE(ccch_sapis),
+ },
+ [GSM_LCHAN_PDTCH] = {
+ .sapis = pdtch_sapis,
+ .num_sapis = ARRAY_SIZE(pdtch_sapis),
+ },
+ [GSM_LCHAN_CBCH] = {
+ .sapis = cbch_sapis,
+ .num_sapis = ARRAY_SIZE(cbch_sapis),
+ },
+};
+
+static const struct lchan_sapis sapis_for_ho = {
+ .sapis = ho_sapis,
+ .num_sapis = ARRAY_SIZE(ho_sapis),
+};
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir);
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan);
+
+/**
+ * Execute the first SAPI command of the queue. In case of the markers
+ * this method is re-entrant so we need to make sure to remove a command
+ * from the list before calling a function that will queue a command.
+ *
+ * \return 0 in case no Gsm Request was sent, 1 otherwise
+ */
+static int sapi_queue_exeute(struct gsm_lchan *lchan)
+{
+ int res;
+ struct sapi_cmd *cmd;
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+
+ switch (cmd->type) {
+ case SAPI_CMD_ACTIVATE:
+ mph_send_activate_req(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_CIPHERING:
+ mph_send_config_ciphering(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_LOGCH_PARAM:
+ mph_send_config_logchpar(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_SACCH_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_TxDownlink);
+ res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_RxUplink);
+ break;
+ case SAPI_CMD_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = lchan_deactivate_sapis(lchan);
+ break;
+ case SAPI_CMD_DEACTIVATE:
+ mph_send_deactivate_req(lchan, cmd);
+ res = 1;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE,
+ "Unimplemented command type %d\n", cmd->type);
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = 0;
+ abort();
+ break;
+ }
+
+ return res;
+}
+
+static void sapi_queue_send(struct gsm_lchan *lchan)
+{
+ int res;
+
+ do {
+ res = sapi_queue_exeute(lchan);
+ } while (res == 0 && !llist_empty(&lchan->sapi_cmds));
+}
+
+static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
+{
+ int end;
+ struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
+ struct sapi_cmd, entry);
+ llist_del(&cmd->entry);
+ end = llist_empty(&lchan->sapi_cmds);
+
+ if (cmd->callback)
+ cmd->callback(lchan, status);
+ talloc_free(cmd);
+
+ if (end || llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_DEBUG,
+ "%s End of SAPI cmd queue encountered.%s\n",
+ gsm_lchan_name(lchan),
+ llist_empty(&lchan->sapi_cmds)
+ ? " Queue is now empty."
+ : " More pending.");
+ return;
+ }
+
+ sapi_queue_send(lchan);
+}
+
+/**
+ * Queue and possible execute a SAPI command. Return 1 in case the command was
+ * already executed and 0 in case if it was only put into the queue
+ */
+static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ int start = llist_empty(&lchan->sapi_cmds);
+ llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
+
+ if (!start)
+ return 0;
+
+ sapi_queue_send(lchan);
+ return 1;
+}
+
+static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n",
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_ASSIGNED;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(oc2gbts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_ACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan)
+{
+ return 0xBB
+ | (lchan->nr << 8)
+ | (lchan->ts->nr << 16)
+ | (lchan->ts->trx->nr << 24);
+}
+
+/* obtain a ptr to the lapdm_channel for a given hLayer */
+struct gsm_lchan *
+l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2)
+{
+ uint8_t magic = hLayer2 & 0xff;
+ uint8_t ts_nr = (hLayer2 >> 16) & 0xff;
+ uint8_t lchan_nr = (hLayer2 >> 8)& 0xff;
+ struct gsm_bts_trx_ts *ts;
+
+ if (magic != 0xBB)
+ return NULL;
+
+ /* FIXME: if we actually run on the BTS, the 32bit field is large
+ * enough to simply put a pointer inside. */
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+/* we regularly check if the DSP L1 is still sending us primitives.
+ * if not, we simply stop the BTS program (and be re-spawned) */
+static void alive_timer_cb(void *data)
+{
+ struct oc2gl1_hdl *fl1h = data;
+
+ if (fl1h->alive_prim_cnt == 0) {
+ LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n");
+ exit(23);
+ }
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+}
+
+static void clear_amr_params(GsmL1_LogChParam_t *lch_par)
+{
+ int j;
+ /* common for the SIGN, V1 and EFR: */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA;
+ lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset;
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+}
+
+static void set_payload_format(GsmL1_LogChParam_t *lch_par)
+{
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp;
+}
+
+static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+ int j;
+
+ LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
+ gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ /* we have to set some TCH payload type even if we don't
+ * know yet what codec we will use later on */
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Efr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Amr;
+ set_payload_format(lch_par);
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */
+ lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
+
+ /* initialize to clean state */
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+
+ j = 0;
+ if (mr_conf->m4_75)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_15)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_90)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m6_70)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_40)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_95)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m10_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+ if (mr_conf->m12_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2;
+ break;
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ int sapi = cmd->sapi;
+ int dir = cmd->dir;
+ GsmL1_MphActivateReq_t *act_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ lch_par = &act_req->logChPrm;
+ act_req->u8Tn = lchan->ts->nr;
+ act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ act_req->dir = dir;
+ act_req->sapi = sapi;
+ act_req->hLayer2 = (HANDLE *)l1if_lchan_to_hLayer(lchan);
+ act_req->hLayer3 = act_req->hLayer2;
+
+ switch (act_req->sapi) {
+ case GsmL1_Sapi_Rach:
+ lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Agch:
+ lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name);
+ break;
+ case GsmL1_Sapi_TchH:
+ case GsmL1_Sapi_TchF:
+ lchan2lch_par(lch_par, lchan);
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Prach:
+ lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Sacch:
+ /*
+ * For the SACCH we need to set the u8MsPowerLevel when
+ * doing manual MS power control.
+ */
+ if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+ /* fall through */
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ default:
+ break;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2,
+ get_value_string(oc2gbts_l1sapi_names, act_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_dir_names, act_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
+}
+
+static void sapi_clear_queue(struct llist_head *queue)
+{
+ struct sapi_cmd *next, *tmp;
+
+ llist_for_each_entry_safe(next, tmp, queue, entry) {
+ llist_del(&next->entry);
+ talloc_free(next);
+ }
+}
+
+static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
+
+ /* FIXME: Error handling */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s act failed mark broken due status: %d\n",
+ gsm_lchan_name(lchan), status);
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
+
+ /* set the initial ciphering parameters for both directions */
+ l1if_set_ciphering(fl1h, lchan, 1);
+ l1if_set_ciphering(fl1h, lchan, 0);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ return 0;
+}
+
+static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_ACTIVATE;
+ cmd->callback = sapi_activate_cb;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_activate(struct gsm_lchan *lchan)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Trying to activate lchan, but commands in queue\n",
+ gsm_lchan_name(lchan));
+
+ /* override the regular SAPIs if this is the first hand-over
+ * related activation of the LCHAN */
+ if (lchan->ho.active == HANDOVER_ENABLED)
+ s4l = &sapis_for_ho;
+
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+
+ if (sapi == GsmL1_Sapi_Sch) {
+ /* once we activate the SCH, we should get MPH-TIME.ind */
+ fl1h->alive_timer.cb = alive_timer_cb;
+ fl1h->alive_timer.data = fl1h;
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+ }
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+#warning "FIXME: Should this be in sapi_activate_cb?"
+ lchan_init_lapdm(lchan);
+
+ return 0;
+}
+
+const struct value_string oc2gbts_l1cfgt_names[] = {
+ { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" },
+ { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" },
+ { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" },
+ { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
+ { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" },
+ { 0, NULL }
+};
+
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi)
+{
+ int i;
+
+ switch (sapi) {
+ case GsmL1_Sapi_Rach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic);
+ break;
+ case GsmL1_Sapi_Agch:
+ LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ",
+ lch_par->agch.u8NbrOfAgch);
+ break;
+ case GsmL1_Sapi_Sacch:
+ LOGPC(DL1C, logl, "MS Power Level 0x%02x",
+ lch_par->sacch.u8MsPowerLevel);
+ break;
+ case GsmL1_Sapi_TchF:
+ case GsmL1_Sapi_TchH:
+ LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (",
+ lch_par->tch.amrCmiPhase,
+ lch_par->tch.amrInitCodecMode);
+ for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) {
+ LOGPC(DL1C, logl, "%x ",
+ lch_par->tch.amrActiveCodecSet[i]);
+ }
+ break;
+ /* FIXME: PRACH / PTCCH */
+ default:
+ break;
+ }
+ LOGPC(DL1C, logl, ")\n");
+}
+
+static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_trx_name(trx),
+ get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n",
+ cc->cfgParams.setTxPowerLevel.fTxPowerLevel);
+
+ power_trx_change_compl(trx,
+ (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000));
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_txpower_backoff_8psk_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_trx_name(trx),
+ get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n",
+ cc->cfgParams.set8pskPowerReduction.u8PowerReduction);
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
+ gsm_trx_name(trx),
+ get_value_string(oc2gbts_sysprim_names, sysp->id),
+ get_value_string(oc2gbts_l1status_names, sac->status));
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
+ gsm_trx_name(trx),
+ get_value_string(oc2gbts_sysprim_names, sysp->id),
+ get_value_string(oc2gbts_l1status_names, sac->status));
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));
+
+ switch (cc->cfgParamId) {
+ case GsmL1_ConfigParamId_SetLogChParams:
+ dump_lch_par(LOGL_INFO,
+ &cc->cfgParams.setLogChParams.logChParams,
+ cc->cfgParams.setLogChParams.sapi);
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetCipheringParams:
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_RX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ break;
+ case LCHAN_CIPH_RX_CONF_TX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ break;
+ case LCHAN_CIPH_RXTX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ break;
+ case LCHAN_CIPH_NONE:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ default:
+ LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
+ break;
+ }
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetNbTsc:
+ default:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ }
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams;
+ conf_req->cfgParams.setLogChParams.sapi = cmd->sapi;
+ conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr;
+ conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ conf_req->cfgParams.setLogChParams.dir = cmd->dir;
+ conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
+ lchan2lch_par(lch_par, lchan);
+
+ /* Update the MS Power Level */
+ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+
+ /* FIXME: update encryption */
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_l1sapi_names,
+ conf_req->cfgParams.setLogChParams.sapi));
+ LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ",
+ conf_req->cfgParams.setLogChParams.u8Tn,
+ conf_req->cfgParams.setLogChParams.subCh,
+ conf_req->cfgParams.setLogChParams.dir);
+ dump_lch_par(LOGL_INFO,
+ &conf_req->cfgParams.setLogChParams.logChParams,
+ conf_req->cfgParams.setLogChParams.sapi);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->sapi = sapi;
+ cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
+ queue_sapi_command(lchan, cmd);
+}
+
+static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
+{
+ enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan));
+ return 0;
+}
+
+int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel;
+ conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
+}
+
+int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_Set8pskPowerReduction;
+ conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL);
+}
+
+int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_SetMaxCellSizeReq;
+ sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size;
+
+ LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ cell_size);
+
+ return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL);
+
+}
+
+int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_SetC0IdleSlotPowerReductionReq;
+ sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red;
+
+ LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ red);
+
+ return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_compl_cb, NULL);
+}
+
+const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
+ [0] = GsmL1_CipherId_A50,
+ [1] = GsmL1_CipherId_A50,
+ [2] = GsmL1_CipherId_A51,
+ [3] = GsmL1_CipherId_A52,
+ [4] = GsmL1_CipherId_A53,
+};
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ struct GsmL1_MphConfigReq_t *cfgr;
+
+ cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+
+ cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams;
+ cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr;
+ cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ cfgr->cfgParams.setCipheringParams.dir = cmd->dir;
+ cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph))
+ return -EINVAL;
+ cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id];
+
+ LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n",
+ gsm_lchan_name(lchan),
+ cfgr->cfgParams.setCipheringParams.cipherId,
+ get_value_string(oc2gbts_dir_names,
+ cfgr->cfgParams.setCipheringParams.dir));
+
+ memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
+ lchan->encr.key, lchan->encr.key_len);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_CIPHERING;
+ queue_sapi_command(lchan, cmd);
+}
+
+int l1if_set_ciphering(struct oc2gl1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink)
+{
+ int dir;
+
+ /* ignore the request when the channel is not active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ if (dir_downlink)
+ dir = GsmL1_Dir_TxDownlink;
+ else
+ dir = GsmL1_Dir_RxUplink;
+
+ enqueue_sapi_ciphering_cmd(lchan, dir);
+
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch);
+ return 0;
+}
+
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink);
+ tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink);
+
+ /* FIXME: update encryption */
+
+ return 0;
+}
+
+static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf;
+
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_NONE;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(oc2gbts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got de-activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_DEACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+ return 0;
+}
+
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphDeactivateReq_t *deact_req;
+
+ deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ deact_req->u8Tn = lchan->ts->nr;
+ deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ deact_req->dir = cmd->dir;
+ deact_req->sapi = cmd->sapi;
+ deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_l1sapi_names, deact_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_dir_names, deact_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
+}
+
+static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
+{
+ /* FIXME: Error handling. There is no NACK... */
+ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ /* Don't send an REL ACK on SACCH deactivate */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+
+ /* Reactivate CCCH due to SI3 update in RSL */
+ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+ lchan_activate(lchan);
+ }
+ return 0;
+}
+
+static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_DEACTIVATE;
+ cmd->callback = sapi_deactivate_cb;
+ return queue_sapi_command(lchan, cmd);
+}
+
+/*
+ * Release the SAPI if it was allocated. E.g. the SACCH might be already
+ * deactivated or during a hand-over the TCH was not allocated yet.
+ */
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ /* check if we should schedule a release */
+ if (dir & GsmL1_Dir_TxDownlink) {
+ if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
+ } else if (dir & GsmL1_Dir_RxUplink) {
+ if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
+ }
+
+ /* now schedule the command and maybe dispatch it */
+ return enqueue_sapi_deact_cmd(lchan, sapi, dir);
+}
+
+static int release_sapis_for_ho(struct gsm_lchan *lchan)
+{
+ int res = 0;
+ int i;
+
+ const struct lchan_sapis *s4l = &sapis_for_ho;
+
+ for (i = s4l->num_sapis-1; i >= 0; i--)
+ res |= check_sapi_release(lchan,
+ s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ return res;
+}
+
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ int i, res;
+
+ res = 0;
+
+ /* The order matters.. the Facch needs to be released first */
+ for (i = s4l->num_sapis-1; i >= 0; i--) {
+ /* Stop the alive timer once we deactivate the SCH */
+ if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch)
+ osmo_timer_del(&fl1h->alive_timer);
+
+ /* Release if it was allocated */
+ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ }
+
+ /* always attempt to disable the RACH burst */
+ res |= release_sapis_for_ho(lchan);
+
+ /* nothing was queued */
+ if (res == 0) {
+ LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ }
+
+ return res;
+}
+
+static void enqueue_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to release all active SAPIs */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ enqueue_rel_marker(lchan);
+ return 0;
+}
+
+static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to check if the SACCH is allocated */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_SACCH_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ enqueue_sacch_rel_marker(lchan);
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ /* FIXME: more checks if the attributes are valid */
+
+ switch (msg_type) {
+ case NM_MT_SET_CHAN_ATTR:
+ /* our L1 only supports one global TSC for all channels
+ * one one TRX, so we need to make sure not to activate
+ * channels with a different TSC!! */
+ if (TLVP_PRESENT(new_attr, NM_ATT_TSC) &&
+ TLVP_LEN(new_attr, NM_ATT_TSC) >= 1 &&
+ *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) {
+ LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n",
+ *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7);
+ return -NM_NACK_PARAM_RANGE;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ if (kind == NM_OC_RADIO_CARRIER) {
+ struct gsm_bts_trx *trx = obj;
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ /* convert max TA to max cell size in qbits */
+ uint8_t cell_size = bts->max_ta << 2;
+
+ /* We do not need to check for L1 handle
+ * because the max cell size parameter can receive before MphInit */
+ if (fl1h->phy_inst->u.oc2g.max_cell_size != cell_size) {
+ /* instruct L1 to apply max cell size */
+ l1if_set_max_cell_size(fl1h, cell_size);
+ /* update current max cell size */
+ fl1h->phy_inst->u.oc2g.max_cell_size = cell_size;
+ }
+
+ /* Did we go through MphInit yet? If yes fire and forget */
+ if (fl1h->hLayer1) {
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+
+ if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) {
+ /* update current Tx power backoff for 8-PSK */
+ fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk = trx->max_power_backoff_8psk;
+ /* instruct L1 to apply Tx power backoff for 8 PSK */
+ l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
+ }
+
+ if (fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red != trx->c0_idle_power_red) {
+ /* update current C0 idle slot Tx power reduction */
+ fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red = trx->c0_idle_power_red;
+ /* instruct L1 to apply C0 idle slot power reduction */
+ l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
+ }
+ }
+
+ }
+
+ /* FIXME: we actaully need to send a ACK or NACK for the OML message */
+ return oml_fom_ack_nack(msg, 0);
+}
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ rc = ts_opstart(obj);
+ break;
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
+ rc = oml_mo_opstart_ack(mo);
+ if (mo->obj_class == NM_OC_BTS) {
+ oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK);
+ }
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ int rc = -EINVAL;
+ int granted = 0;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+
+ if (mo->procedure_pending) {
+ LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
+ "pending procedure on RC %d\n",
+ ((struct gsm_bts_trx *)obj)->nr);
+ return 0;
+ }
+ mo->procedure_pending = 1;
+ switch (adm_state) {
+ case NM_STATE_LOCKED:
+ rc = trx_rf_lock(obj, 1, NULL);
+ break;
+ case NM_STATE_UNLOCKED:
+ rc = trx_rf_lock(obj, 0, NULL);
+ break;
+ default:
+ granted = 1;
+ break;
+ }
+
+ if (!granted && rc == 0)
+ /* in progress, will send ack/nack after completion */
+ return 0;
+
+ mo->procedure_pending = 0;
+
+ break;
+ default:
+ /* blindly accept all state changes */
+ granted = 1;
+ break;
+ }
+
+ if (granted) {
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+ } else
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+
+}
+
+int l1if_rsl_chan_act(struct gsm_lchan *lchan)
+{
+ //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE);
+ //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE);
+ lchan_activate(lchan);
+ return 0;
+}
+
+/**
+ * Modify the given lchan in the handover scenario. This is a lot like
+ * second channel activation but with some additional activation.
+ */
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan)
+{
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ if (lchan->ho.active == HANDOVER_NONE)
+ return -1;
+
+ LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n",
+ gsm_lchan_name(lchan));
+
+ /* Give up listening to RACH bursts */
+ release_sapis_for_ho(lchan);
+
+ /* Activate the normal SAPIs */
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+ return 0;
+}
+
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
+{
+ /* A duplicate RF Release Request, ignore it */
+ if (lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ lchan_deactivate(lchan);
+ return 0;
+}
+
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+ /* Only de-activate the SACCH if the lchan is active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return 0;
+ return bts_model_lchan_deactivate_sacch(lchan);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx);
+
+ return l1if_activate_rf(fl1, 0);
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ return l1if_set_txpower(trx_oc2gl1_hdl(trx), ((float) p_trxout_mdBm)/1000.0);
+}
+
+static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n",
+ gsm_lchan_name(ts->lchan));
+
+ cb_ts_disconnected(ts);
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx);
+ GsmL1_MphDisconnectReq_t *cr;
+
+ DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+
+ return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL);
+}
+
+static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n",
+ gsm_lchan_name(ts->lchan),
+ gsm_pchan_name(ts->pchan),
+ ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
+ ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
+ ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : "");
+
+ cb_ts_connected(ts, 0);
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+
+ rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL);
+ if (rc)
+ cb_ts_connected(ts, rc);
+}
diff --git a/src/osmo-bts-oc2g/oml_router.c b/src/osmo-bts-oc2g/oml_router.c
new file mode 100644
index 00000000..198d5e30
--- /dev/null
+++ b/src/osmo-bts-oc2g/oml_router.c
@@ -0,0 +1,132 @@
+/* Beginnings of an OML router */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "oml_router.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = oml_msgb_alloc();
+ if (!msg) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n");
+ return -1;
+ }
+
+ rc = recv(fd->fd, msg->tail, msg->data_len, 0);
+ if (rc <= 0) {
+ close(fd->fd);
+ osmo_fd_unregister(fd);
+ fd->fd = -1;
+ goto err;
+ }
+
+ msg->l1h = msgb_put(msg, rc);
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid IPA message rc(%d)\n", rc);
+ goto err;
+ }
+
+ rc = msg_verify_oml_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid OML message rc(%d)\n", rc);
+ goto err;
+ }
+
+ /* todo dispatch message */
+
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what)
+{
+ int fd;
+ struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data;
+
+ /* Accept only one connection at a time. De-register it */
+ if (read_fd->fd > -1) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "New OML router connection. Closing old one.\n");
+ close(read_fd->fd);
+ osmo_fd_unregister(read_fd);
+ read_fd->fd = -1;
+ }
+
+ fd = accept(accept_fd->fd, NULL, NULL);
+ if (fd < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n",
+ strerror(errno));
+ return -1;
+ }
+
+ read_fd->fd = fd;
+ if (osmo_fd_register(read_fd) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n");
+ close(fd);
+ read_fd->fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+int oml_router_init(struct gsm_bts *bts, const char *path,
+ struct osmo_fd *accept_fd, struct osmo_fd *read_fd)
+{
+ int rc;
+
+ memset(accept_fd, 0, sizeof(*accept_fd));
+ memset(read_fd, 0, sizeof(*read_fd));
+
+ accept_fd->cb = oml_router_accept_cb;
+ accept_fd->data = read_fd;
+
+ read_fd->cb = oml_router_read_cb;
+ read_fd->data = bts;
+ read_fd->when = BSC_FD_READ;
+ read_fd->fd = -1;
+
+ rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0,
+ path,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+ return rc;
+}
diff --git a/src/osmo-bts-oc2g/oml_router.h b/src/osmo-bts-oc2g/oml_router.h
new file mode 100644
index 00000000..4b22e9c5
--- /dev/null
+++ b/src/osmo-bts-oc2g/oml_router.h
@@ -0,0 +1,13 @@
+#pragma once
+
+struct gsm_bts;
+struct osmo_fd;
+
+/**
+ * The default path oc2gbts will listen for incoming
+ * registrations for OML routing and sending.
+ */
+#define OML_ROUTER_PATH "/var/run/oc2gbts_oml_router"
+
+
+int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read);
diff --git a/src/osmo-bts-oc2g/tch.c b/src/osmo-bts-oc2g/tch.c
new file mode 100644
index 00000000..1bd93e4b
--- /dev/null
+++ b/src/osmo-bts-oc2g/tch.c
@@ -0,0 +1,545 @@
+/* Traffic channel support for NuRAN Wireless OC-2G BTS L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+
+static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_FR_BYTES);
+ memcpy(cur, l1_payload, GSM_FR_BYTES);
+
+ lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+ /* new L1 can deliver bits like we need them */
+ memcpy(l1_payload, rtp_payload, GSM_FR_BYTES);
+ return GSM_FR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
+ uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_EFR_BYTES);
+ memcpy(cur, l1_payload, GSM_EFR_BYTES);
+ enum osmo_amr_type ft;
+ enum osmo_amr_quality bfi;
+ uint8_t cmr;
+ int8_t sti, cmi;
+ osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti);
+ lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan);
+
+ return msg;
+}
+
+static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+ memcpy(l1_payload, rtp_payload, payload_len);
+
+ return payload_len;
+}
+
+static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return NULL;
+ }
+
+ cur = msgb_put(msg, GSM_HR_BYTES);
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+
+ lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return 0;
+ }
+
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+
+ return GSM_HR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t amr_if2_len = payload_len - 2;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ cur = msgb_put(msg, amr_if2_len);
+ memcpy(cur, l1_payload+2, amr_if2_len);
+
+ /*
+ * Audiocode's MGW doesn't like receiving CMRs that are not
+ * the same as the previous one. This means we need to patch
+ * the content here.
+ */
+ if ((cur[0] & 0xF0) == 0xF0)
+ cur[0]= lchan->tch.last_cmr << 4;
+ else
+ lchan->tch.last_cmr = cur[0] >> 4;
+
+ return msg;
+}
+
+/*! \brief convert AMR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ uint8_t payload_len, uint8_t ft)
+{
+ memcpy(l1_payload, rtp_payload, payload_len);
+ return payload_len;
+}
+
+#define RTP_MSGB_ALLOC_SIZE 512
+
+/*! \brief function for incoming RTP via TCH.req
+ * \param[in] rtp_pl buffer containing RTP payload
+ * \param[in] rtp_pl_len length of \a rtp_pl
+ * \param[in] use_cache Use cached payload instead of parsing RTP
+ * \param[in] marker RTP header Marker bit (indicates speech onset)
+ * \returns 0 if encoding result can be sent further to L1 without extra actions
+ * positive value if data is ready AND extra actions are required
+ * negative value otherwise (no data for L1 encoded)
+ *
+ * This function prepares a msgb with a L1 PH-DATA.req primitive and
+ * queues it into lchan->dl_tch_queue.
+ *
+ * Note that the actual L1 primitive header is not fully initialized
+ * yet, as things like the frame number, etc. are unknown at the time we
+ * pre-fill the primtive.
+ */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker)
+{
+ uint8_t *payload_type;
+ uint8_t *l1_payload, ft;
+ int rc = 0;
+ bool is_sid = false;
+
+ DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(rtp_pl, rtp_pl_len));
+
+ payload_type = &data[0];
+ l1_payload = &data[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ *payload_type = GsmL1_TchPlType_Fr;
+ rc = rtppayload_to_l1_fr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len);
+ } else{
+ *payload_type = GsmL1_TchPlType_Hr;
+ rc = rtppayload_to_l1_hr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
+ }
+ if (is_sid)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
+ rtp_pl_len);
+ /* FIXME: detect and save EFR SID */
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ if (use_cache) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache,
+ lchan->tch.dtx.len, ft);
+ *len = lchan->tch.dtx.len + 1;
+ return 0;
+ }
+
+ rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
+ l1_payload, marker, len, &ft);
+ if (rc < 0)
+ return rc;
+ if (!dtx_dl_amr_enabled(lchan)) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ }
+
+ /* DTX DL-specific logic below: */
+ switch (lchan->tch.dtx.dl_amr_fsm->state) {
+ case ST_ONSET_V:
+ *payload_type = GsmL1_TchPlType_Amr_Onset;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ *len = 3;
+ return 1;
+ case ST_VOICE:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F1:
+ if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
+ rtp_pl_len, ft);
+ return 0;
+ }
+ /* AMR FR */
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F2:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_F1_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_U_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_SID_U:
+ case ST_U_NOINH:
+ return -EAGAIN;
+ case ST_FACCH:
+ return -EBADMSG;
+ default:
+ LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
+ "%d\n", lchan->tch.dtx.dl_amr_fsm->state);
+ return -EINVAL;
+ }
+ break;
+ default:
+ /* we don't support CSD modes */
+ rc = -1;
+ break;
+ }
+
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
+ gsm_lchan_name(lchan));
+ return -EBADMSG;
+ }
+
+ *len = rc + 1;
+
+ DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(data, *len));
+ return 0;
+}
+
+static int is_recv_only(uint8_t speech_mode)
+{
+ return (speech_mode & 0xF0) == (1 << 4);
+}
+
+/*! \brief receive a traffic L1 primitive for a given lchan */
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
+ GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
+ uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 };
+ struct msgb *rmsg = NULL;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
+
+ if (is_recv_only(lchan->abis_ip.speech_mode))
+ return -EAGAIN;
+
+ if (data_ind->msgUnitParam.u8Size < 1) {
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr);
+ /* Push empty payload to upper layers */
+ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP");
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+ }
+
+ payload_type = data_ind->msgUnitParam.u8Buffer[0];
+ payload = data_ind->msgUnitParam.u8Buffer + 1;
+ payload_len = data_ind->msgUnitParam.u8Size - 1;
+
+ /* clear RTP marker if the marker has previously sent */
+ if (!lchan->tch.dtx.is_speech_resume)
+ lchan->rtp_tx_marker = false;
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ case GsmL1_TchPlType_Efr:
+ if (lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Hr:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr_Onset:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ /* according to 3GPP TS 26.093 ONSET frames precede the first
+ speech frame of a speech burst - set the marker for next RTP
+ frame */
+ lchan->tch.dtx.is_speech_resume = true;
+ lchan->rtp_tx_marker = true;
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP2:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->tch.dtx.is_speech_resume = true;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidUpdateInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->tch.dtx.is_speech_resume = true;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ default:
+ LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_tch_pl_names, payload_type));
+ break;
+ }
+
+ LOGP(DL1P, LOGL_DEBUG, "%s %s lchan->rtp_tx_marker = %s, len=%u\n",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_tch_pl_names, payload_type),
+ lchan->rtp_tx_marker ? "true" : "false",
+ payload_len);
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Hr:
+ rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Efr:
+ rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Amr:
+ rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ memcpy(sid_first, payload, payload_len);
+ int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD);
+ if (len < 0)
+ return 0;
+ rmsg = l1_to_rtppayload_amr(sid_first, len, lchan);
+ break;
+ }
+
+ if (rmsg)
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+
+ return 0;
+
+err_payload_match:
+ LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type));
+ return -EINVAL;
+}
+
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn)
+{
+ struct msgb *msg;
+ GsmL1_Prim_t *l1p;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ uint8_t *payload_type;
+ uint8_t *l1_payload;
+ int rc;
+
+ msg = l1p_msgb_alloc();
+ if (!msg)
+ return NULL;
+
+ l1p = msgb_l1prim(msg);
+ data_req = &l1p->u.phDataReq;
+ msu_param = &data_req->msgUnitParam;
+ payload_type = &msu_param->u8Buffer[0];
+ l1_payload = &msu_param->u8Buffer[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_AMR:
+ if (lchan->type == GSM_LCHAN_TCH_H &&
+ dtx_dl_amr_enabled(lchan)) {
+ /* we have to explicitly handle sending SID FIRST P2 for
+ AMR HR in here */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
+ rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
+ false, &(msu_param->u8Size),
+ NULL);
+ if (rc == 0)
+ return msg;
+ }
+ *payload_type = GsmL1_TchPlType_Amr;
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ *payload_type = GsmL1_TchPlType_Fr;
+ else
+ *payload_type = GsmL1_TchPlType_Hr;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ break;
+ default:
+ msgb_free(msg);
+ return NULL;
+ }
+
+ rc = repeat_last_sid(lchan, l1_payload, fn);
+ if (!rc) {
+ msgb_free(msg);
+ return NULL;
+ }
+ msu_param->u8Size = rc;
+
+ return msg;
+}
diff --git a/src/osmo-bts-oc2g/utils.c b/src/osmo-bts-oc2g/utils.c
new file mode 100644
index 00000000..cb65f45a
--- /dev/null
+++ b/src/osmo-bts-oc2g/utils.c
@@ -0,0 +1,118 @@
+/* Helper utilities that are used in OMLs */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "utils.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+
+int band_oc2g2osmo(GsmL1_FreqBand_t band)
+{
+ switch (band) {
+ case GsmL1_FreqBand_850:
+ return GSM_BAND_850;
+ case GsmL1_FreqBand_900:
+ return GSM_BAND_900;
+ case GsmL1_FreqBand_1800:
+ return GSM_BAND_1800;
+ case GsmL1_FreqBand_1900:
+ return GSM_BAND_1900;
+ default:
+ return -1;
+ }
+}
+
+static int band_osmo2oc2g(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+
+ /* check if the TRX hardware actually supports the given band */
+ if (!(fl1h->hw_info.band_support & osmo_band))
+ return -1;
+
+ /* if yes, convert from osmcoom style band definition to L1 band */
+ switch (osmo_band) {
+ case GSM_BAND_850:
+ return GsmL1_FreqBand_850;
+ case GSM_BAND_900:
+ return GsmL1_FreqBand_900;
+ case GSM_BAND_1800:
+ return GsmL1_FreqBand_1800;
+ case GSM_BAND_1900:
+ return GsmL1_FreqBand_1900;
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Select the band that matches the ARFCN. In general the ARFCNs
+ * for GSM1800 and GSM1900 overlap and one needs to specify the
+ * rightband. When moving between GSM900/GSM1800 and GSM850/1900
+ * modifying the BTS configuration is a bit annoying. The auto-band
+ * configuration allows to ease with this transition.
+ */
+int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn)
+{
+ enum gsm_band band;
+ struct gsm_bts *bts = trx->bts;
+ int rc;
+
+ if (!bts->auto_band)
+ return band_osmo2oc2g(trx, bts->band);
+
+ /*
+ * We need to check what will happen now.
+ */
+ rc = gsm_arfcn2band_rc(arfcn, &band);
+ if (rc) /* wrong ARFCN, give up */
+ return -1;
+
+ /* if we are already on the right band return */
+ if (band == bts->band)
+ return band_osmo2oc2g(trx, bts->band);
+
+ /* Check if it is GSM1800/GSM1900 */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return band_osmo2oc2g(trx, bts->band);
+
+ /*
+ * Now to the actual autobauding. We just want DCS/DCS and
+ * PCS/PCS for PCS we check for 850/1800 though
+ */
+ if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800)
+ || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900)
+ || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900))
+ return band_osmo2oc2g(trx, band);
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
+ return band_osmo2oc2g(trx, GSM_BAND_1900);
+
+ /* give up */
+ return -1;
+}
diff --git a/src/osmo-bts-oc2g/utils.h b/src/osmo-bts-oc2g/utils.h
new file mode 100644
index 00000000..f2f13e71
--- /dev/null
+++ b/src/osmo-bts-oc2g/utils.h
@@ -0,0 +1,13 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdint.h>
+#include "oc2gbts.h"
+
+struct gsm_bts_trx;
+
+int band_oc2g2osmo(GsmL1_FreqBand_t band);
+
+int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn);
+
+#endif
diff --git a/src/osmo-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am
new file mode 100644
index 00000000..43d9cd7d
--- /dev/null
+++ b/src/osmo-bts-octphy/Makefile.am
@@ -0,0 +1,12 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OCTSDR2G_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS)
+
+EXTRA_DIST = l1_if.h l1_oml.h l1_utils.h octphy_hw_api.h octpkt.h
+
+bin_PROGRAMS = osmo-bts-octphy
+
+COMMON_SOURCES = main.c l1_if.c l1_oml.c l1_utils.c l1_tch.c octphy_hw_api.c octphy_vty.c octpkt.c
+
+osmo_bts_octphy_SOURCES = $(COMMON_SOURCES)
+osmo_bts_octphy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c
new file mode 100644
index 00000000..f149c048
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_if.c
@@ -0,0 +1,1806 @@
+/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org>
+ *
+ * based on a copy of osmo-bts-sysmo/l1_if.c, which is
+ * Copyright (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
+ * Copyright (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/socket.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/handover.h>
+
+#include "l1_if.h"
+#include "l1_oml.h"
+#include "l1_utils.h"
+
+#include "octpkt.h"
+#include <octphy/octvc1/main/octvc1_main_version.h>
+
+/* NOTE: The octphy GPRS frame number handling changed with
+ * OCTSDR-2G-02.07.00-B1314-BETA. From that version on, each ph_data_ind must
+ * subtract 3 from the frame number before passing the frame to the PCU */
+#define cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG 0x41c0522
+
+#include <octphy/octpkt/octpkt_hdr.h>
+#define OCTVC1_RC2STRING_DECLARE
+#include <octphy/octvc1/octvc1_rc2string.h>
+#define OCTVC1_ID2STRING_DECLARE
+#include <octphy/octvc1/octvc1_id2string.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_evt_swap.h>
+#define OCTVC1_OPT_DECLARE_DEFAULTS
+#include <octphy/octvc1/gsm/octvc1_gsm_default.h>
+#include <octphy/octvc1/main/octvc1_main_default.h>
+
+#define cPKTAPI_FIFO_ID_MSG 0xAAAA0001
+
+/* maximum window of unacknowledged commands */
+#define UNACK_CMD_WINDOW 8
+/* maximum number of re-transmissions of a command */
+#define MAX_RETRANS 3
+/* timeout until which we expect PHY to respond */
+#define CMD_TIMEOUT 5
+
+/* allocate a msgb for a Layer1 primitive */
+struct msgb *l1p_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc_headroom(1500, 24, "l1_prim");
+ if (!msg)
+ return msg;
+
+ msg->l2h = msg->data;
+ return msg;
+}
+
+void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg,
+ struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd)
+{
+ octvc1_fill_msg_hdr(mh, msgb_l2len(msg), fl1h->session_id,
+ fl1h->next_trans_id++, 0 /* user_info */,
+ msg_type, 0, api_cmd);
+}
+
+/* Map OSMOCOM BAND type to Octasic type */
+tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM
+osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn)
+{
+ switch (osmo_band) {
+ case GSM_BAND_450:
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_450;
+ case GSM_BAND_850:
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_850;
+ case GSM_BAND_900:
+ if (arfcn == 0)
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900;
+ else if (arfcn >= 955 && arfcn <= 974)
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_R_900;
+ else if (arfcn >= 975 && arfcn <= 1023)
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900;
+ else
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_P_900;
+ case GSM_BAND_1800:
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_DCS_1800;
+ case GSM_BAND_1900:
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_PCS_1900;
+ default:
+ return -EINVAL;
+ }
+};
+
+struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id)
+{
+ struct phy_instance *pinst;
+
+ pinst = phy_instance_by_num(fl1h->phy_link, trx_id);
+ if (!pinst)
+ return NULL;
+
+ return pinst->trx;
+}
+
+struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx,
+ tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id)
+{
+ unsigned int lchan_idx;
+
+ OSMO_ASSERT(lch_id->byTimeslotNb < ARRAY_SIZE(trx->ts));
+ if (lch_id->bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL) {
+ switch (lch_id->bySAPI) {
+ case cOCTVC1_GSM_SAPI_ENUM_FCCH:
+ case cOCTVC1_GSM_SAPI_ENUM_SCH:
+ case cOCTVC1_GSM_SAPI_ENUM_BCCH:
+ case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH:
+ case cOCTVC1_GSM_SAPI_ENUM_RACH:
+ lchan_idx = 4;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_CBCH:
+ /* it is always index 2 (3rd element), whether in a
+ * combined CCCH+SDCCH4 or in a SDCCH8 */
+ lchan_idx = 2;
+ break;
+ default:
+ lchan_idx = 0;
+ break;
+ }
+ } else
+ lchan_idx = lch_id->bySubChannelNb;
+
+ OSMO_ASSERT(lchan_idx < ARRAY_SIZE(trx->ts[0].lchan));
+
+ return &trx->ts[lch_id->byTimeslotNb].lchan[lchan_idx];
+}
+
+
+/* TODO: Unify with sysmobts? */
+struct wait_l1_conf {
+ /* list of wait_l1_conf in the phy handle */
+ struct llist_head list;
+ /* expiration timer */
+ struct osmo_timer_list timer;
+ /* primtivie / command ID */
+ uint32_t prim_id;
+ /* transaction ID */
+ uint32_t trans_id;
+ /* copy of the msgb containing the command */
+ struct msgb *cmd_msg;
+ /* call-back to call on response */
+ l1if_compl_cb *cb;
+ /* data to hand to call-back on response */
+ void *cb_data;
+ /* number of re-transmissions so far */
+ uint32_t num_retrans;
+};
+
+static void release_wlc(struct wait_l1_conf *wlc)
+{
+ osmo_timer_del(&wlc->timer);
+ msgb_free(wlc->cmd_msg);
+ talloc_free(wlc);
+}
+
+static void l1if_req_timeout(void *data)
+{
+ struct wait_l1_conf *wlc = data;
+
+ /* FIXME: Implement re-transmission of command on timer expiration */
+
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(octphy_cid_vals, wlc->prim_id));
+ exit(23);
+}
+
+/* FIXME: this should be in libosmocore */
+static struct llist_head *llist_first(struct llist_head *head)
+{
+ if (llist_empty(head))
+ return NULL;
+ return head->next;
+}
+
+static void check_refill_window(struct octphy_hdl *fl1h, struct wait_l1_conf *recent)
+{
+ struct wait_l1_conf *wlc;
+ int space = UNACK_CMD_WINDOW - fl1h->wlc_list_len;
+ int i;
+
+ for (i = 0; i < space; i++) {
+ /* get head of queue */
+ struct llist_head *first = llist_first(&fl1h->wlc_postponed);
+ struct msgb *msg;
+ if (!first)
+ break;
+ wlc = llist_entry(first, struct wait_l1_conf, list);
+
+ /* remove from head of postponed queue */
+ llist_del(&wlc->list);
+ fl1h->wlc_postponed_len--;
+
+ /* add to window */
+ llist_add_tail(&wlc->list, &fl1h->wlc_list);
+ fl1h->wlc_list_len++;
+
+ if (wlc != recent) {
+ LOGP(DL1C, LOGL_INFO, "Txing formerly postponed "
+ "command %s (trans_id=%u)\n",
+ get_value_string(octphy_cid_vals, wlc->prim_id),
+ wlc->trans_id);
+ }
+ msg = msgb_copy(wlc->cmd_msg, "Tx from wlc_postponed");
+ /* queue for execution and response handling */
+ if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n");
+ llist_del(&wlc->list);
+ msgb_free(msg);
+ exit(24);
+ }
+ /* schedule a timer for CMD_TIMEOUT seconds. If PHY fails to
+ * respond, we terminate */
+ osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0);
+
+ }
+}
+
+/* send a request(command) to L1, scheduling a call-back to be executed
+ * on receiving the response*/
+int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ struct wait_l1_conf *wlc;
+
+ /* assume that there is a VC1 Message header and that it
+ * contains a command ID in network byte order */
+ tOCTVC1_MSG_HEADER *msg_hdr = (tOCTVC1_MSG_HEADER *) msg->l2h;
+ uint32_t type_r_cmdid = ntohl(msg_hdr->ul_Type_R_CmdId);
+ uint32_t cmd_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & cOCTVC1_MSG_ID_BIT_MASK;
+
+ LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s, trans_id=%u)\n",
+ msgb_length(msg), octvc1_id2string(cmd_id),
+ ntohl(msg_hdr->ulTransactionId));
+
+ /* push the two common headers in front */
+ octvocnet_push_ctl_hdr(msg, cOCTVC1_FIFO_ID_MGW_CONTROL,
+ cPKTAPI_FIFO_ID_MSG, fl1h->socket_id);
+ octpkt_push_common_hdr(msg, cOCTVOCNET_PKT_FORMAT_CTRL, 0,
+ cOCTPKT_HDR_CONTROL_PROTOCOL_TYPE_ENUM_OCTVOCNET);
+
+ wlc = talloc_zero(fl1h, struct wait_l1_conf);
+ wlc->cmd_msg = msg;
+ wlc->cb = cb;
+ wlc->cb_data = data;
+ wlc->prim_id = cmd_id;
+ wlc->trans_id = ntohl(msg_hdr->ulTransactionId);
+ wlc->timer.data = wlc;
+ wlc->timer.cb = l1if_req_timeout;
+
+ /* unconditionally add t to the tail of postponed commands */
+ llist_add_tail(&wlc->list, &fl1h->wlc_postponed);
+ fl1h->wlc_postponed_len++;
+
+ /* check if the unacknowledged window has some space to transmit */
+ check_refill_window(fl1h, wlc);
+
+ /* if any messages are in the queue, it must be at least 'our' message,
+ * as we always enqueue from the tail */
+ if (fl1h->wlc_postponed_len) {
+ fl1h->stats.wlc_postponed++;
+ LOGP(DL1C, LOGL_INFO, "Postponed command %s (trans_id=%u)\n",
+ get_value_string(octphy_cid_vals, cmd_id), wlc->trans_id);
+ }
+
+ return 0;
+}
+
+/* For OctPHY, this only about sending state changes to BSC */
+int l1if_activate_rf(struct gsm_bts_trx *trx, int on)
+{
+ int i;
+ if (on) {
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_DEPENDENCY);
+ } else {
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+ }
+
+ return 0;
+}
+
+static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ return GSM_PCHAN_TCH_F;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ default:
+ return ts->pchan;
+ }
+}
+
+static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
+ tOCTVC1_GSM_SAPI_ENUM sapi, uint8_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ enum gsm_phys_chan_config pchan = pick_pchan(ts);
+
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ switch (sapi) {
+ case cOCTVC1_GSM_SAPI_ENUM_BCCH:
+ cbits = 0x10;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_CBCH:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_SACCH:
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ cbits = 0x02 + subCh;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", pchan);
+ return 0;
+ }
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_SDCCH:
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", pchan);
+ return 0;
+ }
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH:
+ cbits = 0x12;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_TCHF:
+ cbits = 0x01;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_TCHH:
+ cbits = 0x02 + subCh;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_FACCHF:
+ cbits = 0x01;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_FACCHH:
+ cbits = 0x02 + subCh;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_PDTCH:
+ case cOCTVC1_GSM_SAPI_ENUM_PACCH:
+ switch (pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", pchan);
+ return 0;
+ }
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_PTCCH:
+ if (!L1SAP_IS_PTCCH(u32Fn)) {
+ LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
+ "number other than 12, got it at %u (%u). "
+ "Please fix!\n", u32Fn % 52, u32Fn);
+ abort();
+ }
+ switch (pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", pchan);
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+ return ((cbits << 3) | u8Tn);
+}
+
+static void data_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req,
+ const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind)
+{
+ data_req->TrxId = rts_ind->TrxId;
+ data_req->LchId = rts_ind->LchId;
+ data_req->Data.ulFrameNumber = rts_ind->ulFrameNumber;
+ data_req->Data.ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_NONE;
+}
+
+#if 0
+static void empty_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD * empty_req,
+ const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind)
+{
+ empty_req->TrxId = rts_ind->TrxId;
+ empty_req->LchId = rts_ind->LchId;
+ empty_req->ulFrameNumber = rts_ind->ulFrameNumber;
+}
+#endif
+
+/***********************************************************************
+ * handle messages coming down from generic part
+ ***********************************************************************/
+
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *l1msg = NULL;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+ int rc;
+
+ if (!msg) {
+ LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "L1SAP PH-DATA.req without msg. "
+ "Please fix!\n");
+ abort();
+ }
+
+ len = msgb_l2len(msg);
+
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ u32Fn = l1sap->u.data.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ subCh = 0xf1;
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_SACCH;
+ if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (ts_is_pdch(&trx->ts[u8Tn])) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_PTCCH;
+ } else {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_PDTCH;
+ }
+ } else {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHF;
+ }
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHH;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_BCCH;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_CBCH;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH;
+ } else {
+ LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d chan_nr %d link_id %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ if (len) {
+ /* create new PHY primitive in l1msg, copying payload */
+
+ l1msg = l1p_msgb_alloc();
+ if (!l1msg) {
+ LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-DATA.req msg alloc failed\n");
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req =
+ (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *)
+ msgb_put(l1msg, sizeof(*data_req));
+
+ l1if_fill_msg_hdr(&data_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID);
+
+ data_req->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ data_req->LchId.byTimeslotNb = u8Tn;
+ data_req->LchId.bySAPI = sapi;
+ data_req->LchId.bySubChannelNb = subCh;
+ data_req->LchId.byDirection = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS;
+ data_req->Data.ulFrameNumber = u32Fn;
+ data_req->Data.ulDataLength = msgb_l2len(msg);
+ memcpy(data_req->Data.abyDataContent, msg->l2h, msgb_l2len(msg));
+
+ mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req);
+ } else {
+ /* No data available, Don't send Empty frame to PHY */
+ rc = 0;
+ goto done;
+ }
+
+ rc = l1if_req_compl(fl1h, l1msg, NULL, NULL);
+done:
+ return rc;
+}
+
+
+static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, sapi;
+ uint8_t chan_nr;
+ struct msgb *nmsg = NULL;
+
+ chan_nr = l1sap->u.tch.chan_nr;
+ u32Fn = l1sap->u.tch.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = cOCTVC1_GSM_SAPI_ENUM_TCHH;
+ } else {
+ subCh = 0xf1;
+ sapi = cOCTVC1_GSM_SAPI_ENUM_TCHF;
+ }
+
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ /* create new message and fill data */
+ if (msg) {
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg) {
+ LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-TCH.req msg alloc failed\n");
+ return -ENOMEM;
+ }
+
+ msgb_pull(msg, sizeof(*l1sap));
+ tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req =
+ (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *)
+ msgb_put(nmsg, sizeof(*data_req));
+
+ mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req);
+
+ l1if_fill_msg_hdr(&data_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID);
+
+ data_req->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ data_req->LchId.byTimeslotNb = u8Tn;
+ data_req->LchId.bySAPI = sapi;
+ data_req->LchId.bySubChannelNb = subCh;
+ data_req->LchId.byDirection =
+ cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS;
+ data_req->Data.ulFrameNumber = u32Fn;
+
+ l1if_tch_encode(lchan,
+ &data_req->Data.ulPayloadType,
+ data_req->Data.abyDataContent,
+ &data_req->Data.ulDataLength,
+ msg->data, msg->len);
+
+ mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req);
+ } else {
+ /* No data available, Don't send Empty frame to PHY */
+ return 0;
+ }
+
+ return l1if_req_compl(fl1h, nmsg, NULL, NULL);
+}
+
+static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ uint8_t chan_nr;
+ struct gsm_lchan *lchan;
+ int rc = 0;
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink) {
+ l1if_set_ciphering(lchan, 0);
+ lchan->ciph_state = LCHAN_CIPH_RX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink) {
+ l1if_set_ciphering(lchan, 1);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink
+ && l1sap->u.info.u.ciph_req.uplink)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
+ l1if_rsl_chan_act(lchan);
+ else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+#pragma message ("Mode Modify is currently not supported for Octasic PHY (OS#3015)")
+ /* l1if_rsl_mode_modify(lchan); */
+ } else if (l1sap->u.info.u.act_req.sacch_only)
+ l1if_rsl_deact_sacch(lchan);
+ else
+ l1if_rsl_chan_rel(lchan);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown L1SAP MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* primitive from common part. We are taking ownership of msgb */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ /* called functions MUST NOT take ownership of msgb, as it is
+ * free()d below */
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ rc = ph_data_req(trx, msg, l1sap);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ rc = ph_tch_req(trx, msg, l1sap);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ rc = mph_info_req(trx, msg, l1sap);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "L1SAP unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int trx_close_all_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car =
+ (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car);
+
+ /* we now know that the PHY link is connected */
+ phy_link_state_set(fl1->phy_link, PHY_LINK_CONNECTED);
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int phy_link_trx_close_all(struct phy_link *plink)
+{
+ struct octphy_hdl *fl1h = plink->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac;
+
+ cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *)
+ msgb_put(msg, sizeof(*cac));
+ l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID);
+
+ mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac);
+
+ return l1if_req_compl(fl1h, msg, trx_close_all_cb, NULL);
+}
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ if (plink->u.octphy.hdl)
+ l1if_close(plink->u.octphy.hdl);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ plink->u.octphy.hdl = l1if_open(plink);
+ if (!plink->u.octphy.hdl) {
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ return -1;
+ }
+
+ /* do we need to iterate over the list of instances and do some
+ * instance-specific initialization? */
+
+ /* close all TRXs that might still exist in this link from
+ * previous execitions / sessions */
+ phy_link_trx_close_all(plink);
+
+ /* in the call-back to the above we will set the link state to
+ * connected */
+
+ return 0;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ LOGP(DL1C, LOGL_NOTICE, "model_init()\n");
+
+ bts->variant = BTS_OSMO_OCTPHY;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ /* FIXME: what is the nominal transmit power of the PHY/board? */
+ bts->c0->nominal_power = 15;
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4) && defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8)
+ gsm_bts_set_feature(bts, BTS_FEAT_CBCH);
+#endif
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+/***********************************************************************
+ * handling of messages coming up from PHY
+ ***********************************************************************/
+
+/* When the measurement indication is received from the phy, the phy will
+ * automatically stamp it with the frame number that matches the frame
+ * number of the SACCH channel that marks the end of the measurement
+ * period. (e.g. fn%104=90, on a TCH/H, TS0). However, the upper layers
+ * expect the frame number to be aligned to the next SACCH frame after,
+ * after the end of the measurement period that has just passed. (e.g.
+ * (fn%104=10, on a TCH/H, TS0). The following function remaps the frame
+ * number in order to match the higher layers expectations.
+ * See also: 3GPP TS 05.02 Clause 7 Table 1 of 9 Mapping of logical channels
+ * onto physical channels (see subclauses 6.3, 6.4, 6.5) */
+static uint32_t translate_tch_meas_rep_fn104_reverse(uint32_t fn)
+{
+ uint8_t new_fn_mod;
+ uint8_t fn_mod;
+
+ fn_mod = fn % 104;
+
+ switch (fn_mod) {
+ case 103:
+ new_fn_mod = 25;
+ break;
+ case 12:
+ new_fn_mod = 38;
+ break;
+ case 25:
+ new_fn_mod = 51;
+ break;
+ case 38:
+ new_fn_mod = 64;
+ break;
+ case 51:
+ new_fn_mod = 77;
+ break;
+ case 64:
+ new_fn_mod = 90;
+ break;
+ case 77:
+ new_fn_mod = 103;
+ break;
+ case 90:
+ new_fn_mod = 12;
+ break;
+ default:
+ /* No translation for frame numbers
+ * fall out of the raster */
+ new_fn_mod = fn_mod;
+ }
+
+ return (fn - fn_mod) + new_fn_mod;
+}
+
+static unsigned int oct_meas2ber10k(const tOCTVC1_GSM_MEASUREMENT_INFO *m)
+{
+ if (m->usBERTotalBitCnt != 0) {
+ return (unsigned int)((m->usBERCnt * BER_10K) / m->usBERTotalBitCnt);
+ } else {
+ return 0;
+ }
+}
+
+static int oct_meas2rssi_dBm(const tOCTVC1_GSM_MEASUREMENT_INFO *m)
+{
+ /* rssi is in q8 format */
+ return (m->sRSSIDbm >> 8);
+}
+
+static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ uint32_t fn, uint32_t data_len,
+ tOCTVC1_GSM_MEASUREMENT_INFO * m)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_MEAS;
+ l1sap.u.info.u.meas_ind.chan_nr = chan_nr;
+
+ /* Update Timing offset for valid radio block */
+ if (data_len != 0) {
+ /* burst timing in 1x */
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = m->sBurstTiming4x*64;
+ } else {
+ /* FIXME, In current implementation, OCTPHY would send DATA_IND
+ * for all radio blocks (valid or invalid) But timing offset
+ * is only correct for valid block. so we need different
+ * counter to accumulate Timing offset.. even we add zero for
+ * invalid block.. timing offset average calucation would not
+ * correct. */
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = 0;
+ }
+
+ l1sap.u.info.u.meas_ind.ber10k = oct_meas2ber10k(m);
+
+ /* rssi is in q8 format */
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) oct_meas2rssi_dBm(m);
+
+ /* copy logical frame number to MEAS IND data structure */
+ l1sap.u.info.u.meas_ind.fn = translate_tch_meas_rep_fn104_reverse(fn);
+
+ /* l1sap wants to take msgb ownership. However, as there is no
+ * msg, it will msgb_free(l1sap.oph.msg == NULL) */
+ l1sap_up(trx, &l1sap);
+}
+
+static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m)
+{
+ LOGP(DMEAS, ll,
+ "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits :%d, "
+ "BER Error Count %d , BER Toatal Bit count %d in last decoded frame\n",
+ m->sRSSIDbm, m->sBurstTiming, m->sBurstTiming4x, m->usBERCnt,
+ m->usBERTotalBitCnt);
+}
+
+static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn)
+{
+ struct gsm_bts_trx *trx = trx_by_l1h(fl1, trx_id);
+ struct osmo_phsap_prim l1sap;
+
+ /* increment the primitive count for the alive timer */
+ fl1->alive_prim_cnt++;
+
+ /* ignore every time indication, except for c0 */
+ if (trx != trx->bts->c0)
+ return 0;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_TIME;
+ l1sap.u.info.u.time_ind.fn = fn;
+
+ l1sap_up(trx, &l1sap);
+
+ return 0;
+}
+
+static int handle_ph_readytosend_ind(struct octphy_hdl *fl1,
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = trx_by_l1h(fl1, evt->TrxId.byTrxId);
+ struct gsm_bts *bts = trx->bts;
+ struct osmo_phsap_prim *l1sap;
+ struct gsm_time g_time;
+ uint8_t chan_nr, link_id;
+ uint32_t fn;
+ int rc;
+ uint32_t t3p;
+ uint8_t ts_num, sc, sapi;
+
+ struct msgb *resp_msg;
+ tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req;
+
+ /* Retrive the data */
+ fn = evt->ulFrameNumber;
+ ts_num = (uint8_t) evt->LchId.byTimeslotNb;
+ sc = (uint8_t) evt->LchId.bySubChannelNb;
+ sapi = (uint8_t) evt->LchId.bySAPI;
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
+ get_value_string(octphy_l1sapi_names, sapi));
+
+ /* in case we need to forward primitive to common part */
+ chan_nr = chan_nr_by_sapi(&trx->ts[ts_num], sapi, sc, ts_num, fn);
+ if (chan_nr) {
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF
+ || sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ } else {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ }
+
+ l1sap_up(trx, l1sap);
+
+ /* return '1' to indicate l1sap_up has taken msgb ownership */
+ return 1;
+ }
+
+ /* in all other cases, we need to allocate a new PH-DATA.ind
+ * primitive msgb and start to fill it */
+ resp_msg = l1p_msgb_alloc();
+ data_req = (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *)
+ msgb_put(resp_msg, sizeof(*data_req));
+
+ mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req);
+
+ l1if_fill_msg_hdr(&data_req->Header, resp_msg, fl1, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID);
+
+ data_req_from_rts_ind(data_req, evt);
+
+ switch (sapi) {
+ /* TODO: SCH via L1SAP */
+ case cOCTVC1_GSM_SAPI_ENUM_SCH:
+ /* compute T3prime */
+ t3p = (g_time.t3 - 1) / 10;
+ /* fill SCH burst with data */
+ data_req->Data.ulDataLength = 4;
+ data_req->Data.abyDataContent[0] =
+ (bts->bsic << 2) | (g_time.t1 >> 9);
+ data_req->Data.abyDataContent[1] = (g_time.t1 >> 1);
+ data_req->Data.abyDataContent[2] =
+ (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
+ data_req->Data.abyDataContent[3] = (t3p & 1);
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_PRACH:
+#if 0
+ /* in case we decide to send an empty frame... */
+
+ tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD
+ *empty_frame_req =
+ (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD
+ *) msgSendBuffer;
+
+ empty_req_from_rts_ind(empty_frame_req, evt);
+
+ /* send empty frame request */
+ rc = Logical_Channel_Empty_Frame_Cmd(empty_frame_req);
+ if (cOCTVC1_RC_OK != rc) {
+ LOGPGT(DL1P, LOGL_ERROR, &g_time,
+ "Sending Empty Frame Request Failed! (%s)\n",
+ octvc1_rc2string(rc));
+ }
+ break;
+#endif
+ default:
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "SAPI %s not handled via L1SAP!\n",
+ get_value_string(octphy_l1sapi_names, sapi));
+#if 0
+ data_req->Data.ulDataLength = GSM_MACBLOCK_LEN;
+ memcpy(data_req->Data.abyDataContent, fill_frame,
+ GSM_MACBLOCK_LEN);
+#endif
+ break;
+ }
+
+ mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req);
+
+ return l1if_req_compl(fl1, resp_msg, NULL, NULL);
+}
+
+static int handle_ph_data_ind(struct octphy_hdl *fl1,
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = trx_by_l1h(fl1, data_ind->TrxId.byTrxId);
+ uint8_t chan_nr, link_id;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ uint8_t *data;
+ uint16_t len;
+ int16_t snr;
+ int rc;
+
+ uint8_t sapi = (uint8_t) data_ind->LchId.bySAPI;
+ uint8_t ts_num = (uint8_t) data_ind->LchId.byTimeslotNb;
+ uint8_t sc = (uint8_t) data_ind->LchId.bySubChannelNb;
+
+ /* Need to combine two 16bit MSB and LSB to form 32bit FN */
+ fn = data_ind->Data.ulFrameNumber;
+
+ /* chan_nr and link_id */
+ chan_nr = chan_nr_by_sapi(&trx->ts[ts_num], sapi, sc, ts_num, fn);
+ if (!chan_nr) {
+ LOGPFN(DL1C, LOGL_ERROR, fn, "Rx PH-DATA.ind for unknown L1 SAPI %s\n",
+ get_value_string(octphy_l1sapi_names, sapi));
+ return ENOTSUP;
+ }
+
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+
+ /* uplink measurement */
+ process_meas_res(trx, chan_nr, fn, data_ind->Data.ulDataLength,
+ &data_ind->MeasurementInfo);
+
+ DEBUGPFN(DL1C, fn, "Rx PH-DATA.ind %s: %s data_len:%d \n",
+ get_value_string(octphy_l1sapi_names, sapi),
+ osmo_hexdump(data_ind->Data.abyDataContent, data_ind->Data.ulDataLength),
+ data_ind->Data.ulDataLength);
+
+ /* check for TCH */
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF ||
+ sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) {
+ /* TCH speech frame handling */
+ rc = l1if_tch_rx(trx, chan_nr, data_ind);
+ return rc;
+ }
+
+ /* get data pointer and length */
+ data = data_ind->Data.abyDataContent;
+ len = data_ind->Data.ulDataLength;
+ /* pull lower header part before data */
+ msgb_pull(l1p_msg, data - l1p_msg->data);
+ /* trim remaining data to it's size, to get rid of upper header part */
+ rc = msgb_trim(l1p_msg, len);
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1p_msg->l2h = l1p_msg->data;
+ /* push new l1 header */
+ l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap));
+ /* fill header */
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+
+#if (cOCTVC1_MAIN_VERSION_ID >= cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG)
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_PDTCH) {
+ /* FIXME::PCU is expecting encode frame number*/
+ l1sap->u.data.fn = fn - 3;
+ } else
+ l1sap->u.data.fn = fn;
+#else
+ l1sap->u.data.fn = fn;
+#endif
+
+ l1sap->u.data.rssi = oct_meas2rssi_dBm(&data_ind->MeasurementInfo);
+ l1sap->u.data.ber10k = oct_meas2ber10k(&data_ind->MeasurementInfo);
+
+ /* burst timing in 1x but PCU is expecting 4X */
+ l1sap->u.data.ta_offs_256bits = data_ind->MeasurementInfo.sBurstTiming4x*64;
+ snr = data_ind->MeasurementInfo.sSNRDb;
+ /* FIXME: better converion formulae for SnR -> C / I?
+ l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 10 / 256;
+ LOGP(DL1C, LOGL_ERROR, "SnR: raw %d, computed %d\n", snr, l1sap->u.data.lqual_cb);
+ */
+ l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 100;
+ l1sap->u.data.pdch_presence_info = PRES_INFO_BOTH; /* FIXME: consider EDGE support */
+
+ l1sap_up(trx, l1sap);
+
+ /* return '1' to indicate that l1sap_up has taken msgb ownership */
+ return 1;
+}
+
+static int handle_ph_rach_ind(struct octphy_hdl *fl1,
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = trx_by_l1h(fl1, ra_ind->TrxId.byTrxId);
+ struct osmo_phsap_prim *l1sap;
+ int rc;
+ struct ph_rach_ind_param rach_ind_param;
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo);
+
+ if (ra_ind->ulMsgLength != 1) {
+ LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber,
+ "Rx PH-RACH.ind has lenghth %d > 1\n", ra_ind->ulMsgLength);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
+ rach_ind_param = (struct ph_rach_ind_param) {
+ /* .chan_nr set below */
+ .ra = ra_ind->abyMsg[0],
+ /* .acc_delay set below */
+ .fn = ra_ind->ulFrameNumber,
+ .is_11bit = 0,
+ /* .burst_type remains unset */
+ .rssi = oct_meas2rssi_dBm(&ra_ind->MeasurementInfo),
+ .ber10k = oct_meas2ber10k(&ra_ind->MeasurementInfo),
+ .acc_delay_256bits = ra_ind->MeasurementInfo.sBurstTiming4x * 64,
+ };
+
+ if (ra_ind->LchId.bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL &&
+ ra_ind->LchId.bySAPI == cOCTVC1_GSM_SAPI_ENUM_RACH) {
+ rach_ind_param.chan_nr = 0x88;
+ } else {
+ struct gsm_lchan *lchan = get_lchan_by_lchid(trx, &ra_ind->LchId);
+ rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);
+ }
+
+ /* check for under/overflow / sign */
+ if (ra_ind->MeasurementInfo.sBurstTiming < 0)
+ rach_ind_param.acc_delay = 0;
+ else
+ rach_ind_param.acc_delay = ra_ind->MeasurementInfo.sBurstTiming;
+
+ /* msgb_trim() invalidates ra_ind, make that abundantly clear: */
+ ra_ind = NULL;
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ l1p_msg);
+ l1sap->u.rach_ind = rach_ind_param;
+
+ l1sap_up(trx, l1sap);
+
+ /* return '1' to indicate l1sap_up has taken msgb ownership */
+ return 1;
+}
+
+static int rx_gsm_trx_time_ind(struct msgb *msg)
+{
+ struct octphy_hdl *fl1h = msg->dst;
+ tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *tind =
+ (tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *) msg->l2h;
+
+ mOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT_SWAP(tind);
+
+ return handle_mph_time_ind(fl1h, tind->TrxId.byTrxId, tind->ulFrameNumber);
+}
+
+/* mark this message as RETRANSMIT of a previous msg */
+static void msg_set_retrans_flag(struct msgb *msg)
+{
+ tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h;
+ uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId);
+ type_r_cmdid |= cOCTVC1_MSG_RETRANSMIT_FLAG;
+ mh->ul_Type_R_CmdId = htonl(type_r_cmdid);
+}
+
+/* re-transmit all commands in the window that have a transaction ID lower than
+ * trans_id */
+static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id)
+{
+ struct wait_l1_conf *wlc;
+ int count = 0;
+
+ LOGP(DL1C, LOGL_INFO, "Retransmitting up to trans_id=%u\n", trans_id);
+
+ /* trans_id represents the trans_id of the just-received response, we
+ * therefore need to re-send any commands with a lower trans_id */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (wlc->trans_id <= trans_id) {
+ struct msgb *msg;
+ if (wlc->num_retrans >= MAX_RETRANS) {
+ LOGP(DL1C, LOGL_ERROR, "Command %s: maximum "
+ "number of retransmissions reached\n",
+ get_value_string(octphy_cid_vals,
+ wlc->prim_id));
+ exit(24);
+ }
+ wlc->num_retrans++;
+ msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans");
+ msg_set_retrans_flag(msg);
+ osmo_wqueue_enqueue(&fl1h->phy_wq, msg);
+ osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0);
+ count++;
+ LOGP(DL1C, LOGL_INFO, "Re-transmitting %s "
+ "(trans_id=%u, attempt %u)\n",
+ get_value_string(octphy_cid_vals, wlc->prim_id),
+ wlc->trans_id, wlc->num_retrans);
+ }
+ }
+
+ return count;
+}
+
+/* Receive a response (to a prior command) from the PHY */
+static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id)
+{
+ tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h;
+ struct llist_head *first;
+ uint32_t return_code = ntohl(mh->ulReturnCode);
+ struct octphy_hdl *fl1h = msg->dst;
+ struct wait_l1_conf *wlc = NULL;
+ int rc;
+
+ LOGP(DL1C, LOGL_DEBUG, "rx_octvc1_resp(msg_id=%s, trans_id=%u)\n",
+ octvc1_rc2string(msg_id), trans_id);
+
+ /* check if the response is for the oldest (first) entry in wlc_list */
+ first = llist_first(&fl1h->wlc_list);
+ if (first) {
+ wlc = llist_entry(first, struct wait_l1_conf, list);
+ if (wlc->trans_id == trans_id) {
+ /* process the received response */
+ llist_del(&wlc->list);
+ fl1h->wlc_list_len--;
+ if (wlc->cb) {
+ /* call-back function must take msgb
+ * ownership. */
+ rc = wlc->cb(fl1h, msg, wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ /* check if there are postponed wlcs and re-fill the window */
+ check_refill_window(fl1h, NULL);
+ return rc;
+ }
+ }
+
+ LOGP(DL1C, LOGL_NOTICE, "Sequence error: Rx response (cmd=%s, trans_id=%u) "
+ "for cmd != oldest entry in window (trans_id=%u)!!\n",
+ get_value_string(octphy_cid_vals, msg_id), trans_id,
+ wlc ? wlc->trans_id : 0);
+
+ /* check if the response is for any of the other entries in wlc_list */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) {
+ /* it is assumed that all of the previous response
+ * message(s) have been lost, and we need to
+ * re-transmit older messages from the window */
+ rc = retransmit_wlc_upto(fl1h, trans_id);
+ fl1h->stats.retrans_cmds_trans_id += rc;
+ /* do not process the received response, we rather wait
+ * for the in-order retransmissions to arrive */
+ msgb_free(msg);
+ return 0;
+ }
+ }
+
+ /* ignore unhandled responses that went ok, but let the user know about
+ * failing ones. */
+ if (return_code != cOCTVC1_RC_OK) {
+ LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=%u)\n",
+ get_value_string(octphy_cid_vals, msg_id), trans_id);
+ }
+ msgb_free(msg);
+ return 0;
+
+}
+
+static int rx_gsm_clockmgr_status_ind(struct msgb *msg)
+{
+ struct octphy_hdl *fl1h = msg->dst;
+ tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *evt =
+ (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *) msg->l2h;
+ mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT_SWAP(evt);
+
+ LOGP(DL1C, LOGL_NOTICE, "Rx ClkMgr Status Change Event: "
+ "%s -> %s\n",
+ get_value_string(octphy_clkmgr_state_vals, evt->ulPreviousState),
+ get_value_string(octphy_clkmgr_state_vals, evt->ulState));
+
+ fl1h->clkmgr_state = evt->ulState;
+
+ return 0;
+}
+
+static int rx_gsm_trx_status_ind(struct msgb *msg)
+{
+ tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *evt =
+ (tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *) msg->l2h;
+
+ mOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT_SWAP(evt);
+
+ if (evt->ulStatus == cOCTVC1_GSM_TRX_STATUS_ENUM_RADIO_READY)
+ LOGP(DL1C, LOGL_INFO, "Rx TRX Status Event: READY\n");
+ else
+ LOGP(DL1C, LOGL_ERROR, "Rx TRX Status Event: %u\n",
+ evt->ulStatus);
+
+ return 0;
+}
+
+/* DATA indication from PHY */
+static int rx_gsm_trx_lchan_data_ind(struct msgb *msg)
+{
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *evt =
+ (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *) msg->l2h;
+ mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT_SWAP(evt);
+
+ return handle_ph_data_ind(msg->dst, evt, msg);
+}
+
+/* Ready-to-Send indication from PHY */
+static int rx_gsm_trx_rts_ind(struct msgb *msg)
+{
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt =
+ (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *) msg->l2h;
+ mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT_SWAP(evt);
+
+ return handle_ph_readytosend_ind(msg->dst, evt, msg);
+}
+
+/* RACH receive indication from PHY */
+static int rx_gsm_trx_rach_ind(struct msgb *msg)
+{
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *evt =
+ (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *) msg->l2h;
+ mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT_SWAP(evt);
+
+ return handle_ph_rach_ind(msg->dst, evt, msg);
+}
+
+/* Receive a notification (indication) from PHY */
+static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id)
+{
+ const char *evt_name = get_value_string(octphy_eid_vals, msg_id);
+ struct octphy_hdl *fl1h = msg->dst;
+ int rc = 0;
+
+ if (!fl1h->opened) {
+ LOGP(DL1P, LOGL_NOTICE, "Rx NOTIF %s: Ignoring as PHY TRX "
+ "hasn't been re-opened yet\n", evt_name);
+ msgb_free(msg);
+ return 0;
+ }
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx NOTIF %s\n", evt_name);
+
+ /* called functions MUST NOT take ownership of the msgb,
+ * as it is free()d below - unless they return 1 */
+ switch (msg_id) {
+ case cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID:
+ rc = rx_gsm_trx_time_ind(msg);
+ break;
+ case cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EID:
+ rc = rx_gsm_clockmgr_status_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID:
+ rc = rx_gsm_trx_status_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID:
+ rc = rx_gsm_trx_lchan_data_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID:
+ rc = rx_gsm_trx_rts_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID:
+ rc = rx_gsm_trx_rach_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID:
+ LOGP(DL1P, LOGL_NOTICE, "Rx Unhandled event %s (%u)\n",
+ evt_name, msg_id);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "Rx Unknown event %s (%u)\n",
+ evt_name, msg_id);
+ }
+
+ /* Special return value '1' means: do not free */
+ if (rc != 1)
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int rx_octvc1_event_msg(struct msgb *msg)
+{
+ tOCTVC1_EVENT_HEADER *eh = (tOCTVC1_EVENT_HEADER *) msg->l2h;
+ uint32_t event_id = ntohl(eh->ulEventId);
+ uint32_t length = ntohl(eh->ulLength);
+ /* DO NOT YET SWAP HEADER HERE, as downstream functions want to
+ * swap it */
+
+ /* OCTSDKAN5001 Chapter 6.1 */
+ if (length < 12 || length > 1024) {
+ LOGP(DL1C, LOGL_ERROR, "Rx EVENT length %u invalid\n", length);
+ msgb_free(msg);
+ return -1;
+ }
+
+ /* verify / ensure length */
+ if (msgb_l2len(msg) < length) {
+ LOGP(DL1C, LOGL_ERROR, "Rx EVENT msgb_l2len(%u) < "
+ "event_msg_length (%u)\n", msgb_l2len(msg), length);
+ msgb_free(msg);
+ return -1;
+ }
+
+ return rx_octvc1_notif(msg, event_id);
+}
+
+/* Receive a supervisory message from the PHY */
+static int rx_octvc1_supv(struct msgb *msg, uint32_t msg_id, uint32_t trans_id)
+{
+ struct octphy_hdl *fl1h = msg->dst;
+ tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h;
+ tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *rej;
+ uint32_t return_code = ntohl(mh->ulReturnCode);
+ uint32_t rejected_msg_id;
+ int rc;
+
+ switch (msg_id) {
+ case cOCTVC1_CTRL_MSG_MODULE_REJECT_SID:
+ rej = (tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *) mh;
+ mOCTVC1_CTRL_MSG_MODULE_REJECT_SPV_SWAP(rej);
+ rejected_msg_id = (rej->ulRejectedCmdId >> cOCTVC1_MSG_ID_BIT_OFFSET) &
+ cOCTVC1_MSG_ID_BIT_MASK;
+ LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (TID=%u, "
+ "ExpectedTID=0x%08x, RejectedCmdID=%s)\n",
+ trans_id, rej->ulExpectedTransactionId,
+ get_value_string(octphy_cid_vals, rejected_msg_id));
+ rc = retransmit_wlc_upto(fl1h, trans_id);
+ fl1h->stats.retrans_cmds_supv += rc;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "Rx unhandled supervisory msg_id "
+ "%u: ReturnCode:%u\n", msg_id, return_code);
+ break;
+ }
+
+ return 0;
+}
+
+static int rx_octvc1_ctrl_msg(struct msgb *msg)
+{
+ tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h;
+ uint32_t length = ntohl(mh->ulLength);
+ uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId);
+ uint32_t msg_type = (type_r_cmdid >> cOCTVC1_MSG_TYPE_BIT_OFFSET) &
+ cOCTVC1_MSG_TYPE_BIT_MASK;
+ uint32_t msg_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) &
+ cOCTVC1_MSG_ID_BIT_MASK;
+ uint32_t return_code = ntohl(mh->ulReturnCode);
+ const char *msg_name = get_value_string(octphy_cid_vals, msg_id);
+
+ /* DO NOT YET SWAP HEADER HERE, as downstream functions want to
+ * swap it */
+
+ /* FIXME: OCTSDKAN5001 Chapter 3.1 states max size is 1024, but we see
+ * larger messages in practise */
+ if (length < 24 || length > 2048) {
+ LOGP(DL1C, LOGL_ERROR, "Rx CTRL length %u invalid\n", length);
+ msgb_free(msg);
+ return -1;
+ }
+
+ /* verify / ensure length */
+ if (msgb_l2len(msg) < length) {
+ LOGP(DL1C, LOGL_ERROR, "Rx CTRL msgb_l2len(%u) < "
+ "ctrl_msg_length (%u)\n", msgb_l2len(msg), length);
+ msgb_free(msg);
+ return -1;
+ }
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx %s.resp (rc=%s(%x))\n", msg_name,
+ octvc1_rc2string(return_code), return_code);
+
+ if (return_code != cOCTVC1_RC_OK) {
+ LOGP(DL1P, LOGL_ERROR, "%s failed, rc=%s\n",
+ msg_name, octvc1_rc2string(return_code));
+ }
+
+ /* called functions must take ownership of msgb */
+ switch (msg_type) {
+ case cOCTVC1_MSG_TYPE_RESPONSE:
+ return rx_octvc1_resp(msg, msg_id, ntohl(mh->ulTransactionId));
+ case cOCTVC1_MSG_TYPE_SUPERVISORY:
+ return rx_octvc1_supv(msg, msg_id, ntohl(mh->ulTransactionId));
+ case cOCTVC1_MSG_TYPE_NOTIFICATION:
+ case cOCTVC1_MSG_TYPE_COMMAND:
+ LOGP(DL1C, LOGL_NOTICE, "Rx unhandled msg_type %s (%u)\n",
+ msg_name, msg_type);
+ msgb_free(msg);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "Rx unknown msg_type %s (%u)\n",
+ msg_name, msg_type);
+ msgb_free(msg);
+ }
+
+ return 0;
+}
+
+static int rx_octvc1_data_f_msg(struct msgb *msg)
+{
+ tOCTVOCNET_PKT_DATA_F_HEADER *datafh =
+ (tOCTVOCNET_PKT_DATA_F_HEADER *) msg->l2h;
+ uint32_t log_obj_port = ntohl(datafh->VocNetHeader.ulLogicalObjPktPort);
+
+ msg->l2h = (uint8_t *) datafh + sizeof(*datafh);
+
+ if (log_obj_port ==
+ cOCTVOCNET_PKT_DATA_LOGICAL_OBJ_PKT_PORT_EVENT_SESSION) {
+ uint32_t sub_type = ntohl(datafh->ulSubType) & 0xF;
+ if (sub_type == cOCTVOCNET_PKT_SUBTYPE_API_EVENT) {
+ /* called function must take msgb ownership */
+ return rx_octvc1_event_msg(msg);
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Unknown DATA_F "
+ "subtype 0x%x\n", sub_type);
+ }
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Unknown logical object pkt port 0x%x\n",
+ log_obj_port);
+ }
+
+ msgb_free(msg);
+ return 0;
+}
+
+/* main receive routine for messages coming up from OCTPHY */
+static int rx_octphy_msg(struct msgb *msg)
+{
+ tOCTVOCNET_PKT_CTL_HEADER *ctlh;
+ int rc = 0;
+
+ /* we assume that the packets start right with the OCTPKT header
+ * and that the ethernet hardware header has already been
+ * stripped before */
+ msg->l1h = msg->data;
+
+ uint32_t ch = ntohl(*(uint32_t *) msg->data);
+ uint32_t format = (ch >> cOCTVOCNET_PKT_FORMAT_BIT_OFFSET)
+ & cOCTVOCNET_PKT_FORMAT_BIT_MASK;
+ uint32_t len = (ch >> cOCTVOCNET_PKT_LENGTH_BIT_OFFSET)
+ & cOCTVOCNET_PKT_LENGTH_MASK;
+
+ if (len > msgb_length(msg)) {
+ LOGP(DL1C, LOGL_ERROR, "Received length (%u) < length "
+ "as per packet header (%u): %s\n", msgb_length(msg),
+ len, osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+ msgb_free(msg);
+ return -1;
+ }
+
+ /* we first need to decode the common OCTPKT header and dispatch
+ * based on contrl (command/resp) or data (event=indication) */
+ switch (format) {
+ case cOCTVOCNET_PKT_FORMAT_CTRL:
+ ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4);
+ /* FIXME: check src/dest fifo, socket ID */
+ msg->l2h = (uint8_t *) ctlh + sizeof(*ctlh);
+ /* called function must take msgb ownership */
+ rc = rx_octvc1_ctrl_msg(msg);
+ break;
+ case cOCTVOCNET_PKT_FORMAT_F:
+ msg->l2h = msg->l1h + 4;
+ /* called function must take msgb ownership */
+ rc = rx_octvc1_data_f_msg(msg);
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "Rx Unknown pkt_format 0x%x\n",
+ format);
+ msgb_free(msg);
+ break;
+ }
+
+ return rc;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+ /* configure some reasonable defaults, to be overridden by VTY */
+ plink->u.octphy.rf_port_index = 0;
+ plink->u.octphy.rx_gain_db = 70;
+ plink->u.octphy.tx_atten_db = 0;
+ plink->u.octphy.over_sample_16x = true;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ pinst->u.octphy.trx_id = pinst->num;
+}
+
+/***********************************************************************
+ * octphy socket / main loop integration
+ ***********************************************************************/
+
+static int octphy_read_cb(struct osmo_fd *ofd)
+{
+ struct sockaddr_ll sll;
+ socklen_t sll_len = sizeof(sll);
+ int rc;
+ struct msgb *msg = msgb_alloc_headroom(1500, 24, "PHY Rx");
+
+ if (!msg)
+ return -ENOMEM;
+
+ /* this is the fl1h over which the message was received */
+ msg->dst = ofd->data;
+
+ rc = recvfrom(ofd->fd, msg->data, msgb_tailroom(msg), 0,
+ (struct sockaddr *) &sll, &sll_len);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Error in recvfrom(): %s\n",
+ strerror(errno));
+ msgb_free(msg);
+ return rc;
+ }
+ msgb_put(msg, rc);
+
+ return rx_octphy_msg(msg);
+}
+
+static int octphy_write_cb(struct osmo_fd *fd, struct msgb *msg)
+{
+ struct octphy_hdl *fl1h = fd->data;
+ int rc;
+
+ /* send the message down the socket */
+ rc = sendto(fd->fd, msg->data, msgb_length(msg), 0,
+ (struct sockaddr *) &fl1h->phy_addr,
+ sizeof(fl1h->phy_addr));
+
+ /* core write uqueue takes care of free() */
+ if (rc < 0) {
+ LOGP(DL1P, LOGL_ERROR, "Tx to PHY has failed: %s\n",
+ strerror(errno));
+ }
+
+ return rc;
+}
+
+struct octphy_hdl *l1if_open(struct phy_link *plink)
+{
+ struct octphy_hdl *fl1h;
+ struct ifreq ifr;
+ int sfd, rc;
+ char *phy_dev = plink->u.octphy.netdev_name;
+
+ fl1h = talloc_zero(plink, struct octphy_hdl);
+ if (!fl1h)
+ return NULL;
+
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+ INIT_LLIST_HEAD(&fl1h->wlc_postponed);
+ fl1h->phy_link = plink;
+
+ if (!phy_dev) {
+ LOGP(DL1C, LOGL_ERROR, "You have to specify a octphy net-device\n");
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ LOGP(DL1C, LOGL_NOTICE, "Opening L1 interface for OctPHY (%s)\n",
+ phy_dev);
+
+ sfd = osmo_sock_packet_init(SOCK_DGRAM, cOCTPKT_HDR_ETHERTYPE,
+ phy_dev, OSMO_SOCK_F_NONBLOCK);
+ if (sfd < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Error opening PHY socket: %s\n",
+ strerror(errno));
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ /* resolve the string device name to an ifindex */
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, phy_dev, sizeof(ifr.ifr_name));
+ rc = ioctl(sfd, SIOCGIFINDEX, &ifr);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Error using network device %s: %s\n",
+ phy_dev, strerror(errno));
+ close(sfd);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ fl1h->session_id = rand();
+
+ /* set fl1h->phy_addr, which we use as sendto() destination */
+ fl1h->phy_addr.sll_family = AF_PACKET;
+ fl1h->phy_addr.sll_protocol = htons(cOCTPKT_HDR_ETHERTYPE);
+ fl1h->phy_addr.sll_ifindex = ifr.ifr_ifindex;
+ fl1h->phy_addr.sll_hatype = ARPHRD_ETHER;
+ fl1h->phy_addr.sll_halen = ETH_ALEN;
+ /* plink->phy_addr.sll_addr is filled by bts_model_vty code */
+ memcpy(fl1h->phy_addr.sll_addr, plink->u.octphy.phy_addr.sll_addr,
+ ETH_ALEN);
+
+ /* Write queue / osmo_fd registration */
+ osmo_wqueue_init(&fl1h->phy_wq, 10);
+ fl1h->phy_wq.write_cb = octphy_write_cb;
+ fl1h->phy_wq.read_cb = octphy_read_cb;
+ fl1h->phy_wq.bfd.fd = sfd;
+ fl1h->phy_wq.bfd.when = BSC_FD_READ;
+ fl1h->phy_wq.bfd.cb = osmo_wqueue_bfd_cb;
+ fl1h->phy_wq.bfd.data = fl1h;
+ rc = osmo_fd_register(&fl1h->phy_wq.bfd);
+ if (rc < 0) {
+ close(sfd);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ return fl1h;
+}
+
+int l1if_close(struct octphy_hdl *fl1h)
+{
+ osmo_fd_unregister(&fl1h->phy_wq.bfd);
+ close(fl1h->phy_wq.bfd.fd);
+ talloc_free(fl1h);
+
+ return 0;
+}
diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h
new file mode 100644
index 00000000..09604822
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_if.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/if_packet.h>
+
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/timer.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+
+#include <octphy/octvc1/gsm/octvc1_gsm_api.h>
+
+#define BER_10K 10000
+
+struct octphy_hdl {
+ /* MAC address of the PHY */
+ struct sockaddr_ll phy_addr;
+
+ /* packet socket to talk with PHY */
+ struct osmo_wqueue phy_wq;
+
+ /* address parameters of the PHY */
+ uint32_t session_id;
+ uint32_t next_trans_id;
+ uint32_t socket_id;
+
+ /* clock manager state */
+ uint32_t clkmgr_state;
+
+ struct {
+ struct {
+ char *name;
+ char *description;
+ char *version;
+ } app;
+ struct {
+ char *platform;
+ char *version;
+ } system;
+ } info;
+
+ /* This is a list of outstanding commands sent to the PHY, for which we
+ * currently still wait for a response. Represented by 'struct
+ * wait_l1_conf' in l1_if.c - Octasic calls this the 'Unacknowledged
+ * Command Window' */
+ struct llist_head wlc_list;
+ int wlc_list_len;
+ struct {
+ /* messages retransmitted due to discontinuity of transaction
+ * ID in responses from PHY */
+ uint32_t retrans_cmds_trans_id;
+ /* messages retransmitted due to supervisory messages by PHY */
+ uint32_t retrans_cmds_supv;
+ /* number of commands/wlcs that we ever had to postpone */
+ uint32_t wlc_postponed;
+ } stats;
+
+ /* This is a list of wait_la_conf that OsmoBTS wanted to transmit to
+ * the PHY, but which couldn't yet been sent as the unacknowledged
+ * command window was full. */
+ struct llist_head wlc_postponed;
+ int wlc_postponed_len;
+
+ /* back pointer to the PHY link */
+ struct phy_link *phy_link;
+
+ struct osmo_timer_list alive_timer;
+ uint32_t alive_prim_cnt;
+
+ /* were we already (re)opened after OsmoBTS start */
+ int opened;
+};
+
+void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg,
+ struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd);
+
+typedef int l1if_compl_cb(struct octphy_hdl *fl1, struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data);
+
+#include <octphy/octvc1/gsm/octvc1_gsm_api.h>
+struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx,
+ tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id);
+
+struct octphy_hdl *l1if_open(struct phy_link *plink);
+int l1if_close(struct octphy_hdl *hdl);
+
+int l1if_trx_open(struct gsm_bts_trx *trx);
+int l1if_trx_close_all(struct gsm_bts *bts);
+int l1if_enable_events(struct gsm_bts_trx *trx);
+
+int l1if_activate_rf(struct gsm_bts_trx *trx, int on);
+
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *
+ data_ind);
+
+struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id);
+
+struct msgb *l1p_msgb_alloc(void);
+
+/* tch.c */
+void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type,
+ uint8_t *data, uint32_t *len, const uint8_t *rtp_pl,
+ unsigned int rtp_pl_len);
+
+tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM
+osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn);
diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c
new file mode 100644
index 00000000..d44f7211
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_oml.c
@@ -0,0 +1,1825 @@
+/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org>
+ *
+ * based on a copy of osmo-bts-sysmo/l1_oml.c, which is
+ * Copyright (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * Copyright (C) 2013-2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "l1_oml.h"
+#include "l1_utils.h"
+#include "octphy_hw_api.h"
+#include "btsconfig.h"
+
+#include <octphy/octvc1/octvc1_rc2string.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_api_swap.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_default.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_id.h>
+#include <octphy/octvc1/main/octvc1_main_default.h>
+#include <octphy/octvc1/main/octvc1_main_version.h>
+
+bool no_fw_check = 0;
+
+#define LOGPTRX(byTrxId, level, fmt, args...) \
+ LOGP(DL1C, level, "(byTrxId %u) " fmt, byTrxId, ## args)
+
+/* Map OSMOCOM logical channel type to OctPHY Logical channel type */
+static tOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM pchan_to_logChComb[_GSM_PCHAN_MAX] =
+{
+ [GSM_PCHAN_NONE] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY,
+ [GSM_PCHAN_CCCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH,
+ [GSM_PCHAN_CCCH_SDCCH4] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_SACCHC4,
+ [GSM_PCHAN_TCH_F] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF,
+ [GSM_PCHAN_TCH_H] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_SACCHC8,
+ // TODO - watch out below two!!!
+ [GSM_PCHAN_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF,
+ [GSM_PCHAN_TCH_F_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF,
+#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4,
+#endif
+#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8,
+#endif
+ [GSM_PCHAN_UNKNOWN] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY
+};
+
+enum sapi_cmd_type {
+ SAPI_CMD_ACTIVATE,
+ SAPI_CMD_CONFIG_CIPHERING,
+ SAPI_CMD_CONFIG_LOGCH_PARAM,
+ SAPI_CMD_SACCH_REL_MARKER,
+ SAPI_CMD_REL_MARKER,
+ SAPI_CMD_DEACTIVATE,
+};
+
+struct sapi_cmd {
+ struct llist_head entry;
+ tOCTVC1_GSM_SAPI_ENUM sapi;
+ tOCTVC1_GSM_DIRECTION_ENUM dir;
+ enum sapi_cmd_type type;
+ int (*callback) (struct gsm_lchan * lchan, int status);
+};
+
+struct sapi_dir {
+ tOCTVC1_GSM_SAPI_ENUM sapi;
+ tOCTVC1_GSM_DIRECTION_ENUM dir;
+};
+
+static const struct sapi_dir ccch_sapis[] = {
+ {cOCTVC1_GSM_SAPI_ENUM_FCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_BCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_RACH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+};
+
+static const struct sapi_dir tchf_sapis[] = {
+ {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+};
+
+static const struct sapi_dir tchh_sapis[] = {
+ {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+};
+
+static const struct sapi_dir sdcch_sapis[] = {
+ {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+};
+
+static const struct sapi_dir cbch_sapis[] = {
+ {cOCTVC1_GSM_SAPI_ENUM_CBCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ /* Does the CBCH really have a SACCH in Downlink */
+ {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+};
+
+static const struct sapi_dir pdtch_sapis[] = {
+ {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS},
+ {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS},
+};
+
+struct lchan_sapis {
+ const struct sapi_dir *sapis;
+ uint32_t num_sapis;
+};
+
+static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
+ [GSM_LCHAN_SDCCH] = {
+ .sapis = sdcch_sapis,
+ .num_sapis = ARRAY_SIZE(sdcch_sapis),
+ },
+ [GSM_LCHAN_TCH_F] = {
+ .sapis = tchf_sapis,
+ .num_sapis = ARRAY_SIZE(tchf_sapis),
+ },
+ [GSM_LCHAN_TCH_H] = {
+ .sapis = tchh_sapis,
+ .num_sapis = ARRAY_SIZE(tchh_sapis),
+ },
+ [GSM_LCHAN_CCCH] = {
+ .sapis = ccch_sapis,
+ .num_sapis = ARRAY_SIZE(ccch_sapis),
+ },
+ [GSM_LCHAN_PDTCH] = {
+ .sapis = pdtch_sapis,
+ .num_sapis = ARRAY_SIZE(pdtch_sapis),
+ },
+ [GSM_LCHAN_CBCH] = {
+ .sapis = cbch_sapis,
+ .num_sapis = ARRAY_SIZE(cbch_sapis),
+ },
+};
+
+static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
+
+extern uint8_t rach_detected_LA_g;
+extern uint8_t rach_detected_Other_g;
+
+static int opstart_compl(struct gsm_abis_mo *mo)
+{
+ /* TODO: Send NACK in case of error! */
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
+ mo->obj_inst.ts_nr == 7) {
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
+ mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ if (cbch) {
+ cbch->rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(cbch);
+ }
+ }
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(mo);
+}
+
+static
+tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM lchan_to_GsmL1_SubCh_t(const struct gsm_lchan
+ * lchan)
+{
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ if (lchan->type == GSM_LCHAN_CCCH)
+ return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL;
+ /* fall-through */
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ return (tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM) lchan->nr;
+ case GSM_PCHAN_NONE:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_UNKNOWN:
+ default:
+ return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL;
+ }
+ return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL;
+}
+
+static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config)
+{
+ /* common for the SIGN, V1 and EFR: */
+ int i;
+ p_Config->byCmiPhase = 0;
+ p_Config->byInitRate = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
+ /* 4 AMR active codec set */
+ for (i = 0; i < cOCTVC1_GSM_RATE_LIST_SIZE; i++)
+ p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
+}
+
+static void lchan2lch_par(struct gsm_lchan *lchan,
+ tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *)amr_mrc->gsm48_ie;
+ int j;
+
+ LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
+ gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ /* we have to set some TCH payload type even if we don't
+ * know yet what codec we will use later on */
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ clear_amr_params(p_Config);
+ }
+ break;
+
+ case GSM48_CMODE_SPEECH_V1:
+ clear_amr_params(p_Config);
+ break;
+
+ case GSM48_CMODE_SPEECH_EFR:
+ clear_amr_params(p_Config);
+ break;
+
+ case GSM48_CMODE_SPEECH_AMR:
+ p_Config->byCmiPhase = 1; /* FIXME? */
+ p_Config->byInitRate =
+ (tOCTVC1_GSM_AMR_CODEC_MODE_ENUM)
+ amr_get_initial_mode(lchan);
+
+ /* initialize to clean state */
+ for (j = 0; j < cOCTVC1_GSM_RATE_LIST_SIZE; j++)
+ p_Config->abyRate[j] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
+
+ j = 0;
+ if (mr_conf->m4_75)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_4_75;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m5_15)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_15;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m5_90)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_90;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m6_70)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_6_70;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m7_40)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_40;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m7_95)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_95;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m10_2)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_10_2;
+
+ if (j >= cOCTVC1_GSM_RATE_LIST_SIZE)
+ break;
+
+ if (mr_conf->m12_2)
+ p_Config->abyRate[j++] =
+ cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_12_2;
+ break;
+
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
+ gsm_lchan_name(lchan));
+ break;
+
+ }
+}
+
+/***********************************************************************
+ * CORE SAPI QUEUE HANDLING
+ ***********************************************************************/
+
+static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status);
+static void sapi_queue_send(struct gsm_lchan *lchan);
+
+static void sapi_clear_queue(struct llist_head *queue)
+{
+ struct sapi_cmd *next, *tmp;
+
+ llist_for_each_entry_safe(next, tmp, queue, entry) {
+ llist_del(&next->entry);
+ talloc_free(next);
+ }
+}
+
+static int lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *ar =
+ (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h;
+ struct gsm_bts_trx *trx;
+ struct gsm_lchan *lchan;
+ uint8_t sapi;
+ uint8_t direction;
+ uint8_t status;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar);
+ trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan activation\n");
+ return -EINVAL;
+ }
+
+ lchan = get_lchan_by_lchid(trx, &ar->LchId);
+ sapi = ar->LchId.bySAPI;
+ direction = ar->LchId.byDirection;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(octphy_l1sapi_names, sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(octphy_dir_names, direction));
+
+ if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) {
+ LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s\n",
+ get_value_string(octphy_l1sapi_names, sapi));
+ status = LCHAN_SAPI_S_ERROR;
+ } else {
+ status = LCHAN_SAPI_S_ASSIGNED;
+ }
+
+ switch (direction) {
+ case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS:
+ lchan->sapis_dl[sapi] = status;
+ break;
+ case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS:
+ lchan->sapis_ul[sapi] = status;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "Unknown direction %d\n",
+ ar->LchId.byDirection);
+ break;
+ }
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ar->Header.ulReturnCode);
+
+err:
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac;
+
+ lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *)
+ msgb_put(msg, sizeof(*lac));
+ l1if_fill_msg_hdr(&lac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID);
+
+ lac->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ lac->LchId.byTimeslotNb = lchan->ts->nr;
+ lac->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan);
+ lac->LchId.bySAPI = cmd->sapi;
+ lac->LchId.byDirection = cmd->dir;
+
+ lac->Config.byTimingAdvance = lchan->rqd_ta;
+ lac->Config.byBSIC = lchan->ts->trx->bts->bsic;
+
+ lchan2lch_par(lchan, &lac->Config);
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(octphy_l1sapi_names, cmd->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(octphy_dir_names, cmd->dir));
+
+ return l1if_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
+}
+
+
+static tOCTVC1_GSM_CIPHERING_ID_ENUM rsl2l1_ciph[] = {
+ [0] = cOCTVC1_GSM_CIPHERING_ID_ENUM_UNUSED,
+ [1] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_0,
+ [2] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_1,
+ [3] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_2,
+ [4] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_3
+};
+
+static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *pcr =
+ (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *) resp->l2h;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP_SWAP(pcr);
+
+ if (pcr->Header.ulReturnCode != cOCTVC1_RC_OK) {
+ LOGP(DL1C, LOGL_ERROR, "Error: Cipher Request Failed!\n\n");
+ LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n");
+ msgb_free(resp);
+ exit(-1);
+ }
+
+ trx = trx_by_l1h(fl1, pcr->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(pcr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during cipher mode activation\n");
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr);
+ ts = &trx->ts[pcr->PchId.byTimeslotNb];
+ /* for some strange reason the response does not tell which
+ * sub-channel, only th request contains this information :( */
+ lchan = &ts->lchan[(unsigned long) data];
+
+ /* TODO: This state machine should be shared accross BTS models? */
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_RX_REQ:
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ break;
+ case LCHAN_CIPH_RX_CONF_TX_REQ:
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ break;
+ case LCHAN_CIPH_RXTX_REQ:
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ break;
+ case LCHAN_CIPH_NONE:
+ break;
+ default:
+ LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
+ }
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+ sapi_queue_dispatch(lchan, pcr->Header.ulReturnCode);
+
+err:
+ msgb_free(resp);
+ return 0;
+}
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *pcc;
+
+ pcc = (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *)
+ msgb_put(msg, sizeof(*pcc));
+ l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID);
+
+ pcc->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ pcc->PchId.byTimeslotNb = lchan->ts->nr;
+ pcc->ulSubchannelNb = lchan_to_GsmL1_SubCh_t(lchan);
+ pcc->ulDirection = cmd->dir;
+ pcc->Config.ulCipherId = rsl2l1_ciph[lchan->encr.alg_id];
+ memcpy(pcc->Config.abyKey, lchan->encr.key, lchan->encr.key_len);
+
+ LOGP(DL1C, LOGL_INFO, "%s SET_CIPHERING (ALG=%u %s)\n",
+ gsm_lchan_name(lchan), pcc->Config.ulCipherId,
+ get_value_string(octphy_dir_names, pcc->ulDirection));
+
+ mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD_SWAP(pcc);
+
+ /* we have to save the lchan number in this strange way, as the
+ * PHY does not return the ulSubchannelNr in the response to
+ * this command */
+ return l1if_req_compl(fl1h, msg, set_ciph_compl_cb, (void *)(unsigned long) lchan->nr);
+}
+
+
+/**
+ * Queue and possible execute a SAPI command. Return 1 in case the command was
+ * already executed and 0 in case if it was only put into the queue
+ */
+static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ int start = llist_empty(&lchan->sapi_cmds);
+ llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
+
+ if (!start)
+ return 0;
+
+ sapi_queue_send(lchan);
+ return 1;
+}
+
+static int mph_info_chan_confirm(struct gsm_lchan *lchan,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(lchan->ts->trx, &l1sap);
+}
+
+static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
+{
+ /* FIXME: Error handling. There is no NACK... */
+ if (status != cOCTVC1_RC_OK && lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s is now broken. Stopping the release.\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ /* Don't send an REL ACK on SACCH deactivate */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return 0;
+}
+
+static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_DEACTIVATE;
+ cmd->callback = sapi_deactivate_cb;
+ return queue_sapi_command(lchan, cmd);
+}
+
+/*
+ * Release the SAPI if it was allocated. E.g. the SACCH might be already
+ * deactivated or during a hand-over the TCH was not allocated yet.
+ */
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ /* check if we should schedule a release */
+ if (dir == cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS) {
+ if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
+ } else if (dir == cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS) {
+ if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
+ }
+ /* now schedule the command and maybe dispatch it */
+ return enqueue_sapi_deact_cmd(lchan, sapi, dir);
+}
+
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ int i, res;
+
+ res = 0;
+
+ /* The order matters.. the Facch needs to be released first */
+ for (i = s4l->num_sapis - 1; i >= 0; i--) {
+ /* Stop the alive timer once we deactivate the SCH */
+ if (s4l->sapis[i].sapi == cOCTVC1_GSM_SAPI_ENUM_SCH)
+ osmo_timer_del(&fl1h->alive_timer);
+
+ /* Release if it was allocated */
+ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ }
+
+ /* nothing was queued */
+ if (res == 0) {
+ LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ }
+
+ return res;
+}
+
+static int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *ldr =
+ (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h;
+ struct gsm_bts_trx *trx;
+ struct gsm_lchan *lchan;
+ struct sapi_cmd *cmd;
+ uint8_t status;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr);
+ trx = trx_by_l1h(fl1, ldr->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(ldr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan deactivation\n");
+ return -EINVAL;
+ }
+
+ lchan = get_lchan_by_lchid(trx, &ldr->LchId);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(octphy_dir_names, ldr->LchId.byDirection));
+
+ if (ldr->Header.ulReturnCode == cOCTVC1_RC_OK) {
+ DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
+ get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI),
+ ldr->LchId.byTimeslotNb);
+ status = LCHAN_SAPI_S_NONE;
+ } else {
+ LOGP(DL1C, LOGL_ERROR,
+ "Error deactivating L1 SAPI %s on TS %u\n",
+ get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI),
+ ldr->LchId.byTimeslotNb);
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ switch (ldr->LchId.byDirection) {
+ case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS:
+ lchan->sapis_dl[ldr->LchId.bySAPI] = status;
+ break;
+ case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS:
+ lchan->sapis_ul[ldr->LchId.bySAPI] = status;
+ break;
+ }
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got de-activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ldr->LchId.bySAPI ||
+ cmd->dir != ldr->LchId.byDirection ||
+ cmd->type != SAPI_CMD_DEACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ldr->LchId.bySAPI, ldr->LchId.byDirection);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, status);
+
+err:
+ msgb_free(resp);
+ return 0;
+}
+
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *ldc;
+
+ ldc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *)
+ msgb_put(msg, sizeof(*ldc));
+ l1if_fill_msg_hdr(&ldc->Header, msg, fl1h,cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID);
+
+ ldc->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ ldc->LchId.byTimeslotNb = lchan->ts->nr;
+ ldc->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan);
+ ldc->LchId.byDirection = cmd->dir;
+ ldc->LchId.bySAPI = cmd->sapi;
+
+ mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(ldc);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(octphy_l1sapi_names, cmd->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(octphy_dir_names, cmd->dir));
+
+ return l1if_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
+
+}
+
+/**
+ * Execute the first SAPI command of the queue. In case of the markers
+ * this method is re-entrant so we need to make sure to remove a command
+ * from the list before calling a function that will queue a command.
+ *
+ * \return 0 in case no Gsm Request was sent, 1 otherwise
+ */
+
+static int sapi_queue_exeute(struct gsm_lchan *lchan)
+{
+ int res = 0;
+ struct sapi_cmd *cmd;
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+
+ switch (cmd->type) {
+ case SAPI_CMD_ACTIVATE:
+ mph_send_activate_req(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_CIPHERING:
+ mph_send_config_ciphering(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_LOGCH_PARAM:
+ /* TODO: Mode modif not supported by OctPHY currently */
+ /* mph_send_config_logchpar(lchan, cmd); */
+ res = 1;
+ break;
+ case SAPI_CMD_SACCH_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res =
+ check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH,
+ cOCTVC1_GSM_ID_DIRECTION_ENUM_TX_BTS_MS);
+ res |=
+ check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH,
+ cOCTVC1_GSM_ID_DIRECTION_ENUM_RX_BTS_MS);
+ break;
+ case SAPI_CMD_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = lchan_deactivate_sapis(lchan);
+ break;
+ case SAPI_CMD_DEACTIVATE:
+ res = mph_send_deactivate_req(lchan, cmd);
+ res = 1;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE,
+ "Unimplemented command type %d\n", cmd->type);
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = 0;
+ abort();
+ break;
+ }
+
+ return res;
+}
+
+static void sapi_queue_send(struct gsm_lchan *lchan)
+{
+ int res;
+
+ do {
+ res = sapi_queue_exeute(lchan);
+ } while (res == 0 && !llist_empty(&lchan->sapi_cmds));
+}
+
+static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
+{
+ int end;
+ struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
+ struct sapi_cmd, entry);
+ llist_del(&cmd->entry);
+ end = llist_empty(&lchan->sapi_cmds);
+
+ if (cmd->callback)
+ cmd->callback(lchan, status);
+ talloc_free(cmd);
+
+ if (end || llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "%s End of queue encountered. Now empty? %d\n",
+ gsm_lchan_name(lchan), llist_empty(&lchan->sapi_cmds));
+ return;
+ }
+
+ sapi_queue_send(lchan);
+}
+
+/* we regularly check if the L1 is still sending us primitives.
+ if not, we simply stop the BTS program (and be re-spawned) */
+static void alive_timer_cb(void *data)
+{
+ struct octphy_hdl *fl1h = data;
+
+ if (fl1h->alive_prim_cnt == 0) {
+ LOGP(DL1C, LOGL_FATAL, "L1 is no longer sending primitives!\n");
+ exit(23);
+ }
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+}
+
+/***********************************************************************
+ * RSL DEACTIVATE SACCH
+ ***********************************************************************/
+
+static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to check if the SACCH is allocated */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_SACCH_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ enqueue_sacch_rel_marker(lchan);
+ return 0;
+}
+
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+ /* Only de-activate the SACCH if the lchan is active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return 0;
+ return bts_model_lchan_deactivate_sacch(lchan);
+}
+
+
+/***********************************************************************
+ * RSL CHANNEL RELEASE
+ ***********************************************************************/
+
+static void enqueue_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to release all active SAPIs */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ enqueue_rel_marker(lchan);
+ return 0;
+}
+
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
+{
+ /* A duplicate RF Release Request, ignore it */
+ if (lchan->state == LCHAN_S_REL_REQ)
+ return 0;
+ lchan_deactivate(lchan);
+ return 0;
+}
+
+
+/***********************************************************************
+ * SET CIPHERING
+ ***********************************************************************/
+
+static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_CIPHERING;
+ queue_sapi_command(lchan, cmd);
+}
+
+int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink)
+{
+ int dir;
+
+ // ignore the request when the channel is not active
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ if (dir_downlink)
+ dir = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS;
+ else
+ dir = cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS;
+
+ enqueue_sapi_ciphering_cmd(lchan, dir);
+
+ return 0;
+}
+
+
+/***********************************************************************
+ * RSL MODE MODIFY
+ ***********************************************************************/
+
+/* Mode modify is currently not supported by OctPHY */
+static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
+ queue_sapi_command(lchan, cmd);
+}
+
+
+/* Mode modify is currently not supported by OctPHY */
+static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
+{
+ enqueue_sapi_logchpar_cmd(lchan, direction);
+
+ return 0;
+}
+
+/* Mode modify is currently not supported by OctPHY */
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS);
+ tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS);
+
+ /* FIXME: update encryption */
+
+ return 0;
+}
+
+
+/***********************************************************************
+ * LCHAN / SAPI ACTIVATION
+ ***********************************************************************/
+
+static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
+{
+ if (status != cOCTVC1_RC_OK) {
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE,
+ RSL_ERR_EQUIPMENT_FAIL);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
+
+ return 0;
+}
+
+static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_ACTIVATE;
+ cmd->callback = sapi_activate_cb;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_activate(struct gsm_lchan *lchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ DEBUGP(DL1C, "lchan_act called\n");
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Trying to activate lchan, but commands in queue\n",
+ gsm_lchan_name(lchan));
+
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_SCH) {
+ /* once we activate the SCH, we should get MPH-TIME.ind */
+ fl1h->alive_timer.cb = alive_timer_cb;
+ fl1h->alive_timer.data = fl1h;
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+ }
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+ lchan_init_lapdm(lchan);
+
+ return 0;
+}
+
+int l1if_rsl_chan_act(struct gsm_lchan *lchan)
+{
+ lchan_activate(lchan);
+ return 0;
+}
+
+#define talloc_replace(dst, ctx, src) \
+ do { \
+ if (dst) \
+ talloc_free(dst); \
+ dst = talloc_strdup(ctx, (const char *) src); \
+ } while (0)
+
+static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
+{
+ tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr =
+ (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *) resp->l2h;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP_SWAP(aisr);
+
+ LOGP(DL1C, LOGL_INFO, "Rx APP-INFO-SYSTEM.resp (platform='%s', version='%s')\n",
+ aisr->szPlatform, aisr->szVersion);
+
+#if OCTPHY_MULTI_TRX == 1
+ LOGP(DL1C, LOGL_INFO, "Note: compiled with multi-trx support.\n");
+#else
+ LOGP(DL1C, LOGL_INFO, "Note: compiled without multi-trx support.\n");
+#endif
+
+ talloc_replace(fl1h->info.system.platform, fl1h, aisr->szPlatform);
+ talloc_replace(fl1h->info.system.version, fl1h, aisr->szVersion);
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+int l1if_check_app_sys_version(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *ais;
+
+ ais = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *)
+ msgb_put(msg, sizeof(*ais));
+ mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_DEF(ais);
+ l1if_fill_msg_hdr(&ais->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID);
+
+ mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_SWAP(ais);
+
+ LOGP(DL1C, LOGL_INFO, "Tx APP-INFO-SYSTEM.req\n");
+
+ return l1if_req_compl(fl1h, msg, app_info_sys_compl_cb, pinst);
+}
+
+static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp,
+ void *data)
+{
+ char ver_hdr[32];
+ struct phy_instance *pinst = data;
+ tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air =
+ (tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h;
+
+ snprintf(ver_hdr, sizeof(ver_hdr), "%02i.%02i.%02i-B%i",
+ cOCTVC1_MAIN_VERSION_MAJOR, cOCTVC1_MAIN_VERSION_MINOR,
+ cOCTVC1_MAIN_VERSION_MAINTENANCE, cOCTVC1_MAIN_VERSION_BUILD);
+
+ mOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP_SWAP(air);
+
+ LOGP(DL1C, LOGL_INFO,
+ "Rx APP-INFO.resp (name='%s', desc='%s', ver='%s', ver_hdr='%s')\n",
+ air->szName, air->szDescription, air->szVersion, ver_hdr);
+
+ /* Check if the firmware version of the DSP matches the header files
+ * that were used to compile osmo-bts */
+ if (strcmp(air->szVersion, ver_hdr) != 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Invalid header-file-version / dsp-firmware-version combination\n");
+ LOGP(DL1C, LOGL_ERROR,
+ "Expected firmware version: %s\n", ver_hdr);
+ LOGP(DL1C, LOGL_ERROR,
+ "Actual firmware version: %s\n", air->szVersion);
+
+ if (!no_fw_check) {
+ LOGP(DL1C, LOGL_ERROR,
+ "use option -I to override the check (not recommened)\n");
+ LOGP(DL1C, LOGL_ERROR,
+ "exiting...\n");
+ exit(1);
+ }
+ }
+
+ talloc_replace(fl1h->info.app.name, fl1h, air->szName);
+ talloc_replace(fl1h->info.app.description, fl1h, air->szDescription);
+ talloc_replace(fl1h->info.app.version, fl1h, air->szVersion);
+ OSMO_ASSERT(strlen(ver_hdr) < sizeof(pinst->version));
+ osmo_strlcpy(pinst->version, ver_hdr, strlen(ver_hdr));
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+ msgb_free(resp);
+
+ return 0;
+}
+
+int l1if_check_app_version(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *ai;
+
+ ai = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *) msgb_put(msg, sizeof(*ai));
+ mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_DEF(ai);
+ l1if_fill_msg_hdr(&ai->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID);
+
+ mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_SWAP(ai);
+
+ LOGP(DL1C, LOGL_INFO, "Tx APP-INFO.req\n");
+
+ return l1if_req_compl(fl1h, msg, app_info_compl_cb, pinst);
+}
+
+static int trx_close_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *car =
+ (tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *) resp->l2h;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_CLOSE_RSP_SWAP(car);
+
+ LOGP(DL1C, LOGL_INFO, "Rx TRX-CLOSE.conf(%u)\n", car->TrxId.byTrxId);
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int trx_close(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+ struct octphy_hdl *fl1h = plink->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *cac;
+
+ cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *)
+ msgb_put(msg, sizeof(*cac));
+ l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_CLOSE_CID);
+
+ cac->TrxId.byTrxId = pinst->u.octphy.trx_id;
+
+ LOGP(DL1C, LOGL_INFO, "Tx TRX-CLOSE.req(%u)\n", cac->TrxId.byTrxId);
+
+ mOCTVC1_GSM_MSG_TRX_CLOSE_CMD_SWAP(cac);
+
+ return l1if_req_compl(fl1h, msg, trx_close_cb, NULL);
+}
+
+/* call-back once the TRX_OPEN_CID response arrives */
+static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data)
+{
+ struct gsm_bts_trx *trx;
+
+ tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or =
+ (tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or);
+ trx = trx_by_l1h(fl1h, or->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(or->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during TRX opening procedure -- abort\n");
+ exit(1);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "TRX-OPEN.resp(trx=%u) = %s\n",
+ trx->nr, octvc1_rc2string(or->Header.ulReturnCode));
+
+ /* FIXME: check for ulReturnCode == OK */
+ if (or->Header.ulReturnCode != cOCTVC1_RC_OK) {
+ LOGP(DL1C, LOGL_ERROR, "TRX-OPEN failed: %s\n",
+ octvc1_rc2string(or->Header.ulReturnCode));
+ msgb_free(resp);
+ exit(1);
+ }
+
+ msgb_free(resp);
+
+ opstart_compl(&trx->mo);
+
+ octphy_hw_get_pcb_info(fl1h);
+ octphy_hw_get_rf_port_info(fl1h, 0);
+ octphy_hw_get_rf_ant_rx_config(fl1h, 0, 0);
+ octphy_hw_get_rf_ant_tx_config(fl1h, 0, 0);
+ octphy_hw_get_rf_ant_rx_config(fl1h, 0, 1);
+ octphy_hw_get_rf_ant_tx_config(fl1h, 0, 1);
+ octphy_hw_get_clock_sync_info(fl1h);
+ fl1h->opened = 1;
+
+ return 0;
+}
+
+int l1if_trx_open(struct gsm_bts_trx *trx)
+{
+ /* putting it all together */
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_OPEN_CMD *oc;
+
+ oc = (tOCTVC1_GSM_MSG_TRX_OPEN_CMD *) msgb_put(msg, sizeof(*oc));
+ l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_OPEN_CID);
+ oc->ulRfPortIndex = plink->u.octphy.rf_port_index;
+ oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn);
+ oc->Config.usArfcn = trx->arfcn;
+
+#if OCTPHY_MULTI_TRX == 1
+ if (pinst->u.octphy.trx_id)
+ oc->Config.usCentreArfcn = plink->u.octphy.center_arfcn;
+ else {
+ oc->Config.usCentreArfcn = trx->arfcn;
+ plink->u.octphy.center_arfcn = trx->arfcn;
+ }
+ oc->Config.usBcchArfcn = trx->bts->c0->arfcn;
+#endif
+ oc->Config.usTsc = trx->bts->bsic & 0x7;
+ oc->RfConfig.ulRxGainDb = plink->u.octphy.rx_gain_db;
+ /* FIXME: compute this based on nominal transmit power, etc. */
+ if (plink->u.octphy.tx_atten_flag) {
+ oc->RfConfig.ulTxAttndB = plink->u.octphy.tx_atten_db;
+ } else {
+ /* Take the Tx Attn received in set radio attribures
+ * x4 is for the value in db */
+ oc->RfConfig.ulTxAttndB = (trx->max_power_red) << 2;
+ }
+
+#if OCTPHY_USE_ANTENNA_ID == 1
+ oc->RfConfig.ulTxAntennaId = plink->u.octphy.tx_ant_id;
+ oc->RfConfig.ulRxAntennaId = plink->u.octphy.rx_ant_id;
+#endif
+
+#if OCTPHY_MULTI_TRX == 1
+ LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, "
+ "center=%u, tsc=%u, rx_gain=%u, tx_atten=%u)\n",
+ oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn,
+ oc->Config.usCentreArfcn, oc->Config.usTsc, oc->RfConfig.ulRxGainDb,
+ oc->RfConfig.ulTxAttndB);
+#else
+ LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, "
+ "tsc=%u, rx_gain=%u, tx_atten=%u)\n",
+ oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn,
+ oc->Config.usTsc, oc->RfConfig.ulRxGainDb,
+ oc->RfConfig.ulTxAttndB);
+#endif
+
+ mOCTVC1_GSM_MSG_TRX_OPEN_CMD_SWAP(oc);
+
+ return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL);
+}
+
+#if OCTPHY_USE_16X_OVERSAMPLING == 1
+static int over_sample_16x_modif_compl_cb(struct octphy_hdl *fl1,
+ struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_RSP *mcr =
+ (tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_RSP*) resp->l2h;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_RSP_SWAP(mcr);
+
+ LOGP(DL1C, LOGL_INFO, "Rx OVER-SAMPLE-16x-MODIFY.conf\n");
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int l1if_over_sample_16x_modif(struct gsm_bts_trx *trx)
+{
+ /* NOTE: The 16x oversampling mode should always be enabled. Single-
+ * TRX operation will work with standard 4x oversampling, but multi-
+ * TRX requires 16x oversampling */
+
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD *mc;
+
+ mc = (tOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD*) msgb_put(msg,
+ sizeof
+ (*mc));
+ mOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD_DEF(mc);
+ l1if_fill_msg_hdr(&mc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CID);
+
+ if (plink->u.octphy.over_sample_16x == true)
+ mc->ulOversample16xEnableFlag = 1;
+ else
+ mc->ulOversample16xEnableFlag = 0;
+
+ mOCTVC1_GSM_MSG_OVERSAMPLE_SELECT_16X_MODIFY_CMD_SWAP(mc);
+
+ LOGP(DL1C, LOGL_INFO, "Tx OVER-SAMPLE-16x-MODIF.req\n");
+
+ return l1if_req_compl(fl1h, msg, over_sample_16x_modif_compl_cb, 0);
+}
+#endif
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx)
+{
+ return 0;
+}
+
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
+ ARRAY_SIZE(trx_rqd_attr))) {
+ /* HACK: spec says we need to decline, but openbsc
+ * doesn't deal with this very well */
+ return oml_mo_opstart_ack(&trx->mo);
+ /* return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); */
+ }
+
+ l1if_check_app_version(trx);
+ l1if_check_app_sys_version(trx);
+
+#if OCTPHY_USE_16X_OVERSAMPLING == 1
+ l1if_over_sample_16x_modif(trx);
+#endif
+
+ return l1if_trx_open(trx);
+}
+
+/***********************************************************************
+ * PHYSICAL CHANNE ACTIVATION
+ ***********************************************************************/
+
+static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar =
+ (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h;
+ uint8_t ts_nr;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_abis_mo *mo;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar);
+ trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during physical channel activation -- abort\n");
+ exit(1);
+ }
+
+ ts_nr = ar->PchId.byTimeslotNb;
+ OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts));
+
+ ts = &trx->ts[ts_nr];
+
+ LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n",
+ ts->trx->nr, ts->nr, ts->pchan,
+ octvc1_rc2string(ar->Header.ulReturnCode));
+
+ if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) {
+ LOGP(DL1C, LOGL_ERROR,
+ "PCHAN-ACT failed: %s\n\n",
+ octvc1_rc2string(ar->Header.ulReturnCode));
+ LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n");
+ msgb_free(resp);
+ exit(-1);
+ }
+
+ trx = ts->trx;
+ mo = &trx->ts[ar->PchId.byTimeslotNb].mo;
+
+ msgb_free(resp);
+
+ return opstart_compl(mo);
+}
+
+static int ts_connect_as(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan,
+ l1if_compl_cb * cb, void *data)
+{
+ struct phy_instance *pinst = trx_phy_instance(ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc =
+ (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc;
+
+ oc = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD*)
+ msgb_put(msg, sizeof(*oc));
+ l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID);
+
+ oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ oc->PchId.byTimeslotNb = ts->nr;
+ oc->ulChannelType = pchan_to_logChComb[pchan];
+
+ /* TODO: how should we know the payload type here? Also, why
+ * would the payload type have to be the same for both halves of
+ * a TCH/H ? */
+ switch (oc->ulChannelType) {
+ case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF:
+ case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF:
+ oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE;
+ break;
+ case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH:
+ oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE;
+ break;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.req(trx=%u, ts=%u, chcomb=%u)\n",
+ ts->trx->nr, ts->nr, pchan);
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc);
+
+ return l1if_req_compl(fl1h, msg, cb, data);
+}
+
+/* Dynamic timeslots: Disconnect callback, reports completed disconnection
+ * to higher layers */
+static int ts_disconnect_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_RSP *ar =
+ (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h;
+ uint8_t ts_nr;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+
+ trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during ts disconnection\n");
+ return -EINVAL;
+ }
+
+ ts_nr = ar->PchId.byTimeslotNb;
+ ts = &trx->ts[ts_nr];
+
+ cb_ts_disconnected(ts);
+
+ return 0;
+}
+
+/* Dynamic timeslots: Connect callback, reports completed disconnection to
+ * higher layers */
+static int ts_connect_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar =
+ (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h;
+ uint8_t ts_nr;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+
+ /* in a completion call-back, we take msgb ownership and must
+ * release it before returning */
+
+ mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar);
+ trx = trx_by_l1h(fl1, ar->TrxId.byTrxId);
+ if (!trx) {
+ LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id while connecting ts\n");
+ return -EINVAL;
+ }
+
+ ts_nr = ar->PchId.byTimeslotNb;
+ OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts));
+
+ ts = &trx->ts[ts_nr];
+
+ LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n",
+ ts->trx->nr, ts->nr, ts->pchan,
+ octvc1_rc2string(ar->Header.ulReturnCode));
+
+ if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) {
+ LOGP(DL1C, LOGL_ERROR,
+ "PCHAN-ACT failed: %s\n\n",
+ octvc1_rc2string(ar->Header.ulReturnCode));
+ LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n");
+ msgb_free(resp);
+ exit(-1);
+ }
+
+ msgb_free(resp);
+
+ cb_ts_connected(ts, 0);
+
+ return 0;
+}
+
+/***********************************************************************
+ * BTS MODEL CALLBACKS
+ ***********************************************************************/
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ /* TODO: How to do this ? */
+ return 0;
+}
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo,
+ const uint8_t * attr_ids, unsigned int num_attr_ids)
+{
+ unsigned int i;
+
+ if (!mo->nm_attr)
+ return 0;
+
+ for (i = 0; i < num_attr_ids; i++) {
+ if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
+ return 0;
+ }
+ return 1;
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ int i;
+ for (i = 0; i < bts->num_trx; i++) {
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i);
+ l1if_activate_rf(trx, 1);
+ }
+ return 0;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ int rc;
+
+ struct gsm_bts_trx *trx;
+ struct phy_instance *pinst;
+ struct octphy_hdl *fl1h;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+
+ trx = ((struct gsm_bts_trx *)obj);
+ pinst = trx_phy_instance(trx);
+ fl1h = pinst->phy_link->u.octphy.hdl;
+
+ if (mo->procedure_pending) {
+ LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
+ "pending procedure on TRX %d\n", trx->nr);
+ return 0;
+ }
+ mo->procedure_pending = 1;
+ switch (adm_state) {
+ case NM_STATE_LOCKED:
+
+ pinst->u.octphy.trx_locked = 1;
+
+ /* Stop heartbeat check */
+ osmo_timer_del(&fl1h->alive_timer);
+
+ bts_model_trx_deact_rf(trx);
+
+ /* Close TRX */
+ rc = bts_model_trx_close(trx);
+ if (rc != 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Cannot close TRX %d, it is already closed.\n",
+ trx->nr);
+ }
+ break;
+
+ case NM_STATE_UNLOCKED:
+
+ if (pinst->u.octphy.trx_locked) {
+ pinst->u.octphy.trx_locked = 0;
+ l1if_activate_rf(trx, 1);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ mo->procedure_pending = 0;
+ break;
+
+ default:
+ /* blindly accept all state changes */
+ break;
+ }
+
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ return l1if_activate_rf(trx, 0);
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ /* FIXME: close only one TRX */
+ return trx_close(trx);
+}
+
+
+/* callback from OML */
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr,
+ struct tlv_parsed *new_attr, void *obj)
+{
+ /* FIXME: check if the attributes are valid */
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ if (kind == NM_OC_RADIO_CARRIER) {
+ struct gsm_bts_trx *trx = obj;
+ /*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */
+
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ }
+ return oml_fom_ack_nack(msg, 0);
+}
+
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj)
+{
+ int rc = -1;
+ struct gsm_bts_trx_ts *ts;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ ts = (struct gsm_bts_trx_ts*) obj;
+ rc = ts_connect_as(ts, ts->pchan, pchan_act_compl_cb, NULL);
+ break;
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
+ rc = oml_mo_opstart_ack(mo);
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+#pragma message ("Implement bts_model_change_power based on TRX_MODIFY_RF_CID (OS#3016)")
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ struct phy_instance *pinst = trx_phy_instance(ts->trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *oc =
+ (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) oc;
+
+ oc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *)
+ msgb_put(msg, sizeof(*oc));
+ l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID);
+
+ oc->TrxId.byTrxId = pinst->u.octphy.trx_id;
+ oc->PchId.byTimeslotNb = ts->nr;
+
+ LOGP(DL1C, LOGL_INFO, "PCHAN-DEACT.req(trx=%u, ts=%u, chcomb=%u)\n",
+ ts->trx->nr, ts->nr, ts->pchan);
+
+ mOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc);
+
+ return l1if_req_compl(fl1h, msg, ts_disconnect_cb, NULL);
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+ if (as_pchan == GSM_PCHAN_TCH_F_PDCH
+ || as_pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Requested TS connect as %s,"
+ " expected a specific pchan instead\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(as_pchan));
+ exit(1);
+ return;
+ }
+
+ rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL);
+ if (rc)
+ cb_ts_connected(ts, rc);
+}
diff --git a/src/osmo-bts-octphy/l1_oml.h b/src/osmo-bts-octphy/l1_oml.h
new file mode 100644
index 00000000..4729df5b
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_oml.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "l1_if.h"
+
+/* channel control */
+int l1if_rsl_chan_act(struct gsm_lchan *lchan);
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
+
+int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink);
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo,
+ const uint8_t * attr_ids, unsigned int num_attr_ids);
+
+int lchan_activate(struct gsm_lchan *lchan);
diff --git a/src/osmo-bts-octphy/l1_tch.c b/src/osmo-bts-octphy/l1_tch.c
new file mode 100644
index 00000000..df0469dd
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_tch.c
@@ -0,0 +1,283 @@
+/* Traffic Channel (TCH) part of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org>
+ *
+ * based on a copy of osmo-bts-sysmo/l1_tch.c, which is
+ * Copyright (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/bits.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+
+struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+
+ cur = msgb_put(msg, GSM_FR_BYTES);
+
+ /* step2: we need to shift the entire L1 payload by 4 bits right */
+ osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS / 4);
+
+ cur[0] |= 0xD0;
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+ /* step2: we need to shift the RTP payload left by one nibble */
+ osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS / 4);
+
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+ return GSM_FR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return NULL;
+ }
+
+ cur = msgb_put(msg, GSM_HR_BYTES);
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+
+ /* reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(cur, GSM_HR_BYTES);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return 0;
+ }
+
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+
+ /* reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES);
+
+ return GSM_HR_BYTES;
+}
+
+
+/* brief receive a traffic L1 primitive for a given lchan */
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *
+ data_ind)
+{
+ uint32_t payload_type = data_ind->Data.ulPayloadType;
+ uint8_t *payload = data_ind->Data.abyDataContent;
+
+ uint32_t fn = data_ind->Data.ulFrameNumber;
+ uint16_t b_total = data_ind->MeasurementInfo.usBERTotalBitCnt;
+ uint16_t b_error = data_ind->MeasurementInfo.usBERCnt;
+ uint16_t ber10k = b_total ? BER_10K * b_error / b_total : 0;
+ int16_t lqual_cb = 0; /* FIXME: check min_qual_norm! */
+
+ uint8_t payload_len;
+ struct msgb *rmsg = NULL;
+ struct gsm_lchan *lchan =
+ &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
+
+ if (data_ind->Data.ulDataLength < 1) {
+ LOGPFN(DL1P, LOGL_DEBUG, fn, "chan_nr %d Rx Payload size 0\n", chan_nr);
+ /* Push empty payload to upper layers */
+ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP");
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr,
+ data_ind->Data.ulFrameNumber,
+ ber10k, lqual_cb);
+ }
+
+ payload_len = data_ind->Data.ulDataLength;
+
+ switch (payload_type) {
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE:
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE:
+ if (lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ break;
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE:
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ default:
+ LOGPFN(DL1P, LOGL_NOTICE, fn, "%s Rx Payload Type %d is unsupported\n",
+ gsm_lchan_name(lchan), payload_type);
+ break;
+ }
+
+ LOGPFN(DL1P, LOGL_DEBUG, fn, "%s Rx codec frame (%u): %s\n", gsm_lchan_name(lchan),
+ payload_len, osmo_hexdump(payload, payload_len));
+
+ switch (payload_type) {
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE:
+ rmsg = l1_to_rtppayload_fr(payload, payload_len);
+ break;
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE:
+ rmsg = l1_to_rtppayload_hr(payload, payload_len);
+ break;
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE:
+ /* Currently not supported */
+#if 0
+ rmsg = l1_to_rtppayload_efr(payload, payload_len);
+ break;
+#endif
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE:
+ case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE:
+ /* Currently not supported */
+#if 0
+ rmsg = l1_to_rtppayload_amr(payload, payload_len,
+ &lchan->tch.amr_mr);
+#else
+ LOGPFN(DL1P, LOGL_ERROR, fn, "OctPHY only supports FR!\n");
+ return -1;
+#endif
+ break;
+ }
+
+ if (rmsg)
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr,
+ data_ind->Data.ulFrameNumber,
+ ber10k, lqual_cb);
+
+ return 0;
+
+err_payload_match:
+ LOGPFN(DL1P, LOGL_ERROR, fn, "%s Rx Payload Type %d incompatible with lchan\n",
+ gsm_lchan_name(lchan), payload_type);
+ return -EINVAL;
+}
+
+#define RTP_MSGB_ALLOC_SIZE 512
+
+/*! \brief function for incoming RTP via TCH.req
+ * \param rs RTP Socket
+ * \param[in] rtp_pl buffer containing RTP payload
+ * \param[in] rtp_pl_len length of \a rtp_pl
+ *
+ * This function prepares a msgb with a L1 PH-DATA.req primitive and
+ * queues it into lchan->dl_tch_queue.
+ *
+ * Note that the actual L1 primitive header is not fully initialized
+ * yet, as things like the frame number, etc. are unknown at the time we
+ * pre-fill the primtive.
+ */
+void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type,
+ uint8_t *data, uint32_t *len, const uint8_t *rtp_pl,
+ unsigned int rtp_pl_len)
+{
+ uint8_t *l1_payload;
+ int rc = -1;
+
+ DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(rtp_pl, rtp_pl_len));
+
+ l1_payload = &data[0];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE;
+ rc = rtppayload_to_l1_fr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ } else {
+ *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE;
+ /* Not supported currently */
+ rc = rtppayload_to_l1_hr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ /* Not supported currently */
+ case GSM48_CMODE_SPEECH_AMR:
+ /* Not supported currently */
+ LOGP(DRTP, LOGL_ERROR, "OctPHY only supports FR!\n");
+ default:
+ /* we don't support CSD modes */
+ rc = -1;
+ break;
+ }
+
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
+ gsm_lchan_name(lchan));
+ return;
+ }
+
+ *len = rc;
+
+ DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(data, *len));
+}
diff --git a/src/osmo-bts-octphy/l1_utils.c b/src/osmo-bts-octphy/l1_utils.c
new file mode 100644
index 00000000..8a8e1554
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_utils.c
@@ -0,0 +1,141 @@
+/* Layer 1 (PHY) Utilities of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "l1_utils.h"
+#include <octphy/octvc1/gsm/octvc1_gsm_api.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_id.h>
+#include <octphy/octvc1/main/octvc1_main_id.h>
+#include <octphy/octvc1/hw/octvc1_hw_api.h>
+
+const struct value_string octphy_l1sapi_names[23] =
+{
+ { cOCTVC1_GSM_SAPI_ENUM_IDLE, "IDLE" },
+ { cOCTVC1_GSM_SAPI_ENUM_FCCH, "FCCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_SCH, "SCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_SACCH, "SACCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_SDCCH, "SDCCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_BCCH, "BCCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH,"PCH_AGCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_CBCH, "CBCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_RACH, "RACH" },
+ { cOCTVC1_GSM_SAPI_ENUM_TCHF, "TCH/F" },
+ { cOCTVC1_GSM_SAPI_ENUM_FACCHF, "FACCH/F" },
+ { cOCTVC1_GSM_SAPI_ENUM_TCHH, "TCH/H" },
+ { cOCTVC1_GSM_SAPI_ENUM_FACCHH, "FACCH/H" },
+ { cOCTVC1_GSM_SAPI_ENUM_NCH, "NCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PDTCH, "PDTCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PACCH, "PACCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PBCCH, "PBCCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PAGCH, "PAGCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PPCH, "PPCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PNCH, "PNCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PTCCH, "PTCCH" },
+ { cOCTVC1_GSM_SAPI_ENUM_PRACH, "PRACH" },
+ { 0, NULL }
+};
+
+const struct value_string octphy_dir_names[5] =
+{
+ { cOCTVC1_GSM_DIRECTION_ENUM_NONE, "None" },
+ { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS, "TX_BTS_MS(DL)" },
+ { cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "RX_BTS_MS(UL)" },
+ { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS | cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "BOTH" },
+ { 0, NULL }
+};
+
+const struct value_string octphy_clkmgr_state_vals[8] = {
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, "UNINITIALIZED" },
+
+/* Note: Octasic renamed cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED to
+ * cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE. The following ifdef
+ * statement ensures that older headers still work. */
+#ifdef cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED, "UNUSED" },
+#else
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "IDLE" },
+#endif
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, "NO_EXT_CLOCK" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "LOCKED" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED, "UNLOCKED" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "ERROR" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "DISABLED" },
+ { 0, NULL }
+};
+
+const struct value_string octphy_cid_vals[37] = {
+ { cOCTVC1_GSM_MSG_TRX_OPEN_CID, "TRX-OPEN" },
+ { cOCTVC1_GSM_MSG_TRX_CLOSE_CID, "TRX-CLOSE" },
+ { cOCTVC1_GSM_MSG_TRX_STATUS_CID, "TRX-STATUS" },
+ { cOCTVC1_GSM_MSG_TRX_INFO_CID, "TRX-INFO" },
+ { cOCTVC1_GSM_MSG_TRX_RESET_CID, "TRX-RESET" },
+ { cOCTVC1_GSM_MSG_TRX_MODIFY_CID, "TRX-MODIFY" },
+ { cOCTVC1_GSM_MSG_TRX_LIST_CID, "TRX-LIST" },
+ { cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID, "TRX-CLOSE-ALL" },
+ { cOCTVC1_GSM_MSG_TRX_START_RECORD_CID, "RECORD-START" },
+ { cOCTVC1_GSM_MSG_TRX_STOP_RECORD_CID, "RECORD-STOP" },
+ { cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-ACT" },
+ { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-DEACT" },
+ { cOCTVC1_GSM_MSG_TRX_STATUS_LOGICAL_CHANNEL_CID, "LCHAN-STATUS" },
+ { cOCTVC1_GSM_MSG_TRX_INFO_LOGICAL_CHANNEL_CID, "LCHAN-INFO" },
+ { cOCTVC1_GSM_MSG_TRX_LIST_LOGICAL_CHANNEL_CID, "LCHAN-LIST" },
+ { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID,
+ "LCHAN-EMPTY-FRAME" },
+ { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID, "LCHAN-DATA" },
+ { cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-ACT" },
+ { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-DEACT" },
+ { cOCTVC1_GSM_MSG_TRX_STATUS_PHYSICAL_CHANNEL_CID, "PCHAN-STATUS" },
+ { cOCTVC1_GSM_MSG_TRX_RESET_PHYSICAL_CHANNEL_CID, "PCHAN-RESET" },
+ { cOCTVC1_GSM_MSG_TRX_LIST_PHYSICAL_CHANNEL_CID, "PCHAN-LIST" },
+ { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CID, "PCHAN-INFO" },
+ { cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID,
+ "PCHAN-CIPH-MODIFY" },
+ { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CIPHERING_CID,
+ "PCHAN-CIPH-INFO" },
+ { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_MEASUREMENT_CID,
+ "PCHAN-MEASUREMENT" },
+ { cOCTVC1_GSM_MSG_TRX_INFO_RF_CID, "RF-INFO" },
+ { cOCTVC1_GSM_MSG_TRX_MODIFY_RF_CID, "RF-MODIFY" },
+ { cOCTVC1_GSM_MSG_TAP_FILTER_LIST_CID, "TAP-FILTER-LIST" },
+ { cOCTVC1_GSM_MSG_TAP_FILTER_INFO_CID, "TAP-FILTER-INFO" },
+ { cOCTVC1_GSM_MSG_TAP_FILTER_STATS_CID, "TAP-FILTER-STATS" },
+ { cOCTVC1_GSM_MSG_TAP_FILTER_MODIFY_CID, "TAP-FILTER-MODIFY" },
+ { cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID, "MAIN_MSG_APP_INFO" },
+ { cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID, "MAIN_MSG_APP_INFO_SYSTEM" },
+ { cOCTVC1_GSM_MSG_TRX_START_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID,
+ "LCHAN-RAW-DATA-START" },
+ { cOCTVC1_GSM_MSG_TRX_STOP_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID,
+ "LCHAN-RAW-DATA-STOP" },
+ { 0, NULL }
+};
+
+const struct value_string octphy_eid_vals[7] = {
+ { cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID, "TIME.ind" },
+ { cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID, "TRX-STATUS-CHG.ind" },
+ { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID,
+ "LCHAN-DATA.ind" },
+ { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID,
+ "LCHAN-RTS.ind" },
+ { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID,
+ "LCHAN-RACH.ind" },
+ { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID,
+ "LCHAN-RAW-DATA.ind" },
+ { 0, NULL }
+};
diff --git a/src/osmo-bts-octphy/l1_utils.h b/src/osmo-bts-octphy/l1_utils.h
new file mode 100644
index 00000000..d1a87170
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_utils.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <osmocom/core/utils.h>
+
+const struct value_string octphy_l1sapi_names[23];
+const struct value_string octphy_dir_names[5];
+const struct value_string octphy_clkmgr_state_vals[8];
+const struct value_string octphy_cid_vals[37];
+const struct value_string octphy_eid_vals[7];
diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c
new file mode 100644
index 00000000..928a4c81
--- /dev/null
+++ b/src/osmo-bts-octphy/main.c
@@ -0,0 +1,101 @@
+/* Main program of osmo-bts for OCTPHY-2G */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015 Harald Welte <laforge@gnumonks.org>
+ *
+ * based on a copy of osmo-bts-sysmo/main.c, which is
+ * Copyright (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+
+#define RF_LOCK_PATH "/var/lock/bts_rf_lock"
+
+extern int pcu_direct;
+extern bool no_fw_check;
+
+int bts_model_print_help()
+{
+ printf(" -I --no-fw-check Override firmware version check\n");
+ return 0;
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* specific to this hardware */
+ { "no-fw-check", 0, 0, 'I' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "I",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'I':
+ no_fw_check = true;
+ break;
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c
new file mode 100644
index 00000000..6da038b1
--- /dev/null
+++ b/src/osmo-bts-octphy/octphy_hw_api.c
@@ -0,0 +1,404 @@
+/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2015 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/logging.h>
+
+#include "l1_if.h"
+#include "l1_oml.h"
+#include "l1_utils.h"
+#include "octphy_hw_api.h"
+
+#include <octphy/octvc1/octvc1_rc2string.h>
+#include <octphy/octvc1/hw/octvc1_hw_api.h>
+#include <octphy/octvc1/hw/octvc1_hw_api_swap.h>
+
+/* Chapter 12.1 */
+static int get_pcb_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ tOCTVC1_HW_MSG_PCB_INFO_RSP *pir =
+ (tOCTVC1_HW_MSG_PCB_INFO_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_PCB_INFO_RSP_SWAP(pir);
+
+ LOGP(DL1C, LOGL_INFO, "HW-PCB-INFO.resp: Name=%s %s, Serial=%s, "
+ "FileName=%s, InfoSource=%u, InfoState=%u, GpsName=%s, "
+ "WiFiName=%s\n", pir->szName, pir->ulDeviceId ? "SEC" : "PRI",
+ pir->szSerial, pir->szFilename, pir->ulInfoSource,
+ pir->ulInfoState, pir->szGpsName, pir->szWifiName);
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.1 */
+int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_PCB_INFO_CMD *pic;
+
+ pic = (tOCTVC1_HW_MSG_PCB_INFO_CMD *) msgb_put(msg, sizeof(*pic));
+
+ l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_PCB_INFO_CID);
+
+ mOCTVC1_HW_MSG_PCB_INFO_CMD_SWAP(pic);
+
+ return l1if_req_compl(fl1h, msg, get_pcb_info_compl_cb, NULL);
+}
+
+/* Chapter 12.9 */
+static int rf_port_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *pir =
+ (tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_RF_PORT_INFO_RSP_SWAP(pir);
+
+ LOGP(DL1C, LOGL_INFO, "RF-PORT-INFO.resp Idx=%u, InService=%u, "
+ "hOwner=0x%x, Id=%u, FreqMin=%u, FreqMax=%u\n",
+ pir->ulPortIndex, pir->ulInService, pir->hOwner,
+ pir->ulPortInterfaceId, pir->ulFrequencyMinKhz,
+ pir->ulFrequencyMaxKhz);
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.9 */
+int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *pic;
+
+ pic = (tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *) msgb_put(msg, sizeof(*pic));
+
+ l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_RF_PORT_INFO_CID);
+
+ pic->ulPortIndex = index;
+
+ mOCTVC1_HW_MSG_RF_PORT_INFO_CMD_SWAP(pic);
+
+ return l1if_req_compl(fl1h, msg, rf_port_info_compl_cb, NULL);
+}
+
+/* Chapter 12.10 */
+static int rf_port_stats_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ struct octphy_hw_get_cb_data *get_cb_data;
+
+ tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr =
+ (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_RF_PORT_STATS_RSP_SWAP(psr);
+
+ LOGP(DL1C, LOGL_INFO, "RF-PORT-STATS.resp Idx=%u RadioStandard=%s, "
+ "Rx(Bytes=%u, Overflow=%u, AvgBps=%u, Period=%uus, Freq=%u) "
+ "Tx(Bytes=%i, Underflow=%u, AvgBps=%u, Period=%uus, Freq=%u)\n",
+ psr->ulPortIndex,
+ get_value_string(radio_std_vals, psr->ulRadioStandard),
+ psr->RxStats.ulRxByteCnt, psr->RxStats.ulRxOverflowCnt,
+ psr->RxStats.ulRxAverageBytePerSecond,
+ psr->RxStats.ulRxAveragePeriodUs,
+#if OCTPHY_USE_FREQUENCY == 1
+ psr->RxStats.Frequency.ulValue,
+#else
+ psr->RxStats.ulFrequencyKhz,
+#endif
+ psr->TxStats.ulTxByteCnt, psr->TxStats.ulTxUnderflowCnt,
+ psr->TxStats.ulTxAverageBytePerSecond,
+ psr->TxStats.ulTxAveragePeriodUs,
+#if OCTPHY_USE_FREQUENCY == 1
+ psr->TxStats.Frequency.ulValue);
+#else
+ psr->TxStats.ulFrequencyKhz);
+#endif
+
+ get_cb_data = (struct octphy_hw_get_cb_data*) data;
+ get_cb_data->cb(resp,get_cb_data->data);
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.10 */
+int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index,
+ struct octphy_hw_get_cb_data *cb_data)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *psc;
+
+ psc = (tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *) msgb_put(msg, sizeof(*psc));
+
+ l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_RF_PORT_STATS_CID);
+
+ psc->ulPortIndex = index;
+ psc->ulResetStatsFlag = cOCT_FALSE;
+
+ mOCTVC1_HW_MSG_RF_PORT_STATS_CMD_SWAP(psc);
+
+ return l1if_req_compl(fl1h, msg, rf_port_stats_compl_cb, cb_data);
+}
+
+static const struct value_string rx_gain_mode_vals[] = {
+ { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_MGC, "Manual" },
+ { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_FAST_ATK, "Automatic (fast)" },
+ { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_SLOW_ATK, "Automatic (slow)" },
+ { 0, NULL }
+};
+
+/* Chapter 12.13 */
+static int rf_ant_rx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *arc =
+ (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP_SWAP(arc);
+
+ LOGP(DL1C, LOGL_INFO, "ANT-RX-CONFIG.resp(Port=%u, Ant=%u): %s, "
+ "Gain %d dB, GainCtrlMode=%s\n",
+ arc->ulPortIndex, arc->ulAntennaIndex,
+#ifdef OCTPHY_USE_RX_CONFIG
+ arc->RxConfig.ulEnableFlag ? "Enabled" : "Disabled",
+ arc->RxConfig.lRxGaindB/512,
+ get_value_string(rx_gain_mode_vals, arc->RxConfig.ulRxGainMode));
+#else
+ arc->ulEnableFlag ? "Enabled" : "Disabled",
+ arc->lRxGaindB/512,
+ get_value_string(rx_gain_mode_vals, arc->ulRxGainMode));
+#endif
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.13 */
+int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx,
+ uint32_t ant_idx)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *psc;
+
+ psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *)
+ msgb_put(msg, sizeof(*psc));
+
+ l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CID);
+
+ psc->ulPortIndex = port_idx;
+ psc->ulAntennaIndex = ant_idx;
+
+ mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD_SWAP(psc);
+
+ return l1if_req_compl(fl1h, msg, rf_ant_rx_compl_cb, NULL);
+
+}
+
+/* Chapter 12.14 */
+static int rf_ant_tx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *atc =
+ (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP_SWAP(atc);
+
+ LOGP(DL1C, LOGL_INFO, "ANT-TX-CONFIG.resp(Port=%u, Ant=%u): %s, "
+ "Gain %d dB\n",
+ atc->ulPortIndex, atc->ulAntennaIndex,
+#ifdef OCTPHY_USE_TX_CONFIG
+ atc->TxConfig.ulEnableFlag? "Enabled" : "Disabled",
+ atc->TxConfig.lTxGaindB/512);
+#else
+ atc->ulEnableFlag ? "Enabled" : "Disabled",
+ atc->lTxGaindB/512);
+
+#endif
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.14 */
+int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx,
+ uint32_t ant_idx)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *psc;
+
+ psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *)
+ msgb_put(msg, sizeof(*psc));
+
+ l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CID);
+
+ psc->ulPortIndex = port_idx;
+ psc->ulAntennaIndex = ant_idx;
+
+ mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD_SWAP(psc);
+
+ return l1if_req_compl(fl1h, msg, rf_ant_tx_compl_cb, NULL);
+
+}
+
+static const struct value_string clocksync_source_vals[] = {
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ, "1 Hz" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_10MHZ, "10 MHz" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_30_72MHZ, "30.72 MHz" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ_EXT, "1 Hz (ext)"},
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_NONE, "None" },
+ { 0, NULL }
+};
+
+#if OCTPHY_USE_CLK_SOURCE_SELECTION == 1
+static const struct value_string clocksync_sel_vals[] = {
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_AUTOSELECT,
+ "Autoselect" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_CONFIG_FILE,
+ "Config File" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_HOST_APPLICATION,
+ "Host Application" },
+ { 0, NULL }
+};
+#endif
+
+/* Chapter 12.15 */
+static int get_clock_sync_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *cir =
+ (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP_SWAP(cir);
+
+ LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-INFO.resp Reference=%s ",
+ get_value_string(clocksync_source_vals, cir->ulClkSourceRef));
+
+#if OCTPHY_USE_CLK_SOURCE_SELECTION == 1
+ LOGPC(DL1C, LOGL_INFO, "Selection=%s)\n",
+ get_value_string(clocksync_sel_vals, cir->ulClkSourceSelection));
+#else
+ LOGPC(DL1C, LOGL_INFO, "Clock Drift= %u Us\n",
+ cir->ulMaxDriftDurationUs);
+#endif
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.15 */
+int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *cic;
+
+ cic = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *)
+ msgb_put(msg, sizeof(*cic));
+ l1if_fill_msg_hdr(&cic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CID);
+
+ mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD_SWAP(cic);
+
+ return l1if_req_compl(fl1h, msg, get_clock_sync_compl_cb, NULL);
+}
+
+/* Chapter 12.16 */
+static int get_clock_sync_stats_cb(struct octphy_hdl *fl1, struct msgb *resp,
+ void *data)
+{
+ struct octphy_hw_get_cb_data *get_cb_data;
+
+ tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr =
+ (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) resp->l2h;
+
+ mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP_SWAP(csr);
+
+ LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-STATS.resp");
+ LOGPC(DL1C, LOGL_INFO, " State=%s,",
+ get_value_string(clocksync_state_vals, csr->ulState));
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR == 1
+ LOGPC(DL1C, LOGL_INFO, " ClockError=%d,", csr->lClockError);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES == 1
+ LOGPC(DL1C, LOGL_INFO, " DroppedCycles=%d,", csr->lDroppedCycles);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ == 1
+ LOGPC(DL1C, LOGL_INFO, " PllFreqHz=%u,", csr->ulPllFreqHz);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ == 1
+ LOGPC(DL1C, LOGL_INFO, " PllFract=%u,", csr->ulPllFractionalFreqHz);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT == 1
+ LOGPC(DL1C, LOGL_INFO, " SlipCnt=%u,", csr->ulSlipCnt);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT == 1
+ LOGPC(DL1C, LOGL_INFO, " SyncLosses=%u,", csr->ulSyncLossCnt);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT == 1
+ LOGPC(DL1C, LOGL_INFO, " SyncLosses=%u,", csr->ulSyncLosseCnt);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE == 1
+ LOGPC(DL1C, LOGL_INFO, " SourceState=%u,", csr->ulSourceState);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1
+ LOGPC(DL1C, LOGL_INFO, " CLOCK-SYNC-MGR-STATS.resp State=%s,",
+ get_value_string(clocksync_dac_vals, csr->ulDacState));
+#endif
+ LOGPC(DL1C, LOGL_INFO, " LOCK-SYNC-MGR-USR-PROCESS.resp State=%s,",
+ get_value_string(usr_process_id, csr->ulOwnerProcessUid));
+ LOGPC(DL1C, LOGL_INFO, " DacValue=%u,", csr->ulDacValue);
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US == 1
+ LOGPC(DL1C, LOGL_INFO, " DriftElapseTime=%u Us,",
+ csr->ulDriftElapseTimeUs);
+#endif
+ LOGPC(DL1C, LOGL_INFO, "\n");
+
+ get_cb_data = (struct octphy_hw_get_cb_data*) data;
+ get_cb_data->cb(resp,get_cb_data->data);
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* Chapter 12.16 */
+int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h,
+ struct octphy_hw_get_cb_data *cb_data)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *csc;
+
+ csc = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *)
+ msgb_put(msg, sizeof(*csc));
+ l1if_fill_msg_hdr(&csc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND,
+ cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CID);
+
+ mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD_SWAP(csc);
+
+ return l1if_req_compl(fl1h, msg, get_clock_sync_stats_cb, cb_data);
+}
+
diff --git a/src/osmo-bts-octphy/octphy_hw_api.h b/src/osmo-bts-octphy/octphy_hw_api.h
new file mode 100644
index 00000000..625fe864
--- /dev/null
+++ b/src/osmo-bts-octphy/octphy_hw_api.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <stdint.h>
+#include "l1_if.h"
+#include <octphy/octvc1/hw/octvc1_hw_api.h>
+
+static const struct value_string radio_std_vals[] = {
+ { cOCTVC1_RADIO_STANDARD_ENUM_GSM, "GSM" },
+ { cOCTVC1_RADIO_STANDARD_ENUM_UMTS, "UMTS" },
+ { cOCTVC1_RADIO_STANDARD_ENUM_LTE, "LTE" },
+ { cOCTVC1_RADIO_STANDARD_ENUM_INVALID, "INVALID" },
+ { 0, NULL }
+};
+
+static const struct value_string clocksync_state_vals[] = {
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE,
+ "Uninitialized" },
+/* Note: Octasic renamed cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED to
+ * cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE. The following ifdef
+ * statement ensures that older headers still work. */
+#ifdef cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED, "Unused" },
+#else
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "Idle" },
+#endif
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK,
+ "No External Clock" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "Locked" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED,"Unlocked" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "Error" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "Disabled" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOSS_EXT_CLOCK,
+ "Loss of Ext Clock" },
+ { 0, NULL }
+};
+
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1
+static const struct value_string clocksync_dac_vals[] = {
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_UNUSED, "Unused" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_MASTER, "Master" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_SLAVE, "Slave" },
+ { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_FREE_RUNNING, "Free_Run"},
+ { 0, NULL }
+};
+#endif
+
+static const struct value_string usr_process_id[] = {
+ { cOCTVC1_USER_ID_PROCESS_ENUM_INVALID, "Invalid" },
+ { cOCTVC1_USER_ID_PROCESS_ENUM_MAIN_APP, "MainApp" },
+ { cOCTVC1_USER_ID_PROCESS_ENUM_MAIN_ROUTER, "MainRouter" },
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DL_0, "DL"},
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULIM_0, "ULIM" },
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULOM_0, "ULOM" },
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_SCHED_0, "SCHED" },
+#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DECOMB
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DECOMB, "DECOMB"},
+#endif
+#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULEQ
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULEQ, "ULEQ" },
+#endif
+#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_TEST
+ { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_TEST, "TEST"},
+#endif
+ { 0, NULL }
+};
+
+typedef void octphy_hw_get_cb(struct msgb *resp, void *data);
+
+struct octphy_hw_get_cb_data {
+ octphy_hw_get_cb* cb;
+ void *data;
+};
+
+int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h);
+int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index);
+int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index,
+ struct octphy_hw_get_cb_data *cb_data);
+int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx,
+ uint32_t ant_idx);
+int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx,
+ uint32_t ant_idx);
+int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h);
+int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h,
+ struct octphy_hw_get_cb_data *cb_data);
diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c
new file mode 100644
index 00000000..d250a957
--- /dev/null
+++ b/src/osmo-bts-octphy/octphy_vty.c
@@ -0,0 +1,466 @@
+/* VTY interface for osmo-bts OCTPHY integration */
+
+/* (C) 2015-2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/macaddr.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/vty.h>
+
+#include "l1_if.h"
+#include "l1_utils.h"
+#include "octphy_hw_api.h"
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+
+#define OCT_STR "OCTPHY Um interface\n"
+
+static struct gsm_bts *vty_bts;
+
+/* configuration */
+
+DEFUN(cfg_phy_hwaddr, cfg_phy_hwaddr_cmd,
+ "octphy hw-addr HWADDR",
+ OCT_STR "Configure the hardware addess of the OCTPHY\n"
+ "hardware address in aa:bb:cc:dd:ee:ff format\n")
+{
+ struct phy_link *plink = vty->index;
+ int rc;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = osmo_macaddr_parse(plink->u.octphy.phy_addr.sll_addr, argv[0]);
+ if (rc < 0)
+ return CMD_WARNING;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_netdev, cfg_phy_netdev_cmd,
+ "octphy net-device NAME",
+ OCT_STR "Configure the hardware device towards the OCTPHY\n"
+ "Ethernet device name\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (plink->u.octphy.netdev_name)
+ talloc_free(plink->u.octphy.netdev_name);
+ plink->u.octphy.netdev_name = talloc_strdup(plink, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_rf_port_idx, cfg_phy_rf_port_idx_cmd,
+ "octphy rf-port-index <0-255>",
+ OCT_STR "Configure the RF Port for this TRX\n"
+ "RF Port Index\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ plink->u.octphy.rf_port_index = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#if OCTPHY_USE_ANTENNA_ID == 1
+DEFUN(cfg_phy_rx_ant_id, cfg_phy_rx_ant_id_cmd,
+ "octphy rx-ant-id <0-1>",
+ OCT_STR "Configure the RX Antenna for this TRX\n" "RX Antenna Id\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ plink->u.octphy.rx_ant_id = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_tx_ant_id, cfg_phy_tx_ant_id_cmd,
+ "octphy tx-ant-id <0-1>",
+ OCT_STR "Configure the TX Antenna for this TRX\n" "TX Antenna Id\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ plink->u.octphy.tx_ant_id = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(cfg_phy_rx_gain_db, cfg_phy_rx_gain_db_cmd,
+ "octphy rx-gain <0-73>",
+ OCT_STR "Configure the Rx Gain in dB\n"
+ "Rx gain in dB\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ plink->u.octphy.rx_gain_db = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_tx_atten_db, cfg_phy_tx_atten_db_cmd,
+ "octphy tx-attenuation (oml|<0-359>)",
+ OCT_STR "Set attenuation on transmitted RF\n"
+ "Use tx-attenuation according to OML instructions from BSC\n"
+ "Fixed tx-attenuation in quarter-dB\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(argv[0], "oml") == 0) {
+ plink->u.octphy.tx_atten_flag = false;
+ } else {
+ plink->u.octphy.tx_atten_db = atoi(argv[0]);
+ plink->u.octphy.tx_atten_flag = true;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#if OCTPHY_USE_16X_OVERSAMPLING == 1
+DEFUN(cfg_phy_over_sample_16x, cfg_phy_over_sample_16x_cmd,
+ "octphy over-sample-16x <0-1>",
+ OCT_STR "Configure 16x over sampling rate for this TRX (restart required)\n"
+ "Over Sampling Rate\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if(atoi(argv[0]))
+ plink->u.octphy.over_sample_16x = true;
+ else
+ plink->u.octphy.over_sample_16x = false;
+
+ return CMD_SUCCESS;
+}
+#endif
+
+void show_rf_port_stats_cb(struct msgb *resp, void *data)
+{
+ struct vty *vty = (struct vty*) data;
+ tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr;
+
+ if (sizeof(tOCTVC1_HW_MSG_RF_PORT_STATS_RSP) != msgb_l2len(resp)) {
+ vty_out(vty,
+ "invalid msgb size (%d bytes, expected %zu bytes)%s",
+ msgb_l2len(resp),
+ sizeof(tOCTVC1_HW_MSG_RF_PORT_STATS_RSP), VTY_NEWLINE);
+ return;
+ }
+
+ psr = (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) msgb_l2(resp);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "RF-PORT-STATS:%s", VTY_NEWLINE);
+ vty_out(vty, "Idx=%d%s", psr->ulPortIndex, VTY_NEWLINE);
+ vty_out(vty, "RadioStandard=%s%s",
+ get_value_string(radio_std_vals, psr->ulRadioStandard),
+ VTY_NEWLINE);
+ vty_out(vty, "Rx Bytes=%u%s", psr->RxStats.ulRxByteCnt, VTY_NEWLINE);
+ vty_out(vty, "Rx Overflow=%u%s", psr->RxStats.ulRxOverflowCnt,
+ VTY_NEWLINE);
+ vty_out(vty, "Rx AvgBps=%u%s", psr->RxStats.ulRxAverageBytePerSecond,
+ VTY_NEWLINE);
+ vty_out(vty, "Rx Period=%u%s", psr->RxStats.ulRxAveragePeriodUs,
+ VTY_NEWLINE);
+#if OCTPHY_USE_FREQUENCY == 1
+ vty_out(vty, "Rx Freq=%u%s", psr->RxStats.Frequency.ulValue, VTY_NEWLINE);
+#else
+ vty_out(vty, "Rx Freq=%u%s", psr->RxStats.ulFrequencyKhz, VTY_NEWLINE);
+#endif
+ vty_out(vty, "Tx Bytes=%u%s", psr->TxStats.ulTxByteCnt, VTY_NEWLINE);
+ vty_out(vty, "Tx Underflow=%u%s", psr->TxStats.ulTxUnderflowCnt,
+ VTY_NEWLINE);
+ vty_out(vty, "Tx AvgBps=%u%s", psr->TxStats.ulTxAverageBytePerSecond,
+ VTY_NEWLINE);
+ vty_out(vty, "Tx Period=%u%s", psr->TxStats.ulTxAveragePeriodUs,
+ VTY_NEWLINE);
+#if OCTPHY_USE_FREQUENCY == 1
+ vty_out(vty, "Tx Freq=%u%s", psr->TxStats.Frequency.ulValue, VTY_NEWLINE);
+#else
+ vty_out(vty, "Tx Freq=%u%s", psr->TxStats.ulFrequencyKhz, VTY_NEWLINE);
+#endif
+}
+
+DEFUN(show_rf_port_stats, show_rf_port_stats_cmd,
+ "show phy <0-255> rf-port-stats <0-1>",
+ "Show statistics for the RF Port\n"
+ "RF Port Number\n")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ static struct octphy_hw_get_cb_data cb_data;
+
+ cb_data.cb = show_rf_port_stats_cb;
+ cb_data.data = vty;
+
+ octphy_hw_get_rf_port_stats(plink->u.octphy.hdl, atoi(argv[1]),
+ &cb_data);
+
+ return CMD_SUCCESS;
+}
+
+void show_clk_sync_stats_cb(struct msgb *resp, void *data)
+{
+ struct vty *vty = (struct vty*) data;
+ tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr;
+
+ if (sizeof(tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP) !=
+ msgb_l2len(resp)) {
+ vty_out(vty,
+ "invalid msgb size (%d bytes, expected %zu bytes)%s",
+ msgb_l2len(resp),
+ sizeof(tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP),
+ VTY_NEWLINE);
+ return;
+ }
+
+ csr = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) msgb_l2(resp);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "CLOCK-SYNC-MGR-STATS:%s", VTY_NEWLINE);
+ vty_out(vty, "State=%s%s",
+ get_value_string(clocksync_state_vals, csr->ulState),
+ VTY_NEWLINE);
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR == 1
+ vty_out(vty, "ClockError=%d%s", csr->lClockError, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES == 1
+ vty_out(vty, "DroppedCycles=%d%s", csr->lDroppedCycles, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ == 1
+ vty_out(vty, "PllFreqHz=%u%s", csr->ulPllFreqHz, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ == 1
+ vty_out(vty, "PllFract=%u%s", csr->ulPllFractionalFreqHz, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT == 1
+ vty_out(vty, "SlipCnt=%u%s", csr->ulSlipCnt, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT == 1
+ vty_out(vty, "SyncLosses=%u%s", csr->ulSyncLossCnt, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT == 1
+ vty_out(vty, "SyncLosses=%u%s", csr->ulSyncLosseCnt, VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE == 1
+ vty_out(vty, "SourceState=%u%s", csr->ulSourceState, VTY_NEWLINE);
+#endif
+ vty_out(vty, "DacValue=%u%s", csr->ulDacValue, VTY_NEWLINE);
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1
+ vty_out(vty, "CLOCK-SYNC-MGR-STATS.resp State=%s%s",
+ get_value_string(clocksync_dac_vals, csr->ulDacState),
+ VTY_NEWLINE);
+#endif
+ vty_out(vty, "LOCK-SYNC-MGR-USR-PROCESS.resp State=%s%s",
+ get_value_string(usr_process_id, csr->ulOwnerProcessUid),
+ VTY_NEWLINE);
+ vty_out(vty, "DacValue=%u%s", csr->ulDacValue, VTY_NEWLINE);
+#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US == 1
+ vty_out(vty, "DriftElapseTime=%u Us%s", csr->ulDriftElapseTimeUs,
+ VTY_NEWLINE);
+#endif
+}
+
+DEFUN(show_clk_sync_stats, show_clk_sync_stats_cmd,
+ "show phy <0-255> clk-sync-stats",
+ "Obtain statistics for the Clock Sync Manager\n")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ static struct octphy_hw_get_cb_data cb_data;
+
+ cb_data.cb = show_clk_sync_stats_cb;
+ cb_data.data = vty;
+
+ octphy_hw_get_clock_sync_stats(plink->u.octphy.hdl, &cb_data);
+ return CMD_SUCCESS;
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+ if (plink->u.octphy.netdev_name)
+ vty_out(vty, " octphy net-device %s%s",
+ plink->u.octphy.netdev_name, VTY_NEWLINE);
+
+ vty_out(vty, " octphy hw-addr %02x:%02x:%02x:%02x:%02x:%02x%s",
+ plink->u.octphy.phy_addr.sll_addr[0],
+ plink->u.octphy.phy_addr.sll_addr[1],
+ plink->u.octphy.phy_addr.sll_addr[2],
+ plink->u.octphy.phy_addr.sll_addr[3],
+ plink->u.octphy.phy_addr.sll_addr[4],
+ plink->u.octphy.phy_addr.sll_addr[5],
+ VTY_NEWLINE);
+ vty_out(vty, " octphy rx-gain %u%s", plink->u.octphy.rx_gain_db,
+ VTY_NEWLINE);
+
+ if (plink->u.octphy.tx_atten_flag) {
+ vty_out(vty, " octphy tx-attenuation %u%s",
+ plink->u.octphy.tx_atten_db, VTY_NEWLINE);
+ } else
+ vty_out(vty, " octphy tx-attenuation oml%s", VTY_NEWLINE);
+
+ vty_out(vty, " octphy rf-port-index %u%s", plink->u.octphy.rf_port_index,
+ VTY_NEWLINE);
+
+#if OCTPHY_USE_ANTENNA_ID == 1
+ vty_out(vty, " octphy tx-ant-id %u%s", plink->u.octphy.tx_ant_id,
+ VTY_NEWLINE);
+
+ vty_out(vty, " octphy rx-ant-id %u%s", plink->u.octphy.rx_ant_id,
+ VTY_NEWLINE);
+#endif
+#if OCTPHY_USE_16X_OVERSAMPLING == 1
+ vty_out(vty, " octphy over-sample-16x %u%s", plink->u.octphy.over_sample_16x,
+ VTY_NEWLINE);
+#endif
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+}
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+}
+
+DEFUN(show_sys_info, show_sys_info_cmd,
+ "show phy <0-255> system-information",
+ SHOW_TRX_STR "Display information about system\n")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ struct octphy_hdl *fl1h;
+
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY number %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ fl1h = plink->u.octphy.hdl;
+
+ vty_out(vty, "System Platform: '%s', Version: '%s'%s",
+ fl1h->info.system.platform, fl1h->info.system.version,
+ VTY_NEWLINE);
+ vty_out(vty, "Application Name: '%s', Description: '%s', Version: '%s'%s",
+ fl1h->info.app.name, fl1h->info.app.description,
+ fl1h->info.app.version, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ install_element(PHY_NODE, &cfg_phy_hwaddr_cmd);
+ install_element(PHY_NODE, &cfg_phy_netdev_cmd);
+ install_element(PHY_NODE, &cfg_phy_rf_port_idx_cmd);
+#if OCTPHY_USE_ANTENNA_ID == 1
+ install_element(PHY_NODE, &cfg_phy_rx_ant_id_cmd);
+ install_element(PHY_NODE, &cfg_phy_tx_ant_id_cmd);
+#endif
+ install_element(PHY_NODE, &cfg_phy_rx_gain_db_cmd);
+ install_element(PHY_NODE, &cfg_phy_tx_atten_db_cmd);
+#if OCTPHY_USE_16X_OVERSAMPLING == 1
+ install_element(PHY_NODE, &cfg_phy_over_sample_16x_cmd);
+#endif
+ install_element_ve(&show_rf_port_stats_cmd);
+ install_element_ve(&show_clk_sync_stats_cmd);
+ install_element_ve(&show_sys_info_cmd);
+
+ return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ return 0;
+}
diff --git a/src/osmo-bts-octphy/octpkt.c b/src/osmo-bts-octphy/octpkt.c
new file mode 100644
index 00000000..d96d93d8
--- /dev/null
+++ b/src/osmo-bts-octphy/octpkt.c
@@ -0,0 +1,158 @@
+/* Utility routines for dealing with OCTPKT/OCTVC1 in msgb */
+
+/* Copyright (c) 2015 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <errno.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+
+#include <osmo-bts/gsm_data.h>
+
+#include <octphy/octpkt/octpkt_hdr.h>
+#include <octphy/octpkt/octpkt_hdr_swap.h>
+#include <octphy/octvc1/octvocnet_pkt.h>
+#include <octphy/octvc1/octvc1_msg.h>
+#include <octphy/octvc1/octvc1_msg_swap.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_api.h>
+
+#include "l1_if.h"
+#include "octpkt.h"
+
+/* push a common header (1 dword) to the start of a msgb */
+void octpkt_push_common_hdr(struct msgb *msg, uint8_t format,
+ uint8_t trace, uint32_t ptype)
+{
+ uint32_t ch;
+ uint32_t *chptr;
+ uint32_t tot_len = msgb_length(msg) + sizeof(ch);
+
+ ch = ((format & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_MASK)
+ << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_OFFSET) |
+ ((trace & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_MASK)
+ << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_OFFSET) |
+ ((ptype & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_MASK)
+ << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_OFFSET) |
+ (tot_len & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_LENGTH_BIT_MASK);
+
+ chptr = (uint32_t *) msgb_push(msg, sizeof(ch));
+ *chptr = htonl(ch);
+}
+
+/* push a control header (3 dwords) to the start of a msgb. This format
+ * is used for command and response packets */
+void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id,
+ uint32_t src_fifo_id, uint32_t socket_id)
+{
+ tOCTVOCNET_PKT_CTL_HEADER *ch;
+
+ ch = (tOCTVOCNET_PKT_CTL_HEADER *) msgb_push(msg, sizeof(*ch));
+
+ ch->ulDestFifoId = htonl(dest_fifo_id);
+ ch->ulSourceFifoId = htonl(src_fifo_id);
+ ch->ulSocketId = htonl(socket_id);
+}
+
+/* common msg_header shared by all control messages. host byte order! */
+void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len,
+ uint32_t sess_id, uint32_t trans_id,
+ uint32_t user_info, uint32_t msg_type,
+ uint32_t flags, uint32_t api_cmd)
+{
+ uint32_t type_r_cmdid;
+ type_r_cmdid = ((msg_type & cOCTVC1_MSG_TYPE_BIT_MASK)
+ << cOCTVC1_MSG_TYPE_BIT_OFFSET) |
+ ((api_cmd & cOCTVC1_MSG_ID_BIT_MASK)
+ << cOCTVC1_MSG_ID_BIT_OFFSET);
+ /* Resync? Flags? */
+
+ mh->ulLength = len;
+ mh->ulTransactionId = trans_id;
+ mh->ul_Type_R_CmdId = type_r_cmdid;
+ mh->ulSessionId = sess_id;
+ mh->ulReturnCode = 0;
+ mh->ulUserInfo = user_info;
+}
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+
+/*! \brief Initialize a packet socket
+ * \param[in] tye Socket type like SOCK_RAW or SOCK_DGRAM
+ * \param[in] proto The link-layer protocol in network byte order
+ * \param[in] bind_dev The name of the interface to bind to (if any)
+ * \param[in] flags flags like \ref OSMO_SOCK_F_BIND
+ *
+ * This function creates a new packet socket of \a type and \a proto
+ * and optionally bnds to it, if stated in the \a flags parameter.
+ */
+int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev,
+ unsigned int flags)
+{
+ int sfd, rc, on = 1;
+
+ if (flags & OSMO_SOCK_F_CONNECT)
+ return -EINVAL;
+
+ sfd = socket(AF_PACKET, type, proto);
+ if (sfd < 0)
+ return -1;
+
+ if (flags & OSMO_SOCK_F_NONBLOCK) {
+ if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
+ perror("cannot set this socket unblocking");
+ close(sfd);
+ return -EINVAL;
+ }
+ }
+
+ if (bind_dev) {
+ struct sockaddr_ll sa;
+ struct ifreq ifr;
+
+ /* resolve the string device name to an ifindex */
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, bind_dev, sizeof(ifr.ifr_name));
+ rc = ioctl(sfd, SIOCGIFINDEX, &ifr);
+ if (rc < 0)
+ goto err;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sll_family = AF_PACKET;
+ sa.sll_protocol = htons(proto);
+ sa.sll_ifindex = ifr.ifr_ifindex;
+ /* according to the packet(7) man page, bind() will only
+ * use sll_protocol nad sll_ifindex */
+ rc = bind(sfd, (struct sockaddr *)&sa, sizeof(sa));
+ if (rc < 0)
+ goto err;
+ }
+
+ return sfd;
+err:
+ close(sfd);
+ return -1;
+}
diff --git a/src/osmo-bts-octphy/octpkt.h b/src/osmo-bts-octphy/octpkt.h
new file mode 100644
index 00000000..fcffec02
--- /dev/null
+++ b/src/osmo-bts-octphy/octpkt.h
@@ -0,0 +1,22 @@
+#pragma once
+#include <stdint.h>
+
+/* push a common header (1 dword) to the start of a msgb */
+void octpkt_push_common_hdr(struct msgb *msg, uint8_t format,
+ uint8_t trace, uint32_t ptype);
+
+/* common msg_header shared by all control messages */
+void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len,
+ uint32_t sess_id, uint32_t trans_id,
+ uint32_t user_info, uint32_t msg_type,
+ uint32_t flags, uint32_t api_cmd);
+
+/* push a control header (3 dwords) to the start of a msgb. This format
+ * is used for command and response packets */
+void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id,
+ uint32_t src_fifo_id, uint32_t socket_id);
+
+int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev,
+ unsigned int flags);
+
+int tx_trx_open(struct gsm_bts_trx *trx);
diff --git a/src/osmo-bts-omldummy/Makefile.am b/src/osmo-bts-omldummy/Makefile.am
new file mode 100644
index 00000000..5a4ce7c5
--- /dev/null
+++ b/src/osmo-bts-omldummy/Makefile.am
@@ -0,0 +1,8 @@
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl
+
+bin_PROGRAMS = osmo-bts-omldummy
+
+osmo_bts_omldummy_SOURCES = main.c bts_model.c
+osmo_bts_omldummy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
diff --git a/src/osmo-bts-omldummy/bts_model.c b/src/osmo-bts-omldummy/bts_model.c
new file mode 100644
index 00000000..c0114015
--- /dev/null
+++ b/src/osmo-bts-omldummy/bts_model.c
@@ -0,0 +1,222 @@
+/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/codec/codec.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+/* TODO: check if dummy method is sufficient, else implement */
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return -1;
+}
+
+/* TODO: check if dummy method is sufficient, else implement */
+int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr,
+ int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return -1;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ return 0;
+}
+
+static uint8_t vbts_set_bts(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+
+ /* report availability of trx to the bts. this will trigger the rsl connection */
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+ }
+ return 0;
+}
+
+static uint8_t vbts_set_trx(struct gsm_bts_trx *trx)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts)
+{
+ return 0;
+}
+
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ int cause = 0;
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_BTS_ATTR:
+ cause = vbts_set_bts(obj);
+ break;
+ case NM_MT_SET_RADIO_ATTR:
+ cause = vbts_set_trx(obj);
+ break;
+ case NM_MT_SET_CHAN_ATTR:
+ cause = vbts_set_ts(obj);
+ break;
+ }
+ return oml_fom_ack_nack(msg, cause);
+}
+
+/* MO: TS 12.21 Managed Object */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ case NM_OC_CHANNEL:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_BTS:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+ rc = oml_mo_opstart_ack(mo);
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ bts->variant = BTS_OSMO_OMLDUMMY;
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+void bts_model_print_help()
+{
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ bts_shutdown(bts, "Abis close");
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ return -ENOTSUP;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan)
+{
+ return;
+}
+
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ return 0;
+}
diff --git a/src/osmo-bts-omldummy/main.c b/src/osmo-bts-omldummy/main.c
new file mode 100644
index 00000000..3f1d58c5
--- /dev/null
+++ b/src/osmo-bts-omldummy/main.c
@@ -0,0 +1,53 @@
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+
+
+int main(int argc, char **argv)
+{
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct e1inp_line *line;
+ int i;
+
+ char *dst_host = argv[1];
+ int site_id = atoi(argv[2]);
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 10*1024);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (!bts)
+ exit(1);
+ bts->ip_access.site_id = site_id;
+ bts->ip_access.bts_id = 0;
+
+ /* Additional TRXs */
+ for (i = 1; i < 8; i++) {
+ trx = gsm_bts_trx_alloc(bts);
+ if (!trx)
+ exit(1);
+ }
+
+ if (bts_init(bts) < 0)
+ exit(1);
+ //btsb = bts_role_bts(bts);
+ abis_init(bts);
+
+
+ line = abis_open(bts, dst_host, "OMLdummy");
+ if (!line)
+ exit(2);
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/osmo-bts-omldummy/respawn.sh b/src/osmo-bts-omldummy/respawn.sh
new file mode 100755
index 00000000..b025d433
--- /dev/null
+++ b/src/osmo-bts-omldummy/respawn.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+while [ -f /etc/passwd ]; do
+ ./osmo-bts-omldummy $*
+ sleep 1
+done
diff --git a/src/osmo-bts-sysmo/Makefile.am b/src/osmo-bts-sysmo/Makefile.am
new file mode 100644
index 00000000..4901ea3c
--- /dev/null
+++ b/src/osmo-bts-sysmo/Makefile.am
@@ -0,0 +1,43 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(SYSMOBTS_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS)
+
+EXTRA_DIST = misc/sysmobts_mgr.h misc/sysmobts_misc.h misc/sysmobts_par.h \
+ misc/sysmobts_eeprom.h misc/sysmobts_nl.h femtobts.h hw_misc.h \
+ misc/sysmobts-layer1.h \
+ l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h
+
+bin_PROGRAMS = osmo-bts-sysmo osmo-bts-sysmo-remote l1fwd-proxy sysmobts-mgr sysmobts-util
+
+COMMON_SOURCES = main.c femtobts.c l1_if.c oml.c sysmobts_vty.c tch.c hw_misc.c calib_file.c \
+ eeprom.c calib_fixup.c utils.c misc/sysmobts_par.c oml_router.c sysmobts_ctrl.c
+
+osmo_bts_sysmo_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+osmo_bts_sysmo_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+osmo_bts_sysmo_remote_SOURCES = $(COMMON_SOURCES) l1_transp_fwd.c
+osmo_bts_sysmo_remote_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+l1fwd_proxy_SOURCES = l1_fwd_main.c l1_transp_hw.c
+l1fwd_proxy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+if ENABLE_SYSMOBTS_CALIB
+bin_PROGRAMS = sysmobts-calib
+
+sysmobts_calib_SOURCES = misc/sysmobts-calib.c misc/sysmobts-layer1.c
+sysmobts_calib_LDADD = -lrt $(COMMON_LDADD)
+endif
+
+sysmobts_mgr_SOURCES = \
+ misc/sysmobts_mgr.c misc/sysmobts_misc.c \
+ misc/sysmobts_par.c misc/sysmobts_nl.c \
+ misc/sysmobts_mgr_2050.c \
+ misc/sysmobts_mgr_vty.c \
+ misc/sysmobts_mgr_nl.c \
+ misc/sysmobts_mgr_temp.c \
+ misc/sysmobts_mgr_calib.c \
+ eeprom.c
+sysmobts_mgr_LDADD = $(LIBGPS_LIBS) $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c
+sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-sysmo/calib_file.c b/src/osmo-bts-sysmo/calib_file.c
new file mode 100644
index 00000000..2f723dd0
--- /dev/null
+++ b/src/osmo-bts-sysmo/calib_file.c
@@ -0,0 +1,475 @@
+/* sysmocom femtobts L1 calibration file routines*/
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1const.h>
+
+#include "l1_if.h"
+#include "femtobts.h"
+#include "eeprom.h"
+#include "utils.h"
+
+struct calib_file_desc {
+ const char *fname;
+ GsmL1_FreqBand_t band;
+ int uplink;
+ int rx;
+};
+
+static const struct calib_file_desc calib_files[] = {
+ {
+ .fname = "calib_rxu_850.cfg",
+ .band = GsmL1_FreqBand_850,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxu_900.cfg",
+ .band = GsmL1_FreqBand_900,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxu_1800.cfg",
+ .band = GsmL1_FreqBand_1800,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxu_1900.cfg",
+ .band = GsmL1_FreqBand_1900,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_850.cfg",
+ .band = GsmL1_FreqBand_850,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_900.cfg",
+ .band = GsmL1_FreqBand_900,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_1800.cfg",
+ .band = GsmL1_FreqBand_1800,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_1900.cfg",
+ .band = GsmL1_FreqBand_1900,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_tx_850.cfg",
+ .band = GsmL1_FreqBand_850,
+ .uplink = 0,
+ .rx = 0,
+ }, {
+ .fname = "calib_tx_900.cfg",
+ .band = GsmL1_FreqBand_900,
+ .uplink = 0,
+ .rx = 0,
+ }, {
+ .fname = "calib_tx_1800.cfg",
+ .band = GsmL1_FreqBand_1800,
+ .uplink = 0,
+ .rx = 0,
+ }, {
+ .fname = "calib_tx_1900.cfg",
+ .band = GsmL1_FreqBand_1900,
+ .uplink = 0,
+ .rx = 0,
+
+ },
+};
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+static const unsigned int arrsize_by_band[] = {
+ [GsmL1_FreqBand_850] = 124,
+ [GsmL1_FreqBand_900] = 194,
+ [GsmL1_FreqBand_1800] = 374,
+ [GsmL1_FreqBand_1900] = 299
+};
+
+static float read_float(FILE *in)
+{
+ int rc;
+ float f = 0.0f;
+
+ rc = fscanf(in, "%f\n", &f);
+ if (rc != 1)
+ LOGP(DL1C, LOGL_ERROR,
+ "Reading a float from calib data failed.\n");
+ return f;
+}
+
+static int read_int(FILE *in)
+{
+ int rc;
+ int i = 0;
+
+ rc = fscanf(in, "%d\n", &i);
+ if (rc != 1)
+ LOGP(DL1C, LOGL_ERROR,
+ "Reading an int from calib data failed.\n");
+ return i;
+}
+
+/* some particular units have calibration data that is incompatible with
+ * firmware >= 3.3, so we need to alter it as follows: */
+static const float delta_by_band[Num_GsmL1_FreqBand] = {
+ [GsmL1_FreqBand_850] = -2.5f,
+ [GsmL1_FreqBand_900] = -2.0f,
+ [GsmL1_FreqBand_1800] = -8.0f,
+ [GsmL1_FreqBand_1900] = -12.0f,
+};
+
+extern const uint8_t fixup_macs[95][6];
+
+static void determine_fixup(struct femtol1_hdl *fl1h)
+{
+ uint8_t macaddr[6];
+ int rc, i;
+
+ rc = eeprom_ReadEthAddr(macaddr);
+ if (rc != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Unable to read Ethenet MAC from EEPROM\n");
+ return;
+ }
+
+ /* assume no fixup is needed */
+ fl1h->fixup_needed = FIXUP_NOT_NEEDED;
+
+ if (fl1h->hw_info.dsp_version[0] < 3 ||
+ (fl1h->hw_info.dsp_version[0] == 3 &&
+ fl1h->hw_info.dsp_version[1] < 3)) {
+ LOGP(DL1C, LOGL_NOTICE, "No calibration table fix-up needed, "
+ "firmware < 3.3\n");
+ return;
+ }
+
+ for (i = 0; i < sizeof(fixup_macs)/6; i++) {
+ if (!memcmp(fixup_macs[i], macaddr, 6)) {
+ fl1h->fixup_needed = FIXUP_NEEDED;
+ break;
+ }
+ }
+
+ LOGP(DL1C, LOGL_NOTICE, "MAC Address is %02x:%02x:%02x:%02x:%02x:%02x -> %s\n",
+ macaddr[0], macaddr[1], macaddr[2], macaddr[3],
+ macaddr[4], macaddr[5],
+ fl1h->fixup_needed == FIXUP_NEEDED ? "FIXUP" : "NO FIXUP");
+}
+
+static int fixup_needed(struct femtol1_hdl *fl1h)
+{
+ if (fl1h->fixup_needed == FIXUP_UNITILIAZED)
+ determine_fixup(fl1h);
+
+ return fl1h->fixup_needed == FIXUP_NEEDED;
+}
+#endif /* API 2.4.0 */
+
+static void calib_fixup_rx(struct femtol1_hdl *fl1h, SuperFemto_Prim_t *prim)
+{
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq;
+
+ if (fixup_needed(fl1h))
+ rx->fExtRxGain += delta_by_band[rx->freqBand];
+#endif
+}
+
+static int calib_file_read(const char *path, const struct calib_file_desc *desc,
+ SuperFemto_Prim_t *prim)
+{
+ FILE *in;
+ char fname[PATH_MAX];
+
+ fname[0] = '\0';
+ snprintf(fname, sizeof(fname)-1, "%s/%s", path, desc->fname);
+ fname[sizeof(fname)-1] = '\0';
+
+ in = fopen(fname, "r");
+ if (!in) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to open '%s' for calibration data.\n", fname);
+ return -1;
+ }
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ int i;
+ if (desc->rx) {
+ SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq;
+ memset(rx, 0, sizeof(*rx));
+
+ prim->id = SuperFemto_PrimId_SetRxCalibTblReq;
+
+ rx->freqBand = desc->band;
+ rx->bUplink = desc->uplink;
+
+ rx->fExtRxGain = read_float(in);
+ rx->fRxMixGainCorr = read_float(in);
+
+ for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++)
+ rx->fRxLnaGainCorr[i] = read_float(in);
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ rx->fRxRollOffCorr[i] = read_float(in);
+
+ if (desc->uplink) {
+ rx->u8IqImbalMode = read_int(in);
+ printf("%s: u8IqImbalMode=%d\n", desc->fname, rx->u8IqImbalMode);
+
+ for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++)
+ rx->u16IqImbalCorr[i] = read_int(in);
+ }
+ } else {
+ SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq;
+ memset(tx, 0, sizeof(*tx));
+
+ prim->id = SuperFemto_PrimId_SetTxCalibTblReq;
+
+ tx->freqBand = desc->band;
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++)
+ tx->fTxGainGmsk[i] = read_float(in);
+
+ tx->fTx8PskCorr = read_float(in);
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++)
+ tx->fTxExtAttCorr[i] = read_float(in);
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ tx->fTxRollOffCorr[i] = read_float(in);
+ }
+#else
+#warning Format of calibration tables before API version 2.4.0 not supported
+#endif
+ fclose(in);
+
+ return 0;
+}
+
+static int calib_eeprom_read(const struct calib_file_desc *desc, SuperFemto_Prim_t *prim)
+{
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ eeprom_Error_t eerr;
+ int i;
+ if (desc->rx) {
+ SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq;
+ eeprom_RxCal_t rx_cal;
+
+ memset(rx, 0, sizeof(*rx));
+
+ prim->id = SuperFemto_PrimId_SetRxCalibTblReq;
+
+ rx->freqBand = desc->band;
+ rx->bUplink = desc->uplink;
+
+ eerr = eeprom_ReadRxCal(desc->band, desc->uplink, &rx_cal);
+ if (eerr != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR, "Error reading RxCalibration "
+ "from EEPROM, band=%d, ul=%d, err=%d\n",
+ desc->band, desc->uplink, eerr);
+ return -EIO;
+ }
+
+ rx->fExtRxGain = rx_cal.fExtRxGain;
+ rx->fRxMixGainCorr = rx_cal.fRxMixGainCorr;
+
+ for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++)
+ rx->fRxLnaGainCorr[i] = rx_cal.fRxLnaGainCorr[i];
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ rx->fRxRollOffCorr[i] = rx_cal.fRxRollOffCorr[i];
+
+ if (desc->uplink) {
+ rx->u8IqImbalMode = rx_cal.u8IqImbalMode;
+
+ for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++)
+ rx->u16IqImbalCorr[i] = rx_cal.u16IqImbalCorr[i];
+ }
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0)
+ rx->u8DspMajVer = rx_cal.u8DspMajVer;
+ rx->u8DspMinVer = rx_cal.u8DspMinVer;
+ rx->u8FpgaMajVer = rx_cal.u8FpgaMajVer;
+ rx->u8FpgaMinVer = rx_cal.u8FpgaMinVer;
+#endif
+ } else {
+ SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq;
+ eeprom_TxCal_t tx_cal;
+
+ memset(tx, 0, sizeof(*tx));
+
+ prim->id = SuperFemto_PrimId_SetTxCalibTblReq;
+ tx->freqBand = desc->band;
+
+ eerr = eeprom_ReadTxCal(desc->band, &tx_cal);
+ if (eerr != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR, "Error reading TxCalibration "
+ "from EEPROM, band=%d, err=%d\n",
+ desc->band, eerr);
+ return -EIO;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++)
+ tx->fTxGainGmsk[i] = tx_cal.fTxGainGmsk[i];
+
+ tx->fTx8PskCorr = tx_cal.fTx8PskCorr;
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++)
+ tx->fTxExtAttCorr[i] = tx_cal.fTxExtAttCorr[i];
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ tx->fTxRollOffCorr[i] = tx_cal.fTxRollOffCorr[i];
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0)
+ tx->u8DspMajVer = tx_cal.u8DspMajVer;
+ tx->u8DspMinVer = tx_cal.u8DspMinVer;
+ tx->u8FpgaMajVer = tx_cal.u8FpgaMajVer;
+ tx->u8FpgaMinVer = tx_cal.u8FpgaMinVer;
+#endif
+ }
+#endif
+
+ return 0;
+}
+
+/* determine next calibration file index based on supported bands */
+static int next_calib_file_idx(uint32_t band_mask, int last_idx)
+{
+ int i;
+
+ for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
+ int band = band_femto2osmo(calib_files[i].band);
+ if (band < 0)
+ continue;
+ if (band_mask & band)
+ return i;
+ }
+ return -1;
+}
+
+/* iteratively download the calibration data into the L1 */
+
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data);
+
+/* send the calibration table for a single specified file */
+static int calib_file_send(struct femtol1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ struct msgb *msg;
+ char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path;
+ int rc;
+
+ msg = sysp_msgb_alloc();
+
+ if (calib_path)
+ rc = calib_file_read(calib_path, desc, msgb_sysprim(msg));
+ else
+ rc = calib_eeprom_read(desc, msgb_sysprim(msg));
+ if (rc < 0) {
+ msgb_free(msg);
+
+ /* still, we'd like to continue trying to load
+ * calibration for all other bands */
+ st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support,
+ st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ else
+ return rc;
+ }
+ calib_fixup_rx(fl1h, msgb_sysprim(msg));
+
+ return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
+}
+
+/* completion callback after every SetCalibTbl is confirmed */
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path;
+
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded (src: %s)\n",
+ calib_files[st->last_file_idx].fname,
+ calib_path ? "file" : "eeprom");
+
+ msgb_free(l1_msg);
+
+ st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support,
+ st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ eeprom_free_resources();
+
+ return 0;
+}
+
+
+int calib_load(struct femtol1_hdl *fl1h)
+{
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+ LOGP(DL1C, LOGL_ERROR, "L1 calibration is not supported on pre 2.4.0 firmware.\n");
+ return -1;
+#else
+ int idx = next_calib_file_idx(fl1h->hw_info.band_support, -1);
+ if (idx < 0) {
+ LOGP(DL1C, LOGL_ERROR, "No band_support?!?\n");
+ return -1;
+ }
+ return calib_file_send(fl1h, &calib_files[idx]);
+#endif
+}
+
+
+#if 0
+int main(int argc, char **argv)
+{
+ SuperFemto_Prim_t p;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(calib_files); i++) {
+ memset(&p, 0, sizeof(p));
+ calib_read_file(argv[1], &calib_files[i], &p);
+ }
+ exit(0);
+}
+#endif
diff --git a/src/osmo-bts-sysmo/calib_fixup.c b/src/osmo-bts-sysmo/calib_fixup.c
new file mode 100644
index 00000000..29dd34dd
--- /dev/null
+++ b/src/osmo-bts-sysmo/calib_fixup.c
@@ -0,0 +1,101 @@
+/* AUTOGENERATED, DO NOT EDIT */
+
+#include <stdint.h>
+
+const uint8_t fixup_macs[95][6] = {
+ { 0x00, 0x0D, 0xCC, 0x08, 0x02, 0x3B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x31 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x32 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x33 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x34 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x35 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x36 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x37 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x38 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x39 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x40 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x41 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x42 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x43 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x44 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x45 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x46 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x47 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x48 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x49 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4F },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x50 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x51 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x52 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x53 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x55 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x56 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x57 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x58 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x59 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5F },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x60 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x97 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x98 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x99 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9F },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA0 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA1 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA3 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA4 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA5 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA6 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA7 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA8 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA9 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAA },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAB },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAC },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAD },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAE },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAF },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB0 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB1 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB2 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB3 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB4 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB5 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB6 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB7 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB8 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB9 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBA },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBB },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBC },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBE },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBF },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC0 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC1 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC3 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC6 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC7 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC8 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC9 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCA },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCB },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCD },
+};
diff --git a/src/osmo-bts-sysmo/eeprom.c b/src/osmo-bts-sysmo/eeprom.c
new file mode 100644
index 00000000..472b78e2
--- /dev/null
+++ b/src/osmo-bts-sysmo/eeprom.c
@@ -0,0 +1,1804 @@
+// $Id: $
+/****************************************************************************
+ *
+ * **** I
+ * ****** ***
+ * ******* ****
+ * ******** **** **** **** ********* ******* **** ***********
+ * ********* **** **** **** ********* ************** *************
+ * **** ***** **** **** **** **** ***** ****** ***** ****
+ * **** ***** **** **** **** **** ***** **** **** ****
+ * **** ********* **** **** **** **** **** **** ****
+ * **** ******** **** ****I **** ***** ***** **** ****
+ * **** ****** ***** ****** ***** ****** ******* ****** *******
+ * **** **** ************ ****** ************* *************
+ * **** *** **** **** **** ***** **** ***** ****
+ * ****
+ * I N N O V A T I O N T O D A Y F O R T O M M O R O W ****
+ * ***
+ *
+ ************************************************************************//**
+ *
+ * @file eeprom.c
+ * @brief SuperFemto EEPROM interface.
+ *
+ * Author : Yves Godin
+ * Date : 2012
+ * $Revision: $
+ *
+ * Copyright (c) Nutaq. 2012
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ ***************************************************************************
+ *
+ * "$Revision: $"
+ * "$Name: $"
+ * "$Date: $"
+ *
+ ***************************************************************************/
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "eeprom.h"
+
+//#define DISP_ERROR 1
+
+#ifdef DISP_ERROR
+#define PERROR(x, args ...) fprintf(stderr, x, ## args)
+#else
+#define PERROR(x, args ...) do { } while (0)
+#endif
+
+/****************************************************************************
+ * Private constants *
+ ****************************************************************************/
+
+/**
+ * EEPROM device file
+ */
+#define EEPROM_DEV "/sys/bus/i2c/devices/i2c-1/1-0050/eeprom"
+
+/**
+ * EEPROM configuration start address
+ */
+#define EEPROM_CFG_START_ADDR 0x0100
+
+/**
+ * EEPROM configuration max size
+ */
+#define EEPROM_CFG_MAX_SIZE (0x2000 - EEPROM_CFG_START_ADDR)
+
+/**
+ * EEPROM config magic ID
+ */
+#define EEPROM_CFG_MAGIC_ID 0x53464548
+
+/**
+ * EEPROM header version
+ */
+#define EEPROM_HDR_V1 1
+#define EEPROM_HDR_V2 2
+
+/**
+ * EEPROM section ID
+ */
+typedef enum
+{
+ EEPROM_SID_SYSINFO = 0x1000, ///< System information
+ EEPROM_SID_RFCLOCK_CAL = 0x2000, ///< RF Clock Calibration
+ EEPROM_SID_GSM850_TXCAL = 0x3000, ///< GSM-850 TX Calibration Table
+ EEPROM_SID_GSM850_RXUCAL = 0x3010, ///< GSM-850 RX Uplink Calibration Table
+ EEPROM_SID_GSM850_RXDCAL = 0x3020, ///< GSM-850 RX Downlink Calibration Table
+ EEPROM_SID_GSM900_TXCAL = 0x3100, ///< GSM-900 TX Calibration Table
+ EEPROM_SID_GSM900_RXUCAL = 0x3110, ///< GSM-900 RX Uplink Calibration Table
+ EEPROM_SID_GSM900_RXDCAL = 0x3120, ///< GSM-900 RX Downlink Calibration Table
+ EEPROM_SID_DCS1800_TXCAL = 0x3200, ///< DCS-1800 TX Calibration Table
+ EEPROM_SID_DCS1800_RXUCAL = 0x3210, ///< DCS-1800 RX Uplink Calibration Table
+ EEPROM_SID_DCS1800_RXDCAL = 0x3220, ///< DCS-1800 RX Downlink Calibration Table
+ EEPROM_SID_PCS1900_TXCAL = 0x3300, ///< PCS-1900 TX Calibration Table
+ EEPROM_SID_PCS1900_RXUCAL = 0x3310, ///< PCS-1900 RX Uplink Calibration Table
+ EEPROM_SID_PCS1900_RXDCAL = 0x3320, ///< PCS-1900 RX Downlink Calibration Table
+ EEPROM_SID_ASSY = 0x3400 ///< Assembly information
+} eeprom_SID_t;
+
+/****************************************************************************
+ * Private types *
+ ****************************************************************************/
+
+/**
+ * TX calibration table (common part) V1
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm
+ int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB)
+ int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB)
+ int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN
+} __attribute__((packed)) eeprom_CfgTxCal_t;
+
+/**
+ * RX calibration table (common part) V1
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto)
+ uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation
+
+ int16_t sfixExtRxGain; ///< [Q6.9] External RX gain
+ int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation
+ int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation
+} __attribute__((packed)) eeprom_CfgRxCal_t;
+
+/**
+ * TX calibration table (common part) V2
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm
+ int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB)
+ int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB)
+ int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN
+} __attribute__((packed)) eeprom_CfgTxCalV2_t;
+
+/**
+ * RX calibration table (common part) V2
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer ; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto)
+ uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation
+ int16_t sfixExtRxGain; ///< [Q6.9] External RX gain
+ int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation
+ int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation
+} __attribute__((packed)) eeprom_CfgRxCalV2_t;
+
+
+/**
+ * EEPROM configuration area format
+ */
+typedef struct
+{
+ struct
+ {
+ uint32_t u32MagicId; ///< Magic ID (0x53464548)
+ uint32_t u16Version : 16; ///< Header format version (v1)
+ uint32_t : 16; ///< unused
+ } hdr;
+
+ union
+ {
+ /** EEPROM Format V1 */
+ struct
+ {
+ /** System information */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ char szSn[16]; ///< Serial number
+ uint32_t u8Rev : 8; ///< Board revision
+ uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t : 12; ///< unused
+ } __attribute__((packed)) sysInfo;
+
+ /** RF Clock configuration */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ int i24ClkCor :24; ///< Clock correction value in PPB.
+ uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge)
+ } __attribute__((packed)) rfClk;
+
+ /** GSM-850 TX Calibration Table */
+ eeprom_CfgTxCal_t gsm850TxCal;
+ uint16_t __gsm850TxCalMem[124];
+
+ /** GSM-850 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t gsm850RxuCal;
+ uint16_t __gsm850RxuCalMem[124];
+
+ /** GSM-850 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t gsm850RxdCal;
+ uint16_t __gsm850RxdCalMem[124];
+
+ /** GSM-900 TX Calibration Table */
+ eeprom_CfgTxCal_t gsm900TxCal;
+ uint16_t __gsm900TxCalMem[194];
+
+ /** GSM-900 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t gsm900RxuCal;
+ uint16_t __gsm900RxuCalMem[194];
+
+ /** GSM-900 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t gsm900RxdCal;
+ uint16_t __gsm900RxdCalMem[194];
+
+ /** DCS-1800 TX Calibration Table */
+ eeprom_CfgTxCal_t dcs1800TxCal;
+ uint16_t __dcs1800TxCalMem[374];
+
+ /** DCS-1800 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t dcs1800RxuCal;
+ uint16_t __dcs1800RxuCalMem[374];
+
+ /** DCS-1800 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t dcs1800RxdCal;
+ uint16_t __dcs1800RxdCalMem[374];
+
+ /** PCS-1900 TX Calibration Table */
+ eeprom_CfgTxCal_t pcs1900TxCal;
+ uint16_t __pcs1900TxCalMem[299];
+
+ /** PCS-1900 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t pcs1900RxuCal;
+ uint16_t __pcs1900RxuCalMem[299];
+
+ /** PCS-1900 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t pcs1900RxdCal;
+ uint16_t __pcs1900RxdCalMem[299];
+
+ } __attribute__((packed)) v1;
+
+ /** EEPROM Format V2 */
+ struct
+ {
+ /** System information */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ char szSn[16]; ///< Serial number
+ uint32_t u8Rev : 8; ///< Board revision
+ uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t : 12; ///< unused
+ } __attribute__((packed)) sysInfo;
+
+ /** RF Clock configuration */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ int i24ClkCor :24; ///< Clock correction value in PPB.
+ uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge)
+ } __attribute__((packed)) rfClk;
+
+ /** GSM-850 TX Calibration Table */
+ eeprom_CfgTxCalV2_t gsm850TxCalV2;
+ uint16_t __gsm850TxCalMemV2[124];
+
+ /** GSM-850 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm850RxuCalV2;
+ uint16_t __gsm850RxuCalMemV2[124];
+
+ /** GSM-850 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm850RxdCalV2;
+ uint16_t __gsm850RxdCalMemV2[124];
+
+ /** GSM-900 TX Calibration Table */
+ eeprom_CfgTxCalV2_t gsm900TxCalV2;
+ uint16_t __gsm900TxCalMemV2[194];
+
+ /** GSM-900 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm900RxuCalV2;
+ uint16_t __gsm900RxuCalMemV2[194];
+
+ /** GSM-900 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm900RxdCalV2;
+ uint16_t __gsm900RxdCalMemV2[194];
+
+ /** DCS-1800 TX Calibration Table */
+ eeprom_CfgTxCalV2_t dcs1800TxCalV2;
+ uint16_t __dcs1800TxCalMemV2[374];
+
+ /** DCS-1800 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t dcs1800RxuCalV2;
+ uint16_t __dcs1800RxuCalMemV2[374];
+
+ /** DCS-1800 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t dcs1800RxdCalV2;
+ uint16_t __dcs1800RxdCalMemV2[374];
+
+ /** PCS-1900 TX Calibration Table */
+ eeprom_CfgTxCalV2_t pcs1900TxCalV2;
+ uint16_t __pcs1900TxCalMemV2[299];
+
+ /** PCS-1900 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t pcs1900RxuCalV2;
+ uint16_t __pcs1900RxuCalMemV2[299];
+
+ /** PCS-1900 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t pcs1900RxdCalV2;
+ uint16_t __pcs1900RxdCalMemV2[299];
+
+ /** Assembly information */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ char szSn[16]; ///< System serial number
+ char szPartNum[20]; ///< System part number
+ uint8_t u8TsID ; ///< Test station ID
+ uint8_t u8TstVer ; ///< Test version
+ uint8_t u8PaType; ///< PA type (0: None, 1-254 supported, 255 ; Unknown)
+ uint8_t u8PaBand; ///< PA GSM band (0: Unknown, 1: 850 MHz, 2: 900 MHz, 4: 1800 MHz, 8: 1900 MHz)
+ uint8_t u8PaMajVer; ///< PA major version
+ uint8_t u8PaMinVer; ///< PA minor version
+ } __attribute__((packed)) assyInfo;
+ } __attribute__((packed)) v2;
+ } __attribute__((packed)) cfg;
+} __attribute__((packed)) eeprom_Cfg_t;
+
+
+
+/****************************************************************************
+ * Private routine prototypes *
+ ****************************************************************************/
+
+static int eeprom_read( int addr, int size, char *pBuff );
+static int eeprom_write( int addr, int size, const char *pBuff );
+static uint16_t eeprom_crc( uint8_t *pu8Data, int len );
+static eeprom_Cfg_t *eeprom_cached_config(void);
+
+
+/****************************************************************************
+ * Public functions *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Function : eeprom_ResetCfg
+ ************************************************************************//**
+ *
+ * This function reset the content of the EEPROM config area.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ResetCfg( void )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Clear the structure
+ memset( &ee, 0xFF, sizeof(eeprom_Cfg_t) );
+
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) )
+ {
+ return EEPROM_ERR_DEVICE;
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr )
+{
+ int err;
+
+ err = eeprom_read(0, 6, (char *) ethaddr);
+ if ( err != 6 )
+ {
+ return EEPROM_ERR_DEVICE;
+ }
+ return EEPROM_SUCCESS;
+}
+
+/****************************************************************************
+ * Function : eeprom_ReadSysInfo
+ ************************************************************************//**
+ *
+ * This function reads the system information from the EEPROM.
+ *
+ * @param [inout] pTime
+ * Pointer to a system info structure.
+ *
+ * @param [inout] pSysInfo
+ * Pointer to a system info structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ PERROR( "Invalid EEPROM format\n" );
+ return EEPROM_ERR_INVALID;
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ case EEPROM_HDR_V2:
+ {
+ // Get a copy of the EEPROM section
+ err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), sizeof(ee.cfg.v1.sysInfo), (char *)&ee.cfg.v1.sysInfo );
+ if ( err != sizeof(ee.cfg.v1.sysInfo) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the ID
+ if ( ee.cfg.v1.sysInfo.u16SectionID != EEPROM_SID_SYSINFO )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.sysInfo.u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ memcpy( (void *)pSysInfo->szSn, ee.cfg.v1.sysInfo.szSn, sizeof(pSysInfo->szSn) );
+ pSysInfo->u8Rev = ee.cfg.v1.sysInfo.u8Rev;
+ pSysInfo->u8Tcxo = ee.cfg.v1.sysInfo.u2Tcxo;
+ pSysInfo->u8Ocxo = ee.cfg.v1.sysInfo.u2Ocxo;
+ pSysInfo->u8GSM850 = ee.cfg.v1.sysInfo.u2GSM850;
+ pSysInfo->u8GSM900 = ee.cfg.v1.sysInfo.u2GSM900;
+ pSysInfo->u8DCS1800 = ee.cfg.v1.sysInfo.u2DCS1800;
+ pSysInfo->u8PCS1900 = ee.cfg.v1.sysInfo.u2PCS1900;
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteSysInfo
+ ************************************************************************//**
+ *
+ * This function writes the system information to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ ee.cfg.v1.sysInfo.u16SectionID = EEPROM_SID_SYSINFO;
+ ee.cfg.v1.sysInfo.u16Crc = 0;
+ ee.cfg.v1.sysInfo.u32Time = time(NULL);
+
+ // Compress the info
+ memcpy( ee.cfg.v1.sysInfo.szSn, pSysInfo->szSn, sizeof(ee.cfg.v1.sysInfo.szSn) );
+ ee.cfg.v1.sysInfo.u8Rev = pSysInfo->u8Rev;
+ ee.cfg.v1.sysInfo.u2Tcxo = pSysInfo->u8Tcxo;
+ ee.cfg.v1.sysInfo.u2Ocxo = pSysInfo->u8Ocxo;
+ ee.cfg.v1.sysInfo.u2GSM850 = pSysInfo->u8GSM850;
+ ee.cfg.v1.sysInfo.u2GSM900 = pSysInfo->u8GSM900;
+ ee.cfg.v1.sysInfo.u2DCS1800 = pSysInfo->u8DCS1800;
+ ee.cfg.v1.sysInfo.u2PCS1900 = pSysInfo->u8PCS1900;
+
+ // Add the CRC
+ ee.cfg.v1.sysInfo.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), sizeof(ee.cfg.v1.sysInfo), (const char *) &ee.cfg.v1.sysInfo );
+ if ( err != sizeof(ee.cfg.v1.sysInfo) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_ReadRfClockCal
+ ************************************************************************//**
+ *
+ * This function reads the RF clock calibration data from the EEPROM.
+ *
+ * @param [inout] pRfClockCal
+ * Pointer to a RF clock calibration structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee), (char *) &ee );
+ if ( err != sizeof(ee) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ PERROR( "Invalid EEPROM format\n" );
+ return EEPROM_ERR_INVALID;
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ case EEPROM_HDR_V2:
+ {
+ // Get a copy of the EEPROM section
+ err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), sizeof(ee.cfg.v1.rfClk), (char *)&ee.cfg.v1.rfClk );
+ if ( err != sizeof(ee.cfg.v1.rfClk) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the ID
+ if ( ee.cfg.v1.rfClk.u16SectionID != EEPROM_SID_RFCLOCK_CAL )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.rfClk.u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ pRfClockCal->iClkCor = ee.cfg.v1.rfClk.i24ClkCor;
+ pRfClockCal->u8ClkSrc = ee.cfg.v1.rfClk.u8ClkSrc;
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteRfClockCal
+ ************************************************************************//**
+ *
+ * This function writes the RF clock calibration data to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ ee.cfg.v1.rfClk.u16SectionID = EEPROM_SID_RFCLOCK_CAL;
+ ee.cfg.v1.rfClk.u16Crc = 0;
+ ee.cfg.v1.rfClk.u32Time = time(NULL);
+
+ // Compress the info
+ ee.cfg.v1.rfClk.i24ClkCor = pRfClockCal->iClkCor;
+ ee.cfg.v1.rfClk.u8ClkSrc = pRfClockCal->u8ClkSrc;
+
+ // Add the CRC
+ ee.cfg.v1.rfClk.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), sizeof(ee.cfg.v1.rfClk), (const char *) &ee.cfg.v1.rfClk );
+ if ( err != sizeof(ee.cfg.v1.rfClk) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_ReadTxCal
+ ************************************************************************//**
+ *
+ * This function reads the TX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [inout] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal )
+{
+ int i;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t *ee = eeprom_cached_config();
+ eeprom_SID_t sId;
+ eeprom_CfgTxCal_t *pCfgTxCal = NULL;
+ eeprom_CfgTxCalV2_t *pCfgTxCalV2 = NULL;
+
+ // Get a copy of the EEPROM header
+ if (!ee)
+ {
+ PERROR( "Reading cached content failed.\n" );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ switch ( ee->hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ {
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ sId = EEPROM_SID_GSM850_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.gsm850TxCal;
+ size = sizeof(ee->cfg.v1.gsm850TxCal) + sizeof(ee->cfg.v1.__gsm850TxCalMem);
+ break;
+ case 1:
+ nArfcn = 194;
+ sId = EEPROM_SID_GSM900_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.gsm900TxCal;
+ size = sizeof(ee->cfg.v1.gsm900TxCal) + sizeof(ee->cfg.v1.__gsm900TxCalMem);
+ break;
+ case 2:
+ nArfcn = 374;
+ sId = EEPROM_SID_DCS1800_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.dcs1800TxCal;
+ size = sizeof(ee->cfg.v1.dcs1800TxCal) + sizeof(ee->cfg.v1.__dcs1800TxCalMem);
+ break;
+ case 3:
+ nArfcn = 299;
+ sId = EEPROM_SID_PCS1900_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.pcs1900TxCal;
+ size = sizeof(ee->cfg.v1.pcs1900TxCal) + sizeof(ee->cfg.v1.__pcs1900TxCalMem);
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ // Validate the ID
+ if ( pCfgTxCal->u16SectionID != sId )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCal->u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ for ( i = 0; i < 80; i++ )
+ {
+ pTxCal->fTxGainGmsk[i] = (float)pCfgTxCal->sfixTxGainGmsk[i] * 0.03125f;
+ }
+ pTxCal->fTx8PskCorr = (float)pCfgTxCal->sfixTx8PskCorr * 0.001953125f;
+ for ( i = 0; i < 31; i++ )
+ {
+ pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCal->sfixTxExtAttCorr[i] * 0.001953125f;
+ }
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCal->sfixTxRollOffCorr[i] * 0.001953125f;
+ }
+
+ //DSP firmware version
+ pTxCal->u8DspMajVer = 0;
+ pTxCal->u8DspMinVer = 0;
+
+ //FPGA firmware version
+ pTxCal->u8FpgaMajVer = 0;
+ pTxCal->u8FpgaMinVer = 0;
+
+ break;
+ }
+
+ case EEPROM_HDR_V2:
+ {
+
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ sId = EEPROM_SID_GSM850_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.gsm850TxCalV2;
+ size = sizeof(ee->cfg.v2.gsm850TxCalV2) + sizeof(ee->cfg.v2.__gsm850TxCalMemV2);
+ break;
+ case 1:
+ nArfcn = 194;
+ sId = EEPROM_SID_GSM900_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.gsm900TxCalV2;
+ size = sizeof(ee->cfg.v2.gsm900TxCalV2) + sizeof(ee->cfg.v2.__gsm900TxCalMemV2);
+ break;
+ case 2:
+ nArfcn = 374;
+ sId = EEPROM_SID_DCS1800_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.dcs1800TxCalV2;
+ size = sizeof(ee->cfg.v2.dcs1800TxCalV2) + sizeof(ee->cfg.v2.__dcs1800TxCalMemV2);
+ break;
+ case 3:
+ nArfcn = 299;
+ sId = EEPROM_SID_PCS1900_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.pcs1900TxCalV2;
+ size = sizeof(ee->cfg.v2.pcs1900TxCalV2) + sizeof(ee->cfg.v2.__pcs1900TxCalMemV2);
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+
+ // Validate the ID
+ if ( pCfgTxCalV2->u16SectionID != sId )
+ {
+ PERROR( "Uninitialised data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgTxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCalV2->u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ for ( i = 0; i < 80; i++ )
+ {
+ pTxCal->fTxGainGmsk[i] = (float)pCfgTxCalV2->sfixTxGainGmsk[i] * 0.03125f;
+ }
+ pTxCal->fTx8PskCorr = (float)pCfgTxCalV2->sfixTx8PskCorr * 0.001953125f;
+ for ( i = 0; i < 31; i++ )
+ {
+ pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCalV2->sfixTxExtAttCorr[i] * 0.001953125f;
+ }
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCalV2->sfixTxRollOffCorr[i] * 0.001953125f;
+ }
+
+ //DSP firmware version
+ pTxCal->u8DspMajVer = pCfgTxCalV2->u8DspMajVer;
+ pTxCal->u8DspMinVer = pCfgTxCalV2->u8DspMinVer;
+
+ //FPGA firmware version
+ pTxCal->u8FpgaMajVer = pCfgTxCalV2->u8FpgaMajVer;
+ pTxCal->u8FpgaMinVer = pCfgTxCalV2->u8FpgaMinVer;
+
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteTxCal
+ ************************************************************************//**
+ *
+ * This function writes the TX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal )
+{
+ int i;
+ int err;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t ee;
+ eeprom_SID_t sId;
+ eeprom_CfgTxCalV2_t *pCfgTxCal = NULL;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ int32_t fixVal;
+
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ sId = EEPROM_SID_GSM850_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.gsm850TxCalV2;
+ size = sizeof(ee.cfg.v2.gsm850TxCalV2) + sizeof(ee.cfg.v2.__gsm850TxCalMemV2);
+ break;
+ case 1:
+ nArfcn = 194;
+ sId = EEPROM_SID_GSM900_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.gsm900TxCalV2;
+ size = sizeof(ee.cfg.v2.gsm900TxCalV2) + sizeof(ee.cfg.v2.__gsm900TxCalMemV2);
+ break;
+ case 2:
+ nArfcn = 374;
+ sId = EEPROM_SID_DCS1800_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.dcs1800TxCalV2;
+ size = sizeof(ee.cfg.v2.dcs1800TxCalV2) + sizeof(ee.cfg.v2.__dcs1800TxCalMemV2);
+ break;
+ case 3:
+ nArfcn = 299;
+ sId = EEPROM_SID_PCS1900_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.pcs1900TxCalV2;
+ size = sizeof(ee.cfg.v2.pcs1900TxCalV2) + sizeof(ee.cfg.v2.__pcs1900TxCalMemV2);
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ pCfgTxCal->u16SectionID = sId;
+ pCfgTxCal->u16Crc = 0;
+ pCfgTxCal->u32Time = time(NULL);
+
+ //DSP firmware version
+ pCfgTxCal->u8DspMajVer = pTxCal->u8DspMajVer;
+ pCfgTxCal->u8DspMinVer = pTxCal->u8DspMinVer;
+
+ //FPGA firmware version
+ pCfgTxCal->u8FpgaMajVer = pTxCal->u8FpgaMajVer;
+ pCfgTxCal->u8FpgaMinVer = pTxCal->u8FpgaMinVer;
+
+ // Compress the calibration tables
+ for ( i = 0; i < 80; i++ )
+ {
+ fixVal = (int32_t)(pTxCal->fTxGainGmsk[i] * 32.f + (pTxCal->fTxGainGmsk[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTxGainGmsk[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTxGainGmsk[i] = -32768;
+ else pCfgTxCal->sfixTxGainGmsk[i] = (int16_t)fixVal;
+ }
+ fixVal = (int32_t)(pTxCal->fTx8PskCorr * 512.f + (pTxCal->fTx8PskCorr>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTx8PskCorr = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTx8PskCorr = -32768;
+ else pCfgTxCal->sfixTx8PskCorr = (int16_t)fixVal;
+ for ( i = 0; i < 31; i++ )
+ {
+ fixVal = (int32_t)(pTxCal->fTxExtAttCorr[i] * 512.f + (pTxCal->fTxExtAttCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTxExtAttCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTxExtAttCorr[i] = -32768;
+ else pCfgTxCal->sfixTxExtAttCorr[i] = (int16_t)fixVal;
+ }
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ fixVal = (int32_t)(pTxCal->fTxRollOffCorr[i] * 512.f + (pTxCal->fTxRollOffCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTxRollOffCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTxRollOffCorr[i] = -32768;
+ else pCfgTxCal->sfixTxRollOffCorr[i] = (int16_t)fixVal;
+ }
+
+ // Add the CRC
+ pCfgTxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint8_t*)pCfgTxCal - (uint8_t*)&ee), size, (const char *)pCfgTxCal );
+ if ( err != size )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_ReadRxCal
+ ************************************************************************//**
+ *
+ * This function reads the RX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [inout] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal )
+{
+ int i;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t *ee = eeprom_cached_config();
+ eeprom_SID_t sId;
+ eeprom_CfgRxCal_t *pCfgRxCal = NULL;
+ eeprom_CfgRxCalV2_t *pCfgRxCalV2 = NULL;
+
+
+ if (!ee)
+ {
+ PERROR( "Reading cached content failed.\n" );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ switch ( ee->hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ {
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM850_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm850RxuCal;
+ size = sizeof(ee->cfg.v1.gsm850RxuCal) + sizeof(ee->cfg.v1.__gsm850RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM850_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm850RxdCal;
+ size = sizeof(ee->cfg.v1.gsm850RxdCal) + sizeof(ee->cfg.v1.__gsm850RxdCalMem);
+ }
+ break;
+ case 1:
+ nArfcn = 194;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM900_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm900RxuCal;
+ size = sizeof(ee->cfg.v1.gsm900RxuCal) + sizeof(ee->cfg.v1.__gsm900RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM900_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm900RxdCal;
+ size = sizeof(ee->cfg.v1.gsm900RxdCal) + sizeof(ee->cfg.v1.__gsm900RxdCalMem);
+ }
+ break;
+ case 2:
+ nArfcn = 374;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_DCS1800_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.dcs1800RxuCal;
+ size = sizeof(ee->cfg.v1.dcs1800RxuCal) + sizeof(ee->cfg.v1.__dcs1800RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_DCS1800_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.dcs1800RxdCal;
+ size = sizeof(ee->cfg.v1.dcs1800RxdCal) + sizeof(ee->cfg.v1.__dcs1800RxdCalMem);
+ }
+ break;
+ case 3:
+ nArfcn = 299;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_PCS1900_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.pcs1900RxuCal;
+ size = sizeof(ee->cfg.v1.pcs1900RxuCal) + sizeof(ee->cfg.v1.__pcs1900RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_PCS1900_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.pcs1900RxdCal;
+ size = sizeof(ee->cfg.v1.pcs1900RxdCal) + sizeof(ee->cfg.v1.__pcs1900RxdCalMem);
+ }
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ // Validate the ID
+ if ( pCfgRxCal->u16SectionID != sId )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCal->u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the IQ imbalance mode (0:off, 1:on, 2:auto)
+ pRxCal->u8IqImbalMode = pCfgRxCal->u16IqImbalMode;
+
+ // Expand the IQ imbalance compensation
+ for ( i = 0; i < 4; i++ )
+ {
+ pRxCal->u16IqImbalCorr[i] = pCfgRxCal->u16IqImbalCorr[i];
+ }
+
+ // Expand the External RX gain
+ pRxCal->fExtRxGain = (float)pCfgRxCal->sfixExtRxGain * 0.001953125f;
+
+ // Expand the Mixer gain error compensation
+ pRxCal->fRxMixGainCorr = (float)pCfgRxCal->sfixRxMixGainCorr * 0.001953125f;
+
+ // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ for ( i = 0; i < 3; i++ )
+ {
+ pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCal->sfixRxLnaGainCorr[i] * 0.001953125f;
+ }
+
+ // Expand the Frequency roll-off compensation
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCal->sfixRxRollOffCorr[i] * 0.001953125f;
+ }
+
+ //DSP firmware version
+ pRxCal->u8DspMajVer = 0;
+ pRxCal->u8DspMinVer = 0;
+
+ //FPGA firmware version
+ pRxCal->u8FpgaMajVer = 0;
+ pRxCal->u8FpgaMinVer = 0;
+
+ break;
+ }
+
+ case EEPROM_HDR_V2:
+ {
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM850_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm850RxuCalV2;
+ size = sizeof(ee->cfg.v2.gsm850RxuCalV2) + sizeof(ee->cfg.v2.__gsm850RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM850_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm850RxdCalV2;
+ size = sizeof(ee->cfg.v2.gsm850RxdCalV2) + sizeof(ee->cfg.v2.__gsm850RxdCalMemV2);
+ }
+ break;
+ case 1:
+ nArfcn = 194;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM900_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm900RxuCalV2;
+ size = sizeof(ee->cfg.v2.gsm900RxuCalV2) + sizeof(ee->cfg.v2.__gsm900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM900_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm900RxdCalV2;
+ size = sizeof(ee->cfg.v2.gsm900RxdCalV2) + sizeof(ee->cfg.v2.__gsm900RxdCalMemV2);
+ }
+ break;
+ case 2:
+ nArfcn = 374;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_DCS1800_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxuCalV2;
+ size = sizeof(ee->cfg.v2.dcs1800RxuCalV2) + sizeof(ee->cfg.v2.__dcs1800RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_DCS1800_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxdCalV2;
+ size = sizeof(ee->cfg.v2.dcs1800RxdCalV2) + sizeof(ee->cfg.v2.__dcs1800RxdCalMemV2);
+ }
+ break;
+ case 3:
+ nArfcn = 299;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_PCS1900_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxuCalV2;
+ size = sizeof(ee->cfg.v2.pcs1900RxuCalV2) + sizeof(ee->cfg.v2.__pcs1900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_PCS1900_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxdCalV2;
+ size = sizeof(ee->cfg.v2.pcs1900RxdCalV2) + sizeof(ee->cfg.v2.__pcs1900RxdCalMemV2);
+ }
+ break;
+
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ // Validate the ID
+ if ( pCfgRxCalV2->u16SectionID != sId )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgRxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCalV2->u16Crc )
+ {
+ PERROR( "Parity error - Band %d\n", iBand );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the IQ imbalance mode (0:off, 1:on, 2:auto)
+ pRxCal->u8IqImbalMode = pCfgRxCalV2->u16IqImbalMode;
+
+ // Expand the IQ imbalance compensation
+ for ( i = 0; i < 4; i++ )
+ {
+ pRxCal->u16IqImbalCorr[i] = pCfgRxCalV2->u16IqImbalCorr[i];
+ }
+
+ // Expand the External RX gain
+ pRxCal->fExtRxGain = (float)pCfgRxCalV2->sfixExtRxGain * 0.001953125;
+
+ // Expand the Mixer gain error compensation
+ pRxCal->fRxMixGainCorr = (float)pCfgRxCalV2->sfixRxMixGainCorr * 0.001953125;
+
+ // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ for ( i = 0; i < 3; i++ )
+ {
+ pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCalV2->sfixRxLnaGainCorr[i] * 0.001953125;
+ }
+
+ // Expand the Frequency roll-off compensation
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCalV2->sfixRxRollOffCorr[i] * 0.001953125;
+ }
+
+ //DSP firmware version
+ pRxCal->u8DspMajVer = pCfgRxCalV2->u8DspMajVer;
+ pRxCal->u8DspMinVer = pCfgRxCalV2->u8DspMinVer;
+
+ //FPGA firmware version
+ pRxCal->u8FpgaMajVer = pCfgRxCalV2->u8FpgaMajVer;
+ pRxCal->u8FpgaMinVer = pCfgRxCalV2->u8FpgaMinVer;
+
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteRxCal
+ ************************************************************************//**
+ *
+ * This function writes the RX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [in] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal )
+{
+ int i;
+ int err;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t ee;
+ eeprom_SID_t sId;
+ eeprom_CfgRxCalV2_t *pCfgRxCal = NULL;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ int32_t fixVal;
+
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM850_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm850RxuCalV2;
+ size = sizeof(ee.cfg.v2.gsm850RxuCalV2) + sizeof(ee.cfg.v2.__gsm850RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM850_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm850RxdCalV2;
+ size = sizeof(ee.cfg.v2.gsm850RxdCalV2) + sizeof(ee.cfg.v2.__gsm850RxdCalMemV2);
+ }
+ break;
+ case 1:
+ nArfcn = 194;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM900_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm900RxuCalV2;
+ size = sizeof(ee.cfg.v2.gsm900RxuCalV2) + sizeof(ee.cfg.v2.__gsm900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM900_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm900RxdCalV2;
+ size = sizeof(ee.cfg.v2.gsm900RxdCalV2) + sizeof(ee.cfg.v2.__gsm900RxdCalMemV2);
+ }
+ break;
+ case 2:
+ nArfcn = 374;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_DCS1800_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.dcs1800RxuCalV2;
+ size = sizeof(ee.cfg.v2.dcs1800RxuCalV2) + sizeof(ee.cfg.v2.__dcs1800RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_DCS1800_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.dcs1800RxdCalV2;
+ size = sizeof(ee.cfg.v2.dcs1800RxdCalV2) + sizeof(ee.cfg.v2.__dcs1800RxdCalMemV2);
+ }
+ break;
+ case 3:
+ nArfcn = 299;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_PCS1900_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.pcs1900RxuCalV2;
+ size = sizeof(ee.cfg.v2.pcs1900RxuCalV2) + sizeof(ee.cfg.v2.__pcs1900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_PCS1900_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.pcs1900RxdCalV2;
+ size = sizeof(ee.cfg.v2.pcs1900RxdCalV2) + sizeof(ee.cfg.v2.__pcs1900RxdCalMemV2);
+ }
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ pCfgRxCal->u16SectionID = sId;
+ pCfgRxCal->u16Crc = 0;
+ pCfgRxCal->u32Time = time(NULL);
+
+ //DSP firmware version
+ pCfgRxCal->u8DspMajVer = pRxCal->u8DspMajVer;
+ pCfgRxCal->u8DspMinVer = pRxCal->u8DspMinVer;
+
+ //FPGA firmware version
+ pCfgRxCal->u8FpgaMajVer = pRxCal->u8FpgaMajVer;
+ pCfgRxCal->u8FpgaMinVer = pRxCal->u8FpgaMinVer;
+
+ // Compress the IQ imbalance mode (0:off, 1:on, 2:auto)
+ pCfgRxCal->u16IqImbalMode = pRxCal->u8IqImbalMode;
+
+ // Compress the IQ imbalance compensation
+ for ( i = 0; i < 4; i++ )
+ {
+ pCfgRxCal->u16IqImbalCorr[i] = pRxCal->u16IqImbalCorr[i];
+ }
+
+ // Compress the External RX gain
+ fixVal = (int32_t)(pRxCal->fExtRxGain * 512.f + (pRxCal->fExtRxGain>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixExtRxGain = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixExtRxGain = -32768;
+ else pCfgRxCal->sfixExtRxGain = (int16_t)fixVal;
+
+ // Compress the Mixer gain error compensation
+ fixVal = (int32_t)(pRxCal->fRxMixGainCorr * 512.f + (pRxCal->fRxMixGainCorr>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixRxMixGainCorr = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixRxMixGainCorr = -32768;
+ else pCfgRxCal->sfixRxMixGainCorr = (int16_t)fixVal;
+
+ // Compress the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ for ( i = 0; i < 3; i++ )
+ {
+ fixVal = (int32_t)(pRxCal->fRxLnaGainCorr[i] * 512.f + (pRxCal->fRxLnaGainCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixRxLnaGainCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixRxLnaGainCorr[i] = -32768;
+ else pCfgRxCal->sfixRxLnaGainCorr[i] = (int16_t)fixVal;
+ }
+
+ // Compress the Frequency roll-off compensation
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ fixVal = (int32_t)(pRxCal->fRxRollOffCorr[i] * 512.f + (pRxCal->fRxRollOffCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixRxRollOffCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixRxRollOffCorr[i] = -32768;
+ else pCfgRxCal->sfixRxRollOffCorr[i] = (int16_t)fixVal;
+ }
+
+ // Add the CRC
+ pCfgRxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint8_t*)pCfgRxCal - (uint8_t*)&ee), size, (const char *)pCfgRxCal );
+ if ( err != size )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Private functions *
+ ****************************************************************************/
+
+/**
+ * Dump the content of the EEPROM to the standard output
+ */
+int eeprom_dump( int addr, int size, int hex )
+{
+ FILE *f;
+ char ch;
+ int i;
+
+ f = fopen( EEPROM_DEV, "r+" );
+ if ( f == NULL )
+ {
+ perror( "eeprom fopen" );
+ return -1;
+ }
+ if (fseek( f, addr, SEEK_SET ) != 0)
+ {
+ perror( "eeprom fseek" );
+ fclose( f );
+ return -1;
+ }
+
+ for ( i = 0; i < size; ++i, ++addr )
+ {
+ if ( fread( &ch, 1, 1, f ) != 1 )
+ {
+ perror( "eeprom fread" );
+ fclose( f );
+ return -1;
+ }
+ if ( hex )
+ {
+ if ( (i % 16) == 0 )
+ {
+ printf( "\n %.4x| ", addr );
+ }
+ else if ( (i % 8) == 0 )
+ {
+ printf( " " );
+ }
+ printf( "%.2x ", ch );
+ }
+ else
+ putchar( ch );
+ }
+ if ( hex )
+ {
+ printf( "\n\n" );
+ }
+ fflush( stdout );
+
+ fclose( f );
+ return 0;
+}
+
+static FILE *g_file;
+static eeprom_Cfg_t *g_cached_cfg;
+
+void eeprom_free_resources(void)
+{
+ if (g_file)
+ fclose(g_file);
+ g_file = NULL;
+
+ /* release the header */
+ free(g_cached_cfg);
+ g_cached_cfg = NULL;
+}
+
+/**
+ * Read up to 'size' bytes of data from the EEPROM starting at offset 'addr'.
+ */
+static int eeprom_read( int addr, int size, char *pBuff )
+{
+ FILE *f = g_file;
+ int n;
+
+ if (!f) {
+ f = fopen( EEPROM_DEV, "r+" );
+ if ( f == NULL )
+ {
+ perror( "eeprom fopen" );
+ return -1;
+ }
+ g_file = f;
+ }
+ if (fseek( f, addr, SEEK_SET ) != 0)
+ {
+ perror( "eeprom fseek" );
+ return -1;
+ }
+
+ n = fread( pBuff, 1, size, f );
+ return n;
+}
+
+static void eeprom_cache_cfg(void)
+{
+ int err;
+
+ free(g_cached_cfg);
+ g_cached_cfg = malloc(sizeof(*g_cached_cfg));
+
+ if (!g_cached_cfg)
+ return;
+
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(*g_cached_cfg), (char *) g_cached_cfg );
+ if ( err != sizeof(*g_cached_cfg) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ goto error;
+ }
+
+ if ( g_cached_cfg->hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ PERROR( "Invalid EEPROM format\n" );
+ goto error;
+ }
+
+ return;
+
+error:
+ free(g_cached_cfg);
+ g_cached_cfg = NULL;
+}
+
+static eeprom_Cfg_t *eeprom_cached_config(void)
+{
+ if (!g_cached_cfg)
+ eeprom_cache_cfg();
+ return g_cached_cfg;
+}
+
+/**
+ * Write up to 'size' bytes of data to the EEPROM starting at offset 'addr'.
+ */
+static int eeprom_write( int addr, int size, const char *pBuff )
+{
+ FILE *f = g_file;
+ int n;
+
+ if (!f) {
+ f = fopen( EEPROM_DEV, "r+" );
+ if ( f == NULL )
+ {
+ perror( "eeprom fopen" );
+ return -1;
+ }
+ g_file = f;
+ }
+ if (fseek( f, addr, SEEK_SET ) != 0)
+ {
+ perror( "eeprom fseek" );
+ n = -1;
+ goto error;
+ }
+
+ n = fwrite( pBuff, 1, size, f );
+
+error:
+ fclose( f );
+ g_file = NULL;
+ return n;
+}
+
+
+/**
+ * EEPROM CRC.
+ */
+static uint16_t eeprom_crc( uint8_t *pu8Data, int len )
+{
+ int i;
+ uint16_t crc = 0xFFFF;
+
+ while (len--) {
+ crc ^= (uint16_t)*pu8Data++;
+
+ for (i=0; i<8; i++) {
+ if (crc & 1) crc = (crc >> 1) ^ 0x8408;
+ else crc = (crc >> 1);
+ }
+ }
+
+ crc = ~crc;
+ return crc;
+}
diff --git a/src/osmo-bts-sysmo/eeprom.h b/src/osmo-bts-sysmo/eeprom.h
new file mode 100644
index 00000000..f75e54f9
--- /dev/null
+++ b/src/osmo-bts-sysmo/eeprom.h
@@ -0,0 +1,304 @@
+/***************************************************************************
+ *
+ * **** I
+ * ****** ***
+ * ******* ****
+ * ******** **** **** **** ********* ******* **** ***********
+ * ********* **** **** **** ********* ************** *************
+ * **** ***** **** **** **** **** ***** ****** ***** ****
+ * **** ***** **** **** **** **** ***** **** **** ****
+ * **** ********* **** **** **** **** **** **** ****
+ * **** ******** **** ****I **** ***** ***** **** ****
+ * **** ****** ***** ****** ***** ****** ******* ****** *******
+ * **** **** ************ ****** ************* *************
+ * **** *** **** **** **** ***** **** ***** ****
+ * ****
+ * I N N O V A T I O N T O D A Y F O R T O M M O R O W ****
+ * ***
+ *
+ ***************************************************************************
+ *
+ * Project : SuperFemto
+ * File : eeprom.h
+ * Description : EEPROM interface.
+ *
+ * Copyright (c) Nutaq. 2012
+ *
+ ***************************************************************************
+ *
+ * "$Revision: 1.1 $"
+ * "$Name: $"
+ * "$Date: 2012/06/20 02:18:30 $"
+ * "$Author: Yves.Godin $"
+ *
+ ***************************************************************************/
+#ifndef EEPROM_H__
+#define EEPROM_H__
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Public constants *
+ ****************************************************************************/
+
+/**
+ * EEPROM error code
+ */
+typedef enum
+{
+ EEPROM_SUCCESS = 0, ///< Success
+ EEPROM_ERR_DEVICE = -1, ///< Device access error
+ EEPROM_ERR_PARITY = -2, ///< Parity error
+ EEPROM_ERR_UNAVAILABLE = -3, ///< Information unavailable
+ EEPROM_ERR_INVALID = -4, ///< Invalid format
+ EEPROM_ERR_UNSUPPORTED = -5, ///< Unsupported format
+} eeprom_Error_t;
+
+
+/****************************************************************************
+ * Struct : eeprom_SysInfo_t
+ ************************************************************************//**
+ *
+ * SuperFemto system information.
+ *
+ ***************************************************************************/
+typedef struct eeprom_SysInfo
+{
+ char szSn[16]; ///< Serial number
+ uint8_t u8Rev; ///< Board revision
+ uint8_t u8Tcxo; ///< TCXO present (0:absent, 1:present, X:unknown)
+ uint8_t u8Ocxo; ///< OCXO present (0:absent, 1:present, X:unknown)
+ uint8_t u8GSM850; ///< GSM-850 supported (0:unsupported, 1:supported, X:unknown)
+ uint8_t u8GSM900; ///< GSM-900 supported (0:unsupported, 1:supported, X:unknown)
+ uint8_t u8DCS1800; ///< GSM-1800 supported (0:unsupported, 1:supported, X:unknown)
+ uint8_t u8PCS1900; ///< GSM-1900 supported (0:unsupported, 1:supported, X:unknown)
+} eeprom_SysInfo_t;
+
+/****************************************************************************
+ * Struct : eeprom_RfClockCal_t
+ ************************************************************************//**
+ *
+ * SuperFemto RF clock calibration.
+ *
+ ***************************************************************************/
+typedef struct eeprom_RfClockCal
+{
+ int iClkCor; ///< Clock correction value in PPB.
+ uint8_t u8ClkSrc; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge)
+} eeprom_RfClockCal_t;
+
+/****************************************************************************
+ * Struct : eeprom_TxCal_t
+ ************************************************************************//**
+ *
+ * SuperFemto transmit calibration table.
+ *
+ ***************************************************************************/
+typedef struct eeprom_TxCal
+{
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ float fTxGainGmsk[80]; ///< Gain setting for GMSK output level from +50dBm to -29 dBm
+ float fTx8PskCorr; ///< Gain adjustment for 8 PSK (default to +3.25 dB)
+ float fTxExtAttCorr[31]; ///< Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB)
+ float fTxRollOffCorr[374]; /**< Gain correction for each ARFCN
+ for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused
+ for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused
+ for DCS-1800: 0=512, 1:513, ..., 373:885
+ for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */
+} eeprom_TxCal_t;
+
+/****************************************************************************
+ * Struct : eeprom_RxCal_t
+ ************************************************************************//**
+ *
+ * SuperFemto receive calibration table.
+ *
+ ***************************************************************************/
+typedef struct eeprom_RxCal
+{
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ float fExtRxGain; ///< External RX gain
+ float fRxMixGainCorr; ///< Mixer gain error compensation
+ float fRxLnaGainCorr[3]; ///< LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ float fRxRollOffCorr[374]; /***< Frequency roll-off compensation
+ for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused
+ for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused
+ for DCS-1800: 0=512, 1:513, ..., 373:885
+ for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */
+ uint8_t u8IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto)
+ uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation
+} eeprom_RxCal_t;
+
+
+/****************************************************************************
+ * Public functions *
+ ****************************************************************************/
+
+eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr );
+
+/****************************************************************************
+ * Function : eeprom_ResetCfg
+ ************************************************************************//**
+ *
+ * This function reset the content of the EEPROM config area.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ResetCfg( void );
+
+/****************************************************************************
+ * Function : eeprom_ReadSysInfo
+ ************************************************************************//**
+ *
+ * This function reads the system information from the EEPROM.
+ *
+ * @param [inout] pTime
+ * Pointer to a system info structure.
+ *
+ * @param [inout] pSysInfo
+ * Pointer to a system info structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo );
+
+/****************************************************************************
+ * Function : eeprom_WriteSysInfo
+ ************************************************************************//**
+ *
+ * This function writes the system information to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo );
+
+/****************************************************************************
+ * Function : eeprom_ReadRfClockCal
+ ************************************************************************//**
+ *
+ * This function reads the RF clock calibration data from the EEPROM.
+ *
+ * @param [inout] pRfClockCal
+ * Pointer to a RF clock calibration structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal );
+
+/****************************************************************************
+ * Function : eeprom_WriteRfClockCal
+ ************************************************************************//**
+ *
+ * This function writes the RF clock calibration data to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal );
+
+/****************************************************************************
+ * Function : eeprom_ReadTxCal
+ ************************************************************************//**
+ *
+ * This function reads the TX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [inout] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal );
+
+/****************************************************************************
+ * Function : eeprom_WriteTxCal
+ ************************************************************************//**
+ *
+ * This function writes the TX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal );
+
+/****************************************************************************
+ * Function : eeprom_ReadRxCal
+ ************************************************************************//**
+ *
+ * This function reads the RX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [inout] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal );
+
+/****************************************************************************
+ * Function : eeprom_WriteRxCal
+ ************************************************************************//**
+ *
+ * This function writes the RX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [in] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal );
+
+void eeprom_free_resources(void);
+
+#endif // EEPROM_H__
diff --git a/src/osmo-bts-sysmo/femtobts.c b/src/osmo-bts-sysmo/femtobts.c
new file mode 100644
index 00000000..480fe06b
--- /dev/null
+++ b/src/osmo-bts-sysmo/femtobts.c
@@ -0,0 +1,370 @@
+/* sysmocom femtobts L1 API related definitions */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1dbg.h>
+
+#include "femtobts.h"
+
+const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM] = {
+ [GsmL1_PrimId_MphInitReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphCloseReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphConnectReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphDisconnectReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphActivateReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphDeactivateReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphConfigReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphMeasureReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphInitCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphCloseCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphConnectCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphDisconnectCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphActivateCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphDeactivateCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphConfigCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphMeasureCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphTimeInd] = L1P_T_IND,
+ [GsmL1_PrimId_MphSyncInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhEmptyFrameReq] = L1P_T_REQ,
+ [GsmL1_PrimId_PhDataReq] = L1P_T_REQ,
+ [GsmL1_PrimId_PhConnectInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhReadyToSendInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhDataInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhRaInd] = L1P_T_IND,
+};
+
+const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1] = {
+ { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
+ { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
+ { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
+ { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
+ { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
+ { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
+ { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
+ { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
+ { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
+ { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
+ { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
+ { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
+ { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
+ { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
+ { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
+ { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
+ { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
+ { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
+ { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
+ { GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
+ { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
+ { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
+ { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
+ { GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
+ { 0, NULL }
+};
+
+const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM] = {
+ [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf,
+ [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf,
+ [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf,
+ [GsmL1_PrimId_MphDisconnectReq] = GsmL1_PrimId_MphDisconnectCnf,
+ [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf,
+ [GsmL1_PrimId_MphDeactivateReq] = GsmL1_PrimId_MphDeactivateCnf,
+ [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf,
+ [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf,
+};
+
+const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM] = {
+ [SuperFemto_PrimId_SystemInfoReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_SystemInfoCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SystemFailureInd] = L1P_T_IND,
+ [SuperFemto_PrimId_ActivateRfReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_ActivateRfCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_DeactivateRfReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_DeactivateRfCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SetTraceFlagsReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_RfClockInfoReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_RfClockInfoCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_RfClockSetupReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_RfClockSetupCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_Layer1ResetReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_Layer1ResetCnf] = L1P_T_CONF,
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ [SuperFemto_PrimId_GetTxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_GetTxCalibTblCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SetTxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_SetTxCalibTblCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_GetRxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_GetRxCalibTblCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SetRxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_SetRxCalibTblCnf] = L1P_T_CONF,
+#endif
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ [SuperFemto_PrimId_MuteRfReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_MuteRfCnf] = L1P_T_CONF,
+#endif
+};
+
+const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1] = {
+ { SuperFemto_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
+ { SuperFemto_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
+ { SuperFemto_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
+ { SuperFemto_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
+ { SuperFemto_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
+ { SuperFemto_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
+ { SuperFemto_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
+ { SuperFemto_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
+ { SuperFemto_PrimId_RfClockInfoReq, "RF-CLOCK-INFO.req" },
+ { SuperFemto_PrimId_RfClockInfoCnf, "RF-CLOCK-INFO.conf" },
+ { SuperFemto_PrimId_RfClockSetupReq, "RF-CLOCK-SETUP.req" },
+ { SuperFemto_PrimId_RfClockSetupCnf, "RF-CLOCK-SETUP.conf" },
+ { SuperFemto_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
+ { SuperFemto_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ { SuperFemto_PrimId_GetTxCalibTblReq, "GET-TX-CALIB.req" },
+ { SuperFemto_PrimId_GetTxCalibTblCnf, "GET-TX-CALIB.cnf" },
+ { SuperFemto_PrimId_SetTxCalibTblReq, "SET-TX-CALIB.req" },
+ { SuperFemto_PrimId_SetTxCalibTblCnf, "SET-TX-CALIB.cnf" },
+ { SuperFemto_PrimId_GetRxCalibTblReq, "GET-RX-CALIB.req" },
+ { SuperFemto_PrimId_GetRxCalibTblCnf, "GET-RX-CALIB.cnf" },
+ { SuperFemto_PrimId_SetRxCalibTblReq, "SET-RX-CALIB.req" },
+ { SuperFemto_PrimId_SetRxCalibTblCnf, "SET-RX-CALIB.cnf" },
+#endif
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ { SuperFemto_PrimId_MuteRfReq, "MUTE-RF.req" },
+ { SuperFemto_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
+#endif
+ { 0, NULL }
+};
+
+const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM] = {
+ [SuperFemto_PrimId_SystemInfoReq] = SuperFemto_PrimId_SystemInfoCnf,
+ [SuperFemto_PrimId_ActivateRfReq] = SuperFemto_PrimId_ActivateRfCnf,
+ [SuperFemto_PrimId_DeactivateRfReq] = SuperFemto_PrimId_DeactivateRfCnf,
+ [SuperFemto_PrimId_RfClockInfoReq] = SuperFemto_PrimId_RfClockInfoCnf,
+ [SuperFemto_PrimId_RfClockSetupReq] = SuperFemto_PrimId_RfClockSetupCnf,
+ [SuperFemto_PrimId_Layer1ResetReq] = SuperFemto_PrimId_Layer1ResetCnf,
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ [SuperFemto_PrimId_GetTxCalibTblReq] = SuperFemto_PrimId_GetTxCalibTblCnf,
+ [SuperFemto_PrimId_SetTxCalibTblReq] = SuperFemto_PrimId_SetTxCalibTblCnf,
+ [SuperFemto_PrimId_GetRxCalibTblReq] = SuperFemto_PrimId_GetRxCalibTblCnf,
+ [SuperFemto_PrimId_SetRxCalibTblReq] = SuperFemto_PrimId_SetRxCalibTblCnf,
+#endif
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ [SuperFemto_PrimId_MuteRfReq] = SuperFemto_PrimId_MuteRfCnf,
+#endif
+};
+
+const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Idle, "IDLE" },
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1] = {
+ { GsmL1_Status_Success, "Success" },
+ { GsmL1_Status_Generic, "Generic error" },
+ { GsmL1_Status_NoMemory, "Not enough memory" },
+ { GsmL1_Status_Timeout, "Timeout" },
+ { GsmL1_Status_InvalidParam, "Invalid parameter" },
+ { GsmL1_Status_Busy, "Resource busy" },
+ { GsmL1_Status_NoRessource, "No more resources" },
+ { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
+ { GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
+ { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
+ { GsmL1_Status_BadCrc, "Bad CRC" },
+ { GsmL1_Status_BadUsf, "Bad USF" },
+ { GsmL1_Status_InvalidCPS, "Invalid CPS field" },
+ { GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
+ { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
+ { GsmL1_Status_CriticalError, "Critical error" },
+ { GsmL1_Status_OverheatError, "Overheat error" },
+ { GsmL1_Status_DeviceError, "Device error" },
+ { GsmL1_Status_FacchError, "FACCH / TCH order error" },
+ { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
+ { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
+ { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
+ { GsmL1_Status_NotSynchronized, "Not synchronized" },
+ { GsmL1_Status_Unsupported, "Unsupported feature" },
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0)
+ { GsmL1_Status_ClockError, "Clock error" },
+#endif
+ { 0, NULL }
+};
+
+const struct value_string femtobts_tracef_names[29] = {
+ { DBG_DEBUG, "DEBUG" },
+ { DBG_L1WARNING, "L1_WARNING" },
+ { DBG_ERROR, "ERROR" },
+ { DBG_L1RXMSG, "L1_RX_MSG" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
+ { DBG_L1TXMSG, "L1_TX_MSG" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
+ { DBG_MPHCNF, "MPH_CNF" },
+ { DBG_MPHIND, "MPH_IND" },
+ { DBG_MPHREQ, "MPH_REQ" },
+ { DBG_PHIND, "PH_IND" },
+ { DBG_PHREQ, "PH_REQ" },
+ { DBG_PHYRF, "PHY_RF" },
+ { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
+ { DBG_MODE, "MODE" },
+ { DBG_TDMAINFO, "TDMA_INFO" },
+ { DBG_BADCRC, "BAD_CRC" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "DEVICE_MSG" },
+ { DBG_RACHINFO, "RACH_INFO" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "MEMORY" },
+ { DBG_PROFILING, "PROFILING" },
+ { DBG_TESTCOMMENT, "TEST_COMMENT" },
+ { DBG_TEST, "TEST" },
+ { DBG_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_tracef_docs[29] = {
+ { DBG_DEBUG, "Debug Region" },
+ { DBG_L1WARNING, "L1 Warning Region" },
+ { DBG_ERROR, "Error Region" },
+ { DBG_L1RXMSG, "L1_RX_MSG Region" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
+ { DBG_L1TXMSG, "L1_TX_MSG Region" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
+ { DBG_MPHCNF, "MphConfirmation Region" },
+ { DBG_MPHIND, "MphIndication Region" },
+ { DBG_MPHREQ, "MphRequest Region" },
+ { DBG_PHIND, "PhIndication Region" },
+ { DBG_PHREQ, "PhRequest Region" },
+ { DBG_PHYRF, "PhyRF Region" },
+ { DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
+ { DBG_MODE, "Mode Region" },
+ { DBG_TDMAINFO, "TDMA Info Region" },
+ { DBG_BADCRC, "Bad CRC Region" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "Device Message Region" },
+ { DBG_RACHINFO, "RACH Info" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "Memory Region" },
+ { DBG_PROFILING, "Profiling Region" },
+ { DBG_TESTCOMMENT, "Test Comments" },
+ { DBG_TEST, "Test Region" },
+ { DBG_STATUS, "Status Region" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_tch_pl_names[] = {
+ { GsmL1_TchPlType_NA, "N/A" },
+ { GsmL1_TchPlType_Fr, "FR" },
+ { GsmL1_TchPlType_Hr, "HR" },
+ { GsmL1_TchPlType_Efr, "EFR" },
+ { GsmL1_TchPlType_Amr, "AMR(IF2)" },
+ { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
+ { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
+ { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
+ { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
+ { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
+ { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
+ { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
+ { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
+ { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_clksrc_names[] = {
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ { SuperFemto_ClkSrcId_None, "None" },
+ { SuperFemto_ClkSrcId_Ocxo, "ocxo" },
+ { SuperFemto_ClkSrcId_Tcxo, "tcxo" },
+ { SuperFemto_ClkSrcId_External, "ext" },
+ { SuperFemto_ClkSrcId_GpsPps, "gps" },
+ { SuperFemto_ClkSrcId_Trx, "trx" },
+ { SuperFemto_ClkSrcId_Rx, "rx" },
+ { SuperFemto_ClkSrcId_Edge, "edge" },
+ { SuperFemto_ClkSrcId_NetList, "nwl" },
+#else
+ { SF_CLKSRC_NONE, "None" },
+ { SF_CLKSRC_OCXO, "ocxo" },
+ { SF_CLKSRC_TCXO, "tcxo" },
+ { SF_CLKSRC_EXT, "ext" },
+ { SF_CLKSRC_GPS, "gps" },
+ { SF_CLKSRC_TRX, "trx" },
+ { SF_CLKSRC_RX, "rx" },
+#endif
+ { 0, NULL }
+};
+
+const struct value_string femtobts_dir_names[] = {
+ { GsmL1_Dir_TxDownlink, "TxDL" },
+ { GsmL1_Dir_TxUplink, "TxUL" },
+ { GsmL1_Dir_RxUplink, "RxUL" },
+ { GsmL1_Dir_RxDownlink, "RxDL" },
+ { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_chcomb_names[] = {
+ { GsmL1_LogChComb_0, "dummy" },
+ { GsmL1_LogChComb_I, "tch_f" },
+ { GsmL1_LogChComb_II, "tch_h" },
+ { GsmL1_LogChComb_IV, "ccch" },
+ { GsmL1_LogChComb_V, "ccch_sdcch4" },
+ { GsmL1_LogChComb_VII, "sdcch8" },
+ { GsmL1_LogChComb_XIII, "pdtch" },
+ { 0, NULL }
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
+ [PDCH_CS_1] = 23,
+ [PDCH_CS_2] = 34,
+ [PDCH_CS_3] = 40,
+ [PDCH_CS_4] = 54,
+ [PDCH_MCS_1] = 27,
+ [PDCH_MCS_2] = 33,
+ [PDCH_MCS_3] = 42,
+ [PDCH_MCS_4] = 49,
+ [PDCH_MCS_5] = 60,
+ [PDCH_MCS_6] = 78,
+ [PDCH_MCS_7] = 118,
+ [PDCH_MCS_8] = 142,
+ [PDCH_MCS_9] = 154
+};
diff --git a/src/osmo-bts-sysmo/femtobts.h b/src/osmo-bts-sysmo/femtobts.h
new file mode 100644
index 00000000..9163ebbf
--- /dev/null
+++ b/src/osmo-bts-sysmo/femtobts.h
@@ -0,0 +1,110 @@
+#ifndef FEMTOBTS_H
+#define FEMTOBTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1const.h>
+
+#ifdef FEMTOBTS_API_VERSION
+#define SuperFemto_PrimId_t FemtoBts_PrimId_t
+#define SuperFemto_Prim_t FemtoBts_Prim_t
+#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq
+#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf
+#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t
+#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd
+#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq
+#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf
+#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq
+#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf
+#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq
+#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq
+#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf
+#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq
+#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf
+#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq
+#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf
+#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM
+#define HW_SYSMOBTS_V1 1
+#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z)
+#endif
+
+#ifdef L1_HAS_RTP_MODE
+/*
+ * The bit ordering has been fixed on >= 3.10 but I am verifying
+ * this on 3.11.
+ */
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 11, 0)
+#define USE_L1_RTP_MODE /* Tell L1 to use RTP mode */
+#endif
+#endif
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or SuperFemto_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define SYSMOBTS_PRIM_SIZE \
+ (OSMO_MAX(sizeof(SuperFemto_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
+
+enum l1prim_type {
+ L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
+ L1P_T_REQ,
+ L1P_T_CONF,
+ L1P_T_IND,
+};
+
+#if !defined(SUPERFEMTO_API_VERSION) || SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0)
+enum uperfemto_clk_src {
+ SF_CLKSRC_NONE = 0,
+ SF_CLKSRC_OCXO = 1,
+ SF_CLKSRC_TCXO = 2,
+ SF_CLKSRC_EXT = 3,
+ SF_CLKSRC_GPS = 4,
+ SF_CLKSRC_TRX = 5,
+ SF_CLKSRC_RX = 6,
+ SF_CLKSRC_NL = 7,
+};
+#endif
+
+const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM];
+const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1];
+const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM];
+
+const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM];
+const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1];
+const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM];
+
+const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string femtobts_tracef_names[29];
+const struct value_string femtobts_tracef_docs[29];
+
+const struct value_string femtobts_tch_pl_names[15];
+const struct value_string femtobts_chcomb_names[8];
+const struct value_string femtobts_clksrc_names[10];
+
+const struct value_string femtobts_dir_names[6];
+
+enum pdch_cs {
+ PDCH_CS_1,
+ PDCH_CS_2,
+ PDCH_CS_3,
+ PDCH_CS_4,
+ PDCH_MCS_1,
+ PDCH_MCS_2,
+ PDCH_MCS_3,
+ PDCH_MCS_4,
+ PDCH_MCS_5,
+ PDCH_MCS_6,
+ PDCH_MCS_7,
+ PDCH_MCS_8,
+ PDCH_MCS_9,
+ _NUM_PDCH_CS
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+#endif /* FEMTOBTS_H */
diff --git a/src/osmo-bts-sysmo/hw_misc.c b/src/osmo-bts-sysmo/hw_misc.c
new file mode 100644
index 00000000..6aa3b83f
--- /dev/null
+++ b/src/osmo-bts-sysmo/hw_misc.c
@@ -0,0 +1,113 @@
+/* Misc HW routines for Sysmocom BTS */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+
+#include "hw_misc.h"
+
+static const struct value_string sysmobts_led_names[] = {
+ { LED_RF_ACTIVE, "activity_led" },
+ { LED_ONLINE, "online_led" },
+ { 0, NULL }
+};
+
+int sysmobts_led_set(enum sysmobts_led nr, int on)
+{
+ char tmp[PATH_MAX+1];
+ const char *filename;
+ int fd;
+ uint8_t byte;
+
+ if (on)
+ byte = '1';
+ else
+ byte = '0';
+
+ filename = get_value_string(sysmobts_led_names, nr);
+ if (!filename)
+ return -EINVAL;
+
+ snprintf(tmp, sizeof(tmp)-1, "/sys/class/leds/%s/brightness", filename);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ fd = open(tmp, O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ write(fd, &byte, 1);
+
+ close(fd);
+
+ return 0;
+}
+
+#if 0
+#define HWMON_PREFIX "/sys/class/hwmon/hwmon0/device"
+
+static FILE *temperature_f[NUM_TEMP];
+
+int sysmobts_temp_init()
+{
+ char tmp[PATH_MAX+1];
+ FILE *in;
+ int rc = 0;
+
+ for (i = 0; i < NUM_TEMP; i++) {
+ snprintf(tmp, sizeof(tmp)-1, HWMON_PREFIX "/temp%u_input", i+1),
+ tmp[sizeof(tmp)-1] = '\0';
+
+ temperature_f[i] = fopen(tmp, "r");
+ if (!temperature_f[i])
+ rc = -ENODEV;
+ }
+
+ return 0;
+}
+
+int sysmobts_temp_get(uint8_t num)
+{
+ if (num >= NUM_TEMP)
+ return -EINVAL;
+
+ if (!temperature_f[num])
+ return -ENODEV;
+
+
+ in = fopen(tmp, "r");
+ if (!in)
+ return -ENODEV;
+
+ fclose(tmp);
+
+ return 0;
+}
+#endif
diff --git a/src/osmo-bts-sysmo/hw_misc.h b/src/osmo-bts-sysmo/hw_misc.h
new file mode 100644
index 00000000..c4838dbf
--- /dev/null
+++ b/src/osmo-bts-sysmo/hw_misc.h
@@ -0,0 +1,12 @@
+#ifndef _SYSMOBTS_HW_MISC_H
+#define _SYSMOBTS_HW_MISC_H
+
+enum sysmobts_led {
+ LED_NONE,
+ LED_RF_ACTIVE,
+ LED_ONLINE,
+};
+
+int sysmobts_led_set(enum sysmobts_led nr, int on);
+
+#endif
diff --git a/src/osmo-bts-sysmo/l1_fwd.h b/src/osmo-bts-sysmo/l1_fwd.h
new file mode 100644
index 00000000..55397920
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_fwd.h
@@ -0,0 +1,5 @@
+#define L1FWD_L1_PORT 9999
+#define L1FWD_SYS_PORT 9998
+#define L1FWD_TCH_PORT 9997
+#define L1FWD_PDTCH_PORT 9996
+
diff --git a/src/osmo-bts-sysmo/l1_fwd_main.c b/src/osmo-bts-sysmo/l1_fwd_main.c
new file mode 100644
index 00000000..bc9fc21c
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_fwd_main.c
@@ -0,0 +1,236 @@
+/* Sysmocom femtobts L1 proxy */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/application.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "l1_fwd.h"
+
+static const uint16_t fwd_udp_ports[_NUM_MQ_WRITE] = {
+ [MQ_SYS_READ] = L1FWD_SYS_PORT,
+ [MQ_L1_READ] = L1FWD_L1_PORT,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_READ] = L1FWD_TCH_PORT,
+ [MQ_PDTCH_READ] = L1FWD_PDTCH_PORT,
+#endif
+};
+
+struct l1fwd_hdl {
+ struct sockaddr_storage remote_sa[_NUM_MQ_WRITE];
+ socklen_t remote_sa_len[_NUM_MQ_WRITE];
+
+ struct osmo_wqueue udp_wq[_NUM_MQ_WRITE];
+
+ struct femtol1_hdl *fl1h;
+};
+
+
+/* callback when there's a new L1 primitive coming in from the HW */
+int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ struct l1fwd_hdl *l1fh = fl1h->priv;
+
+ /* Enqueue message to UDP socket */
+ if (osmo_wqueue_enqueue(&l1fh->udp_wq[wq], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n", wq);
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* callback when there's a new SYS primitive coming in from the HW */
+int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ struct l1fwd_hdl *l1fh = fl1h->priv;
+
+ /* Enqueue message to UDP socket */
+ if (osmo_wqueue_enqueue(&l1fh->udp_wq[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE ful. dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+
+/* data has arrived on the udp socket */
+static int udp_read_cb(struct osmo_fd *ofd)
+{
+ struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx");
+ struct l1fwd_hdl *l1fh = ofd->data;
+ struct femtol1_hdl *fl1h = l1fh->fl1h;
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l1h = msg->data;
+
+ l1fh->remote_sa_len[ofd->priv_nr] = sizeof(l1fh->remote_sa[ofd->priv_nr]);
+ rc = recvfrom(ofd->fd, msg->l1h, msgb_tailroom(msg), 0,
+ (struct sockaddr *) &l1fh->remote_sa[ofd->priv_nr], &l1fh->remote_sa_len[ofd->priv_nr]);
+ if (rc < 0) {
+ perror("read from udp");
+ msgb_free(msg);
+ return rc;
+ } else if (rc == 0) {
+ perror("len=0 read from udp");
+ msgb_free(msg);
+ return rc;
+ }
+ msgb_put(msg, rc);
+
+ DEBUGP(DL1C, "UDP: Received %u bytes for queue %d\n", rc,
+ ofd->priv_nr);
+
+ /* put the message into the right queue */
+ if (osmo_wqueue_enqueue(&fl1h->write_q[ofd->priv_nr], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n",
+ ofd->priv_nr);
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* callback when we can write to the UDP socket */
+static int udp_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+ struct l1fwd_hdl *l1fh = ofd->data;
+
+ DEBUGP(DL1C, "UDP: Writing %u bytes for queue %d\n", msgb_l1len(msg),
+ ofd->priv_nr);
+
+ rc = sendto(ofd->fd, msg->l1h, msgb_l1len(msg), 0,
+ (const struct sockaddr *)&l1fh->remote_sa[ofd->priv_nr], l1fh->remote_sa_len[ofd->priv_nr]);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msgb_l1len(msg)) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msgb_l1len(msg));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct l1fwd_hdl *l1fh;
+ struct femtol1_hdl *fl1h;
+ int rc, i;
+ void *ctx = talloc_named_const(NULL, 0, "l1_fwd");
+
+ printf("sizeof(GsmL1_Prim_t) = %zu\n", sizeof(GsmL1_Prim_t));
+ printf("sizeof(SuperFemto_Prim_t) = %zu\n", sizeof(SuperFemto_Prim_t));
+
+ osmo_init_logging2(ctx, &bts_log_info);
+
+ /*
+ * hack and prevent that two l1fwd-proxy/sysmobts run at the same
+ * time. This is done by binding to the same VTY port.
+ */
+ rc = osmo_sock_init(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
+ "127.0.0.1", 4241, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to bind to the BTS VTY port.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* allocate new femtol1_handle */
+ fl1h = talloc_zero(ctx, struct femtol1_hdl);
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ /* open the actual hardware transport */
+ for (i = 0; i < ARRAY_SIZE(fl1h->write_q); i++) {
+ rc = l1if_transport_open(i, fl1h);
+ if (rc < 0)
+ exit(1);
+ }
+
+ /* create our fwd handle */
+ l1fh = talloc_zero(ctx, struct l1fwd_hdl);
+
+ l1fh->fl1h = fl1h;
+ fl1h->priv = l1fh;
+
+ /* Open UDP */
+ for (i = 0; i < ARRAY_SIZE(l1fh->udp_wq); i++) {
+ struct osmo_wqueue *wq = &l1fh->udp_wq[i];
+
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = udp_write_cb;
+ wq->read_cb = udp_read_cb;
+
+ wq->bfd.when |= BSC_FD_READ;
+ wq->bfd.data = l1fh;
+ wq->bfd.priv_nr = i;
+ rc = osmo_sock_init_ofd(&wq->bfd, AF_UNSPEC, SOCK_DGRAM,
+ IPPROTO_UDP, NULL, fwd_udp_ports[i],
+ OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("sock_init");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0) {
+ perror("select");
+ exit(1);
+ }
+ }
+ exit(0);
+}
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
new file mode 100644
index 00000000..87cf25a0
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -0,0 +1,1899 @@
+/* Interface handler for Sysmocom L1 */
+
+/* (C) 2011-2016 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/tx_power.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "hw_misc.h"
+#include "misc/sysmobts_par.h"
+#include "eeprom.h"
+#include "utils.h"
+
+struct wait_l1_conf {
+ struct llist_head list; /* internal linked list */
+ struct osmo_timer_list timer; /* timer for L1 timeout */
+ unsigned int conf_prim_id; /* primitive we expect in response */
+ HANDLE conf_hLayer3; /* layer 3 handle we expect in response */
+ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */
+ l1if_compl_cb *cb;
+ void *cb_data;
+};
+
+static void release_wlc(struct wait_l1_conf *wlc)
+{
+ osmo_timer_del(&wlc->timer);
+ talloc_free(wlc);
+}
+
+static void l1if_req_timeout(void *data)
+{
+ struct wait_l1_conf *wlc = data;
+
+ if (wlc->is_sys_prim)
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n",
+ get_value_string(femtobts_sysprim_names, wlc->conf_prim_id));
+ else
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(femtobts_l1prim_names, wlc->conf_prim_id));
+ exit(23);
+}
+
+static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitReq:
+ return prim->u.mphInitReq.hLayer3;
+ case GsmL1_PrimId_MphCloseReq:
+ return prim->u.mphCloseReq.hLayer3;
+ case GsmL1_PrimId_MphConnectReq:
+ return prim->u.mphConnectReq.hLayer3;
+ case GsmL1_PrimId_MphDisconnectReq:
+ return prim->u.mphDisconnectReq.hLayer3;
+ case GsmL1_PrimId_MphActivateReq:
+ return prim->u.mphActivateReq.hLayer3;
+ case GsmL1_PrimId_MphDeactivateReq:
+ return prim->u.mphDeactivateReq.hLayer3;
+ case GsmL1_PrimId_MphConfigReq:
+ return prim->u.mphConfigReq.hLayer3;
+ case GsmL1_PrimId_MphMeasureReq:
+ return prim->u.mphMeasureReq.hLayer3;
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.hLayer3;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.hLayer3;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.hLayer3;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.hLayer3;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.hLayer3;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.hLayer3;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.hLayer3;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.hLayer3;
+ case GsmL1_PrimId_MphTimeInd:
+ case GsmL1_PrimId_MphSyncInd:
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ case GsmL1_PrimId_PhDataReq:
+ case GsmL1_PrimId_PhConnectInd:
+ case GsmL1_PrimId_PhReadyToSendInd:
+ case GsmL1_PrimId_PhDataInd:
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id);
+ break;
+ }
+ return 0;
+}
+
+
+static int _l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ int is_system_prim, l1if_compl_cb *cb, void *data)
+{
+ struct wait_l1_conf *wlc;
+ struct osmo_wqueue *wqueue;
+ unsigned int timeout_secs;
+
+ /* allocate new wsc and store reference to mutex and conf_id */
+ wlc = talloc_zero(fl1h, struct wait_l1_conf);
+ wlc->cb = cb;
+ wlc->cb_data = data;
+
+ /* Make sure we actually have received a REQUEST type primitive */
+ if (is_system_prim == 0) {
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+
+ LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n",
+ get_value_string(femtobts_l1prim_names, l1p->id));
+
+ if (femtobts_l1prim_type[l1p->id] != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
+ get_value_string(femtobts_l1prim_names, l1p->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 0;
+ wlc->conf_prim_id = femtobts_l1prim_req2conf[l1p->id];
+ wlc->conf_hLayer3 = l1p_get_hLayer3(l1p);
+ wqueue = &fl1h->write_q[MQ_L1_WRITE];
+ timeout_secs = 30;
+ } else {
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n",
+ get_value_string(femtobts_sysprim_names, sysp->id));
+
+ if (femtobts_sysprim_type[sysp->id] != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
+ get_value_string(femtobts_sysprim_names, sysp->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 1;
+ wlc->conf_prim_id = femtobts_sysprim_req2conf[sysp->id];
+ wqueue = &fl1h->write_q[MQ_SYS_WRITE];
+ timeout_secs = 30;
+ }
+
+ /* enqueue the message in the queue and add wsc to list */
+ if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
+ /* So we will get a timeout but the log message might help */
+ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n",
+ is_system_prim ? "system primitive" : "gsm");
+ msgb_free(msg);
+ }
+ llist_add(&wlc->list, &fl1h->wlc_list);
+
+ /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */
+ wlc->timer.data = wlc;
+ wlc->timer.cb = l1if_req_timeout;
+ osmo_timer_schedule(&wlc->timer, timeout_secs, 0);
+
+ return 0;
+}
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 1, cb, data);
+}
+
+int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 0, cb, data);
+}
+
+/* allocate a msgb containing a GsmL1_Prim_t */
+struct msgb *l1p_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
+
+ return msg;
+}
+
+/* allocate a msgb containing a SuperFemto_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(SuperFemto_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(SuperFemto_Prim_t));
+
+ return msg;
+}
+
+static GsmL1_PhDataReq_t *
+data_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = rts_ind->hLayer1;
+ data_req->u8Tn = rts_ind->u8Tn;
+ data_req->u32Fn = rts_ind->u32Fn;
+ data_req->sapi = rts_ind->sapi;
+ data_req->subCh = rts_ind->subCh;
+ data_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return data_req;
+}
+
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = rts_ind->hLayer1;
+ empty_req->u8Tn = rts_ind->u8Tn;
+ empty_req->u32Fn = rts_ind->u32Fn;
+ empty_req->sapi = rts_ind->sapi;
+ empty_req->subCh = rts_ind->subCh;
+ empty_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return empty_req;
+}
+
+/* fill PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+data_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr, uint8_t len)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = fl1->hLayer1;
+ data_req->u8Tn = tn;
+ data_req->u32Fn = fn;
+ data_req->sapi = sapi;
+ data_req->subCh = sub_ch;
+ data_req->u8BlockNbr = block_nr;
+
+ data_req->msgUnitParam.u8Size = len;
+
+ return data_req;
+}
+
+/* fill PH-EMPTY_FRAME.req from l1sap primitive */
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi,
+ uint8_t subch, uint8_t block_nr)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = fl1->hLayer1;
+ empty_req->u8Tn = tn;
+ empty_req->u32Fn = fn;
+ empty_req->sapi = sapi;
+ empty_req->subCh = subch;
+ empty_req->u8BlockNbr = block_nr;
+
+ return empty_req;
+}
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+ struct msgb *l1msg = l1p_msgb_alloc();
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+
+ if (!msg) {
+ LOGP(DL1C, LOGL_FATAL, "PH-DATA.req without msg. "
+ "Please fix!\n");
+ abort();
+ }
+
+ len = msgb_l2len(msg);
+
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ u32Fn = l1sap->u.data.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ subCh = 0x1f;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ sapi = GsmL1_Sapi_Sacch;
+ if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (ts_is_pdch(&trx->ts[u8Tn])) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = GsmL1_Sapi_Ptcch;
+ u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn);
+ } else {
+ sapi = GsmL1_Sapi_Pdtch;
+ u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn);
+ }
+ } else {
+ sapi = GsmL1_Sapi_FacchF;
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_FacchH;
+ u8BlockNbr = (u32Fn % 26) >> 3;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Bcch;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Cbch;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ u8BlockNbr = l1sap_fn2ccch_block(u32Fn);
+ if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ"))
+ sapi = GsmL1_Sapi_Pch;
+ else
+ sapi = GsmL1_Sapi_Agch;
+ } else {
+ LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d "
+ "chan_nr %d link_id %d\n", l1sap->oph.primitive,
+ l1sap->oph.operation, chan_nr, link_id);
+ msgb_free(l1msg);
+ return -EINVAL;
+ }
+
+ /* convert l1sap message to GsmL1 primitive, keep payload */
+ if (len) {
+ /* data request */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len);
+ if (use_cache)
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ lchan->tch.dtx.facch, msgb_l2len(msg));
+ else if (dtx_dl_amr_enabled(lchan) &&
+ ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) {
+ if (sapi == GsmL1_Sapi_FacchF) {
+ sapi = GsmL1_Sapi_TchF;
+ }
+ if (sapi == GsmL1_Sapi_FacchH) {
+ sapi = GsmL1_Sapi_TchH;
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) {
+ /* FACCH interruption of DTX silence */
+ /* cache FACCH data */
+ memcpy(lchan->tch.dtx.facch, msg->l2h,
+ msgb_l2len(msg));
+ /* prepare ONSET or INH message */
+ if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_Onset;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidUpdateInH;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidFirstInH;
+ /* ignored CMR/CMI pair */
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0;
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0;
+ /* update length */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi,
+ subCh, u8BlockNbr, 3);
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ } else if (dtx_dl_amr_enabled(lchan) &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ else {
+ OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer));
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h,
+ msgb_l2len(msg));
+ }
+ LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n",
+ osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ l1p->u.phDataReq.msgUnitParam.u8Size));
+ } else {
+ /* empty frame */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) {
+ LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(l1msg);
+ } else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan))
+ ph_data_req(trx, msg, l1sap, true);
+ return 0;
+}
+
+static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi;
+ uint8_t chan_nr;
+ GsmL1_Prim_t *l1p;
+ struct msgb *nmsg = NULL;
+ int rc = -1;
+
+ chan_nr = l1sap->u.tch.chan_nr;
+ u32Fn = l1sap->u.tch.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_TchH;
+ } else {
+ subCh = 0x1f;
+ sapi = GsmL1_Sapi_TchF;
+ }
+
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ /* create new message and fill data */
+ if (msg) {
+ msgb_pull(msg, sizeof(*l1sap));
+ /* create new message */
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ l1p = msgb_l1prim(nmsg);
+ rc = l1if_tch_encode(lchan,
+ l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ &l1p->u.phDataReq.msgUnitParam.u8Size,
+ msg->data, msg->len, u32Fn, use_cache,
+ l1sap->u.tch.marker);
+ if (rc < 0) {
+ /* no data encoded for L1: smth will be generated below */
+ msgb_free(nmsg);
+ nmsg = NULL;
+ }
+ }
+
+ /* no message/data, we might generate an empty traffic msg or re-send
+ cached SID in case of DTX */
+ if (!nmsg)
+ nmsg = gen_empty_tch_msg(lchan, u32Fn);
+
+ /* no traffic message, we generate an empty msg */
+ if (!nmsg) {
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ }
+
+ l1p = msgb_l1prim(nmsg);
+
+ /* if we provide data, or if data is already in nmsg */
+ if (l1p->u.phDataReq.msgUnitParam.u8Size) {
+ /* data request */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh,
+ u8BlockNbr,
+ l1p->u.phDataReq.msgUnitParam.u8Size);
+ } else {
+ /* empty frame */
+ if (trx->bts->dtxd && trx != trx->bts->c0)
+ lchan->tch.dtx.dl_active = true;
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) != 0) {
+ LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(nmsg);
+ return -ENOBUFS;
+ }
+ if (dtx_is_first_p1(lchan))
+ dtx_dispatch(lchan, E_FIRST);
+ else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */
+ return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);
+
+ return 0;
+}
+
+static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+ uint8_t chan_nr;
+ struct gsm_lchan *lchan;
+ int rc = 0;
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink) {
+ l1if_set_ciphering(fl1, lchan, 0);
+ lchan->ciph_state = LCHAN_CIPH_RX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink) {
+ l1if_set_ciphering(fl1, lchan, 1);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink
+ && l1sap->u.info.u.ciph_req.uplink)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
+ l1if_rsl_chan_act(lchan);
+ else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ if (lchan->ho.active == HANDOVER_WAIT_FRAME)
+ l1if_rsl_chan_mod(lchan);
+ else
+ l1if_rsl_mode_modify(lchan);
+ } else if (l1sap->u.info.u.act_req.sacch_only)
+ l1if_rsl_deact_sacch(lchan);
+ else
+ l1if_rsl_chan_rel(lchan);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ /* called functions MUST NOT take ownership of msgb, as it is
+ * free()d below */
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ rc = ph_data_req(trx, msg, l1sap, false);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ rc = mph_info_req(trx, msg, l1sap);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_mph_time_ind(struct femtol1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind,
+ struct msgb *msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct osmo_phsap_prim l1sap;
+ uint32_t fn;
+
+ /* increment the primitive count for the alive timer */
+ fl1->alive_prim_cnt++;
+
+ /* ignore every time indication, except for c0 */
+ if (trx != bts->c0) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ fn = time_ind->u32Fn;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_TIME;
+ l1sap.u.info.u.time_ind.fn = fn;
+
+ msgb_free(msg);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ return GSM_PCHAN_TCH_F;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ default:
+ return ts->pchan;
+ }
+}
+
+static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
+ GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ enum gsm_phys_chan_config pchan = pick_pchan(ts);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ switch (sapi) {
+ case GsmL1_Sapi_Bcch:
+ cbits = 0x10;
+ break;
+ case GsmL1_Sapi_Cbch:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ break;
+ case GsmL1_Sapi_Sacch:
+ switch(pchan) {
+ case GSM_PCHAN_TCH_F:
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ cbits = 0x02 + subCh;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Sdcch:
+ switch(pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Agch:
+ case GsmL1_Sapi_Pch:
+ cbits = 0x12;
+ break;
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_TchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_TchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_FacchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_FacchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ if (!L1SAP_IS_PTCCH(u32Fn)) {
+ LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
+ "number other than 12, got it at %u (%u). "
+ "Please fix!\n", u32Fn % 52, u32Fn);
+ abort();
+ }
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ /* not reached due to default case above */
+ return (cbits << 3) | u8Tn;
+}
+
+static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1,
+ GsmL1_PhReadyToSendInd_t *rts_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct msgb *resp_msg;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ struct gsm_time g_time;
+ uint32_t t3p;
+ int rc;
+ struct osmo_phsap_prim *l1sap;
+ uint8_t chan_nr, link_id;
+ uint32_t fn;
+
+ /* check if primitive should be handled by common part */
+ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi,
+ rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn);
+ if (chan_nr) {
+ fn = rts_ind->u32Fn;
+ if (rts_ind->sapi == GsmL1_Sapi_Sacch)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+ /* recycle the msgb and use it for the L1 primitive,
+ * which means that we (or our caller) must not free it */
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ if (rts_ind->sapi == GsmL1_Sapi_TchF
+ || rts_ind->sapi == GsmL1_Sapi_TchH) {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ } else {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ }
+
+ return l1sap_up(trx, l1sap);
+ }
+
+ gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
+ get_value_string(femtobts_l1sapi_names, rts_ind->sapi));
+
+ /* in all other cases, we need to allocate a new PH-DATA.ind
+ * primitive msgb and start to fill it */
+ resp_msg = l1p_msgb_alloc();
+ data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+ msu_param = &data_req->msgUnitParam;
+
+ /* set default size */
+ msu_param->u8Size = GSM_MACBLOCK_LEN;
+
+ switch (rts_ind->sapi) {
+ case GsmL1_Sapi_Sch:
+ /* compute T3prime */
+ t3p = (g_time.t3 - 1) / 10;
+ /* fill SCH burst with data */
+ msu_param->u8Size = 4;
+ msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9);
+ msu_param->u8Buffer[1] = (g_time.t1 >> 1);
+ msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
+ msu_param->u8Buffer[3] = (t3p & 1);
+ break;
+ case GsmL1_Sapi_Prach:
+ goto empty_frame;
+ break;
+ default:
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ break;
+ }
+tx:
+
+ /* transmit */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) {
+ LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(resp_msg);
+ }
+
+ /* free the msgb, as we have not handed it to l1sap and thus
+ * need to release its memory */
+ msgb_free(l1p_msg);
+ return 0;
+
+empty_frame:
+ /* in case we decide to send an empty frame... */
+ empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+
+ goto tx;
+}
+
+static void dump_meas_res(int ll, GsmL1_MeasParam_t *m)
+{
+ LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, "
+ "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality,
+ m->fBer, m->i16BurstTiming);
+}
+
+static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ uint32_t fn, GsmL1_MeasParam_t *m)
+{
+ struct osmo_phsap_prim l1sap;
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_MEAS;
+ l1sap.u.info.u.meas_ind.chan_nr = chan_nr;
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming * 64;
+ l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000);
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1);
+ l1sap.u.info.u.meas_ind.fn = fn;
+
+ /* l1sap wants to take msgb ownership. However, as there is no
+ * msg, it will msgb_free(l1sap.oph.msg == NULL) */
+ return l1sap_up(trx, &l1sap);
+}
+
+static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ uint8_t chan_nr, link_id;
+ struct msgb *sap_msg;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ struct gsm_time g_time;
+ int rc = 0;
+
+ chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi,
+ data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn);
+ if (!chan_nr) {
+ LOGPFN(DL1C, LOGL_ERROR, data_ind->u32Fn, "PH-DATA-INDICATION for unknown sapi %s (%d)\n",
+ get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->sapi);
+ msgb_free(l1p_msg);
+ return ENOTSUP;
+ }
+ fn = data_ind->u32Fn;
+ link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC;
+
+ process_meas_res(trx, chan_nr, fn, &data_ind->measParam);
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+ get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->hLayer2,
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size));
+ dump_meas_res(LOGL_DEBUG, &data_ind->measParam);
+
+ /* check for TCH */
+ if (data_ind->sapi == GsmL1_Sapi_TchF
+ || data_ind->sapi == GsmL1_Sapi_TchH) {
+ /* TCH speech frame handling */
+ rc = l1if_tch_rx(trx, chan_nr, l1p_msg);
+ msgb_free(l1p_msg);
+ return rc;
+ }
+
+ /* fill L1SAP header */
+ sap_msg = l1sap_msgb_alloc(data_ind->msgUnitParam.u8Size);
+ l1sap = msgb_l1sap_prim(sap_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, sap_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = (int8_t) (data_ind->measParam.fRssi);
+ if (!pcu_direct) { /* FIXME: if pcu_direct=1, then this is not set, what to do in pcu_tx_data_ind() in this case ?*/
+ l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000;
+ l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming * 64;
+ l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10;
+ }
+ /* copy data from L1 primitive to L1SAP primitive */
+ sap_msg->l2h = msgb_put(sap_msg, data_ind->msgUnitParam.u8Size);
+ memcpy(sap_msg->l2h, data_ind->msgUnitParam.u8Buffer,
+ data_ind->msgUnitParam.u8Size);
+
+
+ msgb_free(l1p_msg);
+
+ return l1sap_up(trx, l1sap);
+}
+
+static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_lchan *lchan;
+ struct osmo_phsap_prim *l1sap;
+ int rc;
+ struct ph_rach_ind_param rach_ind_param;
+
+ /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */
+ if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) {
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+
+ if ((ra_ind->msgUnitParam.u8Size != 1) &&
+ (ra_ind->msgUnitParam.u8Size != 2)) {
+ LOGPFN(DL1C, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n",
+ ra_ind->sapi);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
+ rach_ind_param = (struct ph_rach_ind_param) {
+ /* .chan_nr set below */
+ /* .ra set below */
+ .acc_delay = 0,
+ .fn = ra_ind->u32Fn,
+ /* .is_11bit set below */
+ /* .burst_type set below */
+ .rssi = (int8_t) ra_ind->measParam.fRssi,
+ .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0),
+ .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64,
+ };
+
+ lchan = l1if_hLayer_to_lchan(trx, ra_ind->hLayer2);
+ if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ rach_ind_param.chan_nr = 0x88;
+ else
+ rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);
+
+ if (ra_ind->msgUnitParam.u8Size == 2) {
+ uint16_t temp;
+ uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0];
+ ra = ra << 3;
+ temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7);
+ ra = ra | temp;
+ rach_ind_param.is_11bit = 1;
+ rach_ind_param.ra = ra;
+ } else {
+ rach_ind_param.is_11bit = 0;
+ rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0];
+ }
+
+ /* the old legacy full-bits acc_delay cannot express negative values */
+ if (ra_ind->measParam.i16BurstTiming > 0)
+ rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+ /* mapping of the burst type, the values are specific to osmo-bts-sysmo */
+ switch (ra_ind->burstType) {
+ case GsmL1_BurstType_Access_0:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_0;
+ break;
+ case GsmL1_BurstType_Access_1:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_1;
+ break;
+ case GsmL1_BurstType_Access_2:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_2;
+ break;
+ default:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_NONE;
+ break;
+ }
+
+ /* msgb_trim() invalidates ra_ind, make that abundantly clear: */
+ ra_ind = NULL;
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ l1p_msg);
+ l1sap->u.rach_ind = rach_ind_param;
+
+ return l1sap_up(trx, l1sap);
+}
+
+/* handle any random indication from the L1 */
+static int l1if_handle_ind(struct femtol1_hdl *fl1, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ int rc = 0;
+
+ /* all the below called functions must take ownership of the msgb */
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg);
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd,
+ msg);
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg);
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg);
+ break;
+ default:
+ msgb_free(msg);
+ }
+
+ return rc;
+}
+
+static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc)
+{
+ if (wlc->is_sys_prim != 0)
+ return 0;
+ if (l1p->id != wlc->conf_prim_id)
+ return 0;
+ if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3)
+ return 0;
+ return 1;
+}
+
+int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ /* silent, don't clog the log file */
+ break;
+ default:
+ LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
+ get_value_string(femtobts_l1prim_names, l1p->id), wq);
+ }
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (is_prim_compat(l1p, wlc)) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(femtol1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n",
+ get_value_string(femtobts_sysprim_names, sysp->id));
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ /* the limitation here is that we cannot have multiple callers
+ * sending the same primitive */
+ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(femtol1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+ unsigned int i;
+
+ if (sysp->id == SuperFemto_PrimId_ActivateRfCnf)
+ on = 1;
+
+ if (on)
+ status = sysp->u.activateRfCnf.status;
+ else
+ status = sysp->u.deactivateRfCnf.status;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
+ get_value_string(femtobts_l1status_names, status));
+
+
+ if (on) {
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-ACT failure");
+ } else
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 1);
+
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+ } else {
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+int get_clk_cal(struct femtol1_hdl *hdl)
+{
+#ifdef FEMTOBTS_API_VERSION
+ return hdl->clk_cal;
+#else
+ switch (hdl->clk_src) {
+ case SuperFemto_ClkSrcId_Ocxo:
+ case SuperFemto_ClkSrcId_Tcxo:
+ /* only for those on-board clocks it makes sense to use
+ * the calibration value */
+ return hdl->clk_cal;
+ default:
+ /* external clocks like GPS are taken 1:1 without any
+ * modification by a local calibration value */
+ LOGP(DL1C, LOGL_INFO, "Ignoring Clock Calibration for "
+ "selected %s clock\n",
+ get_value_string(femtobts_clksrc_names, hdl->clk_src));
+ return 0;
+ }
+#endif
+}
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0)
+/*
+ * RevC was the last HW revision without an external
+ * attenuator. Check for that.
+ */
+static int has_external_atten(struct femtol1_hdl *hdl)
+{
+ /* older version doesn't have an attenuator */
+ return hdl->hw_info.ver_major > 2;
+}
+#endif /* 2.2.0 */
+
+/* activate or de-activate the entire RF-Frontend */
+int l1if_activate_rf(struct femtol1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ if (on) {
+ sysp->id = SuperFemto_PrimId_ActivateRfReq;
+#ifdef HW_SYSMOBTS_V1
+ sysp->u.activateRfReq.u12ClkVc = get_clk_cal(hdl);
+#else
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(0,2,0)
+ sysp->u.activateRfReq.timing.u8TimSrc = 1; /* Master */
+#endif /* 0.2.0 */
+ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;
+ /* Use clock from OCXO or whatever source is configured */
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0)
+ sysp->u.activateRfReq.rfTrx.u8ClkSrc = hdl->clk_src;
+#else
+ sysp->u.activateRfReq.rfTrx.clkSrc = hdl->clk_src;
+#endif /* 2.1.0 */
+ sysp->u.activateRfReq.rfTrx.iClkCor = get_clk_cal(hdl);
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0)
+ sysp->u.activateRfReq.rfRx.u8ClkSrc = hdl->clk_src;
+#else
+ sysp->u.activateRfReq.rfRx.clkSrc = hdl->clk_src;
+#endif /* 2.1.0 */
+ sysp->u.activateRfReq.rfRx.iClkCor = get_clk_cal(hdl);
+#endif /* API 2.4.0 */
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0)
+ struct gsm_bts_trx *trx = hdl->phy_inst->trx;
+ if (has_external_atten(hdl)) {
+ LOGP(DL1C, LOGL_INFO, "Using external attenuator.\n");
+ sysp->u.activateRfReq.rfTrx.u8UseExtAtten = 1;
+ sysp->u.activateRfReq.rfTrx.fMaxTxPower =
+ (float) get_p_trxout_target_mdBm(trx, 0) / 1000;
+ }
+#endif /* 2.2.0 */
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,8,1)
+ /* maximum cell size in quarter-bits, 90 == 12.456 km */
+ sysp->u.activateRfReq.u8MaxCellSize = 90;
+#endif
+#endif /* !HW_SYSMOBTS_V1 */
+ } else {
+ sysp->id = SuperFemto_PrimId_DeactivateRfReq;
+ }
+
+ return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL);
+}
+
+static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
+ struct gsm_lchan *lchan = &ts->lchan[i];
+
+ if (!is_muted)
+ continue;
+
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+
+ /* skip channels that might be active for another reason */
+ if (lchan->type == GSM_LCHAN_CCCH)
+ continue;
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ continue;
+
+ if (lchan->s <= 0)
+ continue;
+
+ lchan->s = 0;
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ }
+}
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n",
+ get_value_string(femtobts_l1status_names, status));
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0);
+ } else {
+ int i;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1);
+
+ osmo_static_assert(
+ ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute),
+ ts_array_size);
+
+ for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i)
+ mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+#endif
+
+/* mute/unmute RF time slots */
+int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
+{
+
+ LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n",
+ mute[0], mute[1], mute[2], mute[3],
+ mute[4], mute[5], mute[6], mute[7]
+ );
+
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3,6,0)
+ const uint8_t unmuted[8] = { 0,0,0,0,0,0,0,0 };
+ struct gsm_bts_trx *trx = hdl->phy_inst->trx;
+ int i;
+ LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n");
+ /* always acknowledge an un-MUTE (which is a no-op if MUTE is not supported */
+ if (!memcmp(mute, unmuted, ARRAY_SIZE(unmuted))) {
+ bts_update_status(BTS_STATUS_RF_MUTE, mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, mute, 1);
+ for (i = 0; i < ARRAY_SIZE(unmuted); ++i)
+ mute_handle_ts(&trx->ts[i], mute[i]);
+ return 0;
+ }
+ return -ENOTSUP;
+#else
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = SuperFemto_PrimId_MuteRfReq;
+ memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute));
+ /* save for later use */
+ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute));
+
+ return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL);
+#endif /* < 3.6.0 */
+}
+
+/* call-back on arrival of DSP+FPGA version + band capability */
+static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ SuperFemto_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ fl1h->hw_info.dsp_version[0] = sic->dspVersion.major;
+ fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor;
+ fl1h->hw_info.dsp_version[2] = sic->dspVersion.build;
+
+ fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major;
+ fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor;
+ fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build;
+
+#ifndef HW_SYSMOBTS_V1
+ fl1h->hw_info.ver_major = sic->boardVersion.rev;
+ fl1h->hw_info.ver_minor = sic->boardVersion.option;
+#endif
+
+ LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn",
+ sic->dspVersion.major, sic->dspVersion.minor,
+ sic->dspVersion.build, sic->fpgaVersion.major,
+ sic->fpgaVersion.minor, sic->fpgaVersion.build);
+
+#ifdef HW_SYSMOBTS_V1
+ if (sic->rfBand.gsm850)
+ fl1h->hw_info.band_support |= GSM_BAND_850;
+ if (sic->rfBand.gsm900)
+ fl1h->hw_info.band_support |= GSM_BAND_900;
+ if (sic->rfBand.dcs1800)
+ fl1h->hw_info.band_support |= GSM_BAND_1800;
+ if (sic->rfBand.pcs1900)
+ fl1h->hw_info.band_support |= GSM_BAND_1900;
+#endif
+
+ if (!(fl1h->hw_info.band_support & trx->bts->band))
+ LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
+ gsm_band_name(trx->bts->band));
+
+ if (l1if_dsp_ver(fl1h) < L1_VER_SHIFT(5,3,3))
+ fl1h->rtp_hr_jumble_needed = true;
+
+ /* Request the activation */
+ l1if_activate_rf(fl1h, 1);
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ /* load calibration tables (if we know their path) */
+ int rc = calib_load(fl1h);
+ if (rc < 0)
+ LOGP(DL1C, LOGL_ERROR, "Operating without calibration; "
+ "unable to load tables!\n");
+#else
+ LOGP(DL1C, LOGL_NOTICE, "Operating without calibration "
+ "as software was compiled against old header files\n");
+#endif
+
+ msgb_free(resp);
+
+ /* FIXME: clock related */
+ return 0;
+}
+
+/* request DSP+FPGA code versions + band capability */
+static int l1if_get_info(struct femtol1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = SuperFemto_PrimId_SystemInfoReq;
+
+ return l1if_req_compl(hdl, msg, info_compl_cb, NULL);
+}
+
+static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status = sysp->u.layer1ResetCnf.status;
+
+ LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n",
+ get_value_string(femtobts_l1status_names, status));
+
+ msgb_free(resp);
+
+ /* If we're coming out of reset .. */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_shutdown(trx->bts, "L1-RESET failure");
+ }
+
+ /* as we cannot get the current DSP trace flags, we simply
+ * set them to zero (or whatever dsp_trace_f has been initialized to */
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f);
+
+ /* obtain version information on DSP/FPGA and band capabilities */
+ l1if_get_info(fl1h);
+
+ return 0;
+}
+
+int l1if_reset(struct femtol1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = SuperFemto_PrimId_Layer1ResetReq;
+
+ return l1if_req_compl(hdl, msg, reset_compl_cb, NULL);
+}
+
+/* set the trace flags within the DSP */
+int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
+ flags);
+
+ sysp->id = SuperFemto_PrimId_SetTraceFlagsReq;
+ sysp->u.setTraceFlagsReq.u32Tf = flags;
+
+ hdl->dsp_trace_f = flags;
+
+ /* There is no confirmation we could wait for */
+ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* get those femtol1_hdl.hw_info elements that sre in EEPROM */
+static int get_hwinfo_eeprom(struct femtol1_hdl *fl1h)
+{
+ eeprom_SysInfo_t sysinfo;
+ int val, rc;
+
+ rc = sysmobts_get_type(&val);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.model_nr = val;
+
+ rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_FLAGS, &val);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.model_flags = val;
+
+ rc = sysmobts_get_trx(&val);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.trx_nr = val;
+
+ rc = eeprom_ReadSysInfo(&sysinfo);
+ if (rc != EEPROM_SUCCESS) {
+ /* some early units don't yet have the EEPROM
+ * information structure */
+ LOGP(DL1C, LOGL_ERROR, "Unable to read band support "
+ "from EEPROM, assuming all bands\n");
+ fl1h->hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | GSM_BAND_1800 | GSM_BAND_1900;
+ return 0;
+ }
+
+ if (sysinfo.u8GSM850)
+ fl1h->hw_info.band_support |= GSM_BAND_850;
+ if (sysinfo.u8GSM900)
+ fl1h->hw_info.band_support |= GSM_BAND_900;
+ if (sysinfo.u8DCS1800)
+ fl1h->hw_info.band_support |= GSM_BAND_1800;
+ if (sysinfo.u8PCS1900)
+ fl1h->hw_info.band_support |= GSM_BAND_1900;
+
+ return 0;
+}
+
+/* Set the clock calibration to the value read from the eeprom. */
+static void clk_cal_use_eeprom(struct femtol1_hdl *hdl)
+{
+ struct phy_instance *pinst = hdl->phy_inst;
+ eeprom_RfClockCal_t rf_clk;
+ int rc;
+
+ if (!pinst->u.sysmobts.clk_use_eeprom)
+ return;
+
+ rc = eeprom_ReadRfClockCal(&rf_clk);
+ if (rc != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to read from EEPROM.\n");
+ return;
+ }
+
+ hdl->clk_cal = rf_clk.iClkCor;
+ LOGP(DL1C, LOGL_NOTICE,
+ "Read clock calibration(%d) from EEPROM.\n", hdl->clk_cal);
+}
+
+struct femtol1_hdl *l1if_open(struct phy_instance *pinst)
+{
+ struct femtol1_hdl *fl1h;
+ int rc;
+
+#ifndef HW_SYSMOBTS_V1
+ LOGP(DL1C, LOGL_INFO, "sysmoBTSv2 L1IF compiled against API headers "
+ "v%u.%u.%u\n", SUPERFEMTO_API_VERSION >> 16,
+ (SUPERFEMTO_API_VERSION >> 8) & 0xff,
+ SUPERFEMTO_API_VERSION & 0xff);
+#else
+ LOGP(DL1C, LOGL_INFO, "sysmoBTSv1 L1IF compiled against API headers "
+ "v%u.%u.%u\n", FEMTOBTS_API_VERSION >> 16,
+ (FEMTOBTS_API_VERSION >> 8) & 0xff,
+ FEMTOBTS_API_VERSION & 0xff);
+#endif
+
+ fl1h = talloc_zero(pinst, struct femtol1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->phy_inst = pinst;
+ fl1h->dsp_trace_f = pinst->u.sysmobts.dsp_trace_f;
+ fl1h->clk_src = pinst->u.sysmobts.clk_src;
+ fl1h->clk_cal = pinst->u.sysmobts.clk_cal;
+ clk_cal_use_eeprom(fl1h);
+ get_hwinfo_eeprom(fl1h);
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ if (fl1h->clk_src == SuperFemto_ClkSrcId_None) {
+ if (fl1h->hw_info.model_nr == 2050) {
+ /* On the sysmoBTS 2050, we don't have an OCXO but
+ * start with the TCXO and will sync it with the PPS
+ * of the GPS in case there is a fix. */
+ fl1h->clk_src = SuperFemto_ClkSrcId_Tcxo;
+ LOGP(DL1C, LOGL_INFO, "Clock source defaulting to GPS 1PPS "
+ "on sysmoBTS 2050\n");
+ } else {
+ /* default clock source: OCXO */
+ fl1h->clk_src = SuperFemto_ClkSrcId_Ocxo;
+ }
+ }
+#else
+ if (fl1h->clk_src == SF_CLKSRC_NONE)
+ fl1h->clk_src = SF_CLKSRC_OCXO;
+#endif
+
+ rc = l1if_transport_open(MQ_SYS_WRITE, fl1h);
+ if (rc < 0) {
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ rc = l1if_transport_open(MQ_L1_WRITE, fl1h);
+ if (rc < 0) {
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ l1if_reset(fl1h);
+
+ return fl1h;
+}
+
+int l1if_close(struct femtol1_hdl *fl1h)
+{
+ l1if_transport_close(MQ_L1_WRITE, fl1h);
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ return 0;
+}
+
+#ifdef HW_SYSMOBTS_V1
+int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h)
+{
+ LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n");
+ return -ENOTSUP;
+}
+
+int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h)
+{
+ LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n");
+ return -ENOTSUP;
+}
+
+#else
+static int clock_reset_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ msgb_free(resp);
+ return 0;
+}
+
+static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+
+ if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success)
+ LOGP(DL1C, LOGL_ERROR, "Rx RfClockSetupConf failed with: %d\n",
+ sysp->u.rfClockSetupCnf.status);
+ msgb_free(resp);
+ return 0;
+}
+
+static int clock_correct_info_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+
+ LOGP(DL1C, LOGL_NOTICE,
+ "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrx.clkSrc),
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc));
+
+ if (sysp->u.rfClockInfoCnf.rfTrx.clkSrc == SuperFemto_ClkSrcId_GpsPps) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Calibrating GPS against GPS doesn not make sense.\n");
+ msgb_free(resp);
+ return -1;
+ }
+
+ if (sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc == SuperFemto_ClkSrcId_None) {
+ LOGP(DL1C, LOGL_ERROR,
+ "No reference clock set. Please reset first.\n");
+ msgb_free(resp);
+ return -1;
+ }
+
+ if (sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes == 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Couldn't determine the clock difference.\n");
+ msgb_free(resp);
+ return -1;
+ }
+
+ fl1h->clk_cal = sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr;
+ fl1h->phy_inst->u.sysmobts.clk_use_eeprom = 0;
+ msgb_free(resp);
+
+ /*
+ * Let's reset the counter and this will lead to applying the
+ * new calibration.
+ */
+ l1if_rf_clock_info_reset(fl1h);
+
+ return 0;
+}
+
+int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ /* Set GPS/PPS as reference */
+ sysp->id = SuperFemto_PrimId_RfClockSetupReq;
+ sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h);
+ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src;
+ sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps;
+ l1if_req_compl(fl1h, msg, clock_setup_cb, NULL);
+
+ /* Reset the error counters */
+ msg = sysp_msgb_alloc();
+ sysp = msgb_sysprim(msg);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 1;
+
+ return l1if_req_compl(fl1h, msg, clock_reset_cb, NULL);
+}
+
+int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 0;
+
+ return l1if_req_compl(fl1h, msg, clock_correct_info_cb, NULL);
+}
+
+#endif
+
+static void set_power_param(struct trx_power_params *out,
+ int trx_p_max_out_dBm,
+ int int_pa_nominal_gain_dB)
+{
+ out->trx_p_max_out_mdBm = to_mdB(trx_p_max_out_dBm);
+ out->pa.nominal_gain_mdB = to_mdB(int_pa_nominal_gain_dB);
+}
+
+static void fill_trx_power_params(struct gsm_bts_trx *trx,
+ struct phy_instance *pinst)
+{
+ struct femtol1_hdl *fl1h = pinst->u.sysmobts.hdl;
+
+ switch (fl1h->hw_info.model_nr) {
+ case 1020:
+ set_power_param(&trx->power_params, 23, 10);
+ break;
+ case 1100:
+ set_power_param(&trx->power_params, 23, 17);
+ break;
+ case 2050:
+ set_power_param(&trx->power_params, 37, 0);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "Unknown/Unsupported "
+ "sysmoBTS Model Number %u\n",
+ fl1h->hw_info.model_nr);
+ /* fall-through */
+ case 0xffff:
+ /* sysmoBTS 1002 without any setting in EEPROM */
+ LOGP(DL1C, LOGL_NOTICE, "Assuming 1002 for sysmoBTS "
+ "Model number %u\n", fl1h->hw_info.model_nr);
+ case 1002:
+ set_power_param(&trx->power_params, 23, 0);
+ }
+}
+
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst = phy_instance_by_num(plink, 0);
+ struct femtol1_hdl *hdl;
+ struct gsm_bts *bts;
+
+ OSMO_ASSERT(pinst);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ pinst->u.sysmobts.hdl = l1if_open(pinst);
+ if (!pinst->u.sysmobts.hdl) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n");
+ return -EIO;
+ }
+
+ fill_trx_power_params(pinst->trx, pinst);
+
+ bts = pinst->trx->bts;
+ if (pinst->trx == bts->c0) {
+ int rc;
+ rc = get_p_max_out_mdBm(bts->c0);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal "
+ "transmit power. Assuming 23dBm.\n");
+ }
+ bts->c0->nominal_power = rc;
+ }
+
+ hdl = pinst->u.sysmobts.hdl;
+ osmo_strlcpy(bts->sub_model, sysmobts_model(hdl->hw_info.model_nr, hdl->hw_info.trx_nr), sizeof(bts->sub_model));
+ snprintf(pinst->version, sizeof(pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u",
+ hdl->hw_info.ver_major, hdl->hw_info.ver_minor,
+ hdl->hw_info.dsp_version[0], hdl->hw_info.dsp_version[1], hdl->hw_info.dsp_version[2],
+ hdl->hw_info.fpga_version[0], hdl->hw_info.fpga_version[1], hdl->hw_info.fpga_version[2]);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h
new file mode 100644
index 00000000..1b214be7
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_if.h
@@ -0,0 +1,171 @@
+#ifndef _FEMTO_L1_H
+#define _FEMTO_L1_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/phy_link.h>
+
+#include <sysmocom/femtobts/gsml1prim.h>
+
+#include <stdbool.h>
+
+enum {
+ MQ_SYS_READ,
+ MQ_L1_READ,
+#ifndef HW_SYSMOBTS_V1
+ MQ_TCH_READ,
+ MQ_PDTCH_READ,
+#endif
+ _NUM_MQ_READ
+};
+
+enum {
+ MQ_SYS_WRITE,
+ MQ_L1_WRITE,
+#ifndef HW_SYSMOBTS_V1
+ MQ_TCH_WRITE,
+ MQ_PDTCH_WRITE,
+#endif
+ _NUM_MQ_WRITE
+};
+
+struct calib_send_state {
+ const char *path;
+ int last_file_idx;
+};
+
+enum {
+ FIXUP_UNITILIAZED,
+ FIXUP_NEEDED,
+ FIXUP_NOT_NEEDED,
+};
+
+struct femtol1_hdl {
+ struct gsm_time gsm_time;
+ uint32_t hLayer1; /* handle to the L1 instance in the DSP */
+ uint32_t dsp_trace_f; /* currently operational DSP trace flags */
+ int clk_cal;
+ uint8_t clk_src;
+ struct llist_head wlc_list;
+
+ struct phy_instance *phy_inst; /* Reference to PHY instance */
+
+ struct osmo_timer_list alive_timer;
+ unsigned int alive_prim_cnt;
+
+ struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
+ struct osmo_wqueue write_q[_NUM_MQ_WRITE];
+
+ struct {
+ /* from DSP/FPGA after L1 Init */
+ uint8_t dsp_version[3];
+ uint8_t fpga_version[3];
+ uint32_t band_support; /* bitmask of GSM_BAND_* */
+ uint8_t ver_major;
+ uint8_t ver_minor;
+ /* from EEPROM */
+ uint16_t model_nr;
+ uint16_t model_flags;
+ uint8_t trx_nr;
+ } hw_info;
+
+ int fixup_needed;
+ bool rtp_hr_jumble_needed;
+
+ struct calib_send_state st;
+
+ uint8_t last_rf_mute[8];
+
+ /* for l1_fwd */
+ void *priv;
+};
+
+
+#define L1_VER_SHIFT(x,y,z) ((x << 16) | (y << 8) | (z))
+
+static inline uint32_t l1if_dsp_ver(struct femtol1_hdl *fl1h)
+{
+ const uint8_t *v = fl1h->hw_info.dsp_version;
+ return L1_VER_SHIFT(v[0], v[1], v[2]);
+}
+
+static inline uint32_t l1if_fpga_ver(struct femtol1_hdl *fl1h)
+{
+ const uint8_t *v = fl1h->hw_info.fpga_version;
+ return L1_VER_SHIFT(v[0], v[1], v[2]);
+}
+
+#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg) ((SuperFemto_Prim_t *)(msg)->l1h)
+
+typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+
+struct femtol1_hdl *l1if_open(struct phy_instance *pinst);
+int l1if_close(struct femtol1_hdl *hdl);
+int l1if_reset(struct femtol1_hdl *hdl);
+int l1if_activate_rf(struct femtol1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power);
+int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
+
+struct msgb *l1p_msgb_alloc(void);
+struct msgb *sysp_msgb_alloc(void);
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
+struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
+
+/* tch.c */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker);
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
+int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
+
+/* ciphering */
+int l1if_set_ciphering(struct femtol1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink);
+
+/* channel control */
+int l1if_rsl_chan_act(struct gsm_lchan *lchan);
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
+
+/* calibration loading */
+int calib_load(struct femtol1_hdl *fl1h);
+int get_clk_cal(struct femtol1_hdl *hdl);
+
+/* on-line re-calibration */
+int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h);
+int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h);
+
+/* public helpers for test */
+int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan);
+
+static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ OSMO_ASSERT(pinst);
+ return pinst->u.sysmobts.hdl;
+}
+
+static inline struct gsm_bts_trx *femtol1_hdl_trx(struct femtol1_hdl *fl1h)
+{
+ OSMO_ASSERT(fl1h->phy_inst);
+ return fl1h->phy_inst->trx;
+}
+#endif /* _FEMTO_L1_H */
diff --git a/src/osmo-bts-sysmo/l1_transp.h b/src/osmo-bts-sysmo/l1_transp.h
new file mode 100644
index 00000000..b2967f93
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_transp.h
@@ -0,0 +1,14 @@
+#ifndef _FEMTOL1_TRANSPH_
+#define _FEMTOL1_TRANSPH_
+
+#include <osmocom/core/msgb.h>
+
+/* functions a transport calls on arrival of primitive from BTS */
+int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg);
+
+/* functions exported by a transport */
+int l1if_transport_open(int q, struct femtol1_hdl *fl1h);
+int l1if_transport_close(int q, struct femtol1_hdl *fl1h);
+
+#endif /* _FEMTOL1_TRANSP_H */
diff --git a/src/osmo-bts-sysmo/l1_transp_fwd.c b/src/osmo-bts-sysmo/l1_transp_fwd.c
new file mode 100644
index 00000000..87c230bb
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_transp_fwd.c
@@ -0,0 +1,152 @@
+/* Interface handler for Sysmocom L1 (forwarding) */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "l1_fwd.h"
+
+static const uint16_t fwd_udp_ports[] = {
+ [MQ_SYS_WRITE] = L1FWD_SYS_PORT,
+ [MQ_L1_WRITE] = L1FWD_L1_PORT,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_WRITE] = L1FWD_TCH_PORT,
+ [MQ_PDTCH_WRITE]= L1FWD_PDTCH_PORT,
+#endif
+};
+
+static int fwd_read_cb(struct osmo_fd *ofd)
+{
+ struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx");
+ struct femtol1_hdl *fl1h = ofd->data;
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l1h = msg->data;
+ rc = read(ofd->fd, msg->l1h, msgb_tailroom(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Short read from UDP\n");
+ msgb_free(msg);
+ return rc;
+ } else if (rc == 0) {
+ LOGP(DL1C, LOGL_ERROR, "Len=0 from UDP\n");
+ msgb_free(msg);
+ return rc;
+ }
+ msgb_put(msg, rc);
+
+ if (ofd->priv_nr == MQ_SYS_WRITE)
+ rc = l1if_handle_sysprim(fl1h, msg);
+ else
+ rc = l1if_handle_l1prim(ofd->priv_nr, fl1h, msg);
+
+ return rc;
+}
+
+static int prim_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ /* write to the fd */
+ return write(ofd->fd, msg->l1h, msgb_l1len(msg));
+}
+
+int l1if_transport_open(int q, struct femtol1_hdl *fl1h)
+{
+ int rc;
+ char *bts_host = getenv("L1FWD_BTS_HOST");
+
+ switch (q) {
+ case MQ_L1_WRITE:
+ LOGP(DL1C, LOGL_INFO, "sizeof(GsmL1_Prim_t) = %zu\n",
+ sizeof(GsmL1_Prim_t));
+ break;
+ case MQ_SYS_WRITE:
+ LOGP(DL1C, LOGL_INFO, "sizeof(SuperFemto_Prim_t) = %zu\n",
+ sizeof(SuperFemto_Prim_t));
+ break;
+ }
+
+ if (!bts_host) {
+ fprintf(stderr, "You have to set the L1FWD_BTS_HOST environment variable\n");
+ exit(2);
+ }
+
+ struct osmo_wqueue *wq = &fl1h->write_q[q];
+ struct osmo_fd *ofd = &wq->bfd;
+
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = prim_write_cb;
+ wq->read_cb = fwd_read_cb;
+
+ ofd->data = fl1h;
+ ofd->priv_nr = q;
+ ofd->when |= BSC_FD_READ;
+
+ rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
+ bts_host, fwd_udp_ports[q],
+ OSMO_SOCK_F_CONNECT);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+int l1if_transport_close(int q, struct femtol1_hdl *fl1h)
+{
+ struct osmo_wqueue *wq = &fl1h->write_q[q];
+ struct osmo_fd *ofd = &wq->bfd;
+
+ osmo_wqueue_clear(wq);
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/l1_transp_hw.c b/src/osmo-bts-sysmo/l1_transp_hw.c
new file mode 100644
index 00000000..01bc2005
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_transp_hw.c
@@ -0,0 +1,329 @@
+/* Interface handler for Sysmocom L1 (real hardware) */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+
+#ifdef HW_SYSMOBTS_V1
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/femtobts_dsp2arm"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/femtobts_arm2dsp"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_dsp2arm"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_arm2dsp"
+#else
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/superfemto_dsp2arm"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/superfemto_arm2dsp"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp"
+
+#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm"
+#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp"
+#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm"
+#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp"
+#endif
+
+static const char *rd_devnames[] = {
+ [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
+ [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
+ [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
+#endif
+};
+
+static const char *wr_devnames[] = {
+ [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
+ [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
+ [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
+#endif
+};
+
+/*
+ * Make sure that all structs we read fit into the SYSMOBTS_PRIM_SIZE
+ */
+osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, l1_prim)
+osmo_static_assert(sizeof(SuperFemto_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, super_prim)
+
+static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct osmo_wqueue *queue;
+
+ queue = container_of(fd, struct osmo_wqueue, bfd);
+
+ if (what & BSC_FD_READ)
+ queue->read_cb(fd);
+
+ if (what & BSC_FD_EXCEPT)
+ queue->except_cb(fd);
+
+ if (what & BSC_FD_WRITE) {
+ struct iovec iov[5];
+ struct msgb *msg, *tmp;
+ int written, count = 0;
+
+ fd->when &= ~BSC_FD_WRITE;
+
+ llist_for_each_entry(msg, &queue->msg_queue, list) {
+ /* more writes than we have */
+ if (count >= ARRAY_SIZE(iov))
+ break;
+
+ iov[count].iov_base = msg->l1h;
+ iov[count].iov_len = msgb_l1len(msg);
+ count += 1;
+ }
+
+ /* TODO: check if all lengths are the same. */
+
+
+ /* Nothing scheduled? This should not happen. */
+ if (count == 0) {
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ written = writev(fd->fd, iov, count);
+ if (written < 0) {
+ /* nothing written?! */
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ /* now delete the written entries */
+ written = written / iov[0].iov_len;
+ count = 0;
+ llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) {
+ queue->current_length -= 1;
+
+ llist_del(&msg->list);
+ msgb_free(msg);
+
+ count += 1;
+ if (count >= written)
+ break;
+ }
+
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ }
+
+ return 0;
+}
+
+static int prim_size_for_queue(int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return sizeof(SuperFemto_Prim_t);
+ case MQ_L1_WRITE:
+#ifndef HW_SYSMOBTS_V1
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+#endif
+ return sizeof(GsmL1_Prim_t);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+}
+
+/* callback when there's something to read from the l1 msg_queue */
+static int read_dispatch_one(struct femtol1_hdl *fl1h, struct msgb *msg, int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return l1if_handle_sysprim(fl1h, msg);
+ case MQ_L1_WRITE:
+#ifndef HW_SYSMOBTS_V1
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+#endif
+ return l1if_handle_l1prim(queue, fl1h, msg);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+};
+
+static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int i, rc;
+
+ const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr);
+ uint32_t count;
+
+ struct iovec iov[3];
+ struct msgb *msg[ARRAY_SIZE(iov)];
+
+ for (i = 0; i < ARRAY_SIZE(iov); ++i) {
+ msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd");
+ msg[i]->l1h = msg[i]->data;
+
+ iov[i].iov_base = msg[i]->l1h;
+ iov[i].iov_len = msgb_tailroom(msg[i]);
+ }
+
+ rc = readv(ofd->fd, iov, ARRAY_SIZE(iov));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno));
+ /* N. B: we do not abort to let the cycle below cleanup allocated memory properly,
+ the return value is ignored by the caller anyway.
+ TODO: use libexplain's explain_readv() to provide detailed error description */
+ count = 0;
+ } else
+ count = rc / prim_size;
+
+ for (i = 0; i < count; ++i) {
+ msgb_put(msg[i], prim_size);
+ read_dispatch_one(ofd->data, msg[i], ofd->priv_nr);
+ }
+
+ for (i = count; i < ARRAY_SIZE(iov); ++i)
+ msgb_free(msg[i]);
+
+ return 1;
+}
+
+/* callback when we can write to one of the l1 msg_queue devices */
+static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msg->len) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msg->len);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int l1if_transport_open(int q, struct femtol1_hdl *hdl)
+{
+ int rc;
+
+ /* Step 1: Open all msg_queue file descriptors */
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_wqueue *wq = &hdl->write_q[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ rc = open(rd_devnames[q], O_RDONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "[%d] unable to open %s for reading: %s\n",
+ q, rd_devnames[q], strerror(errno));
+ return rc;
+ }
+ read_ofd->fd = rc;
+ read_ofd->priv_nr = q;
+ read_ofd->data = hdl;
+ read_ofd->cb = l1if_fd_cb;
+ read_ofd->when = BSC_FD_READ;
+ rc = osmo_fd_register(read_ofd);
+ if (rc < 0) {
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+ return rc;
+ }
+
+ rc = open(wr_devnames[q], O_WRONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "[%d] unable to open %s for writing: %s\n",
+ q, wr_devnames[q], strerror(errno));
+ goto out_read;
+ }
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = l1fd_write_cb;
+ write_ofd->cb = wqueue_vector_cb;
+ write_ofd->fd = rc;
+ write_ofd->priv_nr = q;
+ write_ofd->data = hdl;
+ write_ofd->when = BSC_FD_WRITE;
+ rc = osmo_fd_register(write_ofd);
+ if (rc < 0) {
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+ goto out_read;
+ }
+
+ return 0;
+
+out_read:
+ close(hdl->read_ofd[q].fd);
+ osmo_fd_unregister(&hdl->read_ofd[q]);
+
+ return rc;
+}
+
+int l1if_transport_close(int q, struct femtol1_hdl *hdl)
+{
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ osmo_fd_unregister(read_ofd);
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+
+ osmo_fd_unregister(write_ofd);
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c
new file mode 100644
index 00000000..221e8e8a
--- /dev/null
+++ b/src/osmo-bts-sysmo/main.c
@@ -0,0 +1,199 @@
+/* Main program for Sysmocom BTS */
+
+/* (C) 2011-2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+
+#define SYSMOBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock"
+
+#include "utils.h"
+#include "eeprom.h"
+#include "l1_if.h"
+#include "hw_misc.h"
+#include "oml_router.h"
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ struct stat st;
+ static struct osmo_fd accept_fd, read_fd;
+ int rc;
+
+ bts->variant = BTS_OSMO_SYSMO;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
+ if (rc < 0) {
+ fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
+ OML_ROUTER_PATH, rc);
+ exit(1);
+ }
+
+ if (stat(SYSMOBTS_RF_LOCK_PATH, &st) == 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
+ exit(23);
+ }
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_EGPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+void bts_update_status(enum bts_global_status which, int on)
+{
+ static uint64_t states = 0;
+ uint64_t old_states = states;
+ int led_rf_active_on;
+
+ if (on)
+ states |= (1ULL << which);
+ else
+ states &= ~(1ULL << which);
+
+ led_rf_active_on =
+ (states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
+ !(states & (1ULL << BTS_STATUS_RF_MUTE));
+
+ LOGP(DL1C, LOGL_INFO,
+ "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
+ which, on,
+ (long long)old_states, (long long)states,
+ led_rf_active_on);
+
+ sysmobts_led_set(LED_RF_ACTIVE, led_rf_active_on);
+}
+
+void bts_model_print_help()
+{
+ printf(
+ " -w --hw-version Print the targeted HW Version\n"
+ " -M --pcu-direct Force PCU to access message queue for "
+ "PDCH dchannel directly\n"
+ );
+};
+
+static void print_hwversion()
+{
+#ifdef HW_SYSMOBTS_V1
+ printf("sysmobts was compiled for hw version 1.\n");
+#else
+ printf("sysmobts was compiled for hw version 2.\n");
+#endif
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* specific to this hardware */
+ { "hw-version", 0, 0, 'w' },
+ { "pcu-direct", 0, 0, 'M' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "wM",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'M':
+ pcu_direct = 1;
+ break;
+ case 'w':
+ print_hwversion();
+ exit(0);
+ break;
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ pinst->u.sysmobts.clk_use_eeprom = 1;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-calib.c b/src/osmo-bts-sysmo/misc/sysmobts-calib.c
new file mode 100644
index 00000000..a111d1d5
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts-calib.c
@@ -0,0 +1,537 @@
+/* OCXO/TCXO based calibration utility */
+
+/*
+ * (C) 2012-2013 Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <math.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/core/utils.h>
+
+#include "sysmobts-layer1.h"
+
+enum actions {
+ ACTION_SCAN,
+ ACTION_CALIB,
+ ACTION_BCCH,
+ ACTION_BCCH_CCCH,
+};
+
+static const char *modes[] = {
+ [ACTION_SCAN] = "scan",
+ [ACTION_CALIB] = "calibrate",
+ [ACTION_BCCH] = "bcch",
+ [ACTION_BCCH_CCCH] = "bcch_ccch",
+};
+
+static const char *bands[] = {
+ [GsmL1_FreqBand_850] = "850",
+ [GsmL1_FreqBand_900] = "900",
+ [GsmL1_FreqBand_1800] = "1800",
+ [GsmL1_FreqBand_1900] = "1900",
+};
+
+struct channel_pair {
+ int min;
+ int max;
+};
+
+static const struct channel_pair arfcns[] = {
+ [GsmL1_FreqBand_850] = { .min = 128, .max = 251 },
+ [GsmL1_FreqBand_900] = { .min = 1, .max = 124 },
+ [GsmL1_FreqBand_1800] = { .min = 512, .max = 885 },
+ [GsmL1_FreqBand_1900] = { .min = 512, .max = 810 },
+
+};
+
+static const char *clk_source[] = {
+ [SuperFemto_ClkSrcId_Ocxo] = "ocxo",
+ [SuperFemto_ClkSrcId_Tcxo] = "tcxo",
+ [SuperFemto_ClkSrcId_External] = "external",
+ [SuperFemto_ClkSrcId_GpsPps] = "gps",
+ [SuperFemto_ClkSrcId_Trx] = "trx",
+ [SuperFemto_ClkSrcId_Rx] = "rx",
+ [SuperFemto_ClkSrcId_Edge] = "edge",
+ [SuperFemto_ClkSrcId_NetList] = "netlisten",
+};
+
+static const struct value_string sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+static int action = ACTION_SCAN;
+static int band = GsmL1_FreqBand_900;
+static int calib = SuperFemto_ClkSrcId_Ocxo;
+static int source = SuperFemto_ClkSrcId_NetList;
+static int dsp_flags = 0x0;
+static int cal_arfcn = 0;
+static int initial_cor = 0;
+static int steps = -1;
+
+static void print_usage(void)
+{
+ printf("Usage: sysmobts-calib ARGS\n");
+}
+
+static void print_help(void)
+{
+ printf(" -h --help this text\n");
+ printf(" -c --clock "
+ "ocxo|tcxo|external|gps|trx|rx|edge\n");
+ printf(" -s --calibration-source "
+ "ocxo|tcxo|external|gps|trx|rx|edge|netlisten\n");
+ printf(" -b --band 850|900|1800|1900\n");
+ printf(" -m --mode scan|calibrate|bcch|bcch_ccch\n");
+ printf(" -a --arfcn NR arfcn for calibration\n");
+ printf(" -d --dsp-flags NR dsp mask for debug log\n");
+ printf(" -t --threshold level\n");
+ printf(" -i --initial-clock-correction COR.\n");
+ printf(" -t --steps STEPS\n");
+}
+
+static int find_value(const char **array, int size, char *value)
+{
+ int i = 0;
+ for (i = 0; i < size; ++i) {
+ if (array[i] == NULL)
+ continue;
+ if (strcmp(value, array[i]) == 0)
+ return i;
+ }
+
+ printf("Failed to find: '%s'\n", value);
+ exit(-2);
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"calibration-source", 1, 0, 's'},
+ {"clock", 1, 0, 'c'},
+ {"mode", 1, 0, 'm'},
+ {"band", 1, 0, 'b'},
+ {"dsp-flags", 1, 0, 'd'},
+ {"arfcn", 1, 0, 'a'},
+ {"initial-clock-correction", 1, 0, 'i'},
+ {"steps", 1, 0, 't'},
+ {0, 0, 0, 0},
+ };
+
+ c = getopt_long(argc, argv, "hs:c:m:b:d:a:i:t:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 's':
+ source = find_value(clk_source,
+ ARRAY_SIZE(clk_source), optarg);
+ break;
+ case 'c':
+ calib = find_value(clk_source,
+ ARRAY_SIZE(clk_source), optarg);
+ break;
+ case 'm':
+ action = find_value(modes,
+ ARRAY_SIZE(modes), optarg);
+ break;
+ case 'b':
+ band = find_value(bands,
+ ARRAY_SIZE(bands), optarg);
+ break;
+ case 'd':
+ dsp_flags = strtol(optarg, NULL, 16);
+ break;
+ case 'a':
+ cal_arfcn = atoi(optarg);
+ break;
+ case 'i':
+ initial_cor = atoi(optarg);
+ break;
+ case 't':
+ steps = atoi(optarg);
+ break;
+ default:
+ printf("Unhandled option, terminating.\n");
+ exit(-1);
+ }
+ }
+
+ if (source == calib) {
+ printf("Clock source and reference clock may not be the same.\n");
+ exit(-3);
+ }
+
+ if (calib == SuperFemto_ClkSrcId_NetList) {
+ printf("Clock may not be network listen.\n");
+ exit(-4);
+ }
+
+ if (action == ACTION_CALIB && source == SuperFemto_ClkSrcId_NetList) {
+ if (cal_arfcn == 0) {
+ printf("Please specify the reference ARFCN.\n");
+ exit(-5);
+ }
+
+ if (cal_arfcn < arfcns[band].min || cal_arfcn > arfcns[band].max) {
+ printf("ARFCN(%d) is not in the given band.\n", cal_arfcn);
+ exit(-6);
+ }
+ }
+}
+
+#define CHECK_RC(rc) \
+ if (rc != 0) \
+ return EXIT_FAILURE;
+
+#define CHECK_RC_MSG(rc, msg) \
+ if (rc != 0) { \
+ printf("%s: %d\n", msg, rc); \
+ return EXIT_FAILURE; \
+ }
+#define CHECK_COND_MSG(cond, rc, msg) \
+ if (cond) { \
+ printf("%s: %d\n", msg, rc); \
+ return EXIT_FAILURE; \
+ }
+
+struct scan_result
+{
+ uint16_t arfcn;
+ float rssi;
+};
+
+static int scan_cmp(const void *arg1, const void *arg2)
+{
+ struct scan_result *elem1 = (struct scan_result *) arg1;
+ struct scan_result *elem2 = (struct scan_result * )arg2;
+
+ float diff = elem1->rssi - elem2->rssi;
+ if (diff > 0.0)
+ return 1;
+ else if (diff < 0.0)
+ return -1;
+ else
+ return 0;
+}
+
+static int scan_band()
+{
+ int arfcn, rc, i;
+
+ /* Scan results.. at most 400 items */
+ struct scan_result results[400];
+ memset(&results, 0, sizeof(results));
+ int num_scan_results = 0;
+
+ printf("Going to scan bands.\n");
+
+ for (arfcn = arfcns[band].min; arfcn <= arfcns[band].max; ++arfcn) {
+ float mean_rssi;
+
+ printf(".");
+ fflush(stdout);
+ rc = power_scan(band, arfcn, 10, &mean_rssi);
+ CHECK_RC_MSG(rc, "Power Measurement failed");
+
+ results[num_scan_results].arfcn = arfcn;
+ results[num_scan_results].rssi = mean_rssi;
+ num_scan_results++;
+ }
+
+ qsort(results, num_scan_results, sizeof(struct scan_result), scan_cmp);
+ printf("\nSorted scan results (weakest first):\n");
+ for (i = 0; i < num_scan_results; ++i)
+ printf("ARFCN %3d: %.4f\n", results[i].arfcn, results[i].rssi);
+
+ return 0;
+}
+
+static int calib_get_clock_error(void)
+{
+ int rc, clkErr, clkErrRes;
+
+ printf("Going to determine the clock offset.\n");
+
+ rc = rf_clock_info(&clkErr, &clkErrRes);
+ CHECK_RC_MSG(rc, "Clock info failed.\n");
+
+ if (clkErr == 0 && clkErrRes == 0) {
+ printf("Failed to get the clock info. Are both clocks present?\n");
+ return -1;
+ }
+
+ /*
+ * Empiric gps error determination. With revE and firmware v3.3
+ * the clock error for TCXO to GPS appears to have a different
+ * sign. The device in question doesn't have a networklisten mode
+ * so it is impossible to verify that this only applies to GPS.
+ */
+ if (source == SuperFemto_ClkSrcId_GpsPps)
+ clkErr *= -1;
+
+
+ /* this is an absolute clock error */
+ printf("The calibration value is: %d\n", clkErr);
+ return 0;
+}
+
+static int calib_clock_after_sync(void)
+{
+ int rc, clkErr, clkErrRes, iteration, cor;
+
+ iteration = 0;
+ cor = initial_cor;
+
+ printf("Trying to calibrate now and reducing clock error.\n");
+
+ for (iteration = 0; iteration < steps || steps <= 0; ++iteration) {
+ if (steps > 0)
+ printf("Iteration %d/%d with correction: %d\n", iteration, steps, cor);
+ else
+ printf("Iteration %d with correction: %d\n", iteration, cor);
+
+ rc = rf_clock_info(&clkErr, &clkErrRes);
+ CHECK_RC_MSG(rc, "Clock info failed.\n");
+
+ /*
+ * TODO: use the clock error resolution here, implement it as a
+ * a PID controller..
+ */
+
+ /* Picocell class requires 0.1ppm.. but that is 'too easy' */
+ if (fabs(clkErr / 1000.0f) <= 0.05f) {
+ printf("The calibration value is: %d\n", cor);
+ return 1;
+ }
+
+ cor -= clkErr / 2;
+ rc = set_clock_cor(cor, calib, source);
+ CHECK_RC_MSG(rc, "Clock correction failed.\n");
+ }
+
+ return -1;
+}
+
+static int find_initial_clock(HANDLE layer1, int *clock)
+{
+ int i;
+
+ printf("Trying to find an initial clock value.\n");
+
+ for (i = 0; i < 1000; ++i) {
+ int rc;
+ int cor = i * 150;
+ rc = wait_for_sync(layer1, cor, calib, source);
+ if (rc == 1) {
+ printf("Found initial clock offset: %d\n", cor);
+ *clock = cor;
+ break;
+ } else {
+ CHECK_RC_MSG(rc, "Failed to set new clock value.\n");
+ }
+
+ cor = i * -150;
+ rc = wait_for_sync(layer1, cor, calib, source);
+ if (rc == 1) {
+ printf("Found initial clock offset: %d\n", cor);
+ *clock = cor;
+ break;
+ } else {
+ CHECK_RC_MSG(rc, "Failed to set new clock value.\n");
+ }
+ }
+
+ return 0;
+}
+
+static int calib_clock_netlisten(void)
+{
+ int rc, cor = initial_cor;
+ float mean_rssi;
+ HANDLE layer1;
+
+ rc = power_scan(band, cal_arfcn, 10, &mean_rssi);
+ CHECK_RC_MSG(rc, "ARFCN measurement scan failed");
+ if (mean_rssi < -118.0f)
+ printf("ARFCN has weak signal for calibration: %f\n", mean_rssi);
+
+ /* initial lock */
+ rc = follow_sch(band, cal_arfcn, calib, source, &layer1);
+ if (rc == -23)
+ rc = find_initial_clock(layer1, &cor);
+ CHECK_RC_MSG(rc, "Following SCH failed");
+
+ /* now try to calibrate it */
+ rc = set_clock_cor(cor, calib, source);
+ CHECK_RC_MSG(rc, "Clock setup failed.");
+
+ calib_clock_after_sync();
+
+ rc = mph_close(layer1);
+ CHECK_RC_MSG(rc, "MPH-Close");
+
+ return EXIT_SUCCESS;
+}
+
+static int calib_clock(void)
+{
+ int rc;
+
+ /* now try to calibrate it */
+ rc = set_clock_cor(initial_cor, calib, source);
+ CHECK_RC_MSG(rc, "Clock setup failed.");
+
+ calib_get_clock_error();
+
+ return EXIT_SUCCESS;
+}
+
+static int bcch_follow(void)
+{
+ int rc, cor = initial_cor;
+ float mean_rssi;
+ HANDLE layer1;
+
+ rc = power_scan(band, cal_arfcn, 10, &mean_rssi);
+ CHECK_RC_MSG(rc, "ARFCN measurement scan failed");
+ if (mean_rssi < -118.0f)
+ printf("ARFCN has weak signal for calibration: %f\n", mean_rssi);
+
+ /* initial lock */
+ rc = follow_sch(band, cal_arfcn, calib, source, &layer1);
+ if (rc == -23)
+ rc = find_initial_clock(layer1, &cor);
+ CHECK_RC_MSG(rc, "Following SCH failed");
+
+ /* identify the BSIC and set it as TSC */
+ rc = find_bsic();
+ CHECK_COND_MSG(rc < 0, rc, "Identifying the BSIC failed");
+ rc = set_tsc_from_bsic(layer1, rc);
+ CHECK_RC_MSG(rc, "Setting the TSC failed");
+
+
+ /* follow the bcch */
+ rc = follow_bcch(layer1);
+ CHECK_RC_MSG(rc, "Follow BCCH");
+
+ /* follow the pch */
+ if (action == ACTION_BCCH_CCCH) {
+ rc = follow_pch(layer1);
+ CHECK_RC_MSG(rc, "Follow BCCH/CCCH");
+ }
+
+ /* now wait for the PhDataInd */
+ for (;;) {
+ uint32_t fn;
+ uint8_t block;
+ uint8_t data[23];
+ size_t size;
+ struct gsm_time gsmtime;
+ GsmL1_Sapi_t sapi;
+
+ rc = wait_for_data(data, &size, &fn, &block, &sapi);
+ if (rc == 1)
+ continue;
+ CHECK_RC_MSG(rc, "No Data Indication");
+
+ gsm_fn2gsmtime(&gsmtime, fn);
+ printf("%02u/%02u/%02u %6s %s\n",
+ gsmtime.t1, gsmtime.t2, gsmtime.t3,
+ get_value_string(sapi_names, sapi),
+ osmo_hexdump(data, size));
+ }
+
+ rc = mph_close(layer1);
+ CHECK_RC_MSG(rc, "MPH-Close");
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ handle_options(argc, argv);
+ printf("Initializing the Layer1\n");
+ rc = initialize_layer1(dsp_flags);
+ CHECK_RC(rc);
+
+ printf("Fetching system info.\n");
+ rc = print_system_info();
+ CHECK_RC(rc);
+
+ printf("Opening RF frontend with clock(%d) and correction(%d)\n",
+ calib, initial_cor);
+ rc = activate_rf_frontend(calib, initial_cor);
+ CHECK_RC(rc);
+
+ if (action == ACTION_SCAN)
+ return scan_band();
+ else if (action == ACTION_BCCH || action == ACTION_BCCH_CCCH)
+ return bcch_follow();
+ else {
+ if (source == SuperFemto_ClkSrcId_NetList)
+ return calib_clock_netlisten();
+ return calib_clock();
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
new file mode 100644
index 00000000..4b34f50e
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
@@ -0,0 +1,800 @@
+/* Layer1 handling for the DSP/FPGA */
+/*
+ * (C) 2012-2013 Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+
+#include "sysmobts-layer1.h"
+
+#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof((ar)[0]))
+
+#define BTS_DSP2ARM "/dev/msgq/superfemto_dsp2arm"
+#define BTS_ARM2DSP "/dev/msgq/superfemto_arm2dsp"
+#define L1_SIG_ARM2DSP "/dev/msgq/gsml1_sig_arm2dsp"
+#define L1_SIG_DSP2ARM "/dev/msgq/gsml1_sig_dsp2arm"
+
+int set_clock_cor(int clock_cor, int calib, int source);
+static int wait_read_ignore(int seconds);
+
+static int sys_dsp2arm = -1,
+ sys_arm2dsp = -1,
+ sig_dsp2arm = -1,
+ sig_arm2dsp = -1;
+
+static int sync_indicated = 0;
+static int time_indicated = 0;
+
+static int open_devices()
+{
+ sys_dsp2arm = open(BTS_DSP2ARM, O_RDONLY);
+ if (sys_dsp2arm == -1) {
+ perror("Failed to open dsp2arm system queue");
+ return -1;
+ }
+
+ sys_arm2dsp = open(BTS_ARM2DSP, O_WRONLY);
+ if (sys_arm2dsp == -1) {
+ perror("Failed to open arm2dsp system queue");
+ return -2;
+ }
+
+ sig_dsp2arm = open(L1_SIG_DSP2ARM, O_RDONLY);
+ if (sig_dsp2arm == -1) {
+ perror("Failed to open dsp2arm sig queue");
+ return -3;
+ }
+
+ sig_arm2dsp = open(L1_SIG_ARM2DSP, O_WRONLY);
+ if (sig_arm2dsp == -1) {
+ perror("Failed to open arm2dsp sig queue");
+ return -4;
+ }
+
+ return 0;
+}
+
+/**
+ * Send a primitive to the system queue
+ */
+static int send_primitive(int primitive, SuperFemto_Prim_t *prim)
+{
+ prim->id = primitive;
+ return write(sys_arm2dsp, prim, sizeof(*prim)) != sizeof(*prim);
+}
+
+/**
+ * Wait for a confirmation
+ */
+static int wait_primitive(int wait_for, SuperFemto_Prim_t *prim)
+{
+ memset(prim, 0, sizeof(*prim));
+ int rc = read(sys_dsp2arm, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Short read in %s: %d\n", __func__, rc);
+ return -1;
+ }
+
+ if (prim->id != wait_for) {
+ printf("Got primitive %d but waited for %d\n",
+ prim->id, wait_for);
+ return -2;
+ }
+
+ return 0;
+}
+
+/* The Cnf for the Req, assume it is a +1 */
+static int answer_for(int primitive)
+{
+ return primitive + 1;
+}
+
+static int send_recv_primitive(int p, SuperFemto_Prim_t *prim)
+{
+ int rc;
+ rc = send_primitive(p, prim);
+ if (rc != 0)
+ return -1;
+
+ rc = wait_primitive(answer_for(p), prim);
+ if (rc != 0)
+ return -2;
+ return 0;
+}
+
+static int answer_for_sig(int prim)
+{
+ static const GsmL1_PrimId_t cnf[] = {
+ [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf,
+ [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf,
+ [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf,
+ [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf,
+ [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf,
+ [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf,
+ };
+
+ if (prim < 0 || prim >= ARRAY_SIZE(cnf)) {
+ printf("Unknown primitive: %d\n", prim);
+ exit(-3);
+ }
+
+ return cnf[prim];
+}
+
+static int is_indication(int prim)
+{
+ return
+ prim == GsmL1_PrimId_MphTimeInd ||
+ prim == GsmL1_PrimId_MphSyncInd ||
+ prim == GsmL1_PrimId_PhConnectInd ||
+ prim == GsmL1_PrimId_PhReadyToSendInd ||
+ prim == GsmL1_PrimId_PhDataInd ||
+ prim == GsmL1_PrimId_PhRaInd;
+}
+
+
+static int send_recv_sig_prim(int p, GsmL1_Prim_t *prim)
+{
+ int rc;
+ prim->id = p;
+ rc = write(sig_arm2dsp, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Failed to write: %d\n", rc);
+ return -1;
+ }
+
+ do {
+ rc = read(sig_dsp2arm, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Failed to read: %d\n", rc);
+ return -2;
+ }
+ } while (is_indication(prim->id));
+
+ if (prim->id != answer_for_sig(p)) {
+ printf("Wrong L1 result got %d wanted %d for prim: %d\n",
+ prim->id, answer_for_sig(p), p);
+ return -3;
+ }
+
+ return 0;
+}
+
+static int wait_for_indication(int p, GsmL1_Prim_t *prim)
+{
+ int rc;
+ memset(prim, 0, sizeof(*prim));
+
+ struct timespec start_time, now_time;
+ clock_gettime(CLOCK_MONOTONIC, &start_time);
+
+ /*
+ * TODO: select.... with timeout. The below will work 99% as we will
+ * get time indications very soonish after the connect
+ */
+ for (;;) {
+ clock_gettime(CLOCK_MONOTONIC, &now_time);
+ if (now_time.tv_sec - start_time.tv_sec > 10) {
+ printf("Timeout waiting for indication.\n");
+ return -4;
+ }
+
+ rc = read(sig_dsp2arm, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Failed to read.\n");
+ return -1;
+ }
+
+ if (!is_indication(prim->id)) {
+ printf("No indication: %d\n", prim->id);
+ return -2;
+ }
+
+ if (p != prim->id && prim->id == GsmL1_PrimId_MphSyncInd) {
+ printf("Got sync.\n");
+ sync_indicated = 1;
+ continue;
+ }
+ if (p != prim->id && prim->id == GsmL1_PrimId_MphTimeInd) {
+ time_indicated = 1;
+ continue;
+ }
+
+ if (p != prim->id) {
+ printf("Wrong indication got %d wanted %d\n",
+ prim->id, p);
+ return -3;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static int set_trace_flags(uint32_t dsp)
+{
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.setTraceFlagsReq.u32Tf = dsp;
+ return send_primitive(SuperFemto_PrimId_SetTraceFlagsReq, &prim);
+}
+
+static int reset_and_wait()
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ rc = send_recv_primitive(SuperFemto_PrimId_Layer1ResetReq, &prim);
+ if (rc != 0)
+ return -1;
+ if (prim.u.layer1ResetCnf.status != GsmL1_Status_Success)
+ return -2;
+ return 0;
+}
+
+/**
+ * Open the message queues and (re-)initialize the DSP and FPGA
+ */
+int initialize_layer1(uint32_t dsp_flags)
+{
+ if (open_devices() != 0) {
+ printf("Failed to open devices.\n");
+ return -1;
+ }
+
+ if (set_trace_flags(dsp_flags) != 0) {
+ printf("Failed to set dsp flags.\n");
+ return -2;
+ }
+ if (reset_and_wait() != 0) {
+ printf("Failed to reset the firmware.\n");
+ return -3;
+ }
+ return 0;
+}
+
+/**
+ * Print systems infos
+ */
+int print_system_info()
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ rc = send_recv_primitive(SuperFemto_PrimId_SystemInfoReq, &prim);
+ if (rc != 0) {
+ printf("Failed to send SystemInfoRequest.\n");
+ return -1;
+ }
+
+ if (prim.u.systemInfoCnf.status != GsmL1_Status_Success) {
+ printf("Failed to request SystemInfoRequest.\n");
+ return -2;
+ }
+
+#define INFO_DSP(x) x.u.systemInfoCnf.dspVersion
+#define INFO_FPGA(x) x.u.systemInfoCnf.fpgaVersion
+#ifdef FEMTOBTS_NO_BOARD_VERSION
+#define BOARD_REV(x) -1
+#define BOARD_OPT(x) -1
+#define COMPILED_MAJOR (FEMTOBTS_API_VERSION >> 16)
+#define COMPILED_MINOR ((FEMTOBTS_API_VERSION >> 8) & 0xff)
+#define COMPILED_BUILD (FEMTOBTS_API_VERSION & 0xff)
+#else
+#define BOARD_REV(x) x.u.systemInfoCnf.boardVersion.rev
+#define BOARD_OPT(x) x.u.systemInfoCnf.boardVersion.option
+#define COMPILED_MAJOR (SUPERFEMTO_API_VERSION >> 16)
+#define COMPILED_MINOR ((SUPERFEMTO_API_VERSION >> 8) & 0xff)
+#define COMPILED_BUILD (SUPERFEMTO_API_VERSION & 0xff)
+#endif
+
+ printf("Compiled against: v%u.%u.%u\n",
+ COMPILED_MAJOR, COMPILED_MINOR, COMPILED_BUILD);
+ printf("Running DSP v%d.%d.%d FPGA v%d.%d.%d Rev: %d Option: %d\n",
+ INFO_DSP(prim).major, INFO_DSP(prim).minor, INFO_DSP(prim).build,
+ INFO_FPGA(prim).major, INFO_FPGA(prim).minor, INFO_FPGA(prim).build,
+ BOARD_REV(prim), BOARD_OPT(prim));
+
+ if (COMPILED_MAJOR != INFO_DSP(prim).major || COMPILED_MINOR != INFO_DSP(prim).minor) {
+ printf("WARNING! WARNING! WARNING! WARNING! WARNING\n");
+ printf("You might run this against an incompatible firmware.\n");
+ printf("Continuing anyway but the result might be broken\n");
+ }
+#undef INFO_DSP
+#undef INFO_FPGA
+#undef BOARD_REV
+#undef BOARD_OPT
+#undef COMPILED_MAJOR
+#undef COMPILED_MINOR
+#undef COMPILED_BUILD
+ return 0;
+}
+
+int activate_rf_frontend(int clock_source, int initial_cor)
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.activateRfReq.timing.u8TimSrc = 1;
+ prim.u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ prim.u.activateRfReq.msgq.u8UsePdtchMsgq = 0;
+
+ prim.u.activateRfReq.rfTrx.iClkCor = initial_cor;
+ prim.u.activateRfReq.rfTrx.clkSrc = clock_source;
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+ prim.u.activateRfReq.rfRx.iClkCor = initial_cor;
+ prim.u.activateRfReq.rfRx.clkSrc = clock_source;
+#endif
+
+ rc = send_recv_primitive(SuperFemto_PrimId_ActivateRfReq, &prim);
+ return rc;
+}
+
+static int mph_init(int band, int arfcn, HANDLE *layer1)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.mphInitReq.deviceParam.devType = GsmL1_DevType_Rxd;
+ prim.u.mphInitReq.deviceParam.freqBand = band;
+ prim.u.mphInitReq.deviceParam.u16Arfcn = arfcn;
+ prim.u.mphInitReq.deviceParam.u16BcchArfcn = arfcn;
+ prim.u.mphInitReq.deviceParam.fRxPowerLevel = -75.f;
+ prim.u.mphInitReq.deviceParam.u8AutoTA = 1;
+
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphInitReq, &prim);
+ if (rc != 0) {
+ printf("Failed to initialize the physical channel.\n");
+ return -1;
+ }
+
+ if (prim.u.mphInitCnf.status != GsmL1_Status_Success) {
+ printf("MPH Init failed.\n");
+ return -2;
+ }
+
+#if 0
+ if (prim.u.mphInitCnf.freqBand != band) {
+ printf("Layer1 ignored the band: %d\n",
+ prim.u.mphInitCnf.freqBand);
+ return -3;
+ }
+#endif
+
+ *layer1 = prim.u.mphInitCnf.hLayer1;
+ return 0;
+}
+
+int mph_close(HANDLE layer1)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.mphCloseReq.hLayer1 = layer1;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphCloseReq, &prim);
+ if (rc != 0) {
+ printf("Failed to close the MPH\n");
+ return -6;
+ }
+ if (prim.u.mphCloseCnf.status != GsmL1_Status_Success) {
+ printf("MPH Close failed.\n");
+ return -7;
+ }
+
+ return 0;
+}
+
+int follow_sch(int band, int arfcn, int clock, int ref, HANDLE *layer1)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+
+ time_indicated = 0;
+ sync_indicated = 0;
+
+ rc = mph_init(band, arfcn, layer1);
+ if (rc != 0)
+ return rc;
+
+ /* 1.) Connect */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphConnectReq.hLayer1 = *layer1;
+ prim.u.mphConnectReq.u8Tn = 0;
+ prim.u.mphConnectReq.logChComb = GsmL1_LogChComb_IV;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphConnectReq, &prim);
+ if (rc != 0) {
+ printf("Failed to connect.\n");
+ return -1;
+ }
+ if (prim.u.mphConnectCnf.status != GsmL1_Status_Success) {
+ printf("Connect failed.\n");
+ return -2;
+ }
+ if (prim.u.mphConnectCnf.u8Tn != 0) {
+ printf("Wrong timeslot.\n");
+ return -3;
+ }
+
+ /* 2.) Activate */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphActivateReq.hLayer1 = *layer1;
+ prim.u.mphActivateReq.u8Tn = 0;
+ prim.u.mphActivateReq.sapi = GsmL1_Sapi_Sch;
+ prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim);
+ if (rc != 0) {
+ printf("Activation failed.\n");
+ return -4;
+ }
+ if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) {
+ printf("Activation not successful.\n");
+ return -5;
+ }
+
+ /* 3.) Wait for indication... TODO: check... */
+ printf("Waiting for connect indication.\n");
+ rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim);
+ if (rc != 0) {
+ printf("Didn't get a connect indication.\n");
+ return rc;
+ }
+
+ /* 4.) Indication Syndication TODO: check... */
+ if (!sync_indicated) {
+ printf("Waiting for sync indication.\n");
+ rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim);
+ if (rc < 0) {
+ printf("Didn't get a sync indication.\n");
+ return -23;
+ } else if (rc == 0) {
+ if (!prim.u.mphSyncInd.u8Synced) {
+ printf("Failed to get sync.\n");
+ return -23;
+ } else {
+ printf("Synced.\n");
+ }
+ }
+ } else {
+ printf("Already synced.\n");
+ }
+
+ return 0;
+}
+
+static int follow_sapi(HANDLE layer1, const GsmL1_Sapi_t sapi)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+
+ /* 1.) Activate BCCH or such... */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphActivateReq.hLayer1 = layer1;
+ prim.u.mphActivateReq.u8Tn = 0;
+ prim.u.mphActivateReq.sapi = sapi;
+ prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink;
+
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim);
+ if (rc != 0) {
+ printf("Activation failed.\n");
+ return -4;
+ }
+ if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) {
+ printf("Activation not successful.\n");
+ return -5;
+ }
+
+ /* 2.) Wait for indication... */
+ printf("Waiting for connect indication.\n");
+ rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim);
+ if (rc != 0) {
+ printf("Didn't get a connect indication.\n");
+ return rc;
+ }
+
+ if (prim.u.phConnectInd.sapi != sapi) {
+ printf("Got a connect indication for the wrong type: %d\n",
+ prim.u.phConnectInd.sapi);
+ return -6;
+ }
+
+ /* 3.) Wait for PhDataInd... */
+ printf("Waiting for data.\n");
+ rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim);
+ if (rc != 0) {
+ printf("Didn't get data.\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+int follow_bcch(HANDLE layer1)
+{
+ return follow_sapi(layer1, GsmL1_Sapi_Bcch);
+}
+
+int follow_pch(HANDLE layer1)
+{
+ return follow_sapi(layer1, GsmL1_Sapi_Pch);
+}
+
+int find_bsic(void)
+{
+ int rc, i;
+ GsmL1_Prim_t prim;
+
+ printf("Waiting for SCH data.\n");
+ for (i = 0; i < 10; ++i) {
+ uint8_t bsic;
+ rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim);
+ if (rc < 0) {
+ printf("Didn't get SCH data.\n");
+ return rc;
+ }
+ if (prim.u.phDataInd.sapi != GsmL1_Sapi_Sch)
+ continue;
+
+ bsic = (prim.u.phDataInd.msgUnitParam.u8Buffer[0] >> 2) & 0xFF;
+ return bsic;
+ }
+
+ printf("Giving up finding the SCH\n");
+ return -1;
+}
+
+int set_tsc_from_bsic(HANDLE layer1, int bsic)
+{
+ int rc;
+ int tsc = bsic & 0x7;
+ GsmL1_Prim_t prim;
+
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphConfigReq.hLayer3 = 0x23;
+ prim.u.mphConfigReq.hLayer1 = layer1;
+ prim.u.mphConfigReq.cfgParamId = GsmL1_ConfigParamId_SetNbTsc;
+ prim.u.mphConfigReq.cfgParams.setNbTsc.u8NbTsc = tsc;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphConfigReq, &prim);
+ if (rc != 0) {
+ printf("Failed to send configure.\n");
+ }
+
+ if (prim.u.mphConfigCnf.status != GsmL1_Status_Success) {
+ printf("Failed to set the config cnf.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int set_clock_cor(int clock_cor, int calib, int source)
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.rfClockSetupReq.rfTrx.iClkCor = clock_cor;
+ prim.u.rfClockSetupReq.rfTrx.clkSrc = calib;
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+ prim.u.rfClockSetupReq.rfRx.iClkCor = clock_cor;
+ prim.u.rfClockSetupReq.rfRx.clkSrc = calib;
+#endif
+ prim.u.rfClockSetupReq.rfTrxClkCal.clkSrc = source;
+
+ rc = send_recv_primitive(SuperFemto_PrimId_RfClockSetupReq, &prim);
+ if (rc != 0) {
+ printf("Failed to set the clock setup.\n");
+ return -1;
+ }
+ if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) {
+ printf("Clock setup was not successfull.\n");
+ return -2;
+ }
+
+ return 0;
+}
+
+int rf_clock_info(int *clkErr, int *clkErrRes)
+{
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ int rc;
+
+ /* reset the counter */
+ prim.u.rfClockInfoReq.u8RstClkCal = 1;
+ rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim);
+ if (rc != 0) {
+ printf("Failed to reset the clock info.\n");
+ return -1;
+ }
+
+ /* wait for a value */
+ wait_read_ignore(15);
+
+ /* ask for the current counter/error */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.rfClockInfoReq.u8RstClkCal = 0;
+ rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim);
+ if (rc != 0) {
+ printf("Failed to get the clock info.\n");
+ return -2;
+ }
+
+ printf("Error: %d Res: %d\n",
+ prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes);
+ *clkErr = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr;
+ *clkErrRes = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes;
+ return 0;
+}
+
+int power_scan(int band, int arfcn, int duration, float *mean_rssi)
+{
+ int rc;
+ HANDLE layer1;
+ GsmL1_Prim_t prim;
+
+ /* init */
+ rc = mph_init(band, arfcn, &layer1);
+ if (rc != 0)
+ return rc;
+
+ /* mph measure request */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphMeasureReq.hLayer1 = layer1;
+ prim.u.mphMeasureReq.u32Duration = duration;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphMeasureReq, &prim);
+ if (rc != 0) {
+ printf("Failed to send measurement request.\n");
+ return -4;
+ }
+
+ if (prim.u.mphMeasureCnf.status != GsmL1_Status_Success) {
+ printf("MphMeasureReq was not confirmed.\n");
+ return -5;
+ }
+
+ *mean_rssi = prim.u.mphMeasureCnf.fMeanRssi;
+
+ /* close */
+ rc = mph_close(layer1);
+ return rc;
+}
+
+/**
+ * Wait for indication...
+ */
+int wait_for_sync(HANDLE layer1, int cor, int calib, int source)
+{
+ GsmL1_Prim_t prim;
+ int rc;
+
+ rc = set_clock_cor(cor, calib, source);
+ if (rc != 0) {
+ printf("Failed to set the clock correction.\n");
+ return -1;
+ }
+
+ sync_indicated = 0;
+ rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim);
+ if (rc < 0 && rc != -4) {
+ return rc;
+ } else if (rc == 0) {
+ if (!prim.u.mphSyncInd.u8Synced) {
+ printf("Failed to get sync.\n");
+ return 0;
+ }
+ printf("Synced.\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sap)
+{
+ GsmL1_Prim_t prim;
+ int rc;
+
+ rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim);
+ if (rc < 0)
+ return rc;
+ if (prim.u.phDataInd.sapi == GsmL1_Sapi_Sch)
+ return 1;
+
+ *size = prim.u.phDataInd.msgUnitParam.u8Size;
+ *fn = prim.u.phDataInd.u32Fn;
+ *block = prim.u.phDataInd.u8BlockNbr;
+ *sap = prim.u.phDataInd.sapi;
+ memcpy(data, prim.u.phDataInd.msgUnitParam.u8Buffer, *size);
+ return 0;
+}
+
+/**
+ * Make sure the pipe is not running full.
+ *
+ */
+static int wait_read_ignore(int seconds)
+{
+ int max, rc;
+ fd_set fds;
+ struct timeval timeout;
+
+ max = sys_dsp2arm > sig_dsp2arm ? sys_dsp2arm : sig_dsp2arm;
+
+ timeout.tv_sec = seconds;
+ timeout.tv_usec = 0;
+
+ while (1) {
+ FD_ZERO(&fds);
+ FD_SET(sys_dsp2arm, &fds);
+ FD_SET(sig_dsp2arm, &fds);
+
+
+ rc = select(max + 1, &fds, NULL, NULL, &timeout);
+ if (rc == -1) {
+ printf("Failed to select.\n");
+ return -1;
+ } else if (rc) {
+ if (FD_ISSET(sys_dsp2arm, &fds)) {
+ SuperFemto_Prim_t prim;
+ rc = read(sys_dsp2arm, &prim, sizeof(prim));
+ if (rc != sizeof(prim)) {
+ perror("Failed to read system primitive");
+ return -2;
+ }
+ }
+ if (FD_ISSET(sig_dsp2arm, &fds)) {
+ GsmL1_Prim_t prim;
+ rc = read(sig_dsp2arm, &prim, sizeof(prim));
+ if (rc != sizeof(prim)) {
+ perror("Failed to read signal primitiven");
+ return -3;
+ }
+ }
+ } else if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0) {
+ break;
+ }
+
+#ifndef __linux__
+#error "Non portable code"
+#endif
+ }
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.h b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h
new file mode 100644
index 00000000..e7d59c94
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h
@@ -0,0 +1,45 @@
+#ifndef SYSMOBTS_LAYER_H
+#define SYSMOBTS_LAYER_H
+
+#include <sysmocom/femtobts/superfemto.h>
+
+#ifdef FEMTOBTS_API_VERSION
+#define SuperFemto_PrimId_t FemtoBts_PrimId_t
+#define SuperFemto_Prim_t FemtoBts_Prim_t
+#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq
+#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf
+#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t
+#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd
+#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq
+#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf
+#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq
+#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf
+#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq
+#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq
+#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf
+#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq
+#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf
+#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq
+#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf
+#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM
+#define HW_SYSMOBTS_V1 1
+#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z)
+#endif
+
+extern int initialize_layer1(uint32_t dsp_flags);
+extern int print_system_info();
+extern int activate_rf_frontend(int clock_source, int clock_cor);
+extern int power_scan(int band, int arfcn, int duration, float *mean_rssi);
+extern int follow_sch(int band, int arfcn, int calib, int reference, HANDLE *layer1);
+extern int follow_bch(HANDLE layer1);
+extern int find_bsic(void);
+extern int set_tsc_from_bsic(HANDLE layer1, int bsic);
+extern int set_clock_cor(int clock_corr, int calib, int source);
+extern int rf_clock_info(int *clkErr, int *clkErrRes);
+extern int mph_close(HANDLE layer1);
+extern int wait_for_sync(HANDLE layer1, int cor, int calib, int source);
+extern int follow_bcch(HANDLE layer1);
+extern int follow_pch(HANDLE layer1);
+extern int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sapi);
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
new file mode 100644
index 00000000..b7a27fb7
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
@@ -0,0 +1,44 @@
+#ifndef _SYSMOBTS_EEPROM_H
+#define _SYSMOBTS_EEPROM_H
+
+#include <stdint.h>
+
+struct sysmobts_net_cfg {
+ uint8_t mode; /* 0 */
+ uint32_t ip; /* 1 - 4 */
+ uint32_t mask; /* 5 - 8 */
+ uint32_t gw; /* 9 - 12 */
+ uint32_t dns; /* 13 - 16 */
+} __attribute__((packed));
+
+struct sysmobts_eeprom { /* offset */
+ uint8_t eth_mac[6]; /* 0-5 */
+ uint8_t _pad0[10]; /* 6-15 */
+ uint16_t unused1; /* 16-17 */
+ uint8_t temp1_max; /* 18 */
+ uint8_t temp2_max; /* 19 */
+ uint32_t serial_nr; /* 20-23 */
+ uint32_t operational_hours; /* 24-27 */
+ uint32_t boot_count; /* 28-31 */
+ uint16_t model_nr; /* 32-33 */
+ uint16_t model_flags; /* 34-35 */
+ uint8_t trx_nr; /* 36 */
+ uint8_t boot_state[48]; /* 37-84 */
+ uint8_t _pad1[18]; /* 85-102 */
+ struct sysmobts_net_cfg net_cfg;/* 103-119 */
+ uint8_t crc; /* 120 */
+ uint8_t gpg_key[128]; /* 121-249 */
+} __attribute__((packed));
+
+enum sysmobts_model_number {
+ MODEL_SYSMOBTS_1002 = 1002,
+ MODEL_SYSMOBTS_1020 = 1020,
+ MODEL_SYSMOBTS_2050 = 2050,
+};
+
+enum sysmobts_net_mode {
+ NET_MODE_DHCP,
+ NET_MODE_STATIC,
+};
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
new file mode 100644
index 00000000..a0080738
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
@@ -0,0 +1,336 @@
+/* Main program for SysmoBTS management daemon */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+
+#include "misc/sysmobts_misc.h"
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_par.h"
+
+static int bts_type;
+static int trx_number;
+
+static int no_eeprom_write = 0;
+static int daemonize = 0;
+void *tall_mgr_ctx;
+
+/* every 6 hours means 365*4 = 1460 EEprom writes per year (max) */
+#define TEMP_TIMER_SECS (6 * 3600)
+
+/* every 1 hours means 365*24 = 8760 EEprom writes per year (max) */
+#define HOURS_TIMER_SECS (1 * 3600)
+
+/* the initial state */
+static struct sysmobts_mgr_instance manager = {
+ .config_file = "sysmobts-mgr.cfg",
+ .rf_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .digital_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .board_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .pa_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 100,
+ },
+ .action_warn = 0,
+ .action_crit = TEMP_ACT_PA_OFF,
+ .state = STATE_NORMAL,
+};
+
+
+static int classify_bts(void)
+{
+ int rc;
+
+ rc = sysmobts_get_type(&bts_type);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to get model number.\n");
+ return -1;
+ }
+
+ rc = sysmobts_get_trx(&trx_number);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to get the trx number.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int sysmobts_bts_type(void)
+{
+ return bts_type;
+}
+
+int sysmobts_trx_number(void)
+{
+ return trx_number;
+}
+
+int is_sbts2050(void)
+{
+ return bts_type == 2050;
+}
+
+int is_sbts2050_trx(int trx)
+{
+ return trx_number == trx;
+}
+
+int is_sbts2050_master(void)
+{
+ if (!is_sbts2050())
+ return 0;
+ if (!is_sbts2050_trx(0))
+ return 0;
+ return 1;
+}
+
+static struct osmo_timer_list temp_timer;
+static void check_temp_timer_cb(void *unused)
+{
+ sysmobts_check_temp(no_eeprom_write);
+
+ osmo_timer_schedule(&temp_timer, TEMP_TIMER_SECS, 0);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ sysmobts_update_hours(no_eeprom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+}
+
+static void print_help(void)
+{
+ printf("sysmobts-mgr [-nsD] [-d cat]\n");
+ printf(" -n Do not write to EEPROM\n");
+ printf(" -s Disable color\n");
+ printf(" -d CAT enable debugging\n");
+ printf(" -D daemonize\n");
+ printf(" -c Specify the filename of the config file\n");
+}
+
+static int parse_options(int argc, char **argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
+ switch (opt) {
+ case 'n':
+ no_eeprom_write = 1;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ manager.config_file = optarg;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ sysmobts_check_temp(no_eeprom_write);
+ sysmobts_update_hours(no_eeprom_write);
+ exit(0);
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_mgr_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct log_info_cat mgr_log_info_cat[] = {
+ [DTEMP] = {
+ .name = "DTEMP",
+ .description = "Temperature monitoring",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFW] = {
+ .name = "DFW",
+ .description = "DSP/FPGA firmware management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFIND] = {
+ .name = "DFIND",
+ .description = "ipaccess-find handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DCALIB] = {
+ .name = "DCALIB",
+ .description = "Calibration handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+static const struct log_info mgr_log_info = {
+ .cat = mgr_log_info_cat,
+ .num_cat = ARRAY_SIZE(mgr_log_info_cat),
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+ struct ctrl_connection *ccon;
+
+ tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
+ msgb_talloc_ctx_init(tall_mgr_ctx, 0);
+
+ srand(time(NULL));
+
+ osmo_init_logging2(tall_mgr_ctx, &mgr_log_info);
+ if (classify_bts() != 0)
+ exit(2);
+
+ osmo_init_ignore_signals();
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ sysmobts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = sysmobts_mgr_parse_config(&manager);
+ if (rc < 0) {
+ LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
+ exit(1);
+ }
+
+ rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ /* start temperature check timer */
+ temp_timer.cb = check_temp_timer_cb;
+ check_temp_timer_cb(NULL);
+
+ /* start operational hours timer */
+ hours_timer.cb = hours_timer_cb;
+ hours_timer_cb(NULL);
+
+ /* start uc temperature check timer */
+ sbts2050_uc_initialize();
+
+ /* handle broadcast messages for ipaccess-find */
+ if (sysmobts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the temperature control */
+ ccon = osmo_ctrl_conn_alloc(tall_mgr_ctx, NULL);
+ rc = -1;
+ if (ccon) {
+ ccon->write_queue.bfd.data = ccon;
+ rc = osmo_sock_init_ofd(&ccon->write_queue.bfd, AF_INET,
+ SOCK_STREAM, IPPROTO_TCP,
+ "localhost", OSMO_CTRL_PORT_BTS,
+ OSMO_SOCK_F_CONNECT);
+ }
+ if (rc < 0)
+ LOGP(DLCTRL, LOGL_ERROR, "Can't connect to CTRL @ localhost:%u\n",
+ OSMO_CTRL_PORT_BTS);
+ else
+ LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to locahost:%u\n",
+ OSMO_CTRL_PORT_BTS);
+
+ sysmobts_mgr_temp_init(&manager, ccon);
+
+ if (sysmobts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.h b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h
new file mode 100644
index 00000000..88f4e245
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h
@@ -0,0 +1,122 @@
+#ifndef _SYSMOBTS_MGR_H
+#define _SYSMOBTS_MGR_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <gps.h>
+
+#include <stdint.h>
+
+enum {
+ DTEMP,
+ DFW,
+ DFIND,
+ DCALIB,
+};
+
+
+enum {
+#if 0
+ TEMP_ACT_PWR_CONTRL = 0x1,
+#endif
+ TEMP_ACT_SLAVE_OFF = 0x4,
+ TEMP_ACT_PA_OFF = 0x8,
+ TEMP_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ TEMP_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ TEMP_ACT_NORM_SLAVE_ON = 0x4,
+ TEMP_ACT_NORM_PA_ON = 0x8,
+ TEMP_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum sysmobts_temp_state {
+ STATE_NORMAL, /* Everything is fine */
+ STATE_WARNING_HYST, /* Go back to normal next? */
+ STATE_WARNING, /* We are above the warning threshold */
+ STATE_CRITICAL, /* We have an issue. Wait for below warning */
+};
+
+/**
+ * Temperature Limits. We separate from a threshold
+ * that will generate a warning and one that is so
+ * severe that an action will be taken.
+ */
+struct sysmobts_temp_limit {
+ int thresh_warn;
+ int thresh_crit;
+};
+
+enum mgr_vty_node {
+ MGR_NODE = _LAST_OSMOVTY_NODE + 1,
+
+ ACT_NORM_NODE,
+ ACT_WARN_NODE,
+ ACT_CRIT_NODE,
+ LIMIT_RF_NODE,
+ LIMIT_DIGITAL_NODE,
+ LIMIT_BOARD_NODE,
+ LIMIT_PA_NODE,
+};
+
+struct sysmobts_mgr_instance {
+ const char *config_file;
+
+ struct sysmobts_temp_limit rf_limit;
+ struct sysmobts_temp_limit digital_limit;
+
+ /* Only available on sysmobts 2050 */
+ struct sysmobts_temp_limit board_limit;
+ struct sysmobts_temp_limit pa_limit;
+
+ int action_norm;
+ int action_warn;
+ int action_crit;
+
+ enum sysmobts_temp_state state;
+
+ struct {
+ int initial_calib_started;
+ int is_up;
+ struct osmo_timer_list recon_timer;
+ struct ipa_client_conn *bts_conn;
+
+ int state;
+ struct osmo_timer_list timer;
+ uint32_t last_seqno;
+
+ /* gps structure to see if there is a fix */
+ int gps_open;
+ struct osmo_fd gpsfd;
+ struct gps_data_t gpsdata;
+ struct osmo_timer_list fix_timeout;
+
+ /* Loop/Re-try control */
+ int calib_from_loop;
+ struct osmo_timer_list calib_timeout;
+ } calib;
+};
+
+int sysmobts_mgr_vty_init(void);
+int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *mgr);
+int sysmobts_mgr_nl_init(void);
+int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr,
+ struct ctrl_connection *ctrl);
+const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state);
+
+
+int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr);
+int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr);
+
+
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
new file mode 100644
index 00000000..12961e3f
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
@@ -0,0 +1,384 @@
+/* (C) 2014 by s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sysmobts_misc.h"
+#include "sysmobts_par.h"
+#include "sysmobts_mgr.h"
+#include "btsconfig.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/serial.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifdef BUILD_SBTS2050
+#include <sysmocom/femtobts/sbts2050_header.h>
+
+#define SERIAL_ALLOC_SIZE 300
+#define SIZE_HEADER_RSP 5
+#define SIZE_HEADER_CMD 4
+
+struct uc {
+ int id;
+ int fd;
+ const char *path;
+};
+
+struct ucinfo {
+ uint16_t id;
+ int master;
+ int slave;
+ int pa;
+};
+
+static struct uc ucontrol0 = {
+ .id = 0,
+ .path = "/dev/ttyS0",
+ .fd = -1,
+};
+
+/**********************************************************************
+ * Functions read/write from serial interface
+ *********************************************************************/
+static int hand_serial_read(int fd, struct msgb *msg, int numbytes)
+{
+ int rc, bread = 0;
+
+ if (numbytes > msgb_tailroom(msg))
+ return -ENOSPC;
+
+ while (bread < numbytes) {
+ rc = read(fd, msg->tail, numbytes - bread);
+ if (rc < 0)
+ return -1;
+ if (rc == 0)
+ break;
+
+ bread += rc;
+ msgb_put(msg, rc);
+ }
+
+ return bread;
+}
+
+static int hand_serial_write(int fd, struct msgb *msg)
+{
+ int rc, bwritten = 0;
+
+ while (msg->len > 0) {
+ rc = write(fd, msg->data, msg->len);
+ if (rc <= 0)
+ return -1;
+
+ msgb_pull(msg, rc);
+ bwritten += rc;
+ }
+
+ return bwritten;
+}
+
+/**********************************************************************
+ * Functions request information to Microcontroller
+ *********************************************************************/
+static void add_parity(cmdpkt_t *command)
+{
+ int n;
+ uint8_t parity = 0x00;
+ for (n = 0; n < SIZE_HEADER_CMD+command->u8Len; n++)
+ parity ^= ((uint8_t *)command)[n];
+
+ command->cmd.raw[command->u8Len] = parity;
+}
+
+static struct msgb *sbts2050_ucinfo_sndrcv(struct uc *ucontrol, const struct ucinfo *info)
+{
+ int num, rc;
+ cmdpkt_t *command;
+ rsppkt_t *response;
+ struct msgb *msg;
+ fd_set fdread;
+ struct timeval tout = {
+ .tv_sec = 10,
+ };
+
+ switch (info->id) {
+ case SBTS2050_TEMP_RQT:
+ num = sizeof(command->cmd.tempGet);
+ break;
+ case SBTS2050_PWR_RQT:
+ num = sizeof(command->cmd.pwrSetState);
+ break;
+ case SBTS2050_PWR_STATUS:
+ num = sizeof(command->cmd.pwrGetStatus);
+ break;
+ default:
+ return NULL;
+ }
+ num = num + SIZE_HEADER_CMD+1;
+
+ msg = msgb_alloc(SERIAL_ALLOC_SIZE, "Message Microcontroller");
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR, "Error creating msg\n");
+ return NULL;
+ }
+ command = (cmdpkt_t *) msgb_put(msg, num);
+
+ command->u16Magic = 0xCAFE;
+ switch (info->id) {
+ case SBTS2050_TEMP_RQT:
+ command->u8Id = info->id;
+ command->u8Len = sizeof(command->cmd.tempGet);
+ break;
+ case SBTS2050_PWR_RQT:
+ command->u8Id = info->id;
+ command->u8Len = sizeof(command->cmd.pwrSetState);
+ command->cmd.pwrSetState.u1MasterEn = !!info->master;
+ command->cmd.pwrSetState.u1SlaveEn = !!info->slave;
+ command->cmd.pwrSetState.u1PwrAmpEn = !!info->pa;
+ break;
+ case SBTS2050_PWR_STATUS:
+ command->u8Id = info->id;
+ command->u8Len = sizeof(command->cmd.pwrGetStatus);
+ break;
+ default:
+ goto err;
+ }
+
+ add_parity(command);
+
+ if (hand_serial_write(ucontrol->fd, msg) < 0)
+ goto err;
+
+ msgb_reset(msg);
+
+ FD_ZERO(&fdread);
+ FD_SET(ucontrol->fd, &fdread);
+
+ num = SIZE_HEADER_RSP;
+ while (1) {
+ rc = select(ucontrol->fd+1, &fdread, NULL, NULL, &tout);
+ if (rc > 0) {
+ if (hand_serial_read(ucontrol->fd, msg, num) < 0)
+ goto err;
+
+ response = (rsppkt_t *)msg->data;
+
+ if (response->u8Id != info->id || msg->len <= 0 ||
+ response->i8Error != RQT_SUCCESS)
+ goto err;
+
+ if (msg->len == SIZE_HEADER_RSP + response->u8Len + 1)
+ break;
+
+ num = response->u8Len + 1;
+ } else
+ goto err;
+ }
+
+ return msg;
+
+err:
+ msgb_free(msg);
+ return NULL;
+}
+
+/**********************************************************************
+ * Get power status function
+ *********************************************************************/
+int sbts2050_uc_get_status(struct sbts2050_power_status *status)
+{
+ struct msgb *msg;
+ const struct ucinfo info = {
+ .id = SBTS2050_PWR_STATUS,
+ };
+ rsppkt_t *response;
+
+ memset(status, 0, sizeof(*status));
+ msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info);
+
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Error requesting power status.\n");
+ return -1;
+ }
+
+ response = (rsppkt_t *)msg->data;
+
+ status->main_supply_current = response->rsp.pwrGetStatus.u8MainSupplyA / 64.f;
+
+ status->master_enabled = response->rsp.pwrGetStatus.u1MasterEn;
+ status->master_voltage = response->rsp.pwrGetStatus.u8MasterV / 32.f;
+ status->master_current = response->rsp.pwrGetStatus.u8MasterA / 64.f;;
+
+ status->slave_enabled = response->rsp.pwrGetStatus.u1SlaveEn;
+ status->slave_voltage = response->rsp.pwrGetStatus.u8SlaveV / 32.f;
+ status->slave_current = response->rsp.pwrGetStatus.u8SlaveA / 64.f;
+
+ status->pa_enabled = response->rsp.pwrGetStatus.u1PwrAmpEn;
+ status->pa_voltage = response->rsp.pwrGetStatus.u8PwrAmpV / 4.f;
+ status->pa_current = response->rsp.pwrGetStatus.u8PwrAmpA / 64.f;
+
+ status->pa_bias_voltage = response->rsp.pwrGetStatus.u8PwrAmpBiasV / 16.f;
+
+ msgb_free(msg);
+ return 0;
+}
+
+/**********************************************************************
+ * Uc Power Switching handling
+ *********************************************************************/
+int sbts2050_uc_set_power(int pmaster, int pslave, int ppa)
+{
+ struct msgb *msg;
+ const struct ucinfo info = {
+ .id = SBTS2050_PWR_RQT,
+ .master = pmaster,
+ .slave = pslave,
+ .pa = ppa
+ };
+
+ msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info);
+
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR, "Error switching off some unit.\n");
+ return -1;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Switch off/on success:\n"
+ "MASTER %s\n"
+ "SLAVE %s\n"
+ "PA %s\n",
+ pmaster ? "ON" : "OFF",
+ pslave ? "ON" : "OFF",
+ ppa ? "ON" : "OFF");
+
+ msgb_free(msg);
+ return 0;
+}
+
+/**********************************************************************
+ * Uc temperature handling
+ *********************************************************************/
+int sbts2050_uc_check_temp(int *temp_pa, int *temp_board)
+{
+ rsppkt_t *response;
+ struct msgb *msg;
+ const struct ucinfo info = {
+ .id = SBTS2050_TEMP_RQT,
+ };
+
+ msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info);
+
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
+ return -1;
+ }
+
+ response = (rsppkt_t *)msg->data;
+
+ *temp_board = response->rsp.tempGet.i8BrdTemp;
+ *temp_pa = response->rsp.tempGet.i8PaTemp;
+
+ LOGP(DTEMP, LOGL_DEBUG, "Temperature Board: %+3d C, "
+ "Tempeture PA: %+3d C\n",
+ response->rsp.tempGet.i8BrdTemp,
+ response->rsp.tempGet.i8PaTemp);
+ msgb_free(msg);
+ return 0;
+}
+
+void sbts2050_uc_initialize(void)
+{
+ if (!is_sbts2050())
+ return;
+
+ ucontrol0.fd = osmo_serial_init(ucontrol0.path, 115200);
+ if (ucontrol0.fd < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to open the serial interface\n");
+ return;
+ }
+
+ if (is_sbts2050_master()) {
+ LOGP(DTEMP, LOGL_NOTICE, "Going to enable the PA.\n");
+ sbts2050_uc_set_pa_power(1);
+ }
+}
+
+int sbts2050_uc_set_pa_power(int on_off)
+{
+ struct sbts2050_power_status status;
+ if (sbts2050_uc_get_status(&status) != 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n");
+ return -1;
+ }
+
+ return sbts2050_uc_set_power(status.master_enabled, status.slave_enabled, on_off);
+}
+
+int sbts2050_uc_set_slave_power(int on_off)
+{
+ struct sbts2050_power_status status;
+ if (sbts2050_uc_get_status(&status) != 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n");
+ return -1;
+ }
+
+ return sbts2050_uc_set_power(
+ status.master_enabled,
+ on_off,
+ status.pa_enabled);
+}
+#else
+void sbts2050_uc_initialize(void)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "sysmoBTS2050 was not enabled at compile time.\n");
+}
+
+int sbts2050_uc_check_temp(int *temp_pa, int *temp_board)
+{
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without temp support.\n");
+ *temp_pa = *temp_board = 99999;
+ return -1;
+}
+
+int sbts2050_uc_get_status(struct sbts2050_power_status *status)
+{
+ memset(status, 0, sizeof(*status));
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without status support.\n");
+ return -1;
+}
+
+int sbts2050_uc_set_pa_power(int on_off)
+{
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without PA support.\n");
+ return -1;
+}
+
+int sbts2050_uc_set_slave_power(int on_off)
+{
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without UC support.\n");
+ return -1;
+}
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
new file mode 100644
index 00000000..b0b5edd8
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
@@ -0,0 +1,547 @@
+/* OCXO/TCXO calibration control for SysmoBTS management daemon */
+
+/*
+ * (C) 2014,2015 by Holger Hans Peter Freyther
+ * (C) 2014 by Harald Welte for the IPA code from the oml router
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_misc.h"
+#include "osmo-bts/msg_utils.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipa.h>
+
+static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop);
+static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int reason);
+static void request_clock_reset(struct sysmobts_mgr_instance *mgr);
+static void bts_updown_cb(struct ipa_client_conn *link, int up);
+
+enum calib_state {
+ CALIB_INITIAL,
+ CALIB_GPS_WAIT_FOR_FIX,
+ CALIB_CTR_RESET,
+ CALIB_CTR_WAIT,
+ CALIB_COR_SET,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPS,
+ CALIB_FAIL_CTRL,
+ CALIB_SUCESS,
+};
+
+static inline int compat_gps_read(struct gps_data_t *data)
+{
+/* API break in gpsd 6bba8b329fc7687b15863d30471d5af402467802 */
+#if GPSD_API_MAJOR_VERSION >= 7 && GPSD_API_MINOR_VERSION >= 0
+ return gps_read(data, NULL, 0);
+#else
+ return gps_read(data);
+#endif
+}
+
+static void calib_loop_run(void *_data)
+{
+ int rc;
+ struct sysmobts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0)
+ calib_state_reset(mgr, CALIB_FAIL_START);
+}
+
+static void mgr_gps_close(struct sysmobts_mgr_instance *mgr)
+{
+ if (!mgr->calib.gps_open)
+ return;
+
+ osmo_timer_del(&mgr->calib.fix_timeout);
+
+ osmo_fd_unregister(&mgr->calib.gpsfd);
+ gps_close(&mgr->calib.gpsdata);
+ memset(&mgr->calib.gpsdata, 0, sizeof(mgr->calib.gpsdata));
+ mgr->calib.gps_open = 0;
+}
+
+static void mgr_gps_checkfix(struct sysmobts_mgr_instance *mgr)
+{
+ struct gps_data_t *data = &mgr->calib.gpsdata;
+
+ /* No 2D fix yet */
+ if (data->fix.mode < MODE_2D) {
+ LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
+ data->fix.mode);
+ return;
+ }
+
+ /* The trimble driver is broken...add some sanity checking */
+ if (data->satellites_used < 1) {
+ LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
+ data->satellites_used);
+ return;
+ }
+
+ LOGP(DCALIB, LOGL_NOTICE, "Got a GPS fix continuing.\n");
+ osmo_timer_del(&mgr->calib.fix_timeout);
+ mgr_gps_close(mgr);
+ request_clock_reset(mgr);
+}
+
+static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
+{
+ int rc;
+ struct sysmobts_mgr_instance *mgr = fd->data;
+ rc = compat_gps_read(&mgr->calib.gpsdata);
+ if (rc == -1) {
+ LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+ return -1;
+ }
+
+ if (rc > 0)
+ mgr_gps_checkfix(mgr);
+ return 0;
+}
+
+static void mgr_gps_fix_timeout(void *_data)
+{
+ struct sysmobts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPRS fix.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+}
+
+static void mgr_gps_open(struct sysmobts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->calib.gpsdata);
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+ return;
+ }
+
+ mgr->calib.gps_open = 1;
+ gps_stream(&mgr->calib.gpsdata, WATCH_ENABLE, NULL);
+
+ mgr->calib.gpsfd.data = mgr;
+ mgr->calib.gpsfd.cb = mgr_gps_read;
+ mgr->calib.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT;
+ mgr->calib.gpsfd.fd = mgr->calib.gpsdata.gps_fd;
+ if (osmo_fd_register(&mgr->calib.gpsfd) < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+ }
+
+ mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
+ mgr->calib.fix_timeout.data = mgr;
+ mgr->calib.fix_timeout.cb = mgr_gps_fix_timeout;
+ osmo_timer_schedule(&mgr->calib.fix_timeout, 60, 0);
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Opened the GPSD connection waiting for fix: %d\n",
+ mgr->calib.gpsfd.fd);
+}
+
+static void send_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
+ struct msgb *msg)
+{
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+ ipa_client_conn_send(mgr->calib.bts_conn, msg);
+}
+
+static void send_set_ctrl_cmd_int(struct sysmobts_mgr_instance *mgr,
+ const char *key, const int val)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d",
+ mgr->calib.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_set_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
+ const char *key, const char *val)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %s",
+ mgr->calib.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_get_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
+ const char *key)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL GET");
+ ret = snprintf((char *) msg->data, 4096, "GET %u %s",
+ mgr->calib.last_seqno++, key);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop)
+{
+ if (!mgr->calib.is_up) {
+ LOGP(DCALIB, LOGL_ERROR, "Control interface not connected.\n");
+ return -1;
+ }
+
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -2;
+ }
+
+ mgr->calib.calib_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->calib.initial_calib_started = 1;
+ mgr_gps_open(mgr);
+ return 0;
+}
+
+int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+static void request_clock_reset(struct sysmobts_mgr_instance *mgr)
+{
+ send_set_ctrl_cmd(mgr, "trx.0.clock-info", "1");
+ mgr->calib.state = CALIB_CTR_RESET;
+}
+
+static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->calib.calib_from_loop) {
+ /*
+ * In case of success calibrate in two hours again
+ * and in case of a failure in some minutes.
+ */
+ int timeout = 2 * 60 * 60;
+ if (outcome != CALIB_SUCESS)
+ timeout = 5 * 60;
+
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ osmo_timer_del(&mgr->calib.timer);
+
+ mgr_gps_close(mgr);
+}
+
+static void calib_get_clock_err_cb(void *_data)
+{
+ struct sysmobts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_DEBUG,
+ "Requesting current clock-info.\n");
+ send_get_ctrl_cmd(mgr, "trx.0.clock-info");
+}
+
+static void handle_ctrl_reset_resp(
+ struct sysmobts_mgr_instance *mgr,
+ struct ctrl_cmd *cmd)
+{
+ if (strcmp(cmd->variable, "trx.0.clock-info") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected variable: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ if (strcmp(cmd->reply, "success") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected reply: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ mgr->calib.state = CALIB_CTR_WAIT;
+ mgr->calib.timer.cb = calib_get_clock_err_cb;
+ mgr->calib.timer.data = mgr;
+ osmo_timer_schedule(&mgr->calib.timer, 60, 0);
+ LOGP(DCALIB, LOGL_DEBUG,
+ "Reset the calibration counter. Waiting 60 seconds.\n");
+}
+
+static void handle_ctrl_get_resp(
+ struct sysmobts_mgr_instance *mgr,
+ struct ctrl_cmd *cmd)
+{
+ char *saveptr = NULL;
+ char *clk_cur;
+ char *clk_src;
+ char *cal_err;
+ char *cal_res;
+ char *cal_src;
+ int cal_err_int;
+
+ if (strcmp(cmd->variable, "trx.0.clock-info") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected variable: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ clk_cur = strtok_r(cmd->reply, ",", &saveptr);
+ clk_src = strtok_r(NULL, ",", &saveptr);
+ cal_err = strtok_r(NULL, ",", &saveptr);
+ cal_res = strtok_r(NULL, ",", &saveptr);
+ cal_src = strtok_r(NULL, ",", &saveptr);
+
+ if (!clk_cur || !clk_src || !cal_err || !cal_res || !cal_src) {
+ LOGP(DCALIB, LOGL_ERROR, "Parse error on clock-info reply\n");
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+
+ }
+ cal_err_int = atoi(cal_err);
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calibration CUR(%s) SRC(%s) ERR(%s/%d) RES(%s) SRC(%s)\n",
+ clk_cur, clk_src, cal_err, cal_err_int, cal_res, cal_src);
+
+ if (strcmp(cal_res, "0") == 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Invalid clock resolution. Giving up\n");
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ /* Now we can finally set the new value */
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Going to apply %d as new clock correction.\n",
+ -cal_err_int);
+ send_set_ctrl_cmd_int(mgr, "trx.0.clock-correction", -cal_err_int);
+ mgr->calib.state = CALIB_COR_SET;
+}
+
+static void handle_ctrl_set_cor(
+ struct sysmobts_mgr_instance *mgr,
+ struct ctrl_cmd *cmd)
+{
+ if (strcmp(cmd->variable, "trx.0.clock-correction") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected variable: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ if (strcmp(cmd->reply, "success") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected reply: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calibration process completed\n");
+ calib_state_reset(mgr, CALIB_SUCESS);
+}
+
+static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
+ if (!cmd) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
+ return;
+ }
+
+ switch (cmd->type) {
+ case CTRL_TYPE_GET_REPLY:
+ switch (mgr->calib.state) {
+ case CALIB_CTR_WAIT:
+ handle_ctrl_get_resp(mgr, cmd);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled response in state: %d %s/%s\n",
+ mgr->calib.state, cmd->variable, cmd->reply);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ break;
+ };
+ break;
+ case CTRL_TYPE_SET_REPLY:
+ switch (mgr->calib.state) {
+ case CALIB_CTR_RESET:
+ handle_ctrl_reset_resp(mgr, cmd);
+ break;
+ case CALIB_COR_SET:
+ handle_ctrl_set_cor(mgr, cmd);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled response in state: %d %s/%s\n",
+ mgr->calib.state, cmd->variable, cmd->reply);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ break;
+ };
+ break;
+ case CTRL_TYPE_TRAP:
+ /* ignore any form of trap */
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled CTRL response: %d. Resetting state\n",
+ cmd->type);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ break;
+ }
+
+ talloc_free(cmd);
+}
+
+/* Schedule a connect towards the BTS */
+static void schedule_bts_connect(struct sysmobts_mgr_instance *mgr)
+{
+ DEBUGP(DLCTRL, "Scheduling BTS connect\n");
+ osmo_timer_schedule(&mgr->calib.recon_timer, 1, 0);
+}
+
+/* BTS re-connect timer call-back */
+static void bts_recon_timer_cb(void *data)
+{
+ int rc;
+ struct sysmobts_mgr_instance *mgr = data;
+
+ /* The connection failures are to be expected during boot */
+ mgr->calib.bts_conn->ofd->when |= BSC_FD_WRITE;
+ rc = ipa_client_conn_open(mgr->calib.bts_conn);
+ if (rc < 0) {
+ LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
+ schedule_bts_connect(mgr);
+ }
+}
+
+static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+{
+ int rc;
+ struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg);
+ struct ipaccess_head_ext *hh_ext;
+
+ DEBUGP(DCALIB, "Received data from BTS: %s\n",
+ osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ /* regular message handling */
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Invalid IPA message from BTS (rc=%d)\n", rc);
+ goto err;
+ }
+
+ switch (hh->proto) {
+ case IPAC_PROTO_IPACCESS:
+ /* handle the core IPA CCM messages in libosmoabis */
+ ipa_ccm_rcvmsg_bts_base(msg, link->ofd);
+ msgb_free(msg);
+ break;
+ case IPAC_PROTO_OSMO:
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ switch (hh_ext->proto) {
+ case IPAC_PROTO_EXT_CTRL:
+ handle_ctrl(link->data, msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled osmo ID %u from BTS\n", hh_ext->proto);
+ };
+ msgb_free(msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled stream ID %u from BTS\n", hh->proto);
+ msgb_free(msg);
+ break;
+ }
+
+ return 0;
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+/* link to BSC has gone up or down */
+static void bts_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct sysmobts_mgr_instance *mgr = link->data;
+
+ LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
+
+ if (up) {
+ mgr->calib.is_up = 1;
+ mgr->calib.last_seqno = 0;
+
+ if (!mgr->calib.initial_calib_started)
+ calib_run(mgr, 1);
+ } else {
+ mgr->calib.is_up = 0;
+ schedule_bts_connect(mgr);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ }
+}
+
+int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr)
+{
+ if (!is_sbts2050_master()) {
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calib is only possible on the sysmoBTS2050 master\n");
+ return 0;
+ }
+
+ mgr->calib.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
+ "localhost", 4238,
+ bts_updown_cb, bts_read_cb,
+ NULL, mgr);
+ if (!mgr->calib.bts_conn) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to create IPA connection\n");
+ return -1;
+ }
+
+ mgr->calib.recon_timer.cb = bts_recon_timer_cb;
+ mgr->calib.recon_timer.data = mgr;
+ schedule_bts_connect(mgr);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
new file mode 100644
index 00000000..48a03124
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
@@ -0,0 +1,186 @@
+/* NetworkListen for SysmoBTS management daemon */
+
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_misc.h"
+#include "misc/sysmobts_nl.h"
+#include "misc/sysmobts_par.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+
+static struct osmo_fd nl_fd;
+
+/*
+ * The TLV structure in IPA messages in UDP packages is a bit
+ * weird. First the header appears to have an extra NULL byte
+ * and second the L16 of the L16TV needs to include +1 for the
+ * tag. The default msgb/tlv and libosmo-abis routines do not
+ * provide this.
+ */
+
+static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
+ hh->len = htons(msg->len - sizeof(*hh) - 1);
+ hh->proto = proto;
+}
+
+static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+ const uint8_t *val)
+{
+ uint8_t *buf = msgb_put(msg, len + 2 + 1);
+
+ *buf++ = (len + 1) >> 8;
+ *buf++ = (len + 1) & 0xff;
+ *buf++ = tag;
+ memcpy(buf, val, len);
+}
+
+/*
+ * We don't look at the content of the request yet and lie
+ * about most of the responses.
+ */
+static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
+ uint8_t *data, size_t len)
+{
+ static int fetched_info = 0;
+ static char mac_str[20] = {0, };
+ static char *model_name;
+ static char ser_str[20] = {0, };
+
+ struct sockaddr_in loc_addr;
+ int rc;
+ char loc_ip[INET_ADDRSTRLEN];
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
+ if (!msg) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
+ return;
+ }
+
+ if (!fetched_info) {
+ uint8_t mac[6];
+ int serno;
+
+ /* fetch the MAC */
+ sysmobts_par_get_buf(SYSMOBTS_PAR_MAC, mac, sizeof(mac));
+ snprintf(mac_str, sizeof(mac_str), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
+ mac[0], mac[1], mac[2],
+ mac[3], mac[4], mac[5]);
+
+ /* fetch the serial number */
+ sysmobts_par_get_int(SYSMOBTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ /* fetch the model and trx number */
+ model_name = sysmobts_model(sysmobts_bts_type(), sysmobts_trx_number());
+ fetched_info = 1;
+ }
+
+ if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
+ return;
+ }
+
+ msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
+
+ /* append MAC addr */
+ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
+
+ /* append ip address */
+ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
+ quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
+
+ /* append the serial number */
+ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
+
+ /* abuse some flags */
+ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
+
+ /* ip.access nanoBTS would reply to port==3006 */
+ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
+ rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
+ if (rc != msg->len)
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to send with rc(%d) errno(%d)\n", rc, errno);
+}
+
+static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
+{
+ uint8_t data[2048];
+ char src[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {};
+ socklen_t len = sizeof(addr);
+ int rc;
+
+ rc = recvfrom(fd->fd, data, sizeof(data), 0,
+ (struct sockaddr *) &addr, &len);
+ if (rc <= 0) {
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to read from socket errno(%d)\n", errno);
+ return -1;
+ }
+
+ LOGP(DFIND, LOGL_DEBUG,
+ "Received request from: %s size %d\n",
+ inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
+
+ if (rc < 6)
+ return 0;
+
+ if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
+ return 0;
+
+ respond_to(&addr, fd, data + 6, rc - 6);
+ return 0;
+}
+
+int sysmobts_mgr_nl_init(void)
+{
+ int rc;
+
+ nl_fd.cb = ipaccess_bcast;
+ rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ "0.0.0.0", 3006, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("Socket creation");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
new file mode 100644
index 00000000..1be56ac2
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
@@ -0,0 +1,321 @@
+/* Temperature control for SysmoBTS management daemon */
+
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_misc.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <stdlib.h>
+
+static struct sysmobts_mgr_instance *s_mgr;
+static struct osmo_timer_list temp_ctrl_timer;
+
+static const struct value_string state_names[] = {
+ { STATE_NORMAL, "NORMAL" },
+ { STATE_WARNING_HYST, "WARNING (HYST)" },
+ { STATE_WARNING, "WARNING" },
+ { STATE_CRITICAL, "CRITICAL" },
+ { 0, NULL }
+};
+
+const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum sysmobts_temp_state current_state, int critical, int warning)
+{
+ int next_state = -1;
+ switch (current_state) {
+ case STATE_NORMAL:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ break;
+ case STATE_WARNING_HYST:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ else
+ next_state = STATE_NORMAL;
+ break;
+ case STATE_WARNING:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (!warning)
+ next_state = STATE_WARNING_HYST;
+ break;
+ case STATE_CRITICAL:
+ if (!critical && !warning)
+ next_state = STATE_WARNING;
+ break;
+ };
+
+ return next_state;
+}
+
+static void handle_normal_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & TEMP_ACT_NORM_PA_ON) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "PA can only be switched-on on the master\n");
+ } else if (sbts2050_uc_set_pa_power(1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the PA as normal action.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_NORM_SLAVE_ON) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Slave on only possible on the sysmoBTS2050\n");
+ } else if (sbts2050_uc_set_slave_power(1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the slave BTS\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the slave as normal action.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch on the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl start osmo-bts-sysmo");
+ }
+}
+
+static void handle_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & TEMP_ACT_PA_OFF) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "PA can only be switched-off on the master\n");
+ } else if (sbts2050_uc_set_pa_power(0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA due temperature.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_SLAVE_OFF) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Slave off only possible on the sysmoBTS2050\n");
+ } else if (sbts2050_uc_set_slave_power(0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the slave BTS\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the slave due temperature\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_BTS_SRV_OFF) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch off the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl stop osmo-bts-sysmo");
+ }
+}
+
+/**
+ * Go back to normal! Depending on the configuration execute the normal
+ * actions that could (start to) undo everything we did in the other
+ * states. What is still missing is the power increase/decrease depending
+ * on the state. E.g. starting from WARNING_HYST we might want to slowly
+ * ramp up the output power again.
+ */
+static void execute_normal_act(struct sysmobts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal temperature.\n");
+ handle_normal_actions(manager->action_norm);
+}
+
+static void execute_warning_act(struct sysmobts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached temperature warning.\n");
+ handle_actions(manager->action_warn);
+}
+
+static void execute_critical_act(struct sysmobts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_actions(manager->action_crit);
+}
+
+static void sysmobts_mgr_temp_handle(struct sysmobts_mgr_instance *manager,
+ struct ctrl_connection *ctrl, int critical,
+ int warning)
+{
+ int new_state = next_state(manager->state, critical, warning);
+ struct ctrl_cmd *rep;
+ char *oml_alert = NULL;
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+
+ LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n",
+ get_value_string(state_names, manager->state),
+ get_value_string(state_names, new_state));
+ manager->state = new_state;
+ switch (manager->state) {
+ case STATE_NORMAL:
+ execute_normal_act(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ oml_alert = "Temperature Warning";
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ oml_alert = "Temperature Critical";
+ break;
+ };
+
+ if (!oml_alert)
+ return;
+
+ rep = ctrl_cmd_create(tall_mgr_ctx, CTRL_TYPE_SET);
+ if (!rep) {
+ LOGP(DTEMP, LOGL_ERROR, "OML alert creation failed for %s.\n",
+ oml_alert);
+ return;
+ }
+
+ rep->id = talloc_asprintf(rep, "%d", rand());
+ rep->variable = "oml-alert";
+ rep->value = oml_alert;
+ LOGP(DTEMP, LOGL_ERROR, "OML alert sent: %d\n",
+ ctrl_cmd_send(&ctrl->write_queue, rep));
+ talloc_free(rep);
+}
+
+static void temp_ctrl_check(struct ctrl_connection *ctrl)
+{
+ int rc;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+
+ LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n");
+
+ /* Read the current digital temperature */
+ rc = sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, SYSMOBTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the digital temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->digital_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->digital_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "Digital temperature is: %d\n", temp);
+ }
+
+ /* Read the current RF temperature */
+ rc = sysmobts_temp_get(SYSMOBTS_TEMP_RF, SYSMOBTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the RF temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->rf_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->rf_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "RF temperature is: %d\n", temp);
+ }
+
+ if (is_sbts2050()) {
+ int temp_pa, temp_board;
+
+ rc = sbts2050_uc_check_temp(&temp_pa, &temp_board);
+ if (rc != 0) {
+ /* XXX what do here? */
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the temperature! Reboot?!\n");
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ } else {
+ LOGP(DTEMP, LOGL_DEBUG, "SBTS2050 board(%d) PA(%d)\n",
+ temp_board, temp_pa);
+ if (temp_pa > s_mgr->pa_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp_pa > s_mgr->pa_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ if (temp_board > s_mgr->board_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp_board > s_mgr->board_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ }
+ }
+
+ sysmobts_mgr_temp_handle(s_mgr, ctrl, crit_thresh_passed,
+ warn_thresh_passed);
+}
+
+static void temp_ctrl_check_cb(void *ctrl)
+{
+ temp_ctrl_check(ctrl);
+ /* Check every two minutes? XXX make it configurable! */
+ osmo_timer_schedule(&temp_ctrl_timer, 2 * 60, 0);
+}
+
+int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr,
+ struct ctrl_connection *ctrl)
+{
+ s_mgr = mgr;
+ temp_ctrl_timer.cb = temp_ctrl_check_cb;
+ temp_ctrl_timer.data = ctrl;
+ temp_ctrl_check_cb(ctrl);
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
new file mode 100644
index 00000000..444ee7c3
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
@@ -0,0 +1,531 @@
+/* (C) 2014 by sysmocom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@sysmocom.de>
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "sysmobts_misc.h"
+#include "sysmobts_mgr.h"
+#include "btsconfig.h"
+
+static struct sysmobts_mgr_instance *s_mgr;
+
+static const char copyright[] =
+ "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
+ "(C) 2014 by Holger Hans Peter Freyther\r\n"
+ "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static int go_to_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case MGR_NODE:
+ vty->node = CONFIG_NODE;
+ break;
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_RF_NODE:
+ case LIMIT_DIGITAL_NODE:
+ case LIMIT_BOARD_NODE:
+ case LIMIT_PA_NODE:
+ vty->node = MGR_NODE;
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ return vty->node;
+}
+
+static int is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case MGR_NODE:
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_RF_NODE:
+ case LIMIT_DIGITAL_NODE:
+ case LIMIT_BOARD_NODE:
+ case LIMIT_PA_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "sysmobts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure sysmobts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(sysmobts-mgr)# ",
+ 1,
+};
+
+static struct cmd_node act_norm_node = {
+ ACT_NORM_NODE,
+ "%s(action-normal)# ",
+ 1,
+};
+
+static struct cmd_node act_warn_node = {
+ ACT_WARN_NODE,
+ "%s(action-warn)# ",
+ 1,
+};
+
+static struct cmd_node act_crit_node = {
+ ACT_CRIT_NODE,
+ "%s(action-critical)# ",
+ 1,
+};
+
+static struct cmd_node limit_rf_node = {
+ LIMIT_RF_NODE,
+ "%s(limit-rf)# ",
+ 1,
+};
+
+static struct cmd_node limit_digital_node = {
+ LIMIT_DIGITAL_NODE,
+ "%s(limit-digital)# ",
+ 1,
+};
+
+static struct cmd_node limit_board_node = {
+ LIMIT_BOARD_NODE,
+ "%s(limit-board)# ",
+ 1,
+};
+
+static struct cmd_node limit_pa_node = {
+ LIMIT_PA_NODE,
+ "%s(limit-pa)# ",
+ 1,
+};
+
+DEFUN(cfg_mgr, cfg_mgr_cmd,
+ "sysmobts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_temp_limit(struct vty *vty, const char *name,
+ struct sysmobts_temp_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning %d%s",
+ limit->thresh_warn, VTY_NEWLINE);
+ vty_out(vty, " threshold critical %d%s",
+ limit->thresh_crit, VTY_NEWLINE);
+}
+
+static void write_norm_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa-on%s",
+ (actions & TEMP_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-on%s",
+ (actions & TEMP_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sslave-on%s",
+ (actions & TEMP_ACT_NORM_SLAVE_ON) ? "" : "no ", VTY_NEWLINE);
+}
+
+static void write_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+#if 0
+ vty_out(vty, " %spower-control%s",
+ (actions & TEMP_ACT_PWR_CONTRL) ? "" : "no ", VTY_NEWLINE);
+
+ /* only on the sysmobts 2050 */
+ vty_out(vty, " %smaster-off%s",
+ (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sslave-off%s",
+ (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE);
+#endif
+ vty_out(vty, " %spa-off%s",
+ (actions & TEMP_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-off%s",
+ (actions & TEMP_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sslave-off%s",
+ (actions & TEMP_ACT_SLAVE_OFF) ? "" : "no ", VTY_NEWLINE);
+}
+
+static int config_write_mgr(struct vty *vty)
+{
+ vty_out(vty, "sysmobts-mgr%s", VTY_NEWLINE);
+
+ write_temp_limit(vty, "limits rf", &s_mgr->rf_limit);
+ write_temp_limit(vty, "limits digital", &s_mgr->digital_limit);
+ write_temp_limit(vty, "limits board", &s_mgr->board_limit);
+ write_temp_limit(vty, "limits pa", &s_mgr->pa_limit);
+
+ write_norm_action(vty, "actions normal", s_mgr->action_norm);
+ write_action(vty, "actions warn", s_mgr->action_warn);
+ write_action(vty, "actions critical", s_mgr->action_crit);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+#define CFG_LIMIT(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT(rf, "RF\n", LIMIT_RF_NODE, rf_limit)
+CFG_LIMIT(digital, "Digital\n", LIMIT_DIGITAL_NODE, digital_limit)
+CFG_LIMIT(board, "Board\n", LIMIT_BOARD_NODE, board_limit)
+CFG_LIMIT(pa, "Power Amplifier\n", LIMIT_PA_NODE, pa_limit)
+#undef CFG_LIMIT
+
+DEFUN(cfg_limit_warning, cfg_thresh_warning_cmd,
+ "threshold warning <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct sysmobts_temp_limit *limit = vty->index;
+ limit->thresh_warn = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_crit, cfg_thresh_crit_cmd,
+ "threshold critical <0-200>",
+ "Threshold to reach\n" "Severe level\n" "Range\n")
+{
+ struct sysmobts_temp_limit *limit = vty->index;
+ limit->thresh_crit = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define CFG_ACTION(name, expl, switch_to, variable) \
+DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
+ "actions " #name, \
+ "Configure Actions\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->variable; \
+ return CMD_SUCCESS; \
+}
+CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
+CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
+CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
+#undef CFG_ACTION
+
+DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd,
+ "pa-on",
+ "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd,
+ "no pa-on",
+ NO_STR "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
+ "bts-service-on",
+ "Start the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
+ "no bts-service-on",
+ NO_STR "Start the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_slave_on, cfg_action_slave_on_cmd,
+ "slave-on",
+ "Power-on secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_SLAVE_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_slave_on, cfg_no_action_slave_on_cmd,
+ "no slave-on",
+ NO_STR "Power-on secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_SLAVE_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd,
+ "pa-off",
+ "Switch the Power Amplifier off\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd,
+ "no pa-off",
+ NO_STR "Do not switch off the Power Amplifier\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
+ "no bts-service-off",
+ NO_STR "Stop the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_slave_off, cfg_action_slave_off_cmd,
+ "slave-off",
+ "Power-off secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_SLAVE_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_slave_off, cfg_no_action_slave_off_cmd,
+ "no slave-off",
+ NO_STR "Power-off secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_SLAVE_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mgr, show_mgr_cmd, "show manager",
+ SHOW_STR "Display information about the manager")
+{
+ vty_out(vty, "BTS Control Interface: %s%s",
+ s_mgr->calib.is_up ? "connected" : "disconnected", VTY_NEWLINE);
+ vty_out(vty, "Temperature control state: %s%s",
+ sysmobts_mgr_temp_get_state(s_mgr->state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ vty_out(vty, " Digital: %f Celcius%s",
+ sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL,
+ SYSMOBTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " RF: %f Celcius%s",
+ sysmobts_temp_get(SYSMOBTS_TEMP_RF,
+ SYSMOBTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ if (is_sbts2050()) {
+ int temp_pa, temp_board;
+ struct sbts2050_power_status status;
+
+ vty_out(vty, " sysmoBTS 2050 is %s%s",
+ is_sbts2050_master() ? "master" : "slave", VTY_NEWLINE);
+
+ sbts2050_uc_check_temp(&temp_pa, &temp_board);
+ vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_pa, VTY_NEWLINE);
+ vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_board, VTY_NEWLINE);
+
+ sbts2050_uc_get_status(&status);
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ vty_out(vty, " Main Supply :(ON) [(24.00)Vdc, %4.2f A]%s",
+ status.main_supply_current, VTY_NEWLINE);
+ vty_out(vty, " Master SF : %s [%6.2f Vdc, %4.2f A]%s",
+ status.master_enabled ? "ON " : "OFF",
+ status.master_voltage, status.master_current,
+ VTY_NEWLINE);
+ vty_out(vty, " Slave SF : %s [%6.2f Vdc, %4.2f A]%s",
+ status.slave_enabled ? "ON" : "OFF",
+ status.slave_voltage, status.slave_current,
+ VTY_NEWLINE);
+ vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A]%s",
+ status.pa_enabled ? "ON" : "OFF",
+ status.pa_voltage, status.pa_current,
+ VTY_NEWLINE);
+ vty_out(vty, " PA Bias : %s [%6.2f Vdc, ---- A]%s",
+ status.pa_enabled ? "ON" : "OFF",
+ status.pa_bias_voltage,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(calibrate_trx, calibrate_trx_cmd,
+ "trx 0 calibrate-clock",
+ "Transceiver commands\n" "Transceiver 0\n"
+ "Calibrate clock against GPS PPS\n")
+{
+ if (sysmobts_mgr_calib_run(s_mgr) < 0) {
+ vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+static void register_limit(int limit)
+{
+ install_element(limit, &cfg_thresh_warning_cmd);
+ install_element(limit, &cfg_thresh_crit_cmd);
+}
+
+static void register_normal_action(int act)
+{
+ install_element(act, &cfg_action_pa_on_cmd);
+ install_element(act, &cfg_no_action_pa_on_cmd);
+ install_element(act, &cfg_action_bts_srv_on_cmd);
+ install_element(act, &cfg_no_action_bts_srv_on_cmd);
+
+ /* these only work on the sysmobts 2050 */
+ install_element(act, &cfg_action_slave_on_cmd);
+ install_element(act, &cfg_no_action_slave_on_cmd);
+}
+
+static void register_action(int act)
+{
+#if 0
+ install_element(act, &cfg_action_pwr_contrl_cmd);
+ install_element(act, &cfg_no_action_pwr_contrl_cmd);
+#endif
+ install_element(act, &cfg_action_pa_off_cmd);
+ install_element(act, &cfg_no_action_pa_off_cmd);
+ install_element(act, &cfg_action_bts_srv_off_cmd);
+ install_element(act, &cfg_no_action_bts_srv_off_cmd);
+
+ /* these only work on the sysmobts 2050 */
+ install_element(act, &cfg_action_slave_off_cmd);
+ install_element(act, &cfg_no_action_slave_off_cmd);
+}
+
+int sysmobts_mgr_vty_init(void)
+{
+ vty_init(&vty_info);
+
+ install_element_ve(&show_mgr_cmd);
+
+ install_element(ENABLE_NODE, &calibrate_trx_cmd);
+
+ install_node(&mgr_node, config_write_mgr);
+ install_element(CONFIG_NODE, &cfg_mgr_cmd);
+
+ /* install the limit nodes */
+ install_node(&limit_rf_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_rf_cmd);
+ register_limit(LIMIT_RF_NODE);
+
+ install_node(&limit_digital_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_digital_cmd);
+ register_limit(LIMIT_DIGITAL_NODE);
+
+ install_node(&limit_board_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_board_cmd);
+ register_limit(LIMIT_BOARD_NODE);
+
+ install_node(&limit_pa_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_cmd);
+ register_limit(LIMIT_PA_NODE);
+
+ /* install the normal node */
+ install_node(&act_norm_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_normal_cmd);
+ register_normal_action(ACT_NORM_NODE);
+
+ /* install the warning and critical node */
+ install_node(&act_warn_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_warn_cmd);
+ register_action(ACT_WARN_NODE);
+
+ install_node(&act_crit_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_critical_cmd);
+ register_action(ACT_CRIT_NODE);
+
+ return 0;
+}
+
+int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *manager)
+{
+ int rc;
+
+ s_mgr = manager;
+ rc = vty_read_config_file(s_mgr->config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ s_mgr->config_file);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.c b/src/osmo-bts-sysmo/misc/sysmobts_misc.c
new file mode 100644
index 00000000..d996d644
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.c
@@ -0,0 +1,275 @@
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include "btsconfig.h"
+#include "sysmobts_misc.h"
+#include "sysmobts_par.h"
+#include "sysmobts_mgr.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/temp%u_%s"
+
+static const char *temp_type_str[_NUM_TEMP_TYPES] = {
+ [SYSMOBTS_TEMP_INPUT] = "input",
+ [SYSMOBTS_TEMP_LOWEST] = "lowest",
+ [SYSMOBTS_TEMP_HIGHEST] = "highest",
+};
+
+int sysmobts_temp_get(enum sysmobts_temp_sensor sensor,
+ enum sysmobts_temp_type type)
+{
+ char buf[PATH_MAX];
+ char tempstr[8];
+ int fd, rc;
+
+ if (sensor < SYSMOBTS_TEMP_DIGITAL ||
+ sensor > SYSMOBTS_TEMP_RF)
+ return -EINVAL;
+
+ if (type >= ARRAY_SIZE(temp_type_str))
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, TEMP_PATH, sensor, temp_type_str[type]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, tempstr, sizeof(tempstr));
+ tempstr[sizeof(tempstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+
+ close(fd);
+
+ return atoi(tempstr);
+}
+
+static const struct {
+ const char *name;
+ enum sysmobts_temp_sensor sensor;
+ enum sysmobts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "digital",
+ .sensor = SYSMOBTS_TEMP_DIGITAL,
+ .ee_par = SYSMOBTS_PAR_TEMP_DIG_MAX,
+ }, {
+ .name = "rf",
+ .sensor = SYSMOBTS_TEMP_RF,
+ .ee_par = SYSMOBTS_PAR_TEMP_RF_MAX,
+ }
+};
+
+void sysmobts_check_temp(int no_eeprom_write)
+{
+ int temp_old[ARRAY_SIZE(temp_data)];
+ int temp_hi[ARRAY_SIZE(temp_data)];
+ int temp_cur[ARRAY_SIZE(temp_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
+ int ret;
+ rc = sysmobts_par_get_int(temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+ temp_hi[i] = sysmobts_temp_get(temp_data[i].sensor,
+ SYSMOBTS_TEMP_HIGHEST);
+ temp_cur[i] = sysmobts_temp_get(temp_data[i].sensor,
+ SYSMOBTS_TEMP_INPUT);
+
+ if ((temp_cur[i] < 0 && temp_cur[i] > -1000) ||
+ (temp_hi[i] < 0 && temp_hi[i] > -1000)) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
+ return;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
+ temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
+
+ if (temp_hi[i] > temp_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "temperature: %d.%d C\n", temp_data[i].name,
+ temp_hi[i]/1000, temp_hi[i]%1000);
+
+ if (!no_eeprom_write) {
+ rc = sysmobts_par_set_int(SYSMOBTS_PAR_TEMP_DIG_MAX,
+ temp_hi[0]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max temp %d (%s)\n", temp_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+/*********************************************************************
+ * Hours handling
+ *********************************************************************/
+static time_t last_update;
+
+int sysmobts_update_hours(int no_eeprom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ /* number of hours to increase */
+ op_hrs += (now-last_update)/3600;
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ if (!no_eeprom_write) {
+ rc = sysmobts_par_set_int(SYSMOBTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+#define SYSMOBTS_FW_PATH "/lib/firmware"
+
+static const char *fw_names[_NUM_FW] = {
+ [SYSMOBTS_FW_FPGA] = "sysmobts-v2.bit",
+ [SYSMOBTS_FW_DSP] = "sysmobts-v2.out",
+};
+static const char *fw_devs[_NUM_FW] = {
+ [SYSMOBTS_FW_FPGA] = "/dev/fpgadl_par0",
+ [SYSMOBTS_FW_DSP] = "/dev/dspdl_dm644x_0",
+};
+
+int sysmobts_firmware_reload(enum sysmobts_firmware_type type)
+{
+ char name[PATH_MAX];
+ uint8_t buf[1024];
+ int fd_in, fd_out, rc;
+
+ if (type >= _NUM_FW)
+ return -EINVAL;
+
+ snprintf(name, sizeof(name)-1, "%s/%s",
+ SYSMOBTS_FW_PATH, fw_names[type]);
+ name[sizeof(name)-1] = '\0';
+
+ fd_in = open(name, O_RDONLY);
+ if (fd_in < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware file %s: %s\n",
+ name, strerror(errno));
+ return fd_in;
+ }
+
+ fd_out = open(fw_devs[type], O_WRONLY);
+ if (fd_out < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
+ fw_devs[type], strerror(errno));
+ close(fd_in);
+ return fd_out;
+ }
+
+ while ((rc = read(fd_in, buf, sizeof(buf)))) {
+ int written;
+
+ if (rc < 0) {
+ LOGP(DFW, LOGL_ERROR, "error %d during read "
+ "from %s: %s\n", rc, name, strerror(errno));
+ close(fd_in);
+ close(fd_out);
+ return -EIO;
+ }
+
+ written = write(fd_out, buf, rc);
+ if (written < rc) {
+ LOGP(DFW, LOGL_ERROR, "short write during "
+ "fw write to %s\n", fw_devs[type]);
+ close(fd_in);
+ close(fd_out);
+ return -EIO;
+ }
+ }
+
+ close(fd_in);
+ close(fd_out);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.h b/src/osmo-bts-sysmo/misc/sysmobts_misc.h
new file mode 100644
index 00000000..06166cf6
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.h
@@ -0,0 +1,65 @@
+#ifndef _SYSMOBTS_MISC_H
+#define _SYSMOBTS_MISC_H
+
+#include <stdint.h>
+
+enum sysmobts_temp_sensor {
+ SYSMOBTS_TEMP_DIGITAL = 1,
+ SYSMOBTS_TEMP_RF = 2,
+};
+
+enum sysmobts_temp_type {
+ SYSMOBTS_TEMP_INPUT,
+ SYSMOBTS_TEMP_LOWEST,
+ SYSMOBTS_TEMP_HIGHEST,
+ _NUM_TEMP_TYPES
+};
+
+int sysmobts_temp_get(enum sysmobts_temp_sensor sensor,
+ enum sysmobts_temp_type type);
+
+void sysmobts_check_temp(int no_eeprom_write);
+
+int sysmobts_update_hours(int no_epprom_write);
+
+enum sysmobts_firmware_type {
+ SYSMOBTS_FW_FPGA,
+ SYSMOBTS_FW_DSP,
+ _NUM_FW
+};
+
+int sysmobts_firmware_reload(enum sysmobts_firmware_type type);
+
+
+int sysmobts_bts_type();
+int sysmobts_trx_number();
+int is_sbts2050(void);
+int is_sbts2050_trx(int);
+int is_sbts2050_master(void);
+
+struct sbts2050_power_status {
+ float main_supply_current;
+
+ int master_enabled;
+ float master_voltage;
+ float master_current;
+
+ int slave_enabled;
+ float slave_voltage;
+ float slave_current;
+
+ int pa_enabled;
+ float pa_voltage;
+ float pa_current;
+
+ float pa_bias_voltage;
+};
+
+int sbts2050_uc_check_temp(int *temp_pa, int *temp_board);
+int sbts2050_uc_set_power(int pmaster, int pslave, int ppa);
+int sbts2050_uc_get_status(struct sbts2050_power_status *status);
+int sbts2050_uc_set_pa_power(int on_off);
+int sbts2050_uc_set_slave_power(int on_off);
+void sbts2050_uc_initialize();
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_nl.c
new file mode 100644
index 00000000..67aa6636
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.c
@@ -0,0 +1,120 @@
+/* Helper for netlink */
+
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+
+#include <sys/socket.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/**
+ * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source
+ * address will be used when sending a message this function can be used.
+ * It will ask the routing code of the kernel for the PREFSRC
+ */
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source)
+{
+ int fd, rc;
+ struct rtmsg *r;
+ struct rtattr *rta;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (fd < 0) {
+ perror("nl socket");
+ return -1;
+ }
+
+ /* Send a rtmsg and ask for a response */
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.n.nlmsg_seq = 1;
+
+ /* Prepare the routing request */
+ req.r.rtm_family = AF_INET;
+
+ /* set the dest */
+ rta = NLMSG_TAIL(&req.n);
+ rta->rta_type = RTA_DST;
+ rta->rta_len = RTA_LENGTH(sizeof(*dest));
+ memcpy(RTA_DATA(rta), dest, sizeof(*dest));
+
+ /* update sizes for dest */
+ req.r.rtm_dst_len = sizeof(*dest) * 8;
+ req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
+
+ rc = send(fd, &req, req.n.nlmsg_len, 0);
+ if (rc != req.n.nlmsg_len) {
+ perror("short write");
+ close(fd);
+ return -2;
+ }
+
+
+ /* now receive a response and parse it */
+ rc = recv(fd, &req, sizeof(req), 0);
+ if (rc <= 0) {
+ perror("short read");
+ close(fd);
+ return -3;
+ }
+
+ if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) {
+ close(fd);
+ return -4;
+ }
+
+ r = NLMSG_DATA(&req.n);
+ rc -= NLMSG_LENGTH(sizeof(*r));
+ rta = RTM_RTA(r);
+ while (RTA_OK(rta, rc)) {
+ if (rta->rta_type != RTA_PREFSRC) {
+ rta = RTA_NEXT(rta, rc);
+ continue;
+ }
+
+ /* we are done */
+ memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+ return -5;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.h b/src/osmo-bts-sysmo/misc/sysmobts_nl.h
new file mode 100644
index 00000000..84f4d9cc
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.h
@@ -0,0 +1,24 @@
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+struct in_addr;
+
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source);
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.c b/src/osmo-bts-sysmo/misc/sysmobts_par.c
new file mode 100644
index 00000000..de81fff5
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_par.c
@@ -0,0 +1,382 @@
+/* sysmobts - access to hardware related parameters */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/crc8gen.h>
+#include <osmocom/core/utils.h>
+
+#include "sysmobts_eeprom.h"
+#include "sysmobts_par.h"
+#include "eeprom.h"
+
+#define EEPROM_PATH "/sys/devices/platform/i2c_davinci.1/i2c-1/1-0050/eeprom"
+
+static const struct osmo_crc8gen_code crc8_ccit = {
+ .bits = 8,
+ .poly = 0x83,
+ .init = 0xFF,
+ .remainder = 0x00,
+};
+
+const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1] = {
+ { SYSMOBTS_PAR_MAC, "ethaddr" },
+ { SYSMOBTS_PAR_CLK_FACTORY, "clk-factory" },
+ { SYSMOBTS_PAR_TEMP_DIG_MAX, "temp-dig-max" },
+ { SYSMOBTS_PAR_TEMP_RF_MAX, "temp-rf-max" },
+ { SYSMOBTS_PAR_SERNR, "serial-nr" },
+ { SYSMOBTS_PAR_HOURS, "hours-running" },
+ { SYSMOBTS_PAR_BOOTS, "boot-count" },
+ { SYSMOBTS_PAR_KEY, "key" },
+ { SYSMOBTS_PAR_MODEL_NR, "model-nr" },
+ { SYSMOBTS_PAR_MODEL_FLAGS, "model-flags" },
+ { SYSMOBTS_PAR_TRX_NR, "trx-nr" },
+ { 0, NULL }
+};
+
+static struct {
+ int read;
+ struct sysmobts_eeprom ee;
+} g_ee;
+
+static struct sysmobts_eeprom *get_eeprom(int update_rqd)
+{
+ if (update_rqd || g_ee.read == 0) {
+ int fd, rc;
+
+ fd = open(EEPROM_PATH, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ rc = read(fd, &g_ee.ee, sizeof(g_ee.ee));
+
+ close(fd);
+
+ if (rc < sizeof(g_ee.ee))
+ return NULL;
+
+ g_ee.read = 1;
+ }
+
+ return &g_ee.ee;
+}
+
+static int set_eeprom(struct sysmobts_eeprom *ee)
+{
+ int fd, rc;
+
+ memcpy(&g_ee.ee, ee, sizeof(*ee));
+
+ fd = open(EEPROM_PATH, O_WRONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = write(fd, ee, sizeof(*ee));
+ if (rc < sizeof(*ee)) {
+ close(fd);
+ return -EIO;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+int sysmobts_par_is_int(enum sysmobts_par par)
+{
+ switch (par) {
+ case SYSMOBTS_PAR_CLK_FACTORY:
+ case SYSMOBTS_PAR_TEMP_DIG_MAX:
+ case SYSMOBTS_PAR_TEMP_RF_MAX:
+ case SYSMOBTS_PAR_SERNR:
+ case SYSMOBTS_PAR_HOURS:
+ case SYSMOBTS_PAR_BOOTS:
+ case SYSMOBTS_PAR_MODEL_NR:
+ case SYSMOBTS_PAR_MODEL_FLAGS:
+ case SYSMOBTS_PAR_TRX_NR:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+int sysmobts_par_get_int(enum sysmobts_par par, int *ret)
+{
+ eeprom_RfClockCal_t rf_clk;
+ eeprom_Error_t err;
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_CLK_FACTORY:
+ err = eeprom_ReadRfClockCal(&rf_clk);
+ if (err != EEPROM_SUCCESS)
+ return -EIO;
+ *ret = rf_clk.iClkCor;
+ break;
+ case SYSMOBTS_PAR_TEMP_DIG_MAX:
+ *ret = ee->temp1_max;
+ break;
+ case SYSMOBTS_PAR_TEMP_RF_MAX:
+ *ret = ee->temp2_max;
+ break;
+ case SYSMOBTS_PAR_SERNR:
+ *ret = ee->serial_nr;
+ break;
+ case SYSMOBTS_PAR_HOURS:
+ *ret = ee->operational_hours;
+ break;
+ case SYSMOBTS_PAR_BOOTS:
+ *ret = ee->boot_count;
+ break;
+ case SYSMOBTS_PAR_MODEL_NR:
+ *ret = ee->model_nr;
+ break;
+ case SYSMOBTS_PAR_MODEL_FLAGS:
+ *ret = ee->model_flags;
+ break;
+ case SYSMOBTS_PAR_TRX_NR:
+ *ret = ee->trx_nr;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int sysmobts_par_set_int(enum sysmobts_par par, int val)
+{
+ eeprom_RfClockCal_t rf_clk;
+ eeprom_Error_t err;
+ struct sysmobts_eeprom *ee = get_eeprom(1);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_CLK_FACTORY:
+ err = eeprom_ReadRfClockCal(&rf_clk);
+ if (err != EEPROM_SUCCESS)
+ return -EIO;
+ rf_clk.iClkCor = val;
+ err = eeprom_WriteRfClockCal(&rf_clk);
+ if (err != EEPROM_SUCCESS)
+ return -EIO;
+ break;
+ case SYSMOBTS_PAR_TEMP_DIG_MAX:
+ ee->temp1_max = val;
+ break;
+ case SYSMOBTS_PAR_TEMP_RF_MAX:
+ ee->temp2_max = val;
+ break;
+ case SYSMOBTS_PAR_SERNR:
+ ee->serial_nr = val;
+ break;
+ case SYSMOBTS_PAR_HOURS:
+ ee->operational_hours = val;
+ break;
+ case SYSMOBTS_PAR_BOOTS:
+ ee->boot_count = val;
+ break;
+ case SYSMOBTS_PAR_MODEL_NR:
+ ee->model_nr = val;
+ break;
+ case SYSMOBTS_PAR_MODEL_FLAGS:
+ ee->model_flags = val;
+ break;
+ case SYSMOBTS_PAR_TRX_NR:
+ ee->trx_nr = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ set_eeprom(ee);
+
+ return 0;
+}
+
+int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf,
+ unsigned int size)
+{
+ uint8_t *ptr;
+ unsigned int len;
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_MAC:
+ ptr = ee->eth_mac;
+ len = sizeof(ee->eth_mac);
+ break;
+ case SYSMOBTS_PAR_KEY:
+ ptr = ee->gpg_key;
+ len = sizeof(ee->gpg_key);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (size < len)
+ len = size;
+ memcpy(buf, ptr, len);
+
+ return len;
+}
+
+int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf,
+ unsigned int size)
+{
+ uint8_t *ptr;
+ unsigned int len;
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_MAC:
+ ptr = ee->eth_mac;
+ len = sizeof(ee->eth_mac);
+ break;
+ case SYSMOBTS_PAR_KEY:
+ ptr = ee->gpg_key;
+ len = sizeof(ee->gpg_key);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (len < size)
+ size = len;
+
+ memcpy(ptr, buf, size);
+
+ return len;
+}
+
+int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg)
+{
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+ ubit_t bits[sizeof(*cfg) * 8];
+ uint8_t crc;
+ int rc;
+
+ if (!ee)
+ return -EIO;
+
+ /* convert the net_cfg to unpacked bits */
+ rc = osmo_pbit2ubit(bits, (uint8_t *) &ee->net_cfg, sizeof(bits));
+ if (rc != sizeof(bits))
+ return -EFAULT;
+ /* compute the crc and compare */
+ crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits));
+ if (crc != ee->crc) {
+ fprintf(stderr, "Computed CRC(%d) wanted CRC(%d)\n", crc, ee->crc);
+ return -EBADMSG;
+ }
+ /* return the actual data */
+ *cfg = ee->net_cfg;
+ return 0;
+}
+
+int sysmobts_get_type(int *bts_type)
+{
+ return sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, bts_type);
+}
+
+int sysmobts_get_trx(int *trx_number)
+{
+ return sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, trx_number);
+}
+
+char *sysmobts_model(int bts_type, int trx_num)
+{
+ switch(bts_type) {
+ case 0:
+ case 0xffff:
+ case 1002:
+ return "sysmoBTS 1002";
+ case 2050:
+ switch(trx_num) {
+ case 0:
+ return "sysmoBTS 2050 (master)";
+ case 1:
+ return "sysmoBTS 2050 (slave)";
+ default:
+ return "sysmoBTS 2050 (unknown)";
+ }
+ default:
+ return "Unknown";
+ }
+}
+
+int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg)
+{
+ struct sysmobts_eeprom *ee = get_eeprom(1);
+ ubit_t bits[sizeof(*cfg) * 8];
+ int rc;
+
+ if (!ee)
+ return -EIO;
+
+ /* convert the net_cfg to unpacked bits */
+ rc = osmo_pbit2ubit(bits, (uint8_t *) cfg, sizeof(bits));
+ if (rc != sizeof(bits))
+ return -EFAULT;
+ /* compute and store the result */
+ ee->net_cfg = *cfg;
+ ee->crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits));
+ return set_eeprom(ee);
+}
+
+osmo_static_assert(offsetof(struct sysmobts_eeprom, trx_nr) == 36, offset_36);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, boot_state) == 37, offset_37);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, _pad1) == 85, offset_85);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, net_cfg.mode) == 103, offset_103);
+osmo_static_assert((offsetof(struct sysmobts_eeprom, net_cfg.ip) & 0x3) == 0, ip_32bit_aligned);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, gpg_key) == 121, offset_121);
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.h b/src/osmo-bts-sysmo/misc/sysmobts_par.h
new file mode 100644
index 00000000..52bf67df
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_par.h
@@ -0,0 +1,38 @@
+#ifndef _SYSMOBTS_PAR_H
+#define _SYSMOBTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+struct sysmobts_net_cfg;
+
+enum sysmobts_par {
+ SYSMOBTS_PAR_MAC,
+ SYSMOBTS_PAR_CLK_FACTORY,
+ SYSMOBTS_PAR_TEMP_DIG_MAX,
+ SYSMOBTS_PAR_TEMP_RF_MAX,
+ SYSMOBTS_PAR_SERNR,
+ SYSMOBTS_PAR_HOURS,
+ SYSMOBTS_PAR_BOOTS,
+ SYSMOBTS_PAR_KEY,
+ SYSMOBTS_PAR_MODEL_NR,
+ SYSMOBTS_PAR_MODEL_FLAGS,
+ SYSMOBTS_PAR_TRX_NR,
+ _NUM_SYSMOBTS_PAR
+};
+
+extern const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1];
+
+int sysmobts_par_get_int(enum sysmobts_par par, int *ret);
+int sysmobts_par_set_int(enum sysmobts_par par, int val);
+int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf,
+ unsigned int size);
+int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf,
+ unsigned int size);
+int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg);
+int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg);
+int sysmobts_get_type(int *bts_type);
+int sysmobts_get_trx(int *trx_number);
+char *sysmobts_model(int bts_type, int trx_num);
+int sysmobts_par_is_int(enum sysmobts_par par);
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_util.c b/src/osmo-bts-sysmo/misc/sysmobts_util.c
new file mode 100644
index 00000000..c9930d8f
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_util.c
@@ -0,0 +1,256 @@
+/* sysmobts-util - access to hardware related parameters */
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "sysmobts_par.h"
+#include "sysmobts_eeprom.h"
+
+enum act {
+ ACT_GET,
+ ACT_SET,
+ ACT_NET_GET,
+ ACT_NET_SET,
+};
+
+static enum act action;
+static char *write_arg;
+static int void_warranty;
+
+
+static struct in_addr net_ip = { 0, }, net_dns = { 0, }, net_gw = { 0, }, net_mask = { 0, };
+static uint8_t net_mode = 0;
+
+static void print_help()
+{
+ const struct value_string *par = sysmobts_par_names;
+
+ printf("sysmobts-util [--void-warranty -r | -w value] param_name\n");
+ printf("sysmobts-util --net-read\n");
+ printf("sysmobts-util --net-write --mode INT --ip IP_STR --gw IP_STR --dns IP_STR --net-mask IP_STR\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!sysmobts_par_is_int(par->value))
+ continue;
+ printf(" %s\n", par->str);
+ }
+}
+
+static int parse_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "read", 0, 0, 'r' },
+ { "void-warranty", 0, 0, 1000},
+ { "write", 1, 0, 'w' },
+ { "ip", 1, 0, 241 },
+ { "gw", 1, 0, 242 },
+ { "dns", 1, 0, 243 },
+ { "net-mask", 1, 0, 244 },
+ { "mode", 1, 0, 245 },
+ { "net-read", 0, 0, 246 },
+ { "net-write", 0, 0, 247 },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "rw:h",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'r':
+ action = ACT_GET;
+ break;
+ case 'w':
+ action = ACT_SET;
+ write_arg = optarg;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ break;
+ case 1000:
+ printf("Will void warranty on write.\n");
+ void_warranty = 1;
+ break;
+ case 246:
+ action = ACT_NET_GET;
+ break;
+ case 247:
+ action = ACT_NET_SET;
+ break;
+ case 245:
+ net_mode = atoi(optarg);
+ break;
+ case 244:
+ inet_aton(optarg, &net_mask);
+ break;
+ case 243:
+ inet_aton(optarg, &net_dns);
+ break;
+ case 242:
+ inet_aton(optarg, &net_gw);
+ break;
+ case 241:
+ inet_aton(optarg, &net_ip);
+ break;
+ default:
+ printf("Unknown option %d/%c\n", c, c);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static const char *make_addr(uint32_t saddr)
+{
+ struct in_addr addr;
+ addr.s_addr = ntohl(saddr);
+ return inet_ntoa(addr);
+}
+
+static void dump_net_cfg(struct sysmobts_net_cfg *net_cfg)
+{
+ if (net_cfg->mode == NET_MODE_DHCP) {
+ printf("IP=dhcp\n");
+ printf("DNS=\n");
+ printf("GATEWAY=\n");
+ printf("NETMASK=\n");
+ } else {
+ printf("IP=%s\n", make_addr(net_cfg->ip));
+ printf("GATEWAY=%s\n", make_addr(net_cfg->gw));
+ printf("DNS=%s\n", make_addr(net_cfg->dns));
+ printf("NETMASK=%s\n", make_addr(net_cfg->mask));
+ }
+}
+
+static int handle_net(void)
+{
+ struct sysmobts_net_cfg net_cfg;
+ int rc;
+
+ switch (action) {
+ case ACT_NET_GET:
+ rc = sysmobts_par_get_net(&net_cfg);
+ if (rc != 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ exit(rc);
+ }
+ dump_net_cfg(&net_cfg);
+ break;
+ case ACT_NET_SET:
+ memset(&net_cfg, 0, sizeof(net_cfg));
+ net_cfg.mode = net_mode;
+ net_cfg.ip = htonl(net_ip.s_addr);
+ net_cfg.mask = htonl(net_mask.s_addr);
+ net_cfg.gw = htonl(net_gw.s_addr);
+ net_cfg.dns = htonl(net_dns.s_addr);
+ printf("Going to write\n");
+ dump_net_cfg(&net_cfg);
+
+ rc = sysmobts_par_set_net(&net_cfg);
+ if (rc != 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ exit(rc);
+ }
+ break;
+ default:
+ printf("Unhandled action %d\n", action);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *parname;
+ enum sysmobts_par par;
+ int rc, val;
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ if (action > ACT_SET)
+ return handle_net();
+
+ if (optind >= argc && action <+ ACT_NET_GET) {
+ fprintf(stderr, "You must specify the parameter name\n");
+ exit(2);
+ }
+ parname = argv[optind];
+
+ rc = get_string_value(sysmobts_par_names, parname);
+ if (rc < 0) {
+ fprintf(stderr, "`%s' is not a valid parameter\n", parname);
+ exit(2);
+ } else
+ par = rc;
+
+ switch (action) {
+ case ACT_GET:
+ rc = sysmobts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("%d\n", val);
+ break;
+ case ACT_SET:
+ rc = sysmobts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
+ fprintf(stderr, "Parameter is already set!\r\n");
+ goto err;
+ }
+ rc = sysmobts_par_set_int(par, atoi(write_arg));
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("Success setting %s=%d\n", parname,
+ atoi(write_arg));
+ break;
+ default:
+ fprintf(stderr, "Unsupported action\n");
+ goto err;
+ }
+
+ exit(0);
+
+err:
+ exit(1);
+}
+
diff --git a/src/osmo-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c
new file mode 100644
index 00000000..ea7527dd
--- /dev/null
+++ b/src/osmo-bts-sysmo/oml.c
@@ -0,0 +1,1963 @@
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013-2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+#include <sysmocom/femtobts/superfemto.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "femtobts.h"
+#include "utils.h"
+
+static int mph_info_chan_confirm(struct gsm_lchan *lchan,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(lchan->ts->trx, &l1sap);
+}
+
+enum sapi_cmd_type {
+ SAPI_CMD_ACTIVATE,
+ SAPI_CMD_CONFIG_CIPHERING,
+ SAPI_CMD_CONFIG_LOGCH_PARAM,
+ SAPI_CMD_SACCH_REL_MARKER,
+ SAPI_CMD_REL_MARKER,
+ SAPI_CMD_DEACTIVATE,
+};
+
+struct sapi_cmd {
+ struct llist_head entry;
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+ enum sapi_cmd_type type;
+ int (*callback)(struct gsm_lchan *lchan, int status);
+};
+
+static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = GsmL1_LogChComb_0,
+ [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV,
+ [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I,
+ [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII,
+ [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0,
+ /*
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+ * part of this, only "real" pchan values will be looked up here.
+ * See the callers of ts_connect_as().
+ */
+};
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);
+
+static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct femtol1_hdl *gl1,
+ HANDLE hLayer3)
+{
+ prim->id = id;
+
+ /* for some reason the hLayer1 and hlayer3 fields are not always at the
+ * same position in the GsmL1_Prim_t, so we have to have this ugly case
+ * statement here... */
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq:
+ //prim->u.mphInitReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphInitReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseReq:
+ prim->u.mphCloseReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphCloseReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectReq:
+ prim->u.mphConnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectReq:
+ prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDisconnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateReq:
+ prim->u.mphActivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphActivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateReq:
+ prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDeactivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigReq:
+ prim->u.mphConfigReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConfigReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureReq:
+ prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphMeasureReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphInitCnf:
+ prim->u.mphInitCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseCnf:
+ prim->u.mphCloseCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectCnf:
+ prim->u.mphConnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ prim->u.mphDisconnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateCnf:
+ prim->u.mphActivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ prim->u.mphDeactivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigCnf:
+ prim->u.mphConfigCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureCnf:
+ prim->u.mphMeasureCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphTimeInd:
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ break;
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhDataReq:
+ prim->u.phDataReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id);
+ break;
+ }
+ return &prim->u;
+}
+
+static HANDLE l1p_handle_for_trx(struct gsm_bts_trx *trx)
+{
+ struct gsm_bts *bts = trx->bts;
+
+ osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit);
+ osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit);
+ osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit);
+
+ return bts->nr << 24
+ | trx->nr << 16;
+}
+
+static HANDLE l1p_handle_for_ts(struct gsm_bts_trx_ts *ts)
+{
+ osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit);
+
+ return l1p_handle_for_trx(ts->trx)
+ | ts->nr << 8;
+}
+
+
+static HANDLE l1p_handle_for_lchan(struct gsm_lchan *lchan)
+{
+ osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit);
+
+ return l1p_handle_for_ts(lchan->ts)
+ | lchan->nr;
+}
+
+GsmL1_Status_t prim_status(GsmL1_Prim_t *prim)
+{
+ /* for some reason the Status field is not always at the same position
+ * in the GsmL1_Prim_t, so we have to have this ugly case statement here... */
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.status;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.status;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.status;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.status;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.status;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.status;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.status;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.status;
+ default:
+ break;
+ }
+ return GsmL1_Status_Success;
+}
+
+#if 0
+static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data)
+{
+ struct msgb *resp_msg = data;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+
+ if (prim_status(l1p) != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(femtobts_l1prim_names, l1p->id),
+ get_value_string(femtobts_l1status_names, cc->status));
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ return abis_nm_sendmsg(msg);
+}
+#endif
+
+int lchan_activate(struct gsm_lchan *lchan);
+
+static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_Status_t status = prim_status(l1p);
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(femtobts_l1prim_names, l1p->id),
+ get_value_string(femtobts_l1status_names, status));
+ msgb_free(l1_msg);
+ return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM);
+ }
+
+ msgb_free(l1_msg);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
+ mo->obj_inst.ts_nr == 0) {
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
+ DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n");
+ mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ if (cbch) {
+ cbch->rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(cbch);
+ }
+ }
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(mo);
+}
+
+static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_abis_mo *mo;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+
+ mo = &trx->ts[cnf->u8Tn].mo;
+ return opstart_compl(mo, l1_msg);
+}
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-MUTE failure");
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+#endif
+
+static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf;
+
+ LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n",
+ get_value_string(femtobts_l1status_names, ic->status));
+
+ /* store layer1 handle */
+ if (ic->status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n",
+ get_value_string(femtobts_l1status_names, ic->status));
+ bts_shutdown(trx->bts, "MPH-INIT failure");
+ }
+
+ fl1h->hLayer1 = ic->hLayer1;
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ /* If the TRX was already locked the MphInit would have undone it */
+ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
+ trx_rf_lock(trx, 1, trx_mute_on_init_cb);
+#endif
+
+ /* Begin to ramp up the power */
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+
+ return opstart_compl(&trx->mo, l1_msg);
+}
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids,
+ unsigned int num_attr_ids)
+{
+ unsigned int i;
+
+ if (!mo->nm_attr)
+ return 0;
+
+ for (i = 0; i < num_attr_ids; i++) {
+ if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg;
+ GsmL1_MphInitReq_t *mi_req;
+ GsmL1_DeviceParam_t *dev_par;
+ int femto_band;
+ int initial_mdBm = power_ramp_initial_power_mdBm(trx);
+
+ if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
+ ARRAY_SIZE(trx_rqd_attr))) {
+ /* HACK: spec says we need to decline, but openbsc
+ * doesn't deal with this very well */
+ return oml_mo_opstart_ack(&trx->mo);
+ //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
+ }
+
+ femto_band = sysmobts_select_femto_band(trx, trx->arfcn);
+ if (femto_band < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n",
+ gsm_band_name(trx->bts->band));
+ }
+
+ msg = l1p_msgb_alloc();
+ mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h,
+ l1p_handle_for_trx(trx));
+ dev_par = &mi_req->deviceParam;
+ dev_par->devType = GsmL1_DevType_TxdRxu;
+ dev_par->freqBand = femto_band;
+ dev_par->u16Arfcn = trx->arfcn;
+ dev_par->u16BcchArfcn = trx->bts->c0->arfcn;
+ dev_par->u8NbTsc = trx->bts->bsic & 7;
+ dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx)
+ ? 0.0 : trx->bts->ul_power_target;
+
+ dev_par->fTxPowerLevel = ((float) initial_mdBm) / 1000;
+ LOGP(DL1C, LOGL_NOTICE, "Init TRX (ARFCN %u, TSC %u, RxPower % 2f dBm, "
+ "TxPower % 2.2f dBm\n", dev_par->u16Arfcn, dev_par->u8NbTsc,
+ dev_par->fRxPowerLevel, dev_par->fTxPowerLevel);
+ trx->power_params.p_total_cur_mdBm = trx->power_params.ramp.max_initial_pout_mdBm;
+
+ /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */
+ return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
+}
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ return fl1h->hLayer1;
+}
+
+static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ msgb_free(l1_msg);
+ return 0;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg;
+
+ msg = l1p_msgb_alloc();
+ prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h,
+ l1p_handle_for_trx(trx));
+ LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr);
+
+ return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+}
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ uint8_t mute[8];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mute); ++i)
+ mute[i] = locked ? 1 : 0;
+
+ return l1if_mute_rf(fl1h, mute, cb);
+}
+
+int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
+ int success)
+{
+ if (success) {
+ int i;
+ int is_locked = 1;
+
+ for (i = 0; i < 8; ++i)
+ if (!mute_state[i])
+ is_locked = 0;
+
+ mo->nm_state.administrative =
+ is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_ack(mo);
+ } else {
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+ }
+}
+
+static int ts_connect_as(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan,
+ l1if_compl_cb *cb, void *data)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx);
+ GsmL1_MphConnectReq_t *cr;
+
+ if (pchan == GSM_PCHAN_TCH_F_PDCH
+ || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Requested TS connect as %s,"
+ " expected a specific pchan instead\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan));
+ return -EINVAL;
+ }
+
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+ cr->logChComb = pchan_to_logChComb[pchan];
+
+ DEBUGP(DL1C, "%s pchan=%s ts_connect_as(%s) logChComb=%s\n",
+ gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(pchan), get_value_string(femtobts_chcomb_names,
+ cr->logChComb));
+
+ return l1if_gsm_req_compl(fl1h, msg, cb, NULL);
+}
+
+static int ts_opstart(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan = ts->pchan;
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* First connect as NONE, until first RSL CHAN ACT. */
+ pchan = GSM_PCHAN_NONE;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* First connect as TCH/F, expecting PDCH ACT. */
+ pchan = GSM_PCHAN_TCH_F;
+ break;
+ default:
+ /* simply use ts->pchan */
+ break;
+ }
+ return ts_connect_as(ts, pchan, opstart_compl_cb, NULL);
+}
+
+GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
+{
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return GsmL1_Sapi_TchF;
+ case GSM_LCHAN_TCH_H:
+ return GsmL1_Sapi_TchH;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+ return GsmL1_Sapi_Idle;
+}
+
+GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = lchan->ts->pchan;
+
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ pchan = lchan->ts->dyn.pchan_want;
+
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ if (lchan->type == GSM_LCHAN_CCCH)
+ return GsmL1_SubCh_NA;
+ /* fall-through */
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ return lchan->nr;
+ case GSM_PCHAN_NONE:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_UNKNOWN:
+ default:
+ /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ return GsmL1_SubCh_NA;
+ }
+
+ return GsmL1_SubCh_NA;
+}
+
+struct sapi_dir {
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+};
+
+static const struct sapi_dir ccch_sapis[] = {
+ { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchf_sapis[] = {
+ { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchh_sapis[] = {
+ { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir sdcch_sapis[] = {
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir cbch_sapis[] = {
+ { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink },
+ /* Does the CBCH really have a SACCH in Downlink? */
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+};
+
+static const struct sapi_dir pdtch_sapis[] = {
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink },
+#if 0
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink },
+#endif
+};
+
+static const struct sapi_dir ho_sapis[] = {
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+struct lchan_sapis {
+ const struct sapi_dir *sapis;
+ unsigned int num_sapis;
+};
+
+static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
+ [GSM_LCHAN_SDCCH] = {
+ .sapis = sdcch_sapis,
+ .num_sapis = ARRAY_SIZE(sdcch_sapis),
+ },
+ [GSM_LCHAN_TCH_F] = {
+ .sapis = tchf_sapis,
+ .num_sapis = ARRAY_SIZE(tchf_sapis),
+ },
+ [GSM_LCHAN_TCH_H] = {
+ .sapis = tchh_sapis,
+ .num_sapis = ARRAY_SIZE(tchh_sapis),
+ },
+ [GSM_LCHAN_CCCH] = {
+ .sapis = ccch_sapis,
+ .num_sapis = ARRAY_SIZE(ccch_sapis),
+ },
+ [GSM_LCHAN_PDTCH] = {
+ .sapis = pdtch_sapis,
+ .num_sapis = ARRAY_SIZE(pdtch_sapis),
+ },
+ [GSM_LCHAN_CBCH] = {
+ .sapis = cbch_sapis,
+ .num_sapis = ARRAY_SIZE(cbch_sapis),
+ },
+};
+
+static const struct lchan_sapis sapis_for_ho = {
+ .sapis = ho_sapis,
+ .num_sapis = ARRAY_SIZE(ho_sapis),
+};
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir);
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan);
+
+/**
+ * Execute the first SAPI command of the queue. In case of the markers
+ * this method is re-entrant so we need to make sure to remove a command
+ * from the list before calling a function that will queue a command.
+ *
+ * \return 0 in case no Gsm Request was sent, 1 otherwise
+ */
+static int sapi_queue_exeute(struct gsm_lchan *lchan)
+{
+ int res;
+ struct sapi_cmd *cmd;
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+
+ switch (cmd->type) {
+ case SAPI_CMD_ACTIVATE:
+ mph_send_activate_req(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_CIPHERING:
+ mph_send_config_ciphering(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_LOGCH_PARAM:
+ mph_send_config_logchpar(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_SACCH_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_TxDownlink);
+ res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_RxUplink);
+ break;
+ case SAPI_CMD_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = lchan_deactivate_sapis(lchan);
+ break;
+ case SAPI_CMD_DEACTIVATE:
+ mph_send_deactivate_req(lchan, cmd);
+ res = 1;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE,
+ "Unimplemented command type %d\n", cmd->type);
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = 0;
+ abort();
+ break;
+ }
+
+ return res;
+}
+
+static void sapi_queue_send(struct gsm_lchan *lchan)
+{
+ int res;
+
+ do {
+ res = sapi_queue_exeute(lchan);
+ } while (res == 0 && !llist_empty(&lchan->sapi_cmds));
+}
+
+static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
+{
+ int end;
+ struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
+ struct sapi_cmd, entry);
+ llist_del(&cmd->entry);
+ end = llist_empty(&lchan->sapi_cmds);
+
+ if (cmd->callback)
+ cmd->callback(lchan, status);
+ talloc_free(cmd);
+
+ if (end || llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_DEBUG,
+ "%s End of SAPI cmd queue encountered.%s\n",
+ gsm_lchan_name(lchan),
+ llist_empty(&lchan->sapi_cmds)
+ ? " Queue is now empty."
+ : " More pending.");
+ return;
+ }
+
+ sapi_queue_send(lchan);
+}
+
+/**
+ * Queue and possible execute a SAPI command. Return 1 in case the command was
+ * already executed and 0 in case if it was only put into the queue
+ */
+static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ int start = llist_empty(&lchan->sapi_cmds);
+ llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
+
+ if (!start)
+ return 0;
+
+ sapi_queue_send(lchan);
+ return 1;
+}
+
+static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_ASSIGNED;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(femtobts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_ACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan)
+{
+ return 0xBB
+ | (lchan->nr << 8)
+ | (lchan->ts->nr << 16)
+ | (lchan->ts->trx->nr << 24);
+}
+
+/* obtain a ptr to the lapdm_channel for a given hLayer */
+struct gsm_lchan *
+l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2)
+{
+ uint8_t magic = hLayer2 & 0xff;
+ uint8_t ts_nr = (hLayer2 >> 16) & 0xff;
+ uint8_t lchan_nr = (hLayer2 >> 8)& 0xff;
+ struct gsm_bts_trx_ts *ts;
+
+ if (magic != 0xBB)
+ return NULL;
+
+ /* FIXME: if we actually run on the BTS, the 32bit field is large
+ * enough to simply put a pointer inside. */
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+/* we regularly check if the DSP L1 is still sending us primitives.
+ * if not, we simply stop the BTS program (and be re-spawned) */
+static void alive_timer_cb(void *data)
+{
+ struct femtol1_hdl *fl1h = data;
+
+ if (fl1h->alive_prim_cnt == 0) {
+ LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n");
+ exit(23);
+ }
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+}
+
+static void clear_amr_params(GsmL1_LogChParam_t *lch_par)
+{
+ int j;
+ /* common for the SIGN, V1 and EFR: */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA;
+ lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset;
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+}
+
+static void set_payload_format(GsmL1_LogChParam_t *lch_par)
+{
+#ifdef L1_HAS_RTP_MODE
+#ifdef USE_L1_RTP_MODE
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp;
+#else
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_If2;
+#endif /* USE_L1_RTP_MODE */
+#endif /* L1_HAS_RTP_MODE */
+}
+
+static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+ int j;
+
+ LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
+ gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ /* we have to set some TCH payload type even if we don't
+ * know yet what codec we will use later on */
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Efr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Amr;
+ set_payload_format(lch_par);
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */
+ lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
+
+ /* initialize to clean state */
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+
+ j = 0;
+ if (mr_conf->m4_75)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_15)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_90)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m6_70)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_40)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_95)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m10_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+ if (mr_conf->m12_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2;
+ break;
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ int sapi = cmd->sapi;
+ int dir = cmd->dir;
+ GsmL1_MphActivateReq_t *act_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ lch_par = &act_req->logChPrm;
+ act_req->u8Tn = lchan->ts->nr;
+ act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ act_req->dir = dir;
+ act_req->sapi = sapi;
+ act_req->hLayer2 = l1if_lchan_to_hLayer(lchan);
+ act_req->hLayer3 = act_req->hLayer2;
+
+ switch (act_req->sapi) {
+ case GsmL1_Sapi_Rach:
+ lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Agch:
+ lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name);
+ break;
+ case GsmL1_Sapi_TchH:
+ case GsmL1_Sapi_TchF:
+ lchan2lch_par(lch_par, lchan);
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Prach:
+ lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Sacch:
+ /*
+ * For the SACCH we need to set the u8MsPowerLevel when
+ * doing manual MS power control.
+ */
+ if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+ /* fall through */
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ default:
+ break;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ gsm_lchan_name(lchan), act_req->hLayer2,
+ get_value_string(femtobts_l1sapi_names, act_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, act_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
+}
+
+static void sapi_clear_queue(struct llist_head *queue)
+{
+ struct sapi_cmd *next, *tmp;
+
+ llist_for_each_entry_safe(next, tmp, queue, entry) {
+ llist_del(&next->entry);
+ talloc_free(next);
+ }
+}
+
+static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+
+ /* FIXME: Error handling */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s act failed mark broken due status: %d\n",
+ gsm_lchan_name(lchan), status);
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
+
+ /* set the initial ciphering parameters for both directions */
+ l1if_set_ciphering(fl1h, lchan, 1);
+ l1if_set_ciphering(fl1h, lchan, 0);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ return 0;
+}
+
+static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_ACTIVATE;
+ cmd->callback = sapi_activate_cb;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_activate(struct gsm_lchan *lchan)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Trying to activate lchan, but commands in queue\n",
+ gsm_lchan_name(lchan));
+
+ /* override the regular SAPIs if this is the first hand-over
+ * related activation of the LCHAN */
+ if (lchan->ho.active == HANDOVER_ENABLED)
+ s4l = &sapis_for_ho;
+
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+
+ if (sapi == GsmL1_Sapi_Sch) {
+ /* once we activate the SCH, we should get MPH-TIME.ind */
+ fl1h->alive_timer.cb = alive_timer_cb;
+ fl1h->alive_timer.data = fl1h;
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+ }
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+#warning "FIXME: Should this be in sapi_activate_cb?"
+ lchan_init_lapdm(lchan);
+
+ return 0;
+}
+
+const struct value_string femtobts_l1cfgt_names[] = {
+ { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" },
+ { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" },
+ { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" },
+ { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
+ { 0, NULL }
+};
+
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi)
+{
+ int i;
+
+ switch (sapi) {
+ case GsmL1_Sapi_Rach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic);
+ break;
+ case GsmL1_Sapi_Agch:
+ LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ",
+ lch_par->agch.u8NbrOfAgch);
+ break;
+ case GsmL1_Sapi_Sacch:
+ LOGPC(DL1C, logl, "MS Power Level 0x%02x",
+ lch_par->sacch.u8MsPowerLevel);
+ break;
+ case GsmL1_Sapi_TchF:
+ case GsmL1_Sapi_TchH:
+ LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (",
+ lch_par->tch.amrCmiPhase,
+ lch_par->tch.amrInitCodecMode);
+ for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) {
+ LOGPC(DL1C, logl, "%x ",
+ lch_par->tch.amrActiveCodecSet[i]);
+ }
+ break;
+ case GsmL1_Sapi_Ptcch:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->ptcch.u8Bsic);
+ break;
+ case GsmL1_Sapi_Prach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->prach.u8Bsic);
+ break;
+ default:
+ break;
+ }
+ LOGPC(DL1C, logl, ")\n");
+}
+
+static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_trx_name(trx),
+ get_value_string(femtobts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n",
+ cc->cfgParams.setTxPowerLevel.fTxPowerLevel);
+
+ power_trx_change_compl(trx,
+ (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000));
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, cc->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", cc->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1cfgt_names, cc->cfgParamId));
+
+ switch (cc->cfgParamId) {
+ case GsmL1_ConfigParamId_SetLogChParams:
+ dump_lch_par(LOGL_INFO,
+ &cc->cfgParams.setLogChParams.logChParams,
+ cc->cfgParams.setLogChParams.sapi);
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetCipheringParams:
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_RX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ break;
+ case LCHAN_CIPH_RX_CONF_TX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ break;
+ case LCHAN_CIPH_RXTX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ break;
+ case LCHAN_CIPH_NONE:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ default:
+ LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
+ break;
+ }
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetNbTsc:
+ default:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ }
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams;
+ conf_req->cfgParams.setLogChParams.sapi = cmd->sapi;
+ conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr;
+ conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ conf_req->cfgParams.setLogChParams.dir = cmd->dir;
+ conf_req->hLayer3 = l1if_lchan_to_hLayer(lchan);
+
+ lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
+ lchan2lch_par(lch_par, lchan);
+
+ /* Update the MS Power Level */
+ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+
+ /* FIXME: update encryption */
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names,
+ conf_req->cfgParams.setLogChParams.sapi));
+ LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ",
+ conf_req->cfgParams.setLogChParams.u8Tn,
+ conf_req->cfgParams.setLogChParams.subCh,
+ conf_req->cfgParams.setLogChParams.dir);
+ dump_lch_par(LOGL_INFO,
+ &conf_req->cfgParams.setLogChParams.logChParams,
+ conf_req->cfgParams.setLogChParams.sapi);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->sapi = sapi;
+ cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
+ queue_sapi_command(lchan, cmd);
+}
+
+static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
+{
+ enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan));
+ return 0;
+}
+
+int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel;
+ conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
+}
+
+const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
+ [0] = GsmL1_CipherId_A50,
+ [1] = GsmL1_CipherId_A50,
+ [2] = GsmL1_CipherId_A51,
+ [3] = GsmL1_CipherId_A52,
+ [4] = GsmL1_CipherId_A53,
+};
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ struct GsmL1_MphConfigReq_t *cfgr;
+
+ cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+
+ cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams;
+ cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr;
+ cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ cfgr->cfgParams.setCipheringParams.dir = cmd->dir;
+ cfgr->hLayer3 = l1if_lchan_to_hLayer(lchan);
+
+ if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph))
+ return -EINVAL;
+ cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id];
+
+ LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n",
+ gsm_lchan_name(lchan),
+ cfgr->cfgParams.setCipheringParams.cipherId,
+ get_value_string(femtobts_dir_names,
+ cfgr->cfgParams.setCipheringParams.dir));
+
+ memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
+ lchan->encr.key, lchan->encr.key_len);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_CIPHERING;
+ queue_sapi_command(lchan, cmd);
+}
+
+int l1if_set_ciphering(struct femtol1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink)
+{
+ int dir;
+
+ /* ignore the request when the channel is not active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ if (dir_downlink)
+ dir = GsmL1_Dir_TxDownlink;
+ else
+ dir = GsmL1_Dir_RxUplink;
+
+ enqueue_sapi_ciphering_cmd(lchan, dir);
+
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch);
+ return 0;
+}
+
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink);
+ tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink);
+
+ /* FIXME: update encryption */
+
+ return 0;
+}
+
+static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf;
+
+ lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_NONE;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(femtobts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got de-activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_DEACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+ return 0;
+}
+
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphDeactivateReq_t *deact_req;
+
+ deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ deact_req->u8Tn = lchan->ts->nr;
+ deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ deact_req->dir = cmd->dir;
+ deact_req->sapi = cmd->sapi;
+ deact_req->hLayer3 = l1if_lchan_to_hLayer(lchan);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names, deact_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, deact_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
+}
+
+static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
+{
+ /* FIXME: Error handling. There is no NACK... */
+ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ /* Don't send an REL ACK on SACCH deactivate */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+
+ /* Reactivate CCCH due to SI3 update in RSL */
+ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+ lchan_activate(lchan);
+ }
+ return 0;
+}
+
+static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_DEACTIVATE;
+ cmd->callback = sapi_deactivate_cb;
+ return queue_sapi_command(lchan, cmd);
+}
+
+/*
+ * Release the SAPI if it was allocated. E.g. the SACCH might be already
+ * deactivated or during a hand-over the TCH was not allocated yet.
+ */
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ /* check if we should schedule a release */
+ if (dir & GsmL1_Dir_TxDownlink) {
+ if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
+ } else if (dir & GsmL1_Dir_RxUplink) {
+ if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
+ }
+
+ /* now schedule the command and maybe dispatch it */
+ return enqueue_sapi_deact_cmd(lchan, sapi, dir);
+}
+
+static int release_sapis_for_ho(struct gsm_lchan *lchan)
+{
+ int res = 0;
+ int i;
+
+ const struct lchan_sapis *s4l = &sapis_for_ho;
+
+ for (i = s4l->num_sapis-1; i >= 0; i--)
+ res |= check_sapi_release(lchan,
+ s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ return res;
+}
+
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ int i, res;
+
+ res = 0;
+
+ /* The order matters.. the Facch needs to be released first */
+ for (i = s4l->num_sapis-1; i >= 0; i--) {
+ /* Stop the alive timer once we deactivate the SCH */
+ if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch)
+ osmo_timer_del(&fl1h->alive_timer);
+
+ /* Release if it was allocated */
+ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ }
+
+ /* always attempt to disable the RACH burst */
+ res |= release_sapis_for_ho(lchan);
+
+ /* nothing was queued */
+ if (res == 0) {
+ LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ }
+
+ return res;
+}
+
+static void enqueue_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to release all active SAPIs */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ enqueue_rel_marker(lchan);
+ return 0;
+}
+
+static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to check if the SACCH is allocated */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_SACCH_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ enqueue_sacch_rel_marker(lchan);
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ /* FIXME: more checks if the attributes are valid */
+
+ switch (msg_type) {
+ case NM_MT_SET_CHAN_ATTR:
+ /* our L1 only supports one global TSC for all channels
+ * one one TRX, so we need to make sure not to activate
+ * channels with a different TSC!! */
+ if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) &&
+ *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) {
+ LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n",
+ *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7);
+ return -NM_NACK_PARAM_RANGE;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ if (kind == NM_OC_RADIO_CARRIER) {
+ struct gsm_bts_trx *trx = obj;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ /* Did we go through MphInit yet? If yes fire and forget */
+ if (fl1h->hLayer1)
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ }
+
+ /* FIXME: we actaully need to send a ACK or NACK for the OML message */
+ return oml_fom_ack_nack(msg, 0);
+}
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ rc = ts_opstart(obj);
+ break;
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
+ rc = oml_mo_opstart_ack(mo);
+ if (mo->obj_class == NM_OC_BTS) {
+ oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK);
+ }
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ int rc = -EINVAL;
+ int granted = 0;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+
+ if (mo->procedure_pending) {
+ LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
+ "pending procedure on RC %d\n",
+ ((struct gsm_bts_trx *)obj)->nr);
+ return 0;
+ }
+ mo->procedure_pending = 1;
+ switch (adm_state) {
+ case NM_STATE_LOCKED:
+ rc = trx_rf_lock(obj, 1, NULL);
+ break;
+ case NM_STATE_UNLOCKED:
+ rc = trx_rf_lock(obj, 0, NULL);
+ break;
+ default:
+ granted = 1;
+ break;
+ }
+
+ if (!granted && rc == 0)
+ /* in progress, will send ack/nack after completion */
+ return 0;
+
+ mo->procedure_pending = 0;
+
+ break;
+ default:
+ /* blindly accept all state changes */
+ granted = 1;
+ break;
+ }
+
+ if (granted) {
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+ } else
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+
+}
+
+int l1if_rsl_chan_act(struct gsm_lchan *lchan)
+{
+ //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE);
+ //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE);
+ lchan_activate(lchan);
+ return 0;
+}
+
+/**
+ * Modify the given lchan in the handover scenario. This is a lot like
+ * second channel activation but with some additional activation.
+ */
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan)
+{
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ if (lchan->ho.active == HANDOVER_NONE)
+ return -1;
+
+ LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n",
+ gsm_lchan_name(lchan));
+
+ /* Give up listening to RACH bursts */
+ release_sapis_for_ho(lchan);
+
+ /* Activate the normal SAPIs */
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+ return 0;
+}
+
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
+{
+ /* A duplicate RF Release Request, ignore it */
+ if (lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ lchan_deactivate(lchan);
+ return 0;
+}
+
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+ /* Only de-activate the SACCH if the lchan is active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return 0;
+ return bts_model_lchan_deactivate_sacch(lchan);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+
+ return l1if_activate_rf(fl1, 0);
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ return l1if_set_txpower(trx_femtol1_hdl(trx), ((float) p_trxout_mdBm)/1000.0);
+}
+
+static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n",
+ gsm_lchan_name(ts->lchan));
+
+ cb_ts_disconnected(ts);
+
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx);
+ GsmL1_MphDisconnectReq_t *cr;
+
+ DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+
+ return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL);
+}
+
+static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n",
+ gsm_lchan_name(ts->lchan),
+ gsm_pchan_name(ts->pchan),
+ ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
+ ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
+ ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : "");
+
+ cb_ts_connected(ts, 0);
+
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+
+ rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL);
+ if (rc)
+ cb_ts_connected(ts, rc);
+}
diff --git a/src/osmo-bts-sysmo/oml_router.c b/src/osmo-bts-sysmo/oml_router.c
new file mode 100644
index 00000000..f3d08373
--- /dev/null
+++ b/src/osmo-bts-sysmo/oml_router.c
@@ -0,0 +1,129 @@
+/* Beginnings of an OML router */
+
+/* (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "oml_router.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = oml_msgb_alloc();
+ if (!msg) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n");
+ return -1;
+ }
+
+ rc = recv(fd->fd, msg->tail, msg->data_len, 0);
+ if (rc <= 0) {
+ close(fd->fd);
+ osmo_fd_unregister(fd);
+ fd->fd = -1;
+ goto err;
+ }
+
+ msg->l1h = msgb_put(msg, rc);
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid IPA message rc(%d)\n", rc);
+ goto err;
+ }
+
+ rc = msg_verify_oml_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid OML message rc(%d)\n", rc);
+ goto err;
+ }
+
+ /* todo dispatch message */
+
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what)
+{
+ int fd;
+ struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data;
+
+ /* Accept only one connection at a time. De-register it */
+ if (read_fd->fd > -1) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "New OML router connection. Closing old one.\n");
+ close(read_fd->fd);
+ osmo_fd_unregister(read_fd);
+ read_fd->fd = -1;
+ }
+
+ fd = accept(accept_fd->fd, NULL, NULL);
+ if (fd < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n",
+ strerror(errno));
+ return -1;
+ }
+
+ read_fd->fd = fd;
+ if (osmo_fd_register(read_fd) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n");
+ close(fd);
+ read_fd->fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+int oml_router_init(struct gsm_bts *bts, const char *path,
+ struct osmo_fd *accept_fd, struct osmo_fd *read_fd)
+{
+ int rc;
+
+ memset(accept_fd, 0, sizeof(*accept_fd));
+ memset(read_fd, 0, sizeof(*read_fd));
+
+ accept_fd->cb = oml_router_accept_cb;
+ accept_fd->data = read_fd;
+
+ read_fd->cb = oml_router_read_cb;
+ read_fd->data = bts;
+ read_fd->when = BSC_FD_READ;
+ read_fd->fd = -1;
+
+ rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0,
+ path,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+ return rc;
+}
diff --git a/src/osmo-bts-sysmo/oml_router.h b/src/osmo-bts-sysmo/oml_router.h
new file mode 100644
index 00000000..55f0681d
--- /dev/null
+++ b/src/osmo-bts-sysmo/oml_router.h
@@ -0,0 +1,13 @@
+#pragma once
+
+struct gsm_bts;
+struct osmo_fd;
+
+/**
+ * The default path sysmobts will listen for incoming
+ * registrations for OML routing and sending.
+ */
+#define OML_ROUTER_PATH "/var/run/sysmobts_oml_router"
+
+
+int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read);
diff --git a/src/osmo-bts-sysmo/sysmobts_ctrl.c b/src/osmo-bts-sysmo/sysmobts_ctrl.c
new file mode 100644
index 00000000..21df88e5
--- /dev/null
+++ b/src/osmo-bts-sysmo/sysmobts_ctrl.c
@@ -0,0 +1,274 @@
+/* Control Interface for sysmoBTS */
+
+/* (C) 2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+
+
+/* for control interface */
+
+#ifndef HW_SYSMOBTS_V1
+CTRL_CMD_DEFINE(clock_info, "clock-info");
+static int ctrl_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ LOGP(DL1C, LOGL_NOTICE,
+ "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrx.clkSrc),
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc));
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%d,%s,%d,%d,%s",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrx.clkSrc),
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc));
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int get_clock_info(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 0;
+
+ return l1if_req_compl(fl1h, msg, ctrl_clkinfo_cb, cd);
+}
+static int ctrl_set_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ cmd->reply = "success";
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ msgb_free(resp);
+ return 0;
+}
+
+static int set_clock_info(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ /* Set GPS/PPS as reference */
+ sysp->id = SuperFemto_PrimId_RfClockSetupReq;
+ sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h);
+ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src;
+ sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps;
+ l1if_req_compl(fl1h, msg, clock_setup_cb, NULL);
+
+ /* Reset the error counters */
+ msg = sysp_msgb_alloc();
+ sysp = msgb_sysprim(msg);
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 1;
+
+ l1if_req_compl(fl1h, msg, ctrl_set_clkinfo_cb, cd);
+
+ return CTRL_CMD_HANDLED;
+}
+
+static int verify_clock_info(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return 0;
+}
+
+
+CTRL_CMD_DEFINE(clock_corr, "clock-correction");
+static int ctrl_get_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%d",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor);
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int get_clock_corr(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ /* we could theoretically simply respond with a cached value, but I
+ * prefer to to ask the actual L1 about the currently used value to
+ * avoid any mistakes */
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 0;
+
+ l1if_req_compl(fl1h, msg, ctrl_get_clkcorr_cb, cd);
+
+ return CTRL_CMD_HANDLED;
+}
+static int ctrl_set_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) {
+ cmd->type = CTRL_CMD_ERROR;
+ cmd->reply = "Error setting new correction value.";
+ } else
+ cmd->reply = "success";
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int set_clock_corr(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ fl1h->clk_cal = atoi(cmd->value);
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ sysp->id = SuperFemto_PrimId_RfClockSetupReq;
+ sysp->u.rfClockSetupReq.rfTrx.iClkCor = fl1h->clk_cal;
+ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src;
+ sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps;
+
+ l1if_req_compl(fl1h, msg, ctrl_set_clkcorr_cb, cd);
+
+ return CTRL_CMD_HANDLED;
+}
+
+static int verify_clock_corr(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ /* FIXME: check the range */
+ return 0;
+}
+#endif /* HW_SYSMOBTS_V1 */
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ int rc = 0;
+
+#ifndef HW_SYSMOBTS_V1
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_info);
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_corr);
+#endif /* HW_SYSMOBTS_V1 */
+
+ return rc;
+}
diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c
new file mode 100644
index 00000000..b105bf4d
--- /dev/null
+++ b/src/osmo-bts-sysmo/sysmobts_vty.c
@@ -0,0 +1,542 @@
+/* VTY interface for sysmoBTS */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012,2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/rsl.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+extern int lchan_activate(struct gsm_lchan *lchan);
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+#define DSP_TRACE_F_STR "DSP Trace Flag\n"
+
+static struct gsm_bts *vty_bts;
+
+/* configuration */
+
+DEFUN(cfg_phy_clkcal_eeprom, cfg_phy_clkcal_eeprom_cmd,
+ "clock-calibration eeprom",
+ "Use the eeprom clock calibration value\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_clkcal_def, cfg_phy_clkcal_def_cmd,
+ "clock-calibration default",
+ "Set the clock calibration value\n" "Default Clock DAC value\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 0;
+ pinst->u.sysmobts.clk_cal = 0xffff;
+
+ return CMD_SUCCESS;
+}
+
+#ifdef HW_SYSMOBTS_V1
+DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd,
+ "clock-calibration <0-4095>",
+ "Set the clock calibration value\n" "Clock DAC value\n")
+{
+ unsigned int clkcal = atoi(argv[0]);
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 0;
+ pinst->u.sysmobts.clk_cal = clkcal & 0xfff;
+
+ return CMD_SUCCESS;
+}
+#else
+DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd,
+ "clock-calibration <-4095-4095>",
+ "Set the clock calibration value\n" "Offset in PPB\n")
+{
+ int clkcal = atoi(argv[0]);
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 0;
+ pinst->u.sysmobts.clk_cal = clkcal;
+
+ return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(cfg_phy_clksrc, cfg_phy_clksrc_cmd,
+ "clock-source (tcxo|ocxo|ext|gps)",
+ "Set the clock source value\n"
+ "Use the TCXO\n"
+ "Use the OCXO\n"
+ "Use an external clock\n"
+ "Use the GPS pps\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int rc;
+
+ rc = get_string_value(femtobts_clksrc_names, argv[0]);
+ if (rc < 0)
+ return CMD_WARNING;
+
+ pinst->u.sysmobts.clk_src = rc;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd,
+ "trx-calibration-path PATH",
+ "Set the path name to TRX calibration data\n" "Path name\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ if (pinst->u.sysmobts.calib_path)
+ talloc_free(pinst->u.sysmobts.calib_path);
+
+ pinst->u.sysmobts.calib_path = talloc_strdup(pinst, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_trx_ul_power_target, cfg_trx_ul_power_target_cmd,
+ "uplink-power-target <-110-0>",
+ "Obsolete alias for bts uplink-power-target\n"
+ "Target uplink Rx level in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->bts->ul_power_target = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
+ "nominal-tx-power <0-100>",
+ "Set the nominal transmit output power in dBm\n"
+ "Nominal transmit output power level in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->nominal_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
+ "HIDDEN", TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ pinst->u.sysmobts.dsp_trace_f |= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
+ "HIDDEN", NO_STR TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ pinst->u.sysmobts.dsp_trace_f &= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+/* runtime */
+
+DEFUN(show_phy_clksrc, show_trx_clksrc_cmd,
+ "show phy <0-255> clock-source",
+ SHOW_TRX_STR "Display the clock source for this TRX")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst = vty_get_phy_instance(vty, phy_nr, 0);
+
+ if (!pinst)
+ return CMD_WARNING;
+
+ vty_out(vty, "PHY Clock Source: %s%s",
+ get_value_string(femtobts_clksrc_names,
+ pinst->u.sysmobts.clk_src), VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
+ "show trx <0-0> dsp-trace-flags",
+ SHOW_TRX_STR "Display the current setting of the DSP trace flags")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct femtol1_hdl *fl1h;
+ int i;
+
+ if (!trx)
+ return CMD_WARNING;
+
+ fl1h = trx_femtol1_hdl(trx);
+
+ vty_out(vty, "Femto L1 DSP trace flags:%s", VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(femtobts_tracef_names); i++) {
+ const char *endis;
+
+ if (femtobts_tracef_names[i].value == 0 &&
+ femtobts_tracef_names[i].str == NULL)
+ break;
+
+ if (fl1h->dsp_trace_f & femtobts_tracef_names[i].value)
+ endis = "enabled";
+ else
+ endis = "disabled";
+
+ vty_out(vty, "DSP Trace %-15s %s%s",
+ femtobts_tracef_names[i].str, endis,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct femtol1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.sysmobts.hdl;
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct femtol1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.sysmobts.hdl;
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sys_info, show_sys_info_cmd,
+ "show phy <0-255> instance <0-255> system-information",
+ SHOW_TRX_STR "Display information about system\n")
+{
+ int phy_nr = atoi(argv[0]);
+ int inst_nr = atoi(argv[1]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ struct phy_instance *pinst;
+ struct femtol1_hdl *fl1h;
+ int i;
+
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY link %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "Cannot find PHY instance %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ fl1h = pinst->u.sysmobts.hdl;
+
+ vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
+ fl1h->hw_info.dsp_version[0],
+ fl1h->hw_info.dsp_version[1],
+ fl1h->hw_info.dsp_version[2],
+ fl1h->hw_info.fpga_version[0],
+ fl1h->hw_info.fpga_version[1],
+ fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
+
+ vty_out(vty, "GSM Band Support: ");
+ for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
+ if (fl1h->hw_info.band_support & (1 << i))
+ vty_out(vty, "%s ", gsm_band_name(1 << i));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(activate_lchan, activate_lchan_cmd,
+ "trx <0-0> <0-7> (activate|deactivate) <0-7>",
+ TRX_STR
+ "Timeslot number\n"
+ "Activate Logical Channel\n"
+ "Deactivate Logical Channel\n"
+ "Logical Channel Number\n" )
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[3]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ if (!strcmp(argv[2], "activate"))
+ lchan_activate(lchan);
+ else
+ lchan_deactivate(lchan);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_tx_power, set_tx_power_cmd,
+ "trx <0-0> tx-power <-110-100>",
+ TRX_STR
+ "Set transmit power (override BSC)\n"
+ "Transmit power in dBm\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int power = atoi(argv[1]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+
+ power_ramp_start(trx, to_mdB(power), 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(reset_rf_clock_ctr, reset_rf_clock_ctr_cmd,
+ "trx <0-0> rf-clock-info reset",
+ TRX_STR
+ "RF Clock Information\n" "Reset the counter\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ l1if_rf_clock_info_reset(fl1h);
+ return CMD_SUCCESS;
+}
+
+DEFUN(correct_rf_clock_ctr, correct_rf_clock_ctr_cmd,
+ "trx <0-0> rf-clock-info correct",
+ TRX_STR
+ "RF Clock Information\n" "Apply\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ l1if_rf_clock_info_correct(fl1h);
+ return CMD_SUCCESS;
+}
+
+DEFUN(loopback, loopback_cmd,
+ "trx <0-0> <0-7> loopback <0-1>",
+ TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_loopback, no_loopback_cmd,
+ "no trx <0-0> <0-7> loopback <0-1>",
+ NO_STR TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ if (trx->nominal_power != get_p_max_out_mdBm(trx))
+ vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,
+ VTY_NEWLINE);
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ if (pinst->u.sysmobts.dsp_trace_f & (1 << i)) {
+ const char *name;
+ name = get_value_string(femtobts_tracef_names, (1 << i));
+ vty_out(vty, " dsp-trace-flag %s%s", name,
+ VTY_NEWLINE);
+ }
+ }
+
+ if (pinst->u.sysmobts.clk_use_eeprom)
+ vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " clock-calibration %d%s",
+ pinst->u.sysmobts.clk_cal, VTY_NEWLINE);
+ if (pinst->u.sysmobts.calib_path)
+ vty_out(vty, " trx-calibration-path %s%s",
+ pinst->u.sysmobts.calib_path, VTY_NEWLINE);
+ if (pinst->u.sysmobts.clk_src)
+ vty_out(vty, " clock-source %s%s",
+ get_value_string(femtobts_clksrc_names,
+ pinst->u.sysmobts.clk_src), VTY_NEWLINE);
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ /* runtime-patch the command strings with debug levels */
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "trx <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "no trx <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ NO_STR TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_dsp_trace_f_cmd.string =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "dsp-trace-flag (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_phy_dsp_trace_f_cmd.doc =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ DSP_TRACE_F_STR, "\n", "", 0);
+
+ cfg_phy_no_dsp_trace_f_cmd.string =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "no dsp-trace-flag (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_phy_no_dsp_trace_f_cmd.doc =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ NO_STR DSP_TRACE_F_STR, "\n",
+ "", 0);
+
+ install_element_ve(&show_dsp_trace_f_cmd);
+ install_element_ve(&show_sys_info_cmd);
+ install_element_ve(&show_trx_clksrc_cmd);
+ install_element_ve(&dsp_trace_f_cmd);
+ install_element_ve(&no_dsp_trace_f_cmd);
+
+ install_element(ENABLE_NODE, &activate_lchan_cmd);
+ install_element(ENABLE_NODE, &set_tx_power_cmd);
+ install_element(ENABLE_NODE, &reset_rf_clock_ctr_cmd);
+ install_element(ENABLE_NODE, &correct_rf_clock_ctr_cmd);
+
+ install_element(ENABLE_NODE, &loopback_cmd);
+ install_element(ENABLE_NODE, &no_loopback_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+
+ install_element(TRX_NODE, &cfg_trx_ul_power_target_cmd);
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+
+ install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clkcal_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clkcal_eeprom_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clkcal_def_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clksrc_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c
new file mode 100644
index 00000000..54e73136
--- /dev/null
+++ b/src/osmo-bts-sysmo/tch.c
@@ -0,0 +1,684 @@
+/* Traffic channel support for Sysmocom BTS L1 */
+
+/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+
+static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+#ifdef USE_L1_RTP_MODE
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_FR_BYTES);
+ memcpy(cur, l1_payload, GSM_FR_BYTES);
+#else
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+
+ cur = msgb_put(msg, GSM_FR_BYTES);
+
+ /* step2: we need to shift the entire L1 payload by 4 bits right */
+ osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS/4);
+
+ cur[0] |= 0xD0;
+#endif /* USE_L1_RTP_MODE */
+
+ lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+#ifdef USE_L1_RTP_MODE
+ /* new L1 can deliver bits like we need them */
+ memcpy(l1_payload, rtp_payload, GSM_FR_BYTES);
+#else
+ /* step2: we need to shift the RTP payload left by one nibble*/
+ osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS/4);
+
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+#endif /* USE_L1_RTP_MODE */
+ return GSM_FR_BYTES;
+}
+
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
+ uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+#ifdef USE_L1_RTP_MODE
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_EFR_BYTES);
+ memcpy(cur, l1_payload, GSM_EFR_BYTES);
+#else
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+
+ cur = msgb_put(msg, GSM_EFR_BYTES);
+
+ /* step 2: we need to shift the entire L1 payload by 4 bits right */
+ osmo_nibble_shift_right(cur, l1_payload, GSM_EFR_BITS/4);
+
+ cur[0] |= 0xC0;
+#endif /* USE_L1_RTP_MODE */
+ enum osmo_amr_type ft;
+ enum osmo_amr_quality bfi;
+ uint8_t cmr;
+ int8_t sti, cmi;
+ osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti);
+ lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan);
+
+ return msg;
+}
+
+static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+#ifndef USE_L1_RTP_MODE
+#error We don't support EFR with L1 that doesn't support RTP mode!
+#else
+ memcpy(l1_payload, rtp_payload, payload_len);
+
+ return payload_len;
+#endif
+}
+#else
+#warning No EFR support in L1
+#endif /* L1_HAS_EFR */
+
+#ifdef USE_L1_RTP_MODE
+/* change the bit-order of each unaligned field inside the HR codec
+ * payload from little-endian bit-ordering to bit-endian and vice-versa.
+ * This is required on all sysmoBTS DSP versions < 5.3.3 in order to
+ * be compliant with ETSI TS 101 318 Chapter 5.2 */
+static void hr_jumble(uint8_t *dst, const uint8_t *src)
+{
+ /* Table 2 / Section 5.2.1 of ETSI TS 101 381 */
+ const int p_unvoiced[] =
+ { 5, 11, 9, 8, 1, 2, 7, 7, 5, 7, 7, 5, 7, 7, 5, 7, 7, 5 };
+ /* Table 3 / Section 5.2.1 of ETSI TS 101 381 */
+ const int p_voiced[] =
+ { 5, 11, 9, 8, 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 };
+
+ int base, i, j, l, si, di;
+ const int *p;
+
+ memset(dst, 0x00, GSM_HR_BYTES);
+
+ p = (src[4] & 0x30) ? p_voiced : p_unvoiced;
+
+ base = 0;
+ for (i = 0; i < 18; i++) {
+ l = p[i];
+ for (j = 0; j < l; j++) {
+ si = base + j;
+ di = base + l - j - 1;
+
+ if (src[si >> 3] & (1 << (7 - (si & 7))))
+ dst[di >> 3] |= (1 << (7 - (di & 7)));
+ }
+
+ base += l;
+ }
+}
+#endif /* USE_L1_RTP_MODE */
+
+static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return NULL;
+ }
+
+ cur = msgb_put(msg, GSM_HR_BYTES);
+#ifdef USE_L1_RTP_MODE
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ if (fl1h->rtp_hr_jumble_needed)
+ hr_jumble(cur, l1_payload);
+ else
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+#else /* USE_L1_RTP_MODE */
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+ /* reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(cur, GSM_HR_BYTES);
+#endif /* USE_L1_RTP_MODE */
+
+ lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len, struct gsm_lchan *lchan)
+{
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return 0;
+ }
+
+#ifdef USE_L1_RTP_MODE
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ if (fl1h->rtp_hr_jumble_needed)
+ hr_jumble(l1_payload, rtp_payload);
+ else
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+#else /* USE_L1_RTP_MODE */
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+ /* reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES);
+#endif /* USE_L1_RTP_MODE */
+
+ return GSM_HR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+#ifndef USE_L1_RTP_MODE
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+#endif
+ struct msgb *msg;
+ uint8_t amr_if2_len = payload_len - 2;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+#ifdef USE_L1_RTP_MODE
+ cur = msgb_put(msg, amr_if2_len);
+ memcpy(cur, l1_payload+2, amr_if2_len);
+
+ /*
+ * Audiocode's MGW doesn't like receiving CMRs that are not
+ * the same as the previous one. This means we need to patch
+ * the content here.
+ */
+ if ((cur[0] & 0xF0) == 0xF0)
+ cur[0]= lchan->tch.last_cmr << 4;
+ else
+ lchan->tch.last_cmr = cur[0] >> 4;
+#else
+ u_int8_t cmr;
+ uint8_t ft = l1_payload[2] & 0xF;
+ uint8_t cmr_idx = l1_payload[1];
+ /* CMR == Unset means CMR was not transmitted at this TDMA */
+ if (cmr_idx == GsmL1_AmrCodecMode_Unset)
+ cmr = lchan->tch.last_cmr;
+ else if (cmr_idx >= amr_mrc->num_modes ||
+ cmr_idx > GsmL1_AmrCodecMode_Unset) {
+ /* Make sure the CMR of the phone is in the active codec set */
+ LOGP(DL1P, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx);
+ cmr = AMR_CMR_NONE;
+ } else {
+ cmr = amr_mrc->bts_mode[cmr_idx].mode;
+ lchan->tch.last_cmr = cmr;
+ }
+
+ /* RFC 3267 4.4.1 Payload Header */
+ msgb_put_u8(msg, (cmr << 4));
+
+ /* RFC 3267 AMR TOC */
+ msgb_put_u8(msg, AMR_TOC_QBIT | (ft << 3));
+
+ cur = msgb_put(msg, amr_if2_len-1);
+
+ /* step1: reverse the bit-order within every byte */
+ osmo_revbytebits_buf(l1_payload+2, amr_if2_len);
+
+ /* step2: shift everything left by one nibble */
+ osmo_nibble_shift_left_unal(cur, l1_payload+2, amr_if2_len*2 -1);
+
+#endif /* USE_L1_RTP_MODE */
+
+ return msg;
+}
+
+/*! \brief convert AMR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ uint8_t payload_len, uint8_t ft)
+{
+#ifdef USE_L1_RTP_MODE
+ memcpy(l1_payload, rtp_payload, payload_len);
+#else
+ uint8_t amr_if2_core_len = payload_len - 2;
+
+ /* step1: shift everything right one nibble; make space for FT */
+ osmo_nibble_shift_right(l1_payload+2, rtp_payload+2, amr_if2_core_len*2);
+ /* step2: reverse the bit-order within every byte of the IF2
+ * core frame contained in the RTP payload */
+ osmo_revbytebits_buf(l1_payload+2, amr_if2_core_len+1);
+
+ /* lower 4 bit of first FR2 byte contains FT */
+ l1_payload[2] |= ft;
+#endif /* USE_L1_RTP_MODE */
+ return payload_len;
+}
+
+#define RTP_MSGB_ALLOC_SIZE 512
+
+/*! \brief function for incoming RTP via TCH.req
+ * \param[in] rtp_pl buffer containing RTP payload
+ * \param[in] rtp_pl_len length of \a rtp_pl
+ * \param[in] use_cache Use cached payload instead of parsing RTP
+ * \param[in] marker RTP header Marker bit (indicates speech onset)
+ * \returns 0 if encoding result can be sent further to L1 without extra actions
+ * positive value if data is ready AND extra actions are required
+ * negative value otherwise (no data for L1 encoded)
+ *
+ * This function prepares a msgb with a L1 PH-DATA.req primitive and
+ * queues it into lchan->dl_tch_queue.
+ *
+ * Note that the actual L1 primitive header is not fully initialized
+ * yet, as things like the frame number, etc. are unknown at the time we
+ * pre-fill the primtive.
+ */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker)
+{
+ uint8_t *payload_type;
+ uint8_t *l1_payload, ft;
+ int rc = 0;
+ bool is_sid = false;
+
+ DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(rtp_pl, rtp_pl_len));
+
+ payload_type = &data[0];
+ l1_payload = &data[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ *payload_type = GsmL1_TchPlType_Fr;
+ rc = rtppayload_to_l1_fr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len);
+ } else{
+ *payload_type = GsmL1_TchPlType_Hr;
+ rc = rtppayload_to_l1_hr(l1_payload,
+ rtp_pl, rtp_pl_len, lchan);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
+ }
+ if (is_sid)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
+ break;
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
+ rtp_pl_len);
+ /* FIXME: detect and save EFR SID */
+ break;
+#endif
+ case GSM48_CMODE_SPEECH_AMR:
+ if (use_cache) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache,
+ lchan->tch.dtx.len, ft);
+ *len = lchan->tch.dtx.len + 1;
+ return 0;
+ }
+
+ rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
+ l1_payload, marker, len, &ft);
+ if (rc < 0)
+ return rc;
+ if (!dtx_dl_amr_enabled(lchan)) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ }
+
+ /* DTX DL-specific logic below: */
+ switch (lchan->tch.dtx.dl_amr_fsm->state) {
+ case ST_ONSET_V:
+ *payload_type = GsmL1_TchPlType_Amr_Onset;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ *len = 3;
+ return 1;
+ case ST_VOICE:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F1:
+ if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
+ rtp_pl_len, ft);
+ return 0;
+ }
+ /* AMR FR */
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F2:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_F1_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_U_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_SID_U:
+ case ST_U_NOINH:
+ return -EAGAIN;
+ case ST_FACCH:
+ return -EBADMSG;
+ default:
+ LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
+ "%d\n", lchan->tch.dtx.dl_amr_fsm->state);
+ return -EINVAL;
+ }
+ break;
+ default:
+ /* we don't support CSD modes */
+ rc = -1;
+ break;
+ }
+
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
+ gsm_lchan_name(lchan));
+ return -EBADMSG;
+ }
+
+ *len = rc + 1;
+
+ DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(data, *len));
+ return 0;
+}
+
+static int is_recv_only(uint8_t speech_mode)
+{
+ return (speech_mode & 0xF0) == (1 << 4);
+}
+
+/*! \brief receive a traffic L1 primitive for a given lchan */
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
+ GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
+ uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 };
+ struct msgb *rmsg = NULL;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
+
+ if (is_recv_only(lchan->abis_ip.speech_mode))
+ return -EAGAIN;
+
+ if (data_ind->msgUnitParam.u8Size < 1) {
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr);
+ /* Push empty payload to upper layers */
+ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP");
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+ }
+
+ payload_type = data_ind->msgUnitParam.u8Buffer[0];
+ payload = data_ind->msgUnitParam.u8Buffer + 1;
+ payload_len = data_ind->msgUnitParam.u8Size - 1;
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+ case GsmL1_TchPlType_Efr:
+#endif
+ if (lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Hr:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr_Onset:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ /* according to 3GPP TS 26.093 ONSET frames precede the first
+ speech frame of a speech burst - set the marker for next RTP
+ frame */
+ lchan->rtp_tx_marker = true;
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP2:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidUpdateInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ default:
+ LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_tch_pl_names, payload_type));
+ break;
+ }
+
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Hr:
+ rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan);
+ break;
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+ case GsmL1_TchPlType_Efr:
+ rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan);
+ break;
+#endif
+ case GsmL1_TchPlType_Amr:
+ rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ memcpy(sid_first, payload, payload_len);
+ int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD);
+ if (len < 0)
+ return 0;
+ rmsg = l1_to_rtppayload_amr(sid_first, len, lchan);
+ break;
+ /* FIXME: what about GsmL1_TchPlType_Amr_SidBad? not well documented. */
+ }
+
+ if (rmsg)
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+
+ return 0;
+
+err_payload_match:
+ LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_tch_pl_names, payload_type));
+ return -EINVAL;
+}
+
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn)
+{
+ struct msgb *msg;
+ GsmL1_Prim_t *l1p;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ uint8_t *payload_type;
+ uint8_t *l1_payload;
+ int rc;
+
+ msg = l1p_msgb_alloc();
+ if (!msg)
+ return NULL;
+
+ l1p = msgb_l1prim(msg);
+ data_req = &l1p->u.phDataReq;
+ msu_param = &data_req->msgUnitParam;
+ payload_type = &msu_param->u8Buffer[0];
+ l1_payload = &msu_param->u8Buffer[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_AMR:
+ if (lchan->type == GSM_LCHAN_TCH_H &&
+ dtx_dl_amr_enabled(lchan)) {
+ /* we have to explicitly handle sending SID FIRST P2 for
+ AMR HR in here */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
+ rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
+ false, &(msu_param->u8Size),
+ NULL);
+ if (rc == 0)
+ return msg;
+ }
+ *payload_type = GsmL1_TchPlType_Amr;
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ *payload_type = GsmL1_TchPlType_Fr;
+ else
+ *payload_type = GsmL1_TchPlType_Hr;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ break;
+ default:
+ msgb_free(msg);
+ return NULL;
+ }
+
+ rc = repeat_last_sid(lchan, l1_payload, fn);
+ if (!rc) {
+ msgb_free(msg);
+ return NULL;
+ }
+ msu_param->u8Size = rc;
+
+ return msg;
+}
diff --git a/src/osmo-bts-sysmo/utils.c b/src/osmo-bts-sysmo/utils.c
new file mode 100644
index 00000000..0e3ef273
--- /dev/null
+++ b/src/osmo-bts-sysmo/utils.c
@@ -0,0 +1,116 @@
+/*
+ * Helper utilities that are used in OML
+ *
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "utils.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+
+int band_femto2osmo(GsmL1_FreqBand_t band)
+{
+ switch (band) {
+ case GsmL1_FreqBand_850:
+ return GSM_BAND_850;
+ case GsmL1_FreqBand_900:
+ return GSM_BAND_900;
+ case GsmL1_FreqBand_1800:
+ return GSM_BAND_1800;
+ case GsmL1_FreqBand_1900:
+ return GSM_BAND_1900;
+ default:
+ return -1;
+ }
+}
+
+static int band_osmo2femto(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ /* check if the TRX hardware actually supports the given band */
+ if (!(fl1h->hw_info.band_support & osmo_band))
+ return -1;
+
+ /* if yes, convert from osmcoom style band definition to L1 band */
+ switch (osmo_band) {
+ case GSM_BAND_850:
+ return GsmL1_FreqBand_850;
+ case GSM_BAND_900:
+ return GsmL1_FreqBand_900;
+ case GSM_BAND_1800:
+ return GsmL1_FreqBand_1800;
+ case GSM_BAND_1900:
+ return GsmL1_FreqBand_1900;
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Select the band that matches the ARFCN. In general the ARFCNs
+ * for GSM1800 and GSM1900 overlap and one needs to specify the
+ * rightband. When moving between GSM900/GSM1800 and GSM850/1900
+ * modifying the BTS configuration is a bit annoying. The auto-band
+ * configuration allows to ease with this transition.
+ */
+int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn)
+{
+ enum gsm_band band;
+ struct gsm_bts *bts = trx->bts;
+ int rc;
+
+ if (!bts->auto_band)
+ return band_osmo2femto(trx, bts->band);
+
+ /*
+ * We need to check what will happen now.
+ */
+ rc = gsm_arfcn2band_rc(arfcn, &band);
+ if (rc) /* wrong ARFCN, give up */
+ return -1;
+
+ /* if we are already on the right band return */
+ if (band == bts->band)
+ return band_osmo2femto(trx, bts->band);
+
+ /* Check if it is GSM1800/GSM1900 */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return band_osmo2femto(trx, bts->band);
+
+ /*
+ * Now to the actual autobauding. We just want DCS/DCS and
+ * PCS/PCS for PCS we check for 850/1800 though
+ */
+ if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800)
+ || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900)
+ || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900))
+ return band_osmo2femto(trx, band);
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
+ return band_osmo2femto(trx, GSM_BAND_1900);
+
+ /* give up */
+ return -1;
+}
diff --git a/src/osmo-bts-sysmo/utils.h b/src/osmo-bts-sysmo/utils.h
new file mode 100644
index 00000000..45908d50
--- /dev/null
+++ b/src/osmo-bts-sysmo/utils.h
@@ -0,0 +1,12 @@
+#ifndef SYSMOBTS_UTILS_H
+#define SYSMOBTS_UTILS_H
+
+#include <stdint.h>
+#include "femtobts.h"
+
+struct gsm_bts_trx;
+
+int band_femto2osmo(GsmL1_FreqBand_t band);
+
+int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn);
+#endif
diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am
new file mode 100644
index 00000000..19222405
--- /dev/null
+++ b/src/osmo-bts-trx/Makefile.am
@@ -0,0 +1,10 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOCODING_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOCODING_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl
+
+EXTRA_DIST = trx_if.h l1_if.h loops.h
+
+bin_PROGRAMS = osmo-bts-trx
+
+osmo_bts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler_trx.c trx_vty.c loops.c
+osmo_bts_trx_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c
new file mode 100644
index 00000000..da1b554f
--- /dev/null
+++ b/src/osmo-bts-trx/l1_if.c
@@ -0,0 +1,782 @@
+/*
+ * layer 1 primitive handling and interface
+ *
+ * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2015 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/abis_nm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+
+
+static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = 8,
+ [GSM_PCHAN_CCCH] = 4,
+ [GSM_PCHAN_CCCH_SDCCH4] = 5,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 5,
+ [GSM_PCHAN_TCH_F] = 1,
+ [GSM_PCHAN_TCH_H] = 3,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = 7,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 7,
+ [GSM_PCHAN_PDCH] = 13,
+ /* [GSM_PCHAN_TCH_F_PDCH] not needed here, see trx_set_ts_as_pchan() */
+ [GSM_PCHAN_UNKNOWN] = 0,
+};
+
+struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h;
+ l1h = talloc_zero(tall_ctx, struct trx_l1h);
+ l1h->phy_inst = pinst;
+ trx_if_init(l1h);
+ return l1h;
+}
+
+static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->trx;
+ uint8_t tn;
+
+ /* HACK, we should change state when we receive first clock from
+ * transceiver */
+ if (avail) {
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ if (!pinst->u.osmotrx.sw_act_reported) {
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+ pinst->u.osmotrx.sw_act_reported = true;
+ }
+
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED,
+ (l1h->config.slotmask & (1 << tn)) ?
+ NM_AVSTATE_DEPENDENCY :
+ NM_AVSTATE_NOT_INSTALLED);
+ } else {
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+ }
+}
+
+int check_transceiver_availability(struct gsm_bts *bts, int avail)
+{
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ check_transceiver_availability_trx(l1h, avail);
+ }
+ return 0;
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+ /* FIXME: perform whatever is needed (if any) to set proper PCH/AGCH allocation according to
+ 3GPP TS 44.018 Table 10.5.2.11.1 using num_agch(lchan->ts->trx, "TRX L1"); function */
+ return 0;
+ }
+ /* set lchan inactive */
+ lchan_set_state(lchan, LCHAN_S_NONE);
+
+ return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan),
+ LID_DEDIC, 0);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan),
+ LID_SACCH, 0);
+}
+
+/*
+ * transceiver provisioning
+ */
+int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
+{
+ uint8_t tn;
+
+ if (!transceiver_available)
+ return -EIO;
+
+ if (l1h->config.poweron
+ && l1h->config.tsc_valid
+ && l1h->config.bsic_valid
+ && l1h->config.arfcn_valid) {
+ /* before power on */
+ if (!l1h->config.arfcn_sent) {
+ trx_if_cmd_rxtune(l1h, l1h->config.arfcn);
+ trx_if_cmd_txtune(l1h, l1h->config.arfcn);
+ l1h->config.arfcn_sent = 1;
+ }
+ if (!l1h->config.tsc_sent) {
+ trx_if_cmd_settsc(l1h, l1h->config.tsc);
+ l1h->config.tsc_sent = 1;
+ }
+ if (!l1h->config.bsic_sent) {
+ trx_if_cmd_setbsic(l1h, l1h->config.bsic);
+ l1h->config.bsic_sent = 1;
+ }
+
+ if (!l1h->config.poweron_sent) {
+ trx_if_cmd_poweron(l1h);
+ l1h->config.poweron_sent = 1;
+ }
+
+ /* after power on */
+ if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) {
+ trx_if_cmd_setrxgain(l1h, l1h->config.rxgain);
+ l1h->config.rxgain_sent = 1;
+ }
+ if (l1h->config.power_valid && !l1h->config.power_sent) {
+ trx_if_cmd_setpower(l1h, l1h->config.power);
+ l1h->config.power_sent = 1;
+ }
+ if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) {
+ trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly);
+ l1h->config.maxdly_sent = 1;
+ }
+ if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) {
+ trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb);
+ l1h->config.maxdlynb_sent = 1;
+ }
+
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ if (l1h->config.slottype_valid[tn]
+ && !l1h->config.slottype_sent[tn]) {
+ trx_if_cmd_setslot(l1h, tn,
+ l1h->config.slottype[tn]);
+ l1h->config.slottype_sent[tn] = 1;
+ }
+ }
+ return 0;
+ }
+
+ if (!l1h->config.poweron && !l1h->config.poweron_sent) {
+ trx_if_cmd_poweroff(l1h);
+ l1h->config.poweron_sent = 1;
+ l1h->config.rxgain_sent = 0;
+ l1h->config.power_sent = 0;
+ l1h->config.maxdly_sent = 0;
+ l1h->config.maxdlynb_sent = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ l1h->config.slottype_sent[tn] = 0;
+ }
+
+ return 0;
+}
+
+int l1if_provision_transceiver(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ l1h->config.arfcn_sent = 0;
+ l1h->config.tsc_sent = 0;
+ l1h->config.bsic_sent = 0;
+ l1h->config.poweron_sent = 0;
+ l1h->config.rxgain_sent = 0;
+ l1h->config.power_sent = 0;
+ l1h->config.maxdly_sent = 0;
+ l1h->config.maxdlynb_sent = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ l1h->config.slottype_sent[tn] = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+ return 0;
+}
+
+/*
+ * activation/configuration/deactivation of transceiver's TRX
+ */
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ /* power on transceiver, if not already */
+ if (!l1h->config.poweron) {
+ l1h->config.poweron = 1;
+ l1h->config.poweron_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ if (trx == trx->bts->c0)
+ lchan_init_lapdm(&trx->ts[0].lchan[CCCH_LCHAN]);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(&trx->mo);
+}
+
+/* deactivate transceiver */
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ enum gsm_phys_chan_config pchan = trx->ts[0].pchan;
+
+ /* close all logical channels and reset timeslots */
+ trx_sched_reset(&l1h->l1s);
+
+ /* deactivate lchan for CCCH */
+ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) {
+ lchan_set_state(&trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_INACTIVE);
+ }
+
+ /* power off transceiver, if not already */
+ if (l1h->config.poweron) {
+ l1h->config.poweron = 0;
+ l1h->config.poweron_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ /* Set to Operational State: Disabled */
+ check_transceiver_availability_trx(l1h, 0);
+
+ return 0;
+}
+
+/* on RSL failure, deactivate transceiver */
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ bts_shutdown(bts, "Abis close");
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ /* we always implement the power control loop in osmo-bts software, as
+ * there is no automatism in the underlying osmo-trx */
+ return 0;
+}
+
+/* set bts attributes */
+static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t bsic = bts->bsic;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) {
+ l1h->config.bsic = bsic;
+ l1h->config.bsic_valid = 1;
+ l1h->config.bsic_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+ }
+ check_transceiver_availability(bts, transceiver_available);
+
+
+ return 0;
+}
+
+/* set trx attributes */
+static uint8_t trx_set_trx(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ uint16_t arfcn = trx->arfcn;
+
+ if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) {
+ l1h->config.arfcn = arfcn;
+ l1h->config.arfcn_valid = 1;
+ l1h->config.arfcn_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ if (l1h->config.power_oml) {
+ l1h->config.power = trx->max_power_red;
+ l1h->config.power_valid = 1;
+ l1h->config.power_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ return 0;
+}
+
+/* set ts attributes */
+static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(ts->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ uint8_t tn = ts->nr;
+ uint16_t tsc = ts->tsc;
+ uint8_t slottype;
+ int rc;
+
+ /* all TSC of all timeslots must be equal, because transceiver only
+ * supports one TSC per TRX */
+
+ if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) {
+ l1h->config.tsc = tsc;
+ l1h->config.tsc_valid = 1;
+ l1h->config.tsc_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ /* ignore disabled slots */
+ if (!(l1h->config.slotmask & (1 << tn)))
+ return NM_NACK_RES_NOTAVAIL;
+
+ /* set physical channel. For dynamic timeslots, the caller should have
+ * decided on a more specific PCHAN type already. */
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+ rc = trx_sched_set_pchan(&l1h->l1s, tn, pchan);
+ if (rc)
+ return NM_NACK_RES_NOTAVAIL;
+
+ /* activate lchan for CCCH */
+ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) {
+ ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE);
+ }
+
+ slottype = transceiver_chan_types[pchan];
+
+ if (l1h->config.slottype[tn] != slottype
+ || !l1h->config.slottype_valid[tn]) {
+ l1h->config.slottype[tn] = slottype;
+ l1h->config.slottype_valid[tn] = 1;
+ l1h->config.slottype_sent[tn] = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ return 0;
+}
+
+static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan;
+
+ /* For dynamic timeslots, pick the pchan type that should currently be
+ * active. This should only be called during init, PDCH transitions
+ * will call trx_set_ts_as_pchan() directly. */
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ OSMO_ASSERT((ts->flags & TS_F_PDCH_PENDING_MASK) == 0);
+ pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH
+ : GSM_PCHAN_TCH_F;
+ break;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want);
+ pchan = ts->dyn.pchan_is;
+ break;
+ default:
+ pchan = ts->pchan;
+ break;
+ }
+
+ return trx_set_ts_as_pchan(ts, pchan);
+}
+
+
+/*
+ * primitive handling
+ */
+
+/* enable ciphering */
+static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan,
+ uint8_t chan_nr, int downlink)
+{
+ /* ciphering already enabled in both directions */
+ if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF)
+ return -EINVAL;
+
+ if (!downlink) {
+ /* set uplink */
+ trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, lchan->encr.alg_id - 1,
+ lchan->encr.key, lchan->encr.key_len);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ } else {
+ /* set downlink and also set uplink, if not already */
+ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) {
+ trx_sched_set_cipher(&l1h->l1s, chan_nr, 0,
+ lchan->encr.alg_id - 1, lchan->encr.key,
+ lchan->encr.key_len);
+ }
+ trx_sched_set_cipher(&l1h->l1s, chan_nr, 1, lchan->encr.alg_id - 1,
+ lchan->encr.key, lchan->encr.key_len);
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ }
+
+ return 0;
+}
+
+static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = chan_nr;
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(pinst->trx, &l1sap);
+}
+
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_TIME;
+ l1sap.u.info.u.time_ind.fn = fn;
+
+ if (!bts->c0)
+ return -EINVAL;
+
+ return l1sap_up(bts->c0, &l1sap);
+}
+
+
+static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, int16_t toa256,
+ float ber, float rssi, uint32_t fn)
+{
+ memset(l1sap, 0, sizeof(*l1sap));
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap->u.info.type = PRIM_INFO_MEAS;
+ l1sap->u.info.u.meas_ind.chan_nr = chan_nr;
+ l1sap->u.info.u.meas_ind.ta_offs_256bits = toa256;
+ l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000);
+ l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1);
+ l1sap->u.info.u.meas_ind.fn = fn;
+}
+
+int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr,
+ int n_errors, int n_bits_total, float rssi, int16_t toa256)
+{
+ struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)];
+ struct osmo_phsap_prim l1sap;
+ /* 100% BER is n_bits_total is 0 */
+ float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total;
+
+ LOGPFN(DMEAS, LOGL_DEBUG, fn, "RX UL measurement for %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS "
+ "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa256=%d\n",
+ gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.current),
+ rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa256);
+
+ l1if_fill_meas_res(&l1sap, chan_nr, toa256, ber, rssi, fn);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ struct msgb *msg = l1sap->oph.msg;
+ uint8_t chan_nr;
+ int rc = 0;
+ struct gsm_lchan *lchan;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ if (!msg)
+ break;
+ /* put data into scheduler's queue */
+ return trx_sched_ph_data_req(&l1h->l1s, l1sap);
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ if (!msg)
+ break;
+ /* put data into scheduler's queue */
+ return trx_sched_tch_req(&l1h->l1s, l1sap);
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink)
+ l1if_set_ciphering(l1h, lchan, chan_nr, 0);
+ if (l1sap->u.info.u.ciph_req.downlink)
+ l1if_set_ciphering(l1h, lchan, chan_nr, 1);
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) {
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGP(DL1C, LOGL_ERROR, "Cannot activate"
+ " chan_nr 0x%02x\n", chan_nr);
+ break;
+ }
+
+ /* trx_chan_desc[] in scheduler.c uses the RSL_CHAN_OSMO_PDCH cbits
+ * (0xc0) to indicate the need for PDTCH and PTCCH SAPI activation.
+ * However, 0xc0 is a cbits pattern exclusively used for Osmocom style
+ * dyn TS (a non-standard RSL Chan Activ mod); hence, for IPA style dyn
+ * TS, the chan_nr will never reflect 0xc0 and we would omit the
+ * PDTCH,PTTCH SAPIs. To properly de-/activate the PDTCH SAPIs in
+ * scheduler.c, make sure the 0xc0 cbits are set for de-/activating PDTCH
+ * lchans, i.e. both Osmocom and IPA style dyn TS. (For Osmocom style dyn
+ * TS, the chan_nr typically already reflects 0xc0, while it doesn't for
+ * IPA style.) */
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ chan_nr = RSL_CHAN_OSMO_PDCH | (chan_nr & ~RSL_CHAN_NR_MASK);
+
+ /* activate dedicated channel */
+ trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_DEDIC, 1);
+ /* activate associated channel */
+ trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_SACCH, 1);
+ /* set mode */
+ trx_sched_set_mode(&l1h->l1s, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.bts_mode[0].mode,
+ lchan->tch.amr_mr.bts_mode[1].mode,
+ lchan->tch.amr_mr.bts_mode[2].mode,
+ lchan->tch.amr_mr.bts_mode[3].mode,
+ amr_get_initial_mode(lchan),
+ (lchan->ho.active == 1));
+ /* init lapdm */
+ lchan_init_lapdm(lchan);
+ /* set lchan active */
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ /* set initial ciphering */
+ l1if_set_ciphering(l1h, lchan, chan_nr, 0);
+ l1if_set_ciphering(l1h, lchan, chan_nr, 1);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ /* confirm */
+ mph_info_chan_confirm(l1h, chan_nr,
+ PRIM_INFO_ACTIVATE, 0);
+ break;
+ }
+ if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ /* change mode */
+ trx_sched_set_mode(&l1h->l1s, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.bts_mode[0].mode,
+ lchan->tch.amr_mr.bts_mode[1].mode,
+ lchan->tch.amr_mr.bts_mode[2].mode,
+ lchan->tch.amr_mr.bts_mode[3].mode,
+ amr_get_initial_mode(lchan),
+ 0);
+ break;
+ }
+ /* here, type == PRIM_INFO_DEACTIVATE */
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGP(DL1C, LOGL_ERROR, "Cannot deactivate "
+ "chan_nr 0x%02x\n", chan_nr);
+ break;
+ }
+ /* deactivate associated channel */
+ bts_model_lchan_deactivate_sacch(lchan);
+ if (!l1sap->u.info.u.act_req.sacch_only) {
+ /* deactivate dedicated channel */
+ lchan_deactivate(lchan);
+ /* confirm only on dedicated channel */
+ mph_info_chan_confirm(l1h, chan_nr,
+ PRIM_INFO_DEACTIVATE, 0);
+ }
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ goto done;
+ }
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ goto done;
+ }
+
+done:
+ if (msg)
+ msgb_free(msg);
+ return rc;
+}
+
+
+/*
+ * oml handling
+ */
+
+/* callback from OML */
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ /* FIXME: check if the attributes are valid */
+ return 0;
+}
+
+/* callback from OML */
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ int cause = 0;
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_BTS_ATTR:
+ cause = trx_set_bts(obj, new_attr);
+ break;
+ case NM_MT_SET_RADIO_ATTR:
+ cause = trx_set_trx(obj);
+ break;
+ case NM_MT_SET_CHAN_ATTR:
+ cause = trx_set_ts(obj);
+ break;
+ }
+
+ return oml_fom_ack_nack(msg, cause);
+}
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{
+ int rc;
+ LOGP(DOML, LOGL_DEBUG, "bts_model_opstart: %s received\n",
+ get_value_string(abis_nm_obj_class_names, mo->obj_class));
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ /* activate transceiver */
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+ rc = oml_mo_opstart_ack(mo);
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ /* blindly accept all state changes */
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+#warning "implement bts_model_change_power\n"
+ LOGP(DL1C, LOGL_NOTICE, "Setting TRX output power not supported!\n");
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ /* no action required, signal completion right away. */
+ cb_ts_disconnected(ts);
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+ LOGP(DL1C, LOGL_DEBUG, "%s bts_model_ts_connect(as_pchan=%s)\n",
+ gsm_ts_name(ts), gsm_pchan_name(as_pchan));
+
+ rc = trx_set_ts_as_pchan(ts, as_pchan);
+ if (rc)
+ cb_ts_connected(ts, rc);
+
+ LOGP(DL1C, LOGL_NOTICE, "%s bts_model_ts_connect(as_pchan=%s) success,"
+ " calling cb_ts_connected()\n",
+ gsm_ts_name(ts), gsm_pchan_name(as_pchan));
+ cb_ts_connected(ts, 0);
+}
diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h
new file mode 100644
index 00000000..165f9d81
--- /dev/null
+++ b/src/osmo-bts-trx/l1_if.h
@@ -0,0 +1,82 @@
+#ifndef L1_IF_H_TRX
+#define L1_IF_H_TRX
+
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/phy_link.h>
+#include "trx_if.h"
+
+struct trx_config {
+ uint8_t poweron; /* poweron(1) or poweroff(0) */
+ int poweron_sent;
+
+ int arfcn_valid;
+ uint16_t arfcn;
+ int arfcn_sent;
+
+ int tsc_valid;
+ uint8_t tsc;
+ int tsc_sent;
+
+ int bsic_valid;
+ uint8_t bsic;
+ int bsic_sent;
+
+ int rxgain_valid;
+ uint8_t rxgain;
+ int rxgain_sent;
+
+ int power_valid;
+ uint8_t power;
+ int power_oml;
+ int power_sent;
+
+ int maxdly_valid;
+ int maxdly;
+ int maxdly_sent;
+
+ int maxdlynb_valid;
+ int maxdlynb;
+ int maxdlynb_sent;
+
+ uint8_t slotmask;
+
+ int slottype_valid[TRX_NR_TS];
+ uint8_t slottype[TRX_NR_TS];
+ int slottype_sent[TRX_NR_TS];
+};
+
+struct trx_l1h {
+ struct llist_head trx_ctrl_list;
+ /* Latest RSPed cmd, used to catch duplicate RSPs from sent retransmissions */
+ struct trx_ctrl_msg *last_acked;
+
+ //struct gsm_bts_trx *trx;
+ struct phy_instance *phy_inst;
+
+ struct osmo_fd trx_ofd_ctrl;
+ struct osmo_timer_list trx_ctrl_timer;
+ struct osmo_fd trx_ofd_data;
+
+ /* transceiver config */
+ struct trx_config config;
+ uint8_t ho_rach_detect[TRX_NR_TS][TS_MAX_LCHAN];
+
+ struct l1sched_trx l1s;
+};
+
+struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst);
+int check_transceiver_availability(struct gsm_bts *bts, int avail);
+int l1if_provision_transceiver_trx(struct trx_l1h *l1h);
+int l1if_provision_transceiver(struct gsm_bts *bts);
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn);
+int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr,
+ int n_errors, int n_bits_total, float rssi, int16_t toa256);
+
+static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx->role_bts.l1h;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ return &l1h->l1s;
+}
+
+#endif /* L1_IF_H_TRX */
diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c
new file mode 100644
index 00000000..926b4c6f
--- /dev/null
+++ b/src/osmo-bts-trx/loops.c
@@ -0,0 +1,340 @@
+/* Loop control for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/l1sap.h>
+#include <osmocom/core/bits.h>
+
+#include "trx_if.h"
+#include "l1_if.h"
+#include "loops.h"
+
+/*
+ * MS Power loop
+ */
+
+static int ms_power_diff(struct gsm_lchan *lchan, uint8_t chan_nr, int8_t diff)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ enum gsm_band band = trx->bts->band;
+ uint16_t arfcn = trx->arfcn;
+ int8_t new_power;
+
+ new_power = lchan->ms_power_ctrl.current - (diff >> 1);
+
+ if (diff == 0)
+ return 0;
+
+ if (new_power < 0)
+ new_power = 0;
+
+ // FIXME: to go above 1W, we need to know classmark of MS
+ if (arfcn >= 512 && arfcn <= 885) {
+ if (new_power > 15)
+ new_power = 15;
+ } else {
+ if (new_power > 19)
+ new_power = 19;
+ }
+
+ /* a higher value means a lower level (and vice versa) */
+ if (new_power > lchan->ms_power_ctrl.current + MS_LOWER_MAX)
+ new_power = lchan->ms_power_ctrl.current + MS_LOWER_MAX;
+ else if (new_power < lchan->ms_power_ctrl.current - MS_RAISE_MAX)
+ new_power = lchan->ms_power_ctrl.current - MS_RAISE_MAX;
+
+ if (lchan->ms_power_ctrl.current == new_power) {
+ LOGP(DLOOP, LOGL_INFO, "Keeping MS new_power of trx=%u "
+ "chan_nr=0x%02x at control level %d (%d dBm)\n",
+ trx->nr, chan_nr, new_power,
+ ms_pwr_dbm(band, new_power));
+
+ return 0;
+ }
+
+ LOGP(DLOOP, LOGL_INFO, "%s MS new_power of trx=%u chan_nr=0x%02x from "
+ "control level %d (%d dBm) to %d (%d dBm)\n",
+ (diff > 0) ? "Raising" : "Lowering",
+ trx->nr, chan_nr, lchan->ms_power_ctrl.current,
+ ms_pwr_dbm(band, lchan->ms_power_ctrl.current), new_power,
+ ms_pwr_dbm(band, new_power));
+
+ lchan->ms_power_ctrl.current = new_power;
+
+ return 0;
+}
+
+static int ms_power_val(struct l1sched_chan_state *chan_state, int8_t rssi)
+{
+ /* ignore inserted dummy frames, treat as lost frames */
+ if (rssi < -127)
+ return 0;
+
+ LOGP(DLOOP, LOGL_DEBUG, "Got RSSI value of %d\n", rssi);
+
+ chan_state->meas.rssi_count++;
+
+ chan_state->meas.rssi_got_burst = 1;
+
+ /* store and process RSSI */
+ if (chan_state->meas.rssi_valid_count
+ == ARRAY_SIZE(chan_state->meas.rssi))
+ return 0;
+ chan_state->meas.rssi[chan_state->meas.rssi_valid_count++] = rssi;
+ chan_state->meas.rssi_valid_count++;
+
+ return 0;
+}
+
+static int ms_power_clock(struct gsm_lchan *lchan,
+ uint8_t chan_nr, struct l1sched_chan_state *chan_state)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ int rssi;
+ int i;
+
+ /* skip every second clock, to prevent oscillating due to roundtrip
+ * delay */
+ if (!(chan_state->meas.clock & 1))
+ return 0;
+
+ LOGP(DLOOP, LOGL_DEBUG, "Got SACCH master clock at RSSI count %d\n",
+ chan_state->meas.rssi_count);
+
+ /* wait for initial burst */
+ if (!chan_state->meas.rssi_got_burst)
+ return 0;
+
+ /* if no burst was received from MS at clock */
+ if (chan_state->meas.rssi_count == 0) {
+ LOGP(DLOOP, LOGL_NOTICE, "LOST SACCH frame of trx=%u "
+ "chan_nr=0x%02x, so we raise MS power\n",
+ trx->nr, chan_nr);
+ return ms_power_diff(lchan, chan_nr, MS_RAISE_MAX);
+ }
+
+ /* reset total counter */
+ chan_state->meas.rssi_count = 0;
+
+ /* check the minimum level received after MS acknowledged the ordered
+ * power level */
+ if (chan_state->meas.rssi_valid_count == 0)
+ return 0;
+ for (rssi = 999, i = 0; i < chan_state->meas.rssi_valid_count; i++) {
+ if (rssi > chan_state->meas.rssi[i])
+ rssi = chan_state->meas.rssi[i];
+ }
+
+ /* reset valid counter */
+ chan_state->meas.rssi_valid_count = 0;
+
+ /* change RSSI */
+ LOGP(DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current "
+ "MS power: %d (%d dBm) of trx=%u chan_nr=0x%02x\n", rssi,
+ pinst->phy_link->u.osmotrx.trx_target_rssi, lchan->ms_power_ctrl.current,
+ ms_pwr_dbm(trx->bts->band, lchan->ms_power_ctrl.current),
+ trx->nr, chan_nr);
+ ms_power_diff(lchan, chan_nr, pinst->phy_link->u.osmotrx.trx_target_rssi - rssi);
+
+ return 0;
+}
+
+
+/* 90% of one bit duration in 1/256 symbols: 256*0.9 */
+#define TOA256_9OPERCENT 230
+
+/*
+ * Timing Advance loop
+ */
+
+int ta_val(struct gsm_lchan *lchan, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, int16_t toa256)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+
+ /* check if the current L1 header acks to the current ordered TA */
+ if (lchan->meas.l1_info[1] != lchan->rqd_ta)
+ return 0;
+
+ /* sum measurement */
+ chan_state->meas.toa256_sum += toa256;
+ if (++(chan_state->meas.toa_num) < 16)
+ return 0;
+
+ /* complete set */
+ toa256 = chan_state->meas.toa256_sum / chan_state->meas.toa_num;
+
+ /* check for change of TOA */
+ if (toa256 < -TOA256_9OPERCENT && lchan->rqd_ta > 0) {
+ LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too "
+ "early (%d), now lowering TA from %d to %d\n",
+ trx->nr, chan_nr, toa256, lchan->rqd_ta,
+ lchan->rqd_ta - 1);
+ lchan->rqd_ta--;
+ } else if (toa256 > TOA256_9OPERCENT && lchan->rqd_ta < 63) {
+ LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too "
+ "late (%d), now raising TA from %d to %d\n",
+ trx->nr, chan_nr, toa256, lchan->rqd_ta,
+ lchan->rqd_ta + 1);
+ lchan->rqd_ta++;
+ } else
+ LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is "
+ "correct (%d), keeping current TA of %d\n",
+ trx->nr, chan_nr, toa256, lchan->rqd_ta);
+
+ chan_state->meas.toa_num = 0;
+ chan_state->meas.toa256_sum = 0;
+
+ return 0;
+}
+
+int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa256)
+{
+ struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)]
+ .lchan[l1sap_chan2ss(chan_nr)];
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+
+ if (pinst->phy_link->u.osmotrx.trx_ms_power_loop)
+ ms_power_val(chan_state, rssi);
+
+ if (pinst->phy_link->u.osmotrx.trx_ta_loop)
+ ta_val(lchan, chan_nr, chan_state, toa256);
+
+ return 0;
+}
+
+int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state)
+{
+ struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)]
+ .lchan[l1sap_chan2ss(chan_nr)];
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+
+ if (pinst->phy_link->u.osmotrx.trx_ms_power_loop)
+ ms_power_clock(lchan, chan_nr, chan_state);
+
+ /* count the number of SACCH clocks */
+ chan_state->meas.clock++;
+
+ return 0;
+}
+
+int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, float ber)
+{
+ struct gsm_bts_trx *trx = l1t->trx;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)]
+ .lchan[l1sap_chan2ss(chan_nr)];
+
+ /* check if loop is enabled */
+ if (!chan_state->amr_loop)
+ return 0;
+
+ /* wait for MS to use the requested codec */
+ if (chan_state->ul_ft != chan_state->dl_cmr)
+ return 0;
+
+ /* count bit errors */
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ chan_state->ber_num += 2;
+ chan_state->ber_sum += (ber + ber);
+ } else {
+ chan_state->ber_num++;
+ chan_state->ber_sum += ber;
+ }
+
+ /* count frames */
+ if (chan_state->ber_num < 48)
+ return 0;
+
+ /* calculate average (reuse ber variable) */
+ ber = chan_state->ber_sum / chan_state->ber_num;
+
+ /* reset bit errors */
+ chan_state->ber_num = 0;
+ chan_state->ber_sum = 0;
+
+ LOGP(DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f "
+ "codec id %d of trx=%u chan_nr=0x%02x\n", ber,
+ chan_state->ul_ft, trx->nr, chan_nr);
+
+ /* degrade */
+ if (chan_state->dl_cmr > 0) {
+ /* degrade, if ber is above threshold FIXME: C/I */
+ if (ber >
+ lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr-1].threshold) {
+ LOGP(DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f "
+ "from codec id %d to %d of trx=%u "
+ "chan_nr=0x%02x\n", ber, chan_state->dl_cmr,
+ chan_state->dl_cmr - 1, trx->nr, chan_nr);
+ chan_state->dl_cmr--;
+ }
+
+ return 0;
+ }
+
+ /* upgrade */
+ if (chan_state->dl_cmr < chan_state->codecs - 1) {
+ /* degrade, if ber is above threshold FIXME: C/I*/
+ if (ber <
+ lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].threshold
+ - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].hysteresis) {
+ LOGP(DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f "
+ "from codec id %d to %d of trx=%u "
+ "chan_nr=0x%02x\n", ber, chan_state->dl_cmr,
+ chan_state->dl_cmr + 1, trx->nr, chan_nr);
+ chan_state->dl_cmr++;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop)
+{
+ if (chan_state->amr_loop && !loop) {
+ chan_state->amr_loop = 0;
+
+ return 0;
+ }
+
+ if (!chan_state->amr_loop && loop) {
+ chan_state->amr_loop = 1;
+
+ /* reset bit errors */
+ chan_state->ber_num = 0;
+ chan_state->ber_sum = 0;
+
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h
new file mode 100644
index 00000000..f9e69c84
--- /dev/null
+++ b/src/osmo-bts-trx/loops.h
@@ -0,0 +1,27 @@
+#ifndef _TRX_LOOPS_H
+#define _TRX_LOOPS_H
+
+/*
+ * calibration of loops
+ */
+
+/* how much power levels do we raise/lower as maximum (1 level = 2 dB) */
+#define MS_RAISE_MAX 4
+#define MS_LOWER_MAX 2
+
+/*
+ * loops api
+ */
+
+int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa);
+
+int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state);
+
+int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, float ber);
+
+int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop);
+
+#endif /* _TRX_LOOPS_H */
diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c
new file mode 100644
index 00000000..9529190e
--- /dev/null
+++ b/src/osmo-bts-trx/main.c
@@ -0,0 +1,151 @@
+/* Main program for OsmoBTS-TRX */
+
+/* (C) 2011-2015 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sched.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/bits.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/control_if.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+
+/* dummy, since no direct dsp support */
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+void bts_model_print_help()
+{
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "",
+ long_options, &option_idx);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ bts->variant = BTS_OSMO_TRX;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ /* FIXME: this needs to be overridden with the real hardrware
+ * value */
+ bts->c0->nominal_power = 23;
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_CBCH);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+ plink->u.osmotrx.local_ip = talloc_strdup(plink, "127.0.0.1");
+ plink->u.osmotrx.remote_ip = talloc_strdup(plink, "127.0.0.1");
+ plink->u.osmotrx.base_port_local = 5800;
+ plink->u.osmotrx.base_port_remote = 5700;
+ plink->u.osmotrx.clock_advance = 20;
+ plink->u.osmotrx.rts_advance = 5;
+ plink->u.osmotrx.trx_ta_loop = true;
+ plink->u.osmotrx.trx_ms_power_loop = false;
+ plink->u.osmotrx.trx_target_rssi = -10;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h;
+ l1h = trx_l1h_alloc(tall_bts_ctx, pinst);
+ pinst->u.osmotrx.hdl = l1h;
+
+ l1h->config.power_oml = 1;
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c
new file mode 100644
index 00000000..fa3aed22
--- /dev/null
+++ b/src/osmo-bts-trx/scheduler_trx.c
@@ -0,0 +1,1635 @@
+/* Scheduler worker functions for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <sys/timerfd.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer_compat.h>
+#include <osmocom/codec/codec.h>
+#include <osmocom/codec/ecu.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/a5.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+#include "loops.h"
+
+extern void *tall_bts_ctx;
+
+/* Maximum size of a EGPRS message in bytes */
+#define EGPRS_0503_MAX_BYTES 155
+
+
+/* Compute the bit error rate in 1/10000 units */
+static inline uint16_t compute_ber10k(int n_bits_total, int n_errors)
+{
+ if (n_bits_total == 0)
+ return 10000;
+ else
+ return 10000 * n_errors / n_bits_total;
+}
+
+/*
+ * TX on downlink
+ */
+
+/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */
+ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting IDLE\n");
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ return NULL;
+}
+
+/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */
+ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n");
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ /* BURST BYPASS */
+
+ return (ubit_t *) _sched_fcch_burst;
+}
+
+/* obtain a to-be-transmitted SCH (synchronization channel) burst */
+ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ static ubit_t bits[GSM_BURST_LEN], burst[78];
+ uint8_t sb_info[4];
+ struct gsm_time t;
+ uint8_t t3p, bsic;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n");
+
+ /* BURST BYPASS */
+
+ /* create SB info from GSM time and BSIC */
+ gsm_fn2gsmtime(&t, fn);
+ t3p = t.t3 / 10;
+ bsic = l1t->trx->bts->bsic;
+ sb_info[0] =
+ ((bsic & 0x3f) << 2) |
+ ((t.t1 & 0x600) >> 9);
+ sb_info[1] =
+ ((t.t1 & 0x1fe) >> 1);
+ sb_info[2] =
+ ((t.t1 & 0x001) << 7) |
+ ((t.t2 & 0x1f) << 2) |
+ ((t3p & 0x6) >> 1);
+ sb_info[3] =
+ (t3p & 0x1);
+
+ /* encode bursts */
+ gsm0503_sch_encode(burst, sb_info);
+
+ /* compose burst */
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 39);
+ memcpy(bits + 42, _sched_sch_train, 64);
+ memcpy(bits + 106, burst + 39, 39);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ return bits;
+}
+
+/* obtain a to-be-transmitted data (SACCH/SDCCH) burst */
+ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ uint8_t link_id = trx_chan_desc[chan].link_id;
+ uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn;
+ struct msgb *msg = NULL; /* make GCC happy */
+ ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
+ static ubit_t bits[GSM_BURST_LEN];
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ /* send clock information to loops process */
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ trx_loop_sacch_clock(l1t, chan_nr, &l1ts->chan_state[chan]);
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg)
+ goto got_msg;
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
+
+no_msg:
+ /* free burst memory */
+ if (*bursts_p) {
+ talloc_free(*bursts_p);
+ *bursts_p = NULL;
+ }
+ return NULL;
+
+got_msg:
+ /* check validity of message */
+ if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
+ "(len=%d)\n", msgb_l2len(msg));
+ /* free message */
+ msgb_free(msg);
+ goto no_msg;
+ }
+
+ /* BURST BYPASS */
+
+ /* handle loss detection of SACCH */
+ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
+ /* count and send BFI */
+ if (++(l1ts->chan_state[chan].lost_frames) > 1) {
+ /* TODO: Should we pass old TOA here? Otherwise we risk
+ * unnecessary decreasing TA */
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn,
+ 456, 456, -110, 0);
+ /* FIXME: use actual values for BER etc */
+ _sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0,
+ -110, 0, 0, 10000,
+ PRES_INFO_INVALID);
+ }
+ }
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 464);
+ if (!*bursts_p)
+ return NULL;
+ }
+
+ /* encode bursts */
+ gsm0503_xcch_encode(*bursts_p, msg->l2h);
+
+ /* free message */
+ msgb_free(msg);
+
+send_burst:
+ /* compose burst */
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+/* obtain a to-be-transmitted PDTCH (packet data) burst */
+ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ struct msgb *msg = NULL; /* make GCC happy */
+ ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
+ enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type;
+ static ubit_t bits[EGPRS_BURST_LEN];
+ int rc = 0;
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg)
+ goto got_msg;
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
+
+no_msg:
+ /* free burst memory */
+ if (*bursts_p) {
+ talloc_free(*bursts_p);
+ *bursts_p = NULL;
+ }
+ return NULL;
+
+got_msg:
+ /* BURST BYPASS */
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx,
+ GSM0503_EGPRS_BURSTS_NBITS);
+ if (!*bursts_p)
+ return NULL;
+ }
+
+ /* encode bursts */
+ rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
+ if (rc < 0)
+ rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
+
+ /* check validity of message */
+ if (rc < 0) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! "
+ "(len=%ld)\n", msg->tail - msg->l2h);
+ /* free message */
+ msgb_free(msg);
+ goto no_msg;
+ } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) {
+ *burst_type = TRX_BURST_8PSK;
+ } else {
+ *burst_type = TRX_BURST_GMSK;
+ }
+
+ /* free message */
+ msgb_free(msg);
+
+send_burst:
+ /* compose burst */
+ if (*burst_type == TRX_BURST_8PSK) {
+ burst = *bursts_p + bid * 348;
+ memset(bits, 1, 9);
+ memcpy(bits + 9, burst, 174);
+ memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78);
+ memcpy(bits + 261, burst + 174, 174);
+ memset(bits + 435, 1, 9);
+
+ if (nbits)
+ *nbits = EGPRS_BURST_LEN;
+ } else {
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+ }
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+/* determine if the FN is transmitting a CMR (1) or not (0) */
+static inline int fn_is_codec_mode_request(uint32_t fn)
+{
+ return (((fn + 4) % 26) >> 2) & 1;
+}
+
+/* common section for generation of TCH bursts (TCH/H and TCH/F) */
+static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch,
+ struct msgb **_msg_facch)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ struct osmo_phsap_prim *l1sap;
+
+ /* handle loss detection of received TCH frames */
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+ && ++(chan_state->lost_frames) > 5) {
+ uint8_t tch_data[GSM_FR_BYTES];
+ int len;
+
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Missing TCH bursts detected, sending BFI\n");
+
+ /* indicate bad frame */
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (chan != TRXC_TCHF) { /* HR */
+ tch_data[0] = 0x70; /* F = 0, FT = 111 */
+ memset(tch_data + 1, 0, 14);
+ len = 15;
+ break;
+ }
+ memset(tch_data, 0, GSM_FR_BYTES);
+ len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (chan != TRXC_TCHF)
+ goto inval_mode1;
+ memset(tch_data, 0, GSM_EFR_BYTES);
+ len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ len = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft], AMR_BAD);
+ if (len < 2)
+ break;
+ memset(tch_data + 2, 0, len - 2);
+ _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len);
+ break;
+ default:
+inval_mode1:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+ len = 0;
+ }
+ if (len)
+ _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len);
+ }
+
+ /* get frame and unlink from queue */
+ msg1 = _sched_dequeue_prim(l1t, tn, fn, chan);
+ msg2 = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg1) {
+ l1sap = msgb_l1sap_prim(msg1);
+ if (l1sap->oph.primitive == PRIM_TCH) {
+ msg_tch = msg1;
+ if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive == PRIM_TCH) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "TCH twice, please FIX!\n");
+ msgb_free(msg2);
+ } else
+ msg_facch = msg2;
+ }
+ } else {
+ msg_facch = msg1;
+ if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive != PRIM_TCH) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "FACCH twice, please FIX!\n");
+ msgb_free(msg2);
+ } else
+ msg_tch = msg2;
+ }
+ }
+ } else if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive == PRIM_TCH)
+ msg_tch = msg2;
+ else
+ msg_facch = msg2;
+ }
+
+ /* check validity of message */
+ if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
+ "(len=%d)\n", msgb_l2len(msg_facch));
+ /* free message */
+ msgb_free(msg_facch);
+ msg_facch = NULL;
+ }
+
+ /* check validity of message, get AMR ft and cmr */
+ if (!msg_facch && msg_tch) {
+ int len;
+ uint8_t cmr_codec;
+ int cmr, ft, i;
+ enum osmo_amr_type ft_codec;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
+ "because we are not in speech mode\n");
+ goto free_bad_msg;
+ }
+
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (chan != TRXC_TCHF) /* HR */
+ len = 15;
+ else
+ len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (chan != TRXC_TCHF)
+ goto inval_mode2;
+ len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch),
+ &cmr_codec, &cmi, &ft_codec,
+ &bfi, &sti);
+ cmr = -1;
+ ft = -1;
+ for (i = 0; i < chan_state->codecs; i++) {
+ if (chan_state->codec[i] == cmr_codec)
+ cmr = i;
+ if (chan_state->codec[i] == ft_codec)
+ ft = i;
+ }
+ if (cmr >= 0) { /* new request */
+ chan_state->dl_cmr = cmr;
+ /* disable AMR loop */
+ trx_loop_amr_set(chan_state, 0);
+ } else {
+ /* enable AMR loop */
+ trx_loop_amr_set(chan_state, 1);
+ }
+ if (ft < 0) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+ goto free_bad_msg;
+ }
+ if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) "
+ " of RTP cannot be changed now, but in next frame\n", ft_codec);
+ goto free_bad_msg;
+ }
+ chan_state->dl_ft = ft;
+ if (bfi == AMR_BAD) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Transmitting 'bad AMR frame'\n");
+ goto free_bad_msg;
+ }
+ break;
+ default:
+inval_mode2:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+ goto free_bad_msg;
+ }
+ if (len < 0) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n");
+ goto free_bad_msg;
+ }
+ if (msgb_l2len(msg_tch) != len) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with "
+ "invalid length! (expecting %d, received %d)\n",
+ len, msgb_l2len(msg_tch));
+free_bad_msg:
+ /* free message */
+ msgb_free(msg_tch);
+ msg_tch = NULL;
+ goto send_frame;
+ }
+ }
+
+send_frame:
+ *_msg_tch = msg_tch;
+ *_msg_facch = msg_facch;
+}
+
+/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
+ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct msgb *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ uint8_t tch_mode = chan_state->tch_mode;
+ ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
+ static ubit_t bits[GSM_BURST_LEN];
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
+
+ /* BURST BYPASS */
+
+ /* allocate burst memory, if not already,
+ * otherwise shift buffer by 4 bursts for interleaving */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 928);
+ if (!*bursts_p)
+ return NULL;
+ } else {
+ memcpy(*bursts_p, *bursts_p + 464, 464);
+ memset(*bursts_p + 464, 0, 464);
+ }
+
+ /* no message at all */
+ if (!msg_tch && !msg_facch) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
+ goto send_burst;
+ }
+
+ /* encode bursts (prioritize FACCH) */
+ if (msg_facch)
+ gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch),
+ 1);
+ else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ /* the first FN 4,13,21 defines that CMI is included in frame,
+ * the first FN 0,8,17 defines that CMR is included in frame.
+ */
+ gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2,
+ msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
+ chan_state->codec, chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ else
+ gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1);
+
+ /* free message */
+ if (msg_tch)
+ msgb_free(msg_tch);
+ if (msg_facch)
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */
+ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct msgb *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ uint8_t tch_mode = chan_state->tch_mode;
+ ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
+ static ubit_t bits[GSM_BURST_LEN];
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ /* get TCH and/or FACCH */
+ tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
+
+ /* check for FACCH alignment */
+ if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on "
+ "even frames, please fix RTS!\n");
+ msgb_free(msg_facch);
+ msg_facch = NULL;
+ }
+
+ /* BURST BYPASS */
+
+ /* allocate burst memory, if not already,
+ * otherwise shift buffer by 2 bursts for interleaving */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 696);
+ if (!*bursts_p)
+ return NULL;
+ } else {
+ memcpy(*bursts_p, *bursts_p + 232, 232);
+ if (chan_state->dl_ongoing_facch) {
+ memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+ memset(*bursts_p + 464, 0, 232);
+ } else {
+ memset(*bursts_p + 232, 0, 232);
+ }
+ }
+
+ /* no message at all */
+ if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
+ goto send_burst;
+ }
+
+ /* encode bursts (prioritize FACCH) */
+ if (msg_facch) {
+ gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch));
+ chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */
+ } else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */
+ chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */
+ else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ /* the first FN 4,13,21 or 5,14,22 defines that CMI is included
+ * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is
+ * included in frame. */
+ gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2,
+ msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
+ chan_state->codec, chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ else
+ gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch));
+
+ /* free message */
+ if (msg_tch)
+ msgb_free(msg_tch);
+ if (msg_facch)
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+
+/*
+ * RX on uplink (indication to upper layer)
+ */
+
+int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ uint8_t chan_nr;
+ struct osmo_phsap_prim l1sap;
+ int n_errors, n_bits_total;
+ uint8_t ra;
+ int rc;
+
+ chan_nr = trx_chan_desc[chan].chan_nr | tn;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received RACH toa=%d\n", toa256);
+
+ /* decode */
+ rc = gsm0503_rach_decode_ber(&ra, bits + 8 + 41, l1t->trx->bts->bsic, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad AB frame\n");
+ return 0;
+ }
+
+ /* compose primitive */
+ /* generate prim */
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ NULL);
+ l1sap.u.rach_ind.chan_nr = chan_nr;
+ l1sap.u.rach_ind.ra = ra;
+ l1sap.u.rach_ind.acc_delay = (toa256 >= 0) ? toa256/256 : 0;
+ l1sap.u.rach_ind.fn = fn;
+
+ /* 11bit RACH is not supported for osmo-trx */
+ l1sap.u.rach_ind.is_11bit = 0;
+ l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
+ l1sap.u.rach_ind.rssi = rssi;
+ l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
+ l1sap.u.rach_ind.acc_delay_256bits = toa256;
+
+ /* forward primitive */
+ l1sap_up(l1t->trx, &l1sap);
+
+ return 0;
+}
+
+/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */
+int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ float *rssi_sum = &chan_state->rssi_sum;
+ uint8_t *rssi_num = &chan_state->rssi_num;
+ int32_t *toa256_sum = &chan_state->toa256_sum;
+ uint8_t *toa_num = &chan_state->toa_num;
+ uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
+ int n_errors, n_bits_total;
+ uint16_t ber10k;
+ int rc;
+
+ /* handle RACH, if handover RACH detection is turned on */
+ if (chan_state->ho_rach_detect == 1)
+ return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received Data, bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 464);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst & store frame number of first burst */
+ if (bid == 0) {
+ memset(*bursts_p, 0, 464);
+ *mask = 0x0;
+ *first_fn = fn;
+ *rssi_sum = 0;
+ *rssi_num = 0;
+ *toa256_sum = 0;
+ *toa_num = 0;
+ }
+
+ /* update mask + RSSI */
+ *mask |= (1 << bid);
+ *rssi_sum += rssi;
+ (*rssi_num)++;
+ *toa256_sum += toa256;
+ (*toa_num)++;
+
+ /* copy burst to buffer of 4 bursts */
+ burst = *bursts_p + bid * 116;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+
+ /* send burst information to loops process */
+ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
+ trx_loop_sacch_input(l1t, trx_chan_desc[chan].chan_nr | tn,
+ chan_state, rssi, toa256);
+ }
+
+ /* wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete data (%u/%u)\n",
+ *first_fn, (*first_fn) % l1ts->mf_period);
+
+ /* we require first burst to have correct FN */
+ if (!(*mask & 0x1)) {
+ *mask = 0x0;
+ return 0;
+ }
+ }
+ *mask = 0x0;
+
+ /* decode */
+ rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n",
+ *first_fn, (*first_fn) % l1ts->mf_period);
+ l2_len = 0;
+ } else
+ l2_len = GSM_MACBLOCK_LEN;
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn,
+ n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num);
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+ return _sched_compose_ph_data_ind(l1t, tn, *first_fn, chan, l2, l2_len,
+ *rssi_sum / *rssi_num,
+ 4 * (*toa256_sum) / *toa_num, 0, ber10k,
+ PRES_INFO_UNKNOWN);
+}
+
+/*! \brief a single PDTCH burst was received by the PHY, process it */
+int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ float *rssi_sum = &chan_state->rssi_sum;
+ uint8_t *rssi_num = &chan_state->rssi_num;
+ int32_t *toa256_sum = &chan_state->toa256_sum;
+ uint8_t *toa_num = &chan_state->toa_num;
+ uint8_t l2[EGPRS_0503_MAX_BYTES];
+ int n_errors, n_bursts_bits, n_bits_total;
+ uint16_t ber10k;
+ int rc;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received PDTCH bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx,
+ GSM0503_EGPRS_BURSTS_NBITS);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst */
+ if (bid == 0) {
+ memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS);
+ *mask = 0x0;
+ *first_fn = fn;
+ *rssi_sum = 0;
+ *rssi_num = 0;
+ *toa256_sum = 0;
+ *toa_num = 0;
+ }
+
+ /* update mask + rssi */
+ *mask |= (1 << bid);
+ *rssi_sum += rssi;
+ (*rssi_num)++;
+ *toa256_sum += toa256;
+ (*toa_num)++;
+
+ /* copy burst to buffer of 4 bursts */
+ if (nbits == EGPRS_BURST_LEN) {
+ burst = *bursts_p + bid * 348;
+ memcpy(burst, bits + 9, 174);
+ memcpy(burst + 174, bits + 261, 174);
+ n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
+ } else {
+ burst = *bursts_p + bid * 116;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+ n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
+ }
+
+ /* wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ }
+ *mask = 0x0;
+
+ /*
+ * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we
+ * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS,
+ * then we incur decoding overhead of 31 bits on the Type 3 EGPRS
+ * header, which is tolerable.
+ */
+ rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits,
+ NULL, &n_errors, &n_bits_total);
+
+ if ((nbits == GSM_BURST_LEN) && (rc < 0)) {
+ rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL,
+ &n_errors, &n_bits_total);
+ }
+
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn,
+ n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num);
+
+ if (rc <= 0) {
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad PDTCH (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ return 0;
+ }
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+ return _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 3) % GSM_HYPERFRAME, chan,
+ l2, rc, *rssi_sum / *rssi_num, 4 * (*toa256_sum) / *toa_num, 0,
+ ber10k, PRES_INFO_BOTH);
+}
+
+/*! \brief a single TCH/F burst was received by the PHY, process it */
+int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ uint8_t tch_data[128]; /* just to be safe */
+ int rc, amr = 0;
+ int n_errors, n_bits_total;
+ bool bfi_flag = false;
+ struct gsm_lchan *lchan =
+ get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn);
+
+ /* handle rach, if handover rach detection is turned on */
+ if (chan_state->ho_rach_detect == 1)
+ return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/F, bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 928);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst */
+ if (bid == 0) {
+ memset(*bursts_p + 464, 0, 464);
+ *mask = 0x0;
+ *first_fn = fn;
+ }
+
+ /* update mask */
+ *mask |= (1 << bid);
+
+ /* copy burst to end of buffer of 8 bursts */
+ burst = *bursts_p + bid * 116 + 464;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+
+ /* wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ }
+ *mask = 0x0;
+
+ /* decode
+ * also shift buffer by 4 bursts for interleaving */
+ switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
+ : tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total);
+ if (rc >= 0)
+ lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total);
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /* the first FN 0,8,17 defines that CMI is included in frame,
+ * the first FN 4,13,21 defines that CMR is included in frame.
+ * NOTE: A frame ends 7 FN after start.
+ */
+ rc = gsm0503_tch_afs_decode(tch_data + 2, *bursts_p,
+ (((fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec,
+ chan_state->codecs, &chan_state->ul_ft,
+ &chan_state->ul_cmr, &n_errors, &n_bits_total);
+ if (rc)
+ trx_loop_amr_input(l1t,
+ trx_chan_desc[chan].chan_nr | tn, chan_state,
+ (float)n_errors/(float)n_bits_total);
+ amr = 2; /* we store tch_data + 2 header bytes */
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->ul_cmr],
+ chan_state->codec[chan_state->ul_ft], AMR_GOOD);
+ }
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+ memcpy(*bursts_p, *bursts_p + 464, 464);
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn,
+ n_errors, n_bits_total, rssi, toa256);
+
+ /* Check if the frame is bad */
+ if (rc < 0) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ bfi_flag = true;
+ goto bfi;
+ }
+ if (rc < 4) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) "
+ "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc);
+ bfi_flag = true;
+ goto bfi;
+ }
+
+ /* FACCH */
+ if (rc == GSM_MACBLOCK_LEN) {
+ uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
+ _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan,
+ tch_data + amr, GSM_MACBLOCK_LEN, rssi, 4 * toa256, 0,
+ ber10k, PRES_INFO_UNKNOWN);
+bfi:
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
+ /* indicate bad frame */
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ if (lchan->tch.dtx.ul_sid) {
+ /* DTXu: pause in progress. Push empty payload to upper layers */
+ rc = 0;
+ goto compose_l1sap;
+ }
+
+ /* Perform error concealment if possible */
+ rc = osmo_ecu_fr_conceal(&lchan->ecu_state.fr, tch_data);
+ if (rc) {
+ memset(tch_data, 0, GSM_FR_BYTES);
+ tch_data[0] = 0xd0;
+ }
+
+ rc = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ memset(tch_data, 0, GSM_EFR_BYTES);
+ tch_data[0] = 0xc0;
+ rc = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft],
+ AMR_BAD);
+ if (rc < 2)
+ break;
+ memset(tch_data + 2, 0, rc - 2);
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "TCH mode %u invalid, please fix!\n", tch_mode);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
+ return 0;
+
+ /* Reset ECU with a good frame */
+ if (!bfi_flag && tch_mode == GSM48_CMODE_SPEECH_V1)
+ osmo_ecu_fr_reset(&lchan->ecu_state.fr, tch_data);
+
+ /* TCH or BFI */
+compose_l1sap:
+ return _sched_compose_tch_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan,
+ tch_data, rc);
+}
+
+/*! \brief a single TCH/H burst was received by the PHY, process it */
+int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ uint8_t tch_data[128]; /* just to be safe */
+ int rc, amr = 0;
+ int n_errors, n_bits_total;
+ struct gsm_lchan *lchan =
+ get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn);
+ /* Note on FN-10: If we are at FN 10, we decoded an even aligned
+ * TCH/FACCH frame, because our burst buffer carries 6 bursts.
+ * Even FN ending at: 10,11,19,20,2,3
+ */
+ int fn_is_odd = (((fn + 26 - 10) % 26) >> 2) & 1;
+
+ /* handle RACH, if handover RACH detection is turned on */
+ if (chan_state->ho_rach_detect == 1)
+ return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/H, bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 696);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst */
+ if (bid == 0) {
+ memset(*bursts_p + 464, 0, 232);
+ *mask = 0x0;
+ *first_fn = fn;
+ }
+
+ /* update mask */
+ *mask |= (1 << bid);
+
+ /* copy burst to end of buffer of 6 bursts */
+ burst = *bursts_p + bid * 116 + 464;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+
+ /* wait until complete set of bursts */
+ if (bid != 1)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0x3) != 0x3) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ }
+ *mask = 0x0;
+
+ /* skip second of two TCH frames of FACCH was received */
+ if (chan_state->ul_ongoing_facch) {
+ chan_state->ul_ongoing_facch = 0;
+ memcpy(*bursts_p, *bursts_p + 232, 232);
+ memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+ goto bfi;
+ }
+
+ /* decode
+ * also shift buffer by 4 bursts for interleaving */
+ switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
+ : tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* HR or signalling */
+ /* Note on FN-10: If we are at FN 10, we decoded an even aligned
+ * TCH/FACCH frame, because our burst buffer carries 6 bursts.
+ * Even FN ending at: 10,11,19,20,2,3
+ */
+ rc = gsm0503_tch_hr_decode(tch_data, *bursts_p,
+ fn_is_odd, &n_errors, &n_bits_total);
+ if (rc) /* DTXu */
+ lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan);
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /* the first FN 0,8,17 or 1,9,18 defines that CMI is included
+ * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR
+ * is included in frame.
+ */
+ rc = gsm0503_tch_ahs_decode(tch_data + 2, *bursts_p,
+ fn_is_odd, fn_is_odd, chan_state->codec,
+ chan_state->codecs, &chan_state->ul_ft,
+ &chan_state->ul_cmr, &n_errors, &n_bits_total);
+ if (rc)
+ trx_loop_amr_input(l1t,
+ trx_chan_desc[chan].chan_nr | tn, chan_state,
+ (float)n_errors/(float)n_bits_total);
+ amr = 2; /* we store tch_data + 2 two */
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->ul_cmr],
+ chan_state->codec[chan_state->ul_ft], AMR_GOOD);
+ }
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+ memcpy(*bursts_p, *bursts_p + 232, 232);
+ memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn,
+ n_errors, n_bits_total, rssi, toa256);
+
+ /* Check if the frame is bad */
+ if (rc < 0) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ goto bfi;
+ }
+ if (rc < 4) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) "
+ "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc);
+ goto bfi;
+ }
+
+ /* FACCH */
+ if (rc == GSM_MACBLOCK_LEN) {
+ chan_state->ul_ongoing_facch = 1;
+ uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
+ _sched_compose_ph_data_ind(l1t, tn,
+ (fn + GSM_HYPERFRAME - 10 - ((fn % 26) >= 19)) % GSM_HYPERFRAME, chan,
+ tch_data + amr, GSM_MACBLOCK_LEN, rssi, toa256/64, 0,
+ ber10k, PRES_INFO_UNKNOWN);
+bfi:
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
+ /* indicate bad frame */
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* HR */
+ if (lchan->tch.dtx.ul_sid) {
+ /* DTXu: pause in progress. Push empty payload to upper layers */
+ rc = 0;
+ goto compose_l1sap;
+ }
+ tch_data[0] = 0x70; /* F = 0, FT = 111 */
+ memset(tch_data + 1, 0, 14);
+ rc = 15;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft],
+ AMR_BAD);
+ if (rc < 2)
+ break;
+ memset(tch_data + 2, 0, rc - 2);
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "TCH mode %u invalid, please fix!\n", tch_mode);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
+ return 0;
+
+compose_l1sap:
+ /* TCH or BFI */
+ /* Note on FN 19 or 20: If we received the last burst of a frame,
+ * it actually starts at FN 8 or 9. A burst starting there, overlaps
+ * with the slot 12, so an extra FN must be subtracted to get correct
+ * start of frame.
+ */
+ return _sched_compose_tch_ind(l1t, tn,
+ (fn + GSM_HYPERFRAME - 10 - ((fn%26)==19) - ((fn%26)==20)) % GSM_HYPERFRAME,
+ chan, tch_data, rc);
+}
+
+/* schedule all frames of all TRX for given FN */
+static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
+ const ubit_t *bits;
+ uint8_t gain;
+ uint16_t nbits = 0;
+
+ /* send time indication */
+ l1if_mph_time_ind(bts, fn);
+
+ /* process every TRX */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ struct l1sched_trx *l1t = &l1h->l1s;
+
+ /* advance frame number, so the transceiver has more
+ * time until it must be transmitted. */
+ fn = (fn + plink->u.osmotrx.clock_advance) % GSM_HYPERFRAME;
+
+ /* we don't schedule, if power is off */
+ if (!trx_if_powered(l1h))
+ continue;
+
+ /* process every TS of TRX */
+ for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ /* ready-to-send */
+ _sched_rts(l1t, tn,
+ (fn + plink->u.osmotrx.rts_advance) % GSM_HYPERFRAME);
+ /* get burst for FN */
+ bits = _sched_dl_burst(l1t, tn, fn, &nbits);
+ if (!bits) {
+ /* if no bits, send no burst */
+ continue;
+ } else
+ gain = 0;
+ if (nbits)
+ trx_if_send_burst(l1h, tn, fn, gain, bits, nbits);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * TRX frame clock handling
+ *
+ * In a "normal" synchronous PHY layer, we would be polled every time
+ * the PHY needs data for a given frame number. However, the
+ * OpenBTS-inherited TRX protocol works differently: We (L1) must
+ * autonomously send burst data based on our own clock, and every so
+ * often (currently every ~ 216 frames), we get a clock indication from
+ * the TRX.
+ *
+ * We're using a MONOTONIC timerfd interval timer for the 4.615ms frame
+ * intervals, and then compute + send the 8 bursts for that frame.
+ *
+ * Upon receiving a clock indication from the TRX, we compensate
+ * accordingly: If we were transmitting too fast, we're delaying the
+ * next interval timer accordingly. If we were too slow, we immediately
+ * send burst data for the missing frame numbers.
+ */
+
+/*! clock state of a given TRX */
+struct osmo_trx_clock_state {
+ /*! number of FN periods without TRX clock indication */
+ uint32_t fn_without_clock_ind;
+ struct {
+ /*! last FN we processed based on FN period timer */
+ uint32_t fn;
+ /*! time at which we last processed FN */
+ struct timespec tv;
+ } last_fn_timer;
+ struct {
+ /*! last FN we received a clock indication for */
+ uint32_t fn;
+ /*! time at which we received the last clock indication */
+ struct timespec tv;
+ } last_clk_ind;
+ /*! Osmocom FD wrapper for timerfd */
+ struct osmo_fd fn_timer_ofd;
+};
+
+/* TODO: This must go and become part of the phy_link */
+static struct osmo_trx_clock_state g_clk_s = { .fn_timer_ofd.fd = -1 };
+
+/*! duration of a GSM frame in nano-seconds. (120ms/26) */
+#define FRAME_DURATION_nS 4615384
+/*! duration of a GSM frame in micro-seconds (120s/26) */
+#define FRAME_DURATION_uS (FRAME_DURATION_nS/1000)
+/*! maximum number of 'missed' frame periods we can tolerate of OS doesn't schedule us*/
+#define MAX_FN_SKEW 50
+/*! maximum number of frame periods we can tolerate without TRX Clock Indication*/
+#define TRX_LOSS_FRAMES 400
+
+/*! compute the number of micro-seconds difference elapsed between \a last and \a now */
+static inline int64_t compute_elapsed_us(const struct timespec *last, const struct timespec *now)
+{
+ struct timespec elapsed;
+
+ timespecsub(now, last, &elapsed);
+ return (int64_t)(elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
+}
+
+/*! compute the number of frame number intervals elapsed between \a last and \a now */
+static inline int compute_elapsed_fn(const uint32_t last, const uint32_t now)
+{
+ int elapsed_fn = (now + GSM_HYPERFRAME - last) % GSM_HYPERFRAME;
+ if (elapsed_fn >= 135774)
+ elapsed_fn -= GSM_HYPERFRAME;
+ return elapsed_fn;
+}
+
+/*! normalise given 'struct timespec', i.e. carry nanoseconds into seconds */
+static inline void normalize_timespec(struct timespec *ts)
+{
+ ts->tv_sec += ts->tv_nsec / 1000000000;
+ ts->tv_nsec = ts->tv_nsec % 1000000000;
+}
+
+/*! Increment a GSM frame number modulo GSM_HYPERFRAME */
+#define INCREMENT_FN(fn) (fn) = (((fn) + 1) % GSM_HYPERFRAME)
+
+extern int quit;
+
+/*! this is the timerfd-callback firing for every FN to be processed */
+static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct gsm_bts *bts = ofd->data;
+ struct osmo_trx_clock_state *tcs = &g_clk_s;
+ struct timespec tv_now;
+ uint64_t expire_count;
+ int64_t elapsed_us, error_us;
+ int rc, i;
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ if (expire_count > 1) {
+ LOGP(DL1C, LOGL_NOTICE, "FN timer expire_count=%"PRIu64": We missed %"PRIu64" timers\n",
+ expire_count, expire_count-1);
+ }
+
+ /* check if transceiver is still alive */
+ if (tcs->fn_without_clock_ind++ == TRX_LOSS_FRAMES) {
+ LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n");
+ goto no_clock;
+ }
+
+ /* compute actual elapsed time and resulting OS scheduling error */
+ clock_gettime(CLOCK_MONOTONIC, &tv_now);
+ elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now);
+ error_us = elapsed_us - FRAME_DURATION_uS;
+#ifdef DEBUG_CLOCK
+ printf("%s(): %09ld, elapsed_us=%05" PRId64 ", error_us=%-d: fn=%d\n", __func__,
+ tv_now.tv_nsec, elapsed_us, error_us, tcs->last_fn_timer.fn+1);
+#endif
+ tcs->last_fn_timer.tv = tv_now;
+
+ /* if someone played with clock, or if the process stalled */
+ if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) {
+ LOGP(DL1C, LOGL_ERROR, "PC clock skew: elapsed_us=%" PRId64 ", error_us=%" PRId64 "\n",
+ elapsed_us, error_us);
+ goto no_clock;
+ }
+
+ /* call trx_sched_fn() for all expired FN */
+ for (i = 0; i < expire_count; i++) {
+ INCREMENT_FN(tcs->last_fn_timer.fn);
+ trx_sched_fn(bts, tcs->last_fn_timer.fn);
+ }
+
+ return 0;
+
+no_clock:
+ osmo_timerfd_disable(&tcs->fn_timer_ofd);
+ transceiver_available = 0;
+
+ bts_shutdown(bts, "No clock from osmo-trx");
+
+ return -1;
+}
+
+/*! reset clock with current fn and schedule it. Called when trx becomes
+ * available or when max clock skew is reached */
+static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs,
+ struct timespec *tv_now, const struct timespec *interval, uint32_t fn)
+{
+ tcs->last_fn_timer.fn = fn;
+ /* call trx cheduler function for new 'last' FN */
+ trx_sched_fn(bts, tcs->last_fn_timer.fn);
+
+ /* schedule first FN clock timer */
+ osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_fn_timer_cb, bts);
+ osmo_timerfd_schedule(&tcs->fn_timer_ofd, NULL, interval);
+
+ tcs->last_fn_timer.tv = *tv_now;
+ tcs->last_clk_ind.tv = *tv_now;
+ tcs->last_clk_ind.fn = fn;
+
+ return 0;
+}
+
+/*! called every time we receive a clock indication from TRX */
+int trx_sched_clock(struct gsm_bts *bts, uint32_t fn)
+{
+ struct osmo_trx_clock_state *tcs = &g_clk_s;
+ struct timespec tv_now;
+ int elapsed_fn;
+ int64_t elapsed_us, elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk;
+ unsigned int fn_caught_up = 0;
+ const struct timespec interval = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_nS };
+
+ if (quit)
+ return 0;
+
+ /* reset lost counter */
+ tcs->fn_without_clock_ind = 0;
+
+ clock_gettime(CLOCK_MONOTONIC, &tv_now);
+
+ /* clock becomes valid */
+ if (!transceiver_available) {
+ LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", fn);
+
+ transceiver_available = 1;
+
+ /* start provisioning transceiver */
+ l1if_provision_transceiver(bts);
+
+ /* tell BSC */
+ check_transceiver_availability(bts, 1);
+
+ return trx_setup_clock(bts, tcs, &tv_now, &interval, fn);
+ }
+
+ /* calculate elapsed time +fn since last timer */
+ elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now);
+ elapsed_fn = compute_elapsed_fn(tcs->last_fn_timer.fn, fn);
+#ifdef DEBUG_CLOCK
+ printf("%s(): LAST_TIMER %9ld, elapsed_us=%7d, elapsed_fn=%+3d\n", __func__,
+ tv_now.tv_nsec, elapsed_us, elapsed_fn);
+#endif
+ /* negative elapsed_fn values mean that we've already processed
+ * more FN based on the local interval timer than what the TRX
+ * now reports in the clock indication. Positive elapsed_fn
+ * values mean we still have a backlog to process */
+
+ /* calculate elapsed time +fn since last clk ind */
+ elapsed_us_since_clk = compute_elapsed_us(&tcs->last_clk_ind.tv, &tv_now);
+ elapsed_fn_since_clk = compute_elapsed_fn(tcs->last_clk_ind.fn, fn);
+ /* error (delta) between local clock since last CLK and CLK based on FN clock at TRX */
+ error_us_since_clk = elapsed_us_since_clk - (FRAME_DURATION_uS * elapsed_fn_since_clk);
+ LOGP(DL1C, LOGL_INFO, "TRX Clock Ind: elapsed_us=%7"PRId64", "
+ "elapsed_fn=%3"PRId64", error_us=%+5"PRId64"\n",
+ elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk);
+
+ /* TODO: put this computed error_us_since_clk into some filter
+ * function and use that to adjust our regular timer interval to
+ * compensate for clock drift between the PC clock and the
+ * TRX/SDR clock */
+
+ tcs->last_clk_ind.tv = tv_now;
+ tcs->last_clk_ind.fn = fn;
+
+ /* check for max clock skew */
+ if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
+ LOGP(DL1C, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
+ "new fn=%u\n", tcs->last_fn_timer.fn, fn);
+ return trx_setup_clock(bts, tcs, &tv_now, &interval, fn);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %" PRId64 "us (elapsed_fn=%d)\n",
+ elapsed_fn * FRAME_DURATION_uS - elapsed_us, elapsed_fn);
+
+ /* too many frames have been processed already */
+ if (elapsed_fn < 0) {
+ struct timespec first = interval;
+ /* set clock to the time or last FN should have been
+ * transmitted. */
+ first.tv_nsec += (0 - elapsed_fn) * FRAME_DURATION_nS;
+ normalize_timespec(&first);
+ LOGP(DL1C, LOGL_NOTICE, "We were %d FN faster than TRX, compensating\n", -elapsed_fn);
+ /* set time to the time our next FN has to be transmitted */
+ osmo_timerfd_schedule(&tcs->fn_timer_ofd, &first, &interval);
+ return 0;
+ }
+
+ /* transmit what we still need to transmit */
+ while (fn != tcs->last_fn_timer.fn) {
+ INCREMENT_FN(tcs->last_fn_timer.fn);
+ trx_sched_fn(bts, tcs->last_fn_timer.fn);
+ fn_caught_up++;
+ }
+
+ if (fn_caught_up) {
+ LOGP(DL1C, LOGL_NOTICE, "We were %d FN slower than TRX, compensated\n", elapsed_fn);
+ tcs->last_fn_timer.tv = tv_now;
+ }
+
+ return 0;
+}
+
+void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
+{
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (activate)
+ trx_if_cmd_handover(l1h, tn, ss);
+ else
+ trx_if_cmd_nohandover(l1h, tn, ss);
+}
diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c
new file mode 100644
index 00000000..abe6846d
--- /dev/null
+++ b/src/osmo-bts-trx/trx_if.c
@@ -0,0 +1,838 @@
+/*
+ * OpenBTS-style TRX interface/protocol handling
+ *
+ * This file contains the BTS-side implementation of the OpenBTS-style
+ * UDP TRX protocol. It manages the clock, control + burst-data UDP
+ * sockets and their respective protocol encoding/parsing.
+ *
+ * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2016-2017 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+
+/* enable to print RSSI level graph */
+//#define TOA_RSSI_DEBUG
+
+int transceiver_available = 0;
+
+#define TRX_MAX_BURST_LEN 512
+
+/*
+ * socket helper functions
+ */
+
+/*! convenience wrapper to open socket + fill in osmo_fd */
+static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
+ uint16_t port_local, const char *host_remote, uint16_t port_remote,
+ int (*cb)(struct osmo_fd *fd, unsigned int what))
+{
+ int rc;
+
+ /* Init */
+ ofd->fd = -1;
+ ofd->cb = cb;
+ ofd->data = priv;
+
+ /* Listen / Binds + Connect */
+ rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host_local, port_local,
+ host_remote, port_remote, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/* close socket + unregister osmo_fd */
+static void trx_udp_close(struct osmo_fd *ofd)
+{
+ if (ofd->fd >= 0) {
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+ ofd->fd = -1;
+ }
+}
+
+
+/*
+ * TRX clock socket
+ */
+
+/* get clock from clock socket */
+static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct phy_link *plink = ofd->data;
+ struct phy_instance *pinst = phy_instance_by_num(plink, 0);
+ char buf[1500];
+ int len;
+ uint32_t fn;
+
+ OSMO_ASSERT(pinst);
+
+ len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ return len;
+ buf[len] = '\0';
+
+ if (!!strncmp(buf, "IND CLOCK ", 10)) {
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on clock port: %s\n",
+ buf);
+ return 0;
+ }
+
+ if (sscanf(buf, "IND CLOCK %u", &fn) != 1) {
+ LOGP(DTRX, LOGL_ERROR, "Unable to parse '%s'\n", buf);
+ return 0;
+ }
+
+ LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn);
+
+ if (fn >= GSM_HYPERFRAME) {
+ fn %= GSM_HYPERFRAME;
+ LOGP(DTRX, LOGL_ERROR, "Indicated clock's FN is not wrapping "
+ "correctly, correcting to fn=%u\n", fn);
+ }
+
+ /* inform core TRX clock handling code that a FN has been received */
+ trx_sched_clock(pinst->trx->bts, fn);
+
+ return 0;
+}
+
+
+/*
+ * TRX ctrl socket
+ */
+
+/* send first ctrl message and start timer */
+static void trx_ctrl_send(struct trx_l1h *l1h)
+{
+ struct trx_ctrl_msg *tcm;
+ char buf[1500];
+ int len;
+
+ /* get first command */
+ if (llist_empty(&l1h->trx_ctrl_list))
+ return;
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ len = snprintf(buf, sizeof(buf), "CMD %s%s%s", tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+ OSMO_ASSERT(len < sizeof(buf));
+
+ LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to %s\n", buf, phy_instance_name(l1h->phy_inst));
+ /* send command */
+ send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0);
+
+ /* start timer */
+ osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0);
+}
+
+/* send first ctrl message and start timer */
+static void trx_ctrl_timer_cb(void *data)
+{
+ struct trx_l1h *l1h = data;
+ struct trx_ctrl_msg *tcm = NULL;
+
+ /* get first command */
+ OSMO_ASSERT(!llist_empty(&l1h->trx_ctrl_list));
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ LOGP(DTRX, LOGL_NOTICE, "No satisfactory response from transceiver for %s (CMD %s%s%s)\n",
+ phy_instance_name(l1h->phy_inst),
+ tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+
+ trx_ctrl_send(l1h);
+}
+
+void trx_if_init(struct trx_l1h *l1h)
+{
+ l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
+ l1h->trx_ctrl_timer.data = l1h;
+}
+
+/*! Send a new TRX control command.
+ * \param[inout] l1h TRX Layer1 handle to which to send command
+ * \param[in] criticial
+ * \param[in] cmd zero-terminated string containing command
+ * \param[in] fmt Format string (+ variable list of arguments)
+ * \returns 0 on success; negative on error
+ *
+ * The new ocommand will be added to the end of the control command
+ * queue.
+ */
+static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd,
+ const char *fmt, ...)
+{
+ struct trx_ctrl_msg *tcm;
+ struct trx_ctrl_msg *prev = NULL;
+ va_list ap;
+ int pending;
+
+ if (!transceiver_available &&
+ !(!strcmp(cmd, "POWEROFF") || !strcmp(cmd, "POWERON"))) {
+ LOGP(DTRX, LOGL_ERROR, "CTRL %s ignored: No clock from "
+ "transceiver, please fix!\n", cmd);
+ return -EIO;
+ }
+
+ pending = !llist_empty(&l1h->trx_ctrl_list);
+
+ /* create message */
+ tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg);
+ if (!tcm)
+ return -ENOMEM;
+ snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "%s", cmd);
+ tcm->cmd[sizeof(tcm->cmd)-1] = '\0';
+ tcm->cmd_len = strlen(tcm->cmd);
+ if (fmt && fmt[0]) {
+ va_start(ap, fmt);
+ vsnprintf(tcm->params, sizeof(tcm->params) - 1, fmt, ap);
+ va_end(ap);
+ tcm->params[sizeof(tcm->params)-1] = '\0';
+ tcm->params_len = strlen(tcm->params);
+ } else {
+ tcm->params[0] ='\0';
+ tcm->params_len = 0;
+ }
+ tcm->critical = critical;
+
+ /* Avoid adding consecutive duplicate messages, eg: two consecutive POWEROFF */
+ if(pending)
+ prev = llist_entry(l1h->trx_ctrl_list.prev, struct trx_ctrl_msg, list);
+
+ if (!pending ||
+ !(strcmp(tcm->cmd, prev->cmd) == 0 && strcmp(tcm->params, prev->params) == 0)) {
+ LOGP(DTRX, LOGL_INFO, "Enqueuing TRX control command 'CMD %s%s%s'\n",
+ tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+ llist_add_tail(&tcm->list, &l1h->trx_ctrl_list);
+ }
+
+ /* send message, if we didn't already have pending messages */
+ if (!pending)
+ trx_ctrl_send(l1h);
+
+ return 0;
+}
+
+/*! Send "POWEROFF" command to TRX */
+int trx_if_cmd_poweroff(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (pinst->num == 0)
+ return trx_ctrl_cmd(l1h, 1, "POWEROFF", "");
+ else
+ return 0;
+}
+
+/*! Send "POWERON" command to TRX */
+int trx_if_cmd_poweron(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (pinst->num == 0)
+ return trx_ctrl_cmd(l1h, 1, "POWERON", "");
+ else
+ return 0;
+}
+
+/*! Send "SETTSC" command to TRX */
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (pinst->phy_link->u.osmotrx.use_legacy_setbsic)
+ return 0;
+
+ return trx_ctrl_cmd(l1h, 1, "SETTSC", "%d", tsc);
+}
+
+/*! Send "SETBSIC" command to TRX */
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic)
+ return 0;
+
+ return trx_ctrl_cmd(l1h, 1, "SETBSIC", "%d", bsic);
+}
+
+/*! Send "SETRXGAIN" command to TRX */
+int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db);
+}
+
+/*! Send "SETPOWER" command to TRX */
+int trx_if_cmd_setpower(struct trx_l1h *l1h, int db)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db);
+}
+
+/*! Send "SETMAXDLY" command to TRX, i.e. maximum delay for RACH bursts */
+int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETMAXDLY", "%d", dly);
+}
+
+/*! Send "SETMAXDLYNB" command to TRX, i.e. maximum delay for normal bursts */
+int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETMAXDLYNB", "%d", dly);
+}
+
+/*! Send "SETSLOT" command to TRX: Configure Channel Combination for TS */
+int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type)
+{
+ return trx_ctrl_cmd(l1h, 1, "SETSLOT", "%d %d", tn, type);
+}
+
+/*! Send "RXTUNE" command to TRX: Tune Receiver to given ARFCN */
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ uint16_t freq10;
+
+ if (pinst->trx->bts->band == GSM_BAND_1900)
+ arfcn |= ARFCN_PCS;
+
+ freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n",
+ arfcn & ~ARFCN_FLAG_MASK);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100);
+}
+
+/*! Send "TXTUNE" command to TRX: Tune Transmitter to given ARFCN */
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ uint16_t freq10;
+
+ if (pinst->trx->bts->band == GSM_BAND_1900)
+ arfcn |= ARFCN_PCS;
+
+ freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n",
+ arfcn & ~ARFCN_FLAG_MASK);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100);
+}
+
+/*! Send "HANDOVER" command to TRX: Enable handover RACH Detection on timeslot/sub-slot */
+int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
+{
+ return trx_ctrl_cmd(l1h, 1, "HANDOVER", "%d %d", tn, ss);
+}
+
+/*! Send "NOHANDOVER" command to TRX: Disable handover RACH Detection on timeslot/sub-slot */
+int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
+{
+ return trx_ctrl_cmd(l1h, 1, "NOHANDOVER", "%d %d", tn, ss);
+}
+
+struct trx_ctrl_rsp {
+ char cmd[50];
+ char params[100];
+ int status;
+};
+
+static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp)
+{
+ char *p, *k;
+
+ if (strncmp(buf_in, "RSP ", 4))
+ goto parse_err;
+
+ /* Get the RSP cmd name */
+ if (!(p = strchr(buf_in + 4, ' ')))
+ goto parse_err;
+
+ if (p - buf_in >= sizeof(rsp->cmd)) {
+ LOGP(DTRX, LOGL_ERROR, "cmd buffer too small %lu >= %lu\n",
+ p - buf_in, sizeof(rsp->cmd));
+ goto parse_err;
+ }
+
+ rsp->cmd[0] = '\0';
+ strncat(rsp->cmd, buf_in + 4, p - buf_in - 4);
+
+ /* Now comes the status code of the response */
+ p++;
+ if (sscanf(p, "%d", &rsp->status) != 1)
+ goto parse_err;
+
+ /* Now copy back the parameters */
+ k = strchr(p, ' ');
+ if (k)
+ k++;
+ else
+ k = p + strlen(p);
+
+ if (strlen(k) >= sizeof(rsp->params)) {
+ LOGP(DTRX, LOGL_ERROR, "params buffer too small %lu >= %lu\n",
+ strlen(k), sizeof(rsp->params));
+ goto parse_err;
+ }
+ rsp->params[0] = '\0';
+ strcat(rsp->params, k);
+ return 0;
+
+parse_err:
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n",
+ buf_in);
+ return -1;
+}
+
+static bool cmd_matches_rsp(struct trx_ctrl_msg *tcm, struct trx_ctrl_rsp *rsp)
+{
+ if (strcmp(tcm->cmd, rsp->cmd))
+ return false;
+
+ /* For SETSLOT we also need to check if it's the response for the
+ specific timeslot. For other commands such as SETRXGAIN, it is
+ expected that they can return different values */
+ if (strcmp(tcm->cmd, "SETSLOT") == 0 && strcmp(tcm->params, rsp->params))
+ return false;
+
+ return true;
+}
+
+/* -EINVAL: unrecoverable error, exit BTS
+ * N > 0: try sending originating command again after N seconds
+ * 0: Done with response, get originating command out from send queue
+ */
+static int trx_ctrl_rx_rsp(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp, bool critical)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+
+ /* If TRX fails, try again after 1 sec */
+ if (strcmp(rsp->cmd, "POWERON") == 0) {
+ if (rsp->status == 0) {
+ if (pinst->phy_link->state != PHY_LINK_CONNECTED)
+ phy_link_state_set(pinst->phy_link, PHY_LINK_CONNECTED);
+ return 0;
+ } else {
+ LOGP(DTRX, LOGL_NOTICE,
+ "transceiver (%s) rejected POWERON command (%d), re-trying in a few seconds\n",
+ phy_instance_name(pinst), rsp->status);
+ if (pinst->phy_link->state != PHY_LINK_SHUTDOWN)
+ phy_link_state_set(pinst->phy_link, PHY_LINK_SHUTDOWN);
+ return 5;
+ }
+ }
+
+ if (rsp->status) {
+ LOGP(DTRX, critical ? LOGL_FATAL : LOGL_NOTICE,
+ "transceiver (%s) rejected TRX command with response: '%s%s%s %d'\n",
+ phy_instance_name(pinst), rsp->cmd, rsp->params[0] != '\0' ? " ":"",
+ rsp->params, rsp->status);
+ if (critical)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*! Get + parse response from TRX ctrl socket */
+static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_l1h *l1h = ofd->data;
+ struct phy_instance *pinst = l1h->phy_inst;
+ char buf[1500];
+ struct trx_ctrl_rsp rsp;
+ int len, rc;
+ struct trx_ctrl_msg *tcm;
+
+ len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ return len;
+ buf[len] = '\0';
+
+ if (parse_rsp(buf, len, &rsp) < 0)
+ return 0;
+
+ LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
+
+ /* abort timer and send next message, if any */
+ if (osmo_timer_pending(&l1h->trx_ctrl_timer))
+ osmo_timer_del(&l1h->trx_ctrl_timer);
+
+ /* get command for response message */
+ if (llist_empty(&l1h->trx_ctrl_list)) {
+ /* RSP from a retransmission, skip it */
+ if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, &rsp)) {
+ LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP "
+ "from old CMD '%s'\n", buf);
+ return 0;
+ }
+ LOGP(DTRX, LOGL_NOTICE, "Response message without "
+ "command\n");
+ return -EINVAL;
+ }
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg,
+ list);
+
+ /* check if response matches command */
+ if (!cmd_matches_rsp(tcm, &rsp)) {
+ /* RSP from a retransmission, skip it */
+ if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, &rsp)) {
+ LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP "
+ "from old CMD '%s'\n", buf);
+ return 0;
+ }
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE,
+ "Response message '%s' does not match command "
+ "message 'CMD %s%s%s'\n",
+ buf, tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+ goto rsp_error;
+ }
+
+ /* check for response code */
+ rc = trx_ctrl_rx_rsp(l1h, &rsp, tcm->critical);
+ if (rc == -EINVAL)
+ goto rsp_error;
+
+ /* re-schedule last cmd in rc seconds time */
+ if (rc > 0) {
+ osmo_timer_schedule(&l1h->trx_ctrl_timer, rc, 0);
+ return 0;
+ }
+
+ /* remove command from list, save it to last_acked and removed previous last_acked */
+ llist_del(&tcm->list);
+ talloc_free(l1h->last_acked);
+ l1h->last_acked = tcm;
+
+ trx_ctrl_send(l1h);
+
+ return 0;
+
+rsp_error:
+ bts_shutdown(pinst->trx->bts, "TRX-CTRL-MSG: CRITICAL");
+ /* keep tcm list, so process is stopped */
+ return -EIO;
+}
+
+
+/*
+ * TRX burst data socket
+ */
+
+static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_l1h *l1h = ofd->data;
+ uint8_t buf[TRX_MAX_BURST_LEN];
+ int len;
+ uint8_t tn;
+ int8_t rssi;
+ int16_t toa256 = 0;
+ uint32_t fn;
+ sbit_t bits[EGPRS_BURST_LEN];
+ int i, burst_len = GSM_BURST_LEN;
+
+ len = recv(ofd->fd, buf, sizeof(buf), 0);
+ if (len <= 0) {
+ return len;
+ } else if (len == EGPRS_BURST_LEN + 10) {
+ burst_len = EGPRS_BURST_LEN;
+ /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */
+ } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) {
+ LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght "
+ "'%d'\n", len);
+ return -EINVAL;
+ }
+ tn = buf[0];
+ fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
+ rssi = -(int8_t)buf[5];
+ toa256 = ((int16_t)(buf[6] << 8) | buf[7]);
+
+ /* copy and convert bits {254..0} to sbits {-127..127} */
+ for (i = 0; i < burst_len; i++) {
+ if (buf[8 + i] == 255)
+ bits[i] = -127;
+ else
+ bits[i] = 127 - buf[8 + i];
+ }
+
+ if (tn >= 8) {
+ LOGP(DTRX, LOGL_ERROR, "Illegal TS %d\n", tn);
+ return -EINVAL;
+ }
+ if (fn >= GSM_HYPERFRAME) {
+ LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn);
+ return -EINVAL;
+ }
+
+ LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa256=%d\n",
+ tn, fn, rssi, toa256);
+
+#ifdef TOA_RSSI_DEBUG
+ char deb[128];
+
+ sprintf(deb, "| 0 "
+ " | rssi=%4d toa=%5d fn=%u", rssi, toa256, fn);
+ deb[1 + (128 + rssi) / 4] = '*';
+ fprintf(stderr, "%s\n", deb);
+#endif
+
+ /* feed received burst into scheduler code */
+ trx_sched_ul_burst(&l1h->l1s, tn, fn, bits, burst_len, rssi, toa256);
+
+ return 0;
+}
+
+/*! Send burst data for given FN/timeslot to TRX
+ * \param[inout] l1h TRX Layer1 handle referring to TX
+ * \param[in] tn Timeslot Number (0..7)
+ * \param[in] fn GSM Frame Number
+ * \param[in] pwr Transmit Power to use
+ * \param[in] bits Unpacked bits to be transmitted
+ * \param[in] nbits Number of \a bits
+ * \returns 0 on success; negative on error */
+int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr,
+ const ubit_t *bits, uint16_t nbits)
+{
+ uint8_t buf[TRX_MAX_BURST_LEN];
+
+ if ((nbits != GSM_BURST_LEN) && (nbits != EGPRS_BURST_LEN)) {
+ LOGP(DTRX, LOGL_ERROR, "Tx burst length %u invalid\n", nbits);
+ return -1;
+ }
+
+ LOGP(DTRX, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
+
+ buf[0] = tn;
+ buf[1] = (fn >> 24) & 0xff;
+ buf[2] = (fn >> 16) & 0xff;
+ buf[3] = (fn >> 8) & 0xff;
+ buf[4] = (fn >> 0) & 0xff;
+ buf[5] = pwr;
+
+ /* copy ubits {0,1} */
+ memcpy(buf + 6, bits, nbits);
+
+ /* we must be sure that we have clock, and we have sent all control
+ * data */
+ if (transceiver_available && llist_empty(&l1h->trx_ctrl_list)) {
+ send(l1h->trx_ofd_data.fd, buf, nbits + 6, 0);
+ } else
+ LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, transceiver "
+ "offline.\n");
+
+ return 0;
+}
+
+
+/*
+ * open/close
+ */
+
+/*! flush (delete) all pending control messages */
+void trx_if_flush(struct trx_l1h *l1h)
+{
+ struct trx_ctrl_msg *tcm;
+
+ /* free ctrl message list */
+ while (!llist_empty(&l1h->trx_ctrl_list)) {
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg,
+ list);
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+ }
+ talloc_free(l1h->last_acked);
+}
+
+/*! close the TRX for given handle (data + control socket) */
+void trx_if_close(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ LOGP(DTRX, LOGL_NOTICE, "Close transceiver for %s\n",
+ phy_instance_name(pinst));
+
+ trx_if_flush(l1h);
+
+ /* close sockets */
+ trx_udp_close(&l1h->trx_ofd_ctrl);
+ trx_udp_close(&l1h->trx_ofd_data);
+}
+
+/*! compute UDP port number used for TRX protocol */
+static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data)
+{
+ struct phy_link *plink = pinst->phy_link;
+ uint16_t inc = 1;
+
+ if (is_data)
+ inc = 2;
+
+ if (remote)
+ return plink->u.osmotrx.base_port_remote + (pinst->num << 1) + inc;
+ else
+ return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc;
+}
+
+/*! open a TRX interface. creates contro + data sockets */
+static int trx_if_open(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ int rc;
+
+ LOGP(DTRX, LOGL_NOTICE, "Open transceiver for %s\n",
+ phy_instance_name(pinst));
+
+ /* initialize ctrl queue */
+ INIT_LLIST_HEAD(&l1h->trx_ctrl_list);
+
+ /* open sockets */
+ rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl,
+ plink->u.osmotrx.local_ip,
+ compute_port(pinst, 0, 0),
+ plink->u.osmotrx.remote_ip,
+ compute_port(pinst, 1, 0), trx_ctrl_read_cb);
+ if (rc < 0)
+ goto err;
+ rc = trx_udp_open(l1h, &l1h->trx_ofd_data,
+ plink->u.osmotrx.local_ip,
+ compute_port(pinst, 0, 1),
+ plink->u.osmotrx.remote_ip,
+ compute_port(pinst, 1, 1), trx_data_read_cb);
+ if (rc < 0)
+ goto err;
+
+ /* enable all slots */
+ l1h->config.slotmask = 0xff;
+
+ /* FIXME: why was this only for TRX0 ? */
+ //if (l1h->trx->nr == 0)
+ trx_if_cmd_poweroff(l1h);
+
+ return 0;
+
+err:
+ trx_if_close(l1h);
+ return rc;
+}
+
+/*! close the control + burst data sockets for one phy_instance */
+static void trx_phy_inst_close(struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ trx_if_close(l1h);
+ trx_sched_exit(&l1h->l1s);
+}
+
+/*! open the control + burst data sockets for one phy_instance */
+static int trx_phy_inst_open(struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h;
+ int rc;
+
+ l1h = pinst->u.osmotrx.hdl;
+ if (!l1h)
+ return -EINVAL;
+
+ rc = trx_sched_init(&l1h->l1s, pinst->trx);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot initialize scheduler for phy "
+ "instance %d\n", pinst->num);
+ return -EIO;
+ }
+
+ rc = trx_if_open(l1h);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open TRX interface for phy "
+ "instance %d\n", pinst->num);
+ trx_phy_inst_close(pinst);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*! open the PHY link using TRX protocol */
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst;
+ int rc;
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ /* open the shared/common clock socket */
+ rc = trx_udp_open(plink, &plink->u.osmotrx.trx_ofd_clk,
+ plink->u.osmotrx.local_ip,
+ plink->u.osmotrx.base_port_local,
+ plink->u.osmotrx.remote_ip,
+ plink->u.osmotrx.base_port_remote,
+ trx_clk_read_cb);
+ if (rc < 0) {
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ return -1;
+ }
+
+ /* open the individual instances with their ctrl+data sockets */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (trx_phy_inst_open(pinst) < 0)
+ goto cleanup;
+ }
+ /* FIXME: is there better way to check/report TRX availability? */
+ transceiver_available = 1;
+ return 0;
+
+cleanup:
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->u.osmotrx.hdl) {
+ trx_if_close(pinst->u.osmotrx.hdl);
+ pinst->u.osmotrx.hdl = NULL;
+ }
+ }
+ trx_udp_close(&plink->u.osmotrx.trx_ofd_clk);
+ return -1;
+}
+
+/*! determine if the TRX for given handle is powered up */
+int trx_if_powered(struct trx_l1h *l1h)
+{
+ return l1h->config.poweron;
+}
diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h
new file mode 100644
index 00000000..206f5e54
--- /dev/null
+++ b/src/osmo-bts-trx/trx_if.h
@@ -0,0 +1,35 @@
+#ifndef TRX_IF_H
+#define TRX_IF_H
+
+extern int transceiver_available;
+
+struct trx_l1h;
+
+struct trx_ctrl_msg {
+ struct llist_head list;
+ char cmd[28];
+ char params[100];
+ int cmd_len;
+ int params_len;
+ int critical;
+};
+
+void trx_if_init(struct trx_l1h *l1h);
+int trx_if_cmd_poweroff(struct trx_l1h *l1h);
+int trx_if_cmd_poweron(struct trx_l1h *l1h);
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc);
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic);
+int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db);
+int trx_if_cmd_setpower(struct trx_l1h *l1h, int db);
+int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly);
+int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly);
+int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type);
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn);
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn);
+int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss);
+int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss);
+int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr,
+ const ubit_t *bits, uint16_t nbits);
+int trx_if_powered(struct trx_l1h *l1h);
+
+#endif /* TRX_IF_H */
diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c
new file mode 100644
index 00000000..e9710acd
--- /dev/null
+++ b/src/osmo-bts-trx/trx_vty.c
@@ -0,0 +1,606 @@
+/* VTY interface for sysmoBTS */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/socket.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+#include "loops.h"
+
+#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n"
+
+static struct gsm_bts *vty_bts;
+
+DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
+ SHOW_STR "Display information about transceivers\n")
+{
+ struct gsm_bts *bts = vty_bts;
+ struct gsm_bts_trx *trx;
+ struct trx_l1h *l1h;
+
+ if (!transceiver_available) {
+ vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE);
+ } else {
+ vty_out(vty, "transceiver is connected%s", VTY_NEWLINE);
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ char *sname = osmo_sock_get_name(NULL, pinst->phy_link->u.osmotrx.trx_ofd_clk.fd);
+ l1h = pinst->u.osmotrx.hdl;
+ vty_out(vty, "TRX %d %s%s", trx->nr, sname, VTY_NEWLINE);
+ talloc_free(sname);
+ vty_out(vty, " %s%s",
+ (l1h->config.poweron) ? "poweron":"poweroff",
+ VTY_NEWLINE);
+ if (l1h->config.arfcn_valid)
+ vty_out(vty, " arfcn : %d%s%s",
+ (l1h->config.arfcn & ~ARFCN_PCS),
+ (l1h->config.arfcn & ARFCN_PCS) ? " (PCS)" : "",
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " arfcn : undefined%s", VTY_NEWLINE);
+ if (l1h->config.tsc_valid)
+ vty_out(vty, " tsc : %d%s", l1h->config.tsc,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " tsc : undefined%s", VTY_NEWLINE);
+ if (l1h->config.bsic_valid)
+ vty_out(vty, " bsic : %d%s", l1h->config.bsic,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " bisc : undefined%s", VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst)
+{
+ uint8_t tn;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ vty_out(vty, "PHY Instance %s%s",
+ phy_instance_name(pinst), VTY_NEWLINE);
+
+ if (l1h->config.rxgain_valid)
+ vty_out(vty, " rx-gain : %d dB%s",
+ l1h->config.rxgain, VTY_NEWLINE);
+ else
+ vty_out(vty, " rx-gain : undefined%s", VTY_NEWLINE);
+ if (l1h->config.power_valid)
+ vty_out(vty, " tx-attenuation : %d dB%s",
+ l1h->config.power, VTY_NEWLINE);
+ else
+ vty_out(vty, " tx-attenuation : undefined%s", VTY_NEWLINE);
+ if (l1h->config.maxdly_valid)
+ vty_out(vty, " maxdly : %d%s", l1h->config.maxdly,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " maxdly : undefined%s", VTY_NEWLINE);
+ if (l1h->config.maxdlynb_valid)
+ vty_out(vty, " maxdlynb : %d%s", l1h->config.maxdlynb,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " maxdlynb : undefined%s", VTY_NEWLINE);
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ if (!((1 << tn) & l1h->config.slotmask))
+ vty_out(vty, " slot #%d: unsupported%s", tn,
+ VTY_NEWLINE);
+ else if (l1h->config.slottype_valid[tn])
+ vty_out(vty, " slot #%d: type %d%s", tn,
+ l1h->config.slottype[tn],
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " slot #%d: undefined%s", tn,
+ VTY_NEWLINE);
+ }
+}
+
+static void show_phy_single(struct vty *vty, struct phy_link *plink)
+{
+ struct phy_instance *pinst;
+
+ vty_out(vty, "PHY %u%s", plink->num, VTY_NEWLINE);
+
+ llist_for_each_entry(pinst, &plink->instances, list)
+ show_phy_inst_single(vty, pinst);
+}
+
+DEFUN(show_phy, show_phy_cmd, "show phy",
+ SHOW_STR "Display information about the available PHYs")
+{
+ int i;
+
+ for (i = 0; i < 255; i++) {
+ struct phy_link *plink = phy_link_by_num(i);
+ if (!plink)
+ break;
+ show_phy_single(vty, plink);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd,
+ "osmotrx ms-power-loop <-127-127>", OSMOTRX_STR
+ "Enable MS power control loop\nTarget RSSI value (transceiver specific, "
+ "should be 6dB or more above noise floor)\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_target_rssi = atoi(argv[0]);
+ plink->u.osmotrx.trx_ms_power_loop = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd,
+ "no osmotrx ms-power-loop",
+ NO_STR OSMOTRX_STR "Disable MS power control loop\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_ms_power_loop = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd,
+ "osmotrx timing-advance-loop", OSMOTRX_STR
+ "Enable timing advance control loop\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_ta_loop = true;
+
+ return CMD_SUCCESS;
+}
+DEFUN(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd,
+ "no osmotrx timing-advance-loop",
+ NO_STR OSMOTRX_STR "Disable timing advance control loop\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_ta_loop = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd,
+ "osmotrx maxdly <0-31>",
+ OSMOTRX_STR
+ "Set the maximum acceptable delay of an Access Burst (in GSM symbols)."
+ " Access Burst is the first burst a mobile transmits in order to establish"
+ " a connection and it is used to estimate Timing Advance (TA) which is"
+ " then applied to Normal Bursts to compensate for signal delay due to"
+ " distance. So changing this setting effectively changes maximum range of"
+ " the cell, because if we receive an Access Burst with a delay higher than"
+ " this value, it will be ignored and connection is dropped.\n"
+ "GSM symbols (approx. 1.1km per symbol)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdly = atoi(argv[0]);
+ l1h->config.maxdly_valid = 1;
+ l1h->config.maxdly_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd,
+ "osmotrx maxdlynb <0-31>",
+ OSMOTRX_STR
+ "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)."
+ " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!"
+ " During normal operation, Normal Bursts delay are controled by a Timing"
+ " Advance control loop and thus Normal Bursts arrive to a BTS with no more"
+ " than a couple GSM symbols, which is already taken into account in osmo-trx."
+ " So changing this setting will have no effect in production installations"
+ " except increasing osmo-trx CPU load. This setting is only useful when"
+ " testing with a transmitter which can't precisely synchronize to the BTS"
+ " downlink signal, like e.g. R&S CMD57.\n"
+ "GSM symbols (approx. 1.1km per symbol)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdlynb = atoi(argv[0]);
+ l1h->config.maxdlynb_valid = 1;
+ l1h->config.maxdlynb_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_slotmask, cfg_phyinst_slotmask_cmd,
+ "slotmask (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0)",
+ "Set the supported slots\n"
+ "TS0 supported\nTS0 unsupported\nTS1 supported\nTS1 unsupported\n"
+ "TS2 supported\nTS2 unsupported\nTS3 supported\nTS3 unsupported\n"
+ "TS4 supported\nTS4 unsupported\nTS5 supported\nTS5 unsupported\n"
+ "TS6 supported\nTS6 unsupported\nTS7 supported\nTS7 unsupported\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ uint8_t tn;
+
+ l1h->config.slotmask = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ if (argv[tn][0] == '1')
+ l1h->config.slotmask |= (1 << tn);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd,
+ "osmotrx power (on|off)",
+ OSMOTRX_STR
+ "Change TRX state\n"
+ "Turn it ON or OFF\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (strcmp(argv[0], "on"))
+ vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h), VTY_NEWLINE);
+ else {
+ vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h), VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd,
+ "osmotrx fn-advance <0-30>",
+ OSMOTRX_STR
+ "Set the number of frames to be transmitted to transceiver in advance "
+ "of current FN\n"
+ "Advance in frames\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.clock_advance = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd,
+ "osmotrx rts-advance <0-30>",
+ OSMOTRX_STR
+ "Set the number of frames to be requested (PCU) in advance of current "
+ "FN. Do not change this, unless you have a good reason!\n"
+ "Advance in frames\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.rts_advance = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd,
+ "osmotrx rx-gain <0-50>",
+ OSMOTRX_STR
+ "Set the receiver gain in dB\n"
+ "Gain in dB\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.rxgain = atoi(argv[0]);
+ l1h->config.rxgain_valid = 1;
+ l1h->config.rxgain_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd,
+ "osmotrx tx-attenuation <0-50>",
+ OSMOTRX_STR
+ "Set the transmitter attenuation\n"
+ "Fixed attenuation in dB, overriding OML\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.power = atoi(argv[0]);
+ l1h->config.power_oml = 0;
+ l1h->config.power_valid = 1;
+ l1h->config.power_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_tx_atten_oml, cfg_phyinst_tx_atten_oml_cmd,
+ "osmotrx tx-attenuation oml",
+ OSMOTRX_STR
+ "Set the transmitter attenuation\n"
+ "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.power_oml = 1;
+ l1h->config.power_valid = 1;
+ l1h->config.power_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd,
+ "no osmotrx rx-gain",
+ NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.rxgain_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_tx_atten, cfg_phyinst_no_tx_atten_cmd,
+ "no osmotrx tx-attenuation",
+ NO_STR OSMOTRX_STR "Unset the transmitter attenuation\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.power_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd,
+ "no osmotrx maxdly",
+ NO_STR OSMOTRX_STR
+ "Unset the maximum delay of GSM symbols\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdly_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd,
+ "no osmotrx maxdlynb",
+ NO_STR OSMOTRX_STR
+ "Unset the maximum delay of GSM symbols\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdlynb_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_transc_ip, cfg_phy_transc_ip_cmd,
+ "osmotrx ip HOST",
+ OSMOTRX_STR
+ "Set local and remote IP address\n"
+ "IP address (for both OsmoBtsTrx and OsmoTRX)\n")
+{
+ struct phy_link *plink = vty->index;
+
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[0]);
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_osmotrx_ip, cfg_phy_osmotrx_ip_cmd,
+ "osmotrx ip (local|remote) A.B.C.D",
+ OSMOTRX_STR
+ "Set IP address\n" "Local IP address (BTS)\n"
+ "Remote IP address (OsmoTRX)\n" "IP address\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (!strcmp(argv[0], "local"))
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[1]);
+ else if (!strcmp(argv[0], "remote"))
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[1]);
+ else
+ return CMD_WARNING;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd,
+ "osmotrx base-port (local|remote) <0-65535>",
+ OSMOTRX_STR "Set base UDP port number\n" "Local UDP port\n"
+ "Remote UDP port\n" "UDP base port number\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (!strcmp(argv[0], "local"))
+ plink->u.osmotrx.base_port_local = atoi(argv[1]);
+ else
+ plink->u.osmotrx.base_port_remote = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd,
+ "osmotrx legacy-setbsic", OSMOTRX_STR
+ "Use SETBSIC to configure transceiver (use ONLY with OpenBTS Transceiver!)\n")
+{
+ struct phy_link *plink = vty->index;
+ plink->u.osmotrx.use_legacy_setbsic = true;
+
+ vty_out(vty, "%% You have enabled SETBSIC, which is not supported by OsmoTRX "
+ "but only useful if you want to interface with legacy OpenBTS Transceivers%s",
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd,
+ "no osmotrx legacy-setbsic",
+ NO_STR OSMOTRX_STR "Disable Legacy SETBSIC to configure transceiver\n")
+{
+ struct phy_link *plink = vty->index;
+ plink->u.osmotrx.use_legacy_setbsic = false;
+
+ return CMD_SUCCESS;
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+ if (plink->u.osmotrx.local_ip)
+ vty_out(vty, " osmotrx ip local %s%s",
+ plink->u.osmotrx.local_ip, VTY_NEWLINE);
+ if (plink->u.osmotrx.remote_ip)
+ vty_out(vty, " osmotrx ip remote %s%s",
+ plink->u.osmotrx.remote_ip, VTY_NEWLINE);
+
+ if (plink->u.osmotrx.trx_ms_power_loop)
+ vty_out(vty, " osmotrx ms-power-loop %d%s", plink->u.osmotrx.trx_target_rssi, VTY_NEWLINE);
+ else
+ vty_out(vty, " no osmotrx ms-power-loop%s", VTY_NEWLINE);
+ vty_out(vty, " %sosmotrx timing-advance-loop%s", (plink->u.osmotrx.trx_ta_loop) ? "" : "no ", VTY_NEWLINE);
+
+ if (plink->u.osmotrx.base_port_local)
+ vty_out(vty, " osmotrx base-port local %"PRIu16"%s",
+ plink->u.osmotrx.base_port_local, VTY_NEWLINE);
+ if (plink->u.osmotrx.base_port_remote)
+ vty_out(vty, " osmotrx base-port remote %"PRIu16"%s",
+ plink->u.osmotrx.base_port_remote, VTY_NEWLINE);
+
+ vty_out(vty, " osmotrx fn-advance %d%s",
+ plink->u.osmotrx.clock_advance, VTY_NEWLINE);
+ vty_out(vty, " osmotrx rts-advance %d%s",
+ plink->u.osmotrx.rts_advance, VTY_NEWLINE);
+
+ if (plink->u.osmotrx.use_legacy_setbsic)
+ vty_out(vty, " osmotrx legacy-setbsic%s", VTY_NEWLINE);
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (l1h->config.rxgain_valid)
+ vty_out(vty, " osmotrx rx-gain %d%s",
+ l1h->config.rxgain, VTY_NEWLINE);
+ if (l1h->config.power_valid) {
+ if (l1h->config.power_oml)
+ vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " osmotrx tx-attenuation %d%s",
+ l1h->config.power, VTY_NEWLINE);
+ }
+ if (l1h->config.maxdly_valid)
+ vty_out(vty, " osmotrx maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE);
+ if (l1h->config.maxdlynb_valid)
+ vty_out(vty, " osmotrx maxdlynb %d%s", l1h->config.maxdlynb, VTY_NEWLINE);
+ if (l1h->config.slotmask != 0xff)
+ vty_out(vty, " slotmask %d %d %d %d %d %d %d %d%s",
+ l1h->config.slotmask & 1,
+ (l1h->config.slotmask >> 1) & 1,
+ (l1h->config.slotmask >> 2) & 1,
+ (l1h->config.slotmask >> 3) & 1,
+ (l1h->config.slotmask >> 4) & 1,
+ (l1h->config.slotmask >> 5) & 1,
+ (l1h->config.slotmask >> 6) & 1,
+ l1h->config.slotmask >> 7,
+ VTY_NEWLINE);
+}
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ install_element_ve(&show_transceiver_cmd);
+ install_element_ve(&show_phy_cmd);
+
+ install_element(PHY_NODE, &cfg_phy_ms_power_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_ms_power_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_timing_advance_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_timing_advance_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_base_port_cmd);
+ install_element(PHY_NODE, &cfg_phy_fn_advance_cmd);
+ install_element(PHY_NODE, &cfg_phy_rts_advance_cmd);
+ install_element(PHY_NODE, &cfg_phy_transc_ip_cmd);
+ install_element(PHY_NODE, &cfg_phy_osmotrx_ip_cmd);
+ install_element(PHY_NODE, &cfg_phy_setbsic_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_setbsic_cmd);
+
+ install_element(PHY_INST_NODE, &cfg_phyinst_rxgain_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_oml_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_rxgain_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_tx_atten_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_slotmask_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_power_on_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_maxdly_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdly_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_maxdlynb_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdlynb_cmd);
+
+ return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ return 0;
+}
diff --git a/src/osmo-bts-virtual/Makefile.am b/src/osmo-bts-virtual/Makefile.am
new file mode 100644
index 00000000..070efed6
--- /dev/null
+++ b/src/osmo-bts-virtual/Makefile.am
@@ -0,0 +1,10 @@
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl
+
+noinst_HEADERS = l1_if.h osmo_mcast_sock.h virtual_um.h
+
+bin_PROGRAMS = osmo-bts-virtual
+
+osmo_bts_virtual_SOURCES = main.c bts_model.c virtualbts_vty.c scheduler_virtbts.c l1_if.c virtual_um.c osmo_mcast_sock.c
+osmo_bts_virtual_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
diff --git a/src/osmo-bts-virtual/bts_model.c b/src/osmo-bts-virtual/bts_model.c
new file mode 100644
index 00000000..b971af5c
--- /dev/null
+++ b/src/osmo-bts-virtual/bts_model.c
@@ -0,0 +1,176 @@
+/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/codec/codec.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+/* TODO: check if dummy method is sufficient, else implement */
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return -1;
+}
+
+/* TODO: check if dummy method is sufficient, else implement */
+int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr,
+ int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return -1;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ return 0;
+}
+
+static uint8_t vbts_set_bts(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+
+ /* report availability of trx to the bts. this will trigger the rsl connection */
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+ }
+ return 0;
+}
+
+static uint8_t vbts_set_trx(struct gsm_bts_trx *trx)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts)
+{
+ struct phy_instance *pinst = trx_phy_instance(ts->trx);
+ int rc;
+
+ rc = trx_sched_set_pchan(&pinst->u.virt.sched, ts->nr, ts->pchan);
+ if (rc)
+ return NM_NACK_RES_NOTAVAIL;
+
+ return 0;
+}
+
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ int cause = 0;
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_BTS_ATTR:
+ cause = vbts_set_bts(obj);
+ break;
+ case NM_MT_SET_RADIO_ATTR:
+ cause = vbts_set_trx(obj);
+ break;
+ case NM_MT_SET_CHAN_ATTR:
+ cause = vbts_set_ts(obj);
+ break;
+ }
+ return oml_fom_ack_nack(msg, cause);
+}
+
+/* MO: TS 12.21 Managed Object */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ case NM_OC_CHANNEL:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_BTS:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+ rc = oml_mo_opstart_ack(mo);
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return 0;
+}
diff --git a/src/osmo-bts-virtual/l1_if.c b/src/osmo-bts-virtual/l1_if.c
new file mode 100644
index 00000000..d0c368ee
--- /dev/null
+++ b/src/osmo-bts-virtual/l1_if.c
@@ -0,0 +1,461 @@
+/* Virtual BTS layer 1 primitive handling and interface
+ *
+ * Copyright (C) 2015-2017 Harald Welte <laforge@gnumonks.org>
+ * Copyright (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/scheduler.h>
+#include "virtual_um.h"
+
+extern int vbts_sched_start(struct gsm_bts *bts);
+
+static struct phy_instance *phy_instance_by_arfcn(struct phy_link *plink, uint16_t arfcn)
+{
+ struct phy_instance *pinst;
+
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->trx && pinst->trx->arfcn == arfcn)
+ return pinst;
+ }
+
+ return NULL;
+}
+
+static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr,
+ int n_errors, int n_bits_total, float rssi, float toa);
+/**
+ * Callback to handle incoming messages from the MS.
+ * The incoming message should be GSM_TAP encapsulated.
+ * TODO: implement all channels
+ */
+static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg)
+{
+ struct phy_link *plink = (struct phy_link *)vui->priv;
+ struct phy_instance *pinst;
+ if (!msg) {
+ pinst = phy_instance_by_num(plink, 0);
+ bts_shutdown(pinst->trx->bts, "VirtPHY read socket died\n");
+ return;
+ }
+
+ struct gsmtap_hdr *gh = msgb_l1(msg);
+ uint32_t fn = ntohl(gh->frame_number); /* frame number of the rcv msg */
+ uint16_t arfcn = ntohs(gh->arfcn); /* arfcn of the cell we currently camp on */
+ uint8_t gsmtap_chantype = gh->sub_type; /* gsmtap channel type */
+ uint8_t signal_dbm = gh->signal_dbm; /* signal strength in dBm */
+ //uint8_t snr = gh->snr_db; /* signal noise ratio in dB */
+ uint8_t subslot = gh->sub_slot; /* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */
+ uint8_t timeslot = gh->timeslot; /* tdma timeslot to send in (0-7) */
+ uint8_t rsl_chantype; /* rsl chan type (8.58, 9.3.1) */
+ uint8_t link_id; /* rsl link id tells if this is an ssociated or dedicated link */
+ uint8_t chan_nr; /* encoded rsl channel type, timeslot and mf subslot */
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ /* get rid of l1 gsmtap hdr */
+ msg->l2h = msgb_pull(msg, sizeof(*gh));
+
+ /* convert gsmtap chan to RSL chan and link id */
+ chantype_gsmtap2rsl(gsmtap_chantype, &rsl_chantype, &link_id);
+ chan_nr = rsl_enc_chan_nr(rsl_chantype, subslot, timeslot);
+
+ /* ... or not uplink */
+ if (!(arfcn & GSMTAP_ARFCN_F_UPLINK)) {
+ LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignoring incoming msg - no uplink flag\n");
+ goto nomessage;
+ }
+
+ /* Generally ignore all msgs that are either not received with the right ARFCN... */
+ pinst = phy_instance_by_arfcn(plink, arfcn & GSMTAP_ARFCN_MASK);
+ if (!pinst)
+ goto nomessage;
+
+ /* switch case with removed ACCH flag */
+ switch ((gsmtap_chantype & ~GSMTAP_CHANNEL_ACCH) & 0xff) {
+ case GSMTAP_CHANNEL_RACH:
+ /* generate primitive for upper layer
+ * see 04.08 - 3.3.1.3.1: the IMMEDIATE_ASSIGNMENT coming back from the network has to be
+ * sent with the same ra reference as in the CHANNEL_REQUEST that was received */
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, msg);
+
+ l1sap.u.rach_ind.chan_nr = chan_nr;
+ /* TODO: 11bit RACH */
+ l1sap.u.rach_ind.ra = msgb_pull_u8(msg); /* directly after gh hdr comes ra */
+ l1sap.u.rach_ind.acc_delay = 0; /* probably not used in virt um */
+ l1sap.u.rach_ind.is_11bit = 0;
+ l1sap.u.rach_ind.fn = fn;
+ /* we don't rally know which RACH bursrt type the virtual MS is using, as this field is not
+ * part of information present in the GSMTAP header. So we simply report all of them as 0 */
+ l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
+ break;
+ case GSMTAP_CHANNEL_TCH_F:
+ case GSMTAP_CHANNEL_TCH_H:
+#if 0
+ /* TODO: handle voice messages */
+ if (!facch && ! tch_acch) {
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg);
+ }
+#endif
+ case GSMTAP_CHANNEL_SDCCH4:
+ case GSMTAP_CHANNEL_SDCCH8:
+ case GSMTAP_CHANNEL_PACCH:
+ case GSMTAP_CHANNEL_PDCH:
+ case GSMTAP_CHANNEL_PTCCH:
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, msg);
+ l1sap.u.data.chan_nr = chan_nr;
+ l1sap.u.data.link_id = link_id;
+ l1sap.u.data.fn = fn;
+ l1sap.u.data.rssi = 0; /* Radio Signal Strength Indicator. Best -> 0 */
+ l1sap.u.data.ber10k = 0; /* Bit Error Rate in 0.01%. Best -> 0 */
+ l1sap.u.data.ta_offs_256bits = 0; /* Burst time of arrival in quarter bits. Probably used for Timing Advance calc. Best -> 0 */
+ l1sap.u.data.lqual_cb = 10 * signal_dbm; /* Link quality in centiBel = 10 * dB. */
+ l1sap.u.data.pdch_presence_info = PRES_INFO_BOTH;
+ l1if_process_meas_res(pinst->trx, timeslot, fn, chan_nr, 0, 0, 0, 0);
+ break;
+ case GSMTAP_CHANNEL_AGCH:
+ case GSMTAP_CHANNEL_PCH:
+ case GSMTAP_CHANNEL_BCCH:
+ LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type downlink only!\n");
+ goto nomessage;
+ case GSMTAP_CHANNEL_SDCCH:
+ case GSMTAP_CHANNEL_CCCH:
+ case GSMTAP_CHANNEL_CBCH51:
+ case GSMTAP_CHANNEL_CBCH52:
+ LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type not supported!\n");
+ goto nomessage;
+ default:
+ LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type unknown\n");
+ goto nomessage;
+ }
+
+ /* forward primitive, lsap takes ownership of the msgb. */
+ l1sap_up(pinst->trx, &l1sap);
+ DEBUGPFN(DL1P, fn, "Message forwarded to layer 2.\n");
+ return;
+
+nomessage:
+ talloc_free(msg);
+}
+
+/* called by common part once OML link is established */
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+/* called by bts_main to initialize physical link */
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst;
+
+ //OSMO_ASSERT(plink->type == PHY_LINK_T_VIRTUAL);
+
+ if (plink->u.virt.virt_um)
+ virt_um_destroy(plink->u.virt.virt_um);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ plink->u.virt.virt_um = virt_um_init(plink, plink->u.virt.ms_mcast_group, plink->u.virt.ms_mcast_port,
+ plink->u.virt.bts_mcast_group, plink->u.virt.bts_mcast_port,
+ virt_um_rcv_cb);
+ if (!plink->u.virt.virt_um) {
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ return -1;
+ }
+ /* set back reference to plink */
+ plink->u.virt.virt_um->priv = plink;
+
+ /* iterate over list of PHY instances and initialize the scheduler */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ trx_sched_init(&pinst->u.virt.sched, pinst->trx);
+ /* Only start the scheduler for the transceiver on C0.
+ * If we have multiple tranceivers, CCCH is always on C0
+ * and has to be auto active */
+ /* Other TRX are activated via OML by a PRIM_INFO_MODIFY
+ * / PRIM_INFO_ACTIVATE */
+ if (pinst->trx && pinst->trx == pinst->trx->bts->c0) {
+ vbts_sched_start(pinst->trx->bts);
+ /* init lapdm layer 3 callback for the trx on timeslot 0 == BCCH */
+ lchan_init_lapdm(&pinst->trx->ts[0].lchan[CCCH_LCHAN]);
+ /* FIXME: This is probably the wrong location to set the CCCH to active... the OML link def. needs to be reworked and fixed. */
+ pinst->trx->ts[0].lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_set_state(&pinst->trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_ACTIVE);
+ }
+ }
+
+ /* this will automatically update the MO state of all associated TRX objects */
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ return 0;
+}
+
+
+/*
+ * primitive handling
+ */
+
+/* enable ciphering */
+static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct l1sched_trx *sched = &pinst->u.virt.sched;
+
+ /* ciphering already enabled in both directions */
+ if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF)
+ return -EINVAL;
+
+ if (!downlink) {
+ /* set uplink */
+ trx_sched_set_cipher(sched, chan_nr, 0, lchan->encr.alg_id - 1,
+ lchan->encr.key, lchan->encr.key_len);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ } else {
+ /* set downlink and also set uplink, if not already */
+ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) {
+ trx_sched_set_cipher(sched, chan_nr, 0,
+ lchan->encr.alg_id - 1, lchan->encr.key,
+ lchan->encr.key_len);
+ }
+ trx_sched_set_cipher(sched, chan_nr, 1, lchan->encr.alg_id - 1,
+ lchan->encr.key, lchan->encr.key_len);
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ }
+
+ return 0;
+}
+
+static int mph_info_chan_confirm(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = chan_nr;
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(trx, &l1sap);
+}
+
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap.u.info.type = PRIM_INFO_TIME;
+ l1sap.u.info.u.time_ind.fn = fn;
+
+ if (!bts->c0)
+ return -EINVAL;
+
+ return l1sap_up(bts->c0, &l1sap);
+}
+
+
+static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta,
+ float ber, float rssi, uint32_t fn)
+{
+ memset(l1sap, 0, sizeof(*l1sap));
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_INDICATION, NULL);
+ l1sap->u.info.type = PRIM_INFO_MEAS;
+ l1sap->u.info.u.meas_ind.chan_nr = chan_nr;
+ l1sap->u.info.u.meas_ind.ta_offs_256bits = (int16_t)(ta*4);
+ l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000);
+ l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1);
+ l1sap->u.info.u.meas_ind.fn = fn;
+}
+
+static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr,
+ int n_errors, int n_bits_total, float rssi, float toa)
+{
+ struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)];
+ struct osmo_phsap_prim l1sap;
+ /* 100% BER is n_bits_total is 0 */
+ float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total;
+
+ DEBUGPFN(DMEAS, fn, "RX L1 frame %s chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS "
+ "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa=%.2f\n",
+ gsm_lchan_name(lchan), chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+ rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa);
+
+ l1if_fill_meas_res(&l1sap, chan_nr, lchan->rqd_ta + toa, ber, rssi, fn);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct l1sched_trx *sched = &pinst->u.virt.sched;
+ struct msgb *msg = l1sap->oph.msg;
+ uint8_t chan_nr;
+ uint8_t tn, ss;
+ int rc = 0;
+ struct gsm_lchan *lchan;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ if (!msg)
+ break;
+ /* put data into scheduler's queue */
+ return trx_sched_ph_data_req(sched, l1sap);
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ if (!msg)
+ break;
+ /* put data into scheduler's queue */
+ return trx_sched_tch_req(sched, l1sap);
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ tn = L1SAP_CHAN2TS(chan_nr);
+ ss = l1sap_chan2ss(chan_nr);
+ lchan = &trx->ts[tn].lchan[ss];
+ if (l1sap->u.info.u.ciph_req.uplink)
+ l1if_set_ciphering(lchan, chan_nr, 0);
+ if (l1sap->u.info.u.ciph_req.downlink)
+ l1if_set_ciphering(lchan, chan_nr, 1);
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ tn = L1SAP_CHAN2TS(chan_nr);
+ ss = l1sap_chan2ss(chan_nr);
+ lchan = &trx->ts[tn].lchan[ss];
+ /* we receive a channel activation request from the BSC,
+ * e.g. as a response to a channel req on RACH */
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) {
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGP(DL1C, LOGL_ERROR, "Cannot activate"
+ " chan_nr 0x%02x\n", chan_nr);
+ break;
+ }
+ /* activate dedicated channel */
+ trx_sched_set_lchan(sched, chan_nr, LID_DEDIC, 1);
+ /* activate associated channel */
+ trx_sched_set_lchan(sched, chan_nr, LID_SACCH, 1);
+ /* set mode */
+ trx_sched_set_mode(sched, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.bts_mode[0].mode,
+ lchan->tch.amr_mr.bts_mode[1].mode,
+ lchan->tch.amr_mr.bts_mode[2].mode,
+ lchan->tch.amr_mr.bts_mode[3].mode,
+ amr_get_initial_mode(lchan),
+ (lchan->ho.active == 1));
+ /* init lapdm */
+ lchan_init_lapdm(lchan);
+ /* set lchan active */
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ /* set initial ciphering */
+ l1if_set_ciphering(lchan, chan_nr, 0);
+ l1if_set_ciphering(lchan, chan_nr, 1);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ /* confirm */
+ mph_info_chan_confirm(trx, chan_nr,
+ PRIM_INFO_ACTIVATE, 0);
+ break;
+ }
+ if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ /* change mode */
+ trx_sched_set_mode(sched, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.bts_mode[0].mode,
+ lchan->tch.amr_mr.bts_mode[1].mode,
+ lchan->tch.amr_mr.bts_mode[2].mode,
+ lchan->tch.amr_mr.bts_mode[3].mode,
+ amr_get_initial_mode(lchan),
+ 0);
+ break;
+ }
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGP(DL1C, LOGL_ERROR, "Cannot deactivate "
+ "chan_nr 0x%02x\n", chan_nr);
+ break;
+ }
+ /* deactivate associated channel */
+ trx_sched_set_lchan(sched, chan_nr, 0x40, 0);
+ if (!l1sap->u.info.u.act_req.sacch_only) {
+ /* set lchan inactive */
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ /* deactivate dedicated channel */
+ trx_sched_set_lchan(sched, chan_nr, 0x00, 0);
+ /* confirm only on dedicated channel */
+ mph_info_chan_confirm(trx, chan_nr,
+ PRIM_INFO_DEACTIVATE, 0);
+ lchan->ciph_state = 0; /* FIXME: do this in common/\*.c */
+ }
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ goto done;
+ }
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ goto done;
+ }
+
+done:
+ if (msg)
+ msgb_free(msg);
+ return rc;
+}
diff --git a/src/osmo-bts-virtual/l1_if.h b/src/osmo-bts-virtual/l1_if.h
new file mode 100644
index 00000000..6a843b37
--- /dev/null
+++ b/src/osmo-bts-virtual/l1_if.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/scheduler.h>
+
+#include "virtual_um.h"
+
+struct vbts_l1h {
+ struct gsm_bts_trx *trx;
+ struct l1sched_trx l1s;
+ struct virt_um_inst *virt_um;
+};
+
+struct vbts_l1h *l1if_open(struct gsm_bts_trx *trx);
+void l1if_close(struct vbts_l1h *l1h);
+void l1if_reset(struct vbts_l1h *l1h);
+
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn);
+
+int vbts_sched_start(struct gsm_bts *bts);
diff --git a/src/osmo-bts-virtual/main.c b/src/osmo-bts-virtual/main.c
new file mode 100644
index 00000000..aa1c608e
--- /dev/null
+++ b/src/osmo-bts-virtual/main.c
@@ -0,0 +1,144 @@
+/* Main program for Virtual OsmoBTS */
+
+/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/phy_link.h>
+#include "virtual_um.h"
+
+/* dummy, since no direct dsp support */
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ bts->variant = BTS_OSMO_VIRTUAL;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_CBCH);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+void bts_model_print_help()
+{
+ LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* specific to this hardware */
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+ plink->u.virt.bts_mcast_group = DEFAULT_BTS_MCAST_GROUP;
+ plink->u.virt.bts_mcast_port = DEFAULT_BTS_MCAST_PORT;
+ plink->u.virt.ms_mcast_group = DEFAULT_MS_MCAST_GROUP;
+ plink->u.virt.ms_mcast_port = DEFAULT_MS_MCAST_PORT;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ return -ENOTSUP;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan)
+{
+ LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.c b/src/osmo-bts-virtual/osmo_mcast_sock.c
new file mode 100644
index 00000000..c0f0af58
--- /dev/null
+++ b/src/osmo-bts-virtual/osmo_mcast_sock.c
@@ -0,0 +1,113 @@
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <talloc.h>
+#include <unistd.h>
+#include "osmo_mcast_sock.h"
+
+/* server socket is what we use for transmission. It is not subscribed
+ * to a multicast group or locally bound, but it is just a normal UDP
+ * socket that's connected to the remote mcast group + port */
+int mcast_server_sock_setup(struct osmo_fd *ofd, const char* tx_mcast_group,
+ uint16_t tx_mcast_port, bool loopback)
+{
+ int rc;
+ unsigned int flags = OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_UDP_REUSEADDR;
+
+ if (!loopback)
+ flags |= OSMO_SOCK_F_NO_MCAST_LOOP;
+
+ /* setup mcast server socket */
+ rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ tx_mcast_group, tx_mcast_port, flags);
+ if (rc < 0) {
+ perror("Failed to create Multicast Server Socket");
+ return rc;
+ }
+
+ return 0;
+}
+
+/* the client socket is what we use for reception. It is a UDP socket
+ * that's bound to the GSMTAP UDP port and subscribed to the respective
+ * multicast group */
+int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port,
+ int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+ void *osmo_fd_data)
+{
+ int rc;
+ unsigned int flags = OSMO_SOCK_F_BIND | OSMO_SOCK_F_NO_MCAST_ALL | OSMO_SOCK_F_UDP_REUSEADDR;
+
+ ofd->cb = fd_rx_cb;
+ ofd->when = BSC_FD_READ;
+ ofd->data = osmo_fd_data;
+
+ /* Create mcast client socket */
+ rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ NULL, mcast_port, flags);
+ if (rc < 0) {
+ perror("Could not create mcast client socket");
+ return rc;
+ }
+
+ /* Configure and join the multicast group */
+ rc = osmo_sock_mcast_subscribe(ofd->fd, mcast_group);
+ if (rc < 0) {
+ perror("Failed to join to mcast goup");
+ osmo_fd_close(ofd);
+ return rc;
+ }
+
+ return 0;
+}
+
+struct mcast_bidir_sock *
+mcast_bidir_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port,
+ const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback,
+ int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+ void *osmo_fd_data)
+{
+ struct mcast_bidir_sock *bidir_sock = talloc(ctx, struct mcast_bidir_sock);
+ int rc;
+
+ if (!bidir_sock)
+ return NULL;
+
+ rc = mcast_client_sock_setup(&bidir_sock->rx_ofd, rx_mcast_group, rx_mcast_port,
+ fd_rx_cb, osmo_fd_data);
+ if (rc < 0) {
+ talloc_free(bidir_sock);
+ return NULL;
+ }
+ rc = mcast_server_sock_setup(&bidir_sock->tx_ofd, tx_mcast_group, tx_mcast_port, loopback);
+ if (rc < 0) {
+ osmo_fd_close(&bidir_sock->rx_ofd);
+ talloc_free(bidir_sock);
+ return NULL;
+ }
+ return bidir_sock;
+
+}
+
+int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data,
+ unsigned int data_len)
+{
+ return send(bidir_sock->tx_ofd.fd, data, data_len, 0);
+}
+
+int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len)
+{
+ return recv(bidir_sock->rx_ofd.fd, buf, buf_len, 0);
+}
+
+void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock)
+{
+ osmo_fd_close(&bidir_sock->tx_ofd);
+ osmo_fd_close(&bidir_sock->rx_ofd);
+ talloc_free(bidir_sock);
+}
diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.h b/src/osmo-bts-virtual/osmo_mcast_sock.h
new file mode 100644
index 00000000..aa2013c6
--- /dev/null
+++ b/src/osmo-bts-virtual/osmo_mcast_sock.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <osmocom/core/select.h>
+
+struct mcast_bidir_sock {
+ struct osmo_fd tx_ofd;
+ struct osmo_fd rx_ofd;
+};
+
+struct mcast_bidir_sock *mcast_bidir_sock_setup(void *ctx,
+ const char *tx_mcast_group, uint16_t tx_mcast_port,
+ const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback,
+ int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+ void *osmo_fd_data);
+
+int mcast_server_sock_setup(struct osmo_fd *ofd, const char *tx_mcast_group,
+ uint16_t tx_mcast_port, bool loopback);
+
+int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port,
+ int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+ void *osmo_fd_data);
+
+int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, unsigned int data_len);
+int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len);
+void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock);
+
diff --git a/src/osmo-bts-virtual/scheduler_virtbts.c b/src/osmo-bts-virtual/scheduler_virtbts.c
new file mode 100644
index 00000000..25f65839
--- /dev/null
+++ b/src/osmo-bts-virtual/scheduler_virtbts.c
@@ -0,0 +1,619 @@
+/* Scheduler worker functiosn for Virtua OsmoBTS */
+
+/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+#include "virtual_um.h"
+#include "l1_if.h"
+
+#define MODULO_HYPERFRAME 0
+
+static const char *gsmtap_hdr_stringify(const struct gsmtap_hdr *gh)
+{
+ static char buf[256];
+ snprintf(buf, sizeof(buf), "(ARFCN=%u, ts=%u, ss=%u, type=%u/%u)",
+ gh->arfcn & GSMTAP_ARFCN_MASK, gh->timeslot, gh->sub_slot, gh->type, gh->sub_type);
+ return buf;
+}
+
+/**
+ * Send a message over the virtual um interface.
+ * This will at first wrap the msg with a GSMTAP header and then write it to the declared multicast socket.
+ * TODO: we might want to remove unused argument uint8_t tn
+ */
+static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, struct msgb *msg)
+{
+ const struct trx_chan_desc *chdesc = &trx_chan_desc[chan];
+ struct msgb *outmsg; /* msg to send with gsmtap header prepended */
+ uint16_t arfcn = l1t->trx->arfcn; /* ARFCN of the tranceiver the message is send with */
+ uint8_t signal_dbm = 63; /* signal strength, 63 is best */
+ uint8_t snr = 63; /* signal noise ratio, 63 is best */
+ uint8_t *data = msgb_l2(msg); /* data to transmit (whole message without l1 header) */
+ uint8_t data_len = msgb_l2len(msg); /* length of data */
+ uint8_t rsl_chantype; /* RSL chan type (TS 08.58, 9.3.1) */
+ uint8_t subslot; /* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */
+ uint8_t timeslot; /* TDMA timeslot to send in (0-7) */
+ uint8_t gsmtap_chantype; /* the GSMTAP channel */
+
+ rsl_dec_chan_nr(chdesc->chan_nr, &rsl_chantype, &subslot, &timeslot);
+ /* the timeslot is not encoded in the chan_nr of the chdesc, and so has to be overwritten */
+ timeslot = tn;
+ /* in Osmocom, AGCH is only sent on ccch block 0. no idea why. this seems to cause false GSMTAP channel
+ * types for agch and pch. */
+ if (rsl_chantype == RSL_CHAN_PCH_AGCH &&
+ l1sap_fn2ccch_block(fn) >= num_agch(l1t->trx, "PH-DATA-REQ"))
+ gsmtap_chantype = GSMTAP_CHANNEL_PCH;
+ else
+ gsmtap_chantype = chantype_rsl2gsmtap(rsl_chantype, chdesc->link_id); /* the logical channel type */
+
+#if MODULO_HYPERFRAME
+ /* Restart fn after every superframe (26 * 51 frames) to simulate hyperframe overflow each 6 seconds. */
+ fn %= 26 * 51;
+#endif
+
+ outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, fn, signal_dbm, snr, data, data_len);
+
+ if (outmsg) {
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+ struct gsmtap_hdr *gh = (struct gsmtap_hdr *)msgb_data(outmsg);
+ int rc;
+
+ rc = virt_um_write_msg(pinst->phy_link->u.virt.virt_um, outmsg);
+ if (rc < 0)
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "%s GSMTAP msg could not send to virtual Um\n", gsmtap_hdr_stringify(gh));
+ else if (rc == 0)
+ bts_shutdown(l1t->trx->bts, "VirtPHY write socket died\n");
+ else
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn,
+ "%s Sending GSMTAP message to virtual Um\n", gsmtap_hdr_stringify(gh));
+ } else
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "GSMTAP msg could not be created!\n");
+
+ /* free incoming message */
+ msgb_free(msg);
+}
+
+/*
+ * TX on downlink
+ */
+
+/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */
+ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ return NULL;
+}
+
+ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ return NULL;
+}
+
+ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ return NULL;
+}
+
+ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct msgb *msg;
+
+ if (bid > 0)
+ return NULL;
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (!msg) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n");
+ return NULL;
+ }
+
+ /* check validity of message */
+ if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n",
+ msgb_l2len(msg));
+ /* free message */
+ msgb_free(msg);
+ return NULL;
+ }
+
+ /* transmit the msg received on dl from bsc to layer1 (virt Um) */
+ tx_to_virt_um(l1t, tn, fn, chan, msg);
+
+ return NULL;
+}
+
+ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct msgb *msg = NULL; /* make GCC happy */
+
+ if (bid > 0)
+ return NULL;
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (!msg) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n");
+ return NULL;
+ }
+
+ tx_to_virt_um(l1t, tn, fn, chan, msg);
+
+ return NULL;
+}
+
+static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch,
+ struct msgb **_msg_facch, int codec_mode_request)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ struct osmo_phsap_prim *l1sap;
+#if 0
+ /* handle loss detection of received TCH frames */
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+ && ++(chan_state->lost_frames) > 5) {
+ uint8_t tch_data[GSM_FR_BYTES];
+ int len;
+
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Missing TCH bursts detected, sending "
+ "BFI for %s\n", trx_chan_desc[chan].name);
+
+ /* indicate bad frame */
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (chan != TRXC_TCHF) { /* HR */
+ tch_data[0] = 0x70; /* F = 0, FT = 111 */
+ memset(tch_data + 1, 0, 14);
+ len = 15;
+ break;
+ }
+ memset(tch_data, 0, GSM_FR_BYTES);
+ len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (chan != TRXC_TCHF)
+ goto inval_mode1;
+ memset(tch_data, 0, GSM_EFR_BYTES);
+ len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ len = amr_compose_payload(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft], 1);
+ if (len < 2)
+ break;
+ memset(tch_data + 2, 0, len - 2);
+ _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len);
+ break;
+ default:
+inval_mode1:
+ LOGP(DL1P, LOGL_ERROR, "TCH mode invalid, please "
+ "fix!\n");
+ len = 0;
+ }
+ if (len)
+ _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len);
+ }
+#endif
+
+ /* get frame and unlink from queue */
+ msg1 = _sched_dequeue_prim(l1t, tn, fn, chan);
+ msg2 = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg1) {
+ l1sap = msgb_l1sap_prim(msg1);
+ if (l1sap->oph.primitive == PRIM_TCH) {
+ msg_tch = msg1;
+ if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive == PRIM_TCH) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "TCH twice, please FIX! ");
+ msgb_free(msg2);
+ } else
+ msg_facch = msg2;
+ }
+ } else {
+ msg_facch = msg1;
+ if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive != PRIM_TCH) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "FACCH twice, please FIX! ");
+ msgb_free(msg2);
+ } else
+ msg_tch = msg2;
+ }
+ }
+ } else if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive == PRIM_TCH)
+ msg_tch = msg2;
+ else
+ msg_facch = msg2;
+ }
+
+ /* check validity of message */
+ if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n",
+ msgb_l2len(msg_facch));
+ /* free message */
+ msgb_free(msg_facch);
+ msg_facch = NULL;
+ }
+
+ /* check validity of message, get AMR ft and cmr */
+ if (!msg_facch && msg_tch) {
+ int len;
+#if 0
+ uint8_t bfi, cmr_codec, ft_codec;
+ int cmr, ft, i;
+#endif
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
+ "because we are not in speech mode\n");
+ goto free_bad_msg;
+ }
+
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (chan != TRXC_TCHF) { /* HR */
+ len = 15;
+ if (msgb_l2len(msg_tch) >= 1
+ && (msg_tch->l2h[0] & 0xf0) != 0x00) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Transmitting 'bad HR frame'\n");
+ goto free_bad_msg;
+ }
+ break;
+ }
+ len = GSM_FR_BYTES;
+ if (msgb_l2len(msg_tch) >= 1
+ && (msg_tch->l2h[0] >> 4) != 0xd) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Transmitting 'bad FR frame'\n");
+ goto free_bad_msg;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (chan != TRXC_TCHF)
+ goto inval_mode2;
+ len = GSM_EFR_BYTES;
+ if (msgb_l2len(msg_tch) >= 1
+ && (msg_tch->l2h[0] >> 4) != 0xc) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Transmitting 'bad EFR frame'\n");
+ goto free_bad_msg;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+#if 0
+ len = amr_decompose_payload(msg_tch->l2h,
+ msgb_l2len(msg_tch), &cmr_codec, &ft_codec,
+ &bfi);
+ cmr = -1;
+ ft = -1;
+ for (i = 0; i < chan_state->codecs; i++) {
+ if (chan_state->codec[i] == cmr_codec)
+ cmr = i;
+ if (chan_state->codec[i] == ft_codec)
+ ft = i;
+ }
+ if (cmr >= 0) { /* new request */
+ chan_state->dl_cmr = cmr;
+ /* disable AMR loop */
+ trx_loop_amr_set(chan_state, 0);
+ } else {
+ /* enable AMR loop */
+ trx_loop_amr_set(chan_state, 1);
+ }
+ if (ft < 0) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "Codec (FT = %d) of RTP frame not in list. ", ft_codec);
+ goto free_bad_msg;
+ }
+ if (codec_mode_request && chan_state->dl_ft != ft) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Codec (FT = %d) of RTP cannot be changed now, but in "
+ "next frame\n", ft_codec);
+ goto free_bad_msg;
+ }
+ chan_state->dl_ft = ft;
+ if (bfi) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Transmitting 'bad AMR frame'\n");
+ goto free_bad_msg;
+ }
+#else
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "AMR not supported!\n");
+ goto free_bad_msg;
+#endif
+ break;
+ default:
+inval_mode2:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+ goto free_bad_msg;
+ }
+ if (len < 0) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n");
+ goto free_bad_msg;
+ }
+ if (msgb_l2len(msg_tch) != len) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with "
+ "invalid length! (expecing %d, received %d)\n", len, msgb_l2len(msg_tch));
+free_bad_msg:
+ /* free message */
+ msgb_free(msg_tch);
+ msg_tch = NULL;
+ goto send_frame;
+ }
+ }
+
+send_frame:
+ *_msg_tch = msg_tch;
+ *_msg_facch = msg_facch;
+}
+
+ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct msgb *msg_tch = NULL, *msg_facch = NULL;
+
+ if (bid > 0)
+ return NULL;
+
+ tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch,
+ (((fn + 4) % 26) >> 2) & 1);
+
+ /* no message at all */
+ if (!msg_tch && !msg_facch) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n");
+ goto send_burst;
+ }
+
+ if (msg_facch) {
+ tx_to_virt_um(l1t, tn, fn, chan, msg_facch);
+ msgb_free(msg_tch);
+ } else
+ tx_to_virt_um(l1t, tn, fn, chan, msg_tch);
+
+send_burst:
+
+ return NULL;
+}
+
+ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+ struct msgb *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ //uint8_t tch_mode = chan_state->tch_mode;
+
+ /* send burst, if we already got a frame */
+ if (bid > 0)
+ return NULL;
+
+ /* get TCH and/or FACCH */
+ tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch,
+ (((fn + 4) % 26) >> 2) & 1);
+
+ /* check for FACCH alignment */
+ if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on "
+ "even frames, please fix RTS!\n");
+ msgb_free(msg_facch);
+ msg_facch = NULL;
+ }
+
+ /* no message at all */
+ if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n");
+ goto send_burst;
+ }
+
+ if (msg_facch) {
+ tx_to_virt_um(l1t, tn, fn, chan, msg_facch);
+ msgb_free(msg_tch);
+ } else if (msg_tch)
+ tx_to_virt_um(l1t, tn, fn, chan, msg_tch);
+
+send_burst:
+ return NULL;
+}
+
+
+/***********************************************************************
+ * RX on uplink (indication to upper layer)
+ ***********************************************************************/
+
+/* we don't use those functions, as we feed the MAC frames from GSMTAP
+ * directly into the L1SAP, bypassing the TDMA multiplex logic oriented
+ * towards receiving bursts */
+
+int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ return 0;
+}
+
+/*! \brief a single burst was received by the PHY, process it */
+int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ return 0;
+}
+
+int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ return 0;
+}
+
+int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ return 0;
+}
+
+int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+ int8_t rssi, int16_t toa256)
+{
+ return 0;
+}
+
+void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
+{
+}
+
+/***********************************************************************
+ * main scheduler function
+ ***********************************************************************/
+
+#define RTS_ADVANCE 5 /* about 20ms */
+#define FRAME_DURATION_uS 4615
+
+static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn)
+{
+ struct gsm_bts_trx *trx;
+
+ /* send time indication */
+ /* update model with new frame number, lot of stuff happening, measurements of timeslots */
+ /* saving GSM time in BTS model, and more */
+ l1if_mph_time_ind(bts, fn);
+
+ /* advance the frame number? */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct l1sched_trx *l1t = &pinst->u.virt.sched;
+ int tn;
+ uint16_t nbits;
+
+ /* do for each of the 8 timeslots */
+ for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ /* Generate RTS indication to higher layers */
+ /* This will basically do 2 things (check l1_if:bts_model_l1sap_down):
+ * 1) Get pending messages from layer 2 (from the lapdm queue)
+ * 2) Process the messages
+ * --> Handle and process non-transparent RSL-Messages (activate channel, )
+ * --> Forward transparent RSL-DATA-Messages to the ms by appending them to
+ * the l1-dl-queue */
+ _sched_rts(l1t, tn, (fn + RTS_ADVANCE) % GSM_HYPERFRAME);
+ /* schedule transmit backend functions */
+ /* Process data in the l1-dlqueue and forward it
+ * to MS */
+ /* the returned bits are not used here, the routines called will directly forward their
+ * bits to the virt Um */
+ _sched_dl_burst(l1t, tn, fn, &nbits);
+ }
+ }
+
+ return 0;
+}
+
+static void vbts_fn_timer_cb(void *data)
+{
+ struct gsm_bts *bts = data;
+ struct timeval tv_now;
+ struct timeval *tv_clock = &bts->vbts.tv_clock;
+ int32_t elapsed_us;
+
+ gettimeofday(&tv_now, NULL);
+
+ /* check how much time elapsed till the last timer callback call.
+ * this value should be about 4.615 ms (a bit greater) as this is the scheduling interval */
+ elapsed_us = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000
+ + (tv_now.tv_usec - tv_clock->tv_usec);
+
+ /* not so good somehow a lot of time passed between two timer callbacks */
+ if (elapsed_us > 2 *FRAME_DURATION_uS)
+ LOGP(DL1P, LOGL_NOTICE, "vbts_fn_timer_cb after %d us\n", elapsed_us);
+
+ /* schedule the current frame/s (fn = frame number)
+ * this loop will be called at least once, but can also be executed
+ * multiple times if more than one frame duration (4615us) passed till the last callback */
+ while (elapsed_us > FRAME_DURATION_uS / 2) {
+ const struct timeval tv_frame = {
+ .tv_sec = 0,
+ .tv_usec = FRAME_DURATION_uS,
+ };
+ timeradd(tv_clock, &tv_frame, tv_clock);
+ /* increment the frame number in the BTS model instance */
+ bts->vbts.last_fn = (bts->vbts.last_fn + 1) % GSM_HYPERFRAME;
+ vbts_sched_fn(bts, bts->vbts.last_fn);
+ elapsed_us -= FRAME_DURATION_uS;
+ }
+
+ /* re-schedule the timer */
+ /* timer is set to frame duration - elapsed time to guarantee that this cb method will be
+ * periodically executed every 4.615ms */
+ osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS - elapsed_us);
+}
+
+int vbts_sched_start(struct gsm_bts *bts)
+{
+ LOGP(DL1P, LOGL_NOTICE, "starting VBTS scheduler\n");
+
+ memset(&bts->vbts.fn_timer, 0, sizeof(bts->vbts.fn_timer));
+ bts->vbts.fn_timer.cb = vbts_fn_timer_cb;
+ bts->vbts.fn_timer.data = bts;
+
+ gettimeofday(&bts->vbts.tv_clock, NULL);
+ /* trigger the first timer after 4615us (a frame duration) */
+ osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS);
+
+ return 0;
+}
diff --git a/src/osmo-bts-virtual/virtual_um.c b/src/osmo-bts-virtual/virtual_um.c
new file mode 100644
index 00000000..fd0940f0
--- /dev/null
+++ b/src/osmo-bts-virtual/virtual_um.c
@@ -0,0 +1,100 @@
+/* Routines for a Virtual Um interface over GSMTAP/UDP */
+
+/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include "osmo_mcast_sock.h"
+#include "virtual_um.h"
+#include <unistd.h>
+
+/**
+ * Virtual UM interface file descriptor callback.
+ * Should be called by select.c when the fd is ready for reading.
+ */
+static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct virt_um_inst *vui = ofd->data;
+
+ if (what & BSC_FD_READ) {
+ struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, "Virtual UM Rx");
+ int rc;
+
+ /* read message from fd into message buffer */
+ rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg));
+ if (rc > 0) {
+ msgb_put(msg, rc);
+ msg->l1h = msgb_data(msg);
+ /* call the l1 callback function for a received msg */
+ vui->recv_cb(vui, msg);
+ } else if (rc == 0) {
+ vui->recv_cb(vui, NULL);
+ osmo_fd_close(ofd);
+ } else
+ perror("Read from multicast socket");
+
+ }
+
+ return 0;
+}
+
+struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port,
+ char *rx_mcast_group, uint16_t rx_mcast_port,
+ void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg))
+{
+ struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst);
+ vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port,
+ rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui);
+ if (!vui->mcast_sock) {
+ perror("Unable to create VirtualUm multicast socket");
+ talloc_free(vui);
+ return NULL;
+ }
+ vui->recv_cb = recv_cb;
+
+ return vui;
+
+}
+
+void virt_um_destroy(struct virt_um_inst *vui)
+{
+ mcast_bidir_sock_close(vui->mcast_sock);
+ talloc_free(vui);
+}
+
+/**
+ * Write msg to to multicast socket and free msg afterwards
+ */
+int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg)
+{
+ int rc;
+
+ rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg),
+ msgb_length(msg));
+ if (rc < 0)
+ perror("Writing to multicast socket");
+ msgb_free(msg);
+
+ return rc;
+}
diff --git a/src/osmo-bts-virtual/virtual_um.h b/src/osmo-bts-virtual/virtual_um.h
new file mode 100644
index 00000000..ac098dd4
--- /dev/null
+++ b/src/osmo-bts-virtual/virtual_um.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include "osmo_mcast_sock.h"
+
+/* We use multicast group addresses from the 239.192.0.0/14 rage, as
+ * those are designated by RFC2365 as "IPv4 Organization Local Scope,
+ * "... the space from which an organization should allocate sub-
+ * ranges when defining scopes for private use." */
+
+#define VIRT_UM_MSGB_SIZE 256
+#define DEFAULT_MS_MCAST_GROUP "239.193.23.1"
+#define DEFAULT_MS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */
+#define DEFAULT_BTS_MCAST_GROUP "239.193.23.2"
+#define DEFAULT_BTS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */
+
+struct virt_um_inst {
+ void *priv;
+ struct mcast_bidir_sock *mcast_sock;
+ void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg);
+};
+
+struct virt_um_inst *virt_um_init(
+ void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port,
+ char *rx_mcast_group, uint16_t rx_mcast_port,
+ void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg));
+
+void virt_um_destroy(struct virt_um_inst *vui);
+
+int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg);
diff --git a/src/osmo-bts-virtual/virtualbts_vty.c b/src/osmo-bts-virtual/virtualbts_vty.c
new file mode 100644
index 00000000..323222b4
--- /dev/null
+++ b/src/osmo-bts-virtual/virtualbts_vty.c
@@ -0,0 +1,185 @@
+/* VTY interface for virtual OsmoBTS */
+
+/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com>
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/vty.h>
+#include "virtual_um.h"
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+
+static struct gsm_bts *vty_bts;
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+ if (plink->u.virt.mcast_dev)
+ vty_out(vty, " virtual-um net-device %s%s",
+ plink->u.virt.mcast_dev, VTY_NEWLINE);
+ if (strcmp(plink->u.virt.ms_mcast_group, DEFAULT_BTS_MCAST_GROUP))
+ vty_out(vty, " virtual-um ms-multicast-group %s%s",
+ plink->u.virt.ms_mcast_group, VTY_NEWLINE);
+ if (plink->u.virt.ms_mcast_port != DEFAULT_BTS_MCAST_PORT)
+ vty_out(vty, " virtual-um ms-udp-port %u%s",
+ plink->u.virt.ms_mcast_port, VTY_NEWLINE);
+ if (strcmp(plink->u.virt.bts_mcast_group, DEFAULT_MS_MCAST_GROUP))
+ vty_out(vty, " virtual-um bts-multicast-group %s%s",
+ plink->u.virt.bts_mcast_group, VTY_NEWLINE);
+ if (plink->u.virt.bts_mcast_port != DEFAULT_MS_MCAST_PORT)
+ vty_out(vty, " virtual-um bts-udp-port %u%s",
+ plink->u.virt.bts_mcast_port, VTY_NEWLINE);
+
+}
+
+#define VUM_STR "Virtual Um layer\n"
+
+DEFUN(cfg_phy_ms_mcast_group, cfg_phy_ms_mcast_group_cmd,
+ "virtual-um ms-multicast-group GROUP",
+ VUM_STR "Configure the MS multicast group\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_talloc_replace_string(plink, &plink->u.virt.ms_mcast_group, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_ms_mcast_port, cfg_phy_ms_mcast_port_cmd,
+ "virtual-um ms-udp-port <0-65535>",
+ VUM_STR "Configure the MS UDP port\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ plink->u.virt.ms_mcast_port = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_bts_mcast_group, cfg_phy_bts_mcast_group_cmd,
+ "virtual-um bts-multicast-group GROUP",
+ VUM_STR "Configure the BTS multicast group\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_talloc_replace_string(plink, &plink->u.virt.bts_mcast_group, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_bts_mcast_port, cfg_phy_bts_mcast_port_cmd,
+ "virtual-um bts-udp-port <0-65535>",
+ VUM_STR "Configure the BTS UDP port\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ plink->u.virt.bts_mcast_port = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_mcast_dev, cfg_phy_mcast_dev_cmd,
+ "virtual-um net-device NETDEV",
+ VUM_STR "Configure the network device\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_talloc_replace_string(plink, &plink->u.virt.mcast_dev, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ install_element(PHY_NODE, &cfg_phy_ms_mcast_group_cmd);
+ install_element(PHY_NODE, &cfg_phy_ms_mcast_port_cmd);
+ install_element(PHY_NODE, &cfg_phy_bts_mcast_group_cmd);
+ install_element(PHY_NODE, &cfg_phy_bts_mcast_port_cmd);
+ install_element(PHY_NODE, &cfg_phy_mcast_dev_cmd);
+
+ return 0;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 00000000..1eb28d6f
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,45 @@
+SUBDIRS = paging cipher agch misc handover tx_power power meas
+
+if ENABLE_SYSMOBTS
+SUBDIRS += sysmobts
+endif
+
+# The `:;' works around a Bash 3.2 bug when the output is not writeable.
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+ :;{ \
+ echo '# Signature of the current package.' && \
+ echo 'm4_define([AT_PACKAGE_NAME],' && \
+ echo ' [$(PACKAGE_NAME)])' && \
+ echo 'm4_define([AT_PACKAGE_TARNAME],' && \
+ echo ' [$(PACKAGE_TARNAME)])' && \
+ echo 'm4_define([AT_PACKAGE_VERSION],' && \
+ echo ' [$(PACKAGE_VERSION)])' && \
+ echo 'm4_define([AT_PACKAGE_STRING],' && \
+ echo ' [$(PACKAGE_STRING)])' && \
+ echo 'm4_define([AT_PACKAGE_BUGREPORT],' && \
+ echo ' [$(PACKAGE_BUGREPORT)])'; \
+ echo 'm4_define([AT_PACKAGE_URL],' && \
+ echo ' [$(PACKAGE_URL)])'; \
+ } >'$(srcdir)/package.m4'
+
+EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE)
+TESTSUITE = $(srcdir)/testsuite
+DISTCLEANFILES = atconfig
+
+check-local: atconfig $(TESTSUITE)
+ $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
+
+installcheck-local: atconfig $(TESTSUITE)
+ $(SHELL) '$(TESTSUITE)' AUTOTEST_PATH='$(bindir)' \
+ $(TESTSUITEFLAGS)
+
+clean-local:
+ test ! -f '$(TESTSUITE)' || \
+ $(SHELL) '$(TESTSUITE)' --clean
+
+AUTOM4TE = $(SHELL) $(top_srcdir)/missing --run autom4te
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): $(srcdir)/testsuite.at $(srcdir)/package.m4
+ $(AUTOTEST) -I '$(srcdir)' -o $@.tmp $@.at
+ mv $@.tmp $@
+
diff --git a/tests/agch/Makefile.am b/tests/agch/Makefile.am
new file mode 100644
index 00000000..0c4fce45
--- /dev/null
+++ b/tests/agch/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS)
+noinst_PROGRAMS = agch_test
+EXTRA_DIST = agch_test.ok
+
+agch_test_SOURCES = agch_test.c $(srcdir)/../stubs.c
+agch_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/agch/agch_test.c b/tests/agch/agch_test.c
new file mode 100644
index 00000000..e6c56d97
--- /dev/null
+++ b/tests/agch/agch_test.c
@@ -0,0 +1,240 @@
+/* testing the agch code */
+
+/* (C) 2011 by Holger Hans Peter Freyther
+ * (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <inttypes.h>
+#include <unistd.h>
+
+static struct gsm_bts *bts;
+
+static int count_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej)
+{
+ int count = 0;
+ count++;
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) {
+ count++;
+ }
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3))
+ && memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) {
+ count++;
+ }
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4))
+ && memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4))
+ && memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) {
+ count++;
+ }
+
+ return count;
+}
+
+static void put_imm_ass_rej(struct msgb *msg, int idx, uint8_t wait_ind)
+{
+ /* GSM CCCH - Immediate Assignment Reject */
+ static const unsigned char gsm_a_ccch_data[23] = {
+ 0x4d, 0x06, 0x3a, 0x03, 0x25, 0x00, 0x00, 0x0a,
+ 0x25, 0x00, 0x00, 0x0a, 0x25, 0x00, 0x00, 0x0a,
+ 0x25, 0x00, 0x00, 0x0a, 0x2b, 0x2b, 0x2b
+ };
+
+ struct gsm48_imm_ass_rej *rej;
+ msg->l3h = msgb_put(msg, sizeof(gsm_a_ccch_data));
+ rej = (struct gsm48_imm_ass_rej *)msg->l3h;
+ memmove(msg->l3h, gsm_a_ccch_data, sizeof(gsm_a_ccch_data));
+
+ rej->req_ref1.t1 = idx;
+ rej->wait_ind1 = wait_ind;
+
+ rej->req_ref2.t1 = idx;
+ rej->req_ref3.t1 = idx;
+ rej->req_ref4.t1 = idx;
+}
+
+static void put_imm_ass(struct msgb *msg, int idx)
+{
+ /* GSM CCCH - Immediate Assignment */
+ static const unsigned char gsm_a_ccch_data[23] = {
+ 0x2d, 0x06, 0x3f, 0x03, 0x0c, 0xe3, 0x69, 0x25,
+ 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b
+ };
+
+ struct gsm48_imm_ass *ima;
+ msg->l3h = msgb_put(msg, sizeof(gsm_a_ccch_data));
+ ima = (struct gsm48_imm_ass *)msg->l3h;
+ memmove(msg->l3h, gsm_a_ccch_data, sizeof(gsm_a_ccch_data));
+
+ ima->req_ref.t1 = idx;
+}
+
+static void test_agch_queue(void)
+{
+ int rc;
+ uint8_t out_buf[GSM_MACBLOCK_LEN];
+ struct gsm_time g_time;
+ const int num_rounds = 40;
+ const int num_ima_per_round = 2;
+ const int num_rej_per_round = 16;
+
+ int round, idx;
+ int count = 0;
+ struct msgb *msg = NULL;
+ int multiframes = 0;
+ int imm_ass_count = 0;
+ int imm_ass_rej_count = 0;
+ int imm_ass_rej_ref_count = 0;
+
+ g_time.fn = 0;
+ g_time.t1 = 0;
+ g_time.t2 = 0;
+ g_time.t3 = 6;
+
+ printf("Testing AGCH messages queue handling.\n");
+ bts->agch_queue.max_length = 32;
+
+ bts->agch_queue.low_level = 30;
+ bts->agch_queue.high_level = 30;
+ bts->agch_queue.thresh_level = 60;
+
+ for (round = 1; round <= num_rounds; round++) {
+ for (idx = 0; idx < num_ima_per_round; idx++) {
+ msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__);
+ put_imm_ass(msg, ++count);
+ bts_agch_enqueue(bts, msg);
+ imm_ass_count++;
+ }
+ for (idx = 0; idx < num_rej_per_round; idx++) {
+ msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__);
+ put_imm_ass_rej(msg, ++count, 10);
+ bts_agch_enqueue(bts, msg);
+ imm_ass_rej_count++;
+ imm_ass_rej_ref_count++;
+ }
+ }
+
+ printf("AGCH filled: count %u, imm.ass %d, imm.ass.rej %d (refs %d), "
+ "queue limit %u, occupied %d, "
+ "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", "
+ "ag-res %"PRIu64", non-res %"PRIu64"\n",
+ count, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count,
+ bts->agch_queue.max_length, bts->agch_queue.length,
+ bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs,
+ bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs,
+ bts->agch_queue.pch_msgs);
+
+ imm_ass_count = 0;
+ imm_ass_rej_count = 0;
+ imm_ass_rej_ref_count = 0;
+
+ for (idx = 0; 1; idx++) {
+ struct gsm48_imm_ass *ima;
+ int is_agch = (idx % 3) == 0; /* 1 AG reserved, 2 PCH */
+ if (is_agch)
+ multiframes++;
+
+ rc = bts_ccch_copy_msg(bts, out_buf, &g_time, is_agch);
+ ima = (struct gsm48_imm_ass *)out_buf;
+ switch (ima->msg_type) {
+ case GSM48_MT_RR_IMM_ASS:
+ imm_ass_count++;
+ break;
+ case GSM48_MT_RR_IMM_ASS_REJ:
+ imm_ass_rej_count++;
+ imm_ass_rej_ref_count +=
+ count_imm_ass_rej_refs((struct gsm48_imm_ass_rej *)ima);
+ break;
+ default:
+ break;
+ }
+ if (is_agch && rc <= 0)
+ break;
+
+ }
+
+ printf("AGCH drained: multiframes %u, imm.ass %d, imm.ass.rej %d (refs %d), "
+ "queue limit %u, occupied %d, "
+ "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", "
+ "ag-res %"PRIu64", non-res %"PRIu64"\n",
+ multiframes, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count,
+ bts->agch_queue.max_length, bts->agch_queue.length,
+ bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs,
+ bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs,
+ bts->agch_queue.pch_msgs);
+}
+
+static void test_agch_queue_length_computation(void)
+{
+ static const int ccch_configs[] = {
+ RSL_BCCH_CCCH_CONF_1_NC,
+ RSL_BCCH_CCCH_CONF_1_C,
+ RSL_BCCH_CCCH_CONF_2_NC,
+ RSL_BCCH_CCCH_CONF_3_NC,
+ RSL_BCCH_CCCH_CONF_4_NC,
+ };
+ static const uint8_t tx_integer[] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50,
+ };
+
+ int T_idx, c_idx, max_len;
+
+ printf("Testing AGCH queue length computation.\n");
+
+ printf("T\t\tBCCH slots\n");
+ printf("\t1(NC)\t1(C)\t2(NC)\t3(NC)\t4(NC)\n");
+ for (T_idx = 0; T_idx < ARRAY_SIZE(tx_integer); T_idx++) {
+ printf("%d", tx_integer[T_idx]);
+ for (c_idx = 0; c_idx < ARRAY_SIZE(ccch_configs); c_idx++) {
+ max_len = bts_agch_max_queue_length(tx_integer[T_idx],
+ ccch_configs[c_idx]);
+ printf("\t%d", max_len);
+ }
+ printf("\n");
+ }
+}
+
+int main(int argc, char **argv)
+{
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 0);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to open bts\n");
+ exit(1);
+ }
+
+ test_agch_queue_length_computation();
+ test_agch_queue();
+ printf("Success\n");
+
+ return 0;
+}
+
diff --git a/tests/agch/agch_test.ok b/tests/agch/agch_test.ok
new file mode 100644
index 00000000..a506f451
--- /dev/null
+++ b/tests/agch/agch_test.ok
@@ -0,0 +1,23 @@
+Testing AGCH queue length computation.
+T BCCH slots
+ 1(NC) 1(C) 2(NC) 3(NC) 4(NC)
+3 20 9 20 20 20
+4 28 11 28 28 28
+5 40 13 40 40 40
+6 59 19 59 59 59
+7 79 25 79 79 79
+8 21 9 21 21 21
+9 28 12 28 28 28
+10 40 13 40 40 40
+11 60 20 60 60 60
+12 80 26 80 80 80
+14 22 10 22 22 22
+16 30 13 30 30 30
+20 42 14 42 42 42
+25 63 21 63 63 63
+32 83 28 83 83 83
+50 28 14 28 28 28
+Testing AGCH messages queue handling.
+AGCH filled: count 720, imm.ass 80, imm.ass.rej 640 (refs 640), queue limit 32, occupied 101, dropped 0, merged 198, rejected 421, ag-res 0, non-res 0
+AGCH drained: multiframes 4, imm.ass 2, imm.ass.rej 8 (refs 26), queue limit 32, occupied 0, dropped 92, merged 198, rejected 421, ag-res 3, non-res 6
+Success
diff --git a/tests/cipher/Makefile.am b/tests/cipher/Makefile.am
new file mode 100644
index 00000000..3c23718e
--- /dev/null
+++ b/tests/cipher/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS)
+noinst_PROGRAMS = cipher_test
+EXTRA_DIST = cipher_test.ok
+
+cipher_test_SOURCES = cipher_test.c $(srcdir)/../stubs.c
+cipher_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/cipher/cipher_test.c b/tests/cipher/cipher_test.c
new file mode 100644
index 00000000..9d78a880
--- /dev/null
+++ b/tests/cipher/cipher_test.c
@@ -0,0 +1,85 @@
+/* (C) 2012 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+static struct gsm_bts *bts;
+
+#define ASSERT_TRUE(rc) \
+ if (!(rc)) { \
+ printf("Assert failed in %s:%d.\n", \
+ __FILE__, __LINE__); \
+ abort(); \
+ }
+
+static void test_cipher_parsing(void)
+{
+ int i;
+
+ bts->support.ciphers = 0;
+
+ /* always support A5/0 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x0) == -ENOTSUP);
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x1) == 1); /* A5/0 */
+ for (i = 2; i <= 8; ++i) {
+ ASSERT_TRUE(bts_supports_cipher(bts, i) == 0);
+ }
+
+ /* checking default A5/1 to A5/3 support */
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x0) == -ENOTSUP);
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x1) == 1); /* A5/0 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x2) == 1); /* A5/1 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x3) == 1); /* A5/2 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x4) == 1); /* A5/3 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x5) == 0); /* A5/4 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x6) == 0); /* A5/5 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x7) == 0); /* A5/6 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x8) == 0); /* A5/7 */
+ ASSERT_TRUE(bts_supports_cipher(bts, 0x9) == -ENOTSUP);
+}
+
+int main(int argc, char **argv)
+{
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 0);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to open bts\n");
+ exit(1);
+ }
+
+ test_cipher_parsing();
+ printf("Success\n");
+
+ return 0;
+}
+
diff --git a/tests/cipher/cipher_test.ok b/tests/cipher/cipher_test.ok
new file mode 100644
index 00000000..35821117
--- /dev/null
+++ b/tests/cipher/cipher_test.ok
@@ -0,0 +1 @@
+Success
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
new file mode 100644
index 00000000..966ea469
--- /dev/null
+++ b/tests/handover/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS)
+noinst_PROGRAMS = handover_test
+EXTRA_DIST = handover_test.ok
+
+handover_test_SOURCES = handover_test.c
+handover_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c
new file mode 100644
index 00000000..a805554e
--- /dev/null
+++ b/tests/handover/handover_test.c
@@ -0,0 +1,283 @@
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sched.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/backtrace.h>
+#include <osmocom/abis/abis.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/handover.h>
+
+uint8_t phys_info[] = { 0x03, 0x03, 0x0d, 0x06, 0x2d, 0x00, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b };
+
+static struct gsm_bts *bts;
+struct gsm_bts_trx *trx;
+int quit = 0;
+uint8_t abis_mac[6] = { 0, 1, 2, 3, 4, 5 };
+int modify_count = 0;
+
+static void expect_phys_info(struct lapdm_entity *le)
+{
+ struct osmo_phsap_prim pp;
+ int rc;
+
+ rc = lapdm_phsap_dequeue_prim(le, &pp);
+ OSMO_ASSERT(rc == 0);
+ OSMO_ASSERT(sizeof(phys_info) == pp.oph.msg->len);
+ OSMO_ASSERT(!memcmp(phys_info, pp.oph.msg->data, pp.oph.msg->len));
+ msgb_free(pp.oph.msg);
+}
+
+int main(int argc, char **argv)
+{
+ void *tall_bts_ctx;
+ struct e1inp_line *line;
+ struct gsm_lchan *lchan;
+ struct osmo_phsap_prim nl1sap;
+ struct msgb *msg;
+ struct abis_rsl_dchan_hdr *rslh;
+ int i;
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 0);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+ osmo_stderr_target->categories[DHO].loglevel = LOGL_DEBUG;
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (!bts) {
+ fprintf(stderr, "Failed to create BTS structure\n");
+ exit(1);
+ }
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to init BTS\n");
+ exit(1);
+ }
+
+ trx = gsm_bts_trx_alloc(bts);
+ if (!trx) {
+ fprintf(stderr, "Failed to alloc TRX structure\n");
+ exit(1);
+ }
+ if (bts_trx_init(trx) < 0) {
+ fprintf(stderr, "unable to init TRX\n");
+ exit(1);
+ }
+
+ libosmo_abis_init(NULL);
+
+ line = e1inp_line_create(0, "ipa");
+ OSMO_ASSERT(line);
+
+ e1inp_ts_config_sign(&line->ts[E1INP_SIGN_RSL-1], line);
+ trx->rsl_link = e1inp_sign_link_create(&line->ts[E1INP_SIGN_RSL-1], E1INP_SIGN_RSL, NULL, 0, 0);
+ OSMO_ASSERT(trx->rsl_link);
+ trx->rsl_link->trx = trx;
+
+ fprintf(stderr, "test 1: without timeout\n");
+
+ /* create two lchans for handover */
+ lchan = &trx->ts[1].lchan[0];
+ lchan->type = GSM_LCHAN_SDCCH;
+ l1sap_chan_act(lchan->ts->trx, 0x09, NULL);
+ lchan = &trx->ts[2].lchan[0];
+ lchan->type = GSM_LCHAN_TCH_F;
+ lchan->ho.active = HANDOVER_ENABLED;
+ lchan->ho.ref = 23;
+ l1sap_chan_act(lchan->ts->trx, 0x0a, NULL);
+ OSMO_ASSERT(msgb_dequeue(&trx->rsl_link->tx_list));
+ OSMO_ASSERT(msgb_dequeue(&trx->rsl_link->tx_list));
+ OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list));
+
+ /* send access burst with wrong ref */
+ memset(&nl1sap, 0, sizeof(nl1sap));
+ osmo_prim_init(&nl1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL);
+ nl1sap.u.rach_ind.chan_nr = 0x0a;
+ nl1sap.u.rach_ind.ra = 42;
+ l1sap_up(trx, &nl1sap);
+
+ /* expect no action */
+ OSMO_ASSERT(modify_count == 0);
+ OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list));
+
+ /* send access burst with correct ref */
+ nl1sap.u.rach_ind.ra = 23;
+ l1sap_up(trx, &nl1sap);
+ OSMO_ASSERT(modify_count == 1);
+
+ /* expect PHYS INFO */
+ expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch);
+
+ /* expect exactly one HO.DET */
+ OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list));
+ rslh = msgb_l2(msg);
+ OSMO_ASSERT(rslh->c.msg_type == RSL_MT_HANDO_DET);
+ OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list));
+
+ /* expect T3105 running */
+ OSMO_ASSERT(osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105))
+
+ /* indicate frame */
+ handover_frame(&trx->ts[2].lchan[0]);
+
+ /* expect T3105 not running */
+ OSMO_ASSERT(!osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105))
+
+ fprintf(stderr, "test 2: with timeout\n");
+
+ /* enable handover again */
+ lchan = &trx->ts[2].lchan[0];
+ lchan->ho.active = HANDOVER_ENABLED;
+ lchan->ho.ref = 23;
+ modify_count = 0;
+
+ /* send access burst with correct ref */
+ nl1sap.u.rach_ind.ra = 23;
+ l1sap_up(trx, &nl1sap);
+ OSMO_ASSERT(modify_count == 1);
+
+ /* expect PHYS INFO */
+ expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch);
+
+ /* expect exactly one HO.DET */
+ OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list));
+ rslh = msgb_l2(msg);
+ OSMO_ASSERT(rslh->c.msg_type == RSL_MT_HANDO_DET);
+ OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list));
+
+ for (i = 0; i < bts->ny1 - 1; i++) {
+ /* expect T3105 running */
+ OSMO_ASSERT(osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105))
+
+ /* timeout T3105 */
+ gettimeofday(&trx->ts[2].lchan[0].ho.t3105.timeout, NULL);
+ osmo_select_main(0);
+
+ /* expect PHYS INFO */
+ expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch);
+ }
+
+ /* timeout T3105 */
+ gettimeofday(&trx->ts[2].lchan[0].ho.t3105.timeout, NULL);
+ osmo_select_main(0);
+
+ /* expect T3105 not running */
+ OSMO_ASSERT(!osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105))
+
+ /* expect exactly one CONN.FAIL */
+ OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list));
+ rslh = msgb_l2(msg);
+ OSMO_ASSERT(rslh->c.msg_type == RSL_MT_CONN_FAIL);
+ OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list));
+
+#if 0
+ while (!quit) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+#endif
+
+ printf("Success\n");
+
+ return 0;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ int rc = 0;
+ uint8_t chan_nr;
+ uint8_t tn, ss;
+ struct gsm_lchan *lchan;
+ struct msgb *msg = l1sap->oph.msg;
+ struct osmo_phsap_prim nl1sap;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACTIVATE:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ tn = L1SAP_CHAN2TS(chan_nr);
+ ss = l1sap_chan2ss(chan_nr);
+ lchan = &trx->ts[tn].lchan[ss];
+
+ lchan_init_lapdm(lchan);
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+
+ memset(&nl1sap, 0, sizeof(nl1sap));
+ osmo_prim_init(&nl1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, NULL);
+ nl1sap.u.info.type = PRIM_INFO_ACTIVATE;
+ nl1sap.u.info.u.act_cnf.chan_nr = chan_nr;
+ return l1sap_up(trx, &nl1sap);
+ case PRIM_INFO_MODIFY:
+ modify_count++;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ goto done;
+ }
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ goto done;
+ }
+
+done:
+ if (msg)
+ msgb_free(msg);
+ return rc;
+}
+
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj) { return 0; }
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int obj_kind, void *obj) { return 0; }
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { return 0; }
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { return 0; }
+int bts_model_init(struct gsm_bts *bts) { return 0; }
+int bts_model_trx_init(struct gsm_bts_trx *trx) { return 0; }
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { return 0; }
+int bts_model_trx_close(struct gsm_bts_trx *trx) { return 0; }
+void trx_get_hlayer1(void) {}
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { return 0; }
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) { return 0; }
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) { return; }
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { return 0; }
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return 0; }
diff --git a/tests/handover/handover_test.ok b/tests/handover/handover_test.ok
new file mode 100644
index 00000000..35821117
--- /dev/null
+++ b/tests/handover/handover_test.ok
@@ -0,0 +1 @@
+Success
diff --git a/tests/meas/Makefile.am b/tests/meas/Makefile.am
new file mode 100644
index 00000000..d8fa1182
--- /dev/null
+++ b/tests/meas/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS)
+noinst_PROGRAMS = meas_test
+noinst_HEADERS = sysmobts_fr_samples.h meas_testcases.h
+EXTRA_DIST = meas_test.ok
+
+meas_test_SOURCES = meas_test.c
+meas_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/meas/meas_test.c b/tests/meas/meas_test.c
new file mode 100644
index 00000000..b2bf80e2
--- /dev/null
+++ b/tests/meas/meas_test.c
@@ -0,0 +1,675 @@
+#include <stdio.h>
+#include <stdint.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/rsl.h>
+
+static struct gsm_bts *bts;
+struct gsm_bts_trx *trx;
+
+struct fn_sample {
+ uint32_t fn;
+ uint8_t ts;
+ uint8_t ss;
+ int rc;
+};
+
+#include "sysmobts_fr_samples.h"
+#include "meas_testcases.h"
+
+
+void test_fn_sample(struct fn_sample *s, unsigned int len, uint8_t pchan, uint8_t tsmap)
+{
+ int rc;
+ struct gsm_lchan *lchan;
+ unsigned int i;
+ unsigned int delta = 0;
+ uint8_t tsmap_result = 0;
+ uint32_t fn_prev = 0;
+ struct gsm_time gsm_time;
+
+
+ printf("\n\n");
+ printf("===========================================================\n");
+
+ for (i = 0; i < len; i++) {
+
+ lchan = &trx->ts[s[i].ts].lchan[s[i].ss];
+ trx->ts[s[i].ts].pchan = pchan;
+ lchan->meas.num_ul_meas = 1;
+
+ rc = lchan_meas_check_compute(lchan, s[i].fn);
+ if (rc) {
+ gsm_fn2gsmtime(&gsm_time, s[i].fn);
+ fprintf(stdout, "Testing: ts[%i]->lchan[%i], fn=%u=>%s, fn%%104=%u, rc=%i, delta=%i\n", s[i].ts,
+ s[i].ss, s[i].fn, osmo_dump_gsmtime(&gsm_time), s[i].fn % 104, rc, s[i].fn - fn_prev);
+ fn_prev = s[i].fn;
+ tsmap_result |= (1 << s[i].ts);
+ } else
+ delta++;
+
+ /* If the test data set provides a return
+ * code, we check that as well */
+ if (s[i].rc != -1)
+ OSMO_ASSERT(s[i].rc == rc);
+ }
+
+ /* Make sure that we exactly trigger on the right frames
+ * timeslots must match exactlty to what we expect */
+ OSMO_ASSERT(tsmap_result == tsmap);
+}
+
+static void reset_lchan_meas(struct gsm_lchan *lchan)
+{
+ lchan->state = LCHAN_S_ACTIVE;
+ memset(&lchan->meas, 0, sizeof(lchan->meas));
+}
+
+static void test_meas_compute(const struct meas_testcase *mtc)
+{
+ struct gsm_lchan *lchan;
+ unsigned int i;
+ unsigned int fn = 0;
+
+ printf("\n\n");
+ printf("===========================================================\n");
+ printf("Measurement Compute Test: %s\n", mtc->name);
+
+ lchan = &trx->ts[mtc->ts].lchan[0];
+ lchan->ts->pchan = mtc->pchan;
+ reset_lchan_meas(lchan);
+
+ /* feed uplink measurements into the code */
+ for (i = 0; i < mtc->ulm_count; i++) {
+ lchan_new_ul_meas(lchan, (struct bts_ul_meas *) &mtc->ulm[i], fn);
+ fn += 1;
+ }
+
+ /* compute the results */
+ OSMO_ASSERT(lchan_meas_check_compute(lchan, mtc->final_fn) == mtc->res.success);
+ if (!mtc->res.success) {
+ OSMO_ASSERT(!(lchan->meas.flags & LC_UL_M_F_RES_VALID));
+ } else {
+ OSMO_ASSERT(lchan->meas.flags & (LC_UL_M_F_RES_VALID|LC_UL_M_F_OSMO_EXT_VALID));
+ printf("number of measurements: %u\n", mtc->ulm_count);
+ printf("parameter | actual | expected\n");
+ printf("meas.ext.toa256_min | %6d | %6d\n",
+ lchan->meas.ext.toa256_min, mtc->res.toa256_min);
+ printf("meas.ext.toa256_max | %6d | %6d\n",
+ lchan->meas.ext.toa256_max, mtc->res.toa256_max);
+ printf("meas.ms_toa256 | %6d | %6d\n",
+ lchan->meas.ms_toa256, mtc->res.toa256_mean);
+ printf("meas.ext.toa256_std_dev | %6u | %6u\n",
+ lchan->meas.ext.toa256_std_dev, mtc->res.toa256_std_dev);
+ printf("meas.ul_res.full.rx_lev | %6u | %6u\n",
+ lchan->meas.ul_res.full.rx_lev, mtc->res.rx_lev_full);
+ printf("meas.ul_res.full.rx_qual | %6u | %6u\n",
+ lchan->meas.ul_res.full.rx_qual, mtc->res.rx_qual_full);
+
+ if ((lchan->meas.ext.toa256_min != mtc->res.toa256_min) ||
+ (lchan->meas.ext.toa256_max != mtc->res.toa256_max) ||
+ (lchan->meas.ms_toa256 != mtc->res.toa256_mean) ||
+ (lchan->meas.ext.toa256_std_dev != mtc->res.toa256_std_dev) ||
+ (lchan->meas.ul_res.full.rx_lev != mtc->res.rx_lev_full)) {
+ fprintf(stderr, "%s: Unexpected measurement result!\n", mtc->name);
+ OSMO_ASSERT(false);
+ }
+ }
+
+}
+
+static void test_is_meas_complete_single(struct gsm_lchan *lchan,
+ uint32_t fn_end, uint8_t intv_len)
+{
+ unsigned int i;
+ unsigned int k;
+ int rc;
+ uint32_t offset;
+
+ /* Walk through multiple measurement intervals and make sure that the
+ * interval end is detected only in the expected location */
+ for (k = 0; k < 100; k++) {
+ offset = intv_len * k;
+ for (i = 0; i < intv_len; i++) {
+ rc = is_meas_complete(lchan, i + offset);
+ if (rc)
+ OSMO_ASSERT(i + offset == fn_end + offset);
+ }
+ }
+}
+
+static void test_is_meas_complete(void)
+{
+ struct gsm_lchan *lchan;
+ printf("\n\n");
+ printf("===========================================================\n");
+ printf("Testing is_meas_complete()\n");
+
+ /* Test interval end detection on TCH/F TS0-TS7 */
+ lchan = &trx->ts[0].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 12, 104);
+
+ lchan = &trx->ts[1].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 25, 104);
+
+ lchan = &trx->ts[2].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 38, 104);
+
+ lchan = &trx->ts[3].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 51, 104);
+
+ lchan = &trx->ts[4].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 64, 104);
+
+ lchan = &trx->ts[5].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 77, 104);
+
+ lchan = &trx->ts[6].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 90, 104);
+
+ lchan = &trx->ts[7].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ test_is_meas_complete_single(lchan, 103, 104);
+
+ /* Test interval end detection on TCH/H TS0-TS7 */
+ lchan = &trx->ts[0].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 12, 104);
+
+ lchan = &trx->ts[1].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 12, 104);
+
+ lchan = &trx->ts[0].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 25, 104);
+
+ lchan = &trx->ts[1].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 25, 104);
+
+ lchan = &trx->ts[2].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 38, 104);
+
+ lchan = &trx->ts[3].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 38, 104);
+
+ lchan = &trx->ts[2].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 51, 104);
+
+ lchan = &trx->ts[3].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 51, 104);
+
+ lchan = &trx->ts[4].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 64, 104);
+
+ lchan = &trx->ts[5].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 64, 104);
+
+ lchan = &trx->ts[4].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 77, 104);
+
+ lchan = &trx->ts[5].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 77, 104);
+
+ lchan = &trx->ts[6].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 90, 104);
+
+ lchan = &trx->ts[7].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 90, 104);
+
+ lchan = &trx->ts[6].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 103, 104);
+
+ lchan = &trx->ts[7].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ test_is_meas_complete_single(lchan, 103, 104);
+
+ /* Test interval end detection on SDCCH/8 SS0-SS7 */
+ lchan = &trx->ts[0].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 66, 102);
+
+ lchan = &trx->ts[0].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 70, 102);
+
+ lchan = &trx->ts[0].lchan[2];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 74, 102);
+
+ lchan = &trx->ts[0].lchan[3];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 78, 102);
+
+ lchan = &trx->ts[0].lchan[4];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 98, 102);
+
+ lchan = &trx->ts[0].lchan[5];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 0, 102);
+
+ lchan = &trx->ts[0].lchan[6];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 4, 102);
+
+ lchan = &trx->ts[0].lchan[7];
+ lchan->ts->pchan = GSM_PCHAN_SDCCH8_SACCH8C;
+ test_is_meas_complete_single(lchan, 8, 102);
+
+ /* Test interval end detection on SDCCH/4 SS0-SS3 */
+ lchan = &trx->ts[0].lchan[0];
+ lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
+ test_is_meas_complete_single(lchan, 88, 102);
+
+ lchan = &trx->ts[0].lchan[1];
+ lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
+ test_is_meas_complete_single(lchan, 92, 102);
+
+ lchan = &trx->ts[0].lchan[2];
+ lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
+ test_is_meas_complete_single(lchan, 6, 102);
+
+ lchan = &trx->ts[0].lchan[3];
+ lchan->ts->pchan = GSM_PCHAN_CCCH_SDCCH4;
+ test_is_meas_complete_single(lchan, 10, 102);
+}
+
+/* This tests the robustness of lchan_meas_process_measurement(). This is the
+ * function that is called from l1_sap.c each time a measurement indication is
+ * received. The process must still go on when measurement indications (blocks)
+ * are lost or otherwise spaced out. Even the complete absence of the
+ * measurement indications from the SACCH which are used to detect the interval
+ * end must not keep the interval from beeing processed. */
+void test_lchan_meas_process_measurement(bool no_sacch, bool dropouts)
+{
+ struct gsm_lchan *lchan = &trx->ts[2].lchan[0];
+ unsigned int i;
+ unsigned int k = 0;
+ unsigned int fn = 0;
+ unsigned int fn104;
+ struct bts_ul_meas ulm;
+ int rc;
+
+ printf("\n\n");
+ printf("===========================================================\n");
+ printf("Testing lchan_meas_process_measurement()\n");
+ if (no_sacch)
+ printf(" * SACCH blocks not generated.\n");
+ if (dropouts)
+ printf
+ (" * Simulate dropouts by leaving out every 4th measurement\n");
+
+ ulm.ber10k = 0;
+ ulm.ta_offs_256bits = 256;
+ ulm.c_i = 0;
+ ulm.is_sub = 0;
+ ulm.inv_rssi = 90;
+
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ reset_lchan_meas(lchan);
+
+ /* feed uplink measurements into the code */
+ for (i = 0; i < 100; i++) {
+
+ fn104 = fn % 104;
+ ulm.is_sub = 0;
+
+ if (fn104 >= 52 && fn104 <= 59) {
+ ulm.is_sub = 1;
+ }
+
+ if (dropouts == false || i % 4) {
+ if (ulm.is_sub == 1)
+ printf("(now adding SUB measurement sample %u)\n", fn);
+ rc = lchan_meas_process_measurement(lchan, &ulm, fn);
+ OSMO_ASSERT(rc == 0);
+ } else if (ulm.is_sub == 1)
+ printf("(leaving out SUB measurement sample for frame number %u)\n", fn);
+ else
+ printf("(leaving out measurement sample for frame number %u)\n", fn);
+
+ fn += 4;
+ if (k == 2) {
+ fn++;
+ k = 0;
+ } else
+ k++;
+
+ if (fn % 104 == 39 && no_sacch == false) {
+ printf("(now adding SUB measurement sample for SACCH block at frame number %u)\n", fn);
+ ulm.is_sub = 1;
+ rc = lchan_meas_process_measurement(lchan, &ulm, fn - 1);
+ OSMO_ASSERT(rc);
+ } else if (fn % 104 == 39 && no_sacch == true)
+ printf("(leaving out SUB measurement sample for SACCH block at frame number %u)\n", fn);
+ }
+}
+
+static bool test_ts45008_83_is_sub_is_sacch(uint32_t fn)
+{
+ if (fn % 104 == 12)
+ return true;
+ if (fn % 104 == 25)
+ return true;
+ if (fn % 104 == 38)
+ return true;
+ if (fn % 104 == 51)
+ return true;
+ if (fn % 104 == 64)
+ return true;
+ if (fn % 104 == 77)
+ return true;
+ if (fn % 104 == 90)
+ return true;
+ if (fn % 104 == 103)
+ return true;
+
+ return false;
+}
+
+static bool test_ts45008_83_is_sub_is_sub(uint32_t fn, uint8_t ss)
+{
+ fn = fn % 104;
+
+ if (fn >= 52 && fn <= 59)
+ return true;
+
+ if (ss == 0) {
+ if (fn == 0)
+ return true;
+ if (fn == 2)
+ return true;
+ if (fn == 4)
+ return true;
+ if (fn == 6)
+ return true;
+ if (fn == 52)
+ return true;
+ if (fn == 54)
+ return true;
+ if (fn == 56)
+ return true;
+ if (fn == 58)
+ return true;
+ } else if (ss == 1) {
+ if (fn == 14)
+ return true;
+ if (fn == 16)
+ return true;
+ if (fn == 18)
+ return true;
+ if (fn == 20)
+ return true;
+ if (fn == 66)
+ return true;
+ if (fn == 68)
+ return true;
+ if (fn == 70)
+ return true;
+ if (fn == 72)
+ return true;
+ } else
+ OSMO_ASSERT(false);
+
+ return false;
+}
+
+static void test_ts45008_83_is_sub_single(uint8_t ts, uint8_t ss, bool fr)
+{
+ struct gsm_lchan *lchan;
+ bool rc;
+ unsigned int i;
+
+ lchan = &trx->ts[ts].lchan[ss];
+
+ printf("Checking: ");
+
+ if (fr) {
+ printf("TCH/F");
+ lchan->type = GSM_LCHAN_TCH_F;
+ lchan->ts->pchan = GSM_PCHAN_TCH_F;
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ } else {
+ printf("TCH/H");
+ lchan->type = GSM_LCHAN_TCH_H;
+ lchan->ts->pchan = GSM_PCHAN_TCH_H;
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ }
+
+ printf(" TS=%u ", ts);
+ printf("SS=%u", ss);
+
+ /* Walk trough the first 100 intervals and check for unexpected
+ * results (false positive and false negative) */
+ for (i = 0; i < 104 * 100; i++) {
+ rc = ts45008_83_is_sub(lchan, i, false);
+ if (rc) {
+ if (!test_ts45008_83_is_sub_is_sacch(i)
+ && !test_ts45008_83_is_sub_is_sub(i, ss)) {
+ printf("==> Unexpected SUB frame at fn=%u", i);
+ OSMO_ASSERT(false);
+ }
+ } else {
+ if (test_ts45008_83_is_sub_is_sacch(i)
+ && test_ts45008_83_is_sub_is_sub(i, ss)) {
+ printf("==> Unexpected non-SUB frame at fn=%u",
+ i);
+ OSMO_ASSERT(false);
+ }
+ }
+ }
+ printf("\n");
+}
+
+static void test_ts45008_83_is_sub(void)
+{
+ unsigned int i;
+
+ printf("\n\n");
+ printf("===========================================================\n");
+ printf("Testing ts45008_83_is_sub()\n");
+
+ for (i = 0; i < 7; i++)
+ test_ts45008_83_is_sub_single(i, 0, true);
+ for (i = 0; i < 7; i++)
+ test_ts45008_83_is_sub_single(i, 0, false);
+ for (i = 0; i < 7; i++)
+ test_ts45008_83_is_sub_single(i, 1, false);
+}
+
+int main(int argc, char **argv)
+{
+ void *tall_bts_ctx;
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 0);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+ osmo_stderr_target->categories[DMEAS].loglevel = LOGL_DEBUG;
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (!bts) {
+ fprintf(stderr, "Failed to create BTS structure\n");
+ exit(1);
+ }
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to init BTS\n");
+ exit(1);
+ }
+
+ trx = gsm_bts_trx_alloc(bts);
+ if (!trx) {
+ fprintf(stderr, "Failed to alloc TRX structure\n");
+ exit(1);
+ }
+ if (bts_trx_init(trx) < 0) {
+ fprintf(stderr, "unable to init TRX\n");
+ exit(1);
+ }
+
+ printf("\n");
+ printf("***********************\n");
+ printf("*** FULL RATE TESTS ***\n");
+ printf("***********************\n");
+
+ /* Test full rate */
+ test_fn_sample(test_fn_tch_f_ts_2_3, ARRAY_SIZE(test_fn_tch_f_ts_2_3), GSM_PCHAN_TCH_F, (1 << 2) | (1 << 3));
+ test_fn_sample(test_fn_tch_f_ts_4_5, ARRAY_SIZE(test_fn_tch_f_ts_4_5), GSM_PCHAN_TCH_F, (1 << 4) | (1 << 5));
+ test_fn_sample(test_fn_tch_f_ts_6_7, ARRAY_SIZE(test_fn_tch_f_ts_6_7), GSM_PCHAN_TCH_F, (1 << 6) | (1 << 7));
+
+ printf("\n");
+ printf("***********************\n");
+ printf("*** HALF RATE TESTS ***\n");
+ printf("***********************\n");
+
+ /* Test half rate */
+ test_fn_sample(test_fn_tch_h_ts_2_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_2_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 2));
+ test_fn_sample(test_fn_tch_h_ts_3_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_3_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 3));
+ test_fn_sample(test_fn_tch_h_ts_4_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_4_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 4));
+ test_fn_sample(test_fn_tch_h_ts_5_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_5_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 5));
+ test_fn_sample(test_fn_tch_h_ts_6_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_6_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 6));
+ test_fn_sample(test_fn_tch_h_ts_7_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_7_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 7));
+
+ test_meas_compute(&mtc1);
+ test_meas_compute(&mtc2);
+ test_meas_compute(&mtc3);
+ test_meas_compute(&mtc4);
+ test_meas_compute(&mtc5);
+ test_meas_compute(&mtc_tch_f_complete);
+ test_meas_compute(&mtc_tch_f_dtx_with_lost_subs);
+ test_meas_compute(&mtc_tch_f_dtx);
+ test_meas_compute(&mtc_tch_h_complete);
+ test_meas_compute(&mtc_tch_h_dtx_with_lost_subs);
+ test_meas_compute(&mtc_tch_h_dtx);
+ test_meas_compute(&mtc_overrun);
+ test_meas_compute(&mtc_sdcch4_complete);
+ test_meas_compute(&mtc_sdcch8_complete);
+
+ printf("\n");
+ printf("***************************************************\n");
+ printf("*** MEASUREMENT INTERVAL ENDING DETECTION TESTS ***\n");
+ printf("***************************************************\n");
+
+ test_is_meas_complete();
+ test_lchan_meas_process_measurement(false, false);
+ test_lchan_meas_process_measurement(true, false);
+ test_lchan_meas_process_measurement(false, true);
+ test_lchan_meas_process_measurement(true, true);
+ test_ts45008_83_is_sub();
+
+ printf("Success\n");
+
+ return 0;
+}
+
+/* Stubs */
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ return 0;
+}
+
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{
+ return 0;
+}
+
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int obj_kind, void *obj)
+{
+ return 0;
+}
+
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj)
+{
+ return 0;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state)
+{
+ return 0;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+void trx_get_hlayer1(void)
+{
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan)
+{
+ return;
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ return 0;
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ return 0;
+}
diff --git a/tests/meas/meas_test.ok b/tests/meas/meas_test.ok
new file mode 100644
index 00000000..e62bb42f
--- /dev/null
+++ b/tests/meas/meas_test.ok
@@ -0,0 +1,853 @@
+
+***********************
+*** FULL RATE TESTS ***
+***********************
+
+
+===========================================================
+Testing: ts[2]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=10958
+Testing: ts[2]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=104
+Testing: ts[3]->lchan[0], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=11595=>011595/08/25/18/23, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11686=>011686/08/12/07/10, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=11699=>011699/08/25/20/23, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11790=>011790/08/12/09/14, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=11803=>011803/08/25/22/27, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11894=>011894/08/12/11/14, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=11907=>011907/08/25/24/27, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11998=>011998/09/12/13/14, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12011=>012011/09/25/26/27, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=12102=>012102/09/12/15/18, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12115=>012115/09/25/28/31, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=12206=>012206/09/12/17/18, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12219=>012219/09/25/30/31, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=12310=>012310/09/12/19/22, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12323=>012323/09/25/32/35, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=12414=>012414/09/12/21/22, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12427=>012427/09/25/34/35, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=12518=>012518/09/12/23/22, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12531=>012531/09/25/36/35, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=12622=>012622/09/12/25/26, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[0], fn=12635=>012635/09/25/38/39, fn%104=51, rc=1, delta=13
+
+
+===========================================================
+Testing: ts[4]->lchan[0], fn=5888=>005888/04/12/23/00, fn%104=64, rc=1, delta=5888
+Testing: ts[4]->lchan[0], fn=5992=>005992/04/12/25/00, fn%104=64, rc=1, delta=104
+Testing: ts[4]->lchan[0], fn=6096=>006096/04/12/27/00, fn%104=64, rc=1, delta=104
+Testing: ts[4]->lchan[0], fn=6200=>006200/04/12/29/04, fn%104=64, rc=1, delta=104
+Testing: ts[5]->lchan[0], fn=6213=>006213/04/25/42/17, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6304=>006304/04/12/31/04, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6317=>006317/04/25/44/17, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6408=>006408/04/12/33/08, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6421=>006421/04/25/46/21, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6512=>006512/04/12/35/08, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6525=>006525/04/25/48/21, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6616=>006616/04/12/37/08, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6629=>006629/04/25/50/21, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6720=>006720/05/12/39/12, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6733=>006733/05/25/01/25, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6824=>006824/05/12/41/12, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6837=>006837/05/25/03/25, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=6928=>006928/05/12/43/16, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=6941=>006941/05/25/05/29, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7032=>007032/05/12/45/16, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7045=>007045/05/25/07/29, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7136=>007136/05/12/47/16, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7149=>007149/05/25/09/29, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7240=>007240/05/12/49/20, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7253=>007253/05/25/11/33, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7344=>007344/05/12/00/20, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7357=>007357/05/25/13/33, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7448=>007448/05/12/02/24, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7461=>007461/05/25/15/37, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7552=>007552/05/12/04/24, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7565=>007565/05/25/17/37, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7656=>007656/05/12/06/24, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7669=>007669/05/25/19/37, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7773=>007773/05/25/21/41, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7877=>007877/05/25/23/41, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=7981=>007981/06/25/25/45, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8085=>008085/06/25/27/45, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8592=>008592/06/12/24/40, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8605=>008605/06/25/37/01, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8696=>008696/06/12/26/40, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8709=>008709/06/25/39/05, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8800=>008800/06/12/28/44, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8813=>008813/06/25/41/05, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8904=>008904/06/12/30/44, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=8917=>008917/06/25/43/05, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9008=>009008/06/12/32/48, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9021=>009021/06/25/45/09, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9112=>009112/06/12/34/48, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9125=>009125/06/25/47/09, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9216=>009216/06/12/36/00, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9229=>009229/06/25/49/13, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9320=>009320/07/12/38/00, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9333=>009333/07/25/00/13, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9424=>009424/07/12/40/00, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9437=>009437/07/25/02/13, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9528=>009528/07/12/42/04, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9541=>009541/07/25/04/17, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9632=>009632/07/12/44/04, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[0], fn=9645=>009645/07/25/06/17, fn%104=77, rc=1, delta=13
+
+
+===========================================================
+Testing: ts[6]->lchan[0], fn=8618=>008618/06/12/50/14, fn%104=90, rc=1, delta=8618
+Testing: ts[6]->lchan[0], fn=8722=>008722/06/12/01/18, fn%104=90, rc=1, delta=104
+Testing: ts[6]->lchan[0], fn=8826=>008826/06/12/03/18, fn%104=90, rc=1, delta=104
+Testing: ts[6]->lchan[0], fn=8930=>008930/06/12/05/18, fn%104=90, rc=1, delta=104
+Testing: ts[7]->lchan[0], fn=8943=>008943/06/25/18/31, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9034=>009034/06/12/07/22, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9047=>009047/06/25/20/35, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9138=>009138/06/12/09/22, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9151=>009151/06/25/22/35, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9242=>009242/06/12/11/26, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9255=>009255/06/25/24/39, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9346=>009346/07/12/13/26, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9359=>009359/07/25/26/39, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9450=>009450/07/12/15/26, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9463=>009463/07/25/28/39, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9554=>009554/07/12/17/30, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9567=>009567/07/25/30/43, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9658=>009658/07/12/19/30, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9671=>009671/07/25/32/43, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9762=>009762/07/12/21/34, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9775=>009775/07/25/34/47, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9866=>009866/07/12/23/34, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9879=>009879/07/25/36/47, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9970=>009970/07/12/25/34, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=9983=>009983/07/25/38/47, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10074=>010074/07/12/27/38, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10087=>010087/07/25/40/51, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10178=>010178/07/12/29/38, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10191=>010191/07/25/42/51, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10282=>010282/07/12/31/42, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10295=>010295/07/25/44/03, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10386=>010386/07/12/33/42, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10399=>010399/07/25/46/03, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10490=>010490/07/12/35/42, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10503=>010503/07/25/48/07, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10594=>010594/07/12/37/46, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10607=>010607/07/25/50/07, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10698=>010698/08/12/39/46, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10711=>010711/08/25/01/07, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10802=>010802/08/12/41/50, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10815=>010815/08/25/03/11, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10906=>010906/08/12/43/50, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=10919=>010919/08/25/05/11, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11010=>011010/08/12/45/02, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11023=>011023/08/25/07/15, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11114=>011114/08/12/47/02, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11127=>011127/08/25/09/15, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11218=>011218/08/12/49/02, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11231=>011231/08/25/11/15, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11322=>011322/08/12/00/06, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11335=>011335/08/25/13/19, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11426=>011426/08/12/02/06, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11439=>011439/08/25/15/19, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11530=>011530/08/12/04/10, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11543=>011543/08/25/17/23, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11634=>011634/08/12/06/10, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11647=>011647/08/25/19/23, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11738=>011738/08/12/08/10, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11751=>011751/08/25/21/23, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11842=>011842/08/12/10/14, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11855=>011855/08/25/23/27, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11946=>011946/09/12/12/14, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=11959=>011959/09/25/25/27, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=12050=>012050/09/12/14/18, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=12063=>012063/09/25/27/31, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=12154=>012154/09/12/16/18, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[0], fn=12167=>012167/09/25/29/31, fn%104=103, rc=1, delta=13
+
+***********************
+*** HALF RATE TESTS ***
+***********************
+
+
+===========================================================
+Testing: ts[2]->lchan[0], fn=8982=>008982/06/12/06/22, fn%104=38, rc=1, delta=8982
+Testing: ts[2]->lchan[0], fn=9086=>009086/06/12/08/22, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=9190=>009190/06/12/10/22, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=9294=>009294/07/12/12/26, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[0], fn=9398=>009398/07/12/14/26, fn%104=38, rc=1, delta=104
+Testing: ts[2]->lchan[1], fn=9411=>009411/07/25/27/39, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=9502=>009502/07/12/16/30, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=9515=>009515/07/25/29/43, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=9606=>009606/07/12/18/30, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=9619=>009619/07/25/31/43, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=9710=>009710/07/12/20/30, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=9723=>009723/07/25/33/43, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=9814=>009814/07/12/22/34, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=9827=>009827/07/25/35/47, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=9918=>009918/07/12/24/34, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=9931=>009931/07/25/37/47, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10022=>010022/07/12/26/38, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10035=>010035/07/25/39/51, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10126=>010126/07/12/28/38, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10139=>010139/07/25/41/51, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10230=>010230/07/12/30/38, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10243=>010243/07/25/43/03, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10334=>010334/07/12/32/42, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10347=>010347/07/25/45/03, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10438=>010438/07/12/34/42, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10451=>010451/07/25/47/03, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10542=>010542/07/12/36/46, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10555=>010555/07/25/49/07, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10646=>010646/08/12/38/46, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10659=>010659/08/25/00/07, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10750=>010750/08/12/40/46, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10763=>010763/08/25/02/11, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10854=>010854/08/12/42/50, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10867=>010867/08/25/04/11, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=10971=>010971/08/25/06/11, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=11075=>011075/08/25/08/15, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=11179=>011179/08/25/10/15, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=11283=>011283/08/25/12/19, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=11387=>011387/08/25/14/19, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=91
+Testing: ts[2]->lchan[1], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13
+Testing: ts[2]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91
+
+
+===========================================================
+Testing: ts[3]->lchan[0], fn=10022=>010022/07/12/26/38, fn%104=38, rc=1, delta=10022
+Testing: ts[3]->lchan[1], fn=10035=>010035/07/25/39/51, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10126=>010126/07/12/28/38, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10139=>010139/07/25/41/51, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10230=>010230/07/12/30/38, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10243=>010243/07/25/43/03, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10334=>010334/07/12/32/42, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10347=>010347/07/25/45/03, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10438=>010438/07/12/34/42, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10451=>010451/07/25/47/03, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10542=>010542/07/12/36/46, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10555=>010555/07/25/49/07, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10646=>010646/08/12/38/46, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10659=>010659/08/25/00/07, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10750=>010750/08/12/40/46, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10763=>010763/08/25/02/11, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10854=>010854/08/12/42/50, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10867=>010867/08/25/04/11, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=10971=>010971/08/25/06/11, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11075=>011075/08/25/08/15, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11179=>011179/08/25/10/15, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11283=>011283/08/25/12/19, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11387=>011387/08/25/14/19, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11595=>011595/08/25/18/23, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11686=>011686/08/12/07/10, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11699=>011699/08/25/20/23, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11790=>011790/08/12/09/14, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11803=>011803/08/25/22/27, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11894=>011894/08/12/11/14, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=11907=>011907/08/25/24/27, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=11998=>011998/09/12/13/14, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=12011=>012011/09/25/26/27, fn%104=51, rc=1, delta=13
+Testing: ts[3]->lchan[0], fn=12102=>012102/09/12/15/18, fn%104=38, rc=1, delta=91
+Testing: ts[3]->lchan[1], fn=12115=>012115/09/25/28/31, fn%104=51, rc=1, delta=13
+
+
+===========================================================
+Testing: ts[4]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=7760
+Testing: ts[4]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=104
+Testing: ts[4]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=104
+Testing: ts[4]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=104
+Testing: ts[4]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=104
+Testing: ts[4]->lchan[1], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8592=>008592/06/12/24/40, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8605=>008605/06/25/37/01, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8696=>008696/06/12/26/40, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8709=>008709/06/25/39/05, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8800=>008800/06/12/28/44, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8813=>008813/06/25/41/05, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=8904=>008904/06/12/30/44, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=8917=>008917/06/25/43/05, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9008=>009008/06/12/32/48, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9021=>009021/06/25/45/09, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9112=>009112/06/12/34/48, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9125=>009125/06/25/47/09, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9216=>009216/06/12/36/00, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9229=>009229/06/25/49/13, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9320=>009320/07/12/38/00, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9333=>009333/07/25/00/13, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9424=>009424/07/12/40/00, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9437=>009437/07/25/02/13, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9528=>009528/07/12/42/04, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9541=>009541/07/25/04/17, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9632=>009632/07/12/44/04, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9645=>009645/07/25/06/17, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9736=>009736/07/12/46/08, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9749=>009749/07/25/08/21, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9840=>009840/07/12/48/08, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9853=>009853/07/25/10/21, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=9944=>009944/07/12/50/08, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=9957=>009957/07/25/12/21, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10048=>010048/07/12/01/12, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10061=>010061/07/25/14/25, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10152=>010152/07/12/03/12, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10165=>010165/07/25/16/25, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10256=>010256/07/12/05/16, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10269=>010269/07/25/18/29, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10360=>010360/07/12/07/16, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10373=>010373/07/25/20/29, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10464=>010464/07/12/09/16, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10477=>010477/07/25/22/29, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10568=>010568/07/12/11/20, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10581=>010581/07/25/24/33, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10672=>010672/08/12/13/20, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10685=>010685/08/25/26/33, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10776=>010776/08/12/15/24, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10789=>010789/08/25/28/37, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10880=>010880/08/12/17/24, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10893=>010893/08/25/30/37, fn%104=77, rc=1, delta=13
+Testing: ts[4]->lchan[0], fn=10984=>010984/08/12/19/24, fn%104=64, rc=1, delta=91
+Testing: ts[4]->lchan[1], fn=10997=>010997/08/25/32/37, fn%104=77, rc=1, delta=13
+
+
+===========================================================
+Testing: ts[5]->lchan[0], fn=5264=>005264/03/12/11/40, fn%104=64, rc=1, delta=5264
+Testing: ts[5]->lchan[0], fn=5368=>005368/04/12/13/40, fn%104=64, rc=1, delta=104
+Testing: ts[5]->lchan[0], fn=5472=>005472/04/12/15/44, fn%104=64, rc=1, delta=104
+Testing: ts[5]->lchan[0], fn=5576=>005576/04/12/17/44, fn%104=64, rc=1, delta=104
+Testing: ts[5]->lchan[0], fn=5680=>005680/04/12/19/48, fn%104=64, rc=1, delta=104
+Testing: ts[5]->lchan[1], fn=5693=>005693/04/25/32/09, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=5784=>005784/04/12/21/48, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=5797=>005797/04/25/34/09, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=5888=>005888/04/12/23/00, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=5901=>005901/04/25/36/13, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=5992=>005992/04/12/25/00, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6005=>006005/04/25/38/13, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6096=>006096/04/12/27/00, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6109=>006109/04/25/40/13, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6200=>006200/04/12/29/04, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6213=>006213/04/25/42/17, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6304=>006304/04/12/31/04, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6317=>006317/04/25/44/17, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6408=>006408/04/12/33/08, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6421=>006421/04/25/46/21, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6512=>006512/04/12/35/08, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6525=>006525/04/25/48/21, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6616=>006616/04/12/37/08, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6629=>006629/04/25/50/21, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6720=>006720/05/12/39/12, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6733=>006733/05/25/01/25, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6824=>006824/05/12/41/12, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6837=>006837/05/25/03/25, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=6928=>006928/05/12/43/16, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=6941=>006941/05/25/05/29, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7032=>007032/05/12/45/16, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7045=>007045/05/25/07/29, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7136=>007136/05/12/47/16, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7149=>007149/05/25/09/29, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7240=>007240/05/12/49/20, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7253=>007253/05/25/11/33, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7344=>007344/05/12/00/20, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7357=>007357/05/25/13/33, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7448=>007448/05/12/02/24, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7461=>007461/05/25/15/37, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7552=>007552/05/12/04/24, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7565=>007565/05/25/17/37, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7656=>007656/05/12/06/24, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7669=>007669/05/25/19/37, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7773=>007773/05/25/21/41, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7877=>007877/05/25/23/41, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=7981=>007981/06/25/25/45, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=8085=>008085/06/25/27/45, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13
+Testing: ts[5]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91
+Testing: ts[5]->lchan[1], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13
+
+
+===========================================================
+Testing: ts[6]->lchan[0], fn=8098=>008098/06/12/40/06, fn%104=90, rc=1, delta=8098
+Testing: ts[6]->lchan[0], fn=8202=>008202/06/12/42/10, fn%104=90, rc=1, delta=104
+Testing: ts[6]->lchan[0], fn=8306=>008306/06/12/44/10, fn%104=90, rc=1, delta=104
+Testing: ts[6]->lchan[0], fn=8410=>008410/06/12/46/10, fn%104=90, rc=1, delta=104
+Testing: ts[6]->lchan[1], fn=8423=>008423/06/25/08/23, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=8514=>008514/06/12/48/14, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=8527=>008527/06/25/10/27, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=8618=>008618/06/12/50/14, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=8631=>008631/06/25/12/27, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=8722=>008722/06/12/01/18, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=8735=>008735/06/25/14/31, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=8826=>008826/06/12/03/18, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=8839=>008839/06/25/16/31, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=8930=>008930/06/12/05/18, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=8943=>008943/06/25/18/31, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9034=>009034/06/12/07/22, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9047=>009047/06/25/20/35, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9138=>009138/06/12/09/22, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9151=>009151/06/25/22/35, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9242=>009242/06/12/11/26, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9255=>009255/06/25/24/39, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9346=>009346/07/12/13/26, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9359=>009359/07/25/26/39, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9450=>009450/07/12/15/26, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9463=>009463/07/25/28/39, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9554=>009554/07/12/17/30, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9567=>009567/07/25/30/43, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9658=>009658/07/12/19/30, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9671=>009671/07/25/32/43, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9762=>009762/07/12/21/34, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9775=>009775/07/25/34/47, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9866=>009866/07/12/23/34, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9879=>009879/07/25/36/47, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=9970=>009970/07/12/25/34, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=9983=>009983/07/25/38/47, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10074=>010074/07/12/27/38, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10087=>010087/07/25/40/51, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10178=>010178/07/12/29/38, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10191=>010191/07/25/42/51, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10282=>010282/07/12/31/42, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10295=>010295/07/25/44/03, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10386=>010386/07/12/33/42, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10399=>010399/07/25/46/03, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10490=>010490/07/12/35/42, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10503=>010503/07/25/48/07, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10594=>010594/07/12/37/46, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10607=>010607/07/25/50/07, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10698=>010698/08/12/39/46, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10711=>010711/08/25/01/07, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10802=>010802/08/12/41/50, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10815=>010815/08/25/03/11, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=10906=>010906/08/12/43/50, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=10919=>010919/08/25/05/11, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11010=>011010/08/12/45/02, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=11023=>011023/08/25/07/15, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11114=>011114/08/12/47/02, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=11127=>011127/08/25/09/15, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11218=>011218/08/12/49/02, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=11231=>011231/08/25/11/15, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11322=>011322/08/12/00/06, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=11335=>011335/08/25/13/19, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11426=>011426/08/12/02/06, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=11439=>011439/08/25/15/19, fn%104=103, rc=1, delta=13
+Testing: ts[6]->lchan[0], fn=11530=>011530/08/12/04/10, fn%104=90, rc=1, delta=91
+Testing: ts[6]->lchan[1], fn=11543=>011543/08/25/17/23, fn%104=103, rc=1, delta=13
+
+
+===========================================================
+Testing: ts[7]->lchan[0], fn=11738=>011738/08/12/08/10, fn%104=90, rc=1, delta=11738
+Testing: ts[7]->lchan[0], fn=11842=>011842/08/12/10/14, fn%104=90, rc=1, delta=104
+Testing: ts[7]->lchan[0], fn=11946=>011946/09/12/12/14, fn%104=90, rc=1, delta=104
+Testing: ts[7]->lchan[0], fn=12050=>012050/09/12/14/18, fn%104=90, rc=1, delta=104
+Testing: ts[7]->lchan[1], fn=12063=>012063/09/25/27/31, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12154=>012154/09/12/16/18, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12167=>012167/09/25/29/31, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12258=>012258/09/12/18/18, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12271=>012271/09/25/31/31, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12362=>012362/09/12/20/22, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12375=>012375/09/25/33/35, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12466=>012466/09/12/22/22, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12479=>012479/09/25/35/35, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12570=>012570/09/12/24/26, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12583=>012583/09/25/37/39, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12674=>012674/09/12/26/26, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12687=>012687/09/25/39/39, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12778=>012778/09/12/28/26, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12791=>012791/09/25/41/39, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12882=>012882/09/12/30/30, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12895=>012895/09/25/43/43, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=12986=>012986/09/12/32/30, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=12999=>012999/09/25/45/43, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13090=>013090/09/12/34/34, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13103=>013103/09/25/47/47, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13194=>013194/09/12/36/34, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13207=>013207/09/25/49/47, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13298=>013298/10/12/38/34, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13311=>013311/10/25/00/47, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13402=>013402/10/12/40/38, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13415=>013415/10/25/02/51, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13506=>013506/10/12/42/38, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13519=>013519/10/25/04/51, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13610=>013610/10/12/44/42, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13623=>013623/10/25/06/03, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13714=>013714/10/12/46/42, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13727=>013727/10/25/08/03, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13818=>013818/10/12/48/42, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13831=>013831/10/25/10/07, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=13922=>013922/10/12/50/46, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=13935=>013935/10/25/12/07, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14026=>014026/10/12/01/46, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14039=>014039/10/25/14/07, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14130=>014130/10/12/03/50, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14143=>014143/10/25/16/11, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14234=>014234/10/12/05/50, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14247=>014247/10/25/18/11, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14338=>014338/10/12/07/02, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14351=>014351/10/25/20/15, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14442=>014442/10/12/09/02, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14455=>014455/10/25/22/15, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14546=>014546/10/12/11/02, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14559=>014559/10/25/24/15, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14650=>014650/11/12/13/06, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14663=>014663/11/25/26/19, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14754=>014754/11/12/15/06, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14767=>014767/11/25/28/19, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14858=>014858/11/12/17/10, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14871=>014871/11/25/30/23, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=14962=>014962/11/12/19/10, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=14975=>014975/11/25/32/23, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=15066=>015066/11/12/21/10, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=15079=>015079/11/25/34/23, fn%104=103, rc=1, delta=13
+Testing: ts[7]->lchan[0], fn=15170=>015170/11/12/23/14, fn%104=90, rc=1, delta=91
+Testing: ts[7]->lchan[1], fn=15183=>015183/11/25/36/27, fn%104=103, rc=1, delta=13
+
+
+===========================================================
+Measurement Compute Test: TOA256 Min-Max negative/positive
+number of measurements: 3
+parameter | actual | expected
+meas.ext.toa256_min | -256 | -256
+meas.ext.toa256_max | 256 | 256
+meas.ms_toa256 | 0 | 0
+meas.ext.toa256_std_dev | 209 | 209
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 7 | 0
+
+
+===========================================================
+Measurement Compute Test: TOA256 small jitter around 256
+number of measurements: 25
+parameter | actual | expected
+meas.ext.toa256_min | 254 | 254
+meas.ext.toa256_max | 258 | 258
+meas.ms_toa256 | 256 | 256
+meas.ext.toa256_std_dev | 1 | 1
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 7
+
+
+===========================================================
+Measurement Compute Test: RxLEv averaging
+number of measurements: 5
+parameter | actual | expected
+meas.ext.toa256_min | 0 | 0
+meas.ext.toa256_max | 0 | 0
+meas.ms_toa256 | 0 | 0
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 7 | 0
+
+
+===========================================================
+Measurement Compute Test: Empty measurements
+number of measurements: 0
+parameter | actual | expected
+meas.ext.toa256_min | 0 | 0
+meas.ext.toa256_max | 0 | 0
+meas.ms_toa256 | 0 | 0
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 63 | 63
+meas.ul_res.full.rx_qual | 3 | 3
+
+
+===========================================================
+Measurement Compute Test: TOA256 26 blocks with max TOA256
+number of measurements: 26
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 0
+
+
+===========================================================
+Measurement Compute Test: Complete TCH/F measurement period (26 measurements, 3 sub-frames)
+number of measurements: 25
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 0
+
+
+===========================================================
+Measurement Compute Test: Incomplete TCH/F measurement period (16 measurements, 1 sub-frame)
+number of measurements: 16
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 7 | 7
+
+
+===========================================================
+Measurement Compute Test: Incomplete but normal TCH/F measurement period (16 measurements, 3 sub-frames)
+number of measurements: 16
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 7 | 7
+
+
+===========================================================
+Measurement Compute Test: Complete TCH/H measurement period (26 measurements, 5 sub-frames)
+number of measurements: 25
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 0
+
+
+===========================================================
+Measurement Compute Test: Incomplete TCH/H measurement period (14 measurements, 3 sub-frames)
+number of measurements: 14
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 7 | 7
+
+
+===========================================================
+Measurement Compute Test: Incomplete but normal TCH/F measurement period (16 measurements, 5 sub-frames)
+number of measurements: 16
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 7 | 7
+
+
+===========================================================
+Measurement Compute Test: TCH/F measurement period with too much measurement values (overrun)
+number of measurements: 59
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 0
+
+
+===========================================================
+Measurement Compute Test: Complete SDCCH4 measurement period (3 measurements)
+number of measurements: 3
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 0
+
+
+===========================================================
+Measurement Compute Test: Complete SDCCH8 measurement period (3 measurements)
+number of measurements: 3
+parameter | actual | expected
+meas.ext.toa256_min | 16384 | 16384
+meas.ext.toa256_max | 16384 | 16384
+meas.ms_toa256 | 16384 | 16384
+meas.ext.toa256_std_dev | 0 | 0
+meas.ul_res.full.rx_lev | 20 | 20
+meas.ul_res.full.rx_qual | 0 | 0
+
+***************************************************
+*** MEASUREMENT INTERVAL ENDING DETECTION TESTS ***
+***************************************************
+
+
+===========================================================
+Testing is_meas_complete()
+
+
+===========================================================
+Testing lchan_meas_process_measurement()
+(now adding SUB measurement sample for SACCH block at frame number 39)
+(now adding SUB measurement sample 52)
+(now adding SUB measurement sample 56)
+(now adding SUB measurement sample for SACCH block at frame number 143)
+(now adding SUB measurement sample 156)
+(now adding SUB measurement sample 160)
+(now adding SUB measurement sample for SACCH block at frame number 247)
+(now adding SUB measurement sample 260)
+(now adding SUB measurement sample 264)
+(now adding SUB measurement sample for SACCH block at frame number 351)
+(now adding SUB measurement sample 364)
+(now adding SUB measurement sample 368)
+
+
+===========================================================
+Testing lchan_meas_process_measurement()
+ * SACCH blocks not generated.
+(leaving out SUB measurement sample for SACCH block at frame number 39)
+(now adding SUB measurement sample 52)
+(now adding SUB measurement sample 56)
+(leaving out SUB measurement sample for SACCH block at frame number 143)
+(now adding SUB measurement sample 156)
+(now adding SUB measurement sample 160)
+(leaving out SUB measurement sample for SACCH block at frame number 247)
+(now adding SUB measurement sample 260)
+(now adding SUB measurement sample 264)
+(leaving out SUB measurement sample for SACCH block at frame number 351)
+(now adding SUB measurement sample 364)
+(now adding SUB measurement sample 368)
+
+
+===========================================================
+Testing lchan_meas_process_measurement()
+ * Simulate dropouts by leaving out every 4th measurement
+(leaving out measurement sample for frame number 0)
+(leaving out measurement sample for frame number 17)
+(leaving out measurement sample for frame number 34)
+(now adding SUB measurement sample for SACCH block at frame number 39)
+(leaving out SUB measurement sample for frame number 52)
+(now adding SUB measurement sample 56)
+(leaving out measurement sample for frame number 69)
+(leaving out measurement sample for frame number 86)
+(leaving out measurement sample for frame number 104)
+(leaving out measurement sample for frame number 121)
+(leaving out measurement sample for frame number 138)
+(now adding SUB measurement sample for SACCH block at frame number 143)
+(leaving out SUB measurement sample for frame number 156)
+(now adding SUB measurement sample 160)
+(leaving out measurement sample for frame number 173)
+(leaving out measurement sample for frame number 190)
+(leaving out measurement sample for frame number 208)
+(leaving out measurement sample for frame number 225)
+(leaving out measurement sample for frame number 242)
+(now adding SUB measurement sample for SACCH block at frame number 247)
+(leaving out SUB measurement sample for frame number 260)
+(now adding SUB measurement sample 264)
+(leaving out measurement sample for frame number 277)
+(leaving out measurement sample for frame number 294)
+(leaving out measurement sample for frame number 312)
+(leaving out measurement sample for frame number 329)
+(leaving out measurement sample for frame number 346)
+(now adding SUB measurement sample for SACCH block at frame number 351)
+(leaving out SUB measurement sample for frame number 364)
+(now adding SUB measurement sample 368)
+(leaving out measurement sample for frame number 381)
+(leaving out measurement sample for frame number 398)
+(leaving out measurement sample for frame number 416)
+
+
+===========================================================
+Testing lchan_meas_process_measurement()
+ * SACCH blocks not generated.
+ * Simulate dropouts by leaving out every 4th measurement
+(leaving out measurement sample for frame number 0)
+(leaving out measurement sample for frame number 17)
+(leaving out measurement sample for frame number 34)
+(leaving out SUB measurement sample for SACCH block at frame number 39)
+(leaving out SUB measurement sample for frame number 52)
+(now adding SUB measurement sample 56)
+(leaving out measurement sample for frame number 69)
+(leaving out measurement sample for frame number 86)
+(leaving out measurement sample for frame number 104)
+(leaving out measurement sample for frame number 121)
+(leaving out measurement sample for frame number 138)
+(leaving out SUB measurement sample for SACCH block at frame number 143)
+(leaving out SUB measurement sample for frame number 156)
+(now adding SUB measurement sample 160)
+(leaving out measurement sample for frame number 173)
+(leaving out measurement sample for frame number 190)
+(leaving out measurement sample for frame number 208)
+(leaving out measurement sample for frame number 225)
+(leaving out measurement sample for frame number 242)
+(leaving out SUB measurement sample for SACCH block at frame number 247)
+(leaving out SUB measurement sample for frame number 260)
+(now adding SUB measurement sample 264)
+(leaving out measurement sample for frame number 277)
+(leaving out measurement sample for frame number 294)
+(leaving out measurement sample for frame number 312)
+(leaving out measurement sample for frame number 329)
+(leaving out measurement sample for frame number 346)
+(leaving out SUB measurement sample for SACCH block at frame number 351)
+(leaving out SUB measurement sample for frame number 364)
+(now adding SUB measurement sample 368)
+(leaving out measurement sample for frame number 381)
+(leaving out measurement sample for frame number 398)
+(leaving out measurement sample for frame number 416)
+
+
+===========================================================
+Testing ts45008_83_is_sub()
+Checking: TCH/F TS=0 SS=0
+Checking: TCH/F TS=1 SS=0
+Checking: TCH/F TS=2 SS=0
+Checking: TCH/F TS=3 SS=0
+Checking: TCH/F TS=4 SS=0
+Checking: TCH/F TS=5 SS=0
+Checking: TCH/F TS=6 SS=0
+Checking: TCH/H TS=0 SS=0
+Checking: TCH/H TS=1 SS=0
+Checking: TCH/H TS=2 SS=0
+Checking: TCH/H TS=3 SS=0
+Checking: TCH/H TS=4 SS=0
+Checking: TCH/H TS=5 SS=0
+Checking: TCH/H TS=6 SS=0
+Checking: TCH/H TS=0 SS=1
+Checking: TCH/H TS=1 SS=1
+Checking: TCH/H TS=2 SS=1
+Checking: TCH/H TS=3 SS=1
+Checking: TCH/H TS=4 SS=1
+Checking: TCH/H TS=5 SS=1
+Checking: TCH/H TS=6 SS=1
+Success
diff --git a/tests/meas/meas_testcases.h b/tests/meas/meas_testcases.h
new file mode 100644
index 00000000..fefa34f7
--- /dev/null
+++ b/tests/meas/meas_testcases.h
@@ -0,0 +1,578 @@
+#define ULM(ber, ta, sub, neg_rssi) \
+ { .ber10k = (ber), .ta_offs_256bits = (ta), .c_i = 1.0, .is_sub = sub, .inv_rssi = (neg_rssi) }
+
+struct meas_testcase {
+ const char *name;
+ /* input data */
+ const struct bts_ul_meas *ulm;
+ unsigned int ulm_count;
+ uint32_t final_fn;
+ uint8_t ts;
+ enum gsm_phys_chan_config pchan;
+ /* results */
+ struct {
+ int success;
+ uint8_t rx_lev_full;
+ uint8_t rx_qual_full;
+ int16_t toa256_mean;
+ int16_t toa256_min;
+ int16_t toa256_max;
+ uint16_t toa256_std_dev;
+ } res;
+};
+
+static struct bts_ul_meas ulm1[] = {
+ /* Note: The assumptions about the frame number and the subset
+ * allegiance is random since for the calculation only the amount
+ * is of relevance. This is true for all following testcases */
+ ULM(0, 0, 0, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, -256, 0, 90),
+};
+
+static const struct meas_testcase mtc1 = {
+ .name = "TOA256 Min-Max negative/positive",
+ .ulm = ulm1,
+ .ulm_count = ARRAY_SIZE(ulm1),
+ .final_fn = 25,
+ .ts = 1,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 110-90,
+ .rx_qual_full = 0,
+ .toa256_mean = 0,
+ .toa256_max = 256,
+ .toa256_min = -256,
+ .toa256_std_dev = 209,
+ },
+};
+
+static struct bts_ul_meas ulm2[] = {
+ ULM(0, 256, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 1, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 1, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 0, 90),
+ ULM(0, 256, 1, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 0, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 0, 90),
+ ULM(0, 258, 0, 90),
+ ULM(0, 254, 0, 90),
+ ULM(0, 256, 0, 90),
+ ULM(0, 256, 0, 90),
+};
+
+static const struct meas_testcase mtc2 = {
+ .name = "TOA256 small jitter around 256",
+ .ulm = ulm2,
+ .ulm_count = ARRAY_SIZE(ulm2),
+ .final_fn = 25,
+ .ts = 1,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 110-90,
+ .rx_qual_full = 7,
+ .toa256_mean = 256,
+ .toa256_max = 258,
+ .toa256_min = 254,
+ .toa256_std_dev = 1,
+ },
+};
+
+static struct bts_ul_meas ulm3[] = {
+ ULM(0, 0, 0, 90),
+ ULM(0, 0, 0, 80),
+ ULM(0, 0, 0, 80),
+ ULM(0, 0, 0, 100),
+ ULM(0, 0, 0, 100),
+};
+
+static const struct meas_testcase mtc3 = {
+ .name = "RxLEv averaging",
+ .ulm = ulm3,
+ .ulm_count = ARRAY_SIZE(ulm3),
+ .final_fn = 25,
+ .ts = 1,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 110-90,
+ .rx_qual_full = 0,
+ .toa256_mean = 0,
+ .toa256_max = 0,
+ .toa256_min = 0,
+ .toa256_std_dev = 0,
+ },
+};
+
+static struct bts_ul_meas ulm4[] = {};
+
+static const struct meas_testcase mtc4 = {
+ .name = "Empty measurements",
+ .ulm = ulm4,
+ .ulm_count = ARRAY_SIZE(ulm4),
+ .final_fn = 25,
+ .ts = 1,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 63,
+ .rx_qual_full = 3,
+ .toa256_mean = 0,
+ .toa256_max = 0,
+ .toa256_min = 0,
+ .toa256_std_dev = 0,
+ },
+};
+
+static struct bts_ul_meas ulm5[] = {
+ /* one 104 multiframe can at max contain 26 blocks (TCH/F),
+ * each of which can at maximum be 64 bits in advance (TA range) */
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+};
+
+static const struct meas_testcase mtc5 = {
+ .name = "TOA256 26 blocks with max TOA256",
+ .ulm = ulm5,
+ .ulm_count = ARRAY_SIZE(ulm5),
+ .final_fn = 25,
+ .ts = 1,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 110-90,
+ .rx_qual_full = 0,
+ .toa256_mean = 64*256,
+ .toa256_max = 64*256,
+ .toa256_min = 64*256,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* This testcase models a good case as we can see it when all TCH
+ * and SACCH blocks are received */
+static struct bts_ul_meas ulm_tch_f_complete[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+};
+
+static const struct meas_testcase mtc_tch_f_complete = {
+ .name = "Complete TCH/F measurement period (26 measurements, 3 sub-frames)",
+ .ulm = ulm_tch_f_complete,
+ .ulm_count = ARRAY_SIZE(ulm_tch_f_complete),
+ .final_fn = 38,
+ .ts = 2,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 0,
+ .toa256_mean = 64*256,
+ .toa256_max = 64*256,
+ .toa256_min = 64*256,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* This testcase models an error case where two of 3 expected sub measurements
+ * are lost. The calculation logic must detect this and replace those
+ * measurements. Note that this example also lacks some blocks due to DTX,
+ * which is normal. */
+static struct bts_ul_meas ulm_tch_f_dtx_with_lost_subs[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+};
+
+static const struct meas_testcase mtc_tch_f_dtx_with_lost_subs = {
+ /* This testcase models a good case as we can see it when all TCH
+ * and SACCH blocks are received */
+ .name = "Incomplete TCH/F measurement period (16 measurements, 1 sub-frame)",
+ .ulm = ulm_tch_f_dtx_with_lost_subs,
+ .ulm_count = ARRAY_SIZE(ulm_tch_f_dtx_with_lost_subs),
+ .final_fn = 38,
+ .ts = 2,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 7,
+ .toa256_mean = 16384,
+ .toa256_max = 16384,
+ .toa256_min = 16384,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* This testcase models a good-case with DTX. Some measurements are missing
+ * because no block was transmitted, all sub measurements are there. */
+static struct bts_ul_meas ulm_tch_f_dtx[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+};
+
+static const struct meas_testcase mtc_tch_f_dtx = {
+ .name = "Incomplete but normal TCH/F measurement period (16 measurements, 3 sub-frames)",
+ .ulm = ulm_tch_f_dtx,
+ .ulm_count = ARRAY_SIZE(ulm_tch_f_dtx),
+ .final_fn = 38,
+ .ts = 2,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 7,
+ .toa256_mean = 16384,
+ .toa256_max = 16384,
+ .toa256_min = 16384,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* This testcase models a good case as we can see it when all TCH
+ * and SACCH blocks are received */
+static struct bts_ul_meas ulm_tch_h_complete[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+};
+
+static const struct meas_testcase mtc_tch_h_complete = {
+ .name = "Complete TCH/H measurement period (26 measurements, 5 sub-frames)",
+ .ulm = ulm_tch_h_complete,
+ .ulm_count = ARRAY_SIZE(ulm_tch_h_complete),
+ .final_fn = 38,
+ .ts = 2,
+ .pchan = GSM_PCHAN_TCH_H,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 110 - 90,
+ .rx_qual_full = 0,
+ .toa256_mean = 64*256,
+ .toa256_max = 64*256,
+ .toa256_min = 64*256,
+ .toa256_std_dev = 0,
+ },
+};
+
+static struct bts_ul_meas ulm_tch_h_dtx_with_lost_subs[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+};
+
+static const struct meas_testcase mtc_tch_h_dtx_with_lost_subs = {
+ .name = "Incomplete TCH/H measurement period (14 measurements, 3 sub-frames)",
+ .ulm = ulm_tch_h_dtx_with_lost_subs,
+ .ulm_count = ARRAY_SIZE(ulm_tch_h_dtx_with_lost_subs),
+ .final_fn = 38,
+ .ts = 2,
+ .pchan = GSM_PCHAN_TCH_H,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 7,
+ .toa256_mean = 16384,
+ .toa256_max = 16384,
+ .toa256_min = 16384,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* This testcase models a good-case with DTX. Some measurements are missing
+ * because no block was transmitted, all sub measurements are there. */
+static struct bts_ul_meas ulm_tch_h_dtx[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+};
+
+static const struct meas_testcase mtc_tch_h_dtx = {
+ .name = "Incomplete but normal TCH/F measurement period (16 measurements, 5 sub-frames)",
+ .ulm = ulm_tch_h_dtx,
+ .ulm_count = ARRAY_SIZE(ulm_tch_h_dtx),
+ .final_fn = 38,
+ .ts = 2,
+ .pchan = GSM_PCHAN_TCH_H,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 7,
+ .toa256_mean = 16384,
+ .toa256_max = 16384,
+ .toa256_min = 16384,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* This testcase assumes that too many measurements were collected. This can
+ * happen when the measurement calculation for a previous cycle were not
+ * executed. In this case the older part of the excess data must be discarded.
+ * the calculation algorithm must make sure that the calculation only takes
+ * place on the last measurement interval */
+static struct bts_ul_meas ulm_overrun[] = {
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ /* All measurements above must be discarded */
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+ ULM(0, 64*256, 0, 90),
+};
+
+static const struct meas_testcase mtc_overrun = {
+ .name = "TCH/F measurement period with too much measurement values (overrun)",
+ .ulm = ulm_overrun,
+ .ulm_count = ARRAY_SIZE(ulm_overrun),
+ .final_fn = 25,
+ .ts = 1,
+ .pchan = GSM_PCHAN_TCH_F,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 110 - 90,
+ .rx_qual_full = 0,
+ .toa256_mean = 64*256,
+ .toa256_max = 64*256,
+ .toa256_min = 64*256,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* Test SDCCH4 with all frames received */
+static struct bts_ul_meas ulm_sdcch4_complete[] = {
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 1, 90),
+};
+
+static const struct meas_testcase mtc_sdcch4_complete = {
+ .name = "Complete SDCCH4 measurement period (3 measurements)",
+ .ulm = ulm_sdcch4_complete,
+ .ulm_count = ARRAY_SIZE(ulm_sdcch4_complete),
+ .final_fn = 88,
+ .ts = 0,
+ .pchan = GSM_PCHAN_CCCH_SDCCH4,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 0,
+ .toa256_mean = 16384,
+ .toa256_max = 16384,
+ .toa256_min = 16384,
+ .toa256_std_dev = 0,
+ },
+};
+
+/* Test SDCCH8 with all frames received */
+static struct bts_ul_meas ulm_sdcch8_complete[] = {
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 1, 90),
+ ULM(0, 64*256, 1, 90),
+};
+
+static const struct meas_testcase mtc_sdcch8_complete = {
+ .name = "Complete SDCCH8 measurement period (3 measurements)",
+ .ulm = ulm_sdcch8_complete,
+ .ulm_count = ARRAY_SIZE(ulm_sdcch8_complete),
+ .final_fn = 66,
+ .ts = 0,
+ .pchan = GSM_PCHAN_SDCCH8_SACCH8C,
+ .res = {
+ .success = 1,
+ .rx_lev_full = 20,
+ .rx_qual_full = 0,
+ .toa256_mean = 16384,
+ .toa256_max = 16384,
+ .toa256_min = 16384,
+ .toa256_std_dev = 0,
+ },
+};
diff --git a/tests/meas/sysmobts_fr_samples.h b/tests/meas/sysmobts_fr_samples.h
new file mode 100644
index 00000000..ee70bd7c
--- /dev/null
+++ b/tests/meas/sysmobts_fr_samples.h
@@ -0,0 +1,2601 @@
+/* The following dataset was generated using a sysmobts in order to have
+ * some real data from a real phy to test against. */
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on full rate channels TS2 and TS3 */
+struct fn_sample test_fn_tch_f_ts_2_3[] = {
+{10954,2,0,-1},{10959,2,0,-1},{10972,2,0,-1},{10976,2,0,-1},{10980,2,0,-1},
+{10985,2,0,-1},{10989,2,0,-1},{10993,2,0,-1},{10998,2,0,-1},{11002,2,0,-1},
+{11006,2,0,-1},{11011,2,0,-1},{11015,2,0,-1},{11019,2,0,-1},{11024,2,0,-1},
+{11028,2,0,-1},{10958,2,0,-1},{11032,2,0,-1},{11037,2,0,-1},{11041,2,0,-1},
+{11045,2,0,-1},{11050,2,0,-1},{11054,2,0,-1},{11058,2,0,-1},{11063,2,0,-1},
+{11067,2,0,-1},{11071,2,0,-1},{11076,2,0,-1},{11080,2,0,-1},{11084,2,0,-1},
+{11089,2,0,-1},{11093,2,0,-1},{11097,2,0,-1},{11102,2,0,-1},{11106,2,0,-1},
+{11110,2,0,-1},{11115,2,0,-1},{11119,2,0,-1},{11123,2,0,-1},{11128,2,0,-1},
+{11132,2,0,-1},{11062,2,0,-1},{11136,2,0,-1},{11141,2,0,-1},{11145,2,0,-1},
+{11149,2,0,-1},{11154,2,0,-1},{11158,2,0,-1},{11162,2,0,-1},{11167,2,0,-1},
+{11171,2,0,-1},{11175,2,0,-1},{11180,2,0,-1},{11184,2,0,-1},{11188,2,0,-1},
+{11193,2,0,-1},{11197,2,0,-1},{11201,2,0,-1},{11206,2,0,-1},{11210,2,0,-1},
+{11214,2,0,-1},{11219,2,0,-1},{11223,2,0,-1},{11227,2,0,-1},{11232,2,0,-1},
+{11236,2,0,-1},{11166,2,0,-1},{11240,2,0,-1},{11245,2,0,-1},{11249,2,0,-1},
+{11253,2,0,-1},{11258,2,0,-1},{11262,2,0,-1},{11266,2,0,-1},{11271,2,0,-1},
+{11275,2,0,-1},{11279,2,0,-1},{11284,2,0,-1},{11288,2,0,-1},{11292,2,0,-1},
+{11297,2,0,-1},{11301,2,0,-1},{11305,2,0,-1},{11310,2,0,-1},{11314,2,0,-1},
+{11318,2,0,-1},{11323,2,0,-1},{11327,2,0,-1},{11331,2,0,-1},{11336,2,0,-1},
+{11340,2,0,-1},{11270,2,0,-1},{11344,2,0,-1},{11349,2,0,-1},{11353,2,0,-1},
+{11357,2,0,-1},{11362,2,0,-1},{11366,2,0,-1},{11370,2,0,-1},{11375,2,0,-1},
+{11379,2,0,-1},{11383,2,0,-1},{11388,2,0,-1},{11392,2,0,-1},{11396,2,0,-1},
+{11401,2,0,-1},{11405,2,0,-1},{11409,2,0,-1},{11414,2,0,-1},{11418,2,0,-1},
+{11422,2,0,-1},{11427,2,0,-1},{11431,2,0,-1},{11431,3,0,-1},{11435,2,0,-1},
+{11435,3,0,-1},{11440,2,0,-1},{11440,3,0,-1},{11444,2,0,-1},{11444,3,0,-1},
+{11374,2,0,-1},{11448,2,0,-1},{11448,3,0,-1},{11453,2,0,-1},{11453,3,0,-1},
+{11457,2,0,-1},{11457,3,0,-1},{11461,2,0,-1},{11461,3,0,-1},{11466,2,0,-1},
+{11466,3,0,-1},{11470,2,0,-1},{11470,3,0,-1},{11474,2,0,-1},{11474,3,0,-1},
+{11479,2,0,-1},{11479,3,0,-1},{11483,2,0,-1},{11483,3,0,-1},{11487,2,0,-1},
+{11487,3,0,-1},{11492,2,0,-1},{11492,3,0,-1},{11496,2,0,-1},{11496,3,0,-1},
+{11500,2,0,-1},{11500,3,0,-1},{11505,2,0,-1},{11505,3,0,-1},{11509,2,0,-1},
+{11509,3,0,-1},{11513,2,0,-1},{11513,3,0,-1},{11518,2,0,-1},{11518,3,0,-1},
+{11522,2,0,-1},{11522,3,0,-1},{11526,2,0,-1},{11526,3,0,-1},{11531,2,0,-1},
+{11531,3,0,-1},{11535,2,0,-1},{11535,3,0,-1},{11539,2,0,-1},{11539,3,0,-1},
+{11544,2,0,-1},{11544,3,0,-1},{11548,2,0,-1},{11548,3,0,-1},{11478,2,0,-1},
+{11552,2,0,-1},{11552,3,0,-1},{11557,2,0,-1},{11557,3,0,-1},{11561,2,0,-1},
+{11561,3,0,-1},{11491,3,0,-1},{11565,2,0,-1},{11565,3,0,-1},{11570,2,0,-1},
+{11570,3,0,-1},{11574,2,0,-1},{11574,3,0,-1},{11578,2,0,-1},{11578,3,0,-1},
+{11583,2,0,-1},{11583,3,0,-1},{11587,2,0,-1},{11587,3,0,-1},{11591,2,0,-1},
+{11591,3,0,-1},{11596,2,0,-1},{11596,3,0,-1},{11600,2,0,-1},{11600,3,0,-1},
+{11604,2,0,-1},{11604,3,0,-1},{11609,2,0,-1},{11609,3,0,-1},{11613,2,0,-1},
+{11613,3,0,-1},{11617,2,0,-1},{11617,3,0,-1},{11622,2,0,-1},{11622,3,0,-1},
+{11626,2,0,-1},{11626,3,0,-1},{11630,2,0,-1},{11630,3,0,-1},{11635,2,0,-1},
+{11635,3,0,-1},{11639,2,0,-1},{11639,3,0,-1},{11643,2,0,-1},{11643,3,0,-1},
+{11648,2,0,-1},{11648,3,0,-1},{11652,2,0,-1},{11652,3,0,-1},{11582,2,0,-1},
+{11656,2,0,-1},{11656,3,0,-1},{11661,2,0,-1},{11661,3,0,-1},{11665,2,0,-1},
+{11665,3,0,-1},{11595,3,0,-1},{11669,2,0,-1},{11669,3,0,-1},{11674,2,0,-1},
+{11674,3,0,-1},{11678,2,0,-1},{11678,3,0,-1},{11682,2,0,-1},{11682,3,0,-1},
+{11687,2,0,-1},{11687,3,0,-1},{11691,2,0,-1},{11691,3,0,-1},{11695,2,0,-1},
+{11695,3,0,-1},{11700,2,0,-1},{11700,3,0,-1},{11704,2,0,-1},{11704,3,0,-1},
+{11708,2,0,-1},{11708,3,0,-1},{11713,2,0,-1},{11713,3,0,-1},{11717,2,0,-1},
+{11717,3,0,-1},{11721,2,0,-1},{11721,3,0,-1},{11726,2,0,-1},{11726,3,0,-1},
+{11730,2,0,-1},{11730,3,0,-1},{11734,2,0,-1},{11734,3,0,-1},{11739,2,0,-1},
+{11739,3,0,-1},{11743,2,0,-1},{11743,3,0,-1},{11747,2,0,-1},{11747,3,0,-1},
+{11752,2,0,-1},{11752,3,0,-1},{11756,2,0,-1},{11756,3,0,-1},{11686,2,0,-1},
+{11760,2,0,-1},{11760,3,0,-1},{11765,2,0,-1},{11765,3,0,-1},{11769,2,0,-1},
+{11769,3,0,-1},{11699,3,0,-1},{11773,2,0,-1},{11773,3,0,-1},{11778,2,0,-1},
+{11778,3,0,-1},{11782,2,0,-1},{11782,3,0,-1},{11786,2,0,-1},{11786,3,0,-1},
+{11791,2,0,-1},{11791,3,0,-1},{11795,2,0,-1},{11795,3,0,-1},{11799,2,0,-1},
+{11799,3,0,-1},{11804,2,0,-1},{11804,3,0,-1},{11808,2,0,-1},{11808,3,0,-1},
+{11812,2,0,-1},{11812,3,0,-1},{11817,2,0,-1},{11817,3,0,-1},{11821,2,0,-1},
+{11821,3,0,-1},{11825,2,0,-1},{11825,3,0,-1},{11830,2,0,-1},{11830,3,0,-1},
+{11834,2,0,-1},{11834,3,0,-1},{11838,2,0,-1},{11838,3,0,-1},{11843,2,0,-1},
+{11843,3,0,-1},{11847,2,0,-1},{11847,3,0,-1},{11851,2,0,-1},{11851,3,0,-1},
+{11856,2,0,-1},{11856,3,0,-1},{11860,2,0,-1},{11860,3,0,-1},{11790,2,0,-1},
+{11864,2,0,-1},{11864,3,0,-1},{11869,2,0,-1},{11869,3,0,-1},{11873,2,0,-1},
+{11873,3,0,-1},{11803,3,0,-1},{11877,2,0,-1},{11877,3,0,-1},{11882,2,0,-1},
+{11882,3,0,-1},{11886,2,0,-1},{11886,3,0,-1},{11890,2,0,-1},{11890,3,0,-1},
+{11895,2,0,-1},{11895,3,0,-1},{11899,2,0,-1},{11899,3,0,-1},{11903,2,0,-1},
+{11903,3,0,-1},{11908,2,0,-1},{11908,3,0,-1},{11912,2,0,-1},{11912,3,0,-1},
+{11916,2,0,-1},{11916,3,0,-1},{11921,2,0,-1},{11921,3,0,-1},{11925,2,0,-1},
+{11925,3,0,-1},{11929,2,0,-1},{11929,3,0,-1},{11934,2,0,-1},{11934,3,0,-1},
+{11938,2,0,-1},{11938,3,0,-1},{11942,2,0,-1},{11942,3,0,-1},{11947,2,0,-1},
+{11947,3,0,-1},{11951,2,0,-1},{11951,3,0,-1},{11955,2,0,-1},{11955,3,0,-1},
+{11960,2,0,-1},{11960,3,0,-1},{11964,2,0,-1},{11964,3,0,-1},{11894,2,0,-1},
+{11968,2,0,-1},{11968,3,0,-1},{11973,2,0,-1},{11973,3,0,-1},{11977,2,0,-1},
+{11977,3,0,-1},{11907,3,0,-1},{11981,2,0,-1},{11981,3,0,-1},{11986,2,0,-1},
+{11986,3,0,-1},{11990,2,0,-1},{11990,3,0,-1},{11994,2,0,-1},{11994,3,0,-1},
+{11999,2,0,-1},{11999,3,0,-1},{12003,2,0,-1},{12003,3,0,-1},{12007,2,0,-1},
+{12007,3,0,-1},{12012,2,0,-1},{12012,3,0,-1},{12016,2,0,-1},{12016,3,0,-1},
+{12020,2,0,-1},{12020,3,0,-1},{12025,2,0,-1},{12025,3,0,-1},{12029,2,0,-1},
+{12029,3,0,-1},{12033,2,0,-1},{12033,3,0,-1},{12038,2,0,-1},{12038,3,0,-1},
+{12042,2,0,-1},{12042,3,0,-1},{12046,2,0,-1},{12046,3,0,-1},{12051,2,0,-1},
+{12051,3,0,-1},{12055,2,0,-1},{12055,3,0,-1},{12059,2,0,-1},{12059,3,0,-1},
+{12064,2,0,-1},{12064,3,0,-1},{12068,2,0,-1},{12068,3,0,-1},{11998,2,0,-1},
+{12072,2,0,-1},{12072,3,0,-1},{12077,2,0,-1},{12077,3,0,-1},{12081,2,0,-1},
+{12081,3,0,-1},{12011,3,0,-1},{12085,2,0,-1},{12085,3,0,-1},{12090,2,0,-1},
+{12090,3,0,-1},{12094,2,0,-1},{12094,3,0,-1},{12098,2,0,-1},{12098,3,0,-1},
+{12103,2,0,-1},{12103,3,0,-1},{12107,2,0,-1},{12107,3,0,-1},{12111,2,0,-1},
+{12111,3,0,-1},{12116,2,0,-1},{12116,3,0,-1},{12120,2,0,-1},{12120,3,0,-1},
+{12124,2,0,-1},{12124,3,0,-1},{12129,2,0,-1},{12129,3,0,-1},{12133,2,0,-1},
+{12133,3,0,-1},{12137,2,0,-1},{12137,3,0,-1},{12142,2,0,-1},{12142,3,0,-1},
+{12146,2,0,-1},{12146,3,0,-1},{12150,2,0,-1},{12150,3,0,-1},{12155,2,0,-1},
+{12155,3,0,-1},{12159,2,0,-1},{12159,3,0,-1},{12163,2,0,-1},{12163,3,0,-1},
+{12168,2,0,-1},{12168,3,0,-1},{12172,2,0,-1},{12172,3,0,-1},{12102,2,0,-1},
+{12176,2,0,-1},{12176,3,0,-1},{12181,2,0,-1},{12181,3,0,-1},{12185,2,0,-1},
+{12185,3,0,-1},{12115,3,0,-1},{12189,2,0,-1},{12189,3,0,-1},{12194,2,0,-1},
+{12194,3,0,-1},{12198,2,0,-1},{12198,3,0,-1},{12202,2,0,-1},{12202,3,0,-1},
+{12207,2,0,-1},{12207,3,0,-1},{12211,2,0,-1},{12211,3,0,-1},{12215,2,0,-1},
+{12215,3,0,-1},{12220,2,0,-1},{12220,3,0,-1},{12224,2,0,-1},{12224,3,0,-1},
+{12228,2,0,-1},{12228,3,0,-1},{12233,2,0,-1},{12233,3,0,-1},{12237,2,0,-1},
+{12237,3,0,-1},{12241,2,0,-1},{12241,3,0,-1},{12246,2,0,-1},{12246,3,0,-1},
+{12250,2,0,-1},{12250,3,0,-1},{12254,2,0,-1},{12254,3,0,-1},{12259,2,0,-1},
+{12259,3,0,-1},{12263,2,0,-1},{12263,3,0,-1},{12267,2,0,-1},{12267,3,0,-1},
+{12272,2,0,-1},{12272,3,0,-1},{12276,2,0,-1},{12276,3,0,-1},{12206,2,0,-1},
+{12280,2,0,-1},{12280,3,0,-1},{12285,2,0,-1},{12285,3,0,-1},{12289,2,0,-1},
+{12289,3,0,-1},{12219,3,0,-1},{12293,2,0,-1},{12293,3,0,-1},{12298,2,0,-1},
+{12298,3,0,-1},{12302,2,0,-1},{12302,3,0,-1},{12306,2,0,-1},{12306,3,0,-1},
+{12311,2,0,-1},{12311,3,0,-1},{12315,2,0,-1},{12315,3,0,-1},{12319,2,0,-1},
+{12319,3,0,-1},{12324,2,0,-1},{12324,3,0,-1},{12328,2,0,-1},{12328,3,0,-1},
+{12332,2,0,-1},{12332,3,0,-1},{12337,2,0,-1},{12337,3,0,-1},{12341,2,0,-1},
+{12341,3,0,-1},{12345,2,0,-1},{12345,3,0,-1},{12350,2,0,-1},{12350,3,0,-1},
+{12354,2,0,-1},{12354,3,0,-1},{12358,2,0,-1},{12358,3,0,-1},{12363,2,0,-1},
+{12363,3,0,-1},{12367,2,0,-1},{12367,3,0,-1},{12371,2,0,-1},{12371,3,0,-1},
+{12376,2,0,-1},{12376,3,0,-1},{12380,2,0,-1},{12380,3,0,-1},{12310,2,0,-1},
+{12384,2,0,-1},{12384,3,0,-1},{12389,2,0,-1},{12389,3,0,-1},{12393,2,0,-1},
+{12393,3,0,-1},{12323,3,0,-1},{12397,2,0,-1},{12397,3,0,-1},{12402,2,0,-1},
+{12402,3,0,-1},{12406,2,0,-1},{12406,3,0,-1},{12410,2,0,-1},{12410,3,0,-1},
+{12415,2,0,-1},{12415,3,0,-1},{12419,2,0,-1},{12419,3,0,-1},{12423,2,0,-1},
+{12423,3,0,-1},{12428,2,0,-1},{12428,3,0,-1},{12432,2,0,-1},{12432,3,0,-1},
+{12436,2,0,-1},{12436,3,0,-1},{12441,2,0,-1},{12441,3,0,-1},{12445,2,0,-1},
+{12445,3,0,-1},{12449,2,0,-1},{12449,3,0,-1},{12454,2,0,-1},{12454,3,0,-1},
+{12458,2,0,-1},{12458,3,0,-1},{12462,2,0,-1},{12462,3,0,-1},{12467,2,0,-1},
+{12467,3,0,-1},{12471,2,0,-1},{12471,3,0,-1},{12475,2,0,-1},{12475,3,0,-1},
+{12480,2,0,-1},{12480,3,0,-1},{12484,2,0,-1},{12484,3,0,-1},{12414,2,0,-1},
+{12488,2,0,-1},{12488,3,0,-1},{12493,2,0,-1},{12493,3,0,-1},{12497,2,0,-1},
+{12497,3,0,-1},{12427,3,0,-1},{12501,2,0,-1},{12501,3,0,-1},{12506,2,0,-1},
+{12506,3,0,-1},{12510,2,0,-1},{12510,3,0,-1},{12514,2,0,-1},{12514,3,0,-1},
+{12519,2,0,-1},{12519,3,0,-1},{12523,2,0,-1},{12523,3,0,-1},{12527,2,0,-1},
+{12527,3,0,-1},{12532,2,0,-1},{12532,3,0,-1},{12536,2,0,-1},{12536,3,0,-1},
+{12540,2,0,-1},{12540,3,0,-1},{12545,2,0,-1},{12545,3,0,-1},{12549,2,0,-1},
+{12549,3,0,-1},{12553,2,0,-1},{12553,3,0,-1},{12558,2,0,-1},{12558,3,0,-1},
+{12562,2,0,-1},{12562,3,0,-1},{12566,2,0,-1},{12566,3,0,-1},{12571,2,0,-1},
+{12571,3,0,-1},{12575,2,0,-1},{12575,3,0,-1},{12579,2,0,-1},{12579,3,0,-1},
+{12584,2,0,-1},{12584,3,0,-1},{12588,2,0,-1},{12588,3,0,-1},{12518,2,0,-1},
+{12592,2,0,-1},{12592,3,0,-1},{12597,2,0,-1},{12597,3,0,-1},{12601,2,0,-1},
+{12601,3,0,-1},{12531,3,0,-1},{12605,2,0,-1},{12605,3,0,-1},{12610,2,0,-1},
+{12610,3,0,-1},{12614,2,0,-1},{12614,3,0,-1},{12618,2,0,-1},{12618,3,0,-1},
+{12623,2,0,-1},{12623,3,0,-1},{12627,2,0,-1},{12627,3,0,-1},{12631,2,0,-1},
+{12631,3,0,-1},{12636,2,0,-1},{12636,3,0,-1},{12640,2,0,-1},{12640,3,0,-1},
+{12644,2,0,-1},{12644,3,0,-1},{12649,2,0,-1},{12649,3,0,-1},{12653,2,0,-1},
+{12653,3,0,-1},{12657,2,0,-1},{12657,3,0,-1},{12662,2,0,-1},{12662,3,0,-1},
+{12666,2,0,-1},{12666,3,0,-1},{12670,2,0,-1},{12670,3,0,-1},{12675,2,0,-1},
+{12675,3,0,-1},{12679,2,0,-1},{12679,3,0,-1},{12683,2,0,-1},{12683,3,0,-1},
+{12688,2,0,-1},{12688,3,0,-1},{12692,2,0,-1},{12692,3,0,-1},{12622,2,0,-1},
+{12696,2,0,-1},{12696,3,0,-1},{12701,2,0,-1},{12701,3,0,-1},{12705,2,0,-1},
+{12705,3,0,-1},{12635,3,0,-1},{12709,2,0,-1},{12709,3,0,-1},{12714,2,0,-1},
+{12714,3,0,-1},{12718,2,0,-1},{12718,3,0,-1},{12722,2,0,-1},{12722,3,0,-1},
+{12727,2,0,-1},{12727,3,0,-1},{12731,2,0,-1},{12731,3,0,-1},{12735,2,0,-1},
+{12735,3,0,-1},{12740,2,0,-1},{12740,3,0,-1},{12744,2,0,-1},{12744,3,0,-1},
+{12748,2,0,-1},{12748,3,0,-1},{12753,2,0,-1},{12753,3,0,-1},{12757,2,0,-1},
+{12757,3,0,-1},{12761,2,0,-1},{12761,3,0,-1},{12766,2,0,-1},{12766,3,0,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on full rate channels TS4 and TS5 */
+struct fn_sample test_fn_tch_f_ts_4_5[] = {
+{3407,0,1,-1},{3427,0,1,-1},{3458,0,1,-1},{3509,0,1,-1},{3529,0,1,-1},
+{3560,0,1,-1},{3611,0,1,-1},{3662,0,1,-1},{3713,0,1,-1},{3764,0,1,-1},
+{3780,0,2,-1},{3821,0,2,-1},{3872,0,2,-1},{3882,0,2,-1},{3923,0,2,-1},
+{3974,0,2,-1},{3984,0,2,-1},{4025,0,2,-1},{4076,0,2,-1},{4127,0,2,-1},
+{4178,0,2,-1},{5871,4,0,-1},{5876,4,0,-1},{5880,4,0,-1},{5884,4,0,-1},
+{5889,4,0,-1},{5893,4,0,-1},{5897,4,0,-1},{5902,4,0,-1},{5906,4,0,-1},
+{5910,4,0,-1},{5915,4,0,-1},{5919,4,0,-1},{5923,4,0,-1},{5928,4,0,-1},
+{5932,4,0,-1},{5936,4,0,-1},{5941,4,0,-1},{5945,4,0,-1},{5949,4,0,-1},
+{5954,4,0,-1},{5958,4,0,-1},{5888,4,0,-1},{5962,4,0,-1},{5967,4,0,-1},
+{5971,4,0,-1},{5975,4,0,-1},{5980,4,0,-1},{5984,4,0,-1},{5988,4,0,-1},
+{5993,4,0,-1},{5997,4,0,-1},{6001,4,0,-1},{6006,4,0,-1},{6010,4,0,-1},
+{6014,4,0,-1},{6019,4,0,-1},{6023,4,0,-1},{6027,4,0,-1},{6032,4,0,-1},
+{6036,4,0,-1},{6040,4,0,-1},{6045,4,0,-1},{6049,4,0,-1},{6053,4,0,-1},
+{6058,4,0,-1},{6062,4,0,-1},{5992,4,0,-1},{6066,4,0,-1},{6071,4,0,-1},
+{6075,4,0,-1},{6079,4,0,-1},{6084,4,0,-1},{6088,4,0,-1},{6092,4,0,-1},
+{6097,4,0,-1},{6101,4,0,-1},{6105,4,0,-1},{6110,4,0,-1},{6114,4,0,-1},
+{6118,4,0,-1},{6123,4,0,-1},{6127,4,0,-1},{6131,4,0,-1},{6136,4,0,-1},
+{6140,4,0,-1},{6144,4,0,-1},{6149,4,0,-1},{6153,4,0,-1},{6157,4,0,-1},
+{6162,4,0,-1},{6166,4,0,-1},{6096,4,0,-1},{6170,4,0,-1},{6175,4,0,-1},
+{6175,5,0,-1},{6179,4,0,-1},{6179,5,0,-1},{6183,4,0,-1},{6183,5,0,-1},
+{6188,4,0,-1},{6188,5,0,-1},{6192,4,0,-1},{6192,5,0,-1},{6196,4,0,-1},
+{6196,5,0,-1},{6201,4,0,-1},{6201,5,0,-1},{6205,4,0,-1},{6205,5,0,-1},
+{6209,4,0,-1},{6209,5,0,-1},{6214,4,0,-1},{6214,5,0,-1},{6218,4,0,-1},
+{6218,5,0,-1},{6222,4,0,-1},{6222,5,0,-1},{6227,4,0,-1},{6227,5,0,-1},
+{6231,4,0,-1},{6231,5,0,-1},{6235,4,0,-1},{6235,5,0,-1},{6240,4,0,-1},
+{6240,5,0,-1},{6244,4,0,-1},{6244,5,0,-1},{6248,4,0,-1},{6248,5,0,-1},
+{6253,4,0,-1},{6253,5,0,-1},{6257,4,0,-1},{6257,5,0,-1},{6261,4,0,-1},
+{6261,5,0,-1},{6266,4,0,-1},{6266,5,0,-1},{6270,4,0,-1},{6270,5,0,-1},
+{6200,4,0,-1},{6274,4,0,-1},{6274,5,0,-1},{6279,4,0,-1},{6279,5,0,-1},
+{6283,4,0,-1},{6283,5,0,-1},{6213,5,0,-1},{6287,4,0,-1},{6287,5,0,-1},
+{6292,4,0,-1},{6292,5,0,-1},{6296,4,0,-1},{6296,5,0,-1},{6300,4,0,-1},
+{6300,5,0,-1},{6305,4,0,-1},{6305,5,0,-1},{6309,4,0,-1},{6309,5,0,-1},
+{6313,4,0,-1},{6313,5,0,-1},{6318,4,0,-1},{6318,5,0,-1},{6322,4,0,-1},
+{6322,5,0,-1},{6326,4,0,-1},{6326,5,0,-1},{6331,4,0,-1},{6331,5,0,-1},
+{6335,4,0,-1},{6335,5,0,-1},{6339,4,0,-1},{6339,5,0,-1},{6344,4,0,-1},
+{6344,5,0,-1},{6348,4,0,-1},{6348,5,0,-1},{6352,4,0,-1},{6352,5,0,-1},
+{6357,4,0,-1},{6357,5,0,-1},{6361,4,0,-1},{6361,5,0,-1},{6365,4,0,-1},
+{6365,5,0,-1},{6370,4,0,-1},{6370,5,0,-1},{6374,4,0,-1},{6374,5,0,-1},
+{6304,4,0,-1},{6378,4,0,-1},{6378,5,0,-1},{6383,4,0,-1},{6383,5,0,-1},
+{6387,4,0,-1},{6387,5,0,-1},{6317,5,0,-1},{6391,4,0,-1},{6391,5,0,-1},
+{6396,4,0,-1},{6396,5,0,-1},{6400,4,0,-1},{6400,5,0,-1},{6404,4,0,-1},
+{6404,5,0,-1},{6409,4,0,-1},{6409,5,0,-1},{6413,4,0,-1},{6413,5,0,-1},
+{6417,4,0,-1},{6417,5,0,-1},{6422,4,0,-1},{6422,5,0,-1},{6426,4,0,-1},
+{6426,5,0,-1},{6430,4,0,-1},{6430,5,0,-1},{6435,4,0,-1},{6435,5,0,-1},
+{6439,4,0,-1},{6439,5,0,-1},{6443,4,0,-1},{6443,5,0,-1},{6448,4,0,-1},
+{6448,5,0,-1},{6452,4,0,-1},{6452,5,0,-1},{6456,4,0,-1},{6456,5,0,-1},
+{6461,4,0,-1},{6461,5,0,-1},{6465,4,0,-1},{6465,5,0,-1},{6469,4,0,-1},
+{6469,5,0,-1},{6474,4,0,-1},{6474,5,0,-1},{6478,4,0,-1},{6478,5,0,-1},
+{6408,4,0,-1},{6482,4,0,-1},{6482,5,0,-1},{6487,4,0,-1},{6487,5,0,-1},
+{6491,4,0,-1},{6491,5,0,-1},{6421,5,0,-1},{6495,4,0,-1},{6495,5,0,-1},
+{6500,4,0,-1},{6500,5,0,-1},{6504,4,0,-1},{6504,5,0,-1},{6508,4,0,-1},
+{6508,5,0,-1},{6513,4,0,-1},{6513,5,0,-1},{6517,4,0,-1},{6517,5,0,-1},
+{6521,4,0,-1},{6521,5,0,-1},{6526,4,0,-1},{6526,5,0,-1},{6530,4,0,-1},
+{6530,5,0,-1},{6534,4,0,-1},{6534,5,0,-1},{6539,4,0,-1},{6539,5,0,-1},
+{6543,4,0,-1},{6543,5,0,-1},{6547,4,0,-1},{6547,5,0,-1},{6552,4,0,-1},
+{6552,5,0,-1},{6556,4,0,-1},{6556,5,0,-1},{6560,4,0,-1},{6560,5,0,-1},
+{6565,4,0,-1},{6565,5,0,-1},{6569,4,0,-1},{6569,5,0,-1},{6573,4,0,-1},
+{6573,5,0,-1},{6578,4,0,-1},{6578,5,0,-1},{6582,4,0,-1},{6582,5,0,-1},
+{6512,4,0,-1},{6586,4,0,-1},{6586,5,0,-1},{6591,4,0,-1},{6591,5,0,-1},
+{6595,4,0,-1},{6595,5,0,-1},{6525,5,0,-1},{6599,4,0,-1},{6599,5,0,-1},
+{6604,4,0,-1},{6604,5,0,-1},{6608,4,0,-1},{6608,5,0,-1},{6612,4,0,-1},
+{6612,5,0,-1},{6617,4,0,-1},{6617,5,0,-1},{6621,4,0,-1},{6621,5,0,-1},
+{6625,4,0,-1},{6625,5,0,-1},{6630,4,0,-1},{6630,5,0,-1},{6634,4,0,-1},
+{6634,5,0,-1},{6638,4,0,-1},{6638,5,0,-1},{6643,4,0,-1},{6643,5,0,-1},
+{6647,4,0,-1},{6647,5,0,-1},{6651,4,0,-1},{6651,5,0,-1},{6656,4,0,-1},
+{6656,5,0,-1},{6660,4,0,-1},{6660,5,0,-1},{6664,4,0,-1},{6664,5,0,-1},
+{6669,4,0,-1},{6669,5,0,-1},{6673,4,0,-1},{6673,5,0,-1},{6677,4,0,-1},
+{6677,5,0,-1},{6682,4,0,-1},{6682,5,0,-1},{6686,4,0,-1},{6686,5,0,-1},
+{6616,4,0,-1},{6690,4,0,-1},{6690,5,0,-1},{6695,4,0,-1},{6695,5,0,-1},
+{6699,4,0,-1},{6699,5,0,-1},{6629,5,0,-1},{6703,4,0,-1},{6703,5,0,-1},
+{6708,4,0,-1},{6708,5,0,-1},{6712,4,0,-1},{6712,5,0,-1},{6716,4,0,-1},
+{6716,5,0,-1},{6721,4,0,-1},{6721,5,0,-1},{6725,4,0,-1},{6725,5,0,-1},
+{6729,4,0,-1},{6729,5,0,-1},{6734,4,0,-1},{6734,5,0,-1},{6738,4,0,-1},
+{6738,5,0,-1},{6742,4,0,-1},{6742,5,0,-1},{6747,4,0,-1},{6747,5,0,-1},
+{6751,4,0,-1},{6751,5,0,-1},{6755,4,0,-1},{6755,5,0,-1},{6760,4,0,-1},
+{6760,5,0,-1},{6764,4,0,-1},{6764,5,0,-1},{6768,4,0,-1},{6768,5,0,-1},
+{6773,4,0,-1},{6773,5,0,-1},{6777,4,0,-1},{6777,5,0,-1},{6781,4,0,-1},
+{6781,5,0,-1},{6786,4,0,-1},{6786,5,0,-1},{6790,4,0,-1},{6790,5,0,-1},
+{6720,4,0,-1},{6794,4,0,-1},{6794,5,0,-1},{6799,4,0,-1},{6799,5,0,-1},
+{6803,4,0,-1},{6803,5,0,-1},{6733,5,0,-1},{6807,4,0,-1},{6807,5,0,-1},
+{6812,4,0,-1},{6812,5,0,-1},{6816,4,0,-1},{6816,5,0,-1},{6820,4,0,-1},
+{6820,5,0,-1},{6825,4,0,-1},{6825,5,0,-1},{6829,4,0,-1},{6829,5,0,-1},
+{6833,4,0,-1},{6833,5,0,-1},{6838,4,0,-1},{6838,5,0,-1},{6842,4,0,-1},
+{6842,5,0,-1},{6846,4,0,-1},{6846,5,0,-1},{6851,4,0,-1},{6851,5,0,-1},
+{6855,4,0,-1},{6855,5,0,-1},{6859,4,0,-1},{6859,5,0,-1},{6864,4,0,-1},
+{6864,5,0,-1},{6868,4,0,-1},{6868,5,0,-1},{6872,4,0,-1},{6872,5,0,-1},
+{6877,4,0,-1},{6877,5,0,-1},{6881,4,0,-1},{6881,5,0,-1},{6885,4,0,-1},
+{6885,5,0,-1},{6890,4,0,-1},{6890,5,0,-1},{6894,4,0,-1},{6894,5,0,-1},
+{6824,4,0,-1},{6898,4,0,-1},{6898,5,0,-1},{6903,4,0,-1},{6903,5,0,-1},
+{6907,4,0,-1},{6907,5,0,-1},{6837,5,0,-1},{6911,4,0,-1},{6911,5,0,-1},
+{6916,4,0,-1},{6916,5,0,-1},{6920,4,0,-1},{6920,5,0,-1},{6924,4,0,-1},
+{6924,5,0,-1},{6929,4,0,-1},{6929,5,0,-1},{6933,4,0,-1},{6933,5,0,-1},
+{6937,4,0,-1},{6937,5,0,-1},{6942,4,0,-1},{6942,5,0,-1},{6946,4,0,-1},
+{6946,5,0,-1},{6950,4,0,-1},{6950,5,0,-1},{6955,4,0,-1},{6955,5,0,-1},
+{6959,4,0,-1},{6959,5,0,-1},{6963,4,0,-1},{6963,5,0,-1},{6968,4,0,-1},
+{6968,5,0,-1},{6972,4,0,-1},{6972,5,0,-1},{6976,4,0,-1},{6976,5,0,-1},
+{6981,4,0,-1},{6981,5,0,-1},{6985,4,0,-1},{6985,5,0,-1},{6989,4,0,-1},
+{6989,5,0,-1},{6994,4,0,-1},{6994,5,0,-1},{6998,4,0,-1},{6998,5,0,-1},
+{6928,4,0,-1},{7002,4,0,-1},{7002,5,0,-1},{7007,4,0,-1},{7007,5,0,-1},
+{7011,4,0,-1},{7011,5,0,-1},{6941,5,0,-1},{7015,4,0,-1},{7015,5,0,-1},
+{7020,4,0,-1},{7020,5,0,-1},{7024,4,0,-1},{7024,5,0,-1},{7028,4,0,-1},
+{7028,5,0,-1},{7033,4,0,-1},{7033,5,0,-1},{7037,4,0,-1},{7037,5,0,-1},
+{7041,4,0,-1},{7041,5,0,-1},{7046,4,0,-1},{7046,5,0,-1},{7050,4,0,-1},
+{7050,5,0,-1},{7054,4,0,-1},{7054,5,0,-1},{7059,4,0,-1},{7059,5,0,-1},
+{7063,4,0,-1},{7063,5,0,-1},{7067,4,0,-1},{7067,5,0,-1},{7072,4,0,-1},
+{7072,5,0,-1},{7076,4,0,-1},{7076,5,0,-1},{7080,4,0,-1},{7080,5,0,-1},
+{7085,4,0,-1},{7085,5,0,-1},{7089,4,0,-1},{7089,5,0,-1},{7093,4,0,-1},
+{7093,5,0,-1},{7098,4,0,-1},{7098,5,0,-1},{7102,4,0,-1},{7102,5,0,-1},
+{7032,4,0,-1},{7106,4,0,-1},{7106,5,0,-1},{7111,4,0,-1},{7111,5,0,-1},
+{7115,4,0,-1},{7115,5,0,-1},{7045,5,0,-1},{7119,4,0,-1},{7119,5,0,-1},
+{7124,4,0,-1},{7124,5,0,-1},{7128,4,0,-1},{7128,5,0,-1},{7132,4,0,-1},
+{7132,5,0,-1},{7137,4,0,-1},{7137,5,0,-1},{7141,4,0,-1},{7141,5,0,-1},
+{7145,4,0,-1},{7145,5,0,-1},{7150,4,0,-1},{7150,5,0,-1},{7154,4,0,-1},
+{7154,5,0,-1},{7158,4,0,-1},{7158,5,0,-1},{7163,4,0,-1},{7163,5,0,-1},
+{7167,4,0,-1},{7167,5,0,-1},{7171,4,0,-1},{7171,5,0,-1},{7176,4,0,-1},
+{7176,5,0,-1},{7180,4,0,-1},{7180,5,0,-1},{7184,4,0,-1},{7184,5,0,-1},
+{7189,4,0,-1},{7189,5,0,-1},{7193,4,0,-1},{7193,5,0,-1},{7197,4,0,-1},
+{7197,5,0,-1},{7202,4,0,-1},{7202,5,0,-1},{7206,4,0,-1},{7206,5,0,-1},
+{7136,4,0,-1},{7210,4,0,-1},{7210,5,0,-1},{7215,4,0,-1},{7215,5,0,-1},
+{7219,4,0,-1},{7219,5,0,-1},{7149,5,0,-1},{7223,4,0,-1},{7223,5,0,-1},
+{7228,4,0,-1},{7228,5,0,-1},{7232,4,0,-1},{7232,5,0,-1},{7236,4,0,-1},
+{7236,5,0,-1},{7241,4,0,-1},{7241,5,0,-1},{7245,4,0,-1},{7245,5,0,-1},
+{7249,4,0,-1},{7249,5,0,-1},{7254,4,0,-1},{7254,5,0,-1},{7258,4,0,-1},
+{7258,5,0,-1},{7262,4,0,-1},{7262,5,0,-1},{7267,4,0,-1},{7267,5,0,-1},
+{7271,4,0,-1},{7271,5,0,-1},{7275,4,0,-1},{7275,5,0,-1},{7280,4,0,-1},
+{7280,5,0,-1},{7284,4,0,-1},{7284,5,0,-1},{7288,4,0,-1},{7288,5,0,-1},
+{7293,4,0,-1},{7293,5,0,-1},{7297,4,0,-1},{7297,5,0,-1},{7301,4,0,-1},
+{7301,5,0,-1},{7306,4,0,-1},{7306,5,0,-1},{7310,4,0,-1},{7310,5,0,-1},
+{7240,4,0,-1},{7314,4,0,-1},{7314,5,0,-1},{7319,4,0,-1},{7319,5,0,-1},
+{7323,4,0,-1},{7323,5,0,-1},{7253,5,0,-1},{7327,4,0,-1},{7327,5,0,-1},
+{7332,4,0,-1},{7332,5,0,-1},{7336,4,0,-1},{7336,5,0,-1},{7340,4,0,-1},
+{7340,5,0,-1},{7345,4,0,-1},{7345,5,0,-1},{7349,4,0,-1},{7349,5,0,-1},
+{7353,4,0,-1},{7353,5,0,-1},{7358,4,0,-1},{7358,5,0,-1},{7362,4,0,-1},
+{7362,5,0,-1},{7366,4,0,-1},{7366,5,0,-1},{7371,4,0,-1},{7371,5,0,-1},
+{7375,4,0,-1},{7375,5,0,-1},{7379,4,0,-1},{7379,5,0,-1},{7384,4,0,-1},
+{7384,5,0,-1},{7388,4,0,-1},{7388,5,0,-1},{7392,4,0,-1},{7392,5,0,-1},
+{7397,4,0,-1},{7397,5,0,-1},{7401,4,0,-1},{7401,5,0,-1},{7405,4,0,-1},
+{7405,5,0,-1},{7410,4,0,-1},{7410,5,0,-1},{7414,4,0,-1},{7414,5,0,-1},
+{7344,4,0,-1},{7418,4,0,-1},{7418,5,0,-1},{7423,4,0,-1},{7423,5,0,-1},
+{7427,4,0,-1},{7427,5,0,-1},{7357,5,0,-1},{7431,4,0,-1},{7431,5,0,-1},
+{7436,4,0,-1},{7436,5,0,-1},{7440,4,0,-1},{7440,5,0,-1},{7444,4,0,-1},
+{7444,5,0,-1},{7449,4,0,-1},{7449,5,0,-1},{7453,4,0,-1},{7453,5,0,-1},
+{7457,4,0,-1},{7457,5,0,-1},{7462,4,0,-1},{7462,5,0,-1},{7466,4,0,-1},
+{7466,5,0,-1},{7470,4,0,-1},{7470,5,0,-1},{7475,4,0,-1},{7475,5,0,-1},
+{7479,4,0,-1},{7479,5,0,-1},{7483,4,0,-1},{7483,5,0,-1},{7488,4,0,-1},
+{7488,5,0,-1},{7492,4,0,-1},{7492,5,0,-1},{7496,4,0,-1},{7496,5,0,-1},
+{7501,4,0,-1},{7501,5,0,-1},{7505,4,0,-1},{7505,5,0,-1},{7509,4,0,-1},
+{7509,5,0,-1},{7514,4,0,-1},{7514,5,0,-1},{7518,4,0,-1},{7518,5,0,-1},
+{7448,4,0,-1},{7522,4,0,-1},{7522,5,0,-1},{7527,4,0,-1},{7527,5,0,-1},
+{7531,4,0,-1},{7531,5,0,-1},{7461,5,0,-1},{7535,4,0,-1},{7535,5,0,-1},
+{7540,4,0,-1},{7540,5,0,-1},{7544,4,0,-1},{7544,5,0,-1},{7548,4,0,-1},
+{7548,5,0,-1},{7553,4,0,-1},{7553,5,0,-1},{7557,4,0,-1},{7557,5,0,-1},
+{7561,4,0,-1},{7561,5,0,-1},{7566,4,0,-1},{7566,5,0,-1},{7570,4,0,-1},
+{7570,5,0,-1},{7574,4,0,-1},{7574,5,0,-1},{7579,4,0,-1},{7579,5,0,-1},
+{7583,4,0,-1},{7583,5,0,-1},{7587,4,0,-1},{7587,5,0,-1},{7592,4,0,-1},
+{7592,5,0,-1},{7596,4,0,-1},{7596,5,0,-1},{7600,4,0,-1},{7600,5,0,-1},
+{7605,4,0,-1},{7605,5,0,-1},{7609,4,0,-1},{7609,5,0,-1},{7613,4,0,-1},
+{7613,5,0,-1},{7618,4,0,-1},{7618,5,0,-1},{7622,4,0,-1},{7622,5,0,-1},
+{7552,4,0,-1},{7626,4,0,-1},{7626,5,0,-1},{7631,4,0,-1},{7631,5,0,-1},
+{7635,4,0,-1},{7635,5,0,-1},{7565,5,0,-1},{7639,4,0,-1},{7639,5,0,-1},
+{7644,4,0,-1},{7644,5,0,-1},{7648,4,0,-1},{7648,5,0,-1},{7652,4,0,-1},
+{7652,5,0,-1},{7657,4,0,-1},{7657,5,0,-1},{7661,4,0,-1},{7661,5,0,-1},
+{7665,4,0,-1},{7665,5,0,-1},{7670,4,0,-1},{7670,5,0,-1},{7674,4,0,-1},
+{7674,5,0,-1},{7678,4,0,-1},{7678,5,0,-1},{7683,4,0,-1},{7683,5,0,-1},
+{7687,4,0,-1},{7687,5,0,-1},{7691,4,0,-1},{7691,5,0,-1},{7696,4,0,-1},
+{7696,5,0,-1},{7700,4,0,-1},{7700,5,0,-1},{7704,4,0,-1},{7704,5,0,-1},
+{7709,4,0,-1},{7709,5,0,-1},{7713,4,0,-1},{7713,5,0,-1},{7717,4,0,-1},
+{7717,5,0,-1},{7722,4,0,-1},{7722,5,0,-1},{7726,4,0,-1},{7726,5,0,-1},
+{7656,4,0,-1},{7730,4,0,-1},{7730,5,0,-1},{7735,4,0,-1},{7735,5,0,-1},
+{7739,4,0,-1},{7739,5,0,-1},{7669,5,0,-1},{7743,4,0,-1},{7743,5,0,-1},
+{7748,4,0,-1},{7748,5,0,-1},{7752,4,0,-1},{7752,5,0,-1},{7756,4,0,-1},
+{7756,5,0,-1},{7761,4,0,-1},{7761,5,0,-1},{7765,4,0,-1},{7765,5,0,-1},
+{7769,4,0,-1},{7769,5,0,-1},{7774,4,0,-1},{7774,5,0,-1},{7778,4,0,-1},
+{7778,5,0,-1},{7782,4,0,-1},{7782,5,0,-1},{7787,4,0,-1},{7787,5,0,-1},
+{7791,4,0,-1},{7791,5,0,-1},{7795,4,0,-1},{7795,5,0,-1},{7800,4,0,-1},
+{7800,5,0,-1},{7804,4,0,-1},{7804,5,0,-1},{7808,4,0,-1},{7808,5,0,-1},
+{7813,4,0,-1},{7813,5,0,-1},{7817,4,0,-1},{7817,5,0,-1},{7821,4,0,-1},
+{7821,5,0,-1},{7826,4,0,-1},{7826,5,0,-1},{7830,4,0,-1},{7830,5,0,-1},
+{7760,4,0,-1},{7834,4,0,-1},{7834,5,0,-1},{7839,4,0,-1},{7839,5,0,-1},
+{7843,4,0,-1},{7843,5,0,-1},{7773,5,0,-1},{7847,4,0,-1},{7847,5,0,-1},
+{7852,4,0,-1},{7852,5,0,-1},{7856,4,0,-1},{7856,5,0,-1},{7860,4,0,-1},
+{7860,5,0,-1},{7865,4,0,-1},{7865,5,0,-1},{7869,4,0,-1},{7869,5,0,-1},
+{7873,4,0,-1},{7873,5,0,-1},{7878,4,0,-1},{7878,5,0,-1},{7882,4,0,-1},
+{7882,5,0,-1},{7886,4,0,-1},{7886,5,0,-1},{7891,4,0,-1},{7891,5,0,-1},
+{7895,4,0,-1},{7895,5,0,-1},{7899,4,0,-1},{7899,5,0,-1},{7904,4,0,-1},
+{7904,5,0,-1},{7908,4,0,-1},{7908,5,0,-1},{7912,4,0,-1},{7912,5,0,-1},
+{7917,4,0,-1},{7917,5,0,-1},{7921,4,0,-1},{7921,5,0,-1},{7925,4,0,-1},
+{7925,5,0,-1},{7930,4,0,-1},{7930,5,0,-1},{7934,4,0,-1},{7934,5,0,-1},
+{7864,4,0,-1},{7938,4,0,-1},{7938,5,0,-1},{7943,4,0,-1},{7943,5,0,-1},
+{7947,4,0,-1},{7947,5,0,-1},{7877,5,0,-1},{7951,4,0,-1},{7951,5,0,-1},
+{7956,4,0,-1},{7956,5,0,-1},{7960,4,0,-1},{7960,5,0,-1},{7964,4,0,-1},
+{7964,5,0,-1},{7969,4,0,-1},{7969,5,0,-1},{7973,4,0,-1},{7973,5,0,-1},
+{7977,4,0,-1},{7977,5,0,-1},{7982,4,0,-1},{7982,5,0,-1},{7986,4,0,-1},
+{7986,5,0,-1},{7990,4,0,-1},{7990,5,0,-1},{7995,4,0,-1},{7995,5,0,-1},
+{7999,4,0,-1},{7999,5,0,-1},{8003,4,0,-1},{8003,5,0,-1},{8008,4,0,-1},
+{8008,5,0,-1},{8012,4,0,-1},{8012,5,0,-1},{8016,4,0,-1},{8016,5,0,-1},
+{8021,4,0,-1},{8021,5,0,-1},{8025,4,0,-1},{8025,5,0,-1},{8029,4,0,-1},
+{8029,5,0,-1},{8034,4,0,-1},{8034,5,0,-1},{8038,4,0,-1},{8038,5,0,-1},
+{7968,4,0,-1},{8042,4,0,-1},{8042,5,0,-1},{8047,4,0,-1},{8047,5,0,-1},
+{8051,4,0,-1},{8051,5,0,-1},{7981,5,0,-1},{8055,4,0,-1},{8055,5,0,-1},
+{8060,4,0,-1},{8060,5,0,-1},{8064,4,0,-1},{8064,5,0,-1},{8068,4,0,-1},
+{8068,5,0,-1},{8073,4,0,-1},{8073,5,0,-1},{8077,4,0,-1},{8077,5,0,-1},
+{8081,4,0,-1},{8081,5,0,-1},{8086,4,0,-1},{8086,5,0,-1},{8090,4,0,-1},
+{8090,5,0,-1},{8094,4,0,-1},{8094,5,0,-1},{8099,4,0,-1},{8099,5,0,-1},
+{8103,4,0,-1},{8103,5,0,-1},{8107,4,0,-1},{8107,5,0,-1},{8112,4,0,-1},
+{8112,5,0,-1},{8116,4,0,-1},{8116,5,0,-1},{8120,4,0,-1},{8120,5,0,-1},
+{8125,4,0,-1},{8125,5,0,-1},{8129,4,0,-1},{8129,5,0,-1},{8133,4,0,-1},
+{8133,5,0,-1},{8138,4,0,-1},{8138,5,0,-1},{8142,4,0,-1},{8142,5,0,-1},
+{8072,4,0,-1},{8146,4,0,-1},{8146,5,0,-1},{8151,4,0,-1},{8151,5,0,-1},
+{8155,4,0,-1},{8155,5,0,-1},{8085,5,0,-1},{8159,4,0,-1},{8159,5,0,-1},
+{8164,4,0,-1},{8164,5,0,-1},{8168,4,0,-1},{8168,5,0,-1},{8172,4,0,-1},
+{8172,5,0,-1},{8177,4,0,-1},{8177,5,0,-1},{8181,4,0,-1},{8181,5,0,-1},
+{8185,4,0,-1},{8185,5,0,-1},{8190,4,0,-1},{8190,5,0,-1},{8194,4,0,-1},
+{8194,5,0,-1},{8198,4,0,-1},{8198,5,0,-1},{8203,4,0,-1},{8203,5,0,-1},
+{8207,4,0,-1},{8207,5,0,-1},{8211,4,0,-1},{8211,5,0,-1},{8216,4,0,-1},
+{8216,5,0,-1},{8220,4,0,-1},{8220,5,0,-1},{8224,4,0,-1},{8224,5,0,-1},
+{8229,4,0,-1},{8229,5,0,-1},{8233,4,0,-1},{8233,5,0,-1},{8237,4,0,-1},
+{8237,5,0,-1},{8242,4,0,-1},{8242,5,0,-1},{8246,4,0,-1},{8246,5,0,-1},
+{8176,4,0,-1},{8250,4,0,-1},{8250,5,0,-1},{8255,4,0,-1},{8255,5,0,-1},
+{8259,4,0,-1},{8259,5,0,-1},{8189,5,0,-1},{8263,4,0,-1},{8263,5,0,-1},
+{8268,4,0,-1},{8268,5,0,-1},{8272,4,0,-1},{8272,5,0,-1},{8276,4,0,-1},
+{8276,5,0,-1},{8281,4,0,-1},{8281,5,0,-1},{8285,4,0,-1},{8285,5,0,-1},
+{8289,4,0,-1},{8289,5,0,-1},{8294,4,0,-1},{8294,5,0,-1},{8298,4,0,-1},
+{8298,5,0,-1},{8302,4,0,-1},{8302,5,0,-1},{8307,4,0,-1},{8307,5,0,-1},
+{8311,4,0,-1},{8311,5,0,-1},{8315,4,0,-1},{8315,5,0,-1},{8320,4,0,-1},
+{8320,5,0,-1},{8324,4,0,-1},{8324,5,0,-1},{8328,4,0,-1},{8328,5,0,-1},
+{8333,4,0,-1},{8333,5,0,-1},{8337,4,0,-1},{8337,5,0,-1},{8341,4,0,-1},
+{8341,5,0,-1},{8346,4,0,-1},{8346,5,0,-1},{8350,4,0,-1},{8350,5,0,-1},
+{8280,4,0,-1},{8354,4,0,-1},{8354,5,0,-1},{8359,4,0,-1},{8359,5,0,-1},
+{8363,4,0,-1},{8363,5,0,-1},{8293,5,0,-1},{8367,4,0,-1},{8367,5,0,-1},
+{8372,4,0,-1},{8372,5,0,-1},{8376,4,0,-1},{8376,5,0,-1},{8380,4,0,-1},
+{8380,5,0,-1},{8385,4,0,-1},{8385,5,0,-1},{8389,4,0,-1},{8389,5,0,-1},
+{8393,4,0,-1},{8393,5,0,-1},{8398,4,0,-1},{8398,5,0,-1},{8402,4,0,-1},
+{8402,5,0,-1},{8406,4,0,-1},{8406,5,0,-1},{8411,4,0,-1},{8411,5,0,-1},
+{8415,4,0,-1},{8415,5,0,-1},{8419,4,0,-1},{8419,5,0,-1},{8424,4,0,-1},
+{8424,5,0,-1},{8428,4,0,-1},{8428,5,0,-1},{8432,4,0,-1},{8432,5,0,-1},
+{8437,4,0,-1},{8437,5,0,-1},{8441,4,0,-1},{8441,5,0,-1},{8445,4,0,-1},
+{8445,5,0,-1},{8450,4,0,-1},{8450,5,0,-1},{8454,4,0,-1},{8454,5,0,-1},
+{8384,4,0,-1},{8458,4,0,-1},{8458,5,0,-1},{8463,4,0,-1},{8463,5,0,-1},
+{8467,4,0,-1},{8467,5,0,-1},{8397,5,0,-1},{8471,4,0,-1},{8471,5,0,-1},
+{8476,4,0,-1},{8476,5,0,-1},{8480,4,0,-1},{8480,5,0,-1},{8484,4,0,-1},
+{8484,5,0,-1},{8489,4,0,-1},{8489,5,0,-1},{8493,4,0,-1},{8493,5,0,-1},
+{8497,4,0,-1},{8497,5,0,-1},{8502,4,0,-1},{8502,5,0,-1},{8506,4,0,-1},
+{8506,5,0,-1},{8510,4,0,-1},{8510,5,0,-1},{8515,4,0,-1},{8515,5,0,-1},
+{8519,4,0,-1},{8519,5,0,-1},{8523,4,0,-1},{8523,5,0,-1},{8528,4,0,-1},
+{8528,5,0,-1},{8532,4,0,-1},{8532,5,0,-1},{8536,4,0,-1},{8536,5,0,-1},
+{8541,4,0,-1},{8541,5,0,-1},{8545,4,0,-1},{8545,5,0,-1},{8549,4,0,-1},
+{8549,5,0,-1},{8554,4,0,-1},{8554,5,0,-1},{8558,4,0,-1},{8558,5,0,-1},
+{8488,4,0,-1},{8562,4,0,-1},{8562,5,0,-1},{8567,4,0,-1},{8567,5,0,-1},
+{8571,4,0,-1},{8571,5,0,-1},{8501,5,0,-1},{8575,4,0,-1},{8575,5,0,-1},
+{8580,4,0,-1},{8580,5,0,-1},{8584,4,0,-1},{8584,5,0,-1},{8588,4,0,-1},
+{8588,5,0,-1},{8593,4,0,-1},{8593,5,0,-1},{8597,4,0,-1},{8597,5,0,-1},
+{8601,4,0,-1},{8601,5,0,-1},{8606,4,0,-1},{8606,5,0,-1},{8610,4,0,-1},
+{8610,5,0,-1},{8614,4,0,-1},{8614,5,0,-1},{8619,4,0,-1},{8619,5,0,-1},
+{8623,4,0,-1},{8623,5,0,-1},{8627,4,0,-1},{8627,5,0,-1},{8632,4,0,-1},
+{8632,5,0,-1},{8636,4,0,-1},{8636,5,0,-1},{8640,4,0,-1},{8640,5,0,-1},
+{8645,4,0,-1},{8645,5,0,-1},{8649,4,0,-1},{8649,5,0,-1},{8653,4,0,-1},
+{8653,5,0,-1},{8658,4,0,-1},{8658,5,0,-1},{8662,4,0,-1},{8662,5,0,-1},
+{8592,4,0,-1},{8666,4,0,-1},{8666,5,0,-1},{8671,4,0,-1},{8671,5,0,-1},
+{8675,4,0,-1},{8675,5,0,-1},{8605,5,0,-1},{8679,4,0,-1},{8679,5,0,-1},
+{8684,4,0,-1},{8684,5,0,-1},{8688,4,0,-1},{8688,5,0,-1},{8692,4,0,-1},
+{8692,5,0,-1},{8697,4,0,-1},{8697,5,0,-1},{8701,4,0,-1},{8701,5,0,-1},
+{8705,4,0,-1},{8705,5,0,-1},{8710,4,0,-1},{8710,5,0,-1},{8714,4,0,-1},
+{8714,5,0,-1},{8718,4,0,-1},{8718,5,0,-1},{8723,4,0,-1},{8723,5,0,-1},
+{8727,4,0,-1},{8727,5,0,-1},{8731,4,0,-1},{8731,5,0,-1},{8736,4,0,-1},
+{8736,5,0,-1},{8740,4,0,-1},{8740,5,0,-1},{8744,4,0,-1},{8744,5,0,-1},
+{8749,4,0,-1},{8749,5,0,-1},{8753,4,0,-1},{8753,5,0,-1},{8757,4,0,-1},
+{8757,5,0,-1},{8762,4,0,-1},{8762,5,0,-1},{8766,4,0,-1},{8766,5,0,-1},
+{8696,4,0,-1},{8770,4,0,-1},{8770,5,0,-1},{8775,4,0,-1},{8775,5,0,-1},
+{8779,4,0,-1},{8779,5,0,-1},{8709,5,0,-1},{8783,4,0,-1},{8783,5,0,-1},
+{8788,4,0,-1},{8788,5,0,-1},{8792,4,0,-1},{8792,5,0,-1},{8796,4,0,-1},
+{8796,5,0,-1},{8801,4,0,-1},{8801,5,0,-1},{8805,4,0,-1},{8805,5,0,-1},
+{8809,4,0,-1},{8809,5,0,-1},{8814,4,0,-1},{8814,5,0,-1},{8818,4,0,-1},
+{8818,5,0,-1},{8822,4,0,-1},{8822,5,0,-1},{8827,4,0,-1},{8827,5,0,-1},
+{8831,4,0,-1},{8831,5,0,-1},{8835,4,0,-1},{8835,5,0,-1},{8840,4,0,-1},
+{8840,5,0,-1},{8844,4,0,-1},{8844,5,0,-1},{8848,4,0,-1},{8848,5,0,-1},
+{8853,4,0,-1},{8853,5,0,-1},{8857,4,0,-1},{8857,5,0,-1},{8861,4,0,-1},
+{8861,5,0,-1},{8866,4,0,-1},{8866,5,0,-1},{8870,4,0,-1},{8870,5,0,-1},
+{8800,4,0,-1},{8874,4,0,-1},{8874,5,0,-1},{8879,4,0,-1},{8879,5,0,-1},
+{8883,4,0,-1},{8883,5,0,-1},{8813,5,0,-1},{8887,4,0,-1},{8887,5,0,-1},
+{8892,4,0,-1},{8892,5,0,-1},{8896,4,0,-1},{8896,5,0,-1},{8900,4,0,-1},
+{8900,5,0,-1},{8905,4,0,-1},{8905,5,0,-1},{8909,4,0,-1},{8909,5,0,-1},
+{8913,4,0,-1},{8913,5,0,-1},{8918,4,0,-1},{8918,5,0,-1},{8922,4,0,-1},
+{8922,5,0,-1},{8926,4,0,-1},{8926,5,0,-1},{8931,4,0,-1},{8931,5,0,-1},
+{8935,4,0,-1},{8935,5,0,-1},{8939,4,0,-1},{8939,5,0,-1},{8944,4,0,-1},
+{8944,5,0,-1},{8948,4,0,-1},{8948,5,0,-1},{8952,4,0,-1},{8952,5,0,-1},
+{8957,4,0,-1},{8957,5,0,-1},{8961,4,0,-1},{8961,5,0,-1},{8965,4,0,-1},
+{8965,5,0,-1},{8970,4,0,-1},{8970,5,0,-1},{8974,4,0,-1},{8974,5,0,-1},
+{8904,4,0,-1},{8978,4,0,-1},{8978,5,0,-1},{8983,4,0,-1},{8983,5,0,-1},
+{8987,4,0,-1},{8987,5,0,-1},{8917,5,0,-1},{8991,4,0,-1},{8991,5,0,-1},
+{8996,4,0,-1},{8996,5,0,-1},{9000,4,0,-1},{9000,5,0,-1},{9004,4,0,-1},
+{9004,5,0,-1},{9009,4,0,-1},{9009,5,0,-1},{9013,4,0,-1},{9013,5,0,-1},
+{9017,4,0,-1},{9017,5,0,-1},{9022,4,0,-1},{9022,5,0,-1},{9026,4,0,-1},
+{9026,5,0,-1},{9030,4,0,-1},{9030,5,0,-1},{9035,4,0,-1},{9035,5,0,-1},
+{9039,4,0,-1},{9039,5,0,-1},{9043,4,0,-1},{9043,5,0,-1},{9048,4,0,-1},
+{9048,5,0,-1},{9052,4,0,-1},{9052,5,0,-1},{9056,4,0,-1},{9056,5,0,-1},
+{9061,4,0,-1},{9061,5,0,-1},{9065,4,0,-1},{9065,5,0,-1},{9069,4,0,-1},
+{9069,5,0,-1},{9074,4,0,-1},{9074,5,0,-1},{9078,4,0,-1},{9078,5,0,-1},
+{9008,4,0,-1},{9082,4,0,-1},{9082,5,0,-1},{9087,4,0,-1},{9087,5,0,-1},
+{9091,4,0,-1},{9091,5,0,-1},{9021,5,0,-1},{9095,4,0,-1},{9095,5,0,-1},
+{9100,4,0,-1},{9100,5,0,-1},{9104,4,0,-1},{9104,5,0,-1},{9108,4,0,-1},
+{9108,5,0,-1},{9113,4,0,-1},{9113,5,0,-1},{9117,4,0,-1},{9117,5,0,-1},
+{9121,4,0,-1},{9121,5,0,-1},{9126,4,0,-1},{9126,5,0,-1},{9130,4,0,-1},
+{9130,5,0,-1},{9134,4,0,-1},{9134,5,0,-1},{9139,4,0,-1},{9139,5,0,-1},
+{9143,4,0,-1},{9143,5,0,-1},{9147,4,0,-1},{9147,5,0,-1},{9152,4,0,-1},
+{9152,5,0,-1},{9156,4,0,-1},{9156,5,0,-1},{9160,4,0,-1},{9160,5,0,-1},
+{9165,4,0,-1},{9165,5,0,-1},{9169,4,0,-1},{9169,5,0,-1},{9173,4,0,-1},
+{9173,5,0,-1},{9178,4,0,-1},{9178,5,0,-1},{9182,4,0,-1},{9182,5,0,-1},
+{9112,4,0,-1},{9186,4,0,-1},{9186,5,0,-1},{9191,4,0,-1},{9191,5,0,-1},
+{9195,4,0,-1},{9195,5,0,-1},{9125,5,0,-1},{9199,4,0,-1},{9199,5,0,-1},
+{9204,4,0,-1},{9204,5,0,-1},{9208,4,0,-1},{9208,5,0,-1},{9212,4,0,-1},
+{9212,5,0,-1},{9217,4,0,-1},{9217,5,0,-1},{9221,4,0,-1},{9221,5,0,-1},
+{9225,4,0,-1},{9225,5,0,-1},{9230,4,0,-1},{9230,5,0,-1},{9234,4,0,-1},
+{9234,5,0,-1},{9238,4,0,-1},{9238,5,0,-1},{9243,4,0,-1},{9243,5,0,-1},
+{9247,4,0,-1},{9247,5,0,-1},{9251,4,0,-1},{9251,5,0,-1},{9256,4,0,-1},
+{9256,5,0,-1},{9260,4,0,-1},{9260,5,0,-1},{9264,4,0,-1},{9264,5,0,-1},
+{9269,4,0,-1},{9269,5,0,-1},{9273,4,0,-1},{9273,5,0,-1},{9277,4,0,-1},
+{9277,5,0,-1},{9282,4,0,-1},{9282,5,0,-1},{9286,4,0,-1},{9286,5,0,-1},
+{9216,4,0,-1},{9290,4,0,-1},{9290,5,0,-1},{9295,4,0,-1},{9295,5,0,-1},
+{9299,4,0,-1},{9299,5,0,-1},{9229,5,0,-1},{9303,4,0,-1},{9303,5,0,-1},
+{9308,4,0,-1},{9308,5,0,-1},{9312,4,0,-1},{9312,5,0,-1},{9316,4,0,-1},
+{9316,5,0,-1},{9321,4,0,-1},{9321,5,0,-1},{9325,4,0,-1},{9325,5,0,-1},
+{9329,4,0,-1},{9329,5,0,-1},{9334,4,0,-1},{9334,5,0,-1},{9338,4,0,-1},
+{9338,5,0,-1},{9342,4,0,-1},{9342,5,0,-1},{9347,4,0,-1},{9347,5,0,-1},
+{9351,4,0,-1},{9351,5,0,-1},{9355,4,0,-1},{9355,5,0,-1},{9360,4,0,-1},
+{9360,5,0,-1},{9364,4,0,-1},{9364,5,0,-1},{9368,4,0,-1},{9368,5,0,-1},
+{9373,4,0,-1},{9373,5,0,-1},{9377,4,0,-1},{9377,5,0,-1},{9381,4,0,-1},
+{9381,5,0,-1},{9386,4,0,-1},{9386,5,0,-1},{9390,4,0,-1},{9390,5,0,-1},
+{9320,4,0,-1},{9394,4,0,-1},{9394,5,0,-1},{9399,4,0,-1},{9399,5,0,-1},
+{9403,4,0,-1},{9403,5,0,-1},{9333,5,0,-1},{9407,4,0,-1},{9407,5,0,-1},
+{9412,4,0,-1},{9412,5,0,-1},{9416,4,0,-1},{9416,5,0,-1},{9420,4,0,-1},
+{9420,5,0,-1},{9425,4,0,-1},{9425,5,0,-1},{9429,4,0,-1},{9429,5,0,-1},
+{9433,4,0,-1},{9433,5,0,-1},{9438,4,0,-1},{9438,5,0,-1},{9442,4,0,-1},
+{9442,5,0,-1},{9446,4,0,-1},{9446,5,0,-1},{9451,4,0,-1},{9451,5,0,-1},
+{9455,4,0,-1},{9455,5,0,-1},{9459,4,0,-1},{9459,5,0,-1},{9464,4,0,-1},
+{9464,5,0,-1},{9468,4,0,-1},{9468,5,0,-1},{9472,4,0,-1},{9472,5,0,-1},
+{9477,4,0,-1},{9477,5,0,-1},{9481,4,0,-1},{9481,5,0,-1},{9485,4,0,-1},
+{9485,5,0,-1},{9490,4,0,-1},{9490,5,0,-1},{9494,4,0,-1},{9494,5,0,-1},
+{9424,4,0,-1},{9498,4,0,-1},{9498,5,0,-1},{9503,4,0,-1},{9503,5,0,-1},
+{9507,4,0,-1},{9507,5,0,-1},{9437,5,0,-1},{9511,4,0,-1},{9511,5,0,-1},
+{9516,4,0,-1},{9516,5,0,-1},{9520,4,0,-1},{9520,5,0,-1},{9524,4,0,-1},
+{9524,5,0,-1},{9529,4,0,-1},{9529,5,0,-1},{9533,4,0,-1},{9533,5,0,-1},
+{9537,4,0,-1},{9537,5,0,-1},{9542,4,0,-1},{9542,5,0,-1},{9546,4,0,-1},
+{9546,5,0,-1},{9550,4,0,-1},{9550,5,0,-1},{9555,4,0,-1},{9555,5,0,-1},
+{9559,4,0,-1},{9559,5,0,-1},{9563,4,0,-1},{9563,5,0,-1},{9568,4,0,-1},
+{9568,5,0,-1},{9572,4,0,-1},{9572,5,0,-1},{9576,4,0,-1},{9576,5,0,-1},
+{9581,4,0,-1},{9581,5,0,-1},{9585,4,0,-1},{9585,5,0,-1},{9589,4,0,-1},
+{9589,5,0,-1},{9594,4,0,-1},{9594,5,0,-1},{9598,4,0,-1},{9598,5,0,-1},
+{9528,4,0,-1},{9602,4,0,-1},{9602,5,0,-1},{9607,4,0,-1},{9607,5,0,-1},
+{9611,4,0,-1},{9611,5,0,-1},{9541,5,0,-1},{9615,4,0,-1},{9615,5,0,-1},
+{9620,4,0,-1},{9620,5,0,-1},{9624,4,0,-1},{9624,5,0,-1},{9628,4,0,-1},
+{9628,5,0,-1},{9633,4,0,-1},{9633,5,0,-1},{9637,4,0,-1},{9637,5,0,-1},
+{9641,4,0,-1},{9641,5,0,-1},{9646,4,0,-1},{9646,5,0,-1},{9650,4,0,-1},
+{9650,5,0,-1},{9654,4,0,-1},{9654,5,0,-1},{9659,4,0,-1},{9659,5,0,-1},
+{9663,4,0,-1},{9663,5,0,-1},{9667,4,0,-1},{9667,5,0,-1},{9672,4,0,-1},
+{9672,5,0,-1},{9676,4,0,-1},{9676,5,0,-1},{9680,4,0,-1},{9680,5,0,-1},
+{9685,4,0,-1},{9689,4,0,-1},{9689,5,0,-1},{9693,4,0,-1},{9698,4,0,-1},
+{9702,4,0,-1},{9632,4,0,-1},{9706,4,0,-1},{9706,5,0,-1},{9711,4,0,-1},
+{9711,5,0,-1},{9715,4,0,-1},{9715,5,0,-1},{9645,5,0,-1},{9719,4,0,-1},
+{9719,5,0,-1},{9724,4,0,-1},{9724,5,0,-1},{9728,4,0,-1},{9728,5,0,-1},
+{9732,4,0,-1},{9737,4,0,-1},{9741,4,0,-1},{9741,5,0,-1},{9745,4,0,-1},
+{9745,5,0,-1},{9750,4,0,-1},{9754,4,0,-1},{9758,4,0,-1},{9763,4,0,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on full rate channels TS6 and TS7 */
+struct fn_sample test_fn_tch_f_ts_6_7[] = {
+{4753,0,1,-1},{4784,0,1,-1},{4835,0,1,-1},{4855,0,1,-1},{4886,0,1,-1},
+{4937,0,1,-1},{4957,0,1,-1},{4988,0,1,-1},{5039,0,1,-1},{5090,0,1,-1},
+{5141,0,1,-1},{5198,0,2,-1},{5208,0,2,-1},{5249,0,2,-1},{5300,0,2,-1},
+{5310,0,2,-1},{5351,0,2,-1},{5402,0,2,-1},{5453,0,2,-1},{5504,0,2,-1},
+{5555,0,2,-1},{8597,6,0,-1},{8627,6,0,-1},{8632,6,0,-1},{8636,6,0,-1},
+{8640,6,0,-1},{8645,6,0,-1},{8649,6,0,-1},{8653,6,0,-1},{8658,6,0,-1},
+{8662,6,0,-1},{8666,6,0,-1},{8671,6,0,-1},{8675,6,0,-1},{8679,6,0,-1},
+{8684,6,0,-1},{8688,6,0,-1},{8618,6,0,-1},{8692,6,0,-1},{8697,6,0,-1},
+{8701,6,0,-1},{8705,6,0,-1},{8710,6,0,-1},{8714,6,0,-1},{8718,6,0,-1},
+{8723,6,0,-1},{8727,6,0,-1},{8731,6,0,-1},{8736,6,0,-1},{8740,6,0,-1},
+{8744,6,0,-1},{8749,6,0,-1},{8753,6,0,-1},{8757,6,0,-1},{8762,6,0,-1},
+{8766,6,0,-1},{8770,6,0,-1},{8775,6,0,-1},{8779,6,0,-1},{8783,6,0,-1},
+{8788,6,0,-1},{8792,6,0,-1},{8722,6,0,-1},{8796,6,0,-1},{8801,6,0,-1},
+{8805,6,0,-1},{8809,6,0,-1},{8814,6,0,-1},{8818,6,0,-1},{8822,6,0,-1},
+{8827,6,0,-1},{8831,6,0,-1},{8835,6,0,-1},{8840,6,0,-1},{8844,6,0,-1},
+{8848,6,0,-1},{8853,6,0,-1},{8857,6,0,-1},{8861,6,0,-1},{8866,6,0,-1},
+{8870,6,0,-1},{8874,6,0,-1},{8874,7,0,-1},{8879,6,0,-1},{8879,7,0,-1},
+{8883,6,0,-1},{8883,7,0,-1},{8887,6,0,-1},{8887,7,0,-1},{8892,6,0,-1},
+{8892,7,0,-1},{8896,6,0,-1},{8896,7,0,-1},{8826,6,0,-1},{8900,6,0,-1},
+{8900,7,0,-1},{8905,6,0,-1},{8905,7,0,-1},{8909,6,0,-1},{8909,7,0,-1},
+{8913,6,0,-1},{8913,7,0,-1},{8918,6,0,-1},{8918,7,0,-1},{8922,6,0,-1},
+{8922,7,0,-1},{8926,6,0,-1},{8926,7,0,-1},{8931,6,0,-1},{8931,7,0,-1},
+{8935,6,0,-1},{8935,7,0,-1},{8939,6,0,-1},{8939,7,0,-1},{8944,6,0,-1},
+{8944,7,0,-1},{8948,6,0,-1},{8948,7,0,-1},{8952,6,0,-1},{8952,7,0,-1},
+{8957,6,0,-1},{8957,7,0,-1},{8961,6,0,-1},{8961,7,0,-1},{8965,6,0,-1},
+{8965,7,0,-1},{8970,6,0,-1},{8970,7,0,-1},{8974,6,0,-1},{8974,7,0,-1},
+{8978,6,0,-1},{8978,7,0,-1},{8983,6,0,-1},{8983,7,0,-1},{8987,6,0,-1},
+{8987,7,0,-1},{8991,6,0,-1},{8991,7,0,-1},{8996,6,0,-1},{8996,7,0,-1},
+{9000,6,0,-1},{9000,7,0,-1},{8930,6,0,-1},{9004,6,0,-1},{9004,7,0,-1},
+{9009,6,0,-1},{9009,7,0,-1},{9013,6,0,-1},{9013,7,0,-1},{8943,7,0,-1},
+{9017,6,0,-1},{9017,7,0,-1},{9022,6,0,-1},{9022,7,0,-1},{9026,6,0,-1},
+{9026,7,0,-1},{9030,6,0,-1},{9030,7,0,-1},{9035,6,0,-1},{9035,7,0,-1},
+{9039,6,0,-1},{9039,7,0,-1},{9043,6,0,-1},{9043,7,0,-1},{9048,6,0,-1},
+{9048,7,0,-1},{9052,6,0,-1},{9052,7,0,-1},{9056,6,0,-1},{9056,7,0,-1},
+{9061,6,0,-1},{9061,7,0,-1},{9065,6,0,-1},{9065,7,0,-1},{9069,6,0,-1},
+{9069,7,0,-1},{9074,6,0,-1},{9074,7,0,-1},{9078,6,0,-1},{9078,7,0,-1},
+{9082,6,0,-1},{9082,7,0,-1},{9087,6,0,-1},{9087,7,0,-1},{9091,6,0,-1},
+{9091,7,0,-1},{9095,6,0,-1},{9095,7,0,-1},{9100,6,0,-1},{9100,7,0,-1},
+{9104,6,0,-1},{9104,7,0,-1},{9034,6,0,-1},{9108,6,0,-1},{9108,7,0,-1},
+{9113,6,0,-1},{9113,7,0,-1},{9117,6,0,-1},{9117,7,0,-1},{9047,7,0,-1},
+{9121,6,0,-1},{9121,7,0,-1},{9126,6,0,-1},{9126,7,0,-1},{9130,6,0,-1},
+{9130,7,0,-1},{9134,6,0,-1},{9134,7,0,-1},{9139,6,0,-1},{9139,7,0,-1},
+{9143,6,0,-1},{9143,7,0,-1},{9147,6,0,-1},{9147,7,0,-1},{9152,6,0,-1},
+{9152,7,0,-1},{9156,6,0,-1},{9156,7,0,-1},{9160,6,0,-1},{9160,7,0,-1},
+{9165,6,0,-1},{9165,7,0,-1},{9169,6,0,-1},{9169,7,0,-1},{9173,6,0,-1},
+{9173,7,0,-1},{9178,6,0,-1},{9178,7,0,-1},{9182,6,0,-1},{9182,7,0,-1},
+{9186,6,0,-1},{9186,7,0,-1},{9191,6,0,-1},{9191,7,0,-1},{9195,6,0,-1},
+{9195,7,0,-1},{9199,6,0,-1},{9199,7,0,-1},{9204,6,0,-1},{9204,7,0,-1},
+{9208,6,0,-1},{9208,7,0,-1},{9138,6,0,-1},{9212,6,0,-1},{9212,7,0,-1},
+{9217,6,0,-1},{9217,7,0,-1},{9221,6,0,-1},{9221,7,0,-1},{9151,7,0,-1},
+{9225,6,0,-1},{9225,7,0,-1},{9230,6,0,-1},{9230,7,0,-1},{9234,6,0,-1},
+{9234,7,0,-1},{9238,6,0,-1},{9238,7,0,-1},{9243,6,0,-1},{9243,7,0,-1},
+{9247,6,0,-1},{9247,7,0,-1},{9251,6,0,-1},{9251,7,0,-1},{9256,6,0,-1},
+{9256,7,0,-1},{9260,6,0,-1},{9260,7,0,-1},{9264,6,0,-1},{9264,7,0,-1},
+{9269,6,0,-1},{9269,7,0,-1},{9273,6,0,-1},{9273,7,0,-1},{9277,6,0,-1},
+{9277,7,0,-1},{9282,6,0,-1},{9282,7,0,-1},{9286,6,0,-1},{9286,7,0,-1},
+{9290,6,0,-1},{9290,7,0,-1},{9295,6,0,-1},{9295,7,0,-1},{9299,6,0,-1},
+{9299,7,0,-1},{9303,6,0,-1},{9303,7,0,-1},{9308,6,0,-1},{9308,7,0,-1},
+{9312,6,0,-1},{9312,7,0,-1},{9242,6,0,-1},{9316,6,0,-1},{9316,7,0,-1},
+{9321,6,0,-1},{9321,7,0,-1},{9325,6,0,-1},{9325,7,0,-1},{9255,7,0,-1},
+{9329,6,0,-1},{9329,7,0,-1},{9334,6,0,-1},{9334,7,0,-1},{9338,6,0,-1},
+{9338,7,0,-1},{9342,6,0,-1},{9342,7,0,-1},{9347,6,0,-1},{9347,7,0,-1},
+{9351,6,0,-1},{9351,7,0,-1},{9355,6,0,-1},{9355,7,0,-1},{9360,6,0,-1},
+{9360,7,0,-1},{9364,6,0,-1},{9364,7,0,-1},{9368,6,0,-1},{9368,7,0,-1},
+{9373,6,0,-1},{9373,7,0,-1},{9377,6,0,-1},{9377,7,0,-1},{9381,6,0,-1},
+{9381,7,0,-1},{9386,6,0,-1},{9386,7,0,-1},{9390,6,0,-1},{9390,7,0,-1},
+{9394,6,0,-1},{9394,7,0,-1},{9399,6,0,-1},{9399,7,0,-1},{9403,6,0,-1},
+{9403,7,0,-1},{9407,6,0,-1},{9407,7,0,-1},{9412,6,0,-1},{9412,7,0,-1},
+{9416,6,0,-1},{9416,7,0,-1},{9346,6,0,-1},{9420,6,0,-1},{9420,7,0,-1},
+{9425,6,0,-1},{9425,7,0,-1},{9429,6,0,-1},{9429,7,0,-1},{9359,7,0,-1},
+{9433,6,0,-1},{9433,7,0,-1},{9438,6,0,-1},{9438,7,0,-1},{9442,6,0,-1},
+{9442,7,0,-1},{9446,6,0,-1},{9446,7,0,-1},{9451,6,0,-1},{9451,7,0,-1},
+{9455,6,0,-1},{9455,7,0,-1},{9459,6,0,-1},{9459,7,0,-1},{9464,6,0,-1},
+{9464,7,0,-1},{9468,6,0,-1},{9468,7,0,-1},{9472,6,0,-1},{9472,7,0,-1},
+{9477,6,0,-1},{9477,7,0,-1},{9481,6,0,-1},{9481,7,0,-1},{9485,6,0,-1},
+{9485,7,0,-1},{9490,6,0,-1},{9490,7,0,-1},{9494,6,0,-1},{9494,7,0,-1},
+{9498,6,0,-1},{9498,7,0,-1},{9503,6,0,-1},{9503,7,0,-1},{9507,6,0,-1},
+{9507,7,0,-1},{9511,6,0,-1},{9511,7,0,-1},{9516,6,0,-1},{9516,7,0,-1},
+{9520,6,0,-1},{9520,7,0,-1},{9450,6,0,-1},{9524,6,0,-1},{9524,7,0,-1},
+{9529,6,0,-1},{9529,7,0,-1},{9533,6,0,-1},{9533,7,0,-1},{9463,7,0,-1},
+{9537,6,0,-1},{9537,7,0,-1},{9542,6,0,-1},{9542,7,0,-1},{9546,6,0,-1},
+{9546,7,0,-1},{9550,6,0,-1},{9550,7,0,-1},{9555,6,0,-1},{9555,7,0,-1},
+{9559,6,0,-1},{9559,7,0,-1},{9563,6,0,-1},{9563,7,0,-1},{9568,6,0,-1},
+{9568,7,0,-1},{9572,6,0,-1},{9572,7,0,-1},{9576,6,0,-1},{9576,7,0,-1},
+{9581,6,0,-1},{9581,7,0,-1},{9585,6,0,-1},{9585,7,0,-1},{9589,6,0,-1},
+{9589,7,0,-1},{9594,6,0,-1},{9594,7,0,-1},{9598,6,0,-1},{9598,7,0,-1},
+{9602,6,0,-1},{9602,7,0,-1},{9607,6,0,-1},{9607,7,0,-1},{9611,6,0,-1},
+{9611,7,0,-1},{9615,6,0,-1},{9615,7,0,-1},{9620,6,0,-1},{9620,7,0,-1},
+{9624,6,0,-1},{9624,7,0,-1},{9554,6,0,-1},{9628,6,0,-1},{9628,7,0,-1},
+{9633,6,0,-1},{9633,7,0,-1},{9637,6,0,-1},{9637,7,0,-1},{9567,7,0,-1},
+{9641,6,0,-1},{9641,7,0,-1},{9646,6,0,-1},{9646,7,0,-1},{9650,6,0,-1},
+{9650,7,0,-1},{9654,6,0,-1},{9654,7,0,-1},{9659,6,0,-1},{9659,7,0,-1},
+{9663,6,0,-1},{9663,7,0,-1},{9667,6,0,-1},{9667,7,0,-1},{9672,6,0,-1},
+{9672,7,0,-1},{9676,6,0,-1},{9676,7,0,-1},{9680,6,0,-1},{9680,7,0,-1},
+{9685,6,0,-1},{9685,7,0,-1},{9689,6,0,-1},{9689,7,0,-1},{9693,6,0,-1},
+{9693,7,0,-1},{9698,6,0,-1},{9698,7,0,-1},{9702,6,0,-1},{9702,7,0,-1},
+{9706,6,0,-1},{9706,7,0,-1},{9711,6,0,-1},{9711,7,0,-1},{9715,6,0,-1},
+{9715,7,0,-1},{9719,6,0,-1},{9719,7,0,-1},{9724,6,0,-1},{9724,7,0,-1},
+{9728,6,0,-1},{9728,7,0,-1},{9658,6,0,-1},{9732,6,0,-1},{9732,7,0,-1},
+{9737,6,0,-1},{9737,7,0,-1},{9741,6,0,-1},{9741,7,0,-1},{9671,7,0,-1},
+{9745,6,0,-1},{9745,7,0,-1},{9750,6,0,-1},{9750,7,0,-1},{9754,6,0,-1},
+{9754,7,0,-1},{9758,6,0,-1},{9758,7,0,-1},{9763,6,0,-1},{9763,7,0,-1},
+{9767,6,0,-1},{9767,7,0,-1},{9771,6,0,-1},{9771,7,0,-1},{9776,6,0,-1},
+{9776,7,0,-1},{9780,6,0,-1},{9780,7,0,-1},{9784,6,0,-1},{9784,7,0,-1},
+{9789,6,0,-1},{9789,7,0,-1},{9793,6,0,-1},{9793,7,0,-1},{9797,6,0,-1},
+{9797,7,0,-1},{9802,6,0,-1},{9802,7,0,-1},{9806,6,0,-1},{9806,7,0,-1},
+{9810,6,0,-1},{9810,7,0,-1},{9815,6,0,-1},{9815,7,0,-1},{9819,6,0,-1},
+{9819,7,0,-1},{9823,6,0,-1},{9823,7,0,-1},{9828,6,0,-1},{9828,7,0,-1},
+{9832,6,0,-1},{9832,7,0,-1},{9762,6,0,-1},{9836,6,0,-1},{9836,7,0,-1},
+{9841,6,0,-1},{9841,7,0,-1},{9845,6,0,-1},{9845,7,0,-1},{9775,7,0,-1},
+{9849,6,0,-1},{9849,7,0,-1},{9854,6,0,-1},{9854,7,0,-1},{9858,6,0,-1},
+{9858,7,0,-1},{9862,6,0,-1},{9862,7,0,-1},{9867,6,0,-1},{9867,7,0,-1},
+{9871,6,0,-1},{9871,7,0,-1},{9875,6,0,-1},{9875,7,0,-1},{9880,6,0,-1},
+{9880,7,0,-1},{9884,6,0,-1},{9884,7,0,-1},{9888,6,0,-1},{9888,7,0,-1},
+{9893,6,0,-1},{9893,7,0,-1},{9897,6,0,-1},{9897,7,0,-1},{9901,6,0,-1},
+{9901,7,0,-1},{9906,6,0,-1},{9906,7,0,-1},{9910,6,0,-1},{9910,7,0,-1},
+{9914,6,0,-1},{9914,7,0,-1},{9919,6,0,-1},{9919,7,0,-1},{9923,6,0,-1},
+{9923,7,0,-1},{9927,6,0,-1},{9927,7,0,-1},{9932,6,0,-1},{9932,7,0,-1},
+{9936,6,0,-1},{9936,7,0,-1},{9866,6,0,-1},{9940,6,0,-1},{9940,7,0,-1},
+{9945,6,0,-1},{9945,7,0,-1},{9949,6,0,-1},{9949,7,0,-1},{9879,7,0,-1},
+{9953,6,0,-1},{9953,7,0,-1},{9958,6,0,-1},{9958,7,0,-1},{9962,6,0,-1},
+{9962,7,0,-1},{9966,6,0,-1},{9966,7,0,-1},{9971,6,0,-1},{9971,7,0,-1},
+{9975,6,0,-1},{9975,7,0,-1},{9979,6,0,-1},{9979,7,0,-1},{9984,6,0,-1},
+{9984,7,0,-1},{9988,6,0,-1},{9988,7,0,-1},{9992,6,0,-1},{9992,7,0,-1},
+{9997,6,0,-1},{9997,7,0,-1},{10001,6,0,-1},{10001,7,0,-1},{10005,6,0,-1},
+{10005,7,0,-1},{10010,6,0,-1},{10010,7,0,-1},{10014,6,0,-1},{10014,7,0,-1},
+{10018,6,0,-1},{10018,7,0,-1},{10023,6,0,-1},{10023,7,0,-1},{10027,6,0,-1},
+{10027,7,0,-1},{10031,6,0,-1},{10031,7,0,-1},{10036,6,0,-1},{10036,7,0,-1},
+{10040,6,0,-1},{10040,7,0,-1},{9970,6,0,-1},{10044,6,0,-1},{10044,7,0,-1},
+{10049,6,0,-1},{10049,7,0,-1},{10053,6,0,-1},{10053,7,0,-1},{9983,7,0,-1},
+{10057,6,0,-1},{10057,7,0,-1},{10062,6,0,-1},{10062,7,0,-1},{10066,6,0,-1},
+{10066,7,0,-1},{10070,6,0,-1},{10070,7,0,-1},{10075,6,0,-1},{10075,7,0,-1},
+{10079,6,0,-1},{10079,7,0,-1},{10083,6,0,-1},{10083,7,0,-1},{10088,6,0,-1},
+{10088,7,0,-1},{10092,6,0,-1},{10092,7,0,-1},{10096,6,0,-1},{10096,7,0,-1},
+{10101,6,0,-1},{10101,7,0,-1},{10105,6,0,-1},{10105,7,0,-1},{10109,6,0,-1},
+{10109,7,0,-1},{10114,6,0,-1},{10114,7,0,-1},{10118,6,0,-1},{10118,7,0,-1},
+{10122,6,0,-1},{10122,7,0,-1},{10127,6,0,-1},{10127,7,0,-1},{10131,6,0,-1},
+{10131,7,0,-1},{10135,6,0,-1},{10135,7,0,-1},{10140,6,0,-1},{10140,7,0,-1},
+{10144,6,0,-1},{10144,7,0,-1},{10074,6,0,-1},{10148,6,0,-1},{10148,7,0,-1},
+{10153,6,0,-1},{10153,7,0,-1},{10157,6,0,-1},{10157,7,0,-1},{10087,7,0,-1},
+{10161,6,0,-1},{10161,7,0,-1},{10166,6,0,-1},{10166,7,0,-1},{10170,6,0,-1},
+{10170,7,0,-1},{10174,6,0,-1},{10174,7,0,-1},{10179,6,0,-1},{10179,7,0,-1},
+{10183,6,0,-1},{10183,7,0,-1},{10187,6,0,-1},{10187,7,0,-1},{10192,6,0,-1},
+{10192,7,0,-1},{10196,6,0,-1},{10196,7,0,-1},{10200,6,0,-1},{10200,7,0,-1},
+{10205,6,0,-1},{10205,7,0,-1},{10209,6,0,-1},{10209,7,0,-1},{10213,6,0,-1},
+{10213,7,0,-1},{10218,6,0,-1},{10218,7,0,-1},{10222,6,0,-1},{10222,7,0,-1},
+{10226,6,0,-1},{10226,7,0,-1},{10231,6,0,-1},{10231,7,0,-1},{10235,6,0,-1},
+{10235,7,0,-1},{10239,6,0,-1},{10239,7,0,-1},{10244,6,0,-1},{10244,7,0,-1},
+{10248,6,0,-1},{10248,7,0,-1},{10178,6,0,-1},{10252,6,0,-1},{10252,7,0,-1},
+{10257,6,0,-1},{10257,7,0,-1},{10261,6,0,-1},{10261,7,0,-1},{10191,7,0,-1},
+{10265,6,0,-1},{10265,7,0,-1},{10270,6,0,-1},{10270,7,0,-1},{10274,6,0,-1},
+{10274,7,0,-1},{10278,6,0,-1},{10278,7,0,-1},{10283,6,0,-1},{10283,7,0,-1},
+{10287,6,0,-1},{10287,7,0,-1},{10291,6,0,-1},{10291,7,0,-1},{10296,6,0,-1},
+{10296,7,0,-1},{10300,6,0,-1},{10300,7,0,-1},{10304,6,0,-1},{10304,7,0,-1},
+{10309,6,0,-1},{10309,7,0,-1},{10313,6,0,-1},{10313,7,0,-1},{10317,6,0,-1},
+{10317,7,0,-1},{10322,6,0,-1},{10322,7,0,-1},{10326,6,0,-1},{10326,7,0,-1},
+{10330,6,0,-1},{10330,7,0,-1},{10335,6,0,-1},{10335,7,0,-1},{10339,6,0,-1},
+{10339,7,0,-1},{10343,6,0,-1},{10343,7,0,-1},{10348,6,0,-1},{10348,7,0,-1},
+{10352,6,0,-1},{10352,7,0,-1},{10282,6,0,-1},{10356,6,0,-1},{10356,7,0,-1},
+{10361,6,0,-1},{10361,7,0,-1},{10365,6,0,-1},{10365,7,0,-1},{10295,7,0,-1},
+{10369,6,0,-1},{10369,7,0,-1},{10374,6,0,-1},{10374,7,0,-1},{10378,6,0,-1},
+{10378,7,0,-1},{10382,6,0,-1},{10382,7,0,-1},{10387,6,0,-1},{10387,7,0,-1},
+{10391,6,0,-1},{10391,7,0,-1},{10395,6,0,-1},{10395,7,0,-1},{10400,6,0,-1},
+{10400,7,0,-1},{10404,6,0,-1},{10404,7,0,-1},{10408,6,0,-1},{10408,7,0,-1},
+{10413,6,0,-1},{10413,7,0,-1},{10417,6,0,-1},{10417,7,0,-1},{10421,6,0,-1},
+{10421,7,0,-1},{10426,6,0,-1},{10426,7,0,-1},{10430,6,0,-1},{10430,7,0,-1},
+{10434,6,0,-1},{10434,7,0,-1},{10439,6,0,-1},{10439,7,0,-1},{10443,6,0,-1},
+{10443,7,0,-1},{10447,6,0,-1},{10447,7,0,-1},{10452,6,0,-1},{10452,7,0,-1},
+{10456,6,0,-1},{10456,7,0,-1},{10386,6,0,-1},{10460,6,0,-1},{10460,7,0,-1},
+{10465,6,0,-1},{10465,7,0,-1},{10469,6,0,-1},{10469,7,0,-1},{10399,7,0,-1},
+{10473,6,0,-1},{10473,7,0,-1},{10478,6,0,-1},{10478,7,0,-1},{10482,6,0,-1},
+{10482,7,0,-1},{10486,6,0,-1},{10486,7,0,-1},{10491,6,0,-1},{10491,7,0,-1},
+{10495,6,0,-1},{10495,7,0,-1},{10499,6,0,-1},{10499,7,0,-1},{10504,6,0,-1},
+{10504,7,0,-1},{10508,6,0,-1},{10508,7,0,-1},{10512,6,0,-1},{10512,7,0,-1},
+{10517,6,0,-1},{10517,7,0,-1},{10521,6,0,-1},{10521,7,0,-1},{10525,6,0,-1},
+{10525,7,0,-1},{10530,6,0,-1},{10530,7,0,-1},{10534,6,0,-1},{10534,7,0,-1},
+{10538,6,0,-1},{10538,7,0,-1},{10543,6,0,-1},{10543,7,0,-1},{10547,6,0,-1},
+{10547,7,0,-1},{10551,6,0,-1},{10551,7,0,-1},{10556,6,0,-1},{10556,7,0,-1},
+{10560,6,0,-1},{10560,7,0,-1},{10490,6,0,-1},{10564,6,0,-1},{10564,7,0,-1},
+{10569,6,0,-1},{10569,7,0,-1},{10573,6,0,-1},{10573,7,0,-1},{10503,7,0,-1},
+{10577,6,0,-1},{10577,7,0,-1},{10582,6,0,-1},{10582,7,0,-1},{10586,6,0,-1},
+{10586,7,0,-1},{10590,6,0,-1},{10590,7,0,-1},{10595,6,0,-1},{10595,7,0,-1},
+{10599,6,0,-1},{10599,7,0,-1},{10603,6,0,-1},{10603,7,0,-1},{10608,6,0,-1},
+{10608,7,0,-1},{10612,6,0,-1},{10612,7,0,-1},{10616,6,0,-1},{10616,7,0,-1},
+{10621,6,0,-1},{10621,7,0,-1},{10625,6,0,-1},{10625,7,0,-1},{10629,6,0,-1},
+{10629,7,0,-1},{10634,6,0,-1},{10634,7,0,-1},{10638,6,0,-1},{10638,7,0,-1},
+{10642,6,0,-1},{10642,7,0,-1},{10647,6,0,-1},{10647,7,0,-1},{10651,6,0,-1},
+{10651,7,0,-1},{10655,6,0,-1},{10655,7,0,-1},{10660,6,0,-1},{10660,7,0,-1},
+{10664,6,0,-1},{10664,7,0,-1},{10594,6,0,-1},{10668,6,0,-1},{10668,7,0,-1},
+{10673,6,0,-1},{10673,7,0,-1},{10677,6,0,-1},{10677,7,0,-1},{10607,7,0,-1},
+{10681,6,0,-1},{10681,7,0,-1},{10686,6,0,-1},{10686,7,0,-1},{10690,6,0,-1},
+{10690,7,0,-1},{10694,6,0,-1},{10694,7,0,-1},{10699,6,0,-1},{10699,7,0,-1},
+{10703,6,0,-1},{10703,7,0,-1},{10707,6,0,-1},{10707,7,0,-1},{10712,6,0,-1},
+{10712,7,0,-1},{10716,6,0,-1},{10716,7,0,-1},{10720,6,0,-1},{10720,7,0,-1},
+{10725,6,0,-1},{10725,7,0,-1},{10729,6,0,-1},{10729,7,0,-1},{10733,6,0,-1},
+{10733,7,0,-1},{10738,6,0,-1},{10738,7,0,-1},{10742,6,0,-1},{10742,7,0,-1},
+{10746,6,0,-1},{10746,7,0,-1},{10751,6,0,-1},{10751,7,0,-1},{10755,6,0,-1},
+{10755,7,0,-1},{10759,6,0,-1},{10759,7,0,-1},{10764,6,0,-1},{10764,7,0,-1},
+{10768,6,0,-1},{10768,7,0,-1},{10698,6,0,-1},{10772,6,0,-1},{10772,7,0,-1},
+{10777,6,0,-1},{10777,7,0,-1},{10781,6,0,-1},{10781,7,0,-1},{10711,7,0,-1},
+{10785,6,0,-1},{10785,7,0,-1},{10790,6,0,-1},{10790,7,0,-1},{10794,6,0,-1},
+{10794,7,0,-1},{10798,6,0,-1},{10798,7,0,-1},{10803,6,0,-1},{10803,7,0,-1},
+{10807,6,0,-1},{10807,7,0,-1},{10811,6,0,-1},{10811,7,0,-1},{10816,6,0,-1},
+{10816,7,0,-1},{10820,6,0,-1},{10820,7,0,-1},{10824,6,0,-1},{10824,7,0,-1},
+{10829,6,0,-1},{10829,7,0,-1},{10833,6,0,-1},{10833,7,0,-1},{10837,6,0,-1},
+{10837,7,0,-1},{10842,6,0,-1},{10842,7,0,-1},{10846,6,0,-1},{10846,7,0,-1},
+{10850,6,0,-1},{10850,7,0,-1},{10855,6,0,-1},{10855,7,0,-1},{10859,6,0,-1},
+{10859,7,0,-1},{10863,6,0,-1},{10863,7,0,-1},{10868,6,0,-1},{10868,7,0,-1},
+{10872,6,0,-1},{10872,7,0,-1},{10802,6,0,-1},{10876,6,0,-1},{10876,7,0,-1},
+{10881,6,0,-1},{10881,7,0,-1},{10885,6,0,-1},{10885,7,0,-1},{10815,7,0,-1},
+{10889,6,0,-1},{10889,7,0,-1},{10894,6,0,-1},{10894,7,0,-1},{10898,6,0,-1},
+{10898,7,0,-1},{10902,6,0,-1},{10902,7,0,-1},{10907,6,0,-1},{10907,7,0,-1},
+{10911,6,0,-1},{10911,7,0,-1},{10915,6,0,-1},{10915,7,0,-1},{10920,6,0,-1},
+{10920,7,0,-1},{10924,6,0,-1},{10924,7,0,-1},{10928,6,0,-1},{10928,7,0,-1},
+{10933,6,0,-1},{10933,7,0,-1},{10937,6,0,-1},{10937,7,0,-1},{10941,6,0,-1},
+{10941,7,0,-1},{10946,6,0,-1},{10946,7,0,-1},{10950,6,0,-1},{10950,7,0,-1},
+{10954,6,0,-1},{10954,7,0,-1},{10959,6,0,-1},{10959,7,0,-1},{10963,6,0,-1},
+{10963,7,0,-1},{10967,6,0,-1},{10967,7,0,-1},{10972,6,0,-1},{10972,7,0,-1},
+{10976,6,0,-1},{10976,7,0,-1},{10906,6,0,-1},{10980,6,0,-1},{10980,7,0,-1},
+{10985,6,0,-1},{10985,7,0,-1},{10989,6,0,-1},{10989,7,0,-1},{10919,7,0,-1},
+{10993,6,0,-1},{10993,7,0,-1},{10998,6,0,-1},{10998,7,0,-1},{11002,6,0,-1},
+{11002,7,0,-1},{11006,6,0,-1},{11006,7,0,-1},{11011,6,0,-1},{11011,7,0,-1},
+{11015,6,0,-1},{11015,7,0,-1},{11019,6,0,-1},{11019,7,0,-1},{11024,6,0,-1},
+{11024,7,0,-1},{11028,6,0,-1},{11028,7,0,-1},{11032,6,0,-1},{11032,7,0,-1},
+{11037,6,0,-1},{11037,7,0,-1},{11041,6,0,-1},{11041,7,0,-1},{11045,6,0,-1},
+{11045,7,0,-1},{11050,6,0,-1},{11050,7,0,-1},{11054,6,0,-1},{11054,7,0,-1},
+{11058,6,0,-1},{11058,7,0,-1},{11063,6,0,-1},{11063,7,0,-1},{11067,6,0,-1},
+{11067,7,0,-1},{11071,6,0,-1},{11071,7,0,-1},{11076,6,0,-1},{11076,7,0,-1},
+{11080,6,0,-1},{11080,7,0,-1},{11010,6,0,-1},{11084,6,0,-1},{11084,7,0,-1},
+{11089,6,0,-1},{11089,7,0,-1},{11093,6,0,-1},{11093,7,0,-1},{11023,7,0,-1},
+{11097,6,0,-1},{11097,7,0,-1},{11102,6,0,-1},{11102,7,0,-1},{11106,6,0,-1},
+{11106,7,0,-1},{11110,6,0,-1},{11110,7,0,-1},{11115,6,0,-1},{11115,7,0,-1},
+{11119,6,0,-1},{11119,7,0,-1},{11123,6,0,-1},{11123,7,0,-1},{11128,6,0,-1},
+{11128,7,0,-1},{11132,6,0,-1},{11132,7,0,-1},{11136,6,0,-1},{11136,7,0,-1},
+{11141,6,0,-1},{11141,7,0,-1},{11145,6,0,-1},{11145,7,0,-1},{11149,6,0,-1},
+{11149,7,0,-1},{11154,6,0,-1},{11154,7,0,-1},{11158,6,0,-1},{11158,7,0,-1},
+{11162,6,0,-1},{11162,7,0,-1},{11167,6,0,-1},{11167,7,0,-1},{11171,6,0,-1},
+{11171,7,0,-1},{11175,6,0,-1},{11175,7,0,-1},{11180,6,0,-1},{11180,7,0,-1},
+{11184,6,0,-1},{11184,7,0,-1},{11114,6,0,-1},{11188,6,0,-1},{11188,7,0,-1},
+{11193,6,0,-1},{11193,7,0,-1},{11197,6,0,-1},{11197,7,0,-1},{11127,7,0,-1},
+{11201,6,0,-1},{11201,7,0,-1},{11206,6,0,-1},{11206,7,0,-1},{11210,6,0,-1},
+{11210,7,0,-1},{11214,6,0,-1},{11214,7,0,-1},{11219,6,0,-1},{11219,7,0,-1},
+{11223,6,0,-1},{11223,7,0,-1},{11227,6,0,-1},{11227,7,0,-1},{11232,6,0,-1},
+{11232,7,0,-1},{11236,6,0,-1},{11236,7,0,-1},{11240,6,0,-1},{11240,7,0,-1},
+{11245,6,0,-1},{11245,7,0,-1},{11249,6,0,-1},{11249,7,0,-1},{11253,6,0,-1},
+{11253,7,0,-1},{11258,6,0,-1},{11258,7,0,-1},{11262,6,0,-1},{11262,7,0,-1},
+{11266,6,0,-1},{11266,7,0,-1},{11271,6,0,-1},{11271,7,0,-1},{11275,6,0,-1},
+{11275,7,0,-1},{11279,6,0,-1},{11279,7,0,-1},{11284,6,0,-1},{11284,7,0,-1},
+{11288,6,0,-1},{11288,7,0,-1},{11218,6,0,-1},{11292,6,0,-1},{11292,7,0,-1},
+{11297,6,0,-1},{11297,7,0,-1},{11301,6,0,-1},{11301,7,0,-1},{11231,7,0,-1},
+{11305,6,0,-1},{11305,7,0,-1},{11310,6,0,-1},{11310,7,0,-1},{11314,6,0,-1},
+{11314,7,0,-1},{11318,6,0,-1},{11318,7,0,-1},{11323,6,0,-1},{11323,7,0,-1},
+{11327,6,0,-1},{11327,7,0,-1},{11331,6,0,-1},{11331,7,0,-1},{11336,6,0,-1},
+{11336,7,0,-1},{11340,6,0,-1},{11340,7,0,-1},{11344,6,0,-1},{11344,7,0,-1},
+{11349,6,0,-1},{11349,7,0,-1},{11353,6,0,-1},{11353,7,0,-1},{11357,6,0,-1},
+{11357,7,0,-1},{11362,6,0,-1},{11362,7,0,-1},{11366,6,0,-1},{11366,7,0,-1},
+{11370,6,0,-1},{11370,7,0,-1},{11375,6,0,-1},{11375,7,0,-1},{11379,6,0,-1},
+{11379,7,0,-1},{11383,6,0,-1},{11383,7,0,-1},{11388,6,0,-1},{11388,7,0,-1},
+{11392,6,0,-1},{11392,7,0,-1},{11322,6,0,-1},{11396,6,0,-1},{11396,7,0,-1},
+{11401,6,0,-1},{11401,7,0,-1},{11405,6,0,-1},{11405,7,0,-1},{11335,7,0,-1},
+{11409,6,0,-1},{11409,7,0,-1},{11414,6,0,-1},{11414,7,0,-1},{11418,6,0,-1},
+{11418,7,0,-1},{11422,6,0,-1},{11422,7,0,-1},{11427,6,0,-1},{11427,7,0,-1},
+{11431,6,0,-1},{11431,7,0,-1},{11435,6,0,-1},{11435,7,0,-1},{11440,6,0,-1},
+{11440,7,0,-1},{11444,6,0,-1},{11444,7,0,-1},{11448,6,0,-1},{11448,7,0,-1},
+{11453,6,0,-1},{11453,7,0,-1},{11457,6,0,-1},{11457,7,0,-1},{11461,6,0,-1},
+{11461,7,0,-1},{11466,6,0,-1},{11466,7,0,-1},{11470,6,0,-1},{11470,7,0,-1},
+{11474,6,0,-1},{11474,7,0,-1},{11479,6,0,-1},{11479,7,0,-1},{11483,6,0,-1},
+{11483,7,0,-1},{11487,6,0,-1},{11487,7,0,-1},{11492,6,0,-1},{11492,7,0,-1},
+{11496,6,0,-1},{11496,7,0,-1},{11426,6,0,-1},{11500,6,0,-1},{11500,7,0,-1},
+{11505,6,0,-1},{11505,7,0,-1},{11509,6,0,-1},{11509,7,0,-1},{11439,7,0,-1},
+{11513,6,0,-1},{11513,7,0,-1},{11518,6,0,-1},{11518,7,0,-1},{11522,6,0,-1},
+{11522,7,0,-1},{11526,6,0,-1},{11526,7,0,-1},{11531,6,0,-1},{11531,7,0,-1},
+{11535,6,0,-1},{11535,7,0,-1},{11539,6,0,-1},{11539,7,0,-1},{11544,6,0,-1},
+{11544,7,0,-1},{11548,6,0,-1},{11548,7,0,-1},{11552,6,0,-1},{11552,7,0,-1},
+{11557,6,0,-1},{11557,7,0,-1},{11561,6,0,-1},{11561,7,0,-1},{11565,6,0,-1},
+{11565,7,0,-1},{11570,6,0,-1},{11570,7,0,-1},{11574,6,0,-1},{11574,7,0,-1},
+{11578,6,0,-1},{11578,7,0,-1},{11583,6,0,-1},{11583,7,0,-1},{11587,6,0,-1},
+{11587,7,0,-1},{11591,6,0,-1},{11591,7,0,-1},{11596,6,0,-1},{11596,7,0,-1},
+{11600,6,0,-1},{11600,7,0,-1},{11530,6,0,-1},{11604,6,0,-1},{11604,7,0,-1},
+{11609,6,0,-1},{11609,7,0,-1},{11613,6,0,-1},{11613,7,0,-1},{11543,7,0,-1},
+{11617,6,0,-1},{11617,7,0,-1},{11622,6,0,-1},{11622,7,0,-1},{11626,6,0,-1},
+{11626,7,0,-1},{11630,6,0,-1},{11630,7,0,-1},{11635,6,0,-1},{11635,7,0,-1},
+{11639,6,0,-1},{11639,7,0,-1},{11643,6,0,-1},{11643,7,0,-1},{11648,6,0,-1},
+{11648,7,0,-1},{11652,6,0,-1},{11652,7,0,-1},{11656,6,0,-1},{11656,7,0,-1},
+{11661,6,0,-1},{11661,7,0,-1},{11665,6,0,-1},{11665,7,0,-1},{11669,6,0,-1},
+{11669,7,0,-1},{11674,6,0,-1},{11674,7,0,-1},{11678,6,0,-1},{11678,7,0,-1},
+{11682,6,0,-1},{11682,7,0,-1},{11687,6,0,-1},{11687,7,0,-1},{11691,6,0,-1},
+{11691,7,0,-1},{11695,6,0,-1},{11695,7,0,-1},{11700,6,0,-1},{11700,7,0,-1},
+{11704,6,0,-1},{11704,7,0,-1},{11634,6,0,-1},{11708,6,0,-1},{11708,7,0,-1},
+{11713,6,0,-1},{11713,7,0,-1},{11717,6,0,-1},{11717,7,0,-1},{11647,7,0,-1},
+{11721,6,0,-1},{11721,7,0,-1},{11726,6,0,-1},{11726,7,0,-1},{11730,6,0,-1},
+{11730,7,0,-1},{11734,6,0,-1},{11734,7,0,-1},{11739,6,0,-1},{11739,7,0,-1},
+{11743,6,0,-1},{11743,7,0,-1},{11747,6,0,-1},{11747,7,0,-1},{11752,6,0,-1},
+{11752,7,0,-1},{11756,6,0,-1},{11756,7,0,-1},{11760,6,0,-1},{11760,7,0,-1},
+{11765,6,0,-1},{11765,7,0,-1},{11769,6,0,-1},{11769,7,0,-1},{11773,6,0,-1},
+{11773,7,0,-1},{11778,6,0,-1},{11778,7,0,-1},{11782,6,0,-1},{11782,7,0,-1},
+{11786,6,0,-1},{11786,7,0,-1},{11791,6,0,-1},{11791,7,0,-1},{11795,6,0,-1},
+{11795,7,0,-1},{11799,6,0,-1},{11799,7,0,-1},{11804,6,0,-1},{11804,7,0,-1},
+{11808,6,0,-1},{11808,7,0,-1},{11738,6,0,-1},{11812,6,0,-1},{11812,7,0,-1},
+{11817,6,0,-1},{11817,7,0,-1},{11821,6,0,-1},{11821,7,0,-1},{11751,7,0,-1},
+{11825,6,0,-1},{11825,7,0,-1},{11830,6,0,-1},{11830,7,0,-1},{11834,6,0,-1},
+{11834,7,0,-1},{11838,6,0,-1},{11838,7,0,-1},{11843,6,0,-1},{11843,7,0,-1},
+{11847,6,0,-1},{11847,7,0,-1},{11851,6,0,-1},{11851,7,0,-1},{11856,6,0,-1},
+{11856,7,0,-1},{11860,6,0,-1},{11860,7,0,-1},{11864,6,0,-1},{11864,7,0,-1},
+{11869,6,0,-1},{11869,7,0,-1},{11873,6,0,-1},{11873,7,0,-1},{11877,6,0,-1},
+{11877,7,0,-1},{11882,6,0,-1},{11882,7,0,-1},{11886,6,0,-1},{11886,7,0,-1},
+{11890,6,0,-1},{11890,7,0,-1},{11895,6,0,-1},{11895,7,0,-1},{11899,6,0,-1},
+{11899,7,0,-1},{11903,6,0,-1},{11903,7,0,-1},{11908,6,0,-1},{11908,7,0,-1},
+{11912,6,0,-1},{11912,7,0,-1},{11842,6,0,-1},{11916,6,0,-1},{11916,7,0,-1},
+{11921,6,0,-1},{11921,7,0,-1},{11925,6,0,-1},{11925,7,0,-1},{11855,7,0,-1},
+{11929,6,0,-1},{11929,7,0,-1},{11934,6,0,-1},{11934,7,0,-1},{11938,6,0,-1},
+{11938,7,0,-1},{11942,6,0,-1},{11942,7,0,-1},{11947,6,0,-1},{11947,7,0,-1},
+{11951,6,0,-1},{11951,7,0,-1},{11955,6,0,-1},{11955,7,0,-1},{11960,6,0,-1},
+{11960,7,0,-1},{11964,6,0,-1},{11964,7,0,-1},{11968,6,0,-1},{11968,7,0,-1},
+{11973,6,0,-1},{11973,7,0,-1},{11977,6,0,-1},{11977,7,0,-1},{11981,6,0,-1},
+{11981,7,0,-1},{11986,6,0,-1},{11986,7,0,-1},{11990,6,0,-1},{11990,7,0,-1},
+{11994,6,0,-1},{11994,7,0,-1},{11999,6,0,-1},{11999,7,0,-1},{12003,6,0,-1},
+{12003,7,0,-1},{12007,6,0,-1},{12007,7,0,-1},{12012,6,0,-1},{12012,7,0,-1},
+{12016,6,0,-1},{12016,7,0,-1},{11946,6,0,-1},{12020,6,0,-1},{12020,7,0,-1},
+{12025,6,0,-1},{12025,7,0,-1},{12029,6,0,-1},{12029,7,0,-1},{11959,7,0,-1},
+{12033,6,0,-1},{12033,7,0,-1},{12038,6,0,-1},{12038,7,0,-1},{12042,6,0,-1},
+{12042,7,0,-1},{12046,6,0,-1},{12046,7,0,-1},{12051,6,0,-1},{12051,7,0,-1},
+{12055,6,0,-1},{12055,7,0,-1},{12059,6,0,-1},{12059,7,0,-1},{12064,6,0,-1},
+{12064,7,0,-1},{12068,6,0,-1},{12068,7,0,-1},{12072,6,0,-1},{12072,7,0,-1},
+{12077,6,0,-1},{12077,7,0,-1},{12081,6,0,-1},{12081,7,0,-1},{12085,6,0,-1},
+{12085,7,0,-1},{12090,6,0,-1},{12090,7,0,-1},{12094,6,0,-1},{12094,7,0,-1},
+{12098,6,0,-1},{12098,7,0,-1},{12103,6,0,-1},{12103,7,0,-1},{12107,6,0,-1},
+{12107,7,0,-1},{12111,6,0,-1},{12111,7,0,-1},{12116,6,0,-1},{12116,7,0,-1},
+{12120,6,0,-1},{12120,7,0,-1},{12050,6,0,-1},{12124,6,0,-1},{12124,7,0,-1},
+{12129,6,0,-1},{12129,7,0,-1},{12133,6,0,-1},{12133,7,0,-1},{12063,7,0,-1},
+{12137,6,0,-1},{12137,7,0,-1},{12142,6,0,-1},{12142,7,0,-1},{12146,6,0,-1},
+{12146,7,0,-1},{12150,6,0,-1},{12150,7,0,-1},{12155,6,0,-1},{12155,7,0,-1},
+{12159,6,0,-1},{12159,7,0,-1},{12163,6,0,-1},{12163,7,0,-1},{12168,6,0,-1},
+{12168,7,0,-1},{12172,6,0,-1},{12172,7,0,-1},{12176,6,0,-1},{12176,7,0,-1},
+{12181,6,0,-1},{12181,7,0,-1},{12185,6,0,-1},{12185,7,0,-1},{12189,6,0,-1},
+{12189,7,0,-1},{12194,6,0,-1},{12194,7,0,-1},{12198,6,0,-1},{12198,7,0,-1},
+{12202,6,0,-1},{12202,7,0,-1},{12207,6,0,-1},{12207,7,0,-1},{12211,6,0,-1},
+{12211,7,0,-1},{12215,6,0,-1},{12215,7,0,-1},{12220,6,0,-1},{12220,7,0,-1},
+{12224,6,0,-1},{12224,7,0,-1},{12154,6,0,-1},{12228,6,0,-1},{12228,7,0,-1},
+{12233,6,0,-1},{12233,7,0,-1},{12237,6,0,-1},{12237,7,0,-1},{12167,7,0,-1},
+{12241,6,0,-1},{12241,7,0,-1},{12246,6,0,-1},{12246,7,0,-1},{12250,7,0,-1},
+{12254,7,0,-1},{12259,6,0,-1},{12259,7,0,-1},{12263,7,0,-1},{12267,7,0,-1},
+{12272,6,0,-1},{12272,7,0,-1},{12276,6,0,-1},{12276,7,0,-1},{12280,6,0,-1},
+{12280,7,0,-1},{12285,6,0,-1},{12285,7,0,-1},{12289,7,0,-1},{12293,7,0,-1},
+{12298,7,0,-1},{12302,7,0,-1},{12306,6,0,-1},{12306,7,0,-1},{12311,6,0,-1},
+{12311,7,0,-1},{12315,7,0,-1},{12319,7,0,-1},{12324,7,0,-1},{12328,7,0,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on half rate channels TS2, SS0 and SS1 */
+struct fn_sample test_fn_tch_h_ts_2_ss0_ss1[] = {
+{8252,0,1,-1},{8987,2,0,-1},{8996,2,0,-1},{9004,2,0,-1},{9013,2,0,-1},
+{9022,2,0,-1},{9030,2,0,-1},{9039,2,0,-1},{9043,2,0,-1},{9048,2,0,-1},
+{8982,2,0,-1},{9056,2,0,-1},{9065,2,0,-1},{9074,2,0,-1},{9082,2,0,-1},
+{9091,2,0,-1},{9100,2,0,-1},{9108,2,0,-1},{9117,2,0,-1},{9126,2,0,-1},
+{9134,2,0,-1},{9143,2,0,-1},{9152,2,0,-1},{9086,2,0,-1},{9160,2,0,-1},
+{9169,2,0,-1},{9173,2,0,-1},{9178,2,0,-1},{9182,2,0,-1},{9186,2,0,-1},
+{9191,2,0,-1},{9195,2,0,-1},{9199,2,0,-1},{9204,2,0,-1},{9208,2,0,-1},
+{9212,2,0,-1},{9217,2,0,-1},{9221,2,0,-1},{9225,2,0,-1},{9230,2,0,-1},
+{9234,2,0,-1},{9238,2,0,-1},{9243,2,0,-1},{9247,2,0,-1},{9251,2,0,-1},
+{9256,2,0,-1},{9190,2,0,-1},{9260,2,0,-1},{9264,2,0,-1},{9269,2,0,-1},
+{9273,2,0,-1},{9277,2,0,-1},{9282,2,0,-1},{9286,2,0,-1},{9290,2,0,-1},
+{9295,2,0,-1},{9299,2,0,-1},{9303,2,0,-1},{9308,2,0,-1},{9312,2,0,-1},
+{9316,2,0,-1},{9321,2,0,-1},{9325,2,0,-1},{9329,2,0,-1},{9334,2,0,-1},
+{9338,2,0,-1},{9342,2,0,-1},{9347,2,0,-1},{9351,2,0,-1},{9355,2,0,-1},
+{9360,2,0,-1},{9294,2,0,-1},{9364,2,0,-1},{9368,2,0,-1},{9369,2,1,-1},
+{9373,2,0,-1},{9377,2,0,-1},{9381,2,0,-1},{9382,2,1,-1},{9386,2,0,-1},
+{9390,2,0,-1},{9391,2,1,-1},{9394,2,0,-1},{9395,2,1,-1},{9399,2,0,-1},
+{9403,2,0,-1},{9404,2,1,-1},{9407,2,0,-1},{9412,2,0,-1},{9413,2,1,-1},
+{9416,2,0,-1},{9420,2,0,-1},{9421,2,1,-1},{9425,2,0,-1},{9429,2,0,-1},
+{9430,2,1,-1},{9433,2,0,-1},{9438,2,0,-1},{9439,2,1,-1},{9442,2,0,-1},
+{9446,2,0,-1},{9447,2,1,-1},{9451,2,0,-1},{9455,2,0,-1},{9456,2,1,-1},
+{9459,2,0,-1},{9464,2,0,-1},{9465,2,1,-1},{9398,2,0,-1},{9468,2,0,-1},
+{9472,2,0,-1},{9473,2,1,-1},{9477,2,0,-1},{9411,2,1,-1},{9481,2,0,-1},
+{9482,2,1,-1},{9485,2,0,-1},{9490,2,0,-1},{9491,2,1,-1},{9494,2,0,-1},
+{9498,2,0,-1},{9499,2,1,-1},{9503,2,0,-1},{9504,2,1,-1},{9507,2,0,-1},
+{9508,2,1,-1},{9511,2,0,-1},{9512,2,1,-1},{9516,2,0,-1},{9517,2,1,-1},
+{9520,2,0,-1},{9521,2,1,-1},{9524,2,0,-1},{9525,2,1,-1},{9529,2,0,-1},
+{9533,2,0,-1},{9534,2,1,-1},{9537,2,0,-1},{9538,2,1,-1},{9542,2,0,-1},
+{9543,2,1,-1},{9546,2,0,-1},{9547,2,1,-1},{9550,2,0,-1},{9551,2,1,-1},
+{9555,2,0,-1},{9556,2,1,-1},{9559,2,0,-1},{9560,2,1,-1},{9563,2,0,-1},
+{9564,2,1,-1},{9568,2,0,-1},{9569,2,1,-1},{9502,2,0,-1},{9573,2,1,-1},
+{9576,2,0,-1},{9577,2,1,-1},{9581,2,0,-1},{9582,2,1,-1},{9515,2,1,-1},
+{9585,2,0,-1},{9586,2,1,-1},{9589,2,0,-1},{9590,2,1,-1},{9594,2,0,-1},
+{9595,2,1,-1},{9598,2,0,-1},{9599,2,1,-1},{9602,2,0,-1},{9603,2,1,-1},
+{9607,2,0,-1},{9608,2,1,-1},{9611,2,0,-1},{9612,2,1,-1},{9615,2,0,-1},
+{9616,2,1,-1},{9620,2,0,-1},{9621,2,1,-1},{9624,2,0,-1},{9625,2,1,-1},
+{9628,2,0,-1},{9629,2,1,-1},{9633,2,0,-1},{9634,2,1,-1},{9637,2,0,-1},
+{9638,2,1,-1},{9641,2,0,-1},{9642,2,1,-1},{9646,2,0,-1},{9647,2,1,-1},
+{9650,2,0,-1},{9651,2,1,-1},{9654,2,0,-1},{9655,2,1,-1},{9659,2,0,-1},
+{9660,2,1,-1},{9663,2,0,-1},{9664,2,1,-1},{9667,2,0,-1},{9668,2,1,-1},
+{9672,2,0,-1},{9673,2,1,-1},{9606,2,0,-1},{9676,2,0,-1},{9677,2,1,-1},
+{9680,2,0,-1},{9681,2,1,-1},{9685,2,0,-1},{9686,2,1,-1},{9619,2,1,-1},
+{9689,2,0,-1},{9690,2,1,-1},{9693,2,0,-1},{9694,2,1,-1},{9698,2,0,-1},
+{9699,2,1,-1},{9702,2,0,-1},{9703,2,1,-1},{9706,2,0,-1},{9707,2,1,-1},
+{9711,2,0,-1},{9712,2,1,-1},{9715,2,0,-1},{9716,2,1,-1},{9719,2,0,-1},
+{9724,2,0,-1},{9725,2,1,-1},{9728,2,0,-1},{9729,2,1,-1},{9732,2,0,-1},
+{9733,2,1,-1},{9737,2,0,-1},{9738,2,1,-1},{9741,2,0,-1},{9742,2,1,-1},
+{9745,2,0,-1},{9746,2,1,-1},{9750,2,0,-1},{9751,2,1,-1},{9754,2,0,-1},
+{9755,2,1,-1},{9758,2,0,-1},{9759,2,1,-1},{9764,2,1,-1},{9767,2,0,-1},
+{9768,2,1,-1},{9771,2,0,-1},{9776,2,0,-1},{9777,2,1,-1},{9710,2,0,-1},
+{9780,2,0,-1},{9781,2,1,-1},{9784,2,0,-1},{9785,2,1,-1},{9789,2,0,-1},
+{9790,2,1,-1},{9723,2,1,-1},{9793,2,0,-1},{9794,2,1,-1},{9797,2,0,-1},
+{9798,2,1,-1},{9802,2,0,-1},{9803,2,1,-1},{9806,2,0,-1},{9807,2,1,-1},
+{9810,2,0,-1},{9811,2,1,-1},{9815,2,0,-1},{9816,2,1,-1},{9819,2,0,-1},
+{9820,2,1,-1},{9823,2,0,-1},{9824,2,1,-1},{9828,2,0,-1},{9829,2,1,-1},
+{9832,2,0,-1},{9833,2,1,-1},{9836,2,0,-1},{9837,2,1,-1},{9841,2,0,-1},
+{9842,2,1,-1},{9845,2,0,-1},{9846,2,1,-1},{9849,2,0,-1},{9850,2,1,-1},
+{9854,2,0,-1},{9855,2,1,-1},{9858,2,0,-1},{9859,2,1,-1},{9862,2,0,-1},
+{9863,2,1,-1},{9867,2,0,-1},{9868,2,1,-1},{9871,2,0,-1},{9872,2,1,-1},
+{9875,2,0,-1},{9876,2,1,-1},{9880,2,0,-1},{9881,2,1,-1},{9814,2,0,-1},
+{9884,2,0,-1},{9885,2,1,-1},{9888,2,0,-1},{9889,2,1,-1},{9893,2,0,-1},
+{9894,2,1,-1},{9827,2,1,-1},{9897,2,0,-1},{9898,2,1,-1},{9901,2,0,-1},
+{9902,2,1,-1},{9906,2,0,-1},{9907,2,1,-1},{9910,2,0,-1},{9911,2,1,-1},
+{9914,2,0,-1},{9915,2,1,-1},{9919,2,0,-1},{9920,2,1,-1},{9923,2,0,-1},
+{9924,2,1,-1},{9927,2,0,-1},{9928,2,1,-1},{9932,2,0,-1},{9933,2,1,-1},
+{9936,2,0,-1},{9937,2,1,-1},{9940,2,0,-1},{9941,2,1,-1},{9945,2,0,-1},
+{9946,2,1,-1},{9949,2,0,-1},{9950,2,1,-1},{9953,2,0,-1},{9954,2,1,-1},
+{9958,2,0,-1},{9959,2,1,-1},{9962,2,0,-1},{9963,2,1,-1},{9966,2,0,-1},
+{9967,2,1,-1},{9971,2,0,-1},{9972,2,1,-1},{9975,2,0,-1},{9976,2,1,-1},
+{9979,2,0,-1},{9980,2,1,-1},{9984,2,0,-1},{9985,2,1,-1},{9918,2,0,-1},
+{9988,2,0,-1},{9989,2,1,-1},{9992,2,0,-1},{9993,2,1,-1},{9997,2,0,-1},
+{9998,2,1,-1},{9931,2,1,-1},{10001,2,0,-1},{10002,2,1,-1},{10005,2,0,-1},
+{10006,2,1,-1},{10010,2,0,-1},{10011,2,1,-1},{10014,2,0,-1},{10015,2,1,-1},
+{10018,2,0,-1},{10019,2,1,-1},{10023,2,0,-1},{10024,2,1,-1},{10027,2,0,-1},
+{10028,2,1,-1},{10031,2,0,-1},{10032,2,1,-1},{10036,2,0,-1},{10037,2,1,-1},
+{10040,2,0,-1},{10041,2,1,-1},{10044,2,0,-1},{10045,2,1,-1},{10049,2,0,-1},
+{10050,2,1,-1},{10053,2,0,-1},{10054,2,1,-1},{10057,2,0,-1},{10058,2,1,-1},
+{10062,2,0,-1},{10063,2,1,-1},{10066,2,0,-1},{10067,2,1,-1},{10070,2,0,-1},
+{10071,2,1,-1},{10075,2,0,-1},{10076,2,1,-1},{10079,2,0,-1},{10080,2,1,-1},
+{10083,2,0,-1},{10084,2,1,-1},{10088,2,0,-1},{10089,2,1,-1},{10022,2,0,-1},
+{10092,2,0,-1},{10093,2,1,-1},{10096,2,0,-1},{10097,2,1,-1},{10101,2,0,-1},
+{10102,2,1,-1},{10035,2,1,-1},{10105,2,0,-1},{10106,2,1,-1},{10109,2,0,-1},
+{10110,2,1,-1},{10114,2,0,-1},{10115,2,1,-1},{10118,2,0,-1},{10119,2,1,-1},
+{10122,2,0,-1},{10123,2,1,-1},{10127,2,0,-1},{10128,2,1,-1},{10131,2,0,-1},
+{10132,2,1,-1},{10135,2,0,-1},{10136,2,1,-1},{10140,2,0,-1},{10141,2,1,-1},
+{10144,2,0,-1},{10145,2,1,-1},{10148,2,0,-1},{10149,2,1,-1},{10153,2,0,-1},
+{10154,2,1,-1},{10157,2,0,-1},{10158,2,1,-1},{10161,2,0,-1},{10162,2,1,-1},
+{10166,2,0,-1},{10167,2,1,-1},{10170,2,0,-1},{10171,2,1,-1},{10174,2,0,-1},
+{10175,2,1,-1},{10179,2,0,-1},{10180,2,1,-1},{10183,2,0,-1},{10184,2,1,-1},
+{10187,2,0,-1},{10188,2,1,-1},{10192,2,0,-1},{10193,2,1,-1},{10126,2,0,-1},
+{10196,2,0,-1},{10197,2,1,-1},{10200,2,0,-1},{10201,2,1,-1},{10205,2,0,-1},
+{10206,2,1,-1},{10139,2,1,-1},{10209,2,0,-1},{10210,2,1,-1},{10213,2,0,-1},
+{10214,2,1,-1},{10218,2,0,-1},{10219,2,1,-1},{10222,2,0,-1},{10223,2,1,-1},
+{10226,2,0,-1},{10227,2,1,-1},{10231,2,0,-1},{10232,2,1,-1},{10235,2,0,-1},
+{10236,2,1,-1},{10239,2,0,-1},{10240,2,1,-1},{10244,2,0,-1},{10245,2,1,-1},
+{10248,2,0,-1},{10249,2,1,-1},{10252,2,0,-1},{10253,2,1,-1},{10257,2,0,-1},
+{10258,2,1,-1},{10261,2,0,-1},{10262,2,1,-1},{10265,2,0,-1},{10266,2,1,-1},
+{10270,2,0,-1},{10271,2,1,-1},{10274,2,0,-1},{10275,2,1,-1},{10278,2,0,-1},
+{10279,2,1,-1},{10283,2,0,-1},{10284,2,1,-1},{10287,2,0,-1},{10288,2,1,-1},
+{10291,2,0,-1},{10292,2,1,-1},{10296,2,0,-1},{10297,2,1,-1},{10230,2,0,-1},
+{10300,2,0,-1},{10301,2,1,-1},{10304,2,0,-1},{10305,2,1,-1},{10309,2,0,-1},
+{10310,2,1,-1},{10243,2,1,-1},{10313,2,0,-1},{10314,2,1,-1},{10317,2,0,-1},
+{10318,2,1,-1},{10322,2,0,-1},{10323,2,1,-1},{10326,2,0,-1},{10327,2,1,-1},
+{10330,2,0,-1},{10331,2,1,-1},{10335,2,0,-1},{10336,2,1,-1},{10339,2,0,-1},
+{10340,2,1,-1},{10343,2,0,-1},{10344,2,1,-1},{10348,2,0,-1},{10349,2,1,-1},
+{10352,2,0,-1},{10353,2,1,-1},{10356,2,0,-1},{10357,2,1,-1},{10361,2,0,-1},
+{10362,2,1,-1},{10365,2,0,-1},{10366,2,1,-1},{10369,2,0,-1},{10370,2,1,-1},
+{10374,2,0,-1},{10375,2,1,-1},{10378,2,0,-1},{10379,2,1,-1},{10382,2,0,-1},
+{10383,2,1,-1},{10387,2,0,-1},{10388,2,1,-1},{10391,2,0,-1},{10392,2,1,-1},
+{10395,2,0,-1},{10396,2,1,-1},{10400,2,0,-1},{10401,2,1,-1},{10334,2,0,-1},
+{10404,2,0,-1},{10405,2,1,-1},{10408,2,0,-1},{10409,2,1,-1},{10413,2,0,-1},
+{10414,2,1,-1},{10347,2,1,-1},{10417,2,0,-1},{10418,2,1,-1},{10421,2,0,-1},
+{10422,2,1,-1},{10426,2,0,-1},{10427,2,1,-1},{10430,2,0,-1},{10431,2,1,-1},
+{10434,2,0,-1},{10435,2,1,-1},{10439,2,0,-1},{10440,2,1,-1},{10443,2,0,-1},
+{10444,2,1,-1},{10447,2,0,-1},{10448,2,1,-1},{10452,2,0,-1},{10453,2,1,-1},
+{10456,2,0,-1},{10457,2,1,-1},{10460,2,0,-1},{10461,2,1,-1},{10465,2,0,-1},
+{10466,2,1,-1},{10469,2,0,-1},{10470,2,1,-1},{10473,2,0,-1},{10474,2,1,-1},
+{10478,2,0,-1},{10479,2,1,-1},{10482,2,0,-1},{10483,2,1,-1},{10486,2,0,-1},
+{10487,2,1,-1},{10491,2,0,-1},{10492,2,1,-1},{10495,2,0,-1},{10496,2,1,-1},
+{10499,2,0,-1},{10500,2,1,-1},{10504,2,0,-1},{10505,2,1,-1},{10438,2,0,-1},
+{10508,2,0,-1},{10509,2,1,-1},{10512,2,0,-1},{10513,2,1,-1},{10517,2,0,-1},
+{10518,2,1,-1},{10451,2,1,-1},{10521,2,0,-1},{10522,2,1,-1},{10525,2,0,-1},
+{10526,2,1,-1},{10530,2,0,-1},{10531,2,1,-1},{10534,2,0,-1},{10535,2,1,-1},
+{10538,2,0,-1},{10539,2,1,-1},{10543,2,0,-1},{10544,2,1,-1},{10547,2,0,-1},
+{10548,2,1,-1},{10551,2,0,-1},{10552,2,1,-1},{10556,2,0,-1},{10557,2,1,-1},
+{10560,2,0,-1},{10561,2,1,-1},{10564,2,0,-1},{10565,2,1,-1},{10569,2,0,-1},
+{10570,2,1,-1},{10573,2,0,-1},{10574,2,1,-1},{10577,2,0,-1},{10578,2,1,-1},
+{10582,2,0,-1},{10583,2,1,-1},{10586,2,0,-1},{10587,2,1,-1},{10590,2,0,-1},
+{10591,2,1,-1},{10595,2,0,-1},{10596,2,1,-1},{10599,2,0,-1},{10600,2,1,-1},
+{10603,2,0,-1},{10604,2,1,-1},{10608,2,0,-1},{10609,2,1,-1},{10542,2,0,-1},
+{10612,2,0,-1},{10613,2,1,-1},{10616,2,0,-1},{10617,2,1,-1},{10621,2,0,-1},
+{10622,2,1,-1},{10555,2,1,-1},{10625,2,0,-1},{10626,2,1,-1},{10629,2,0,-1},
+{10630,2,1,-1},{10634,2,0,-1},{10635,2,1,-1},{10638,2,0,-1},{10639,2,1,-1},
+{10642,2,0,-1},{10643,2,1,-1},{10647,2,0,-1},{10648,2,1,-1},{10651,2,0,-1},
+{10652,2,1,-1},{10655,2,0,-1},{10656,2,1,-1},{10660,2,0,-1},{10661,2,1,-1},
+{10664,2,0,-1},{10665,2,1,-1},{10668,2,0,-1},{10669,2,1,-1},{10673,2,0,-1},
+{10674,2,1,-1},{10677,2,0,-1},{10678,2,1,-1},{10681,2,0,-1},{10682,2,1,-1},
+{10686,2,0,-1},{10687,2,1,-1},{10690,2,0,-1},{10691,2,1,-1},{10694,2,0,-1},
+{10695,2,1,-1},{10699,2,0,-1},{10700,2,1,-1},{10703,2,0,-1},{10704,2,1,-1},
+{10707,2,0,-1},{10708,2,1,-1},{10712,2,0,-1},{10713,2,1,-1},{10646,2,0,-1},
+{10716,2,0,-1},{10717,2,1,-1},{10720,2,0,-1},{10721,2,1,-1},{10725,2,0,-1},
+{10726,2,1,-1},{10659,2,1,-1},{10729,2,0,-1},{10730,2,1,-1},{10733,2,0,-1},
+{10734,2,1,-1},{10738,2,0,-1},{10739,2,1,-1},{10742,2,0,-1},{10743,2,1,-1},
+{10746,2,0,-1},{10747,2,1,-1},{10751,2,0,-1},{10752,2,1,-1},{10755,2,0,-1},
+{10756,2,1,-1},{10759,2,0,-1},{10760,2,1,-1},{10764,2,0,-1},{10765,2,1,-1},
+{10768,2,0,-1},{10769,2,1,-1},{10772,2,0,-1},{10773,2,1,-1},{10777,2,0,-1},
+{10778,2,1,-1},{10781,2,0,-1},{10782,2,1,-1},{10785,2,0,-1},{10786,2,1,-1},
+{10790,2,0,-1},{10791,2,1,-1},{10794,2,0,-1},{10795,2,1,-1},{10798,2,0,-1},
+{10799,2,1,-1},{10803,2,0,-1},{10804,2,1,-1},{10807,2,0,-1},{10808,2,1,-1},
+{10811,2,0,-1},{10812,2,1,-1},{10816,2,0,-1},{10817,2,1,-1},{10750,2,0,-1},
+{10820,2,0,-1},{10821,2,1,-1},{10824,2,0,-1},{10825,2,1,-1},{10829,2,0,-1},
+{10830,2,1,-1},{10763,2,1,-1},{10833,2,0,-1},{10834,2,1,-1},{10837,2,0,-1},
+{10838,2,1,-1},{10842,2,0,-1},{10843,2,1,-1},{10846,2,0,-1},{10847,2,1,-1},
+{10850,2,0,-1},{10851,2,1,-1},{10855,2,0,-1},{10856,2,1,-1},{10859,2,0,-1},
+{10860,2,1,-1},{10863,2,0,-1},{10864,2,1,-1},{10868,2,0,-1},{10869,2,1,-1},
+{10872,2,0,-1},{10873,2,1,-1},{10876,2,0,-1},{10877,2,1,-1},{10881,2,0,-1},
+{10882,2,1,-1},{10885,2,0,-1},{10886,2,1,-1},{10889,2,0,-1},{10890,2,1,-1},
+{10894,2,0,-1},{10895,2,1,-1},{10898,2,0,-1},{10899,2,1,-1},{10902,2,0,-1},
+{10903,2,1,-1},{10907,2,0,-1},{10908,2,1,-1},{10911,2,0,-1},{10912,2,1,-1},
+{10915,2,0,-1},{10916,2,1,-1},{10920,2,0,-1},{10921,2,1,-1},{10854,2,0,-1},
+{10924,2,0,-1},{10925,2,1,-1},{10928,2,0,-1},{10929,2,1,-1},{10933,2,0,-1},
+{10934,2,1,-1},{10867,2,1,-1},{10937,2,0,-1},{10938,2,1,-1},{10941,2,0,-1},
+{10942,2,1,-1},{10946,2,0,-1},{10947,2,1,-1},{10950,2,0,-1},{10951,2,1,-1},
+{10954,2,0,-1},{10955,2,1,-1},{10959,2,0,-1},{10960,2,1,-1},{10963,2,0,-1},
+{10964,2,1,-1},{10967,2,0,-1},{10968,2,1,-1},{10972,2,0,-1},{10973,2,1,-1},
+{10976,2,0,-1},{10977,2,1,-1},{10980,2,0,-1},{10981,2,1,-1},{10985,2,0,-1},
+{10986,2,1,-1},{10989,2,0,-1},{10990,2,1,-1},{10993,2,0,-1},{10994,2,1,-1},
+{10998,2,0,-1},{10999,2,1,-1},{11002,2,0,-1},{11003,2,1,-1},{11006,2,0,-1},
+{11007,2,1,-1},{11011,2,0,-1},{11012,2,1,-1},{11015,2,0,-1},{11016,2,1,-1},
+{11019,2,0,-1},{11020,2,1,-1},{11024,2,0,-1},{11025,2,1,-1},{10958,2,0,-1},
+{11028,2,0,-1},{11029,2,1,-1},{11032,2,0,-1},{11033,2,1,-1},{11037,2,0,-1},
+{11038,2,1,-1},{10971,2,1,-1},{11041,2,0,-1},{11042,2,1,-1},{11045,2,0,-1},
+{11046,2,1,-1},{11050,2,0,-1},{11051,2,1,-1},{11054,2,0,-1},{11055,2,1,-1},
+{11058,2,0,-1},{11059,2,1,-1},{11063,2,0,-1},{11064,2,1,-1},{11067,2,0,-1},
+{11068,2,1,-1},{11071,2,0,-1},{11072,2,1,-1},{11076,2,0,-1},{11077,2,1,-1},
+{11080,2,0,-1},{11081,2,1,-1},{11084,2,0,-1},{11085,2,1,-1},{11089,2,0,-1},
+{11090,2,1,-1},{11093,2,0,-1},{11094,2,1,-1},{11097,2,0,-1},{11098,2,1,-1},
+{11102,2,0,-1},{11103,2,1,-1},{11106,2,0,-1},{11107,2,1,-1},{11110,2,0,-1},
+{11111,2,1,-1},{11115,2,0,-1},{11116,2,1,-1},{11119,2,0,-1},{11120,2,1,-1},
+{11123,2,0,-1},{11124,2,1,-1},{11128,2,0,-1},{11129,2,1,-1},{11062,2,0,-1},
+{11132,2,0,-1},{11133,2,1,-1},{11136,2,0,-1},{11137,2,1,-1},{11141,2,0,-1},
+{11142,2,1,-1},{11075,2,1,-1},{11145,2,0,-1},{11146,2,1,-1},{11149,2,0,-1},
+{11150,2,1,-1},{11154,2,0,-1},{11155,2,1,-1},{11158,2,0,-1},{11159,2,1,-1},
+{11162,2,0,-1},{11163,2,1,-1},{11167,2,0,-1},{11168,2,1,-1},{11171,2,0,-1},
+{11172,2,1,-1},{11175,2,0,-1},{11176,2,1,-1},{11180,2,0,-1},{11181,2,1,-1},
+{11184,2,0,-1},{11185,2,1,-1},{11188,2,0,-1},{11189,2,1,-1},{11193,2,0,-1},
+{11194,2,1,-1},{11197,2,0,-1},{11198,2,1,-1},{11201,2,0,-1},{11202,2,1,-1},
+{11206,2,0,-1},{11207,2,1,-1},{11210,2,0,-1},{11211,2,1,-1},{11214,2,0,-1},
+{11215,2,1,-1},{11219,2,0,-1},{11220,2,1,-1},{11223,2,0,-1},{11224,2,1,-1},
+{11227,2,0,-1},{11228,2,1,-1},{11232,2,0,-1},{11233,2,1,-1},{11166,2,0,-1},
+{11236,2,0,-1},{11237,2,1,-1},{11240,2,0,-1},{11241,2,1,-1},{11245,2,0,-1},
+{11246,2,1,-1},{11179,2,1,-1},{11249,2,0,-1},{11250,2,1,-1},{11253,2,0,-1},
+{11254,2,1,-1},{11258,2,0,-1},{11259,2,1,-1},{11262,2,0,-1},{11263,2,1,-1},
+{11266,2,0,-1},{11267,2,1,-1},{11271,2,0,-1},{11272,2,1,-1},{11275,2,0,-1},
+{11276,2,1,-1},{11279,2,0,-1},{11280,2,1,-1},{11284,2,0,-1},{11285,2,1,-1},
+{11288,2,0,-1},{11289,2,1,-1},{11292,2,0,-1},{11293,2,1,-1},{11297,2,0,-1},
+{11298,2,1,-1},{11301,2,0,-1},{11302,2,1,-1},{11305,2,0,-1},{11306,2,1,-1},
+{11310,2,0,-1},{11311,2,1,-1},{11314,2,0,-1},{11315,2,1,-1},{11318,2,0,-1},
+{11319,2,1,-1},{11323,2,0,-1},{11324,2,1,-1},{11327,2,0,-1},{11328,2,1,-1},
+{11331,2,0,-1},{11332,2,1,-1},{11336,2,0,-1},{11337,2,1,-1},{11270,2,0,-1},
+{11340,2,0,-1},{11341,2,1,-1},{11344,2,0,-1},{11345,2,1,-1},{11349,2,0,-1},
+{11350,2,1,-1},{11283,2,1,-1},{11353,2,0,-1},{11354,2,1,-1},{11357,2,0,-1},
+{11358,2,1,-1},{11362,2,0,-1},{11363,2,1,-1},{11366,2,0,-1},{11367,2,1,-1},
+{11370,2,0,-1},{11371,2,1,-1},{11375,2,0,-1},{11376,2,1,-1},{11379,2,0,-1},
+{11380,2,1,-1},{11383,2,0,-1},{11384,2,1,-1},{11388,2,0,-1},{11389,2,1,-1},
+{11392,2,0,-1},{11393,2,1,-1},{11396,2,0,-1},{11397,2,1,-1},{11401,2,0,-1},
+{11402,2,1,-1},{11405,2,0,-1},{11406,2,1,-1},{11409,2,0,-1},{11410,2,1,-1},
+{11414,2,0,-1},{11415,2,1,-1},{11418,2,0,-1},{11419,2,1,-1},{11422,2,0,-1},
+{11423,2,1,-1},{11427,2,0,-1},{11428,2,1,-1},{11431,2,0,-1},{11432,2,1,-1},
+{11435,2,0,-1},{11436,2,1,-1},{11440,2,0,-1},{11441,2,1,-1},{11374,2,0,-1},
+{11444,2,0,-1},{11445,2,1,-1},{11448,2,0,-1},{11449,2,1,-1},{11453,2,0,-1},
+{11454,2,1,-1},{11387,2,1,-1},{11457,2,0,-1},{11458,2,1,-1},{11461,2,0,-1},
+{11462,2,1,-1},{11466,2,0,-1},{11467,2,1,-1},{11470,2,0,-1},{11471,2,1,-1},
+{11474,2,0,-1},{11475,2,1,-1},{11479,2,0,-1},{11480,2,1,-1},{11483,2,0,-1},
+{11484,2,1,-1},{11487,2,0,-1},{11488,2,1,-1},{11492,2,0,-1},{11493,2,1,-1},
+{11496,2,0,-1},{11497,2,1,-1},{11500,2,0,-1},{11501,2,1,-1},{11505,2,0,-1},
+{11506,2,1,-1},{11509,2,0,-1},{11510,2,1,-1},{11513,2,0,-1},{11514,2,1,-1},
+{11518,2,0,-1},{11519,2,1,-1},{11522,2,0,-1},{11523,2,1,-1},{11526,2,0,-1},
+{11527,2,1,-1},{11531,2,0,-1},{11532,2,1,-1},{11535,2,0,-1},{11536,2,1,-1},
+{11539,2,0,-1},{11540,2,1,-1},{11544,2,0,-1},{11545,2,1,-1},{11478,2,0,-1},
+{11548,2,0,-1},{11549,2,1,-1},{11552,2,0,-1},{11553,2,1,-1},{11557,2,0,-1},
+{11558,2,1,-1},{11491,2,1,-1},{11561,2,0,-1},{11562,2,1,-1},{11565,2,0,-1},
+{11566,2,1,-1},{11570,2,0,-1},{11571,2,1,-1},{11574,2,0,-1},{11575,2,1,-1},
+{11578,2,0,-1},{11579,2,1,-1},{11583,2,0,-1},{11584,2,1,-1},{11587,2,0,-1},
+{11588,2,1,-1},{11591,2,0,-1},{11596,2,0,-1},{11597,2,1,-1},{11600,2,0,-1},
+{11601,2,1,-1},{11604,2,0,-1},{11605,2,1,-1},{11609,2,0,-1},{11610,2,1,-1},
+{11613,2,0,-1},{11614,2,1,-1},{11617,2,0,-1},{11618,2,1,-1},{11622,2,0,-1},
+{11623,2,1,-1},{11626,2,0,-1},{11627,2,1,-1},{11630,2,0,-1},{11631,2,1,-1},
+{11635,2,0,-1},{11636,2,1,-1},{11639,2,0,-1},{11640,2,1,-1},{11648,2,0,-1},
+{11649,2,1,-1},{11582,2,0,-1},{11652,2,0,-1},{11653,2,1,-1},{11656,2,0,-1},
+{11657,2,1,-1},{11661,2,0,-1},{11662,2,1,-1},{11665,2,0,-1},{11666,2,1,-1},
+{11669,2,0,-1},{11670,2,1,-1},{11674,2,0,-1},{11675,2,1,-1},{11678,2,0,-1},
+{11679,2,1,-1},{11682,2,0,-1},{11683,2,1,-1},{11687,2,0,-1},{11688,2,1,-1},
+{11691,2,0,-1},{11692,2,1,-1},{11700,2,0,-1},{11704,2,0,-1},{11708,2,0,-1},
+{11713,2,0,-1},{11717,2,0,-1},{11721,2,0,-1},{11726,2,0,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on half rate channels TS3, SS0 and SS1 */
+struct fn_sample test_fn_tch_h_ts_3_ss0_ss1[] = {
+{10001,3,0,-1},{10002,3,1,-1},{10005,3,0,-1},{10006,3,1,-1},{10010,3,0,-1},
+{10011,3,1,-1},{10014,3,0,-1},{10015,3,1,-1},{10018,3,0,-1},{10019,3,1,-1},
+{10023,3,0,-1},{10024,3,1,-1},{10027,3,0,-1},{10028,3,1,-1},{10031,3,0,-1},
+{10032,3,1,-1},{10036,3,0,-1},{10037,3,1,-1},{10040,3,0,-1},{10041,3,1,-1},
+{10044,3,0,-1},{10045,3,1,-1},{10049,3,0,-1},{10050,3,1,-1},{10053,3,0,-1},
+{10054,3,1,-1},{10057,3,0,-1},{10058,3,1,-1},{10062,3,0,-1},{10063,3,1,-1},
+{10066,3,0,-1},{10067,3,1,-1},{10070,3,0,-1},{10071,3,1,-1},{10075,3,0,-1},
+{10076,3,1,-1},{10079,3,0,-1},{10080,3,1,-1},{10083,3,0,-1},{10084,3,1,-1},
+{10088,3,0,-1},{10089,3,1,-1},{10022,3,0,-1},{10092,3,0,-1},{10093,3,1,-1},
+{10096,3,0,-1},{10097,3,1,-1},{10101,3,0,-1},{10102,3,1,-1},{10035,3,1,-1},
+{10105,3,0,-1},{10106,3,1,-1},{10109,3,0,-1},{10110,3,1,-1},{10114,3,0,-1},
+{10115,3,1,-1},{10118,3,0,-1},{10119,3,1,-1},{10122,3,0,-1},{10123,3,1,-1},
+{10127,3,0,-1},{10128,3,1,-1},{10131,3,0,-1},{10132,3,1,-1},{10135,3,0,-1},
+{10136,3,1,-1},{10140,3,0,-1},{10141,3,1,-1},{10144,3,0,-1},{10145,3,1,-1},
+{10148,3,0,-1},{10149,3,1,-1},{10153,3,0,-1},{10154,3,1,-1},{10157,3,0,-1},
+{10158,3,1,-1},{10161,3,0,-1},{10162,3,1,-1},{10166,3,0,-1},{10167,3,1,-1},
+{10170,3,0,-1},{10171,3,1,-1},{10174,3,0,-1},{10175,3,1,-1},{10179,3,0,-1},
+{10180,3,1,-1},{10183,3,0,-1},{10184,3,1,-1},{10187,3,0,-1},{10188,3,1,-1},
+{10192,3,0,-1},{10193,3,1,-1},{10126,3,0,-1},{10196,3,0,-1},{10197,3,1,-1},
+{10200,3,0,-1},{10201,3,1,-1},{10205,3,0,-1},{10206,3,1,-1},{10139,3,1,-1},
+{10209,3,0,-1},{10210,3,1,-1},{10213,3,0,-1},{10214,3,1,-1},{10218,3,0,-1},
+{10219,3,1,-1},{10222,3,0,-1},{10223,3,1,-1},{10226,3,0,-1},{10227,3,1,-1},
+{10231,3,0,-1},{10232,3,1,-1},{10235,3,0,-1},{10236,3,1,-1},{10239,3,0,-1},
+{10240,3,1,-1},{10244,3,0,-1},{10245,3,1,-1},{10248,3,0,-1},{10249,3,1,-1},
+{10252,3,0,-1},{10253,3,1,-1},{10257,3,0,-1},{10258,3,1,-1},{10261,3,0,-1},
+{10262,3,1,-1},{10265,3,0,-1},{10266,3,1,-1},{10270,3,0,-1},{10271,3,1,-1},
+{10274,3,0,-1},{10275,3,1,-1},{10278,3,0,-1},{10279,3,1,-1},{10283,3,0,-1},
+{10284,3,1,-1},{10287,3,0,-1},{10288,3,1,-1},{10291,3,0,-1},{10292,3,1,-1},
+{10296,3,0,-1},{10297,3,1,-1},{10230,3,0,-1},{10300,3,0,-1},{10301,3,1,-1},
+{10304,3,0,-1},{10305,3,1,-1},{10309,3,0,-1},{10310,3,1,-1},{10243,3,1,-1},
+{10313,3,0,-1},{10314,3,1,-1},{10317,3,0,-1},{10318,3,1,-1},{10322,3,0,-1},
+{10323,3,1,-1},{10326,3,0,-1},{10327,3,1,-1},{10330,3,0,-1},{10331,3,1,-1},
+{10335,3,0,-1},{10336,3,1,-1},{10339,3,0,-1},{10340,3,1,-1},{10343,3,0,-1},
+{10344,3,1,-1},{10348,3,0,-1},{10349,3,1,-1},{10352,3,0,-1},{10353,3,1,-1},
+{10356,3,0,-1},{10357,3,1,-1},{10361,3,0,-1},{10362,3,1,-1},{10365,3,0,-1},
+{10366,3,1,-1},{10369,3,0,-1},{10370,3,1,-1},{10374,3,0,-1},{10375,3,1,-1},
+{10378,3,0,-1},{10379,3,1,-1},{10382,3,0,-1},{10383,3,1,-1},{10387,3,0,-1},
+{10388,3,1,-1},{10391,3,0,-1},{10392,3,1,-1},{10395,3,0,-1},{10396,3,1,-1},
+{10400,3,0,-1},{10401,3,1,-1},{10334,3,0,-1},{10404,3,0,-1},{10405,3,1,-1},
+{10408,3,0,-1},{10409,3,1,-1},{10413,3,0,-1},{10414,3,1,-1},{10347,3,1,-1},
+{10417,3,0,-1},{10418,3,1,-1},{10421,3,0,-1},{10422,3,1,-1},{10426,3,0,-1},
+{10427,3,1,-1},{10430,3,0,-1},{10431,3,1,-1},{10434,3,0,-1},{10435,3,1,-1},
+{10439,3,0,-1},{10440,3,1,-1},{10443,3,0,-1},{10444,3,1,-1},{10447,3,0,-1},
+{10448,3,1,-1},{10452,3,0,-1},{10453,3,1,-1},{10456,3,0,-1},{10457,3,1,-1},
+{10460,3,0,-1},{10461,3,1,-1},{10465,3,0,-1},{10466,3,1,-1},{10469,3,0,-1},
+{10470,3,1,-1},{10473,3,0,-1},{10474,3,1,-1},{10478,3,0,-1},{10479,3,1,-1},
+{10482,3,0,-1},{10483,3,1,-1},{10486,3,0,-1},{10487,3,1,-1},{10491,3,0,-1},
+{10492,3,1,-1},{10495,3,0,-1},{10496,3,1,-1},{10499,3,0,-1},{10500,3,1,-1},
+{10504,3,0,-1},{10505,3,1,-1},{10438,3,0,-1},{10508,3,0,-1},{10509,3,1,-1},
+{10512,3,0,-1},{10513,3,1,-1},{10517,3,0,-1},{10518,3,1,-1},{10451,3,1,-1},
+{10521,3,0,-1},{10522,3,1,-1},{10525,3,0,-1},{10526,3,1,-1},{10530,3,0,-1},
+{10531,3,1,-1},{10534,3,0,-1},{10535,3,1,-1},{10538,3,0,-1},{10539,3,1,-1},
+{10543,3,0,-1},{10544,3,1,-1},{10547,3,0,-1},{10548,3,1,-1},{10551,3,0,-1},
+{10552,3,1,-1},{10556,3,0,-1},{10557,3,1,-1},{10560,3,0,-1},{10561,3,1,-1},
+{10564,3,0,-1},{10565,3,1,-1},{10569,3,0,-1},{10570,3,1,-1},{10573,3,0,-1},
+{10574,3,1,-1},{10577,3,0,-1},{10578,3,1,-1},{10582,3,0,-1},{10583,3,1,-1},
+{10586,3,0,-1},{10587,3,1,-1},{10590,3,0,-1},{10591,3,1,-1},{10595,3,0,-1},
+{10596,3,1,-1},{10599,3,0,-1},{10600,3,1,-1},{10603,3,0,-1},{10604,3,1,-1},
+{10608,3,0,-1},{10609,3,1,-1},{10542,3,0,-1},{10612,3,0,-1},{10613,3,1,-1},
+{10616,3,0,-1},{10617,3,1,-1},{10621,3,0,-1},{10622,3,1,-1},{10555,3,1,-1},
+{10625,3,0,-1},{10626,3,1,-1},{10629,3,0,-1},{10630,3,1,-1},{10634,3,0,-1},
+{10635,3,1,-1},{10638,3,0,-1},{10639,3,1,-1},{10642,3,0,-1},{10643,3,1,-1},
+{10647,3,0,-1},{10648,3,1,-1},{10651,3,0,-1},{10652,3,1,-1},{10655,3,0,-1},
+{10656,3,1,-1},{10660,3,0,-1},{10661,3,1,-1},{10664,3,0,-1},{10665,3,1,-1},
+{10668,3,0,-1},{10669,3,1,-1},{10673,3,0,-1},{10674,3,1,-1},{10677,3,0,-1},
+{10678,3,1,-1},{10681,3,0,-1},{10682,3,1,-1},{10686,3,0,-1},{10687,3,1,-1},
+{10690,3,0,-1},{10691,3,1,-1},{10694,3,0,-1},{10695,3,1,-1},{10699,3,0,-1},
+{10700,3,1,-1},{10703,3,0,-1},{10704,3,1,-1},{10707,3,0,-1},{10708,3,1,-1},
+{10712,3,0,-1},{10713,3,1,-1},{10646,3,0,-1},{10716,3,0,-1},{10717,3,1,-1},
+{10720,3,0,-1},{10721,3,1,-1},{10725,3,0,-1},{10726,3,1,-1},{10659,3,1,-1},
+{10729,3,0,-1},{10730,3,1,-1},{10733,3,0,-1},{10734,3,1,-1},{10738,3,0,-1},
+{10739,3,1,-1},{10742,3,0,-1},{10743,3,1,-1},{10746,3,0,-1},{10747,3,1,-1},
+{10751,3,0,-1},{10752,3,1,-1},{10755,3,0,-1},{10756,3,1,-1},{10759,3,0,-1},
+{10760,3,1,-1},{10764,3,0,-1},{10765,3,1,-1},{10768,3,0,-1},{10769,3,1,-1},
+{10772,3,0,-1},{10773,3,1,-1},{10777,3,0,-1},{10778,3,1,-1},{10781,3,0,-1},
+{10782,3,1,-1},{10785,3,0,-1},{10786,3,1,-1},{10790,3,0,-1},{10791,3,1,-1},
+{10794,3,0,-1},{10795,3,1,-1},{10798,3,0,-1},{10799,3,1,-1},{10803,3,0,-1},
+{10804,3,1,-1},{10807,3,0,-1},{10808,3,1,-1},{10811,3,0,-1},{10812,3,1,-1},
+{10816,3,0,-1},{10817,3,1,-1},{10750,3,0,-1},{10820,3,0,-1},{10821,3,1,-1},
+{10824,3,0,-1},{10825,3,1,-1},{10829,3,0,-1},{10830,3,1,-1},{10763,3,1,-1},
+{10833,3,0,-1},{10834,3,1,-1},{10837,3,0,-1},{10838,3,1,-1},{10842,3,0,-1},
+{10843,3,1,-1},{10846,3,0,-1},{10847,3,1,-1},{10850,3,0,-1},{10851,3,1,-1},
+{10855,3,0,-1},{10856,3,1,-1},{10859,3,0,-1},{10860,3,1,-1},{10863,3,0,-1},
+{10864,3,1,-1},{10868,3,0,-1},{10869,3,1,-1},{10872,3,0,-1},{10873,3,1,-1},
+{10876,3,0,-1},{10877,3,1,-1},{10881,3,0,-1},{10882,3,1,-1},{10885,3,0,-1},
+{10886,3,1,-1},{10889,3,0,-1},{10890,3,1,-1},{10894,3,0,-1},{10895,3,1,-1},
+{10898,3,0,-1},{10899,3,1,-1},{10902,3,0,-1},{10903,3,1,-1},{10907,3,0,-1},
+{10908,3,1,-1},{10911,3,0,-1},{10912,3,1,-1},{10915,3,0,-1},{10916,3,1,-1},
+{10920,3,0,-1},{10921,3,1,-1},{10854,3,0,-1},{10924,3,0,-1},{10925,3,1,-1},
+{10928,3,0,-1},{10929,3,1,-1},{10933,3,0,-1},{10934,3,1,-1},{10867,3,1,-1},
+{10937,3,0,-1},{10938,3,1,-1},{10941,3,0,-1},{10942,3,1,-1},{10946,3,0,-1},
+{10947,3,1,-1},{10950,3,0,-1},{10951,3,1,-1},{10954,3,0,-1},{10955,3,1,-1},
+{10959,3,0,-1},{10960,3,1,-1},{10963,3,0,-1},{10964,3,1,-1},{10967,3,0,-1},
+{10968,3,1,-1},{10972,3,0,-1},{10973,3,1,-1},{10976,3,0,-1},{10977,3,1,-1},
+{10980,3,0,-1},{10981,3,1,-1},{10985,3,0,-1},{10986,3,1,-1},{10989,3,0,-1},
+{10990,3,1,-1},{10993,3,0,-1},{10994,3,1,-1},{10998,3,0,-1},{10999,3,1,-1},
+{11002,3,0,-1},{11003,3,1,-1},{11006,3,0,-1},{11007,3,1,-1},{11011,3,0,-1},
+{11012,3,1,-1},{11015,3,0,-1},{11016,3,1,-1},{11019,3,0,-1},{11020,3,1,-1},
+{11024,3,0,-1},{11025,3,1,-1},{10958,3,0,-1},{11028,3,0,-1},{11029,3,1,-1},
+{11032,3,0,-1},{11033,3,1,-1},{11037,3,0,-1},{11038,3,1,-1},{10971,3,1,-1},
+{11041,3,0,-1},{11042,3,1,-1},{11045,3,0,-1},{11046,3,1,-1},{11050,3,0,-1},
+{11051,3,1,-1},{11054,3,0,-1},{11055,3,1,-1},{11058,3,0,-1},{11059,3,1,-1},
+{11063,3,0,-1},{11064,3,1,-1},{11067,3,0,-1},{11068,3,1,-1},{11071,3,0,-1},
+{11072,3,1,-1},{11076,3,0,-1},{11077,3,1,-1},{11080,3,0,-1},{11081,3,1,-1},
+{11084,3,0,-1},{11085,3,1,-1},{11089,3,0,-1},{11090,3,1,-1},{11093,3,0,-1},
+{11094,3,1,-1},{11097,3,0,-1},{11098,3,1,-1},{11102,3,0,-1},{11103,3,1,-1},
+{11106,3,0,-1},{11107,3,1,-1},{11110,3,0,-1},{11111,3,1,-1},{11115,3,0,-1},
+{11116,3,1,-1},{11119,3,0,-1},{11120,3,1,-1},{11123,3,0,-1},{11124,3,1,-1},
+{11128,3,0,-1},{11129,3,1,-1},{11062,3,0,-1},{11132,3,0,-1},{11133,3,1,-1},
+{11136,3,0,-1},{11137,3,1,-1},{11141,3,0,-1},{11142,3,1,-1},{11075,3,1,-1},
+{11145,3,0,-1},{11146,3,1,-1},{11149,3,0,-1},{11150,3,1,-1},{11154,3,0,-1},
+{11155,3,1,-1},{11158,3,0,-1},{11159,3,1,-1},{11162,3,0,-1},{11163,3,1,-1},
+{11167,3,0,-1},{11168,3,1,-1},{11171,3,0,-1},{11172,3,1,-1},{11175,3,0,-1},
+{11176,3,1,-1},{11180,3,0,-1},{11181,3,1,-1},{11184,3,0,-1},{11185,3,1,-1},
+{11188,3,0,-1},{11189,3,1,-1},{11193,3,0,-1},{11194,3,1,-1},{11197,3,0,-1},
+{11198,3,1,-1},{11201,3,0,-1},{11202,3,1,-1},{11206,3,0,-1},{11207,3,1,-1},
+{11210,3,0,-1},{11211,3,1,-1},{11214,3,0,-1},{11215,3,1,-1},{11219,3,0,-1},
+{11220,3,1,-1},{11223,3,0,-1},{11224,3,1,-1},{11227,3,0,-1},{11228,3,1,-1},
+{11232,3,0,-1},{11233,3,1,-1},{11166,3,0,-1},{11236,3,0,-1},{11237,3,1,-1},
+{11240,3,0,-1},{11241,3,1,-1},{11245,3,0,-1},{11246,3,1,-1},{11179,3,1,-1},
+{11249,3,0,-1},{11250,3,1,-1},{11253,3,0,-1},{11254,3,1,-1},{11258,3,0,-1},
+{11259,3,1,-1},{11262,3,0,-1},{11263,3,1,-1},{11266,3,0,-1},{11267,3,1,-1},
+{11271,3,0,-1},{11272,3,1,-1},{11275,3,0,-1},{11276,3,1,-1},{11279,3,0,-1},
+{11280,3,1,-1},{11284,3,0,-1},{11285,3,1,-1},{11288,3,0,-1},{11289,3,1,-1},
+{11292,3,0,-1},{11293,3,1,-1},{11297,3,0,-1},{11298,3,1,-1},{11301,3,0,-1},
+{11302,3,1,-1},{11305,3,0,-1},{11306,3,1,-1},{11310,3,0,-1},{11311,3,1,-1},
+{11314,3,0,-1},{11315,3,1,-1},{11318,3,0,-1},{11319,3,1,-1},{11323,3,0,-1},
+{11324,3,1,-1},{11327,3,0,-1},{11328,3,1,-1},{11331,3,0,-1},{11332,3,1,-1},
+{11336,3,0,-1},{11337,3,1,-1},{11270,3,0,-1},{11340,3,0,-1},{11341,3,1,-1},
+{11344,3,0,-1},{11345,3,1,-1},{11349,3,0,-1},{11350,3,1,-1},{11283,3,1,-1},
+{11353,3,0,-1},{11354,3,1,-1},{11357,3,0,-1},{11358,3,1,-1},{11362,3,0,-1},
+{11363,3,1,-1},{11366,3,0,-1},{11367,3,1,-1},{11370,3,0,-1},{11371,3,1,-1},
+{11375,3,0,-1},{11376,3,1,-1},{11379,3,0,-1},{11380,3,1,-1},{11383,3,0,-1},
+{11384,3,1,-1},{11388,3,0,-1},{11389,3,1,-1},{11392,3,0,-1},{11393,3,1,-1},
+{11396,3,0,-1},{11397,3,1,-1},{11401,3,0,-1},{11402,3,1,-1},{11405,3,0,-1},
+{11406,3,1,-1},{11409,3,0,-1},{11410,3,1,-1},{11414,3,0,-1},{11415,3,1,-1},
+{11418,3,0,-1},{11419,3,1,-1},{11422,3,0,-1},{11423,3,1,-1},{11427,3,0,-1},
+{11428,3,1,-1},{11431,3,0,-1},{11432,3,1,-1},{11435,3,0,-1},{11436,3,1,-1},
+{11440,3,0,-1},{11441,3,1,-1},{11374,3,0,-1},{11444,3,0,-1},{11445,3,1,-1},
+{11448,3,0,-1},{11449,3,1,-1},{11453,3,0,-1},{11454,3,1,-1},{11387,3,1,-1},
+{11457,3,0,-1},{11458,3,1,-1},{11461,3,0,-1},{11462,3,1,-1},{11466,3,0,-1},
+{11467,3,1,-1},{11470,3,0,-1},{11471,3,1,-1},{11474,3,0,-1},{11475,3,1,-1},
+{11479,3,0,-1},{11480,3,1,-1},{11483,3,0,-1},{11484,3,1,-1},{11487,3,0,-1},
+{11488,3,1,-1},{11492,3,0,-1},{11493,3,1,-1},{11496,3,0,-1},{11497,3,1,-1},
+{11500,3,0,-1},{11501,3,1,-1},{11505,3,0,-1},{11506,3,1,-1},{11509,3,0,-1},
+{11510,3,1,-1},{11513,3,0,-1},{11514,3,1,-1},{11518,3,0,-1},{11519,3,1,-1},
+{11522,3,0,-1},{11523,3,1,-1},{11526,3,0,-1},{11527,3,1,-1},{11531,3,0,-1},
+{11532,3,1,-1},{11535,3,0,-1},{11536,3,1,-1},{11539,3,0,-1},{11540,3,1,-1},
+{11544,3,0,-1},{11545,3,1,-1},{11478,3,0,-1},{11548,3,0,-1},{11549,3,1,-1},
+{11552,3,0,-1},{11553,3,1,-1},{11557,3,0,-1},{11558,3,1,-1},{11491,3,1,-1},
+{11561,3,0,-1},{11562,3,1,-1},{11565,3,0,-1},{11566,3,1,-1},{11570,3,0,-1},
+{11571,3,1,-1},{11574,3,0,-1},{11575,3,1,-1},{11578,3,0,-1},{11579,3,1,-1},
+{11583,3,0,-1},{11584,3,1,-1},{11587,3,0,-1},{11588,3,1,-1},{11591,3,0,-1},
+{11592,3,1,-1},{11596,3,0,-1},{11597,3,1,-1},{11600,3,0,-1},{11601,3,1,-1},
+{11604,3,0,-1},{11605,3,1,-1},{11609,3,0,-1},{11610,3,1,-1},{11613,3,0,-1},
+{11614,3,1,-1},{11617,3,0,-1},{11618,3,1,-1},{11622,3,0,-1},{11623,3,1,-1},
+{11626,3,0,-1},{11627,3,1,-1},{11630,3,0,-1},{11631,3,1,-1},{11635,3,0,-1},
+{11636,3,1,-1},{11639,3,0,-1},{11640,3,1,-1},{11643,3,0,-1},{11644,3,1,-1},
+{11648,3,0,-1},{11649,3,1,-1},{11582,3,0,-1},{11652,3,0,-1},{11653,3,1,-1},
+{11656,3,0,-1},{11657,3,1,-1},{11661,3,0,-1},{11662,3,1,-1},{11595,3,1,-1},
+{11665,3,0,-1},{11666,3,1,-1},{11669,3,0,-1},{11670,3,1,-1},{11674,3,0,-1},
+{11675,3,1,-1},{11678,3,0,-1},{11679,3,1,-1},{11682,3,0,-1},{11683,3,1,-1},
+{11687,3,0,-1},{11688,3,1,-1},{11691,3,0,-1},{11692,3,1,-1},{11695,3,0,-1},
+{11696,3,1,-1},{11700,3,0,-1},{11701,3,1,-1},{11704,3,0,-1},{11705,3,1,-1},
+{11708,3,0,-1},{11709,3,1,-1},{11713,3,0,-1},{11714,3,1,-1},{11717,3,0,-1},
+{11718,3,1,-1},{11721,3,0,-1},{11722,3,1,-1},{11726,3,0,-1},{11727,3,1,-1},
+{11730,3,0,-1},{11731,3,1,-1},{11734,3,0,-1},{11735,3,1,-1},{11739,3,0,-1},
+{11740,3,1,-1},{11743,3,0,-1},{11744,3,1,-1},{11747,3,0,-1},{11748,3,1,-1},
+{11752,3,0,-1},{11753,3,1,-1},{11686,3,0,-1},{11756,3,0,-1},{11757,3,1,-1},
+{11760,3,0,-1},{11761,3,1,-1},{11765,3,0,-1},{11766,3,1,-1},{11699,3,1,-1},
+{11769,3,0,-1},{11770,3,1,-1},{11773,3,0,-1},{11774,3,1,-1},{11778,3,0,-1},
+{11779,3,1,-1},{11782,3,0,-1},{11783,3,1,-1},{11786,3,0,-1},{11787,3,1,-1},
+{11791,3,0,-1},{11792,3,1,-1},{11795,3,0,-1},{11796,3,1,-1},{11799,3,0,-1},
+{11800,3,1,-1},{11804,3,0,-1},{11805,3,1,-1},{11808,3,0,-1},{11809,3,1,-1},
+{11812,3,0,-1},{11813,3,1,-1},{11817,3,0,-1},{11818,3,1,-1},{11821,3,0,-1},
+{11822,3,1,-1},{11825,3,0,-1},{11826,3,1,-1},{11830,3,0,-1},{11831,3,1,-1},
+{11834,3,0,-1},{11835,3,1,-1},{11838,3,0,-1},{11839,3,1,-1},{11843,3,0,-1},
+{11844,3,1,-1},{11847,3,0,-1},{11848,3,1,-1},{11851,3,0,-1},{11852,3,1,-1},
+{11856,3,0,-1},{11857,3,1,-1},{11790,3,0,-1},{11860,3,0,-1},{11861,3,1,-1},
+{11864,3,0,-1},{11865,3,1,-1},{11869,3,0,-1},{11870,3,1,-1},{11803,3,1,-1},
+{11873,3,0,-1},{11874,3,1,-1},{11877,3,0,-1},{11878,3,1,-1},{11882,3,0,-1},
+{11883,3,1,-1},{11886,3,0,-1},{11887,3,1,-1},{11890,3,0,-1},{11891,3,1,-1},
+{11895,3,0,-1},{11896,3,1,-1},{11899,3,0,-1},{11900,3,1,-1},{11903,3,0,-1},
+{11904,3,1,-1},{11908,3,0,-1},{11909,3,1,-1},{11912,3,0,-1},{11913,3,1,-1},
+{11916,3,0,-1},{11917,3,1,-1},{11921,3,0,-1},{11922,3,1,-1},{11925,3,0,-1},
+{11926,3,1,-1},{11929,3,0,-1},{11930,3,1,-1},{11934,3,0,-1},{11935,3,1,-1},
+{11938,3,0,-1},{11939,3,1,-1},{11942,3,0,-1},{11943,3,1,-1},{11947,3,0,-1},
+{11948,3,1,-1},{11951,3,0,-1},{11952,3,1,-1},{11955,3,0,-1},{11956,3,1,-1},
+{11960,3,0,-1},{11961,3,1,-1},{11894,3,0,-1},{11964,3,0,-1},{11965,3,1,-1},
+{11968,3,0,-1},{11969,3,1,-1},{11973,3,0,-1},{11974,3,1,-1},{11907,3,1,-1},
+{11977,3,0,-1},{11978,3,1,-1},{11981,3,0,-1},{11982,3,1,-1},{11986,3,0,-1},
+{11987,3,1,-1},{11990,3,0,-1},{11991,3,1,-1},{11994,3,0,-1},{11995,3,1,-1},
+{11999,3,0,-1},{12000,3,1,-1},{12003,3,0,-1},{12004,3,1,-1},{12007,3,0,-1},
+{12008,3,1,-1},{12012,3,0,-1},{12013,3,1,-1},{12016,3,0,-1},{12017,3,1,-1},
+{12020,3,0,-1},{12021,3,1,-1},{12025,3,0,-1},{12026,3,1,-1},{12029,3,0,-1},
+{12030,3,1,-1},{12033,3,0,-1},{12034,3,1,-1},{12038,3,0,-1},{12039,3,1,-1},
+{12042,3,0,-1},{12043,3,1,-1},{12046,3,0,-1},{12047,3,1,-1},{12051,3,0,-1},
+{12052,3,1,-1},{12055,3,0,-1},{12056,3,1,-1},{12059,3,0,-1},{12060,3,1,-1},
+{12064,3,0,-1},{12065,3,1,-1},{11998,3,0,-1},{12068,3,0,-1},{12069,3,1,-1},
+{12072,3,0,-1},{12073,3,1,-1},{12077,3,0,-1},{12078,3,1,-1},{12011,3,1,-1},
+{12081,3,0,-1},{12082,3,1,-1},{12085,3,0,-1},{12086,3,1,-1},{12090,3,0,-1},
+{12091,3,1,-1},{12094,3,0,-1},{12095,3,1,-1},{12098,3,0,-1},{12099,3,1,-1},
+{12103,3,0,-1},{12104,3,1,-1},{12107,3,0,-1},{12108,3,1,-1},{12111,3,0,-1},
+{12112,3,1,-1},{12116,3,0,-1},{12117,3,1,-1},{12120,3,0,-1},{12121,3,1,-1},
+{12124,3,0,-1},{12125,3,1,-1},{12129,3,0,-1},{12130,3,1,-1},{12133,3,0,-1},
+{12134,3,1,-1},{12137,3,0,-1},{12138,3,1,-1},{12142,3,0,-1},{12143,3,1,-1},
+{12146,3,0,-1},{12147,3,1,-1},{12150,3,0,-1},{12151,3,1,-1},{12155,3,0,-1},
+{12156,3,1,-1},{12159,3,0,-1},{12160,3,1,-1},{12164,3,1,-1},{12168,3,0,-1},
+{12169,3,1,-1},{12102,3,0,-1},{12172,3,0,-1},{12173,3,1,-1},{12176,3,0,-1},
+{12177,3,1,-1},{12181,3,0,-1},{12182,3,1,-1},{12115,3,1,-1},{12185,3,0,-1},
+{12186,3,1,-1},{12189,3,0,-1},{12190,3,1,-1},{12194,3,0,-1},{12195,3,1,-1},
+{12198,3,0,-1},{12199,3,1,-1},{12202,3,0,-1},{12203,3,1,-1},{12207,3,0,-1},
+{12208,3,1,-1},{12211,3,0,-1},{12212,3,1,-1},{12216,3,1,-1},{12220,3,0,-1},
+{12221,3,1,-1},{12224,3,0,-1},{12225,3,1,-1},{12228,3,0,-1},{12229,3,1,-1},
+{12233,3,0,-1},{12234,3,1,-1},{12237,3,0,-1},{12238,3,1,-1},{12241,3,0,-1},
+{12242,3,1,-1},{12246,3,0,-1},{12247,3,1,-1},{12250,3,0,-1},{12251,3,1,-1},
+{12254,3,0,-1},{12255,3,1,-1},{12260,3,1,-1},{12264,3,1,-1},{12268,3,1,-1},
+{12273,3,1,-1},{12281,3,1,-1},{12286,3,1,-1},{12290,3,1,-1},{12294,3,1,-1},
+{12299,3,1,-1},{12303,3,1,-1},{12307,3,1,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on half rate channels TS4, SS0 and SS1 */
+struct fn_sample test_fn_tch_h_ts_4_ss0_ss1[] = {
+{7704,4,0,-1},{7713,4,0,-1},{7722,4,0,-1},{7730,4,0,-1},{7739,4,0,-1},
+{7748,4,0,-1},{7756,4,0,-1},{7765,4,0,-1},{7774,4,0,-1},{7782,4,0,-1},
+{7791,4,0,-1},{7800,4,0,-1},{7808,4,0,-1},{7817,4,0,-1},{7826,4,0,-1},
+{7760,4,0,-1},{7834,4,0,-1},{7843,4,0,-1},{7852,4,0,-1},{7860,4,0,-1},
+{7865,4,0,-1},{7869,4,0,-1},{7873,4,0,-1},{7878,4,0,-1},{7882,4,0,-1},
+{7886,4,0,-1},{7891,4,0,-1},{7895,4,0,-1},{7899,4,0,-1},{7904,4,0,-1},
+{7908,4,0,-1},{7912,4,0,-1},{7917,4,0,-1},{7921,4,0,-1},{7925,4,0,-1},
+{7930,4,0,-1},{7864,4,0,-1},{7934,4,0,-1},{7938,4,0,-1},{7943,4,0,-1},
+{7947,4,0,-1},{7951,4,0,-1},{7956,4,0,-1},{7960,4,0,-1},{7964,4,0,-1},
+{7969,4,0,-1},{7973,4,0,-1},{7977,4,0,-1},{7982,4,0,-1},{7986,4,0,-1},
+{7990,4,0,-1},{7995,4,0,-1},{7999,4,0,-1},{8003,4,0,-1},{8008,4,0,-1},
+{8012,4,0,-1},{8016,4,0,-1},{8021,4,0,-1},{8025,4,0,-1},{8029,4,0,-1},
+{8034,4,0,-1},{7968,4,0,-1},{8038,4,0,-1},{8042,4,0,-1},{8047,4,0,-1},
+{8051,4,0,-1},{8055,4,0,-1},{8060,4,0,-1},{8064,4,0,-1},{8068,4,0,-1},
+{8073,4,0,-1},{8077,4,0,-1},{8081,4,0,-1},{8086,4,0,-1},{8090,4,0,-1},
+{8094,4,0,-1},{8099,4,0,-1},{8103,4,0,-1},{8107,4,0,-1},{8112,4,0,-1},
+{8116,4,0,-1},{8120,4,0,-1},{8121,4,1,-1},{8125,4,0,-1},{8129,4,0,-1},
+{8130,4,1,-1},{8133,4,0,-1},{8138,4,0,-1},{8139,4,1,-1},{8072,4,0,-1},
+{8142,4,0,-1},{8146,4,0,-1},{8147,4,1,-1},{8151,4,0,-1},{8155,4,0,-1},
+{8156,4,1,-1},{8159,4,0,-1},{8164,4,0,-1},{8165,4,1,-1},{8168,4,0,-1},
+{8172,4,0,-1},{8173,4,1,-1},{8177,4,0,-1},{8181,4,0,-1},{8182,4,1,-1},
+{8185,4,0,-1},{8190,4,0,-1},{8191,4,1,-1},{8194,4,0,-1},{8198,4,0,-1},
+{8199,4,1,-1},{8203,4,0,-1},{8207,4,0,-1},{8208,4,1,-1},{8211,4,0,-1},
+{8216,4,0,-1},{8217,4,1,-1},{8220,4,0,-1},{8224,4,0,-1},{8225,4,1,-1},
+{8229,4,0,-1},{8230,4,1,-1},{8233,4,0,-1},{8234,4,1,-1},{8237,4,0,-1},
+{8238,4,1,-1},{8242,4,0,-1},{8243,4,1,-1},{8176,4,0,-1},{8246,4,0,-1},
+{8247,4,1,-1},{8250,4,0,-1},{8251,4,1,-1},{8255,4,0,-1},{8189,4,1,-1},
+{8259,4,0,-1},{8260,4,1,-1},{8263,4,0,-1},{8264,4,1,-1},{8268,4,0,-1},
+{8269,4,1,-1},{8272,4,0,-1},{8273,4,1,-1},{8276,4,0,-1},{8277,4,1,-1},
+{8281,4,0,-1},{8282,4,1,-1},{8285,4,0,-1},{8286,4,1,-1},{8289,4,0,-1},
+{8290,4,1,-1},{8294,4,0,-1},{8295,4,1,-1},{8299,4,1,-1},{8302,4,0,-1},
+{8303,4,1,-1},{8307,4,0,-1},{8308,4,1,-1},{8311,4,0,-1},{8312,4,1,-1},
+{8315,4,0,-1},{8316,4,1,-1},{8320,4,0,-1},{8321,4,1,-1},{8324,4,0,-1},
+{8325,4,1,-1},{8328,4,0,-1},{8329,4,1,-1},{8333,4,0,-1},{8334,4,1,-1},
+{8337,4,0,-1},{8338,4,1,-1},{8341,4,0,-1},{8342,4,1,-1},{8346,4,0,-1},
+{8347,4,1,-1},{8280,4,0,-1},{8350,4,0,-1},{8351,4,1,-1},{8354,4,0,-1},
+{8355,4,1,-1},{8359,4,0,-1},{8360,4,1,-1},{8293,4,1,-1},{8363,4,0,-1},
+{8364,4,1,-1},{8367,4,0,-1},{8368,4,1,-1},{8372,4,0,-1},{8373,4,1,-1},
+{8376,4,0,-1},{8377,4,1,-1},{8380,4,0,-1},{8381,4,1,-1},{8385,4,0,-1},
+{8386,4,1,-1},{8389,4,0,-1},{8390,4,1,-1},{8393,4,0,-1},{8394,4,1,-1},
+{8398,4,0,-1},{8399,4,1,-1},{8402,4,0,-1},{8403,4,1,-1},{8406,4,0,-1},
+{8407,4,1,-1},{8411,4,0,-1},{8412,4,1,-1},{8415,4,0,-1},{8416,4,1,-1},
+{8419,4,0,-1},{8420,4,1,-1},{8424,4,0,-1},{8425,4,1,-1},{8428,4,0,-1},
+{8429,4,1,-1},{8432,4,0,-1},{8433,4,1,-1},{8437,4,0,-1},{8438,4,1,-1},
+{8441,4,0,-1},{8442,4,1,-1},{8445,4,0,-1},{8446,4,1,-1},{8450,4,0,-1},
+{8451,4,1,-1},{8384,4,0,-1},{8454,4,0,-1},{8455,4,1,-1},{8458,4,0,-1},
+{8459,4,1,-1},{8463,4,0,-1},{8397,4,1,-1},{8467,4,0,-1},{8468,4,1,-1},
+{8471,4,0,-1},{8472,4,1,-1},{8476,4,0,-1},{8477,4,1,-1},{8480,4,0,-1},
+{8481,4,1,-1},{8484,4,0,-1},{8485,4,1,-1},{8489,4,0,-1},{8490,4,1,-1},
+{8493,4,0,-1},{8494,4,1,-1},{8497,4,0,-1},{8498,4,1,-1},{8502,4,0,-1},
+{8503,4,1,-1},{8507,4,1,-1},{8510,4,0,-1},{8511,4,1,-1},{8515,4,0,-1},
+{8519,4,0,-1},{8520,4,1,-1},{8523,4,0,-1},{8524,4,1,-1},{8528,4,0,-1},
+{8529,4,1,-1},{8532,4,0,-1},{8533,4,1,-1},{8536,4,0,-1},{8537,4,1,-1},
+{8541,4,0,-1},{8542,4,1,-1},{8545,4,0,-1},{8546,4,1,-1},{8549,4,0,-1},
+{8550,4,1,-1},{8554,4,0,-1},{8555,4,1,-1},{8488,4,0,-1},{8558,4,0,-1},
+{8559,4,1,-1},{8562,4,0,-1},{8563,4,1,-1},{8567,4,0,-1},{8568,4,1,-1},
+{8501,4,1,-1},{8571,4,0,-1},{8572,4,1,-1},{8575,4,0,-1},{8576,4,1,-1},
+{8580,4,0,-1},{8581,4,1,-1},{8584,4,0,-1},{8585,4,1,-1},{8588,4,0,-1},
+{8589,4,1,-1},{8593,4,0,-1},{8594,4,1,-1},{8597,4,0,-1},{8598,4,1,-1},
+{8601,4,0,-1},{8602,4,1,-1},{8606,4,0,-1},{8607,4,1,-1},{8610,4,0,-1},
+{8611,4,1,-1},{8614,4,0,-1},{8615,4,1,-1},{8619,4,0,-1},{8620,4,1,-1},
+{8623,4,0,-1},{8624,4,1,-1},{8627,4,0,-1},{8628,4,1,-1},{8632,4,0,-1},
+{8633,4,1,-1},{8636,4,0,-1},{8637,4,1,-1},{8640,4,0,-1},{8641,4,1,-1},
+{8645,4,0,-1},{8646,4,1,-1},{8649,4,0,-1},{8650,4,1,-1},{8653,4,0,-1},
+{8654,4,1,-1},{8658,4,0,-1},{8659,4,1,-1},{8592,4,0,-1},{8662,4,0,-1},
+{8663,4,1,-1},{8666,4,0,-1},{8667,4,1,-1},{8671,4,0,-1},{8672,4,1,-1},
+{8605,4,1,-1},{8675,4,0,-1},{8676,4,1,-1},{8679,4,0,-1},{8680,4,1,-1},
+{8684,4,0,-1},{8685,4,1,-1},{8688,4,0,-1},{8689,4,1,-1},{8692,4,0,-1},
+{8693,4,1,-1},{8697,4,0,-1},{8698,4,1,-1},{8701,4,0,-1},{8702,4,1,-1},
+{8705,4,0,-1},{8706,4,1,-1},{8710,4,0,-1},{8711,4,1,-1},{8714,4,0,-1},
+{8715,4,1,-1},{8718,4,0,-1},{8719,4,1,-1},{8723,4,0,-1},{8724,4,1,-1},
+{8727,4,0,-1},{8728,4,1,-1},{8731,4,0,-1},{8732,4,1,-1},{8736,4,0,-1},
+{8737,4,1,-1},{8740,4,0,-1},{8741,4,1,-1},{8744,4,0,-1},{8745,4,1,-1},
+{8749,4,0,-1},{8750,4,1,-1},{8753,4,0,-1},{8754,4,1,-1},{8757,4,0,-1},
+{8758,4,1,-1},{8762,4,0,-1},{8763,4,1,-1},{8696,4,0,-1},{8766,4,0,-1},
+{8767,4,1,-1},{8770,4,0,-1},{8771,4,1,-1},{8775,4,0,-1},{8776,4,1,-1},
+{8709,4,1,-1},{8779,4,0,-1},{8780,4,1,-1},{8783,4,0,-1},{8784,4,1,-1},
+{8788,4,0,-1},{8789,4,1,-1},{8792,4,0,-1},{8793,4,1,-1},{8796,4,0,-1},
+{8797,4,1,-1},{8801,4,0,-1},{8802,4,1,-1},{8805,4,0,-1},{8806,4,1,-1},
+{8809,4,0,-1},{8810,4,1,-1},{8814,4,0,-1},{8815,4,1,-1},{8818,4,0,-1},
+{8819,4,1,-1},{8822,4,0,-1},{8823,4,1,-1},{8827,4,0,-1},{8828,4,1,-1},
+{8831,4,0,-1},{8832,4,1,-1},{8835,4,0,-1},{8836,4,1,-1},{8840,4,0,-1},
+{8841,4,1,-1},{8844,4,0,-1},{8845,4,1,-1},{8848,4,0,-1},{8849,4,1,-1},
+{8853,4,0,-1},{8854,4,1,-1},{8857,4,0,-1},{8858,4,1,-1},{8861,4,0,-1},
+{8862,4,1,-1},{8866,4,0,-1},{8867,4,1,-1},{8800,4,0,-1},{8870,4,0,-1},
+{8871,4,1,-1},{8874,4,0,-1},{8875,4,1,-1},{8879,4,0,-1},{8880,4,1,-1},
+{8813,4,1,-1},{8883,4,0,-1},{8884,4,1,-1},{8887,4,0,-1},{8888,4,1,-1},
+{8892,4,0,-1},{8893,4,1,-1},{8896,4,0,-1},{8897,4,1,-1},{8900,4,0,-1},
+{8901,4,1,-1},{8905,4,0,-1},{8906,4,1,-1},{8909,4,0,-1},{8910,4,1,-1},
+{8913,4,0,-1},{8914,4,1,-1},{8918,4,0,-1},{8919,4,1,-1},{8922,4,0,-1},
+{8923,4,1,-1},{8926,4,0,-1},{8927,4,1,-1},{8931,4,0,-1},{8932,4,1,-1},
+{8935,4,0,-1},{8936,4,1,-1},{8939,4,0,-1},{8940,4,1,-1},{8944,4,0,-1},
+{8945,4,1,-1},{8948,4,0,-1},{8949,4,1,-1},{8952,4,0,-1},{8953,4,1,-1},
+{8957,4,0,-1},{8958,4,1,-1},{8961,4,0,-1},{8962,4,1,-1},{8965,4,0,-1},
+{8966,4,1,-1},{8970,4,0,-1},{8971,4,1,-1},{8904,4,0,-1},{8974,4,0,-1},
+{8975,4,1,-1},{8978,4,0,-1},{8979,4,1,-1},{8983,4,0,-1},{8984,4,1,-1},
+{8917,4,1,-1},{8987,4,0,-1},{8988,4,1,-1},{8991,4,0,-1},{8992,4,1,-1},
+{8996,4,0,-1},{8997,4,1,-1},{9000,4,0,-1},{9001,4,1,-1},{9004,4,0,-1},
+{9005,4,1,-1},{9009,4,0,-1},{9010,4,1,-1},{9013,4,0,-1},{9014,4,1,-1},
+{9017,4,0,-1},{9018,4,1,-1},{9022,4,0,-1},{9023,4,1,-1},{9026,4,0,-1},
+{9027,4,1,-1},{9030,4,0,-1},{9031,4,1,-1},{9035,4,0,-1},{9036,4,1,-1},
+{9039,4,0,-1},{9040,4,1,-1},{9043,4,0,-1},{9044,4,1,-1},{9048,4,0,-1},
+{9049,4,1,-1},{9052,4,0,-1},{9053,4,1,-1},{9056,4,0,-1},{9057,4,1,-1},
+{9061,4,0,-1},{9062,4,1,-1},{9065,4,0,-1},{9066,4,1,-1},{9069,4,0,-1},
+{9070,4,1,-1},{9074,4,0,-1},{9075,4,1,-1},{9008,4,0,-1},{9078,4,0,-1},
+{9079,4,1,-1},{9082,4,0,-1},{9083,4,1,-1},{9087,4,0,-1},{9088,4,1,-1},
+{9021,4,1,-1},{9091,4,0,-1},{9092,4,1,-1},{9095,4,0,-1},{9096,4,1,-1},
+{9100,4,0,-1},{9101,4,1,-1},{9104,4,0,-1},{9105,4,1,-1},{9108,4,0,-1},
+{9109,4,1,-1},{9113,4,0,-1},{9114,4,1,-1},{9117,4,0,-1},{9118,4,1,-1},
+{9121,4,0,-1},{9122,4,1,-1},{9126,4,0,-1},{9127,4,1,-1},{9130,4,0,-1},
+{9131,4,1,-1},{9134,4,0,-1},{9135,4,1,-1},{9139,4,0,-1},{9140,4,1,-1},
+{9143,4,0,-1},{9144,4,1,-1},{9147,4,0,-1},{9148,4,1,-1},{9152,4,0,-1},
+{9153,4,1,-1},{9156,4,0,-1},{9157,4,1,-1},{9160,4,0,-1},{9161,4,1,-1},
+{9165,4,0,-1},{9166,4,1,-1},{9169,4,0,-1},{9170,4,1,-1},{9173,4,0,-1},
+{9174,4,1,-1},{9178,4,0,-1},{9179,4,1,-1},{9112,4,0,-1},{9182,4,0,-1},
+{9183,4,1,-1},{9186,4,0,-1},{9187,4,1,-1},{9191,4,0,-1},{9192,4,1,-1},
+{9125,4,1,-1},{9195,4,0,-1},{9196,4,1,-1},{9199,4,0,-1},{9200,4,1,-1},
+{9204,4,0,-1},{9205,4,1,-1},{9208,4,0,-1},{9209,4,1,-1},{9212,4,0,-1},
+{9213,4,1,-1},{9217,4,0,-1},{9218,4,1,-1},{9221,4,0,-1},{9222,4,1,-1},
+{9225,4,0,-1},{9226,4,1,-1},{9230,4,0,-1},{9231,4,1,-1},{9234,4,0,-1},
+{9235,4,1,-1},{9238,4,0,-1},{9239,4,1,-1},{9243,4,0,-1},{9244,4,1,-1},
+{9247,4,0,-1},{9248,4,1,-1},{9251,4,0,-1},{9252,4,1,-1},{9256,4,0,-1},
+{9257,4,1,-1},{9260,4,0,-1},{9261,4,1,-1},{9264,4,0,-1},{9265,4,1,-1},
+{9269,4,0,-1},{9270,4,1,-1},{9273,4,0,-1},{9274,4,1,-1},{9277,4,0,-1},
+{9278,4,1,-1},{9282,4,0,-1},{9283,4,1,-1},{9216,4,0,-1},{9286,4,0,-1},
+{9287,4,1,-1},{9290,4,0,-1},{9291,4,1,-1},{9295,4,0,-1},{9296,4,1,-1},
+{9229,4,1,-1},{9299,4,0,-1},{9300,4,1,-1},{9303,4,0,-1},{9304,4,1,-1},
+{9308,4,0,-1},{9309,4,1,-1},{9312,4,0,-1},{9313,4,1,-1},{9316,4,0,-1},
+{9317,4,1,-1},{9321,4,0,-1},{9322,4,1,-1},{9325,4,0,-1},{9326,4,1,-1},
+{9329,4,0,-1},{9330,4,1,-1},{9334,4,0,-1},{9335,4,1,-1},{9338,4,0,-1},
+{9339,4,1,-1},{9342,4,0,-1},{9343,4,1,-1},{9347,4,0,-1},{9348,4,1,-1},
+{9351,4,0,-1},{9352,4,1,-1},{9355,4,0,-1},{9356,4,1,-1},{9360,4,0,-1},
+{9361,4,1,-1},{9364,4,0,-1},{9365,4,1,-1},{9368,4,0,-1},{9369,4,1,-1},
+{9373,4,0,-1},{9374,4,1,-1},{9377,4,0,-1},{9378,4,1,-1},{9381,4,0,-1},
+{9382,4,1,-1},{9386,4,0,-1},{9387,4,1,-1},{9320,4,0,-1},{9390,4,0,-1},
+{9391,4,1,-1},{9394,4,0,-1},{9395,4,1,-1},{9399,4,0,-1},{9400,4,1,-1},
+{9333,4,1,-1},{9403,4,0,-1},{9404,4,1,-1},{9407,4,0,-1},{9408,4,1,-1},
+{9412,4,0,-1},{9413,4,1,-1},{9416,4,0,-1},{9417,4,1,-1},{9420,4,0,-1},
+{9421,4,1,-1},{9425,4,0,-1},{9426,4,1,-1},{9429,4,0,-1},{9430,4,1,-1},
+{9433,4,0,-1},{9434,4,1,-1},{9438,4,0,-1},{9439,4,1,-1},{9442,4,0,-1},
+{9443,4,1,-1},{9446,4,0,-1},{9447,4,1,-1},{9451,4,0,-1},{9452,4,1,-1},
+{9455,4,0,-1},{9456,4,1,-1},{9459,4,0,-1},{9460,4,1,-1},{9464,4,0,-1},
+{9465,4,1,-1},{9468,4,0,-1},{9469,4,1,-1},{9472,4,0,-1},{9473,4,1,-1},
+{9477,4,0,-1},{9478,4,1,-1},{9481,4,0,-1},{9482,4,1,-1},{9485,4,0,-1},
+{9486,4,1,-1},{9490,4,0,-1},{9491,4,1,-1},{9424,4,0,-1},{9494,4,0,-1},
+{9495,4,1,-1},{9498,4,0,-1},{9499,4,1,-1},{9503,4,0,-1},{9504,4,1,-1},
+{9437,4,1,-1},{9507,4,0,-1},{9508,4,1,-1},{9511,4,0,-1},{9512,4,1,-1},
+{9516,4,0,-1},{9517,4,1,-1},{9520,4,0,-1},{9521,4,1,-1},{9524,4,0,-1},
+{9525,4,1,-1},{9529,4,0,-1},{9530,4,1,-1},{9533,4,0,-1},{9534,4,1,-1},
+{9537,4,0,-1},{9538,4,1,-1},{9542,4,0,-1},{9543,4,1,-1},{9546,4,0,-1},
+{9547,4,1,-1},{9550,4,0,-1},{9551,4,1,-1},{9555,4,0,-1},{9556,4,1,-1},
+{9559,4,0,-1},{9560,4,1,-1},{9563,4,0,-1},{9564,4,1,-1},{9568,4,0,-1},
+{9569,4,1,-1},{9572,4,0,-1},{9573,4,1,-1},{9576,4,0,-1},{9577,4,1,-1},
+{9581,4,0,-1},{9582,4,1,-1},{9585,4,0,-1},{9586,4,1,-1},{9589,4,0,-1},
+{9590,4,1,-1},{9594,4,0,-1},{9595,4,1,-1},{9528,4,0,-1},{9598,4,0,-1},
+{9599,4,1,-1},{9602,4,0,-1},{9603,4,1,-1},{9607,4,0,-1},{9608,4,1,-1},
+{9541,4,1,-1},{9611,4,0,-1},{9612,4,1,-1},{9615,4,0,-1},{9616,4,1,-1},
+{9620,4,0,-1},{9621,4,1,-1},{9624,4,0,-1},{9625,4,1,-1},{9628,4,0,-1},
+{9629,4,1,-1},{9633,4,0,-1},{9634,4,1,-1},{9637,4,0,-1},{9638,4,1,-1},
+{9641,4,0,-1},{9642,4,1,-1},{9646,4,0,-1},{9647,4,1,-1},{9650,4,0,-1},
+{9651,4,1,-1},{9654,4,0,-1},{9655,4,1,-1},{9659,4,0,-1},{9660,4,1,-1},
+{9663,4,0,-1},{9664,4,1,-1},{9667,4,0,-1},{9668,4,1,-1},{9672,4,0,-1},
+{9673,4,1,-1},{9676,4,0,-1},{9677,4,1,-1},{9680,4,0,-1},{9681,4,1,-1},
+{9685,4,0,-1},{9686,4,1,-1},{9689,4,0,-1},{9690,4,1,-1},{9693,4,0,-1},
+{9694,4,1,-1},{9698,4,0,-1},{9699,4,1,-1},{9632,4,0,-1},{9702,4,0,-1},
+{9703,4,1,-1},{9706,4,0,-1},{9707,4,1,-1},{9711,4,0,-1},{9712,4,1,-1},
+{9645,4,1,-1},{9715,4,0,-1},{9716,4,1,-1},{9719,4,0,-1},{9720,4,1,-1},
+{9724,4,0,-1},{9725,4,1,-1},{9728,4,0,-1},{9729,4,1,-1},{9732,4,0,-1},
+{9733,4,1,-1},{9737,4,0,-1},{9738,4,1,-1},{9741,4,0,-1},{9742,4,1,-1},
+{9745,4,0,-1},{9746,4,1,-1},{9750,4,0,-1},{9751,4,1,-1},{9754,4,0,-1},
+{9755,4,1,-1},{9758,4,0,-1},{9759,4,1,-1},{9763,4,0,-1},{9764,4,1,-1},
+{9767,4,0,-1},{9768,4,1,-1},{9771,4,0,-1},{9772,4,1,-1},{9776,4,0,-1},
+{9777,4,1,-1},{9780,4,0,-1},{9781,4,1,-1},{9784,4,0,-1},{9785,4,1,-1},
+{9789,4,0,-1},{9790,4,1,-1},{9793,4,0,-1},{9794,4,1,-1},{9797,4,0,-1},
+{9798,4,1,-1},{9802,4,0,-1},{9803,4,1,-1},{9736,4,0,-1},{9806,4,0,-1},
+{9807,4,1,-1},{9810,4,0,-1},{9811,4,1,-1},{9815,4,0,-1},{9816,4,1,-1},
+{9749,4,1,-1},{9819,4,0,-1},{9820,4,1,-1},{9823,4,0,-1},{9824,4,1,-1},
+{9828,4,0,-1},{9829,4,1,-1},{9832,4,0,-1},{9833,4,1,-1},{9836,4,0,-1},
+{9837,4,1,-1},{9841,4,0,-1},{9842,4,1,-1},{9845,4,0,-1},{9846,4,1,-1},
+{9849,4,0,-1},{9850,4,1,-1},{9854,4,0,-1},{9855,4,1,-1},{9858,4,0,-1},
+{9859,4,1,-1},{9862,4,0,-1},{9863,4,1,-1},{9867,4,0,-1},{9868,4,1,-1},
+{9871,4,0,-1},{9872,4,1,-1},{9875,4,0,-1},{9876,4,1,-1},{9880,4,0,-1},
+{9881,4,1,-1},{9884,4,0,-1},{9885,4,1,-1},{9888,4,0,-1},{9889,4,1,-1},
+{9893,4,0,-1},{9894,4,1,-1},{9897,4,0,-1},{9898,4,1,-1},{9901,4,0,-1},
+{9902,4,1,-1},{9906,4,0,-1},{9907,4,1,-1},{9840,4,0,-1},{9910,4,0,-1},
+{9911,4,1,-1},{9914,4,0,-1},{9915,4,1,-1},{9919,4,0,-1},{9920,4,1,-1},
+{9853,4,1,-1},{9923,4,0,-1},{9924,4,1,-1},{9927,4,0,-1},{9928,4,1,-1},
+{9932,4,0,-1},{9933,4,1,-1},{9936,4,0,-1},{9937,4,1,-1},{9940,4,0,-1},
+{9941,4,1,-1},{9945,4,0,-1},{9946,4,1,-1},{9949,4,0,-1},{9950,4,1,-1},
+{9953,4,0,-1},{9954,4,1,-1},{9958,4,0,-1},{9959,4,1,-1},{9962,4,0,-1},
+{9963,4,1,-1},{9966,4,0,-1},{9967,4,1,-1},{9971,4,0,-1},{9972,4,1,-1},
+{9975,4,0,-1},{9976,4,1,-1},{9979,4,0,-1},{9980,4,1,-1},{9984,4,0,-1},
+{9985,4,1,-1},{9988,4,0,-1},{9989,4,1,-1},{9992,4,0,-1},{9993,4,1,-1},
+{9997,4,0,-1},{9998,4,1,-1},{10001,4,0,-1},{10002,4,1,-1},{10005,4,0,-1},
+{10006,4,1,-1},{10010,4,0,-1},{10011,4,1,-1},{9944,4,0,-1},{10014,4,0,-1},
+{10015,4,1,-1},{10018,4,0,-1},{10019,4,1,-1},{10023,4,0,-1},{10024,4,1,-1},
+{9957,4,1,-1},{10027,4,0,-1},{10028,4,1,-1},{10031,4,0,-1},{10032,4,1,-1},
+{10036,4,0,-1},{10037,4,1,-1},{10040,4,0,-1},{10041,4,1,-1},{10044,4,0,-1},
+{10045,4,1,-1},{10049,4,0,-1},{10050,4,1,-1},{10053,4,0,-1},{10054,4,1,-1},
+{10057,4,0,-1},{10058,4,1,-1},{10062,4,0,-1},{10063,4,1,-1},{10066,4,0,-1},
+{10067,4,1,-1},{10070,4,0,-1},{10071,4,1,-1},{10075,4,0,-1},{10076,4,1,-1},
+{10079,4,0,-1},{10080,4,1,-1},{10083,4,0,-1},{10084,4,1,-1},{10088,4,0,-1},
+{10089,4,1,-1},{10092,4,0,-1},{10093,4,1,-1},{10096,4,0,-1},{10097,4,1,-1},
+{10101,4,0,-1},{10102,4,1,-1},{10105,4,0,-1},{10106,4,1,-1},{10109,4,0,-1},
+{10110,4,1,-1},{10114,4,0,-1},{10115,4,1,-1},{10048,4,0,-1},{10118,4,0,-1},
+{10119,4,1,-1},{10122,4,0,-1},{10123,4,1,-1},{10127,4,0,-1},{10128,4,1,-1},
+{10061,4,1,-1},{10131,4,0,-1},{10132,4,1,-1},{10135,4,0,-1},{10136,4,1,-1},
+{10140,4,0,-1},{10141,4,1,-1},{10144,4,0,-1},{10145,4,1,-1},{10148,4,0,-1},
+{10149,4,1,-1},{10153,4,0,-1},{10154,4,1,-1},{10157,4,0,-1},{10158,4,1,-1},
+{10161,4,0,-1},{10162,4,1,-1},{10166,4,0,-1},{10167,4,1,-1},{10170,4,0,-1},
+{10171,4,1,-1},{10174,4,0,-1},{10175,4,1,-1},{10179,4,0,-1},{10180,4,1,-1},
+{10183,4,0,-1},{10184,4,1,-1},{10187,4,0,-1},{10188,4,1,-1},{10192,4,0,-1},
+{10193,4,1,-1},{10196,4,0,-1},{10197,4,1,-1},{10200,4,0,-1},{10201,4,1,-1},
+{10205,4,0,-1},{10206,4,1,-1},{10209,4,0,-1},{10210,4,1,-1},{10213,4,0,-1},
+{10214,4,1,-1},{10218,4,0,-1},{10219,4,1,-1},{10152,4,0,-1},{10222,4,0,-1},
+{10223,4,1,-1},{10226,4,0,-1},{10227,4,1,-1},{10231,4,0,-1},{10232,4,1,-1},
+{10165,4,1,-1},{10235,4,0,-1},{10236,4,1,-1},{10239,4,0,-1},{10240,4,1,-1},
+{10244,4,0,-1},{10245,4,1,-1},{10248,4,0,-1},{10249,4,1,-1},{10252,4,0,-1},
+{10253,4,1,-1},{10257,4,0,-1},{10258,4,1,-1},{10261,4,0,-1},{10262,4,1,-1},
+{10265,4,0,-1},{10266,4,1,-1},{10270,4,0,-1},{10271,4,1,-1},{10274,4,0,-1},
+{10275,4,1,-1},{10278,4,0,-1},{10279,4,1,-1},{10283,4,0,-1},{10284,4,1,-1},
+{10287,4,0,-1},{10288,4,1,-1},{10291,4,0,-1},{10292,4,1,-1},{10296,4,0,-1},
+{10297,4,1,-1},{10300,4,0,-1},{10301,4,1,-1},{10304,4,0,-1},{10305,4,1,-1},
+{10309,4,0,-1},{10310,4,1,-1},{10313,4,0,-1},{10314,4,1,-1},{10317,4,0,-1},
+{10318,4,1,-1},{10322,4,0,-1},{10323,4,1,-1},{10256,4,0,-1},{10326,4,0,-1},
+{10327,4,1,-1},{10330,4,0,-1},{10331,4,1,-1},{10335,4,0,-1},{10336,4,1,-1},
+{10269,4,1,-1},{10339,4,0,-1},{10340,4,1,-1},{10343,4,0,-1},{10344,4,1,-1},
+{10348,4,0,-1},{10349,4,1,-1},{10352,4,0,-1},{10353,4,1,-1},{10356,4,0,-1},
+{10357,4,1,-1},{10361,4,0,-1},{10362,4,1,-1},{10365,4,0,-1},{10366,4,1,-1},
+{10369,4,0,-1},{10370,4,1,-1},{10374,4,0,-1},{10375,4,1,-1},{10378,4,0,-1},
+{10379,4,1,-1},{10382,4,0,-1},{10383,4,1,-1},{10387,4,0,-1},{10388,4,1,-1},
+{10391,4,0,-1},{10392,4,1,-1},{10395,4,0,-1},{10396,4,1,-1},{10400,4,0,-1},
+{10401,4,1,-1},{10404,4,0,-1},{10405,4,1,-1},{10408,4,0,-1},{10409,4,1,-1},
+{10413,4,0,-1},{10414,4,1,-1},{10417,4,0,-1},{10418,4,1,-1},{10421,4,0,-1},
+{10422,4,1,-1},{10426,4,0,-1},{10427,4,1,-1},{10360,4,0,-1},{10430,4,0,-1},
+{10431,4,1,-1},{10434,4,0,-1},{10435,4,1,-1},{10439,4,0,-1},{10440,4,1,-1},
+{10373,4,1,-1},{10443,4,0,-1},{10444,4,1,-1},{10447,4,0,-1},{10448,4,1,-1},
+{10452,4,0,-1},{10453,4,1,-1},{10456,4,0,-1},{10457,4,1,-1},{10460,4,0,-1},
+{10461,4,1,-1},{10465,4,0,-1},{10466,4,1,-1},{10469,4,0,-1},{10470,4,1,-1},
+{10473,4,0,-1},{10474,4,1,-1},{10478,4,0,-1},{10479,4,1,-1},{10482,4,0,-1},
+{10483,4,1,-1},{10486,4,0,-1},{10487,4,1,-1},{10491,4,0,-1},{10492,4,1,-1},
+{10495,4,0,-1},{10496,4,1,-1},{10499,4,0,-1},{10500,4,1,-1},{10504,4,0,-1},
+{10505,4,1,-1},{10508,4,0,-1},{10509,4,1,-1},{10512,4,0,-1},{10513,4,1,-1},
+{10517,4,0,-1},{10518,4,1,-1},{10521,4,0,-1},{10522,4,1,-1},{10525,4,0,-1},
+{10526,4,1,-1},{10530,4,0,-1},{10531,4,1,-1},{10464,4,0,-1},{10534,4,0,-1},
+{10535,4,1,-1},{10538,4,0,-1},{10539,4,1,-1},{10543,4,0,-1},{10544,4,1,-1},
+{10477,4,1,-1},{10547,4,0,-1},{10548,4,1,-1},{10551,4,0,-1},{10552,4,1,-1},
+{10556,4,0,-1},{10557,4,1,-1},{10560,4,0,-1},{10561,4,1,-1},{10564,4,0,-1},
+{10565,4,1,-1},{10569,4,0,-1},{10570,4,1,-1},{10573,4,0,-1},{10574,4,1,-1},
+{10577,4,0,-1},{10578,4,1,-1},{10582,4,0,-1},{10583,4,1,-1},{10586,4,0,-1},
+{10587,4,1,-1},{10590,4,0,-1},{10591,4,1,-1},{10595,4,0,-1},{10596,4,1,-1},
+{10599,4,0,-1},{10600,4,1,-1},{10603,4,0,-1},{10604,4,1,-1},{10608,4,0,-1},
+{10609,4,1,-1},{10612,4,0,-1},{10613,4,1,-1},{10616,4,0,-1},{10617,4,1,-1},
+{10621,4,0,-1},{10622,4,1,-1},{10625,4,0,-1},{10626,4,1,-1},{10629,4,0,-1},
+{10630,4,1,-1},{10634,4,0,-1},{10635,4,1,-1},{10568,4,0,-1},{10638,4,0,-1},
+{10639,4,1,-1},{10642,4,0,-1},{10643,4,1,-1},{10647,4,0,-1},{10648,4,1,-1},
+{10581,4,1,-1},{10651,4,0,-1},{10652,4,1,-1},{10655,4,0,-1},{10656,4,1,-1},
+{10660,4,0,-1},{10661,4,1,-1},{10664,4,0,-1},{10665,4,1,-1},{10668,4,0,-1},
+{10669,4,1,-1},{10673,4,0,-1},{10674,4,1,-1},{10677,4,0,-1},{10678,4,1,-1},
+{10681,4,0,-1},{10682,4,1,-1},{10686,4,0,-1},{10687,4,1,-1},{10690,4,0,-1},
+{10691,4,1,-1},{10694,4,0,-1},{10695,4,1,-1},{10699,4,0,-1},{10700,4,1,-1},
+{10703,4,0,-1},{10704,4,1,-1},{10707,4,0,-1},{10708,4,1,-1},{10712,4,0,-1},
+{10713,4,1,-1},{10716,4,0,-1},{10717,4,1,-1},{10720,4,0,-1},{10721,4,1,-1},
+{10725,4,0,-1},{10726,4,1,-1},{10729,4,0,-1},{10730,4,1,-1},{10733,4,0,-1},
+{10734,4,1,-1},{10738,4,0,-1},{10739,4,1,-1},{10672,4,0,-1},{10742,4,0,-1},
+{10743,4,1,-1},{10746,4,0,-1},{10747,4,1,-1},{10751,4,0,-1},{10752,4,1,-1},
+{10685,4,1,-1},{10755,4,0,-1},{10756,4,1,-1},{10759,4,0,-1},{10760,4,1,-1},
+{10764,4,0,-1},{10765,4,1,-1},{10768,4,0,-1},{10769,4,1,-1},{10772,4,0,-1},
+{10773,4,1,-1},{10777,4,0,-1},{10778,4,1,-1},{10781,4,0,-1},{10782,4,1,-1},
+{10785,4,0,-1},{10786,4,1,-1},{10790,4,0,-1},{10791,4,1,-1},{10794,4,0,-1},
+{10795,4,1,-1},{10798,4,0,-1},{10799,4,1,-1},{10803,4,0,-1},{10804,4,1,-1},
+{10807,4,0,-1},{10808,4,1,-1},{10811,4,0,-1},{10812,4,1,-1},{10816,4,0,-1},
+{10817,4,1,-1},{10820,4,0,-1},{10821,4,1,-1},{10824,4,0,-1},{10825,4,1,-1},
+{10829,4,0,-1},{10830,4,1,-1},{10833,4,0,-1},{10834,4,1,-1},{10837,4,0,-1},
+{10838,4,1,-1},{10842,4,0,-1},{10843,4,1,-1},{10776,4,0,-1},{10846,4,0,-1},
+{10847,4,1,-1},{10850,4,0,-1},{10851,4,1,-1},{10855,4,0,-1},{10856,4,1,-1},
+{10789,4,1,-1},{10859,4,0,-1},{10860,4,1,-1},{10863,4,0,-1},{10864,4,1,-1},
+{10868,4,0,-1},{10869,4,1,-1},{10872,4,0,-1},{10873,4,1,-1},{10876,4,0,-1},
+{10877,4,1,-1},{10881,4,0,-1},{10882,4,1,-1},{10885,4,0,-1},{10886,4,1,-1},
+{10889,4,0,-1},{10890,4,1,-1},{10894,4,0,-1},{10895,4,1,-1},{10898,4,0,-1},
+{10899,4,1,-1},{10902,4,0,-1},{10903,4,1,-1},{10907,4,0,-1},{10908,4,1,-1},
+{10911,4,0,-1},{10912,4,1,-1},{10915,4,0,-1},{10916,4,1,-1},{10920,4,0,-1},
+{10921,4,1,-1},{10924,4,0,-1},{10925,4,1,-1},{10928,4,0,-1},{10929,4,1,-1},
+{10933,4,0,-1},{10934,4,1,-1},{10937,4,0,-1},{10938,4,1,-1},{10941,4,0,-1},
+{10942,4,1,-1},{10946,4,0,-1},{10947,4,1,-1},{10880,4,0,-1},{10950,4,0,-1},
+{10951,4,1,-1},{10954,4,0,-1},{10955,4,1,-1},{10959,4,0,-1},{10960,4,1,-1},
+{10893,4,1,-1},{10963,4,0,-1},{10964,4,1,-1},{10967,4,0,-1},{10968,4,1,-1},
+{10972,4,0,-1},{10973,4,1,-1},{10976,4,0,-1},{10977,4,1,-1},{10980,4,0,-1},
+{10981,4,1,-1},{10985,4,0,-1},{10986,4,1,-1},{10989,4,0,-1},{10990,4,1,-1},
+{10993,4,0,-1},{10994,4,1,-1},{10998,4,0,-1},{10999,4,1,-1},{11002,4,0,-1},
+{11003,4,1,-1},{11006,4,0,-1},{11007,4,1,-1},{11012,4,1,-1},{11016,4,1,-1},
+{11020,4,1,-1},{11025,4,1,-1},{11029,4,1,-1},{11033,4,1,-1},{11038,4,1,-1},
+{11042,4,1,-1},{11046,4,1,-1},{11051,4,1,-1},{10984,4,0,-1},{11055,4,1,-1},
+{11058,4,0,-1},{11059,4,1,-1},{10997,4,1,-1},{11067,4,0,-1},{11068,4,1,-1},
+{11072,4,1,-1},{11077,4,1,-1},{11081,4,1,-1},{11085,4,1,-1},{11090,4,1,-1},
+{11094,4,1,-1},{11098,4,1,-1},{11102,4,0,-1},{11103,4,1,-1},{11107,4,1,-1},
+{11111,4,1,-1},{11116,4,1,-1},{11120,4,1,-1},{11129,4,1,-1},{11133,4,1,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on half rate channels TS4, SS0 and SS1 */
+struct fn_sample test_fn_tch_h_ts_5_ss0_ss1[] = {
+{5269,5,0,-1},{5278,5,0,-1},{5286,5,0,-1},{5295,5,0,-1},{5304,5,0,-1},
+{5312,5,0,-1},{5321,5,0,-1},{5330,5,0,-1},{5264,5,0,-1},{5338,5,0,-1},
+{5347,5,0,-1},{5351,5,0,-1},{5356,5,0,-1},{5360,5,0,-1},{5364,5,0,-1},
+{5369,5,0,-1},{5373,5,0,-1},{5377,5,0,-1},{5382,5,0,-1},{5386,5,0,-1},
+{5390,5,0,-1},{5395,5,0,-1},{5399,5,0,-1},{5403,5,0,-1},{5408,5,0,-1},
+{5412,5,0,-1},{5416,5,0,-1},{5421,5,0,-1},{5425,5,0,-1},{5429,5,0,-1},
+{5434,5,0,-1},{5368,5,0,-1},{5438,5,0,-1},{5442,5,0,-1},{5447,5,0,-1},
+{5451,5,0,-1},{5455,5,0,-1},{5460,5,0,-1},{5464,5,0,-1},{5468,5,0,-1},
+{5473,5,0,-1},{5477,5,0,-1},{5481,5,0,-1},{5486,5,0,-1},{5490,5,0,-1},
+{5494,5,0,-1},{5499,5,0,-1},{5503,5,0,-1},{5507,5,0,-1},{5512,5,0,-1},
+{5516,5,0,-1},{5520,5,0,-1},{5525,5,0,-1},{5529,5,0,-1},{5533,5,0,-1},
+{5538,5,0,-1},{5472,5,0,-1},{5542,5,0,-1},{5546,5,0,-1},{5551,5,0,-1},
+{5555,5,0,-1},{5559,5,0,-1},{5564,5,0,-1},{5568,5,0,-1},{5572,5,0,-1},
+{5577,5,0,-1},{5581,5,0,-1},{5585,5,0,-1},{5590,5,0,-1},{5594,5,0,-1},
+{5598,5,0,-1},{5603,5,0,-1},{5607,5,0,-1},{5611,5,0,-1},{5616,5,0,-1},
+{5620,5,0,-1},{5624,5,0,-1},{5629,5,0,-1},{5633,5,0,-1},{5637,5,0,-1},
+{5638,5,1,-1},{5642,5,0,-1},{5576,5,0,-1},{5646,5,0,-1},{5647,5,1,-1},
+{5650,5,0,-1},{5655,5,0,-1},{5656,5,1,-1},{5659,5,0,-1},{5663,5,0,-1},
+{5668,5,0,-1},{5669,5,1,-1},{5672,5,0,-1},{5676,5,0,-1},{5677,5,1,-1},
+{5681,5,0,-1},{5685,5,0,-1},{5686,5,1,-1},{5689,5,0,-1},{5694,5,0,-1},
+{5695,5,1,-1},{5698,5,0,-1},{5702,5,0,-1},{5703,5,1,-1},{5707,5,0,-1},
+{5711,5,0,-1},{5712,5,1,-1},{5715,5,0,-1},{5720,5,0,-1},{5721,5,1,-1},
+{5724,5,0,-1},{5728,5,0,-1},{5729,5,1,-1},{5733,5,0,-1},{5737,5,0,-1},
+{5738,5,1,-1},{5741,5,0,-1},{5746,5,0,-1},{5747,5,1,-1},{5680,5,0,-1},
+{5750,5,0,-1},{5754,5,0,-1},{5755,5,1,-1},{5759,5,0,-1},{5693,5,1,-1},
+{5763,5,0,-1},{5764,5,1,-1},{5767,5,0,-1},{5772,5,0,-1},{5773,5,1,-1},
+{5776,5,0,-1},{5780,5,0,-1},{5781,5,1,-1},{5785,5,0,-1},{5789,5,0,-1},
+{5790,5,1,-1},{5793,5,0,-1},{5798,5,0,-1},{5799,5,1,-1},{5802,5,0,-1},
+{5806,5,0,-1},{5807,5,1,-1},{5811,5,0,-1},{5815,5,0,-1},{5816,5,1,-1},
+{5819,5,0,-1},{5820,5,1,-1},{5824,5,0,-1},{5825,5,1,-1},{5828,5,0,-1},
+{5829,5,1,-1},{5832,5,0,-1},{5833,5,1,-1},{5837,5,0,-1},{5838,5,1,-1},
+{5841,5,0,-1},{5842,5,1,-1},{5845,5,0,-1},{5850,5,0,-1},{5851,5,1,-1},
+{5784,5,0,-1},{5854,5,0,-1},{5855,5,1,-1},{5858,5,0,-1},{5859,5,1,-1},
+{5863,5,0,-1},{5864,5,1,-1},{5797,5,1,-1},{5867,5,0,-1},{5868,5,1,-1},
+{5871,5,0,-1},{5872,5,1,-1},{5876,5,0,-1},{5877,5,1,-1},{5880,5,0,-1},
+{5881,5,1,-1},{5884,5,0,-1},{5885,5,1,-1},{5890,5,1,-1},{5893,5,0,-1},
+{5894,5,1,-1},{5897,5,0,-1},{5898,5,1,-1},{5902,5,0,-1},{5903,5,1,-1},
+{5906,5,0,-1},{5907,5,1,-1},{5910,5,0,-1},{5911,5,1,-1},{5915,5,0,-1},
+{5916,5,1,-1},{5919,5,0,-1},{5920,5,1,-1},{5923,5,0,-1},{5924,5,1,-1},
+{5928,5,0,-1},{5929,5,1,-1},{5932,5,0,-1},{5933,5,1,-1},{5936,5,0,-1},
+{5937,5,1,-1},{5941,5,0,-1},{5942,5,1,-1},{5945,5,0,-1},{5946,5,1,-1},
+{5949,5,0,-1},{5950,5,1,-1},{5954,5,0,-1},{5955,5,1,-1},{5888,5,0,-1},
+{5958,5,0,-1},{5959,5,1,-1},{5962,5,0,-1},{5963,5,1,-1},{5967,5,0,-1},
+{5968,5,1,-1},{5901,5,1,-1},{5971,5,0,-1},{5972,5,1,-1},{5975,5,0,-1},
+{5976,5,1,-1},{5980,5,0,-1},{5981,5,1,-1},{5984,5,0,-1},{5988,5,0,-1},
+{5989,5,1,-1},{5993,5,0,-1},{5994,5,1,-1},{5997,5,0,-1},{5998,5,1,-1},
+{6001,5,0,-1},{6002,5,1,-1},{6006,5,0,-1},{6007,5,1,-1},{6010,5,0,-1},
+{6011,5,1,-1},{6014,5,0,-1},{6015,5,1,-1},{6019,5,0,-1},{6020,5,1,-1},
+{6023,5,0,-1},{6024,5,1,-1},{6028,5,1,-1},{6032,5,0,-1},{6033,5,1,-1},
+{6036,5,0,-1},{6040,5,0,-1},{6041,5,1,-1},{6045,5,0,-1},{6046,5,1,-1},
+{6049,5,0,-1},{6050,5,1,-1},{6053,5,0,-1},{6054,5,1,-1},{6058,5,0,-1},
+{6059,5,1,-1},{5992,5,0,-1},{6062,5,0,-1},{6063,5,1,-1},{6066,5,0,-1},
+{6067,5,1,-1},{6071,5,0,-1},{6072,5,1,-1},{6005,5,1,-1},{6075,5,0,-1},
+{6076,5,1,-1},{6079,5,0,-1},{6080,5,1,-1},{6084,5,0,-1},{6085,5,1,-1},
+{6088,5,0,-1},{6089,5,1,-1},{6092,5,0,-1},{6093,5,1,-1},{6097,5,0,-1},
+{6098,5,1,-1},{6101,5,0,-1},{6102,5,1,-1},{6105,5,0,-1},{6106,5,1,-1},
+{6110,5,0,-1},{6111,5,1,-1},{6114,5,0,-1},{6115,5,1,-1},{6118,5,0,-1},
+{6119,5,1,-1},{6123,5,0,-1},{6124,5,1,-1},{6127,5,0,-1},{6128,5,1,-1},
+{6131,5,0,-1},{6132,5,1,-1},{6136,5,0,-1},{6137,5,1,-1},{6140,5,0,-1},
+{6141,5,1,-1},{6144,5,0,-1},{6145,5,1,-1},{6149,5,0,-1},{6150,5,1,-1},
+{6153,5,0,-1},{6154,5,1,-1},{6157,5,0,-1},{6158,5,1,-1},{6162,5,0,-1},
+{6163,5,1,-1},{6096,5,0,-1},{6166,5,0,-1},{6167,5,1,-1},{6170,5,0,-1},
+{6171,5,1,-1},{6175,5,0,-1},{6176,5,1,-1},{6109,5,1,-1},{6179,5,0,-1},
+{6180,5,1,-1},{6183,5,0,-1},{6184,5,1,-1},{6188,5,0,-1},{6189,5,1,-1},
+{6192,5,0,-1},{6193,5,1,-1},{6196,5,0,-1},{6197,5,1,-1},{6201,5,0,-1},
+{6202,5,1,-1},{6205,5,0,-1},{6206,5,1,-1},{6209,5,0,-1},{6210,5,1,-1},
+{6214,5,0,-1},{6215,5,1,-1},{6218,5,0,-1},{6219,5,1,-1},{6222,5,0,-1},
+{6223,5,1,-1},{6227,5,0,-1},{6228,5,1,-1},{6231,5,0,-1},{6232,5,1,-1},
+{6235,5,0,-1},{6236,5,1,-1},{6240,5,0,-1},{6241,5,1,-1},{6244,5,0,-1},
+{6245,5,1,-1},{6248,5,0,-1},{6249,5,1,-1},{6253,5,0,-1},{6254,5,1,-1},
+{6257,5,0,-1},{6258,5,1,-1},{6261,5,0,-1},{6262,5,1,-1},{6266,5,0,-1},
+{6267,5,1,-1},{6200,5,0,-1},{6270,5,0,-1},{6271,5,1,-1},{6274,5,0,-1},
+{6275,5,1,-1},{6279,5,0,-1},{6280,5,1,-1},{6213,5,1,-1},{6283,5,0,-1},
+{6284,5,1,-1},{6287,5,0,-1},{6288,5,1,-1},{6292,5,0,-1},{6293,5,1,-1},
+{6296,5,0,-1},{6297,5,1,-1},{6300,5,0,-1},{6301,5,1,-1},{6305,5,0,-1},
+{6306,5,1,-1},{6309,5,0,-1},{6310,5,1,-1},{6313,5,0,-1},{6314,5,1,-1},
+{6318,5,0,-1},{6319,5,1,-1},{6322,5,0,-1},{6323,5,1,-1},{6326,5,0,-1},
+{6327,5,1,-1},{6331,5,0,-1},{6332,5,1,-1},{6335,5,0,-1},{6336,5,1,-1},
+{6339,5,0,-1},{6340,5,1,-1},{6344,5,0,-1},{6345,5,1,-1},{6348,5,0,-1},
+{6349,5,1,-1},{6352,5,0,-1},{6353,5,1,-1},{6357,5,0,-1},{6358,5,1,-1},
+{6361,5,0,-1},{6362,5,1,-1},{6365,5,0,-1},{6366,5,1,-1},{6370,5,0,-1},
+{6371,5,1,-1},{6304,5,0,-1},{6374,5,0,-1},{6375,5,1,-1},{6378,5,0,-1},
+{6379,5,1,-1},{6383,5,0,-1},{6384,5,1,-1},{6317,5,1,-1},{6387,5,0,-1},
+{6388,5,1,-1},{6391,5,0,-1},{6392,5,1,-1},{6396,5,0,-1},{6397,5,1,-1},
+{6400,5,0,-1},{6401,5,1,-1},{6404,5,0,-1},{6405,5,1,-1},{6409,5,0,-1},
+{6410,5,1,-1},{6413,5,0,-1},{6414,5,1,-1},{6417,5,0,-1},{6418,5,1,-1},
+{6422,5,0,-1},{6423,5,1,-1},{6426,5,0,-1},{6427,5,1,-1},{6430,5,0,-1},
+{6431,5,1,-1},{6435,5,0,-1},{6436,5,1,-1},{6439,5,0,-1},{6440,5,1,-1},
+{6443,5,0,-1},{6444,5,1,-1},{6448,5,0,-1},{6449,5,1,-1},{6452,5,0,-1},
+{6453,5,1,-1},{6456,5,0,-1},{6457,5,1,-1},{6461,5,0,-1},{6462,5,1,-1},
+{6465,5,0,-1},{6466,5,1,-1},{6469,5,0,-1},{6470,5,1,-1},{6474,5,0,-1},
+{6475,5,1,-1},{6408,5,0,-1},{6478,5,0,-1},{6479,5,1,-1},{6482,5,0,-1},
+{6483,5,1,-1},{6487,5,0,-1},{6488,5,1,-1},{6421,5,1,-1},{6491,5,0,-1},
+{6492,5,1,-1},{6495,5,0,-1},{6496,5,1,-1},{6500,5,0,-1},{6501,5,1,-1},
+{6504,5,0,-1},{6505,5,1,-1},{6508,5,0,-1},{6509,5,1,-1},{6513,5,0,-1},
+{6514,5,1,-1},{6517,5,0,-1},{6518,5,1,-1},{6521,5,0,-1},{6522,5,1,-1},
+{6526,5,0,-1},{6527,5,1,-1},{6530,5,0,-1},{6531,5,1,-1},{6534,5,0,-1},
+{6535,5,1,-1},{6539,5,0,-1},{6540,5,1,-1},{6543,5,0,-1},{6544,5,1,-1},
+{6547,5,0,-1},{6548,5,1,-1},{6552,5,0,-1},{6553,5,1,-1},{6556,5,0,-1},
+{6557,5,1,-1},{6560,5,0,-1},{6561,5,1,-1},{6565,5,0,-1},{6566,5,1,-1},
+{6569,5,0,-1},{6570,5,1,-1},{6573,5,0,-1},{6574,5,1,-1},{6578,5,0,-1},
+{6579,5,1,-1},{6512,5,0,-1},{6582,5,0,-1},{6583,5,1,-1},{6586,5,0,-1},
+{6587,5,1,-1},{6591,5,0,-1},{6592,5,1,-1},{6525,5,1,-1},{6595,5,0,-1},
+{6596,5,1,-1},{6599,5,0,-1},{6600,5,1,-1},{6604,5,0,-1},{6605,5,1,-1},
+{6608,5,0,-1},{6609,5,1,-1},{6612,5,0,-1},{6613,5,1,-1},{6617,5,0,-1},
+{6618,5,1,-1},{6621,5,0,-1},{6622,5,1,-1},{6625,5,0,-1},{6626,5,1,-1},
+{6630,5,0,-1},{6631,5,1,-1},{6634,5,0,-1},{6635,5,1,-1},{6638,5,0,-1},
+{6639,5,1,-1},{6643,5,0,-1},{6644,5,1,-1},{6647,5,0,-1},{6648,5,1,-1},
+{6651,5,0,-1},{6652,5,1,-1},{6656,5,0,-1},{6657,5,1,-1},{6660,5,0,-1},
+{6661,5,1,-1},{6664,5,0,-1},{6665,5,1,-1},{6669,5,0,-1},{6670,5,1,-1},
+{6673,5,0,-1},{6674,5,1,-1},{6677,5,0,-1},{6678,5,1,-1},{6682,5,0,-1},
+{6683,5,1,-1},{6616,5,0,-1},{6686,5,0,-1},{6687,5,1,-1},{6690,5,0,-1},
+{6691,5,1,-1},{6695,5,0,-1},{6696,5,1,-1},{6629,5,1,-1},{6699,5,0,-1},
+{6700,5,1,-1},{6703,5,0,-1},{6704,5,1,-1},{6708,5,0,-1},{6709,5,1,-1},
+{6712,5,0,-1},{6713,5,1,-1},{6716,5,0,-1},{6717,5,1,-1},{6721,5,0,-1},
+{6722,5,1,-1},{6725,5,0,-1},{6726,5,1,-1},{6729,5,0,-1},{6730,5,1,-1},
+{6734,5,0,-1},{6735,5,1,-1},{6738,5,0,-1},{6739,5,1,-1},{6742,5,0,-1},
+{6743,5,1,-1},{6747,5,0,-1},{6748,5,1,-1},{6751,5,0,-1},{6752,5,1,-1},
+{6755,5,0,-1},{6756,5,1,-1},{6760,5,0,-1},{6761,5,1,-1},{6764,5,0,-1},
+{6765,5,1,-1},{6768,5,0,-1},{6769,5,1,-1},{6773,5,0,-1},{6774,5,1,-1},
+{6777,5,0,-1},{6778,5,1,-1},{6781,5,0,-1},{6782,5,1,-1},{6786,5,0,-1},
+{6787,5,1,-1},{6720,5,0,-1},{6790,5,0,-1},{6791,5,1,-1},{6794,5,0,-1},
+{6795,5,1,-1},{6799,5,0,-1},{6800,5,1,-1},{6733,5,1,-1},{6803,5,0,-1},
+{6804,5,1,-1},{6807,5,0,-1},{6808,5,1,-1},{6812,5,0,-1},{6813,5,1,-1},
+{6816,5,0,-1},{6817,5,1,-1},{6820,5,0,-1},{6821,5,1,-1},{6825,5,0,-1},
+{6826,5,1,-1},{6829,5,0,-1},{6830,5,1,-1},{6833,5,0,-1},{6834,5,1,-1},
+{6838,5,0,-1},{6839,5,1,-1},{6842,5,0,-1},{6843,5,1,-1},{6846,5,0,-1},
+{6847,5,1,-1},{6851,5,0,-1},{6852,5,1,-1},{6855,5,0,-1},{6856,5,1,-1},
+{6859,5,0,-1},{6860,5,1,-1},{6864,5,0,-1},{6865,5,1,-1},{6868,5,0,-1},
+{6869,5,1,-1},{6872,5,0,-1},{6873,5,1,-1},{6877,5,0,-1},{6878,5,1,-1},
+{6881,5,0,-1},{6882,5,1,-1},{6885,5,0,-1},{6886,5,1,-1},{6890,5,0,-1},
+{6891,5,1,-1},{6824,5,0,-1},{6894,5,0,-1},{6895,5,1,-1},{6898,5,0,-1},
+{6899,5,1,-1},{6903,5,0,-1},{6904,5,1,-1},{6837,5,1,-1},{6907,5,0,-1},
+{6908,5,1,-1},{6911,5,0,-1},{6912,5,1,-1},{6916,5,0,-1},{6917,5,1,-1},
+{6920,5,0,-1},{6921,5,1,-1},{6924,5,0,-1},{6925,5,1,-1},{6929,5,0,-1},
+{6930,5,1,-1},{6933,5,0,-1},{6934,5,1,-1},{6937,5,0,-1},{6938,5,1,-1},
+{6942,5,0,-1},{6943,5,1,-1},{6946,5,0,-1},{6947,5,1,-1},{6950,5,0,-1},
+{6951,5,1,-1},{6955,5,0,-1},{6956,5,1,-1},{6959,5,0,-1},{6960,5,1,-1},
+{6963,5,0,-1},{6964,5,1,-1},{6968,5,0,-1},{6969,5,1,-1},{6972,5,0,-1},
+{6973,5,1,-1},{6976,5,0,-1},{6977,5,1,-1},{6981,5,0,-1},{6982,5,1,-1},
+{6985,5,0,-1},{6986,5,1,-1},{6989,5,0,-1},{6990,5,1,-1},{6994,5,0,-1},
+{6995,5,1,-1},{6928,5,0,-1},{6998,5,0,-1},{6999,5,1,-1},{7002,5,0,-1},
+{7003,5,1,-1},{7007,5,0,-1},{7008,5,1,-1},{6941,5,1,-1},{7011,5,0,-1},
+{7012,5,1,-1},{7015,5,0,-1},{7016,5,1,-1},{7020,5,0,-1},{7021,5,1,-1},
+{7024,5,0,-1},{7025,5,1,-1},{7028,5,0,-1},{7029,5,1,-1},{7033,5,0,-1},
+{7034,5,1,-1},{7037,5,0,-1},{7038,5,1,-1},{7041,5,0,-1},{7042,5,1,-1},
+{7046,5,0,-1},{7047,5,1,-1},{7050,5,0,-1},{7051,5,1,-1},{7054,5,0,-1},
+{7055,5,1,-1},{7059,5,0,-1},{7060,5,1,-1},{7063,5,0,-1},{7064,5,1,-1},
+{7067,5,0,-1},{7068,5,1,-1},{7072,5,0,-1},{7073,5,1,-1},{7076,5,0,-1},
+{7077,5,1,-1},{7080,5,0,-1},{7081,5,1,-1},{7085,5,0,-1},{7086,5,1,-1},
+{7089,5,0,-1},{7090,5,1,-1},{7093,5,0,-1},{7094,5,1,-1},{7098,5,0,-1},
+{7099,5,1,-1},{7032,5,0,-1},{7102,5,0,-1},{7103,5,1,-1},{7106,5,0,-1},
+{7107,5,1,-1},{7111,5,0,-1},{7112,5,1,-1},{7045,5,1,-1},{7115,5,0,-1},
+{7116,5,1,-1},{7119,5,0,-1},{7120,5,1,-1},{7124,5,0,-1},{7125,5,1,-1},
+{7128,5,0,-1},{7129,5,1,-1},{7132,5,0,-1},{7133,5,1,-1},{7137,5,0,-1},
+{7138,5,1,-1},{7141,5,0,-1},{7142,5,1,-1},{7145,5,0,-1},{7146,5,1,-1},
+{7150,5,0,-1},{7151,5,1,-1},{7154,5,0,-1},{7155,5,1,-1},{7158,5,0,-1},
+{7159,5,1,-1},{7163,5,0,-1},{7164,5,1,-1},{7167,5,0,-1},{7168,5,1,-1},
+{7171,5,0,-1},{7172,5,1,-1},{7176,5,0,-1},{7177,5,1,-1},{7180,5,0,-1},
+{7181,5,1,-1},{7184,5,0,-1},{7185,5,1,-1},{7189,5,0,-1},{7190,5,1,-1},
+{7193,5,0,-1},{7194,5,1,-1},{7197,5,0,-1},{7198,5,1,-1},{7202,5,0,-1},
+{7203,5,1,-1},{7136,5,0,-1},{7206,5,0,-1},{7207,5,1,-1},{7210,5,0,-1},
+{7211,5,1,-1},{7215,5,0,-1},{7216,5,1,-1},{7149,5,1,-1},{7219,5,0,-1},
+{7220,5,1,-1},{7223,5,0,-1},{7224,5,1,-1},{7228,5,0,-1},{7229,5,1,-1},
+{7232,5,0,-1},{7233,5,1,-1},{7236,5,0,-1},{7237,5,1,-1},{7241,5,0,-1},
+{7242,5,1,-1},{7245,5,0,-1},{7246,5,1,-1},{7249,5,0,-1},{7250,5,1,-1},
+{7254,5,0,-1},{7255,5,1,-1},{7258,5,0,-1},{7259,5,1,-1},{7262,5,0,-1},
+{7263,5,1,-1},{7267,5,0,-1},{7268,5,1,-1},{7271,5,0,-1},{7272,5,1,-1},
+{7275,5,0,-1},{7276,5,1,-1},{7280,5,0,-1},{7281,5,1,-1},{7284,5,0,-1},
+{7285,5,1,-1},{7288,5,0,-1},{7289,5,1,-1},{7293,5,0,-1},{7294,5,1,-1},
+{7297,5,0,-1},{7298,5,1,-1},{7301,5,0,-1},{7302,5,1,-1},{7306,5,0,-1},
+{7307,5,1,-1},{7240,5,0,-1},{7310,5,0,-1},{7311,5,1,-1},{7314,5,0,-1},
+{7315,5,1,-1},{7319,5,0,-1},{7320,5,1,-1},{7253,5,1,-1},{7323,5,0,-1},
+{7324,5,1,-1},{7327,5,0,-1},{7328,5,1,-1},{7332,5,0,-1},{7333,5,1,-1},
+{7336,5,0,-1},{7337,5,1,-1},{7340,5,0,-1},{7341,5,1,-1},{7345,5,0,-1},
+{7346,5,1,-1},{7349,5,0,-1},{7350,5,1,-1},{7353,5,0,-1},{7354,5,1,-1},
+{7358,5,0,-1},{7359,5,1,-1},{7362,5,0,-1},{7363,5,1,-1},{7366,5,0,-1},
+{7367,5,1,-1},{7371,5,0,-1},{7372,5,1,-1},{7375,5,0,-1},{7376,5,1,-1},
+{7379,5,0,-1},{7380,5,1,-1},{7384,5,0,-1},{7385,5,1,-1},{7388,5,0,-1},
+{7389,5,1,-1},{7392,5,0,-1},{7393,5,1,-1},{7397,5,0,-1},{7398,5,1,-1},
+{7401,5,0,-1},{7402,5,1,-1},{7405,5,0,-1},{7406,5,1,-1},{7410,5,0,-1},
+{7411,5,1,-1},{7344,5,0,-1},{7414,5,0,-1},{7415,5,1,-1},{7418,5,0,-1},
+{7419,5,1,-1},{7423,5,0,-1},{7424,5,1,-1},{7357,5,1,-1},{7427,5,0,-1},
+{7428,5,1,-1},{7431,5,0,-1},{7432,5,1,-1},{7436,5,0,-1},{7437,5,1,-1},
+{7440,5,0,-1},{7441,5,1,-1},{7444,5,0,-1},{7445,5,1,-1},{7449,5,0,-1},
+{7450,5,1,-1},{7453,5,0,-1},{7454,5,1,-1},{7457,5,0,-1},{7458,5,1,-1},
+{7462,5,0,-1},{7463,5,1,-1},{7466,5,0,-1},{7467,5,1,-1},{7470,5,0,-1},
+{7471,5,1,-1},{7475,5,0,-1},{7476,5,1,-1},{7479,5,0,-1},{7480,5,1,-1},
+{7483,5,0,-1},{7484,5,1,-1},{7488,5,0,-1},{7489,5,1,-1},{7492,5,0,-1},
+{7493,5,1,-1},{7496,5,0,-1},{7497,5,1,-1},{7501,5,0,-1},{7502,5,1,-1},
+{7505,5,0,-1},{7506,5,1,-1},{7509,5,0,-1},{7510,5,1,-1},{7514,5,0,-1},
+{7515,5,1,-1},{7448,5,0,-1},{7518,5,0,-1},{7519,5,1,-1},{7522,5,0,-1},
+{7523,5,1,-1},{7527,5,0,-1},{7528,5,1,-1},{7461,5,1,-1},{7531,5,0,-1},
+{7532,5,1,-1},{7535,5,0,-1},{7536,5,1,-1},{7540,5,0,-1},{7541,5,1,-1},
+{7544,5,0,-1},{7545,5,1,-1},{7548,5,0,-1},{7549,5,1,-1},{7553,5,0,-1},
+{7554,5,1,-1},{7557,5,0,-1},{7558,5,1,-1},{7561,5,0,-1},{7562,5,1,-1},
+{7566,5,0,-1},{7567,5,1,-1},{7570,5,0,-1},{7571,5,1,-1},{7574,5,0,-1},
+{7575,5,1,-1},{7579,5,0,-1},{7580,5,1,-1},{7583,5,0,-1},{7584,5,1,-1},
+{7587,5,0,-1},{7588,5,1,-1},{7592,5,0,-1},{7593,5,1,-1},{7596,5,0,-1},
+{7597,5,1,-1},{7600,5,0,-1},{7601,5,1,-1},{7605,5,0,-1},{7606,5,1,-1},
+{7609,5,0,-1},{7610,5,1,-1},{7613,5,0,-1},{7614,5,1,-1},{7618,5,0,-1},
+{7619,5,1,-1},{7552,5,0,-1},{7622,5,0,-1},{7623,5,1,-1},{7626,5,0,-1},
+{7627,5,1,-1},{7631,5,0,-1},{7632,5,1,-1},{7565,5,1,-1},{7635,5,0,-1},
+{7636,5,1,-1},{7639,5,0,-1},{7640,5,1,-1},{7644,5,0,-1},{7645,5,1,-1},
+{7648,5,0,-1},{7649,5,1,-1},{7652,5,0,-1},{7653,5,1,-1},{7657,5,0,-1},
+{7658,5,1,-1},{7661,5,0,-1},{7662,5,1,-1},{7665,5,0,-1},{7666,5,1,-1},
+{7670,5,0,-1},{7671,5,1,-1},{7674,5,0,-1},{7675,5,1,-1},{7678,5,0,-1},
+{7679,5,1,-1},{7683,5,0,-1},{7684,5,1,-1},{7687,5,0,-1},{7688,5,1,-1},
+{7691,5,0,-1},{7692,5,1,-1},{7696,5,0,-1},{7697,5,1,-1},{7700,5,0,-1},
+{7701,5,1,-1},{7704,5,0,-1},{7705,5,1,-1},{7709,5,0,-1},{7710,5,1,-1},
+{7713,5,0,-1},{7714,5,1,-1},{7717,5,0,-1},{7718,5,1,-1},{7722,5,0,-1},
+{7723,5,1,-1},{7656,5,0,-1},{7726,5,0,-1},{7727,5,1,-1},{7730,5,0,-1},
+{7731,5,1,-1},{7735,5,0,-1},{7736,5,1,-1},{7669,5,1,-1},{7739,5,0,-1},
+{7740,5,1,-1},{7743,5,0,-1},{7744,5,1,-1},{7748,5,0,-1},{7749,5,1,-1},
+{7752,5,0,-1},{7753,5,1,-1},{7756,5,0,-1},{7757,5,1,-1},{7761,5,0,-1},
+{7762,5,1,-1},{7765,5,0,-1},{7766,5,1,-1},{7769,5,0,-1},{7770,5,1,-1},
+{7774,5,0,-1},{7775,5,1,-1},{7778,5,0,-1},{7779,5,1,-1},{7782,5,0,-1},
+{7783,5,1,-1},{7787,5,0,-1},{7788,5,1,-1},{7791,5,0,-1},{7792,5,1,-1},
+{7795,5,0,-1},{7796,5,1,-1},{7800,5,0,-1},{7801,5,1,-1},{7804,5,0,-1},
+{7805,5,1,-1},{7808,5,0,-1},{7809,5,1,-1},{7813,5,0,-1},{7814,5,1,-1},
+{7817,5,0,-1},{7818,5,1,-1},{7821,5,0,-1},{7822,5,1,-1},{7826,5,0,-1},
+{7827,5,1,-1},{7760,5,0,-1},{7830,5,0,-1},{7831,5,1,-1},{7834,5,0,-1},
+{7835,5,1,-1},{7839,5,0,-1},{7840,5,1,-1},{7773,5,1,-1},{7843,5,0,-1},
+{7844,5,1,-1},{7847,5,0,-1},{7848,5,1,-1},{7852,5,0,-1},{7853,5,1,-1},
+{7856,5,0,-1},{7857,5,1,-1},{7860,5,0,-1},{7861,5,1,-1},{7865,5,0,-1},
+{7866,5,1,-1},{7869,5,0,-1},{7870,5,1,-1},{7873,5,0,-1},{7874,5,1,-1},
+{7878,5,0,-1},{7879,5,1,-1},{7882,5,0,-1},{7883,5,1,-1},{7886,5,0,-1},
+{7887,5,1,-1},{7891,5,0,-1},{7892,5,1,-1},{7895,5,0,-1},{7896,5,1,-1},
+{7899,5,0,-1},{7900,5,1,-1},{7904,5,0,-1},{7905,5,1,-1},{7908,5,0,-1},
+{7909,5,1,-1},{7912,5,0,-1},{7913,5,1,-1},{7917,5,0,-1},{7918,5,1,-1},
+{7921,5,0,-1},{7922,5,1,-1},{7925,5,0,-1},{7926,5,1,-1},{7930,5,0,-1},
+{7931,5,1,-1},{7864,5,0,-1},{7934,5,0,-1},{7935,5,1,-1},{7938,5,0,-1},
+{7939,5,1,-1},{7943,5,0,-1},{7944,5,1,-1},{7877,5,1,-1},{7947,5,0,-1},
+{7948,5,1,-1},{7951,5,0,-1},{7952,5,1,-1},{7956,5,0,-1},{7957,5,1,-1},
+{7960,5,0,-1},{7961,5,1,-1},{7964,5,0,-1},{7965,5,1,-1},{7969,5,0,-1},
+{7970,5,1,-1},{7973,5,0,-1},{7974,5,1,-1},{7977,5,0,-1},{7978,5,1,-1},
+{7982,5,0,-1},{7983,5,1,-1},{7986,5,0,-1},{7987,5,1,-1},{7990,5,0,-1},
+{7991,5,1,-1},{7995,5,0,-1},{7996,5,1,-1},{7999,5,0,-1},{8000,5,1,-1},
+{8003,5,0,-1},{8004,5,1,-1},{8008,5,0,-1},{8009,5,1,-1},{8012,5,0,-1},
+{8013,5,1,-1},{8016,5,0,-1},{8017,5,1,-1},{8021,5,0,-1},{8022,5,1,-1},
+{8025,5,0,-1},{8026,5,1,-1},{8029,5,0,-1},{8030,5,1,-1},{8034,5,0,-1},
+{8035,5,1,-1},{7968,5,0,-1},{8038,5,0,-1},{8039,5,1,-1},{8042,5,0,-1},
+{8043,5,1,-1},{8047,5,0,-1},{8048,5,1,-1},{7981,5,1,-1},{8051,5,0,-1},
+{8052,5,1,-1},{8055,5,0,-1},{8056,5,1,-1},{8060,5,0,-1},{8061,5,1,-1},
+{8064,5,0,-1},{8065,5,1,-1},{8068,5,0,-1},{8069,5,1,-1},{8073,5,0,-1},
+{8074,5,1,-1},{8077,5,0,-1},{8078,5,1,-1},{8081,5,0,-1},{8082,5,1,-1},
+{8086,5,0,-1},{8087,5,1,-1},{8090,5,0,-1},{8091,5,1,-1},{8094,5,0,-1},
+{8095,5,1,-1},{8099,5,0,-1},{8100,5,1,-1},{8103,5,0,-1},{8104,5,1,-1},
+{8107,5,0,-1},{8108,5,1,-1},{8112,5,0,-1},{8113,5,1,-1},{8116,5,0,-1},
+{8117,5,1,-1},{8120,5,0,-1},{8121,5,1,-1},{8125,5,0,-1},{8126,5,1,-1},
+{8129,5,0,-1},{8130,5,1,-1},{8133,5,0,-1},{8134,5,1,-1},{8138,5,0,-1},
+{8139,5,1,-1},{8072,5,0,-1},{8142,5,0,-1},{8143,5,1,-1},{8146,5,0,-1},
+{8147,5,1,-1},{8151,5,0,-1},{8152,5,1,-1},{8085,5,1,-1},{8155,5,0,-1},
+{8156,5,1,-1},{8159,5,0,-1},{8160,5,1,-1},{8164,5,0,-1},{8165,5,1,-1},
+{8168,5,0,-1},{8169,5,1,-1},{8172,5,0,-1},{8173,5,1,-1},{8177,5,0,-1},
+{8178,5,1,-1},{8181,5,0,-1},{8182,5,1,-1},{8185,5,0,-1},{8186,5,1,-1},
+{8190,5,0,-1},{8191,5,1,-1},{8194,5,0,-1},{8195,5,1,-1},{8198,5,0,-1},
+{8199,5,1,-1},{8203,5,0,-1},{8204,5,1,-1},{8207,5,0,-1},{8208,5,1,-1},
+{8211,5,0,-1},{8212,5,1,-1},{8216,5,0,-1},{8217,5,1,-1},{8220,5,0,-1},
+{8221,5,1,-1},{8224,5,0,-1},{8225,5,1,-1},{8229,5,0,-1},{8230,5,1,-1},
+{8233,5,0,-1},{8234,5,1,-1},{8237,5,0,-1},{8238,5,1,-1},{8242,5,0,-1},
+{8243,5,1,-1},{8176,5,0,-1},{8246,5,0,-1},{8247,5,1,-1},{8250,5,0,-1},
+{8251,5,1,-1},{8255,5,0,-1},{8256,5,1,-1},{8189,5,1,-1},{8259,5,0,-1},
+{8260,5,1,-1},{8263,5,0,-1},{8264,5,1,-1},{8268,5,0,-1},{8269,5,1,-1},
+{8272,5,0,-1},{8273,5,1,-1},{8276,5,0,-1},{8277,5,1,-1},{8281,5,0,-1},
+{8282,5,1,-1},{8285,5,0,-1},{8286,5,1,-1},{8289,5,0,-1},{8290,5,1,-1},
+{8294,5,0,-1},{8295,5,1,-1},{8298,5,0,-1},{8299,5,1,-1},{8302,5,0,-1},
+{8303,5,1,-1},{8307,5,0,-1},{8308,5,1,-1},{8311,5,0,-1},{8312,5,1,-1},
+{8315,5,0,-1},{8316,5,1,-1},{8320,5,0,-1},{8321,5,1,-1},{8324,5,0,-1},
+{8325,5,1,-1},{8328,5,0,-1},{8329,5,1,-1},{8333,5,0,-1},{8334,5,1,-1},
+{8337,5,0,-1},{8338,5,1,-1},{8341,5,0,-1},{8342,5,1,-1},{8346,5,0,-1},
+{8347,5,1,-1},{8280,5,0,-1},{8350,5,0,-1},{8351,5,1,-1},{8354,5,0,-1},
+{8355,5,1,-1},{8359,5,0,-1},{8360,5,1,-1},{8293,5,1,-1},{8363,5,0,-1},
+{8364,5,1,-1},{8367,5,0,-1},{8368,5,1,-1},{8372,5,0,-1},{8373,5,1,-1},
+{8376,5,0,-1},{8377,5,1,-1},{8380,5,0,-1},{8381,5,1,-1},{8385,5,0,-1},
+{8386,5,1,-1},{8389,5,0,-1},{8390,5,1,-1},{8393,5,0,-1},{8394,5,1,-1},
+{8398,5,0,-1},{8399,5,1,-1},{8402,5,0,-1},{8403,5,1,-1},{8406,5,0,-1},
+{8407,5,1,-1},{8411,5,0,-1},{8412,5,1,-1},{8415,5,0,-1},{8416,5,1,-1},
+{8419,5,0,-1},{8420,5,1,-1},{8424,5,0,-1},{8425,5,1,-1},{8428,5,0,-1},
+{8429,5,1,-1},{8432,5,0,-1},{8433,5,1,-1},{8437,5,0,-1},{8438,5,1,-1},
+{8441,5,0,-1},{8442,5,1,-1},{8445,5,0,-1},{8446,5,1,-1},{8450,5,0,-1},
+{8451,5,1,-1},{8384,5,0,-1},{8454,5,0,-1},{8455,5,1,-1},{8458,5,0,-1},
+{8459,5,1,-1},{8463,5,0,-1},{8464,5,1,-1},{8397,5,1,-1},{8467,5,0,-1},
+{8468,5,1,-1},{8471,5,0,-1},{8472,5,1,-1},{8476,5,0,-1},{8477,5,1,-1},
+{8480,5,0,-1},{8481,5,1,-1},{8484,5,0,-1},{8485,5,1,-1},{8489,5,0,-1},
+{8490,5,1,-1},{8493,5,0,-1},{8494,5,1,-1},{8497,5,0,-1},{8498,5,1,-1},
+{8502,5,0,-1},{8503,5,1,-1},{8506,5,0,-1},{8507,5,1,-1},{8510,5,0,-1},
+{8511,5,1,-1},{8515,5,0,-1},{8516,5,1,-1},{8519,5,0,-1},{8520,5,1,-1},
+{8523,5,0,-1},{8524,5,1,-1},{8528,5,0,-1},{8529,5,1,-1},{8532,5,0,-1},
+{8533,5,1,-1},{8536,5,0,-1},{8537,5,1,-1},{8541,5,0,-1},{8545,5,0,-1},
+{8549,5,0,-1},{8554,5,0,-1},{8488,5,0,-1},{8558,5,0,-1},{8562,5,0,-1},
+{8567,5,0,-1},{8501,5,1,-1},{8571,5,0,-1},{8575,5,0,-1},{8580,5,0,-1},
+{8584,5,0,-1},{8585,5,1,-1},{8588,5,0,-1},{8589,5,1,-1},{8597,5,0,-1},
+{8601,5,0,-1},{8606,5,0,-1},{8610,5,0,-1},{8614,5,0,-1},{8619,5,0,-1},
+{8623,5,0,-1},{8627,5,0,-1},{8632,5,0,-1},{8636,5,0,-1},{8640,5,0,-1},
+{8641,5,1,-1},{8649,5,0,-1},{8653,5,0,-1},{8658,5,0,-1},{8662,5,0,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on half rate channels TS6, SS0 and SS1 */
+struct fn_sample test_fn_tch_h_ts_6_ss0_ss1[] = {
+{8112,6,0,-1},{8120,6,0,-1},{8129,6,0,-1},{8138,6,0,-1},{8146,6,0,-1},
+{8155,6,0,-1},{8164,6,0,-1},{8098,6,0,-1},{8172,6,0,-1},{8181,6,0,-1},
+{8190,6,0,-1},{8198,6,0,-1},{8207,6,0,-1},{8211,6,0,-1},{8216,6,0,-1},
+{8220,6,0,-1},{8224,6,0,-1},{8229,6,0,-1},{8233,6,0,-1},{8237,6,0,-1},
+{8242,6,0,-1},{8246,6,0,-1},{8250,6,0,-1},{8255,6,0,-1},{8259,6,0,-1},
+{8263,6,0,-1},{8268,6,0,-1},{8202,6,0,-1},{8272,6,0,-1},{8276,6,0,-1},
+{8281,6,0,-1},{8285,6,0,-1},{8289,6,0,-1},{8294,6,0,-1},{8298,6,0,-1},
+{8302,6,0,-1},{8307,6,0,-1},{8311,6,0,-1},{8315,6,0,-1},{8320,6,0,-1},
+{8324,6,0,-1},{8328,6,0,-1},{8333,6,0,-1},{8337,6,0,-1},{8341,6,0,-1},
+{8346,6,0,-1},{8350,6,0,-1},{8354,6,0,-1},{8355,6,1,-1},{8359,6,0,-1},
+{8363,6,0,-1},{8364,6,1,-1},{8367,6,0,-1},{8372,6,0,-1},{8373,6,1,-1},
+{8306,6,0,-1},{8376,6,0,-1},{8380,6,0,-1},{8381,6,1,-1},{8385,6,0,-1},
+{8389,6,0,-1},{8390,6,1,-1},{8393,6,0,-1},{8398,6,0,-1},{8399,6,1,-1},
+{8402,6,0,-1},{8406,6,0,-1},{8407,6,1,-1},{8411,6,0,-1},{8412,6,1,-1},
+{8415,6,0,-1},{8416,6,1,-1},{8419,6,0,-1},{8420,6,1,-1},{8424,6,0,-1},
+{8425,6,1,-1},{8428,6,0,-1},{8432,6,0,-1},{8433,6,1,-1},{8437,6,0,-1},
+{8441,6,0,-1},{8442,6,1,-1},{8445,6,0,-1},{8450,6,0,-1},{8451,6,1,-1},
+{8454,6,0,-1},{8458,6,0,-1},{8459,6,1,-1},{8463,6,0,-1},{8467,6,0,-1},
+{8468,6,1,-1},{8471,6,0,-1},{8476,6,0,-1},{8477,6,1,-1},{8410,6,0,-1},
+{8480,6,0,-1},{8484,6,0,-1},{8485,6,1,-1},{8489,6,0,-1},{8490,6,1,-1},
+{8423,6,1,-1},{8493,6,0,-1},{8494,6,1,-1},{8497,6,0,-1},{8498,6,1,-1},
+{8502,6,0,-1},{8503,6,1,-1},{8506,6,0,-1},{8507,6,1,-1},{8510,6,0,-1},
+{8511,6,1,-1},{8515,6,0,-1},{8519,6,0,-1},{8520,6,1,-1},{8523,6,0,-1},
+{8524,6,1,-1},{8528,6,0,-1},{8529,6,1,-1},{8532,6,0,-1},{8533,6,1,-1},
+{8536,6,0,-1},{8537,6,1,-1},{8541,6,0,-1},{8542,6,1,-1},{8545,6,0,-1},
+{8546,6,1,-1},{8549,6,0,-1},{8550,6,1,-1},{8554,6,0,-1},{8555,6,1,-1},
+{8558,6,0,-1},{8559,6,1,-1},{8562,6,0,-1},{8563,6,1,-1},{8567,6,0,-1},
+{8568,6,1,-1},{8571,6,0,-1},{8572,6,1,-1},{8575,6,0,-1},{8576,6,1,-1},
+{8580,6,0,-1},{8581,6,1,-1},{8514,6,0,-1},{8584,6,0,-1},{8585,6,1,-1},
+{8588,6,0,-1},{8589,6,1,-1},{8593,6,0,-1},{8594,6,1,-1},{8527,6,1,-1},
+{8597,6,0,-1},{8598,6,1,-1},{8601,6,0,-1},{8602,6,1,-1},{8606,6,0,-1},
+{8607,6,1,-1},{8610,6,0,-1},{8611,6,1,-1},{8614,6,0,-1},{8615,6,1,-1},
+{8619,6,0,-1},{8620,6,1,-1},{8623,6,0,-1},{8624,6,1,-1},{8627,6,0,-1},
+{8628,6,1,-1},{8632,6,0,-1},{8633,6,1,-1},{8636,6,0,-1},{8637,6,1,-1},
+{8640,6,0,-1},{8641,6,1,-1},{8645,6,0,-1},{8646,6,1,-1},{8649,6,0,-1},
+{8650,6,1,-1},{8653,6,0,-1},{8654,6,1,-1},{8658,6,0,-1},{8659,6,1,-1},
+{8662,6,0,-1},{8663,6,1,-1},{8666,6,0,-1},{8667,6,1,-1},{8671,6,0,-1},
+{8672,6,1,-1},{8675,6,0,-1},{8676,6,1,-1},{8679,6,0,-1},{8680,6,1,-1},
+{8684,6,0,-1},{8685,6,1,-1},{8618,6,0,-1},{8688,6,0,-1},{8689,6,1,-1},
+{8692,6,0,-1},{8693,6,1,-1},{8697,6,0,-1},{8698,6,1,-1},{8631,6,1,-1},
+{8701,6,0,-1},{8702,6,1,-1},{8705,6,0,-1},{8706,6,1,-1},{8710,6,0,-1},
+{8711,6,1,-1},{8714,6,0,-1},{8715,6,1,-1},{8718,6,0,-1},{8719,6,1,-1},
+{8723,6,0,-1},{8724,6,1,-1},{8727,6,0,-1},{8728,6,1,-1},{8731,6,0,-1},
+{8732,6,1,-1},{8736,6,0,-1},{8737,6,1,-1},{8740,6,0,-1},{8741,6,1,-1},
+{8744,6,0,-1},{8745,6,1,-1},{8749,6,0,-1},{8750,6,1,-1},{8753,6,0,-1},
+{8754,6,1,-1},{8757,6,0,-1},{8758,6,1,-1},{8762,6,0,-1},{8763,6,1,-1},
+{8766,6,0,-1},{8770,6,0,-1},{8771,6,1,-1},{8775,6,0,-1},{8776,6,1,-1},
+{8779,6,0,-1},{8780,6,1,-1},{8783,6,0,-1},{8784,6,1,-1},{8788,6,0,-1},
+{8789,6,1,-1},{8722,6,0,-1},{8792,6,0,-1},{8793,6,1,-1},{8796,6,0,-1},
+{8797,6,1,-1},{8801,6,0,-1},{8802,6,1,-1},{8735,6,1,-1},{8805,6,0,-1},
+{8806,6,1,-1},{8810,6,1,-1},{8814,6,0,-1},{8815,6,1,-1},{8818,6,0,-1},
+{8822,6,0,-1},{8823,6,1,-1},{8827,6,0,-1},{8828,6,1,-1},{8831,6,0,-1},
+{8832,6,1,-1},{8835,6,0,-1},{8836,6,1,-1},{8840,6,0,-1},{8841,6,1,-1},
+{8844,6,0,-1},{8845,6,1,-1},{8848,6,0,-1},{8849,6,1,-1},{8853,6,0,-1},
+{8854,6,1,-1},{8857,6,0,-1},{8858,6,1,-1},{8861,6,0,-1},{8862,6,1,-1},
+{8866,6,0,-1},{8867,6,1,-1},{8870,6,0,-1},{8871,6,1,-1},{8874,6,0,-1},
+{8875,6,1,-1},{8879,6,0,-1},{8880,6,1,-1},{8883,6,0,-1},{8884,6,1,-1},
+{8887,6,0,-1},{8888,6,1,-1},{8892,6,0,-1},{8893,6,1,-1},{8826,6,0,-1},
+{8896,6,0,-1},{8897,6,1,-1},{8900,6,0,-1},{8901,6,1,-1},{8905,6,0,-1},
+{8906,6,1,-1},{8839,6,1,-1},{8909,6,0,-1},{8910,6,1,-1},{8913,6,0,-1},
+{8914,6,1,-1},{8918,6,0,-1},{8919,6,1,-1},{8922,6,0,-1},{8923,6,1,-1},
+{8926,6,0,-1},{8927,6,1,-1},{8931,6,0,-1},{8932,6,1,-1},{8935,6,0,-1},
+{8936,6,1,-1},{8939,6,0,-1},{8940,6,1,-1},{8944,6,0,-1},{8945,6,1,-1},
+{8948,6,0,-1},{8949,6,1,-1},{8952,6,0,-1},{8953,6,1,-1},{8957,6,0,-1},
+{8958,6,1,-1},{8961,6,0,-1},{8962,6,1,-1},{8965,6,0,-1},{8966,6,1,-1},
+{8970,6,0,-1},{8971,6,1,-1},{8974,6,0,-1},{8975,6,1,-1},{8978,6,0,-1},
+{8979,6,1,-1},{8983,6,0,-1},{8984,6,1,-1},{8987,6,0,-1},{8988,6,1,-1},
+{8991,6,0,-1},{8992,6,1,-1},{8996,6,0,-1},{8997,6,1,-1},{8930,6,0,-1},
+{9000,6,0,-1},{9001,6,1,-1},{9004,6,0,-1},{9005,6,1,-1},{9009,6,0,-1},
+{9010,6,1,-1},{8943,6,1,-1},{9013,6,0,-1},{9014,6,1,-1},{9017,6,0,-1},
+{9018,6,1,-1},{9022,6,0,-1},{9023,6,1,-1},{9026,6,0,-1},{9027,6,1,-1},
+{9030,6,0,-1},{9031,6,1,-1},{9035,6,0,-1},{9036,6,1,-1},{9039,6,0,-1},
+{9040,6,1,-1},{9043,6,0,-1},{9044,6,1,-1},{9048,6,0,-1},{9049,6,1,-1},
+{9052,6,0,-1},{9053,6,1,-1},{9056,6,0,-1},{9057,6,1,-1},{9061,6,0,-1},
+{9062,6,1,-1},{9065,6,0,-1},{9066,6,1,-1},{9069,6,0,-1},{9070,6,1,-1},
+{9074,6,0,-1},{9075,6,1,-1},{9078,6,0,-1},{9079,6,1,-1},{9082,6,0,-1},
+{9083,6,1,-1},{9087,6,0,-1},{9088,6,1,-1},{9091,6,0,-1},{9092,6,1,-1},
+{9095,6,0,-1},{9096,6,1,-1},{9100,6,0,-1},{9101,6,1,-1},{9034,6,0,-1},
+{9104,6,0,-1},{9105,6,1,-1},{9108,6,0,-1},{9109,6,1,-1},{9113,6,0,-1},
+{9114,6,1,-1},{9047,6,1,-1},{9117,6,0,-1},{9118,6,1,-1},{9121,6,0,-1},
+{9122,6,1,-1},{9126,6,0,-1},{9127,6,1,-1},{9130,6,0,-1},{9131,6,1,-1},
+{9134,6,0,-1},{9135,6,1,-1},{9139,6,0,-1},{9140,6,1,-1},{9143,6,0,-1},
+{9144,6,1,-1},{9147,6,0,-1},{9148,6,1,-1},{9152,6,0,-1},{9153,6,1,-1},
+{9156,6,0,-1},{9157,6,1,-1},{9160,6,0,-1},{9161,6,1,-1},{9165,6,0,-1},
+{9166,6,1,-1},{9169,6,0,-1},{9170,6,1,-1},{9173,6,0,-1},{9174,6,1,-1},
+{9178,6,0,-1},{9179,6,1,-1},{9182,6,0,-1},{9183,6,1,-1},{9186,6,0,-1},
+{9187,6,1,-1},{9191,6,0,-1},{9192,6,1,-1},{9195,6,0,-1},{9196,6,1,-1},
+{9199,6,0,-1},{9200,6,1,-1},{9204,6,0,-1},{9205,6,1,-1},{9138,6,0,-1},
+{9208,6,0,-1},{9209,6,1,-1},{9212,6,0,-1},{9213,6,1,-1},{9217,6,0,-1},
+{9218,6,1,-1},{9151,6,1,-1},{9221,6,0,-1},{9222,6,1,-1},{9225,6,0,-1},
+{9226,6,1,-1},{9230,6,0,-1},{9231,6,1,-1},{9234,6,0,-1},{9235,6,1,-1},
+{9238,6,0,-1},{9239,6,1,-1},{9243,6,0,-1},{9244,6,1,-1},{9247,6,0,-1},
+{9248,6,1,-1},{9251,6,0,-1},{9252,6,1,-1},{9256,6,0,-1},{9257,6,1,-1},
+{9260,6,0,-1},{9261,6,1,-1},{9264,6,0,-1},{9265,6,1,-1},{9269,6,0,-1},
+{9270,6,1,-1},{9273,6,0,-1},{9274,6,1,-1},{9277,6,0,-1},{9278,6,1,-1},
+{9282,6,0,-1},{9283,6,1,-1},{9286,6,0,-1},{9287,6,1,-1},{9290,6,0,-1},
+{9291,6,1,-1},{9295,6,0,-1},{9296,6,1,-1},{9299,6,0,-1},{9300,6,1,-1},
+{9303,6,0,-1},{9304,6,1,-1},{9308,6,0,-1},{9309,6,1,-1},{9242,6,0,-1},
+{9312,6,0,-1},{9313,6,1,-1},{9316,6,0,-1},{9317,6,1,-1},{9321,6,0,-1},
+{9322,6,1,-1},{9255,6,1,-1},{9325,6,0,-1},{9326,6,1,-1},{9329,6,0,-1},
+{9330,6,1,-1},{9334,6,0,-1},{9335,6,1,-1},{9338,6,0,-1},{9339,6,1,-1},
+{9342,6,0,-1},{9343,6,1,-1},{9347,6,0,-1},{9348,6,1,-1},{9351,6,0,-1},
+{9352,6,1,-1},{9355,6,0,-1},{9356,6,1,-1},{9360,6,0,-1},{9361,6,1,-1},
+{9364,6,0,-1},{9365,6,1,-1},{9368,6,0,-1},{9369,6,1,-1},{9373,6,0,-1},
+{9374,6,1,-1},{9377,6,0,-1},{9378,6,1,-1},{9381,6,0,-1},{9382,6,1,-1},
+{9386,6,0,-1},{9387,6,1,-1},{9390,6,0,-1},{9391,6,1,-1},{9394,6,0,-1},
+{9395,6,1,-1},{9399,6,0,-1},{9400,6,1,-1},{9403,6,0,-1},{9404,6,1,-1},
+{9407,6,0,-1},{9408,6,1,-1},{9412,6,0,-1},{9413,6,1,-1},{9346,6,0,-1},
+{9416,6,0,-1},{9417,6,1,-1},{9420,6,0,-1},{9421,6,1,-1},{9425,6,0,-1},
+{9426,6,1,-1},{9359,6,1,-1},{9429,6,0,-1},{9430,6,1,-1},{9433,6,0,-1},
+{9434,6,1,-1},{9438,6,0,-1},{9439,6,1,-1},{9442,6,0,-1},{9443,6,1,-1},
+{9446,6,0,-1},{9447,6,1,-1},{9451,6,0,-1},{9452,6,1,-1},{9455,6,0,-1},
+{9456,6,1,-1},{9459,6,0,-1},{9460,6,1,-1},{9464,6,0,-1},{9465,6,1,-1},
+{9468,6,0,-1},{9469,6,1,-1},{9472,6,0,-1},{9473,6,1,-1},{9477,6,0,-1},
+{9478,6,1,-1},{9481,6,0,-1},{9482,6,1,-1},{9485,6,0,-1},{9486,6,1,-1},
+{9490,6,0,-1},{9491,6,1,-1},{9494,6,0,-1},{9495,6,1,-1},{9498,6,0,-1},
+{9499,6,1,-1},{9503,6,0,-1},{9504,6,1,-1},{9507,6,0,-1},{9508,6,1,-1},
+{9511,6,0,-1},{9512,6,1,-1},{9516,6,0,-1},{9517,6,1,-1},{9450,6,0,-1},
+{9520,6,0,-1},{9521,6,1,-1},{9524,6,0,-1},{9525,6,1,-1},{9529,6,0,-1},
+{9530,6,1,-1},{9463,6,1,-1},{9533,6,0,-1},{9534,6,1,-1},{9537,6,0,-1},
+{9538,6,1,-1},{9542,6,0,-1},{9543,6,1,-1},{9546,6,0,-1},{9547,6,1,-1},
+{9550,6,0,-1},{9551,6,1,-1},{9555,6,0,-1},{9556,6,1,-1},{9559,6,0,-1},
+{9560,6,1,-1},{9563,6,0,-1},{9564,6,1,-1},{9568,6,0,-1},{9569,6,1,-1},
+{9572,6,0,-1},{9573,6,1,-1},{9576,6,0,-1},{9577,6,1,-1},{9581,6,0,-1},
+{9582,6,1,-1},{9585,6,0,-1},{9586,6,1,-1},{9589,6,0,-1},{9590,6,1,-1},
+{9594,6,0,-1},{9595,6,1,-1},{9598,6,0,-1},{9599,6,1,-1},{9602,6,0,-1},
+{9603,6,1,-1},{9607,6,0,-1},{9608,6,1,-1},{9611,6,0,-1},{9612,6,1,-1},
+{9615,6,0,-1},{9616,6,1,-1},{9620,6,0,-1},{9621,6,1,-1},{9554,6,0,-1},
+{9624,6,0,-1},{9625,6,1,-1},{9628,6,0,-1},{9629,6,1,-1},{9633,6,0,-1},
+{9634,6,1,-1},{9567,6,1,-1},{9637,6,0,-1},{9638,6,1,-1},{9641,6,0,-1},
+{9642,6,1,-1},{9646,6,0,-1},{9647,6,1,-1},{9650,6,0,-1},{9651,6,1,-1},
+{9654,6,0,-1},{9655,6,1,-1},{9659,6,0,-1},{9660,6,1,-1},{9663,6,0,-1},
+{9664,6,1,-1},{9667,6,0,-1},{9668,6,1,-1},{9672,6,0,-1},{9673,6,1,-1},
+{9676,6,0,-1},{9677,6,1,-1},{9680,6,0,-1},{9681,6,1,-1},{9685,6,0,-1},
+{9686,6,1,-1},{9689,6,0,-1},{9690,6,1,-1},{9693,6,0,-1},{9694,6,1,-1},
+{9698,6,0,-1},{9699,6,1,-1},{9702,6,0,-1},{9703,6,1,-1},{9706,6,0,-1},
+{9707,6,1,-1},{9711,6,0,-1},{9712,6,1,-1},{9715,6,0,-1},{9716,6,1,-1},
+{9719,6,0,-1},{9720,6,1,-1},{9724,6,0,-1},{9725,6,1,-1},{9658,6,0,-1},
+{9728,6,0,-1},{9729,6,1,-1},{9732,6,0,-1},{9733,6,1,-1},{9737,6,0,-1},
+{9738,6,1,-1},{9671,6,1,-1},{9741,6,0,-1},{9742,6,1,-1},{9745,6,0,-1},
+{9746,6,1,-1},{9750,6,0,-1},{9751,6,1,-1},{9754,6,0,-1},{9755,6,1,-1},
+{9758,6,0,-1},{9759,6,1,-1},{9763,6,0,-1},{9764,6,1,-1},{9767,6,0,-1},
+{9768,6,1,-1},{9771,6,0,-1},{9772,6,1,-1},{9776,6,0,-1},{9777,6,1,-1},
+{9780,6,0,-1},{9781,6,1,-1},{9784,6,0,-1},{9785,6,1,-1},{9789,6,0,-1},
+{9790,6,1,-1},{9793,6,0,-1},{9794,6,1,-1},{9797,6,0,-1},{9798,6,1,-1},
+{9802,6,0,-1},{9803,6,1,-1},{9806,6,0,-1},{9807,6,1,-1},{9810,6,0,-1},
+{9811,6,1,-1},{9815,6,0,-1},{9816,6,1,-1},{9819,6,0,-1},{9820,6,1,-1},
+{9823,6,0,-1},{9824,6,1,-1},{9828,6,0,-1},{9829,6,1,-1},{9762,6,0,-1},
+{9832,6,0,-1},{9833,6,1,-1},{9836,6,0,-1},{9837,6,1,-1},{9841,6,0,-1},
+{9842,6,1,-1},{9775,6,1,-1},{9845,6,0,-1},{9846,6,1,-1},{9849,6,0,-1},
+{9850,6,1,-1},{9854,6,0,-1},{9855,6,1,-1},{9858,6,0,-1},{9859,6,1,-1},
+{9862,6,0,-1},{9863,6,1,-1},{9867,6,0,-1},{9868,6,1,-1},{9871,6,0,-1},
+{9872,6,1,-1},{9875,6,0,-1},{9876,6,1,-1},{9880,6,0,-1},{9881,6,1,-1},
+{9884,6,0,-1},{9885,6,1,-1},{9888,6,0,-1},{9889,6,1,-1},{9893,6,0,-1},
+{9894,6,1,-1},{9897,6,0,-1},{9898,6,1,-1},{9901,6,0,-1},{9902,6,1,-1},
+{9906,6,0,-1},{9907,6,1,-1},{9910,6,0,-1},{9911,6,1,-1},{9914,6,0,-1},
+{9915,6,1,-1},{9919,6,0,-1},{9920,6,1,-1},{9923,6,0,-1},{9924,6,1,-1},
+{9927,6,0,-1},{9928,6,1,-1},{9932,6,0,-1},{9933,6,1,-1},{9866,6,0,-1},
+{9936,6,0,-1},{9937,6,1,-1},{9940,6,0,-1},{9941,6,1,-1},{9945,6,0,-1},
+{9946,6,1,-1},{9879,6,1,-1},{9949,6,0,-1},{9950,6,1,-1},{9953,6,0,-1},
+{9954,6,1,-1},{9958,6,0,-1},{9959,6,1,-1},{9962,6,0,-1},{9963,6,1,-1},
+{9966,6,0,-1},{9967,6,1,-1},{9971,6,0,-1},{9972,6,1,-1},{9975,6,0,-1},
+{9976,6,1,-1},{9979,6,0,-1},{9980,6,1,-1},{9984,6,0,-1},{9985,6,1,-1},
+{9988,6,0,-1},{9989,6,1,-1},{9992,6,0,-1},{9993,6,1,-1},{9997,6,0,-1},
+{9998,6,1,-1},{10001,6,0,-1},{10002,6,1,-1},{10005,6,0,-1},{10006,6,1,-1},
+{10010,6,0,-1},{10011,6,1,-1},{10014,6,0,-1},{10015,6,1,-1},{10018,6,0,-1},
+{10019,6,1,-1},{10023,6,0,-1},{10024,6,1,-1},{10027,6,0,-1},{10028,6,1,-1},
+{10031,6,0,-1},{10032,6,1,-1},{10036,6,0,-1},{10037,6,1,-1},{9970,6,0,-1},
+{10040,6,0,-1},{10041,6,1,-1},{10044,6,0,-1},{10045,6,1,-1},{10049,6,0,-1},
+{10050,6,1,-1},{9983,6,1,-1},{10053,6,0,-1},{10054,6,1,-1},{10057,6,0,-1},
+{10058,6,1,-1},{10062,6,0,-1},{10063,6,1,-1},{10066,6,0,-1},{10067,6,1,-1},
+{10070,6,0,-1},{10071,6,1,-1},{10075,6,0,-1},{10076,6,1,-1},{10079,6,0,-1},
+{10080,6,1,-1},{10083,6,0,-1},{10084,6,1,-1},{10088,6,0,-1},{10089,6,1,-1},
+{10092,6,0,-1},{10093,6,1,-1},{10096,6,0,-1},{10097,6,1,-1},{10101,6,0,-1},
+{10102,6,1,-1},{10105,6,0,-1},{10106,6,1,-1},{10109,6,0,-1},{10110,6,1,-1},
+{10114,6,0,-1},{10115,6,1,-1},{10118,6,0,-1},{10119,6,1,-1},{10122,6,0,-1},
+{10123,6,1,-1},{10127,6,0,-1},{10128,6,1,-1},{10131,6,0,-1},{10132,6,1,-1},
+{10135,6,0,-1},{10136,6,1,-1},{10140,6,0,-1},{10141,6,1,-1},{10074,6,0,-1},
+{10144,6,0,-1},{10145,6,1,-1},{10148,6,0,-1},{10149,6,1,-1},{10153,6,0,-1},
+{10154,6,1,-1},{10087,6,1,-1},{10157,6,0,-1},{10158,6,1,-1},{10161,6,0,-1},
+{10162,6,1,-1},{10166,6,0,-1},{10167,6,1,-1},{10170,6,0,-1},{10171,6,1,-1},
+{10174,6,0,-1},{10175,6,1,-1},{10179,6,0,-1},{10180,6,1,-1},{10183,6,0,-1},
+{10184,6,1,-1},{10187,6,0,-1},{10188,6,1,-1},{10192,6,0,-1},{10193,6,1,-1},
+{10196,6,0,-1},{10197,6,1,-1},{10200,6,0,-1},{10201,6,1,-1},{10205,6,0,-1},
+{10206,6,1,-1},{10209,6,0,-1},{10210,6,1,-1},{10213,6,0,-1},{10214,6,1,-1},
+{10218,6,0,-1},{10219,6,1,-1},{10222,6,0,-1},{10223,6,1,-1},{10226,6,0,-1},
+{10227,6,1,-1},{10231,6,0,-1},{10232,6,1,-1},{10235,6,0,-1},{10236,6,1,-1},
+{10239,6,0,-1},{10240,6,1,-1},{10244,6,0,-1},{10245,6,1,-1},{10178,6,0,-1},
+{10248,6,0,-1},{10249,6,1,-1},{10252,6,0,-1},{10253,6,1,-1},{10257,6,0,-1},
+{10258,6,1,-1},{10191,6,1,-1},{10261,6,0,-1},{10262,6,1,-1},{10265,6,0,-1},
+{10266,6,1,-1},{10270,6,0,-1},{10271,6,1,-1},{10274,6,0,-1},{10275,6,1,-1},
+{10278,6,0,-1},{10279,6,1,-1},{10283,6,0,-1},{10284,6,1,-1},{10287,6,0,-1},
+{10288,6,1,-1},{10291,6,0,-1},{10292,6,1,-1},{10296,6,0,-1},{10297,6,1,-1},
+{10300,6,0,-1},{10301,6,1,-1},{10304,6,0,-1},{10305,6,1,-1},{10309,6,0,-1},
+{10310,6,1,-1},{10313,6,0,-1},{10314,6,1,-1},{10317,6,0,-1},{10318,6,1,-1},
+{10322,6,0,-1},{10323,6,1,-1},{10326,6,0,-1},{10327,6,1,-1},{10330,6,0,-1},
+{10331,6,1,-1},{10335,6,0,-1},{10336,6,1,-1},{10339,6,0,-1},{10340,6,1,-1},
+{10343,6,0,-1},{10344,6,1,-1},{10348,6,0,-1},{10349,6,1,-1},{10282,6,0,-1},
+{10352,6,0,-1},{10353,6,1,-1},{10356,6,0,-1},{10357,6,1,-1},{10361,6,0,-1},
+{10362,6,1,-1},{10295,6,1,-1},{10365,6,0,-1},{10366,6,1,-1},{10369,6,0,-1},
+{10370,6,1,-1},{10374,6,0,-1},{10375,6,1,-1},{10378,6,0,-1},{10379,6,1,-1},
+{10382,6,0,-1},{10383,6,1,-1},{10387,6,0,-1},{10388,6,1,-1},{10391,6,0,-1},
+{10392,6,1,-1},{10395,6,0,-1},{10396,6,1,-1},{10400,6,0,-1},{10401,6,1,-1},
+{10404,6,0,-1},{10405,6,1,-1},{10408,6,0,-1},{10409,6,1,-1},{10413,6,0,-1},
+{10414,6,1,-1},{10417,6,0,-1},{10418,6,1,-1},{10421,6,0,-1},{10422,6,1,-1},
+{10426,6,0,-1},{10427,6,1,-1},{10430,6,0,-1},{10431,6,1,-1},{10434,6,0,-1},
+{10435,6,1,-1},{10439,6,0,-1},{10440,6,1,-1},{10443,6,0,-1},{10444,6,1,-1},
+{10447,6,0,-1},{10448,6,1,-1},{10452,6,0,-1},{10453,6,1,-1},{10386,6,0,-1},
+{10456,6,0,-1},{10457,6,1,-1},{10460,6,0,-1},{10461,6,1,-1},{10465,6,0,-1},
+{10466,6,1,-1},{10399,6,1,-1},{10469,6,0,-1},{10470,6,1,-1},{10473,6,0,-1},
+{10474,6,1,-1},{10478,6,0,-1},{10479,6,1,-1},{10482,6,0,-1},{10483,6,1,-1},
+{10486,6,0,-1},{10487,6,1,-1},{10491,6,0,-1},{10492,6,1,-1},{10495,6,0,-1},
+{10496,6,1,-1},{10499,6,0,-1},{10500,6,1,-1},{10504,6,0,-1},{10505,6,1,-1},
+{10508,6,0,-1},{10509,6,1,-1},{10512,6,0,-1},{10513,6,1,-1},{10517,6,0,-1},
+{10518,6,1,-1},{10521,6,0,-1},{10522,6,1,-1},{10525,6,0,-1},{10526,6,1,-1},
+{10530,6,0,-1},{10531,6,1,-1},{10534,6,0,-1},{10535,6,1,-1},{10538,6,0,-1},
+{10539,6,1,-1},{10543,6,0,-1},{10544,6,1,-1},{10547,6,0,-1},{10548,6,1,-1},
+{10551,6,0,-1},{10552,6,1,-1},{10556,6,0,-1},{10557,6,1,-1},{10490,6,0,-1},
+{10560,6,0,-1},{10561,6,1,-1},{10564,6,0,-1},{10565,6,1,-1},{10569,6,0,-1},
+{10570,6,1,-1},{10503,6,1,-1},{10573,6,0,-1},{10574,6,1,-1},{10577,6,0,-1},
+{10578,6,1,-1},{10582,6,0,-1},{10583,6,1,-1},{10586,6,0,-1},{10587,6,1,-1},
+{10590,6,0,-1},{10591,6,1,-1},{10595,6,0,-1},{10596,6,1,-1},{10599,6,0,-1},
+{10600,6,1,-1},{10603,6,0,-1},{10604,6,1,-1},{10608,6,0,-1},{10609,6,1,-1},
+{10612,6,0,-1},{10613,6,1,-1},{10616,6,0,-1},{10617,6,1,-1},{10621,6,0,-1},
+{10622,6,1,-1},{10625,6,0,-1},{10626,6,1,-1},{10629,6,0,-1},{10630,6,1,-1},
+{10634,6,0,-1},{10635,6,1,-1},{10638,6,0,-1},{10639,6,1,-1},{10642,6,0,-1},
+{10643,6,1,-1},{10647,6,0,-1},{10648,6,1,-1},{10651,6,0,-1},{10652,6,1,-1},
+{10655,6,0,-1},{10656,6,1,-1},{10660,6,0,-1},{10661,6,1,-1},{10594,6,0,-1},
+{10664,6,0,-1},{10665,6,1,-1},{10668,6,0,-1},{10669,6,1,-1},{10673,6,0,-1},
+{10674,6,1,-1},{10607,6,1,-1},{10677,6,0,-1},{10678,6,1,-1},{10681,6,0,-1},
+{10682,6,1,-1},{10686,6,0,-1},{10687,6,1,-1},{10690,6,0,-1},{10691,6,1,-1},
+{10694,6,0,-1},{10695,6,1,-1},{10699,6,0,-1},{10700,6,1,-1},{10703,6,0,-1},
+{10704,6,1,-1},{10707,6,0,-1},{10708,6,1,-1},{10712,6,0,-1},{10713,6,1,-1},
+{10716,6,0,-1},{10717,6,1,-1},{10720,6,0,-1},{10721,6,1,-1},{10725,6,0,-1},
+{10726,6,1,-1},{10729,6,0,-1},{10730,6,1,-1},{10733,6,0,-1},{10734,6,1,-1},
+{10738,6,0,-1},{10739,6,1,-1},{10742,6,0,-1},{10743,6,1,-1},{10746,6,0,-1},
+{10747,6,1,-1},{10751,6,0,-1},{10752,6,1,-1},{10755,6,0,-1},{10756,6,1,-1},
+{10759,6,0,-1},{10760,6,1,-1},{10764,6,0,-1},{10765,6,1,-1},{10698,6,0,-1},
+{10768,6,0,-1},{10769,6,1,-1},{10772,6,0,-1},{10773,6,1,-1},{10777,6,0,-1},
+{10778,6,1,-1},{10711,6,1,-1},{10781,6,0,-1},{10782,6,1,-1},{10785,6,0,-1},
+{10786,6,1,-1},{10790,6,0,-1},{10791,6,1,-1},{10794,6,0,-1},{10795,6,1,-1},
+{10798,6,0,-1},{10799,6,1,-1},{10803,6,0,-1},{10804,6,1,-1},{10807,6,0,-1},
+{10808,6,1,-1},{10811,6,0,-1},{10812,6,1,-1},{10816,6,0,-1},{10817,6,1,-1},
+{10820,6,0,-1},{10821,6,1,-1},{10824,6,0,-1},{10825,6,1,-1},{10829,6,0,-1},
+{10830,6,1,-1},{10833,6,0,-1},{10834,6,1,-1},{10837,6,0,-1},{10838,6,1,-1},
+{10842,6,0,-1},{10843,6,1,-1},{10846,6,0,-1},{10847,6,1,-1},{10850,6,0,-1},
+{10851,6,1,-1},{10855,6,0,-1},{10856,6,1,-1},{10859,6,0,-1},{10860,6,1,-1},
+{10863,6,0,-1},{10864,6,1,-1},{10868,6,0,-1},{10869,6,1,-1},{10802,6,0,-1},
+{10872,6,0,-1},{10873,6,1,-1},{10876,6,0,-1},{10877,6,1,-1},{10881,6,0,-1},
+{10882,6,1,-1},{10815,6,1,-1},{10885,6,0,-1},{10886,6,1,-1},{10889,6,0,-1},
+{10890,6,1,-1},{10894,6,0,-1},{10895,6,1,-1},{10898,6,0,-1},{10899,6,1,-1},
+{10902,6,0,-1},{10903,6,1,-1},{10907,6,0,-1},{10908,6,1,-1},{10911,6,0,-1},
+{10912,6,1,-1},{10915,6,0,-1},{10916,6,1,-1},{10920,6,0,-1},{10921,6,1,-1},
+{10924,6,0,-1},{10925,6,1,-1},{10928,6,0,-1},{10929,6,1,-1},{10933,6,0,-1},
+{10934,6,1,-1},{10937,6,0,-1},{10938,6,1,-1},{10941,6,0,-1},{10942,6,1,-1},
+{10946,6,0,-1},{10947,6,1,-1},{10950,6,0,-1},{10951,6,1,-1},{10954,6,0,-1},
+{10955,6,1,-1},{10959,6,0,-1},{10960,6,1,-1},{10963,6,0,-1},{10964,6,1,-1},
+{10967,6,0,-1},{10968,6,1,-1},{10972,6,0,-1},{10973,6,1,-1},{10906,6,0,-1},
+{10976,6,0,-1},{10977,6,1,-1},{10980,6,0,-1},{10981,6,1,-1},{10985,6,0,-1},
+{10986,6,1,-1},{10919,6,1,-1},{10989,6,0,-1},{10990,6,1,-1},{10993,6,0,-1},
+{10994,6,1,-1},{10998,6,0,-1},{10999,6,1,-1},{11002,6,0,-1},{11003,6,1,-1},
+{11006,6,0,-1},{11007,6,1,-1},{11011,6,0,-1},{11012,6,1,-1},{11015,6,0,-1},
+{11016,6,1,-1},{11019,6,0,-1},{11020,6,1,-1},{11024,6,0,-1},{11025,6,1,-1},
+{11028,6,0,-1},{11029,6,1,-1},{11032,6,0,-1},{11033,6,1,-1},{11037,6,0,-1},
+{11038,6,1,-1},{11041,6,0,-1},{11042,6,1,-1},{11045,6,0,-1},{11046,6,1,-1},
+{11050,6,0,-1},{11051,6,1,-1},{11054,6,0,-1},{11055,6,1,-1},{11058,6,0,-1},
+{11059,6,1,-1},{11063,6,0,-1},{11064,6,1,-1},{11067,6,0,-1},{11068,6,1,-1},
+{11071,6,0,-1},{11072,6,1,-1},{11076,6,0,-1},{11077,6,1,-1},{11010,6,0,-1},
+{11080,6,0,-1},{11081,6,1,-1},{11084,6,0,-1},{11085,6,1,-1},{11089,6,0,-1},
+{11090,6,1,-1},{11023,6,1,-1},{11093,6,0,-1},{11094,6,1,-1},{11097,6,0,-1},
+{11098,6,1,-1},{11102,6,0,-1},{11103,6,1,-1},{11106,6,0,-1},{11107,6,1,-1},
+{11110,6,0,-1},{11111,6,1,-1},{11115,6,0,-1},{11116,6,1,-1},{11119,6,0,-1},
+{11120,6,1,-1},{11123,6,0,-1},{11124,6,1,-1},{11128,6,0,-1},{11129,6,1,-1},
+{11132,6,0,-1},{11133,6,1,-1},{11136,6,0,-1},{11137,6,1,-1},{11141,6,0,-1},
+{11142,6,1,-1},{11145,6,0,-1},{11146,6,1,-1},{11149,6,0,-1},{11150,6,1,-1},
+{11154,6,0,-1},{11155,6,1,-1},{11158,6,0,-1},{11159,6,1,-1},{11162,6,0,-1},
+{11163,6,1,-1},{11167,6,0,-1},{11168,6,1,-1},{11171,6,0,-1},{11172,6,1,-1},
+{11175,6,0,-1},{11176,6,1,-1},{11180,6,0,-1},{11181,6,1,-1},{11114,6,0,-1},
+{11184,6,0,-1},{11185,6,1,-1},{11188,6,0,-1},{11189,6,1,-1},{11193,6,0,-1},
+{11194,6,1,-1},{11127,6,1,-1},{11197,6,0,-1},{11198,6,1,-1},{11201,6,0,-1},
+{11202,6,1,-1},{11206,6,0,-1},{11207,6,1,-1},{11210,6,0,-1},{11211,6,1,-1},
+{11214,6,0,-1},{11215,6,1,-1},{11219,6,0,-1},{11220,6,1,-1},{11223,6,0,-1},
+{11224,6,1,-1},{11227,6,0,-1},{11228,6,1,-1},{11232,6,0,-1},{11233,6,1,-1},
+{11236,6,0,-1},{11237,6,1,-1},{11240,6,0,-1},{11241,6,1,-1},{11245,6,0,-1},
+{11246,6,1,-1},{11249,6,0,-1},{11250,6,1,-1},{11253,6,0,-1},{11254,6,1,-1},
+{11258,6,0,-1},{11259,6,1,-1},{11262,6,0,-1},{11263,6,1,-1},{11266,6,0,-1},
+{11267,6,1,-1},{11271,6,0,-1},{11272,6,1,-1},{11275,6,0,-1},{11276,6,1,-1},
+{11279,6,0,-1},{11280,6,1,-1},{11284,6,0,-1},{11285,6,1,-1},{11218,6,0,-1},
+{11288,6,0,-1},{11289,6,1,-1},{11292,6,0,-1},{11293,6,1,-1},{11297,6,0,-1},
+{11298,6,1,-1},{11231,6,1,-1},{11301,6,0,-1},{11302,6,1,-1},{11305,6,0,-1},
+{11306,6,1,-1},{11310,6,0,-1},{11311,6,1,-1},{11314,6,0,-1},{11315,6,1,-1},
+{11318,6,0,-1},{11319,6,1,-1},{11323,6,0,-1},{11324,6,1,-1},{11327,6,0,-1},
+{11328,6,1,-1},{11331,6,0,-1},{11332,6,1,-1},{11336,6,0,-1},{11337,6,1,-1},
+{11340,6,0,-1},{11341,6,1,-1},{11344,6,0,-1},{11345,6,1,-1},{11349,6,0,-1},
+{11350,6,1,-1},{11353,6,0,-1},{11354,6,1,-1},{11357,6,0,-1},{11358,6,1,-1},
+{11362,6,0,-1},{11363,6,1,-1},{11366,6,0,-1},{11367,6,1,-1},{11370,6,0,-1},
+{11371,6,1,-1},{11375,6,0,-1},{11376,6,1,-1},{11379,6,0,-1},{11380,6,1,-1},
+{11383,6,0,-1},{11384,6,1,-1},{11388,6,0,-1},{11389,6,1,-1},{11322,6,0,-1},
+{11392,6,0,-1},{11393,6,1,-1},{11396,6,0,-1},{11397,6,1,-1},{11401,6,0,-1},
+{11402,6,1,-1},{11335,6,1,-1},{11405,6,0,-1},{11406,6,1,-1},{11409,6,0,-1},
+{11410,6,1,-1},{11414,6,0,-1},{11415,6,1,-1},{11418,6,0,-1},{11419,6,1,-1},
+{11422,6,0,-1},{11423,6,1,-1},{11427,6,0,-1},{11428,6,1,-1},{11431,6,0,-1},
+{11432,6,1,-1},{11435,6,0,-1},{11436,6,1,-1},{11440,6,0,-1},{11441,6,1,-1},
+{11444,6,0,-1},{11445,6,1,-1},{11448,6,0,-1},{11449,6,1,-1},{11453,6,0,-1},
+{11454,6,1,-1},{11457,6,0,-1},{11458,6,1,-1},{11461,6,0,-1},{11462,6,1,-1},
+{11466,6,0,-1},{11467,6,1,-1},{11470,6,0,-1},{11471,6,1,-1},{11474,6,0,-1},
+{11475,6,1,-1},{11479,6,0,-1},{11480,6,1,-1},{11483,6,0,-1},{11484,6,1,-1},
+{11487,6,0,-1},{11488,6,1,-1},{11492,6,0,-1},{11493,6,1,-1},{11426,6,0,-1},
+{11496,6,0,-1},{11497,6,1,-1},{11500,6,0,-1},{11501,6,1,-1},{11505,6,0,-1},
+{11506,6,1,-1},{11439,6,1,-1},{11509,6,0,-1},{11510,6,1,-1},{11513,6,0,-1},
+{11514,6,1,-1},{11518,6,0,-1},{11519,6,1,-1},{11522,6,0,-1},{11523,6,1,-1},
+{11526,6,0,-1},{11527,6,1,-1},{11531,6,0,-1},{11532,6,1,-1},{11535,6,0,-1},
+{11536,6,1,-1},{11539,6,0,-1},{11540,6,1,-1},{11544,6,0,-1},{11545,6,1,-1},
+{11548,6,0,-1},{11549,6,1,-1},{11552,6,0,-1},{11553,6,1,-1},{11557,6,0,-1},
+{11558,6,1,-1},{11561,6,0,-1},{11562,6,1,-1},{11565,6,0,-1},{11566,6,1,-1},
+{11570,6,0,-1},{11571,6,1,-1},{11574,6,0,-1},{11575,6,1,-1},{11578,6,0,-1},
+{11579,6,1,-1},{11583,6,0,-1},{11584,6,1,-1},{11587,6,0,-1},{11588,6,1,-1},
+{11591,6,0,-1},{11592,6,1,-1},{11596,6,0,-1},{11597,6,1,-1},{11530,6,0,-1},
+{11600,6,0,-1},{11601,6,1,-1},{11604,6,0,-1},{11605,6,1,-1},{11609,6,0,-1},
+{11610,6,1,-1},{11543,6,1,-1},{11613,6,0,-1},{11614,6,1,-1},{11617,6,0,-1},
+{11622,6,0,-1},{11623,6,1,-1},{11626,6,0,-1},{11627,6,1,-1},{11630,6,0,-1},
+{11631,6,1,-1},{11635,6,0,-1},{11636,6,1,-1},{11639,6,0,-1},{11640,6,1,-1},
+{11643,6,0,-1},{11644,6,1,-1},{11648,6,0,-1},{11649,6,1,-1},{11652,6,0,-1},
+{11653,6,1,-1},{11656,6,0,-1},{11657,6,1,-1},{11661,6,0,-1},{11662,6,1,-1},
+{11665,6,0,-1},{11666,6,1,-1},{11674,6,0,-1},{11675,6,1,-1},{11678,6,0,-1},
+{11679,6,1,-1},{11682,6,0,-1},{11683,6,1,-1},{11687,6,0,-1},{11688,6,1,-1},
+{11691,6,0,-1},{11692,6,1,-1},{11695,6,0,-1},{11696,6,1,-1},{11700,6,0,-1},
+{11701,6,1,-1},{11704,6,0,-1},{11705,6,1,-1},{11708,6,0,-1},{11709,6,1,-1},
+{11713,6,0,-1},{11717,6,0,-1},{11726,6,0,-1},{11730,6,0,-1},{11734,6,0,-1}};
+
+/* Frame number data sampled from measurement.c:lchan_meas_check_compute()
+ * Call was made between to phones on half rate channels TS7, SS0 and SS1 */
+struct fn_sample test_fn_tch_h_ts_7_ss0_ss1[] = {
+{11752,7,0,-1},{11760,7,0,-1},{11769,7,0,-1},{11778,7,0,-1},{11786,7,0,-1},
+{11795,7,0,-1},{11799,7,0,-1},{11804,7,0,-1},{11738,7,0,-1},{11808,7,0,-1},
+{11812,7,0,-1},{11817,7,0,-1},{11821,7,0,-1},{11825,7,0,-1},{11830,7,0,-1},
+{11834,7,0,-1},{11838,7,0,-1},{11843,7,0,-1},{11847,7,0,-1},{11851,7,0,-1},
+{11856,7,0,-1},{11860,7,0,-1},{11864,7,0,-1},{11869,7,0,-1},{11873,7,0,-1},
+{11877,7,0,-1},{11882,7,0,-1},{11886,7,0,-1},{11890,7,0,-1},{11895,7,0,-1},
+{11899,7,0,-1},{11903,7,0,-1},{11908,7,0,-1},{11842,7,0,-1},{11912,7,0,-1},
+{11916,7,0,-1},{11921,7,0,-1},{11925,7,0,-1},{11929,7,0,-1},{11934,7,0,-1},
+{11938,7,0,-1},{11942,7,0,-1},{11947,7,0,-1},{11951,7,0,-1},{11955,7,0,-1},
+{11960,7,0,-1},{11964,7,0,-1},{11968,7,0,-1},{11973,7,0,-1},{11977,7,0,-1},
+{11981,7,0,-1},{11986,7,0,-1},{11990,7,0,-1},{11994,7,0,-1},{11999,7,0,-1},
+{12003,7,0,-1},{12007,7,0,-1},{12012,7,0,-1},{11946,7,0,-1},{12016,7,0,-1},
+{12020,7,0,-1},{12025,7,0,-1},{12026,7,1,-1},{12029,7,0,-1},{12033,7,0,-1},
+{12034,7,1,-1},{12038,7,0,-1},{12042,7,0,-1},{12046,7,0,-1},{12047,7,1,-1},
+{12051,7,0,-1},{12055,7,0,-1},{12056,7,1,-1},{12059,7,0,-1},{12064,7,0,-1},
+{12065,7,1,-1},{12068,7,0,-1},{12072,7,0,-1},{12073,7,1,-1},{12077,7,0,-1},
+{12081,7,0,-1},{12082,7,1,-1},{12085,7,0,-1},{12090,7,0,-1},{12091,7,1,-1},
+{12094,7,0,-1},{12098,7,0,-1},{12099,7,1,-1},{12103,7,0,-1},{12107,7,0,-1},
+{12108,7,1,-1},{12111,7,0,-1},{12116,7,0,-1},{12117,7,1,-1},{12050,7,0,-1},
+{12120,7,0,-1},{12124,7,0,-1},{12125,7,1,-1},{12129,7,0,-1},{12063,7,1,-1},
+{12133,7,0,-1},{12134,7,1,-1},{12137,7,0,-1},{12142,7,0,-1},{12143,7,1,-1},
+{12146,7,0,-1},{12150,7,0,-1},{12151,7,1,-1},{12155,7,0,-1},{12159,7,0,-1},
+{12160,7,1,-1},{12163,7,0,-1},{12168,7,0,-1},{12169,7,1,-1},{12172,7,0,-1},
+{12176,7,0,-1},{12177,7,1,-1},{12181,7,0,-1},{12185,7,0,-1},{12186,7,1,-1},
+{12189,7,0,-1},{12190,7,1,-1},{12194,7,0,-1},{12195,7,1,-1},{12198,7,0,-1},
+{12199,7,1,-1},{12202,7,0,-1},{12203,7,1,-1},{12207,7,0,-1},{12208,7,1,-1},
+{12211,7,0,-1},{12212,7,1,-1},{12215,7,0,-1},{12220,7,0,-1},{12221,7,1,-1},
+{12154,7,0,-1},{12224,7,0,-1},{12225,7,1,-1},{12228,7,0,-1},{12229,7,1,-1},
+{12233,7,0,-1},{12234,7,1,-1},{12167,7,1,-1},{12237,7,0,-1},{12238,7,1,-1},
+{12241,7,0,-1},{12242,7,1,-1},{12246,7,0,-1},{12247,7,1,-1},{12250,7,0,-1},
+{12251,7,1,-1},{12254,7,0,-1},{12255,7,1,-1},{12260,7,1,-1},{12263,7,0,-1},
+{12264,7,1,-1},{12267,7,0,-1},{12268,7,1,-1},{12272,7,0,-1},{12273,7,1,-1},
+{12276,7,0,-1},{12277,7,1,-1},{12280,7,0,-1},{12281,7,1,-1},{12285,7,0,-1},
+{12286,7,1,-1},{12289,7,0,-1},{12290,7,1,-1},{12293,7,0,-1},{12294,7,1,-1},
+{12298,7,0,-1},{12299,7,1,-1},{12302,7,0,-1},{12303,7,1,-1},{12306,7,0,-1},
+{12307,7,1,-1},{12311,7,0,-1},{12312,7,1,-1},{12315,7,0,-1},{12316,7,1,-1},
+{12319,7,0,-1},{12320,7,1,-1},{12324,7,0,-1},{12325,7,1,-1},{12258,7,0,-1},
+{12328,7,0,-1},{12329,7,1,-1},{12332,7,0,-1},{12333,7,1,-1},{12337,7,0,-1},
+{12338,7,1,-1},{12271,7,1,-1},{12341,7,0,-1},{12342,7,1,-1},{12345,7,0,-1},
+{12346,7,1,-1},{12350,7,0,-1},{12351,7,1,-1},{12354,7,0,-1},{12355,7,1,-1},
+{12358,7,0,-1},{12359,7,1,-1},{12363,7,0,-1},{12367,7,0,-1},{12368,7,1,-1},
+{12371,7,0,-1},{12372,7,1,-1},{12376,7,0,-1},{12377,7,1,-1},{12380,7,0,-1},
+{12381,7,1,-1},{12384,7,0,-1},{12385,7,1,-1},{12389,7,0,-1},{12390,7,1,-1},
+{12393,7,0,-1},{12394,7,1,-1},{12397,7,0,-1},{12398,7,1,-1},{12402,7,0,-1},
+{12403,7,1,-1},{12407,7,1,-1},{12410,7,0,-1},{12411,7,1,-1},{12415,7,0,-1},
+{12419,7,0,-1},{12420,7,1,-1},{12423,7,0,-1},{12424,7,1,-1},{12428,7,0,-1},
+{12429,7,1,-1},{12362,7,0,-1},{12432,7,0,-1},{12433,7,1,-1},{12436,7,0,-1},
+{12437,7,1,-1},{12441,7,0,-1},{12442,7,1,-1},{12375,7,1,-1},{12445,7,0,-1},
+{12446,7,1,-1},{12449,7,0,-1},{12450,7,1,-1},{12454,7,0,-1},{12455,7,1,-1},
+{12458,7,0,-1},{12459,7,1,-1},{12462,7,0,-1},{12463,7,1,-1},{12467,7,0,-1},
+{12468,7,1,-1},{12471,7,0,-1},{12472,7,1,-1},{12475,7,0,-1},{12476,7,1,-1},
+{12480,7,0,-1},{12481,7,1,-1},{12484,7,0,-1},{12485,7,1,-1},{12488,7,0,-1},
+{12489,7,1,-1},{12493,7,0,-1},{12494,7,1,-1},{12497,7,0,-1},{12498,7,1,-1},
+{12501,7,0,-1},{12502,7,1,-1},{12506,7,0,-1},{12507,7,1,-1},{12510,7,0,-1},
+{12511,7,1,-1},{12514,7,0,-1},{12515,7,1,-1},{12519,7,0,-1},{12520,7,1,-1},
+{12523,7,0,-1},{12524,7,1,-1},{12527,7,0,-1},{12528,7,1,-1},{12532,7,0,-1},
+{12533,7,1,-1},{12466,7,0,-1},{12536,7,0,-1},{12537,7,1,-1},{12540,7,0,-1},
+{12541,7,1,-1},{12545,7,0,-1},{12546,7,1,-1},{12479,7,1,-1},{12549,7,0,-1},
+{12550,7,1,-1},{12553,7,0,-1},{12554,7,1,-1},{12558,7,0,-1},{12559,7,1,-1},
+{12562,7,0,-1},{12563,7,1,-1},{12566,7,0,-1},{12567,7,1,-1},{12571,7,0,-1},
+{12572,7,1,-1},{12575,7,0,-1},{12576,7,1,-1},{12579,7,0,-1},{12580,7,1,-1},
+{12584,7,0,-1},{12585,7,1,-1},{12588,7,0,-1},{12589,7,1,-1},{12592,7,0,-1},
+{12593,7,1,-1},{12597,7,0,-1},{12598,7,1,-1},{12601,7,0,-1},{12602,7,1,-1},
+{12605,7,0,-1},{12606,7,1,-1},{12610,7,0,-1},{12611,7,1,-1},{12614,7,0,-1},
+{12615,7,1,-1},{12618,7,0,-1},{12619,7,1,-1},{12623,7,0,-1},{12624,7,1,-1},
+{12627,7,0,-1},{12628,7,1,-1},{12631,7,0,-1},{12632,7,1,-1},{12636,7,0,-1},
+{12637,7,1,-1},{12570,7,0,-1},{12640,7,0,-1},{12641,7,1,-1},{12644,7,0,-1},
+{12645,7,1,-1},{12649,7,0,-1},{12650,7,1,-1},{12583,7,1,-1},{12653,7,0,-1},
+{12654,7,1,-1},{12657,7,0,-1},{12658,7,1,-1},{12662,7,0,-1},{12663,7,1,-1},
+{12666,7,0,-1},{12667,7,1,-1},{12670,7,0,-1},{12671,7,1,-1},{12675,7,0,-1},
+{12676,7,1,-1},{12679,7,0,-1},{12680,7,1,-1},{12683,7,0,-1},{12684,7,1,-1},
+{12688,7,0,-1},{12689,7,1,-1},{12692,7,0,-1},{12693,7,1,-1},{12696,7,0,-1},
+{12697,7,1,-1},{12701,7,0,-1},{12702,7,1,-1},{12705,7,0,-1},{12706,7,1,-1},
+{12709,7,0,-1},{12710,7,1,-1},{12714,7,0,-1},{12715,7,1,-1},{12718,7,0,-1},
+{12719,7,1,-1},{12722,7,0,-1},{12723,7,1,-1},{12727,7,0,-1},{12728,7,1,-1},
+{12731,7,0,-1},{12732,7,1,-1},{12735,7,0,-1},{12736,7,1,-1},{12740,7,0,-1},
+{12741,7,1,-1},{12674,7,0,-1},{12744,7,0,-1},{12745,7,1,-1},{12748,7,0,-1},
+{12749,7,1,-1},{12753,7,0,-1},{12754,7,1,-1},{12687,7,1,-1},{12757,7,0,-1},
+{12758,7,1,-1},{12761,7,0,-1},{12762,7,1,-1},{12766,7,0,-1},{12767,7,1,-1},
+{12770,7,0,-1},{12771,7,1,-1},{12774,7,0,-1},{12775,7,1,-1},{12779,7,0,-1},
+{12780,7,1,-1},{12783,7,0,-1},{12784,7,1,-1},{12787,7,0,-1},{12788,7,1,-1},
+{12792,7,0,-1},{12793,7,1,-1},{12796,7,0,-1},{12797,7,1,-1},{12800,7,0,-1},
+{12801,7,1,-1},{12805,7,0,-1},{12806,7,1,-1},{12809,7,0,-1},{12810,7,1,-1},
+{12813,7,0,-1},{12814,7,1,-1},{12818,7,0,-1},{12819,7,1,-1},{12822,7,0,-1},
+{12823,7,1,-1},{12826,7,0,-1},{12827,7,1,-1},{12831,7,0,-1},{12832,7,1,-1},
+{12835,7,0,-1},{12836,7,1,-1},{12839,7,0,-1},{12840,7,1,-1},{12844,7,0,-1},
+{12845,7,1,-1},{12778,7,0,-1},{12848,7,0,-1},{12849,7,1,-1},{12852,7,0,-1},
+{12853,7,1,-1},{12857,7,0,-1},{12858,7,1,-1},{12791,7,1,-1},{12861,7,0,-1},
+{12862,7,1,-1},{12865,7,0,-1},{12866,7,1,-1},{12870,7,0,-1},{12871,7,1,-1},
+{12874,7,0,-1},{12875,7,1,-1},{12878,7,0,-1},{12879,7,1,-1},{12883,7,0,-1},
+{12884,7,1,-1},{12887,7,0,-1},{12888,7,1,-1},{12891,7,0,-1},{12892,7,1,-1},
+{12896,7,0,-1},{12897,7,1,-1},{12900,7,0,-1},{12901,7,1,-1},{12904,7,0,-1},
+{12905,7,1,-1},{12909,7,0,-1},{12910,7,1,-1},{12913,7,0,-1},{12914,7,1,-1},
+{12917,7,0,-1},{12918,7,1,-1},{12922,7,0,-1},{12923,7,1,-1},{12926,7,0,-1},
+{12927,7,1,-1},{12930,7,0,-1},{12931,7,1,-1},{12935,7,0,-1},{12936,7,1,-1},
+{12939,7,0,-1},{12940,7,1,-1},{12943,7,0,-1},{12944,7,1,-1},{12948,7,0,-1},
+{12949,7,1,-1},{12882,7,0,-1},{12952,7,0,-1},{12953,7,1,-1},{12956,7,0,-1},
+{12957,7,1,-1},{12961,7,0,-1},{12962,7,1,-1},{12895,7,1,-1},{12965,7,0,-1},
+{12966,7,1,-1},{12969,7,0,-1},{12970,7,1,-1},{12974,7,0,-1},{12975,7,1,-1},
+{12978,7,0,-1},{12979,7,1,-1},{12982,7,0,-1},{12983,7,1,-1},{12987,7,0,-1},
+{12988,7,1,-1},{12991,7,0,-1},{12992,7,1,-1},{12995,7,0,-1},{12996,7,1,-1},
+{13000,7,0,-1},{13001,7,1,-1},{13004,7,0,-1},{13005,7,1,-1},{13008,7,0,-1},
+{13009,7,1,-1},{13013,7,0,-1},{13014,7,1,-1},{13017,7,0,-1},{13018,7,1,-1},
+{13021,7,0,-1},{13022,7,1,-1},{13026,7,0,-1},{13027,7,1,-1},{13030,7,0,-1},
+{13031,7,1,-1},{13034,7,0,-1},{13035,7,1,-1},{13039,7,0,-1},{13040,7,1,-1},
+{13043,7,0,-1},{13044,7,1,-1},{13047,7,0,-1},{13048,7,1,-1},{13052,7,0,-1},
+{13053,7,1,-1},{12986,7,0,-1},{13056,7,0,-1},{13057,7,1,-1},{13060,7,0,-1},
+{13061,7,1,-1},{13065,7,0,-1},{13066,7,1,-1},{12999,7,1,-1},{13069,7,0,-1},
+{13070,7,1,-1},{13073,7,0,-1},{13074,7,1,-1},{13078,7,0,-1},{13079,7,1,-1},
+{13082,7,0,-1},{13083,7,1,-1},{13086,7,0,-1},{13087,7,1,-1},{13091,7,0,-1},
+{13092,7,1,-1},{13095,7,0,-1},{13096,7,1,-1},{13099,7,0,-1},{13100,7,1,-1},
+{13104,7,0,-1},{13105,7,1,-1},{13108,7,0,-1},{13109,7,1,-1},{13112,7,0,-1},
+{13113,7,1,-1},{13117,7,0,-1},{13118,7,1,-1},{13121,7,0,-1},{13122,7,1,-1},
+{13125,7,0,-1},{13126,7,1,-1},{13130,7,0,-1},{13131,7,1,-1},{13134,7,0,-1},
+{13135,7,1,-1},{13138,7,0,-1},{13139,7,1,-1},{13143,7,0,-1},{13144,7,1,-1},
+{13147,7,0,-1},{13148,7,1,-1},{13151,7,0,-1},{13152,7,1,-1},{13156,7,0,-1},
+{13157,7,1,-1},{13090,7,0,-1},{13160,7,0,-1},{13161,7,1,-1},{13164,7,0,-1},
+{13165,7,1,-1},{13169,7,0,-1},{13170,7,1,-1},{13103,7,1,-1},{13173,7,0,-1},
+{13174,7,1,-1},{13177,7,0,-1},{13178,7,1,-1},{13182,7,0,-1},{13183,7,1,-1},
+{13186,7,0,-1},{13187,7,1,-1},{13190,7,0,-1},{13191,7,1,-1},{13195,7,0,-1},
+{13196,7,1,-1},{13199,7,0,-1},{13200,7,1,-1},{13203,7,0,-1},{13204,7,1,-1},
+{13208,7,0,-1},{13209,7,1,-1},{13212,7,0,-1},{13213,7,1,-1},{13216,7,0,-1},
+{13217,7,1,-1},{13221,7,0,-1},{13222,7,1,-1},{13225,7,0,-1},{13226,7,1,-1},
+{13229,7,0,-1},{13230,7,1,-1},{13234,7,0,-1},{13235,7,1,-1},{13238,7,0,-1},
+{13239,7,1,-1},{13242,7,0,-1},{13243,7,1,-1},{13247,7,0,-1},{13248,7,1,-1},
+{13251,7,0,-1},{13252,7,1,-1},{13255,7,0,-1},{13256,7,1,-1},{13260,7,0,-1},
+{13261,7,1,-1},{13194,7,0,-1},{13264,7,0,-1},{13265,7,1,-1},{13268,7,0,-1},
+{13269,7,1,-1},{13273,7,0,-1},{13274,7,1,-1},{13207,7,1,-1},{13277,7,0,-1},
+{13278,7,1,-1},{13281,7,0,-1},{13282,7,1,-1},{13286,7,0,-1},{13287,7,1,-1},
+{13290,7,0,-1},{13291,7,1,-1},{13294,7,0,-1},{13295,7,1,-1},{13299,7,0,-1},
+{13300,7,1,-1},{13303,7,0,-1},{13304,7,1,-1},{13307,7,0,-1},{13308,7,1,-1},
+{13312,7,0,-1},{13313,7,1,-1},{13316,7,0,-1},{13317,7,1,-1},{13320,7,0,-1},
+{13321,7,1,-1},{13325,7,0,-1},{13326,7,1,-1},{13329,7,0,-1},{13330,7,1,-1},
+{13333,7,0,-1},{13334,7,1,-1},{13338,7,0,-1},{13339,7,1,-1},{13342,7,0,-1},
+{13343,7,1,-1},{13346,7,0,-1},{13347,7,1,-1},{13351,7,0,-1},{13352,7,1,-1},
+{13355,7,0,-1},{13356,7,1,-1},{13359,7,0,-1},{13360,7,1,-1},{13364,7,0,-1},
+{13365,7,1,-1},{13298,7,0,-1},{13368,7,0,-1},{13369,7,1,-1},{13372,7,0,-1},
+{13373,7,1,-1},{13377,7,0,-1},{13378,7,1,-1},{13311,7,1,-1},{13381,7,0,-1},
+{13382,7,1,-1},{13385,7,0,-1},{13386,7,1,-1},{13390,7,0,-1},{13391,7,1,-1},
+{13394,7,0,-1},{13395,7,1,-1},{13398,7,0,-1},{13399,7,1,-1},{13403,7,0,-1},
+{13404,7,1,-1},{13407,7,0,-1},{13408,7,1,-1},{13411,7,0,-1},{13412,7,1,-1},
+{13416,7,0,-1},{13417,7,1,-1},{13420,7,0,-1},{13421,7,1,-1},{13424,7,0,-1},
+{13425,7,1,-1},{13429,7,0,-1},{13430,7,1,-1},{13433,7,0,-1},{13434,7,1,-1},
+{13437,7,0,-1},{13438,7,1,-1},{13442,7,0,-1},{13443,7,1,-1},{13446,7,0,-1},
+{13447,7,1,-1},{13450,7,0,-1},{13451,7,1,-1},{13455,7,0,-1},{13456,7,1,-1},
+{13459,7,0,-1},{13460,7,1,-1},{13463,7,0,-1},{13464,7,1,-1},{13468,7,0,-1},
+{13469,7,1,-1},{13402,7,0,-1},{13472,7,0,-1},{13473,7,1,-1},{13476,7,0,-1},
+{13477,7,1,-1},{13481,7,0,-1},{13482,7,1,-1},{13415,7,1,-1},{13485,7,0,-1},
+{13486,7,1,-1},{13489,7,0,-1},{13490,7,1,-1},{13494,7,0,-1},{13495,7,1,-1},
+{13498,7,0,-1},{13499,7,1,-1},{13502,7,0,-1},{13503,7,1,-1},{13507,7,0,-1},
+{13508,7,1,-1},{13511,7,0,-1},{13512,7,1,-1},{13515,7,0,-1},{13516,7,1,-1},
+{13520,7,0,-1},{13521,7,1,-1},{13524,7,0,-1},{13525,7,1,-1},{13528,7,0,-1},
+{13529,7,1,-1},{13533,7,0,-1},{13534,7,1,-1},{13537,7,0,-1},{13538,7,1,-1},
+{13541,7,0,-1},{13542,7,1,-1},{13546,7,0,-1},{13547,7,1,-1},{13550,7,0,-1},
+{13551,7,1,-1},{13554,7,0,-1},{13555,7,1,-1},{13559,7,0,-1},{13560,7,1,-1},
+{13563,7,0,-1},{13564,7,1,-1},{13567,7,0,-1},{13568,7,1,-1},{13572,7,0,-1},
+{13573,7,1,-1},{13506,7,0,-1},{13576,7,0,-1},{13577,7,1,-1},{13580,7,0,-1},
+{13581,7,1,-1},{13585,7,0,-1},{13586,7,1,-1},{13519,7,1,-1},{13589,7,0,-1},
+{13590,7,1,-1},{13593,7,0,-1},{13594,7,1,-1},{13598,7,0,-1},{13599,7,1,-1},
+{13602,7,0,-1},{13603,7,1,-1},{13606,7,0,-1},{13607,7,1,-1},{13611,7,0,-1},
+{13612,7,1,-1},{13615,7,0,-1},{13616,7,1,-1},{13619,7,0,-1},{13620,7,1,-1},
+{13624,7,0,-1},{13625,7,1,-1},{13628,7,0,-1},{13629,7,1,-1},{13632,7,0,-1},
+{13633,7,1,-1},{13637,7,0,-1},{13638,7,1,-1},{13641,7,0,-1},{13642,7,1,-1},
+{13645,7,0,-1},{13646,7,1,-1},{13650,7,0,-1},{13651,7,1,-1},{13654,7,0,-1},
+{13655,7,1,-1},{13658,7,0,-1},{13659,7,1,-1},{13663,7,0,-1},{13664,7,1,-1},
+{13667,7,0,-1},{13668,7,1,-1},{13671,7,0,-1},{13672,7,1,-1},{13676,7,0,-1},
+{13677,7,1,-1},{13610,7,0,-1},{13680,7,0,-1},{13681,7,1,-1},{13684,7,0,-1},
+{13685,7,1,-1},{13689,7,0,-1},{13690,7,1,-1},{13623,7,1,-1},{13693,7,0,-1},
+{13694,7,1,-1},{13697,7,0,-1},{13698,7,1,-1},{13702,7,0,-1},{13703,7,1,-1},
+{13706,7,0,-1},{13707,7,1,-1},{13710,7,0,-1},{13711,7,1,-1},{13715,7,0,-1},
+{13716,7,1,-1},{13719,7,0,-1},{13720,7,1,-1},{13723,7,0,-1},{13724,7,1,-1},
+{13728,7,0,-1},{13729,7,1,-1},{13732,7,0,-1},{13733,7,1,-1},{13736,7,0,-1},
+{13737,7,1,-1},{13741,7,0,-1},{13742,7,1,-1},{13745,7,0,-1},{13746,7,1,-1},
+{13749,7,0,-1},{13750,7,1,-1},{13754,7,0,-1},{13755,7,1,-1},{13758,7,0,-1},
+{13759,7,1,-1},{13762,7,0,-1},{13763,7,1,-1},{13767,7,0,-1},{13768,7,1,-1},
+{13771,7,0,-1},{13772,7,1,-1},{13775,7,0,-1},{13776,7,1,-1},{13780,7,0,-1},
+{13781,7,1,-1},{13714,7,0,-1},{13784,7,0,-1},{13785,7,1,-1},{13788,7,0,-1},
+{13789,7,1,-1},{13793,7,0,-1},{13794,7,1,-1},{13727,7,1,-1},{13797,7,0,-1},
+{13798,7,1,-1},{13801,7,0,-1},{13802,7,1,-1},{13806,7,0,-1},{13807,7,1,-1},
+{13810,7,0,-1},{13811,7,1,-1},{13814,7,0,-1},{13815,7,1,-1},{13819,7,0,-1},
+{13820,7,1,-1},{13823,7,0,-1},{13824,7,1,-1},{13827,7,0,-1},{13828,7,1,-1},
+{13832,7,0,-1},{13833,7,1,-1},{13836,7,0,-1},{13837,7,1,-1},{13840,7,0,-1},
+{13841,7,1,-1},{13845,7,0,-1},{13846,7,1,-1},{13849,7,0,-1},{13850,7,1,-1},
+{13853,7,0,-1},{13854,7,1,-1},{13858,7,0,-1},{13859,7,1,-1},{13862,7,0,-1},
+{13863,7,1,-1},{13866,7,0,-1},{13867,7,1,-1},{13871,7,0,-1},{13872,7,1,-1},
+{13875,7,0,-1},{13876,7,1,-1},{13879,7,0,-1},{13880,7,1,-1},{13884,7,0,-1},
+{13885,7,1,-1},{13818,7,0,-1},{13888,7,0,-1},{13889,7,1,-1},{13892,7,0,-1},
+{13893,7,1,-1},{13897,7,0,-1},{13898,7,1,-1},{13831,7,1,-1},{13901,7,0,-1},
+{13902,7,1,-1},{13905,7,0,-1},{13906,7,1,-1},{13910,7,0,-1},{13911,7,1,-1},
+{13914,7,0,-1},{13915,7,1,-1},{13918,7,0,-1},{13919,7,1,-1},{13923,7,0,-1},
+{13924,7,1,-1},{13927,7,0,-1},{13928,7,1,-1},{13931,7,0,-1},{13932,7,1,-1},
+{13936,7,0,-1},{13937,7,1,-1},{13940,7,0,-1},{13941,7,1,-1},{13944,7,0,-1},
+{13945,7,1,-1},{13949,7,0,-1},{13950,7,1,-1},{13953,7,0,-1},{13954,7,1,-1},
+{13957,7,0,-1},{13958,7,1,-1},{13962,7,0,-1},{13963,7,1,-1},{13966,7,0,-1},
+{13967,7,1,-1},{13970,7,0,-1},{13971,7,1,-1},{13975,7,0,-1},{13976,7,1,-1},
+{13979,7,0,-1},{13980,7,1,-1},{13983,7,0,-1},{13984,7,1,-1},{13988,7,0,-1},
+{13989,7,1,-1},{13922,7,0,-1},{13992,7,0,-1},{13993,7,1,-1},{13996,7,0,-1},
+{13997,7,1,-1},{14001,7,0,-1},{14002,7,1,-1},{13935,7,1,-1},{14005,7,0,-1},
+{14006,7,1,-1},{14009,7,0,-1},{14010,7,1,-1},{14014,7,0,-1},{14015,7,1,-1},
+{14018,7,0,-1},{14019,7,1,-1},{14022,7,0,-1},{14023,7,1,-1},{14027,7,0,-1},
+{14028,7,1,-1},{14031,7,0,-1},{14032,7,1,-1},{14035,7,0,-1},{14036,7,1,-1},
+{14040,7,0,-1},{14041,7,1,-1},{14044,7,0,-1},{14045,7,1,-1},{14048,7,0,-1},
+{14049,7,1,-1},{14053,7,0,-1},{14054,7,1,-1},{14057,7,0,-1},{14058,7,1,-1},
+{14061,7,0,-1},{14062,7,1,-1},{14066,7,0,-1},{14067,7,1,-1},{14070,7,0,-1},
+{14071,7,1,-1},{14074,7,0,-1},{14075,7,1,-1},{14079,7,0,-1},{14080,7,1,-1},
+{14083,7,0,-1},{14084,7,1,-1},{14087,7,0,-1},{14088,7,1,-1},{14092,7,0,-1},
+{14093,7,1,-1},{14026,7,0,-1},{14096,7,0,-1},{14097,7,1,-1},{14100,7,0,-1},
+{14101,7,1,-1},{14105,7,0,-1},{14106,7,1,-1},{14039,7,1,-1},{14109,7,0,-1},
+{14110,7,1,-1},{14113,7,0,-1},{14114,7,1,-1},{14118,7,0,-1},{14119,7,1,-1},
+{14122,7,0,-1},{14123,7,1,-1},{14126,7,0,-1},{14127,7,1,-1},{14131,7,0,-1},
+{14132,7,1,-1},{14135,7,0,-1},{14136,7,1,-1},{14139,7,0,-1},{14140,7,1,-1},
+{14144,7,0,-1},{14145,7,1,-1},{14148,7,0,-1},{14149,7,1,-1},{14152,7,0,-1},
+{14153,7,1,-1},{14157,7,0,-1},{14158,7,1,-1},{14161,7,0,-1},{14162,7,1,-1},
+{14165,7,0,-1},{14166,7,1,-1},{14170,7,0,-1},{14171,7,1,-1},{14174,7,0,-1},
+{14175,7,1,-1},{14178,7,0,-1},{14179,7,1,-1},{14183,7,0,-1},{14184,7,1,-1},
+{14187,7,0,-1},{14188,7,1,-1},{14191,7,0,-1},{14192,7,1,-1},{14196,7,0,-1},
+{14197,7,1,-1},{14130,7,0,-1},{14200,7,0,-1},{14201,7,1,-1},{14204,7,0,-1},
+{14205,7,1,-1},{14209,7,0,-1},{14210,7,1,-1},{14143,7,1,-1},{14213,7,0,-1},
+{14214,7,1,-1},{14217,7,0,-1},{14218,7,1,-1},{14222,7,0,-1},{14223,7,1,-1},
+{14226,7,0,-1},{14227,7,1,-1},{14230,7,0,-1},{14231,7,1,-1},{14235,7,0,-1},
+{14236,7,1,-1},{14239,7,0,-1},{14240,7,1,-1},{14243,7,0,-1},{14244,7,1,-1},
+{14248,7,0,-1},{14249,7,1,-1},{14252,7,0,-1},{14253,7,1,-1},{14256,7,0,-1},
+{14257,7,1,-1},{14261,7,0,-1},{14262,7,1,-1},{14265,7,0,-1},{14266,7,1,-1},
+{14269,7,0,-1},{14270,7,1,-1},{14274,7,0,-1},{14275,7,1,-1},{14278,7,0,-1},
+{14279,7,1,-1},{14282,7,0,-1},{14283,7,1,-1},{14287,7,0,-1},{14288,7,1,-1},
+{14291,7,0,-1},{14292,7,1,-1},{14295,7,0,-1},{14296,7,1,-1},{14300,7,0,-1},
+{14301,7,1,-1},{14234,7,0,-1},{14304,7,0,-1},{14305,7,1,-1},{14308,7,0,-1},
+{14309,7,1,-1},{14313,7,0,-1},{14314,7,1,-1},{14247,7,1,-1},{14317,7,0,-1},
+{14318,7,1,-1},{14321,7,0,-1},{14322,7,1,-1},{14326,7,0,-1},{14327,7,1,-1},
+{14330,7,0,-1},{14331,7,1,-1},{14334,7,0,-1},{14335,7,1,-1},{14339,7,0,-1},
+{14340,7,1,-1},{14343,7,0,-1},{14344,7,1,-1},{14347,7,0,-1},{14348,7,1,-1},
+{14352,7,0,-1},{14353,7,1,-1},{14356,7,0,-1},{14357,7,1,-1},{14360,7,0,-1},
+{14361,7,1,-1},{14365,7,0,-1},{14366,7,1,-1},{14369,7,0,-1},{14370,7,1,-1},
+{14373,7,0,-1},{14374,7,1,-1},{14378,7,0,-1},{14379,7,1,-1},{14382,7,0,-1},
+{14383,7,1,-1},{14386,7,0,-1},{14387,7,1,-1},{14391,7,0,-1},{14392,7,1,-1},
+{14395,7,0,-1},{14396,7,1,-1},{14399,7,0,-1},{14400,7,1,-1},{14404,7,0,-1},
+{14405,7,1,-1},{14338,7,0,-1},{14408,7,0,-1},{14409,7,1,-1},{14412,7,0,-1},
+{14413,7,1,-1},{14417,7,0,-1},{14418,7,1,-1},{14351,7,1,-1},{14421,7,0,-1},
+{14422,7,1,-1},{14425,7,0,-1},{14426,7,1,-1},{14430,7,0,-1},{14431,7,1,-1},
+{14434,7,0,-1},{14435,7,1,-1},{14438,7,0,-1},{14439,7,1,-1},{14443,7,0,-1},
+{14444,7,1,-1},{14447,7,0,-1},{14448,7,1,-1},{14451,7,0,-1},{14452,7,1,-1},
+{14456,7,0,-1},{14457,7,1,-1},{14460,7,0,-1},{14461,7,1,-1},{14464,7,0,-1},
+{14465,7,1,-1},{14469,7,0,-1},{14470,7,1,-1},{14473,7,0,-1},{14474,7,1,-1},
+{14477,7,0,-1},{14478,7,1,-1},{14482,7,0,-1},{14483,7,1,-1},{14486,7,0,-1},
+{14487,7,1,-1},{14490,7,0,-1},{14491,7,1,-1},{14495,7,0,-1},{14496,7,1,-1},
+{14499,7,0,-1},{14500,7,1,-1},{14503,7,0,-1},{14504,7,1,-1},{14508,7,0,-1},
+{14509,7,1,-1},{14442,7,0,-1},{14512,7,0,-1},{14513,7,1,-1},{14516,7,0,-1},
+{14517,7,1,-1},{14521,7,0,-1},{14522,7,1,-1},{14455,7,1,-1},{14525,7,0,-1},
+{14526,7,1,-1},{14529,7,0,-1},{14530,7,1,-1},{14534,7,0,-1},{14535,7,1,-1},
+{14538,7,0,-1},{14539,7,1,-1},{14542,7,0,-1},{14543,7,1,-1},{14547,7,0,-1},
+{14548,7,1,-1},{14551,7,0,-1},{14552,7,1,-1},{14555,7,0,-1},{14556,7,1,-1},
+{14560,7,0,-1},{14561,7,1,-1},{14564,7,0,-1},{14565,7,1,-1},{14568,7,0,-1},
+{14569,7,1,-1},{14573,7,0,-1},{14574,7,1,-1},{14577,7,0,-1},{14578,7,1,-1},
+{14581,7,0,-1},{14582,7,1,-1},{14586,7,0,-1},{14587,7,1,-1},{14590,7,0,-1},
+{14591,7,1,-1},{14594,7,0,-1},{14595,7,1,-1},{14599,7,0,-1},{14600,7,1,-1},
+{14603,7,0,-1},{14604,7,1,-1},{14607,7,0,-1},{14608,7,1,-1},{14612,7,0,-1},
+{14613,7,1,-1},{14546,7,0,-1},{14616,7,0,-1},{14617,7,1,-1},{14620,7,0,-1},
+{14621,7,1,-1},{14625,7,0,-1},{14626,7,1,-1},{14559,7,1,-1},{14629,7,0,-1},
+{14630,7,1,-1},{14633,7,0,-1},{14634,7,1,-1},{14638,7,0,-1},{14639,7,1,-1},
+{14642,7,0,-1},{14643,7,1,-1},{14646,7,0,-1},{14647,7,1,-1},{14651,7,0,-1},
+{14652,7,1,-1},{14655,7,0,-1},{14656,7,1,-1},{14659,7,0,-1},{14660,7,1,-1},
+{14664,7,0,-1},{14665,7,1,-1},{14668,7,0,-1},{14669,7,1,-1},{14672,7,0,-1},
+{14673,7,1,-1},{14677,7,0,-1},{14678,7,1,-1},{14681,7,0,-1},{14682,7,1,-1},
+{14685,7,0,-1},{14686,7,1,-1},{14690,7,0,-1},{14691,7,1,-1},{14694,7,0,-1},
+{14695,7,1,-1},{14698,7,0,-1},{14699,7,1,-1},{14703,7,0,-1},{14704,7,1,-1},
+{14707,7,0,-1},{14708,7,1,-1},{14711,7,0,-1},{14712,7,1,-1},{14716,7,0,-1},
+{14717,7,1,-1},{14650,7,0,-1},{14720,7,0,-1},{14721,7,1,-1},{14724,7,0,-1},
+{14725,7,1,-1},{14729,7,0,-1},{14730,7,1,-1},{14663,7,1,-1},{14733,7,0,-1},
+{14734,7,1,-1},{14737,7,0,-1},{14738,7,1,-1},{14742,7,0,-1},{14743,7,1,-1},
+{14746,7,0,-1},{14747,7,1,-1},{14750,7,0,-1},{14751,7,1,-1},{14755,7,0,-1},
+{14756,7,1,-1},{14759,7,0,-1},{14760,7,1,-1},{14763,7,0,-1},{14764,7,1,-1},
+{14768,7,0,-1},{14769,7,1,-1},{14772,7,0,-1},{14773,7,1,-1},{14776,7,0,-1},
+{14777,7,1,-1},{14781,7,0,-1},{14782,7,1,-1},{14785,7,0,-1},{14786,7,1,-1},
+{14789,7,0,-1},{14790,7,1,-1},{14794,7,0,-1},{14795,7,1,-1},{14798,7,0,-1},
+{14799,7,1,-1},{14802,7,0,-1},{14803,7,1,-1},{14807,7,0,-1},{14808,7,1,-1},
+{14811,7,0,-1},{14812,7,1,-1},{14815,7,0,-1},{14816,7,1,-1},{14820,7,0,-1},
+{14821,7,1,-1},{14754,7,0,-1},{14824,7,0,-1},{14825,7,1,-1},{14828,7,0,-1},
+{14829,7,1,-1},{14833,7,0,-1},{14834,7,1,-1},{14767,7,1,-1},{14837,7,0,-1},
+{14838,7,1,-1},{14841,7,0,-1},{14842,7,1,-1},{14846,7,0,-1},{14847,7,1,-1},
+{14850,7,0,-1},{14851,7,1,-1},{14854,7,0,-1},{14855,7,1,-1},{14859,7,0,-1},
+{14860,7,1,-1},{14863,7,0,-1},{14864,7,1,-1},{14867,7,0,-1},{14868,7,1,-1},
+{14872,7,0,-1},{14873,7,1,-1},{14876,7,0,-1},{14877,7,1,-1},{14880,7,0,-1},
+{14881,7,1,-1},{14885,7,0,-1},{14886,7,1,-1},{14889,7,0,-1},{14890,7,1,-1},
+{14893,7,0,-1},{14894,7,1,-1},{14898,7,0,-1},{14899,7,1,-1},{14902,7,0,-1},
+{14903,7,1,-1},{14906,7,0,-1},{14907,7,1,-1},{14911,7,0,-1},{14912,7,1,-1},
+{14915,7,0,-1},{14916,7,1,-1},{14919,7,0,-1},{14920,7,1,-1},{14924,7,0,-1},
+{14925,7,1,-1},{14858,7,0,-1},{14928,7,0,-1},{14929,7,1,-1},{14932,7,0,-1},
+{14933,7,1,-1},{14937,7,0,-1},{14938,7,1,-1},{14871,7,1,-1},{14941,7,0,-1},
+{14942,7,1,-1},{14945,7,0,-1},{14946,7,1,-1},{14950,7,0,-1},{14951,7,1,-1},
+{14954,7,0,-1},{14955,7,1,-1},{14958,7,0,-1},{14959,7,1,-1},{14963,7,0,-1},
+{14964,7,1,-1},{14967,7,0,-1},{14968,7,1,-1},{14971,7,0,-1},{14972,7,1,-1},
+{14976,7,0,-1},{14977,7,1,-1},{14980,7,0,-1},{14981,7,1,-1},{14984,7,0,-1},
+{14985,7,1,-1},{14989,7,0,-1},{14990,7,1,-1},{14993,7,0,-1},{14994,7,1,-1},
+{14997,7,0,-1},{14998,7,1,-1},{15002,7,0,-1},{15003,7,1,-1},{15006,7,0,-1},
+{15007,7,1,-1},{15010,7,0,-1},{15011,7,1,-1},{15015,7,0,-1},{15016,7,1,-1},
+{15019,7,0,-1},{15020,7,1,-1},{15023,7,0,-1},{15024,7,1,-1},{15028,7,0,-1},
+{15029,7,1,-1},{14962,7,0,-1},{15032,7,0,-1},{15033,7,1,-1},{15036,7,0,-1},
+{15037,7,1,-1},{15041,7,0,-1},{15042,7,1,-1},{14975,7,1,-1},{15045,7,0,-1},
+{15046,7,1,-1},{15049,7,0,-1},{15050,7,1,-1},{15054,7,0,-1},{15055,7,1,-1},
+{15058,7,0,-1},{15059,7,1,-1},{15062,7,0,-1},{15063,7,1,-1},{15067,7,0,-1},
+{15068,7,1,-1},{15071,7,0,-1},{15072,7,1,-1},{15075,7,0,-1},{15076,7,1,-1},
+{15080,7,0,-1},{15081,7,1,-1},{15084,7,0,-1},{15085,7,1,-1},{15088,7,0,-1},
+{15089,7,1,-1},{15093,7,0,-1},{15094,7,1,-1},{15097,7,0,-1},{15098,7,1,-1},
+{15101,7,0,-1},{15102,7,1,-1},{15106,7,0,-1},{15107,7,1,-1},{15110,7,0,-1},
+{15111,7,1,-1},{15114,7,0,-1},{15115,7,1,-1},{15119,7,0,-1},{15120,7,1,-1},
+{15123,7,0,-1},{15124,7,1,-1},{15127,7,0,-1},{15128,7,1,-1},{15132,7,0,-1},
+{15133,7,1,-1},{15066,7,0,-1},{15136,7,0,-1},{15137,7,1,-1},{15140,7,0,-1},
+{15141,7,1,-1},{15145,7,0,-1},{15146,7,1,-1},{15079,7,1,-1},{15149,7,0,-1},
+{15150,7,1,-1},{15153,7,0,-1},{15154,7,1,-1},{15158,7,0,-1},{15159,7,1,-1},
+{15162,7,0,-1},{15163,7,1,-1},{15166,7,0,-1},{15167,7,1,-1},{15171,7,0,-1},
+{15172,7,1,-1},{15175,7,0,-1},{15176,7,1,-1},{15179,7,0,-1},{15180,7,1,-1},
+{15184,7,0,-1},{15185,7,1,-1},{15188,7,0,-1},{15189,7,1,-1},{15192,7,0,-1},
+{15193,7,1,-1},{15197,7,0,-1},{15198,7,1,-1},{15201,7,0,-1},{15202,7,1,-1},
+{15205,7,0,-1},{15206,7,1,-1},{15210,7,0,-1},{15211,7,1,-1},{15214,7,0,-1},
+{15215,7,1,-1},{15218,7,0,-1},{15219,7,1,-1},{15223,7,0,-1},{15224,7,1,-1},
+{15227,7,0,-1},{15228,7,1,-1},{15231,7,0,-1},{15232,7,1,-1},{15236,7,0,-1},
+{15237,7,1,-1},{15170,7,0,-1},{15240,7,0,-1},{15241,7,1,-1},{15244,7,0,-1},
+{15245,7,1,-1},{15249,7,0,-1},{15250,7,1,-1},{15183,7,1,-1},{15253,7,0,-1},
+{15254,7,1,-1},{15257,7,0,-1},{15258,7,1,-1},{15262,7,0,-1},{15263,7,1,-1},
+{15266,7,0,-1},{15267,7,1,-1},{15270,7,0,-1},{15271,7,1,-1},{15275,7,0,-1},
+{15276,7,1,-1},{15279,7,0,-1},{15280,7,1,-1},{15284,7,1,-1},{15288,7,0,-1},
+{15289,7,1,-1},{15292,7,0,-1},{15293,7,1,-1},{15296,7,0,-1},{15297,7,1,-1},
+{15301,7,0,-1},{15302,7,1,-1},{15305,7,0,-1},{15306,7,1,-1},{15309,7,0,-1},
+{15310,7,1,-1},{15314,7,0,-1},{15315,7,1,-1},{15318,7,0,-1},{15319,7,1,-1},
+{15322,7,0,-1},{15323,7,1,-1},{15327,7,0,-1},{15328,7,1,-1},{15331,7,0,-1},
+{15332,7,1,-1},{15336,7,1,-1},{15340,7,0,-1},{15341,7,1,-1},{15344,7,0,-1},
+{15345,7,1,-1},{15348,7,0,-1},{15349,7,1,-1},{15353,7,0,-1},{15354,7,1,-1},
+{15357,7,0,-1},{15361,7,0,-1},{15366,7,0,-1},{15370,7,0,-1},{15374,7,0,-1}};
diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am
new file mode 100644
index 00000000..f232c493
--- /dev/null
+++ b/tests/misc/Makefile.am
@@ -0,0 +1,11 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) $(LIBOSMOTRAU_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOABIS_LIBS) $(LIBOSMOTRAU_LIBS)
+noinst_PROGRAMS = misc_test
+EXTRA_DIST = misc_test.ok
+
+misc_test_SOURCES = misc_test.c $(srcdir)/../stubs.c
+misc_test_LDADD = $(top_builddir)/src/common/libbts.a \
+ $(LDADD)
diff --git a/tests/misc/misc_test.c b/tests/misc/misc_test.c
new file mode 100644
index 00000000..c4d3a595
--- /dev/null
+++ b/tests/misc/misc_test.c
@@ -0,0 +1,199 @@
+/* testing misc code */
+
+/* (C) 2011 by Holger Hans Peter Freyther
+ * (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+void *ctx = NULL;
+
+static const uint8_t ipa_rsl_connect[] = {
+ 0x00, 0x1c, 0xff, 0x10, 0x80, 0x00, 0x0a, 0x0d,
+ 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x70, 0x61, 0x63,
+ 0x63, 0x65, 0x73, 0x73, 0x00, 0xe0, 0x04, 0x00,
+ 0x00, 0xff, 0x85, 0x00, 0x81, 0x0b, 0xbb
+};
+
+static const uint8_t osmo_rsl_power[] = {
+ 0x00, 0x18, 0xff, 0x10, 0x80, 0x00, 0x07, 0x0c,
+ 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x73, 0x6d, 0x6f,
+ 0x63, 0x6f, 0x6d, 0x00, 0x44, 0x02, 0x00, 0x00,
+ 0xff, 0xfe, 0x04
+};
+
+static const uint8_t etsi_oml_opstart[] = {
+ 0x00, 0x09, 0xff, 0x80, 0x80, 0x00, 0x05, 0x74,
+ 0x00, 0xff, 0xff, 0xff
+};
+
+static void test_msg_utils_ipa(void)
+{
+ struct msgb *msg;
+ int rc, size;
+
+ printf("Testing IPA structure\n");
+
+ msg = msgb_alloc(sizeof(ipa_rsl_connect), "IPA test");
+ msg->l1h = msgb_put(msg, sizeof(ipa_rsl_connect));
+ memcpy(msg->l1h, ipa_rsl_connect, sizeof(ipa_rsl_connect));
+ rc = msg_verify_ipa_structure(msg);
+ OSMO_ASSERT(rc == 0);
+ msgb_free(msg);
+
+ /* test truncated messages and they should fail */
+ for (size = sizeof(ipa_rsl_connect) - 1; size >= 0; --size) {
+ msg = msgb_alloc(sizeof(ipa_rsl_connect) - 1, "IPA test");
+ msg->l1h = msgb_put(msg, size);
+ memcpy(msg->l1h, ipa_rsl_connect, size);
+ rc = msg_verify_ipa_structure(msg);
+ OSMO_ASSERT(rc == -1);
+ msgb_free(msg);
+ }
+
+ /* change the type of the message */
+ msg = msgb_alloc(sizeof(ipa_rsl_connect), "IPA test");
+ msg->l1h = msgb_put(msg, sizeof(ipa_rsl_connect));
+ memcpy(msg->l1h, ipa_rsl_connect, sizeof(ipa_rsl_connect));
+ msg->l1h[2] = 0x23;
+ rc = msg_verify_ipa_structure(msg);
+ OSMO_ASSERT(rc == 0);
+ msgb_free(msg);
+}
+
+static void test_oml_data(const uint8_t *data, const size_t len, const int exp)
+{
+ int rc;
+ struct msgb *msg;
+
+ msg = msgb_alloc(len, "IPA test");
+ msg->l2h = msgb_put(msg, len);
+ memcpy(msg->l2h, data, len);
+ rc = msg_verify_oml_structure(msg);
+ if (rc >= 0)
+ OSMO_ASSERT(msg->l3h > msg->l2h);
+ OSMO_ASSERT(rc == exp);
+ msgb_free(msg);
+}
+
+static void test_msg_utils_oml(void)
+{
+ static const size_t hh_size = sizeof(struct ipaccess_head);
+ int size;
+
+ printf("Testing OML structure\n");
+
+ /* test with IPA message */
+ printf(" Testing IPA messages.\n");
+ test_oml_data(ipa_rsl_connect + hh_size,
+ sizeof(ipa_rsl_connect) - hh_size,
+ OML_MSG_TYPE_IPA);
+
+ /* test truncated messages and they should fail */
+ for (size = sizeof(ipa_rsl_connect) - hh_size - 1; size >=0; --size)
+ test_oml_data(ipa_rsl_connect + hh_size, size, -1);
+
+ /* test with Osmo message */
+ printf(" Testing Osmo messages.\n");
+ test_oml_data(osmo_rsl_power + hh_size,
+ sizeof(osmo_rsl_power) - hh_size,
+ OML_MSG_TYPE_OSMO);
+ for (size = sizeof(osmo_rsl_power) - hh_size - 1; size >=0; --size)
+ test_oml_data(osmo_rsl_power + hh_size, size, -1);
+
+ /* test with plain ETSI message */
+ printf(" Testing ETSI messages.\n");
+ test_oml_data(etsi_oml_opstart + hh_size,
+ sizeof(etsi_oml_opstart) - hh_size,
+ OML_MSG_TYPE_ETSI);
+ for (size = sizeof(etsi_oml_opstart) - hh_size - 1; size >=0; --size)
+ test_oml_data(etsi_oml_opstart + hh_size, size, -1);
+}
+
+static void test_sacch_get(void)
+{
+ struct gsm_lchan lchan;
+ int i, off;
+
+ printf("Testing lchan_sacch_get\n");
+ memset(&lchan, 0, sizeof(lchan));
+
+ /* initialize the input. */
+ for (i = 1; i < _MAX_SYSINFO_TYPE; ++i) {
+ lchan.si.valid |= (1 << i);
+ memset(GSM_LCHAN_SI(&lchan, i), i, GSM_MACBLOCK_LEN);
+ }
+
+ /* It will start with '1' */
+ for (i = 1, off = 0; i <= 32; ++i) {
+ uint8_t *data = lchan_sacch_get(&lchan);
+ off = (off + 1) % _MAX_SYSINFO_TYPE;
+ if (off == 0)
+ off += 1;
+
+ //printf("i=%d (%%=%d) -> data[0]=%d\n", i, off, data[0]);
+ OSMO_ASSERT(data[0] == off);
+ }
+}
+
+static void test_bts_supports_cm(void)
+{
+ struct gsm_bts *bts;
+
+ bts = gsm_bts_alloc(ctx, 0);
+
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+
+ OSMO_ASSERT(bts_supports_cm
+ (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_V1) == 1);
+ OSMO_ASSERT(bts_supports_cm
+ (bts, GSM_PCHAN_TCH_H, GSM48_CMODE_SPEECH_V1) == 1);
+ OSMO_ASSERT(bts_supports_cm
+ (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_EFR) == 0);
+ OSMO_ASSERT(bts_supports_cm
+ (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_AMR) == 1);
+ OSMO_ASSERT(bts_supports_cm
+ (bts, GSM_PCHAN_TCH_H, GSM48_CMODE_SPEECH_AMR) == 1);
+
+ talloc_free(bts);
+}
+
+int main(int argc, char **argv)
+{
+ ctx = talloc_named_const(NULL, 0, "misc_test");
+
+ osmo_init_logging2(ctx, &bts_log_info);
+
+ test_sacch_get();
+ test_msg_utils_ipa();
+ test_msg_utils_oml();
+ test_bts_supports_cm();
+ return EXIT_SUCCESS;
+}
diff --git a/tests/misc/misc_test.ok b/tests/misc/misc_test.ok
new file mode 100644
index 00000000..a52ce5dd
--- /dev/null
+++ b/tests/misc/misc_test.ok
@@ -0,0 +1,6 @@
+Testing lchan_sacch_get
+Testing IPA structure
+Testing OML structure
+ Testing IPA messages.
+ Testing Osmo messages.
+ Testing ETSI messages.
diff --git a/tests/paging/Makefile.am b/tests/paging/Makefile.am
new file mode 100644
index 00000000..74d98265
--- /dev/null
+++ b/tests/paging/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS)
+noinst_PROGRAMS = paging_test
+EXTRA_DIST = paging_test.ok
+
+paging_test_SOURCES = paging_test.c $(srcdir)/../stubs.c
+paging_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/paging/paging_test.c b/tests/paging/paging_test.c
new file mode 100644
index 00000000..f112404a
--- /dev/null
+++ b/tests/paging/paging_test.c
@@ -0,0 +1,187 @@
+/* testing the paging code */
+
+/* (C) 2011 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/l1sap.h>
+
+#include <unistd.h>
+
+static struct gsm_bts *bts;
+
+static const uint8_t static_ilv[] = {
+ 0x08, 0x59, 0x51, 0x30, 0x99, 0x00, 0x00, 0x00, 0x19
+};
+
+#define ASSERT_TRUE(rc) \
+ if (!(rc)) { \
+ printf("Assert failed in %s:%d.\n", \
+ __FILE__, __LINE__); \
+ abort(); \
+ }
+
+static void test_paging_smoke(void)
+{
+ int rc;
+ uint8_t out_buf[GSM_MACBLOCK_LEN];
+ struct gsm_time g_time;
+ int is_empty = -1;
+ printf("Testing that paging messages expire.\n");
+
+ /* add paging entry */
+ rc = paging_add_identity(bts->paging_state, 0, static_ilv, 0);
+ ASSERT_TRUE(rc == 0);
+ ASSERT_TRUE(paging_queue_length(bts->paging_state) == 1);
+
+ /* generate messages */
+ g_time.fn = 0;
+ g_time.t1 = 0;
+ g_time.t2 = 0;
+ g_time.t3 = 6;
+ rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty);
+ ASSERT_TRUE(rc == 13);
+ ASSERT_TRUE(is_empty == 0);
+
+ ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0));
+ ASSERT_TRUE(paging_queue_length(bts->paging_state) == 0);
+
+ /* now test the empty queue */
+ g_time.fn = 0;
+ g_time.t1 = 0;
+ g_time.t2 = 0;
+ g_time.t3 = 6;
+ rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty);
+ ASSERT_TRUE(rc == 6);
+ ASSERT_TRUE(is_empty == 1);
+
+ /*
+ * TODO: test all the cases of different amount tmsi/imsi and check
+ * if we fill the slots in a optimal way.
+ */
+}
+
+static void test_paging_sleep(void)
+{
+ int rc;
+ uint8_t out_buf[GSM_MACBLOCK_LEN];
+ struct gsm_time g_time;
+ int is_empty = -1;
+ printf("Testing that paging messages expire with sleep.\n");
+
+ /* add paging entry */
+ rc = paging_add_identity(bts->paging_state, 0, static_ilv, 0);
+ ASSERT_TRUE(rc == 0);
+ ASSERT_TRUE(paging_queue_length(bts->paging_state) == 1);
+
+ /* sleep */
+ sleep(1);
+
+ /* generate messages */
+ g_time.fn = 0;
+ g_time.t1 = 0;
+ g_time.t2 = 0;
+ g_time.t3 = 6;
+ rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty);
+ ASSERT_TRUE(rc == 13);
+ ASSERT_TRUE(is_empty == 0);
+
+ ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0));
+ ASSERT_TRUE(paging_queue_length(bts->paging_state) == 0);
+}
+
+/* Set up a dummy trx with a valid setting for bs_ag_blks_res in SI3 */
+static struct gsm_bts_trx *test_is_ccch_for_agch_setup(uint8_t bs_ag_blks_res)
+{
+ static struct gsm_bts_trx trx;
+ static struct gsm_bts bts;
+ struct gsm48_system_information_type_3 si3 = { 0 };
+ si3.control_channel_desc.bs_ag_blks_res = bs_ag_blks_res;
+ trx.bts = &bts;
+ bts.si_valid |= 0x8;
+ memcpy(&bts.si_buf[SYSINFO_TYPE_3][0], &si3, sizeof(si3));
+ return &trx;
+}
+
+/* Walk through all possible settings for bs_ag_blks_res for two
+ * multiframe 51. The patterns shown in 3GPP TS 05.02 Clause 7
+ * Table 5 of 9 must occur. */
+static void test_is_ccch_for_agch(void)
+{
+ int is_ag_res;
+ int fn;
+ uint8_t bs_ag_blks_res;
+ struct gsm_bts_trx *trx;
+
+ printf("Fn: AGCH: (bs_ag_blks_res=[0:7]\n");
+ for (fn = 0; fn < 102; fn++) {
+ uint8_t fn51 = fn % 51;
+ /* Note: the formula that computes the CCCH block number for a
+ * given frame number is optimized to work on block boarders,
+ * for frame numbers that do not fall at the beginning of the
+ * related block this formula would produce wrong results, so
+ * we only check with frame numbers that mark the beginning
+ * of a new block. See also L1SAP_FN2CCCHBLOCK() in l1sap.h */
+
+ if (fn51 % 10 != 2 && fn51 % 10 != 6)
+ continue;
+
+ printf("%03u: ", fn);
+
+ if (fn51 == 2) {
+ printf(" . . . . . . . . (BCCH)\n");
+ continue;
+ }
+
+ /* Try allo possible settings for bs_ag_blks_res */
+ for (bs_ag_blks_res = 0; bs_ag_blks_res <= 7; bs_ag_blks_res++) {
+ trx = test_is_ccch_for_agch_setup(bs_ag_blks_res);
+ is_ag_res = is_ccch_for_agch(trx, fn);
+ printf(" %u", is_ag_res);
+ }
+ printf("\n");
+ }
+}
+
+int main(int argc, char **argv)
+{
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 0);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to open bts\n");
+ exit(1);
+ }
+
+ test_paging_smoke();
+ test_paging_sleep();
+ test_is_ccch_for_agch();
+ printf("Success\n");
+
+ return 0;
+}
+
diff --git a/tests/paging/paging_test.ok b/tests/paging/paging_test.ok
new file mode 100644
index 00000000..927fdf92
--- /dev/null
+++ b/tests/paging/paging_test.ok
@@ -0,0 +1,24 @@
+Testing that paging messages expire.
+Testing that paging messages expire with sleep.
+Fn: AGCH: (bs_ag_blks_res=[0:7]
+002: . . . . . . . . (BCCH)
+006: 0 1 1 1 1 1 1 1
+012: 0 0 1 1 1 1 1 1
+016: 0 0 0 1 1 1 1 1
+022: 0 0 0 0 1 1 1 1
+026: 0 0 0 0 0 1 1 1
+032: 0 0 0 0 0 0 1 1
+036: 0 0 0 0 0 0 0 1
+042: 0 0 0 0 0 0 0 0
+046: 0 0 0 0 0 0 0 0
+053: . . . . . . . . (BCCH)
+057: 0 1 1 1 1 1 1 1
+063: 0 0 1 1 1 1 1 1
+067: 0 0 0 1 1 1 1 1
+073: 0 0 0 0 1 1 1 1
+077: 0 0 0 0 0 1 1 1
+083: 0 0 0 0 0 0 1 1
+087: 0 0 0 0 0 0 0 1
+093: 0 0 0 0 0 0 0 0
+097: 0 0 0 0 0 0 0 0
+Success
diff --git a/tests/power/Makefile.am b/tests/power/Makefile.am
new file mode 100644
index 00000000..ac45f238
--- /dev/null
+++ b/tests/power/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS)
+
+noinst_PROGRAMS = power_test
+EXTRA_DIST = power_test.ok
+
+power_test_SOURCES = power_test.c $(srcdir)/../stubs.c
+power_test_LDADD = $(top_builddir)/src/common/libbts.a $(LIBOSMOABIS_LIBS) $(LDADD)
diff --git a/tests/power/power_test.c b/tests/power/power_test.c
new file mode 100644
index 00000000..a46a430c
--- /dev/null
+++ b/tests/power/power_test.c
@@ -0,0 +1,88 @@
+/*
+ * (C) 2013,2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/power_control.h>
+
+#include <stdio.h>
+
+static inline void apply_power_test(struct gsm_lchan *lchan, int rxlev, int exp_ret, uint8_t exp_current)
+{
+ int ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, rxlev);
+
+ printf("power control [%d]: MS current power %u\n", ret, lchan->ms_power_ctrl.current);
+ OSMO_ASSERT(ret == exp_ret);
+ OSMO_ASSERT(lchan->ms_power_ctrl.current == exp_current);
+}
+
+static void test_power_loop(void)
+{
+ struct gsm_bts bts;
+ struct gsm_bts_trx trx;
+ struct gsm_bts_trx_ts ts;
+ struct gsm_lchan *lchan;
+
+ memset(&bts, 0, sizeof(bts));
+ memset(&trx, 0, sizeof(trx));
+ memset(&ts, 0, sizeof(ts));
+
+ lchan = &ts.lchan[0];
+ lchan->ts = &ts;
+ ts.trx = &trx;
+ trx.bts = &bts;
+ bts.band = GSM_BAND_1800;
+ trx.ms_power_control = 1;
+ bts.ul_power_target = -75;
+
+ lchan->state = LCHAN_S_NONE;
+ lchan->ms_power_ctrl.current = ms_pwr_ctl_lvl(GSM_BAND_1800, 0);
+ OSMO_ASSERT(lchan->ms_power_ctrl.current == 15);
+
+ /* Simply clamping */
+ apply_power_test(lchan, -60, 0, 15);
+
+ /*
+ * Now 15 dB too little and we should power it up. Could be a
+ * power level of 7 or 8 for 15 dBm
+ */
+ apply_power_test(lchan, -90, 1, 7);
+
+ /* It should be clamped to level 0 and 30 dBm */
+ apply_power_test(lchan, -100, 1, 0);
+
+ /* Fix it and jump down */
+ lchan->ms_power_ctrl.fixed = 1;
+ apply_power_test(lchan, -60, 0, 0);
+
+ /* And leave it again */
+ lchan->ms_power_ctrl.fixed = 0;
+ apply_power_test(lchan, -40, 1, 15);
+}
+
+int main(int argc, char **argv)
+{
+ printf("Testing power loop...\n");
+
+ test_power_loop();
+
+ printf("Power loop test OK\n");
+
+ return 0;
+}
diff --git a/tests/power/power_test.ok b/tests/power/power_test.ok
new file mode 100644
index 00000000..cf0a38b4
--- /dev/null
+++ b/tests/power/power_test.ok
@@ -0,0 +1,7 @@
+Testing power loop...
+power control [0]: MS current power 15
+power control [1]: MS current power 7
+power control [1]: MS current power 0
+power control [0]: MS current power 0
+power control [1]: MS current power 15
+Power loop test OK
diff --git a/tests/stubs.c b/tests/stubs.c
new file mode 100644
index 00000000..7c64034b
--- /dev/null
+++ b/tests/stubs.c
@@ -0,0 +1,59 @@
+#include <osmo-bts/bts.h>
+
+struct femtol1_hdl;
+struct bts_model_set_dyn_pdch_data;
+
+/*
+ * Stubs to provide an empty bts model implementation for testing.
+ * If we ever want to re-define such a symbol we can make them weak
+ * here.
+ */
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{ return 0; }
+int bts_model_init(struct gsm_bts *bts)
+{ return 0; }
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{ return 0; }
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+ struct tlv_parsed *new_attr, int kind, void *obj)
+{ return 0; }
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{ return 0; }
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{ return 0; }
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+ struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+ void *obj)
+{ return 0; }
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{ return 0; }
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{ return 0; }
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{ return 0; }
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{ return 0; }
+
+int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power)
+{ return 0; }
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { return 0; }
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return 0; }
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{ return 0; }
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{ }
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{ return 0; }
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{ return; }
diff --git a/tests/sysmobts/Makefile.am b/tests/sysmobts/Makefile.am
new file mode 100644
index 00000000..0829ca52
--- /dev/null
+++ b/tests/sysmobts/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_srcdir)/src/osmo-bts-sysmo $(SYSMOBTS_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS)
+
+noinst_PROGRAMS = sysmobts_test
+EXTRA_DIST = sysmobts_test.ok
+
+sysmobts_test_SOURCES = sysmobts_test.c $(top_srcdir)/src/osmo-bts-sysmo/utils.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/l1_if.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/oml.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/l1_transp_hw.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/tch.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/calib_file.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/calib_fixup.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/misc/sysmobts_par.c \
+ $(top_srcdir)/src/osmo-bts-sysmo/eeprom.c
+sysmobts_test_LDADD = $(top_builddir)/src/common/libbts.a $(LIBOSMOABIS_LIBS) $(LDADD)
diff --git a/tests/sysmobts/sysmobts_test.c b/tests/sysmobts/sysmobts_test.c
new file mode 100644
index 00000000..4b01ed7a
--- /dev/null
+++ b/tests/sysmobts/sysmobts_test.c
@@ -0,0 +1,210 @@
+/*
+ * (C) 2013,2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/power_control.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+#include <sysmocom/femtobts/gsml1prim.h>
+
+#include <stdio.h>
+
+static int direct_map[][3] = {
+ { GSM_BAND_850, GsmL1_FreqBand_850, 128 },
+ { GSM_BAND_900, GsmL1_FreqBand_900, 1 },
+ { GSM_BAND_1800, GsmL1_FreqBand_1800, 600 },
+ { GSM_BAND_1900, GsmL1_FreqBand_1900, 600 },
+};
+
+static int dcs_to_dcs[][3] = {
+ { GSM_BAND_900, GsmL1_FreqBand_1800, 600 },
+ { GSM_BAND_1800, GsmL1_FreqBand_900, 1 },
+ { GSM_BAND_900, -1, 438 },
+};
+
+static int pcs_to_pcs[][3] = {
+ { GSM_BAND_850, GsmL1_FreqBand_1900, 512 },
+ { GSM_BAND_1900, GsmL1_FreqBand_850, 128 },
+ { GSM_BAND_900, -1, 438 },
+};
+
+static void test_sysmobts_auto_band(void)
+{
+ struct gsm_bts bts;
+ struct gsm_bts_trx trx;
+ struct femtol1_hdl hdl;
+ int i;
+
+ memset(&bts, 0, sizeof(bts));
+ memset(&trx, 0, sizeof(trx));
+ memset(&hdl, 0, sizeof(hdl));
+ trx.bts = &bts;
+ trx.role_bts.l1h = &hdl;
+
+ /* claim to support all hw_info's */
+ hdl.hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 |
+ GSM_BAND_1800 | GSM_BAND_1900;
+
+ /* start with the current option */
+ printf("Testing the no auto-band mapping.\n");
+ for (i = 0; i < ARRAY_SIZE(direct_map); ++i) {
+ uint16_t arfcn;
+ int res;
+
+ bts.auto_band = 0;
+ bts.band = direct_map[i][0];
+ arfcn = direct_map[i][2];
+ res = sysmobts_select_femto_band(&trx, arfcn);
+ printf("No auto-band band(%d) arfcn(%u) want(%d) got(%d)\n",
+ bts.band, arfcn, direct_map[i][1], res);
+ OSMO_ASSERT(res == direct_map[i][1]);
+ }
+
+ /* Check if auto-band does not break things */
+ printf("Checking the mapping with auto-band.\n");
+ for (i = 0; i < ARRAY_SIZE(direct_map); ++i) {
+ uint16_t arfcn;
+ int res;
+
+ bts.auto_band = 1;
+ bts.band = direct_map[i][0];
+ arfcn = direct_map[i][2];
+ res = sysmobts_select_femto_band(&trx, arfcn);
+ printf("Auto-band band(%d) arfcn(%u) want(%d) got(%d)\n",
+ bts.band, arfcn, direct_map[i][1], res);
+ OSMO_ASSERT(res == direct_map[i][1]);
+ }
+
+ /* Check DCS to DCS change */
+ printf("Checking DCS to DCS\n");
+ for (i = 0; i < ARRAY_SIZE(dcs_to_dcs); ++i) {
+ uint16_t arfcn;
+ int res;
+
+ bts.auto_band = 1;
+ bts.band = dcs_to_dcs[i][0];
+ arfcn = dcs_to_dcs[i][2];
+ res = sysmobts_select_femto_band(&trx, arfcn);
+ printf("DCS to DCS band(%d) arfcn(%u) want(%d) got(%d)\n",
+ bts.band, arfcn, dcs_to_dcs[i][1], res);
+ OSMO_ASSERT(res == dcs_to_dcs[i][1]);
+ }
+
+ /* Check for a PCS to PCS change */
+ printf("Checking PCS to PCS\n");
+ for (i = 0; i < ARRAY_SIZE(pcs_to_pcs); ++i) {
+ uint16_t arfcn;
+ int res;
+
+ bts.auto_band = 1;
+ bts.band = pcs_to_pcs[i][0];
+ arfcn = pcs_to_pcs[i][2];
+ res = sysmobts_select_femto_band(&trx, arfcn);
+ printf("PCS to PCS band(%d) arfcn(%u) want(%d) got(%d)\n",
+ bts.band, arfcn, pcs_to_pcs[i][1], res);
+ OSMO_ASSERT(res == pcs_to_pcs[i][1]);
+ }
+}
+
+static void test_sysmobts_cipher(void)
+{
+ static const uint8_t cipher_cmd[] = {
+ 0x03, 0x00, 0x0d, 0x06, 0x35, 0x11, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b };
+ static const uint8_t too_early_classmark[] = {
+ 0x01, 0x00, 0x4d, 0x06, 0x16, 0x03, 0x30, 0x18,
+ 0xa2, 0x20, 0x0b, 0x60, 0x14, 0x4c, 0xa7, 0x7b,
+ 0x29, 0x11, 0xdc, 0x40, 0x04, 0x00, 0x2b };
+ static const uint8_t first_ciphered_cipher_cmpl[] = {
+ 0x01, 0x30, 0x4d, 0x06, 0x16, 0x03, 0x30, 0x18,
+ 0xa2, 0x20, 0x0b, 0x60, 0x14, 0x4c, 0xa7, 0x7b,
+ 0x29, 0x11, 0xdc, 0x40, 0x04, 0x00, 0x2b };
+
+ struct gsm_lchan lchan;
+ struct femtol1_hdl fl1h;
+ struct msgb *msg;
+ GsmL1_MsgUnitParam_t unit;
+ int rc;
+
+ memset(&lchan, 0, sizeof(lchan));
+ memset(&fl1h, 0, sizeof(fl1h));
+
+ /* Inject the cipher mode command */
+ msg = msgb_alloc_headroom(128, 64, "ciphering mode command");
+ lchan.ciph_state = LCHAN_CIPH_NONE;
+ memcpy(msgb_put(msg, ARRAY_SIZE(cipher_cmd)), cipher_cmd, ARRAY_SIZE(cipher_cmd));
+ rc = bts_check_for_ciph_cmd(&fl1h, msg, &lchan);
+ OSMO_ASSERT(rc == 1);
+ OSMO_ASSERT(lchan.ciph_state == LCHAN_CIPH_RX_REQ);
+ OSMO_ASSERT(lchan.ciph_ns == 1);
+ msgb_free(msg);
+
+ /* Move to the confirmed state */
+ lchan.ciph_state = LCHAN_CIPH_RX_CONF;
+
+ /* Handle message sent before ciphering was received */
+ memcpy(&unit.u8Buffer[0], too_early_classmark, ARRAY_SIZE(too_early_classmark));
+ unit.u8Size = ARRAY_SIZE(too_early_classmark);
+ rc = bts_check_for_first_ciphrd(&lchan, unit.u8Buffer, unit.u8Size);
+ OSMO_ASSERT(rc == 0);
+ OSMO_ASSERT(lchan.ciph_state == LCHAN_CIPH_RX_CONF);
+
+ /* Now send the first ciphered message */
+ memcpy(&unit.u8Buffer[0], first_ciphered_cipher_cmpl, ARRAY_SIZE(first_ciphered_cipher_cmpl));
+ unit.u8Size = ARRAY_SIZE(first_ciphered_cipher_cmpl);
+ rc = bts_check_for_first_ciphrd(&lchan, unit.u8Buffer, unit.u8Size);
+ OSMO_ASSERT(rc == 1);
+ /* we cannot test for lchan.ciph_state == * LCHAN_CIPH_RX_CONF_TX_REQ, as
+ * this happens asynchronously on the other side of the l1sap queue */
+}
+
+int main(int argc, char **argv)
+{
+ printf("Testing sysmobts routines\n");
+ test_sysmobts_auto_band();
+ test_sysmobts_cipher();
+
+ return 0;
+}
+
+
+/*
+ * some local stubs. We need to pull in a lot more code and can't
+ * use the generic stubs unless we make all of them weak
+ */
+void bts_update_status(enum bts_global_status which, int on)
+{}
+
+int bts_model_init(struct gsm_bts *bts)
+{ return 0; }
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{ return 0; }
+int bts_model_oml_estab(struct gsm_bts *bts)
+{ return 0; }
+void bts_model_abis_close(struct gsm_bts *bts)
+{ }
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{ }
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{ }
diff --git a/tests/sysmobts/sysmobts_test.ok b/tests/sysmobts/sysmobts_test.ok
new file mode 100644
index 00000000..1f534172
--- /dev/null
+++ b/tests/sysmobts/sysmobts_test.ok
@@ -0,0 +1,19 @@
+Testing sysmobts routines
+Testing the no auto-band mapping.
+No auto-band band(1) arfcn(128) want(0) got(0)
+No auto-band band(2) arfcn(1) want(1) got(1)
+No auto-band band(4) arfcn(600) want(2) got(2)
+No auto-band band(8) arfcn(600) want(3) got(3)
+Checking the mapping with auto-band.
+Auto-band band(1) arfcn(128) want(0) got(0)
+Auto-band band(2) arfcn(1) want(1) got(1)
+Auto-band band(4) arfcn(600) want(2) got(2)
+Auto-band band(8) arfcn(600) want(3) got(3)
+Checking DCS to DCS
+DCS to DCS band(2) arfcn(600) want(2) got(2)
+DCS to DCS band(4) arfcn(1) want(1) got(1)
+DCS to DCS band(2) arfcn(438) want(-1) got(-1)
+Checking PCS to PCS
+PCS to PCS band(1) arfcn(512) want(3) got(3)
+PCS to PCS band(8) arfcn(128) want(0) got(0)
+PCS to PCS band(2) arfcn(438) want(-1) got(-1)
diff --git a/tests/testsuite.at b/tests/testsuite.at
new file mode 100644
index 00000000..2d1cefd3
--- /dev/null
+++ b/tests/testsuite.at
@@ -0,0 +1,51 @@
+AT_INIT
+AT_BANNER([Regression tests.])
+
+AT_SETUP([paging])
+AT_KEYWORDS([paging])
+cat $abs_srcdir/paging/paging_test.ok > expout
+AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/paging/paging_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([agch])
+AT_KEYWORDS([agch])
+cat $abs_srcdir/agch/agch_test.ok > expout
+AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/agch/agch_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([cipher])
+AT_KEYWORDS([cipher])
+cat $abs_srcdir/cipher/cipher_test.ok > expout
+AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/cipher/cipher_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([misc])
+AT_KEYWORDS([misc])
+cat $abs_srcdir/misc/misc_test.ok > expout
+AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/misc/misc_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([power])
+AT_KEYWORDS([power])
+cat $abs_srcdir/power/power_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/power/power_test], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([tx_power])
+AT_KEYWORDS([tx_power])
+cat $abs_srcdir/tx_power/tx_power_test.ok > expout
+cat $abs_srcdir/tx_power/tx_power_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/tx_power/tx_power_test], [], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([meas])
+AT_KEYWORDS([meas])
+cat $abs_srcdir/meas/meas_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/meas/meas_test], [], [expout], [ignore])
+AT_CLEANUP
diff --git a/tests/tx_power/Makefile.am b/tests/tx_power/Makefile.am
new file mode 100644
index 00000000..cd7ccc2f
--- /dev/null
+++ b/tests/tx_power/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS)
+noinst_PROGRAMS = tx_power_test
+EXTRA_DIST = tx_power_test.ok tx_power_test.err
+
+tx_power_test_SOURCES = tx_power_test.c $(srcdir)/../stubs.c
+tx_power_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/tests/tx_power/tx_power_test.c b/tests/tx_power/tx_power_test.c
new file mode 100644
index 00000000..ad3f68ce
--- /dev/null
+++ b/tests/tx_power/tx_power_test.c
@@ -0,0 +1,247 @@
+/* Test cases for tx_power.c Transmit Power Computation */
+
+/* (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program 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.
+ *
+ * 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 Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/tx_power.h>
+
+
+static const struct trx_power_params tpp_1002 = {
+ .trx_p_max_out_mdBm = to_mdB(23),
+ .p_total_tgt_mdBm = to_mdB(23),
+ .p_total_cur_mdBm = 0,
+ .thermal_attenuation_mdB = 0,
+ .user_gain_mdB = 0,
+ .pa = {
+ .nominal_gain_mdB = 0,
+ },
+ .user_pa = {
+ .nominal_gain_mdB = 0,
+ },
+ .ramp = {
+ .max_initial_pout_mdBm = to_mdB(23),
+ .step_size_mdB = to_mdB(2),
+ .step_interval_sec = 1,
+ },
+};
+
+static const struct trx_power_params tpp_1020 = {
+ .trx_p_max_out_mdBm = to_mdB(23),
+ .p_total_tgt_mdBm = to_mdB(33),
+ .p_total_cur_mdBm = 0,
+ .thermal_attenuation_mdB = 0,
+ .user_gain_mdB = 0,
+ .pa = {
+ .nominal_gain_mdB = to_mdB(10),
+ },
+ .user_pa = {
+ .nominal_gain_mdB = 0,
+ },
+ .ramp = {
+ .max_initial_pout_mdBm = to_mdB(0),
+ .step_size_mdB = to_mdB(2),
+ .step_interval_sec = 1,
+ },
+};
+
+static const struct trx_power_params tpp_1100 = {
+ .trx_p_max_out_mdBm = to_mdB(23),
+ .p_total_tgt_mdBm = to_mdB(40),
+ .p_total_cur_mdBm = 0,
+ .thermal_attenuation_mdB = 0,
+ .user_gain_mdB = 0,
+ .pa = {
+ .nominal_gain_mdB = to_mdB(17),
+ },
+ .user_pa = {
+ .nominal_gain_mdB = 0,
+ },
+ .ramp = {
+ .max_initial_pout_mdBm = to_mdB(0),
+ .step_size_mdB = to_mdB(2),
+ .step_interval_sec = 1,
+ },
+};
+
+static const struct trx_power_params tpp_2050 = {
+ .trx_p_max_out_mdBm = to_mdB(37),
+ .p_total_tgt_mdBm = to_mdB(37),
+ .p_total_cur_mdBm = 0,
+ .thermal_attenuation_mdB = 0,
+ .user_gain_mdB = 0,
+ .pa = {
+ .nominal_gain_mdB = 0,
+ },
+ .user_pa = {
+ .nominal_gain_mdB = 0,
+ },
+ .ramp = {
+ .max_initial_pout_mdBm = to_mdB(0),
+ .step_size_mdB = to_mdB(2),
+ .step_interval_sec = 1,
+ },
+};
+
+static void test_sbts1002(struct gsm_bts_trx *trx)
+{
+ printf("Testing tx_power calculation for sysmoBTS 1002\n");
+ trx->power_params = tpp_1002;
+ trx->max_power_red = 0;
+ OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(23));
+ OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(23));
+ /* at max_power_red = 0, we expect full 23dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(23));
+ trx->max_power_red = 2;
+ /* at max_power_red = 2, we expect 21dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(21));
+ /* at 1 step (of 2dB), we expect full 23-2-2=19 dBm */
+ OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(19));
+ /* at 2 steps (= 4dB), we expect 23-2-4=17*/
+ OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17));
+}
+
+static void test_sbts1020(struct gsm_bts_trx *trx)
+{
+ printf("Testing tx_power calculation for sysmoBTS 1020\n");
+ trx->power_params = tpp_1020;
+ trx->max_power_red = 0;
+ OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(-10));
+ OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(33));
+ /* at max_power_red = 0, we expect full 33dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(33));
+ trx->max_power_red = 2;
+ /* at max_power_red = 2, we expect 31dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(31));
+ /* at 1 step (of 2dB), we expect full 33-2-2=29 dBm */
+ OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(29));
+ /* at 2 steps (= 4dB), we expect 33-2-4-10=17*/
+ OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17));
+}
+
+
+static void test_sbts1100(struct gsm_bts_trx *trx)
+{
+ printf("Testing tx_power calculation for sysmoBTS 1100\n");
+ trx->power_params = tpp_1100;
+ trx->max_power_red = 0;
+ OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(-17));
+ OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(40));
+ /* at max_power_red = 0, we expect full 33dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(40));
+ trx->max_power_red = 2;
+ /* at max_power_red = 2, we expect 38dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(38));
+ /* at 1 step (of 2dB), we expect full 40-2-2=36 dBm */
+ OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(36));
+ /* at 2 steps (= 4dB), we expect 40-2-4-17=17*/
+ OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17));
+}
+
+static void test_sbts2050(struct gsm_bts_trx *trx)
+{
+ printf("Testing tx_power calculation for sysmoBTS 2050\n");
+ trx->power_params = tpp_2050;
+ trx->max_power_red = 0;
+ OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(0));
+ OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(37));
+ /* at max_power_red = 0, we expect full 37dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(37));
+ trx->max_power_red = 2;
+ /* at max_power_red = 2, we expect 35dBm */
+ OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(35));
+ /* at 1 step (of 2dB), we expect full 37-2-2=33 dBm */
+ OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(33));
+ /* at 2 steps (= 4dB), we expect 37-2-4=31dBm */
+ OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(31));
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ printf("CHANGE_POWER(%d)\n", p_trxout_mdBm);
+
+ if (tpp->ramp.attenuation_mdB == 0)
+ exit(0);
+
+ power_trx_change_compl(trx, p_trxout_mdBm);
+ return 0;
+}
+
+static void test_power_ramp(struct gsm_bts_trx *trx, int dBm)
+{
+ printf("Testing tx_power ramping for sysmoBTS 1020\n");
+ trx->power_params = tpp_1020;
+ trx->max_power_red = 0;
+
+ power_ramp_start(trx, to_mdB(dBm), 0);
+}
+
+int main(int argc, char **argv)
+{
+ static struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ void *tall_bts_ctx;
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 0);
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+ osmo_stderr_target->categories[DL1C].loglevel = LOGL_DEBUG;
+ log_set_print_filename(osmo_stderr_target, 0);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (!bts) {
+ fprintf(stderr, "Failed to create BTS structure\n");
+ exit(1);
+ }
+ trx = gsm_bts_trx_alloc(bts);
+ if (!trx) {
+ fprintf(stderr, "Failed to TRX structure\n");
+ exit(1);
+ }
+
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to to open bts\n");
+ exit(1);
+ }
+
+ test_sbts1002(trx);
+ test_sbts1020(trx);
+ test_sbts1100(trx);
+ test_sbts2050(trx);
+
+ /* test error case / excess power (40 dBm is too much) */
+ test_power_ramp(trx, 40);
+ /* test actaul ramping to full 33 dBm */
+ test_power_ramp(trx, 33);
+
+ while (1) {
+ osmo_select_main(0);
+ }
+}
diff --git a/tests/tx_power/tx_power_test.err b/tests/tx_power/tx_power_test.err
new file mode 100644
index 00000000..bf33b424
--- /dev/null
+++ b/tests/tx_power/tx_power_test.err
@@ -0,0 +1,38 @@
+power_ramp_start(cur=0, tgt=40000)
+Asked to ramp power up to 40000 mdBm, which exceeds P_max_out (33000)
+power_ramp_start(cur=0, tgt=33000)
+ramp_timer_cb(cur_pout=2000, tgt_pout=33000, ramp_att=31000, therm_att=0, user_gain=0)
+ramping TRX board output power to -8000 mdBm.
+ramp_timer_cb(cur_pout=4000, tgt_pout=33000, ramp_att=29000, therm_att=0, user_gain=0)
+ramping TRX board output power to -6000 mdBm.
+ramp_timer_cb(cur_pout=6000, tgt_pout=33000, ramp_att=27000, therm_att=0, user_gain=0)
+ramping TRX board output power to -4000 mdBm.
+ramp_timer_cb(cur_pout=8000, tgt_pout=33000, ramp_att=25000, therm_att=0, user_gain=0)
+ramping TRX board output power to -2000 mdBm.
+ramp_timer_cb(cur_pout=10000, tgt_pout=33000, ramp_att=23000, therm_att=0, user_gain=0)
+ramping TRX board output power to 0 mdBm.
+ramp_timer_cb(cur_pout=12000, tgt_pout=33000, ramp_att=21000, therm_att=0, user_gain=0)
+ramping TRX board output power to 2000 mdBm.
+ramp_timer_cb(cur_pout=14000, tgt_pout=33000, ramp_att=19000, therm_att=0, user_gain=0)
+ramping TRX board output power to 4000 mdBm.
+ramp_timer_cb(cur_pout=16000, tgt_pout=33000, ramp_att=17000, therm_att=0, user_gain=0)
+ramping TRX board output power to 6000 mdBm.
+ramp_timer_cb(cur_pout=18000, tgt_pout=33000, ramp_att=15000, therm_att=0, user_gain=0)
+ramping TRX board output power to 8000 mdBm.
+ramp_timer_cb(cur_pout=20000, tgt_pout=33000, ramp_att=13000, therm_att=0, user_gain=0)
+ramping TRX board output power to 10000 mdBm.
+ramp_timer_cb(cur_pout=22000, tgt_pout=33000, ramp_att=11000, therm_att=0, user_gain=0)
+ramping TRX board output power to 12000 mdBm.
+ramp_timer_cb(cur_pout=24000, tgt_pout=33000, ramp_att=9000, therm_att=0, user_gain=0)
+ramping TRX board output power to 14000 mdBm.
+ramp_timer_cb(cur_pout=26000, tgt_pout=33000, ramp_att=7000, therm_att=0, user_gain=0)
+ramping TRX board output power to 16000 mdBm.
+ramp_timer_cb(cur_pout=28000, tgt_pout=33000, ramp_att=5000, therm_att=0, user_gain=0)
+ramping TRX board output power to 18000 mdBm.
+ramp_timer_cb(cur_pout=30000, tgt_pout=33000, ramp_att=3000, therm_att=0, user_gain=0)
+ramping TRX board output power to 20000 mdBm.
+ramp_timer_cb(cur_pout=32000, tgt_pout=33000, ramp_att=1000, therm_att=0, user_gain=0)
+ramping TRX board output power to 22000 mdBm.
+ramp_timer_cb(cur_pout=33000, tgt_pout=33000, ramp_att=0, therm_att=0, user_gain=0)
+ramping TRX board output power to 23000 mdBm.
+ \ No newline at end of file
diff --git a/tests/tx_power/tx_power_test.ok b/tests/tx_power/tx_power_test.ok
new file mode 100644
index 00000000..ceb88ab4
--- /dev/null
+++ b/tests/tx_power/tx_power_test.ok
@@ -0,0 +1,23 @@
+Testing tx_power calculation for sysmoBTS 1002
+Testing tx_power calculation for sysmoBTS 1020
+Testing tx_power calculation for sysmoBTS 1100
+Testing tx_power calculation for sysmoBTS 2050
+Testing tx_power ramping for sysmoBTS 1020
+Testing tx_power ramping for sysmoBTS 1020
+CHANGE_POWER(-8000)
+CHANGE_POWER(-6000)
+CHANGE_POWER(-4000)
+CHANGE_POWER(-2000)
+CHANGE_POWER(0)
+CHANGE_POWER(2000)
+CHANGE_POWER(4000)
+CHANGE_POWER(6000)
+CHANGE_POWER(8000)
+CHANGE_POWER(10000)
+CHANGE_POWER(12000)
+CHANGE_POWER(14000)
+CHANGE_POWER(16000)
+CHANGE_POWER(18000)
+CHANGE_POWER(20000)
+CHANGE_POWER(22000)
+CHANGE_POWER(23000)