aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/common/Makefile.am88
-rw-r--r--src/common/abis.c436
-rw-r--r--src/common/abis_osmo.c135
-rw-r--r--src/common/amr.c142
-rw-r--r--src/common/asci.c211
-rw-r--r--src/common/bts.c718
-rw-r--r--src/common/bts_ctrl_commands.c37
-rw-r--r--src/common/bts_ctrl_lookup.c9
-rw-r--r--src/common/bts_shutdown_fsm.c283
-rw-r--r--src/common/bts_sm.c95
-rw-r--r--src/common/bts_trx.c256
-rw-r--r--src/common/cbch.c42
-rw-r--r--src/common/csd_v110.c188
-rw-r--r--src/common/dtx_dl_amr_fsm.c2
-rw-r--r--src/common/gsm_data.c348
-rw-r--r--src/common/gsm_data_shared.c831
-rw-r--r--src/common/handover.c11
-rw-r--r--src/common/l1sap.c1706
-rw-r--r--src/common/lchan.c646
-rw-r--r--src/common/load_indication.c15
-rw-r--r--src/common/logging.c80
-rw-r--r--src/common/main.c219
-rw-r--r--src/common/measurement.c577
-rw-r--r--src/common/msg_utils.c21
-rw-r--r--src/common/nm_bb_transc_fsm.c325
-rw-r--r--src/common/nm_bts_fsm.c230
-rw-r--r--src/common/nm_bts_sm_fsm.c209
-rw-r--r--src/common/nm_channel_fsm.c317
-rw-r--r--src/common/nm_common_fsm.c45
-rw-r--r--src/common/nm_gprs_cell_fsm.c260
-rw-r--r--src/common/nm_gprs_nse_fsm.c280
-rw-r--r--src/common/nm_gprs_nsvc_fsm.c259
-rw-r--r--src/common/nm_radio_carrier_fsm.c285
-rw-r--r--src/common/notification.c256
-rw-r--r--src/common/oml.c1325
-rw-r--r--src/common/osmux.c545
-rw-r--r--src/common/paging.c388
-rw-r--r--src/common/pcu_sock.c712
-rw-r--r--src/common/phy_link.c81
-rw-r--r--src/common/power_control.c568
-rw-r--r--src/common/probes.d2
-rw-r--r--src/common/rsl.c2032
-rw-r--r--src/common/rtp_input_preen.c151
-rw-r--r--src/common/scheduler.c1031
-rw-r--r--src/common/scheduler_mframe.c6
-rw-r--r--src/common/sysinfo.c119
-rw-r--r--src/common/ta_control.c102
-rw-r--r--src/common/tx_power.c105
-rw-r--r--src/common/vty.c1828
-rw-r--r--src/osmo-bts-lc15/Makefile.am102
-rw-r--r--src/osmo-bts-lc15/calib_file.c (renamed from src/osmo-bts-litecell15/calib_file.c)4
-rw-r--r--src/osmo-bts-lc15/hw_misc.c (renamed from src/osmo-bts-litecell15/hw_misc.c)2
-rw-r--r--src/osmo-bts-lc15/hw_misc.h (renamed from src/osmo-bts-litecell15/hw_misc.h)0
-rw-r--r--src/osmo-bts-lc15/l1_if.c (renamed from src/osmo-bts-litecell15/l1_if.c)259
-rw-r--r--src/osmo-bts-lc15/l1_if.h (renamed from src/osmo-bts-litecell15/l1_if.h)21
-rw-r--r--src/osmo-bts-lc15/l1_transp.h (renamed from src/osmo-bts-litecell15/l1_transp.h)0
-rw-r--r--src/osmo-bts-lc15/l1_transp_hw.c (renamed from src/osmo-bts-litecell15/l1_transp_hw.c)28
-rw-r--r--src/osmo-bts-lc15/lc15bts.c (renamed from src/osmo-bts-litecell15/lc15bts.c)23
-rw-r--r--src/osmo-bts-lc15/lc15bts.h101
-rw-r--r--src/osmo-bts-lc15/lc15bts_vty.c (renamed from src/osmo-bts-litecell15/lc15bts_vty.c)249
-rw-r--r--src/osmo-bts-lc15/main.c (renamed from src/osmo-bts-litecell15/main.c)73
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_bid.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_bid.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_bid.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_bid.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_bts.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_bts.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_bts.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_bts.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_clock.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_clock.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_clock.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_clock.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_led.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_led.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_led.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_led.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_mgr.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_mgr.c)33
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_mgr.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_mgr.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c)49
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_misc.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_misc.c)4
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_misc.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_misc.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_nl.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_nl.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_nl.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_nl.h)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_par.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_par.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_par.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_par.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_power.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_power.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_power.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_power.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_swd.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_swd.c)4
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_swd.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_swd.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_temp.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_temp.c)2
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_temp.h (renamed from src/osmo-bts-litecell15/misc/lc15bts_temp.h)0
-rw-r--r--src/osmo-bts-lc15/misc/lc15bts_util.c (renamed from src/osmo-bts-litecell15/misc/lc15bts_util.c)2
-rw-r--r--src/osmo-bts-lc15/oml.c (renamed from src/osmo-bts-litecell15/oml.c)495
-rw-r--r--src/osmo-bts-lc15/tch.c (renamed from src/osmo-bts-litecell15/tch.c)60
-rw-r--r--src/osmo-bts-lc15/utils.c (renamed from src/osmo-bts-litecell15/utils.c)2
-rw-r--r--src/osmo-bts-lc15/utils.h (renamed from src/osmo-bts-litecell15/utils.h)0
-rw-r--r--src/osmo-bts-litecell15/Makefile.am38
-rw-r--r--src/osmo-bts-litecell15/lc15bts.h64
-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-oc2g/Makefile.am100
-rw-r--r--src/osmo-bts-oc2g/calib_file.c4
-rw-r--r--src/osmo-bts-oc2g/hw_misc.c2
-rw-r--r--src/osmo-bts-oc2g/l1_if.c155
-rw-r--r--src/osmo-bts-oc2g/l1_if.h11
-rw-r--r--src/osmo-bts-oc2g/l1_transp_hw.c28
-rw-r--r--src/osmo-bts-oc2g/main.c72
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.c33
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c15
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c45
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.c4
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.h2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.c4
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.c2
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_util.c2
-rw-r--r--src/osmo-bts-oc2g/oc2gbts.c2
-rw-r--r--src/osmo-bts-oc2g/oc2gbts.h22
-rw-r--r--src/osmo-bts-oc2g/oc2gbts_vty.c96
-rw-r--r--src/osmo-bts-oc2g/oml.c359
-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.c68
-rw-r--r--src/osmo-bts-oc2g/utils.c2
-rw-r--r--src/osmo-bts-octphy/Makefile.am36
-rw-r--r--src/osmo-bts-octphy/l1_if.c146
-rw-r--r--src/osmo-bts-octphy/l1_oml.c176
-rw-r--r--src/osmo-bts-octphy/l1_oml.h2
-rw-r--r--src/osmo-bts-octphy/l1_tch.c29
-rw-r--r--src/osmo-bts-octphy/l1_utils.h10
-rw-r--r--src/osmo-bts-octphy/main.c3
-rw-r--r--src/osmo-bts-octphy/octphy_hw_api.c2
-rw-r--r--src/osmo-bts-octphy/octphy_vty.c16
-rw-r--r--src/osmo-bts-octphy/octpkt.c2
-rw-r--r--src/osmo-bts-omldummy/Makefile.am26
-rw-r--r--src/osmo-bts-omldummy/bts_model.c66
-rw-r--r--src/osmo-bts-omldummy/main.c133
-rw-r--r--src/osmo-bts-sysmo/Makefile.am74
-rw-r--r--src/osmo-bts-sysmo/calib_file.c2
-rw-r--r--src/osmo-bts-sysmo/eeprom.c24
-rw-r--r--src/osmo-bts-sysmo/femtobts.c2
-rw-r--r--src/osmo-bts-sysmo/femtobts.h30
-rw-r--r--src/osmo-bts-sysmo/hw_misc.c2
-rw-r--r--src/osmo-bts-sysmo/l1_fwd_main.c6
-rw-r--r--src/osmo-bts-sysmo/l1_if.c421
-rw-r--r--src/osmo-bts-sysmo/l1_if.h7
-rw-r--r--src/osmo-bts-sysmo/l1_transp_fwd.c7
-rw-r--r--src/osmo-bts-sysmo/l1_transp_hw.c28
-rw-r--r--src/osmo-bts-sysmo/main.c68
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-layer1.c2
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_eeprom.h1
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr.c33
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c2
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c20
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c2
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c4
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c28
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_misc.c8
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_nl.c2
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_nl.h2
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_par.c4
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_util.c2
-rw-r--r--src/osmo-bts-sysmo/oml.c375
-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.c10
-rw-r--r--src/osmo-bts-sysmo/sysmobts_vty.c66
-rw-r--r--src/osmo-bts-sysmo/tch.c92
-rw-r--r--src/osmo-bts-sysmo/utils.c2
-rw-r--r--src/osmo-bts-trx/Makefile.am77
-rw-r--r--src/osmo-bts-trx/amr_loop.c107
-rw-r--r--src/osmo-bts-trx/amr_loop.h16
-rw-r--r--src/osmo-bts-trx/l1_if.c763
-rw-r--r--src/osmo-bts-trx/l1_if.h136
-rw-r--r--src/osmo-bts-trx/loops.c322
-rw-r--r--src/osmo-bts-trx/loops.h27
-rw-r--r--src/osmo-bts-trx/main.c125
-rw-r--r--src/osmo-bts-trx/probes.d7
-rw-r--r--src/osmo-bts-trx/sched_lchan_fcch_sch.c89
-rw-r--r--src/osmo-bts-trx/sched_lchan_pdtch.c221
-rw-r--r--src/osmo-bts-trx/sched_lchan_rach.c214
-rw-r--r--src/osmo-bts-trx/sched_lchan_tchf.c677
-rw-r--r--src/osmo-bts-trx/sched_lchan_tchh.c580
-rw-r--r--src/osmo-bts-trx/sched_lchan_xcch.c205
-rw-r--r--src/osmo-bts-trx/sched_utils.h51
-rw-r--r--src/osmo-bts-trx/scheduler_trx.c1942
-rw-r--r--src/osmo-bts-trx/trx_if.c1095
-rw-r--r--src/osmo-bts-trx/trx_if.h44
-rw-r--r--src/osmo-bts-trx/trx_provision_fsm.c740
-rw-r--r--src/osmo-bts-trx/trx_provision_fsm.h68
-rw-r--r--src/osmo-bts-trx/trx_vty.c433
-rw-r--r--src/osmo-bts-virtual/Makefile.am50
-rw-r--r--src/osmo-bts-virtual/bts_model.c106
-rw-r--r--src/osmo-bts-virtual/l1_if.c206
-rw-r--r--src/osmo-bts-virtual/l1_if.h8
-rw-r--r--src/osmo-bts-virtual/main.c66
-rw-r--r--src/osmo-bts-virtual/osmo_mcast_sock.c114
-rw-r--r--src/osmo-bts-virtual/osmo_mcast_sock.h28
-rw-r--r--src/osmo-bts-virtual/scheduler_virtbts.c420
-rw-r--r--src/osmo-bts-virtual/virtual_um.c79
-rw-r--r--src/osmo-bts-virtual/virtual_um.h2
-rw-r--r--src/osmo-bts-virtual/virtualbts_vty.c33
207 files changed, 22927 insertions, 9771 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 70e4d968..b2870264 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,7 +13,7 @@ SUBDIRS += osmo-bts-octphy
endif
if ENABLE_LC15BTS
-SUBDIRS += osmo-bts-litecell15
+SUBDIRS += osmo-bts-lc15
endif
if ENABLE_OC2GBTS
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 113ff2f4..d13415d1 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -1,17 +1,87 @@
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
-AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS)
-LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOCODEC_LIBS)
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(NULL)
+
+LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(NULL)
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
+libbts_a_SOURCES = \
+ gsm_data.c \
+ sysinfo.c \
+ logging.c \
+ abis.c \
+ abis_osmo.c \
+ oml.c \
+ osmux.c \
+ bts.c \
+ bts_sm.c \
+ bts_trx.c \
+ rsl.c \
+ rtp_input_preen.c \
+ vty.c \
+ paging.c \
+ measurement.c \
+ amr.c \
+ asci.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 \
+ bts_shutdown_fsm.c \
+ csd_v110.c \
+ l1sap.c \
+ cbch.c \
+ power_control.c \
+ main.c \
+ phy_link.c \
+ dtx_dl_amr_fsm.c \
+ scheduler_mframe.c \
+ ta_control.c \
+ nm_common_fsm.c \
+ nm_bts_sm_fsm.c \
+ nm_bts_fsm.c \
+ nm_bb_transc_fsm.c \
+ nm_channel_fsm.c \
+ nm_gprs_cell_fsm.c \
+ nm_gprs_nse_fsm.c \
+ nm_gprs_nsvc_fsm.c \
+ nm_radio_carrier_fsm.c \
+ notification.c \
+ probes.d \
+ $(NULL)
libl1sched_a_SOURCES = scheduler.c
+
+if ENABLE_SYSTEMTAP
+probes.h: probes.d
+ $(DTRACE) -C -h -s $< -o $@
+
+probes.lo: probes.d
+ $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@
+
+BUILT_SOURCES = probes.h probes.lo
+libbts_la_LDADD = probes.lo
+endif
diff --git a/src/common/abis.c b/src/common/abis.c
index 84a3a047..5629cf23 100644
--- a/src/common/abis.c
+++ b/src/common/abis.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -38,46 +38,319 @@
#include <osmocom/core/msgb.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/macaddr.h>
+#include <osmocom/core/fsm.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/abis.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/abis_osmo.h>
#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts_trx.h>
+#include <osmo-bts/bts_shutdown_fsm.h>
static struct gsm_bts *g_bts;
-int abis_oml_sendmsg(struct msgb *msg)
+static struct e1inp_line_ops line_ops;
+
+static struct ipaccess_unit bts_dev_info;
+
+#define S(x) (1 << (x))
+#define OML_RETRY_TIMER 5
+
+enum abis_link_fsm_state {
+ ABIS_LINK_ST_WAIT_RECONNECT, /* OML link has not yet been established */
+ ABIS_LINK_ST_CONNECTING, /* OML link in process of been established */
+ ABIS_LINK_ST_CONNECTED, /* OML link is established, RSL links may be established or not */
+ ABIS_LINK_ST_FAILED, /* There used to be an active OML connection but it became broken */
+};
+
+static const struct value_string abis_link_fsm_event_names[] = {
+ { ABIS_LINK_EV_SIGN_LINK_OML_UP, "SIGN_LINK_OML_UP", },
+ { ABIS_LINK_EV_SIGN_LINK_DOWN, "SIGN_LINK_DOWN" },
+ { ABIS_LINK_EV_VTY_RM_ADDR, "VTY_RM_ADDR" },
+ {}
+};
+
+struct abis_link_fsm_priv {
+ struct bsc_oml_host *current_bsc;
+ struct gsm_bts *bts;
+ char *model_name;
+ bool reconnect_to_current_bsc;
+};
+
+static void reset_oml_link(struct gsm_bts *bts)
{
- struct gsm_bts *bts = msg->trx->bts;
+ if (bts->oml_link) {
+ struct timespec now;
- if (!bts->oml_link) {
- llist_add_tail(&msg->list, &bts->oml_queue);
+ e1inp_sign_link_destroy(bts->oml_link);
+
+ /* Log a special notice if the OML connection was dropped relatively quickly. */
+ if (bts->oml_conn_established_timestamp.tv_sec != 0 && clock_gettime(CLOCK_MONOTONIC, &now) == 0 &&
+ 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 - bts->oml_conn_established_timestamp.tv_sec));
+ }
+ bts->oml_link = NULL;
+ }
+ memset(&bts->oml_conn_established_timestamp, 0, sizeof(bts->oml_conn_established_timestamp));
+
+ /* Same for IPAC_PROTO_OSMO on the same ipa connection: */
+ if (bts->osmo_link) {
+ e1inp_sign_link_destroy(bts->osmo_link);
+ bts->osmo_link = NULL;
+ }
+
+}
+
+static int pick_next_bsc(struct osmo_fsm_inst *fi)
+{
+ struct abis_link_fsm_priv *priv = fi->priv;
+ struct gsm_bts *bts = priv->bts;
+ struct bsc_oml_host *last;
+
+ if (llist_empty(&bts->bsc_oml_hosts)) {
+ LOGPFSML(fi, LOGL_ERROR, "List of BSCs to connect to is empty!\n");
+ return -1;
+ }
+
+ /* Keep current pointer to priv->current_bsc: */
+ if (priv->reconnect_to_current_bsc) {
+ OSMO_ASSERT(priv->current_bsc);
+ priv->reconnect_to_current_bsc = false;
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);
}
+
+ last = (struct bsc_oml_host *)llist_last_entry(&bts->bsc_oml_hosts, struct bsc_oml_host, list);
+
+ if (!priv->current_bsc || priv->current_bsc == last) /* Pick first one (wrap around): */
+ priv->current_bsc = (struct bsc_oml_host *)llist_first_entry(&bts->bsc_oml_hosts, struct bsc_oml_host, list);
+ else
+ priv->current_bsc = (struct bsc_oml_host *)llist_entry(priv->current_bsc->list.next, struct bsc_oml_host, list);
+
+ return 0;
}
-static void drain_oml_queue(struct gsm_bts *bts)
+static void abis_link_connecting_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
- 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);
+ struct e1inp_line *line;
+ struct abis_link_fsm_priv *priv = fi->priv;
+ struct gsm_bts *bts = priv->bts;
+
+ if (bts_shutdown_in_progress(bts)) {
+ LOGPFSML(fi, LOGL_NOTICE, "BTS is shutting down, delaying A-bis connection establishment to BSC\n");
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_WAIT_RECONNECT, OML_RETRY_TIMER, 0);
+ return;
+ }
+
+ if (pick_next_bsc(fi) < 0) {
+ LOGPFSML(fi, LOGL_FATAL, "No BSC available, A-bis connection establishment failed\n");
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_FAILED, 0, 0);
+ return;
}
+
+ LOGP(DABIS, LOGL_NOTICE, "A-bis connection establishment to BSC (%s) in progress...\n", priv->current_bsc->addr);
+
+ /* patch in various data from VTY and other sources */
+ line_ops.cfg.ipa.addr = priv->current_bsc->addr;
+ 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 = priv->model_name;
+ if (bts->description)
+ bts_dev_info.unit_name = bts->description;
+ bts_dev_info.location2 = priv->model_name;
+
+ line = e1inp_line_find(0);
+ if (!line)
+ line = e1inp_line_create(0, "ipa");
+ if (!line) {
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_FAILED, 0, 0);
+ return;
+ }
+ /* Line always comes already with a "ctor" reference, enough to keep it alive forever. */
+
+ e1inp_line_bind_ops(line, &line_ops);
+ /* This will open the OML connection now */
+ if (e1inp_line_update(line) < 0) {
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_FAILED, 0, 0);
+ return;
+ }
+
+ /* The TCP connection to the BSC is now in progress.
+ * Wait for OML Link UP to transition to CONNECTED. */
+}
+
+static void abis_link_connecting(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct abis_link_fsm_priv *priv = fi->priv;
+ struct gsm_bts *bts = priv->bts;
+
+ switch (event) {
+ case ABIS_LINK_EV_SIGN_LINK_OML_UP:
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_CONNECTED, 0, 0);
+ break;
+ case ABIS_LINK_EV_SIGN_LINK_DOWN:
+ reset_oml_link(bts);
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_WAIT_RECONNECT, OML_RETRY_TIMER, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void abis_link_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ bts_link_estab(g_bts);
+}
+
+static void abis_link_connected(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct abis_link_fsm_priv *priv = fi->priv;
+ struct gsm_bts *bts = priv->bts;
+ struct gsm_bts_trx *trx;
+ OSMO_ASSERT(event == ABIS_LINK_EV_SIGN_LINK_DOWN);
+
+ /* First remove the OML signalling link */
+ reset_oml_link(bts);
+
+ /* Then iterate over the RSL signalling links */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->bb_transc.rsl.link) {
+ e1inp_sign_link_destroy(trx->bb_transc.rsl.link);
+ trx->bb_transc.rsl.link = NULL;
+ if (trx == trx->bts->c0)
+ load_timer_stop(trx->bts);
+ } else {
+ /* If we have no rsl_link yet it may mean that lower
+ * layers are still establishing the socket (TCP, IPA).
+ * Let's tell it to stop connection establishment since
+ * we are shutting down. */
+ struct e1inp_line *line = e1inp_line_find(0);
+ if (line)
+ e1inp_ipa_bts_rsl_close_n(line, trx->nr);
+ }
+ /* Note: Here we could send NM_EV_RSL_DOWN to each
+ * trx->(bb_transc.)mo.fi, but we are starting shutdown of the
+ * entire BTS anyway through bts_model_abis_close(), so simply
+ * let bts_shutdown FSM take care of slowly powering down all
+ * the TRX. It would make sense to send NM_EV_RSL_DOWN only if a
+ * RSL link TRX!=C0 was going down, in order to selectively stop
+ * that TRX only. But libosmo-abis expects us to drop the entire
+ * line when something goes wrong... */
+ }
+ bts_model_abis_close(bts);
+
+ /* We want to try reconnecting to the current BSC at least once before switching to a new one: */
+ priv->reconnect_to_current_bsc = true;
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_WAIT_RECONNECT, OML_RETRY_TIMER, 0);
+}
+
+static void abis_link_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct abis_link_fsm_priv *priv = fi->priv;
+ struct gsm_bts *bts = priv->bts;
+
+ /* None of the configured BSCs was reachable or there was an existing
+ * OML/RSL connection that broke. Initiate BTS process shut down now. */
+ bts_model_abis_close(bts);
+}
+
+static void abis_link_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct abis_link_fsm_priv *priv = fi->priv;
+ struct gsm_bts *bts = priv->bts;
+
+ OSMO_ASSERT(event == ABIS_LINK_EV_VTY_RM_ADDR);
+
+ if (priv->current_bsc == data) {
+ if (llist_count(&bts->bsc_oml_hosts) <= 1)
+ priv->current_bsc = NULL;
+ else
+ pick_next_bsc(fi);
+ }
+}
+
+int abis_link_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ switch (fi->state) {
+ case ABIS_LINK_ST_WAIT_RECONNECT:
+ osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_CONNECTING, 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ return 0;
+}
+
+
+static struct osmo_fsm_state abis_link_fsm_states[] = {
+ [ABIS_LINK_ST_WAIT_RECONNECT] = {
+ .name = "WAIT_RECONNECT",
+ .out_state_mask =
+ S(ABIS_LINK_ST_CONNECTING),
+ },
+ [ABIS_LINK_ST_CONNECTING] = {
+ .name = "CONNECTING",
+ .in_event_mask =
+ S(ABIS_LINK_EV_SIGN_LINK_OML_UP) |
+ S(ABIS_LINK_EV_SIGN_LINK_DOWN),
+ .out_state_mask =
+ S(ABIS_LINK_ST_WAIT_RECONNECT) |
+ S(ABIS_LINK_ST_CONNECTED) |
+ S(ABIS_LINK_ST_FAILED),
+ .onenter = abis_link_connecting_onenter,
+ .action = abis_link_connecting,
+ },
+ [ABIS_LINK_ST_CONNECTED] = {
+ .name = "CONNECTED",
+ .in_event_mask =
+ S(ABIS_LINK_EV_SIGN_LINK_DOWN),
+ .out_state_mask =
+ S(ABIS_LINK_ST_WAIT_RECONNECT),
+ .onenter = abis_link_connected_onenter,
+ .action = abis_link_connected,
+ },
+ [ABIS_LINK_ST_FAILED] = {
+ .name = "FAILED",
+ .onenter = abis_link_failed_onenter,
+ },
+};
+
+static struct osmo_fsm abis_link_fsm = {
+ .name = "abis_link",
+ .states = abis_link_fsm_states,
+ .num_states = ARRAY_SIZE(abis_link_fsm_states),
+ .log_subsys = DABIS,
+ .event_names = abis_link_fsm_event_names,
+ .allstate_action = abis_link_allstate,
+ .allstate_event_mask = S(ABIS_LINK_EV_VTY_RM_ADDR),
+ .timer_cb = abis_link_fsm_timer_cb,
+};
+
+int abis_oml_sendmsg(struct msgb *msg)
+{
+ struct gsm_bts *bts = msg->trx->bts;
+
+ if (!bts->oml_link) {
+ LOGP(DABIS, LOGL_INFO, "Drop Tx OML msg, OML link is down\n");
+ msgb_free(msg);
+ return 0;
+ }
+
+ /* 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);
}
int abis_bts_rsl_sendmsg(struct msgb *msg)
@@ -91,33 +364,37 @@ int abis_bts_rsl_sendmsg(struct msgb *msg)
/* osmo-bts uses msg->trx internally, but libosmo-abis uses
* the signalling link at msg->dst */
- msg->dst = msg->trx->rsl_link;
+ msg->dst = msg->trx->bb_transc.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 e1inp_ts *sign_ts;
struct gsm_bts_trx *trx;
int trx_nr;
switch (type) {
case E1INP_SIGN_OML:
+ sign_ts = e1inp_line_ipa_oml_ts(line);
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);
+ e1inp_ts_config_sign(sign_ts, line);
+ g_bts->oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+ g_bts->c0, IPAC_PROTO_OML, 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;
+ g_bts->osmo_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OSMO,
+ g_bts->c0, IPAC_PROTO_OSMO, 0);
+ osmo_fsm_inst_dispatch(g_bts->abis_link_fi, ABIS_LINK_EV_SIGN_LINK_OML_UP, NULL);
+ return g_bts->oml_link;
+
+ case E1INP_SIGN_RSL:
+ /* fall through to default to catch TRXn having type = E1INP_SIGN_RSL + n */
default:
trx_nr = type - E1INP_SIGN_RSL;
+ sign_ts = e1inp_line_ipa_rsl_ts(line, trx_nr);
LOGP(DABIS, LOGL_INFO, "RSL Signalling link for TRX%d up\n",
trx_nr);
trx = gsm_bts_trx_num(g_bts, trx_nr);
@@ -126,54 +403,23 @@ static struct e1inp_sign_link *sign_link_up(void *unit, struct e1inp_line *line,
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;
+ e1inp_ts_config_sign(sign_ts, line);
+ trx->bb_transc.rsl.link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+ trx, trx->bb_transc.rsl.tei, 0);
trx_link_estab(trx);
- break;
+ return trx->bb_transc.rsl.link;
}
-
- return sign_link;
+ return NULL;
}
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);
+ LOGPIL(line, DABIS, LOGL_ERROR, "Signalling link down\n");
+ osmo_fsm_inst_dispatch(g_bts->abis_link_fi, ABIS_LINK_EV_SIGN_LINK_DOWN, NULL);
}
-/* callback for incoming mesages from A-bis/IP */
+/* callback for incoming messages from A-bis/IP */
static int sign_link_cb(struct msgb *msg)
{
struct e1inp_sign_link *link = msg->dst;
@@ -190,6 +436,9 @@ static int sign_link_cb(struct msgb *msg)
case E1INP_SIGN_RSL:
down_rsl(link->trx, msg);
break;
+ case E1INP_SIGN_OSMO:
+ down_osmo(link->trx->bts, msg);
+ break;
default:
msgb_free(msg);
break;
@@ -212,7 +461,7 @@ uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link)
return 0;
}
- /* we assume that the soket is AF_INET. As Abis/IP contains
+ /* we assume that the socket is AF_INET. As Abis/IP contains
* lots of hard-coded IPv4 addresses, this safe */
OSMO_ASSERT(sin.sin_family == AF_INET);
@@ -235,7 +484,7 @@ static int inp_s_cbfn(unsigned int subsys, unsigned int signal,
static struct ipaccess_unit bts_dev_info = {
- .unit_name = "sysmoBTS",
+ .unit_name = "osmo-bts",
.equipvers = "", /* FIXME: read this from hw */
.swversion = PACKAGE_VERSION,
.location1 = "",
@@ -259,37 +508,36 @@ void abis_init(struct gsm_bts *bts)
{
g_bts = bts;
- oml_init(&bts->mo);
- libosmo_abis_init(NULL);
+ oml_init();
+ libosmo_abis_init(tall_bts_ctx);
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)
+int abis_open(struct gsm_bts *bts, char *model_name)
{
- struct e1inp_line *line;
+ struct abis_link_fsm_priv *abis_link_fsm_priv;
- /* 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;
+ if (llist_empty(&bts->bsc_oml_hosts)) {
+ LOGP(DABIS, LOGL_FATAL, "No BSC configured, cannot start BTS without knowing BSC OML IP\n");
+ return -EINVAL;
+ }
- line = e1inp_line_find(0);
- if (!line)
- line = e1inp_line_create(0, "ipa");
- if (!line)
- return NULL;
- e1inp_line_bind_ops(line, &line_ops);
+ bts->abis_link_fi = osmo_fsm_inst_alloc(&abis_link_fsm, bts, NULL, LOGL_DEBUG, "abis_link");
+ OSMO_ASSERT(bts->abis_link_fi);
- /* This will open the OML connection now */
- if (e1inp_line_update(line) < 0)
- return NULL;
+ abis_link_fsm_priv = talloc_zero(bts->abis_link_fi, struct abis_link_fsm_priv);
+ OSMO_ASSERT(abis_link_fsm_priv);
+ abis_link_fsm_priv->bts = bts;
+ abis_link_fsm_priv->model_name = model_name;
+ bts->abis_link_fi->priv = abis_link_fsm_priv;
+
+ osmo_fsm_inst_state_chg(bts->abis_link_fi, ABIS_LINK_ST_CONNECTING, 0, 0);
- return line;
+ return 0;
+}
+
+static __attribute__((constructor)) void abis_link_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&abis_link_fsm) == 0);
}
diff --git a/src/common/abis_osmo.c b/src/common/abis_osmo.c
new file mode 100644
index 00000000..beb9992d
--- /dev/null
+++ b/src/common/abis_osmo.c
@@ -0,0 +1,135 @@
+/* OSMO extenion link associated to same line as oml_link: */
+
+/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <osmocom/core/msgb.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/bts_sm.h>
+
+#define OM_HEADROOM_SIZE 128
+
+////////////////////////////////////////
+// OSMO ABIS extensions (PCU)
+///////////////////////////////////////
+
+static struct msgb *abis_osmo_pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr, size_t extra_size)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ msg = msgb_alloc_headroom(OM_HEADROOM_SIZE + sizeof(struct gsm_pcu_if) + extra_size,
+ OM_HEADROOM_SIZE, "IPA/ABIS/OSMO");
+ /* Only header is filled, caller is responible for reserving + filling
+ * message type specific contents: */
+ msgb_put(msg, PCUIF_HDR_SIZE);
+ pcu_prim = (struct gsm_pcu_if *) msgb_data(msg);
+ pcu_prim->msg_type = msg_type;
+ pcu_prim->bts_nr = bts_nr;
+ return msg;
+}
+
+/* Send a OML NM Message from BSC to BTS */
+int abis_osmo_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+ msg->dst = bts->osmo_link;
+ msg->l2h = msg->data;
+ return abis_sendmsg(msg);
+}
+
+
+/* Send IPA/OSMO/PCU extension Abis message from PCU to BSC */
+static int abis_osmo_pcu_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_PCU);
+ return abis_osmo_sendmsg(bts, msg);
+}
+
+int abis_osmo_pcu_tx_container(struct gsm_bts *bts, const struct gsm_pcu_if_container *container)
+{
+ uint16_t data_length = osmo_load16be(&container->length);
+ struct msgb *msg = abis_osmo_pcu_msgb_alloc(PCU_IF_MSG_CONTAINER, bts->nr, data_length);
+ struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msgb_data(msg);
+ struct gsm_pcu_if_container *tx_cont = &pcu_prim->u.container;
+
+ msgb_put(msg, sizeof(*tx_cont) + data_length);
+ tx_cont->msg_type = container->msg_type;
+ tx_cont->length = container->length;
+ if (data_length)
+ memcpy(tx_cont->data, container->data, data_length);
+
+ return abis_osmo_pcu_sendmsg(bts, msg);
+}
+
+
+/* incoming IPA/OSMOEXT/PCU Abis message from BSC */
+static int rx_down_osmo_pcu(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct gsm_pcu_if *pcu_prim;
+ if (msgb_l2len(msg) < PCUIF_HDR_SIZE) {
+ LOGP(DPCU, LOGL_ERROR, "ABIS_OSMO_PCU message too short\n");
+ oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UKWN_MSG,
+ "ABIS_OSMO_PCU message too short\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ pcu_prim = msgb_l2(msg);
+ LOGP(DPCU, LOGL_INFO, "Rx BSC->BTS%d ABIS_OSMO_PCU msg type %u\n",
+ pcu_prim->bts_nr, pcu_prim->msg_type);
+ /* we patch the bts_nr received from BTS with the bts_nr we used to set up in the local PCU */
+ pcu_prim->bts_nr = bts->nr;
+ /* Trim Abis lower layers: */
+ msgb_pull_to_l2(msg);
+ /* we simply forward it to PCUIF: */
+ return pcu_sock_send(msg);
+}
+
+/* incoming IPA/OSMO extension Abis message from BSC */
+int down_osmo(struct gsm_bts *bts, struct msgb *msg)
+{
+ uint8_t *type;
+
+ if (msgb_l2len(msg) < 1) {
+ oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UKWN_MSG,
+ "OSMO message too short\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ type = msgb_l2(msg);
+ msg->l2h = type + 1;
+
+ switch (*type) {
+ case IPAC_PROTO_EXT_PCU:
+ return rx_down_osmo_pcu(bts, msg);
+ default:
+ oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UKWN_MSG,
+ "OSMO message unknown extension %u\n", *type);
+ msgb_free(msg);
+ return -EIO;
+ }
+}
diff --git a/src/common/amr.c b/src/common/amr.c
index 05d1aaac..47c9dcb5 100644
--- a/src/common/amr.c
+++ b/src/common/amr.c
@@ -6,6 +6,85 @@
#include <osmo-bts/logging.h>
#include <osmo-bts/amr.h>
+/* Reasonable defaults for AMR-FR and AMR-HR rate configuration.
+ * The values are taken from 3GPP TS 51.010-1 (version 13.11.0).
+ * See 14.2.19.4.1 and 14.2.20.4.1 for AMR-FR and AMR-HR, respectively.
+ *
+ * ^ C/I (dB) | FR / HR |
+ * | |
+ * | |
+ * MODE4 | |
+ * = | ----+---- THR_MX_Up(3) | 20.5 / 18.0 |
+ * | | |
+ * | = ----+---- THR_MX_Dn(4) | 18.5 / 16.0 |
+ * MODE3 | |
+ * | = ----+---- THR_MX_Up(2) | 14.5 / 14.0 |
+ * | | |
+ * = | ----+---- THR_MX_Dn(3) | 12.5 / 12.0 |
+ * MODE2 | |
+ * = | ----+---- THR_MX_Up(1) | 8.5 / 10.0 |
+ * | | |
+ * | = ----+---- THR_MX_Dn(2) | 6.5 / 8.0 |
+ * MODE1 | |
+ * | |
+ * | |
+ */
+static const struct gsm48_multi_rate_conf amr_fr_mr_cfg_def = {
+ .m4_75 = 1,
+ .m5_90 = 1,
+ .m7_95 = 1,
+ .m12_2 = 1,
+};
+static const struct amr_mode amr_fr_bts_mode_def[] = {
+ {
+ .mode = 0, /* 4.75k */
+ .threshold = 13, /* THR_MX_Dn(2): 6.5 dB */
+ .hysteresis = 4, /* THR_MX_Up(1): 8.5 dB */
+ },
+ {
+ .mode = 2, /* 5.90k */
+ .threshold = 25, /* THR_MX_Dn(3): 12.5 dB */
+ .hysteresis = 4, /* THR_MX_Up(2): 14.5 dB */
+ },
+ {
+ .mode = 5, /* 7.95k */
+ .threshold = 37, /* THR_MX_Dn(4): 18.5 dB */
+ .hysteresis = 4, /* THR_MX_Up(3): 20.5 dB */
+ },
+ {
+ .mode = 7, /* 12.2k */
+ /* this is the last mode, so no threshold */
+ },
+};
+
+static const struct gsm48_multi_rate_conf amr_hr_mr_cfg_def = {
+ .m4_75 = 1,
+ .m5_90 = 1,
+ .m6_70 = 1,
+ .m7_95 = 1,
+};
+static const struct amr_mode amr_hr_bts_mode_def[] = {
+ {
+ .mode = 0, /* 4.75k */
+ .threshold = 16, /* THR_MX_Dn(2): 8.0 dB */
+ .hysteresis = 4, /* THR_MX_Up(1): 10.0 dB */
+ },
+ {
+ .mode = 2, /* 5.90k */
+ .threshold = 24, /* THR_MX_Dn(3): 12.0 dB */
+ .hysteresis = 4, /* THR_MX_Up(2): 14.0 dB */
+ },
+ {
+ .mode = 3, /* 6.70k */
+ .threshold = 32, /* THR_MX_Dn(4): 16.0 dB */
+ .hysteresis = 4, /* THR_MX_Up(3): 18.0 dB */
+ },
+ {
+ .mode = 5, /* 7.95k */
+ /* this is the last mode, so no threshold */
+ },
+};
+
void amr_log_mr_conf(int ss, int logl, const char *pfx,
struct amr_multirate_conf *amr_mrc)
{
@@ -16,9 +95,9 @@ void amr_log_mr_conf(int ss, int logl, const char *pfx,
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);
+ i, amr_mrc->mode[i].mode,
+ amr_mrc->mode[i].threshold,
+ amr_mrc->mode[i].hysteresis);
LOGPC(ss, logl, "\n");
}
@@ -27,7 +106,7 @@ static inline int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc,
{
unsigned int i;
for (i = 0; i < amr_mrc->num_modes; i++) {
- if (amr_mrc->bts_mode[i].mode == cmi)
+ if (amr_mrc->mode[i].mode == cmi)
return i;
}
return -EINVAL;
@@ -78,13 +157,16 @@ void amr_set_mode_pref(uint8_t *data, const 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)
{
- 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);
+ if (len < 2) {
+ LOGP(DRSL, LOGL_ERROR, "AMR Multirate IE is too short (%u)\n", len);
+ goto ret_einval;
+ }
+
+ if ((mr_conf[0] >> 5) != 1) {
+ LOGP(DRSL, LOGL_ERROR, "AMR Multirate Version %u unknown\n", (mr_conf[0] >> 5));
goto ret_einval;
}
@@ -114,23 +196,26 @@ int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc,
for (i = 0; i < 8; i++) {
if (mr_conf[1] & (1 << i)) {
- amr_mrc->bts_mode[j++].mode = i;
+ amr_mrc->mode[j++].mode = i;
}
}
+ /* skip the first two octets of the IE */
+ mr_conf += 2;
+
if (num_codecs >= 2) {
- amr_mrc->bts_mode[0].threshold = mr_conf[1] & 0x3F;
- amr_mrc->bts_mode[0].hysteresis = mr_conf[2] >> 4;
+ amr_mrc->mode[0].threshold = mr_conf[0] & 0x3F;
+ amr_mrc->mode[0].hysteresis = mr_conf[1] >> 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;
+ amr_mrc->mode[1].threshold =
+ ((mr_conf[1] & 0xF) << 2) | (mr_conf[2] >> 6);
+ amr_mrc->mode[1].hysteresis = (mr_conf[2] >> 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;
+ amr_mrc->mode[2].threshold =
+ ((mr_conf[2] & 0x3) << 4) | (mr_conf[3] >> 4);
+ amr_mrc->mode[2].hysteresis = mr_conf[3] & 0xF;
}
return num_codecs;
@@ -168,3 +253,26 @@ unsigned int amr_get_initial_mode(struct gsm_lchan *lchan)
}
}
}
+
+void amr_init_mr_conf_def(struct gsm_lchan *lchan)
+{
+ const struct gsm48_multi_rate_conf *mr_cfg;
+ const struct amr_mode *bts_mode;
+ unsigned int num_modes;
+
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ num_modes = ARRAY_SIZE(amr_fr_bts_mode_def);
+ bts_mode = &amr_fr_bts_mode_def[0];
+ mr_cfg = &amr_fr_mr_cfg_def;
+ } else {
+ num_modes = ARRAY_SIZE(amr_hr_bts_mode_def);
+ bts_mode = &amr_hr_bts_mode_def[0];
+ mr_cfg = &amr_hr_mr_cfg_def;
+ }
+
+ memcpy(lchan->tch.amr_mr.gsm48_ie, mr_cfg,
+ sizeof(lchan->tch.amr_mr.gsm48_ie));
+ memcpy(&lchan->tch.amr_mr.mode[0], &bts_mode[0],
+ sizeof(lchan->tch.amr_mr.mode));
+ lchan->tch.amr_mr.num_modes = num_modes;
+}
diff --git a/src/common/asci.c b/src/common/asci.c
new file mode 100644
index 00000000..4342f17c
--- /dev/null
+++ b/src/common/asci.c
@@ -0,0 +1,211 @@
+/* ASCI (VGCS/VBS) related common code */
+
+/* (C) 2023 by Harald Welte <laforge@osmocom.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 <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/l1sap.h>
+#include <osmo-bts/asci.h>
+
+static int tx_vgcs_ul_grant(struct gsm_lchan *lchan)
+{
+ struct gsm0408_vgcs_ul_grant ul_grant;
+ struct gsm_time gt;
+ struct msgb *msg;
+
+ gsm_fn2gsmtime(&gt, lchan->asci.fn);
+
+ /* build the RR VGCS UPLINK GRANT message as per TS 44.018 Section 9.1.49 */
+ ul_grant = (struct gsm0408_vgcs_ul_grant) {
+ .hdr = {
+ .proto_discr = GSM48_PDISC_RR,
+ .msg_type = GSM48_MT_RR_VGCS_UPL_GRANT,
+ },
+ .req_ref = {
+ .ra = lchan->asci.ref,
+ .t1 = gt.t1,
+ .t2 = gt.t2,
+ .t3_low = gt.t3 & 7,
+ .t3_high = gt.t3 >> 3,
+ },
+ .ta = lchan->ta_ctrl.current,
+ };
+
+ /* Wrap it in a RSL UNITDATA REQUEST */
+ msg = rsl_rll_simple(RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), 0x00, 0);
+ msg->l3h = msg->tail; /* emulate rsl_rx_rll() behaviour */
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, sizeof(ul_grant), (uint8_t *) &ul_grant);
+
+ /* send it towards MS, just like a RSL message from the BSC */
+ return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+}
+
+/* timer call-back for T3115 (VGCS UPLINK GRANT re-transmit) */
+static void vgcs_t3115_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ LOGPLCHAN(lchan, DASCI, LOGL_INFO, "T3115 timeout (%d resends left)\n",
+ bts->ny2 - lchan->asci.vgcs_ul_grant_count);
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGPLCHAN(lchan, DASCI, LOGL_NOTICE, "is not active. It is in state %s. Ignoring\n",
+ gsm_lchans_name(lchan->state));
+ return;
+ }
+
+ if (lchan->asci.vgcs_ul_grant_count >= bts->ny2) {
+ lchan->asci.vgcs_ul_grant_count = 0;
+ LOGPLCHAN(lchan, DASCI, LOGL_NOTICE, "NY2 reached, sending CONNection FAILure to BSC.\n");
+ rsl_tx_conn_fail(lchan, RSL_ERR_TALKER_ACC_FAIL);
+ lchan->asci.talker_active = VGCS_TALKER_NONE;
+ return;
+ }
+
+ tx_vgcs_ul_grant(lchan);
+ lchan->asci.vgcs_ul_grant_count++;
+ osmo_timer_schedule(&lchan->asci.t3115, 0, bts->t3115_ms * 1000);
+}
+
+/* Received random access on dedicated channel. */
+void vgcs_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay, uint32_t fn)
+{
+ LOGPLCHAN(lchan, DASCI, LOGL_NOTICE, "VGCS RACH on dedicated channel type %s received with "
+ "TA=%u, ref=%u\n", gsm_lchant_name(lchan->type), acc_delay, ra);
+
+ if (ra == 0x25) { /* See TS 44.018 Table 9.1.45.1 */
+ /* Listener Detection (TS 48.058 Section 4.14) */
+ if (!lchan->asci.listener_detected) {
+ rsl_tx_listener_det(lchan, &acc_delay);
+ lchan->asci.listener_detected = true;
+ }
+ } else {
+ /* Talker Detection (TS 48.058 Section 4.13) */
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* Talker detection on group channels only */
+ if (!rsl_chan_rt_is_vgcs(lchan->rsl_chan_rt))
+ return;
+
+ if (lchan->asci.talker_active != VGCS_TALKER_NONE) {
+ LOGPLCHAN(lchan, DASCI, LOGL_DEBUG, "Ignoring RACH, there is an active talker already.\n");
+ return;
+ }
+
+ /* Set timing advance, power level and activate SACCH */
+ lchan->ta_ctrl.current = acc_delay;
+ lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max;
+ lchan->want_dl_sacch_active = true;
+
+ /* Stop RACH detection, wait for valid frame */
+ lchan->asci.talker_active = VGCS_TALKER_WAIT_FRAME;
+ if (l1sap_uplink_access(lchan, false) != 0) {
+ LOGPLCHAN(lchan, DASCI, LOGL_ERROR, "Failed to deactivate uplink access after TALKER DET.\n");
+ rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL);
+ lchan->asci.talker_active = VGCS_TALKER_NONE;
+ return;
+ }
+
+ lchan->asci.ref = ra;
+ lchan->asci.fn = fn;
+
+ /* Send TALKER DETECT via RSL to BSC */
+ rsl_tx_talker_det(lchan, &acc_delay);
+
+ /* Send VGCS UPLINK GRANT */
+ lchan->asci.vgcs_ul_grant_count = 1;
+ tx_vgcs_ul_grant(lchan);
+
+ /* Start T3115 */
+ LOGPLCHAN(lchan, DASCI, LOGL_DEBUG, "Starting T3115 with %u ms\n", bts->t3115_ms);
+ lchan->asci.t3115.cb = vgcs_t3115_cb;
+ lchan->asci.t3115.data = lchan;
+ osmo_timer_schedule(&lchan->asci.t3115, 0, bts->t3115_ms * 1000);
+ }
+}
+
+/* Received channel activation. */
+void vgcs_lchan_activate(struct gsm_lchan *lchan)
+{
+ LOGPLCHAN(lchan, DASCI, LOGL_INFO, "Channel is activated.\n");
+ if (l1sap_uplink_access(lchan, true) != 0) {
+ LOGPLCHAN(lchan, DASCI, LOGL_ERROR, "Failed to activate uplink access after channel activation.\n");
+ rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL);
+ }
+}
+
+/* Received channel reactivation. (for assignment) */
+void vgcs_lchan_react(struct gsm_lchan *lchan)
+{
+ LOGPLCHAN(lchan, DASCI, LOGL_INFO, "Channel is activated for assignment.\n");
+ lchan->asci.talker_active = VGCS_TALKER_WAIT_FRAME;
+ if (l1sap_uplink_access(lchan, false) != 0) {
+ LOGPLCHAN(lchan, DASCI, LOGL_ERROR, "Failed to deactivate uplink access for assignment.\n");
+ rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL);
+ }
+ radio_link_timeout_reset(lchan);
+}
+
+/* Received first valid data frame on dedicated channel. */
+void vgcs_talker_frame(struct gsm_lchan *lchan)
+{
+ LOGPLCHAN(lchan, DASCI, LOGL_INFO, "First valid frame detected, talker now active.\n");
+ osmo_timer_del(&lchan->asci.t3115);
+ lchan->asci.talker_active = VGCS_TALKER_ACTIVE;
+ radio_link_timeout_reset(lchan);
+}
+
+/* Release VGCS Talker state. */
+void vgcs_talker_reset(struct gsm_lchan *lchan, bool ul_access)
+{
+ if (lchan->asci.talker_active == VGCS_TALKER_NONE)
+ return;
+
+ LOGPLCHAN(lchan, DASCI, LOGL_INFO, "Uplink released, no talker.\n");
+
+ /* Stop T3115 */
+ osmo_timer_del(&lchan->asci.t3115);
+
+ /* Talker released. */
+ lchan->asci.talker_active = VGCS_TALKER_NONE;
+ if (ul_access) {
+ if (l1sap_uplink_access(lchan, true) != 0) {
+ LOGPLCHAN(lchan, DASCI, LOGL_ERROR,
+ "Failed to activate uplink access after uplink became free.\n");
+ rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL);
+ }
+ }
+}
+
+/* Release VGCS Listener state. */
+void vgcs_listener_reset(struct gsm_lchan *lchan)
+{
+ lchan->asci.listener_detected = false;
+}
diff --git a/src/common/bts.c b/src/common/bts.c
index 73631ae6..56765eca 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -32,6 +32,7 @@
#include <osmocom/core/timer.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/tdef.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/gsm/protocol/gsm_12_21.h>
@@ -43,24 +44,27 @@
#include <osmo-bts/abis.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts_sm.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/pcu_if.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/signal.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
#include <osmo-bts/cbch.h>
+#include <osmo-bts/bts_shutdown_fsm.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/power_control.h>
+#include <osmo-bts/osmux.h>
+#include <osmo-bts/notification.h>
+#define MAX_TA_DEF 63 /* default max Timing Advance value */
#define MIN_QUAL_RACH 50 /* minimum link quality (in centiBels) for Access Bursts */
#define MIN_QUAL_NORM -5 /* minimum link quality (in centiBels) for Normal Bursts */
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 */
@@ -86,17 +90,29 @@ static int bts_signal_cbfn(unsigned int subsys, unsigned int signal,
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_DROP_PS] = {"paging:drop-ps", "Dropped paging requests (PS/PCU)"},
+ [BTS_CTR_PAGING_CONG] = {"paging:cong", "Paging congestion detected (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_VGCS] = {"rach:vgcs", "Received RACH requests (VGCS)"},
[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)"},
+
+ [BTS_CTR_RTP_RX_TOTAL] = {"rtp:rx:total", "Total number of received RTP packets"},
+ [BTS_CTR_RTP_RX_MARKER] = {"rtp:rx:marker", "Number of received RTP packets with marker bit set"},
+ [BTS_CTR_RTP_RX_DROP_PREEN] = {"rtp:rx:drop:preen", "Total number of received RTP packets dropped during preening"},
+ [BTS_CTR_RTP_RX_DROP_LOOPBACK] = {"rtp:rx:drop:loopback", "Total number of received RTP packets dropped during loopback"},
+ [BTS_CTR_RTP_RX_DROP_OVERFLOW] = {"rtp:rx:drop:overflow", "Total number of received RTP packets dropped during DL queue overflow"},
+ [BTS_CTR_RTP_RX_DROP_V110_DEC] = {"rtp:rx:drop:v110_dec", "Total number of received RTP packets dropped during V.110 decode"},
+ [BTS_CTR_RTP_TX_TOTAL] = {"rtp:tx:total", "Total number of transmitted RTP packets"},
+ [BTS_CTR_RTP_TX_MARKER] = {"rtp:tx:marker", "Number of transmitted RTP packets with marker bit set"},
};
static const struct rate_ctr_group_desc bts_ctrg_desc = {
"bts",
@@ -122,6 +138,178 @@ static const struct rate_ctr_group_desc cbch_ctrg_desc = {
cbch_ctr_desc
};
+struct osmo_tdef bts_T_defs[] = {
+ /* T-1: FIXME: Ideally should be dynamically calculated per trx at
+ * shutdown start based on params below, and highest trx value taken:
+ * + VTY's power-ramp step-interval.
+ * + Amount of steps needed (taking into account how many dB each step moves).
+ * + Extra time to get response back for each step.
+ * For now we simply give 5 mins, which should be enough for any
+ * acceptable setup, while still ensuring will timeout at some point if
+ * something fails in the ramp down procedure.
+ */
+ { .T=-1, .default_val=300, .desc="Time after which osmo-bts exits if regular ramp down during shut down process does not finish (s)" },
+ { .T=-2, .default_val=3, .desc="Time after which osmo-bts exits if requesting transceivers to stop during shut down process does not finish (s)" },
+ {}
+};
+
+struct osmo_tdef abis_T_defs[] = {
+ { .T=-15, .default_val=0, .unit=OSMO_TDEF_MS, .desc="Time to wait between Channel Activation and dispatching a cached early Immediate Assignment" },
+ {}
+};
+
+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,
+};
+
+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, "osmo-bts-trx" },
+ { BTS_OSMO_VIRTUAL, "osmo-bts-virtual" },
+ { BTS_OSMO_OMLDUMMY, "osmo-bts-omldummy" },
+ { 0, NULL }
+};
+
+const char *btsvariant2str(enum gsm_bts_type_variant v)
+{
+ return get_value_string(osmo_bts_variant_names, v);
+}
+
+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 }
+};
+
+const char *btsatttr2str(enum bts_attribute v)
+{
+ return get_value_string(bts_attribute_names, v);
+}
+
+const struct value_string bts_impl_flag_desc[] = {
+ { BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP, "DSP/HW based MS Power Control Loop" },
+ { BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB, "Measurement and Payload data combined" },
+ { BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER, "OML RadioChannel MO depends on RadioCarrier MO" },
+ { BTS_INTERNAL_FLAG_INTERF_MEAS, "Uplink interference measurements" },
+ { 0, NULL }
+};
+
+/* Ensure that all BTS_INTERNAL_FLAG_* entries are present in bts_impl_flag_desc[] */
+osmo_static_assert(ARRAY_SIZE(bts_impl_flag_desc) == _BTS_INTERNAL_FLAG_NUM + 1, _bts_impl_flag_desc);
+
+static int gsm_bts_talloc_destructor(struct gsm_bts *bts)
+{
+ if (bts->mo.fi) {
+ osmo_fsm_inst_free(bts->mo.fi);
+ bts->mo.fi = NULL;
+ }
+ if (bts->shutdown_fi) {
+ osmo_fsm_inst_free(bts->shutdown_fi);
+ bts->shutdown_fi = NULL;
+ }
+
+ bts_osmux_release(bts);
+
+ llist_del(&bts->list);
+ g_bts_sm->num_bts--;
+ return 0;
+}
+
+struct gsm_bts *gsm_bts_alloc(struct gsm_bts_sm *bts_sm, uint8_t bts_num)
+{
+ struct gsm_bts *bts = talloc_zero(bts_sm, struct gsm_bts);
+
+ if (!bts)
+ return NULL;
+
+ talloc_set_destructor(bts, gsm_bts_talloc_destructor);
+
+ /* add to list of BTSs */
+ llist_add_tail(&bts->list, &bts_sm->bts_list);
+ g_bts_sm->num_bts++;
+
+ bts->site_mgr = bts_sm;
+ bts->nr = bts_num;
+ bts->num_trx = 0;
+ INIT_LLIST_HEAD(&bts->trx_list);
+ bts->ms_max_power = 15; /* dBm */
+
+ bts->T_defs = bts_T_defs;
+ osmo_tdefs_reset(bts->T_defs);
+ osmo_tdefs_reset(abis_T_defs);
+ bts->shutdown_fi = osmo_fsm_inst_alloc(&bts_shutdown_fsm, bts, bts,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts->shutdown_fi, "bts%d", bts->nr);
+
+ /* NM BTS */
+ bts->mo.fi = osmo_fsm_inst_alloc(&nm_bts_fsm, bts, bts,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts->mo.fi, "bts%d", bts->nr);
+ gsm_mo_init(&bts->mo, bts, NM_OC_BTS, bts->nr, 0xff, 0xff);
+
+ /* NM GPRS CELL */
+ bts->gprs.cell.mo.fi = osmo_fsm_inst_alloc(&nm_gprs_cell_fsm, bts, &bts->gprs.cell,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts->gprs.cell.mo.fi, "gprs_cell%d-0", bts->nr);
+ gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL, bts->nr, 0, 0xff);
+ memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default, sizeof(bts->gprs.cell.rlc_cfg));
+ memcpy(&bts->gprs.cell.timer, bts_cell_timer_default, sizeof(bts->gprs.cell.timer));
+
+ /* 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->features = bitvec_alloc(MAX_BTS_FEATURES / 8, bts);
+ OSMO_ASSERT(bts->features != NULL);
+
+ return bts;
+}
+
+struct gsm_bts *gsm_bts_num(const struct gsm_bts_sm *bts_sm, int num)
+{
+ struct gsm_bts *bts;
+
+ if (num >= bts_sm->num_bts)
+ return NULL;
+
+ llist_for_each_entry(bts, &bts_sm->bts_list, list) {
+ if (bts->nr == num)
+ return bts;
+ }
+
+ return NULL;
+}
+
/* Initialize the BTS data structures, called before config
* file reading */
int bts_init(struct gsm_bts *bts)
@@ -130,19 +318,14 @@ int bts_init(struct gsm_bts *bts)
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);
- if (!bts->ctrs) {
- llist_del(&bts->list);
+ if (!bts->ctrs)
return -1;
- }
/* enable management with default levels,
* raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to
@@ -154,56 +337,72 @@ int bts_init(struct gsm_bts *bts)
/* 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;
+ bts->rtp_ip_dscp = -1;
+ bts->rtp_priority = -1;
+ bts->emit_hr_rfc5993 = true;
+
+ /* Default (fall-back) MS/BS Power control parameters */
+ power_ctrl_params_def_reset(&bts->bs_dpc_params, true);
+ power_ctrl_params_def_reset(&bts->ms_dpc_params, false);
/* configurable via OML */
+ bts->bsic = 0xff; /* invalid value, guarded by bsc_configured=false */
+ bts->bsic_configured = false;
bts->load.ccch.load_ind_period = 112;
- load_timer_start(bts);
bts->rtp_jitter_buf_ms = 100;
- bts->max_ta = 63;
+ bts->max_ta = MAX_TA_DEF;
bts->ny1 = 4;
+ bts->ny2 = 4;
bts->t3105_ms = 300;
+ bts->t3115_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];
+ bts->pcu.sock_wqueue_len_max = BTS_PCU_SOCK_WQUEUE_LEN_DEFAULT;
+ for (i = 0; i < ARRAY_SIZE(bts->t200_fn); i++)
+ bts->t200_fn[i] = oml_default_t200_fn[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);
+ bts->radio_link_timeout.oml = 32;
+ bts->radio_link_timeout.current = bts->radio_link_timeout.oml;
- /* 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);
+ /* Start with the BTS */
+ oml_mo_state_init(&bts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+ oml_mo_state_init(&bts->gprs.cell.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
/* 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);
- /* features implemented in 'common', available for all models */
- gsm_bts_set_feature(bts, BTS_FEAT_ETWS_PN);
+ /* Osmux */
+ rc = bts_osmux_init(bts);
+ if (rc < 0)
+ return rc;
+
+ /* features implemented in 'common', available for all models,
+ * order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_ABIS_OSMO_PCU);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CCN);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_DYN_TS_SDCCH8);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_ETWS_PN);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_IPV6_NSVC);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_PAGING_COORDINATION);
+
+ /* Maximum TA supported by the PHY (can be overridden by PHY specific code) */
+ bts->support.max_ta = MAX_TA_DEF;
rc = bts_model_init(bts);
- if (rc < 0) {
- llist_del(&bts->list);
+ if (rc < 0)
return rc;
- }
/* TRX0 was allocated early during gsm_bts_alloc, not later through VTY */
- bts_trx_init(bts->c0);
- bts_gsmnet.num_bts++;
+ bts_model_trx_init(bts->c0);
if (!initialized) {
osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL);
@@ -220,7 +419,10 @@ int bts_init(struct gsm_bts *bts)
bts->smscb_queue_tgt_len = 2;
bts->smscb_queue_hyst = 2;
- INIT_LLIST_HEAD(&bts->oml_queue);
+ bts->asci.pos_nch = -ENOTSUP;
+ INIT_LLIST_HEAD(&bts->asci.notifications);
+
+ INIT_LLIST_HEAD(&bts->bsc_oml_hosts);
/* register DTX DL FSM */
rc = osmo_fsm_register(&dtx_dl_amr_fsm);
@@ -234,206 +436,20 @@ int bts_init(struct gsm_bts *bts)
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);
+ LOGP(DOML, LOGL_INFO, "Main link established, sending NM Status\n");
- /* 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);
- }
- }
+ /* Signal OML UP to BTS SITE MGR. It will automatically SW_ACT repoort
+ * and become Disabled-Offline, then dispatch same event to its children
+ * objects.
+ */
+ osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_OML_UP, NULL);
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_tx_failure_event_rep(&trx->bb_transc.mo, NM_SEVER_MAJOR, 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;
-}
-
-/* prepare the per-SAPI T200 arrays for a given lchan */
-static int t200_by_lchan(int *t200_ms_dcch, int *t200_ms_acch, struct gsm_lchan *lchan)
-{
- struct gsm_bts *bts = lchan->ts->trx->bts;
-
- /* we have to compensate for the "RTS advance" due to the asynchronous interface between
- * the BTS (LAPDm) and the PHY/L1 (OsmoTRX or DSP in case of osmo-bts-{sysmo,lc15,oc2g,octphy} */
- int32_t fn_advance = bts_get_avg_fn_advance(bts);
- int32_t fn_advance_us = fn_advance * 4615;
- int fn_advance_ms = fn_advance_us / 1000;
-
- t200_ms_acch[DL_SAPI0] = bts->t200_ms[T200_SACCH_SDCCH] + fn_advance_ms;
- t200_ms_acch[DL_SAPI3] = bts->t200_ms[T200_SACCH_SDCCH] + fn_advance_ms;
-
- switch (lchan->type) {
- case GSM_LCHAN_SDCCH:
- t200_ms_dcch[DL_SAPI0] = bts->t200_ms[T200_SDCCH] + fn_advance_ms;
- t200_ms_dcch[DL_SAPI3] = bts->t200_ms[T200_SDCCH_SAPI3] + fn_advance_ms;
- break;
- case GSM_LCHAN_TCH_F:
- t200_ms_dcch[DL_SAPI0] = bts->t200_ms[T200_FACCH_F] + fn_advance_ms;
- t200_ms_dcch[DL_SAPI3] = bts->t200_ms[T200_FACCH_F] + fn_advance_ms;
- break;
- case GSM_LCHAN_TCH_H:
- t200_ms_dcch[DL_SAPI0] = bts->t200_ms[T200_FACCH_H] + fn_advance_ms;
- t200_ms_dcch[DL_SAPI3] = bts->t200_ms[T200_FACCH_H] + fn_advance_ms;
- break;
- default:
- /* Channels such as CCCH don't use lapdm DL, and hence no T200 is needed */
- return -1;
- }
- return 0;
-}
-
-int lchan_init_lapdm(struct gsm_lchan *lchan)
-{
- struct lapdm_channel *lc = &lchan->lapdm_ch;
- int t200_ms_dcch[_NR_DL_SAPI], t200_ms_acch[_NR_DL_SAPI];
-
- if (t200_by_lchan(t200_ms_dcch, t200_ms_acch, lchan) == 0) {
- LOGPLCHAN(lchan, DLLAPD, LOGL_DEBUG,
- "Setting T200 D0=%u, D3=%u, S0=%u, S3=%u (all in ms)\n",
- t200_ms_dcch[DL_SAPI0], t200_ms_dcch[DL_SAPI3],
- t200_ms_acch[DL_SAPI0], t200_ms_acch[DL_SAPI3]);
- lapdm_channel_init2(lc, LAPDM_MODE_BTS, t200_ms_dcch, t200_ms_acch, lchan->type);
- lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY);
- lapdm_channel_set_l1(lc, NULL, lchan);
- }
- /* We still need to set Rx callback to receive RACH requests: */
- lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan);
-
- return 0;
-}
-
#define CCCH_RACH_RATIO_COMBINED256 (256*1/9)
#define CCCH_RACH_RATIO_SEPARATE256 (256*10/55)
@@ -591,7 +607,7 @@ static int try_merge_imm_ass_rej(struct gsm48_imm_ass_rej *old_rej,
return 0;
/* GSM 08.58, 5.7
- * -> The BTS may combine serveral IMM.ASS.REJ messages
+ * -> The BTS may combine several IMM.ASS.REJ messages
* -> Identical request refs in one message may be squeezed
*
* GSM 04.08, 9.1.20.2
@@ -628,7 +644,7 @@ int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg)
struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg);
if (bts->agch_queue.length > hard_limit) {
- LOGP(DSUM, LOGL_ERROR,
+ LOGP(DRR, 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),
@@ -656,7 +672,7 @@ int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg)
return 0;
}
-struct msgb *bts_agch_dequeue(struct gsm_bts *bts)
+static struct msgb *bts_agch_dequeue(struct gsm_bts *bts)
{
struct msgb *msg = msgb_dequeue(&bts->agch_queue.queue);
if (!msg)
@@ -669,7 +685,7 @@ struct msgb *bts_agch_dequeue(struct gsm_bts *bts)
/*
* 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
+ * \return 0 if 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)
@@ -722,12 +738,12 @@ static void compact_agch_queue(struct gsm_bts *bts)
return;
}
-int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt,
- int is_ag_res)
+int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, enum ccch_msgt ccch)
{
struct msgb *msg = NULL;
int rc = 0;
int is_empty = 1;
+ const struct bts_agch_msg_cb *msg_cb;
/* Do queue house keeping.
* This needs to be done every time a CCCH message is requested, since
@@ -736,26 +752,39 @@ int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt
*/
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)
+ switch (ccch) {
+ case CCCH_MSGT_NCH:
+ /* Send NCH message, it has priority over AGCH and does not overlap with PCH. */
+ rc = bts_asci_notify_nch_gen_msg(bts, out_buf);
return rc;
+ case CCCH_MSGT_PCH:
+ /* Check whether the block may be overwritten by AGCH. */
+ rc = paging_gen_msg(bts->paging_state, out_buf, gt, &is_empty);
+ if (!is_empty)
+ return rc;
+ /* fall-through */
+ case CCCH_MSGT_AGCH:
+ /* If fallen here and the AGCH queue is empty, return empty PCH message. */
+ msg = bts_agch_dequeue(bts);
+ if (!msg)
+ return rc;
+ /* Continue to return AGCH message. */
+ break;
+ }
rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_SENT);
+ /* Confirm sending of the AGCH message towards the PCU */
+ msg_cb = (struct bts_agch_msg_cb *) msg->cb;
+ if (msg_cb->confirm)
+ pcu_tx_data_cnf(msg_cb->msg_id, PCU_IF_SAPI_AGCH_2);
+
/* Copy AGCH message */
memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg));
rc = msgb_l3len(msg);
msgb_free(msg);
- if (is_ag_res)
+ if (ccch == CCCH_MSGT_AGCH)
bts->agch_queue.agch_msgs++;
else
bts->agch_queue.pch_msgs++;
@@ -778,69 +807,206 @@ int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher)
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)
+bool bts_supports_cm_speech(const struct gsm_bts *bts,
+ const struct rsl_ie_chan_mode *cm)
{
- enum gsm_bts_features feature = _NUM_BTS_FEAT;
-
- /* We assume that signalling support is mandatory,
- * there is no BTS_FEAT_* definition to check that. */
- if (cm == GSM48_CMODE_SIGN)
- return 1;
+ enum osmo_bts_features feature = _NUM_BTS_FEAT;
+
+ /* Stage 1: check support for the requested channel type */
+ switch (cm->chan_rt) {
+ case RSL_CMOD_CRT_TCH_GROUP_Bm:
+ case RSL_CMOD_CRT_TCH_GROUP_Lm:
+ if (!osmo_bts_has_feature(bts->features, BTS_FEAT_VGCS))
+ return false;
+ break;
+ case RSL_CMOD_CRT_TCH_BCAST_Bm:
+ case RSL_CMOD_CRT_TCH_BCAST_Lm:
+ if (!osmo_bts_has_feature(bts->features, BTS_FEAT_VBS))
+ return false;
+ break;
+ case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm:
+ case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm:
+ if (!osmo_bts_has_feature(bts->features, BTS_FEAT_VAMOS))
+ return false;
+ break;
+ }
- /* 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:
+ /* Stage 2: check support for the requested codec */
+ switch (cm->chan_rt) {
+ case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm:
+ case RSL_CMOD_CRT_TCH_GROUP_Bm:
+ case RSL_CMOD_CRT_TCH_BCAST_Bm:
+ case RSL_CMOD_CRT_TCH_Bm:
+ switch (cm->chan_rate) {
+ case RSL_CMOD_SP_GSM1:
feature = BTS_FEAT_SPEECH_F_V1;
break;
- case GSM48_CMODE_SPEECH_EFR:
+ case RSL_CMOD_SP_GSM2:
feature = BTS_FEAT_SPEECH_F_EFR;
break;
- case GSM48_CMODE_SPEECH_AMR:
+ case RSL_CMOD_SP_GSM3:
feature = BTS_FEAT_SPEECH_F_AMR;
break;
default:
/* Invalid speech codec type => Not supported! */
- return 0;
+ return false;
}
break;
- case GSM_PCHAN_TCH_H:
- switch(cm) {
- case GSM48_CMODE_SPEECH_V1:
+ case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm:
+ case RSL_CMOD_CRT_TCH_GROUP_Lm:
+ case RSL_CMOD_CRT_TCH_BCAST_Lm:
+ case RSL_CMOD_CRT_TCH_Lm:
+ switch (cm->chan_rate) {
+ case RSL_CMOD_SP_GSM1:
feature = BTS_FEAT_SPEECH_H_V1;
break;
- case GSM48_CMODE_SPEECH_AMR:
+ case RSL_CMOD_SP_GSM3:
feature = BTS_FEAT_SPEECH_H_AMR;
break;
default:
/* Invalid speech codec type => Not supported! */
- return 0;
+ return false;
}
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;
+ LOGP(DRSL, LOGL_ERROR,
+ "Unhandled RSL channel type=0x%02x/rate=0x%02x\n",
+ cm->chan_rt, cm->chan_rate);
+ return false;
}
/* Check if the feature is supported by this BTS */
- if (gsm_bts_has_feature(bts, feature))
- return 1;
+ if (osmo_bts_has_feature(bts->features, feature))
+ return true;
+
+ return false;
+}
+
+static bool bts_supports_cm_data(const struct gsm_bts *bts,
+ const struct rsl_ie_chan_mode *cm)
+{
+ switch (bts->variant) {
+ case BTS_OSMO_TRX:
+ switch (cm->chan_rate) {
+ /* TODO: RSL_CMOD_CSD_NT_14k5 */
+ /* TODO: RSL_CMOD_CSD_T_14k4 */
+ case RSL_CMOD_CSD_NT_12k0:
+ case RSL_CMOD_CSD_T_9k6:
+ if (cm->chan_rt != RSL_CMOD_CRT_TCH_Bm)
+ return false; /* invalid */
+ /* fall-through */
+ case RSL_CMOD_CSD_NT_6k0:
+ case RSL_CMOD_CSD_T_4k8:
+ case RSL_CMOD_CSD_T_2k4:
+ case RSL_CMOD_CSD_T_1k2:
+ case RSL_CMOD_CSD_T_600:
+ case RSL_CMOD_CSD_T_1200_75:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return 0;
+ }
+}
+
+bool bts_supports_cm(const struct gsm_bts *bts,
+ const struct rsl_ie_chan_mode *cm)
+{
+ switch (cm->spd_ind) {
+ case RSL_CMOD_SPD_SIGN:
+ /* We assume that signalling support is mandatory,
+ * there is no BTS_FEAT_* definition to check that. */
+ return true;
+ case RSL_CMOD_SPD_SPEECH:
+ return bts_supports_cm_speech(bts, cm);
+ case RSL_CMOD_SPD_DATA:
+ return bts_supports_cm_data(bts, cm);
+ default:
+ return false;
+ }
+}
+
+/* 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_bts_trx *trx = bts->c0;
+
+ /* According to 3GPP TS 45.002, table 3, CBCH can be allocated
+ * either on C0/TS0 (CCCH+SDCCH4) or on C0..n/TS0..3 (SDCCH/8). */
+ if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ return &trx->ts[0].lchan[2]; /* C0/TS0 */
+
+ llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */
+ unsigned int tn;
+ for (tn = 0; tn <= 3; tn++) { /* TS0..3 */
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH)
+ return &ts->lchan[2];
+ }
+ }
+
+ return NULL;
+}
+
+/* BCCH carrier power reduction (see 3GPP TS 45.008, section 7.1) */
+int bts_set_c0_pwr_red(struct gsm_bts *bts, const uint8_t red)
+{
+ struct gsm_bts_trx *c0 = bts->c0;
+ unsigned int tn;
+
+ if (!osmo_bts_has_feature(bts->features, BTS_FEAT_BCCH_POWER_RED)) {
+ LOGPTRX(c0, DRSL, LOGL_ERROR, "BCCH carrier power reduction "
+ "is not supported by this BTS model\n");
+ return -ENOTSUP;
+ }
+
+ if (red > 6 || red % 2 != 0) {
+ LOGPTRX(c0, DRSL, LOGL_ERROR, "BCCH carrier power reduction "
+ "value (%u dB) is incorrect or out of range\n", red);
+ return -EINVAL;
+ }
+
+ LOGPTRX(c0, DRSL, LOGL_NOTICE, "BCCH carrier power reduction: "
+ "%u dB (%s)\n", red, red ? "enabled" : "disabled");
+
+ /* Timeslot 0 is always transmitting BCCH/CCCH */
+ c0->ts[0].c0_power_red_db = 0;
+
+ for (tn = 1; tn < ARRAY_SIZE(c0->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &c0->ts[tn];
+ struct gsm_bts_trx_ts *prev = ts - 1;
+
+ switch (ts_pchan(ts)) {
+ /* Not allowed on CCCH/BCCH */
+ case GSM_PCHAN_CCCH:
+ /* Preceeding timeslot shall not exceed 2 dB */
+ if (prev->c0_power_red_db > 0)
+ prev->c0_power_red_db = 2;
+ /* fall-through */
+ /* Not recommended on SDCCH/8 */
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ ts->c0_power_red_db = 0;
+ break;
+ default:
+ ts->c0_power_red_db = red;
+ break;
+ }
+ }
+
+ /* Timeslot 7 is always preceding BCCH/CCCH */
+ if (c0->ts[7].c0_power_red_db > 0)
+ c0->ts[7].c0_power_red_db = 2;
+
+ bts->c0_power_red_db = red;
return 0;
}
diff --git a/src/common/bts_ctrl_commands.c b/src/common/bts_ctrl_commands.c
index 0d318900..5f6857cf 100644
--- a/src/common/bts_ctrl_commands.c
+++ b/src/common/bts_ctrl_commands.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -57,7 +57,7 @@ static int set_therm_att(struct ctrl_cmd *cmd, void *data)
tpp->thermal_attenuation_mdB = val;
- power_ramp_start(trx, tpp->p_total_cur_mdBm, 0);
+ power_ramp_start(trx, tpp->p_total_cur_mdBm, 0, NULL);
return get_therm_att(cmd, data);
}
@@ -84,12 +84,45 @@ static int set_oml_alert(struct ctrl_cmd *cmd, void *data)
return CTRL_CMD_REPLY;
}
+static int verify_max_ber10k_rach(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int max_ber10k_rach = atoi(cmd->value);
+
+ if (max_ber10k_rach < 0 || max_ber10k_rach > 10000) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_max_ber10k_rach(struct ctrl_cmd *cmd, void *data)
+{
+ cmd->reply = talloc_asprintf(cmd, "%u", g_bts->max_ber10k_rach);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_max_ber10k_rach(struct ctrl_cmd *cmd, void *data)
+{
+ g_bts->max_ber10k_rach = atoi(cmd->value);
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(max_ber10k_rach, "max-ber10k-rach");
+
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);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_max_ber10k_rach);
g_bts = bts;
return rc;
diff --git a/src/common/bts_ctrl_lookup.c b/src/common/bts_ctrl_lookup.c
index f0157e9a..2a5ff522 100644
--- a/src/common/bts_ctrl_lookup.c
+++ b/src/common/bts_ctrl_lookup.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -28,6 +28,7 @@
#include <osmo-bts/logging.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/control_if.h>
+#include <osmo-bts/bts_trx.h>
extern vector ctrl_node_vec;
@@ -87,14 +88,12 @@ err_index:
return -ERANGE;
}
-struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts,
- const char *bind_addr, uint16_t port)
+struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts, uint16_t port)
{
struct ctrl_handle *hdl;
int rc = 0;
- hdl = ctrl_interface_setup_dynip(bts, bind_addr, port,
- bts_ctrl_node_lookup);
+ hdl = ctrl_interface_setup(bts, port, bts_ctrl_node_lookup);
if (!hdl)
return NULL;
diff --git a/src/common/bts_shutdown_fsm.c b/src/common/bts_shutdown_fsm.c
new file mode 100644
index 00000000..13a0e1d0
--- /dev/null
+++ b/src/common/bts_shutdown_fsm.c
@@ -0,0 +1,283 @@
+/* BTS shutdown FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/bts_shutdown_fsm.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+#define X(s) (1 << (s))
+
+#define BTS_SHUTDOWN_POWER_RAMP_TGT -10
+
+static const struct osmo_tdef_state_timeout bts_shutdown_fsm_timeouts[32] = {
+ [BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL] = { .T = -1 },
+ [BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED] = { .T = -2 },
+};
+
+#define bts_shutdown_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, bts_shutdown_fsm_timeouts, ((struct gsm_bts *)fi->priv)->T_defs, -1)
+
+static unsigned int count_trx_operational(struct gsm_bts *bts) {
+ unsigned int count = 0;
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED)
+ count++;
+ }
+ return count;
+}
+
+static void st_none(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ unsigned int count;
+ switch(event) {
+ case BTS_SHUTDOWN_EV_START:
+ /* Firt announce to NM objects that we are starting a shutdown procedure: */
+ osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_SHUTDOWN_START, NULL);
+
+ count = count_trx_operational(bts);
+ if (count) {
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL);
+ } else {
+ /* we can skip ramp down since no TRX is running anyway.
+ * Let's jump into WAIT_TRX_CLOSED to make sure we
+ * tell lower layer to close all TRX in case there's some
+ * open() WIP */
+ LOGPFSML(fi, LOGL_INFO, "No TRX is operational, skipping power ramp down\n");
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED);
+ }
+ break;
+ }
+}
+
+static void ramp_down_compl_cb(struct gsm_bts_trx *trx) {
+ osmo_fsm_inst_dispatch(trx->bts->shutdown_fi, BTS_SHUTDOWN_EV_TRX_RAMP_COMPL, trx);
+}
+
+static void st_wait_ramp_down_compl_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (bts->shutdown_fi_skip_power_ramp)
+ power_ramp_force(trx, to_mdB(BTS_SHUTDOWN_POWER_RAMP_TGT), 1, ramp_down_compl_cb);
+ else
+ power_ramp_start(trx, to_mdB(BTS_SHUTDOWN_POWER_RAMP_TGT), 1, ramp_down_compl_cb);
+ }
+}
+
+static void st_wait_ramp_down_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct gsm_bts_trx *src_trx;
+ unsigned int remaining = 0;
+ struct gsm_bts_trx *trx;
+
+ switch(event) {
+ case BTS_SHUTDOWN_EV_TRX_RAMP_COMPL:
+ src_trx = (struct gsm_bts_trx *)data;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED &&
+ trx->power_params.p_total_cur_mdBm > BTS_SHUTDOWN_POWER_RAMP_TGT)
+ remaining++;
+ }
+
+ LOGPFSML(fi, LOGL_INFO, "%s Ramping down complete, %u TRX remaining\n",
+ gsm_trx_name(src_trx), remaining);
+ if (remaining == 0) {
+ /* Make sure we end up any remaining ongoing power ramp
+ * down under target shutdown tx power level, then
+ * finally transit to next state:
+ */
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ power_ramp_abort(trx);
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED);
+ }
+ break;
+ }
+}
+
+static void st_wait_trx_closed_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
+ bts_model_trx_deact_rf(trx);
+ }
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
+ bts_model_trx_close(trx);
+ }
+ /* Now wait until all TRX are closed asynchronously, we'll get feedback through events... */
+}
+
+static void st_wait_trx_closed(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct gsm_bts_trx *src_trx;
+ unsigned int remaining;
+
+ switch(event) {
+ case BTS_SHUTDOWN_EV_TRX_CLOSED:
+ src_trx = (struct gsm_bts_trx *)data;
+ remaining = count_trx_operational(bts);
+
+ LOGPFSML(fi, LOGL_INFO, "%s TRX closed, %u TRX remaining\n",
+ gsm_trx_name(src_trx), remaining);
+ if (remaining == 0)
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_EXIT);
+ break;
+ }
+}
+
+static void st_exit_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_SHUTDOWN_FINISH, NULL);
+
+ if (bts->shutdown_fi_exit_proc) {
+ LOGPFSML(fi, LOGL_NOTICE, "Shutdown process completed successfully, exiting process\n");
+ exit(0);
+ }
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_NONE);
+}
+
+static struct osmo_fsm_state bts_shutdown_fsm_states[] = {
+ [BTS_SHUTDOWN_ST_NONE] = {
+ .in_event_mask =
+ X(BTS_SHUTDOWN_EV_START),
+ .out_state_mask =
+ X(BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL) |
+ X(BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED),
+ .name = "NONE",
+ .action = st_none,
+ },
+ [BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL] = {
+ .in_event_mask =
+ X(BTS_SHUTDOWN_EV_TRX_RAMP_COMPL),
+ .out_state_mask =
+ X(BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED),
+ .name = "WAIT_RAMP_DOWN_COMPL",
+ .onenter = st_wait_ramp_down_compl_on_enter,
+ .action = st_wait_ramp_down_compl,
+ },
+ [BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED] = {
+ .in_event_mask =
+ X(BTS_SHUTDOWN_EV_TRX_CLOSED),
+ .out_state_mask =
+ X(BTS_SHUTDOWN_ST_EXIT),
+ .name = "WAIT_TRX_CLOSED",
+ .onenter = st_wait_trx_closed_on_enter,
+ .action = st_wait_trx_closed,
+ },
+ [BTS_SHUTDOWN_ST_EXIT] = {
+ .name = "EXIT",
+ .out_state_mask =
+ X(BTS_SHUTDOWN_ST_NONE),
+ .onenter = st_exit_on_enter,
+ }
+};
+
+const struct value_string bts_shutdown_fsm_event_names[] = {
+ OSMO_VALUE_STRING(BTS_SHUTDOWN_EV_START),
+ OSMO_VALUE_STRING(BTS_SHUTDOWN_EV_TRX_RAMP_COMPL),
+ OSMO_VALUE_STRING(BTS_SHUTDOWN_EV_TRX_CLOSED),
+ { 0, NULL }
+};
+
+int bts_shutdown_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ switch (fi->state) {
+ case BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL:
+ LOGPFSML(fi, LOGL_ERROR, "Timer expired waiting for ramp down complete\n");
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED);
+ break;
+ case BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED:
+ LOGPFSML(fi, LOGL_ERROR, "Timer expired waiting for TRX close\n");
+ bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_EXIT);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+ return 0;
+}
+
+struct osmo_fsm bts_shutdown_fsm = {
+ .name = "BTS_SHUTDOWN",
+ .states = bts_shutdown_fsm_states,
+ .num_states = ARRAY_SIZE(bts_shutdown_fsm_states),
+ .event_names = bts_shutdown_fsm_event_names,
+ .log_subsys = DOML,
+ .timer_cb = bts_shutdown_fsm_timer_cb,
+};
+
+static __attribute__((constructor)) void bts_shutdown_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&bts_shutdown_fsm) == 0);
+}
+
+bool bts_shutdown_in_progress(const struct gsm_bts *bts)
+{
+ const struct osmo_fsm_inst *fi = bts->shutdown_fi;
+ return fi->state != BTS_SHUTDOWN_ST_NONE;
+}
+
+void bts_shutdown_ext(struct gsm_bts *bts, const char *reason, bool exit_proc, bool skip_power_ramp)
+{
+ struct osmo_fsm_inst *fi = bts->shutdown_fi;
+ if (bts_shutdown_in_progress(bts)) {
+ LOGPFSML(fi, LOGL_NOTICE, "BTS is already being shutdown.\n");
+ if (exit_proc)
+ bts->shutdown_fi_exit_proc = true;
+ return;
+ }
+ bts->shutdown_fi_exit_proc = exit_proc;
+ bts->shutdown_fi_skip_power_ramp = skip_power_ramp;
+ LOGPFSML(fi, LOGL_NOTICE, "Shutting down BTS, exit %u, reason: %s\n",
+ exit_proc, reason);
+ osmo_fsm_inst_dispatch(fi, BTS_SHUTDOWN_EV_START, NULL);
+}
+
+void bts_shutdown(struct gsm_bts *bts, const char *reason)
+{
+ bts_shutdown_ext(bts, reason, true, true);
+}
+
+void bts_model_trx_close_cb(struct gsm_bts_trx *trx, int rc)
+{
+ struct osmo_fsm_inst *fi = trx->bts->shutdown_fi;
+ LOGPFSML(fi, LOGL_DEBUG, "%s Received TRX close cb rc=%d\n", gsm_trx_name(trx), rc);
+ osmo_fsm_inst_dispatch(fi, BTS_SHUTDOWN_EV_TRX_CLOSED, trx);
+}
diff --git a/src/common/bts_sm.c b/src/common/bts_sm.c
new file mode 100644
index 00000000..6e889b71
--- /dev/null
+++ b/src/common/bts_sm.c
@@ -0,0 +1,95 @@
+/* BTS support code common to all supported BTS models */
+
+/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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 <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmo-bts/bts_sm.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+struct gsm_bts_sm *g_bts_sm;
+
+static const uint8_t nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+
+struct gsm_bts *gsm_gprs_nse_get_bts(const struct gsm_gprs_nse *nse)
+{
+ return gsm_bts_num(g_bts_sm, nse->mo.obj_inst.bts_nr);
+}
+
+static int gsm_bts_sm_talloc_destructor(struct gsm_bts_sm *bts_sm)
+{
+ struct gsm_bts *bts;
+
+ while ((bts = llist_first_entry_or_null(&bts_sm->bts_list, struct gsm_bts, list)))
+ talloc_free(bts);
+
+ if (bts_sm->mo.fi) {
+ osmo_fsm_inst_free(bts_sm->mo.fi);
+ bts_sm->mo.fi = NULL;
+ }
+
+ return 0;
+}
+
+struct gsm_bts_sm *gsm_bts_sm_alloc(void *talloc_ctx)
+{
+ struct gsm_bts_sm *bts_sm = talloc_zero(talloc_ctx, struct gsm_bts_sm);
+ struct gsm_gprs_nse *nse = &bts_sm->gprs.nse;
+ unsigned int i;
+
+ if (!bts_sm)
+ return NULL;
+
+ talloc_set_destructor(bts_sm, gsm_bts_sm_talloc_destructor);
+
+ INIT_LLIST_HEAD(&bts_sm->bts_list);
+
+ /* NM SITE_MGR */
+ bts_sm->mo.fi = osmo_fsm_inst_alloc(&nm_bts_sm_fsm, bts_sm, bts_sm,
+ LOGL_INFO, "bts_sm");
+ gsm_mo_init(&bts_sm->mo, NULL, NM_OC_SITE_MANAGER,
+ 0xff, 0xff, 0xff);
+ oml_mo_state_init(&bts_sm->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+
+ /* NM GPRS NSE */
+ nse->mo.fi = osmo_fsm_inst_alloc(&nm_gprs_nse_fsm, bts_sm, nse,
+ LOGL_INFO, "gprs_nse0");
+ gsm_mo_init(&nse->mo, NULL, NM_OC_GPRS_NSE, 0, 0xff, 0xff);
+ oml_mo_state_init(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+ memcpy(&nse->timer, nse_timer_default, sizeof(nse->timer));
+
+ /* NM GPRS NSVCs */
+ for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) {
+ struct gsm_gprs_nsvc *nsvc = &nse->nsvc[i];
+ nsvc->nse = nse;
+ nsvc->id = i;
+ nsvc->mo.fi = osmo_fsm_inst_alloc(&nm_gprs_nsvc_fsm, bts_sm, nsvc,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(nsvc->mo.fi, "gprs_nsvc%d-%d",
+ nse->mo.obj_inst.bts_nr, i);
+ gsm_mo_init(&nsvc->mo, NULL, NM_OC_GPRS_NSVC, nse->mo.obj_inst.bts_nr, i, 0xff);
+ oml_mo_state_init(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+ }
+
+ return bts_sm;
+}
diff --git a/src/common/bts_trx.c b/src/common/bts_trx.c
new file mode 100644
index 00000000..251b6735
--- /dev/null
+++ b/src/common/bts_trx.c
@@ -0,0 +1,256 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * 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 <osmocom/core/fsm.h>
+
+#include <osmocom/gsm/abis_nm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_trx.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+static int gsm_bts_trx_talloc_destructor(struct gsm_bts_trx *trx)
+{
+ unsigned int i;
+
+ if (trx->bb_transc.mo.fi) {
+ osmo_fsm_inst_free(trx->bb_transc.mo.fi);
+ trx->bb_transc.mo.fi = NULL;
+ }
+ if (trx->mo.fi) {
+ osmo_fsm_inst_free(trx->mo.fi);
+ trx->mo.fi = NULL;
+ }
+ for (i = 0; i < TRX_NR_TS; i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ if (ts->mo.fi) {
+ osmo_fsm_inst_free(ts->mo.fi);
+ ts->mo.fi = NULL;
+ }
+ }
+ return 0;
+}
+
+/* Initialize all logical channels of the given timeslot */
+static void gsm_bts_trx_ts_init_lchan(struct gsm_bts_trx_ts *ts)
+{
+ unsigned int ln;
+
+ for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) {
+ struct gsm_lchan *lchan = &ts->lchan[ln];
+ gsm_lchan_init(lchan, ts, ln);
+ }
+}
+
+/* Initialize primary timeslots of the given transceiver */
+static void gsm_bts_trx_init_ts(struct gsm_bts_trx *trx)
+{
+ unsigned int tn;
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ ts->trx = trx;
+ ts->nr = tn;
+
+ ts->tsc_oml_configured = false;
+ ts->tsc_rsl_configured = false;
+ ts->tsc = ts->tsc_oml = ts->tsc_rsl = 0xff;
+
+ ts->mo.fi = osmo_fsm_inst_alloc(&nm_chan_fsm, trx, ts,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(ts->mo.fi, "%s-ts%u",
+ trx->bb_transc.mo.fi->id, ts->nr);
+ gsm_mo_init(&ts->mo, trx->bts, NM_OC_CHANNEL,
+ trx->bts->nr, trx->nr, ts->nr);
+ oml_mo_state_init(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+
+ gsm_bts_trx_ts_init_lchan(ts);
+ }
+}
+
+/* Initialize shadow timeslots of the given transceiver */
+void gsm_bts_trx_init_shadow_ts(struct gsm_bts_trx *trx)
+{
+ unsigned int tn;
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts;
+
+ ts = talloc_zero(trx, struct gsm_bts_trx_ts);
+ OSMO_ASSERT(ts != NULL);
+
+ ts->trx = trx;
+ ts->nr = tn;
+
+ ts->tsc_oml_configured = false;
+ ts->tsc_rsl_configured = false;
+ ts->tsc = ts->tsc_oml = ts->tsc_rsl = 0xff;
+
+ /* Link both primary and shadow */
+ trx->ts[tn].vamos.peer = ts;
+ ts->vamos.peer = &trx->ts[tn];
+ ts->vamos.is_shadow = true;
+
+ /* Shadow timeslot uses the primary's NM FSM */
+ OSMO_ASSERT(trx->ts[tn].mo.fi != NULL);
+ ts->mo.fi = trx->ts[tn].mo.fi;
+
+ gsm_bts_trx_ts_init_lchan(ts);
+ }
+}
+
+void gsm_bts_trx_free_shadow_ts(struct gsm_bts_trx *trx)
+{
+ unsigned int tn;
+ unsigned int ln;
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *shadow_ts = trx->ts[tn].vamos.peer;
+ if (!shadow_ts)
+ continue;
+
+ /* free lchan related mem allocated on the trx object: */
+ for (ln = 0; ln < ARRAY_SIZE(shadow_ts->lchan); ln++) {
+ struct gsm_lchan *lchan = &shadow_ts->lchan[ln];
+ TALLOC_FREE(lchan->name);
+ }
+
+ talloc_free(shadow_ts);
+ trx->ts[tn].vamos.peer = 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);
+
+ if (!trx)
+ return NULL;
+
+ talloc_set_destructor(trx, gsm_bts_trx_talloc_destructor);
+
+ trx->bts = bts;
+ trx->nr = bts->num_trx++;
+
+ trx->mo.fi = osmo_fsm_inst_alloc(&nm_rcarrier_fsm, trx, trx,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(trx->mo.fi, "bts%d-trx%d", bts->nr, trx->nr);
+ gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, bts->nr, trx->nr, 0xff);
+ oml_mo_state_init(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+
+ trx->bb_transc.mo.fi = osmo_fsm_inst_alloc(&nm_bb_transc_fsm, trx, &trx->bb_transc,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(trx->bb_transc.mo.fi, "bts%d-trx%d", bts->nr, trx->nr);
+ gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, bts->nr, trx->nr, 0xff);
+ oml_mo_state_init(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED);
+
+ gsm_bts_trx_init_ts(trx);
+
+ if (trx->nr != 0)
+ trx->nominal_power = bts->c0->nominal_power;
+
+ /* Default values for the power adjustments */
+ trx->power_params.ramp.max_initial_pout_mdBm = to_mdB(0);
+ trx->power_params.ramp.step_size_mdB = to_mdB(2);
+ trx->power_params.ramp.step_interval_sec = 1;
+
+ /* Default (fall-back) Dynamic Power Control parameters */
+ trx->bs_dpc_params = &bts->bs_dpc_params;
+ trx->ms_dpc_params = &bts->ms_dpc_params;
+
+ /* IF BTS model doesn't DSP/HW support MS Power Control Loop, enable osmo algo by default: */
+ if (!bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP))
+ trx->ms_pwr_ctl_soft = true;
+
+ llist_add_tail(&trx->list, &bts->trx_list);
+
+ return trx;
+}
+
+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;
+}
+
+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;
+}
+
+/* RSL link is established, send status report */
+int trx_link_estab(struct gsm_bts_trx *trx)
+{
+ int rc;
+
+ LOGPTRX(trx, DRSL, LOGL_INFO, "RSL link up\n");
+
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_RSL_UP, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_UP, NULL);
+
+ if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED ||
+ trx->bb_transc.mo.nm_state.operational == NM_OPSTATE_ENABLED) {
+ rc = rsl_tx_rf_res(trx);
+ if (rc < 0)
+ oml_tx_failure_event_rep(&trx->bb_transc.mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_RSL_FAIL,
+ "Failed to establish RSL link (%d)", rc);
+ }
+ if (trx == trx->bts->c0)
+ load_timer_start(trx->bts);
+
+ return 0;
+}
+
+
+bool trx_ms_pwr_ctrl_is_osmo(const struct gsm_bts_trx *trx)
+{
+ return trx->ms_pwr_ctl_soft;
+}
diff --git a/src/common/cbch.c b/src/common/cbch.c
index 7ed11c2f..363eb672 100644
--- a/src/common/cbch.c
+++ b/src/common/cbch.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -38,7 +38,7 @@ struct smscb_msg {
uint8_t num_segs; /* total number of segments */
};
-/* determine if current queue length differes more than permitted hysteresis from target
+/* determine if current queue length differs more than permitted hysteresis from target
* queue length. If it does, send CBCH LOAD IND */
static void check_and_send_cbch_load(struct gsm_bts *bts, struct bts_smscb_state *bts_ss)
{
@@ -139,12 +139,8 @@ static int get_smscb_block(struct bts_smscb_state *bts_ss, uint8_t *out, uint8_t
block_type->seq_nr = block_nr;
/* determine if this is the last block */
- if (block_nr + 1 == msg->num_segs)
- block_type->lb = 1;
- else
- block_type->lb = 0;
-
- if (block_nr == 4) {
+ block_type->lb = (block_nr + 1 == msg->num_segs);
+ if (block_type->lb) {
if (msg != bts_ss->default_msg) {
DEBUGPGT(DLSMS, g_time, "%s: deleting fully-transmitted message %p\n",
chan_name, msg);
@@ -198,7 +194,7 @@ int bts_process_smscb_cmd(struct gsm_bts *bts, struct rsl_ie_cb_cmd_type cmd_typ
return -EINVAL;
}
- scm = talloc_zero_size(bts, sizeof(*scm));
+ scm = talloc_zero(bts, struct smscb_msg);
if (!scm)
return -1;
@@ -233,10 +229,10 @@ int bts_process_smscb_cmd(struct gsm_bts *bts, struct rsl_ie_cb_cmd_type cmd_typ
rate_ctr_inc2(bts_ss->ctrs, CBCH_CTR_RCVD_QUEUED);
break;
case RSL_CB_CMD_TYPE_DEFAULT:
- /* old default msg will be free'd in get_smscb_block() if it is currently in transit
- * and we set a new default_msg here */
+ /* clear the cur_msg pointer if it is the old default message */
if (bts_ss->cur_msg && bts_ss->cur_msg == bts_ss->default_msg)
- talloc_free(bts_ss->cur_msg);
+ bts_ss->cur_msg = NULL;
+ talloc_free(bts_ss->default_msg);
if (cmd_type.def_bcast == RSL_CB_CMD_DEFBCAST_NORMAL)
/* def_bcast == 0: normal message */
bts_ss->default_msg = scm;
@@ -322,3 +318,25 @@ int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time)
return rc;
}
+
+static void bts_smscb_state_reset(struct bts_smscb_state *bts_ss)
+{
+ struct smscb_msg *scm, *tmp;
+ llist_for_each_entry_safe(scm, tmp, &bts_ss->queue, list) {
+ llist_del(&scm->list);
+ talloc_free(scm);
+ }
+ bts_ss->queue_len = 0;
+ rate_ctr_group_reset(bts_ss->ctrs);
+ /* avoid double-free of default_msg in case cur_msg == default_msg */
+ if (bts_ss->cur_msg && bts_ss->cur_msg != bts_ss->default_msg)
+ talloc_free(bts_ss->cur_msg);
+ bts_ss->cur_msg = NULL;
+ TALLOC_FREE(bts_ss->default_msg);
+}
+
+void bts_cbch_reset(struct gsm_bts *bts)
+{
+ bts_smscb_state_reset(&bts->smscb_basic);
+ bts_smscb_state_reset(&bts->smscb_extended);
+}
diff --git a/src/common/csd_v110.c b/src/common/csd_v110.c
new file mode 100644
index 00000000..d6c00b03
--- /dev/null
+++ b/src/common/csd_v110.c
@@ -0,0 +1,188 @@
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
+ *
+ * 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/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm44021.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/isdn/v110.h>
+
+#include <osmo-bts/csd_v110.h>
+#include <osmo-bts/lchan.h>
+
+/* key is enum gsm48_chan_mode, so assuming a value in range 0..255 */
+const struct csd_v110_lchan_desc csd_v110_lchan_desc[256] = {
+#if 0
+ [GSM48_CMODE_DATA_14k5] = {
+ /* TCH/F14.4: 290 bits every 20 ms (14.5 kbit/s) */
+ .fr = { .num_blocks = 1, .num_bits = 290 },
+ },
+#endif
+ [GSM48_CMODE_DATA_12k0] = {
+ /* TCH/F9.6: 4 * 60 bits every 20 ms (12.0 kbit/s) */
+ .fr = { .num_blocks = 4, .num_bits = 60 },
+ },
+ [GSM48_CMODE_DATA_6k0] = {
+ /* TCH/F4.8: 2 * 60 bits every 20 ms (6.0 kbit/s) */
+ .fr = { .num_blocks = 2, .num_bits = 60 },
+ /* TCH/H4.8: 4 * 60 bits every 40 ms (6.0 kbit/s) */
+ .hr = { .num_blocks = 4, .num_bits = 60 },
+ },
+ [GSM48_CMODE_DATA_3k6] = {
+ /* TCH/F2.4: 2 * 36 bits every 20 ms (3.6 kbit/s) */
+ .fr = { .num_blocks = 2, .num_bits = 36 },
+ /* TCH/H2.4: 4 * 36 bits every 40 ms (3.6 kbit/s) */
+ .hr = { .num_blocks = 4, .num_bits = 36 },
+ },
+};
+
+/* 3GPP TS 44.021, Figure 4: Coding of data rates (E1/E2/E3 bits) */
+static const uint8_t e1e2e3_map[_LCHAN_CSD_M_NUM][3] = {
+ [LCHAN_CSD_M_T_600] = { 1, 0, 0 },
+ [LCHAN_CSD_M_T_1200] = { 0, 1, 0 },
+ [LCHAN_CSD_M_T_2400] = { 1, 1, 0 },
+ [LCHAN_CSD_M_T_4800] = { 0, 1, 1 },
+ [LCHAN_CSD_M_T_9600] = { 0, 1, 1 },
+#if 0
+ [LCHAN_CSD_M_T_19200] = { 0, 1, 1 },
+ [LCHAN_CSD_M_T_38400] = { 0, 1, 1 },
+ [LCHAN_CSD_M_T_14400] = { 1, 0, 1 },
+ [LCHAN_CSD_M_T_28800] = { 1, 0, 1 },
+#endif
+};
+
+int csd_v110_rtp_encode(const struct gsm_lchan *lchan, uint8_t *rtp,
+ const uint8_t *data, size_t data_len)
+{
+ const struct csd_v110_frame_desc *desc;
+ ubit_t ra_bits[80 * 4];
+
+ OSMO_ASSERT(lchan->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc));
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].fr;
+ else
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].hr;
+ if (OSMO_UNLIKELY(desc->num_blocks == 0))
+ return -ENOTSUP;
+
+ /* handle empty/incomplete Uplink frames gracefully */
+ if (OSMO_UNLIKELY(data_len < (desc->num_blocks * desc->num_bits))) {
+ /* encode N idle frames as per 3GPP TS 44.021, section 8.1.6 */
+ memset(&ra_bits[0], 0x01, sizeof(ra_bits));
+ for (unsigned int i = 0; i < desc->num_blocks; i++)
+ memset(&ra_bits[i * 80], 0x00, 8); /* alignment pattern */
+ goto ra1_ra2;
+ }
+
+ /* RA1'/RA1: convert from radio rate to an intermediate data rate */
+ for (unsigned int i = 0; i < desc->num_blocks; i++) {
+ struct osmo_v110_decoded_frame df;
+
+ /* convert a V.110 36-/60-bit frame to a V.110 80-bit frame */
+ if (desc->num_bits == 60)
+ osmo_csd_12k_6k_decode_frame(&df, &data[i * 60], 60);
+ else /* desc->num_bits == 36 */
+ osmo_csd_3k6_decode_frame(&df, &data[i * 36], 36);
+
+ /* E1 .. E3 must set by out-of-band knowledge */
+ if (lchan->csd_mode == LCHAN_CSD_M_NT) {
+ /* non-transparent: as per 3GPP TS 48.020, Table 7 */
+ df.e_bits[0] = 0; /* E1: as per 15.1.2, shall be set to 0 (for BSS-MSC) */
+ df.e_bits[1] = (i >> 1) & 0x01; /* E2: 0 for Q1/Q2, 1 for Q3/Q4 */
+ df.e_bits[2] = (i >> 0) & 0x01; /* E3: 0 for Q1/Q3, 1 for Q2/Q4 */
+ } else {
+ /* transparent: as per 3GPP TS 44.021, Figure 4 */
+ df.e_bits[0] = e1e2e3_map[lchan->csd_mode][0]; /* E1 */
+ df.e_bits[1] = e1e2e3_map[lchan->csd_mode][1]; /* E2 */
+ df.e_bits[2] = e1e2e3_map[lchan->csd_mode][2]; /* E3 */
+ }
+
+ osmo_v110_encode_frame(&ra_bits[i * 80], 80, &df);
+ }
+
+ra1_ra2:
+ /* RA1/RA2: convert from an intermediate rate to 64 kbit/s */
+ if (desc->num_blocks == 4) {
+ /* 4 * 80 bits (16 kbit/s) => 2 bits per octet */
+ for (unsigned int i = 0, j = 0; i < RFC4040_RTP_PLEN; i++) {
+ rtp[i] = (0xff >> 2);
+ rtp[i] |= (ra_bits[j++] << 7);
+ rtp[i] |= (ra_bits[j++] << 6);
+ }
+ } else {
+ /* 2 * 80 bits (8 kbit/s) => 1 bit per octet */
+ for (unsigned int i = 0; i < RFC4040_RTP_PLEN; i++) {
+ rtp[i] = (0xff >> 1);
+ rtp[i] |= (ra_bits[i] << 7);
+ }
+ }
+
+ return RFC4040_RTP_PLEN;
+}
+
+int csd_v110_rtp_decode(const struct gsm_lchan *lchan, uint8_t *data,
+ const uint8_t *rtp, size_t rtp_len)
+{
+ const struct csd_v110_frame_desc *desc;
+ ubit_t ra_bits[80 * 4];
+
+ OSMO_ASSERT(lchan->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc));
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].fr;
+ else
+ desc = &csd_v110_lchan_desc[lchan->tch_mode].hr;
+ if (OSMO_UNLIKELY(desc->num_blocks == 0))
+ return -ENOTSUP;
+
+ if (OSMO_UNLIKELY(rtp_len != RFC4040_RTP_PLEN))
+ return -EINVAL;
+
+ /* RA1/RA2: convert from 64 kbit/s to an intermediate rate */
+ if (desc->num_blocks == 4) {
+ /* 4 * 80 bits (16 kbit/s) => 2 bits per octet */
+ for (unsigned int i = 0, j = 0; i < RFC4040_RTP_PLEN; i++) {
+ ra_bits[j++] = (rtp[i] >> 7);
+ ra_bits[j++] = (rtp[i] >> 6) & 0x01;
+ }
+ } else {
+ /* 2 * 80 bits (8 kbit/s) => 1 bit per octet */
+ for (unsigned int i = 0; i < RFC4040_RTP_PLEN; i++)
+ ra_bits[i] = (rtp[i] >> 7);
+ }
+
+ /* RA1'/RA1: convert from an intermediate rate to radio rate */
+ for (unsigned int i = 0; i < desc->num_blocks; i++) {
+ struct osmo_v110_decoded_frame df;
+
+ /* convert a V.110 80-bit frame to a V.110 36-/60-bit frame */
+ osmo_v110_decode_frame(&df, &ra_bits[i * 80], 80);
+ if (desc->num_bits == 60)
+ osmo_csd_12k_6k_encode_frame(&data[i * 60], 60, &df);
+ else /* desc->num_bits == 36 */
+ osmo_csd_3k6_encode_frame(&data[i * 36], 36, &df);
+ }
+
+ return desc->num_blocks * desc->num_bits;
+}
diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c
index 38e22c95..bbe1b0ad 100644
--- a/src/common/dtx_dl_amr_fsm.c
+++ b/src/common/dtx_dl_amr_fsm.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
diff --git a/src/common/gsm_data.c b/src/common/gsm_data.c
new file mode 100644
index 00000000..dad5587b
--- /dev/null
+++ b/src/common/gsm_data.c
@@ -0,0 +1,348 @@
+/* (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/core/statistics.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/codec/ecu.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_trx.h>
+#include <osmo-bts/logging.h>
+
+struct osmo_tdef_group bts_tdef_groups[] = {
+ { .name = "bts", .tdefs = bts_T_defs, .desc = "BTS process timers" },
+ { .name = "abis", .tdefs = abis_T_defs, .desc = "Abis (RSL) related timers" },
+ {}
+};
+
+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_OSMO_DYN, "TCH/F_TCH/H_SDCCH8_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_OSMO_DYN, "Dynamic TCH/F or TCH/H or SDCCH/8 or GPRS PDCH" },
+ { 0, NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+ return get_value_string(gsm_pchant_names, c);
+}
+
+/* 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 char ts2str[255];
+
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts)
+{
+ snprintf(ts2str, sizeof(ts2str),
+ "(" GSM_TS_NAME_FMT ")",
+ GSM_TS_NAME_ARGS(ts));
+
+ 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_OSMO_DYN:
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+ snprintf(ts2str, sizeof(ts2str),
+ "(" GSM_TS_NAME_FMT ",pchan=%s as %s)",
+ GSM_TS_NAME_ARGS(ts),
+ gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(ts->dyn.pchan_is));
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(" GSM_TS_NAME_FMT ",pchan=%s switching %s -> %s)",
+ GSM_TS_NAME_ARGS(ts),
+ 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),
+ "(" GSM_TS_NAME_FMT ",pchan=%s as %s)",
+ GSM_TS_NAME_ARGS(ts),
+ gsm_pchan_name(ts->pchan),
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F");
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(" GSM_TS_NAME_FMT ",pchan=%s switching %s -> %s)",
+ GSM_TS_NAME_ARGS(ts),
+ 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), "(" GSM_TS_NAME_FMT ",pchan=%s)",
+ GSM_TS_NAME_ARGS(ts), gsm_pchan_name(ts->pchan));
+ break;
+ }
+
+ return ts2str;
+}
+
+/* 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;
+
+ switch (cbits) {
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs:
+ if (ts->vamos.peer == NULL)
+ return NULL;
+ ts = ts->vamos.peer;
+ /* fall-through */
+ case ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs:
+ 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_OSMO_DYN)
+ ok = false;
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(0):
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(1):
+ if (ts->vamos.peer == NULL)
+ return NULL;
+ ts = ts->vamos.peer;
+ /* fall-through */
+ case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0):
+ case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(1):
+ lch_idx = cbits & 0x1; /* TCH/H */
+ if (ts->pchan != GSM_PCHAN_TCH_H &&
+ ts->pchan != GSM_PCHAN_OSMO_DYN)
+ ok = false;
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(1):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(2):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(3):
+ lch_idx = cbits & 0x3; /* SDCCH/4 */
+ if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ ok = false;
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(1):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(2):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(3):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(4):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(5):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(6):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(7):
+ lch_idx = cbits & 0x7; /* SDCCH/8 */
+ if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C &&
+ ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH &&
+ ts->pchan != GSM_PCHAN_OSMO_DYN)
+ ok = false;
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_BCCH:
+ case ABIS_RSL_CHAN_NR_CBITS_RACH:
+ case ABIS_RSL_CHAN_NR_CBITS_PCH_AGCH:
+ 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 !!! */
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH:
+ lch_idx = 0;
+ if (ts->pchan != GSM_PCHAN_OSMO_DYN)
+ ok = false;
+ break;
+ default:
+ 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_OSMO_DYN 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(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_OSMO_DYN:
+ 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(const 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(const struct gsm_bts_trx_ts *ts)
+{
+ return pchan_is_tch(ts_pchan(ts));
+}
+
+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_OSMO_DYN:
+ return ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && ts->dyn.pchan_want == ts->dyn.pchan_is;
+ default:
+ return false;
+ }
+}
+
+/* Apply ts->tsc based on what was configured coming from different sources.
+ * Priorities (preferred first, overrides ones afterward):
+ * 1- RSL OSMO_TSC IE
+ * 2- OML SetChannelAttr TSC IE
+ * 3- OML SetBtsAttr BSIC IE
+ */
+void gsm_ts_apply_configured_tsc(struct gsm_bts_trx_ts *ts)
+{
+ if (ts->tsc_rsl_configured) {
+ ts->tsc = ts->tsc_rsl;
+ return;
+ }
+ if (ts->tsc_oml_configured) {
+ ts->tsc = ts->tsc_oml;
+ return;
+ }
+ if (ts->trx->bts->bsic_configured) {
+ ts->tsc = BTS_TSC(ts->trx->bts);
+ return;
+ }
+ ts->tsc = 0xff; /* invalid value */
+}
+
+void gsm_ts_release(struct gsm_bts_trx_ts *ts)
+{
+ unsigned int ln;
+
+ for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) {
+ struct gsm_lchan *lchan = &ts->lchan[ln];
+ gsm_lchan_release(lchan, LCHAN_REL_ACT_OML);
+ }
+ ts->pchan = GSM_PCHAN_NONE;
+ /* Make sure pchan_is is reset, since PCU act_req to release it will be
+ * ignored as the lchan will already be released. */
+ ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+
+ ts->tsc_oml_configured = false;
+ ts->tsc_rsl_configured = false;
+ ts->tsc = ts->tsc_oml = ts->tsc_rsl = 0xff;
+}
diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c
deleted file mode 100644
index f0f5ae20..00000000
--- a/src/common/gsm_data_shared.c
+++ /dev/null
@@ -1,831 +0,0 @@
-/* (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 <osmocom/codec/ecu.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;
- mo->nm_state.administrative = NM_STATE_LOCKED;
-}
-
-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" },
- { BTS_FEAT_ETWS_PN, "ETWS Primary Notification on PCH" },
- { 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++;
-
- 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 */
-static 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 }
-};
-
-/* determine the ECU codec constant for the codec used by given lchan */
-int lchan2ecu_codec(const struct gsm_lchan *lchan)
-{
- struct gsm_bts_trx_ts *ts = lchan->ts;
-
- switch (lchan->tch_mode) {
- case GSM48_CMODE_SPEECH_V1:
- if (ts_pchan(ts) == GSM_PCHAN_TCH_H)
- return OSMO_ECU_CODEC_HR;
- else
- return OSMO_ECU_CODEC_FR;
- break;
- case GSM48_CMODE_SPEECH_EFR:
- return OSMO_ECU_CODEC_EFR;
- case GSM48_CMODE_SPEECH_AMR:
- return OSMO_ECU_CODEC_AMR;
- default:
- return -1;
- }
-}
diff --git a/src/common/handover.c b/src/common/handover.c
index 63a98324..4b2e6bec 100644
--- a/src/common/handover.c
+++ b/src/common/handover.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -51,7 +51,7 @@ static int ho_tx_phys_info(struct gsm_lchan *lchan)
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);
+ msgb_put_u8(msg, lchan->ta_ctrl.current);
rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan),
0x00, 0);
@@ -111,7 +111,8 @@ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay)
"TA=%u, ref=%u\n", gsm_lchant_name(lchan->type), acc_delay, ra);
/* Set timing advance */
- lchan->rqd_ta = acc_delay;
+ lchan->ta_ctrl.current = acc_delay;
+ lchan->want_dl_sacch_active = true;
/* Stop handover detection, wait for valid frame */
lchan->ho.active = HANDOVER_WAIT_FRAME;
@@ -122,7 +123,7 @@ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay)
}
/* Send HANDover DETect to BSC */
- rsl_tx_hando_det(lchan, &lchan->rqd_ta);
+ rsl_tx_hando_det(lchan, &lchan->ta_ctrl.current);
/* Send PHYS INFO */
lchan->ho.phys_info_count = 1;
@@ -135,7 +136,7 @@ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay)
osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000);
}
-/* received frist valid data frame on dedicated channel */
+/* received first valid data frame on dedicated channel */
void handover_frame(struct gsm_lchan *lchan)
{
LOGPLCHAN(lchan, DHO, LOGL_INFO, "First valid frame detected\n");
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index f07e79ca..5f275cd8 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -33,10 +33,13 @@
#include <osmocom/gsm/l1sap.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/rlp.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/utils.h>
+#include <osmocom/codec/codec.h>
+
#include <osmocom/trau/osmo_ortp.h>
#include <osmo-bts/logging.h>
@@ -51,41 +54,17 @@
#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/rtp_input_preen.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 */
-};
+#include <osmo-bts/asci.h>
+#include <osmo-bts/csd_v110.h>
/* 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];
+ int rc = gsm0502_fn2ccch_block(fn);
/* if FN is negative, we were called for something that's not CCCH! */
OSMO_ASSERT(rc >= 0);
return rc;
@@ -94,18 +73,25 @@ unsigned int l1sap_fn2ccch_block(uint32_t fn)
struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx,
unsigned int chan_nr)
{
+ struct gsm_bts_trx_ts *ts;
unsigned int tn, ss;
tn = L1SAP_CHAN2TS(chan_nr);
- OSMO_ASSERT(tn < ARRAY_SIZE(trx->ts));
+ ts = &trx->ts[tn];
+
+ if (L1SAP_IS_CHAN_VAMOS(chan_nr)) {
+ if (ts->vamos.peer == NULL)
+ return NULL;
+ ts = ts->vamos.peer;
+ }
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));
+ OSMO_ASSERT(ss < ARRAY_SIZE(ts->lchan));
- return &trx->ts[tn].lchan[ss];
+ return &ts->lchan[ss];
}
static struct gsm_lchan *
@@ -131,7 +117,8 @@ static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan)
/* 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);
+ const uint32_t num_fn = GSM_TDMA_FN_SUB(fn, lchan->tch.last_fn);
+ samples_passed = num_fn * 12 * 160 / (13 * 4);
/* round number of samples to the nearest multiple of
GSM_RTP_DURATION */
r = samples_passed + GSM_RTP_DURATION / 2;
@@ -145,27 +132,13 @@ static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan)
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 order to wrap femtobts header around 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;
+ const int headroom = L1SAP_MSGB_HEADROOM;
+ const int size = headroom + sizeof(struct osmo_phsap_prim) + l2_len;
struct msgb *msg = msgb_alloc_headroom(size, headroom, "l1sap_prim");
if (!msg)
@@ -176,9 +149,15 @@ struct msgb *l1sap_msgb_alloc(unsigned int l2_len)
return msg;
}
+/* Enclose rmsg into an osmo_phsap primitive and hand it over to the higher
+ * layers. The phsap primitive also contains measurement information. The
+ * parameters rssi, ta_offs and is_sub are only needed when the measurement
+ * information is passed along with the TCH data. When separate measurement
+ * indications are used, those last three parameters may be set to zero. */
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)
+ uint16_t ber10k, int16_t lqual_cb, int8_t rssi,
+ int16_t ta_offs, uint8_t is_sub)
{
struct osmo_phsap_prim *l1sap;
@@ -194,6 +173,10 @@ int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg,
l1sap->u.tch.ber10k = ber10k;
l1sap->u.tch.lqual_cb = lqual_cb;
+ l1sap->u.tch.rssi = rssi;
+ l1sap->u.tch.ta_offs_256bits = ta_offs;
+ l1sap->u.tch.is_sub = is_sub;
+
return l1sap_up(trx, l1sap);
}
@@ -257,37 +240,96 @@ int bts_check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan,
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" },
+uint16_t l1sap_log_ctx_sapi;
+
+const struct value_string l1sap_common_sapi_names[] = {
+ { L1SAP_COMMON_SAPI_UNKNOWN, "UNKNOWN" },
+ /* alphabetic order */
+ { L1SAP_COMMON_SAPI_AGCH, "AGCH" },
+ { L1SAP_COMMON_SAPI_BCCH, "BCCH" },
+ { L1SAP_COMMON_SAPI_CBCH, "CBCH" },
+ { L1SAP_COMMON_SAPI_FACCH_F, "FACCH/F" },
+ { L1SAP_COMMON_SAPI_FACCH_H, "FACCH/H" },
+ { L1SAP_COMMON_SAPI_FCCH, "FCCH" },
+ { L1SAP_COMMON_SAPI_IDLE, "IDLE" },
+ { L1SAP_COMMON_SAPI_NCH, "NCH" },
+ { L1SAP_COMMON_SAPI_PACCH, "PACCH" },
+ { L1SAP_COMMON_SAPI_PAGCH, "PAGCH" },
+ { L1SAP_COMMON_SAPI_PBCCH, "PBCCH" },
+ { L1SAP_COMMON_SAPI_PCH, "PCH" },
+ { L1SAP_COMMON_SAPI_PDTCH, "PDTCH" },
+ { L1SAP_COMMON_SAPI_PNCH, "PNCH" },
+ { L1SAP_COMMON_SAPI_PPCH, "PPCH" },
+ { L1SAP_COMMON_SAPI_PRACH, "PRACH" },
+ { L1SAP_COMMON_SAPI_PTCCH, "PTCCH" },
+ { L1SAP_COMMON_SAPI_RACH, "RACH" },
+ { L1SAP_COMMON_SAPI_SACCH, "SACCH" },
+ { L1SAP_COMMON_SAPI_SCH, "SCH" },
+ { L1SAP_COMMON_SAPI_SDCCH, "SDCCH" },
+ { L1SAP_COMMON_SAPI_TCH_F, "TCH/F" },
+ { L1SAP_COMMON_SAPI_TCH_H, "TCH/H" },
{ 0, NULL }
};
+static enum l1sap_common_sapi get_common_sapi_ph_data(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t link_id = l1sap->u.data.link_id;
+ uint8_t chan_nr = l1sap->u.data.chan_nr;
+ uint32_t u32Fn = l1sap->u.data.fn;
+
+ if (L1SAP_IS_CHAN_TCHF(chan_nr))
+ return L1SAP_COMMON_SAPI_TCH_F;
+
+ if (L1SAP_IS_CHAN_TCHH(chan_nr))
+ return L1SAP_COMMON_SAPI_TCH_H;
+
+ if (L1SAP_IS_CHAN_SDCCH4(chan_nr) || L1SAP_IS_CHAN_SDCCH8(chan_nr))
+ return L1SAP_COMMON_SAPI_SDCCH;
+
+ if (L1SAP_IS_CHAN_BCCH(chan_nr))
+ return L1SAP_COMMON_SAPI_BCCH;
+
+ if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr))
+ /* The sapi depends on DSP configuration, not on the actual SYSTEM INFORMATION 3. */
+ return ((l1sap_fn2ccch_block(u32Fn) >= num_agch(trx, "PH-DATA-REQ"))
+ ? L1SAP_COMMON_SAPI_PCH
+ : L1SAP_COMMON_SAPI_AGCH);
+
+ if (L1SAP_IS_CHAN_CBCH(chan_nr))
+ return L1SAP_COMMON_SAPI_CBCH;
+
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ return L1SAP_COMMON_SAPI_SACCH;
+
+ return L1SAP_COMMON_SAPI_UNKNOWN;
+}
+
+static enum l1sap_common_sapi get_common_sapi_by_trx_prim(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ /* Only downlink prims are relevant */
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ if (ts_is_pdch(&trx->ts[L1SAP_CHAN2TS(l1sap->u.data.chan_nr)]))
+ return ((L1SAP_IS_PTCCH(l1sap->u.data.fn))
+ ? L1SAP_COMMON_SAPI_PTCCH
+ : L1SAP_COMMON_SAPI_PDTCH);
+ return get_common_sapi_ph_data(trx, l1sap);
+ default:
+ return L1SAP_COMMON_SAPI_UNKNOWN;
+ }
+}
+
/* 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,
+static int gsmtap_ph_data(const 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);
+ *len = msgb_l2(msg) ? msgb_l2len(msg) : 0;
chan_nr = l1sap->u.data.chan_nr;
link_id = l1sap->u.data.link_id;
@@ -322,47 +364,63 @@ static int gsmtap_ph_data(struct osmo_phsap_prim *l1sap, uint8_t *chan_type,
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)
+static int gsmtap_pdch(const 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);
+ *len = msgb_l2(msg) ? msgb_l2len(msg) : 0;
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;
+ } else {
+ /* TODO: distinguish PACCH */
+ *chan_type = GSMTAP_CHANNEL_PDTCH;
+ }
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)
+static int gsmtap_ph_rach(const 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;
+ uint8_t chan_nr = l1sap->u.rach_ind.chan_nr;
+ static uint8_t ra_buf[2];
*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;
+ *tn = L1SAP_CHAN2TS(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;
+ else if (L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (L1SAP_IS_PTCCH(*fn)) {
+ /* TODO: calculate sub-slot from frame-number */
+ *chan_type = GSMTAP_CHANNEL_PTCCH;
+ } else {
+ *chan_type = GSMTAP_CHANNEL_PDTCH;
+ }
+ }
+
+ if (l1sap->u.rach_ind.is_11bit) {
+ /* Pack as described in 3GPP TS 44.004, figure 7.4a.b */
+ ra_buf[0] = (uint8_t) (l1sap->u.rach_ind.ra >> 3);
+ ra_buf[1] = (uint8_t) (l1sap->u.rach_ind.ra & 0x07);
+ *len = sizeof(ra_buf);
+ *data = ra_buf;
+ } else {
+ ra_buf[0] = (uint8_t) (l1sap->u.rach_ind.ra & 0xff);
+ *len = sizeof(ra_buf[0]);
+ *data = ra_buf;
+ }
return 0;
}
@@ -375,8 +433,14 @@ static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = {
static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int len)
{
+ if (len != GSM_MACBLOCK_LEN)
+ return false;
+
switch (chan_type) {
case GSMTAP_CHANNEL_AGCH:
+ case GSMTAP_CHANNEL_SDCCH:
+ case GSMTAP_CHANNEL_TCH_F:
+ case GSMTAP_CHANNEL_TCH_H:
if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN))
return true;
break;
@@ -384,6 +448,7 @@ static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int l
if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN))
return true;
break;
+ /* FIXME: implement the same for GSMTAP_CHANNEL_PDTCH from/to PCU */
/* don't use 'default' case here as the above only conditionally return true */
}
return false;
@@ -396,9 +461,11 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
uint8_t chan_type = 0, tn = 0, ss = 0;
uint32_t fn;
uint16_t uplink = GSMTAP_ARFCN_F_UPLINK;
+ int8_t signal_dbm;
int rc;
- if (!gsmtap)
+ struct gsmtap_inst *inst = trx->bts->gsmtap.inst;
+ if (!inst)
return 0;
switch (OSMO_PRIM_HDR(&l1sap->oph)) {
@@ -414,10 +481,12 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
else
rc = gsmtap_ph_data(l1sap, &chan_type, &ss, fn, &data,
&len, num_agch(trx, "GSMTAP"));
+ signal_dbm = l1sap->u.data.rssi;
break;
case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
rc = gsmtap_ph_rach(l1sap, &chan_type, &tn, &ss, &fn, &data,
&len);
+ signal_dbm = l1sap->u.rach_ind.rssi;
break;
default:
rc = -ENOTSUP;
@@ -429,10 +498,10 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
if (len == 0)
return 0;
if ((chan_type & GSMTAP_CHANNEL_ACCH)) {
- if (!gsmtap_sapi_acch)
+ if (!trx->bts->gsmtap.sapi_acch)
return 0;
} else {
- if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask))
+ if (!((1 << (chan_type & 31)) & trx->bts->gsmtap.sapi_mask))
return 0;
}
@@ -441,8 +510,8 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
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);
+ gsmtap_send(inst, trx->arfcn | uplink, tn, chan_type, ss, fn,
+ signal_dbm, 0 /* TODO: SNR */, data, len);
return 0;
}
@@ -487,22 +556,73 @@ static unsigned int calc_exprd_rach_frames(struct gsm_bts *bts, uint32_t fn)
return rach_frames_expired;
}
+static void l1sap_interf_meas_calc_avg(struct gsm_bts_trx *trx)
+{
+ unsigned int tn, ln;
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (ts->mo.nm_state.availability != NM_AVSTATE_OK)
+ continue;
+
+ for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) {
+ struct gsm_lchan *lchan = &ts->lchan[ln];
+
+ lchan->meas.interf_meas_avg_dbm = 0;
+ lchan->meas.interf_band = 0;
+
+ /* There must be at least one sample */
+ if (lchan->meas.interf_meas_num == 0)
+ continue;
+
+ /* Average all collected samples */
+ gsm_lchan_interf_meas_calc_avg(lchan);
+ }
+ }
+}
+
+static void l1sap_interf_meas_report(struct gsm_bts *bts)
+{
+ const uint32_t period = bts->interference.intave * 104;
+ struct gsm_bts_trx *trx;
+
+ if (bts->interference.intave == 0)
+ return;
+ if (bts->gsm_time.fn % period != 0)
+ return;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
+ trx->bb_transc.mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ /* Calculate the average of all received samples */
+ l1sap_interf_meas_calc_avg(trx);
+ /* Report to the BSC over the A-bis/RSL */
+ rsl_tx_rf_res(trx);
+ /* Report to the PCU over the PCUIF */
+ pcu_tx_interf_ind(trx, bts->gsm_time.fn);
+ }
+}
+
/* 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;
- int i;
+ unsigned int frames_expired;
+ unsigned int i;
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;
+ frames_expired = GSM_TDMA_FN_SUB(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",
+ "Invalid condition detected: Frame difference is %"PRIu32"-%"PRIu32"=%u > 1!\n",
info_time_ind->fn, bts->gsm_time.fn, frames_expired);
}
@@ -515,10 +635,14 @@ static int l1sap_info_time_ind(struct gsm_bts *bts,
/* increment number of RACH slots that have passed by since the
* last time indication */
for (i = 0; i < frames_expired; i++) {
- uint32_t fn = (info_time_ind->fn + GSM_HYPERFRAME - i) % GSM_HYPERFRAME;
+ uint32_t fn = GSM_TDMA_FN_SUB(info_time_ind->fn, i);
bts->load.rach.total += calc_exprd_rach_frames(bts, fn);
}
+ /* Report interference levels to the BSC */
+ if (bts_internal_flag_get(bts, BTS_INTERNAL_FLAG_INTERF_MEAS))
+ l1sap_interf_meas_report(bts);
+
return 0;
}
@@ -542,49 +666,93 @@ static inline void set_ms_to_data(struct gsm_lchan *lchan, int16_t data, bool se
}
}
+bool trx_sched_is_sacch_fn(const struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink);
+
/* 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)
+static void process_l1sap_meas_data(struct gsm_lchan *lchan,
+ const struct osmo_phsap_prim *l1sap,
+ enum osmo_ph_prim ind_type)
{
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;
- }
+ const struct info_meas_ind_param *info_meas_ind;
+ const struct ph_data_param *ph_data_ind;
+ const struct ph_tch_param *ph_tch_ind;
+ uint32_t fn;
+ const char *ind_name;
- 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);
+ /* Do not process measurement reports from non-active VGCS calls. */
+ if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt) && lchan->asci.talker_active != VGCS_TALKER_ACTIVE)
+ return;
- /* 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;
+ switch (ind_type) {
+ case PRIM_MPH_INFO:
+ /* (legacy way, see also OS#2977) */
+ info_meas_ind = &l1sap->u.info.u.meas_ind;
+ fn = info_meas_ind->fn;
+ ind_name = "MPH INFO";
+ ulm = (struct bts_ul_meas) {
+ .ta_offs_256bits = info_meas_ind->ta_offs_256bits,
+ .inv_rssi = info_meas_ind->inv_rssi,
+ .ber10k = info_meas_ind->ber10k,
+ .ci_cb = info_meas_ind->c_i_cb,
+ };
+ /* additionally treat SACCH frames (match by TDMA FN) as SUB frames */
+ if (info_meas_ind->is_sub || trx_sched_is_sacch_fn(lchan->ts, fn, true))
+ ulm.is_sub = 1;
+ break;
+ case PRIM_TCH:
+ ph_tch_ind = &l1sap->u.tch;
+ if (ph_tch_ind->rssi == 0)
+ return;
+ fn = ph_tch_ind->fn;
+ ind_name = "TCH";
+ ulm = (struct bts_ul_meas) {
+ .ta_offs_256bits = ph_tch_ind->ta_offs_256bits,
+ .inv_rssi = abs(ph_tch_ind->rssi),
+ .ber10k = ph_tch_ind->ber10k,
+ .ci_cb = ph_tch_ind->lqual_cb,
+ .is_sub = ph_tch_ind->is_sub,
+ };
+ /* PRIM_TCH always carries DCCH, not SACCH */
+ break;
+ case PRIM_PH_DATA:
+ ph_data_ind = &l1sap->u.data;
+ if (ph_data_ind->rssi == 0)
+ return;
+ fn = ph_data_ind->fn;
+ ind_name = "DATA";
+ ulm = (struct bts_ul_meas) {
+ .ta_offs_256bits = ph_data_ind->ta_offs_256bits,
+ .inv_rssi = abs(ph_data_ind->rssi),
+ .ber10k = ph_data_ind->ber10k,
+ .ci_cb = ph_data_ind->lqual_cb,
+ };
+ /* additionally treat SACCH frames (match by RSL link ID) as SUB frames */
+ if (ph_data_ind->is_sub || L1SAP_IS_LINK_SACCH(ph_data_ind->link_id))
+ ulm.is_sub = 1;
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
- 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;
+ LOGPLCFN(lchan, fn, DL1P, LOGL_DEBUG,
+ "%s meas ind, ta_offs_256bits=%d, ber10k=%d, inv_rssi=%u, C/I=%d cB\n", ind_name,
+ ulm.ta_offs_256bits, ulm.ber10k, ulm.inv_rssi, ulm.ci_cb);
/* we assume that symbol period is 1 bit: */
- set_ms_to_data(lchan, info_meas_ind->ta_offs_256bits / 256, true);
+ set_ms_to_data(lchan, ulm.ta_offs_256bits / 256, true);
- lchan_meas_process_measurement(lchan, &ulm, info_meas_ind->fn);
+ lchan_meas_process_measurement(lchan, &ulm, fn);
- return 0;
+ return;
}
-/* any L1 MPH_INFO indication prim recevied from bts model */
+/* any L1 MPH_INFO indication prim received from bts model */
static int l1sap_mph_info_ind(struct gsm_bts_trx *trx,
struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
{
+ const struct info_meas_ind_param *meas_ind;
+ struct gsm_lchan *lchan;
int rc = 0;
switch (info->type) {
@@ -599,7 +767,22 @@ static int l1sap_mph_info_ind(struct gsm_bts_trx *trx,
&info->u.time_ind);
break;
case PRIM_INFO_MEAS:
- rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind);
+ /* We should never get an INFO_IND with PRIM_INFO_MEAS
+ * when BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB is set */
+ if (bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB))
+ OSMO_ASSERT(false);
+
+ meas_ind = &l1sap->u.info.u.meas_ind;
+
+ lchan = get_active_lchan_by_chan_nr(trx, meas_ind->chan_nr);
+ if (!lchan) {
+ LOGPFN(DL1P, LOGL_ERROR, meas_ind->fn,
+ "No lchan for chan_nr=%s\n",
+ rsl_chan_nr_str(meas_ind->chan_nr));
+ return 0;
+ }
+
+ process_l1sap_meas_data(lchan, l1sap, PRIM_MPH_INFO);
break;
default:
LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n",
@@ -616,6 +799,12 @@ static int l1sap_info_act_cnf(struct gsm_bts_trx *trx,
struct info_act_cnf_param *info_act_cnf)
{
struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr);
+ if (lchan == NULL) {
+ LOGPTRX(trx, DL1C, LOGL_ERROR, "get_lchan_by_chan_nr(chan_nr=%s) "
+ "yields NULL for PRIM_INFO_ACTIVATE.conf\n",
+ rsl_chan_nr_str(info_act_cnf->chan_nr));
+ return -ENODEV;
+ }
LOGPLCHAN(lchan, DL1C, LOGL_INFO, "activate confirm chan_nr=%s trx=%d\n",
rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr);
@@ -639,6 +828,12 @@ static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx,
struct info_act_cnf_param *info_act_cnf)
{
struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr);
+ if (lchan == NULL) {
+ LOGPTRX(trx, DL1C, LOGL_ERROR, "get_lchan_by_chan_nr(chan_nr=%s) "
+ "yields NULL for PRIM_INFO_ACTIVATE.conf\n",
+ rsl_chan_nr_str(info_act_cnf->chan_nr));
+ return -ENODEV;
+ }
LOGPLCHAN(lchan, DL1C, LOGL_INFO, "deactivate confirm chan_nr=%s trx=%d\n",
rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr);
@@ -655,7 +850,7 @@ static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx,
return 0;
}
-/* any L1 MPH_INFO confirm prim recevied from bts model */
+/* any L1 MPH_INFO confirm prim received from bts model */
static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx,
struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
{
@@ -692,16 +887,15 @@ static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan,
uint8_t *p;
/* de-queue response message (loopback) */
- loop_msg = msgb_dequeue(&lchan->dl_tch_queue);
+ loop_msg = msgb_dequeue_count(&lchan->dl_tch_queue, &lchan->dl_tch_queue_len);
if (!loop_msg) {
- LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: no looped PDTCH message, sending empty\n",
- gsm_lchan_name(lchan));
+ LOGPLCGT(lchan, tm, DL1P, LOGL_NOTICE, "no looped PDTCH message, sending empty\n");
/* 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));
+ LOGPLCGT(lchan, tm, DL1P, LOGL_NOTICE, "looped PDTCH message of %u bytes\n",
+ 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));
@@ -710,17 +904,34 @@ static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan,
return 0;
}
-/* Check if given CCCH frame number is for a PCH or for an AGCH (this function is
+/* Check if given CCCH frame number is for a NCH, 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) {
+enum ccch_msgt get_ccch_msgt(struct gsm_bts_trx *trx, uint32_t fn)
+{
+ uint8_t block, first_block, num_blocks;
+ int rc;
+
+ block = l1sap_fn2ccch_block(fn);
+
+ /* If there is an NCH, check if the block number matches. It has priority over PCH/AGCH. */
+ if (trx->bts->asci.pos_nch >= 0) {
+ rc = osmo_gsm48_si1ro_nch_pos_decode(trx->bts->asci.pos_nch, &num_blocks, &first_block);
+ if (rc >= 0 && block >= first_block && block < first_block + num_blocks)
+ return CCCH_MSGT_NCH;
+ }
+
/* 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");
+ * transferred to osmo-bts via RSL */
+ if (l1sap_fn2ccch_block(fn) < num_agch(trx, "PH-RTS-IND"))
+ return CCCH_MSGT_AGCH;
+
+ return CCCH_MSGT_PCH;
}
+
/* return the measured average of frame numbers that the RTS clock is running in advance */
-int32_t bts_get_avg_fn_advance(struct gsm_bts *bts)
+int32_t bts_get_avg_fn_advance(const struct gsm_bts *bts)
{
if (bts->fn_stats.avg_count == 0)
return 0;
@@ -729,7 +940,7 @@ int32_t bts_get_avg_fn_advance(struct gsm_bts *bts)
static void l1sap_update_fnstats(struct gsm_bts *bts, uint32_t rts_fn)
{
- int32_t delta = (rts_fn + GSM_HYPERFRAME - bts->gsm_time.fn) % GSM_HYPERFRAME;
+ int32_t delta = GSM_TDMA_FN_SUB(rts_fn, bts->gsm_time.fn);
if (delta < bts->fn_stats.min)
bts->fn_stats.min = delta;
@@ -746,6 +957,100 @@ static void l1sap_update_fnstats(struct gsm_bts *bts, uint32_t rts_fn)
}
}
+/* Common dequeueing function */
+static inline struct msgb *lapdm_phsap_dequeue_msg(struct lapdm_entity *le, uint32_t fn)
+{
+ struct osmo_phsap_prim pp;
+ if (lapdm_phsap_dequeue_prim_fn(le, &pp, fn) < 0)
+ return NULL;
+ return pp.oph.msg;
+}
+
+/* Special dequeueing function with FACCH repetition (3GPP TS 44.006, section 10) */
+static inline struct msgb *lapdm_phsap_dequeue_msg_facch(struct gsm_lchan *lchan, struct lapdm_entity *le, uint32_t fn)
+{
+ struct osmo_phsap_prim pp;
+ struct msgb *msg;
+
+ /* Note: The repeated version of the FACCH block must be scheduled 8 or 9 bursts after the original
+ * transmission. see 3GPP TS 44.006, section 10.2 for a more detailed explaination. */
+ if (lchan->rep_acch.dl_facch[0].msg && GSM_TDMA_FN_SUB(fn, lchan->rep_acch.dl_facch[0].fn) >= 8) {
+ /* Re-use stored FACCH message buffer from SLOT #0 for repetition. */
+ msg = lchan->rep_acch.dl_facch[0].msg;
+ lchan->rep_acch.dl_facch[0].msg = NULL;
+ } else if (lchan->rep_acch.dl_facch[1].msg && GSM_TDMA_FN_SUB(fn, lchan->rep_acch.dl_facch[1].fn) >= 8) {
+ /* Re-use stored FACCH message buffer from SLOT #1 for repetition. */
+ msg = lchan->rep_acch.dl_facch[1].msg;
+ lchan->rep_acch.dl_facch[1].msg = NULL;
+ } else {
+ /* Fetch new FACCH from queue ... */
+ if (lapdm_phsap_dequeue_prim_fn(le, &pp, fn) < 0)
+ return NULL;
+ msg = pp.oph.msg;
+
+ /* Check if the LAPDm frame is a command frame,
+ * see also: 3GPP TS 04.06 section 3.2 and 3.3.2.
+ * If the MS explicitly indicated that repeated ACCH is
+ * supported, than all FACCH frames may be repeated
+ * see also: 3GPP TS 44.006, section 10.3). */
+ if (!(lchan->rep_acch_cap.dl_facch_all || msg->data[0] & 0x02))
+ return msg;
+
+ /* ... and store the message buffer for repetition. */
+ if (lchan->rep_acch.dl_facch[0].msg == NULL) {
+ lchan->rep_acch.dl_facch[0].msg = msgb_copy(msg, "rep_facch_0");
+ lchan->rep_acch.dl_facch[0].fn = fn;
+ } else if (lchan->rep_acch.dl_facch[1].msg == NULL) {
+ lchan->rep_acch.dl_facch[1].msg = msgb_copy(msg, "rep_facch_1");
+ lchan->rep_acch.dl_facch[1].fn = fn;
+ } else {
+ /* By definition 3GPP TS 05.02 does not allow more than two (for TCH/H only one) FACCH blocks
+ * to be transmitted simultaniously. */
+ OSMO_ASSERT(false);
+ }
+ }
+
+ return msg;
+}
+
+/* Special dequeueing function with SACCH repetition (3GPP TS 44.006, section 11) */
+static inline struct msgb *lapdm_phsap_dequeue_msg_sacch(struct gsm_lchan *lchan, struct lapdm_entity *le, uint32_t fn)
+{
+ struct osmo_phsap_prim pp;
+ struct msgb *msg;
+ uint8_t sapi;
+
+ /* Note: When the MS disables SACCH repetition, we still must collect
+ * possible candidates in order to have one ready in case the MS enables
+ * SACCH repetition. */
+
+ if (lchan->rep_acch.dl_sacch_msg) {
+ if (lchan->meas.l1_info.srr_sro == 0) {
+ /* Toss previous repetition candidate */
+ msgb_free(lchan->rep_acch.dl_sacch_msg);
+ lchan->rep_acch.dl_sacch_msg = NULL;
+ } else {
+ /* Use previous repetition candidate */
+ msg = lchan->rep_acch.dl_sacch_msg;
+ lchan->rep_acch.dl_sacch_msg = NULL;
+ return msg;
+ }
+ }
+
+ /* Fetch new repetition candidate from queue */
+ if (lapdm_phsap_dequeue_prim_fn(le, &pp, fn) < 0)
+ return NULL;
+ msg = pp.oph.msg;
+ sapi = (msg->data[0] >> 2) & 0x07;
+
+ /* Only LAPDm frames for SAPI 0 may become a repetition
+ * candidate. */
+ if (sapi == 0)
+ lchan->rep_acch.dl_sacch_msg = msgb_copy(msg, "rep_sacch");
+
+ return msg;
+}
+
/* 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)
@@ -756,12 +1061,12 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
uint8_t chan_nr, link_id;
uint8_t tn;
uint32_t fn;
- uint8_t *p, *si;
+ uint8_t *p = NULL;
+ uint8_t *si;
struct lapdm_entity *le;
- struct osmo_phsap_prim pp;
+ struct msgb *pp_msg;
bool dtxd_facch = false;
int rc;
- int is_ag_res;
chan_nr = rts_ind->chan_nr;
link_id = rts_ind->link_id;
@@ -793,10 +1098,10 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
} else {
/* forward RTS.ind to PCU */
if (L1SAP_IS_PTCCH(rts_ind->fn)) {
- pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */,
+ pcu_tx_rts_req(&trx->ts[tn], 1, fn, trx->arfcn,
L1SAP_FN2PTCCHBLOCK(fn));
} else {
- pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */,
+ pcu_tx_rts_req(&trx->ts[tn], 0, fn, trx->arfcn,
L1SAP_FN2MACBLOCK(fn));
}
/* return early, PCU takes care of rest */
@@ -820,19 +1125,45 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
rsl_chan_nr_str(chan_nr));
return 0;
}
+ if (lchan->pending_rel_ind_msg) {
+ LOGPLCGT(lchan, &g_time, DRSL, LOGL_INFO, "Forward RLL RELease INDication to the BSC\n");
+ abis_bts_rsl_sendmsg(lchan->pending_rel_ind_msg);
+ lchan->pending_rel_ind_msg = NULL;
+ }
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;
+ if (lchan->rep_acch.ul_sacch_active)
+ p[0] |= 0x40; /* See also: 3GPP TS 44.004, section 7.1 */
+ p[1] = lchan->ta_ctrl.current;
le = &lchan->lapdm_ch.lapdm_acch;
+ if (lchan->rep_acch_cap.dl_sacch) {
+ /* Check if MS requests SACCH repetition and update state accordingly */
+ if (lchan->meas.l1_info.srr_sro) {
+ if (lchan->rep_acch.dl_sacch_active == false)
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-SACCH repetition: inactive => active\n");
+ lchan->rep_acch.dl_sacch_active = true;
+ } else {
+ if (lchan->rep_acch.dl_sacch_active == true)
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-SACCH repetition: active => inactive\n");
+ lchan->rep_acch.dl_sacch_active = false;
+ }
+ pp_msg = lapdm_phsap_dequeue_msg_sacch(lchan, le, fn);
+ } else {
+ pp_msg = lapdm_phsap_dequeue_msg(le, fn);
+ }
} else {
if (lchan->ts->trx->bts->dtxd)
dtxd_facch = true;
le = &lchan->lapdm_ch.lapdm_dcch;
+ if (lchan->rep_acch.dl_facch_active && lchan->rsl_cmode != RSL_CMOD_SPD_SIGN)
+ pp_msg = lapdm_phsap_dequeue_msg_facch(lchan, le, fn);
+ else
+ pp_msg = lapdm_phsap_dequeue_msg(le, fn);
+ lchan->tch.dtx_fr_hr_efr.dl_facch_stealing = (pp_msg != NULL);
}
- rc = lapdm_phsap_dequeue_prim(le, &pp);
- if (rc < 0) {
+ if (!pp_msg) {
if (L1SAP_IS_LINK_SACCH(link_id)) {
/* No SACCH data from LAPDM pending, send SACCH filling */
uint8_t *si = lchan_sacch_get(lchan);
@@ -841,6 +1172,10 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2);
} else
memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2);
+ } else if (vgcs_is_uplink_free(lchan)) {
+ /* If UPLINK FREE message is stored, send it with every DCCH frame. */
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ vgcs_uplink_free_get(lchan, p);
} 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)) {
/*
@@ -857,21 +1192,28 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
} 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);
+ memcpy(p + 2, pp_msg->data + 2, GSM_MACBLOCK_LEN - 2);
else {
p = msgb_put(msg, GSM_MACBLOCK_LEN);
- memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN);
+ memcpy(p, pp_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);
+ check_for_ciph_cmd(pp_msg, lchan, chan_nr);
if (dtxd_facch)
dtx_dispatch(lchan, E_FACCH);
+ if (rsl_chan_rt_is_vgcs(lchan->rsl_chan_rt)) {
+ /* Check for UPLINK FREE message and store. */
+ if (pp_msg->data[0] == GSM48_MT_RR_SH_UL_FREE << 2)
+ vgcs_uplink_free_set(lchan, pp_msg->data);
+ /* Keep UPLINK FREE message when sending short header messages. */
+ else if ((pp_msg->data[0] & 0x03) != 0x00)
+ vgcs_uplink_free_reset(lchan);
+ }
}
- msgb_free(pp.oph.msg);
+ msgb_free(pp_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);
+ rc = bts_ccch_copy_msg(trx->bts, p, &g_time, get_ccch_msgt(trx, fn));
if (rc <= 0)
memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
}
@@ -884,48 +1226,270 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
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)
+/* The following static functions are helpers for l1sap_tch_rts_ind(),
+ * used only for FR/HR/EFR speech modes. For these speech TCH modes,
+ * if our incoming RTP stream includes SID frames, we need to apply
+ * the following transformations to the downlink frame stream we actually
+ * transmit:
+ *
+ * - We need to cache the last received SID and retransmit it in
+ * SACCH-aligned frame positions, or if the SACCH-aligned frame
+ * position was stolen by FACCH, then right after that FACCH.
+ *
+ * - That cached SID needs to be aged and expired, in accord with
+ * TS 28.062 section C.3.2.1.1 paragraph 5 - the paragraph concerning
+ * expiration of cached downlink SID.
+ *
+ * - In all other frame positions, extraneous SID frames need to be
+ * dropped - we send an empty payload to the BTS model, causing it
+ * to transmit an induced BFI condition on the air (fake DTXd),
+ * or perhaps real DTXd (actually turning off Tx) in the future.
+ */
- if(payload_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl))
+/*! \brief Check if the given FN of TCH-RTS-IND corresponds to a mandatory
+ * SID position for non-AMR codecs that follow SACCH alignment.
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] fn Frame Number for which we check scheduling
+ * \returns true if this FN is a mandatory SID position, false otherwise
+ */
+static inline bool fr_hr_efr_sid_position(struct gsm_lchan *lchan, uint32_t fn)
+{
+ /* See GSM 05.08 section 8.3 for frame numbers - but we are
+ * specifically looking for FNs corresponding to the beginning
+ * of the complete SID frame to be transmitted, rather than all FNs
+ * where we have to put out a non-suppressed burst. */
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return fn % 104 == 52;
+ case GSM_LCHAN_TCH_H:
+ switch (lchan->nr) {
+ case 0:
+ return fn % 104 == 0 || fn % 104 == 52;
+ case 1:
+ return fn % 104 == 14 || fn % 104 == 66;
+ default:
+ return false;
+ }
+ default:
return false;
+ }
+}
+/*! \brief This helper function implements DTXd input processing for FR/HR/EFR:
+ * we got an RTP input frame, now we need to check if it is SID or not,
+ * and update our DL SID reshaper state accordingly.
+ * \param[in] lchan Logical channel structure of the TCH we work with
+ * \param[in] resp_msg The input frame from RTP
+ * \param[out] sid_result Output flag indicating if the received frame is SID
+ * \returns true if the frame in resp_msg is good, false otherwise
+ */
+static bool fr_hr_efr_dtxd_input(struct gsm_lchan *lchan, struct msgb *resp_msg,
+ bool *sid_result)
+{
+ enum osmo_gsm631_sid_class sidc;
+ bool is_sid;
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ sidc = osmo_fr_sid_classify(msgb_l2(resp_msg));
+ switch (sidc) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ is_sid = false;
+ break;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ /* TS 28.062 section C.3.2.1.1: invalid SIDs
+ * from call leg A UL are treated like BFIs.
+ * Drop this frame and act as if we got nothing
+ * at all from RTP for this FN. */
+ return false;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ is_sid = true;
+ /* The SID code word may have a one bit error -
+ * rejuvenate it. */
+ osmo_fr_sid_reset(msgb_l2(resp_msg));
+ break;
+ default:
+ /* SID classification per GSM 06.31 section
+ * 6.1.1 has only 3 possible outcomes. */
+ OSMO_ASSERT(0);
+ }
+ } else {
+ /* The same kind of classification as we do in
+ * osmo_{fr,efr}_sid_classify() is impossible for HR:
+ * the equivalent ternary SID classification per
+ * GSM 06.41 can only be done in the UL-handling BTS,
+ * directly coupled to the GSM 05.03 channel decoder,
+ * and cannot be reconstructed downstream from frame
+ * payload bits. The only kind of SID we can detect
+ * here is the perfect, error-free kind. However,
+ * if we received RFC 5993 payload and the sender
+ * told us it is valid SID, honor that indication
+ * and rejuvenate the SID codeword. */
+ if (rtpmsg_is_rfc5993_sid(resp_msg)) {
+ is_sid = true;
+ osmo_hr_sid_reset(msgb_l2(resp_msg));
+ } else {
+ is_sid = osmo_hr_check_sid(msgb_l2(resp_msg),
+ msgb_l2len(resp_msg));
+ }
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ /* same logic as for FRv1 */
+ sidc = osmo_efr_sid_classify(msgb_l2(resp_msg));
+ switch (sidc) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ is_sid = false;
+ break;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ return false;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ is_sid = true;
+ osmo_efr_sid_reset(msgb_l2(resp_msg));
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ break;
+ default:
+ /* This static function should never be called except for
+ * V1 and EFR speech modes. */
+ OSMO_ASSERT(0);
+ }
+ *sid_result = is_sid;
+ lchan->tch.dtx_fr_hr_efr.last_rtp_input_was_sid = is_sid;
+ if (is_sid) {
+ uint8_t copy_len;
+
+ copy_len = OSMO_MIN(msgb_l2len(resp_msg),
+ ARRAY_SIZE(lchan->tch.dtx_fr_hr_efr.last_sid));
+ memcpy(lchan->tch.dtx_fr_hr_efr.last_sid,
+ msgb_l2(resp_msg), copy_len);
+ lchan->tch.dtx_fr_hr_efr.last_sid_len = copy_len;
+ lchan->tch.dtx_fr_hr_efr.last_sid_age = 0;
+ } else {
+ /* We got a speech frame, not SID - therefore, the state flag
+ * of "we already transmitted a SID" needs to be cleared,
+ * so that the very first SID that follows this talkspurt
+ * will get transmitted right away, without waiting for
+ * the next mandatory SID position. */
+ lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted = false;
+ }
return true;
}
-static bool rtppayload_is_valid(struct gsm_lchan *lchan, struct msgb *resp_msg)
+/*! \brief This helper function implements DTXd output processing for FR/HR/EFR:
+ * here we update our state to deal with cached SID aging, mandatory-Tx
+ * frame positions and FACCH stealing, and we make the desired output
+ * transformations of either regurgitating a cached SID or vice-versa,
+ * dropping a SID we received from RTP.
+ * \param[in] lchan Logical channel structure of the TCH we work with
+ * \param[in] fn Frame Number for which we are preparing DL
+ * \param[in] current_input_is_sid Self-explanatory
+ * \param[in] resp_msg_p Pointer to l1sap_tch_rts_ind() internal variable
+ * \param[in] resp_l1sap_p ditto
+ * \param[in] empty_l1sap_p ditto
+ *
+ * This function gets called with pointers to l1sap_tch_rts_ind() internal
+ * variables and cannot be properly understood on its own, without
+ * understanding the parent function first. This situation is unfortunate,
+ * but it was the only way to factor the present logic out of
+ * l1sap_tch_rts_ind() main body.
+ */
+static void fr_hr_efr_dtxd_output(struct gsm_lchan *lchan, uint32_t fn,
+ bool current_input_is_sid,
+ struct msgb **resp_msg_p,
+ struct osmo_phsap_prim **resp_l1sap_p,
+ struct osmo_phsap_prim *empty_l1sap_p)
{
- /* 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)) {
- LOGPLCHAN(lchan, DL1P, LOGL_NOTICE,
- "RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n",
- osmo_hexdump(resp_msg->data, resp_msg->len));
- return false;
+ struct msgb *resp_msg = *resp_msg_p;
+ bool sid_in_hand = current_input_is_sid;
+
+ /* Are we at a mandatory-Tx frame position? If so, clear the state flag
+ * of "we already transmitted a SID in this window" - as of right now,
+ * a SID has _not_ been transmitted in the present window yet, and if
+ * we are in a DTX pause, we do need to transmit a SID update as soon
+ * as we are able to, FACCH permitting. */
+ if (fr_hr_efr_sid_position(lchan, fn))
+ lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted = false;
+
+ /* The next stanza implements logic that was originally meant to reside
+ * in the TFO block in TRAUs: if the source feeding us RTP is in a DTXu
+ * pause (resp_msg == NULL, meaning we got nothing from RTP) *and* the
+ * most recent RTP input we did get was a SID, and that SID hasn't
+ * expired, then we need to replicate that SID. */
+ if (!resp_msg && lchan->tch.dtx_fr_hr_efr.last_rtp_input_was_sid &&
+ lchan->tch.dtx_fr_hr_efr.last_sid_age < 47) {
+ /* Whatever we do further below, any time another 20 ms window
+ * has passed since the last SID was received in RTP, we have
+ * to age that cached SID. */
+ lchan->tch.dtx_fr_hr_efr.last_sid_age++;
+
+ /* The following conditional checking dl_sid_transmitted flag
+ * is peculiar to our sans-E1 architecture. In the original
+ * T1/E1 Abis architecture the TFO-enabled TRAU would repeat
+ * the cached SID from call leg A into *every* 20 ms frame in
+ * its Abis output, and then the BTS would select which ones
+ * it should transmit per the rules of GSM 06.31/06.81 section
+ * 5.1.2. But in our architecture it would be silly and
+ * wasteful to allocate and fill an msgb for this cached SID
+ * only to toss it later in the same function - hence the
+ * logic of section 5.1.2 is absorbed into the decision right
+ * here to proceed with cached SID regurgitation or not,
+ * in the form of the following conditional. */
+ if (!lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted)
+ resp_msg = l1sap_msgb_alloc(lchan->tch.dtx_fr_hr_efr.last_sid_len);
+ if (resp_msg) {
+ resp_msg->l2h = msgb_put(resp_msg,
+ lchan->tch.dtx_fr_hr_efr.last_sid_len);
+ memcpy(resp_msg->l2h, lchan->tch.dtx_fr_hr_efr.last_sid,
+ lchan->tch.dtx_fr_hr_efr.last_sid_len);
+ *resp_msg_p = resp_msg;
+ *resp_l1sap_p = msgb_l1sap_prim(resp_msg);
+ sid_in_hand = true;
+ }
+ } else if (resp_msg && sid_in_hand) {
+ /* This "else if" leg implements the logic of section 5.1.2
+ * for cases when a SID is already present in the RTP input,
+ * as indicated by (resp_msg != NULL) and sid_in_hand being
+ * true. Because we are in the "else" clause of the big "if"
+ * above, this path executes only when the SID has come from
+ * RTP in _this_ frame, rather than regurgitated from cache.
+ * But be it fresh or cached, the rules of section 5.1.2 still
+ * apply, and if we've already transmitted a SID in the current
+ * window, then we need to suppress further SIDs and send
+ * an empty payload to the BTS model, causing the latter
+ * to transmit an induced BFI condition on the air. This
+ * strange-seeming behavior is needed so that the spec-compliant
+ * Rx DTX handler in the MS will produce the expected output,
+ * same as if the GSM network were the old-fashioned kind with
+ * Abis TRAUs and TFO. */
+ if (lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted) {
+ msgb_free(resp_msg);
+ resp_msg = NULL;
+ *resp_msg_p = NULL;
+ *resp_l1sap_p = empty_l1sap_p;
+ }
}
- return true;
+
+ /* The following conditional answers this question: are we actually
+ * transmitting a SID frame on the air right now, at this frame number?
+ * If we are certain the BTS model is going to transmit this SID,
+ * we set the state flag so we won't be transmitting any more SIDs
+ * until we either hit the next mandatory-Tx position or get another
+ * little talkspurt followed by new SID. The check for FACCH stealing
+ * is included because if the BTS model is going to transmit FACCH in
+ * the current FN, then we are not actually transmitting SID right now,
+ * and we still need to transmit a SID ASAP, as soon as the TCH becomes
+ * becomes free from FACCH activity. GSM 06.31/06.81 section 5.1.2
+ * does mention that if the mandatory-Tx frame position is taken up
+ * by FACCH, then we need to send SID in the following frame. */
+ if (resp_msg && sid_in_hand && !lchan->tch.dtx_fr_hr_efr.dl_facch_stealing)
+ lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted = true;
}
-/* TCH-RTS-IND prim recevied from bts model */
+/* TCH-RTS-IND prim received 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)
{
@@ -935,19 +1499,19 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx,
struct gsm_lchan *lchan;
uint8_t chan_nr, marker = 0;
uint32_t fn;
- int rc;
+ bool is_fr_hr_efr_sid = false;
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;
+ } else {
+ LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "Rx TCH-RTS.ind\n");
}
if (!lchan->loopback && lchan->abis_ip.rtp_socket) {
@@ -961,13 +1525,9 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx,
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);
+ resp_msg = msgb_dequeue_count(&lchan->dl_tch_queue, &lchan->dl_tch_queue_len);
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;
+ LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "DL TCH Tx queue underrun\n");
resp_l1sap = &empty_l1sap;
} else {
/* Obtain RTP header Marker bit from control buffer */
@@ -977,16 +1537,27 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx,
msgb_push(resp_msg, sizeof(*resp_l1sap));
resp_msg->l1h = resp_msg->data;
resp_l1sap = msgb_l1sap_prim(resp_msg);
+
+ /* FR/HR/EFR SID or non-SID input handling */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 ||
+ lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) {
+ bool drop;
+
+ drop = !fr_hr_efr_dtxd_input(lchan, resp_msg,
+ &is_fr_hr_efr_sid);
+ if (OSMO_UNLIKELY(drop)) {
+ msgb_free(resp_msg);
+ resp_msg = NULL;
+ resp_l1sap = &empty_l1sap;
+ }
+ }
}
- /* 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;
+ /* FR/HR/EFR DTXd output stage */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 ||
+ lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) {
+ fr_hr_efr_dtxd_output(lchan, fn, is_fr_hr_efr_sid, &resp_msg,
+ &resp_l1sap, &empty_l1sap);
}
memset(resp_l1sap, 0, sizeof(*resp_l1sap));
@@ -996,48 +1567,67 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx,
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));
+ LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "Tx TCH.req\n");
l1sap_down(trx, resp_l1sap);
return 0;
}
+/* Reset link timeout to current value. */
+void radio_link_timeout_reset(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ lchan->s = bts->radio_link_timeout.current;
+}
+
/* 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)
+static void radio_link_timeout(struct gsm_lchan *lchan, bool bad_frame)
{
struct gsm_bts *bts = lchan->ts->trx->bts;
+ /* Bypass radio link timeout on VGCS/VBS channels: There is no
+ * uplink SACCH on these when talker is not active. */
+ if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt) && lchan->asci.talker_active != VGCS_TALKER_ACTIVE)
+ return;
+
/* Bypass radio link timeout if set to -1 */
- if (bts->radio_link_timeout < 0)
+ if (bts->radio_link_timeout.current < 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));
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
+ "radio link timeout counter S is already 0\n");
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)
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
+ "decreasing radio link timeout counter S=%d -> %d\n",
+ lchan->s, lchan->s - 1);
+ lchan->s--; /* count down radio link counter S */
+ if (lchan->s == 0) {
+ LOGPLCHAN(lchan, DMEAS, LOGL_NOTICE,
+ "radio link timeout counter S reached zero, "
+ "dropping connection\n");
rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ }
return;
}
- if (lchan->s < bts->radio_link_timeout) {
+ if (lchan->s < bts->radio_link_timeout.current) {
/* 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);
+ int s = lchan->s + 2;
+ if (s > bts->radio_link_timeout.current)
+ s = bts->radio_link_timeout.current;
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
+ "increasing radio link timeout counter S=%d -> %d\n",
+ lchan->s, s);
+ lchan->s = s;
}
}
@@ -1074,6 +1664,59 @@ int bts_check_for_first_ciphrd(struct gsm_lchan *lchan,
return check_for_first_ciphrd(lchan, data, len);
}
+/* Decide if repeated UL-SACCH should be applied or not. If the BER level, of
+ * the received SACCH blocks rises above a certain threshold UL-SACCH
+ * repetition is enabled */
+static void repeated_ul_sacch_active_decision(struct gsm_lchan *lchan,
+ uint16_t ber10k)
+{
+ uint16_t upper = 0;
+ uint16_t lower = 0;
+ bool prev_repeated_ul_sacch_active = lchan->rep_acch.ul_sacch_active;
+
+ /* This is an optimization so that we exit as quickly as possible if
+ * there are no uplink SACCH repetition capabilities present.
+ * However If the repeated UL-SACCH capabilities vanish for whatever
+ * reason, we must be sure that UL-SACCH repetition is disabled. */
+ if (!lchan->rep_acch_cap.ul_sacch) {
+ lchan->rep_acch.ul_sacch_active = false;
+ goto out;
+ }
+
+ /* Threshold disabled (repetition is always on) */
+ if (lchan->rep_acch_cap.rxqual == 0) {
+ lchan->rep_acch.ul_sacch_active = true;
+ goto out;
+ }
+
+ /* convert from RXQUAL value to ber10k value.
+ * see also GSM 05.08, section 8.2.4 (first table, without frame */
+ static const uint16_t ber10k_by_rxqual_upper[] =
+ { 0, 20, 40, 80, 160, 320, 640, 1280 };
+ static const uint16_t ber10k_by_rxqual_lower[] =
+ { 0, 0, 0, 20, 40, 80, 160, 320 };
+ /* Note: The values in the upper vector are taken from the left side
+ * of the table in GSM 05.08, section 8.2.4. The lower vector is just
+ * the upper vector shifted by 2. */
+
+ upper = ber10k_by_rxqual_upper[lchan->rep_acch_cap.rxqual];
+ lower = ber10k_by_rxqual_lower[lchan->rep_acch_cap.rxqual];
+
+ /* If upper/rxqual == 0, then repeated UL-SACCH is always on */
+ if (ber10k >= upper)
+ lchan->rep_acch.ul_sacch_active = true;
+ else if (ber10k <= lower)
+ lchan->rep_acch.ul_sacch_active = false;
+
+out:
+ if (lchan->rep_acch.ul_sacch_active == prev_repeated_ul_sacch_active)
+ return;
+ if (lchan->rep_acch.ul_sacch_active)
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "UL-SACCH repetition: inactive => active\n");
+ else
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "UL-SACCH repetition: active => inactive\n");
+}
+
/* 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)
@@ -1087,10 +1730,8 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
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;
@@ -1105,36 +1746,33 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
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)) {
+ if (lchan && lchan->loopback) {
/* 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);
+ lchan_dl_tch_queue_enqueue(lchan, msg, 1);
/* 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;
+ /* There can be no DATA.ind on PTCCH/U (rather RACH.ind instead), but some
+ * BTS models with buggy implementation may still be sending them to us. */
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);
+ LOGPGT(DL1P, LOGL_NOTICE, &g_time, "There can be no DATA.ind on PTCCH/U. "
+ "This is probably a bug of the BTS model you're using, please fix!\n");
+ return -EINVAL;
}
+
+ /* Drop all data from incomplete UL block */
+ if (pr_info != PRES_INFO_BOTH)
+ len = 0;
+
+ /* PDTCH / PACCH frame handling */
+ pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PDTCH, fn, trx->arfcn,
+ L1SAP_FN2MACBLOCK(fn), data, len, data_ind->rssi, data_ind->ber10k,
+ data_ind->ta_offs_256bits/64, data_ind->lqual_cb);
return 0;
}
@@ -1144,10 +1782,37 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
return 0;
}
+ /* The ph_data_param contained in the l1sap primitive may contain
+ * measurement data. If this data is present, forward it for
+ * processing */
+ if (bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB))
+ process_l1sap_meas_data(lchan, l1sap, PRIM_PH_DATA);
+
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ repeated_ul_sacch_active_decision(lchan, data_ind->ber10k);
+
+ /* Radio Link Timeout counter */
+ if (len == 0) {
+ LOGPLCGT(lchan, &g_time, DL1P, LOGL_INFO, "Lost SACCH block\n");
+ radio_link_timeout(lchan, true);
+ } else {
+ radio_link_timeout(lchan, false);
+ }
+
+ /* Trigger the measurement reporting/processing logic */
+ lchan_meas_handle_sacch(lchan, msg);
+ }
+
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ le = &lchan->lapdm_ch.lapdm_acch;
+ else
+ le = &lchan->lapdm_ch.lapdm_dcch;
+
/* bad frame */
if (len == 0) {
- if (L1SAP_IS_LINK_SACCH(link_id))
- radio_link_timeout(lchan, 1);
+ /* Notify current receive FN to lapdm. */
+ lapdm_t200_fn(le, fn);
+
return -EINVAL;
}
@@ -1155,39 +1820,221 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
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 (rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) {
+ /* report first valid received frame to VGCS talker process */
+ if (lchan->asci.talker_active == VGCS_TALKER_WAIT_FRAME)
+ vgcs_talker_frame(lchan);
+ /* Do not forward any message that is received on the uplink to LAPD while
+ * the uplink is not active. If the MS did not recognize (fast enough) that
+ * the uplink is free, it may continue to transmit LAPD messages. A
+ * response by LAPD to these messages is not desired and not required. If
+ * LAPD would respond, it would cause stopping transmission of UPLINK FREE
+ * messages. No MS could access the uplink anymore.
+ */
+ if (lchan->asci.talker_active != VGCS_TALKER_ACTIVE)
+ return 0;
+ }
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;
+ msgb_pull_to_l2(msg);
lapdm_phsap_up(&l1sap->oph, le);
+ /* Notify current receive FN to lapdm. */
+ lapdm_t200_fn(le, fn);
+
/* don't free, because we forwarded data */
return 1;
}
+/* process one MAC block of unpacked bits of a non-transparent CSD channel */
+static void gsmtap_csd_rlp_process(struct gsm_lchan *lchan, bool is_uplink,
+ const struct ph_tch_param *tch_ind,
+ const uint8_t *data, unsigned int data_len)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct gsmtap_inst *inst = trx->bts->gsmtap.inst;
+ struct osmo_rlp_frame_decoded rlpf;
+ pbit_t *rlp_buf;
+ uint16_t arfcn;
+ int byte_len;
+
+ if (!inst || !trx->bts->gsmtap.rlp)
+ return;
+
+ if (lchan->csd_mode != LCHAN_CSD_M_NT)
+ return;
+
+ if (is_uplink)
+ rlp_buf = lchan->tch.csd.rlp_buf_ul;
+ else
+ rlp_buf = lchan->tch.csd.rlp_buf_dl;
+
+ /* TCH/F 9.6: 4x60bit block => 240bit RLP frame
+ * TCH/F 4.8: 2x 2x60bit blocks starting at B0/B2/B4 => 240bit RLP frame
+ * TCH/H 4.8: 4x60bit block => 240bit RLP frame
+ * TCH/F 2.4: 2x36bit blocks => transparent only
+ * TCH/H 2.4: 4x36bit blocks => transparent only
+ * TCH/F 14.4: 2x 290 bit block (starting with M1=0) => 576-bit RLP frame
+ */
+
+ if (lchan->type == GSM_LCHAN_TCH_F && lchan->tch_mode == GSM48_CMODE_DATA_6k0) {
+ /* in this mode we have 120bit MAC blocks; two of them need to be concatenated
+ * to render a 240-bit RLP frame. The fist block is present in B0/B2/B4.
+ * The E7 bit is used to indicate the Frame MF0a */
+ OSMO_ASSERT(data_len == 120);
+ ubit_t e7 = data[4*7+3];
+ if (e7 == 0) {
+ osmo_ubit2pbit_ext(rlp_buf, 0, data, 0, data_len, 1);
+ return;
+ } else {
+ osmo_ubit2pbit_ext(rlp_buf, 120, data, 0, data_len, 1);
+ byte_len = 240/8;
+ }
+ } else if (lchan->type == GSM_LCHAN_TCH_F && lchan->tch_mode == GSM48_CMODE_DATA_14k5) {
+ /* in this mode we have 290bit MAC blocks containing M1, M2 and 288 data bits;
+ * two of them need to be concatenated to render a
+ * 576-bit RLP frame. The start of a RLP frame is
+ * denoted by a block with M1-bit set to 0. */
+ OSMO_ASSERT(data_len == 290);
+ ubit_t m1 = data[0];
+ if (m1 == 0) {
+ osmo_ubit2pbit_ext(rlp_buf, 0, data, 2, data_len, 1);
+ return;
+ } else {
+ osmo_ubit2pbit_ext(rlp_buf, 288, data, 2, data_len, 1);
+ byte_len = 576/8;
+ }
+ } else {
+ byte_len = osmo_ubit2pbit_ext(rlp_buf, 0, data, 0, data_len, 1);
+ }
+
+ if (trx->bts->gsmtap.rlp_skip_null) {
+ int rc = osmo_rlp_decode(&rlpf, 0, rlp_buf, byte_len);
+ if (rc == 0 && rlpf.ftype == OSMO_RLP_FT_U && rlpf.u_ftype == OSMO_RLP_U_FT_NULL)
+ return;
+ }
+
+ arfcn = trx->arfcn;
+ if (is_uplink)
+ arfcn |= GSMTAP_ARFCN_F_UPLINK;
+
+ gsmtap_send_ex(inst, GSMTAP_TYPE_GSM_RLP, arfcn, lchan->ts->nr,
+ lchan->type == GSM_LCHAN_TCH_H ? GSMTAP_CHANNEL_VOICE_H : GSMTAP_CHANNEL_VOICE_F,
+ lchan->nr, tch_ind->fn, tch_ind->rssi, 0, rlp_buf, byte_len);
+
+}
+
+static void send_ul_rtp_packet_data(struct gsm_lchan *lchan, const struct ph_tch_param *tch_ind,
+ const uint8_t *data, uint16_t data_len)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ uint8_t rtp_pl[RFC4040_RTP_PLEN];
+ int rc;
+
+ gsmtap_csd_rlp_process(lchan, true, tch_ind, data, data_len);
+
+ rc = csd_v110_rtp_encode(lchan, &rtp_pl[0], data, data_len);
+ if (rc < 0)
+ return;
+
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_TOTAL);
+ if (lchan->rtp_tx_marker)
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_MARKER);
+
+ osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket,
+ &rtp_pl[0], sizeof(rtp_pl),
+ fn_ms_adj(tch_ind->fn, lchan),
+ lchan->rtp_tx_marker);
+ /* Only clear the marker bit once we have sent a RTP packet with it */
+ lchan->rtp_tx_marker = false;
+}
+
+/* a helper function for the logic in l1sap_tch_ind() */
+static void send_ul_rtp_packet_speech(struct gsm_lchan *lchan, uint32_t fn,
+ const uint8_t *rtp_pl, uint16_t rtp_pl_len)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ if (lchan->abis_ip.osmux.use) {
+ lchan_osmux_send_frame(lchan, rtp_pl, rtp_pl_len,
+ fn_ms_adj(fn, lchan), lchan->rtp_tx_marker);
+ } else if (lchan->abis_ip.rtp_socket) {
+ osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket,
+ rtp_pl, rtp_pl_len, fn_ms_adj(fn, lchan), lchan->rtp_tx_marker);
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_TOTAL);
+ if (lchan->rtp_tx_marker)
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_MARKER);
+ }
+ /* Only clear the marker bit once we have sent a RTP packet with it */
+ lchan->rtp_tx_marker = false;
+}
+
+/* a helper function for emitting HR1 UL in RFC 5993 format */
+static void send_rtp_rfc5993(struct gsm_lchan *lchan, uint32_t fn,
+ struct msgb *msg)
+{
+ uint8_t toc;
+
+ OSMO_ASSERT(msg->len == GSM_HR_BYTES);
+ /* FIXME: implement proper SID classification per GSM 06.41 section
+ * 6.1.1; see OS#6036. Until then, detect error-free SID frames
+ * using our existing osmo_hr_check_sid() function. */
+ if (osmo_hr_check_sid(msg->data, msg->len))
+ toc = 0x20;
+ else
+ toc = 0x00;
+ msgb_push_u8(msg, toc);
+ send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len);
+}
+
+/* A helper function for l1sap_tch_ind(): handling BFI
+ *
+ * Please note that we pass the msgb to this function, even though it is
+ * currently not used. This msgb passing is a provision for adding
+ * support for TRAU-UL-like RTP payload formats like TW-TS-001 that allow
+ * indicating BFI along with deemed-bad frame data bits, just like
+ * GSM 08.60 and 08.61 TRAU-UL frames.
+ */
+static void tch_ul_bfi_handler(struct gsm_lchan *lchan,
+ const struct gsm_time *g_time, struct msgb *msg)
+{
+ uint32_t fn = g_time->fn;
+ uint8_t ecu_out[GSM_FR_BYTES];
+ int rc;
+
+ /* Are we applying an ECU to this uplink, and are we in a state
+ * (not DTX pause) where we emit ECU output? */
+ if (lchan->ecu_state && !osmo_ecu_is_dtx_pause(lchan->ecu_state)) {
+ rc = osmo_ecu_frame_out(lchan->ecu_state, ecu_out);
+ /* did it actually give us some output? */
+ if (rc > 0) {
+ /* yes, send it out in RTP */
+ send_ul_rtp_packet_speech(lchan, fn, ecu_out, rc);
+ return;
+ }
+ }
+
+ /* Are we in rtp continuous-streaming special mode? If so, send out
+ * a BFI packet as zero-length RTP payload. */
+ if (lchan->ts->trx->bts->rtp_nogaps_mode) {
+ send_ul_rtp_packet_speech(lchan, fn, NULL, 0);
+ return;
+ }
+
+ /* Most classic form of BFI handling: generate an intentional gap
+ * in the outgoing RTP stream. */
+ LOGPLCGT(lchan, g_time, DRTP, LOGL_DEBUG,
+ "Skipping RTP frame with lost payload\n");
+ if (lchan->abis_ip.osmux.use)
+ lchan_osmux_skipped_frame(lchan, fn_ms_adj(fn, lchan));
+ else 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;
+}
+
/* 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)
@@ -1204,42 +2051,62 @@ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap,
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;
+ } else {
+ LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "Rx TCH.ind\n");
}
- msgb_pull(msg, sizeof(*l1sap));
+ /* Notify current receive FN to lapdm.
+ * TCH frames may be indicated before FACCH frames are indicated. To prevent T200 timeout before FACCH is
+ * received, subtract one frame number, so that timeout is processed next time after FACCH is received.
+ */
+ lapdm_t200_fn(&lchan->lapdm_ch.lapdm_dcch, GSM_TDMA_FN_SUB(fn, 1));
+
+ /* The ph_tch_param contained in the l1sap primitive may contain
+ * measurement data. If this data is present, forward it for
+ * processing */
+ if (bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB))
+ process_l1sap_meas_data(lchan, l1sap, PRIM_TCH);
+
+ msgb_pull_to_l2(msg);
/* 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 >= bts->min_qual_norm) {
+ /* feed the good frame to the ECU, if we are applying one */
+ if (lchan->ecu_state)
+ osmo_ecu_frame_in(lchan->ecu_state, false, msg->data, msg->len);
/* 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);
+ switch (lchan->rsl_cmode) {
+ case RSL_CMOD_SPD_SPEECH:
+ if (bts->emit_hr_rfc5993 && lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch_mode == GSM48_CMODE_SPEECH_V1)
+ send_rtp_rfc5993(lchan, fn, msg);
+ else
+ send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len);
+ break;
+ case RSL_CMOD_SPD_DATA:
+ send_ul_rtp_packet_data(lchan, tch_ind, msg->data, msg->len);
+ break;
+ case RSL_CMOD_SPD_SIGN:
+ return 0; /* drop stale TCH.ind */
+ default: /* shall not happen */
+ OSMO_ASSERT(0);
+ }
/* 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);
+ /* add new frame to queue, make sure the queue doesn't get too long */
+ lchan_dl_tch_queue_enqueue(lchan, msg, 1);
/* 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;
+ tch_ul_bfi_handler(lchan, &g_time, msg);
}
lchan->tch.last_fn = fn;
@@ -1248,14 +2115,15 @@ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap,
#define RACH_MIN_TOA256 -2 * 256
-static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts)
+static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts,
+ const char *chan_name)
{
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",
+ LOGPFN(DL1C, LOGL_DEBUG, rach_ind->fn, "Ignoring an Access Burst on %s: "
+ "BER10k(%u) > BER10k_MAX(%u)\n", chan_name,
rach_ind->ber10k, bts->max_ber10k_rach);
return false;
}
@@ -1266,16 +2134,16 @@ static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts
* 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",
+ LOGPFN(DL1C, LOGL_DEBUG, rach_ind->fn, "Ignoring an Access Burst on %s: "
+ "ToA(%d) exceeds the allowed range (%d..%d)\n", chan_name,
toa256, RACH_MIN_TOA256, bts->max_ta * 256);
return false;
}
/* Link quality defined by C/I (Carrier-to-Interference ratio) */
if (rach_ind->lqual_cb < bts->min_qual_rach) {
- LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: "
- "link quality (%d) below the minimum (%d)\n",
+ LOGPFN(DL1C, LOGL_DEBUG, rach_ind->fn, "Ignoring an Access Burst on %s: "
+ "link quality (%d) below the minimum (%d)\n", chan_name,
rach_ind->lqual_cb, bts->min_qual_rach);
return false;
}
@@ -1283,23 +2151,58 @@ static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts
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)
+/* Special case where RACH on DCCH uplink is detected */
+static int l1sap_dcch_rach(struct gsm_bts_trx *trx, struct ph_rach_ind_param *rach_ind)
{
+ struct gsm_lchan *lchan;
+
/* Filter out noise / interference / ghosts */
- if (!rach_pass_filter(rach_ind, trx->bts)) {
+ if (!rach_pass_filter(rach_ind, trx->bts, "DCCH")) {
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);
+ lchan = get_lchan_by_chan_nr(trx, rach_ind->chan_nr);
+ /* Differentiate + dispatch hand-over and VGCS RACH */
+ if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_VGCS);
+ vgcs_rach(lchan, rach_ind->ra, rach_ind->acc_delay, rach_ind->fn);
+ } else {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_HO);
+ handover_rach(lchan, rach_ind->ra, rach_ind->acc_delay);
+ }
/* must return 0, so in case of msg at l1sap, it will be freed */
return 0;
}
+/* Special case for Access Bursts on PDTCH/U or PTCCH/U */
+static int l1sap_pdch_rach(struct gsm_bts_trx *trx, struct ph_rach_ind_param *rach_ind)
+{
+ /* Filter out noise / interference / ghosts */
+ if (!rach_pass_filter(rach_ind, trx->bts, "PDCH"))
+ return -EAGAIN;
+
+ /* PTCCH/U (Packet Timing Advance Control Channel) */
+ if (L1SAP_IS_PTCCH(rach_ind->fn)) {
+ LOGPFN(DL1P, LOGL_DEBUG, rach_ind->fn,
+ /* TODO: calculate and print Timing Advance Index */
+ "Access Burst for continuous Timing Advance control (toa256=%d)\n",
+ rach_ind->acc_delay_256bits);
+
+ /* QTA: Timing Advance in units of 1/4 of a symbol */
+ pcu_tx_rach_ind(trx->bts->nr, trx->nr, rach_ind->chan_nr & 0x07,
+ rach_ind->acc_delay_256bits >> 6,
+ rach_ind->ra, rach_ind->fn, rach_ind->is_11bit,
+ rach_ind->burst_type, PCU_IF_SAPI_PTCCH);
+ return 0;
+ } else { /* The MS may acknowledge DL data by 4 consequent Access Bursts */
+ LOGPFN(DL1P, LOGL_NOTICE, rach_ind->fn,
+ "Access Bursts on PDTCH/U are not (yet) supported\n");
+ return -ENOTSUP;
+ }
+}
+
/* 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)
@@ -1309,10 +2212,16 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx,
DEBUGPFN(DL1P, rach_ind->fn, "Rx PH-RA.ind\n");
- /* 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);
+ /* Check the origin of an Access Burst */
+ switch (rach_ind->chan_nr & 0xf8) {
+ case RSL_CHAN_RACH:
+ /* CS or PS RACH, to be handled in this function */
+ break;
+ case RSL_CHAN_OSMO_PDCH:
+ /* TODO: do we need to count Access Bursts on PDCH? */
+ return l1sap_pdch_rach(trx, rach_ind);
+ default:
+ return l1sap_dcch_rach(trx, rach_ind);
}
rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_RCVD);
@@ -1322,7 +2231,7 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx,
bts->load.rach.busy++;
/* Filter out noise / interference / ghosts */
- if (!rach_pass_filter(rach_ind, bts)) {
+ if (!rach_pass_filter(rach_ind, bts, "CCCH")) {
rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP);
return 0;
}
@@ -1344,9 +2253,11 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx,
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);
+ /* QTA: Timing Advance in units of 1/4 of a symbol */
+ pcu_tx_rach_ind(bts->nr, trx->nr, rach_ind->chan_nr & 0x07,
+ rach_ind->acc_delay_256bits >> 6,
+ rach_ind->ra, rach_ind->fn, rach_ind->is_11bit,
+ rach_ind->burst_type, PCU_IF_SAPI_RACH);
return 0;
}
@@ -1412,6 +2323,9 @@ int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
/* any L1 prim sent to bts model */
static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
{
+ l1sap_log_ctx_sapi = get_common_sapi_by_trx_prim(trx, l1sap);
+ log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi);
+
if (OSMO_PRIM_HDR(&l1sap->oph) ==
OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST))
to_gsmtap(trx, l1sap);
@@ -1440,8 +2354,12 @@ int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
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);
+ if (len) {
+ msg->l2h = msgb_put(msg, len);
+ memcpy(msg->l2h, data, len);
+ } else {
+ msg->l2h = NULL; /* Idle block */
+ }
return l1sap_down(ts->trx, l1sap);
}
@@ -1452,19 +2370,59 @@ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
uint32_t timestamp, bool marker)
{
struct gsm_lchan *lchan = rs->priv;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
struct msgb *msg;
- struct osmo_phsap_prim *l1sap;
+ bool rfc5993_sid = false;
+
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_TOTAL);
+ if (marker)
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_MARKER);
/* if we're in loopback mode, we don't accept frames from the
* RTP socket anymore */
- if (lchan->loopback)
+ if (lchan->loopback) {
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_DROP_LOOPBACK);
return;
+ }
- msg = l1sap_msgb_alloc(rtp_pl_len);
+ /* initial preen */
+ switch (rtp_payload_input_preen(lchan, rtp_pl, rtp_pl_len, &rfc5993_sid)) {
+ case PL_DECISION_DROP:
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_DROP_PREEN);
+ return;
+ case PL_DECISION_ACCEPT:
+ break;
+ case PL_DECISION_STRIP_HDR_OCTET:
+ rtp_pl++;
+ rtp_pl_len--;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ msg = l1sap_msgb_alloc(512);
if (!msg)
return;
- memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len);
- msgb_pull(msg, sizeof(*l1sap));
+
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) {
+ int rc = csd_v110_rtp_decode(lchan, msg->tail,
+ rtp_pl, rtp_pl_len);
+ if (rc > 0) {
+ /* 'fake' tch_ind containing all-zero so gsmtap code can be shared
+ * between UL and DL */
+ static const struct ph_tch_param fake_tch_ind = {};
+ gsmtap_csd_rlp_process(lchan, false, &fake_tch_ind, msg->tail, rc);
+ msgb_put(msg, rc);
+ } else {
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_DROP_V110_DEC);
+ msgb_free(msg);
+ return;
+ }
+ } else {
+ memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len);
+ }
+
+ msgb_pull(msg, sizeof(struct osmo_phsap_prim));
/* Store RTP header Marker bit in control buffer */
rtpmsg_marker_bit(msg) = marker;
@@ -1472,11 +2430,11 @@ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
rtpmsg_seq(msg) = seq_number;
/* Store RTP header Timestamp in control buffer */
rtpmsg_ts(msg) = timestamp;
+ /* Store RFC 5993 SID flag likewise */
+ rtpmsg_is_rfc5993_sid(msg) = rfc5993_sid;
/* 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);
+ lchan_dl_tch_queue_enqueue(lchan, msg, 1);
}
static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr,
@@ -1494,53 +2452,50 @@ static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr,
return l1sap_down(trx, &l1sap);
}
-int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp)
+int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr)
{
struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
- struct gsm48_chan_desc *cd;
int rc;
- LOGPLCHAN(lchan, 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)) {
- LOGPLCHAN(lchan, 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;
- }
+ if (lchan->state == LCHAN_S_ACTIVE) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate already active channel %s\n",
+ rsl_chan_nr_str(chan_nr));
+ return -1;
}
- lchan->sacch_deact = 0;
- lchan->s = lchan->ts->trx->bts->radio_link_timeout;
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Activating channel %s\n", rsl_chan_nr_str(chan_nr));
+
+ radio_link_timeout_reset(lchan);
rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0);
if (rc)
return -RSL_ERR_EQUIPMENT_FAIL;
+ /* Is it TCH? If it is, attempt to allocate an Error Concealment Unit
+ * instance, if available, unless it is disabled by vty config. */
+ if (lchan_is_tch(lchan) && trx->bts->use_ul_ecu)
+ lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan));
+ else
+ lchan->ecu_state = NULL;
+
/* 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);
+ if (trx->bts->dtxd && lchan_is_tch(lchan)) {
lchan->tch.dtx.dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm,
tall_bts_ctx,
lchan,
LOGL_DEBUG,
- name);
+ NULL);
if (!lchan->tch.dtx.dl_amr_fsm) {
l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0);
return -RSL_ERR_EQUIPMENT_FAIL;
}
+
+ rc = osmo_fsm_inst_update_id_f(lchan->tch.dtx.dl_amr_fsm,
+ "bts%u-trx%u-ts%u-ss%u%s",
+ trx->bts->nr, trx->nr,
+ lchan->ts->nr, lchan->nr,
+ lchan->ts->vamos.is_shadow ? "-shadow" : "");
+ OSMO_ASSERT(rc == 0);
}
return 0;
}
@@ -1548,14 +2503,31 @@ int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *
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);
- LOGPLCHAN(lchan, DL1C, LOGL_INFO, "deactivating channel chan_nr=%s trx=%d\n",
- rsl_chan_nr_str(chan_nr), trx->nr);
+
+ if (lchan->state == LCHAN_S_NONE) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to deactivate already deactivated channel %s\n",
+ rsl_chan_nr_str(chan_nr));
+ return -1;
+ }
+
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Deactivating channel %s\n",
+ rsl_chan_nr_str(chan_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;
}
+ /* clear ECU state (if any) */
+ if (lchan->ecu_state) {
+ osmo_ecu_destroy(lchan->ecu_state);
+ lchan->ecu_state = NULL;
+ }
+
+ /* reset CSD RLP buffers to avoid any user plane data leaking from
+ * one previous lchan into a later one */
+ memset(&lchan->tch.csd, 0, sizeof(lchan->tch.csd));
+
return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
0);
}
@@ -1564,10 +2536,8 @@ 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);
- LOGPLCHAN(lchan, DL1C, LOGL_INFO, "deactivating sacch chan_nr=%s trx=%d\n",
- rsl_chan_nr_str(chan_nr), trx->nr);
-
- lchan->sacch_deact = 1;
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Deactivating SACCH on channel %s\n",
+ rsl_chan_nr_str(chan_nr));
return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
1);
@@ -1575,8 +2545,46 @@ 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)
{
- LOGP(DL1C, LOGL_INFO, "modifying channel chan_nr=%s trx=%d\n",
- rsl_chan_nr_str(chan_nr), trx->nr);
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Modifying channel %s\n",
+ rsl_chan_nr_str(chan_nr));
+
+ /* Is it TCH? If it is and we are applying internal uplink ECUs,
+ * the new channel mode calls for a different ECU. Any changes
+ * in vty config (enabling or disabling this ECU application)
+ * will also take effect upon channel modification. */
+ if (lchan_is_tch(lchan)) {
+ if (lchan->ecu_state)
+ osmo_ecu_destroy(lchan->ecu_state);
+ if (trx->bts->use_ul_ecu)
+ lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan));
+ else
+ lchan->ecu_state = NULL;
+ }
return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0);
}
+
+int l1sap_uplink_access(struct gsm_lchan *lchan, bool active)
+{
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ struct osmo_phsap_prim l1sap;
+
+ if (lchan->state != LCHAN_S_ACTIVE && !rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel %s is not an active ASCI type channel.\n",
+ rsl_chan_nr_str(chan_nr));
+ return -EINVAL;
+ }
+
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s uplink access detection on channel %s\n",
+ (active) ? "Activating" : "Deactivating", rsl_chan_nr_str(chan_nr));
+
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST, NULL);
+ l1sap.u.info.type = (active) ? PRIM_INFO_ACT_UL_ACC : PRIM_INFO_DEACT_UL_ACC;
+ l1sap.u.info.u.ulacc_req.chan_nr = chan_nr;
+
+ return l1sap_down(lchan->ts->trx, &l1sap);
+}
diff --git a/src/common/lchan.c b/src/common/lchan.c
index 9e98166d..5b41a158 100644
--- a/src/common/lchan.c
+++ b/src/common/lchan.c
@@ -12,38 +12,654 @@
* 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.
+ * 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 "btsconfig.h" /* for PACKAGE_VERSION */
+
#include <osmocom/core/logging.h>
+
+#include <osmocom/trau/osmo_ortp.h>
+
#include <osmo-bts/logging.h>
-#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/lchan.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/asci.h>
+#include <errno.h>
+
+static const struct value_string lchan_s_names[] = {
+ { LCHAN_S_NONE, "NONE" },
+ { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" },
+ { LCHAN_S_ACTIVE, "ACTIVE" },
+ { LCHAN_S_REL_REQ, "RELEASE REQUESTED" },
+ { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" },
+ { LCHAN_S_BROKEN, "BROKEN UNUSABLE" },
+ { 0, NULL }
+};
+
+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 }
+};
+
+/* prepare the per-SAPI T200 arrays for a given lchan */
+static int t200_by_lchan(uint32_t *t200_fn_dcch, uint32_t *t200_fn_acch, struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ t200_fn_dcch[DL_SAPI0] = bts->t200_fn[T200_SDCCH];
+ t200_fn_dcch[DL_SAPI3] = bts->t200_fn[T200_SDCCH_SAPI3];
+ t200_fn_acch[DL_SAPI0] = bts->t200_fn[T200_SACCH_SDCCH];
+ t200_fn_acch[DL_SAPI3] = bts->t200_fn[T200_SACCH_SDCCH];
+ break;
+ case GSM_LCHAN_TCH_F:
+ t200_fn_dcch[DL_SAPI0] = bts->t200_fn[T200_FACCH_F];
+ t200_fn_dcch[DL_SAPI3] = bts->t200_fn[T200_FACCH_F];
+ t200_fn_acch[DL_SAPI0] = bts->t200_fn[T200_SACCH_TCH_SAPI0];
+ t200_fn_acch[DL_SAPI3] = bts->t200_fn[T200_SACCH_TCH_SAPI3];
+ break;
+ case GSM_LCHAN_TCH_H:
+ t200_fn_dcch[DL_SAPI0] = bts->t200_fn[T200_FACCH_H];
+ t200_fn_dcch[DL_SAPI3] = bts->t200_fn[T200_FACCH_H];
+ t200_fn_acch[DL_SAPI0] = bts->t200_fn[T200_SACCH_TCH_SAPI0];
+ t200_fn_acch[DL_SAPI3] = bts->t200_fn[T200_SACCH_TCH_SAPI3];
+ break;
+ default:
+ /* Channels such as CCCH don't use lapdm DL, and hence no T200 is needed */
+ return -1;
+ }
+
+ /* Add time of two extra messages frames. */
+ if (lchan->rep_acch_cap.dl_facch_all && lchan_is_tch(lchan)) {
+ t200_fn_acch[DL_SAPI0] += 104 * 2;
+ t200_fn_acch[DL_SAPI3] += 104 * 2;
+ }
+
+ return 0;
+}
+
+static void early_rr_ia_delay_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ if (!lchan->early_rr_ia) {
+ /* The IA message has disappeared since the timer was started. */
+ return;
+ }
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ /* Release has happened since the timer was started. */
+ msgb_free(lchan->early_rr_ia);
+ lchan->early_rr_ia = NULL;
+ return;
+ }
+
+ /* Activation is done, send the RR IA now. Put RR IA msg into the AGCH queue of the BTS. */
+ if (bts_agch_enqueue(bts, lchan->early_rr_ia) < 0) {
+ /* if there is no space in the queue: send DELETE IND */
+ rsl_tx_delete_ind(bts, lchan->early_rr_ia->data, lchan->early_rr_ia->len);
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_DELETED);
+ msgb_free(lchan->early_rr_ia);
+ }
+ lchan->early_rr_ia = NULL;
+}
+
+void gsm_lchan_init(struct gsm_lchan *lchan, struct gsm_bts_trx_ts *ts, unsigned int lchan_nr)
+{
+ lchan->ts = ts;
+ lchan->nr = lchan_nr;
+ lchan->type = GSM_LCHAN_NONE;
+ gsm_lchan_name_update(lchan);
+
+ osmo_timer_setup(&lchan->early_rr_ia_delay, early_rr_ia_delay_cb, lchan);
+
+ INIT_LLIST_HEAD(&lchan->sapi_cmds);
+ INIT_LLIST_HEAD(&lchan->dl_tch_queue);
+ lchan->dl_tch_queue_len = 0;
+}
+
+void gsm_lchan_name_update(struct gsm_lchan *lchan)
+{
+ const struct gsm_bts_trx_ts *ts = lchan->ts;
+ const struct gsm_bts_trx *trx = ts->trx;
+ char *name;
+
+ name = talloc_asprintf(trx, "(" GSM_TS_NAME_FMT ",ss=%u)",
+ GSM_TS_NAME_ARGS(ts), lchan->nr);
+ if (lchan->name != NULL)
+ talloc_free(lchan->name);
+ lchan->name = name;
+}
+
+int lchan_init_lapdm(struct gsm_lchan *lchan)
+{
+ struct lapdm_channel *lc = &lchan->lapdm_ch;
+ uint32_t t200_fn_dcch[_NR_DL_SAPI], t200_fn_acch[_NR_DL_SAPI];
+
+ if (t200_by_lchan(t200_fn_dcch, t200_fn_acch, lchan) == 0) {
+ LOGPLCHAN(lchan, DLLAPD, LOGL_DEBUG,
+ "Setting T200 D0=%u, D3=%u, S0=%u, S3=%u (all in frames)\n",
+ t200_fn_dcch[DL_SAPI0], t200_fn_dcch[DL_SAPI3],
+ t200_fn_acch[DL_SAPI0], t200_fn_acch[DL_SAPI3]);
+ lapdm_channel_init3(lc, LAPDM_MODE_BTS, NULL, NULL, lchan->type, gsm_lchan_name(lchan));
+ lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY | LAPDM_ENT_F_RTS);
+ lapdm_channel_set_l1(lc, NULL, lchan);
+ lapdm_channel_set_t200_fn(lc, t200_fn_dcch, t200_fn_acch);
+ }
+ /* We still need to set Rx callback to receive RACH requests: */
+ lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan);
+
+ 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();
+}
+
+void gsm_lchan_release(struct gsm_lchan *lchan, enum lchan_rel_act_kind rel_kind)
+{
+ int rc;
+
+ 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 ");
+ lchan_rtp_socket_free(lchan);
+ } else if (lchan->abis_ip.osmux.use) {
+ lchan_osmux_release(lchan);
+ }
+ /* reset all Abis related config: */
+ memset(&lchan->abis_ip, 0, sizeof(lchan->abis_ip));
+
+ /* FIXME: right now we allow creating the rtp_socket even if chan is not
+ * activated... Once we check for that, we can move this check at the
+ * start of the function */
+ if (lchan->state == LCHAN_S_NONE)
+ return;
+
+ /* release handover, listener and talker states */
+ handover_reset(lchan);
+ vgcs_talker_reset(lchan, false);
+ vgcs_listener_reset(lchan);
+ vgcs_uplink_free_reset(lchan);
+
+ lchan->rel_act_kind = rel_kind;
+
+ /* Dynamic channel in PDCH mode is released via PCU */
+ if (lchan->ts->pchan == GSM_PCHAN_OSMO_DYN
+ && 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;
+ rsl_tx_rf_rel_ack(lchan);
+ return;
+ }
+ /* Waiting for PDCH release */
+ return;
+ }
+
+ l1sap_chan_rel(lchan->ts->trx, gsm_lchan2chan_nr(lchan));
+}
+
+int lchan_deactivate(struct gsm_lchan *lchan)
+{
+ OSMO_ASSERT(lchan);
+
+ lchan->ciph_state = 0;
+ return bts_model_lchan_deactivate(lchan);
+}
+
+const char *gsm_lchans_name(enum gsm_lchan_state s)
+{
+ return get_value_string(lchan_s_names, s);
+}
+
+/* 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);
+ }
+ LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, "SACCH no SI available\n");
+ return NULL;
+}
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));
+ if (lchan->state == state)
+ return;
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "state %s -> %s\n",
+ gsm_lchans_name(lchan->state), gsm_lchans_name(state));
lchan->state = state;
+
+ switch (lchan->state) {
+ case LCHAN_S_ACT_REQ:
+ /* Early Immediate Assignment: Activation is requested, keep the
+ * early IA until active. This allows the BSC to send the IA
+ * even before a dynamic timeslot is done switching to a
+ * different pchan kind (experimental). */
+ break;
+ case LCHAN_S_ACTIVE:
+ lchan_init_lapdm(lchan);
+ if (lchan->early_rr_ia) {
+ /* Early Immediate Assignment: Activation is done, send
+ * the RR IA now. Delay a bit more to give Um time to
+ * let the lchan light up for the MS */
+ osmo_timer_del(&lchan->early_rr_ia_delay);
+ osmo_timer_schedule(&lchan->early_rr_ia_delay, 0,
+ osmo_tdef_get(abis_T_defs, -15, OSMO_TDEF_US, -1));
+ }
+ break;
+ case LCHAN_S_NONE:
+ lapdm_channel_exit(&lchan->lapdm_ch);
+ /* Also ensure that there are no leftovers from repeated FACCH or
+ * repeated SACCH that might cause memory leakage. */
+ msgb_free(lchan->rep_acch.dl_facch[0].msg);
+ msgb_free(lchan->rep_acch.dl_facch[1].msg);
+ lchan->rep_acch.dl_facch[0].msg = NULL;
+ lchan->rep_acch.dl_facch[1].msg = NULL;
+ msgb_free(lchan->rep_acch.dl_sacch_msg);
+ lchan->rep_acch.dl_sacch_msg = NULL;
+ /* free() pending messages */
+ msgb_free(lchan->pending_rel_ind_msg);
+ lchan->pending_rel_ind_msg = NULL;
+ msgb_free(lchan->pending_chan_activ);
+ lchan->pending_chan_activ = NULL;
+ /* fall through */
+ default:
+ if (lchan->early_rr_ia) {
+ /* Early Immediate Assignment: Transition to any other
+ * state means whatever IA the BSC has sent shall now
+ * not be relevant anymore. */
+ osmo_timer_del(&lchan->early_rr_ia_delay);
+ msgb_free(lchan->early_rr_ia);
+ lchan->early_rr_ia = NULL;
+ }
+ break;
+ }
}
-bool ts_is_pdch(const struct gsm_bts_trx_ts *ts)
+/* See 3GPP TS 44.018 Table 10.5.2.5.1 "Channel Description information element" */
+static uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr)
{
- switch (ts->pchan) {
+ uint8_t cbits, chan_nr;
+
+ OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs;
+ break;
case GSM_PCHAN_PDCH:
- return true;
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH;
+ break;
+ case GSM_PCHAN_TCH_H:
+ OSMO_ASSERT(lchan_nr < 2);
+ cbits = ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(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 = ABIS_RSL_CHAN_NR_CBITS_BCCH;
+ else {
+ OSMO_ASSERT(lchan_nr < 4);
+ cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(lchan_nr);
+ }
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ OSMO_ASSERT(lchan_nr < 8);
+ cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(lchan_nr);
+ break;
+ case GSM_PCHAN_CCCH:
+ cbits = ABIS_RSL_CHAN_NR_CBITS_BCCH;
+ break;
+ case GSM_PCHAN_NONE:
+ LOGP(DRSL, LOGL_ERROR, "ts=%u,ss=%u Physical channel %s not expected!\n",
+ ts_nr, lchan_nr, gsm_pchan_name(pchan));
+ cbits = 0x00; /* Reserved */
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR, "ts=%u,ss=%u Physical channel %s (0x%02x) not expected!\n",
+ ts_nr, lchan_nr, gsm_pchan_name(pchan), (int)pchan);
+ OSMO_ASSERT(0);
+ break;
+ }
+
+ chan_nr = (cbits << 3) | (ts_nr & 0x7);
+
+ return chan_nr;
+}
+
+static uint8_t _gsm_lchan2chan_nr(const struct gsm_lchan *lchan, bool rsl)
+{
+ uint8_t chan_nr;
+
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_OSMO_DYN:
+ /* Return chan_nr reflecting the current TS pchan, either a standard TCH kind, or the
+ * nonstandard value reflecting PDCH for Osmocom style dyn TS. */
+ chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, lchan->ts->dyn.pchan_is);
+ break;
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;
+ /* For ip.access style dyn TS, on RSL we 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. */
+ if (rsl)
+ chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F);
+ else if (~lchan->ts->flags & TS_F_PDCH_ACTIVE)
+ chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F);
+ else
+ chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH);
+ break;
+ default:
+ chan_nr = gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr);
+ break;
+ }
+
+ /* VAMOS: if this lchan belongs to a shadow timeslot, we must reflect
+ * this in the channel number. Convert it to Osmocom specific value. */
+ if (lchan->ts->vamos.is_shadow)
+ chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK;
+
+ return chan_nr;
+}
+
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+ return _gsm_lchan2chan_nr(lchan, false);
+}
+
+uint8_t gsm_lchan2chan_nr_rsl(const struct gsm_lchan *lchan)
+{
+ return _gsm_lchan2chan_nr(lchan, true);
+}
+
+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_OSMO_DYN
+ && 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);
+}
+
+/* Called by the model specific code every 104 TDMA frames (SACCH period) */
+void gsm_lchan_interf_meas_push(struct gsm_lchan *lchan, int dbm)
+{
+ const uint8_t meas_num = lchan->meas.interf_meas_num;
+
+ if (meas_num >= ARRAY_SIZE(lchan->meas.interf_meas_dbm)) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Not enough room "
+ "to store interference report (%ddBm)\n", dbm);
+ return;
+ }
+
+ lchan->meas.interf_meas_dbm[meas_num] = dbm;
+ lchan->meas.interf_meas_num++;
+}
+
+/* Called by the higher layers every Intave * 104 TDMA frames */
+void gsm_lchan_interf_meas_calc_avg(struct gsm_lchan *lchan)
+{
+ const uint8_t meas_num = lchan->meas.interf_meas_num;
+ const struct gsm_bts *bts = lchan->ts->trx->bts;
+ int b, meas_avg, meas_sum = 0;
+
+ /* There must be at least one sample */
+ OSMO_ASSERT(meas_num > 0);
+
+ /* Calculate the sum of all collected samples (in -x dBm) */
+ while (lchan->meas.interf_meas_num) {
+ uint8_t i = --lchan->meas.interf_meas_num;
+ meas_sum += lchan->meas.interf_meas_dbm[i];
+ }
+
+ /* Calculate the average of all collected samples */
+ meas_avg = meas_sum / (int) meas_num;
+
+ /* 3GPP TS 48.008 defines 5 interference bands, and 6 interference level
+ * boundaries (0, X1, ... X5). It's not clear how to handle values
+ * exceeding the outer boundaries (0 or X5), because bands 0 and 6 do
+ * not exist (sigh). Let's map such values to closest bands 1 and 5. */
+ if (bts->interference.boundary[0] < bts->interference.boundary[5]) {
+ /* Ascending order (band=1 indicates lowest interference) */
+ for (b = 1; b < ARRAY_SIZE(bts->interference.boundary) - 1; b++) {
+ if (meas_avg < bts->interference.boundary[b])
+ break; /* Current 'b' is the band value */
+ }
+ } else {
+ /* Descending order (band=1 indicates highest interference) */
+ for (b = 1; b < ARRAY_SIZE(bts->interference.boundary) - 1; b++) {
+ if (meas_avg >= bts->interference.boundary[b])
+ break; /* Current 'b' is the band value */
+ }
+ }
+
+ LOGPLCHAN(lchan, DL1C, LOGL_DEBUG,
+ "Interference AVG: %ddBm (band %d, samples %u)\n",
+ meas_avg, b, meas_num);
+
+ lchan->meas.interf_meas_avg_dbm = meas_avg;
+ lchan->meas.interf_band = b;
+}
+
+/* determine the ECU codec constant for the codec used by given lchan */
+int lchan2ecu_codec(const struct gsm_lchan *lchan)
+{
+ const struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (ts_pchan(ts) == GSM_PCHAN_TCH_H)
+ return OSMO_ECU_CODEC_HR;
+ else
+ return OSMO_ECU_CODEC_FR;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ return OSMO_ECU_CODEC_EFR;
+ case GSM48_CMODE_SPEECH_AMR:
+ return OSMO_ECU_CODEC_AMR;
default:
- return false;
+ return -1;
+ }
+}
+
+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)
+ continue;
+
+ if (bts->rtp_ip_dscp != -1) {
+ if (osmo_rtp_socket_set_dscp(rs, bts->rtp_ip_dscp))
+ LOGP(DRSL, LOGL_ERROR, "failed to set DSCP=%d: %s\n",
+ bts->rtp_ip_dscp, strerror(errno));
+ }
+ if (bts->rtp_priority != -1) {
+ if (osmo_rtp_socket_set_priority(rs, bts->rtp_priority))
+ LOGP(DRSL, LOGL_ERROR, "failed to set socket priority %d: %s\n",
+ bts->rtp_priority, strerror(errno));
+ }
+ return 0;
+ }
+
+ return -1;
+}
+
+int lchan_rtp_socket_create(struct gsm_lchan *lchan, const char *bind_ip)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ char cname[256+4];
+ int rc;
+
+ if (lchan->abis_ip.rtp_socket) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC CRCX, "
+ "but we already have socket!\n");
+ return -EALREADY;
+ }
+
+ /* 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) {
+ LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to create RTP/RTCP sockets\n");
+ oml_tx_failure_event_rep(&lchan->ts->trx->mo,
+ NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT,
+ "%s IPAC Failed to create RTP/RTCP sockets",
+ gsm_lchan_name(lchan));
+ return -ENOTCONN;
+ }
+
+ 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)
+ LOGPLCHAN(lchan, DRTP, LOGL_ERROR,
+ "IPAC Failed to set RTP socket parameters: %s\n", strerror(-rc));
+ else
+ LOGPLCHAN(lchan, DRTP, LOGL_INFO, "IPAC set RTP socket parameters: %d\n", rc);
+
+ lchan->abis_ip.rtp_socket->priv = lchan;
+ lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb;
+
+ rc = bind_rtp(bts, lchan->abis_ip.rtp_socket, bind_ip);
+ if (rc < 0) {
+ LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to bind RTP/RTCP sockets\n");
+ oml_tx_failure_event_rep(&lchan->ts->trx->mo,
+ NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT,
+ "%s IPAC Failed to bind RTP/RTCP sockets",
+ gsm_lchan_name(lchan));
+ lchan_rtp_socket_free(lchan);
+ return -EBADFD;
+ }
+
+ /* Ensure RTCP SDES contains some useful information */
+ snprintf(cname, sizeof(cname), "bts@%s", bind_ip);
+ 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 */
+ return 0;
+}
+
+
+int lchan_rtp_socket_connect(struct gsm_lchan *lchan, const struct in_addr *ia, uint16_t connect_port)
+{
+ int bound_port = 0;
+ int rc;
+
+ rc = osmo_rtp_socket_connect(lchan->abis_ip.rtp_socket,
+ inet_ntoa(*ia), connect_port);
+ if (rc < 0) {
+ LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "Failed to connect RTP/RTCP sockets\n");
+ return -ECONNREFUSED;
+ }
+ /* save IP address and port number */
+ lchan->abis_ip.connect_ip = ntohl(ia->s_addr);
+ lchan->abis_ip.connect_port = connect_port;
+
+ rc = osmo_rtp_get_bound_ip_port(lchan->abis_ip.rtp_socket,
+ &lchan->abis_ip.bound_ip,
+ &bound_port);
+ if (rc < 0)
+ LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC cannot obtain locally bound IP/port: %d\n", rc);
+ lchan->abis_ip.bound_port = bound_port;
+ return 0;
+}
+
+void lchan_rtp_socket_free(struct gsm_lchan *lchan)
+{
+ osmo_rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ msgb_queue_free(&lchan->dl_tch_queue);
+ lchan->dl_tch_queue_len = 0;
+}
+
+/*! limit number of queue entries to %u; drops any surplus messages */
+void lchan_dl_tch_queue_enqueue(struct gsm_lchan *lchan, struct msgb *msg, unsigned int limit)
+{
+ if (lchan->dl_tch_queue_len > limit) {
+ unsigned int excess = lchan->dl_tch_queue_len - limit;
+ LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, "freeing %d queued frames\n", excess);
+ rate_ctr_add2(lchan->ts->trx->bts->ctrs, BTS_CTR_RTP_RX_DROP_OVERFLOW, excess);
+ }
+ while (lchan->dl_tch_queue_len > limit) {
+ struct msgb *tmp = msgb_dequeue_count(&lchan->dl_tch_queue, &lchan->dl_tch_queue_len);
+ msgb_free(tmp);
}
+ msgb_enqueue_count(&lchan->dl_tch_queue, msg, &lchan->dl_tch_queue_len);
}
diff --git a/src/common/load_indication.c b/src/common/load_indication.c
index fa4745b1..c0d6efb1 100644
--- a/src/common/load_indication.c
+++ b/src/common/load_indication.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -27,6 +27,7 @@
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/paging.h>
+#include <osmo-bts/bts.h>
static void reset_load_counters(struct gsm_bts *bts)
{
@@ -40,6 +41,10 @@ static void load_timer_cb(void *data)
struct gsm_bts *bts = data;
unsigned int pch_percent, rach_percent;
+ /* It makes no sense to send Load Indication if CCCH is still disabled...*/
+ if (bts->c0->ts[0].mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ goto retry_later;
+
/* compute percentages */
if (bts->load.ccch.pch_total == 0)
pch_percent = 0;
@@ -52,7 +57,7 @@ static void load_timer_cb(void *data)
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
+ /* This is an extension 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 */
@@ -72,6 +77,7 @@ static void load_timer_cb(void *data)
bts->load.rach.access);
}
+retry_later:
reset_load_counters(bts);
/* re-schedule the timer */
@@ -93,3 +99,8 @@ void load_timer_stop(struct gsm_bts *bts)
{
osmo_timer_del(&bts->load.ccch.timer);
}
+
+bool load_timer_is_running(const struct gsm_bts *bts)
+{
+ return osmo_timer_pending(&bts->load.ccch.timer);
+}
diff --git a/src/common/logging.c b/src/common/logging.c
index 3315a019..15c126a2 100644
--- a/src/common/logging.c
+++ b/src/common/logging.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -35,13 +35,13 @@ static struct log_info_cat bts_log_info_cat[] = {
.name = "DRSL",
.description = "A-bis Radio Siganlling Link (RSL)",
.color = "\033[1;35m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DOML] = {
.name = "DOML",
.description = "A-bis Network Management / O&M (NM/OML)",
.color = "\033[1;36m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DRLL] = {
.name = "DRLL",
@@ -64,34 +64,23 @@ static struct log_info_cat bts_log_info_cat[] = {
.name = "DPAG",
.description = "Paging Subsystem",
.color = "\033[1;38m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DL1C] = {
.name = "DL1C",
.description = "Layer 1 Control (MPH)",
- .loglevel = LOGL_INFO,
+ .loglevel = LOGL_NOTICE,
.enabled = 1,
},
[DL1P] = {
.name = "DL1P",
.description = "Layer 1 Primitives (PH)",
- .loglevel = LOGL_INFO,
+ .loglevel = LOGL_NOTICE,
.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,
},
@@ -116,35 +105,50 @@ static struct log_info_cat bts_log_info_cat[] = {
[DLOOP] = {
.name = "DLOOP",
.description = "Control loops",
- .color = "\033[0;34m",
+ .color = "\033[0;94m",
.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",
+ [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,
+ },
+ [DOSMUX] = {
+ .name = "DOSMUX",
+ .description = "Osmux (Osmocom RTP multiplexing)",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DASCI] = {
+ .name = "DASCI",
+ .description = "ASCI (Advanced Speech Call Items: VGCS/VBS)",
.loglevel = LOGL_NOTICE,
.enabled = 1,
},
+
};
+static int osmo_bts_filter_fn(const struct log_context *ctx, struct log_target *tgt)
+{
+ uint8_t *sapi = ctx->ctx[LOG_CTX_L1_SAPI];
+ uint16_t *sapi_mask = tgt->filter_data[LOG_FLT_L1_SAPI];
+
+ if ((tgt->filter_map & (1 << LOG_FLT_L1_SAPI)) != 0
+ && sapi_mask && sapi
+ && (*sapi_mask & (1 << *sapi)) != 0)
+ return 1;
+
+ return 0;
+}
+
const struct log_info bts_log_info = {
+ .filter_fn = osmo_bts_filter_fn,
.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
index 6d8088ca..e57885a6 100644
--- a/src/common/main.c
+++ b/src/common/main.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -38,6 +38,9 @@
#include <osmocom/core/application.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/cpu_sched_vty.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/gsmtap.h>
@@ -46,6 +49,7 @@
#include <osmo-bts/logging.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/l1sap.h>
#include <osmo-bts/bts_model.h>
@@ -56,30 +60,62 @@
#include <osmocom/ctrl/control_vty.h>
#include <osmo-bts/oml.h>
-int quit = 0;
+static 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 bool vty_test_mode = false;
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"
+ " -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"
+ "\nVTY reference generation:\n"
+ " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
+ " --vty-ref-xml Generate the VTY reference XML output and exit.\n"
+ "\nRegression testing:\n"
+ " --vty-test VTY test mode. Do not connect to BSC, do not exit.\n"
);
bts_model_print_help();
}
+static void handle_long_options(const char *prog_name, const int long_option)
+{
+ static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
+
+ switch (long_option) {
+ case 1:
+ vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
+ if (vty_ref_mode < 0) {
+ fprintf(stderr, "%s: Unknown VTY reference generation "
+ "mode '%s'\n", prog_name, optarg);
+ exit(2);
+ }
+ break;
+ case 2:
+ fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
+ get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
+ get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
+ vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
+ exit(0);
+ case 3:
+ vty_test_mode = true;
+ break;
+ default:
+ fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
+ exit(2);
+ }
+}
+
/* FIXME: finally get some option parsing code into libosmocore */
static void handle_options(int argc, char **argv)
{
@@ -94,6 +130,7 @@ static void handle_options(int argc, char **argv)
while (1) {
int option_idx = 0, c;
+ static int long_option = 0;
static const struct option long_options[] = {
/* FIXME: all those are generic Osmocom app options */
{ "help", 0, 0, 'h' },
@@ -108,6 +145,9 @@ static void handle_options(int argc, char **argv)
{ "gsmtap-ip", 1, 0, 'i' },
{ "trx-num", 1, 0, 't' },
{ "realtime", 1, 0, 'r' },
+ { "vty-ref-mode", 1, &long_option, 1 },
+ { "vty-ref-xml", 0, &long_option, 2 },
+ { "vty-test", 0, &long_option, 3 },
{ 0, 0, 0, 0 }
};
@@ -121,6 +161,9 @@ static void handle_options(int argc, char **argv)
print_help();
exit(0);
break;
+ case 0:
+ handle_long_options(argv[0], long_option);
+ break;
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
@@ -145,13 +188,17 @@ static void handle_options(int argc, char **argv)
break;
case 'r':
rt_prio = atoi(optarg);
+ fprintf(stderr, "Command line argument '-r' is deprecated, use VTY "
+ "cpu-sched node setting 'policy rr %d' instead.\n", rt_prio);
break;
case 'i':
gsmtap_ip = optarg;
+ fprintf(stderr, "Command line argument '-i' is deprecated, use VTY "
+ "parameter 'gsmtap-remote-host %s' instead.\n", gsmtap_ip);
break;
case 't':
- fprintf(stderr, "Parameter -t is deprecated and does nothing, "
- "TRX num is calculated from VTY\n");
+ fprintf(stderr, "Command line argument '-t' is deprecated and does nothing, "
+ "TRX number is calculated from the VTY automatically.\n");
break;
case '?':
case 1:
@@ -173,24 +220,35 @@ static void handle_options(int argc, char **argv)
}
}
-static struct gsm_bts *bts;
+/* FIXME: remove this once we add multi-BTS support */
+struct gsm_bts *g_bts = NULL;
-static void signal_handler(int signal)
+static void signal_handler(int signum)
{
- fprintf(stderr, "signal %u received\n", signal);
+ fprintf(stderr, "signal %u received\n", signum);
- switch (signal) {
+ switch (signum) {
case SIGINT:
case SIGTERM:
if (!quit) {
- oml_tx_failure_event_rep(&bts->mo,
+ oml_tx_failure_event_rep(&g_bts->mo,
NM_SEVER_CRITICAL, OSMO_EVT_CRIT_PROC_STOP,
"BTS: SIGINT received -> shutdown");
- bts_shutdown(bts, "SIGINT");
+ bts_shutdown_ext(g_bts, "SIGINT", true, false);
}
quit++;
break;
case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_bts_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_bts_ctx, stderr);
@@ -200,33 +258,11 @@ static void signal_handler(int signal)
}
}
-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();
@@ -238,18 +274,36 @@ int bts_main(int argc, char **argv)
osmo_stats_init(tall_bts_ctx);
vty_init(&bts_vty_info);
ctrl_vty_init(tall_bts_ctx);
+ osmo_cpu_sched_vty_init(tall_bts_ctx);
rate_ctr_init(tall_bts_ctx);
+ logging_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+ osmo_fsm_vty_add_cmds();
+
+ bts_vty_init(tall_bts_ctx);
+ e1inp_vty_init();
+
+ logging_vty_add_deprecated_subsys(tall_bts_ctx, "sum");
+
handle_options(argc, argv);
- bts = gsm_bts_alloc(tall_bts_ctx, 0);
- if (!bts) {
- fprintf(stderr, "Failed to create BTS structure\n");
+ fprintf(stderr, "((*))\n |\n / \\ OsmoBTS\n");
+ if (vty_test_mode)
+ fprintf(stderr, "--- VTY test mode: not connecting to BSC, not exiting ---\n");
+
+ g_bts_sm = gsm_bts_sm_alloc(tall_bts_ctx);
+ if (!g_bts_sm) {
+ fprintf(stderr, "Failed to create BTS Site Manager structure\n");
exit(1);
}
- e1inp_vty_init();
- bts_vty_init(bts);
+ g_bts = gsm_bts_alloc(g_bts_sm, 0);
+ if (!g_bts) {
+ fprintf(stderr, "Failed to create BTS structure\n");
+ exit(1);
+ }
/* enable realtime priority for us */
if (rt_prio != -1) {
@@ -265,21 +319,12 @@ int bts_main(int argc, char **argv)
}
}
- 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) {
+ if (bts_init(g_bts) < 0) {
fprintf(stderr, "unable to open bts\n");
exit(1);
}
- abis_init(bts);
+ abis_init(g_bts);
rc = vty_read_config_file(config_file, NULL);
if (rc < 0) {
@@ -293,48 +338,78 @@ int bts_main(int argc, char **argv)
exit(1);
}
- llist_for_each_entry(trx, &bts->trx_list, list) {
- if (!trx->role_bts.l1h) {
+ llist_for_each_entry(trx, &g_bts->trx_list, list) {
+ if (!trx->pinst) {
fprintf(stderr, "TRX %u has no associated PHY instance\n",
trx->nr);
exit(1);
}
}
- write_pid_file("osmo-bts");
+ /* Accept a GSMTAP host from VTY config, but a commandline option overrides that. */
+ if (gsmtap_ip != NULL) {
+ if (g_bts->gsmtap.remote_host != NULL) {
+ LOGP(DLGLOBAL, LOGL_NOTICE,
+ "Command line argument '-i %s' overrides "
+ "'gsmtap-remote-host %s' from the config file\n",
+ gsmtap_ip, g_bts->gsmtap.remote_host);
+ talloc_free(g_bts->gsmtap.remote_host);
+ }
+ g_bts->gsmtap.remote_host = talloc_strdup(g_bts, gsmtap_ip);
+ }
- bts_controlif_setup(bts, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_BTS);
+ /* TODO: move this to gsm_bts_alloc() */
+ if (g_bts->gsmtap.remote_host != NULL) {
+ LOGP(DLGLOBAL, LOGL_NOTICE,
+ "Setting up GSMTAP Um forwarding '%s->'%s:%u'\n",
+ g_bts->gsmtap.local_host, g_bts->gsmtap.remote_host, GSMTAP_UDP_PORT);
+ g_bts->gsmtap.inst = gsmtap_source_init2(g_bts->gsmtap.local_host, 0,
+ g_bts->gsmtap.remote_host, GSMTAP_UDP_PORT, 1);
+ if (g_bts->gsmtap.inst == NULL) {
+ fprintf(stderr, "Failed during gsmtap_source_init2()\n");
+ exit(1);
+ }
+ gsmtap_source_add_sink(g_bts->gsmtap.inst);
+ }
- rc = telnet_init_dynif(tall_bts_ctx, NULL, vty_get_bind_addr(),
- g_vty_port_num);
+ bts_controlif_setup(g_bts, OSMO_CTRL_PORT_BTS);
+
+ rc = telnet_init_default(tall_bts_ctx, NULL, g_vty_port_num);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
}
- if (pcu_sock_init(bts->pcu.sock_path)) {
+ if (pcu_sock_init(g_bts->pcu.sock_path, g_bts->pcu.sock_wqueue_len_max)) {
fprintf(stderr, "PCU L1 socket failed\n");
exit(1);
}
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
- //signal(SIGABRT, &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");
+ if (bts_osmux_open(g_bts) < 0) {
+ fprintf(stderr, "Osmux setup failed\n");
exit(1);
}
- line = abis_open(bts, bts->bsc_oml_host, "sysmoBTS");
- if (!line) {
- fprintf(stderr, "unable to connect to BSC\n");
- exit(2);
+ if (vty_test_mode) {
+ /* Just select-loop without connecting to the BSC, don't exit. This allows running tests on the VTY
+ * telnet port. */
+ while (!quit) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+ return EXIT_SUCCESS;
}
+ if (abis_open(g_bts, "osmo-bts") != 0)
+ exit(1);
+
rc = phy_links_open();
if (rc < 0) {
fprintf(stderr, "unable to open PHY link(s)\n");
diff --git a/src/common/measurement.c b/src/common/measurement.c
index c2001dae..19bff714 100644
--- a/src/common/measurement.c
+++ b/src/common/measurement.c
@@ -2,19 +2,40 @@
#include <stdint.h>
#include <errno.h>
-#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/endian.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/protocol/gsm_44_004.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>
+#include <osmo-bts/power_control.h>
+#include <osmo-bts/ta_control.h>
+
+/* Active TDMA frame subset for TCH/H in DTX mode (see 3GPP TS 45.008 Section 8.3).
+ * This mapping is used to determine if a L2 block starting at the given TDMA FN
+ * belongs to the SUB set and thus shall always be transmitted in DTX mode. */
+static const uint8_t ts45008_dtx_tchh_speech_fn_map[104] = {
+ /* TCH/H(0): 0, 2, 4, 6, 52, 54, 56, 58 */
+ [0] = 1, /* block { 0, 2, 4, 6} */
+ [52] = 1, /* block {52, 54, 56, 58} */
+ /* TCH/H(1): 14, 16, 18, 20, 66, 68, 70, 72 */
+ [14] = 1, /* block {14, 16, 18, 20} */
+ [66] = 1, /* block {66, 68, 70, 72} */
+};
-/* 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 };
+static const uint8_t ts45008_dtx_tchh_data_fn_map[104] = {
+ /* UL TCH/H(0): 52, 54, 56, 58, 60, 62, 65, 67, 69, 71 */
+ [52] = 1, /* block {52, 54, 56, 58, 60, 62} */
+ [60] = 1, /* block {60, 62, 65, 67, 69, 71} */
+ /* UL TCH/H(1): 70, 72, 74, 76, 79, 81, 83, 85, 87, 89 */
+ [70] = 1, /* block {70, 72, 74, 76, 79, 81} */
+ [79] = 1, /* block {79, 81, 83, 85, 87, 89} */
+};
/* 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
@@ -23,88 +44,74 @@ static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 };
* 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) {
+static const struct bts_ul_meas measurement_dummy = {
.ber10k = MEASUREMENT_DUMMY_BER,
.ta_offs_256bits = 0,
- .c_i = 0,
+ .ci_cb = 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)
+bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn)
{
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 */
+ * implemented here. We implement the logic for both speech and data (CSD). */
+
+ /* AMR is special, SID frames may be scheduled dynamically at any time */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ return false;
switch (lchan->type) {
case GSM_LCHAN_TCH_F:
switch (lchan->tch_mode) {
- case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SIGN: /* TCH/F sign: DTX *is* permitted */
case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
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)
+ case GSM48_CMODE_SPEECH_V2_VAMOS:
+ /* Active TDMA frame subset for TCH/F: 52, 53, 54, 55, 56, 57, 58, 59.
+ * There is only one *complete* block in this subset starting at FN=52.
+ * Incomplete blocks {... 52, 53, 54, 55} and {56, 57, 58, 59 ...}
+ * contain only 50% of the useful bits (partial SID) and thus ~50% BER. */
+ if (fn104 == 52)
return true;
break;
+ case GSM48_CMODE_DATA_12k0: /* TCH/F9.6 */
+ case GSM48_CMODE_DATA_6k0: /* TCH/F4.8 */
+ /* FIXME: The RXQUAL_SUB (not RXLEV!) report shall include measurements on
+ * the TDMA frames given in the table of subclause 8.3 only if L2 fill frames
+ * have been received as FACCH/F frames at the corresponding frame positions. */
default:
- LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
- gsm_lchan_name(lchan), lchan->tch_mode);
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA)
+ return fn104 == 52;
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_ERROR, "Unsupported lchan->tch_mode %u\n", 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)
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ if (ts45008_dtx_tchh_speech_fn_map[fn104])
return true;
break;
case GSM48_CMODE_SIGN:
/* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are
* SUB */
return true;
+ case GSM48_CMODE_DATA_6k0: /* TCH/H4.8 */
+ case GSM48_CMODE_DATA_3k6: /* TCH/H2.4 */
+ /* FIXME: The RXQUAL_SUB (not RXLEV!) report shall include measurements on
+ * the TDMA frames given in the table of subclause 8.3 only if L2 fill frames
+ * have been received as FACCH/H frames at the corresponding frame positions. */
default:
- LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
- gsm_lchan_name(lchan), lchan->tch_mode);
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA)
+ return ts45008_dtx_tchh_data_fn_map[fn104] == 1;
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_ERROR, "Unsupported lchan->tch_mode %u\n", lchan->tch_mode);
break;
}
break;
@@ -250,11 +257,6 @@ int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn)
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);
@@ -288,9 +290,8 @@ int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn)
}
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));
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, "meas period end fn_mod:%d, status:%d, pchan:%s\n", fn_mod,
+ rc, gsm_pchan_name(pchan));
}
return rc;
@@ -305,50 +306,51 @@ static uint8_t modulus_by_lchan(struct gsm_lchan *lchan)
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)
+int lchan_new_ul_meas(struct gsm_lchan *lchan,
+ const struct bts_ul_meas *ulm,
+ uint32_t fn)
{
uint32_t fn_mod = fn % modulus_by_lchan(lchan);
+ struct bts_ul_meas *dest;
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);
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_NOTICE,
+ "measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n",
+ 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);
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_NOTICE,
+ "no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n", lchan->meas.num_ul_meas,
+ fn_mod);
return -ENOSPC;
}
+ dest = &lchan->meas.uplink[lchan->meas.num_ul_meas++];
+ memcpy(dest, ulm, sizeof(*ulm));
+
/* 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 */
+ * In this function, we only deal with the common logic as per the TS 45.008 tables */
if (!ulm->is_sub)
- ulm->is_sub = ts45008_83_is_sub(lchan, fn, false);
+ dest->is_sub = ts45008_83_is_sub(lchan, fn);
- 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));
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG,
+ "adding a %s measurement (ber10k=%u, ta_offs=%d, ci_cB=%d, rssi=-%u), num_ul_meas=%d, fn_mod=%u\n",
+ dest->is_sub ? "SUB" : "FULL", ulm->ber10k, ulm->ta_offs_256bits, ulm->ci_cb, ulm->inv_rssi,
+ lchan->meas.num_ul_meas, fn_mod);
lchan->meas.last_fn = fn;
@@ -398,11 +400,16 @@ static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan)
switch (pchan) {
case GSM_PCHAN_TCH_F:
- /* 24 for TCH + 1 for SACCH */
+ /* 24 blocks for TCH + 1 for SACCH */
return 25;
case GSM_PCHAN_TCH_H:
- /* 24 half-blocks for TCH + 1 for SACCH */
- return 25;
+ if (lchan->tch_mode == GSM48_CMODE_SIGN) {
+ /* 12 blocks for TCH + 1 for SACCH */
+ return 13;
+ } else {
+ /* 24 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 */
@@ -417,23 +424,51 @@ static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan)
}
/* 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)
+ * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX)
+ * Return value N: (N < 0) -- at least N SUB frames expected;
+ * (N > 0) -- exactly N SUB frames expected;
+ * (N == 0) - unknown channel type/mode? */
+static 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;
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA)
+ return 1 + 1; /* 1 x SACCH + 1 x FACCH */
+ /* else: signalling or speech */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN: /* TCH/F sign: DTX *is* permitted */
+ case GSM48_CMODE_SPEECH_V1: /* TCH/FS */
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ case GSM48_CMODE_SPEECH_EFR: /* TCH/EFS */
+ case GSM48_CMODE_SPEECH_V2_VAMOS:
+ return 1 + 1; /* 1 x SACCH + 1 x TCH */
+ case GSM48_CMODE_SPEECH_AMR: /* TCH/AFS */
+ case GSM48_CMODE_SPEECH_V3_VAMOS:
+ case GSM48_CMODE_SPEECH_V4: /* O-TCH/WFS */
+ case GSM48_CMODE_SPEECH_V5: /* TCH/WFS */
+ case GSM48_CMODE_SPEECH_V5_VAMOS:
+ default:
+ return -1; /* at least 1 x SACCH + M x TCH (variable) */
+ }
case GSM_PCHAN_TCH_H:
- /* 1 block SDCCH, 4 half-blocks TCH */
- return 5;
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA)
+ return 1 + 2; /* 1 x SACCH + 2 x FACCH */
+ /* else: signalling or speech */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN: /* TCH/H sign: DTX *is not* permitted */
+ return 1 + 12; /* 1 x SACCH + 12 x TCH */
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ return 1 + 2; /* 1 x SACCH + 2 x TCH */
+ case GSM48_CMODE_SPEECH_AMR: /* TCH/AHS */
+ case GSM48_CMODE_SPEECH_V3_VAMOS:
+ case GSM48_CMODE_SPEECH_V4: /* O-TCH/WHS */
+ case GSM48_CMODE_SPEECH_V6: /* O-TCH/AHS */
+ default:
+ return -1; /* at least 1 x SACCH + M x TCH (variable) */
+ }
case GSM_PCHAN_SDCCH8_SACCH8C:
case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
/* no DTX here, all blocks must be present! */
@@ -471,7 +506,7 @@ static void lchan_meas_compute_extended(struct gsm_lchan *lchan)
/* 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;
+ uint64_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
@@ -542,13 +577,15 @@ 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;
+ int32_t ci_full_sum = 0;
uint32_t ber_sub_sum = 0;
uint32_t irssi_sub_sum = 0;
+ int32_t ci_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;
+ int num_meas_sub_expect;
unsigned int num_ul_meas;
unsigned int num_ul_meas_actual = 0;
unsigned int num_ul_meas_subst = 0;
@@ -560,33 +597,25 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
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)));
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Calculating measurement results "
+ "for physical channel: %s\n", 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;
- }
+ num_meas_sub_expect = lchan_meas_sub_num_expected(lchan);
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);
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received %u UL measurements, expected %u\n",
+ 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);
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received %u excess UL measurements\n",
+ num_ul_meas_excess);
/* Measurement computation step 1: add up */
for (i = 0; i < num_ul_meas; i++) {
@@ -610,18 +639,24 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
m = &lchan->meas.uplink[i + num_ul_meas_excess];
if (m->is_sub) {
irssi_sub_sum += m->inv_rssi;
+ ci_sub_sum += m->ci_cb;
num_meas_sub_actual++;
is_sub = true;
}
irssi_full_sum += m->inv_rssi;
ta256b_sum += m->ta_offs_256bits;
+ ci_full_sum += m->ci_cb;
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;
+
+ /* only if we know the exact number of SUB measurements */
+ if (num_meas_sub_expect >= 0) {
+ if (num_meas_sub < num_meas_sub_expect) {
+ num_meas_sub_subst++;
+ is_sub = true;
+ }
}
num_ul_meas_subst++;
@@ -634,50 +669,71 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
}
}
- 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. */
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Replaced %u measurements with dummy values, "
+ "from which %u were SUB measurements\n", num_ul_meas_subst, num_meas_sub_subst);
+
+ /* 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. */
+ if (num_meas_sub_expect < 0) {
+ num_meas_sub_expect = -num_meas_sub_expect;
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
+ "Received UL measurements contain %u SUB measurements, expected at least %d\n",
+ num_meas_sub_actual, num_meas_sub_expect);
+ if (OSMO_UNLIKELY(num_meas_sub < num_meas_sub_expect)) {
+ LOGPLCHAN(lchan, DMEAS, LOGL_ERROR,
+ "Incorrect number of SUB measurements detected! "
+ "(%u vs exp >=%d)\n", num_meas_sub, num_meas_sub_expect);
+ }
+ } else {
+ LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG,
+ "Received UL measurements contain %u SUB measurements, expected %d\n",
+ num_meas_sub_actual, num_meas_sub_expect);
+ if (OSMO_UNLIKELY(num_meas_sub != num_meas_sub_expect)) {
+ LOGPLCHAN(lchan, DMEAS, LOGL_ERROR,
+ "Incorrect number of SUB measurements detected! "
+ "(%u vs exp %d)\n", num_meas_sub, num_meas_sub_expect);
+ }
}
/* Measurement computation step 2: divide */
ber_full_sum = ber_full_sum / num_ul_meas;
if (!irssi_full_sum)
- ber_full_sum = MEASUREMENT_DUMMY_IRSSI;
+ irssi_full_sum = MEASUREMENT_DUMMY_IRSSI;
else
irssi_full_sum = irssi_full_sum / num_ul_meas_actual;
- if (!num_ul_meas_actual)
+ if (!num_ul_meas_actual) {
ta256b_sum = lchan->meas.ms_toa256;
- else
- ta256b_sum = ta256b_sum / num_ul_meas_actual;
+ ci_full_sum = lchan->meas.ul_ci_cb_full;
+ } else {
+ ta256b_sum = ta256b_sum / (signed)num_ul_meas_actual;
+ ci_full_sum = ci_full_sum / (signed)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)
+ if (!num_meas_sub_actual) {
irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI;
- else
+ ci_sub_sum = lchan->meas.ul_ci_cb_sub;
+ } else {
irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual;
+ ci_sub_sum = ci_sub_sum / (signed)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);
+ LOGPLCHAN(lchan, DMEAS, LOGL_INFO,
+ "Computed TA256(% 4d), BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), C/I-FULL(% 4d cB), "
+ "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm), C/I-SUB(% 4d cB)\n",
+ ta256b_sum, ber_full_sum / 100, ber_full_sum % 100, irssi_full_sum, ci_full_sum,
+ ber_sub_sum / 100, ber_sub_sum % 100, irssi_sub_sum, ci_sub_sum);
/* store results */
mru = &lchan->meas.ul_res;
@@ -686,11 +742,15 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
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;
+ lchan->meas.ul_ci_cb_full = ci_full_sum;
+ lchan->meas.ul_ci_cb_sub = ci_sub_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);
+ LOGPLCHAN(lchan, DMEAS, LOGL_INFO,
+ "UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u), RXQUAL_FULL(%u), RXQUAL_SUB(%u), "
+ "num_meas_sub(%u), num_ul_meas(%u)\n",
+ 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;
@@ -698,7 +758,7 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
lchan->meas.num_ul_meas = 0;
- /* return 1 to indicte that the computation has been done and the next
+ /* return 1 to indicate that the computation has been done and the next
* interval begins. */
return 1;
}
@@ -707,7 +767,9 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn)
* 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)
+int lchan_meas_process_measurement(struct gsm_lchan *lchan,
+ const struct bts_ul_meas *ulm,
+ uint32_t fn)
{
lchan_new_ul_meas(lchan, ulm, fn);
return lchan_meas_check_compute(lchan, fn);
@@ -721,3 +783,230 @@ void lchan_meas_reset(struct gsm_lchan *lchan)
memset(&lchan->meas, 0, sizeof(lchan->meas));
lchan->meas.last_fn = LCHAN_FN_DUMMY;
}
+
+static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, uint8_t ta)
+{
+ return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - ta);
+}
+
+static inline bool ms_to_valid(const struct gsm_lchan *lchan)
+{
+ return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0);
+}
+
+/* Decide if repeated FACCH should be applied or not. If RXQUAL level, that the
+ * MS reports is high enough, FACCH repetition is not needed. */
+static void repeated_dl_facch_active_decision(struct gsm_lchan *lchan,
+ const struct gsm48_meas_res *meas_res)
+{
+ uint8_t upper;
+ uint8_t lower;
+ uint8_t rxqual;
+ bool prev_repeated_dl_facch_active = lchan->rep_acch.dl_facch_active;
+
+ /* This is an optimization so that we exit as quickly as possible if
+ * there are no FACCH repetition capabilities present. However If the
+ * repeated FACCH capabilities vanish for whatever reason, we must be
+ * sure that FACCH repetition is disabled. */
+ if (!lchan->rep_acch_cap.dl_facch_cmd
+ && !lchan->rep_acch_cap.dl_facch_all) {
+ lchan->rep_acch.dl_facch_active = false;
+ goto out;
+ }
+
+ /* Threshold disabled (always on) */
+ if (lchan->rep_acch_cap.rxqual == 0) {
+ lchan->rep_acch.dl_facch_active = true;
+ goto out;
+ }
+
+ /* When the MS sets the SRR bit in the UL-SACCH L1 header
+ * (repeated SACCH requested) then it makes sense to enable
+ * FACCH repetition too. */
+ if (lchan->meas.l1_info.srr_sro) {
+ lchan->rep_acch.dl_facch_active = true;
+ goto out;
+ }
+
+ /* Parse MS measurement results */
+ if (meas_res == NULL)
+ goto out;
+ if (!gsm48_meas_res_is_valid(meas_res))
+ goto out;
+
+ /* If the RXQUAL level at the MS drops under a certain threshold
+ * we enable FACCH repetition. */
+ upper = lchan->rep_acch_cap.rxqual;
+ if (upper > 2)
+ lower = lchan->rep_acch_cap.rxqual - 2;
+ else
+ lower = 0;
+
+ /* When downlink DTX is applied, use RXQUAL-SUB, otherwise use
+ * RXQUAL-FULL. */
+ if (meas_res->dtx_used)
+ rxqual = meas_res->rxqual_sub;
+ else
+ rxqual = meas_res->rxqual_full;
+
+ if (rxqual >= upper)
+ lchan->rep_acch.dl_facch_active = true;
+ else if (rxqual <= lower)
+ lchan->rep_acch.dl_facch_active = false;
+
+out:
+ if (lchan->rep_acch.dl_facch_active == prev_repeated_dl_facch_active)
+ return;
+ if (lchan->rep_acch.dl_facch_active)
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-FACCH repetition: inactive => active\n");
+ else
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-FACCH repetition: active => inactive\n");
+}
+
+static void acch_overpower_active_decision(struct gsm_lchan *lchan,
+ const struct gsm48_meas_res *meas_res)
+{
+ const bool old = lchan->top_acch_active;
+ uint8_t upper, lower, rxqual;
+
+ /* ACCH overpower is not allowed => nothing to do */
+ if (lchan->top_acch_cap.overpower_db == 0)
+ return;
+ /* RxQual threshold is disabled => overpower is always on */
+ if (lchan->top_acch_cap.rxqual == 0)
+ return;
+
+ /* If DTx is active on Downlink, use the '-SUB' */
+ if (meas_res->dtx_used)
+ rxqual = meas_res->rxqual_sub;
+ else /* ... otherwise use the '-FULL' */
+ rxqual = meas_res->rxqual_full;
+
+ upper = lchan->top_acch_cap.rxqual;
+ if (upper > 2)
+ lower = upper - 2;
+ else
+ lower = 0;
+
+ if (rxqual >= upper)
+ lchan->top_acch_active = true;
+ else if (rxqual <= lower)
+ lchan->top_acch_active = false;
+
+ if (lchan->top_acch_active != old) {
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "Temporary ACCH overpower: %s\n",
+ lchan->top_acch_active ? "inactive => active"
+ : "active => inactive");
+ }
+}
+
+static bool data_is_rr_meas_rep(const uint8_t *data)
+{
+ const struct gsm48_hdr *gh = (void *)(data + 5);
+ const uint8_t *lapdm_hdr = (void *)(data + 2);
+
+ /* LAPDm address field: SAPI=0, C/R=0, EA=1 */
+ if (lapdm_hdr[0] != 0x01)
+ return false;
+ /* LAPDm control field: U, func=UI */
+ if (lapdm_hdr[1] != 0x03)
+ return false;
+ /* Protocol discriminator: RR */
+ if (gh->proto_discr != GSM48_PDISC_RR)
+ return false;
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_EXT_MEAS_REP:
+ case GSM48_MT_RR_MEAS_REP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Called every time a SACCH block is received from lower layers */
+void lchan_meas_handle_sacch(struct gsm_lchan *lchan, struct msgb *msg)
+{
+ const struct gsm48_meas_res *mr = NULL;
+ const struct gsm48_hdr *gh = NULL;
+ int timing_offset, rc;
+ bool dtxu_used = true; /* safe default assumption */
+ uint8_t ms_pwr;
+ uint8_t ms_ta;
+ int8_t ul_rssi;
+ int16_t ul_ci_cb;
+ uint8_t *l3;
+ unsigned int l3_len;
+
+ if (msgb_l2len(msg) == GSM_MACBLOCK_LEN) {
+ /* Some brilliant engineer decided that the ordering of
+ * fields on the Um interface is different from the
+ * order of fields in RSL. See 3GPP TS 44.004 (section 7.2)
+ * vs. 3GPP TS 48.058 (section 9.3.10). */
+ const struct gsm_sacch_l1_hdr *l1h = msgb_l2(msg);
+ lchan->meas.l1_info.ms_pwr = l1h->ms_pwr;
+ lchan->meas.l1_info.fpc_epc = l1h->fpc_epc;
+ lchan->meas.l1_info.srr_sro = l1h->srr_sro;
+ lchan->meas.l1_info.ta = l1h->ta;
+ lchan->meas.flags |= LC_UL_M_F_L1_VALID;
+
+ /* Check if this is a Measurement Report */
+ if (data_is_rr_meas_rep(msgb_l2(msg))) {
+ /* Skip both L1 SACCH and LAPDm headers */
+ msg->l3h = (void *)(msg->l2h + 2 + 3);
+ gh = msgb_l3(msg);
+ }
+
+ ms_pwr = lchan->meas.l1_info.ms_pwr;
+ ms_ta = lchan->meas.l1_info.ta;
+ } else {
+ lchan->meas.flags &= ~LC_UL_M_F_L1_VALID;
+ ms_pwr = lchan->ms_power_ctrl.current;
+ ms_ta = lchan->ta_ctrl.current;
+ }
+
+ timing_offset = ms_to_valid(lchan) ? ms_to2rsl(lchan, ms_ta) : -1;
+ l3 = msgb_l3(msg);
+ l3_len = l3 ? msgb_l3len(msg) : 0;
+ rc = rsl_tx_meas_res(lchan, l3, l3_len, timing_offset);
+ if (rc == 0) /* Count successful transmissions */
+ lchan->meas.res_nr++;
+
+ /* Run control loops now that we have all the information: */
+ /* 3GPP TS 45.008 sec 4.2: UL L1 SACCH Header contains TA and
+ * MS_PWR used "for the last burst of the previous SACCH
+ * period". Since MS must use the values provided in DL SACCH
+ * starting at next meas period, the value of the "last burst"
+ * is actually the value used in the entire meas period. Since
+ * it contains info about the previous meas period, we want to
+ * feed the Control Loop with the measurements for the same
+ * period (the previous one), which is stored in lchan->meas(.ul_res):
+ */
+ if (gh && gh->msg_type == GSM48_MT_RR_MEAS_REP) {
+ mr = (const struct gsm48_meas_res *)gh->data;
+ if (gsm48_meas_res_is_valid(mr))
+ dtxu_used = mr->dtx_used;
+ }
+
+ if (dtxu_used) {
+ ul_rssi = rxlev2dbm(lchan->meas.ul_res.sub.rx_lev);
+ ul_ci_cb = lchan->meas.ul_ci_cb_sub;
+ } else {
+ ul_rssi = rxlev2dbm(lchan->meas.ul_res.full.rx_lev);
+ ul_ci_cb = lchan->meas.ul_ci_cb_full;
+ }
+ lchan_ms_ta_ctrl(lchan, ms_ta, lchan->meas.ms_toa256);
+ lchan_ms_pwr_ctrl(lchan, ms_pwr, ul_rssi, ul_ci_cb);
+ if (mr && gsm48_meas_res_is_valid(mr)) {
+ lchan_bs_pwr_ctrl(lchan, mr);
+ acch_overpower_active_decision(lchan, mr);
+ }
+
+ repeated_dl_facch_active_decision(lchan, mr);
+
+ /* Reset state for next iteration */
+ lchan->tch.dtx.dl_active = false;
+ lchan->meas.flags &= ~LC_UL_M_F_OSMO_EXT_VALID;
+ lchan->ms_t_offs = -1;
+ lchan->p_offs = -1;
+}
diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c
index 52b05668..04a0c344 100644
--- a/src/common/msg_utils.c
+++ b/src/common/msg_utils.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -23,6 +23,7 @@
#include <osmo-bts/oml.h>
#include <osmo-bts/amr.h>
#include <osmo-bts/rsl.h>
+#include <osmo-bts/bts.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/gsm/protocol/gsm_12_21.h>
@@ -320,17 +321,17 @@ static inline bool dtx_amr_sid_optional(struct gsm_lchan *lchan, uint32_t fn)
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->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) {
+ } 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) {
+ } 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);
@@ -379,15 +380,19 @@ static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn)
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) {
+ switch (lchan->tch_mode) {
+ case 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));
+ case GSM48_CMODE_SPEECH_EFR:
+ return fn_chk(f, fn, ARRAY_SIZE(f));
+ default:
+ return false;
}
- return false;
}
/*! \brief Check if DTX DL AMR is enabled for a given lchan (it have proper type,
@@ -465,10 +470,6 @@ void dtx_int_signal(struct gsm_lchan *lchan)
*/
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;
diff --git a/src/common/nm_bb_transc_fsm.c b/src/common/nm_bb_transc_fsm.c
new file mode 100644
index 00000000..b173102e
--- /dev/null
+++ b/src/common/nm_bb_transc_fsm.c
@@ -0,0 +1,325 @@
+/* NM Radio Carrier FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/phy_link.h>
+
+#define X(s) (1 << (s))
+
+#define nm_bb_transc_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static void ev_dispatch_children(struct gsm_bts_bb_trx *bb_transc, uint32_t event)
+{
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ uint8_t tn;
+
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ osmo_fsm_inst_dispatch(ts->mo.fi, event, NULL);
+ }
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ /* Reset state: */
+ TALLOC_FREE(bb_transc->mo.nm_attr);
+
+ bb_transc->mo.setattr_success = false;
+ bb_transc->mo.opstart_success = false;
+ oml_mo_state_chg(&bb_transc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SW_ACT:
+ oml_mo_tx_sw_act_rep(&bb_transc->mo);
+ nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_DISABLED_OFFLINE);
+ ev_dispatch_children(bb_transc, event);
+ return;
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&bb_transc->mo);
+ ev_dispatch_children(bb_transc, event);
+ return;
+ case NM_EV_RSL_UP:
+ return;
+ case NM_EV_RSL_DOWN:
+ return;
+ case NM_EV_PHYLINK_UP:
+ return;
+ case NM_EV_PHYLINK_DOWN:
+ return;
+ case NM_EV_DISABLE:
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ int i;
+
+ bb_transc->mo.setattr_success = false;
+ bb_transc->mo.opstart_success = false;
+ oml_mo_state_chg(&bb_transc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+
+ if (prev_state == NM_BBTRANSC_ST_OP_ENABLED) {
+ for (i = 0; i < TRX_NR_TS; i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_BBTRANSC_DISABLED, NULL);
+ }
+ }
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ struct gsm_bts *bts = trx->bts;
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ bool phy_state_connected;
+ bool rsl_link_connected;
+ int rc;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&bb_transc->mo);
+ ev_dispatch_children(bb_transc, event);
+ return;
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(trx->bts, setattr_data->msg,
+ &bb_transc->mo, bb_transc);
+ bb_transc->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ return;
+ case NM_EV_RX_OPSTART:
+#if 0
+ /* Disabled because osmo-bsc doesn't send SetAttr on BB_TRANSC object */
+ if (!bb_transc->mo.setattr_success) {
+ oml_mo_opstart_nack(&bb_transc->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+#endif
+ /* Connect RSL link: */
+ if (bts->variant == BTS_OSMO_OMLDUMMY) {
+ LOGPFSML(fi, LOGL_NOTICE, "Not connecting RSL in OML-DUMMY!\n");
+ } else {
+ rc = e1inp_ipa_bts_rsl_connect_n(bts->oml_link->ts->line,
+ bb_transc->rsl.rem_addrstr.ip,
+ bb_transc->rsl.rem_addrstr.port, trx->nr);
+ if (rc < 0) {
+ LOGPFSML(fi, LOGL_NOTICE, "Error connecting IPA RSL: %d\n", rc);
+ oml_mo_opstart_nack(&bb_transc->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ }
+ bts_model_opstart(trx->bts, &bb_transc->mo, bb_transc);
+ return;
+ case NM_EV_OPSTART_ACK:
+ bb_transc->mo.opstart_success = true;
+ oml_mo_opstart_ack(&bb_transc->mo);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ bb_transc->mo.opstart_success = false;
+ oml_mo_opstart_nack(&bb_transc->mo, (int)(intptr_t)data);
+ return;
+ case NM_EV_RSL_UP:
+ break; /* check statechg below */
+ case NM_EV_RSL_DOWN:
+ return;
+ case NM_EV_PHYLINK_UP:
+ break; /* check statechg below */
+ case NM_EV_PHYLINK_DOWN:
+ return;
+ case NM_EV_DISABLE:
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+
+ if (bts->variant != BTS_OSMO_OMLDUMMY) { /* In OMLDUMMY, phy=NULL */
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ phy_state_connected = phy_link_state_get(pinst->phy_link) == PHY_LINK_CONNECTED;
+ rsl_link_connected = !!trx->bb_transc.rsl.link;
+ } else {
+ phy_state_connected = true;
+ rsl_link_connected = true;
+ }
+
+ /* We so far don't expect any SetAttributes for this NM object */
+ if (rsl_link_connected && phy_state_connected &&
+ bb_transc->mo.opstart_success) {
+ nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_ENABLED);
+ } else {
+ LOGPFSML(fi, LOGL_INFO, "Delay switch to operative state Enabled, wait for:%s%s%s\n",
+ rsl_link_connected ? "" : " rsl",
+ phy_state_connected ? "" : " phy",
+ bb_transc->mo.opstart_success ? "" : " opstart");
+
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ uint8_t tn;
+
+ oml_mo_state_chg(&bb_transc->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+ /* Mark Dependency TS as Offline (ready to be Opstarted) */
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_BBTRANSC_ENABLED, NULL);
+ }
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case NM_EV_RSL_DOWN:
+ break;
+ case NM_EV_PHYLINK_DOWN:
+ break;
+ case NM_EV_DISABLE:
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_DISABLED_OFFLINE);
+}
+
+static void nm_bb_transc_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&bb_transc->mo, -1, -1, NM_STATE_SHUTDOWN);
+
+ /* Propagate event to children: */
+ ev_dispatch_children(bb_transc, event);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ /* Propagate event to children: */
+ ev_dispatch_children(bb_transc, event);
+ nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_bb_transc_fsm_states[] = {
+ [NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT) |
+ X(NM_EV_OML_UP) |
+ X(NM_EV_RSL_UP) |
+ X(NM_EV_RSL_DOWN) |
+ X(NM_EV_PHYLINK_UP) |
+ X(NM_EV_PHYLINK_DOWN) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BBTRANSC_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_BBTRANSC_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP) |
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_RSL_UP) |
+ X(NM_EV_RSL_DOWN) |
+ X(NM_EV_PHYLINK_UP) |
+ X(NM_EV_PHYLINK_DOWN) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BBTRANSC_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_BBTRANSC_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_RSL_DOWN) |
+ X(NM_EV_PHYLINK_DOWN) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BBTRANSC_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_bb_transc_fsm = {
+ .name = "NM_BBTRANSC_OP",
+ .states = nm_bb_transc_fsm_states,
+ .num_states = ARRAY_SIZE(nm_bb_transc_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_bb_transc_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_bb_transc_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_bb_transc_fsm) == 0);
+}
diff --git a/src/common/nm_bts_fsm.c b/src/common/nm_bts_fsm.c
new file mode 100644
index 00000000..36aad733
--- /dev/null
+++ b/src/common/nm_bts_fsm.c
@@ -0,0 +1,230 @@
+/* NM BTS FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/cbch.h>
+#include <osmo-bts/notification.h>
+
+#define X(s) (1 << (s))
+
+#define nm_bts_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static void ev_dispatch_children(struct gsm_bts *bts, uint32_t event)
+{
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ osmo_fsm_inst_dispatch(trx->mo.fi, event, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, event, NULL);
+ }
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ /* Reset state: */
+ bts->si_valid = 0;
+ bts->bsic_configured = false;
+ bts->bsic = 0xff; /* invalid value */
+ TALLOC_FREE(bts->mo.nm_attr);
+ bts_cbch_reset(bts);
+ bts_asci_notification_reset(bts);
+ if (bts->c0_power_red_db > 0)
+ bts_set_c0_pwr_red(bts, 0);
+
+ bts->mo.setattr_success = false;
+ bts->mo.opstart_success = false;
+ oml_mo_state_chg(&bts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct gsm_bts_trx *trx;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* automatic SW_ACT upon OML link establishment: */
+ oml_mo_tx_sw_act_rep(&bts->mo);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->bts->variant == BTS_OSMO_OMLDUMMY) /* In OMLDUMMY, phy=NULL */
+ continue;
+ /* During startup, phy_links are already opened, but if we are
+ * re-connecting, phy_link was closed when disconnected from
+ * previous BSC, so let's re-open it.
+ */
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+ if (phy_link_state_get(plink) == PHY_LINK_SHUTDOWN)
+ bts_model_phy_link_open(plink);
+ }
+
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE);
+ ev_dispatch_children(bts, event);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ bts->mo.setattr_success = false;
+ bts->mo.opstart_success = false;
+ oml_mo_state_chg(&bts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg, &bts->mo, bts);
+ bts->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!bts->mo.setattr_success) {
+ oml_mo_opstart_nack(&bts->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &bts->mo, bts);
+ break;
+ case NM_EV_OPSTART_ACK:
+ bts->mo.opstart_success = true;
+ oml_mo_opstart_ack(&bts->mo);
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ bts->mo.opstart_success = false;
+ oml_mo_opstart_nack(&bts->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ oml_mo_state_chg(&bts->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void nm_bts_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&bts->mo, -1, -1, NM_STATE_SHUTDOWN);
+
+ /* Propagate event to children: */
+ ev_dispatch_children(bts, event);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ /* Propagate event to children: */
+ ev_dispatch_children(bts, event);
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_bts_fsm_states[] = {
+ [NM_BTS_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP),
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_BTS_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK),
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_BTS_ST_OP_ENABLED] = {
+ .in_event_mask = 0,
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_bts_fsm = {
+ .name = "NM_BTS_OP",
+ .states = nm_bts_fsm_states,
+ .num_states = ARRAY_SIZE(nm_bts_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_bts_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_bts_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_bts_fsm) == 0);
+}
diff --git a/src/common/nm_bts_sm_fsm.c b/src/common/nm_bts_sm_fsm.c
new file mode 100644
index 00000000..9ac0edd9
--- /dev/null
+++ b/src/common/nm_bts_sm_fsm.c
@@ -0,0 +1,209 @@
+/* NM BTS Site Manager FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/phy_link.h>
+
+#define X(s) (1 << (s))
+
+#define nm_bts_sm_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+
+static void ev_dispatch_children(struct gsm_bts_sm *site_mgr, uint32_t event)
+{
+ struct gsm_bts *bts;
+ osmo_fsm_inst_dispatch(site_mgr->gprs.nse.mo.fi, event, NULL);
+ llist_for_each_entry(bts, &site_mgr->bts_list, list) {
+ osmo_fsm_inst_dispatch(bts->mo.fi, event, NULL);
+ }
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ site_mgr->mo.setattr_success = false;
+ site_mgr->mo.opstart_success = false;
+ oml_mo_state_chg(&site_mgr->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* automatic SW_ACT upon OML link establishment: */
+ oml_mo_tx_sw_act_rep(&site_mgr->mo);
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_OFFLINE);
+ ev_dispatch_children(site_mgr, event);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ site_mgr->mo.setattr_success = false;
+ site_mgr->mo.opstart_success = false;
+ oml_mo_state_chg(&site_mgr->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ /* No bts_model_apply_oml() needed yet for site_mgr obj yet: */
+ rc = 0;
+ site_mgr->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+#if 0
+ /* Disabled because osmo-bsc doesn't send SetAttr on SITE_MGR object */
+ if (!site_mgr->mo.setattr_success) {
+ oml_mo_opstart_nack(&site_mgr->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+#endif
+ bts_model_opstart(NULL, &site_mgr->mo, site_mgr);
+ break;
+ case NM_EV_OPSTART_ACK:
+ site_mgr->mo.opstart_success = true;
+ oml_mo_opstart_ack(&site_mgr->mo);
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ site_mgr->mo.opstart_success = false;
+ oml_mo_opstart_nack(&site_mgr->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ oml_mo_state_chg(&site_mgr->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void nm_bts_sm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&site_mgr->mo, -1, -1, NM_STATE_SHUTDOWN);
+
+ /* Propagate event to children: */
+ ev_dispatch_children(site_mgr, event);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ /* Propagate event to children: */
+ ev_dispatch_children(site_mgr, event);
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_bts_sm_fsm_states[] = {
+ [NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP),
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_BTS_SM_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK),
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_SM_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_BTS_SM_ST_OP_ENABLED] = {
+ .in_event_mask = 0,
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_bts_sm_fsm = {
+ .name = "NM_BTS_SM_OP",
+ .states = nm_bts_sm_fsm_states,
+ .num_states = ARRAY_SIZE(nm_bts_sm_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_bts_sm_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_bts_sm_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_bts_sm_fsm) == 0);
+}
diff --git a/src/common/nm_channel_fsm.c b/src/common/nm_channel_fsm.c
new file mode 100644
index 00000000..d41fcd11
--- /dev/null
+++ b/src/common/nm_channel_fsm.c
@@ -0,0 +1,317 @@
+/* NM Radio Carrier FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+#define X(s) (1 << (s))
+
+#define nm_chan_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+/* Can the TS be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */
+static bool ts_can_be_enabled(const struct gsm_bts_trx_ts *ts)
+{
+ return (ts->trx->bb_transc.mo.nm_state.operational == NM_OPSTATE_ENABLED &&
+ (!bts_internal_flag_get(ts->trx->bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER) ||
+ ts->trx->mo.nm_state.operational == NM_OPSTATE_ENABLED));
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ /* Reset state: */
+ gsm_ts_release(ts);
+ if (ts->vamos.peer)
+ gsm_ts_release(ts->vamos.peer);
+ TALLOC_FREE(ts->mo.nm_attr);
+
+ ts->mo.setattr_success = false;
+ ts->mo.opstart_success = false;
+ oml_mo_state_chg(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&ts->mo);
+ return;
+ case NM_EV_SW_ACT:
+ oml_mo_tx_sw_act_rep(&ts->mo);
+ if (ts_can_be_enabled(ts))
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ else
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ ts->mo.opstart_success = false;
+ oml_mo_state_chg(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&ts->mo);
+ return;
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(ts->trx->bts, setattr_data->msg,
+ &ts->mo, ts);
+ ts->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ LOGPFSML(fi, LOGL_NOTICE, "BSC trying to activate TS while still in avail=dependency. "
+ "Allowing it to stay backward-compatible with older osmo-bts versions, but BSC is wrong.\n");
+ if (!ts->mo.setattr_success) {
+ oml_mo_opstart_nack(&ts->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(ts->trx->bts, &ts->mo, ts);
+ break;
+ case NM_EV_OPSTART_ACK:
+ ts->mo.opstart_success = true;
+ oml_mo_opstart_ack(&ts->mo);
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ case NM_EV_OPSTART_NACK:
+ ts->mo.opstart_success = false;
+ oml_mo_opstart_nack(&ts->mo, (int)(intptr_t)data);
+ return;
+ case NM_EV_BBTRANSC_ENABLED:
+ case NM_EV_RCARRIER_ENABLED:
+ if (ts_can_be_enabled(ts))
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_EV_BBTRANSC_DISABLED:
+ case NM_EV_RCARRIER_DISABLED:
+ /* do nothing, we are simply waiting for (potentially) both to be enabled */
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ ts->mo.opstart_success = false;
+ oml_mo_state_chg(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&ts->mo);
+ return;
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(ts->trx->bts, setattr_data->msg,
+ &ts->mo, ts);
+ ts->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!ts->mo.setattr_success) {
+ oml_mo_opstart_nack(&ts->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(ts->trx->bts, &ts->mo, ts);
+ break;
+ case NM_EV_OPSTART_ACK:
+ ts->mo.opstart_success = true;
+ oml_mo_opstart_ack(&ts->mo);
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ case NM_EV_OPSTART_NACK:
+ ts->mo.opstart_success = false;
+ oml_mo_opstart_nack(&ts->mo, (int)(intptr_t)data);
+ return;
+ case NM_EV_BBTRANSC_DISABLED:
+ case NM_EV_RCARRIER_DISABLED:
+ if (!ts_can_be_enabled(ts))
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ oml_mo_state_chg(&ts->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ switch (event) {
+ case NM_EV_BBTRANSC_DISABLED:
+ case NM_EV_RCARRIER_DISABLED:
+ if (!ts_can_be_enabled(ts))
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_EV_DISABLE:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void nm_chan_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&ts->mo, -1, -1, NM_STATE_SHUTDOWN);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_chan_fsm_states[] = {
+ [NM_CHAN_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT) |
+ X(NM_EV_OML_UP),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_CHAN_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP) |
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_NACK) | /* backward compatibility, buggy BSC */
+ X(NM_EV_BBTRANSC_ENABLED) |
+ X(NM_EV_RCARRIER_ENABLED) |
+ X(NM_EV_BBTRANSC_DISABLED) |
+ X(NM_EV_RCARRIER_DISABLED),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_CHAN_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP) |
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_BBTRANSC_DISABLED) |
+ X(NM_EV_RCARRIER_DISABLED),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_ENABLED) |
+ X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_CHAN_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_BBTRANSC_DISABLED) |
+ X(NM_EV_RCARRIER_DISABLED) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_chan_fsm = {
+ .name = "NM_CHAN_OP",
+ .states = nm_chan_fsm_states,
+ .num_states = ARRAY_SIZE(nm_chan_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_chan_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_chan_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_chan_fsm) == 0);
+}
diff --git a/src/common/nm_common_fsm.c b/src/common/nm_common_fsm.c
new file mode 100644
index 00000000..aa9bda4c
--- /dev/null
+++ b/src/common/nm_common_fsm.c
@@ -0,0 +1,45 @@
+/* NM FSM, common bits */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <osmocom/core/utils.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+
+const struct value_string nm_fsm_event_names[] = {
+ { NM_EV_SW_ACT, "SW_ACT" },
+ { NM_EV_RX_SETATTR, "RX_SETATTR" },
+ { NM_EV_RX_OPSTART, "RX_OPSTART" },
+ { NM_EV_OPSTART_ACK, "OPSTART_ACK" },
+ { NM_EV_OPSTART_NACK, "OPSTART_NACK" },
+ { NM_EV_SHUTDOWN_START, "SHUTDOWN_START" },
+ { NM_EV_SHUTDOWN_FINISH, "SHUTDOWN_FINISH" },
+ { NM_EV_OML_UP, "OML_UP" },
+ { NM_EV_RSL_UP, "RSL_UP" },
+ { NM_EV_RSL_DOWN, "RSL_DOWN" },
+ { NM_EV_PHYLINK_UP, "PHYLINK_UP" },
+ { NM_EV_PHYLINK_DOWN, "PHYLINK_DOWN" },
+ { NM_EV_DISABLE, "DISABLE" },
+ { NM_EV_BBTRANSC_ENABLED, "BBTRANSC_ENABLED" },
+ { NM_EV_BBTRANSC_DISABLED, "BBTRANSC_DISABLED" },
+ { NM_EV_RCARRIER_ENABLED, "RCARRIER_ENABLED" },
+ { NM_EV_RCARRIER_DISABLED, "RCARRIER_DISABLED" },
+ { 0, NULL }
+};
diff --git a/src/common/nm_gprs_cell_fsm.c b/src/common/nm_gprs_cell_fsm.c
new file mode 100644
index 00000000..95f46e31
--- /dev/null
+++ b/src/common/nm_gprs_cell_fsm.c
@@ -0,0 +1,260 @@
+/* NM GPRS Cell FSM */
+
+/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+#define X(s) (1 << (s))
+
+#define nm_gprs_cell_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+/* Can the GPRS Cell be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */
+static bool gprs_cell_can_be_enabled(struct gsm_gprs_cell *cell)
+{
+ struct gsm_bts *bts = gsm_gprs_cell_get_bts(cell);
+ return bts->site_mgr->gprs.nse.mo.nm_state.operational == NM_OPSTATE_ENABLED;
+}
+
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ /* Reset state here: */
+
+ cell->mo.setattr_success = false;
+ cell->mo.opstart_success = false;
+ oml_mo_state_chg(&cell->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* automatic SW_ACT upon OML link establishment: */
+ oml_mo_tx_sw_act_rep(&cell->mo);
+ if (gprs_cell_can_be_enabled(cell))
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE);
+ else
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ cell->mo.setattr_success = false;
+ cell->mo.opstart_success = false;
+ oml_mo_state_chg(&cell->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct gsm_bts *bts = gsm_gprs_cell_get_bts(cell);
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg,
+ &cell->mo, cell);
+ cell->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!cell->mo.setattr_success) {
+ oml_mo_opstart_nack(&cell->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &cell->mo, cell);
+ break;
+ case NM_EV_OPSTART_ACK:
+ cell->mo.opstart_success = true;
+ oml_mo_opstart_ack(&cell->mo);
+ nm_gprs_cell_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ case NM_EV_OPSTART_NACK:
+ cell->mo.opstart_success = false;
+ oml_mo_opstart_nack(&cell->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ cell->mo.opstart_success = false;
+ oml_mo_state_chg(&cell->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct gsm_bts *bts = gsm_gprs_cell_get_bts(cell);
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg, &cell->mo, bts);
+ cell->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!cell->mo.setattr_success) {
+ oml_mo_opstart_nack(&cell->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &cell->mo, bts);
+ break;
+ case NM_EV_OPSTART_ACK:
+ cell->mo.opstart_success = true;
+ oml_mo_opstart_ack(&cell->mo);
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ cell->mo.opstart_success = false;
+ oml_mo_opstart_nack(&cell->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ oml_mo_state_chg(&cell->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void nm_gprs_cell_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&cell->mo, -1, -1, NM_STATE_SHUTDOWN);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_gprs_cell_fsm_states[] = {
+ [NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP),
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_NACK), /* backward compatibility, buggy BSC */
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK),
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_CELL_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_GPRS_CELL_ST_OP_ENABLED] = {
+ .in_event_mask = 0,
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_gprs_cell_fsm = {
+ .name = "NM_GPRS_CELL_OP",
+ .states = nm_gprs_cell_fsm_states,
+ .num_states = ARRAY_SIZE(nm_gprs_cell_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_gprs_cell_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_gprs_cell_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_gprs_cell_fsm) == 0);
+}
diff --git a/src/common/nm_gprs_nse_fsm.c b/src/common/nm_gprs_nse_fsm.c
new file mode 100644
index 00000000..2818c868
--- /dev/null
+++ b/src/common/nm_gprs_nse_fsm.c
@@ -0,0 +1,280 @@
+/* NM GPRS NSE FSM */
+
+/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/cbch.h>
+
+#define X(s) (1 << (s))
+
+#define nm_gprs_nse_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static void ev_dispatch_children(struct gsm_gprs_nse *nse, uint32_t event)
+{
+ unsigned int i;
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse);
+
+ osmo_fsm_inst_dispatch(bts->gprs.cell.mo.fi, event, NULL);
+ for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) {
+ struct gsm_gprs_nsvc *nsvc = &nse->nsvc[i];
+ osmo_fsm_inst_dispatch(nsvc->mo.fi, event, NULL);
+ }
+}
+
+/* Can the NSE be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */
+static bool nse_can_be_enabled(struct gsm_gprs_nse *nse)
+{
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse);
+ return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED;
+}
+
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ /* Reset state here: */
+
+ nse->mo.setattr_success = false;
+ nse->mo.opstart_success = false;
+ oml_mo_state_chg(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* automatic SW_ACT upon OML link establishment: */
+ oml_mo_tx_sw_act_rep(&nse->mo);
+ ev_dispatch_children(nse, event);
+ if (nse_can_be_enabled(nse))
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE);
+ else
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ nse->mo.setattr_success = false;
+ nse->mo.opstart_success = false;
+ oml_mo_state_chg(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse);
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg,
+ &nse->mo, nse);
+ nse->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!nse->mo.setattr_success) {
+ oml_mo_opstart_nack(&nse->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &nse->mo, nse);
+ break;
+ case NM_EV_OPSTART_ACK:
+ nse->mo.opstart_success = true;
+ oml_mo_opstart_ack(&nse->mo);
+ nm_gprs_nse_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ case NM_EV_OPSTART_NACK:
+ nse->mo.opstart_success = false;
+ oml_mo_opstart_nack(&nse->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ nse->mo.opstart_success = false;
+ oml_mo_state_chg(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse);
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg, &nse->mo, bts);
+ nse->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!nse->mo.setattr_success) {
+ oml_mo_opstart_nack(&nse->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &nse->mo, bts);
+ break;
+ case NM_EV_OPSTART_ACK:
+ nse->mo.opstart_success = true;
+ oml_mo_opstart_ack(&nse->mo);
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ nse->mo.opstart_success = false;
+ oml_mo_opstart_nack(&nse->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ oml_mo_state_chg(&nse->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void nm_gprs_nse_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&nse->mo, -1, -1, NM_STATE_SHUTDOWN);
+
+ /* Propagate event to children: */
+ ev_dispatch_children(nse, event);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ /* Propagate event to children: */
+ ev_dispatch_children(nse, event);
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_gprs_nse_fsm_states[] = {
+ [NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP),
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_NACK), /* backward compatibility, buggy BSC */
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK),
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSE_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_GPRS_NSE_ST_OP_ENABLED] = {
+ .in_event_mask = 0,
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_gprs_nse_fsm = {
+ .name = "NM_GPRS_NSE_OP",
+ .states = nm_gprs_nse_fsm_states,
+ .num_states = ARRAY_SIZE(nm_gprs_nse_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_gprs_nse_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_gprs_nse_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nse_fsm) == 0);
+}
diff --git a/src/common/nm_gprs_nsvc_fsm.c b/src/common/nm_gprs_nsvc_fsm.c
new file mode 100644
index 00000000..cbfd97af
--- /dev/null
+++ b/src/common/nm_gprs_nsvc_fsm.c
@@ -0,0 +1,259 @@
+/* NM GPRS NSVC FSM */
+
+/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+#define X(s) (1 << (s))
+
+#define nm_gprs_nsvc_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+/* Can the GPRS Cell be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */
+static bool gprs_nsvc_can_be_enabled(struct gsm_gprs_nsvc *nsvc)
+{
+ return nsvc->nse->mo.nm_state.operational == NM_OPSTATE_ENABLED;
+}
+
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ /* Reset state here: */
+
+ nsvc->mo.setattr_success = false;
+ nsvc->mo.opstart_success = false;
+ oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* automatic SW_ACT upon OML link establishment: */
+ oml_mo_tx_sw_act_rep(&nsvc->mo);
+ if (gprs_nsvc_can_be_enabled(nsvc))
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE);
+ else
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ nsvc->mo.setattr_success = false;
+ nsvc->mo.opstart_success = false;
+ oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nsvc->nse);
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg,
+ &nsvc->mo, nsvc);
+ nsvc->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!nsvc->mo.setattr_success) {
+ oml_mo_opstart_nack(&nsvc->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &nsvc->mo, nsvc);
+ break;
+ case NM_EV_OPSTART_ACK:
+ nsvc->mo.opstart_success = true;
+ oml_mo_opstart_ack(&nsvc->mo);
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ case NM_EV_OPSTART_NACK:
+ nsvc->mo.opstart_success = false;
+ oml_mo_opstart_nack(&nsvc->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ nsvc->mo.opstart_success = false;
+ oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nsvc->nse);
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ int rc;
+
+ switch (event) {
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(bts, setattr_data->msg, &nsvc->mo, bts);
+ nsvc->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break;
+ case NM_EV_RX_OPSTART:
+ if (!nsvc->mo.setattr_success) {
+ oml_mo_opstart_nack(&nsvc->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(bts, &nsvc->mo, bts);
+ break;
+ case NM_EV_OPSTART_ACK:
+ nsvc->mo.opstart_success = true;
+ oml_mo_opstart_ack(&nsvc->mo);
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ nsvc->mo.opstart_success = false;
+ oml_mo_opstart_nack(&nsvc->mo, (int)(intptr_t)data);
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+}
+
+static void nm_gprs_nsvc_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&nsvc->mo, -1, -1, NM_STATE_SHUTDOWN);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_gprs_nsvc_fsm_states[] = {
+ [NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP),
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */
+ X(NM_EV_OPSTART_NACK), /* backward compatibility, buggy BSC */
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK),
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSVC_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_GPRS_NSVC_ST_OP_ENABLED] = {
+ .in_event_mask = 0,
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_gprs_nsvc_fsm = {
+ .name = "NM_GPRS_NSVC_OP",
+ .states = nm_gprs_nsvc_fsm_states,
+ .num_states = ARRAY_SIZE(nm_gprs_nsvc_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_gprs_nsvc_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_gprs_nsvc_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nsvc_fsm) == 0);
+}
diff --git a/src/common/nm_radio_carrier_fsm.c b/src/common/nm_radio_carrier_fsm.c
new file mode 100644
index 00000000..f9589532
--- /dev/null
+++ b/src/common/nm_radio_carrier_fsm.c
@@ -0,0 +1,285 @@
+/* NM Radio Carrier FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/phy_link.h>
+
+#define X(s) (1 << (s))
+
+#define nm_rcarrier_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ /* Reset state: */
+ TALLOC_FREE(trx->mo.nm_attr);
+
+ trx->mo.setattr_success = false;
+ trx->mo.opstart_success = false;
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED);
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SW_ACT:
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&trx->mo);
+ return;
+ case NM_EV_RSL_UP:
+ return;
+ case NM_EV_RSL_DOWN:
+ return;
+ case NM_EV_PHYLINK_UP:
+ return;
+ case NM_EV_PHYLINK_DOWN:
+ return;
+ case NM_EV_DISABLE:
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ unsigned int i;
+
+ trx->mo.setattr_success = false;
+ trx->mo.opstart_success = false;
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1);
+
+ if (prev_state == NM_RCARRIER_ST_OP_ENABLED) {
+ for (i = 0; i < TRX_NR_TS; i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_RCARRIER_DISABLED, NULL);
+ }
+ }
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ struct nm_fsm_ev_setattr_data *setattr_data;
+ bool phy_state_connected;
+ bool rsl_link_connected;
+ int rc;
+
+ switch (event) {
+ case NM_EV_OML_UP:
+ /* Report current state: */
+ oml_tx_state_changed(&trx->mo);
+ return;
+ case NM_EV_RX_SETATTR:
+ setattr_data = (struct nm_fsm_ev_setattr_data *)data;
+ rc = bts_model_apply_oml(trx->bts, setattr_data->msg,
+ &trx->mo, trx);
+ trx->mo.setattr_success = rc == 0;
+ oml_fom_ack_nack_copy_msg(setattr_data->msg, rc);
+ break; /* check statechg below */
+ case NM_EV_RX_OPSTART:
+ if (!trx->mo.setattr_success) {
+ oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
+ return;
+ }
+ bts_model_opstart(trx->bts, &trx->mo, trx);
+ return;
+ case NM_EV_OPSTART_ACK:
+ trx->mo.opstart_success = true;
+ oml_mo_opstart_ack(&trx->mo);
+ break; /* check statechg below */
+ case NM_EV_OPSTART_NACK:
+ trx->mo.opstart_success = false;
+ oml_mo_opstart_nack(&trx->mo, (int)(intptr_t)data);
+ return;
+ case NM_EV_RSL_UP:
+ break; /* check statechg below */
+ case NM_EV_RSL_DOWN:
+ return;
+ case NM_EV_PHYLINK_UP:
+ break; /* check statechg below */
+ case NM_EV_PHYLINK_DOWN:
+ return;
+ case NM_EV_DISABLE:
+ return;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ if (trx->bts->variant != BTS_OSMO_OMLDUMMY) { /* In OMLDUMMY, phy=NULL */
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ phy_state_connected = phy_link_state_get(pinst->phy_link) == PHY_LINK_CONNECTED;
+ rsl_link_connected = !!trx->bb_transc.rsl.link;
+ } else {
+ phy_state_connected = true;
+ rsl_link_connected = true;
+ }
+
+ if (rsl_link_connected && phy_state_connected &&
+ trx->mo.setattr_success && trx->mo.opstart_success) {
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED);
+ } else {
+ LOGPFSML(fi, LOGL_INFO, "Delay switch to operative state Enabled, wait for:%s%s%s%s\n",
+ rsl_link_connected ? "" : " rsl",
+ phy_state_connected ? "" : " phy",
+ trx->mo.setattr_success ? "" : " setattr",
+ trx->mo.opstart_success ? "" : " opstart");
+
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ unsigned int tn;
+
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1);
+ /* Mark Dependency TS as Offline (ready to be Opstarted) */
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_RCARRIER_ENABLED, NULL);
+ }
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case NM_EV_RSL_DOWN:
+ break;
+ case NM_EV_PHYLINK_DOWN:
+ break;
+ case NM_EV_DISABLE:
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE);
+}
+
+static void nm_rcarrier_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SHUTDOWN_START:
+ /* Announce we start shutting down */
+ oml_mo_state_chg(&trx->mo, -1, -1, NM_STATE_SHUTDOWN);
+ break;
+ case NM_EV_SHUTDOWN_FINISH:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static struct osmo_fsm_state nm_rcarrier_fsm_states[] = {
+ [NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT) |
+ X(NM_EV_OML_UP) |
+ X(NM_EV_RSL_UP) |
+ X(NM_EV_RSL_DOWN) |
+ X(NM_EV_PHYLINK_UP) |
+ X(NM_EV_PHYLINK_DOWN) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_RCARRIER_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_OML_UP) |
+ X(NM_EV_RX_SETATTR) |
+ X(NM_EV_RX_OPSTART) |
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_RSL_UP) |
+ X(NM_EV_RSL_DOWN) |
+ X(NM_EV_PHYLINK_UP) |
+ X(NM_EV_PHYLINK_DOWN) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_RCARRIER_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_RCARRIER_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_RSL_DOWN) |
+ X(NM_EV_PHYLINK_DOWN) |
+ X(NM_EV_DISABLE),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_rcarrier_fsm = {
+ .name = "NM_RCARRIER_OP",
+ .states = nm_rcarrier_fsm_states,
+ .num_states = ARRAY_SIZE(nm_rcarrier_fsm_states),
+ .event_names = nm_fsm_event_names,
+ .allstate_action = nm_rcarrier_allstate,
+ .allstate_event_mask = X(NM_EV_SHUTDOWN_START) |
+ X(NM_EV_SHUTDOWN_FINISH),
+ .log_subsys = DOML,
+};
+
+static __attribute__((constructor)) void nm_rcarrier_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_rcarrier_fsm) == 0);
+}
diff --git a/src/common/notification.c b/src/common/notification.c
new file mode 100644
index 00000000..9351ec04
--- /dev/null
+++ b/src/common/notification.c
@@ -0,0 +1,256 @@
+/* Maintain and generate ASCI notifications */
+
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Harald Welte
+ *
+ * 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 <osmocom/core/bitvec.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/notification.h>
+
+static struct asci_notification *bts_asci_notification_find(struct gsm_bts *bts, const uint8_t *group_call_ref)
+{
+ struct asci_notification *n;
+ llist_for_each_entry(n, &bts->asci.notifications, list) {
+ if (!memcmp(n->group_call_ref, group_call_ref, sizeof(n->group_call_ref)))
+ return n;
+ }
+ return NULL;
+}
+
+int bts_asci_notification_add(struct gsm_bts *bts, const uint8_t *group_call_ref, const uint8_t *chan_desc,
+ uint8_t chan_desc_len, const struct rsl_ie_nch_drx_info *nch_drx_info)
+{
+ struct asci_notification *n;
+
+ if (bts_asci_notification_find(bts, group_call_ref))
+ return -EEXIST;
+
+ n = talloc_zero(bts, struct asci_notification);
+ if (!n)
+ return -ENOMEM;
+
+ memcpy(n->group_call_ref, group_call_ref, sizeof(n->group_call_ref));
+ if (chan_desc && chan_desc_len) {
+ n->chan_desc.present = true;
+ n->chan_desc.len = chan_desc_len;
+ memcpy(&n->chan_desc.value, chan_desc, chan_desc_len);
+ }
+ if (nch_drx_info) {
+ n->nch_drx_info.present = true;
+ n->nch_drx_info.value = *nch_drx_info;
+ }
+
+ LOGP(DASCI, LOGL_INFO, "Added ASCI Notification for group call reference %s\n",
+ osmo_hexdump_nospc(n->group_call_ref, ARRAY_SIZE(n->group_call_ref)));
+
+ /* add at beginning of "queue" to make sure a new call is notified first */
+ llist_add(&n->list, &bts->asci.notifications);
+
+ bts->asci.notification_entries++;
+ bts->asci.notification_count = 0;
+ bts->asci.nln = (bts->asci.nln + 1) % 4;
+
+ return 0;
+}
+
+int bts_asci_notification_del(struct gsm_bts *bts, const uint8_t *group_call_ref)
+{
+ struct asci_notification *n = bts_asci_notification_find(bts, group_call_ref);
+ if (!n)
+ return -ENODEV;
+
+ LOGP(DASCI, LOGL_INFO, "Deleting ASCI Notification for group call reference %s\n",
+ osmo_hexdump_nospc(n->group_call_ref, ARRAY_SIZE(n->group_call_ref)));
+
+ llist_del(&n->list);
+ talloc_free(n);
+
+ bts->asci.notification_entries--;
+ bts->asci.notification_count = 0;
+ bts->asci.nln_status = (bts->asci.nln_status + 1) % 2;
+
+ return 0;
+}
+
+int bts_asci_notification_reset(struct gsm_bts *bts)
+{
+ struct asci_notification *n, *n2;
+
+ LOGP(DASCI, LOGL_INFO, "Deleting all %u ASCI Notifications of BTS\n",
+ llist_count(&bts->asci.notifications));
+
+ llist_for_each_entry_safe(n, n2, &bts->asci.notifications, list) {
+ llist_del(&n->list);
+ talloc_free(n);
+ }
+
+ bts->asci.notification_entries = 0;
+ bts->asci.notification_count = 0;
+ bts->asci.nln_status = (bts->asci.nln_status + 1) % 2;
+
+ return 0;
+}
+
+const struct asci_notification *bts_asci_notification_get_next(struct gsm_bts *bts)
+{
+ struct asci_notification *n;
+
+ n = llist_first_entry_or_null(&bts->asci.notifications, struct asci_notification, list);
+ if (!n)
+ return NULL;
+
+ /* move to end of list to iterate over them */
+ llist_del(&n->list);
+ llist_add_tail(&n->list, &bts->asci.notifications);
+
+ return n;
+}
+
+
+/*! append a "Group Call Information" CSN.1 structure to the caller-provided bit-vector.
+ * \param[out] bv caller-provided output bit-vector
+ * \param[in] gcr 5-byte group call reference
+ * \param[in] ch_desc optional group channel description (may be NULL)
+ * \param[in] ch_desc_len length of group channel description (in bytes) */
+void append_group_call_information(struct bitvec *bv, const uint8_t *gcr, const uint8_t *ch_desc, uint8_t ch_desc_len)
+{
+ /* spec reference: TS 44.018 Section 9.1.21a */
+
+ /* <Group Call Reference : bit(36)> */
+ struct bitvec *gcr_bv = bitvec_alloc(5*8, NULL);
+ OSMO_ASSERT(gcr_bv);
+ bitvec_unpack(gcr_bv, gcr);
+ for (unsigned int i = 0; i < 36; i++)
+ bitvec_set_bit(bv, bitvec_get_bit_pos(gcr_bv, i));
+
+ /* Group Channel Description */
+ if (ch_desc && ch_desc_len) {
+ struct bitvec *chd_bv = bitvec_alloc(ch_desc_len*8, NULL);
+ OSMO_ASSERT(chd_bv);
+ bitvec_unpack(chd_bv, ch_desc);
+ bitvec_set_bit(bv, 1);
+ /* <Channel Description : bit(24)> */
+ for (unsigned int i = 0; i < ch_desc_len * 8; i++)
+ bitvec_set_bit(bv, bitvec_get_bit_pos(chd_bv, i));
+ bitvec_free(chd_bv);
+ /* FIXME: hopping */
+ bitvec_set_bit(bv, 0);
+ } else {
+ bitvec_set_bit(bv, 0);
+ }
+
+ bitvec_free(gcr_bv);
+}
+
+#define L2_PLEN(len) (((len - 1) << 2) | 0x01)
+
+int bts_asci_notify_nch_gen_msg(struct gsm_bts *bts, uint8_t *out_buf)
+{
+ struct gsm48_notification_nch *nn = (struct gsm48_notification_nch *) out_buf;
+ const struct asci_notification *notif;
+ unsigned int ro_len;
+
+ notif = bts_asci_notification_get_next(bts);
+
+ *nn = (struct gsm48_notification_nch) {
+ .proto_discr = GSM48_PDISC_RR,
+ .msg_type = GSM48_MT_RR_NOTIF_NCH,
+ };
+
+ nn->l2_plen = L2_PLEN(nn->data - out_buf);
+
+ /* Pad remaining octets with constant '2B'O */
+ ro_len = GSM_MACBLOCK_LEN - (nn->data - out_buf);
+ memset(nn->data, GSM_MACBLOCK_PADDING, ro_len);
+
+ struct bitvec bv = {
+ .data_len = ro_len,
+ .data = nn->data,
+ };
+
+ /* {0 | 1 < NLN(NCH) : bit (2) >}
+ * Only send NLN, at the last notifications.
+ * When the phone receives two NLN with the same value, it knows that all notifications has been received.
+ * Also send NLN if no notification is available. */
+ if (bts->asci.notification_count >= bts->asci.notification_entries - 1) {
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, bts->asci.nln, 2);
+ } else {
+ bitvec_set_bit(&bv, 0);
+ }
+
+ /* Count NLN. */
+ if (++bts->asci.notification_count >= bts->asci.notification_entries)
+ bts->asci.notification_count = 0;
+
+ /* < List of Group Call NCH information > ::=
+ * { 0 | 1 < Group Call information > < List of Group Call NCH information > } ; */
+ if (notif) {
+ bitvec_set_bit(&bv, 1);
+ append_group_call_information(&bv, notif->group_call_ref,
+ notif->chan_desc.present ? notif->chan_desc.value : NULL,
+ notif->chan_desc.len);
+ }
+ bitvec_set_bit(&bv, 0); /* End of list */
+
+ /* TODO: Additions in Release 6 */
+ /* TODO: Additions in Release 7 */
+
+ return GSM_MACBLOCK_LEN;
+}
+
+int bts_asci_notify_facch_gen_msg(struct gsm_bts *bts, uint8_t *out_buf, const uint8_t *group_call_ref,
+ const uint8_t *chan_desc, uint8_t chan_desc_len)
+{
+ struct gsm48_hdr_sh *sh = (struct gsm48_hdr_sh *) out_buf;
+ unsigned int ro_len;
+
+ *sh = (struct gsm48_hdr_sh) {
+ .rr_short_pd = GSM48_PDISC_SH_RR,
+ .msg_type = GSM48_MT_RR_SH_FACCH,
+ .l2_header = 0,
+ };
+
+ /* Pad remaining octets with constant '2B'O */
+ ro_len = GSM_MACBLOCK_LEN - (sh->data - out_buf);
+ memset(sh->data, GSM_MACBLOCK_PADDING, ro_len);
+
+ struct bitvec bv = {
+ .data_len = ro_len,
+ .data = sh->data,
+ };
+
+ /* 0 < Group Call information > */
+ bitvec_set_bit(&bv, 0);
+ append_group_call_information(&bv, group_call_ref, chan_desc, chan_desc_len);
+
+ /* TODO: Additions in Release 6 */
+ /* TODO: Additions in Release 7 */
+
+ return GSM_MACBLOCK_LEN;
+}
diff --git a/src/common/oml.c b/src/common/oml.c
index 3defa494..a9e13b52 100644
--- a/src/common/oml.c
+++ b/src/common/oml.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -34,6 +34,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
#include <osmocom/gsm/protocol/gsm_12_21.h>
#include <osmocom/gsm/abis_nm.h>
#include <osmocom/gsm/tlv.h>
@@ -46,8 +47,10 @@
#include <osmo-bts/oml.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
#include <osmo-bts/signal.h>
#include <osmo-bts/phy_link.h>
+#include <osmo-bts/nm_common_fsm.h>
#define LOGPFOH(ss, lvl, foh, fmt, args ...) LOGP(ss, lvl, "%s: " fmt, abis_nm_dump_foh(foh), ## args)
#define DEBUGPFOH(ss, foh, fmt, args ...) LOGPFOH(ss, LOGL_DEBUG, foh, fmt, ## args)
@@ -131,6 +134,7 @@ int oml_send_msg(struct msgb *msg, int is_manuf)
int oml_mo_send_msg(const struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type)
{
struct abis_om_fom_hdr *foh;
+ struct gsm_bts *bts;
msg->l3h = msgb_push(msg, sizeof(*foh));
foh = (struct abis_om_fom_hdr *) msg->l3h;
@@ -138,17 +142,31 @@ int oml_mo_send_msg(const struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_
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;
+ /* Find and set OML TRX on msg: */
+ switch (mo->obj_class) {
+ case NM_OC_SITE_MANAGER:
+ /* Pick the first BTS: */
+ bts = gsm_bts_num(g_bts_sm, 0);
+ break;
+ default:
+ /* Other objects should have a valid BTS available: */
+ bts = gsm_bts_num(g_bts_sm, mo->obj_inst.bts_nr);
+ }
+ if (OSMO_UNLIKELY(!bts)) {
+ LOGPFOH(DOML, LOGL_NOTICE, foh,
+ "Sending FOM failed (no related BTS object found)\n");
+ return -EINVAL;
+ }
+ msg->trx = bts->c0;
DEBUGPFOH(DOML, foh, "Tx %s\n", get_value_string(abis_nm_msgtype_names, foh->msg_type));
return oml_send_msg(msg, 0);
}
-static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts)
+/* Put NM_ATT_SW_CONFIG as per 9.4.61 "SW Configuration" */
+static int add_att_sw_config(struct msgb *msg, const struct gsm_abis_mo *mo)
{
- uint16_t total_len = 0;
uint8_t *len;
/* Put NM_ATT_SW_CONFIG as per 9.4.61 "SW Configuration". */
@@ -157,117 +175,291 @@ static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts)
/* We don't know the length yet, so we update it later. */
len = msgb_put(msg, 2);
- total_len += abis_nm_put_sw_file(msg, "osmobts", PACKAGE_VERSION, true);
- total_len += abis_nm_put_sw_file(msg, btsatttr2str(BTS_TYPE_VARIANT),
- btsvariant2str(bts->variant), true);
+ switch (mo->obj_class) {
+ case NM_OC_BTS:
+ {
+ const struct gsm_bts *bts = mo->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);
+ }
+ break;
+ }
+ case NM_OC_BASEB_TRANSC:
+ {
+ const struct gsm_bts_trx *trx = container_of(mo, struct gsm_bts_trx, bb_transc.mo);
+ const struct phy_instance *pinst = trx->pinst;
+ const char *phy_version;
- if (strlen(bts->sub_model)) {
- total_len += abis_nm_put_sw_file(msg, btsatttr2str(BTS_SUB_MODEL),
- bts->sub_model, true);
+ phy_version = pinst && strlen(pinst->version) ? pinst->version : "Unknown";
+ abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), phy_version, true);
+ break;
+ }
+ default:
+ msgb_get(msg, 1 + 2); /* TL16 */
+ return -ENOTSUP;
}
/* Finally, update the length */
- osmo_store16be(total_len, len);
+ osmo_store16be((uint16_t)(msg->tail - (len + 2)), len);
+
+ return 0;
}
/* 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);
+ unsigned int len = OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT);
+ msgb_tl16v_put(msg, NM_ATT_MANUF_ID, len, bts->features->data);
}
-static inline void add_trx_attr(struct msgb *msg, const struct gsm_bts_trx *trx)
+/* Add ip.access feature flags for the given MO */
+static int add_att_ipacc_features(struct msgb *msg, const struct gsm_abis_mo *mo)
{
- const struct phy_instance *pinst = trx_phy_instance(trx);
- const char *phy_version;
- uint16_t total_len;
+ const struct gsm_bts *bts = mo->bts;
+ const struct gsm_bts_trx *trx;
+ uint32_t val;
uint8_t *len;
- /* Put NM_ATT_SW_CONFIG as per 9.4.61 "SW Configuration". */
- msgb_v_put(msg, NM_ATT_SW_CONFIG);
+ msgb_v_put(msg, NM_ATT_IPACC_SUPP_FEATURES);
/* We don't know the length yet, so we update it later. */
len = msgb_put(msg, 2);
- phy_version = pinst && strlen(pinst->version) ? pinst->version : "Unknown";
- total_len = abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), phy_version, true);
+ switch (mo->obj_class) {
+ case NM_OC_BTS:
+ msgb_tv16_put(msg, NM_IPAC_EIE_MAX_TA, 1); /* TL16 */
+ msgb_put_u8(msg, (bts->support.max_ta >> 0) & 0xff);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ trx = container_of(mo, struct gsm_bts_trx, mo);
+ msgb_tv16_put(msg, NM_IPAC_EIE_FREQ_BANDS, 1); /* TL16 */
+ msgb_put_u8(msg, (trx->support.freq_bands >> 0) & 0xff);
+ break;
+ case NM_OC_BASEB_TRANSC:
+ trx = container_of(mo, struct gsm_bts_trx, bb_transc.mo);
+ msgb_tv16_put(msg, NM_IPAC_EIE_CIPH_ALGOS, 1); /* TL16 */
+ msgb_put_u8(msg, bts->support.ciphers); /* LSB is A5/1 */
+
+ msgb_tv16_put(msg, NM_IPAC_EIE_CHAN_TYPES, 2); /* TL16 */
+ msgb_put_u8(msg, (trx->support.chan_types >> 0) & 0xff);
+ msgb_put_u8(msg, (trx->support.chan_types >> 8) & 0xff);
+
+ msgb_tv16_put(msg, NM_IPAC_EIE_CHAN_MODES, 3); /* TL16 */
+ msgb_put_u8(msg, (trx->support.chan_modes >> 0) & 0xff);
+ msgb_put_u8(msg, (trx->support.chan_modes >> 8) & 0xff);
+ msgb_put_u8(msg, (trx->support.chan_modes >> 16) & 0xff);
+
+ msgb_tv16_put(msg, NM_IPAC_EIE_RTP_FEATURES, 1); /* TL16 */
+ val = NM_IPAC_F_RTP_FEAT_IR_64k;
+ msgb_put_u8(msg, (val >> 0) & 0xff);
+
+ msgb_tv16_put(msg, NM_IPAC_EIE_RSL_FEATURES, 1); /* TL16 */
+ val = NM_IPAC_F_RSL_FEAT_DYN_PDCH_ACT
+ | NM_IPAC_F_RSL_FEAT_RTP_PT2;
+ msgb_put_u8(msg, (val >> 0) & 0xff);
+ break;
+ case NM_OC_GPRS_CELL:
+ msgb_tv16_put(msg, NM_IPAC_EIE_GPRS_CODING, 2); /* TL16 */
+ msgb_put_u8(msg, (bts->gprs.cell.support.gprs_codings >> 0) & 0xff);
+ msgb_put_u8(msg, (bts->gprs.cell.support.gprs_codings >> 8) & 0xff);
+ break;
+ default:
+ msgb_get(msg, 1 + 2); /* TL16 */
+ return -ENOTSUP;
+ }
/* Finally, update the length */
- osmo_store16be(total_len, len);
+ osmo_store16be((uint16_t)(msg->tail - (len + 2)), len);
+
+ return 0;
}
-/* Handle a list of attributes requested by the BSC, compose
- * TRX-specific Get Attribute Response IE as per 9.4.64. */
-static inline int handle_attrs_trx(struct msgb *out_msg, const struct gsm_bts_trx *trx,
- const uint8_t *attr, uint16_t attr_len)
+/* Add attribute 9.4.8 BCCH ARFCN for BTS class */
+static inline void add_att_bcch_arfcn(struct msgb *msg, const struct gsm_bts *bts)
{
- uint8_t num_unsupported = 0;
- uint8_t *buf;
- int i;
+ /* type + 16 bit value */
+ msgb_tv16_put(msg, NM_ATT_BCCH_ARFCN, bts->c0->arfcn);
+}
- if (!trx) {
- LOGP(DOML, LOGL_ERROR, "%s: O&M Get Attributes for unknown TRX\n", gsm_trx_name(trx));
- return -NM_NACK_TRXNR_UNKN;
- }
+/* Add attribute 9.4.25 Interference Level Boundaries for BTS class */
+static inline void add_att_interf_bound(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit values */
+ msgb_put_u8(msg, NM_ATT_INTERF_BOUND);
+ for (int j = 0; j < ARRAY_SIZE(bts->interference.boundary); j++)
+ msgb_put_u8(msg, abs(bts->interference.boundary[j]));
+}
- for (i = 0; i < attr_len; i++) {
- switch (attr[i]) {
- case NM_ATT_SW_CONFIG:
- add_trx_attr(out_msg, trx);
- break;
- default:
- LOGP(DOML, LOGL_ERROR, "%s: O&M Get Attributes [%u], %s is unsupported by TRX.\n",
- gsm_trx_name(trx), i, get_value_string(abis_nm_att_names, attr[i]));
- /* Push this tag to the list of unsupported attributes */
- buf = msgb_push(out_msg, 1);
- *buf = attr[i];
- num_unsupported++;
- }
- }
+/* Add attribute 9.4.24 Intave Parameter for BTS class */
+static inline void add_att_intave_param(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_INTAVE_PARAM, bts->interference.intave);
+}
- /* Push the amount of unsupported attributes */
- buf = msgb_push(out_msg, 1);
- *buf = num_unsupported;
+/* Add attribute 9.4.14 Connection Failure Criterion for BTS class */
+static inline void add_att_conn_fail_crit(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + length + values */
+ msgb_tv16_put(msg, NM_ATT_CONN_FAIL_CRIT, 2);
+ msgb_put_u8(msg, 0x01);
+ msgb_put_u8(msg, bts->radio_link_timeout.current);
+}
+
+/* Add attribute 9.4.31 Maximum Timing Advance for BTS class */
+static inline void add_att_max_ta(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_MAX_TA, bts->max_ta);
+}
+
+/* Add attribute 9.4.39 Overload Period for BTS class */
+static inline void add_att_overl_period(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + length + value */
+ msgb_tv16_put(msg, NM_ATT_OVERL_PERIOD, 1);
+ msgb_put_u8(msg, bts->load.overload_period);
+}
+/* Add attribute 9.4.12 CCCH Load Threshold for BTS class */
+static inline void add_att_ccch_l_t(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_CCCH_L_T, bts->load.ccch.load_ind_thresh);
+}
+
+/* Add attribute 9.4.11 CCCH Load Indication Period for BTS class */
+static inline void add_att_ccch_l_i_p(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_CCCH_L_I_P, bts->load.ccch.load_ind_period);
+}
+
+/* Add attribute 9.4.44 RACH Busy Threshold for BTS class */
+static inline void add_att_rach_b_thresh(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_RACH_B_THRESH, abs(bts->load.rach.busy_thresh));
+}
+
+/* Add attribute 9.4.45 RACH Load Averaging Slots for BTS class */
+static inline void add_att_ldavg_slots(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 16 bit value */
+ msgb_tv16_put(msg, NM_ATT_LDAVG_SLOTS, bts->load.rach.averaging_slots);
+}
+
+/* Add attribute 9.4.10 BTS Air Timer for BTS class */
+static inline void add_att_bts_air_timer(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_BTS_AIR_TIMER, bts->t3105_ms / 10);
+}
+
+/* Add attribute 9.4.37 NY1 for BTS class */
+static inline void add_att_ny1(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_NY1, bts->ny1);
+}
+
+/* Add attribute 9.4.9 BSIC for BTS class */
+static inline int add_att_bsic(struct msgb *msg, const struct gsm_bts *bts)
+{
+ /* BSIC must be configured. */
+ if (!bts->bsic_configured)
+ return -EINVAL;
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_BSIC, bts->bsic);
return 0;
}
-/* Handle a list of attributes requested by the BSC, compose
- * BTS-specific Get Attribute Response IE as per 9.4.64. */
-static inline int handle_attrs_bts(struct msgb *out_msg, const struct gsm_bts *bts,
- const uint8_t *attr, uint16_t attr_len)
+/* Add attribute 9.4.20 GSM Time for BTS class */
+static inline void add_att_gsm_time(struct msgb *msg, const struct gsm_bts *bts)
{
- uint8_t num_unsupported = 0;
- uint8_t *buf;
- int i;
+ /* type + 16 bit value */
+ msgb_tv16_put(msg, NM_ATT_GSM_TIME, bts->gsm_time.fn % GSM_RFN_MODULUS);
+}
- if (!bts) {
- LOGP(DOML, LOGL_ERROR, "O&M Get Attributes for unknown BTS\n");
- return -NM_NACK_BTSNR_UNKN;
- }
+/* Add attribute 9.4.47 RF Max Power Reduction for radio carrier class */
+static inline void add_att_rf_maxpowr_r(struct msgb *msg, const struct gsm_bts_trx *trx)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2);
+}
- for (i = 0; i < attr_len; i++) {
- switch (attr[i]) {
- case NM_ATT_SW_CONFIG:
- add_bts_attrs(out_msg, bts);
- break;
- case NM_ATT_MANUF_ID:
- add_bts_feat(out_msg, 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]));
- /* Push this tag to the list of unsupported attributes */
- buf = msgb_push(out_msg, 1);
- *buf = attr[i];
- num_unsupported++;
- }
+/* Add attribute 9.4.5 ARFCN List for radio carrier class */
+static inline void add_att_arfcn_list(struct msgb *msg, const struct gsm_bts_trx *trx)
+{
+#if 0
+ /* type + length + values */
+ msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, trx->arfcn_num * 2);
+ for (int j = 0; j < trx->arfcn_num; j++)
+ msgb_put_u16(msg, trx->arfcn_list[j]);
+#else
+ /* type + length + values */
+ msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, 2);
+ msgb_put_u16(msg, trx->arfcn);
+#endif
+}
+
+/* Add attribute 9.4.5 ARFCN List for channel class */
+static inline void add_att_arfcn_list_ts(struct msgb *msg, const struct gsm_bts_trx_ts *ts)
+{
+ if (ts->hopping.enabled) {
+ /* type + length + values */
+ msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, ts->hopping.arfcn_num * 2);
+ for (int j = 0; j < ts->hopping.arfcn_num; j++)
+ msgb_put_u16(msg, ts->hopping.arfcn_list[j]);
+ } else {
+ /* type + length + values */
+ msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, 2);
+ msgb_put_u16(msg, ts->trx->arfcn);
}
+}
- /* Push the amount of unsupported attributes */
- buf = msgb_push(out_msg, 1);
- *buf = num_unsupported;
+/* Add attribute 9.4.13 Channel Combination for channel class */
+static inline int add_att_chan_comb(struct msgb *msg, const struct gsm_bts_trx_ts *ts)
+{
+ int comb = abis_nm_chcomb4pchan(ts->pchan);
+ /* If current channel combination is not yet set, 0xff is returned. */
+ if (comb < 0 || comb == 0xff)
+ return -EINVAL;
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_CHAN_COMB, comb);
+ return 0;
+}
+
+/* Add attribute 9.4.60 TSC for channel class */
+static inline void add_att_tsc(struct msgb *msg, const struct gsm_bts_trx_ts *ts)
+{
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_TSC, ts->tsc);
+}
+
+/* Add attribute 9.4.60 HSN for channel class */
+static inline int add_att_hsn(struct msgb *msg, const struct gsm_bts_trx_ts *ts)
+{
+ if (!ts->hopping.enabled)
+ return -EINVAL;
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn);
+ return 0;
+}
+
+/* Add attribute 9.4.21 MAIO for channel class */
+static inline int add_att_maio(struct msgb *msg, const struct gsm_bts_trx_ts *ts)
+{
+ if (!ts->hopping.enabled)
+ return -EINVAL;
+ /* type + 8 bit value */
+ msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio);
return 0;
}
@@ -276,32 +468,177 @@ static int oml_tx_attr_resp(const struct gsm_abis_mo *mo,
const uint8_t *attr, uint16_t attr_len)
{
struct msgb *nmsg = oml_msgb_alloc();
- const char *mo_name = gsm_abis_mo_name(mo);
+ unsigned int num_unsupported = 0;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
int rc;
if (!nmsg)
return -NM_NACK_CANT_PERFORM;
+ /* Set TRX, if object class is Radio Carrier, Baseband Transceiver or Channel. */
switch (mo->obj_class) {
- case NM_OC_BTS:
- rc = handle_attrs_bts(nmsg, mo->bts, attr, attr_len);
- break;
+ case NM_OC_RADIO_CARRIER:
case NM_OC_BASEB_TRANSC:
- rc = handle_attrs_trx(nmsg, gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr), attr, attr_len);
+ case NM_OC_CHANNEL:
+ trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr);
break;
- default:
- LOGP(DOML, LOGL_ERROR, "%s: Unsupported MO class in Get Attribute Response\n",
- mo_name);
- rc = -NM_NACK_OBJCLASS_NOTSUPP;
}
- if (rc < 0) {
- LOGP(DOML, LOGL_ERROR, "%s: Tx Get Attribute Response FAILED with rc=%d\n",
- mo_name, rc);
- msgb_free(nmsg);
- return rc;
+ /* Set TS, if object class is Channel. */
+ if (mo->obj_class == NM_OC_CHANNEL && trx)
+ ts = &trx->ts[mo->obj_inst.ts_nr];
+
+ for (unsigned int i = 0; i < attr_len; i++) {
+ switch (attr[i]) {
+ case NM_ATT_OPER_STATE:
+ msgb_tv16_put(nmsg, attr[i], 1);
+ msgb_put_u8(nmsg, mo->nm_state.operational);
+ break;
+ case NM_ATT_ADM_STATE:
+ msgb_tv16_put(nmsg, attr[i], 1);
+ msgb_put_u8(nmsg, mo->nm_state.administrative);
+ break;
+ case NM_ATT_AVAIL_STATUS:
+ msgb_tv16_put(nmsg, attr[i], 1);
+ msgb_put_u8(nmsg, mo->nm_state.availability);
+ break;
+ case NM_ATT_SW_CONFIG:
+ if (add_att_sw_config(nmsg, mo) != 0)
+ goto unsupported;
+ break;
+ case NM_ATT_MANUF_ID:
+ if (mo->obj_class == NM_OC_BTS)
+ add_bts_feat(nmsg, mo->bts);
+ else
+ goto unsupported;
+ break;
+ case NM_ATT_IPACC_SUPP_FEATURES:
+ if (add_att_ipacc_features(nmsg, mo) != 0)
+ goto unsupported;
+ break;
+ case NM_ATT_BCCH_ARFCN:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_bcch_arfcn(nmsg, mo->bts);
+ break;
+ case NM_ATT_INTERF_BOUND:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_interf_bound(nmsg, mo->bts);
+ break;
+ case NM_ATT_INTAVE_PARAM:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_intave_param(nmsg, mo->bts);
+ break;
+ case NM_ATT_CONN_FAIL_CRIT:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_conn_fail_crit(nmsg, mo->bts);
+ break;
+ case NM_ATT_MAX_TA:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_max_ta(nmsg, mo->bts);
+ break;
+ case NM_ATT_OVERL_PERIOD:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_overl_period(nmsg, mo->bts);
+ break;
+ case NM_ATT_CCCH_L_T:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_ccch_l_t(nmsg, mo->bts);
+ break;
+ case NM_ATT_CCCH_L_I_P:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_ccch_l_i_p(nmsg, mo->bts);
+ break;
+ case NM_ATT_RACH_B_THRESH:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_rach_b_thresh(nmsg, mo->bts);
+ break;
+ case NM_ATT_LDAVG_SLOTS:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_ldavg_slots(nmsg, mo->bts);
+ break;
+ case NM_ATT_BTS_AIR_TIMER:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_bts_air_timer(nmsg, mo->bts);
+ break;
+ case NM_ATT_NY1:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_ny1(nmsg, mo->bts);
+ break;
+ case NM_ATT_BSIC:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ if (add_att_bsic(nmsg, mo->bts) != 0)
+ goto unsupported;
+ break;
+ case NM_ATT_GSM_TIME:
+ if (mo->obj_class != NM_OC_BTS)
+ goto unsupported;
+ add_att_gsm_time(nmsg, mo->bts);
+ break;
+ case NM_ATT_RF_MAXPOWR_R:
+ if (mo->obj_class != NM_OC_RADIO_CARRIER || !trx)
+ goto unsupported;
+ add_att_rf_maxpowr_r(nmsg, trx);
+ break;
+ case NM_ATT_ARFCN_LIST:
+ if (mo->obj_class == NM_OC_RADIO_CARRIER && trx) {
+ add_att_arfcn_list(nmsg, trx);
+ break;
+ }
+ if (mo->obj_class == NM_OC_CHANNEL && ts) {
+ add_att_arfcn_list_ts(nmsg, ts);
+ break;
+ }
+ goto unsupported;
+ case NM_ATT_CHAN_COMB:
+ if (mo->obj_class != NM_OC_CHANNEL || !ts)
+ goto unsupported;
+ if (add_att_chan_comb(nmsg, ts) != 0)
+ goto unsupported;
+ break;
+ case NM_ATT_TSC:
+ if (mo->obj_class != NM_OC_CHANNEL || !ts)
+ goto unsupported;
+ add_att_tsc(nmsg, ts);
+ break;
+ case NM_ATT_HSN:
+ if (mo->obj_class != NM_OC_CHANNEL || !ts)
+ goto unsupported;
+ if (add_att_hsn(nmsg, ts) != 0)
+ goto unsupported;
+ break;
+ case NM_ATT_MAIO:
+ if (mo->obj_class != NM_OC_CHANNEL || !ts)
+ goto unsupported;
+ if (add_att_maio(nmsg, ts) != 0)
+ goto unsupported;
+ break;
+ default:
+unsupported:
+ LOGP(DOML, LOGL_ERROR, "%s: O&M Get Attributes [%u], %s is unsupported\n",
+ gsm_abis_mo_name(mo), i, get_value_string(abis_nm_att_names, attr[i]));
+ /* Push this tag to the list of unsupported attributes */
+ msgb_push_u8(nmsg, attr[i]);
+ num_unsupported++;
+ }
}
+ /* Push the amount of unsupported attributes */
+ msgb_push_u8(nmsg, num_unsupported);
+
/* Push Get Attribute Response Info TL (actually TV where V is L) */
msgb_tv16_push(nmsg, NM_ATT_GET_ARI, msgb_length(nmsg));
@@ -313,6 +650,7 @@ static int oml_tx_attr_resp(const struct gsm_abis_mo *mo,
int oml_tx_state_changed(const struct gsm_abis_mo *mo)
{
struct msgb *nmsg;
+ uint8_t avail_state;
nmsg = oml_msgb_alloc();
if (!nmsg)
@@ -322,7 +660,8 @@ int oml_tx_state_changed(const struct gsm_abis_mo *mo)
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);
+ avail_state = (uint8_t) mo->nm_state.availability;
+ msgb_tl16v_put(nmsg, NM_ATT_AVAIL_STATUS, 1, &avail_state);
/* 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);
@@ -337,12 +676,13 @@ void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int 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 oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state, int adm_state)
{
int rc = 0;
if ((op_state != -1 && mo->nm_state.operational != op_state) ||
- (avail_state != -1 && mo->nm_state.availability != avail_state)) {
+ (avail_state != -1 && mo->nm_state.availability != avail_state) ||
+ (adm_state != -1 && mo->nm_state.administrative != adm_state)) {
if (avail_state != -1) {
LOGP(DOML, LOGL_INFO, "%s AVAIL STATE %s -> %s\n",
gsm_abis_mo_name(mo),
@@ -351,13 +691,25 @@ int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state)
mo->nm_state.availability = avail_state;
}
if (op_state != -1) {
+ struct nm_statechg_signal_data nsd;
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));
+ nsd.mo = mo;
+ nsd.old_state = mo->nm_state.operational;
+ nsd.new_state = op_state;
mo->nm_state.operational = op_state;
- osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, NULL);
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, &nsd);
}
+ if (adm_state != -1) {
+ LOGP(DOML, LOGL_INFO, "%s ADMIN STATE %s -> %s\n",
+ gsm_abis_mo_name(mo),
+ abis_nm_admin_name(mo->nm_state.administrative),
+ abis_nm_admin_name(adm_state));
+ mo->nm_state.administrative = adm_state;
+ }
+
/* send state change report */
rc = oml_tx_state_changed(mo);
@@ -421,42 +773,48 @@ int oml_mo_opstart_nack(const struct gsm_abis_mo *mo, uint8_t nack_cause)
return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, nack_cause);
}
-/* Send an ACK or NACK response for 'msg' to BSC, deriving message
- * type, obj class, obj inst from 'msg' and copying all attributes
- * contained in 'msg'. ACK is sent if cause == 0; NACK otherwise */
-int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause)
+/* Send an ACK or NACK response to BSC for the given OML message,
+ * reusing it. ACK is sent if cause == 0; NACK otherwise. */
+int oml_fom_ack_nack(struct msgb *msg, uint8_t cause)
{
- struct msgb *msg;
struct abis_om_fom_hdr *foh;
- msg = msgb_copy(old_msg, "OML_fom_ack_nack");
- if (!msg)
- return -ENOMEM;
-
- /* remove any l2/l1 that may be present in copy */
+ /* remove any l2/l1 that may be already present */
msgb_pull_to_l2(msg);
- msg->trx = old_msg->trx;
-
foh = (struct abis_om_fom_hdr *) msg->l3h;
/* alter message type */
if (cause) {
- LOGPFOH(DOML, LOGL_NOTICE, foh, "Sending FOM NACK with cause %s.\n",
+ LOGPFOH(DOML, LOGL_NOTICE, foh, "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);
- /* update the length as we just made the message larger */
- struct abis_om_hdr *omh = (struct abis_om_hdr *) msgb_l2(msg);
- omh->length = msgb_l3len(msg);
} else {
- LOGPFOH(DOML, LOGL_DEBUG, foh, "Sending FOM ACK.\n");
+ LOGPFOH(DOML, LOGL_DEBUG, foh, "Sending FOM ACK\n");
foh->msg_type++; /* ack */
}
+ /* ensure that the message length is up to date */
+ struct abis_om_hdr *omh = (struct abis_om_hdr *) msgb_l2(msg);
+ omh->length = msgb_l3len(msg);
+
/* we cannot use oml_send_msg() as we already have the OML header */
- return abis_oml_sendmsg(msg);
+ if (abis_oml_sendmsg(msg) != 0)
+ LOGPFOH(DOML, LOGL_ERROR, foh, "Failed to send ACK/NACK\n");
+
+ /* msgb was reused, do not free() */
+ return 1;
+}
+
+/* Copy msg before calling oml_fom_ack_nack(), which takes its ownership */
+int oml_fom_ack_nack_copy_msg(const struct msgb *old_msg, uint8_t cause)
+{
+ struct msgb *msg = msgb_copy(old_msg, "OML-ack_nack");
+ msg->trx = old_msg->trx;
+ oml_fom_ack_nack(msg, cause);
+ return 0;
}
/*
@@ -472,20 +830,24 @@ int oml_mo_tx_sw_act_rep(const struct gsm_abis_mo *mo)
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 various sources/recommendations that could be found online.
- * 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,
+/* The defaults below correspond to the number of frames until a response from the MS is expected.
+ * It defines the FN distance between the frame number when a message is sent (first frame) and when the response is
+ * received (first frame). On SACCH the duration is two frames, because SAPI0 and SAPI3 are are transmitted in
+ * alternating order. On DCCH with SAPI3 the duration is two seconds, because SAPI0 has priority over SAPI3.
+ *
+ * See Table 8 if 3GPP TS 44.006. Note that the table only shows the FN distance between frames.
+ */
+const uint32_t oml_default_t200_fn[7] = {
+ [T200_SDCCH] = 4+32,
+ [T200_FACCH_F] = 8+9,
+ [T200_FACCH_H] = 6+10,
+ [T200_SACCH_TCH_SAPI0] = 79+25+104,
+ [T200_SACCH_SDCCH] = 4+32+51,
+ [T200_SDCCH_SAPI3] = 4+32+408, /* two seconds */
+ [T200_SACCH_TCH_SAPI3] = 79+25+104,
};
/* 3GPP TS 52.021 §8.11.1 Get Attributes has been received */
@@ -495,6 +857,7 @@ static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg)
const struct gsm_abis_mo *mo;
struct tlv_parsed tp;
int rc;
+ enum abis_nm_nack_cause c;
if (!foh || !bts)
return -EINVAL;
@@ -502,10 +865,9 @@ static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg)
DEBUGPFOH(DOML, foh, "Rx GET ATTR\n");
/* Determine which OML object is addressed */
- mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
- if (!mo) {
+ if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) {
LOGPFOH(DOML, LOGL_ERROR, foh, "Get Attributes for unknown Object Instance\n");
- return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+ return oml_fom_ack_nack(msg, c);
}
rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
@@ -537,6 +899,7 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
struct tlv_parsed tp, *tp_merged;
int rc, i;
const uint8_t *payload;
+ struct nm_fsm_ev_setattr_data ev_data;
DEBUGPFOH(DOML, foh, "Rx SET BTS ATTR\n");
@@ -550,11 +913,11 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
/* 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) {
+ if (arfcn >= 1024) { /* 0 .. 1023 (1024 channels total) */
oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_WARN_SW_WARN,
"Given ARFCN %u is not supported",
arfcn);
- LOGPFOH(DOML, LOGL_ERROR, foh, "Given ARFCN %u is not supported.\n", arfcn);
+ LOGPFOH(DOML, LOGL_ERROR, foh, "Given ARFCN %u is not supported\n", arfcn);
return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL);
}
}
@@ -568,6 +931,7 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
/* merge existing BTS attributes with new attributes */
tp_merged = osmo_tlvp_copy(bts->mo.nm_attr, bts);
+ talloc_set_name_const(tp_merged, "oml_bts_attr");
osmo_tlvp_merge(tp_merged, &tp);
/* Ask BTS driver to validate new merged attributes */
@@ -586,8 +950,8 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
/* 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;
+ for (i = 0; i < ARRAY_SIZE(bts->interference.boundary); i++) {
+ const int16_t boundary = payload[i];
bts->interference.boundary[i] = -1 * boundary;
}
}
@@ -603,12 +967,16 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
case 0xFF: /* Osmocom specific Extension of TS 12.21 */
LOGPFOH(DOML, LOGL_NOTICE, foh, "WARNING: Radio Link Timeout "
"explicitly disabled, only use this for lab testing!\n");
- bts->radio_link_timeout = -1;
+ bts->radio_link_timeout.oml = -1;
+ if (!bts->radio_link_timeout.vty_override)
+ bts->radio_link_timeout.current = bts->radio_link_timeout.oml;
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];
+ bts->radio_link_timeout.oml = val[1];
+ if (!bts->radio_link_timeout.vty_override)
+ bts->radio_link_timeout.current = bts->radio_link_timeout.oml;
break;
}
/* fall-through */
@@ -622,25 +990,26 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
}
/* 9.4.53 T200 */
- if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_ms))) {
+ if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_fn))) {
+ /* The OML message NM_ATT_T200 is ignored, because T200 timeouts are set to
+ * the minimal response time. Longer timeouts would cause lower throughput
+ * in case of lost frames. Shorter timeouts would cause LAPDm to fail. */
+ DEBUGPFOH(DOML, foh, "Ignoring T200 BTS attribute.\n");
+#if 0
payload = TLVP_VAL(&tp, NM_ATT_T200);
- for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) {
+ for (i = 0; i < ARRAY_SIZE(bts->t200_fn); i++) {
uint32_t t200_ms = payload[i] * abis_nm_t200_ms[i];
-#if 0
- bts->t200_ms[i] = t200_ms;
- DEBUGPFOH(DOML, foh, "T200[%u]: OML=%u, mult=%u => %u ms\n",
+ uint32_t t200_fn = t200_ms * 1000 + (GSM_TDMA_FN_DURATION_uS - 1) / GSM_TDMA_FN_DURATION_uS;
+ /* Values must not be less than absolute minimum. */
+ if (oml_default_t200_fn[i] <= t200_fn)
+ bts->t200_fn[i] = t200_fn;
+ else
+ bts->t200_fn[i] = oml_default_t200_fn[i];
+ DEBUGPFOH(DOML, foh, "T200[%u]: OML=%u, mult=%u => %u ms -> %u fn\n",
i, payload[i], abis_nm_t200_ms[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? */
- LOGPFOH(DOML, LOGL_NOTICE, foh, "Ignoring T200[%u] (%u ms) "
- "as sent by BSC due to suspected LAPDm bug!\n",
- i, t200_ms);
-#endif
+ t200_ms, bts->t200_fn[i]);
}
+#endif
}
/* 9.4.31 Maximum Timing Advance */
@@ -658,7 +1027,10 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
/* 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);
+ if (load_timer_is_running(bts)) {
+ load_timer_stop(bts);
+ load_timer_start(bts);
+ }
}
/* 9.4.44 RACH Busy Threshold */
@@ -677,26 +1049,64 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
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) {
- LOGPFOH(DOML, LOGL_NOTICE, foh, "T3105 must have a value != 0.\n");
+ LOGPFOH(DOML, LOGL_NOTICE, foh, "T3105 must have a value != 0\n");
return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
}
bts->t3105_ms = t3105 * 10;
+ /* there are no OML IEs for T3115; let's use T3105 as HO detection is a similar procedure */
+ bts->t3115_ms = bts->t3105_ms;
}
/* 9.4.37 NY1 */
- if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1))
+ if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1)) {
bts->ny1 = *TLVP_VAL(&tp, NM_ATT_NY1);
+ /* there are no OML IEs for NY2; let's use NY1 as HO detection is a similar procedure */
+ bts->ny2 = bts->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))
+ if (TLVP_PRES_LEN(&tp, NM_ATT_BSIC, 1)) {
+ struct gsm_bts_trx *trx;
+ uint8_t bts_tsc;
+
bts->bsic = *TLVP_VAL(&tp, NM_ATT_BSIC);
+ bts->bsic_configured = true;
+ bts_tsc = BTS_TSC(bts);
+
+ /* Apply TSC update on each TS if required: */
+ llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */
+ unsigned int tn;
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ /* First some config validation: */
+ if (ts->tsc_oml_configured &&
+ ts->tsc_oml != bts_tsc &&
+ !osmo_bts_has_feature(bts->features, BTS_FEAT_MULTI_TSC)) {
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET BTS ATTR: this BTS model does not "
+ "support TSC %u != BSIC-BCC %u (TSC %u)\n",
+ ts->tsc_oml, bts->bsic, bts_tsc);
+ return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
+ }
+
+ /* Now update TS TSC if needed: */
+ gsm_ts_apply_configured_tsc(ts);
+ }
+ }
+ }
+
+ ev_data = (struct nm_fsm_ev_setattr_data){
+ .msg = msg,
+ };
- /* call into BTS driver to apply new attributes to hardware */
- return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_BTS, bts);
+ rc = osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_RX_SETATTR, &ev_data);
+ if (rc < 0)
+ return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
+ return rc;
}
/* 8.6.2 Set Radio Attributes has been received */
@@ -705,6 +1115,7 @@ 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;
+ struct nm_fsm_ev_setattr_data ev_data;
DEBUGPFOH(DOML, foh, "Rx SET RADIO CARRIER ATTR\n");
@@ -716,8 +1127,9 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg)
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);
+ /* merge existing TRX attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(trx->mo.nm_attr, trx);
+ talloc_set_name_const(tp_merged, "oml_trx_attr");
osmo_tlvp_merge(tp_merged, &tp);
/* Ask BTS driver to validate new merged attributes */
@@ -727,7 +1139,7 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg)
return oml_fom_ack_nack(msg, -rc);
}
- /* Success: replace old BTS attributes with new */
+ /* Success: replace old TRX attributes with new */
talloc_free(trx->mo.nm_attr);
trx->mo.nm_attr = tp_merged;
@@ -773,22 +1185,33 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg)
memcpy(&_value, value, 2);
arfcn = ntohs(_value);
value += 2;
- if (arfcn > 1024) {
+ if (arfcn >= 1024) { /* 0 .. 1023 (1024 channels total) */
oml_tx_failure_event_rep(&trx->bts->mo, NM_SEVER_MAJOR, OSMO_EVT_WARN_SW_WARN,
"Given ARFCN %u is unsupported", arfcn);
- LOGPFOH(DOML, LOGL_NOTICE, foh, "Given ARFCN %u is unsupported.\n", arfcn);
+ LOGPFOH(DOML, LOGL_NOTICE, foh, "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);
+
+ ev_data = (struct nm_fsm_ev_setattr_data){
+ .msg = msg,
+ };
+
+ rc = osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_RX_SETATTR, &ev_data);
+ if (rc < 0)
+ return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
+ return rc;
+
}
-static int conf_lchans(struct gsm_bts_trx_ts *ts)
+static int handle_chan_comb(struct gsm_bts_trx_ts *ts, const uint8_t comb)
{
- enum gsm_phys_chan_config pchan = ts->pchan;
+ enum gsm_phys_chan_config pchan;
+
+ pchan = abis_nm_pchan4chcomb(comb);
+ ts->pchan = pchan;
/* RSL_MT_IPAC_PDCH_ACT style dyn PDCH */
if (pchan == GSM_PCHAN_TCH_F_PDCH)
@@ -796,7 +1219,7 @@ static int conf_lchans(struct gsm_bts_trx_ts *ts)
: GSM_PCHAN_TCH_F;
/* Osmocom RSL CHAN ACT style dyn TS */
- if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ if (pchan == GSM_PCHAN_OSMO_DYN) {
pchan = ts->dyn.pchan_is;
/* If the dyn TS doesn't have a pchan yet, do nothing. */
@@ -807,62 +1230,65 @@ static int conf_lchans(struct gsm_bts_trx_ts *ts)
return conf_lchans_as_pchan(ts, pchan);
}
+static inline void lchans_type_set(struct gsm_bts_trx_ts *ts,
+ enum gsm_chan_t lchan_type,
+ unsigned int num_lchans)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_lchans; i++)
+ ts->lchan[i].type = lchan_type;
+}
+
int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts,
enum gsm_phys_chan_config pchan)
{
- struct gsm_lchan *lchan;
- unsigned int i;
+ /* Initialize all lchans with GSM_LCHAN_NONE first */
+ lchans_type_set(ts, GSM_LCHAN_NONE, ARRAY_SIZE(ts->lchan));
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;
- }
- }
+ lchans_type_set(ts, GSM_LCHAN_SDCCH, 4);
+ if (pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ ts->lchan[2].type = GSM_LCHAN_CBCH;
/* fallthrough */
case GSM_PCHAN_CCCH:
- lchan = &ts->lchan[CCCH_LCHAN];
- lchan->type = GSM_LCHAN_CCCH;
+ ts->lchan[CCCH_LCHAN].type = GSM_LCHAN_CCCH;
break;
case GSM_PCHAN_TCH_F:
- lchan = &ts->lchan[0];
- lchan->type = GSM_LCHAN_TCH_F;
+ if (ts->vamos.peer != NULL) { /* VAMOS: enable shadow lchans */
+ lchans_type_set(ts->vamos.peer, GSM_LCHAN_TCH_F, 1);
+ ts->vamos.peer->pchan = GSM_PCHAN_TCH_F;
+ }
+ lchans_type_set(ts, GSM_LCHAN_TCH_F, 1);
break;
case GSM_PCHAN_TCH_H:
- for (i = 0; i < 2; i++) {
- lchan = &ts->lchan[i];
- lchan->type = GSM_LCHAN_TCH_H;
+ if (ts->vamos.peer != NULL) { /* VAMOS: enable shadow lchans */
+ lchans_type_set(ts->vamos.peer, GSM_LCHAN_TCH_H, 2);
+ ts->vamos.peer->pchan = GSM_PCHAN_TCH_H;
}
+ lchans_type_set(ts, GSM_LCHAN_TCH_H, 2);
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;
- }
- }
+ lchans_type_set(ts, GSM_LCHAN_SDCCH, 8);
+ if (pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH)
+ ts->lchan[2].type = GSM_LCHAN_CBCH;
break;
case GSM_PCHAN_PDCH:
- lchan = &ts->lchan[0];
- lchan->type = GSM_LCHAN_PDTCH;
+ if (ts->vamos.peer != NULL) { /* VAMOS: disable shadow lchans */
+ lchans_type_set(ts->vamos.peer, GSM_LCHAN_NONE, 1);
+ ts->vamos.peer->pchan = GSM_PCHAN_NONE;
+ }
+ lchans_type_set(ts, GSM_LCHAN_PDTCH, 1);
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;
}
@@ -872,7 +1298,8 @@ 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;
+ int rc, i;
+ struct nm_fsm_ev_setattr_data ev_data;
DEBUGPFOH(DOML, foh, "Rx SET CHAN ATTR\n");
@@ -883,21 +1310,50 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg)
return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
}
- /* 9.4.21 HSN... */
- /* 9.4.27 MAIO */
+ /* Check frequency hopping parameters (HSN, MAIO, ARFCN list) */
if (TLVP_PRESENT(&tp, NM_ATT_HSN) || TLVP_PRESENT(&tp, NM_ATT_MAIO)) {
- LOGPFOH(DOML, LOGL_NOTICE, foh, "SET CHAN ATTR: Frequency hopping not supported.\n");
- return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ if (!osmo_bts_has_feature(bts->features, BTS_FEAT_HOPPING)) {
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: Frequency hopping not supported\n");
+ return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ }
+
+ if (!TLVP_PRES_LEN(&tp, NM_ATT_HSN, 1) || !TLVP_PRES_LEN(&tp, NM_ATT_MAIO, 1)) {
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: HSN and/or MAIO is missing: "
+ "hsn=%u, maio=%u\n", TLVP_LEN(&tp, NM_ATT_HSN), TLVP_LEN(&tp, NM_ATT_MAIO));
+ return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT);
+ }
+
+ if (!TLVP_PRES_LEN(&tp, NM_ATT_ARFCN_LIST, 2)) { /* At least one ARFCN */
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: ARFCN list is missing\n");
+ return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT);
+ }
+
+ if (TLVP_LEN(&tp, NM_ATT_ARFCN_LIST) > sizeof(ts->hopping.arfcn_list)) {
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: ARFCN list is too long\n");
+ return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT);
+ } else if (TLVP_LEN(&tp, NM_ATT_ARFCN_LIST) % 2 != 0) {
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: ARFCN list has odd length\n");
+ return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT);
+ }
+
+ ts->hopping.enabled = true;
+ ts->hopping.hsn = *TLVP_VAL(&tp, NM_ATT_HSN);
+ ts->hopping.maio = *TLVP_VAL(&tp, NM_ATT_MAIO);
+
+ ts->hopping.arfcn_num = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST) / sizeof(uint16_t);
+ for (i = 0; i < ts->hopping.arfcn_num; i++)
+ ts->hopping.arfcn_list[i] = osmo_load16be(TLVP_VAL(&tp, NM_ATT_ARFCN_LIST) + i * 2);
}
/* 9.4.52 Starting Time */
if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) {
- LOGPFOH(DOML, LOGL_NOTICE, foh, "SET CHAN ATTR: Starting time not supported.\n");
+ LOGPFOH(DOML, LOGL_NOTICE, foh, "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);
+ /* merge existing CHAN attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(ts->mo.nm_attr, ts->trx);
+ talloc_set_name_const(tp_merged, "oml_chan_attr");
osmo_tlvp_merge(tp_merged, &tp);
/* Call into BTS driver to check attribute values */
@@ -909,18 +1365,16 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg)
return oml_fom_ack_nack(msg, -rc);
}
- /* Success: replace old BTS attributes with new */
+ /* Success: replace old CHAN 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) {
+ const uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB);
+ if ((rc = handle_chan_comb(ts, comb)) != 0) {
LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: invalid Chan Comb 0x%x"
- " (pchan=%s, conf_lchans()->%d)\n",
+ " (pchan=%s, handle_chan_comb() returns %d)\n",
comb, gsm_pchan_name(ts->pchan), rc);
talloc_free(tp_merged);
/* Send NACK */
@@ -928,20 +1382,40 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg)
}
}
- /* 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);
+ ts->tsc_oml = *TLVP_VAL(&tp, NM_ATT_TSC);
+ ts->tsc_oml_configured = true;
+ }
+
+ if (ts->tsc_oml_configured) {
+ if (bts->bsic_configured &&
+ ts->tsc_oml != BTS_TSC(bts) &&
+ !osmo_bts_has_feature(bts->features, BTS_FEAT_MULTI_TSC)) {
+ LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: this BTS model does not "
+ "support TSC %u != BSIC-BCC %u\n", ts->tsc_oml, BTS_TSC(bts));
+ talloc_free(tp_merged);
+ return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
+ }
+ gsm_ts_apply_configured_tsc(ts);
}
- LOGPFOH(DOML, LOGL_INFO, foh, "%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);
+ LOGPFOH(DOML, LOGL_INFO, foh, "SET CHAN ATTR (TSC=%d pchan=%s",
+ ts->tsc_oml_configured ? (int)ts->tsc_oml : -1,
+ gsm_pchan_name(ts->pchan));
+ if (ts->hopping.enabled)
+ LOGPC(DOML, LOGL_INFO, " hsn=%u maio=%u chan_num=%u",
+ ts->hopping.hsn, ts->hopping.maio, ts->hopping.arfcn_num);
+ LOGPC(DOML, LOGL_INFO, ")\n");
+
+ ev_data = (struct nm_fsm_ev_setattr_data){
+ .msg = msg,
+ };
+
+ rc = osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_RX_SETATTR, &ev_data);
+ if (rc < 0)
+ return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
+ return rc;
}
/* 8.9.2 Opstart has been received */
@@ -950,14 +1424,16 @@ 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;
+ int rc;
+ enum abis_nm_nack_cause c;
DEBUGPFOH(DOML, foh, "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);
+ if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
+ if ((obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
/* Step 2: Do some global dependency/consistency checking */
if (mo->nm_state.operational == NM_OPSTATE_ENABLED) {
@@ -965,8 +1441,13 @@ static int oml_rx_opstart(struct gsm_bts *bts, struct msgb *msg)
return oml_mo_opstart_ack(mo);
}
- /* Step 3: Ask BTS driver to apply the opstart */
- return bts_model_opstart(bts, mo, obj);
+ /* Make sure all NM objects already have an FSM implemented: */
+ OSMO_ASSERT(mo->fi);
+
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_RX_OPSTART, NULL);
+ if (rc < 0)
+ return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
+ return rc;
}
static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg)
@@ -977,6 +1458,7 @@ static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg)
uint8_t adm_state;
void *obj;
int rc;
+ enum abis_nm_nack_cause c;
DEBUGPFOH(DOML, foh, "Rx CHG ADM STATE\n");
@@ -994,15 +1476,20 @@ static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg)
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);
+ if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
+ if ((obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
/* Step 2: Do some global dependency/consistency checking */
- if (mo->nm_state.administrative == adm_state)
+ if (mo->nm_state.administrative == adm_state) {
LOGPFOH(DOML, LOGL_NOTICE, foh, "ADM state already was %s\n",
get_value_string(abis_nm_adm_state_names, adm_state));
+ return oml_mo_statechg_ack(mo);
+ }
+ LOGPFOH(DOML, LOGL_NOTICE, foh, "ADM STATE %s -> %s\n",
+ get_value_string(abis_nm_adm_state_names, mo->nm_state.administrative),
+ 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);
@@ -1114,16 +1601,21 @@ static int down_fom(struct gsm_bts *bts, struct msgb *msg)
* manufacturer related messages
*/
-static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp)
+static int oml_ipa_mo_set_attr_nse(void *obj,
+ const struct msgb *msg,
+ const struct tlv_parsed *tp)
{
- struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.nse);
+ struct gsm_gprs_nse *nse = obj;
+ struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse);
+ struct nm_fsm_ev_setattr_data ev_data;
+ int rc;
if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSEI, 2))
- bts->gprs.nse.nsei =
+ 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,
+ memcpy(&nse->timer,
TLVP_VAL(tp, NM_ATT_IPACC_NS_CFG), 7);
}
@@ -1132,15 +1624,27 @@ static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp)
TLVP_VAL(tp, NM_ATT_IPACC_BSSGP_CFG), 11);
}
+ ev_data = (struct nm_fsm_ev_setattr_data){
+ .msg = msg,
+ };
+ rc = osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_RX_SETATTR, &ev_data);
+ if (rc < 0)
+ return NM_NACK_CANT_PERFORM;
+
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)
+static int oml_ipa_mo_set_attr_cell(void *obj,
+ const struct msgb *msg,
+ const 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;
+ struct gsm_gprs_cell *gprs_cell = obj;
+ struct gsm_bts *bts = gsm_gprs_cell_get_bts(gprs_cell);
+ struct gprs_rlc_cfg *rlcc = &gprs_cell->rlc_cfg;
+ struct nm_fsm_ev_setattr_data ev_data;
+ int rc;
const uint8_t *cur;
uint16_t _cur_s;
@@ -1154,8 +1658,7 @@ static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp)
}
if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BVCI, 2))
- bts->gprs.cell.bvci =
- ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_BVCI));
+ 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);
@@ -1202,14 +1705,25 @@ static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp)
rlcc->initial_mcs = *TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_3);
}
+ ev_data = (struct nm_fsm_ev_setattr_data){
+ .msg = msg,
+ };
+ rc = osmo_fsm_inst_dispatch(gprs_cell->mo.fi, NM_EV_RX_SETATTR, &ev_data);
+ if (rc < 0)
+ return NM_NACK_CANT_PERFORM;
+
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)
+static int oml_ipa_mo_set_attr_nsvc(struct gsm_gprs_nsvc *nsvc,
+ const struct msgb *msg,
+ const struct tlv_parsed *tp)
{
+ struct nm_fsm_ev_setattr_data ev_data;
+ int rc;
+
if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSVCI, 2))
nsvc->nsvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSVCI));
@@ -1218,41 +1732,69 @@ static int oml_ipa_mo_set_attr_nsvc(struct gsm_bts_gprs_nsvc *nsvc,
uint16_t _cur_s;
uint32_t _cur_l;
+ memset(&nsvc->local, 0, sizeof(nsvc->local));
+ memset(&nsvc->remote, 0, sizeof(nsvc->remote));
+
+ nsvc->local.u.sin.sin_family = AF_INET;
+ nsvc->remote.u.sin.sin_family = AF_INET;
+
memcpy(&_cur_s, cur, 2);
- nsvc->remote_port = ntohs(_cur_s);
+ nsvc->remote.u.sin.sin_port = _cur_s;
cur += 2;
memcpy(&_cur_l, cur, 4);
- nsvc->remote_ip = ntohl(_cur_l);
+ nsvc->remote.u.sin.sin_addr.s_addr = _cur_l;
cur += 4;
memcpy(&_cur_s, cur, 2);
- nsvc->local_port = ntohs(_cur_s);
+ nsvc->local.u.sin.sin_port = _cur_s;
}
- osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc);
+ if (TLVP_PRES_LEN(tp, NM_ATT_OSMO_NS_LINK_CFG, 10)) {
+ const uint8_t *cur = TLVP_VAL(tp, NM_ATT_OSMO_NS_LINK_CFG);
+ uint8_t address_family;
- return 0;
-}
+ memset(&nsvc->local, 0, sizeof(nsvc->local));
+ memset(&nsvc->remote, 0, sizeof(nsvc->remote));
-static int oml_ipa_mo_set_attr(struct gsm_bts *bts, const struct gsm_abis_mo *mo,
- void *obj, struct tlv_parsed *tp)
-{
- int rc;
+ address_family = *cur;
+ /* 1byte padding */
+ cur += 2;
- 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;
+ memcpy(&nsvc->local.u.sin.sin_port, cur, 2);
+ cur += 2;
+
+ memcpy(&nsvc->remote.u.sin.sin_port, cur, 2);
+ cur += 2;
+
+ switch (address_family) {
+ case OSMO_NSVC_ADDR_IPV4:
+ /* we already checked for 10 bytes */
+ nsvc->remote.u.sas.ss_family = AF_INET;
+ nsvc->local.u.sas.ss_family = AF_INET;
+ memcpy(&nsvc->remote.u.sin.sin_addr.s_addr, cur, sizeof(in_addr_t));
+ break;
+ case OSMO_NSVC_ADDR_IPV6:
+ if (TLVP_LEN(tp, NM_ATT_OSMO_NS_LINK_CFG) < 22) {
+ return -1;
+ }
+ nsvc->remote.u.sas.ss_family = AF_INET6;
+ nsvc->local.u.sas.ss_family = AF_INET6;
+ memcpy(&nsvc->remote.u.sin6.sin6_addr, cur, sizeof(struct in6_addr));
+ break;
+ default:
+ return -1;
+ }
}
- return rc;
+ ev_data = (struct nm_fsm_ev_setattr_data){
+ .msg = msg,
+ };
+ rc = osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_RX_SETATTR, &ev_data);
+ if (rc < 0)
+ return NM_NACK_CANT_PERFORM;
+
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc);
+
+ return 0;
}
static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg)
@@ -1262,75 +1804,107 @@ static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg)
struct tlv_parsed tp, *tp_merged;
void *obj;
int rc;
+ enum abis_nm_nack_cause c;
DEBUGPFOH(DOML, foh, "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);
+ if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
oml_tx_failure_event_rep(mo, NM_SEVER_MAJOR, 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);
+ if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
+ if ((obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL)
+ return oml_fom_ack_nack(msg, c);
+
- rc = oml_ipa_mo_set_attr(bts, mo, obj, &tp);
- if (rc == 0) {
- /* Success: replace old MO attributes with new */
- /* merge existing MO attributes with new attributes */
- tp_merged = osmo_tlvp_copy(mo->nm_attr, bts);
- osmo_tlvp_merge(tp_merged, &tp);
- talloc_free(mo->nm_attr);
- mo->nm_attr = tp_merged;
+ switch (mo->obj_class) {
+ case NM_OC_GPRS_NSE:
+ rc = oml_ipa_mo_set_attr_nse(obj, msg, &tp);
+ break;
+ case NM_OC_GPRS_CELL:
+ rc = oml_ipa_mo_set_attr_cell(obj, msg, &tp);
+ break;
+ case NM_OC_GPRS_NSVC:
+ rc = oml_ipa_mo_set_attr_nsvc(obj, msg, &tp);
+ break;
+ default:
+ rc = NM_NACK_OBJINST_UNKN;
}
- return oml_fom_ack_nack(msg, rc);
+ if (rc != 0)
+ return oml_fom_ack_nack(msg, rc);
+
+ /* Success: replace old MO attributes with new */
+ /* merge existing MO attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(mo->nm_attr, bts);
+ talloc_set_name_const(tp_merged, "oml_ipa_attr");
+ osmo_tlvp_merge(tp_merged, &tp);
+ talloc_free(mo->nm_attr);
+ mo->nm_attr = tp_merged;
+
+ return rc;
}
-static int rx_oml_ipa_rsl_connect(struct gsm_bts_trx *trx, struct msgb *msg,
- struct tlv_parsed *tp)
+static int rx_oml_ipa_rsl_connect(struct gsm_bts *bts, struct msgb *msg,
+ const 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);
- const char *trx_name = gsm_trx_name(trx);
+ struct e1inp_sign_link *oml_link = bts->oml_link;
+ const struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ struct gsm_bts_bb_trx *bb_transc;
+ const char *trx_name;
struct in_addr in;
+ uint16_t port = IPA_TCP_PORT_RSL;
+ uint8_t stream_id = 0;
int rc;
- uint8_t stream_id = 0;
+ if (TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP))
+ in.s_addr = tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP);
+ else
+ in.s_addr = htonl(get_signlink_remote_ip(oml_link));
- 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)) {
+ if (TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP_PORT))
port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT));
- }
- if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_STREAM_ID, 1)) {
+
+ if (TLVP_PRESENT(tp, NM_ATT_IPACC_STREAM_ID))
stream_id = *TLVP_VAL(tp, NM_ATT_IPACC_STREAM_ID);
+
+ if (!trx) {
+ LOGP(DOML, LOGL_ERROR, "Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x for unknown TRX_NR=%u\n",
+ inet_ntoa(in), port, stream_id, foh->obj_inst.trx_nr);
+ rc = NM_NACK_TRXNR_UNKN;
+ goto tx_ack_nack;
}
- in.s_addr = htonl(ip);
- LOGP(DOML, LOGL_INFO, "%s: Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
- trx_name, inet_ntoa(in), port, stream_id);
+ bb_transc = &trx->bb_transc;
+ osmo_sockaddr_str_from_in_addr(&bb_transc->rsl.rem_addrstr, &in, port);
+ bb_transc->rsl.tei = stream_id;
- if (trx->bts->variant == BTS_OSMO_OMLDUMMY) {
- rc = 0;
- LOGP(DOML, LOGL_NOTICE, "%s: Not connecting RSL in OML-DUMMY!\n", trx_name);
- } 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, "%s: Error in abis_open(RSL): %d\n", trx_name, rc);
- return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
- }
+ trx_name = gsm_trx_name(trx);
+ LOGP(DOML, LOGL_INFO, "%s: Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ trx_name, bb_transc->rsl.rem_addrstr.ip, bb_transc->rsl.rem_addrstr.port,
+ bb_transc->rsl.tei);
+
+ rc = 0;
- return oml_fom_ack_nack(msg, 0);
+tx_ack_nack:
+ /* The ACK/NACK is expected to contain all IEs */
+ if (!TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP)) /* TV32 */
+ msgb_tv_fixed_put(msg, NM_ATT_IPACC_DST_IP, sizeof(in),
+ (const uint8_t *) &in);
+ if (!TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP_PORT)) /* TV16 */
+ msgb_tv16_put(msg, NM_ATT_IPACC_DST_IP_PORT, port);
+ if (!TLVP_PRESENT(tp, NM_ATT_IPACC_STREAM_ID)) /* TV */
+ msgb_tv_put(msg, NM_ATT_IPACC_STREAM_ID, stream_id);
+
+ return oml_fom_ack_nack(msg, rc);
}
static int down_mom(struct gsm_bts *bts, struct msgb *msg)
@@ -1379,8 +1953,7 @@ static int down_mom(struct gsm_bts *bts, struct msgb *msg)
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);
+ ret = rx_oml_ipa_rsl_connect(bts, msg, &tp);
break;
case NM_MT_IPACC_SET_ATTR:
ret = oml_ipa_set_attr(bts, msg);
@@ -1452,16 +2025,144 @@ int down_oml(struct gsm_bts *bts, struct msgb *msg)
ret = -EINVAL;
}
+ /* msgb was reused, do not free() */
+ if (ret == 1)
+ return 0;
+
msgb_free(msg);
return ret;
}
-int oml_init(struct gsm_abis_mo *mo)
+int oml_init()
{
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);
+ tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_osmo_att_tlvdef);
return 0;
}
+
+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;
+ mo->nm_state.operational = NM_OPSTATE_DISABLED;
+ mo->nm_state.availability = NM_AVSTATE_POWER_OFF;
+ mo->nm_state.administrative = NM_STATE_LOCKED;
+}
+
+/* Obtain the MO structure for a given object instance
+ * \param[out] c nack cause for reply in case of error. Ignored if NULL */
+struct gsm_abis_mo *gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst,
+ enum abis_nm_nack_cause *c)
+{
+ struct gsm_bts_trx *trx;
+
+ switch ((enum abis_nm_obj_class)obj_class) {
+ case NM_OC_BTS:
+ return &bts->mo;
+ case NM_OC_RADIO_CARRIER:
+ if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr)))
+ goto nm_nack_trxnr_unkn;
+ return &trx->mo;
+ case NM_OC_BASEB_TRANSC:
+ if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr)))
+ goto nm_nack_trxnr_unkn;
+ return &trx->bb_transc.mo;
+ case NM_OC_CHANNEL:
+ if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr)))
+ goto nm_nack_trxnr_unkn;
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ goto nm_nack_objinst_unkn;
+ return &trx->ts[obj_inst->ts_nr].mo;
+ case NM_OC_SITE_MANAGER:
+ return &g_bts_sm->mo;
+ case NM_OC_GPRS_NSE:
+ if (obj_inst->bts_nr > 0)
+ goto nm_nack_objinst_unkn;
+ return &g_bts_sm->gprs.nse.mo;
+ case NM_OC_GPRS_CELL:
+ return &bts->gprs.cell.mo;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->bts_nr > 0)
+ goto nm_nack_objinst_unkn;
+ if (obj_inst->trx_nr >= ARRAY_SIZE(g_bts_sm->gprs.nse.nsvc))
+ goto nm_nack_objinst_unkn;
+ return &g_bts_sm->gprs.nse.nsvc[obj_inst->trx_nr].mo;
+ default:
+ if (c != NULL)
+ *c = NM_NACK_OBJCLASS_NOTSUPP;
+ return NULL;
+ }
+
+nm_nack_trxnr_unkn:
+ if (c != NULL)
+ *c = NM_NACK_TRXNR_UNKN;
+ return NULL;
+nm_nack_objinst_unkn:
+ if (c != NULL)
+ *c = NM_NACK_OBJINST_UNKN;
+ return NULL;
+}
+
+/* Obtain the in-memory data structure of a given object instance
+ * \param[out] c nack cause for reply in case of error. Ignored if NULL */
+void *gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst,
+ enum abis_nm_nack_cause *c)
+{
+ struct gsm_bts_trx *trx;
+
+ switch ((enum abis_nm_obj_class)obj_class) {
+ case NM_OC_BTS:
+ return bts;
+ case NM_OC_RADIO_CARRIER:
+ if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr)))
+ goto nm_nack_trxnr_unkn;
+ return trx;
+ case NM_OC_BASEB_TRANSC:
+ if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr)))
+ goto nm_nack_trxnr_unkn;
+ return &trx->bb_transc;
+ case NM_OC_CHANNEL:
+ if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr)))
+ goto nm_nack_trxnr_unkn;
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ goto nm_nack_objinst_unkn;
+ return &trx->ts[obj_inst->ts_nr];
+ case NM_OC_SITE_MANAGER:
+ return g_bts_sm;
+ case NM_OC_GPRS_NSE:
+ if (obj_inst->bts_nr > 0)
+ goto nm_nack_objinst_unkn;
+ return &g_bts_sm->gprs.nse;
+ case NM_OC_GPRS_CELL:
+ return &bts->gprs.cell;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->bts_nr > 0)
+ goto nm_nack_objinst_unkn;
+ if (obj_inst->trx_nr >= ARRAY_SIZE(g_bts_sm->gprs.nse.nsvc))
+ goto nm_nack_objinst_unkn;
+ return &g_bts_sm->gprs.nse.nsvc[obj_inst->trx_nr];
+ default:
+ if (c != NULL)
+ *c = NM_NACK_OBJCLASS_NOTSUPP;
+ return NULL;
+ }
+
+nm_nack_trxnr_unkn:
+ if (c != NULL)
+ *c = NM_NACK_TRXNR_UNKN;
+ return NULL;
+nm_nack_objinst_unkn:
+ if (c != NULL)
+ *c = NM_NACK_OBJINST_UNKN;
+ return NULL;
+}
diff --git a/src/common/osmux.c b/src/common/osmux.c
new file mode 100644
index 00000000..9513bdc4
--- /dev/null
+++ b/src/common/osmux.c
@@ -0,0 +1,545 @@
+/* Osmux related routines & logic */
+
+/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ * Author: Pau Espin Pedrol <pespin@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 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 <sys/socket.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/netif/rtp.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/osmux.h>
+#include <osmo-bts/lchan.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/l1sap.h>
+
+/* Bitmask containing Allocated Osmux circuit ID. +7 to round up to 8 bit boundary. */
+static uint8_t osmux_cid_bitmap[OSMO_BYTES_FOR_BITS(OSMUX_CID_MAX + 1)];
+
+/*! Find and reserve a free OSMUX cid. Keep state of last allocated CID to
+ * rotate allocated CIDs over time. This helps in letting CIDs unused for some
+ * time after last use.
+ * \returns OSMUX cid */
+static int osmux_get_local_cid(void)
+{
+ static uint8_t next_free_osmux_cid_lookup = 0;
+ uint8_t start_i, start_j;
+ uint8_t i, j, cid;
+
+ /* i = octet index, j = bit index inside ith octet */
+ start_i = next_free_osmux_cid_lookup >> 3;
+ start_j = next_free_osmux_cid_lookup & 0x07;
+
+ for (i = start_i; i < sizeof(osmux_cid_bitmap); i++) {
+ for (j = start_j; j < 8; j++) {
+ if (osmux_cid_bitmap[i] & (1 << j))
+ continue;
+ goto found;
+ }
+ }
+
+ for (i = 0; i <= start_i; i++) {
+ for (j = 0; j < start_j; j++) {
+ if (osmux_cid_bitmap[i] & (1 << j))
+ continue;
+ goto found;
+ }
+ }
+
+ LOGP(DOSMUX, LOGL_ERROR, "All Osmux circuits are in use!\n");
+ return -1;
+
+found:
+ osmux_cid_bitmap[i] |= (1 << j);
+ cid = (i << 3) | j;
+ next_free_osmux_cid_lookup = (cid + 1) & 0xff;
+ LOGP(DOSMUX, LOGL_DEBUG,
+ "Allocating Osmux CID %u from pool\n", cid);
+ return cid;
+}
+
+/*! put back a no longer used OSMUX cid.
+ * \param[in] osmux_cid OSMUX cid */
+void osmux_put_local_cid(uint8_t osmux_cid)
+{
+ LOGP(DOSMUX, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid);
+ osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8));
+}
+
+/* Deliver OSMUX batch to the remote end */
+static void osmux_deliver_cb(struct msgb *batch_msg, void *data)
+{
+ struct osmux_handle *handle = data;
+ struct gsm_bts *bts = handle->bts;
+ socklen_t dest_len;
+ ssize_t rc;
+
+ switch (handle->rem_addr.u.sa.sa_family) {
+ case AF_INET6:
+ dest_len = sizeof(handle->rem_addr.u.sin6);
+ break;
+ case AF_INET:
+ default:
+ dest_len = sizeof(handle->rem_addr.u.sin);
+ break;
+ }
+ rc = sendto(bts->osmux.fd.fd, batch_msg->data, batch_msg->len, 0,
+ (struct sockaddr *)&handle->rem_addr.u.sa, dest_len);
+ if (rc < 0) {
+ char errbuf[129];
+ strerror_r(errno, errbuf, sizeof(errbuf));
+ LOGP(DOSMUX, LOGL_ERROR, "osmux sendto(%s) failed: %s\n",
+ osmo_sockaddr_to_str(&handle->rem_addr), errbuf);
+ }
+ msgb_free(batch_msg);
+}
+
+/* Lookup existing OSMUX handle for specified destination address. */
+static struct osmux_handle *osmux_handle_find_get(const struct gsm_bts *bts,
+ const struct osmo_sockaddr *rem_addr)
+{
+ struct osmux_handle *h;
+
+ llist_for_each_entry(h, &bts->osmux.osmux_handle_list, head) {
+ if (osmo_sockaddr_cmp(&h->rem_addr, rem_addr) == 0) {
+ LOGP(DOSMUX, LOGL_DEBUG,
+ "Using existing OSMUX handle for rem_addr=%s\n",
+ osmo_sockaddr_to_str(rem_addr));
+ h->refcnt++;
+ return h;
+ }
+ }
+
+ return NULL;
+}
+
+/* Put down no longer needed OSMUX handle */
+static void osmux_handle_put(struct gsm_bts *bts, struct osmux_in_handle *in)
+{
+ struct osmux_handle *h;
+
+ llist_for_each_entry(h, &bts->osmux.osmux_handle_list, head) {
+ if (h->in == in) {
+ if (--h->refcnt == 0) {
+ LOGP(DOSMUX, LOGL_INFO,
+ "Releasing unused osmux handle for %s\n",
+ osmo_sockaddr_to_str(&h->rem_addr));
+ llist_del(&h->head);
+ TALLOC_FREE(h->in);
+ talloc_free(h);
+ }
+ return;
+ }
+ }
+ LOGP(DOSMUX, LOGL_ERROR, "Cannot find Osmux input handle %p\n", in);
+}
+
+/* Allocate free OSMUX handle */
+static struct osmux_handle *osmux_handle_alloc(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr)
+{
+ struct osmux_handle *h;
+ char name[128] = "r=";
+
+ h = talloc_zero(bts, struct osmux_handle);
+ if (!h)
+ return NULL;
+ h->bts = bts;
+ h->rem_addr = *rem_addr;
+ h->refcnt++;
+
+ h->in = osmux_xfrm_input_alloc(h);
+ if (!h->in) {
+ talloc_free(h);
+ return NULL;
+ }
+
+ osmo_sockaddr_to_str_buf(name + 2, sizeof(name) - 2, rem_addr);
+ osmux_xfrm_input_set_name(h->in, name);
+ /* sequence number to start OSMUX message from */
+ osmux_xfrm_input_set_initial_seqnum(h->in, 0);
+ osmux_xfrm_input_set_batch_factor(h->in, bts->osmux.batch_factor);
+ /* If batch size is zero, the library defaults to 1472 bytes. */
+ osmux_xfrm_input_set_batch_size(h->in, bts->osmux.batch_size);
+ osmux_xfrm_input_set_deliver_cb(h->in, osmux_deliver_cb, h);
+
+ llist_add(&h->head, &bts->osmux.osmux_handle_list);
+
+ LOGP(DOSMUX, LOGL_DEBUG, "Created new OSMUX handle for rem_addr=%s\n",
+ osmo_sockaddr_to_str(rem_addr));
+
+ return h;
+}
+
+/* Lookup existing handle for a specified address, if the handle can not be
+ * found, the function will automatically allocate one */
+static struct osmux_in_handle *
+osmux_handle_find_or_create(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr)
+{
+ struct osmux_handle *h;
+
+ if (rem_addr->u.sa.sa_family != AF_INET) {
+ LOGP(DOSMUX, LOGL_DEBUG, "IPv6 not supported in osmux yet!\n");
+ return NULL;
+ }
+
+ h = osmux_handle_find_get(bts, rem_addr);
+ if (h != NULL)
+ return h->in;
+
+ h = osmux_handle_alloc(bts, rem_addr);
+ if (h == NULL)
+ return NULL;
+
+ return h->in;
+}
+
+
+static struct msgb *osmux_recv(struct osmo_fd *ofd, struct osmo_sockaddr *addr)
+{
+ struct msgb *msg;
+ socklen_t slen = sizeof(addr->u.sas);
+ int ret;
+
+ msg = msgb_alloc(4096, "OSMUX"); /* TODO: pool? */
+ if (!msg) {
+ LOGP(DOSMUX, LOGL_ERROR, "cannot allocate message\n");
+ return NULL;
+ }
+ ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0, &addr->u.sa, &slen);
+ if (ret <= 0) {
+ msgb_free(msg);
+ LOGP(DOSMUX, LOGL_ERROR, "cannot receive message\n");
+ return NULL;
+ }
+ msgb_put(msg, ret);
+
+ return msg;
+}
+
+static struct gsm_lchan *osmux_lchan_find(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr, uint8_t osmux_cid)
+{
+ /* TODO: Optimize this by maintaining a hashmap local_cid->lchan in bts */
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */
+ unsigned int tn;
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ uint8_t subslot, subslots;
+ if (!ts_is_tch(ts))
+ continue;
+
+ subslots = ts_subslots(ts);
+ for (subslot = 0; subslot < subslots; subslot++) {
+ struct gsm_lchan *lchan = &ts->lchan[subslot];
+ struct osmux_handle *h;
+ if (!lchan->abis_ip.osmux.use)
+ continue;
+ if (!lchan_osmux_connected(lchan))
+ continue;
+ if (lchan->abis_ip.osmux.local_cid != osmux_cid)
+ continue;
+ h = osmux_xfrm_input_get_deliver_cb_data(lchan->abis_ip.osmux.in);
+ if (osmo_sockaddr_cmp(&h->rem_addr, rem_addr) != 0)
+ continue;
+ return lchan; /* Found it! */
+ }
+ }
+ }
+ return NULL;
+}
+
+static int osmux_read_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct msgb *msg;
+ struct osmux_hdr *osmuxh;
+ struct osmo_sockaddr rem_addr;
+ struct gsm_bts *bts = ofd->data;
+
+ msg = osmux_recv(ofd, &rem_addr);
+ if (!msg)
+ return -1;
+
+ while ((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) {
+ struct gsm_lchan *lchan = osmux_lchan_find(bts, &rem_addr, osmuxh->circuit_id);
+ if (!lchan) {
+ char addr_str[64];
+ osmo_sockaddr_to_str_buf(addr_str, sizeof(addr_str), &rem_addr);
+ LOGP(DOSMUX, LOGL_DEBUG,
+ "Cannot find lchan for %s CID=%d\n",
+ addr_str, osmuxh->circuit_id);
+ continue;
+ }
+ osmux_xfrm_output_sched(lchan->abis_ip.osmux.out, osmuxh);
+ }
+ msgb_free(msg);
+ return 0;
+}
+
+/* Called before config file read, set defaults */
+int bts_osmux_init(struct gsm_bts *bts)
+{
+ bts->osmux.use = OSMUX_USAGE_OFF;
+ bts->osmux.local_addr = talloc_strdup(bts, "127.0.0.1");
+ bts->osmux.local_port = OSMUX_DEFAULT_PORT;
+ bts->osmux.batch_factor = 4;
+ bts->osmux.batch_size = OSMUX_BATCH_DEFAULT_MAX;
+ bts->osmux.dummy_padding = false;
+ INIT_LLIST_HEAD(&bts->osmux.osmux_handle_list);
+ bts->osmux.fd.fd = -1;
+ return 0;
+}
+
+void bts_osmux_release(struct gsm_bts *bts)
+{
+ /* bts->osmux.osmux_handle_list should end up empty when all lchans are
+ * released/freed upon talloc_free(bts). */
+ /* If bts->osmux.fd.data is NULL, bts is being released/freed without
+ * passing bts_osmux_init()/through bts_osmux_open() and hence fd is
+ * probably 0 (memzeored). Avoid accessing it if not initialized. */
+ if (bts->osmux.fd.fd != -1 && bts->osmux.fd.data)
+ osmo_fd_close(&bts->osmux.fd);
+}
+
+/* Called after config file read, start services */
+int bts_osmux_open(struct gsm_bts *bts)
+{
+ int rc;
+
+ /* If Osmux is not enabled by VTY, don't initialize stuff */
+ if (bts->osmux.use == OSMUX_USAGE_OFF)
+ return 0;
+
+ bts->osmux.fd.cb = osmux_read_fd_cb;
+ bts->osmux.fd.data = bts;
+ rc = osmo_sock_init2_ofd(&bts->osmux.fd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
+ bts->osmux.local_addr, bts->osmux.local_port,
+ NULL, 0, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ LOGP(DOSMUX, LOGL_ERROR,
+ "Failed binding Osmux socket to %s:%u\n",
+ bts->osmux.local_addr ? : "*", bts->osmux.local_port);
+ return rc;
+ }
+
+ LOGP(DOSMUX, LOGL_INFO,
+ "Osmux socket listening on %s:%u\n",
+ bts->osmux.local_addr ? : "*", bts->osmux.local_port);
+
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OSMUX);
+ return rc;
+}
+
+static struct msgb *osmux_rtp_msgb_alloc_cb(void *rtp_msgb_alloc_priv_data,
+ unsigned int msg_len)
+{
+ struct msgb *msg;
+ msg = l1sap_msgb_alloc(msg_len);
+ /* We have size for "struct osmo_phsap_prim" reserved & aligned at the
+ * start of the msg. Osmux will start filling RTP Header at the tail.
+ * Later on, when pushing it down the stack (scheduled_from_osmux_tx_rtp_cb)
+ * we'll want to get rid of the RTP header and have RTP payload
+ * immediately follow the the struct osmo_phsap_prim. Hence, we rework
+ * reserved space so that end of RTP header (12 bytes) filled by Osmux
+ * ends up at the same position where "struct osmo_phsap_prim" currently
+ * ends up */
+ msg->l2h = msgb_get(msg, sizeof(struct rtp_hdr));
+ return msg;
+}
+
+static void scheduled_from_osmux_tx_rtp_cb(struct msgb *msg, void *data)
+{
+ struct gsm_lchan *lchan = data;
+ struct rtp_hdr *rtph;
+
+ /* if we're in loopback mode, we don't accept frames from the
+ * RTP socket anymore */
+ if (lchan->loopback) {
+ msgb_free(msg);
+ return;
+ }
+
+ /* This is where start of rtp_hdr was prepared in osmux_rtp_msgb_alloc_cb() */
+ rtph = (struct rtp_hdr *)msg->l2h;
+ if (msgb_l2len(msg) < sizeof(*rtph)) {
+ LOGPLCHAN(lchan, DOSMUX, LOGL_ERROR, "received RTP frame too short (len = %d)\n",
+ msgb_l2len(msg));
+ msgb_free(msg);
+ return;
+ }
+
+ /* Store RTP header Marker bit in control buffer */
+ rtpmsg_marker_bit(msg) = rtph->marker;
+ /* Store RTP header Sequence Number in control buffer */
+ rtpmsg_seq(msg) = ntohs(rtph->sequence);
+ /* Store RTP header Timestamp in control buffer */
+ rtpmsg_ts(msg) = ntohl(rtph->timestamp);
+
+ /* No need to pull() rtph out of msg here, because it was written inside
+ * initial space reserved for "struct osmo_phsap_prim". We need to pull
+ * the whole "struct osmo_phsap_prim" since it will be pushed and filled
+ * by lower layers:
+ */
+ msgb_pull(msg, sizeof(struct osmo_phsap_prim));
+
+ /* enqueue making sure the queue doesn't get too long */
+ lchan_dl_tch_queue_enqueue(lchan, msg, 16);
+}
+
+int lchan_osmux_init(struct gsm_lchan *lchan, uint8_t rtp_payload)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ int local_cid = osmux_get_local_cid();
+ struct in_addr ia;
+
+ if (local_cid < 0)
+ return local_cid;
+
+ if (inet_pton(AF_INET, trx->bts->osmux.local_addr, &ia) != 1)
+ return -1;
+
+ lchan->abis_ip.osmux.out = osmux_xfrm_output_alloc(trx);
+ osmux_xfrm_output_set_rtp_ssrc(lchan->abis_ip.osmux.out, random() /*TODO: SSRC */);
+ osmux_xfrm_output_set_rtp_pl_type(lchan->abis_ip.osmux.out, rtp_payload);
+ osmux_xfrm_output_set_tx_cb(lchan->abis_ip.osmux.out, scheduled_from_osmux_tx_rtp_cb, lchan);
+ osmux_xfrm_output_set_rtp_msgb_alloc_cb(lchan->abis_ip.osmux.out, osmux_rtp_msgb_alloc_cb, lchan);
+
+ lchan->abis_ip.bound_ip = ntohl(ia.s_addr);
+ lchan->abis_ip.bound_port = trx->bts->osmux.local_port;
+ lchan->abis_ip.osmux.local_cid = local_cid;
+ lchan->abis_ip.osmux.rtpst = osmo_rtp_handle_create(trx);
+ lchan->abis_ip.osmux.use = true;
+ return 0;
+}
+
+void lchan_osmux_release(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ OSMO_ASSERT(lchan->abis_ip.osmux.use);
+ /* We are closing, we don't need pending RTP packets to be transmitted */
+ osmux_xfrm_output_set_tx_cb(lchan->abis_ip.osmux.out, NULL, NULL);
+ TALLOC_FREE(lchan->abis_ip.osmux.out);
+
+ msgb_queue_free(&lchan->dl_tch_queue);
+ lchan->dl_tch_queue_len = 0;
+
+ osmux_put_local_cid(lchan->abis_ip.osmux.local_cid);
+
+ /* Now the remote / tx part, if ever set (connected): */
+ if (lchan->abis_ip.osmux.in) {
+ osmux_xfrm_input_close_circuit(lchan->abis_ip.osmux.in,
+ lchan->abis_ip.osmux.remote_cid);
+ osmux_handle_put(bts, lchan->abis_ip.osmux.in);
+ lchan->abis_ip.osmux.in = NULL;
+ }
+ if (lchan->abis_ip.osmux.rtpst) {
+ osmo_rtp_handle_free(lchan->abis_ip.osmux.rtpst);
+ lchan->abis_ip.osmux.rtpst = NULL;
+ }
+
+ lchan->abis_ip.osmux.use = false;
+}
+
+bool lchan_osmux_connected(const struct gsm_lchan *lchan)
+{
+ return lchan->abis_ip.osmux.in != NULL;
+}
+
+int lchan_osmux_connect(struct gsm_lchan *lchan)
+{
+ struct osmo_sockaddr rem_addr;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ OSMO_ASSERT(lchan->abis_ip.connect_ip != 0);
+ OSMO_ASSERT(lchan->abis_ip.connect_port != 0);
+
+ memset(&rem_addr, 0, sizeof(rem_addr));
+ rem_addr.u.sa.sa_family = AF_INET;
+ rem_addr.u.sin.sin_addr.s_addr = lchan->abis_ip.connect_ip;
+ rem_addr.u.sin.sin_port = htons(lchan->abis_ip.connect_port);
+ lchan->abis_ip.osmux.in = osmux_handle_find_or_create(bts, &rem_addr);
+ if (!lchan->abis_ip.osmux.in) {
+ LOGPLCHAN(lchan, DOSMUX, LOGL_ERROR, "Cannot allocate input osmux handle\n");
+ return -1;
+ }
+ if (osmux_xfrm_input_open_circuit(lchan->abis_ip.osmux.in,
+ lchan->abis_ip.osmux.remote_cid,
+ bts->osmux.dummy_padding) < 0) {
+ LOGPLCHAN(lchan, DOSMUX, LOGL_ERROR, "Cannot open osmux circuit %u\n",
+ lchan->abis_ip.osmux.remote_cid);
+ osmux_handle_put(bts, lchan->abis_ip.osmux.in);
+ lchan->abis_ip.osmux.in = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+/* Create RTP packet from l1sap payload and feed it to osmux */
+int lchan_osmux_send_frame(struct gsm_lchan *lchan, const uint8_t *payload,
+ unsigned int payload_len, unsigned int duration, bool marker)
+{
+ struct msgb *msg;
+ struct rtp_hdr *rtph;
+ int rc;
+
+ msg = osmo_rtp_build(lchan->abis_ip.osmux.rtpst, lchan->abis_ip.rtp_payload,
+ payload_len, payload, duration);
+ if (!msg)
+ return -1;
+
+ /* Set marker bit: */
+ rtph = (struct rtp_hdr *)msgb_data(msg);
+ rtph->marker = marker;
+
+ /* Avoid using the osmux.in if not yet connected. */
+ if (!lchan_osmux_connected(lchan)) {
+ msgb_free(msg);
+ return -1;
+ }
+
+ while ((rc = osmux_xfrm_input(lchan->abis_ip.osmux.in, msg,
+ lchan->abis_ip.osmux.remote_cid)) > 0) {
+ /* batch full, build and deliver it */
+ osmux_xfrm_input_deliver(lchan->abis_ip.osmux.in);
+ }
+ return 0;
+}
+
+int lchan_osmux_skipped_frame(struct gsm_lchan *lchan, unsigned int duration)
+{
+ struct msgb *msg;
+
+ /* Let osmo_rtp_handle take care of updating state, and send nothing: */
+ msg = osmo_rtp_build(lchan->abis_ip.osmux.rtpst, lchan->abis_ip.rtp_payload,
+ 0, NULL, duration);
+ if (!msg)
+ return -1;
+ msgb_free(msg);
+ return 0;
+}
diff --git a/src/common/paging.c b/src/common/paging.c
index fca58b5f..bdd51801 100644
--- a/src/common/paging.c
+++ b/src/common/paging.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -43,13 +43,14 @@
#include <osmo-bts/paging.h>
#include <osmo-bts/signal.h>
#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/notification.h>
#define MAX_PAGING_BLOCKS_CCCH 9
#define MAX_BS_PA_MFRMS 9
enum paging_record_type {
- PAGING_RECORD_PAGING,
- PAGING_RECORD_IMM_ASS
+ PAGING_RECORD_NORMAL,
+ PAGING_RECORD_MACBLOCK
};
struct paging_record {
@@ -60,10 +61,12 @@ struct paging_record {
time_t expiration_time;
uint8_t chan_needed;
uint8_t identity_lv[9];
- } paging;
+ } normal;
struct {
uint8_t msg[GSM_MACBLOCK_LEN];
- } imm_ass;
+ bool confirm;
+ uint32_t msg_id; /* used as identifier for confirmation */
+ } macblock;
} u;
};
@@ -80,8 +83,37 @@ struct paging_state {
/* 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];
+
+ /* prioritization of cs pagings will automatically become
+ * active on congestions (queue almost full) */
+ bool cs_priority_active;
};
+/* The prioritization of cs pagings is controlled by a hysteresis. When the
+ * fill state of the paging queue exceeds the upper fill level
+ * THRESHOLD_CONGESTED [%], then PS pagings (immediate assignments and pagings
+ * from the PCU) will be dropped until fill state of the paging queue drops
+ * under the lower fill level THRESHOLD_CLEAR [%]. */
+#define THRESHOLD_CONGESTED 66 /* (percent of num_paging_max) */
+#define THRESHOLD_CLEAR 50 /* (percent of num_paging_max) */
+
+/* Check the queue fill status and decide if prioritization of CS pagings
+ * must be turned on to flatten the negative effects of the congestion
+ * situation on the CS domain. */
+static void check_congestion(struct paging_state *ps)
+{
+ int pag_queue_len = paging_queue_length(ps);
+ int pag_queue_max = paging_get_queue_max(ps);
+ unsigned int treshold_upper = pag_queue_max * THRESHOLD_CONGESTED / 100;
+ unsigned int treshold_lower = pag_queue_max * THRESHOLD_CLEAR / 100;
+
+ if (pag_queue_len > treshold_upper && ps->cs_priority_active == false) {
+ ps->cs_priority_active = true;
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_CONG);
+ } else if (pag_queue_len < treshold_lower)
+ ps->cs_priority_active = false;
+}
+
unsigned int paging_get_lifetime(struct paging_state *ps)
{
return ps->paging_lifetime;
@@ -181,6 +213,8 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
int blocks = gsm48_number_of_paging_subchannels(&ps->chan_desc);
struct paging_record *pr;
+ check_congestion(ps);
+
rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_RCVD);
if (paging_group >= blocks) {
@@ -199,13 +233,13 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
/* Check if we already have this identity */
llist_for_each_entry(pr, group_q, list) {
- if (pr->type != PAGING_RECORD_PAGING)
+ if (pr->type != PAGING_RECORD_NORMAL)
continue;
- if (identity_lv[0] == pr->u.paging.identity_lv[0] &&
- !memcmp(identity_lv+1, pr->u.paging.identity_lv+1,
+ if (identity_lv[0] == pr->u.normal.identity_lv[0] &&
+ !memcmp(identity_lv+1, pr->u.normal.identity_lv+1,
identity_lv[0])) {
LOGP(DPAG, LOGL_INFO, "Ignoring duplicate paging\n");
- pr->u.paging.expiration_time =
+ pr->u.normal.expiration_time =
time(NULL) + ps->paging_lifetime;
return -EEXIST;
}
@@ -214,9 +248,9 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
pr = talloc_zero(ps, struct paging_record);
if (!pr)
return -ENOMEM;
- pr->type = PAGING_RECORD_PAGING;
+ pr->type = PAGING_RECORD_NORMAL;
- if (*identity_lv + 1 > sizeof(pr->u.paging.identity_lv)) {
+ if (*identity_lv + 1 > sizeof(pr->u.normal.identity_lv)) {
talloc_free(pr);
return -E2BIG;
}
@@ -224,9 +258,9 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
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);
+ pr->u.normal.expiration_time = time(NULL) + ps->paging_lifetime;
+ pr->u.normal.chan_needed = chan_needed;
+ memcpy(&pr->u.normal.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. */
@@ -236,35 +270,59 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
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)
+/* Convert the last three digits of a given IMSI string to their decimal representation. In case the given IMSI string
+ * is shorter than three or zero digits, it will be assumed as "000" */
+static uint16_t convert_imsi_to_decimal(const char *imsi)
+{
+ uint16_t _imsi;
+ size_t imsi_len = strlen(imsi);
+
+ /* Tha paging group is calculated from the last three digits of the IMSI */
+ if (imsi_len < 3) {
+ LOGP(DPAG, LOGL_ERROR, "short IMSI (%zu digits), will assume \"000\" to calculate paging group\n", imsi_len);
+ _imsi = 0;
+ } else {
+ imsi = imsi + imsi_len - 3;
+ _imsi = 100 * ((*(imsi++)) - '0');
+ _imsi += 10 * ((*(imsi++)) - '0');
+ _imsi += (*(imsi++)) - '0';
+ }
+
+ return _imsi;
+}
+
+/* Add a ready formatted MAC block message to the paging queue, this can be an IMMEDIATE ASSIGNMENT, or a
+ * PAGING COMMAND (from the PCU) */
+int paging_add_macblock(struct paging_state *ps, uint32_t msg_id, const char *imsi, bool confirm, const uint8_t *macblock)
{
struct llist_head *group_q;
struct paging_record *pr;
- uint16_t imsi, paging_group;
+ uint16_t paging_group;
+ uint16_t _imsi;
- if (len != GSM_MACBLOCK_LEN + 3) {
- LOGP(DPAG, LOGL_ERROR, "IMM.ASS invalid length %d\n", len);
- return -EINVAL;
- }
- len -= 3;
+ check_congestion(ps);
- imsi = 100 * ((*(data++)) - '0');
- imsi += 10 * ((*(data++)) - '0');
- imsi += (*(data++)) - '0';
- paging_group = gsm0502_calc_paging_group(&ps->chan_desc, imsi);
+ if (ps->cs_priority_active) {
+ LOGP(DPAG, LOGL_NOTICE, "Dropping paging for PS, queue congested (%u)\n",
+ ps->num_paging);
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP_PS);
+ return -ENOSPC;
+ }
+ _imsi = convert_imsi_to_decimal(imsi);
+ 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;
+ pr->type = PAGING_RECORD_MACBLOCK;
- LOGP(DPAG, LOGL_INFO, "Add IMM.ASS to queue (group=%u)\n",
+ LOGP(DPAG, LOGL_INFO, "Add MAC block to paging queue (group=%u)\n",
paging_group);
- memcpy(pr->u.imm_ass.msg, data, GSM_MACBLOCK_LEN);
+ memcpy(pr->u.macblock.msg, macblock, GSM_MACBLOCK_LEN);
+ pr->u.macblock.confirm = confirm;
+ pr->u.macblock.msg_id = msg_id;
/* enqueue the new message to the HEAD of the queue */
llist_add(&pr->list, group_q);
@@ -274,22 +332,6 @@ int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data,
#define L2_PLEN(len) (((len - 1) << 2) | 0x01)
-/* abstract representation of P1 rest octets; we only implement those parts we need for now */
-struct p1_rest_octets {
- bool packet_page_ind[2];
- bool r8_present;
- struct {
- bool prio_ul_access;
- bool etws_present;
- struct {
- bool is_first;
- uint8_t page_nr;
- const uint8_t *page;
- size_t page_bytes;
- } etws;
- } r8;
-};
-
/* 3GPP TS 44.018 10.5.2.23 append a segment/page of an ETWS primary notification to given bitvec */
static void append_etws_prim_notif(struct bitvec *bv, bool is_first, uint8_t page_nr,
const uint8_t *etws, ssize_t etws_len)
@@ -316,13 +358,27 @@ static void append_etws_prim_notif(struct bitvec *bv, bool is_first, uint8_t pag
}
/* 3GPP TS 44.018 10.5.2.23 append P1 Rest Octets to given bit-vector */
-static void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets *p1ro)
+void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets *p1ro,
+ const struct asci_notification *notif)
{
/* Paging 1 RO (at least 10 bits before ETWS struct) */
- bitvec_set_bit(bv, L); /* no NLN */
+ if (p1ro->nln_pch.present) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, p1ro->nln_pch.nln, 2);
+ bitvec_set_uint(bv, p1ro->nln_pch.nln_status, 1);
+ } else {
+ bitvec_set_bit(bv, L); /* no NLN */
+ }
bitvec_set_bit(bv, L); /* no Priority1 */
bitvec_set_bit(bv, L); /* no Priority2 */
- bitvec_set_bit(bv, L); /* no Group Call Info */
+ if (notif) {
+ bitvec_set_bit(bv, H); /* Group Call Info */
+ append_group_call_information(bv, notif->group_call_ref,
+ notif->chan_desc.present ? notif->chan_desc.value : NULL,
+ notif->chan_desc.len);
+ } else {
+ bitvec_set_bit(bv, L); /* no Group Call Info */
+ }
if (p1ro->packet_page_ind[0])
bitvec_set_bit(bv, H); /* Packet Page Indication 1 */
else
@@ -347,49 +403,97 @@ static void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets
}
}
+/* 3GPP TS 44.018 10.5.2.24 append P2 Rest Octets to given bit-vector */
+void append_p2_rest_octets(struct bitvec *bv, const struct p2_rest_octets *p2ro)
+{
+ /* {L | H <CN3: bit (2)>} */
+ if (p2ro->cneed.present) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, p2ro->cneed.cn3, 2);
+ } else
+ bitvec_set_bit(bv, L); /* no CN3 */
+
+ /* {L | H < NLN(PCH) : bit (2) <NLN status(PCH) : bit>} */
+ if (p2ro->nln_pch.present) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, p2ro->nln_pch.nln, 2);
+ bitvec_set_uint(bv, p2ro->nln_pch.nln_status, 1);
+ } else
+ bitvec_set_bit(bv, L); /* no NLN */
+
+ /* Note: If this needs to be extended in the future, check if it actually fits into rest of P2! */
+}
+
+/* 3GPP TS 44.018 10.5.2.25 append P3 Rest Octets to given bit-vector */
+void append_p3_rest_octets(struct bitvec *bv, const struct p3_rest_octets *p3ro)
+{
+ /* {L | H <CN3: bit (2)> <CN3: bit (2)>} */
+ if (p3ro->cneed.present) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, p3ro->cneed.cn3, 2);
+ bitvec_set_uint(bv, p3ro->cneed.cn4, 2);
+ } else
+ bitvec_set_bit(bv, L); /* no CN3/CN4 */
+
+ /* {L | H < NLN(PCH) : bit (2) <NLN status(PCH) : bit>} */
+ if (p3ro->nln_pch.present) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, p3ro->nln_pch.nln, 2);
+ bitvec_set_uint(bv, p3ro->nln_pch.nln_status, 1);
+ } else
+ bitvec_set_bit(bv, L); /* no NLN */
+
+ /* Note: If this needs to be extended in the future, check if it actually fits into 3 octets! */
+}
+
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, const struct p1_rest_octets *p1ro)
+ uint8_t chan2, const struct p1_rest_octets *p1ro,
+ const struct asci_notification *notif)
{
struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf;
- struct bitvec bv;
- unsigned int paging_len;
+ unsigned int ro_len;
uint8_t *cur;
- memset(out_buf, 0, sizeof(*pt1));
+ *pt1 = (struct gsm48_paging1) {
+ .proto_discr = GSM48_PDISC_RR,
+ .msg_type = GSM48_MT_RR_PAG_REQ_1,
+ .pag_mode = GSM48_PM_NORMAL,
+ .cneed1 = chan1 & 3,
+ .cneed2 = chan2 & 3,
+ };
- 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);
- paging_len = cur - out_buf;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = cur;
- bv.data_len = GSM_MACBLOCK_LEN - paging_len;
+ /* Pad remaining octets with constant '2B'O */
+ ro_len = GSM_MACBLOCK_LEN - (cur - out_buf);
+ memset(cur, GSM_MACBLOCK_PADDING, ro_len);
- if (p1ro)
- append_p1_rest_octets(&bv, p1ro);
+ /* Optional P1 Rest Octets */
+ if (p1ro) {
+ struct bitvec bv = {
+ .data_len = ro_len,
+ .data = cur,
+ };
- /* pad to the end of the MAC block */
- bitvec_spare_padding(&bv, bv.data_len *8);
+ append_p1_rest_octets(&bv, p1ro, notif);
+ }
return GSM_MACBLOCK_LEN;
}
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)
+ uint8_t cneed2, const uint8_t *identity3_lv,
+ const struct p2_rest_octets *p2ro)
{
struct gsm48_paging2 *pt2 = (struct gsm48_paging2 *) out_buf;
uint32_t tmsi;
+ unsigned int ro_len;
uint8_t *cur;
int rc;
@@ -413,16 +517,32 @@ static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv,
pt2->l2_plen = L2_PLEN(cur - out_buf);
- return cur - out_buf;
+ /* Pad remaining octets with constant '2B'O */
+ ro_len = GSM_MACBLOCK_LEN - (cur - out_buf);
+ memset(cur, GSM_MACBLOCK_PADDING, ro_len);
+
+ /* Optional P2 Rest Octets */
+ if (p2ro) {
+ struct bitvec bv = {
+ .data_len = ro_len,
+ .data = cur,
+ };
+
+ append_p2_rest_octets(&bv, p2ro);
+ }
+
+ return GSM_MACBLOCK_LEN;
}
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)
+ const uint8_t *tmsi3_lv, const uint8_t *tmsi4_lv,
+ const struct p3_rest_octets *p3ro)
{
struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf;
uint32_t tmsi;
+ unsigned int ro_len;
+ uint8_t *cur;
int rc;
memset(out_buf, 0, sizeof(*pt3));
@@ -444,13 +564,25 @@ static int fill_paging_type_3(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t
rc = tmsi_mi_to_uint(&tmsi, tmsi4_lv);
if (rc == 0)
pt3->tmsi4 = tmsi;
+ cur = out_buf + 20; /* Cannot use sizeof(*pt3), because it has more octets. */
- /* 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;
+ pt3->l2_plen = L2_PLEN(cur - out_buf);
- return 21;
+ /* Pad remaining octets with constant '2B'O */
+ ro_len = GSM_MACBLOCK_LEN - (cur - out_buf);
+ memset(cur, GSM_MACBLOCK_PADDING, ro_len);
+
+ /* Optional P3 Rest Octets */
+ if (p3ro) {
+ struct bitvec bv = {
+ .data_len = ro_len,
+ .data = cur,
+ };
+
+ append_p3_rest_octets(&bv, p3ro);
+ }
+
+ return GSM_MACBLOCK_LEN;
}
static const uint8_t empty_id_lv[] = { 0x01, 0xF0 };
@@ -467,7 +599,7 @@ static struct paging_record *dequeue_pr(struct llist_head *group_q)
static int pr_is_imsi(struct paging_record *pr)
{
- if ((pr->u.paging.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI)
+ if ((pr->u.normal.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI)
return 1;
else
return 0;
@@ -496,6 +628,7 @@ static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n)
static void build_p1_rest_octets(struct p1_rest_octets *p1ro, struct gsm_bts *bts)
{
memset(p1ro, 0, sizeof(*p1ro));
+ p1ro->nln_pch.present = false;
p1ro->packet_page_ind[0] = false;
p1ro->packet_page_ind[1] = false;
p1ro->r8_present = true;
@@ -528,6 +661,10 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
int group;
int len;
+ /* This will have no effect on behavior of this function, we just need
+ * need to check the congestion status of the queue from time to time. */
+ check_congestion(ps);
+
*is_empty = 0;
bts->load.ccch.pch_total += 1;
@@ -544,16 +681,24 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
if (ps->bts->etws.prim_notif) {
struct p1_rest_octets p1ro;
build_p1_rest_octets(&p1ro, bts);
- len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro);
+ /* we intentioanally don't try to add notifications here, as ETWS is more critical */
+ len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro, NULL);
} else if (llist_empty(group_q)) {
+ struct p1_rest_octets p1ro;
+ memset(&p1ro, 0, sizeof(p1ro));
+ /* Use NLN to notify MS about ongoing VGCS/VBS calls.
+ * This is required to make the phone read the NCH to get an updated list of ongoing calls.
+ * Without this the phone will not allow making VGCS/VBS calls. */
+ p1ro.nln_pch.present = (bts->asci.pos_nch >= 0);
+ p1ro.nln_pch.nln = bts->asci.nln;
+ p1ro.nln_pch.nln_status = bts->asci.nln_status;
/* There is nobody to be paged, send Type1 with two empty ID */
//DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n");
- len = fill_paging_type_1(out_buf, empty_id_lv, 0,
- NULL, 0, NULL);
+ len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro, NULL);
*is_empty = 1;
} else {
struct paging_record *pr[4];
- unsigned int num_pr = 0, imm_ass = 0;
+ unsigned int num_pr = 0, macblock = 0;
time_t now = time(NULL);
unsigned int i, num_imsi = 0;
@@ -565,9 +710,9 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
break;
pr[i] = dequeue_pr(group_q);
- /* check for IMM.ASS */
- if (pr[i]->type == PAGING_RECORD_IMM_ASS) {
- imm_ass = 1;
+ /* check for MAC block */
+ if (pr[i]->type == PAGING_RECORD_MACBLOCK) {
+ macblock = 1;
break;
}
@@ -578,17 +723,18 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
num_imsi++;
}
- /* if we have an IMMEDIATE ASSIGNMENT */
- if (imm_ass) {
- /* re-add paging records */
+ /* Handle MAC block (from the PCU) */
+ if (macblock) {
+ /* re-add normal 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,
+ /* get MAC block message and free record */
+ memcpy(out_buf, pr[num_pr]->u.macblock.msg,
GSM_MACBLOCK_LEN);
+ /* send a confirmation back (if required) */
+ if (pr[num_pr]->u.macblock.confirm)
+ pcu_tx_data_cnf(pr[num_pr]->u.macblock.msg_id, PCU_IF_SAPI_PCH_2);
talloc_free(pr[num_pr]);
return GSM_MACBLOCK_LEN;
}
@@ -599,24 +745,39 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
if (num_pr == 4 && num_imsi == 0) {
/* No IMSI: easy case, can use TYPE 3 */
DEBUGP(DPAG, "Tx PAGING TYPE 3 (4 TMSI)\n");
+ struct p3_rest_octets p3ro;
+ memset(&p3ro, 0, sizeof(p3ro));
+ p3ro.cneed.present = true;
+ p3ro.cneed.cn3 = pr[2]->u.normal.chan_needed;
+ p3ro.cneed.cn4 = pr[3]->u.normal.chan_needed;
+ p3ro.nln_pch.present = (bts->asci.pos_nch >= 0);
+ p3ro.nln_pch.nln = bts->asci.nln;
+ p3ro.nln_pch.nln_status = bts->asci.nln_status;
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);
+ pr[0]->u.normal.identity_lv,
+ pr[0]->u.normal.chan_needed,
+ pr[1]->u.normal.identity_lv,
+ pr[1]->u.normal.chan_needed,
+ pr[2]->u.normal.identity_lv,
+ pr[3]->u.normal.identity_lv,
+ &p3ro);
} 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");
+ struct p2_rest_octets p2ro;
+ memset(&p2ro, 0, sizeof(p2ro));
+ p2ro.cneed.present = true;
+ p2ro.cneed.cn3 = pr[2]->u.normal.chan_needed;
+ p2ro.nln_pch.present = (bts->asci.pos_nch >= 0);
+ p2ro.nln_pch.nln = bts->asci.nln;
+ p2ro.nln_pch.nln_status = bts->asci.nln_status;
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);
+ pr[0]->u.normal.identity_lv,
+ pr[0]->u.normal.chan_needed,
+ pr[1]->u.normal.identity_lv,
+ pr[1]->u.normal.chan_needed,
+ pr[2]->u.normal.identity_lv,
+ &p2ro);
if (num_pr == 4) {
/* re-add #4 for next time */
llist_add(&pr[3]->list, group_q);
@@ -624,19 +785,21 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
}
} else if (num_pr == 1) {
DEBUGP(DPAG, "Tx PAGING TYPE 1 (1 xMSI,1 empty)\n");
+ /* TODO: check if we can include an ASCI notification */
len = fill_paging_type_1(out_buf,
- pr[0]->u.paging.identity_lv,
- pr[0]->u.paging.chan_needed,
- NULL, 0, NULL);
+ pr[0]->u.normal.identity_lv,
+ pr[0]->u.normal.chan_needed,
+ NULL, 0, NULL, NULL);
} else {
/* 2 (any type) or
* 3 or 4, of which only 2 will be sent */
DEBUGP(DPAG, "Tx PAGING TYPE 1 (2 xMSI)\n");
+ /* TODO: check if we can include an ASCI notification */
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, NULL);
+ pr[0]->u.normal.identity_lv,
+ pr[0]->u.normal.chan_needed,
+ pr[1]->u.normal.identity_lv,
+ pr[1]->u.normal.chan_needed, NULL, NULL);
if (num_pr >= 3) {
/* re-add #4 for next time */
llist_add(&pr[2]->list, group_q);
@@ -656,7 +819,7 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g
rate_ctr_inc2(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) {
+ if (pr[i]->u.normal.expiration_time <= now) {
talloc_free(pr[i]);
ps->num_paging--;
LOGP(DPAG, LOGL_INFO, "Removed paging record, queue_len=%u\n",
@@ -709,6 +872,7 @@ struct paging_state *paging_init(struct gsm_bts *bts,
ps->bts = bts;
ps->paging_lifetime = paging_lifetime;
ps->num_paging_max = num_paging_max;
+ ps->cs_priority_active = false;
for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++)
INIT_LLIST_HEAD(&ps->paging_queue[i]);
diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c
index ba9e1721..048e7668 100644
--- a/src/common/pcu_sock.c
+++ b/src/common/pcu_sock.c
@@ -15,10 +15,6 @@
* 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>
@@ -32,37 +28,39 @@
#include <inttypes.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/write_queue.h>
#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/gsm/abis_nm.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/bts_sm.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/signal.h>
#include <osmo-bts/l1sap.h>
#include <osmo-bts/oml.h>
+#include <osmo-bts/abis_osmo.h>
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
+uint32_t trx_get_hlayer1(const 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",
+ [PCU_IF_SAPI_PCH_2] = "PCH_2",
+ [PCU_IF_SAPI_AGCH_2] = "AGCH_2",
};
-static int pcu_sock_send(struct gsm_network *net, struct msgb *msg);
-
/*
* PCU messages
*/
@@ -83,10 +81,12 @@ struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr)
return msg;
}
-static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
- if (ts->pchan == GSM_PCHAN_PDCH)
+static bool ts_should_be_pdch(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_PDCH:
return true;
- if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+ case 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.
@@ -97,35 +97,174 @@ static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
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) {
+ case GSM_PCHAN_OSMO_DYN:
/*
* 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.
+ * response. To make it available to PCU, we want to make sure
+ * it's already configured by phy (pchan_is==PDCH) and that we
+ * are not in progress of removing it (pchan_want=None).
*/
- return ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+
+ return ts->dyn.pchan_is == GSM_PCHAN_PDCH && ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+ default:
+ return false;
+ }
+}
+
+/* As a BTS, we do not (and neither need to) know the Mobile Allocation, because
+ * in CS domain it's responsibility of the BSC to encode RR messages containing
+ * this IE. However, a BTS co-located PCU needs to know all hopping parameters,
+ * including the Mobile Allocation, because it's responsible for encoding of the
+ * packet resource assignment messages.
+ *
+ * This function, similar to generate_ma_for_ts() in osmo-bsc, computes the
+ * Mobile Allocation bit-mask and populates the given part of INFO.ind with
+ * the hopping parameters for the given timeslot. */
+static void info_ind_fill_fhp(struct gsm_pcu_if_info_trx_ts *ts_info,
+ const struct gsm_bts_trx_ts *ts)
+{
+ const struct gsm_bts *bts = ts->trx->bts;
+ const struct gsm_bts_trx *trx;
+ uint8_t ca_buf[1024 / 8] = { 0 };
+ uint8_t sa_buf[1024 / 8] = { 0 };
+ struct bitvec ca, sa, ma;
+ unsigned int i;
+
+ ts_info->maio = ts->hopping.maio;
+ ts_info->hsn = ts->hopping.hsn;
+ ts_info->hopping = 0x01;
+
+ /* Cell Allocation bit-mask */
+ ca = (struct bitvec) {
+ .data_len = sizeof(ca_buf),
+ .data = &ca_buf[0],
+ };
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ /* Skip non-provisioned transceivers */
+ if (trx->mo.nm_attr == NULL) {
+ LOGPTRX(trx, DPCU, LOGL_NOTICE, "not (yet) provisioned\n");
+ continue;
+ }
+
+ bitvec_set_bit_pos(&ca, trx->arfcn, ONE);
+ ts_info->ma_bit_len++;
+ }
+
+ /* Slot Allocation bit-mask */
+ sa = (struct bitvec) {
+ .data_len = sizeof(sa_buf),
+ .data = &sa_buf[0],
+ };
+
+ for (i = 0; i < ts->hopping.arfcn_num; i++) {
+ bitvec_set_bit_pos(&sa, ts->hopping.arfcn_list[i], ONE);
+ if (bitvec_get_bit_pos(&ca, ts->hopping.arfcn_list[i]) != ONE) {
+ LOGP(DPCU, LOGL_NOTICE, "A transceiver with ARFCN %u "
+ "is not (yet) provisioned\n", ts->hopping.arfcn_list[i]);
+ bitvec_set_bit_pos(&ca, ts->hopping.arfcn_list[i], ONE);
+ ts_info->ma_bit_len++;
+ }
+ }
+
+ /* Mobile Allocation bit-mask */
+ ma = (struct bitvec) {
+ .cur_bit = sizeof(ts_info->ma) * 8 - 1,
+ .data_len = sizeof(ts_info->ma),
+ .data = &ts_info->ma[0],
+ };
+
+ /* Skip ARFCN 0, it goes to the end of MA bit-mask */
+ for (i = 1; i < sizeof(ca_buf) * 8; i++) {
+ if (bitvec_get_bit_pos(&ca, i) != ONE)
+ continue;
+ if (bitvec_get_bit_pos(&sa, i) == ONE)
+ bitvec_set_bit_pos(&ma, ma.cur_bit, ONE);
+ ma.cur_bit--;
+ }
+
+ if (bitvec_get_bit_pos(&sa, 0) == ONE)
+ bitvec_set_bit_pos(&ma, ma.cur_bit, ONE);
+}
+
+static void info_ind_fill_trx(struct gsm_pcu_if_info_trx *trx_info,
+ const struct gsm_bts_trx *trx)
+{
+ unsigned int tn;
+
+ trx_info->pdch_mask = 0;
+ trx_info->arfcn = trx->arfcn;
+ trx_info->hlayer1 = trx_get_hlayer1(trx);
+
+ if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
+ trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) {
+ LOGPTRX(trx, DPCU, LOGL_INFO, "unavailable for PCU (op=%s adm=%s)\n",
+ abis_nm_opstate_name(trx->mo.nm_state.operational),
+ abis_nm_admin_name(trx->mo.nm_state.administrative));
+ return;
+ }
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (!ts_should_be_pdch(ts))
+ continue;
+
+ trx_info->pdch_mask |= (1 << tn);
+ trx_info->ts[tn].tsc = ts->tsc;
+
+ if (ts->hopping.enabled)
+ info_ind_fill_fhp(&trx_info->ts[tn], ts);
+
+ LOGPTRX(trx, DPCU, LOGL_INFO, "PDCH on ts=%u is available "
+ "(tsc=%u ", ts->nr, trx_info->ts[tn].tsc);
+ if (ts->hopping.enabled) {
+ LOGPC(DPCU, LOGL_INFO, "hopping=yes hsn=%u maio=%u ma_bit_len=%u)\n",
+ ts->hopping.hsn, ts->hopping.maio, trx_info->ts[tn].ma_bit_len);
+ } else {
+ LOGPC(DPCU, LOGL_INFO, "hopping=no arfcn=%u)\n", trx->arfcn);
+ }
+ }
+}
+
+static enum gsm_pcuif_bts_model bts_model_from_variant(enum gsm_bts_type_variant variant)
+{
+ switch (variant) {
+ case BTS_OSMO_LITECELL15:
+ return PCU_IF_BTS_MODEL_LC15;
+ case BTS_OSMO_OC2G:
+ return PCU_IF_BTS_MODEL_OC2G;
+ case BTS_OSMO_OCTPHY:
+ return PCU_IF_BTS_MODEL_OCTPHY;
+ case BTS_OSMO_SYSMO:
+ return PCU_IF_BTS_MODEL_SYSMO;
+ case BTS_OSMO_TRX:
+ case BTS_OSMO_VIRTUAL:
+ return PCU_IF_BTS_MODEL_TRX;
+ default:
+ return PCU_IF_BTS_MODEL_UNSPEC;
}
- 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;
+ int i;
+ struct gsm_gprs_nse *nse;
LOGP(DPCU, LOGL_INFO, "Sending info\n");
+ nse = &g_bts_sm->gprs.nse;
/* FIXME: allow multiple BTS */
- bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+ bts = llist_entry(g_bts_sm->bts_list.next, struct gsm_bts, list);
rlcc = &bts->gprs.cell.rlc_cfg;
msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr);
@@ -142,18 +281,19 @@ int pcu_tx_info_ind(void)
LOGP(DPCU, LOGL_INFO, "BTS is down\n");
if (pcu_direct)
- info_ind->flags |= PCU_IF_FLAG_SYSMO;
+ info_ind->flags |= PCU_IF_FLAG_DIRECT_PHY;
+ info_ind->bsic = bts->bsic;
/* 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->mcc = g_bts_sm->plmn.mcc;
+ info_ind->mnc = g_bts_sm->plmn.mnc;
+ info_ind->mnc_3_digits = g_bts_sm->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);
+ info_ind->nsei = nse->nsei;
+ memcpy(info_ind->nse_timer, nse->timer, 7);
memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11);
/* cell attributes */
@@ -196,53 +336,56 @@ int pcu_tx_info_ind(void)
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"
+ /* FIXME: 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"
+ /* FIXME: 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];
+ for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) {
+ const struct gsm_gprs_nsvc *nsvc = &nse->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;
+ /* PCUIF beauty: the NSVC addresses are sent in the network byte order,
+ * while the port numbers need to be send in the host order. Sigh. */
+ info_ind->local_port[i] = ntohs(nsvc->local.u.sin.sin_port);
+ info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin.sin_port);
+ switch (nsvc->remote.u.sas.ss_family) {
+ case AF_INET:
+ info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV4;
+ info_ind->remote_ip[i].v4 = nsvc->remote.u.sin.sin_addr;
+ break;
+ case AF_INET6:
+ info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV6;
+ info_ind->remote_ip[i].v6 = nsvc->remote.u.sin6.sin6_addr;
+ break;
+ default:
+ info_ind->address_type[i] = PCU_IF_ADDR_TYPE_UNSPEC;
+ break;
+ }
}
- for (i = 0; i < 8; i++) {
- trx = gsm_bts_trx_num(bts, i);
- if (!trx)
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->nr >= ARRAY_SIZE(info_ind->trx)) {
+ LOGPTRX(trx, DPCU, LOGL_NOTICE, "PCU interface (version %u) "
+ "cannot handle more than %zu transceivers => skipped\n",
+ PCU_IF_VERSION, ARRAY_SIZE(info_ind->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);
- }
}
+
+ info_ind_fill_trx(&info_ind->trx[trx->nr], trx);
}
- return pcu_sock_send(net, msg);
+ info_ind->bts_model = bts_model_from_variant(bts->variant);
+
+ return pcu_sock_send(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_gprs_nsvc *nsvc;
struct gsm_bts *bts;
struct gsm48_system_information_type_3 *si3;
int id;
@@ -257,7 +400,7 @@ static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal,
break;
si3 = (struct gsm48_system_information_type_3 *)
bts->si_buf[SYSINFO_TYPE_3];
- osmo_plmn_from_bcd(si3->lai.digits, &net->plmn);
+ osmo_plmn_from_bcd(si3->lai.digits, &g_bts_sm->plmn);
bts->location_area_code = ntohs(si3->lai.lac);
bts->cell_identity = ntohs(si3->cell_identity);
avail_lai = 1;
@@ -283,6 +426,10 @@ static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal,
return -EINVAL;
}
+ /* Do not send INFO.ind if PCU is not connected */
+ if (!pcu_connected())
+ return 0;
+
/* 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])
@@ -309,7 +456,7 @@ int pcu_tx_app_info_req(struct gsm_bts *bts, uint8_t app_type, uint8_t len, cons
ai_req->len = len;
memcpy(ai_req->data, app_data, ai_req->len);
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(msg);
}
int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn,
@@ -336,7 +483,7 @@ int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn,
rts_req->ts_nr = ts->nr;
rts_req->block_nr = block_nr;
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(msg);
}
int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn,
@@ -351,12 +498,6 @@ int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn,
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 < bts->min_qual_norm) {
- LOGP(DPCU, LOGL_DEBUG, "Link quality %"PRId16" is below threshold %d, 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;
@@ -373,14 +514,16 @@ int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn,
data_ind->ber10k = ber10k;
data_ind->ta_offs_qbits = bto;
data_ind->lqual_cb = lqual;
- memcpy(data_ind->data, data, len);
+ if (len)
+ memcpy(data_ind->data, data, len);
data_ind->len = len;
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(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)
+int pcu_tx_rach_ind(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+ int16_t qta, uint16_t ra, uint32_t fn, uint8_t is_11bit,
+ enum ph_burst_type burst_type, uint8_t sapi)
{
struct msgb *msg;
struct gsm_pcu_if *pcu_prim;
@@ -389,20 +532,22 @@ int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
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);
+ 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->sapi = sapi;
rach_ind->ra = ra;
rach_ind->qta = qta;
rach_ind->fn = fn;
rach_ind->is_11bit = is_11bit;
rach_ind->burst_type = burst_type;
+ rach_ind->trx_nr = trx_nr;
+ rach_ind->ts_nr = ts_nr;
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(msg);
}
int pcu_tx_time_ind(uint32_t fn)
@@ -424,12 +569,45 @@ int pcu_tx_time_ind(uint32_t fn)
time_ind->fn = fn;
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(msg);
+}
+
+int pcu_tx_interf_ind(const struct gsm_bts_trx *trx, uint32_t fn)
+{
+ struct gsm_pcu_if_interf_ind *interf_ind;
+ struct gsm_pcu_if *pcu_prim;
+ struct msgb *msg;
+ unsigned int tn;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_INTERF_IND, trx->bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ interf_ind = &pcu_prim->u.interf_ind;
+
+ interf_ind->trx_nr = trx->nr;
+ interf_ind->fn = fn;
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ const struct gsm_lchan *lchan = &ts->lchan[0];
+
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (ts->mo.nm_state.availability != NM_AVSTATE_OK)
+ continue;
+ if (ts_pchan(ts) != GSM_PCHAN_PDCH)
+ continue;
+
+ interf_ind->interf[tn] = -1 * lchan->meas.interf_meas_avg_dbm;
+ }
+
+ return pcu_sock_send(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 pcu_sock_state *state = g_bts_sm->gprs.pcu_state;
struct msgb *msg;
struct gsm_pcu_if *pcu_prim;
struct gsm_pcu_if_pag_req *pag_req;
@@ -457,34 +635,31 @@ int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed)
pag_req->chan_needed = chan_needed;
memcpy(pag_req->identity_lv, identity_lv, identity_lv[0] + 1);
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(msg);
}
-int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len)
+int pcu_tx_data_cnf(uint32_t msg_id, uint8_t sapi)
{
- 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);
+ bts = llist_entry(g_bts_sm->bts_list.next, struct gsm_bts, list);
- LOGP(DPCU, LOGL_INFO, "Sending PCH confirm\n");
+ LOGP(DPCU, LOGL_DEBUG, "Sending DATA.cnf: sapi=%s msg_id=%08x\n",
+ sapi_string[sapi], msg_id);
- msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF, bts->nr);
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_2, bts->nr);
if (!msg)
return -ENOMEM;
pcu_prim = (struct gsm_pcu_if *) msg->data;
- data_cnf = &pcu_prim->u.data_cnf;
+ pcu_prim->u.data_cnf2 = (struct gsm_pcu_if_data_cnf) {
+ .sapi = sapi,
+ .msg_id = msg_id,
+ };
- 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);
+ return pcu_sock_send(msg);
}
/* forward data from a RR GPRS SUSPEND REQ towards PCU */
@@ -501,7 +676,7 @@ int pcu_tx_susp_req(struct gsm_lchan *lchan, uint32_t tlli, const uint8_t *ra_id
memcpy(pcu_prim->u.susp_req.ra_id, ra_id, sizeof(pcu_prim->u.susp_req.ra_id));
pcu_prim->u.susp_req.cause = cause;
- return pcu_sock_send(&bts_gsmnet, msg);
+ return pcu_sock_send(msg);
}
static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
@@ -519,22 +694,45 @@ static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
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);
+ case PCU_IF_SAPI_PCH_2:
+ {
+ const struct gsm_pcu_if_pch *gsm_pcu_if_pch;
+
+ if (OSMO_UNLIKELY(data_req->len != sizeof(*gsm_pcu_if_pch))) {
+ LOGP(DPCU, LOGL_ERROR, "Rx malformed DATA.req for PCH\n");
+ rc = -EINVAL;
+ break;
+ }
+
+ gsm_pcu_if_pch = (struct gsm_pcu_if_pch *)data_req->data;
+ rc = paging_add_macblock(bts->paging_state, gsm_pcu_if_pch->msg_id,
+ gsm_pcu_if_pch->imsi, gsm_pcu_if_pch->confirm, gsm_pcu_if_pch->data);
break;
- case PCU_IF_SAPI_AGCH:
- msg = msgb_alloc(data_req->len, "pcu_agch");
+ }
+ case PCU_IF_SAPI_AGCH_2:
+ {
+ const struct gsm_pcu_if_agch *gsm_pcu_if_agch;
+ struct bts_agch_msg_cb *msg_cb;
+
+ gsm_pcu_if_agch = (struct gsm_pcu_if_agch *)data_req->data;
+
+ msg = msgb_alloc(GSM_MACBLOCK_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);
+ msg->l3h = msgb_put(msg, GSM_MACBLOCK_LEN);
+ memcpy(msg->l3h, gsm_pcu_if_agch->data, GSM_MACBLOCK_LEN);
+
+ msg_cb = (struct bts_agch_msg_cb *) msg->cb;
+ msg_cb->confirm = gsm_pcu_if_agch->confirm;
+ msg_cb->msg_id = gsm_pcu_if_agch->msg_id;
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);
@@ -594,24 +792,74 @@ static int pcu_rx_pag_req(struct gsm_bts *bts, uint8_t msg_type,
return rc;
}
-int pcu_tx_si13(const struct gsm_bts *bts, bool enable)
+int pcu_tx_si(const struct gsm_bts *bts, enum osmo_sysinfo_type si_type,
+ 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);
+ uint8_t si_buf[GSM_MACBLOCK_LEN];
+ uint8_t len;
+ int rc;
+
+ if (enable) {
+ memcpy(si_buf, GSM_BTS_SI(bts, si_type), GSM_MACBLOCK_LEN);
+ len = GSM_MACBLOCK_LEN;
+ LOGP(DPCU, LOGL_DEBUG, "Updating SI%s to PCU: %s\n",
+ get_value_string(osmo_sitype_strs, si_type),
+ osmo_hexdump_nospc(si_buf, GSM_MACBLOCK_LEN));
+ } else {
+ si_buf[0] = si_type;
+ len = 1;
+
+ /* Note: SI13 is the only system information type that is revked
+ * by just sending a completely empty message. This is due to
+ * historical reasons */
+ if (si_type != SYSINFO_TYPE_13)
+ len = 0;
+
+ LOGP(DPCU, LOGL_DEBUG, "Revoking SI%s from PCU\n",
+ get_value_string(osmo_sitype_strs, si_buf[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 */
+ rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, si_buf, len,
+ 0, 0, 0, INT16_MAX);
if (rc < 0)
- LOGP(DPCU, LOGL_NOTICE, "Failed to send SI13 to PCU: %d\n", rc);
+ LOGP(DPCU, LOGL_NOTICE, "Failed to send SI%s to PCU: rc=%d\n",
+ get_value_string(osmo_sitype_strs, si_type), rc);
return rc;
}
+static int pcu_tx_si_all(struct gsm_bts *bts)
+{
+ const enum osmo_sysinfo_type si_types[] =
+ { SYSINFO_TYPE_1, SYSINFO_TYPE_2, SYSINFO_TYPE_3, SYSINFO_TYPE_13 };
+ unsigned int i;
+ int rc = 0;
+
+ for (i = 0; i < ARRAY_SIZE(si_types); i++) {
+ if (GSM_BTS_HAS_SI(bts, si_types[i])) {
+ rc = pcu_tx_si(bts, si_types[i], true);
+ if (rc < 0)
+ return rc;
+ } else {
+ LOGP(DPCU, LOGL_INFO,
+ "SI%s is not available on PCU connection\n",
+ get_value_string(osmo_sitype_strs, si_types[i]));
+ }
+ }
+
+ return 0;
+}
+
static int pcu_rx_txt_ind(struct gsm_bts *bts,
struct gsm_pcu_if_txt_ind *txt)
{
+ int rc;
+
switch (txt->type) {
case PCU_VERSION:
LOGP(DPCU, LOGL_INFO, "OsmoPCU version %s connected\n",
@@ -619,13 +867,14 @@ static int pcu_rx_txt_ind(struct gsm_bts *bts,
oml_tx_failure_event_rep(&bts->gprs.cell.mo, NM_SEVER_CEASED, OSMO_EVT_PCU_VERS, txt->text);
osmo_strlcpy(bts->pcu_version, txt->text, MAX_VERSION_LENGTH);
- /* patch SI3 to advertise GPRS, *if* the SI3 sent by BSC said so */
+ /* patch SI to advertise GPRS, *if* the SI sent by BSC said so */
regenerate_si3_restoctets(bts);
+ regenerate_si4_restoctets(bts);
- if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13))
- return pcu_tx_si13(bts, true);
+ rc = pcu_tx_si_all(bts);
+ if (rc < 0)
+ return -EINVAL;
- LOGP(DPCU, LOGL_INFO, "SI13 is not available on PCU connection\n");
break;
case PCU_OML_ALERT:
oml_tx_failure_event_rep(&bts->gprs.cell.mo, NM_SEVER_INDETERMINATE, OSMO_EVT_EXT_ALARM,
@@ -646,7 +895,7 @@ static int pcu_rx_act_req(struct gsm_bts *bts,
struct gsm_bts_trx *trx;
struct gsm_lchan *lchan;
- LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TX=%d\n",
+ LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TS=%d\n",
(act_req->activate) ? "Activate" : "Deactivate",
act_req->trx_nr, act_req->ts_nr);
@@ -663,40 +912,70 @@ static int pcu_rx_act_req(struct gsm_bts *bts,
gsm_lchant_name(lchan->type));
return -EINVAL;
}
+ if (lchan->ts->pchan == GSM_PCHAN_OSMO_DYN &&
+ lchan->ts->dyn.pchan_is != GSM_PCHAN_PDCH) {
+ LOGP(DPCU, LOGL_ERROR,
+ "%s request, but lchan in dyn TS is not configured as PDCH in lower layers (is %s)\n",
+ (act_req->activate) ? "Activate" : "Deactivate",
+ gsm_pchan_name(lchan->ts->dyn.pchan_is));
+ return -EINVAL;
+ }
if (act_req->activate)
- l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan), NULL);
+ l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan));
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)
+#define CHECK_IF_MSG_SIZE(prim_len, prim_msg) \
+ do { \
+ size_t _len = PCUIF_HDR_SIZE + sizeof(prim_msg); \
+ if (prim_len < _len) { \
+ LOGP(DPCU, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive %s " \
+ "size is %zu, discarding\n", prim_len, #prim_msg, _len); \
+ return -EINVAL; \
+ } \
+ } while (0)
+static int pcu_rx(uint8_t msg_type, struct gsm_pcu_if *pcu_prim, size_t prim_len)
{
int rc = 0;
struct gsm_bts *bts;
+ size_t exp_len;
- /* FIXME: allow multiple BTS */
- if (pcu_prim->bts_nr != 0) {
+ if ((bts = gsm_bts_num(g_bts_sm, pcu_prim->bts_nr)) == NULL) {
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:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.data_req);
rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req);
break;
case PCU_IF_MSG_PAG_REQ:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.pag_req);
rc = pcu_rx_pag_req(bts, msg_type, &pcu_prim->u.pag_req);
break;
case PCU_IF_MSG_ACT_REQ:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.act_req);
rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req);
break;
case PCU_IF_MSG_TXT_IND:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.txt_ind);
rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind);
break;
+ case PCU_IF_MSG_CONTAINER:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.container);
+ /* ^ check if we can access container fields, v check with container data length */
+ exp_len = PCUIF_HDR_SIZE + sizeof(pcu_prim->u.container) + osmo_load16be(&pcu_prim->u.container.length);
+ if (prim_len < exp_len) {
+ LOGP(DPCU, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive "
+ "container size is %zu, discarding\n", prim_len, exp_len);
+ return -EINVAL;
+ }
+ rc = abis_osmo_pcu_tx_container(bts, &pcu_prim->u.container);
+ break;
default:
LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n",
msg_type);
@@ -711,49 +990,58 @@ static int pcu_rx(struct gsm_network *net, uint8_t msg_type,
*/
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 */
+ struct osmo_wqueue upqueue; /* For sending messages; has fd for conn. to PCU */
};
-static int pcu_sock_send(struct gsm_network *net, struct msgb *msg)
+static void pcu_sock_close(struct pcu_sock_state *state);
+
+int pcu_sock_send(struct msgb *msg)
{
- struct pcu_sock_state *state = net->pcu_state;
+ struct pcu_sock_state *state = g_bts_sm->gprs.pcu_state;
struct osmo_fd *conn_bfd;
struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data;
+ int rc;
if (!state) {
- if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND &&
+ pcu_prim->msg_type != PCU_IF_MSG_INTERF_IND)
LOGP(DPCU, LOGL_INFO, "PCU socket not created, "
"dropping message\n");
msgb_free(msg);
return -EINVAL;
}
- conn_bfd = &state->conn_bfd;
+ conn_bfd = &state->upqueue.bfd;
if (conn_bfd->fd <= 0) {
- if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND &&
+ pcu_prim->msg_type != PCU_IF_MSG_INTERF_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;
+ rc = osmo_wqueue_enqueue(&state->upqueue, msg);
+ if (rc < 0) {
+ if (rc == -ENOSPC)
+ LOGP(DPCU, LOGL_NOTICE, "PCU not reacting (more than %u messages waiting). Closing connection\n",
+ state->upqueue.max_length);
+ pcu_sock_close(state);
+ msgb_free(msg);
+ return rc;
+ }
return 0;
}
static void pcu_sock_close(struct pcu_sock_state *state)
{
- struct osmo_fd *bfd = &state->conn_bfd;
+ struct osmo_fd *bfd = &state->upqueue.bfd;
struct gsm_bts *bts;
struct gsm_bts_trx *trx;
- struct gsm_bts_trx_ts *ts;
- int i, j;
+ unsigned int tn;
/* FIXME: allow multiple BTS */
- bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list);
+ bts = llist_entry(g_bts_sm->bts_list.next, struct gsm_bts, list);
LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
oml_tx_failure_event_rep(&bts->gprs.cell.mo, NM_SEVER_MAJOR, OSMO_EVT_PCU_VERS,
@@ -761,15 +1049,16 @@ static void pcu_sock_close(struct pcu_sock_state *state)
bts->pcu_version[0] = '\0';
+ osmo_fd_unregister(bfd);
close(bfd->fd);
bfd->fd = -1;
- osmo_fd_unregister(bfd);
/* patch SI3 to remove GPRS indicator */
regenerate_si3_restoctets(bts);
+ regenerate_si4_restoctets(bts);
/* re-enable the generation of ACCEPT for new connections */
- state->listen_bfd.when |= BSC_FD_READ;
+ osmo_fd_read_enable(&state->listen_bfd);
#if 0
/* remove si13, ... */
@@ -777,27 +1066,22 @@ static void pcu_sock_close(struct pcu_sock_state *state)
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]));
- }
+ /* Deactivate all active PDCH timeslots */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (tn = 0; tn < 8; tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (!ts_should_be_pdch(ts))
+ continue;
+
+ 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);
- }
+ osmo_wqueue_clear(&state->upqueue);
}
static int pcu_sock_read(struct osmo_fd *bfd)
@@ -807,7 +1091,7 @@ static int pcu_sock_read(struct osmo_fd *bfd)
struct msgb *msg;
int rc;
- msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx");
+ msg = msgb_alloc(sizeof(*pcu_prim) + 1000, "pcu_sock_rx");
if (!msg)
return -ENOMEM;
@@ -825,14 +1109,14 @@ static int pcu_sock_read(struct osmo_fd *bfd)
goto close;
}
- if (rc < sizeof(*pcu_prim)) {
- LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive size "
- "is %zu, discarding\n", rc, sizeof(*pcu_prim));
+ if (rc < PCUIF_HDR_SIZE) {
+ LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive hdr size "
+ "is %zu, discarding\n", rc, PCUIF_HDR_SIZE);
msgb_free(msg);
return 0;
}
- rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim);
+ rc = pcu_rx(pcu_prim->msg_type, pcu_prim, rc);
/* as we always synchronously process the message in pcu_rx() and
* its callbacks, we can free the message here. */
@@ -846,102 +1130,57 @@ close:
return -1;
}
-static int pcu_sock_write(struct osmo_fd *bfd)
+static int pcu_sock_write(struct osmo_fd *bfd, struct msgb *msg)
{
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);
+ /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+ OSMO_ASSERT(msgb_length(msg) > 0);
+ /* try to send it over the socket */
+ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+ if (OSMO_UNLIKELY(rc == 0))
+ goto close;
+ if (OSMO_UNLIKELY(rc < 0)) {
+ if (errno == EAGAIN)
+ return -EAGAIN;
+ return -1;
}
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 */
+/* accept connection coming 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 osmo_fd *conn_bfd = &state->upqueue.bfd;
struct sockaddr_un un_addr;
socklen_t len;
- int rc;
+ int fd;
len = sizeof(un_addr);
- rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
- if (rc < 0) {
+ fd = accept(bfd->fd, (struct sockaddr *)&un_addr, &len);
+ if (fd < 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");
+ 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);
+ osmo_fd_read_disable(&state->listen_bfd);
+ close(fd);
return 0;
}
- conn_bfd->fd = rc;
- conn_bfd->when = BSC_FD_READ;
- conn_bfd->cb = pcu_sock_cb;
- conn_bfd->data = state;
+ osmo_fd_setup(conn_bfd, fd, OSMO_FD_READ, osmo_wqueue_bfd_cb, state, 0);
if (osmo_fd_register(conn_bfd) != 0) {
- LOGP(DPCU, LOGL_ERROR, "Failed to register new connection "
- "fd\n");
+ LOGP(DPCU, LOGL_ERROR, "Failed to register new connection fd\n");
close(conn_bfd->fd);
conn_bfd->fd = -1;
return -1;
@@ -955,34 +1194,32 @@ static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags)
return 0;
}
-int pcu_sock_init(const char *path)
+int pcu_sock_init(const char *path, int qlength_max)
{
struct pcu_sock_state *state;
struct osmo_fd *bfd;
int rc;
- state = talloc_zero(NULL, struct pcu_sock_state);
+ state = talloc_zero(g_bts_sm, struct pcu_sock_state);
if (!state)
return -ENOMEM;
- INIT_LLIST_HEAD(&state->upqueue);
- state->net = &bts_gsmnet;
- state->conn_bfd.fd = -1;
+ osmo_wqueue_init(&state->upqueue, qlength_max);
+ state->upqueue.read_cb = pcu_sock_read;
+ state->upqueue.write_cb = pcu_sock_write;
+ state->upqueue.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) {
+ rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND);
+ if (rc < 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;
+ osmo_fd_setup(bfd, rc, OSMO_FD_READ, pcu_sock_accept, state, 0);
rc = osmo_fd_register(bfd);
if (rc < 0) {
@@ -995,39 +1232,38 @@ int pcu_sock_init(const char *path)
osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL);
- bts_gsmnet.pcu_state = state;
+ g_bts_sm->gprs.pcu_state = state;
- LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path);
+ LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket (PCU IF v%u): %s\n", PCU_IF_VERSION, path);
return 0;
}
void pcu_sock_exit(void)
{
- struct pcu_sock_state *state = bts_gsmnet.pcu_state;
+ struct pcu_sock_state *state = g_bts_sm->gprs.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;
+ conn_bfd = &state->upqueue.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;
+ g_bts_sm->gprs.pcu_state = NULL;
}
bool pcu_connected(void) {
- struct gsm_network *net = &bts_gsmnet;
- struct pcu_sock_state *state = net->pcu_state;
+ struct pcu_sock_state *state = g_bts_sm->gprs.pcu_state;
if (!state)
return false;
- if (state->conn_bfd.fd <= 0)
+ if (state->upqueue.bfd.fd <= 0)
return false;
return true;
}
diff --git a/src/common/phy_link.c b/src/common/phy_link.c
index 588fcc91..352d8f72 100644
--- a/src/common/phy_link.c
+++ b/src/common/phy_link.c
@@ -2,6 +2,7 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/fsm.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
@@ -9,6 +10,7 @@
#include <osmo-bts/oml.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts_model.h>
+#include <osmo-bts/nm_common_fsm.h>
static LLIST_HEAD(g_phy_links);
@@ -53,9 +55,11 @@ 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));
+ LOGPPHL(plink, 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));
+
+ plink->state = state;
/* notify all TRX associated with this phy */
llist_for_each_entry(pinst, &plink->instances, list) {
@@ -63,25 +67,28 @@ void phy_link_state_set(struct phy_link *plink, enum phy_link_state state)
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;
- }
+ osmo_fsm_inst_dispatch(trx->mo.fi,
+ state == PHY_LINK_CONNECTED ? NM_EV_PHYLINK_UP :
+ NM_EV_PHYLINK_DOWN,
+ NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi,
+ state == PHY_LINK_CONNECTED ? NM_EV_PHYLINK_UP :
+ NM_EV_PHYLINK_DOWN,
+ NULL);
}
+}
- plink->state = state;
+enum phy_link_state phy_link_state_get(struct phy_link *plink)
+{
+ return plink->state;
+}
+
+const char *phy_link_state_name(enum phy_link_state state)
+{
+ return get_value_string(phy_link_state_vals, state);
}
-struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num)
+struct phy_instance *phy_instance_by_num(const struct phy_link *plink, int num)
{
struct phy_instance *pinst;
@@ -111,7 +118,9 @@ 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)
{
- trx->role_bts.l1h = pinst;
+ /* There might already be an associated TRX */
+ OSMO_ASSERT(pinst->trx == NULL)
+ trx->pinst = pinst;
pinst->trx = trx;
}
@@ -120,10 +129,12 @@ 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;
+ /* remove reverse link from TRX (if associated) */
+ if (pinst->trx != NULL) {
+ OSMO_ASSERT(pinst->trx->pinst == pinst);
+ pinst->trx->pinst = NULL;
+ pinst->trx = NULL;
+ }
talloc_free(pinst);
}
@@ -138,13 +149,29 @@ void phy_link_destroy(struct phy_link *plink)
talloc_free(plink);
}
+static char name_buf[32];
+const char *phy_link_name(const struct phy_link *plink)
+{
+ snprintf(name_buf, sizeof(name_buf), "phy%u", plink->num);
+ return name_buf;
+}
+
int phy_links_open(void)
{
+ const struct phy_instance *pinst;
struct phy_link *plink;
llist_for_each_entry(plink, &g_phy_links, list) {
int rc;
+ /* Warn about dangling PHY instances */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->trx != NULL)
+ continue;
+ LOGPPHI(pinst, DL1C, LOGL_NOTICE, "This PHY instance is not associated "
+ "with a TRX instance, check the configuration file!\n");
+ }
+
rc = bts_model_phy_link_open(plink);
if (rc < 0)
return rc;
@@ -153,11 +180,9 @@ int phy_links_open(void)
return 0;
}
-const char *phy_instance_name(struct phy_instance *pinst)
+const char *phy_instance_name(const struct phy_instance *pinst)
{
- static char buf[32];
-
- snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num,
+ snprintf(name_buf, sizeof(name_buf), "phy%u.%u", pinst->phy_link->num,
pinst->num);
- return buf;
+ return name_buf;
}
diff --git a/src/common/power_control.c b/src/common/power_control.c
index b1728705..7f98a417 100644
--- a/src/common/power_control.c
+++ b/src/common/power_control.c
@@ -1,6 +1,8 @@
/* MS Power Control Loop L1 */
/* (C) 2014 by Holger Hans Peter Freyther
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
*
* All Rights Reserved
*
@@ -12,7 +14,7 @@
* 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.
+ * 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/>.
@@ -22,6 +24,7 @@
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
+#include <inttypes.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
@@ -29,61 +32,544 @@
#include <osmo-bts/measurement.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/power_control.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.
+/* We don't want to deal with floating point, so we scale up */
+#define EWMA_SCALE_FACTOR 100
+/* EWMA_SCALE_FACTOR/2 = +50: Round to nearest value when downscaling, otherwise floor() is applied. */
+#define EWMA_ROUND_FACTOR (EWMA_SCALE_FACTOR / 2)
+
+/* Base Low-Pass Single-Pole IIR Filter (EWMA) formula:
+ *
+ * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1]
+ *
+ * where parameter 'a' determines how much weight of the latest measurement value
+ * 'Val[n]' carries vs the weight of the accumulated average 'Avg[n - 1]'. The
+ * value of 'a' is usually a float in range 0 .. 1, so:
+ *
+ * - value 0.5 gives equal weight to both 'Val[n]' and 'Avg[n - 1]';
+ * - value 1.0 means no filtering at all (pass through);
+ * - value 0.0 makes no sense.
+ *
+ * Further optimization:
+ *
+ * Avg[n] = a * Val[n] + Avg[n - 1] - a * Avg[n - 1]
+ * ^^^^^^ ^^^^^^^^^^
+ *
+ * a) this can be implemented in C using '+=' operator:
+ *
+ * Avg += a * Val - a * Avg
+ * Avg += a * (Val - Avg)
+ *
+ * b) everything is scaled up by 100 to avoid floating point stuff:
+ *
+ * Avg100 += A * (Val - Avg)
+ *
+ * where 'Avg100' is 'Avg * 100' and 'A' is 'a * 100'.
+ *
+ * For more details, see:
+ *
+ * https://en.wikipedia.org/wiki/Moving_average
+ * https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter
+ * https://tomroelandts.com/articles/low-pass-single-pole-iir-filter
+ */
+static int do_pf_ewma(const struct gsm_power_ctrl_meas_params *mp,
+ struct gsm_power_ctrl_meas_proc_state *mps,
+ const int Val)
+{
+ const uint8_t A = mp->ewma.alpha;
+ int *Avg100 = &mps->ewma.Avg100;
+
+ /* We don't have 'Avg[n - 1]' if this is the first run */
+ if (mps->meas_num++ == 0) {
+ *Avg100 = Val * EWMA_SCALE_FACTOR;
+ return Val;
+ }
+
+ *Avg100 += A * (Val - (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR);
+ return (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR;
+}
+
+/* Calculate target RxLev value from lower/upper thresholds */
+#define CALC_TARGET(mp) \
+ ((mp).lower_thresh + (mp).upper_thresh) / 2
+
+static int do_avg_algo(const struct gsm_power_ctrl_meas_params *mp,
+ struct gsm_power_ctrl_meas_proc_state *mps,
+ const int val)
+{
+ int val_avg;
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
+ val_avg = do_pf_ewma(mp, mps, val);
+ break;
+ /* TODO: implement other pre-processing methods */
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
+ default:
+ /* No filtering (pass through) */
+ val_avg = val;
+ }
+ return val_avg;
+}
+/* Calculate a 'delta' value (for the given MS/BS power control parameters)
+ * to be applied to the current Tx power level to approach the target level. */
+static int calc_delta_rxlev(const struct gsm_power_ctrl_params *params, const uint8_t rxlev)
+{
+ int delta;
+
+ /* Check if RxLev is within the threshold window */
+ if (rxlev >= params->rxlev_meas.lower_thresh &&
+ rxlev <= params->rxlev_meas.upper_thresh)
+ return 0;
+
+ /* How many dBs measured power should be increased (+) or decreased (-)
+ * to reach expected power. */
+ delta = CALC_TARGET(params->rxlev_meas) - rxlev;
+
+ /* Don't ever change more than PWR_{LOWER,RAISE}_MAX_DBM during one loop
+ * iteration, i.e. reduce the speed at which the MS transmit power can
+ * change. A higher value means a lower level (and vice versa) */
+ if (delta > params->inc_step_size_db)
+ delta = params->inc_step_size_db;
+ else if (delta < -params->red_step_size_db)
+ delta = -params->red_step_size_db;
+
+ return delta;
+}
+
+/* Shall we skip current block based on configured interval? */
+static bool ctrl_interval_skip_block(const struct gsm_power_ctrl_params *params,
+ struct lchan_power_ctrl_state *state)
+{
+ /* Power control interval: how many blocks do we skip? */
+ if (state->skip_block_num-- > 0)
+ return true;
+
+ /* Reset the number of SACCH blocks to be skipped:
+ * ctrl_interval=0 => 0 blocks to skip,
+ * ctrl_interval=1 => 1 blocks to skip,
+ * ctrl_interval=2 => 3 blocks to skip,
+ * so basically ctrl_interval * 2 - 1. */
+ state->skip_block_num = params->ctrl_interval * 2 - 1;
+ return false;
+}
+
+static const struct gsm_power_ctrl_meas_params *lchan_get_ci_thresholds(const struct gsm_lchan *lchan)
+{
+ const struct gsm_power_ctrl_params *params = lchan->ms_power_ctrl.dpc_params;
+
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ return &params->ci_sdcch_meas;
+ case GSM_LCHAN_PDTCH:
+ return &params->ci_gprs_meas;
+ case GSM_LCHAN_TCH_F:
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ return &params->ci_amr_fr_meas;
+ else
+ return &params->ci_fr_meas;
+ case GSM_LCHAN_TCH_H:
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ return &params->ci_amr_hr_meas;
+ else
+ return &params->ci_hr_meas;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/*! compute the new MS POWER LEVEL communicated to the MS and store it in lchan.
+ * \param lchan logical channel for which to compute (and in which to store) new power value.
+ * \param[in] ms_power_lvl MS Power Level received from Uplink L1 SACCH Header in SACCH block.
+ * \param[in] ul_rssi_dbm Signal level of the received SACCH block, in dBm.
+ * \param[in] ul_lqual_cb C/I of the received SACCH block, in dB.
*/
int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
- const uint8_t ms_power, const int rxLevel)
+ const uint8_t ms_power_lvl,
+ const int8_t ul_rssi_dbm,
+ const int16_t ul_lqual_cb)
{
- int rx;
- int cur_dBm, new_dBm, new_pwr;
- struct gsm_bts *bts = lchan->ts->trx->bts;
- const enum gsm_band band = bts->band;
+ struct lchan_power_ctrl_state *state = &lchan->ms_power_ctrl;
+ const struct gsm_power_ctrl_params *params = state->dpc_params;
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct gsm_bts *bts = trx->bts;
+ enum gsm_band band = bts->band;
+ int8_t new_power_lvl; /* TS 05.05 power level */
+ int8_t ms_dbm, new_dbm, current_dbm, bsc_max_dbm;
+ uint8_t rxlev_avg;
+ int16_t ul_lqual_cb_avg;
+ const struct gsm_power_ctrl_meas_params *ci_meas;
+ bool ignore, ci_on;
- if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ if (!trx_ms_pwr_ctrl_is_osmo(trx))
return 0;
- if (lchan->ms_power_ctrl.fixed)
+ if (params == NULL)
return 0;
- /* The phone hasn't reached the power level yet */
- if (lchan->ms_power_ctrl.current != ms_power)
+ /* Shall we skip current block based on configured interval? */
+ if (ctrl_interval_skip_block(params, state))
return 0;
- /* What is the difference between what we want and received? */
- rx = bts->ul_power_target - rxLevel;
+ ms_dbm = ms_pwr_dbm(band, ms_power_lvl);
+ if (ms_dbm < 0) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE,
+ "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n",
+ ms_power_lvl, gsm_band_name(band));
+ return 0;
+ }
+ bsc_max_dbm = ms_pwr_dbm(band, state->max);
+ if (bsc_max_dbm < 0) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE,
+ "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n",
+ state->max, gsm_band_name(band));
+ return 0;
+ }
- cur_dBm = ms_pwr_dbm(band, ms_power);
- new_dBm = cur_dBm + rx;
+ ci_meas = lchan_get_ci_thresholds(lchan);
- /* Clamp negative values and do it depending on the band */
- if (new_dBm < 0)
- new_dBm = 0;
+ /* Is C/I based algo enabled by config?
+ * FIXME: this can later be generalized when properly implementing P & N counting. */
+ ci_on = ci_meas->lower_cmp_n && ci_meas->upper_cmp_n;
- 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;
+ ul_lqual_cb_avg = do_avg_algo(ci_meas, &state->ci_meas_proc, ul_lqual_cb);
+ rxlev_avg = do_avg_algo(&params->rxlev_meas, &state->rxlev_meas_proc, dbm2rxlev(ul_rssi_dbm));
+
+ /* If computed C/I is enabled and out of acceptable thresholds: */
+ if (ci_on && ul_lqual_cb_avg < ci_meas->lower_thresh * 10) {
+ new_dbm = ms_dbm + params->inc_step_size_db;
+ } else if (ci_on && ul_lqual_cb_avg > ci_meas->upper_thresh * 10) {
+ new_dbm = ms_dbm - params->red_step_size_db;
+ } else {
+ /* Calculate the new Tx power value (in dBm) */
+ new_dbm = ms_dbm + calc_delta_rxlev(params, rxlev_avg);
}
- 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;
+ /* Make sure new_dbm is never negative. ms_pwr_ctl_lvl() can later on
+ cope with any unsigned dbm value, regardless of band minimal value. */
+ if (new_dbm < 0)
+ new_dbm = 0;
+
+ /* Don't ask for smaller ms power level than the one set by BSC upon RSL CHAN ACT */
+ if (new_dbm > bsc_max_dbm)
+ new_dbm = bsc_max_dbm;
+
+ new_power_lvl = ms_pwr_ctl_lvl(band, new_dbm);
+ if (new_power_lvl < 0) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE,
+ "Failed to retrieve power level for %" PRId8 " dBm on band %d\n",
+ new_dbm, band);
+ return 0;
}
- return 0;
+ current_dbm = ms_pwr_dbm(band, state->current);
+
+ /* In this Power Control Loop, we infer a new good MS Power Level based
+ * on the previous MS Power Level announced by the MS (not the previous
+ * one we requested!) together with the related computed measurements.
+ * Hence, and since we allow for several good MS Power Levels falling into our
+ * thresholds, we could finally converge into an oscillation loop where
+ * the MS bounces between 2 different correct MS Power levels all the
+ * time, due to the fact that we "accept" and "request back" whatever
+ * good MS Power Level we received from the MS, but at that time the MS
+ * will be transmitting using the previous MS Power Level we
+ * requested, which we will later "accept" and "request back" on next loop
+ * iteration. As a result MS effectively bounces between those 2 MS
+ * Power Levels.
+ * In order to fix this permanent oscillation, if current MS_PWR used/announced
+ * by MS is good ("ms_dbm == new_dbm", hence within thresholds and no change
+ * required) but has higher Tx power than the one we last requested, we ignore
+ * it and keep requesting for one with lower Tx power. This way we converge to
+ * the lowest good Tx power avoiding oscillating over values within thresholds.
+ */
+ ignore = (ms_dbm == new_dbm && ms_dbm > current_dbm);
+
+ if (state->current == new_power_lvl || ignore) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping MS power at control level %d (%d dBm): "
+ "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm,"
+ " C/I[curr %d, avg %d, thresh %d..%d] dB\n",
+ new_power_lvl, new_dbm, ms_power_lvl, state->max, ul_rssi_dbm, rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh),
+ ul_lqual_cb/10, ul_lqual_cb_avg/10, ci_meas->lower_thresh, ci_meas->upper_thresh);
+ return 0;
+ }
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s MS power control level %d (%d dBm) => %d (%d dBm): "
+ "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm,"
+ " C/I[curr %d, avg %d, thresh %d..%d] dB\n",
+ (new_dbm > current_dbm) ? "Raising" : "Lowering",
+ state->current, current_dbm, new_power_lvl, new_dbm, ms_power_lvl,
+ state->max, ul_rssi_dbm, rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh),
+ ul_lqual_cb/10, ul_lqual_cb_avg/10, ci_meas->lower_thresh, ci_meas->upper_thresh);
+
+ /* store the resulting new MS power level in the lchan */
+ state->current = new_power_lvl;
+ bts_model_adjst_ms_pwr(lchan);
+
+ return 1;
+}
+
+/*! compute the new Downlink attenuation value for the given logical channel.
+ * \param lchan logical channel for which to compute (and in which to store) new power value.
+ * \param[in] mr pointer to a *valid* Measurement Report.
+ */
+int lchan_bs_pwr_ctrl(struct gsm_lchan *lchan,
+ const struct gsm48_meas_res *mr)
+{
+ struct lchan_power_ctrl_state *state = &lchan->bs_power_ctrl;
+ const struct gsm_power_ctrl_params *params = state->dpc_params;
+ uint8_t rxqual, rxqual_avg, rxlev, rxlev_avg;
+ int new_att;
+
+ /* Check if dynamic BS Power Control is enabled */
+ if (params == NULL)
+ return 0;
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Rx DL Measurement Report: "
+ "RXLEV-FULL(%02u), RXQUAL-FULL(%u), "
+ "RXLEV-SUB(%02u), RXQUAL-SUB(%u), "
+ "DTx is %s => using %s\n",
+ mr->rxlev_full, mr->rxqual_full,
+ mr->rxlev_sub, mr->rxqual_sub,
+ lchan->tch.dtx.dl_active ? "enabled" : "disabled",
+ lchan->tch.dtx.dl_active ? "SUB" : "FULL");
+
+ /* Shall we skip current block based on configured interval? */
+ if (ctrl_interval_skip_block(params, state))
+ return 0;
+
+ /* If DTx is active on Downlink, use the '-SUB' */
+ if (lchan->tch.dtx.dl_active) {
+ rxqual = mr->rxqual_sub;
+ rxlev = mr->rxlev_sub;
+ } else { /* ... otherwise use the '-FULL' */
+ rxqual = mr->rxqual_full;
+ rxlev = mr->rxlev_full;
+ }
+
+ rxlev_avg = do_avg_algo(&params->rxlev_meas, &state->rxlev_meas_proc, rxlev);
+ rxqual_avg = do_avg_algo(&params->rxqual_meas, &state->rxqual_meas_proc, rxqual);
+ /* If RxQual > L_RXQUAL_XX_P, try to increase Tx power */
+ if (rxqual_avg > params->rxqual_meas.lower_thresh) {
+ /* Increase Tx power by reducing Tx attenuation */
+ new_att = state->current - params->inc_step_size_db;
+ } else if (rxqual_avg < params->rxqual_meas.upper_thresh) {
+ /* Increase Tx power by Increasing Tx attenuation */
+ new_att = state->current + params->red_step_size_db;
+ } else {
+ /* Basic signal transmission / reception formula:
+ *
+ * RxLev = TxPwr - (PathLoss + TxAtt)
+ *
+ * Here we want to change RxLev at the MS side, so:
+ *
+ * RxLev + Delta = TxPwr - (PathLoss + TxAtt) + Delta
+ *
+ * The only parameter we can change here is TxAtt, so:
+ *
+ * RxLev + Delta = TxPwr - PathLoss - TxAtt + Delta
+ * RxLev + Delta = TxPwr - PathLoss - (TxAtt - Delta)
+ */
+ new_att = state->current - calc_delta_rxlev(params, rxlev_avg);
+ }
+
+ /* Make sure new TxAtt is never negative: */
+ if (new_att < 0)
+ new_att = 0;
+
+ /* Don't ask for higher TxAtt than permitted: */
+ if (new_att > state->max)
+ new_att = state->max;
+
+ if (state->current == new_att) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping DL attenuation at %u dB: "
+ "max %u dB, RSSI[curr %d, avg %d, thresh %d..%d] dBm, "
+ "RxQual[curr %d, avg %d, thresh %d..%d]\n",
+ state->current, state->max, rxlev2dbm(rxlev), rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh),
+ rxqual, rxqual_avg, params->rxqual_meas.lower_thresh, params->rxqual_meas.upper_thresh);
+ return 0;
+ }
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s DL attenuation %u dB => %u dB:"
+ "max %u dB, RSSI[curr %d, avg %d, thresh %d..%d] dBm, "
+ "RxQual[curr %d, avg %d, thresh %d..%d]\n",
+ (new_att > state->current) ? "Raising" : "Lowering",
+ state->current, new_att, state->max, rxlev2dbm(rxlev), rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh),
+ rxqual, rxqual_avg, params->rxqual_meas.lower_thresh, params->rxqual_meas.upper_thresh);
+ state->current = new_att;
+ return 1;
+}
+
+/* Default MS/BS Power Control parameters (see 3GPP TS 45.008, table A.1) */
+const struct gsm_power_ctrl_params power_ctrl_params_def = {
+ /* Power increasing/reducing step size (optimal defaults) */
+ .inc_step_size_db = 4, /* quickly increase MS/BS power */
+ .red_step_size_db = 2, /* slowly decrease MS/BS power */
+
+ /* RxLev measurement parameters */
+ .rxlev_meas = {
+ /* Thresholds for RxLev (see 3GPP TS 45.008, A.3.2.1) */
+ .lower_thresh = 32, /* L_RXLEV_XX_P (-78 dBm) */
+ .upper_thresh = 38, /* U_RXLEV_XX_P (-72 dBm) */
+
+ /* NOTE: only Osmocom specific EWMA is supported */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA,
+ .ewma.alpha = 50, /* Smoothing factor 50% */
+ },
+
+ /* RxQual measurement parameters */
+ .rxqual_meas = {
+ /* Thresholds for RxQual (see 3GPP TS 45.008, A.3.2.1) */
+ .lower_thresh = 3, /* L_RXQUAL_XX_P (0.8% <= BER < 1.6%) */
+ .upper_thresh = 0, /* U_RXQUAL_XX_P (BER < 0.2%) */
+
+ /* No averaging (filtering) by default.
+ * NOTE: only Osmocom specific EWMA is supported */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+ },
+
+ /* C/I measurement parameters.
+ * Target C/I retrieved from "GSM/EDGE: Evolution and Performance" Table 10.3.
+ * Set lower and upper so that (lower + upper) / 2 is equal or slightly
+ * above the target.
+ */
+ .ci_fr_meas = { /* FR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */
+ .lower_thresh = 13,
+ .upper_thresh = 17,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_FR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_FR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_hr_meas = { /* HR: Target C/I = 18 dB, Soft blocking threshold = 13 dB */
+ .lower_thresh = 16,
+ .upper_thresh = 21,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_HR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_HR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_amr_fr_meas = { /* AMR-FR: Target C/I = 9 dB, Soft blocking threshold = 4 dB */
+ .lower_thresh = 7,
+ .upper_thresh = 11,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_AMR_FR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_AMR_FR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_amr_hr_meas = { /* AMR-HR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */
+ .lower_thresh = 13,
+ .upper_thresh = 17,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_AMR_HR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_AMR_HR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_sdcch_meas = { /* SDCCH: Target C/I = 14 dB, Soft blocking threshold = 9 dB */
+ .lower_thresh = 12,
+ .upper_thresh = 16,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_SDCCH_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_SDCCH_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_gprs_meas = { /* GPRS: Target C/I = 20 dB, Soft blocking threshold = 15 dB */
+ .lower_thresh = 18,
+ .upper_thresh = 24,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_GPRS_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_GPRS_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+};
+
+void power_ctrl_params_def_reset(struct gsm_power_ctrl_params *params, bool is_bs_pwr)
+{
+ *params = power_ctrl_params_def;
+
+ /* Trigger loop every N-th SACCH block. See 3GPP TS 45.008 section 4.7.1. */
+ if (!is_bs_pwr)
+ params->ctrl_interval = 2; /* N=4 (1.92s) */
+ else
+ params->ctrl_interval = 1; /* N=2 (0.960) */
}
diff --git a/src/common/probes.d b/src/common/probes.d
new file mode 100644
index 00000000..aaf9030e
--- /dev/null
+++ b/src/common/probes.d
@@ -0,0 +1,2 @@
+provider osmo_bts {
+};
diff --git a/src/common/rsl.c b/src/common/rsl.c
index c0d43d0e..40690f05 100644
--- a/src/common/rsl.c
+++ b/src/common/rsl.c
@@ -2,6 +2,7 @@
/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2011-2019 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -13,19 +14,18 @@
* 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.
+ * 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 "btsconfig.h" /* for PACKAGE_VERSION */
-
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <stdbool.h>
+#include <inttypes.h>
#include <sys/types.h>
#include <arpa/inet.h>
@@ -56,9 +56,39 @@
#include <osmo-bts/l1sap.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/notification.h>
+#include <osmo-bts/asci.h>
//#define FAKE_CIPH_MODE_COMPL
+/* Parse power attenuation (in dB) from BS Power IE (see 9.3.4) */
+#define BS_POWER2DB(bs_power) \
+ ((bs_power & 0x0f) * 2)
+
+bool rsl_chan_rt_is_asci(enum rsl_cmod_crt chan_rt)
+{
+ switch (chan_rt) {
+ case RSL_CMOD_CRT_TCH_GROUP_Bm:
+ case RSL_CMOD_CRT_TCH_GROUP_Lm:
+ case RSL_CMOD_CRT_TCH_BCAST_Bm:
+ case RSL_CMOD_CRT_TCH_BCAST_Lm:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool rsl_chan_rt_is_vgcs(enum rsl_cmod_crt chan_rt)
+{
+ switch (chan_rt) {
+ case RSL_CMOD_CRT_TCH_GROUP_Bm:
+ case RSL_CMOD_CRT_TCH_GROUP_Lm:
+ return true;
+ default:
+ return false;
+ }
+}
+
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);
@@ -69,6 +99,7 @@ static const unsigned int rsl_sacch_sitypes[] = {
RSL_SYSTEM_INFO_6,
RSL_SYSTEM_INFO_5bis,
RSL_SYSTEM_INFO_5ter,
+ RSL_SYSTEM_INFO_10,
RSL_EXT_MEAS_ORDER,
RSL_MEAS_INFO,
};
@@ -85,19 +116,6 @@ int osmo_in_array(unsigned int search, const unsigned int *arr, unsigned int siz
}
#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)
{
@@ -106,33 +124,236 @@ void gsm48_gen_starting_time(uint8_t *out, struct gsm_time *gtime)
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)
+/* Handle RSL Channel Mode IE (see section 9.3.6) */
+static int rsl_handle_chan_mod_ie(struct gsm_lchan *lchan,
+ const struct tlv_parsed *tp,
+ uint8_t *cause)
{
+ const struct rsl_ie_chan_mode *cm;
+
+ if (!TLVP_PRES_LEN(tp, RSL_IE_CHAN_MODE, sizeof(*cm))) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel Mode IE is not present\n");
+ *cause = RSL_ERR_MAND_IE_ERROR;
+ return -ENODEV;
+ }
+
+ cm = (const struct rsl_ie_chan_mode *) TLVP_VAL(tp, RSL_IE_CHAN_MODE);
lchan->rsl_cmode = cm->spd_ind;
+ lchan->rsl_chan_rt = cm->chan_rt;
lchan->ts->trx->bts->dtxd = (cm->dtx_dtu & RSL_CMOD_DTXd) ? true : false;
- switch (cm->chan_rate) {
- case RSL_CMOD_SP_GSM1:
+ /* Octet 5: Channel rate and type */
+ switch (cm->chan_rt) {
+ case RSL_CMOD_CRT_SDCCH:
+ case RSL_CMOD_CRT_TCH_Bm:
+ case RSL_CMOD_CRT_TCH_Lm:
+ case RSL_CMOD_CRT_TCH_GROUP_Bm:
+ case RSL_CMOD_CRT_TCH_GROUP_Lm:
+ case RSL_CMOD_CRT_TCH_BCAST_Bm:
+ case RSL_CMOD_CRT_TCH_BCAST_Lm:
+ break;
+ case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm:
+ case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm:
+ /* Make sure that Osmocom specific TSC IE is present */
+ if (!TLVP_PRES_LEN(tp, RSL_IE_OSMO_TRAINING_SEQUENCE, 2)) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR,
+ "Training Sequence IE is not present\n");
+ *cause = RSL_ERR_MAND_IE_ERROR;
+ return -ENODEV;
+ }
+ break;
+ default:
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel Mode IE contains "
+ "unknown 'Channel rate and type' value 0x%02x\n",
+ cm->chan_rt);
+ *cause = RSL_ERR_IE_CONTENT;
+ return -ENOTSUP;
+ }
+
+#define RSL_CMODE(spd_ind, chan_rate) \
+ ((spd_ind << 8) | chan_rate)
+
+ /* Octet 6: Speech coding algorithm/data rate + transparency indicator.
+ * NOTE: coding of this octet depends on 'Speech or data indicator' */
+ switch (RSL_CMODE(cm->spd_ind, cm->chan_rate)) {
+ /* If octet 4 indicates signalling */
+ case RSL_CMODE(RSL_CMOD_SPD_SIGN, 0x00):
+ /* No resources required, all other values are reserved */
+ lchan->tch_mode = GSM48_CMODE_SIGN;
+ break;
+
+ /* If octet 4 indicates speech */
+ case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM1):
lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
break;
- case RSL_CMOD_SP_GSM2:
+ case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM2):
lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
break;
- case RSL_CMOD_SP_GSM3:
+ case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM3):
lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
break;
- case RSL_CMOD_SP_NT_14k5:
+ case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM4):
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V4;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM5):
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V5;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM6):
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V6;
+ break;
+
+ /* If octet 4 indicates non-transparent data */
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_14k5):
lchan->tch_mode = GSM48_CMODE_DATA_14k5;
break;
- case RSL_CMOD_SP_NT_12k0:
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_12k0):
lchan->tch_mode = GSM48_CMODE_DATA_12k0;
break;
- case RSL_CMOD_SP_NT_6k0:
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_6k0):
lchan->tch_mode = GSM48_CMODE_DATA_6k0;
break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_43k5):
+ lchan->tch_mode = GSM48_CMODE_DATA_43k5;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_28k8):
+ /* 28.8 kbit/s services, 29.0 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_29k0;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_43k5_14k5):
+ lchan->tch_mode = GSM48_CMODE_DATA_43k5_14k5;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_29k0_14k5):
+ lchan->tch_mode = GSM48_CMODE_DATA_29k0_14k5;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_43k5_29k0):
+ lchan->tch_mode = GSM48_CMODE_DATA_43k5_29k0;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_14k5_43k5):
+ lchan->tch_mode = GSM48_CMODE_DATA_14k5_43k5;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_14k5_29k0):
+ lchan->tch_mode = GSM48_CMODE_DATA_14k5_29k0;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_29k0_43k5):
+ lchan->tch_mode = GSM48_CMODE_DATA_29k0_43k5;
+ break;
+
+ /* If octet 4 indicates transparent data */
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_32k0):
+ /* 32.0 kbit/s services, 32.0 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_32k0;
+ lchan->csd_mode = LCHAN_CSD_M_T_32000;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_29k0):
+ /* 29.0 kbit/s services, 29.0 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_29k0;
+ lchan->csd_mode = LCHAN_CSD_M_T_29000;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_14k4):
+ /* 14.4 kbit/s services, 14.5 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_14k5;
+ lchan->csd_mode = LCHAN_CSD_M_T_14400;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_9k6):
+ /* 9.6 kbit/s services, 12.0 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_12k0;
+ lchan->csd_mode = LCHAN_CSD_M_T_9600;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_4k8):
+ /* 4.8 kbit/s services, 6.0 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_6k0;
+ lchan->csd_mode = LCHAN_CSD_M_T_4800;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_2k4):
+ /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_3k6;
+ lchan->csd_mode = LCHAN_CSD_M_T_2400;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_1k2):
+ /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_3k6;
+ lchan->csd_mode = LCHAN_CSD_M_T_1200;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_600):
+ /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_3k6;
+ lchan->csd_mode = LCHAN_CSD_M_T_600;
+ break;
+ case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_1200_75):
+ /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */
+ lchan->tch_mode = GSM48_CMODE_DATA_3k6;
+ lchan->csd_mode = LCHAN_CSD_M_T_1200_75;
+ break;
+
+ default:
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel Mode IE contains "
+ "an unknown/unhandled combination of "
+ "'Speech or data indicator' 0x%02x and "
+ "'Speech coding algorithm/data rate' 0x%02x\n",
+ cm->spd_ind, cm->chan_rate);
+ *cause = RSL_ERR_IE_CONTENT;
+ return -ENOPROTOOPT;
+ }
+
+#undef RSL_CMODE
+
+ if (!bts_supports_cm(lchan->ts->trx->bts, cm)) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel type=0x%02x/mode=%s "
+ "is not supported by the PHY\n", cm->chan_rt,
+ gsm48_chan_mode_name(lchan->tch_mode));
+ *cause = RSL_ERR_SERV_OPT_UNAVAIL;
+ return -ENOTSUP;
+ }
+
+ return 0;
+}
+
+/* Handle RSL Channel Identification IE (see section 9.3.5) */
+static int rsl_handle_chan_ident_ie(struct gsm_lchan *lchan,
+ const struct tlv_parsed *tp,
+ uint8_t *cause)
+{
+ const struct gsm_bts_trx_ts *ts = lchan->ts;
+ const struct gsm_bts *bts = ts->trx->bts;
+ const struct gsm48_chan_desc *cd;
+
+ if (TLVP_PRES_LEN(tp, RSL_IE_CHAN_IDENT, sizeof(*cd) + 1)) {
+ /* Channel Description IE comes together with its IEI (see 9.3.5) */
+ cd = (const struct gsm48_chan_desc *) (TLVP_VAL(tp, RSL_IE_CHAN_IDENT) + 1);
+
+ /* The PHY may not support using different TSCs */
+ if (!osmo_bts_has_feature(bts->features, BTS_FEAT_MULTI_TSC)
+ && cd->h0.tsc != BTS_TSC(bts)) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "This PHY does not support "
+ "lchan TSC %u != BSIC-TSC %u, sending NACK\n",
+ cd->h0.tsc, BTS_TSC(bts));
+ *cause = RSL_ERR_SERV_OPT_UNIMPL;
+ return -ENOTSUP;
+ }
+ }
+
+ return 0;
+}
+
+/* Handle Osmocom specific TSC IE */
+static int rsl_handle_osmo_tsc_ie(struct gsm_lchan *lchan,
+ const struct tlv_parsed *tp,
+ uint8_t *cause)
+{
+ /* Osmocom specific IE indicating Training Sequence Code and Set */
+ if (TLVP_PRES_LEN(tp, RSL_IE_OSMO_TRAINING_SEQUENCE, 2)) {
+ const uint8_t *ie = TLVP_VAL(tp, RSL_IE_OSMO_TRAINING_SEQUENCE);
+ lchan->ts->tsc_set = ie[0] & 0x03; /* Range: 0..3 */
+ lchan->ts->tsc_rsl = ie[1] & 0x07; /* Range: 0..7 */
+ lchan->ts->tsc_rsl_configured = true;
+ } else {
+ lchan->ts->tsc_rsl_configured = false;
+ lchan->ts->tsc_rsl = 0xff;
+ lchan->ts->tsc_set = 0;
}
+ gsm_ts_apply_configured_tsc(lchan->ts);
+
+ return 0;
}
@@ -150,6 +371,16 @@ static bool chan_nr_is_dchan(uint8_t chan_nr)
return true;
}
+static struct gsm_bts_trx *trx_lookup_by_arfcn(struct llist_head *trx_list, uint16_t arfcn)
+{
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, trx_list, list) {
+ if (trx->arfcn == arfcn)
+ return trx;
+ }
+ return NULL;
+}
+
static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
const char *log_name)
{
@@ -269,6 +500,7 @@ static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uin
/* 8.6.1 sending RF RESOURCE INDICATION */
int rsl_tx_rf_res(struct gsm_bts_trx *trx)
{
+ unsigned int tn, ln;
struct msgb *nmsg;
LOGP(DRSL, LOGL_INFO, "Tx RSL RF RESource INDication\n");
@@ -276,8 +508,48 @@ int rsl_tx_rf_res(struct gsm_bts_trx *trx)
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);
+
+ /* Add interference levels for each logical channel */
+ uint8_t *len = msgb_tl_put(nmsg, RSL_IE_RESOURCE_INFO);
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (ts->mo.nm_state.availability != NM_AVSTATE_OK)
+ continue;
+
+ for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) {
+ const struct gsm_lchan *lchan = &ts->lchan[ln];
+
+ /* No average interference value => no band */
+ if (lchan->meas.interf_meas_avg_dbm == 0)
+ continue;
+
+ /* Only for GSM_LCHAN_{SDCCH,TCH_F,TCH_H,PDTCH} */
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ /* We're not interested in active CS lchans */
+ if (lchan->state == LCHAN_S_ACTIVE)
+ continue;
+ break;
+ case GSM_LCHAN_PDTCH:
+ break;
+ default:
+ continue;
+ }
+
+ msgb_v_put(nmsg, gsm_lchan2chan_nr_rsl(lchan));
+ msgb_v_put(nmsg, (lchan->meas.interf_band & 0x07) << 5);
+ }
+ }
+
+ /* Calculate length of the V part */
+ *len = msgb_l3len(nmsg) - 2;
+
rsl_trx_push_hdr(nmsg, RSL_MT_RF_RES_IND);
nmsg->trx = trx;
@@ -285,7 +557,7 @@ int rsl_tx_rf_res(struct gsm_bts_trx *trx)
}
/*
- * common channel releated messages
+ * common channel related messages
*/
/* 8.5.1 BCCH INFOrmation is received */
@@ -298,7 +570,13 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg)
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));
+ const uint8_t *si_buf;
+ uint8_t prev_bs_ag_blks_res = 0xff; /* 0xff = unknown */
+
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg);
+ }
/* 9.3.30 System Info Type */
if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
@@ -325,7 +603,8 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg)
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) {
+ switch (osmo_si) {
+ case SYSINFO_TYPE_2quater:
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;
@@ -353,32 +632,68 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *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 {
+ break;
+ case SYSINFO_TYPE_3:
+ /* Keep previous BS_AG_BLKS_RES, used below */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_3)) {
+ const struct gsm48_system_information_type_3 *si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ prev_bs_ag_blks_res = si3->control_channel_desc.bs_ag_blks_res;
+ }
+ /* fall-through */
+ default:
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) {
- if (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() */
+ switch (osmo_si) {
+ case SYSINFO_TYPE_3:
+ /* If CCCH config on TS0 changed, reactivate the chan with the new config: */
+ if (trx->nr == 0 && trx->bts->c0->ts[0].lchan[CCCH_LCHAN].state != LCHAN_S_NONE &&
+ num_agch(trx, "RSL") != prev_bs_ag_blks_res) {
trx->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
LCHAN_REL_ACT_REACT;
+ lchan_deactivate(&trx->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ /* will be reactivated by (see OS#1575):
+ * - bts-trx: lchan_deactivate()
+ * - sysmo,lc15,oc2g: lchan_deactivate()....[async]...sapi_deactivate_cb() */
}
/* decode original SI3 Rest Octets as sent by BSC */
- const uint8_t *si3_ro_buf = (uint8_t *) GSM_BTS_SI(bts, osmo_si);
- si3_ro_buf += offsetof(struct gsm48_system_information_type_3, rest_octets);
- osmo_gsm48_rest_octets_si3_decode(&bts->si3_ro_decoded, si3_ro_buf);
+ si_buf = (const uint8_t *) GSM_BTS_SI(bts, osmo_si);
+ si_buf += offsetof(struct gsm48_system_information_type_3, rest_octets);
+ osmo_gsm48_rest_octets_si3_decode(&bts->si3_ro_decoded, si_buf);
/* patch out GPRS indicator from binary if PCU is not connected; will be enabled
* after PCU connects */
regenerate_si3_restoctets(bts);
+ pcu_tx_si(trx->bts, SYSINFO_TYPE_3, true);
+ break;
+ case SYSINFO_TYPE_4:
+ /* decode original SI4 Rest Octets as sent by BSC */
+ si_buf = (const uint8_t *) GSM_BTS_SI(bts, osmo_si);
+ int si4_ro_offset = get_si4_ro_offset(si_buf);
+ if (si4_ro_offset > 0) {
+ osmo_gsm48_rest_octets_si4_decode(&bts->si4_ro_decoded,
+ si_buf + si4_ro_offset,
+ GSM_MACBLOCK_LEN - si4_ro_offset);
+ /* patch out GPRS indicator from binary if PCU is not connected; will be
+ * enabled after PCU connects */
+ regenerate_si4_restoctets(bts);
+ }
+ break;
+ case SYSINFO_TYPE_1:
+ /* Get the position of the NCH, if enabled. */
+ trx->bts->asci.pos_nch = pos_nch(trx, "BCCH INFO");
+ pcu_tx_si(trx->bts, SYSINFO_TYPE_1, true);
+ break;
+ case SYSINFO_TYPE_2:
+ case SYSINFO_TYPE_13:
+ pcu_tx_si(trx->bts, osmo_si, true);
+ break;
+ default:
+ break;
}
- 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))
@@ -393,10 +708,20 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg)
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);
- if (SYSINFO_TYPE_3 == osmo_si)
+ switch (osmo_si) {
+ case SYSINFO_TYPE_13:
+ pcu_tx_si(trx->bts, SYSINFO_TYPE_13, false);
+ break;
+ case SYSINFO_TYPE_3:
memset(&bts->si3_ro_decoded, 0, sizeof(bts->si3_ro_decoded));
+ pcu_tx_si(trx->bts, SYSINFO_TYPE_3, false);
+ break;
+ case SYSINFO_TYPE_1:
+ pcu_tx_si(trx->bts, SYSINFO_TYPE_1, false);
+ break;
+ default:
+ break;
+ }
}
osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
@@ -465,7 +790,10 @@ static int rsl_rx_paging_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
const uint8_t *identity_lv;
int rc;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg);
+ }
if (!TLVP_PRESENT(&tp, RSL_IE_PAGING_GROUP) ||
!TLVP_PRESENT(&tp, RSL_IE_MS_IDENTITY))
@@ -500,7 +828,10 @@ static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
bool extended_cbch = false;
int rc;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg);
+ }
if (!TLVP_PRESENT(&tp, RSL_IE_CB_CMD_TYPE) ||
!TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG))
@@ -522,6 +853,100 @@ static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
return 0;
}
+/* Broadcast notification about new VGCS/VBS call on every dedicated channel.
+ * This is required for MSs that are currently in dedicated mode that there is an ongoing call and on which channel
+ * the call is active. Most MSs in dedicated mode may not be able to receive the NCH otherwise.
+ * MSs that do not support ASCI will ignore it, as it is an unsupported message for them.
+ */
+static int asci_broadcast_facch(struct gsm_bts *bts, const uint8_t *group_call_ref, const uint8_t *chan_desc,
+ uint8_t chan_desc_len, unsigned int count)
+{
+ uint8_t notif[23];
+ struct msgb *msg, *cmsg;
+ struct gsm_bts_trx *trx;
+ struct gsm_lchan *lchan;
+ unsigned int tn, ln, n;
+ int rc;
+
+ rc = bts_asci_notify_facch_gen_msg(bts, notif, group_call_ref, chan_desc, chan_desc_len);
+ if (rc < 0)
+ return rc;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ for (ln = 0; ln < ARRAY_SIZE(trx->ts[tn].lchan); ln++) {
+ lchan = &trx->ts[tn].lchan[ln];
+ if (!lchan_is_dcch(lchan))
+ continue;
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+ msg = rsl_rll_simple(RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), 0x00, 0);
+ msg->l3h = msg->tail; /* emulate rsl_rx_rll() behaviour */
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, sizeof(notif), (uint8_t *) &notif);
+ for (n = 1; n < count; n++) {
+ cmsg = msgb_copy(msg, "FACCH copy");
+ lapdm_rslms_recvmsg(cmsg, &lchan->lapdm_ch);
+ }
+ lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Number of times to broadcast ASCI call on every dedicated channel. */
+#define ASCI_BROADCAST_NUM 3
+
+/* 8.5.10 NOTIFICATION COMMAND */
+static int rsl_rx_notification_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_cchan_hdr *cch = msgb_l2(msg);
+ struct tlv_parsed tp;
+ uint8_t command_indicator;
+ int rc;
+
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg);
+ }
+
+ if (cch->chan_nr != RSL_CHAN_PCH_AGCH) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): chan nr is not Downlink CCCH\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, &cch->chan_nr, NULL, msg);
+ }
+
+ if (!TLVP_PRES_LEN(&tp, RSL_IE_CMD_INDICATOR, 1))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg);
+ command_indicator = *TLVP_VAL(&tp, RSL_IE_CMD_INDICATOR);
+
+ switch (command_indicator) {
+ case RSL_CMD_INDICATOR_START:
+ /* we need at least a Group Call Reference to start notification */
+ if (!TLVP_PRES_LEN(&tp, RSL_IE_GROUP_CALL_REF, 5))
+ return rsl_tx_error_report(trx, RSL_ERR_OPT_IE_ERROR, &cch->chan_nr, NULL, msg);
+ rc = bts_asci_notification_add(trx->bts, TLVP_VAL(&tp, RSL_IE_GROUP_CALL_REF),
+ TLVP_VAL(&tp, RSL_IE_CHAN_DESC), TLVP_LEN(&tp, RSL_IE_CHAN_DESC),
+ (struct rsl_ie_nch_drx_info *) TLVP_VAL(&tp, RSL_IE_NCH_DRX_INFO));
+ /* Broadcast to FACCH */
+ asci_broadcast_facch(trx->bts, TLVP_VAL(&tp, RSL_IE_GROUP_CALL_REF), TLVP_VAL(&tp, RSL_IE_CHAN_DESC),
+ TLVP_LEN(&tp, RSL_IE_CHAN_DESC), ASCI_BROADCAST_NUM);
+ break;
+ case RSL_CMD_INDICATOR_STOP:
+ if (!TLVP_PRES_LEN(&tp, RSL_IE_GROUP_CALL_REF, 5)) {
+ /* interpret this as stopping of all notification */
+ rc = bts_asci_notification_reset(trx->bts);
+ } else {
+ rc = bts_asci_notification_del(trx->bts, TLVP_VAL(&tp, RSL_IE_GROUP_CALL_REF));
+ }
+ break;
+ default:
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, &cch->chan_nr, NULL, msg);
+ }
+
+ return rc;
+}
+
/* OSMO_ETWS_CMD - proprietary extension as TS 48.058 has no standardized way to do this :( */
static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
{
@@ -529,7 +954,10 @@ static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
struct gsm_bts *bts = trx->bts;
struct tlv_parsed tp;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg);
+ }
if (!TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG))
return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg);
@@ -571,10 +999,19 @@ static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
* \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] osmo_si Sytstem Information 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)
{
+ /* Special case for short header SI. Do not pre-fix the two-byte UI header. */
+ switch (osmo_si) {
+ case SYSINFO_TYPE_10:
+ (*valid) |= (1 << osmo_si);
+ memset(buf, GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t));
+ memcpy(buf, current, len);
+ return;
+ }
+
/* 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",
@@ -593,7 +1030,7 @@ static inline void lapdm_ui_prefix(uint8_t *buf, uint32_t *valid, const uint8_t
/*! 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] osmo_si Sytstem Information 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)
{
@@ -603,7 +1040,7 @@ static inline void lapdm_ui_prefix_bts(struct gsm_bts *bts, const uint8_t *curre
/*! 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] osmo_si Sytstem Information 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)
{
@@ -618,7 +1055,10 @@ static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg)
uint8_t rsl_si;
enum osmo_sysinfo_type osmo_si;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ }
/* 9.3.30 System Info Type */
if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
@@ -682,13 +1122,256 @@ static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg)
}
+/* Parser for ip.access specific MS/BS Power parameters */
+static int parse_power_ctrl_params(struct gsm_power_ctrl_params *params,
+ const uint8_t *data, size_t data_len)
+{
+ const struct tlv_p_entry *ie;
+ struct tlv_parsed tp[3];
+ unsigned int i;
+ int rc;
+
+ /* There can be multiple RSL_IPAC_EIE_MEAS_AVG_CFG, so we use tlv_parse2() */
+ rc = tlv_parse2(&tp[0], ARRAY_SIZE(tp), &rsl_ipac_eie_tlvdef,
+ data, data_len, 0, 0);
+ if (rc < 0)
+ return rc;
+
+ /* Either of RSL_IPAC_EIE_{BS,MS}_PWR_CTL must be present */
+ if (TLVP_PRESENT(&tp[0], RSL_IPAC_EIE_BS_PWR_CTL) &&
+ TLVP_PRESENT(&tp[0], RSL_IPAC_EIE_MS_PWR_CTL))
+ return -EINVAL;
+
+ /* (TV) Thresholds: {L,U}_RXLEV_XX_P and {L,U}_RXQUAL_XX_P */
+ if ((ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_BS_PWR_CTL)) != NULL ||
+ (ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_MS_PWR_CTL)) != NULL) {
+ const struct ipac_preproc_pc_thresh *thresh;
+
+ thresh = (const struct ipac_preproc_pc_thresh *) ie->val;
+
+ params->rxlev_meas.lower_thresh = thresh->l_rxlev;
+ params->rxlev_meas.upper_thresh = thresh->u_rxlev;
+
+ params->rxqual_meas.lower_thresh = thresh->l_rxqual;
+ params->rxqual_meas.upper_thresh = thresh->u_rxqual;
+ }
+
+ /* Osmocom extension, C/I related thresholds: */
+ if (TLVP_PRES_LEN(&tp[0], RSL_IPAC_EIE_OSMO_MS_PWR_CTL, sizeof(struct osmo_preproc_pc_thresh))) {
+ const struct osmo_preproc_pc_thresh *osmo_thresh;
+ ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_OSMO_MS_PWR_CTL);
+ osmo_thresh = (const struct osmo_preproc_pc_thresh *) ie->val;
+ params->ci_fr_meas.lower_thresh = osmo_thresh->l_ci_fr;
+ params->ci_fr_meas.upper_thresh = osmo_thresh->u_ci_fr;
+
+ params->ci_hr_meas.lower_thresh = osmo_thresh->l_ci_hr;
+ params->ci_hr_meas.upper_thresh = osmo_thresh->u_ci_hr;
+
+ params->ci_amr_fr_meas.lower_thresh = osmo_thresh->l_ci_amr_fr;
+ params->ci_amr_fr_meas.upper_thresh = osmo_thresh->u_ci_amr_fr;
+
+ params->ci_amr_hr_meas.lower_thresh = osmo_thresh->l_ci_amr_hr;
+ params->ci_amr_hr_meas.upper_thresh = osmo_thresh->u_ci_amr_hr;
+
+ params->ci_sdcch_meas.lower_thresh = osmo_thresh->l_ci_sdcch;
+ params->ci_sdcch_meas.upper_thresh = osmo_thresh->u_ci_sdcch;
+
+ params->ci_gprs_meas.lower_thresh = osmo_thresh->l_ci_gprs;
+ params->ci_gprs_meas.upper_thresh = osmo_thresh->u_ci_gprs;
+ }
+
+ /* (TV) PC Threshold Comparators */
+ if ((ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_PC_THRESH_COMP)) != NULL) {
+ const struct ipac_preproc_pc_comp *thresh_comp;
+
+ thresh_comp = (const struct ipac_preproc_pc_comp *) ie->val;
+
+ /* RxLev: P1, N1, P2, N2 (see 3GPP TS 45.008, A.3.2.1, a & b) */
+ params->rxlev_meas.lower_cmp_p = thresh_comp->p1;
+ params->rxlev_meas.lower_cmp_n = thresh_comp->n1;
+ params->rxlev_meas.upper_cmp_p = thresh_comp->p2;
+ params->rxlev_meas.upper_cmp_n = thresh_comp->n2;
+
+ /* RxQual: P3, N3, P4, N4 (see 3GPP TS 45.008, A.3.2.1, c & d) */
+ params->rxqual_meas.lower_cmp_p = thresh_comp->p3;
+ params->rxqual_meas.lower_cmp_n = thresh_comp->n3;
+ params->rxqual_meas.upper_cmp_p = thresh_comp->p4;
+ params->rxqual_meas.upper_cmp_n = thresh_comp->n4;
+
+ /* Minimum interval between power level changes (P_Con_INTERVAL) */
+ params->ctrl_interval = thresh_comp->pc_interval;
+
+ /* Power increase / reduce step size: POWER_{INC,RED}_STEP_SIZE */
+ params->inc_step_size_db = thresh_comp->inc_step_size;
+ params->red_step_size_db = thresh_comp->red_step_size;
+ }
+
+ /* Osmocom extension, C/I related thresholds: */
+ if (TLVP_PRES_LEN(&tp[0], RSL_IPAC_EIE_OSMO_PC_THRESH_COMP, sizeof(struct osmo_preproc_pc_thresh))) {
+ const struct osmo_preproc_pc_comp *osmo_thresh_comp;
+ ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_OSMO_PC_THRESH_COMP);
+ osmo_thresh_comp = (const struct osmo_preproc_pc_comp *) ie->val;
+ #define SET_PREPROC_PC(PARAMS, FROM, TYPE) \
+ (PARAMS)->TYPE##_meas.lower_cmp_p = (FROM)->TYPE.lower_p; \
+ (PARAMS)->TYPE##_meas.lower_cmp_n = (FROM)->TYPE.lower_n; \
+ (PARAMS)->TYPE##_meas.upper_cmp_p = (FROM)->TYPE.upper_p; \
+ (PARAMS)->TYPE##_meas.upper_cmp_n = (FROM)->TYPE.upper_n
+ SET_PREPROC_PC(params, osmo_thresh_comp, ci_fr);
+ SET_PREPROC_PC(params, osmo_thresh_comp, ci_hr);
+ SET_PREPROC_PC(params, osmo_thresh_comp, ci_amr_fr);
+ SET_PREPROC_PC(params, osmo_thresh_comp, ci_amr_hr);
+ SET_PREPROC_PC(params, osmo_thresh_comp, ci_sdcch);
+ SET_PREPROC_PC(params, osmo_thresh_comp, ci_gprs);
+ #undef SET_PREPROC_PC
+ }
+
+ /* (TLV) Measurement Averaging parameters for RxLev/RxQual */
+ for (i = 0; i < ARRAY_SIZE(tp); i++) {
+ const struct ipac_preproc_ave_cfg *ave_cfg;
+ struct gsm_power_ctrl_meas_params *mp;
+
+ ie = TLVP_GET(&tp[i], RSL_IPAC_EIE_MEAS_AVG_CFG);
+ if (ie == NULL)
+ break;
+
+ if (ie->len < sizeof(*ave_cfg))
+ return -EINVAL;
+
+ ave_cfg = (const struct ipac_preproc_ave_cfg *) ie->val;
+
+ switch (ave_cfg->param_id) {
+ case IPAC_RXQUAL_AVE:
+ mp = &params->rxqual_meas;
+ break;
+ case IPAC_RXLEV_AVE:
+ mp = &params->rxlev_meas;
+ break;
+ default:
+ /* Skip unknown parameters */
+ continue;
+ }
+
+ mp->h_reqave = ave_cfg->h_reqave;
+ mp->h_reqt = ave_cfg->h_reqt;
+
+ mp->algo = ave_cfg->ave_method + 1;
+ switch (ave_cfg->ave_method) {
+ case IPAC_OSMO_EWMA_AVE:
+ if (ie->len > sizeof(*ave_cfg))
+ mp->ewma.alpha = ave_cfg->params[0];
+ break;
+
+ /* FIXME: not implemented */
+ case IPAC_UNWEIGHTED_AVE:
+ case IPAC_WEIGHTED_AVE:
+ case IPAC_MEDIAN_AVE:
+ break;
+ }
+ }
+
+ /* (TLV) Measurement Averaging parameters for C/I (Osmocom extension)*/
+ if (TLVP_PRES_LEN(&tp[0], RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG, sizeof(struct osmo_preproc_ave_cfg))) {
+ ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG);
+ const struct osmo_preproc_ave_cfg *cfg = (const struct osmo_preproc_ave_cfg *) ie->val;
+ unsigned params_offset = 0;
+ #define SET_AVE_CFG(PARAMS, FROM, TYPE, PARAM_OFFSET) do {\
+ if ((FROM)->TYPE.ave_enabled) { \
+ (PARAMS)->TYPE##_meas.h_reqave = (FROM)->TYPE.h_reqave; \
+ (PARAMS)->TYPE##_meas.h_reqt = (FROM)->TYPE.h_reqt; \
+ (PARAMS)->TYPE##_meas.algo = (FROM)->TYPE.ave_method + 1; \
+ switch ((FROM)->TYPE.ave_method) { \
+ case IPAC_OSMO_EWMA_AVE: \
+ if (ie->len > sizeof(*cfg) + (PARAM_OFFSET)) { \
+ (PARAMS)->TYPE##_meas.ewma.alpha = (FROM)->params[PARAM_OFFSET]; \
+ (PARAM_OFFSET)++; \
+ } \
+ break; \
+ /* FIXME: not implemented */ \
+ case IPAC_UNWEIGHTED_AVE: \
+ case IPAC_WEIGHTED_AVE: \
+ case IPAC_MEDIAN_AVE: \
+ break; \
+ } \
+ } else { \
+ (PARAMS)->TYPE##_meas.algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; \
+ } \
+ } while(0)
+ SET_AVE_CFG(params, cfg, ci_fr, params_offset);
+ SET_AVE_CFG(params, cfg, ci_hr, params_offset);
+ SET_AVE_CFG(params, cfg, ci_amr_fr, params_offset);
+ SET_AVE_CFG(params, cfg, ci_amr_hr, params_offset);
+ SET_AVE_CFG(params, cfg, ci_sdcch, params_offset);
+ SET_AVE_CFG(params, cfg, ci_gprs, params_offset);
+ #undef SET_AVE_CFG
+ }
+
+ return 0;
+}
+
+/* ip.access specific Measurement Pre-processing Defaults for MS/BS Power control */
+static int rsl_rx_meas_preproc_dft(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ const struct gsm_bts *bts = trx->bts;
+ struct gsm_power_ctrl_params *params;
+ const struct tlv_p_entry *ie;
+ struct tlv_parsed tp;
+
+ LOGPTRX(trx, DRSL, LOGL_INFO, "Rx Measurement Pre-processing Defaults\n");
+
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ }
+
+ /* TLV (O) BS Power Parameters IE */
+ if ((ie = TLVP_GET(&tp, RSL_IE_BS_POWER_PARAM)) != NULL) {
+ /* Allocate a new chunk and initialize with default values */
+ params = talloc(trx, struct gsm_power_ctrl_params);
+ power_ctrl_params_def_reset(params, true);
+
+ if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) == 0) {
+ /* Initially it points to the global defaults */
+ if (trx->bs_dpc_params != &bts->bs_dpc_params)
+ talloc_free(trx->bs_dpc_params);
+ trx->bs_dpc_params = params;
+ } else {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "Failed to parse BS Power Parameters IE\n");
+ rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ talloc_free(params);
+ }
+ }
+
+ /* TLV (O) MS Power Parameters IE */
+ if ((ie = TLVP_GET(&tp, RSL_IE_MS_POWER_PARAM)) != NULL) {
+ /* Allocate a new chunk and initialize with default values */
+ params = talloc(trx, struct gsm_power_ctrl_params);
+ power_ctrl_params_def_reset(params, false);
+
+ if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) == 0) {
+ /* Initially it points to the global defaults */
+ if (trx->ms_dpc_params != &bts->ms_dpc_params)
+ talloc_free(trx->ms_dpc_params);
+ trx->ms_dpc_params = params;
+ } else {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "Failed to parse MS Power Parameters IE\n");
+ rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ talloc_free(params);
+ }
+ }
+
+ 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 abis_rsl_cchan_hdr *cch = msgb_l2(msg);
struct tlv_parsed tp;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg);
+ }
if (!TLVP_PRESENT(&tp, RSL_IE_FULL_IMM_ASS_INFO))
return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg);
@@ -701,6 +1384,45 @@ static int rsl_rx_imm_ass(struct gsm_bts_trx *trx, struct msgb *msg)
msg->l2h = NULL;
msg->len = TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO);
+ /* Early Immediate Assignment: when there is a lot of latency on Abis, the Abis roundtrip of Chan Activ -> Chan
+ * Activ ACK -> Immediate Assignment may take so long that each MS sends a second RACH for Chan Rqd, reserving
+ * two SDCCH for each request but using only one. To help with that, the Early IA feature in osmo-bsc sends the
+ * Immediate Assignment without waiting for the Channel Activation ACK. This may then be too early, and the MS
+ * may not be able to establish a channel. So to help with Early IA, look up whether the target lchan is already
+ * active. If not, then hold back the RR Immediate Assignment message, and send it once L1 has confirmed that
+ * the channel is active. Hence we still wait for the activation, but don't need the Abis roundtrip of Activ ACK
+ * -> Immediate Assignment via the BSC.
+ * If anything is wrong with the sizes or the lchan lookup, behave normally, i.e. do not do the RR IA caching,
+ * but just send the RR message to the MS as-is.
+ * 'trx' here is the TRX of the BCCH channel. To find the correct TRX for the IMM ASS target, we need to look up
+ * the ARFCN that is contained in the IMM ASS message. When frequency hopping is enabled, there will not be an
+ * ARFCN, so we cannot support early-IA with frequency hopping enabled. */
+ if (msg->len >= sizeof(struct gsm48_imm_ass)) {
+ struct gsm48_imm_ass *rr_ia = (void*)msg->data;
+ if (rr_ia->chan_desc.h0.h == 0) {
+ /* hopping is disabled. */
+ struct gsm_bts_trx *ia_target_trx;
+ uint16_t arfcn;
+ arfcn = (rr_ia->chan_desc.h0.arfcn_high << 8) + rr_ia->chan_desc.h0.arfcn_low;
+
+ ia_target_trx = trx_lookup_by_arfcn(&trx->bts->trx_list, arfcn);
+ if (ia_target_trx) {
+ /* found the ARFCN's trx */
+ struct gsm_lchan *ia_target_lchan;
+ ia_target_lchan = lchan_lookup(ia_target_trx, rr_ia->chan_desc.chan_nr, "Early IA check: ");
+ if (ia_target_lchan && ia_target_lchan->state != LCHAN_S_ACTIVE) {
+ /* Target lchan is not yet active. Cache the IA.
+ * If a previous IA is still lingering, free it. */
+ msgb_free(ia_target_lchan->early_rr_ia);
+ ia_target_lchan->early_rr_ia = msg;
+
+ /* return 1 means: don't msgb_free() the msg */
+ return 1;
+ }
+ }
+ }
+ }
+
/* 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 */
@@ -738,7 +1460,7 @@ static int tx_rf_rel_ack(struct gsm_lchan *lchan, uint8_t chan_nr)
/* 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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
bool send_rel_ack;
switch (lchan->rel_act_kind) {
@@ -748,26 +1470,40 @@ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan)
case LCHAN_REL_ACT_PCU:
switch (lchan->ts->pchan) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
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;
+ if (lchan->ts->dyn.pchan_want != GSM_PCHAN_PDCH) {
+ /* Continue to ack the release below. (This is a non-standard rel ack invented
+ * specifically for GSM_PCHAN_OSMO_DYN). */
+ /* remember the fact that the TS is now released */
+ lchan->ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ send_rel_ack = true;
+ } else {
+ /* Administrteively locked TRX, no need to
+ inform BSC. Keep pchan_is for when we are
+ unlocked again, since lower layers are stil
+ lconfigured for PDCH but we simply annonced
+ non-availability to PCU */
+ send_rel_ack = false;
+ }
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;
+ case GSM_PCHAN_PDCH:
+ /* Release was instructed by the BTS, for instance because the TRX is
+ * administrateively Locked */
+ send_rel_ack = false;
+ break;
default:
LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "PCU rel ack for unexpected lchan kind %s\n",
- gsm_pchan_name(lchan->rel_act_kind));
+ gsm_pchan_name(lchan->ts->pchan));
/* Release certainly was not requested by the BSC via RSL, so don't ack. */
send_rel_ack = false;
break;
@@ -782,20 +1518,14 @@ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan)
}
if (!send_rel_ack) {
- LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "%s not sending REL ACK\n", gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "not sending REL ACK\n");
return 0;
}
- LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN REL ACK\n",
+ LOGP(DRSL, LOGL_INFO, "%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);
}
@@ -804,10 +1534,10 @@ 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 chan_nr = gsm_lchan2chan_nr_rsl(lchan);
uint8_t ie[2];
- LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN ACT ACK\n",
+ LOGP(DRSL, LOGL_INFO, "%s (ss=%d) %s Tx CHAN ACT ACK\n",
gsm_ts_and_pchan_name(lchan->ts), lchan->nr,
gsm_lchant_name(lchan->type));
@@ -826,28 +1556,50 @@ static int rsl_tx_chan_act_ack(struct gsm_lchan *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)
+/* common helper function for *_DETECT */
+static int _rsl_tx_detect(struct gsm_lchan *lchan, uint8_t msg_type, uint8_t *acc_delay)
{
struct msgb *msg;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
-
- LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending HANDOver DETect\n");
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
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);
+ if (acc_delay)
+ msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, *acc_delay);
- rsl_dch_push_hdr(msg, RSL_MT_HANDO_DET, chan_nr);
+ rsl_dch_push_hdr(msg, msg_type, chan_nr);
msg->trx = lchan->ts->trx;
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)
+{
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending HANDOver DETect\n");
+
+ return _rsl_tx_detect(lchan, RSL_MT_HANDO_DET, ho_delay);
+}
+
+/* 8.4.22 sending LISTENER DETection */
+int rsl_tx_listener_det(struct gsm_lchan *lchan, uint8_t *acc_delay)
+{
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending LISTENER DETect\n");
+
+ return _rsl_tx_detect(lchan, RSL_MT_LISTENER_DET, acc_delay);
+}
+
+/* 8.4.21 sending TALKER DETection */
+int rsl_tx_talker_det(struct gsm_lchan *lchan, uint8_t *acc_delay)
+{
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending TALKER DETect\n");
+
+ return _rsl_tx_detect(lchan, RSL_MT_TALKER_DET, acc_delay);
+}
+
/* 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)
@@ -855,7 +1607,7 @@ static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8
struct msgb *msg;
if (lchan)
- LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "");
else
LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr);
LOGPC(DRSL, LOGL_NOTICE, "Sending Channel Activated NACK: cause = 0x%02x\n", cause);
@@ -872,14 +1624,14 @@ static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8
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);
+ return _rsl_tx_chan_act_nack(lchan->ts->trx, gsm_lchan2chan_nr_rsl(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) {
- LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "not sending CHAN ACT %s\n",
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "not sending CHAN ACT %s\n",
cause ? "NACK" : "ACK");
return 0;
}
@@ -890,10 +1642,10 @@ int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause)
}
/* 8.4.4 sending CONNection FAILure */
-int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause)
+int rsl_tx_conn_fail(const struct gsm_lchan *lchan, uint8_t cause)
{
struct msgb *msg;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Sending Connection Failure: cause = 0x%02x\n", cause);
@@ -996,17 +1748,13 @@ 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;
+ memset(&lchan->bs_power_ctrl, 0, sizeof(lchan->bs_power_ctrl));
+ lchan->ta_ctrl.current = 0;
copy_sacch_si_to_lchan(lchan);
memset(&lchan->tch, 0, sizeof(lchan->tch));
}
@@ -1015,13 +1763,14 @@ static void clear_lchan_for_pdch_activ(struct gsm_lchan *lchan)
* 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)
+static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts)
{
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:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
break;
case GSM_PCHAN_PDCH:
/* Only the first lchan matters for PDCH */
@@ -1035,9 +1784,6 @@ static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg)
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);
@@ -1045,14 +1791,23 @@ static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg)
static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr)
{
- uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK;
+ uint8_t cbits = chan_nr >> 3;
switch (cbits) {
- case RSL_CHAN_Bm_ACCHs:
+ case ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs:
return GSM_PCHAN_TCH_F;
- case RSL_CHAN_Lm_ACCHs:
- case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1):
+ case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0):
+ case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(1):
return GSM_PCHAN_TCH_H;
- case RSL_CHAN_OSMO_PDCH:
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(1):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(2):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(3):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(4):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(5):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(6):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(7):
+ return GSM_PCHAN_SDCCH8_SACCH8C;
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH:
return GSM_PCHAN_PDCH;
default:
LOGP(DRSL, LOGL_ERROR,
@@ -1062,24 +1817,145 @@ static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr)
}
}
+/* Parse RSL_IE_OSMO_REP_ACCH_CAP */
+static int parse_repeated_acch_capability(struct gsm_lchan *lchan, struct tlv_parsed *tp)
+{
+ /* 3GPP TS 24.008, section 10.5.1.7 defines a Repeated ACCH Capability
+ * bit that indicates if REPEATED FACCH/SACCH is supported or not.
+ * Unfortunately there is not 3gpp spec that describes how this bit
+ * should be communicated in the RSL CHANNEL ACTIVATION. For osmo-bts
+ * we will use a propritary IE. */
+
+ memset(&lchan->rep_acch_cap, 0, sizeof(lchan->rep_acch_cap));
+
+ if (!TLVP_PRES_LEN(tp, RSL_IE_OSMO_REP_ACCH_CAP, sizeof(lchan->rep_acch_cap)))
+ return 0;
+
+ if (!osmo_bts_has_feature(lchan->ts->trx->bts->features, BTS_FEAT_ACCH_REP))
+ return -RSL_ERR_OPT_IE_ERROR;
+
+ memcpy(&lchan->rep_acch_cap, TLVP_VAL(tp, RSL_IE_OSMO_REP_ACCH_CAP),
+ sizeof(lchan->rep_acch_cap));
+
+ return 0;
+}
+
+/* Parse RSL_IE_OSMO_TOP_ACCH_CAP */
+static int parse_temporary_overpower_acch_capability(struct gsm_lchan *lchan,
+ const struct tlv_parsed *tp)
+{
+ memset(&lchan->top_acch_cap, 0, sizeof(lchan->top_acch_cap));
+
+ if (!TLVP_PRES_LEN(tp, RSL_IE_OSMO_TEMP_OVP_ACCH_CAP, sizeof(lchan->top_acch_cap)))
+ return 0;
+
+ if (!osmo_bts_has_feature(lchan->ts->trx->bts->features, BTS_FEAT_ACCH_TEMP_OVP))
+ return -RSL_ERR_OPT_IE_ERROR;
+
+ memcpy(&lchan->top_acch_cap,
+ TLVP_VAL(tp, RSL_IE_OSMO_TEMP_OVP_ACCH_CAP),
+ sizeof(lchan->top_acch_cap));
+
+ /* Simplify checking whether the overpower is enabled at all: allow
+ * testing just one parameter (overpower_db > 0) instead of all three. */
+ if (!lchan->top_acch_cap.sacch_enable && !lchan->top_acch_cap.facch_enable)
+ lchan->top_acch_cap.overpower_db = 0;
+
+ return 0;
+}
+
+/* Parse (O) MultiRate configuration IE (see 9.3.52) */
+static int parse_multirate_config(struct gsm_lchan *lchan,
+ const struct tlv_parsed *tp)
+{
+ int rc;
+
+ if (!TLVP_PRESENT(tp, RSL_IE_MR_CONFIG)) {
+ /* Included if the Channel Mode indicates that a multi-rate codec is used */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Missing MultiRate conf IE "
+ "(TCH mode is %s)\n", gsm48_chan_mode_name(lchan->tch_mode));
+ /* Init lchan->tch.amr_mr with hard-coded default values */
+ amr_init_mr_conf_def(lchan);
+ goto parsed;
+ }
+ return 0;
+ }
+
+ /* Included if the Channel Mode indicates that a multi-rate codec is used */
+ if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Unexpected MultiRate conf IE "
+ "(TCH mode is %s)\n", gsm48_chan_mode_name(lchan->tch_mode));
+ return -RSL_ERR_OPT_IE_ERROR;
+ }
+
+ rc = amr_parse_mr_conf(&lchan->tch.amr_mr,
+ TLVP_VAL(tp, RSL_IE_MR_CONFIG),
+ TLVP_LEN(tp, RSL_IE_MR_CONFIG));
+ if (rc < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n");
+ return -RSL_ERR_IE_CONTENT;
+ }
+
+parsed:
+ amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), &lchan->tch.amr_mr);
+ lchan->tch.last_cmr = AMR_CMR_NONE;
+ return 0;
+}
+
/* 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 gsm_bts_trx_ts *primary_ts;
struct tlv_parsed tp;
- uint8_t type;
+ const struct tlv_p_entry *ie;
+ uint8_t type, cause;
+ bool reactivation = false;
int rc;
- if (lchan->state != LCHAN_S_NONE) {
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_PROTO);
+ }
+
+ /* 9.3.3 Activation Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) {
+ LOGPLCHAN(lchan, 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);
+ if ((type & RSL_ACT_TYPE_REACT)) {
+ type -= RSL_ACT_TYPE_REACT;
+ reactivation = true;
+ }
+
+ /* If Activation Type is IMMEDIATE ASSIGNMENT, we expect L3 info with establishment. */
+ lchan->l3_info_estab = (type == RSL_ACT_INTRA_IMM_ASS);
+
+ if (!reactivation && lchan->state != LCHAN_S_NONE) {
LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "error: lchan is not available, but in state: %s.\n",
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) {
+ if (reactivation && lchan->state == LCHAN_S_NONE) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "error: reactivation on inactive lchan.\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL);
+ }
+
+ /* We need to pick the real TS here to check NM state: */
+ primary_ts = ts->vamos.is_shadow ? ts->vamos.peer : ts;
+ if (primary_ts->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
+ primary_ts->mo.nm_state.availability != NM_AVSTATE_OK) {
+ LOGP(DRSL, LOGL_ERROR, "%s rx chan activ but TS not in nm_state oper=ENABLED avail=OK, nack!\n",
+ gsm_ts_and_pchan_name(ts));
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_RR_UNAVAIL);
+ }
+
+ if (ts->pchan == GSM_PCHAN_OSMO_DYN) {
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));
@@ -1089,39 +1965,37 @@ static int rsl_rx_chan_activ(struct msgb *msg)
* 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);
+ rc = dyn_ts_l1_reconnect(ts);
if (rc)
return rsl_tx_chan_act_nack(lchan, RSL_ERR_NORMAL_UNSPEC);
+ /* will be fed back to rsl_rx_chan_activ() later */
+ OSMO_ASSERT(lchan->pending_chan_activ == NULL);
+ lchan->pending_chan_activ = msg;
/* indicate that the msgb should not be freed. */
return 1;
}
}
- LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, "rx Channel Activation in state: %s.\n",
- 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));
+ /* Initialize MS Power Control defaults */
+ lchan->ms_power_ctrl = (struct lchan_power_ctrl_state) {
+ .max = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0),
+ .current = lchan->ms_power_ctrl.max,
+ };
- /* 9.3.3 Activation Type */
- if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) {
- LOGPLCHAN(lchan, 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);
+ /* Initialize BS Power Control defaults */
+ lchan->bs_power_ctrl = (struct lchan_power_ctrl_state) {
+ .max = 2 * 15, /* maximum defined in 9.3.4 */
+ .current = 0,
+ };
/* 9.3.6 Channel Mode */
if (type != RSL_ACT_OSMO_PDCH) {
- if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
- LOGPLCHAN(lchan, 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);
+ if (rsl_handle_chan_mod_ie(lchan, &tp, &cause) != 0)
+ return rsl_tx_chan_act_nack(lchan, cause);
+ if (rsl_handle_chan_ident_ie(lchan, &tp, &cause) != 0)
+ return rsl_tx_chan_act_nack(lchan, cause);
+ if (rsl_handle_osmo_tsc_ie(lchan, &tp, &cause) != 0)
+ return rsl_tx_chan_act_nack(lchan, cause);
}
/* 9.3.7 Encryption Information */
@@ -1149,28 +2023,73 @@ static int rsl_rx_chan_activ(struct msgb *msg)
}
/* 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);
+ if (TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1)) {
+ if (*TLVP_VAL(&tp, RSL_IE_BS_POWER) & (1 << 4)) {
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE,
+ "Fast Power Control is not supported\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_SERV_OPT_UNIMPL);
+ }
+
+ uint8_t red = BS_POWER2DB(*TLVP_VAL(&tp, RSL_IE_BS_POWER));
+
+ /* BS power reduction is generally not allowed on BCCH/CCCH carrier.
+ * However, we allow it in the BCCH carrier power reduction operation.
+ * Constrain BS power value by the maximum reduction for this timeslot. */
+ if (ts->trx->bts->c0 == ts->trx)
+ red = OSMO_MIN(red, ts->c0_power_red_db);
+
+ lchan->bs_power_ctrl.max = red;
+ lchan->bs_power_ctrl.current = red;
+
+ LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, "BS Power attenuation %u dB\n",
+ lchan->bs_power_ctrl.current);
+ }
+
/* 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;
+ lchan->ms_power_ctrl.max = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F;
+ lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max;
}
/* 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);
+ lchan->ta_ctrl.current = *TLVP_VAL(&tp, RSL_IE_TIMING_ADVANCE);
+
+ /* 9.3.31 (TLV) MS Power Parameters IE (vendor specific) */
+ if ((ie = TLVP_GET(&tp, RSL_IE_MS_POWER_PARAM)) != NULL) {
+ struct gsm_power_ctrl_params *params = &lchan->ms_dpc_params;
+
+ /* Parsed parameters will override per-TRX defaults */
+ memcpy(params, ts->trx->ms_dpc_params, sizeof(*params));
+
+ if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse MS Power Parameters IE\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT);
+ }
- /* 9.3.32 BS Power Parameters */
- /* 9.3.31 MS Power Parameters */
- if (TLVP_PRESENT(&tp, RSL_IE_MS_POWER_PARAM))
- lchan->ms_power_ctrl.fixed = 0;
- else {
/* Spec explicitly states BTS should only perform
* autonomous MS power control loop in BTS if 'MS Power
* Parameters' IE is present! */
- lchan->ms_power_ctrl.fixed = 1;
+ lchan->ms_power_ctrl.dpc_params = params;
+ }
+
+ /* 9.3.32 (TLV) BS Power Parameters IE (vendor specific) */
+ if ((ie = TLVP_GET(&tp, RSL_IE_BS_POWER_PARAM)) != NULL) {
+ struct gsm_power_ctrl_params *params = &lchan->bs_dpc_params;
+
+ /* Parsed parameters will override per-TRX defaults */
+ memcpy(params, ts->trx->bs_dpc_params, sizeof(*params));
+
+ /* Parsed parameters will override per-TRX defaults */
+ if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse BS Power Parameters IE\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT);
+ }
+
+ /* NOTE: it's safer to start from 0 */
+ lchan->bs_power_ctrl.current = 0;
+ lchan->bs_power_ctrl.dpc_params = params;
}
+
/* 9.3.16 Physical Context */
/* 9.3.29 SACCH Information */
@@ -1213,29 +2132,22 @@ static int rsl_rx_chan_activ(struct msgb *msg)
/* 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) {
- LOGPLCHAN(lchan, 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;
- }
+ rc = parse_multirate_config(lchan, &tp);
+ if (rc < 0)
+ return rsl_tx_chan_act_acknack(lchan, -rc);
+
/* 9.3.53 MultiRate Control */
/* 9.3.54 Supported Codec Types */
- LOGPLCHAN(lchan, DRSL, LOGL_INFO, "chan_nr=%s type=0x%02x mode=%s\n",
- rsl_chan_nr_str(dch->chan_nr), type, gsm48_chan_mode_name(lchan->tch_mode));
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "chan_nr=%s type=0x%02x=%s mode=%s\n",
+ rsl_chan_nr_str(dch->chan_nr),
+ type, get_value_string(rsl_act_type_names, 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
+ if (ts->pchan == GSM_PCHAN_OSMO_DYN
&& ts->dyn.pchan_want == GSM_PCHAN_PDCH) {
/*
* We ack the activation to the BSC right away, regardless of
@@ -1281,48 +2193,75 @@ static int rsl_rx_chan_activ(struct msgb *msg)
return 0;
}
+ /* Indicate which SAPIs should be enabled before the first RACH is received, for handover. See 3GPP TS 48.058
+ * 4.1.3 and 4.1.4.
+ *
+ * | | Timing || transmit | activate | This implementation
+ * | MS Power | Advance || on main channel | dl SACCH | activates DL SACCH
+ * -----------------------------------------------------------------------------------------
+ * async ho no * --> yes no no
+ * async ho yes * --> yes may be started no
+ * async ho yes yes --> yes may be started yes
+ * sync ho no no --> yes no no
+ * sync ho yes no --> yes may be started no
+ * sync ho yes yes --> yes shall be started yes
+ */
+ switch (type) {
+ case RSL_ACT_INTER_ASYNC:
+ case RSL_ACT_INTER_SYNC:
+ lchan->want_dl_sacch_active = (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)
+ && TLVP_PRES_LEN(&tp, RSL_IE_TIMING_ADVANCE, 1));
+ break;
+ default:
+ lchan->want_dl_sacch_active = true;
+ break;
+ }
+
/* 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);
+ rc = parse_repeated_acch_capability(lchan, &tp);
+ if (rc < 0)
+ return rsl_tx_chan_act_acknack(lchan, -rc);
+ rc = parse_temporary_overpower_acch_capability(lchan, &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;
+ /* Take the first ACCH overpower decision (if allowed): it can be
+ * enabled immediately if the RxQual threshold is disabled (0). */
+ if (lchan->top_acch_cap.overpower_db > 0)
+ lchan->top_acch_active = !lchan->top_acch_cap.rxqual;
+ else
+ lchan->top_acch_active = false;
- 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;
+ /* set ASCI channel into right state */
+ if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) {
+ if (reactivation)
+ vgcs_lchan_react(lchan);
+ else
+ vgcs_lchan_activate(lchan);
}
- /*
- * 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;
+ /* on reactivation, the channel is already activated */
+ if (reactivation) {
+ 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);
+ return 0;
}
- return pcu_tx_info_ind();
+ /* actually activate the channel in the BTS */
+ rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr);
+ if (rc < 0)
+ return rsl_tx_chan_act_acknack(lchan, -rc);
+
+ return 0;
}
/* 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;"
@@ -1333,38 +2272,7 @@ static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr)
* 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);
-
+ gsm_lchan_release(lchan, LCHAN_REL_ACT_RSL);
return 0;
}
@@ -1398,7 +2306,7 @@ static int tx_ciph_mod_compl_hack(struct gsm_lchan *lchan, uint8_t link_id,
}
}
- rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr(lchan),
+ rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr_rsl(lchan),
link_id, 1);
fake_msg->lchan = lchan;
@@ -1443,7 +2351,8 @@ static int rsl_rx_encr_cmd(struct msgb *msg)
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);
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(msg->trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg);
}
if (!TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO) ||
@@ -1512,7 +2421,7 @@ static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uin
struct msgb *msg;
if (lchan)
- LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "");
else
LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr);
LOGPC(DRSL, LOGL_NOTICE, "Tx MODE MODIFY NACK (cause = 0x%02x)\n", cause);
@@ -1533,7 +2442,7 @@ static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uin
}
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);
+ return _rsl_tx_mode_modif_nack(lchan->ts->trx, gsm_lchan2chan_nr_rsl(lchan), cause, lchan);
}
@@ -1541,7 +2450,7 @@ static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause)
static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan)
{
struct msgb *msg;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Tx MODE MODIF ACK\n");
@@ -1560,24 +2469,24 @@ 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;
+ uint8_t cause;
+ int rc;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
-
- /* 9.3.6 Channel Mode */
- if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
- LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "missing Channel Mode\n");
- return rsl_tx_mode_modif_nack(lchan, RSL_ERR_MAND_IE_ERROR);
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_PROTO);
}
- 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) {
- LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s: invalid mode: %s (wrong BSC configuration?)\n",
- gsm_ts_and_pchan_name(lchan->ts), gsm48_chan_mode_name(lchan->tch_mode));
- return rsl_tx_mode_modif_nack(lchan, RSL_ERR_SERV_OPT_UNAVAIL);
- }
+ /* 9.3.6 Channel Mode */
+ if (rsl_handle_chan_mod_ie(lchan, &tp, &cause) != 0)
+ return rsl_tx_mode_modif_nack(lchan, cause);
+ /* 9.3.5 Channel Identification */
+ if (rsl_handle_chan_ident_ie(lchan, &tp, &cause) != 0)
+ return rsl_tx_mode_modif_nack(lchan, cause);
+ /* Osmocom specific TSC IE for VAMOS */
+ if (rsl_handle_osmo_tsc_ie(lchan, &tp, &cause) != 0)
+ return rsl_tx_mode_modif_nack(lchan, cause);
/* 9.3.7 Encryption Information */
if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
@@ -1593,23 +2502,27 @@ static int rsl_rx_mode_modif(struct msgb *msg)
/* 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) {
- LOGPLCHAN(lchan, 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;
- }
+ rc = parse_multirate_config(lchan, &tp);
+ if (rc < 0)
+ return rsl_tx_mode_modif_nack(lchan, -rc);
+
/* 9.3.53 MultiRate Control */
/* 9.3.54 Supported Codec Types */
+ rc = parse_repeated_acch_capability(lchan, &tp);
+ if (rc < 0)
+ return rsl_tx_mode_modif_nack(lchan, -rc);
+ rc = parse_temporary_overpower_acch_capability(lchan, &tp);
+ if (rc < 0)
+ return rsl_tx_mode_modif_nack(lchan, -rc);
+
+ /* Immediately disable ACCH overpower if the value is 0 dB,
+ * or enable if the RxQual threshold becomes disabled (0). */
+ if (lchan->top_acch_cap.overpower_db == 0)
+ lchan->top_acch_active = false;
+ else if (lchan->top_acch_cap.rxqual == 0)
+ lchan->top_acch_active = true;
+
l1sap_chan_modify(lchan->ts->trx, dch->chan_nr);
/* FIXME: delay this until L1 says OK? */
@@ -1623,28 +2536,61 @@ static int rsl_rx_ms_pwr_ctrl(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;
+ const struct tlv_p_entry *ie;
struct tlv_parsed tp;
uint8_t pwr;
+ int max_pwr, curr_pwr;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(msg->trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg);
+ }
/* 9.3.13 MS Power (M) */
if (!TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1))
return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
pwr = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F;
- lchan->ms_power_ctrl.current = pwr;
+ lchan->ms_power_ctrl.max = pwr;
- LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Rx MS POWER CONTROL %d\n", lchan->ms_power_ctrl.current);
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Rx MS POWER CONTROL %" PRIu8 "\n", pwr);
- /* 9.3.31 MS Power Parameters (O) */
- if (TLVP_PRESENT(&tp, RSL_IE_MS_POWER_PARAM))
- lchan->ms_power_ctrl.fixed = 0;
- else {
- /* Spec explicitly states BTS should only perform
- * autonomous MS power control loop in BTS if 'MS Power
- * Parameters' IE is present! */
- lchan->ms_power_ctrl.fixed = 1;
+ /* Spec explicitly states BTS should only perform autonomous MS Power
+ * control loop in BTS if 'MS Power Parameters' IE is present! */
+ lchan->ms_power_ctrl.dpc_params = NULL;
+
+ /* 9.3.31 (TLV) MS Power Parameters IE (vendor specific) */
+ if ((ie = TLVP_GET(&tp, RSL_IE_MS_POWER_PARAM)) != NULL) {
+ struct gsm_power_ctrl_params *params = &lchan->ms_dpc_params;
+
+ /* Parsed parameters will override per-TRX defaults */
+ memcpy(params, msg->trx->ms_dpc_params, sizeof(*params));
+
+ /* Parsed parameters will override per-TRX defaults */
+ if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse MS Power Parameters IE\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT);
+ }
+
+ lchan->ms_power_ctrl.dpc_params = params;
+ }
+
+ /* Only set current to max if actual value of current
+ in dBm > value in dBm from max, or if fixed. */
+ if (lchan->ms_power_ctrl.dpc_params == NULL) {
+ lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max;
+ } else {
+ max_pwr = ms_pwr_dbm(bts->band, lchan->ms_power_ctrl.max);
+ curr_pwr = ms_pwr_dbm(bts->band, lchan->ms_power_ctrl.current);
+ if (max_pwr < 0 || curr_pwr < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR,
+ "Unable to calculate power levels to dBm: %" PRIu8 " -> %d, %" PRIu8 " -> %d\n",
+ lchan->ms_power_ctrl.max, max_pwr,
+ lchan->ms_power_ctrl.current, curr_pwr);
+ } else if (curr_pwr > max_pwr) {
+ lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max;
+ }
}
bts_model_adjst_ms_pwr(lchan);
@@ -1652,41 +2598,77 @@ static int rsl_rx_ms_pwr_ctrl(struct msgb *msg)
return 0;
}
-/* See TS 48.058 Section 9.3.4 */
-static int bs_power_attenuation_dB(uint8_t bs_power)
-{
- /* the lower nibble contains the number of 2dB steps that the BS power is reduced compared
- * to its nominal transmit power */
- return - ((bs_power & 0xF) *2);
-}
-
/* 8.4.16 BS POWER CONTROL */
static int rsl_rx_bs_pwr_ctrl(struct msgb *msg)
{
struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts_trx *trx = msg->trx;
+ const struct tlv_p_entry *ie;
struct tlv_parsed tp;
- uint8_t new_bs_power;
+ uint8_t old, new;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg);
+ }
/* 9.3.4 BS Power (M) */
if (!TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1))
- return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
+
+ if (*TLVP_VAL(&tp, RSL_IE_BS_POWER) & (1 << 4)) {
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Fast Power Control is not supported\n");
+ return rsl_tx_error_report(trx, RSL_ERR_SERV_OPT_UNIMPL, &dch->chan_nr, NULL, msg);
+ }
+
+ new = BS_POWER2DB(*TLVP_VAL(&tp, RSL_IE_BS_POWER));
+ old = lchan->bs_power_ctrl.current;
+
+ /* Osmocom specific extension for BCCH carrier power reduction */
+ if (dch->chan_nr == RSL_CHAN_BCCH) {
+ int rc = bts_set_c0_pwr_red(trx->bts, new);
+ if (rc != 0) {
+ const uint8_t cause = (rc == -ENOTSUP) ?
+ RSL_ERR_SERV_OPT_UNIMPL : RSL_ERR_IE_CONTENT;
+ return rsl_tx_error_report(trx, cause, &dch->chan_nr, NULL, msg);
+ }
- new_bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+ return 0;
+ }
+
+ /* BS power reduction is generally not allowed on BCCH/CCCH carrier.
+ * However, we allow it in the BCCH carrier power reduction operation.
+ * Constrain BS power value by the maximum reduction for this timeslot. */
+ if (trx->bts->c0 == trx)
+ new = OSMO_MIN(new, lchan->ts->c0_power_red_db);
- LOGPLCHAN(lchan, DRSL, LOGL_INFO, "BS POWER CONTROL Attenuation %d -> %d dB\n",
- bs_power_attenuation_dB(lchan->bs_power), bs_power_attenuation_dB(new_bs_power));
+ /* 9.3.32 (TLV) BS Power Parameters IE (vendor specific) */
+ if ((ie = TLVP_GET(&tp, RSL_IE_BS_POWER_PARAM)) != NULL) {
+ struct gsm_power_ctrl_params *params = &lchan->bs_dpc_params;
- lchan->bs_power = new_bs_power;
+ /* Parsed parameters will override per-TRX defaults */
+ memcpy(params, trx->bs_dpc_params, sizeof(*params));
+
+ /* Parsed parameters will override per-TRX defaults */
+ if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse BS Power Parameters IE\n");
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT);
+ }
- /* 9.3.31 MS Power Parameters (O) */
- if (TLVP_PRESENT(&tp, RSL_IE_BS_POWER_PARAM)) {
- /* Spec explicitly states BTS should perform autonomous
- * BS power control loop in BTS if 'BS Power Parameters'
- * IE is present! WE don't support that. */
- return rsl_tx_error_report(msg->trx, RSL_ERR_OPT_IE_ERROR, &dch->chan_nr, NULL, msg);
+ /* NOTE: it's safer to start from 0 */
+ lchan->bs_power_ctrl.current = 0;
+ lchan->bs_power_ctrl.max = new;
+ lchan->bs_power_ctrl.dpc_params = params;
+ } else {
+ lchan->bs_power_ctrl.dpc_params = NULL;
+ lchan->bs_power_ctrl.current = new;
+ }
+
+ if (lchan->bs_power_ctrl.current != old) {
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "BS POWER CONTROL: "
+ "attenuation change %u -> %u dB\n",
+ old, lchan->bs_power_ctrl.current);
}
return 0;
@@ -1702,7 +2684,10 @@ static int rsl_rx_sacch_inf_mod(struct msgb *msg)
struct tlv_parsed tp;
uint8_t rsl_si, osmo_si;
- rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_error_report(msg->trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg);
+ }
if (TLVP_PRESENT(&tp, RSL_IE_STARTNG_TIME)) {
LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Starting time not supported\n");
@@ -1758,7 +2743,7 @@ int rsl_tx_cbch_load_indication(struct gsm_bts *bts, bool ext_cbch, bool overflo
return -ENOMEM;
/* 9.3.1 Channel Number */
- rsl_cch_push_hdr(msg, RSL_MT_CBCH_LOAD_IND, gsm_lchan2chan_nr(lchan));
+ rsl_cch_push_hdr(msg, RSL_MT_CBCH_LOAD_IND, gsm_lchan2chan_nr_rsl(lchan));
/* 9.3.43 CBCH Load Information */
load_info = ((overflow & 1) << 7) | (amount & 0x0F);
@@ -1810,7 +2795,9 @@ int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause)
{
struct msgb *nmsg;
- LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Sending RTP delete indication: cause = %s\n",
+ LOGPLCHAN(lchan, DRSL,
+ (cause == RSL_ERR_NORMAL_UNSPEC) ? LOGL_INFO : LOGL_NOTICE,
+ "Sending RTP delete indication: cause = %s\n",
rsl_err_name(cause));
nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
@@ -1820,7 +2807,7 @@ int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause)
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));
+ rsl_ipa_push_hdr(nmsg, RSL_MT_IPAC_DLCX_IND, gsm_lchan2chan_nr_rsl(lchan));
nmsg->trx = lchan->ts->trx;
@@ -1832,7 +2819,7 @@ 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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
const char *name;
struct in_addr ia;
@@ -1870,6 +2857,11 @@ static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2,
lchan->abis_ip.rtp_payload2);
}
+ /* Osmocom Extension: Osmux CID */
+ if (lchan->abis_ip.osmux.use)
+ msgb_tlv_put(msg, RSL_IE_OSMO_OSMUX_CID, 1,
+ &lchan->abis_ip.osmux.local_cid);
+
/* push the header in front */
rsl_ipa_push_hdr(msg, orig_msgt + 1, chan_nr);
msg->trx = lchan->ts->trx;
@@ -1880,7 +2872,7 @@ static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2,
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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
LOGPLCHAN(lchan, DRSL, LOGL_INFO, "RSL Tx IPAC_DLCX_ACK\n");
@@ -1903,7 +2895,7 @@ 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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
LOGPLCHAN(lchan, DRSL, LOGL_INFO, "RSL Tx IPAC_DLCX_NACK\n");
@@ -1924,15 +2916,15 @@ static int rsl_tx_ipac_dlcx_nack(struct gsm_lchan *lchan, int inc_conn_id,
}
-/* transmit an CRCX NACK for the lchan */
+/* Send an xxCX NACK for the given xxCX message type and 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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
+ uint8_t msg_type = orig_msgtype + 2;
- /* FIXME: allocate new msgb and copy old over */
- LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "RSL Tx IPAC_BIND_NACK\n");
+ LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "RSL Tx %s\n", rsl_ipac_msg_name(msg_type));
msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
if (!msg)
@@ -1952,7 +2944,7 @@ static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t 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);
+ rsl_ipa_push_hdr(msg, msg_type, chan_nr);
msg->trx = lchan->ts->trx;
return abis_bts_rsl_sendmsg(msg);
@@ -1960,7 +2952,7 @@ static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause,
static char *get_rsl_local_ip(struct gsm_bts_trx *trx)
{
- struct e1inp_ts *ts = trx->rsl_link->ts;
+ struct e1inp_ts *ts = trx->bb_transc.rsl.link->ts;
struct sockaddr_storage ss;
socklen_t sa_len = sizeof(ss);
static char hostbuf[256];
@@ -1980,42 +2972,21 @@ static char *get_rsl_local_ip(struct gsm_bts_trx *trx)
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;
+ const uint8_t *payload_type, *speech_mode, *payload_type2, *csd_fmt;
+ const uint8_t *osmux_cid = NULL;
uint32_t connect_ip = 0;
uint16_t connect_port = 0;
- int rc, inc_ip_port = 0, port;
+ int rc, inc_ip_port = 0;
char *name;
struct in_addr ia;
- struct in_addr addr;
+ enum rsl_ipac_rtp_csd_format_d csd_fmt_d;
+ enum rsl_ipac_rtp_csd_format_ir csd_fmt_ir;
if (dch->c.msg_type == RSL_MT_IPAC_CRCX)
name = "CRCX";
@@ -2027,22 +2998,22 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg)
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);
+ if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_PROTO, 0, dch->c.msg_type);
+ }
LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, "IPAC_%s: ", name);
if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_IP, 4)) {
+ struct in_addr addr;
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));
+ connect_port = tlvp_val16be(&tp, RSL_IE_IPAC_REMOTE_PORT);
+ LOGPC(DRSL, LOGL_DEBUG, "connect_port=%u ", connect_port);
}
speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE);
@@ -2056,6 +3027,14 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg)
LOGPC(DRSL, LOGL_DEBUG, "\n");
payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2);
+ if (payload_type2)
+ LOGPC(DRSL, LOGL_DEBUG, "payload_type2=%u ", *payload_type2);
+
+ /* this IE has TLV format when TV would have been good enough */
+ if (TLVP_PRES_LEN(&tp, RSL_IE_OSMO_OSMUX_CID, 1))
+ osmux_cid = TLVP_VAL(&tp, RSL_IE_OSMO_OSMUX_CID);
+ if (osmux_cid)
+ LOGPC(DRSL, LOGL_DEBUG, "osmux_cid=%u ", *osmux_cid);
if (dch->c.msg_type == RSL_MT_IPAC_CRCX && connect_ip && connect_port)
inc_ip_port = 1;
@@ -2067,116 +3046,107 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg)
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) {
- LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC CRCX, "
- "but we already have socket!\n");
- return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
- inc_ip_port, dch->c.msg_type);
+ if ((csd_fmt = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_CSD_FMT))) {
+ csd_fmt_d = *csd_fmt & 0xf;
+ csd_fmt_ir = *csd_fmt >> 4;
+ LOGPC(DRSL, LOGL_DEBUG, "csd_fmt_d=%d csd_fmt_ir=%d ", csd_fmt_d, csd_fmt_ir);
+ if (csd_fmt_d != RSL_IPAC_RTP_CSD_TRAU_BTS) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC %s, csd_fmt_d=%d is not supported\n",
+ name, csd_fmt_d);
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_SERV_OPT_UNIMPL, 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) {
- LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to create RTP/RTCP sockets\n");
- oml_tx_failure_event_rep(&lchan->ts->trx->mo,
- NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT,
- "%s IPAC Failed to create RTP/RTCP sockets",
- gsm_lchan_name(lchan));
+ }
+
+ if (!osmux_cid) { /* Regular RTP */
+ if (bts->osmux.use == OSMUX_USAGE_ONLY) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC XXcx without Osmux CID"
+ "goes against configured Osmux policy 'only'\n");
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)
- LOGPLCHAN(lchan, DRTP, LOGL_ERROR,
- "IPAC Failed to set RTP socket parameters: %s\n", strerror(-rc));
- else
- LOGPLCHAN(lchan, DRTP, LOGL_INFO, "IPAC set RTP socket parameters: %d\n", 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);
+
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { /* CRCX */
+ char *ipstr = NULL;
+ 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 = lchan_rtp_socket_create(lchan, ipstr);
+ if (rc < 0)
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ } else { /* MDCX */
+ if (!lchan->abis_ip.rtp_socket) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC MDCX, "
+ "but we have no RTP socket!\n");
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
}
- rc = bind_rtp(bts, lchan->abis_ip.rtp_socket, ipstr);
+
+ /* 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->bb_transc.rsl.link;
+
+ ia.s_addr = htonl(get_signlink_remote_ip(sign_link));
+ } else
+ ia.s_addr = connect_ip;
+ rc = lchan_rtp_socket_connect(lchan, &ia, connect_port);
if (rc < 0) {
- LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to bind RTP/RTCP sockets\n");
- oml_tx_failure_event_rep(&lchan->ts->trx->mo,
- NM_SEVER_MINOR, 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);
+ lchan_rtp_socket_free(lchan);
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) {
- LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC MDCX, "
- "but we have no RTP socket!\n");
+
+ } else { /* Osmux */
+ if (bts->osmux.use == OSMUX_USAGE_OFF) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC XXcx with Osmux CID"
+ "goes against configured Osmux policy 'off'\n");
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;
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { /* CRCX */
+ rc = lchan_osmux_init(lchan, payload_type ? *payload_type : 0);
+ if (rc < 0)
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ } else { /* MDCX */
+ if (!lchan->abis_ip.osmux.use) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC MDCX with Osmux CID, "
+ "CRCX was configured as RTP!\n");
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ inc_ip_port, dch->c.msg_type);
+ }
+ }
- 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) {
- LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "Failed to connect RTP/RTCP sockets\n");
- 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);
+ if (connect_ip != 0)
+ lchan->abis_ip.connect_ip = connect_ip;
+ if (connect_port != 0)
+ lchan->abis_ip.connect_port = connect_port;
+ lchan->abis_ip.osmux.remote_cid = *osmux_cid;
+ if (lchan->abis_ip.connect_ip && lchan->abis_ip.connect_port &&
+ !lchan_osmux_connected(lchan)) {
+ rc = lchan_osmux_connect(lchan);
+ if (rc < 0) {
+ lchan_osmux_release(lchan);
+ 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)
- LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC cannot obtain locally bound IP/port: %d\n", rc);
- lchan->abis_ip.bound_port = port;
/* Everything has succeeded, we can store new values in lchan */
if (payload_type) {
@@ -2206,9 +3176,10 @@ static int rsl_rx_ipac_dlcx(struct msgb *msg)
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 (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__);
+ return rsl_tx_ipac_dlcx_nack(lchan, 0, RSL_ERR_PROTO);
+ }
if (TLVP_PRESENT(&tp, RSL_IE_IPAC_CONN_ID))
inc_conn_id = 1;
@@ -2217,9 +3188,7 @@ static int rsl_rx_ipac_dlcx(struct msgb *msg)
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);
+ lchan_rtp_socket_free(lchan);
}
return rc;
}
@@ -2233,7 +3202,7 @@ static int rsl_rx_ipac_dlcx(struct msgb *msg)
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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
struct msgb *msg;
uint8_t ie[2];
@@ -2262,7 +3231,7 @@ 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);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Tx PDCH %s NACK (cause = 0x%02x)\n",
pdch_act ? "ACT" : "DEACT", cause);
@@ -2371,7 +3340,6 @@ static void rsl_rx_dyn_pdch(struct msgb *msg, bool pdch_act)
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 {
@@ -2400,14 +3368,12 @@ static void ipacc_dyn_pdch_ts_disconnected(struct gsm_bts_trx_ts *ts)
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));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG,
+ "PDCH DEACT operation: channel disconnected, will reconnect as TCH\n");
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));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG,
+ "PDCH ACT operation: channel disconnected, will reconnect as PDTCH\n");
as_pchan = GSM_PCHAN_PDCH;
} else
/* No reconnect pending. */
@@ -2434,6 +3400,7 @@ static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts)
switch (ts->dyn.pchan_want) {
case GSM_PCHAN_TCH_F:
case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
case GSM_PCHAN_PDCH:
break;
default:
@@ -2457,7 +3424,7 @@ void cb_ts_disconnected(struct gsm_bts_trx_ts *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:
+ case GSM_PCHAN_OSMO_DYN:
return osmo_dyn_ts_disconnected(ts);
default:
return;
@@ -2467,39 +3434,31 @@ void cb_ts_disconnected(struct gsm_bts_trx_ts *ts)
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);
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_NOTICE, "PDCH ACT IPA operation failed (%d) in bts model\n", 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));
+ if (ts->lchan[0].type != GSM_LCHAN_TCH_F) {
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_ERROR, "PDCH DEACT error: timeslot connected, so "
+ "expecting lchan type TCH/F, but is %s\n", 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));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "PDCH DEACT operation: timeslot connected as TCH/F\n");
/* 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),
+ if (ts->lchan[0].type != GSM_LCHAN_PDTCH) {
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_ERROR, "PDCH ACT error: timeslot connected, "
+ "so expecting lchan type PDTCH, but is %s\n",
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));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "PDCH ACT operation: timeslot connected as PDTCH\n");
/* The PDTCH is connected, now tell the PCU about it. Except
* when the PCU is not connected (yet), then there's nothing
@@ -2522,30 +3481,31 @@ static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts, int 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;
+ unsigned int ln;
if (rc) {
- LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT OSMO operation failed (%d) in bts model\n",
- gsm_lchan_name(ts->lchan), rc);
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_NOTICE, "PDCH ACT OSMO operation failed (%d) in bts model\n", 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);
+ /* Handle postponed RSL CHANnel ACTIVation messages (if any) */
+ for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) {
+ struct gsm_lchan *lchan = &ts->lchan[ln];
+
+ if (lchan->pending_chan_activ == NULL)
+ continue;
+
+ struct msgb *msg = lchan->pending_chan_activ;
+ lchan->pending_chan_activ = NULL;
+
+ /* Continue where we left off before re-connecting the TS */
+ if (rsl_rx_chan_activ(msg) != 1)
+ msgb_free(msg);
+ }
}
void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc)
@@ -2555,7 +3515,7 @@ void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc)
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:
+ case GSM_PCHAN_OSMO_DYN:
return osmo_dyn_ts_connected(ts, rc);
default:
return;
@@ -2569,10 +3529,10 @@ void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc)
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));
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == TS_F_PDCH_PENDING_MASK) {
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_ERROR,
+ "Internal Error: both PDCH ACT and PDCH DEACT pending\n");
+ }
ts->flags &= ~TS_F_PDCH_PENDING_MASK;
@@ -2588,9 +3548,8 @@ void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc)
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);
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "%s switched to %s mode (ts->flags == %x)\n",
+ gsm_pchan_name(ts->pchan), pdch_act ? "PDCH" : "TCH/F", ts->flags);
rc = rsl_tx_dyn_pdch_ack(ts->lchan, pdch_act);
if (rc)
@@ -2692,8 +3651,11 @@ static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg)
return -1;
}
- DEBUGP(DRLL, "%s Rx RLL %s Abis -> LAPDm\n", gsm_lchan_name(lchan),
- rsl_msg_name(rh->c.msg_type));
+ /* VGCS Uplink is released by MSC using REL-REQ. */
+ if (rh->c.msg_type == RSL_MT_REL_REQ)
+ vgcs_talker_reset(lchan, true);
+
+ LOGPLCHAN(lchan, DRLL, LOGL_DEBUG, "Rx RLL %s Abis -> LAPDm\n", rsl_msg_name(rh->c.msg_type));
/* make copy of RLL header, as the message will be free'd in case of erroneous return */
rh2 = *rh;
@@ -2788,15 +3750,15 @@ static int handle_gprs_susp_req(struct msgb *msg)
int rc;
if (!gh || msgb_l3len(msg) < sizeof(*gh)+sizeof(*gsr)) {
- LOGP(DRSL, LOGL_NOTICE, "%s Short GPRS SUSPEND REQ received, ignoring\n", gsm_lchan_name(msg->lchan));
+ LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "Short GPRS SUSPEND REQ received, ignoring\n");
+ msgb_free(msg);
return -EINVAL;
}
gsr = (struct gsm48_gprs_susp_req *) gh->data;
tlli = osmo_ntohl(gsr->tlli);
- LOGP(DRSL, LOGL_INFO, "%s Fwd GPRS SUSPEND REQ for TLLI=0x%08x to PCU\n",
- gsm_lchan_name(msg->lchan), tlli);
+ LOGPLCHAN(msg->lchan, DRSL, LOGL_INFO, "Fwd GPRS SUSPEND REQ for TLLI=0x%08x to PCU\n", tlli);
rc = pcu_tx_susp_req(msg->lchan, tlli, gsr->ra_id, gsr->cause);
msgb_free(msg);
@@ -2804,16 +3766,6 @@ static int handle_gprs_susp_req(struct msgb *msg)
return rc;
}
-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;
@@ -2821,12 +3773,12 @@ struct osmo_bts_supp_meas_info {
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)
+/* Compose and send 8.4.8 MEASUREMENT RESult via RSL. (timing_offset=-1 -> not present) */
+int rsl_tx_meas_res(struct gsm_lchan *lchan, const uint8_t *l3, unsigned int l3_len, int timing_offset)
{
struct msgb *msg;
uint8_t meas_res[16];
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan);
int res_valid = lchan->meas.flags & LC_UL_M_F_RES_VALID;
struct gsm_bts *bts = lchan->ts->trx->bts;
@@ -2841,20 +3793,19 @@ static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, con
return -ENOMEM;
LOGPLCHAN(lchan, DRSL, LOGL_DEBUG,
- "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",
+ "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:%u, TimingOff:%u\n",
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);
+ lchan->meas.l1_info.ms_pwr,
+ lchan->meas.l1_info.ta, l3_len, timing_offset - MEAS_MAX_TIMING_ADVANCE);
- msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++);
+ 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;
@@ -2867,26 +3818,23 @@ static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, con
* 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;
+ int16_t ta256 = lchan->meas.l1_info.ta * 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);
+ msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power_ctrl.current / 2);
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_tv_fixed_put(msg, RSL_IE_L1_INFO, sizeof(lchan->meas.l1_info), (uint8_t*)&lchan->meas.l1_info);
}
- 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;
+
+ if (l3 && l3_len > 0) {
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3);
+ if (timing_offset != -1)
+ msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, timing_offset);
}
rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr);
@@ -2914,22 +3862,42 @@ int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx)
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;
+ /* If DL estabishment on main signaling link and SAPI 0 with L3 info is expected. */
+ if (lchan->l3_info_estab && rh->msg_type == RSL_MT_EST_IND) {
+ struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+ if ((rllh->link_id & 0xc7) == 0) {
+ /* Reject initial establishment without L3 info. */
+ if (msgb_l2len(msg) == sizeof(struct abis_rsl_rll_hdr)) {
+ LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "RLL EST IND without contention resolution.\n");
+ /* Release normally, re-use the msgb. */
+ rh->msg_type = RSL_MT_REL_REQ;
+ msgb_tv_put(msg, RSL_IE_RELEASE_MODE, RSL_REL_NORMAL);
+ return rsl_rx_rll(lchan->ts->trx, msg);
+ }
+ /* Re-estabishment without contention resoltuion is allowed. */
+ lchan->l3_info_estab = false;
+ }
+ }
- LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Handing RLL msg %s from LAPDm to MEAS REP\n",
+ /* If this is a Measurement Report, then we simply ignore it,
+ * because it has already been processed in l1sap_ph_data_ind(). */
+ if (rslms_is_meas_rep(msg)) {
+ msgb_free(msg);
+ return 0;
+ } else if (rslms_is_gprs_susp_req(msg)) {
+ return handle_gprs_susp_req(msg);
+ } else {
+ LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Fwd RLL msg %s from LAPDm to A-bis\n",
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)) {
+ if (rh->msg_type == RSL_MT_REL_IND && lchan_is_tch(lchan)) {
+ vgcs_talker_reset(lchan, true);
LOGPLCHAN(lchan, DRSL, LOGL_INFO,
"Scheduling %s to L3 in next associated TCH-RTS.ind\n",
rsl_msg_name(rh->msg_type));
- if(lchan->pending_rel_ind_msg) {
+ if (lchan->pending_rel_ind_msg) {
LOGPLCHAN(lchan, DRSL, LOGL_INFO,
"Dropping pending release indication message\n");
msgb_free(lchan->pending_rel_ind_msg);
@@ -2939,15 +3907,6 @@ int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx)
return 0;
}
- rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg), le);
- msgb_free(msg);
- return rc;
- } else if (rslms_is_gprs_susp_req(msg)) {
- return handle_gprs_susp_req(msg);
- } else {
- LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Fwd RLL msg %s from LAPDm to A-bis\n",
- rsl_msg_name(rh->msg_type));
-
return abis_bts_rsl_sendmsg(msg);
}
}
@@ -2994,8 +3953,10 @@ static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg)
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:
+ ret = rsl_rx_notification_cmd(trx, msg);
+ break;
+ case RSL_MT_SMS_BC_REQ:
LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "unimplemented RSL cchan msg_type %s\n",
rsl_msg_name(cch->c.msg_type));
rsl_tx_error_report(trx, RSL_ERR_MSG_TYPE, &cch->chan_nr, NULL, msg);
@@ -3003,6 +3964,10 @@ static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg)
case RSL_MT_OSMO_ETWS_CMD:
ret = rsl_rx_osmo_etws_cmd(trx, msg);
break;
+ /* Osmocom specific extension for BCCH carrier power reduction */
+ case RSL_MT_BS_POWER_CONTROL:
+ ret = rsl_rx_bs_pwr_ctrl(msg);
+ break;
default:
LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n",
cch->c.msg_type);
@@ -3113,6 +4078,9 @@ static int rsl_rx_trx(struct gsm_bts_trx *trx, struct msgb *msg)
case RSL_MT_SACCH_FILL:
ret = rsl_rx_sacch_fill(trx, msg);
break;
+ case RSL_MT_IPAC_MEAS_PREPROC_DFT:
+ ret = rsl_rx_meas_preproc_dft(trx, msg);
+ break;
default:
LOGP(DRSL, LOGL_NOTICE, "undefined RSL TRX msg_type 0x%02x\n",
th->msg_type);
@@ -3171,14 +4139,6 @@ static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *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;
diff --git a/src/common/rtp_input_preen.c b/src/common/rtp_input_preen.c
new file mode 100644
index 00000000..5729229f
--- /dev/null
+++ b/src/common/rtp_input_preen.c
@@ -0,0 +1,151 @@
+/*
+ * This module implements a helper function for the RTP input path:
+ * validates incoming RTP payloads, makes the accept-or-drop decision,
+ * and for some codecs signals additional required actions such as
+ * dropping one header octet.
+ *
+ * Author: Mychaela N. Falconia <falcon@freecalypso.org>, 2023 - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ *
+ * 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 <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/codec/codec.h>
+
+#include <osmo-bts/lchan.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rtp_input_preen.h>
+
+static bool amr_is_octet_aligned(const uint8_t *rtp_pl, unsigned rtp_pl_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 (rtp_pl_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl))
+ return false;
+
+ return true;
+}
+
+static enum pl_input_decision
+input_preen_fr(const uint8_t *rtp_pl, unsigned rtp_pl_len)
+{
+ if (rtp_pl_len != GSM_FR_BYTES)
+ return PL_DECISION_DROP;
+ if ((rtp_pl[0] & 0xF0) != 0xD0)
+ return PL_DECISION_DROP;
+ return PL_DECISION_ACCEPT;
+}
+
+static enum pl_input_decision
+input_preen_efr(const uint8_t *rtp_pl, unsigned rtp_pl_len)
+{
+ if (rtp_pl_len != GSM_EFR_BYTES)
+ return PL_DECISION_DROP;
+ if ((rtp_pl[0] & 0xF0) != 0xC0)
+ return PL_DECISION_DROP;
+ return PL_DECISION_ACCEPT;
+}
+
+static enum pl_input_decision
+input_preen_hr(const uint8_t *rtp_pl, unsigned rtp_pl_len,
+ bool *rfc5993_sid_flag)
+{
+ switch (rtp_pl_len) {
+ case GSM_HR_BYTES:
+ /* RTP input matches our internal format - we are good */
+ return PL_DECISION_ACCEPT;
+ case GSM_HR_BYTES_RTP_RFC5993:
+ /* Validate ToC octet: for payload of this length to be valid,
+ * the F bit must be 0 and the FT field must be either 0 (good
+ * speech) or 2 (good SID). */
+ switch (rtp_pl[0] & 0xF0) {
+ case 0x00:
+ break;
+ case 0x20:
+ *rfc5993_sid_flag = true;
+ break;
+ default:
+ /* invalid payload */
+ return PL_DECISION_DROP;
+ }
+ /* Strip ToC octet, leaving only "pure" TS 101 318 payload. */
+ return PL_DECISION_STRIP_HDR_OCTET;
+ default:
+ /* invalid payload */
+ return PL_DECISION_DROP;
+ }
+}
+
+enum pl_input_decision
+rtp_payload_input_preen(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
+ unsigned rtp_pl_len, bool *rfc5993_sid_flag)
+{
+ /* If rtp continuous-streaming is enabled, we shall emit RTP packets
+ * with zero-length payloads as BFI markers. In a TrFO scenario such
+ * RTP packets sent by call leg A will be received by call leg B,
+ * hence we need to handle them gracefully. For the purposes of a BTS
+ * that runs on its own TDMA timing and does not need timing ticks from
+ * an incoming RTP stream, the correct action upon receiving such
+ * timing-tick-only RTP packets should be the same as when receiving
+ * no RTP packet at all. The simplest way to produce that behavior
+ * is to treat zero-length RTP payloads as invalid. */
+ if (rtp_pl_len == 0)
+ return PL_DECISION_DROP;
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ return input_preen_fr(rtp_pl, rtp_pl_len);
+ else
+ return input_preen_hr(rtp_pl, rtp_pl_len, rfc5993_sid_flag);
+ case GSM48_CMODE_SPEECH_EFR:
+ return input_preen_efr(rtp_pl, rtp_pl_len);
+ case GSM48_CMODE_SPEECH_AMR:
+ /* Avoid forwarding bw-efficient AMR to lower layers,
+ * most bts models don't support it. */
+ if (!amr_is_octet_aligned(rtp_pl, rtp_pl_len)) {
+ LOGPLCHAN(lchan, DL1P, LOGL_NOTICE,
+ "RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n",
+ osmo_hexdump(rtp_pl, rtp_pl_len));
+ return PL_DECISION_DROP;
+ }
+ return PL_DECISION_ACCEPT;
+ default:
+ return PL_DECISION_ACCEPT;
+ }
+}
diff --git a/src/common/scheduler.c b/src/common/scheduler.c
index 95a1b00e..a449d167 100644
--- a/src/common/scheduler.c
+++ b/src/common/scheduler.c
@@ -3,6 +3,7 @@
/* (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>
+ * Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@@ -14,7 +15,7 @@
* 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.
+ * 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/>.
@@ -29,8 +30,12 @@
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/gsm0502.h>
#include <osmocom/gsm/a5.h>
#include <osmo-bts/gsm_data.h>
@@ -39,17 +44,16 @@
#include <osmo-bts/l1sap.h>
#include <osmo-bts/scheduler.h>
#include <osmo-bts/scheduler_backend.h>
+#include <osmo-bts/bts.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);
+static int rts_data_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br);
+static int rts_tchf_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br);
+static int rts_tchh_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br);
+
/*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */
-static const ubit_t dummy_burst[GSM_BURST_LEN] = {
+const ubit_t _sched_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,
@@ -59,28 +63,51 @@ static const ubit_t dummy_burst[GSM_BURST_LEN] = {
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, },
+/*! Training Sequences for Normal Burst (see 3GPP TS 45.002, section 5.2.3) */
+const ubit_t _sched_train_seq_gmsk_nb[4][8][26] = {
+ { /* TSC set 1, table 5.2.3a */
+ { 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 },
+ },
+ { /* TSC set 2, table 5.2.3b */
+ { 0,1,1,0,0,0,1,0,0,0,1,0,0,1,0,0,1,1,1,1,0,1,0,1,1,1 },
+ { 0,1,0,1,1,1,1,0,1,0,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,1 },
+ { 0,1,0,0,0,0,0,1,0,1,1,0,0,0,1,1,1,0,1,1,1,0,1,1,0,0 },
+ { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0 },
+ { 0,1,1,1,0,1,0,0,1,1,1,1,0,1,0,0,1,1,1,0,1,1,1,1,1,0 },
+ { 0,1,0,0,0,0,0,1,0,0,1,1,0,1,0,1,0,0,1,1,1,1,0,0,1,1 },
+ { 0,0,0,1,0,0,0,0,1,1,0,1,0,0,0,0,1,1,0,1,1,1,0,1,0,1 },
+ { 0,1,0,0,0,1,0,1,1,1,0,0,1,1,1,1,1,1,0,0,1,0,1,0,0,1 },
+ },
+ { /* TSC set 3, table 5.2.3c */
+ { 1,1,0,0,0,0,1,0,0,1,0,0,0,1,1,1,1,0,1,0,1,0,0,0,1,0 },
+ { 0,0,1,0,1,1,1,1,1,0,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0,0 },
+ { 1,1,0,0,1,0,0,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,0,1,1,0 },
+ { 0,0,1,1,0,0,0,0,1,0,1,0,0,1,1,0,0,0,0,0,1,0,1,1,0,0 },
+ { 0,0,0,1,1,1,1,0,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,1,1,0 },
+ { 1,1,0,0,1,1,1,1,0,1,0,1,0,1,1,1,1,0,0,1,0,0,0,0,0,0 },
+ { 1,0,1,1,1,0,0,1,1,0,1,0,1,1,1,1,1,1,0,0,0,1,0,0,0,0 },
+ { 1,1,1,0,0,1,0,1,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0 },
+ },
+ { /* TSC set 4, table 5.2.3d */
+ { 1,1,0,0,1,1,1,0,1,0,0,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0 },
+ { 0,1,1,0,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,0,0,0 },
+ { 1,1,1,0,0,1,0,0,0,0,0,1,0,1,0,1,0,0,1,1,1,0,0,0,0,0 },
+ { 0,1,1,0,1,1,0,0,1,1,1,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0 },
+ { 1,1,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0 },
+ { 1,1,0,1,0,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,0,1,0,1,1,0 },
+ { 0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,0,1,0,1,0,1,1,0,0,0,0 },
+ { 0,1,0,1,1,1,0,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,1,1,0 },
+ },
};
-const ubit_t _sched_egprs_tsc[8][78] = {
+const ubit_t _sched_train_seq_8psk_nb[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, },
@@ -108,7 +135,7 @@ const ubit_t _sched_egprs_tsc[8][78] = {
};
/*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */
-const ubit_t _sched_sch_train[64] = {
+const ubit_t _sched_train_seq_gmsk_sb[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,
};
@@ -118,18 +145,12 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
[TRXC_IDLE] = {
.name = "IDLE",
.desc = "Idle channel",
-
- /* On C0, BTS needs to ensure discontinuous burst transmission.
- * Therefore we need to send dummy bursts on IDLE slots. */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
- .dl_fn = tx_idle_fn,
},
[TRXC_FCCH] = {
.name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
.desc = "Frequency correction channel",
/* Tx only, frequency correction bursts */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
.dl_fn = tx_fcch_fn,
},
[TRXC_SCH] = {
@@ -137,7 +158,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
.desc = "Synchronization channel",
/* Tx only, synchronization bursts */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
.dl_fn = tx_sch_fn,
},
[TRXC_BCCH] = {
@@ -148,7 +168,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
/* Tx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
* regular interleaving (3GPP TS 05.02, clause 7, table 3):
* a L2 frame is interleaved over 4 consecutive bursts. */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
.rts_fn = rts_data_fn,
.dl_fn = tx_data_fn,
},
@@ -158,7 +177,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
.chan_nr = RSL_CHAN_RACH,
/* Rx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
.ul_fn = rx_rach_fn,
},
[TRXC_CCCH] = {
@@ -169,7 +187,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
/* Tx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4),
* regular interleaving (3GPP TS 05.02, clause 7, table 3):
* a L2 frame is interleaved over 4 consecutive bursts. */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
.rts_fn = rts_data_fn,
.dl_fn = tx_data_fn,
},
@@ -200,13 +217,14 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
/* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03,
* chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7):
*
- * - a traffic frame is interleaved over 6 consecutive bursts
+ * - a traffic frame is interleaved over 4 consecutive bursts
* using the even numbered bits of the first 2 bursts,
- * all bits of the middle two 2 bursts,
* and odd numbered bits of the last 2 bursts;
* - a FACCH/H frame 'steals' (replaces) two traffic frames,
- * interleaving is done over 4 consecutive bursts,
- * the same as given for a TCH/FS. */
+ * interleaving is done over 6 consecutive bursts,
+ * using the even numbered bits of the first 2 bursts,
+ * all bits of the middle two 2 bursts,
+ * and odd numbered bits of the last 2 bursts. */
.rts_fn = rts_tchh_fn,
.dl_fn = tx_tchh_fn,
.ul_fn = rx_tchh_fn,
@@ -525,10 +543,7 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
.chan_nr = RSL_CHAN_OSMO_PDCH,
/* Rx and Tx, multiple coding schemes: CS-2..4 and MCS-1..9 (3GPP TS
- * 05.03, chapter 5), regular interleaving as specified for xCCH.
- * NOTE: the burst buffer is three times bigger because the
- * payload of EDGE bursts is three times longer. */
- .flags = TRX_CHAN_FLAG_PDCH,
+ * 05.03, chapter 5), regular interleaving as specified for xCCH. */
.rts_fn = rts_data_fn,
.dl_fn = tx_pdtch_fn,
.ul_fn = rx_pdtch_fn,
@@ -538,11 +553,14 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
.desc = "Packet Timing advance control channel",
.chan_nr = RSL_CHAN_OSMO_PDCH,
- /* Same as for TRXC_BCCH (xCCH), see above. */
- .flags = TRX_CHAN_FLAG_PDCH,
+ /* On the Uplink, mobile stations transmit random Access Bursts
+ * to allow estimation of the timing advance for one MS in packet
+ * transfer mode. On Downlink, the network sends timing advance
+ * updates for several mobile stations. The coding scheme used
+ * for PTCCH/D messages is the same as for PDTCH CS-1. */
.rts_fn = rts_data_fn,
- .dl_fn = tx_data_fn,
- .ul_fn = rx_data_fn,
+ .dl_fn = tx_pdtch_fn,
+ .ul_fn = rx_rach_fn,
},
[TRXC_CBCH] = {
/* TODO: distinguish CBCH on SDCCH/4 and SDCCH/8 */
@@ -551,163 +569,203 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
.chan_nr = RSL_CHAN_OSMO_CBCH4,
/* Tx only, same as for TRXC_BCCH (xCCH), see above. */
- .flags = TRX_CHAN_FLAG_AUTO_ACTIVE,
.rts_fn = rts_data_fn,
.dl_fn = tx_data_fn,
},
};
+enum {
+ L1SCHED_TS_CTR_DL_LATE,
+ L1SCHED_TS_CTR_DL_NOT_FOUND,
+};
+
+static const struct rate_ctr_desc l1sched_ts_ctr_desc[] = {
+ [L1SCHED_TS_CTR_DL_LATE] = {"l1sched_ts:dl_late", "Downlink frames arrived too late to submit to lower layers"},
+ [L1SCHED_TS_CTR_DL_NOT_FOUND] = {"l1sched_ts:dl_not_found", "Downlink frames not found while scheduling"},
+};
+static const struct rate_ctr_group_desc l1sched_ts_ctrg_desc = {
+ "l1sched_ts",
+ "L1 scheduler timeslot",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(l1sched_ts_ctr_desc),
+ l1sched_ts_ctr_desc
+};
+
/*
* init / exit
*/
-int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx)
+static void trx_sched_init_ts(struct gsm_bts_trx_ts *ts,
+ const unsigned int rate_ctr_idx)
{
- uint8_t tn;
+ struct l1sched_ts *l1ts;
unsigned int i;
+ char name[128];
- if (!trx)
- return -EINVAL;
+ l1ts = talloc_zero(ts->trx, struct l1sched_ts);
+ OSMO_ASSERT(l1ts != NULL);
- l1t->trx = trx;
+ /* Link both structures */
+ ts->priv = l1ts;
+ l1ts->ts = ts;
- LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr);
+ l1ts->ctrs = rate_ctr_group_alloc(ts->trx,
+ &l1sched_ts_ctrg_desc,
+ rate_ctr_idx);
+ snprintf(name, sizeof(name), "bts%u-trx%u-ts%u%s",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ ts->vamos.is_shadow ? "-shadow" : "");
+ rate_ctr_group_set_name(l1ts->ctrs, name);
- for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ INIT_LLIST_HEAD(&l1ts->dl_prims);
- 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;
- }
+ 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 = false;
}
-
- return 0;
}
-void trx_sched_exit(struct l1sched_trx *l1t)
+void trx_sched_init(struct gsm_bts_trx *trx)
{
- struct gsm_bts_trx_ts *ts;
- uint8_t tn;
- int i;
+ unsigned int tn;
- 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);
+ OSMO_ASSERT(trx != NULL);
+
+ LOGPTRX(trx, DL1C, LOGL_DEBUG, "Init scheduler structures\n");
+
+ /* Allocate shadow timeslots */
+ gsm_bts_trx_init_shadow_ts(trx);
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ unsigned int rate_ctr_idx = trx->nr * 100 + tn;
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ /* Init primary and shadow timeslots */
+ trx_sched_init_ts(ts, rate_ctr_idx);
+ trx_sched_init_ts(ts->vamos.peer, rate_ctr_idx + 10);
}
}
-/* close all logical channels and reset timeslots */
-void trx_sched_reset(struct l1sched_trx *l1t)
+static void trx_sched_clean_ts(struct gsm_bts_trx_ts *ts)
{
- trx_sched_exit(l1t);
- trx_sched_init(l1t, l1t->trx);
+ struct l1sched_ts *l1ts = ts->priv;
+ unsigned int i;
+
+ msgb_queue_free(&l1ts->dl_prims);
+ rate_ctr_group_free(l1ts->ctrs);
+ l1ts->ctrs = NULL;
+
+ /* clear lchan channel states */
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++)
+ lchan_set_state(&ts->lchan[i], LCHAN_S_NONE);
+
+ talloc_free(l1ts);
+ ts->priv = NULL;
+}
+
+void trx_sched_clean(struct gsm_bts_trx *trx)
+{
+ unsigned int tn;
+
+ LOGPTRX(trx, DL1C, LOGL_DEBUG, "Clean scheduler structures\n");
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+
+ /* Clean primary and shadow timeslots */
+ trx_sched_clean_ts(ts);
+ trx_sched_clean_ts(ts->vamos.peer);
+ }
+
+ /* Free previously allocated shadow timeslots */
+ gsm_bts_trx_free_shadow_ts(trx);
}
-struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn,
- enum trx_chan_type chan)
+struct msgb *_sched_dequeue_prim(struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br)
{
struct msgb *msg, *msg2;
- struct osmo_phsap_prim *l1sap;
- uint32_t prim_fn;
+ uint32_t prim_fn, l1sap_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;
- }
+ struct osmo_phsap_prim *l1sap = msgb_l1sap_prim(msg);
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);
+ l1sap_fn = l1sap->u.data.fn;
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);
+ l1sap_fn = l1sap->u.tch.fn;
break;
default:
- goto wrong_type;
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Prim has wrong type.\n");
+ goto free_msg;
}
- if (prim_fn > 100) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
- "Prim %u is out of range (100), or channel %s with "
+ prim_fn = GSM_TDMA_FN_SUB(l1sap_fn, br->fn);
+ if (prim_fn > 100) { /* l1sap_fn < fn */
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br,
+ "Prim %u is out of range (%u vs exp %u), 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,
- trx_chan_desc[chan].name);
+ prim_fn, l1sap_fn, br->fn,
+ get_lchan_by_chan_nr(l1ts->ts->trx, chan_nr)->name,
+ trx_chan_desc[br->chan].name);
+ rate_ctr_inc2(l1ts->ctrs, L1SCHED_TS_CTR_DL_LATE);
/* unlink and free message */
llist_del(&msg->list);
msgb_free(msg);
continue;
}
- if (prim_fn > 0)
- continue;
+ if (prim_fn > 0) /* l1sap_fn > fn */
+ break;
- goto found_msg;
+ /* l1sap_fn == fn */
+ if ((chan_nr ^ (trx_chan_desc[br->chan].chan_nr | br->tn))
+ || ((link_id & 0xc0) ^ trx_chan_desc[br->chan].link_id)) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "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[br->chan].chan_nr | br->tn, trx_chan_desc[br->chan].link_id);
+ goto free_msg;
+ }
+
+ /* unlink and return message */
+ llist_del(&msg->list);
+ return msg;
}
+ /* Queue was traversed with no candidate, no prim is available for current FN: */
+ rate_ctr_inc2(l1ts->ctrs, L1SCHED_TS_CTR_DL_NOT_FOUND);
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 */
+free_msg:
+ /* unlink and free message */
llist_del(&msg->list);
- return msg;
+ msgb_free(msg);
+ return NULL;
}
-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,
+int _sched_compose_ph_data_ind(struct l1sched_ts *l1ts, uint32_t fn,
+ enum trx_chan_type chan,
+ const uint8_t *data, size_t data_len,
+ uint16_t ber10k, 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);
+ uint8_t chan_nr = trx_chan_desc[chan].chan_nr | l1ts->ts->nr;
+
+ /* VAMOS: use Osmocom specific channel number */
+ if (l1ts->ts->vamos.is_shadow)
+ chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK;
/* compose primitive */
- msg = l1sap_msgb_alloc(l2_len);
+ msg = l1sap_msgb_alloc(data_len);
l1sap = msgb_l1sap_prim(msg);
osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
PRIM_OP_INDICATION, msg);
@@ -719,48 +777,53 @@ int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
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;
+ msg->l2h = msgb_put(msg, data_len);
+ if (data_len)
+ memcpy(msg->l2h, data, data_len);
/* forward primitive */
- l1sap_up(l1t->trx, l1sap);
+ l1sap_up(l1ts->ts->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)
+int _sched_compose_tch_ind(struct l1sched_ts *l1ts, uint32_t fn,
+ enum trx_chan_type chan,
+ const uint8_t *data, size_t data_len,
+ uint16_t ber10k, float rssi,
+ int16_t ta_offs_256bits, int16_t link_qual_cb,
+ uint8_t is_sub)
{
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)];
+ uint8_t chan_nr = trx_chan_desc[chan].chan_nr | l1ts->ts->nr;
+ struct gsm_lchan *lchan = &l1ts->ts->lchan[l1sap_chan2ss(chan_nr)];
+
+ /* VAMOS: use Osmocom specific channel number */
+ if (l1ts->ts->vamos.is_shadow)
+ chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK;
/* compose primitive */
- msg = l1sap_msgb_alloc(tch_len);
+ msg = l1sap_msgb_alloc(data_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)));
+ l1sap->u.tch.rssi = (int8_t) (rssi);
+ l1sap->u.tch.ber10k = ber10k;
+ l1sap->u.tch.ta_offs_256bits = ta_offs_256bits;
+ l1sap->u.tch.lqual_cb = link_qual_cb;
+ l1sap->u.tch.is_sub = is_sub & 1;
+
+ msg->l2h = msgb_put(msg, data_len);
+ if (data_len)
+ memcpy(msg->l2h, data, data_len);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1ts, chan, l1sap->u.tch.fn, "%s Rx -> RTP: %s\n",
+ gsm_lchan_name(lchan), msgb_hexdump_l2(msg));
/* forward primitive */
- l1sap_up(l1t->trx, l1sap);
+ l1sap_up(l1ts->ts->trx, l1sap);
return 0;
}
@@ -771,39 +834,37 @@ int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
* data request (from upper layer)
*/
-int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
+int trx_sched_ph_data_req(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
{
- uint8_t tn = l1sap->u.data.chan_nr & 7;
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ uint8_t tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr);
+ struct l1sched_ts *l1ts = trx->ts[tn].priv;
- LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.data.fn,
+ LOGL1S(DL1P, LOGL_DEBUG, l1ts, -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)) {
+ if (!l1sap->oph.msg->l2h || msgb_l2len(l1sap->oph.msg) == 0) {
msgb_free(l1sap->oph.msg);
return 0;
}
+ /* VAMOS: convert Osmocom specific channel number to a generic one */
+ if (trx->ts[tn].vamos.is_shadow)
+ l1sap->u.data.chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK;
+
msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg);
return 0;
}
-int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
+int trx_sched_tch_req(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
{
- uint8_t tn = l1sap->u.tch.chan_nr & 7;
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ uint8_t tn = L1SAP_CHAN2TS(l1sap->u.tch.chan_nr);
+ struct l1sched_ts *l1ts = trx->ts[tn].priv;
- 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();
+ LOGL1S(DL1P, LOGL_DEBUG, l1ts, -1, l1sap->u.tch.fn,
+ "TCH.req: chan_nr=0x%02x\n", l1sap->u.tch.chan_nr);
/* ignore empty frame */
if (!msgb_l2len(l1sap->oph.msg)) {
@@ -811,36 +872,43 @@ int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
return 0;
}
+ /* VAMOS: convert Osmocom specific channel number to a generic one */
+ if (trx->ts[tn].vamos.is_shadow)
+ l1sap->u.tch.chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK;
+
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)
+static int rts_data_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br)
{
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;
+ chan_nr = trx_chan_desc[br->chan].chan_nr | br->tn;
+ link_id = trx_chan_desc[br->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);
+ /* VAMOS: use Osmocom specific channel number */
+ if (l1ts->ts->vamos.is_shadow)
+ chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK;
+
+ /* For handover detection, there are cases where the SACCH should remain inactive until the first RACH
+ * indicating the TA is received. */
+ if (L1SAP_IS_LINK_SACCH(link_id)
+ && !l1ts->chan_state[br->chan].lchan->want_dl_sacch_active)
+ return 0;
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id);
/* generate prim */
msg = l1sap_msgb_alloc(200);
@@ -851,31 +919,30 @@ static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
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.fn = br->fn;
- return l1sap_up(l1t->trx, l1sap);
+ return l1sap_up(l1ts->ts->trx, l1sap);
}
-static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
- enum trx_chan_type chan, int facch)
+static int rts_tch_common(const struct l1sched_ts *l1ts,
+ const struct trx_dl_burst_req *br,
+ bool 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;
+ chan_nr = trx_chan_desc[br->chan].chan_nr | br->tn;
+ link_id = trx_chan_desc[br->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);
+ /* VAMOS: use Osmocom specific channel number */
+ if (l1ts->ts->vamos.is_shadow)
+ chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK;
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr);
/* only send, if FACCH is selected */
if (facch) {
@@ -888,13 +955,13 @@ static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
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.fn = br->fn;
- rc = l1sap_up(l1t->trx, l1sap);
+ rc = l1sap_up(l1ts->ts->trx, l1sap);
}
- /* dont send, if TCH is in signalling only mode */
- if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) {
+ /* don't send, if TCH is in signalling only mode */
+ if (l1ts->chan_state[br->chan].rsl_cmode != RSL_CMOD_SPD_SIGN) {
/* generate prim */
msg = l1sap_msgb_alloc(200);
if (!msg)
@@ -903,128 +970,259 @@ static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
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;
+ l1sap->u.tch.fn = br->fn;
- return l1sap_up(l1t->trx, l1sap);
+ return l1sap_up(l1ts->ts->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)
+static int rts_tchf_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br)
{
/* TCH/F may include FACCH on every 4th burst */
- return rts_tch_common(l1t, tn, fn, chan, 1);
+ return rts_tch_common(l1ts, br, true);
}
+/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1).
+ * This mapping is valid for both FACCH/H(0) and FACCH/H(1). */
+const uint8_t sched_tchh_dl_facch_map[26] = {
+ [4] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */
+ [5] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */
+ [13] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */
+ [14] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */
+ [21] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */
+ [22] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */
+};
/* 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)
+static int rts_tchh_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br)
{
- /* 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);
+ return rts_tch_common(l1ts, br, sched_tchh_dl_facch_map[br->fn % 26]);
}
/* set multiframe scheduler to given pchan */
-int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn,
- enum gsm_phys_chan_config pchan)
+int trx_sched_set_pchan(struct gsm_bts_trx_ts *ts, 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);
+ struct l1sched_ts *l1ts = ts->priv;
+ int i = find_sched_mframe_idx(pchan, ts->nr);
if (i < 0) {
- LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe "
- "trx=%d ts=%d\n", l1t->trx->nr, tn);
+ LOGP(DL1C, LOGL_NOTICE, "%s Failed to configure multiframe (pchan=0x%02x)\n",
+ gsm_ts_name(ts), pchan);
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);
+ if (ts->vamos.peer != NULL) {
+ l1ts = ts->vamos.peer->priv;
+ l1ts->mf_index = i;
+ l1ts->mf_period = trx_sched_multiframes[i].period;
+ l1ts->mf_frames = trx_sched_multiframes[i].frames;
+ }
+ LOGP(DL1C, LOGL_NOTICE, "%s Configured multiframe with '%s'\n",
+ gsm_ts_name(ts), trx_sched_multiframes[i].name);
return 0;
}
+/* Remove all matching (by chan_nr & link_id) primitives from the given queue */
+static void trx_sched_queue_filter(struct llist_head *q, uint8_t chan_nr, uint8_t link_id)
+{
+ struct msgb *msg, *_msg;
+
+ llist_for_each_entry_safe(msg, _msg, q, list) {
+ struct osmo_phsap_prim *l1sap = msgb_l1sap_prim(msg);
+ switch (l1sap->oph.primitive) {
+ case PRIM_PH_DATA:
+ if (l1sap->u.data.chan_nr != chan_nr)
+ continue;
+ if (l1sap->u.data.link_id != link_id)
+ continue;
+ break;
+ case PRIM_TCH:
+ if (l1sap->u.tch.chan_nr != chan_nr)
+ continue;
+ if (link_id != 0x00)
+ continue;
+ break;
+ default:
+ /* Shall not happen */
+ OSMO_ASSERT(0);
+ }
+
+ /* Unlink and free() */
+ llist_del(&msg->list);
+ talloc_free(msg);
+ }
+}
+
+static void _trx_sched_set_lchan(struct gsm_lchan *lchan,
+ enum trx_chan_type chan,
+ bool active)
+{
+ struct l1sched_ts *l1ts = lchan->ts->priv;
+ struct l1sched_chan_state *chan_state;
+
+ OSMO_ASSERT(l1ts != NULL);
+ chan_state = &l1ts->chan_state[chan];
+
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s %s\n",
+ (active) ? "Activating" : "Deactivating",
+ trx_chan_desc[chan].name);
+
+ if (active) {
+ /* Clean up everything */
+ memset(chan_state, 0, sizeof(*chan_state));
+
+ /* Bind to generic 'struct gsm_lchan' */
+ chan_state->lchan = lchan;
+
+ /* Allocate memory for Rx/Tx burst buffers. Use the maximim size
+ * of 24 * (2 * 58) bytes, which is sufficient to store up to 24 GMSK
+ * modulated bursts for CSD or up to 8 8PSK modulated bursts for EGPRS. */
+ const size_t buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD;
+ if (trx_chan_desc[chan].dl_fn != NULL)
+ chan_state->dl_bursts = talloc_zero_size(l1ts, buf_size);
+ if (trx_chan_desc[chan].ul_fn != NULL)
+ chan_state->ul_bursts = talloc_zero_size(l1ts, buf_size);
+ } else {
+ chan_state->ho_rach_detect = 0;
+
+ /* Remove pending Tx prims belonging to this lchan */
+ trx_sched_queue_filter(&l1ts->dl_prims,
+ trx_chan_desc[chan].chan_nr,
+ trx_chan_desc[chan].link_id);
+
+ /* Release memory used by Rx/Tx burst buffers */
+ TALLOC_FREE(chan_state->dl_bursts);
+ TALLOC_FREE(chan_state->ul_bursts);
+ }
+
+ chan_state->active = active;
+}
+
/* 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)
+int trx_sched_set_lchan(struct gsm_lchan *lchan, uint8_t chan_nr, uint8_t link_id, bool active)
{
+ struct l1sched_ts *l1ts = lchan->ts->priv;
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;
+ bool found = false;
+
+ if (!l1ts) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "%s lchan with uninitialized scheduler structure\n",
+ (active) ? "Activating" : "Deactivating");
+ return -EINVAL;
+ }
+
+ /* VAMOS: convert Osmocom specific channel number to a generic one,
+ * otherwise we won't match anything in trx_chan_desc[]. */
+ if (lchan->ts->vamos.is_shadow)
+ chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK;
/* 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.
- * FIXME: Is it possible at all? Clarify if so. */
- if ((trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH)
- && !(trx_chan_desc[i].flags & TRX_CHAN_FLAG_PDCH))
+ for (enum trx_chan_type chan = 0; chan < _TRX_CHAN_MAX; chan++) {
+ if (trx_chan_desc[chan].chan_nr != (chan_nr & RSL_CHAN_NR_MASK))
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;
- }
+ if (trx_chan_desc[chan].link_id != link_id)
+ continue;
+ if (l1ts->chan_state[chan].active == active)
+ continue;
+ found = true;
+ _trx_sched_set_lchan(lchan, chan, active);
}
/* disable handover detection (on deactivation) */
if (!active)
- _sched_act_rach_det(l1t, tn, ss, 0);
+ _sched_act_rach_det(lchan->ts->trx, tn, ss, 0);
- return rc;
+ return found ? 0 : -EINVAL;
+}
+
+int trx_sched_set_ul_access(struct gsm_lchan *lchan, uint8_t chan_nr, bool active)
+{
+ struct l1sched_ts *l1ts = lchan->ts->priv;
+ uint8_t tn = L1SAP_CHAN2TS(chan_nr);
+ uint8_t ss = l1sap_chan2ss(chan_nr);
+ int i;
+
+ if (!l1ts) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "%s UL access on lchan with uninitialized scheduler structure.\n",
+ (active) ? "Activating" : "Deactivating");
+ return -EINVAL;
+ }
+
+ /* look for all matching chan_nr */
+ for (i = 0; i < _TRX_CHAN_MAX; i++) {
+ if (trx_chan_desc[i].chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) {
+ struct l1sched_chan_state *l1cs = &l1ts->chan_state[i];
+
+ l1cs->ho_rach_detect = active;
+ }
+ }
+
+ _sched_act_rach_det(lchan->ts->trx, tn, ss, active);
+
+ return 0;
+}
+
+int trx_sched_set_bcch_ccch(struct gsm_lchan *lchan, bool active)
+{
+ struct l1sched_ts *l1ts = lchan->ts->priv;
+ static const enum trx_chan_type chans[] = {
+ TRXC_FCCH,
+ TRXC_SCH,
+ TRXC_BCCH,
+ TRXC_RACH,
+ TRXC_CCCH,
+ };
+
+ if (!l1ts)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(chans); i++) {
+ enum trx_chan_type chan = chans[i];
+
+ if (l1ts->chan_state[chan].active == active)
+ continue;
+ _trx_sched_set_lchan(lchan, chan, active);
+ }
+
+ return 0;
}
/* 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,
+int trx_sched_set_mode(struct gsm_bts_trx_ts *ts, 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)
{
+ struct l1sched_ts *l1ts = ts->priv;
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)
+ if (ts->pchan == GSM_PCHAN_PDCH)
return 0;
+ /* VAMOS: convert Osmocom specific channel number to a generic one,
+ * otherwise we won't match anything in trx_chan_desc[]. */
+ if (ts->vamos.is_shadow)
+ chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK;
+
/* 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);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[i];
+
+ LOGP(DL1C, LOGL_INFO,
+ "%s Set mode for %s (rsl_cmode=%u, tch_mode=%u, handover=%u)\n",
+ gsm_ts_name(ts), trx_chan_desc[i].name,
+ rsl_cmode, tch_mode, handover);
+
chan_state->rsl_cmode = rsl_cmode;
chan_state->tch_mode = tch_mode;
chan_state->ho_rach_detect = handover;
@@ -1039,8 +1237,8 @@ int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmo
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;
+ chan_state->lqual_cb_sum = 0;
+ chan_state->lqual_cb_num = 0;
}
rc = 0;
}
@@ -1051,53 +1249,54 @@ int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmo
* 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);
+ _sched_act_rach_det(ts->trx, 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)
+int trx_sched_set_cipher(struct gsm_lchan *lchan, uint8_t chan_nr, bool downlink)
{
- 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;
+ int algo = lchan->encr.alg_id - 1;
+ int i, rc = -EINVAL;
/* no cipher for PDCH */
- if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH)
+ if (ts_pchan(lchan->ts) == GSM_PCHAN_PDCH)
return 0;
+ /* VAMOS: convert Osmocom specific channel number to a generic one,
+ * otherwise we won't match anything in trx_chan_desc[]. */
+ if (lchan->ts->vamos.is_shadow)
+ chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK;
+
/* 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);
+ else if (lchan->encr.key_len != 8 && lchan->encr.key_len != 16) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR,
+ "Algo A5/%d not supported with given key_len=%u\n",
+ algo, lchan->encr.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].flags & TRX_CHAN_FLAG_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 (trx_chan_desc[i].chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) {
+ struct l1sched_ts *l1ts = lchan->ts->priv;
+ struct l1sched_chan_state *l1cs = &l1ts->chan_state[i];
+
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Set A5/%d %s for %s\n",
+ algo, (downlink) ? "downlink" : "uplink",
+ trx_chan_desc[i].name);
+
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;
+ l1cs->dl_encr_algo = algo;
+ memcpy(l1cs->dl_encr_key, lchan->encr.key, lchan->encr.key_len);
+ l1cs->dl_encr_key_len = lchan->encr.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;
+ l1cs->ul_encr_algo = algo;
+ memcpy(l1cs->ul_encr_key, lchan->encr.key, lchan->encr.key_len);
+ l1cs->ul_encr_key_len = lchan->encr.key_len;
}
rc = 0;
}
@@ -1107,9 +1306,8 @@ int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink,
}
/* process ready-to-send */
-int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn)
+int _sched_rts(const struct l1sched_ts *l1ts, 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;
@@ -1137,87 +1335,99 @@ int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn)
return 0;
/* check if channel is active */
- if (!TRX_CHAN_IS_ACTIVE(&l1ts->chan_state[chan], chan))
+ if (!l1ts->chan_state[chan].active)
return -EINVAL;
- return func(l1t, tn, fn, frame->dl_chan);
+ /* There is no burst, just for logging */
+ struct trx_dl_burst_req dbr = {
+ .fn = fn,
+ .tn = l1ts->ts->nr,
+ .bid = bid,
+ .chan = chan,
+ };
+
+ return func(l1ts, &dbr);
+}
+
+static void trx_sched_apply_att(const struct gsm_lchan *lchan,
+ struct trx_dl_burst_req *br)
+{
+ const struct trx_chan_desc *desc = &trx_chan_desc[br->chan];
+
+ /* Current BS power reduction value in dB */
+ br->att = lchan->bs_power_ctrl.current;
+
+ /* Temporary Overpower for SACCH/FACCH bursts */
+ if (!lchan->top_acch_active)
+ return;
+ if ((lchan->top_acch_cap.sacch_enable && desc->link_id == LID_SACCH) ||
+ (lchan->top_acch_cap.facch_enable && br->flags & TRX_BR_F_FACCH)) {
+ if (br->att > lchan->top_acch_cap.overpower_db)
+ br->att -= lchan->top_acch_cap.overpower_db;
+ else
+ br->att = 0;
+ }
}
/* process downlink burst */
-const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn,
- uint32_t fn, uint16_t *nbits)
+void _sched_dl_burst(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
{
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
- struct l1sched_chan_state *l1cs;
+ const struct l1sched_chan_state *l1cs;
const struct trx_sched_frame *frame;
- uint8_t offset, period, bid;
+ uint8_t offset, period;
trx_sched_dl_func *func;
- enum trx_chan_type chan;
- ubit_t *bits = NULL;
if (!l1ts->mf_index)
- goto no_data;
+ return;
/* get frame from multiframe */
period = l1ts->mf_period;
- offset = fn % period;
+ offset = br->fn % period;
frame = l1ts->mf_frames + offset;
- chan = frame->dl_chan;
- bid = frame->dl_bid;
- func = trx_chan_desc[chan].dl_fn;
+ br->chan = frame->dl_chan;
+ br->bid = frame->dl_bid;
+ func = trx_chan_desc[br->chan].dl_fn;
- l1cs = &l1ts->chan_state[chan];
+ l1cs = &l1ts->chan_state[br->chan];
/* check if channel is active */
- if (!TRX_CHAN_IS_ACTIVE(l1cs, chan)) {
- if (nbits)
- *nbits = GSM_BURST_LEN;
- goto no_data;
- }
+ if (!l1cs->active)
+ return;
+
+ /* Training Sequence Code and Set */
+ br->tsc_set = l1ts->ts->tsc_set;
+ br->tsc = l1ts->ts->tsc;
/* get burst from function */
- bits = func(l1t, tn, fn, chan, bid, nbits);
+ if (func(l1ts, br) != 0)
+ return;
+
+ /* Modulation is indicated by func() */
+ br->mod = l1cs->dl_mod_type;
+
+ /* BS Power reduction (in dB) per logical channel */
+ if (l1cs->lchan != NULL)
+ trx_sched_apply_att(l1cs->lchan, br);
/* encrypt */
- if (bits && l1cs->dl_encr_algo) {
+ if (br->burst_len && l1cs->dl_encr_algo) {
ubit_t ks[114];
int i;
- osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL);
+ osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, br->fn, ks, NULL);
for (i = 0; i < 57; i++) {
- bits[i + 3] ^= ks[i];
- bits[i + 88] ^= ks[i + 57];
+ br->burst[i + 3] ^= ks[i];
+ br->burst[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)
+static int trx_sched_calc_frame_loss(struct l1sched_ts *l1ts,
+ struct l1sched_chan_state *l1cs,
+ const struct trx_ul_burst_ind *bi)
{
- 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;
@@ -1230,13 +1440,8 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t,
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) {
+ switch (bi->chan) {
case TRXC_IDLE:
case TRXC_RACH:
case TRXC_PDTCH:
@@ -1249,9 +1454,9 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t,
}
/* How many frames elapsed since the last one? */
- elapsed_fs = TDMA_FN_SUB(fn, l1cs->last_tdma_fn);
+ elapsed_fs = GSM_TDMA_FN_SUB(bi->fn, l1cs->last_tdma_fn);
if (elapsed_fs > l1ts->mf_period) { /* Too many! */
- LOGL1S(DL1P, LOGL_ERROR, l1t, tn, frame_head->ul_chan, fn,
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi,
"Too many (>%u) contiguous TDMA frames=%u elapsed "
"since the last processed fn=%u\n", l1ts->mf_period,
elapsed_fs, l1cs->last_tdma_fn);
@@ -1263,21 +1468,21 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t,
* 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.
+ * we didn't receive the corresponding 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);
+ fn_i = GSM_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)
+ if (frame->ul_chan == bi->chan)
l1cs->lost_tdma_fs++;
}
if (l1cs->lost_tdma_fs > 0) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame_head->ul_chan, fn,
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi,
"At least %u TDMA frames were lost since the last "
"processed fn=%u\n", l1cs->lost_tdma_fs, l1cs->last_tdma_fn);
@@ -1290,31 +1495,33 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t,
trx_sched_ul_func *func;
/* Prepare dummy burst indication */
- struct trx_ul_burst_ind bi = {
+ struct trx_ul_burst_ind dbi = {
.flags = TRX_BI_F_NOPE_IND,
.burst_len = GSM_BURST_LEN,
.burst = { 0 },
.rssi = -128,
.toa256 = 0,
+ .chan = bi->chan,
/* TDMA FN is set below */
- .tn = tn,
+ .tn = bi->tn,
};
for (i = 1; i < elapsed_fs; i++) {
- fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i);
+ fn_i = GSM_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)
+ if (frame->ul_chan != bi->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);
+ dbi.bid = frame->ul_bid;
+ dbi.fn = fn_i;
- bi.fn = fn_i;
- func(l1t, frame->ul_chan, frame->ul_bid, &bi);
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, &dbi,
+ "Substituting lost burst with NOPE.ind\n");
+
+ func(l1ts, &dbi);
l1cs->lost_tdma_fs--;
}
@@ -1323,15 +1530,42 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t,
return 0;
}
+/* Process a single noise measurement for an inactive timeslot. */
+static void trx_sched_noise_meas(struct l1sched_chan_state *l1cs,
+ const struct trx_ul_burst_ind *bi)
+{
+ int *Avg = &l1cs->meas.interf_avg;
+
+ /* EWMA (Exponentially Weighted Moving Average):
+ *
+ * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1]
+ *
+ * Implemented using the '+=' operator:
+ *
+ * Avg += a * Val - a * Avg
+ * Avg += a * (Val - Avg)
+ *
+ * We use constant 'a' = 0.5, what is equal to:
+ *
+ * Avg += (Val - Avg) / 2
+ *
+ * We don't really need precisity here, so no scaling.
+ */
+
+ *Avg += (bi->rssi - *Avg) / 2;
+}
+
/* Process an Uplink burst indication */
-int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi)
+int trx_sched_ul_burst(struct l1sched_ts *l1ts, struct trx_ul_burst_ind *bi)
{
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
struct l1sched_chan_state *l1cs;
const struct trx_sched_frame *frame;
- uint8_t offset, period, bid;
+ uint8_t offset, period;
trx_sched_ul_func *func;
- enum trx_chan_type chan;
+
+ /* VAMOS: redirect to the shadow timeslot */
+ if (bi->flags & TRX_BI_F_SHADOW_IND)
+ l1ts = l1ts->ts->vamos.peer->priv;
if (!l1ts->mf_index)
return -EINVAL;
@@ -1341,26 +1575,37 @@ int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi)
offset = bi->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;
+ bi->chan = frame->ul_chan;
+ bi->bid = frame->ul_bid;
+ l1cs = &l1ts->chan_state[bi->chan];
+ func = trx_chan_desc[bi->chan].ul_fn;
/* check if channel is active */
- if (!TRX_CHAN_IS_ACTIVE(l1cs, chan))
- return -EINVAL;
+ if (!l1cs->active) {
+ /* handle noise measurements on dedicated and idle channels */
+ if (TRX_CHAN_IS_DEDIC(bi->chan) || bi->chan == TRXC_IDLE)
+ trx_sched_noise_meas(l1cs, bi);
+ return 0;
+ }
/* 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, bi->tn, bi->fn);
+ trx_sched_calc_frame_loss(l1ts, l1cs, bi);
/* update TDMA frame counters */
l1cs->last_tdma_fn = bi->fn;
l1cs->proc_tdma_fs++;
+ /* handle NOPE indications */
+ if (bi->flags & TRX_BI_F_NOPE_IND) {
+ /* NOTE: Uplink burst handler must check bi->burst_len before
+ * accessing bi->burst to avoid uninitialized memory access. */
+ return func(l1ts, bi);
+ }
+
/* decrypt */
if (bi->burst_len && l1cs->ul_encr_algo) {
ubit_t ks[114];
@@ -1376,13 +1621,7 @@ int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi)
}
/* Invoke the logical channel handler */
- func(l1t, chan, bid, bi);
+ func(l1ts, bi);
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
index b969407c..60b63531 100644
--- a/src/common/scheduler_mframe.c
+++ b/src/common/scheduler_mframe.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -1000,14 +1000,14 @@ 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)
+bool trx_sched_is_sacch_fn(const 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);
+ i = find_sched_mframe_idx(ts_pchan(ts), ts->nr);
if (i < 0)
return -EINVAL;
sched = &trx_sched_multiframes[i];
diff --git a/src/common/sysinfo.c b/src/common/sysinfo.c
index c41f9d6e..48b1a19e 100644
--- a/src/common/sysinfo.c
+++ b/src/common/sysinfo.c
@@ -1,4 +1,4 @@
-/* (C) 2011-2019 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2011-2020 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -18,6 +18,7 @@
*/
#include <stdint.h>
+#include <errno.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/sysinfo.h>
@@ -25,6 +26,8 @@
#include <osmo-bts/logging.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_trx.h>
/* properly increment SI2q index and return SI2q data for scheduling */
static inline uint8_t *get_si2q_inc_index(struct gsm_bts *bts)
@@ -96,7 +99,7 @@ uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time)
tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater;
tc4_cnt += 1;
}
- if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) {
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13) && pcu_connected()) {
tc4_sub[tc4_cnt] = SYSINFO_TYPE_13;
tc4_cnt += 1;
}
@@ -148,33 +151,37 @@ uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time)
return 0;
}
-uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg)
+uint8_t num_agch(const struct gsm_bts_trx *trx, const char * arg)
{
- struct gsm_bts *b = trx->bts;
- struct gsm48_system_information_type_3 *si3;
+ const struct gsm_bts *b = trx->bts;
+ const 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 "
+ LOGP(DL1P, LOGL_NOTICE, "%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)
+/* Returns position of the NCH accroding to SI1 rest octets. See Table 10.5.2.32.1 of TS 44.018.
+ * Returns < 0, if not present. */
+int pos_nch(const struct gsm_bts_trx *trx, const char *arg)
{
- 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);
+ const struct gsm_bts *b = trx->bts;
+ const struct gsm48_system_information_type_1 *si1;
+
+ if (GSM_BTS_HAS_SI(b, SYSINFO_TYPE_1)) {
+ si1 = GSM_BTS_SI(b, SYSINFO_TYPE_1);
+ if (si1->rest_octets[0] & 0x80) {
+ /* H <NCH Position : bit (5)> */
+ return (si1->rest_octets[0] >> 2) & 0x1f;
+ }
+ return -ENOTSUP;
}
- LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, "SACCH no SI available\n");
- return NULL;
+ LOGP(DL1P, LOGL_NOTICE, "%s: Unable to determine actual NCH Position "
+ "value as SI1 is not available yet.\n", arg);
+ return -EINVAL;
}
/* re-generate SI3 restoctets with GPRS indicator depending on the PCU socket connection state */
@@ -195,17 +202,79 @@ void regenerate_si3_restoctets(struct gsm_bts *bts)
/* Create a temporary copy and patch that, if no PCU is around */
si3ro_tmp = bts->si3_ro_decoded;
if (!pcu_connected()) {
- if (!bts->si3_gprs_ind_disabled)
- LOGP(DPCU, LOGL_NOTICE, "Disabling GPRS Indicator in SI3 (No PCU connected)\n");
- bts->si3_gprs_ind_disabled = true;
+ if (!bts->si_gprs_ind_disabled)
+ LOGP(DPCU, LOGL_NOTICE, "Disabling GPRS Indicator in SI (No PCU connected)\n");
+ bts->si_gprs_ind_disabled = true;
si3ro_tmp.gprs_ind.present = 0;
} else {
- if (bts->si3_gprs_ind_disabled)
- LOGP(DPCU, LOGL_NOTICE, "Enabling GPRS Indicator in SI3 (PCU connected)\n");
- bts->si3_gprs_ind_disabled = false;
+ if (bts->si_gprs_ind_disabled)
+ LOGP(DPCU, LOGL_NOTICE, "Enabling GPRS Indicator in SI (PCU connected)\n");
+ bts->si_gprs_ind_disabled = false;
si3ro_tmp.gprs_ind.present = 1; /* is a no-op as we copy from bts->si3_ro_decoded */
}
/* re-generate the binary SI3 rest octets */
osmo_gsm48_rest_octets_si3_encode(si3_buf + si3_size, &si3ro_tmp);
}
+
+/* get the offset of the SI4 rest octets */
+int get_si4_ro_offset(const uint8_t *si4_buf)
+{
+ const struct gsm48_system_information_type_4 *si4 =
+ (const struct gsm48_system_information_type_4 *) si4_buf;
+ int si4_size;
+
+ /* start with the length of the mandatory part */
+ si4_size = offsetof(struct gsm48_system_information_type_4, data);
+ /* then add optional parts, if any */
+ if (si4->data[0] == GSM48_IE_CBCH_CHAN_DESC) {
+ /* fixed 4-byte TV IE, see Table 9.1.36.1 of TS 44.018 */
+ si4_size += 4;
+ if (si4->data[4] == GSM48_IE_CBCH_MOB_AL)
+ si4_size += TLV_GROSS_LEN(si4->data[5]);
+ }
+
+ if (si4_size >= GSM_MACBLOCK_LEN)
+ return -EINVAL;
+
+ return si4_size;
+}
+
+/* re-generate SI4 restoctets with GPRS indicator depending on the PCU socket connection state */
+void regenerate_si4_restoctets(struct gsm_bts *bts)
+{
+ uint8_t *si4_buf = GSM_BTS_SI(bts, SYSINFO_TYPE_4);
+ struct osmo_gsm48_si_ro_info si4ro_tmp;
+ int si4_size;
+
+ /* If BSC has never set SI4, there's nothing to patch */
+ if (!GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_4))
+ return;
+
+ /* If SI4 from BSC doesn't have a GPRS indicator, we won't have anything to patch */
+ if (!bts->si4_ro_decoded.gprs_ind.present)
+ return;
+
+ si4_size = get_si4_ro_offset(si4_buf);
+ if (si4_size < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Cannot parse SI4, hence not patching GPRS indicator\n");
+ return;
+ }
+
+ /* Create a temporary copy and patch that, if no PCU is around */
+ si4ro_tmp = bts->si4_ro_decoded;
+ if (!pcu_connected()) {
+ if (!bts->si_gprs_ind_disabled)
+ LOGP(DPCU, LOGL_NOTICE, "Disabling GPRS Indicator in SI (No PCU connected)\n");
+ bts->si_gprs_ind_disabled = true;
+ si4ro_tmp.gprs_ind.present = 0;
+ } else {
+ if (bts->si_gprs_ind_disabled)
+ LOGP(DPCU, LOGL_NOTICE, "Enabling GPRS Indicator in SI (PCU connected)\n");
+ bts->si_gprs_ind_disabled = false;
+ si4ro_tmp.gprs_ind.present = 1; /* is a no-op as we copy from bts->si4_ro_decoded */
+ }
+
+ /* re-generate the binary SI4 rest octets */
+ osmo_gsm48_rest_octets_si4_encode(si4_buf + si4_size, &si4ro_tmp, GSM_MACBLOCK_LEN - si4_size);
+}
diff --git a/src/common/ta_control.c b/src/common/ta_control.c
new file mode 100644
index 00000000..6fe409a2
--- /dev/null
+++ b/src/common/ta_control.c
@@ -0,0 +1,102 @@
+/* Loop control for Timing Advance */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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/>.
+ *
+ */
+
+/* Related specs: 3GPP TS 45.010 sections 5.5, 5.6 */
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_trx.h>
+#include <osmo-bts/logging.h>
+
+/* 3GPP TS 45.010 sec 5.6.3 Delay assessment error:
+ * 75% of one bit duration in 1/256 symbols: 256*0.75 */
+#define TOA256_THRESH 192
+
+/* rqd_ta value range */
+#define TA_MIN 0
+#define TA_MAX 63
+
+/* TODO: make configurable over osmo-bts VTY? Pass it BSC->BTS? */
+#define TA_MAX_INC_STEP 2
+#define TA_MAX_DEC_STEP 2
+
+
+/*! compute the new "Ordered Timing Advance" communicated to the MS and store it in lchan.
+ * \param lchan logical channel for which to compute (and in which to store) new power value.
+ * \param[in] ms_tx_ta The TA used by the MS and reported in L1SACCH, see struct gsm_sacch_l1_hdr field "ta".
+ * \param[in] toa256 Time of Arrival (in 1/256th bits) computed at Rx side
+ */
+void lchan_ms_ta_ctrl(struct gsm_lchan *lchan, uint8_t ms_tx_ta, int16_t toa256)
+{
+ int16_t new_ta;
+ /* Shall we skip current block based on configured interval? */
+
+ /* TA control interval: how many blocks do we skip? */
+ if (lchan->ta_ctrl.skip_block_num-- > 0)
+ return;
+
+ /* Reset the number of SACCH blocks to be skipped:
+ * ctrl_interval=0 => 0 blocks to skip,
+ * ctrl_interval=1 => 1 blocks to skip,
+ * ctrl_interval=2 => 3 blocks to skip,
+ * so basically ctrl_interval * 2 - 1. */
+ lchan->ta_ctrl.skip_block_num = lchan->ts->trx->ta_ctrl_interval * 2 - 1;
+
+ int16_t delta_ta = toa256/256;
+ if (toa256 >= 0) {
+ if ((toa256 - (256 * delta_ta)) > TOA256_THRESH)
+ delta_ta++;
+ if (delta_ta > TA_MAX_INC_STEP)
+ delta_ta = TA_MAX_INC_STEP;
+ } else {
+ if ((toa256 - (256 * delta_ta)) < -TOA256_THRESH)
+ delta_ta--;
+ if (delta_ta < -TA_MAX_DEC_STEP)
+ delta_ta = -TA_MAX_DEC_STEP;
+ }
+
+ new_ta = ms_tx_ta + delta_ta;
+
+ /* Make sure new_ta is never negative: */
+ if (new_ta < TA_MIN)
+ new_ta = TA_MIN;
+
+ /* Don't ask for out of range TA: */
+ if (new_ta > TA_MAX)
+ new_ta = TA_MAX;
+
+ if (lchan->ta_ctrl.current == (uint8_t)new_ta) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG,
+ "Keeping current TA at %u: TOA was %d\n",
+ lchan->ta_ctrl.current, toa256);
+ return;
+ }
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO,
+ "%s TA %u => %u: TOA was too %s (%d)\n",
+ (uint8_t)new_ta > lchan->ta_ctrl.current ? "Raising" : "Lowering",
+ lchan->ta_ctrl.current, (uint8_t)new_ta,
+ (uint8_t)new_ta > lchan->ta_ctrl.current ? "late" : "early",
+ toa256);
+
+ /* store the resulting new TA in the lchan */
+ lchan->ta_ctrl.current = (uint8_t)new_ta;
+}
diff --git a/src/common/tx_power.c b/src/common/tx_power.c
index e418cec5..83cdb629 100644
--- a/src/common/tx_power.c
+++ b/src/common/tx_power.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -29,6 +29,7 @@
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/tx_power.h>
+#include <osmo-bts/bts_trx.h>
static int get_pa_drive_level_mdBm(const struct power_amp *pa,
int desired_p_out_mdBm, unsigned int arfcn)
@@ -42,16 +43,16 @@ static int get_pa_drive_level_mdBm(const struct power_amp *pa,
}
/* maximum output power of the system */
-int get_p_max_out_mdBm(struct gsm_bts_trx *trx)
+int get_p_max_out_mdBm(const struct gsm_bts_trx *trx)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const 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)
+int get_p_nominal_mdBm(const 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);
@@ -60,21 +61,21 @@ int get_p_nominal_mdBm(struct gsm_bts_trx *trx)
/* 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)
+int get_p_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red)
{
- /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */
- return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2);
+ /* Pn subtracted by RSL BS Power Recudtion (in 1 dB steps) */
+ return get_p_nominal_mdBm(trx) - to_mdB(bs_power_red);
}
-int get_p_target_mdBm_lchan(struct gsm_lchan *lchan)
+int get_p_target_mdBm_lchan(const struct gsm_lchan *lchan)
{
- return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power);
+ return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current);
}
/* 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)
+int get_p_actual_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const struct trx_power_params *tpp = &trx->power_params;
/* P_target subtracted by ramp attenuation */
return p_target_mdBm - tpp->ramp.attenuation_mdB;
@@ -82,9 +83,9 @@ int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm)
/* 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)
+int get_p_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const 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;
@@ -92,9 +93,9 @@ int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm)
/* 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)
+int get_p_trxout_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const struct trx_power_params *tpp = &trx->power_params;
int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm;
unsigned int arfcn = trx->arfcn;
@@ -113,14 +114,14 @@ int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_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)
+int get_p_trxout_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const 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;
+ p_target_mdBm = get_p_target_mdBm(trx, bs_power_red) - 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);
@@ -131,9 +132,9 @@ int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie)
/* internal PA input drive level is TRX output power */
return pa_drvlvl_mdBm;
}
-int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan)
+int get_p_trxout_target_mdBm_lchan(const struct gsm_lchan *lchan)
{
- return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power);
+ return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current);
}
@@ -146,9 +147,9 @@ int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan)
* 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)
+static int we_are_ramping_up(const struct gsm_bts_trx *trx)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const struct trx_power_params *tpp = &trx->power_params;
if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm)
return 1;
@@ -171,13 +172,13 @@ static void power_ramp_timer_cb(void *_trx)
/* 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, "
+ LOGPTRX(trx, 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,
+ LOGPTRX(trx, 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 */
@@ -196,9 +197,9 @@ void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_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);
+ LOGPTRX(trx, DL1C, LOGL_ERROR, "bts_model notifies us of %d mdBm TRX "
+ "output power. However, it should be %d mdBm!\n",
+ p_trxout_cur_mdBm, p_trxout_should_mdBm);
}
/* and do another step... */
@@ -210,8 +211,11 @@ 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)
+ if (!first && tpp->ramp.attenuation_mdB == 0) {
+ if (tpp->ramp.compl_cb)
+ tpp->ramp.compl_cb(trx);
return;
+ }
if (we_are_ramping_up(trx)) {
/* ramp up power -> ramp down attenuation */
@@ -235,8 +239,7 @@ static void power_ramp_do_step(struct gsm_bts_trx *trx, int first)
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)
+int _power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass, ramp_compl_cb_t ramp_compl_cb, bool skip_ramping)
{
struct trx_power_params *tpp = &trx->power_params;
@@ -244,35 +247,43 @@ int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass)
* 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);
+ LOGPTRX(trx, DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d%s)\n",
+ tpp->p_total_cur_mdBm, p_total_tgt_mdBm, bypass ? ", bypass" : "");
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));
+ LOGPTRX(trx, 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);
+ power_ramp_abort(trx);
/* set the new target */
tpp->p_total_tgt_mdBm = p_total_tgt_mdBm;
-
- if (we_are_ramping_up(trx)) {
+ tpp->ramp.compl_cb = ramp_compl_cb;
+
+ if (skip_ramping) {
+ /* Jump straight to the target power */
+ tpp->p_total_cur_mdBm = p_total_tgt_mdBm;
+ tpp->ramp.attenuation_mdB = 0;
+ power_ramp_timer_cb(trx);
+ } else 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);
+ LOGPTRX(trx, DL1C, LOGL_INFO,
+ "target_power (%d mdBm) is below or equal to 'power ramp max-initial' power (%d mdBm)\n",
+ tpp->p_total_tgt_mdBm, tpp->ramp.max_initial_pout_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 */
+ /* We need to step it up. Start from the current value, shortcutting to max-initial. */
/* Set attenuation to cause no power change right now */
+ if (tpp->p_total_cur_mdBm + (int)tpp->ramp.step_size_mdB < tpp->ramp.max_initial_pout_mdBm)
+ tpp->p_total_cur_mdBm = tpp->ramp.max_initial_pout_mdBm - tpp->ramp.step_size_mdB;
tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
/* start with the first step */
@@ -290,10 +301,16 @@ int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass)
return 0;
}
+/* Cancel any pending request */
+void power_ramp_abort(struct gsm_bts_trx *trx)
+{
+ osmo_timer_del(&trx->power_params.ramp.step_timer);
+}
+
/* determine the initial transceiver output power at start-up time */
-int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx)
+int power_ramp_initial_power_mdBm(const struct gsm_bts_trx *trx)
{
- struct trx_power_params *tpp = &trx->power_params;
+ const struct trx_power_params *tpp = &trx->power_params;
int pout_mdBm;
/* this is the maximum initial output on the antenna connector
diff --git a/src/common/vty.c b/src/common/vty.c
index 801f34c0..c0008a85 100644
--- a/src/common/vty.c
+++ b/src/common/vty.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -22,10 +22,12 @@
#include "btsconfig.h"
#include <inttypes.h>
+#include <limits.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
+#include <errno.h>
#include <osmocom/core/talloc.h>
#include <osmocom/gsm/abis_nm.h>
@@ -36,16 +38,20 @@
#include <osmocom/vty/logging.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/ports.h>
+#include <osmocom/vty/tdef_vty.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/sockaddr_str.h>
#include <osmocom/trau/osmo_ortp.h>
-
+#include <osmocom/core/fsm.h>
+#include <osmocom/codec/codec.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/bts_sm.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/signal.h>
@@ -54,6 +60,7 @@
#include <osmo-bts/measurement.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/osmux.h>
#define VTY_STR "Configure the VTY\n"
@@ -64,23 +71,45 @@
#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
+/* INT32_MAX, because osmo_wqueue_init takes int as an argument
+ * and INT_MAX can't be stringified as a decimal */
+#define BTS_CFG_PCU_SOCK_WQUEUE_LEN_MAX_MAX 2147483647
+
+#define X(x) (1 << x)
int g_vty_port_num = OSMO_VTY_PORT_BTS;
+static 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 }
+};
+
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",
+ 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",
+ vty_out(vty, "%% Cannot find PHY instance number %d%s",
inst_nr, VTY_NEWLINE);
return NULL;
}
@@ -111,63 +140,9 @@ int bts_vty_go_parent(struct vty *vty)
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"
+ "Copyright (C) 2010-2011 by Harald Welte, Andreas Eversberg and On-Waves\r\n"
+ "Copyright (C) 2011-2022 by sysmocom - s.f.m.c. GmbH\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";
@@ -177,16 +152,18 @@ struct vty_app_info bts_vty_info = {
.version = PACKAGE_VERSION,
.copyright = osmobts_copyright,
.go_parent_cb = bts_vty_go_parent,
- .is_config_node = bts_vty_is_config_node,
+ .usr_attr_desc = {
+ [BTS_VTY_ATTR_NEW_LCHAN] = \
+ "This command applies for newly created lchans",
+ [BTS_VTY_TRX_POWERCYCLE] = \
+ "This command applies when the TRX powercycles or restarts",
+ },
+ .usr_attr_letters = {
+ [BTS_VTY_ATTR_NEW_LCHAN] = 'l',
+ [BTS_VTY_TRX_POWERCYCLE] = 'p',
+ },
};
-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)# ",
@@ -199,6 +176,12 @@ static struct cmd_node trx_node = {
1,
};
+static struct cmd_node osmux_node = {
+ OSMUX_NODE,
+ "%s(osmux)# ",
+ 1,
+};
+
gDEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd,
"auto-band",
"Automatically select band for ARFCN based on configured band\n")
@@ -219,10 +202,94 @@ gDEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd,
return CMD_SUCCESS;
}
+DEFUN_ATTR(cfg_bts_osmux, cfg_bts_osmux_cmd,
+ "osmux",
+ "Configure Osmux\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ vty->node = OSMUX_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_osmux_use, cfg_bts_osmux_use_cmd,
+ "use (off|on|only)",
+ "Configure Osmux usage\n"
+ "Never use Osmux\n"
+ "Use Osmux if requested by BSC (default)\n"
+ "Always use Osmux, reject non-Osmux BSC requests\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ if (strcmp(argv[0], "off") == 0)
+ bts->osmux.use = OSMUX_USAGE_OFF;
+ else if (strcmp(argv[0], "on") == 0)
+ bts->osmux.use = OSMUX_USAGE_ON;
+ else if (strcmp(argv[0], "only") == 0)
+ bts->osmux.use = OSMUX_USAGE_ONLY;
+ return CMD_SUCCESS;
+}
-DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
- "trx <0-254>",
- "Select a TRX to configure\n" "TRX number\n")
+DEFUN(cfg_bts_osmux_ip,
+ cfg_bts_osmux_ip_cmd,
+ "local-ip " VTY_IPV46_CMD,
+ IP_STR
+ "IPv4 Address to bind to\n"
+ "IPv6 Address to bind to\n")
+{
+ struct gsm_bts *bts = vty->index;
+ osmo_talloc_replace_string(bts, &bts->osmux.local_addr, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_osmux_port,
+ cfg_bts_osmux_port_cmd,
+ "local-port <1-65535>",
+ "Osmux port\n" "UDP port\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->osmux.local_port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_osmux_batch_factor,
+ cfg_bts_osmux_batch_factor_cmd,
+ "batch-factor <1-8>",
+ "Batching factor\n" "Number of messages in the batch\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->osmux.batch_factor = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_osmux_batch_size,
+ cfg_bts_osmux_batch_size_cmd,
+ "batch-size <1-65535>",
+ "Batch size\n" "Batch size in bytes\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->osmux.batch_size = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_osmux_dummy_padding,
+ cfg_bts_osmux_dummy_padding_cmd,
+ "dummy-padding (on|off)",
+ "Dummy padding\n"
+ "Enable dummy padding\n"
+ "Disable dummy padding (default)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ if (strcmp(argv[0], "on") == 0)
+ bts->osmux.dummy_padding = true;
+ else if (strcmp(argv[0], "off") == 0)
+ bts->osmux.dummy_padding = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_trx, cfg_bts_trx_cmd,
+ "trx <0-254>",
+ "Select a TRX to configure\n" "TRX number\n",
+ CMD_ATTR_IMMEDIATE)
{
int trx_nr = atoi(argv[0]);
struct gsm_bts *bts = vty->index;
@@ -240,7 +307,7 @@ DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
*/
trx = gsm_bts_trx_alloc(bts);
if (trx)
- bts_trx_init(trx);
+ bts_model_trx_init(trx);
} else
trx = gsm_bts_trx_num(bts, trx_nr);
@@ -257,11 +324,66 @@ DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
return CMD_SUCCESS;
}
-static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+static void config_write_dpc_params(struct vty *vty, const char *prefix,
+ const struct gsm_power_ctrl_params *params)
{
- struct gsm_bts_trx *trx;
+ const struct gsm_power_ctrl_meas_params *mp = &params->rxlev_meas;
+
+ if (mp->lower_thresh != power_ctrl_params_def.rxlev_meas.lower_thresh ||
+ mp->upper_thresh != power_ctrl_params_def.rxlev_meas.upper_thresh) {
+ int target = (mp->lower_thresh + mp->upper_thresh) / 2;
+ int hyst = (mp->upper_thresh - mp->lower_thresh) / 2;
+
+ vty_out(vty, " %s-power-target %d", prefix, rxlev2dbm(target));
+ if (hyst > 0)
+ vty_out(vty, " hysteresis %d", hyst);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
+
+ /* MS Tx power filtering algorithm and parameters */
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: /* EWMA is the default */
+ if (mp->ewma.alpha != power_ctrl_params_def.rxlev_meas.ewma.alpha)
+ vty_out(vty, " %s-power-filtering algo ewma beta %u%s",
+ prefix, 100 - mp->ewma.alpha, VTY_NEWLINE);
+ break;
+ /* Other algorithm cannot be set via the VTY */
+ case BTS_PF_ALGO_NONE:
+ default:
+ vty_out(vty, " no %s-power-filtering%s", prefix, VTY_NEWLINE);
+ break;
+ }
+}
+
+static void config_write_osmux(struct vty *vty, const char *prefix, const struct gsm_bts *bts)
+{
+ vty_out(vty, "%sosmux%s", prefix, VTY_NEWLINE);
+ vty_out(vty, "%s use ", prefix);
+ switch (bts->osmux.use) {
+ case OSMUX_USAGE_ON:
+ vty_out(vty, "on%s", VTY_NEWLINE);
+ break;
+ case OSMUX_USAGE_ONLY:
+ vty_out(vty, "only%s", VTY_NEWLINE);
+ break;
+ case OSMUX_USAGE_OFF:
+ default:
+ vty_out(vty, "off%s", VTY_NEWLINE);
+ break;
+ }
+ vty_out(vty, "%s local-ip %s%s", prefix, bts->osmux.local_addr, VTY_NEWLINE);
+ vty_out(vty, "%s local-port %u%s", prefix, bts->osmux.local_port, VTY_NEWLINE);
+ vty_out(vty, "%s batch-factor %d%s", prefix, bts->osmux.batch_factor, VTY_NEWLINE);
+ vty_out(vty, "%s batch-size %u%s", prefix, bts->osmux.batch_size, VTY_NEWLINE);
+ vty_out(vty, "%s dummy-padding %s%s", prefix, bts->osmux.dummy_padding ? "on" : "off", VTY_NEWLINE);
+}
+
+static void config_write_bts_single(struct vty *vty, const struct gsm_bts *bts)
+{
+ const struct gsm_bts_trx *trx;
const char *sapi_buf;
int i;
+ struct bsc_oml_host *bsc_oml_host;
vty_out(vty, "bts %u%s", bts->nr, VTY_NEWLINE);
if (bts->description)
@@ -271,18 +393,32 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
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);
+ llist_for_each_entry(bsc_oml_host, &bts->bsc_oml_hosts, list)
+ vty_out(vty, " oml remote-ip %s%s", bsc_oml_host->addr, 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);
+ if (bts->rtp_ip_dscp != -1)
+ vty_out(vty, " rtp ip-dscp %d%s", bts->rtp_ip_dscp, VTY_NEWLINE);
+ if (bts->rtp_priority != -1)
+ vty_out(vty, " rtp socket-priority %d%s", bts->rtp_priority, VTY_NEWLINE);
+ if (bts->rtp_nogaps_mode)
+ vty_out(vty, " rtp continuous-streaming%s", VTY_NEWLINE);
+ vty_out(vty, " %srtp internal-uplink-ecu%s",
+ bts->use_ul_ecu ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " rtp hr-format %s%s",
+ bts->emit_hr_rfc5993 ? "rfc5993" : "ts101318", 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);
+
+ /* Fall-back MS Power Control parameters may be changed by the user */
+ config_write_dpc_params(vty, "uplink", &bts->ms_dpc_params);
+
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)
@@ -290,16 +426,33 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
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));
+ if (bts->gsmtap.remote_host != NULL)
+ vty_out(vty, " gsmtap-remote-host %s%s",
+ bts->gsmtap.remote_host,
+ VTY_NEWLINE);
+ if (bts->gsmtap.local_host != NULL)
+ vty_out(vty, " gsmtap-local-host %s%s",
+ bts->gsmtap.local_host,
+ VTY_NEWLINE);
+ for (i = 0; i < sizeof(uint32_t) * 8; i++) {
+ if (bts->gsmtap.sapi_mask & ((uint32_t) 1 << i)) {
+ sapi_buf = get_value_string_or_null(gsmtap_sapi_names, i);
+ if (sapi_buf == NULL)
+ continue;
+ sapi_buf = osmo_str_tolower(sapi_buf);
vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE);
}
}
- if (gsmtap_sapi_acch) {
+ if (bts->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);
}
+ if (bts->gsmtap.rlp) {
+ if (bts->gsmtap.rlp_skip_null)
+ vty_out(vty, " gsmtap-rlp skip-null%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " gsmtap-rlp%s", VTY_NEWLINE);
+ }
vty_out(vty, " min-qual-rach %d%s", bts->min_qual_rach,
VTY_NEWLINE);
vty_out(vty, " min-qual-norm %d%s", bts->min_qual_norm,
@@ -308,20 +461,24 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
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->pcu.sock_wqueue_len_max != BTS_CFG_PCU_SOCK_WQUEUE_LEN_MAX_MAX)
+ vty_out(vty, " pcu-socket-wqueue-length %u%s", bts->pcu.sock_wqueue_len_max, VTY_NEWLINE);
if (bts->supp_meas_toa256)
vty_out(vty, " supp-meas-info toa256%s", VTY_NEWLINE);
vty_out(vty, " smscb queue-max-length %d%s", bts->smscb_queue_max_len, VTY_NEWLINE);
vty_out(vty, " smscb queue-target-length %d%s", bts->smscb_queue_tgt_len, VTY_NEWLINE);
vty_out(vty, " smscb queue-hysteresis %d%s", bts->smscb_queue_hyst, VTY_NEWLINE);
+ config_write_osmux(vty, " ", bts);
+
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);
+ const struct trx_power_params *tpp = &trx->power_params;
+ const 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)
+ if (tpp->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",
@@ -331,8 +488,10 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
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",
+ trx->ms_pwr_ctl_soft ? "osmo" : "dsp",
VTY_NEWLINE);
+ vty_out(vty, " ta-control interval %u%s",
+ trx->ta_ctrl_interval, VTY_NEWLINE);
vty_out(vty, " phy %u instance %u%s", pinst->phy_link->num,
pinst->num, VTY_NEWLINE);
@@ -342,16 +501,16 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
static int config_write_bts(struct vty *vty)
{
- struct gsm_network *net = gsmnet_from_vty(vty);
- struct gsm_bts *bts;
+ const struct gsm_bts *bts;
- llist_for_each_entry(bts, &net->bts_list, list)
+ llist_for_each_entry(bts, &g_bts_sm->bts_list, list)
config_write_bts_single(vty, bts);
+ osmo_tdef_vty_groups_write(vty, "");
return CMD_SUCCESS;
}
-static void config_write_phy_single(struct vty *vty, struct phy_link *plink)
+static void config_write_phy_single(struct vty *vty, const struct phy_link *plink)
{
int i;
@@ -359,7 +518,7 @@ static void config_write_phy_single(struct vty *vty, struct phy_link *plink)
bts_model_config_write_phy(vty, plink);
for (i = 0; i < 255; i++) {
- struct phy_instance *pinst = phy_instance_by_num(plink, i);
+ const struct phy_instance *pinst = phy_instance_by_num(plink, i);
if (!pinst)
break;
vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE);
@@ -372,7 +531,7 @@ 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);
+ const struct phy_link *plink = phy_link_by_num(i);
if (!plink)
break;
config_write_phy_single(vty, plink);
@@ -396,22 +555,22 @@ DEFUN(cfg_vty_telnet_port, cfg_vty_telnet_port_cmd,
}
/* per-BTS configuration */
-DEFUN(cfg_bts,
- cfg_bts_cmd,
- "bts BTS_NR",
- "Select a BTS to configure\n"
- "BTS Number\n")
+DEFUN_ATTR(cfg_bts,
+ cfg_bts_cmd,
+ "bts BTS_NR",
+ "Select a BTS to configure\n"
+ "BTS Number\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
int bts_nr = atoi(argv[0]);
struct gsm_bts *bts;
- if (bts_nr >= gsmnet->num_bts) {
+ if (bts_nr >= g_bts_sm->num_bts) {
vty_out(vty, "%% Unknown BTS number %u (num %u)%s",
- bts_nr, gsmnet->num_bts, VTY_NEWLINE);
+ bts_nr, g_bts_sm->num_bts, VTY_NEWLINE);
return CMD_WARNING;
} else
- bts = gsm_bts_num(gsmnet, bts_nr);
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
vty->index = bts;
vty->index_sub = &bts->description;
@@ -453,9 +612,11 @@ DEFUN(cfg_bts_band,
struct gsm_bts *bts = vty->index;
int band = gsm_band_parse(argv[0]);
+ /* This should not happen with the recent versions of libosmovty,
+ * but old versions may pass incomplete choice values like 'GSM9'. */
if (band < 0) {
- vty_out(vty, "%% BAND %d is not a valid GSM band%s",
- band, VTY_NEWLINE);
+ vty_out(vty, "%% BAND '%s' is not a valid GSM band%s",
+ argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
@@ -470,11 +631,51 @@ DEFUN(cfg_bts_oml_ip,
"OML Parameters\n" "OML IP Address\n" "OML IP Address\n")
{
struct gsm_bts *bts = vty->index;
+ struct bsc_oml_host *bsc_oml_host;
+
+ /* stop when the address is already in the list */
+ llist_for_each_entry(bsc_oml_host, &bts->bsc_oml_hosts, list) {
+ if (strcmp(argv[0], bsc_oml_host->addr) == 0) {
+ vty_out(vty, "%% duplicate BSC (A-Bis/OML) ip address configured ('%s')%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ bsc_oml_host = talloc_zero(bts, struct bsc_oml_host);
+ OSMO_ASSERT(bsc_oml_host);
+ bsc_oml_host->addr = talloc_strdup(bsc_oml_host, argv[0]);
+ OSMO_ASSERT(bsc_oml_host->addr);
+ llist_add_tail(&bsc_oml_host->list, &bts->bsc_oml_hosts);
- if (bts->bsc_oml_host)
- talloc_free(bts->bsc_oml_host);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_oml_ip,
+ cfg_bts_no_oml_ip_cmd,
+ "no oml remote-ip A.B.C.D",
+ NO_STR "OML Parameters\n" "OML IP Address\n" "OML IP Address\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct bsc_oml_host *bsc_oml_host;
+ struct bsc_oml_host *bsc_oml_host_del = NULL;
+
+ llist_for_each_entry(bsc_oml_host, &bts->bsc_oml_hosts, list) {
+ if (strcmp(argv[0], bsc_oml_host->addr) == 0)
+ bsc_oml_host_del = bsc_oml_host;
+ }
- bts->bsc_oml_host = talloc_strdup(bts, argv[0]);
+ if (bsc_oml_host_del) {
+ if (bts->abis_link_fi) {
+ osmo_fsm_inst_dispatch(bts->abis_link_fi, ABIS_LINK_EV_VTY_RM_ADDR, bsc_oml_host_del);
+ llist_del(&bsc_oml_host_del->list);
+ talloc_free(bsc_oml_host_del);
+ }
+ } else {
+ vty_out(vty, "%% no such BSC (A-Bis/OML) ip address configured ('%s')%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
return CMD_SUCCESS;
}
@@ -491,10 +692,13 @@ DEFUN_DEPRECATED(cfg_bts_rtp_bind_ip,
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")
+DEFUN_USRATTR(cfg_bts_rtp_jitbuf,
+ cfg_bts_rtp_jitbuf_cmd,
+ X(BTS_VTY_ATTR_NEW_LCHAN),
+ "rtp jitter-buffer <0-10000> [adaptive]",
+ RTP_STR "RTP jitter buffer\n"
+ "Jitter buffer in ms\n"
+ "Enable adaptive RTP jitter buffering\n")
{
struct gsm_bts *bts = vty->index;
@@ -506,9 +710,10 @@ DEFUN(cfg_bts_rtp_jitbuf,
}
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")
+ 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"
+ "Port range start (inclusive)\n" "Port range end (inclusive)\n")
{
struct gsm_bts *bts = vty->index;
unsigned int start;
@@ -518,19 +723,19 @@ DEFUN(cfg_bts_rtp_port_range,
end = atoi(argv[1]);
if (end < start) {
- vty_out(vty, "range end port (%u) must be greater than the range start port (%u)!%s",
+ 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",
+ vty_out(vty, "%% Range must begin at an even port number! (%u is odd)%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",
+ vty_out(vty, "%% Range must end at an odd port number! (%u is even)%s",
end, VTY_NEWLINE);
return CMD_WARNING;
}
@@ -542,13 +747,99 @@ DEFUN(cfg_bts_rtp_port_range,
return CMD_SUCCESS;
}
+DEFUN_USRATTR(cfg_bts_rtp_ip_dscp,
+ cfg_bts_rtp_ip_dscp_cmd,
+ X(BTS_VTY_ATTR_NEW_LCHAN),
+ "rtp ip-dscp <0-63>",
+ RTP_STR "Specify DSCP for RTP/IP packets\n" "The DSCP value (upper 6 bits of TOS)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int dscp = atoi(argv[0]);
+
+ bts->rtp_ip_dscp = dscp;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rtp_priority,
+ cfg_bts_rtp_priority_cmd,
+ X(BTS_VTY_ATTR_NEW_LCHAN),
+ "rtp socket-priority <0-255>",
+ RTP_STR "Specify socket priority for RTP/IP packets\n"
+ "The socket priority value (> 6 requires CAP_NET_ADMIN)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int prio = atoi(argv[0]);
+
+ bts->rtp_priority = prio;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rtp_cont_stream,
+ cfg_bts_rtp_cont_stream_cmd,
+ "rtp continuous-streaming",
+ RTP_STR "Always emit an RTP packet every 20 ms\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->rtp_nogaps_mode = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_rtp_cont_stream,
+ cfg_bts_no_rtp_cont_stream_cmd,
+ "no rtp continuous-streaming",
+ NO_STR RTP_STR "Always emit an RTP packet every 20 ms\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->rtp_nogaps_mode = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rtp_int_ul_ecu,
+ cfg_bts_rtp_int_ul_ecu_cmd,
+ "rtp internal-uplink-ecu",
+ RTP_STR "Apply a BTS-internal ECU to the uplink traffic frame stream\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->use_ul_ecu = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_rtp_int_ul_ecu,
+ cfg_bts_no_rtp_int_ul_ecu_cmd,
+ "no rtp internal-uplink-ecu",
+ NO_STR RTP_STR "Apply a BTS-internal ECU to the uplink traffic frame stream\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->use_ul_ecu = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_rtp_hr_format,
+ cfg_bts_rtp_hr_format_cmd,
+ "rtp hr-format (rfc5993|ts101318)",
+ RTP_STR "HRv1 codec output format\n" "RFC 5993\n" "TS 101 318\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->emit_hr_rfc5993 = !strcmp(argv[0], "rfc5993");
+ 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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -557,11 +848,12 @@ DEFUN(cfg_bts_paging_queue_size,
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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -572,13 +864,14 @@ DEFUN(cfg_bts_paging_lifetime,
#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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -589,11 +882,12 @@ DEFUN(cfg_bts_agch_queue_mgmt_params,
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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -604,22 +898,94 @@ DEFUN(cfg_bts_agch_queue_mgmt_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")
+#define UL_POWER_TARGET_CMD \
+ "uplink-power-target <-110-0>"
+#define UL_POWER_TARGET_CMD_DESC \
+ "Set the nominal target Rx Level for uplink power control loop\n" \
+ "Target uplink Rx level in dBm\n"
+
+#define UL_POWER_DEPR_MSG(fmt, args...) \
+ vty_out(vty, "%% Command '%s' has been deprecated.%s" \
+ "%% MS/BS Power control parameters should be configured in osmo-bsc: " \
+ fmt, self->string, VTY_NEWLINE, ##args)
+
+DEFUN_ATTR(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd,
+ UL_POWER_TARGET_CMD, UL_POWER_TARGET_CMD_DESC,
+ CMD_ATTR_DEPRECATED)
+{
+ struct gsm_power_ctrl_meas_params *mp;
+ struct gsm_bts *bts = vty->index;
+ int rxlev_dbm = atoi(argv[0]);
+ int hyst = 0;
+
+ if (argc > 1) /* optional argument */
+ hyst = atoi(argv[1]);
+
+ mp = &bts->ms_dpc_params.rxlev_meas;
+ mp->lower_thresh = dbm2rxlev(rxlev_dbm - hyst);
+ mp->upper_thresh = dbm2rxlev(rxlev_dbm + hyst);
+
+ UL_POWER_DEPR_MSG("use 'rxlev-thresh lower %u upper %u'.%s",
+ mp->lower_thresh, mp->upper_thresh,
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+/* FIXME: libosmovty is unable to handle 'foo <-110-0> [bar <1-25>]' */
+DEFUN_CMD_ELEMENT(cfg_bts_ul_power_target,
+ cfg_bts_ul_power_target_hysteresis_cmd,
+ UL_POWER_TARGET_CMD " hysteresis <1-25>",
+ UL_POWER_TARGET_CMD_DESC
+ "Target Rx Level hysteresis\n"
+ "Tolerable deviation in dBm\n",
+ CMD_ATTR_DEPRECATED, 0);
+
+DEFUN_ATTR(cfg_no_bts_ul_power_filter,
+ cfg_bts_no_ul_power_filter_cmd,
+ "no uplink-power-filtering",
+ NO_STR "Disable filtering for uplink power control loop\n",
+ CMD_ATTR_DEPRECATED)
{
+ struct gsm_power_ctrl_meas_params *mp;
struct gsm_bts *bts = vty->index;
- bts->ul_power_target = atoi(argv[0]);
+ mp = &bts->ms_dpc_params.rxlev_meas;
+ mp->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE;
+
+ UL_POWER_DEPR_MSG("use 'no rxlev-avg'.%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd,
- "min-qual-rach <-100-100>",
- "Set the minimum link quality level of Access Bursts to be accepted\n"
- "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n")
+DEFUN_ATTR(cfg_bts_ul_power_filter_ewma,
+ cfg_bts_ul_power_filter_ewma_cmd,
+ "uplink-power-filtering algo ewma beta <1-99>",
+ "Configure filtering for uplink power control loop\n"
+ "Select the filtering algorithm\n"
+ "Exponentially Weighted Moving Average (EWMA)\n"
+ "Smoothing factor (in %): beta = (100 - alpha)\n"
+ "1% - lowest smoothing, 99% - highest smoothing\n",
+ CMD_ATTR_DEPRECATED)
+{
+ struct gsm_power_ctrl_meas_params *mp;
+ struct gsm_bts *bts = vty->index;
+
+ mp = &bts->ms_dpc_params.rxlev_meas;
+ mp->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA;
+ mp->ewma.alpha = 100 - atoi(argv[0]);
+
+ UL_POWER_DEPR_MSG("use 'rxlev-avg algo osmo-ewma beta %s'.%s",
+ argv[0], VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd,
+ "min-qual-rach <-100-100>",
+ "Set the minimum link quality level of Access Bursts to be accepted\n"
+ "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -628,10 +994,11 @@ DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd,
- "min-qual-norm <-100-100>",
- "Set the minimum link quality level of Normal Bursts to be accepted\n"
- "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n")
+DEFUN_ATTR(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd,
+ "min-qual-norm <-100-100>",
+ "Set the minimum link quality level of Normal Bursts to be accepted\n"
+ "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -640,10 +1007,11 @@ DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd,
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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -652,26 +1020,34 @@ DEFUN(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd,
+DEFUN(cfg_bts_pcu_sock_path, cfg_bts_pcu_sock_path_cmd,
"pcu-socket PATH",
- "Configure the PCU socket file/path name\n")
+ "Configure the PCU socket file/path name\n"
+ "UNIX socket path\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]);
+ osmo_talloc_replace_string(bts, &bts->pcu.sock_path, argv[0]);
+
/* FIXME: re-open the interface? */
+ return CMD_SUCCESS;
+}
+DEFUN(cfg_bts_pcu_sock_ql, cfg_bts_pcu_sock_ql_cmd,
+ "pcu-socket-wqueue-length <1-" OSMO_STRINGIFY_VAL(BTS_CFG_PCU_SOCK_WQUEUE_LEN_MAX_MAX) ">",
+ "Configure the PCU socket queue length\n"
+ "Queue length\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->pcu.sock_wqueue_len_max = atoi(argv[0]);
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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -679,10 +1055,11 @@ DEFUN(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd,
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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
@@ -690,27 +1067,36 @@ DEFUN(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_smscb_max_qlen, cfg_bts_smscb_max_qlen_cmd,
- "smscb queue-max-length <1-60>",
- "Maximum queue length for SMSCB (CBCH) queue. In count of messages/pages (Default: 15)")
+#define SMSCB_STR \
+ "SMSCB (SMS Cell Broadcast) / CBCH configuration\n"
+
+DEFUN_ATTR(cfg_bts_smscb_max_qlen, cfg_bts_smscb_max_qlen_cmd,
+ "smscb queue-max-length <1-60>",
+ SMSCB_STR "Maximum length of the SMSCB (CBCH) queue\n"
+ "Length in count of messages/pages (default: 15)\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
bts->smscb_queue_max_len = atoi(argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_smscb_tgt_qlen, cfg_bts_smscb_tgt_qlen_cmd,
- "smscb queue-target-length <1-30>",
- "Target queue length for SMSCB (CBCH) queue. In count of messages/pages (Default: 2)")
+DEFUN_ATTR(cfg_bts_smscb_tgt_qlen, cfg_bts_smscb_tgt_qlen_cmd,
+ "smscb queue-target-length <1-30>",
+ SMSCB_STR "Target length of the SMSCB (CBCH) queue\n"
+ "Length in count of messages/pages (default: 2)\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
bts->smscb_queue_tgt_len = atoi(argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_smscb_qhyst, cfg_bts_smscb_qhyst_cmd,
- "smscb queue-hysteresis <0-30>",
- "Hysteresis for SMSCB (CBCH) queue. In count of messages/pages (Default: 2)")
+DEFUN_ATTR(cfg_bts_smscb_qhyst, cfg_bts_smscb_qhyst_cmd,
+ "smscb queue-hysteresis <0-30>",
+ SMSCB_STR "Hysteresis of the SMSCB (CBCH) queue\n"
+ "In count of messages/pages (default: 2)\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
bts->smscb_queue_hyst = atoi(argv[0]);
@@ -718,7 +1104,7 @@ DEFUN(cfg_bts_smscb_qhyst, cfg_bts_smscb_qhyst_cmd,
}
-#define DB_DBM_STR \
+#define DB_MDB_STR \
"Unit is dB (decibels)\n" \
"Unit is mdB (milli-decibels, or rather 1/10000 bel)\n"
@@ -732,11 +1118,12 @@ static int parse_mdbm(const char *valstr, const char *unit)
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)
+DEFUN_ATTR(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_MDB_STR,
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts_trx *trx = vty->index;
@@ -745,11 +1132,11 @@ DEFUN(cfg_trx_user_gain,
return CMD_SUCCESS;
}
-#define PR_STR "Power-Ramp settings"
+#define PR_STR "Power-Ramp settings\n"
DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd,
- "power-ramp max-initial <0-100000> (dBm|mdBm)",
+ "power-ramp max-initial <-10000-100000> (dBm|mdBm)",
PR_STR "Maximum initial power\n"
- "Value\n" DB_DBM_STR)
+ "Value\n" DB_MDB_STR)
{
struct gsm_bts_trx *trx = vty->index;
@@ -762,7 +1149,7 @@ DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd,
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)
+ "Step size\n" DB_MDB_STR)
{
struct gsm_bts_trx *trx = vty->index;
@@ -784,12 +1171,40 @@ DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd,
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"
+ "Mobile Station Power Level Control\n"
"Handled by DSP\n" "Handled by OsmoBTS\n")
{
struct gsm_bts_trx *trx = vty->index;
+ bool soft = !strcmp(argv[0], "osmo");
+
+ if (!soft && !bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP)) {
+ /* NOTE: osmo-bts-trx used to have its own (low-level) MS Power Control loop, which
+ * has been ripped out in favour of the common implementation. Configuration files
+ * may still contain 'dsp', so let's be tolerant and override 'dsp' by 'osmo'. */
+ if (trx->bts->variant == BTS_OSMO_TRX && vty->type == VTY_FILE) {
+ vty_out(vty, "%% BTS model 'osmo-bts-trx' has no DSP/HW MS Power Control support, "
+ "consider updating your configuration file!%s", VTY_NEWLINE);
+ soft = true; /* override */
+ } else {
+ vty_out(vty, "%% This BTS model has no DSP/HW MS Power Control support%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ trx->ms_pwr_ctl_soft = soft;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ta_ctrl_interval, cfg_ta_ctrl_interval_cmd,
+ "ta-control interval <0-31>",
+ "Timing Advance Control Parameters\n"
+ "Set TA control loop interval\n"
+ "As in P_CON_INTERVAL, in units of 2 SACCH periods (0.96 seconds) (default=0, every SACCH block)\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->ta_ctrl_interval = atoi(argv[0]);
- trx->ms_power_control = argv[0][0] == 'd' ? 0 : 1;
return CMD_SUCCESS;
}
@@ -803,20 +1218,25 @@ DEFUN(cfg_trx_phy, cfg_trx_phy_cmd,
struct phy_instance *pinst;
if (!plink) {
- vty_out(vty, "phy%s does not exist%s",
+ 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",
+ 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;
+ if (pinst->trx != NULL) {
+ vty_out(vty, "%% phy%s instance %s is already bound to %s%s",
+ argv[0], argv[1], gsm_trx_name(pinst->trx), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ phy_instance_link_to_trx(pinst, trx);
return CMD_SUCCESS;
}
@@ -825,7 +1245,7 @@ DEFUN(cfg_trx_phy, cfg_trx_phy_cmd,
* SHOW
* ======================================================================*/
-static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+static void net_dump_nmstate(struct vty *vty, const struct gsm_nm_state *nms)
{
vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s",
abis_nm_opstate_name(nms->operational),
@@ -833,16 +1253,30 @@ static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
abis_nm_avail_name(nms->availability), VTY_NEWLINE);
}
-static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
+static void bts_dump_vty_features(struct vty *vty, const struct gsm_bts *bts)
{
unsigned int i;
- bool no_features = true;
+ bool no_features;
+
vty_out(vty, " Features:%s", VTY_NEWLINE);
- for (i = 0; i < _NUM_BTS_FEAT; i++) {
- if (gsm_bts_has_feature(bts, i)) {
+ for (i = 0, no_features = true; i < _NUM_BTS_FEAT; i++) {
+ if (osmo_bts_has_feature(bts->features, i)) {
vty_out(vty, " %03u ", i);
- vty_out(vty, "%-40s%s", get_value_string(gsm_bts_features_descs, i), VTY_NEWLINE);
+ vty_out(vty, "%-40s%s", osmo_bts_features_desc(i), VTY_NEWLINE);
+ no_features = false;
+ }
+ }
+
+ if (no_features)
+ vty_out(vty, " (not available)%s", VTY_NEWLINE);
+
+ vty_out(vty, " BTS model specific (internal) flags:%s", VTY_NEWLINE);
+
+ for (i = 0, no_features = true; i < _BTS_INTERNAL_FLAG_NUM; i++) {
+ if (bts_internal_flag_get(bts, i)) {
+ vty_out(vty, " %03u ", i);
+ vty_out(vty, "%-40s%s", get_value_string(bts_impl_flag_desc, i), VTY_NEWLINE);
no_features = false;
}
}
@@ -851,13 +1285,24 @@ static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
vty_out(vty, " (not available)%s", VTY_NEWLINE);
}
-static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+static const char *stringify_radio_link_timeout(int val)
{
- struct gsm_bts_trx *trx;
+ static char buf[32];
+ if (val == -1)
+ snprintf(buf, sizeof(buf), "%s", "infinite");
+ else
+ snprintf(buf, sizeof(buf), "%d SACCH blocks", val);
+ return buf;
+}
+
+static void bts_dump_vty(struct vty *vty, const struct gsm_bts *bts)
+{
+ const struct gsm_bts_trx *trx;
- vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+ vty_out(vty, "BTS %u is of type '%s', in band %s, has CI %u LAC %u, "
"BSIC %u and %u TRX%s",
- bts->nr, "FIXME", gsm_band_name(bts->band),
+ bts->nr, btsvariant2str(bts->variant),
+ gsm_band_name(bts->band),
bts->cell_identity,
bts->location_area_code, bts->bsic,
bts->num_trx, VTY_NEWLINE);
@@ -865,11 +1310,12 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
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);
+ bts->oml_link ? bts->oml_link->tei : 0x00,
+ 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);
+ net_dump_nmstate(vty, &g_bts_sm->mo.nm_state);
if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH))
vty_out(vty, " PCU version %s connected%s",
bts->pcu_version, VTY_NEWLINE);
@@ -897,9 +1343,19 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE);
vty_out(vty, " PH-RTS.ind FN advance average: %d, min: %d, max: %d%s",
bts_get_avg_fn_advance(bts), bts->fn_stats.min, bts->fn_stats.max, VTY_NEWLINE);
+ vty_out(vty, " Radio Link Timeout (OML): %s%s",
+ stringify_radio_link_timeout(bts->radio_link_timeout.oml), VTY_NEWLINE);
+ if (bts->radio_link_timeout.vty_override) {
+ vty_out(vty, " Radio Link Timeout (OVERRIDE): %s%s",
+ stringify_radio_link_timeout(bts->radio_link_timeout.current), VTY_NEWLINE);
+ }
+ if (bts->c0_power_red_db > 0) {
+ vty_out(vty, " BCCH carrier power reduction: %u dB%s",
+ bts->c0_power_red_db, VTY_NEWLINE);
+ }
llist_for_each_entry(trx, &bts->trx_list, list) {
- struct phy_instance *pinst = trx_phy_instance(trx);
+ const 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);
@@ -914,32 +1370,262 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
}
-DEFUN(show_bts, show_bts_cmd, "show bts <0-255>",
- SHOW_STR "Display information about a BTS\n"
- BTS_NR_STR)
+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) {
+ if (bts_nr >= g_bts_sm->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));
+ bts_dump_vty(vty, gsm_bts_num(g_bts_sm, 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));
+ for (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++)
+ bts_dump_vty(vty, gsm_bts_num(g_bts_sm, bts_nr));
+
+ return CMD_SUCCESS;
+}
+
+static void gprs_dump_vty(struct vty *vty, const struct gsm_bts *bts)
+{
+ unsigned int i;
+ const struct gsm_gprs_nse *nse = &bts->site_mgr->gprs.nse;
+
+ /* GPRS parameters received from the BSC */
+ vty_out(vty, "BTS %u, RAC %u, NSEI %u, BVCI %u%s",
+ bts->nr, bts->gprs.rac,
+ nse->nsei,
+ bts->gprs.cell.bvci,
+ VTY_NEWLINE);
+
+ vty_out(vty, " Cell NM state: ");
+ net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state);
+ vty_out(vty, " NSE NM state: ");
+ net_dump_nmstate(vty, &nse->mo.nm_state);
+
+ for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) {
+ const struct gsm_gprs_nsvc *nsvc = &nse->nsvc[i];
+
+ vty_out(vty, " NSVC%u (NSVCI %u) NM state: ", i, nsvc->nsvci);
+ net_dump_nmstate(vty, &nsvc->mo.nm_state);
+
+ if (nsvc->mo.nm_state.operational == NM_OPSTATE_ENABLED) {
+ struct osmo_sockaddr_str remote;
+ struct osmo_sockaddr_str local;
+
+ if (osmo_sockaddr_str_from_sockaddr(&remote, &nsvc->remote.u.sas) != 0)
+ remote = (struct osmo_sockaddr_str) { .ip = "<INVALID>" };
+ if (osmo_sockaddr_str_from_sockaddr(&local, &nsvc->local.u.sas) != 0)
+ local = (struct osmo_sockaddr_str) { .ip = "<INVALID>" };
+
+ /* Work around for over-defensiveness of OSMO_SOCKADDR_STR_FMT_ARGS():
+ * error: the address of ‘remote’ will always evaluate as ‘true’
+ * error: the address of ‘local’ will always evaluate as ‘true’ */
+ const struct osmo_sockaddr_str *r = &remote;
+ const struct osmo_sockaddr_str *l = &local;
+
+ /* Getting remote/local address info has never been so easy, right? */
+ vty_out(vty, " Address: r=" OSMO_SOCKADDR_STR_FMT
+ "<->l=" OSMO_SOCKADDR_STR_FMT "%s",
+ OSMO_SOCKADDR_STR_FMT_ARGS(r),
+ OSMO_SOCKADDR_STR_FMT_ARGS(l),
+ VTY_NEWLINE);
+ }
+ }
+}
+
+DEFUN(show_bts_gprs, show_bts_gprs_cmd,
+ "show bts <0-255> gprs",
+ SHOW_STR "Display information about a BTS\n"
+ BTS_NR_STR "GPRS/EGPRS configuration\n")
+{
+ const struct gsm_bts *bts;
+
+ bts = gsm_bts_num(g_bts_sm, atoi(argv[0]));
+ if (bts == NULL) {
+ vty_out(vty, "%% can't find BTS '%s'%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* TODO: also print info about PCUIF connection */
+ gprs_dump_vty(vty, bts);
return CMD_SUCCESS;
}
-static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+DEFUN(test_send_failure_event_report, test_send_failure_event_report_cmd, "test send-failure-event-report <0-255>",
+ "Various testing commands\n"
+ "Send a test OML failure event report to the BSC\n" BTS_NR_STR)
+{
+ int bts_nr = atoi(argv[0]);
+ const struct gsm_bts *bts;
+
+ if (bts_nr >= g_bts_sm->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
+ oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MINOR, OSMO_EVT_WARN_SW_WARN, "test message sent from VTY");
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN(radio_link_timeout, radio_link_timeout_cmd, "bts <0-0> radio-link-timeout (oml|infinite|<4-64>)",
+ "BTS Specific Commands\n" BTS_NR_STR "Manually override Radio Link Timeout\n"
+ "Use value provided by BSC via A-bis OML (Connection Failure Criterion)\n"
+ "Use infinite timeout (DANGEROUS: only use during testing!)\n"
+ "Number of lost SACCH blocks\n")
+{
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts = gsm_bts_num(g_bts_sm, bts_nr);
+
+ if (!bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "oml")) {
+ bts->radio_link_timeout.current = bts->radio_link_timeout.oml;
+ bts->radio_link_timeout.vty_override = false;
+ } else if (!strcmp(argv[1], "infinite")) {
+ bts->radio_link_timeout.current = -1;
+ bts->radio_link_timeout.vty_override = true;
+ vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE);
+ } else {
+ bts->radio_link_timeout.current = atoi(argv[1]);
+ bts->radio_link_timeout.vty_override = true;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(bts_c0_power_red,
+ bts_c0_power_red_cmd,
+ "bts <0-0> c0-power-red <0-6>",
+ "BTS Specific Commands\n" BTS_NR_STR
+ "BCCH carrier power reduction operation\n"
+ "Power reduction value (in dB, even numbers only)\n")
+{
+ const int bts_nr = atoi(argv[0]);
+ const int red = atoi(argv[1]);
+ struct gsm_bts *bts;
+
+ bts = gsm_bts_num(g_bts_sm, atoi(argv[0]));
+ if (bts == NULL) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (red % 2 != 0) {
+ vty_out(vty, "%% Incorrect BCCH power reduction value, "
+ "an even number is expected%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bts_set_c0_pwr_red(bts, red) != 0) {
+ vty_out(vty, "%% BCCH carrier power reduction operation mode "
+ "is not supported for BTS (%d)%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* TODO: generalize and move indention handling to libosmocore */
+#define cfg_out(vty, fmt, args...) \
+ vty_out(vty, "%*s" fmt, indent, "", ##args)
+
+static void dump_dpc_meas_params(struct vty *vty, const unsigned int indent,
+ const struct gsm_power_ctrl_meas_params *mp,
+ const char *pname, const unsigned int pn)
+{
+ cfg_out(vty, "Lower threshold (L_%s_XX_P): %u%s",
+ pname, mp->lower_thresh, VTY_NEWLINE);
+ cfg_out(vty, "Upper threshold (U_%s_XX_P): %u%s",
+ pname, mp->upper_thresh, VTY_NEWLINE);
+
+ cfg_out(vty, "Lower threshold comparators: P%u=%02u / N%u=%02u%s",
+ pn, mp->lower_cmp_p, pn, mp->lower_cmp_n, VTY_NEWLINE);
+ cfg_out(vty, "Upper threshold comparators: P%u=%02u / N%u=%02u%s",
+ pn + 1, mp->upper_cmp_p, pn + 1, mp->upper_cmp_n, VTY_NEWLINE);
+
+ cfg_out(vty, "Pre-processing algorithm: ");
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED:
+ vty_out(vty, "unweighted average%s", VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED:
+ vty_out(vty, "weighted average%s", VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN:
+ vty_out(vty, "modified median%s", VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
+ vty_out(vty, "EWMA (alpha=%u)%s",
+ mp->ewma.alpha, VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
+ vty_out(vty, "disabled (pass-through)%s", VTY_NEWLINE);
+ return;
+ default:
+ vty_out(vty, "unknown%s", VTY_NEWLINE);
+ return;
+ }
+
+ cfg_out(vty, "Pre-processing parameters: Hreqave=%u / Hreqt=%u%s",
+ mp->h_reqave, mp->h_reqt, VTY_NEWLINE);
+}
+
+static void dump_dpc_params(struct vty *vty, const unsigned int indent,
+ const struct gsm_power_ctrl_params *cp, bool uplink)
+{
+ cfg_out(vty, "Power control interval: %u ms (every %u SACCH block(s))%s",
+ cp->ctrl_interval ? cp->ctrl_interval * 2 * 480 : 480,
+ cp->ctrl_interval ? cp->ctrl_interval * 2 : 1,
+ VTY_NEWLINE);
+
+ cfg_out(vty, "Power increase step size: %u%s",
+ cp->inc_step_size_db, VTY_NEWLINE);
+ cfg_out(vty, "Power reduce step size: %u%s",
+ cp->red_step_size_db, VTY_NEWLINE);
+
+ cfg_out(vty, "RxLev measurement processing:%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->rxlev_meas, "RXLEV", 1);
+
+ cfg_out(vty, "RxQual measurement processing:%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->rxqual_meas, "RXQUAL", 3);
+
+ if (uplink) {
+ cfg_out(vty, "C/I measurement processing (FR/EFR):%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->ci_fr_meas, "CI_FR", 0);
+
+ cfg_out(vty, "C/I measurement processing (HR):%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->ci_hr_meas, "CI_HR", 0);
+
+ cfg_out(vty, "C/I measurement processing (AMR-FR):%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->ci_amr_fr_meas, "CI_AMR_FR", 0);
+
+ cfg_out(vty, "C/I measurement processing (AMR-HR):%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->ci_amr_hr_meas, "CI_AMR_HR", 0);
+
+ cfg_out(vty, "C/I measurement processing (SDCCH):%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->ci_sdcch_meas, "CI_SDCCH", 0);
+
+ cfg_out(vty, "C/I measurement processing (GPRS):%s", VTY_NEWLINE);
+ dump_dpc_meas_params(vty, indent + 2, &cp->ci_gprs_meas, "CI_GPRS", 0);
+ }
+}
+
+static void trx_dump_vty(struct vty *vty, const 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);
@@ -949,12 +1635,25 @@ static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
"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, " BS Power control parameters (%s):%s",
+ trx->bs_dpc_params == &trx->bts->bs_dpc_params ?
+ "fall-back" : "from BSC",
+ VTY_NEWLINE);
+ dump_dpc_params(vty, 4, trx->bs_dpc_params, false);
+
+ vty_out(vty, " MS Power control parameters (%s):%s",
+ trx->ms_dpc_params == &trx->bts->ms_dpc_params ?
+ "fall-back" : "from BSC",
+ VTY_NEWLINE);
+ dump_dpc_params(vty, 4, trx->ms_dpc_params, true);
+
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, " RSL State: %s%s", trx->bb_transc.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);
+ vty_out(vty, " IPA stream ID: 0x%02x%s", trx->bb_transc.rsl.tei, VTY_NEWLINE);
}
static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts)
@@ -970,19 +1669,18 @@ DEFUN(show_trx,
SHOW_STR "Display information about a TRX\n"
BTS_TRX_STR)
{
- struct gsm_network *net = gsmnet_from_vty(vty);
- struct gsm_bts *bts = NULL;
+ const 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) {
+ if (bts_nr >= g_bts_sm->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);
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
}
if (argc >= 2) {
trx_nr = atoi(argv[1]);
@@ -1000,18 +1698,18 @@ DEFUN(show_trx,
return CMD_SUCCESS;
}
- for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
- print_all_trx(vty, gsm_bts_num(net, bts_nr));
+ for (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++)
+ print_all_trx(vty, gsm_bts_num(g_bts_sm, bts_nr));
return CMD_SUCCESS;
}
-static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+static void ts_dump_vty(struct vty *vty, const 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));
+ gsm_pchan_name(ts->pchan), ts->tsc);
if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
vty_out(vty, " (%s mode)",
ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F");
@@ -1026,21 +1724,20 @@ DEFUN(show_ts,
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;
+ const struct gsm_bts *bts = NULL;
+ const struct gsm_bts_trx *trx = NULL;
+ const 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) {
+ if (bts_nr >= g_bts_sm->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);
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
}
if (argc >= 2) {
trx_nr = atoi(argv[1]);
@@ -1081,8 +1778,8 @@ DEFUN(show_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 (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++) {
+ bts = gsm_bts_num(g_bts_sm, 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++) {
@@ -1096,25 +1793,12 @@ DEFUN(show_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)
+static void vty_out_dyn_ts_status(struct vty *vty, const struct gsm_bts_trx_ts *ts)
{
switch (ts->pchan) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
if (ts->dyn.pchan_is == ts->dyn.pchan_want)
vty_out(vty, " as %s",
gsm_pchan_name(ts->dyn.pchan_is));
@@ -1141,16 +1825,157 @@ static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
}
}
-static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+static void lchan_bs_power_ctrl_state_dump(struct vty *vty, unsigned int indent,
+ const struct gsm_lchan *lchan)
+{
+ const struct lchan_power_ctrl_state *st = &lchan->bs_power_ctrl;
+ const struct gsm_bts_trx *trx = lchan->ts->trx;
+
+ cfg_out(vty, "BS (Downlink) Power Control (%s mode):%s",
+ st->dpc_params ? "dynamic" : "static", VTY_NEWLINE);
+ indent += 2;
+
+ cfg_out(vty, "Channel reduction: %u dB", st->current);
+ if (st->dpc_params != NULL)
+ vty_out(vty, " (max %u dB)", st->max);
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ cfg_out(vty, "TRX reduction: %u dB%s",
+ trx->max_power_red, VTY_NEWLINE);
+
+ int actual = trx->nominal_power - (trx->max_power_red + st->current);
+ cfg_out(vty, "Actual / Nominal power: %d dBm / %d dBm%s",
+ actual, trx->nominal_power, VTY_NEWLINE);
+
+ if (st->dpc_params == NULL)
+ return;
+
+ cfg_out(vty, "Power Control parameters:%s", VTY_NEWLINE);
+ dump_dpc_params(vty, indent + 2, st->dpc_params, false);
+}
+
+static void lchan_ms_power_ctrl_state_dump(struct vty *vty, unsigned int indent,
+ const struct gsm_lchan *lchan)
+{
+ const struct lchan_power_ctrl_state *st = &lchan->ms_power_ctrl;
+ const struct gsm_bts_trx *trx = lchan->ts->trx;
+
+ cfg_out(vty, "MS (Uplink) Power Control (%s):%s",
+ st->dpc_params ? "dynamic" : "static", VTY_NEWLINE);
+ indent += 2;
+
+ int current_dbm = ms_pwr_dbm(trx->bts->band, st->current);
+ int max_dbm = ms_pwr_dbm(trx->bts->band, st->max);
+
+ cfg_out(vty, "Current power level: %u, %d dBm",
+ st->current, current_dbm);
+ if (st->dpc_params != NULL)
+ vty_out(vty, " (max %u, %d dBm)", st->max, max_dbm);
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ if (st->dpc_params == NULL)
+ return;
+
+ cfg_out(vty, "Power Control parameters:%s", VTY_NEWLINE);
+ dump_dpc_params(vty, indent + 2, st->dpc_params, true);
+}
+
+static void lchan_acch_rep_state_dump(struct vty *vty, unsigned int indent,
+ const struct gsm_lchan *lchan)
+{
+ cfg_out(vty, "ACCH repetition:%s", VTY_NEWLINE);
+ indent += 2;
+ if (lchan->rep_acch_cap.rxqual)
+ cfg_out(vty, "Enable RXQUAL threshold: %u%s",
+ lchan->rep_acch_cap.rxqual, VTY_NEWLINE);
+ else
+ cfg_out(vty, "Enable RXQUAL threshold: (none, alway on)%s",
+ VTY_NEWLINE);
+
+ cfg_out(vty, "DL-FACCH:%s", VTY_NEWLINE);
+ indent += 2;
+ if (lchan->rep_acch_cap.dl_facch_all)
+ cfg_out(vty, "retramsit all LAPDM block types%s", VTY_NEWLINE);
+ else if (lchan->rep_acch_cap.dl_facch_cmd)
+ cfg_out(vty, "retramsit only LAPDM command blocks%s",
+ VTY_NEWLINE);
+ else
+ cfg_out(vty, "no retransmission (disabled)%s", VTY_NEWLINE);
+ if (lchan->rep_acch.dl_facch_active)
+ cfg_out(vty, "retransmission currently active%s", VTY_NEWLINE);
+ else
+ cfg_out(vty, "retransmission currently inactive%s",
+ VTY_NEWLINE);
+ indent -= 2;
+
+ cfg_out(vty, "DL-SACCH:%s", VTY_NEWLINE);
+ indent += 2;
+ if (lchan->rep_acch_cap.ul_sacch)
+ cfg_out(vty, "retramsit all SACCH blocks for SAPI=0%s",
+ VTY_NEWLINE);
+ else
+ cfg_out(vty, "no retransmission (disabled)%s", VTY_NEWLINE);
+ if (lchan->rep_acch.dl_sacch_active)
+ cfg_out(vty, "retransmission currently active%s", VTY_NEWLINE);
+ else
+ cfg_out(vty, "retransmission currently inactive%s",
+ VTY_NEWLINE);
+ indent -= 2;
+
+ cfg_out(vty, "UL-SACCH:%s", VTY_NEWLINE);
+ indent += 2;
+ if (lchan->rep_acch_cap.dl_sacch)
+ cfg_out(vty, "retramsit all SACCH blocks for SAPI=0%s",
+ VTY_NEWLINE);
+ else
+ cfg_out(vty, "no retransmission (disabled)%s", VTY_NEWLINE);
+ if (lchan->rep_acch.ul_sacch_active)
+ cfg_out(vty, "retransmission currently active%s", VTY_NEWLINE);
+ else
+ cfg_out(vty, "retransmission currently inactive%s",
+ VTY_NEWLINE);
+ indent -= 2;
+}
+
+static void lchan_acch_top_state_dump(struct vty *vty, unsigned int indent,
+ const struct gsm_lchan *lchan)
+{
+ if (lchan->top_acch_cap.overpower_db == 0)
+ return;
+
+ cfg_out(vty, "Temporary ACCH overpower:%s", VTY_NEWLINE);
+ indent += 2;
+
+ cfg_out(vty, "Overpower value: %u dB%s",
+ lchan->top_acch_cap.overpower_db, VTY_NEWLINE);
+
+ cfg_out(vty, "SACCH overpower: %sabled%s",
+ lchan->top_acch_cap.sacch_enable ? "en" : "dis",
+ VTY_NEWLINE);
+ cfg_out(vty, "FACCH overpower: %sabled%s",
+ lchan->top_acch_cap.facch_enable ? "en" : "dis",
+ VTY_NEWLINE);
+
+ if (lchan->top_acch_cap.rxqual == 0) {
+ cfg_out(vty, "RxQual threshold: disabled "
+ "(overpower is always on)%s", VTY_NEWLINE);
+ } else {
+ cfg_out(vty, "RxQual threshold: %u%s",
+ lchan->top_acch_cap.rxqual, VTY_NEWLINE);
+ }
+}
+
+static void lchan_dump_full_vty(struct vty *vty, const struct gsm_lchan *lchan)
{
struct in_addr ia;
- vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u (%s), Lchan %u: Type %s%s",
lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ lchan->ts->vamos.is_shadow ? "shadow" : "primary",
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:
+ case GSM_PCHAN_OSMO_DYN:
vty_out(vty, " Osmocom Dyn TS:");
vty_out_dyn_ts_status(vty, lchan->ts);
vty_out(vty, VTY_NEWLINE);
@@ -1169,28 +1994,42 @@ static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
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),
+ gsm48_chan_mode_name(lchan->tch_mode),
VTY_NEWLINE);
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ const struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ const struct gsm48_multi_rate_conf *mr_conf =
+ (const struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+ vty_out(vty, " AMR Multi-Rate Configuration: ICMI=%u, Start Mode=%u gsm48=%02x%02x%s",
+ mr_conf->icmi, mr_conf->smod, amr_mrc->gsm48_ie[0], amr_mrc->gsm48_ie[1], VTY_NEWLINE);
+ for (uint8_t i = 0; i < amr_mrc->num_modes; i++) {
+ const struct amr_mode *amode = &amr_mrc->mode[i];
+ vty_out(vty, " AMR Mode %u (%s), threshold=%u, hysteresis=%u%s",
+ amode->mode, osmo_amr_type_name(amode->mode),
+ amode->threshold, amode->hysteresis, 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",
+ vty_out(vty, " Bound IP: %s Port %u CONN_ID=%u",
inet_ntoa(ia), lchan->abis_ip.bound_port,
- lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
- VTY_NEWLINE);
+ lchan->abis_ip.conn_id);
+ if (lchan->abis_ip.osmux.use)
+ vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.local_cid, VTY_NEWLINE);
+ else
+ vty_out(vty, " RTP_TYPE2=%u%s", lchan->abis_ip.rtp_payload2, 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",
+ vty_out(vty, " Conn. IP: %s Port %u SPEECH_MODE=0x%02x",
inet_ntoa(ia), lchan->abis_ip.connect_port,
- lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode,
- VTY_NEWLINE);
+ lchan->abis_ip.speech_mode);
+ if (lchan->abis_ip.osmux.use)
+ vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.remote_cid, VTY_NEWLINE);
+ else
+ vty_out(vty, " RTP_TYPE=%u%s", lchan->abis_ip.rtp_payload, VTY_NEWLINE);
}
#define LAPDM_ESTABLISHED(link, sapi_idx) \
(link).datalink[sapi_idx].dl.state == LAPD_STATE_MF_EST
@@ -1215,15 +2054,31 @@ static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
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 */
+
+ /* Interference levels */
+ if (lchan->meas.interf_meas_avg_dbm != 0) {
+ vty_out(vty, " Interference: %d dBm (band %d)%s",
+ lchan->meas.interf_meas_avg_dbm,
+ lchan->meas.interf_band,
+ VTY_NEWLINE);
+ }
+
+ /* BS/MS Power Control state and parameters */
+ lchan_bs_power_ctrl_state_dump(vty, 2, lchan);
+ lchan_ms_power_ctrl_state_dump(vty, 2, lchan);
+
+ /* ACCH repetition / overpower state */
+ lchan_acch_rep_state_dump(vty, 2, lchan);
+ lchan_acch_top_state_dump(vty, 2, lchan);
}
-static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
+static void lchan_dump_short_vty(struct vty *vty, const 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",
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u (%s) %s",
lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ lchan->ts->vamos.is_shadow ? "shadow" : "primary",
gsm_pchan_name(lchan->ts->pchan));
vty_out_dyn_ts_status(vty, lchan->ts);
vty_out(vty, ", Lchan %u, Type %s, State %s - "
@@ -1234,40 +2089,50 @@ static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
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 *))
+static int dump_lchan_trx_ts(const struct gsm_bts_trx_ts *ts, struct vty *vty,
+ void (*dump_cb)(struct vty *, const 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];
+ const struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
if (lchan->state == LCHAN_S_NONE)
continue;
- dump_cb(vty, lchan);
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ dump_cb(vty, lchan);
+ break;
+ default:
+ continue;
+ }
}
return CMD_SUCCESS;
}
-static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty,
- void (*dump_cb)(struct vty *, struct gsm_lchan *))
+static int dump_lchan_trx(const struct gsm_bts_trx *trx, struct vty *vty,
+ void (*dump_cb)(struct vty *, const 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];
+ const struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
dump_lchan_trx_ts(ts, vty, dump_cb);
+ if (ts->vamos.peer != NULL) /* VAMOS: shadow timeslot */
+ dump_lchan_trx_ts(ts->vamos.peer, 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 *))
+static int dump_lchan_bts(const struct gsm_bts *bts, struct vty *vty,
+ void (*dump_cb)(struct vty *, const 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);
+ const struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr);
dump_lchan_trx(trx, vty, dump_cb);
}
@@ -1275,24 +2140,23 @@ static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty,
}
static int lchan_summary(struct vty *vty, int argc, const char **argv,
- void (*dump_cb)(struct vty *, struct gsm_lchan *))
+ void (*dump_cb)(struct vty *, const 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;
+ const struct gsm_bts *bts = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ const struct gsm_bts_trx *trx = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ const struct gsm_bts_trx_ts *ts = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ const 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) {
+ if (bts_nr >= g_bts_sm->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);
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
if (argc == 1)
return dump_lchan_bts(bts, vty, dump_cb);
@@ -1333,8 +2197,8 @@ static int lchan_summary(struct vty *vty, int argc, const char **argv,
return CMD_SUCCESS;
}
- for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
- bts = gsm_bts_num(net, bts_nr);
+ for (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++) {
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
dump_lchan_bts(bts, vty, dump_cb);
}
@@ -1360,18 +2224,18 @@ DEFUN(show_lchan_summary,
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)
+static struct gsm_lchan *resolve_lchan(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]);
+ bool shadow = argv[idx+3][0] == 's';
+ int lchan_nr = atoi(argv[idx+4]);
struct gsm_bts *bts;
struct gsm_bts_trx *trx;
struct gsm_bts_trx_ts *ts;
- bts = gsm_bts_num(net, bts_nr);
+ bts = gsm_bts_num(g_bts_sm, bts_nr);
if (!bts)
return NULL;
@@ -1382,6 +2246,11 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net,
if (ts_nr >= ARRAY_SIZE(trx->ts))
return NULL;
ts = &trx->ts[ts_nr];
+ if (shadow) { /* VAMOS shadow */
+ if (ts->vamos.peer == NULL)
+ return NULL;
+ ts = ts->vamos.peer;
+ }
if (lchan_nr >= ARRAY_SIZE(ts->lchan))
return NULL;
@@ -1389,6 +2258,8 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net,
return &ts->lchan[lchan_nr];
}
+#define BTS_T_T_L_CMD \
+ "bts <0-0> trx <0-255> ts <0-7> (lchan|shadow-lchan) <0-7>"
#define BTS_T_T_L_STR \
"BTS related commands\n" \
"BTS number\n" \
@@ -1396,38 +2267,151 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net,
"TRX number\n" \
"timeslot related commands\n" \
"timeslot number\n" \
- "logical channel commands\n" \
+ "Primary logical channel commands\n" \
+ "Shadow logical channel commands\n" \
"logical channel number\n"
-DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd,
+DEFUN(cfg_bts_gsmtap_remote_host,
+ cfg_bts_gsmtap_remote_host_cmd,
+ "gsmtap-remote-host [HOSTNAME]",
+ "Enable GSMTAP Um logging (see also 'gsmtap-sapi')\n"
+ "Remote IP address or hostname ('localhost' if omitted)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ osmo_talloc_replace_string(bts, &bts->gsmtap.remote_host,
+ argc > 0 ? argv[0] : "localhost");
+
+ if (vty->type != VTY_FILE)
+ vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gsmtap_local_host,
+ cfg_bts_gsmtap_local_host_cmd,
+ "gsmtap-local-host HOSTNAME",
+ "Enable local bind for GSMTAP Um logging (see also 'gsmtap-sapi')\n"
+ "Local IP address or hostname\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ osmo_talloc_replace_string(bts, &bts->gsmtap.local_host, argv[0]);
+
+ if (vty->type != VTY_FILE)
+ vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_gsmtap_remote_host,
+ cfg_bts_no_gsmtap_remote_host_cmd,
+ "no gsmtap-remote-host",
+ NO_STR "Disable GSMTAP Um logging\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gsmtap.remote_host != NULL)
+ talloc_free(bts->gsmtap.remote_host);
+ bts->gsmtap.remote_host = NULL;
+
+ if (vty->type != VTY_FILE)
+ vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_gsmtap_local_host,
+ cfg_bts_no_gsmtap_local_host_cmd,
+ "no gsmtap-local-host",
+ NO_STR "Disable local bind for GSMTAP Um logging\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gsmtap.local_host != NULL)
+ talloc_free(bts->gsmtap.local_host);
+
+ bts->gsmtap.local_host = NULL;
+
+ if (vty->type != VTY_FILE)
+ vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gsmtap_sapi_all, cfg_bts_gsmtap_sapi_all_cmd,
+ "gsmtap-sapi (enable-all|disable-all)",
+ "Enable/disable sending of UL/DL messages over GSMTAP\n"
+ "Enable all kinds of messages (all SAPI)\n"
+ "Disable all kinds of messages (all SAPI)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (argv[0][0] == 'e') {
+ bts->gsmtap.sapi_mask = UINT32_MAX;
+ bts->gsmtap.sapi_acch = 1;
+ } else {
+ bts->gsmtap.sapi_mask = 0x00;
+ bts->gsmtap.sapi_acch = 0;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gsmtap_sapi, cfg_bts_gsmtap_sapi_cmd,
"HIDDEN", "HIDDEN")
{
+ struct gsm_bts *bts = vty->index;
int sapi;
sapi = get_string_value(gsmtap_sapi_names, argv[0]);
OSMO_ASSERT(sapi >= 0);
if (sapi == GSMTAP_CHANNEL_ACCH)
- gsmtap_sapi_acch = 1;
+ bts->gsmtap.sapi_acch = 1;
else
- gsmtap_sapi_mask |= (1 << sapi);
+ bts->gsmtap.sapi_mask |= (1 << sapi);
return CMD_SUCCESS;
}
-DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd,
+DEFUN(cfg_bts_no_gsmtap_sapi, cfg_bts_no_gsmtap_sapi_cmd,
"HIDDEN", "HIDDEN")
{
+ struct gsm_bts *bts = vty->index;
int sapi;
sapi = get_string_value(gsmtap_sapi_names, argv[0]);
OSMO_ASSERT(sapi >= 0);
if (sapi == GSMTAP_CHANNEL_ACCH)
- gsmtap_sapi_acch = 0;
+ bts->gsmtap.sapi_acch = 0;
+ else
+ bts->gsmtap.sapi_mask &= ~(1 << sapi);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gsmtap_rlp, cfg_bts_gsmtap_rlp_cmd,
+ "gsmtap-rlp [skip-null]",
+ "Enable generation of GSMTAP frames for RLP (non-transparent CSD)\n"
+ "Skip the generation of GSMTAP for RLP NULL frames\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->gsmtap.rlp = true;
+ if (argc >= 1 && !strcmp(argv[0], "skip-null"))
+ bts->gsmtap.rlp_skip_null = true;
else
- gsmtap_sapi_mask &= ~(1 << sapi);
+ bts->gsmtap.rlp_skip_null = false;
+ return CMD_SUCCESS;
+}
+DEFUN(cfg_bts_no_gsmtap_rlp, cfg_bts_no_gsmtap_rlp_cmd,
+ "no gsmtap-rlp",
+ NO_STR "Disable generation of GSMTAP frames for RLP (non-transparent CSD)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->gsmtap.rlp = false;
return CMD_SUCCESS;
}
@@ -1443,9 +2427,10 @@ static struct cmd_node phy_inst_node = {
1,
};
-DEFUN(cfg_phy, cfg_phy_cmd,
- "phy <0-255>",
- "Select a PHY to configure\n" "PHY number\n")
+DEFUN_ATTR(cfg_phy, cfg_phy_cmd,
+ "phy <0-255>",
+ "Select a PHY to configure\n" "PHY number\n",
+ CMD_ATTR_IMMEDIATE)
{
int phy_nr = atoi(argv[0]);
struct phy_link *plink;
@@ -1463,9 +2448,10 @@ DEFUN(cfg_phy, cfg_phy_cmd,
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")
+DEFUN_ATTR(cfg_phy_inst, cfg_phy_inst_cmd,
+ "instance <0-255>",
+ "Select a PHY instance to configure\n" "PHY Instance number\n",
+ CMD_ATTR_IMMEDIATE)
{
int inst_nr = atoi(argv[0]);
struct phy_link *plink = vty->index;
@@ -1475,7 +2461,7 @@ DEFUN(cfg_phy_inst, cfg_phy_inst_cmd,
if (!pinst) {
pinst = phy_instance_create(plink, inst_nr);
if (!pinst) {
- vty_out(vty, "Unable to create phy%u instance %u%s",
+ vty_out(vty, "%% Unable to create phy%u instance %u%s",
plink->num, inst_nr, VTY_NEWLINE);
return CMD_WARNING;
}
@@ -1498,7 +2484,7 @@ DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd,
pinst = phy_instance_by_num(plink, inst_nr);
if (!pinst) {
- vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE);
+ vty_out(vty, "%% No such instance %u%s", inst_nr, VTY_NEWLINE);
return CMD_WARNING;
}
@@ -1518,7 +2504,7 @@ DEFUN(cfg_phy_type, cfg_phy_type_cmd,
struct phy_link *plink = vty->index;
if (plink->state != PHY_LINK_SHUTDOWN) {
- vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE);
+ vty_out(vty, "%% Cannot change type of active PHY%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -1533,17 +2519,16 @@ DEFUN(cfg_phy_type, cfg_phy_type_cmd,
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_CMD " 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);
+ lchan = resolve_lchan(argv, 0);
if (!lchan) {
- vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE);
return CMD_WARNING;
}
if (!lchan->abis_ip.rtp_socket) {
@@ -1564,17 +2549,17 @@ DEFUN(bts_t_t_l_jitter_buf,
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")
+DEFUN_ATTR(bts_t_t_l_loopback,
+ bts_t_t_l_loopback_cmd,
+ BTS_T_T_L_CMD " loopback",
+ BTS_T_T_L_STR "Set loopback\n",
+ CMD_ATTR_HIDDEN)
{
- struct gsm_network *net = gsmnet_from_vty(vty);
struct gsm_lchan *lchan;
- lchan = resolve_lchan(net, argv, 0);
+ lchan = resolve_lchan(argv, 0);
if (!lchan) {
- vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE);
return CMD_WARNING;
}
lchan->loopback = 1;
@@ -1582,17 +2567,17 @@ DEFUN(bts_t_t_l_loopback,
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")
+DEFUN_ATTR(no_bts_t_t_l_loopback,
+ no_bts_t_t_l_loopback_cmd,
+ "no " BTS_T_T_L_CMD " loopback",
+ NO_STR BTS_T_T_L_STR "Set loopback\n",
+ CMD_ATTR_HIDDEN)
{
- struct gsm_network *net = gsmnet_from_vty(vty);
struct gsm_lchan *lchan;
- lchan = resolve_lchan(net, argv, 0);
+ lchan = resolve_lchan(argv, 0);
if (!lchan) {
- vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE);
return CMD_WARNING;
}
lchan->loopback = 0;
@@ -1600,20 +2585,147 @@ DEFUN(no_bts_t_t_l_loopback,
return CMD_SUCCESS;
}
-int bts_vty_init(struct gsm_bts *bts)
+#define LCHAN_PWR_CTRL_CMD \
+ BTS_T_T_L_CMD " (bs-power-ctrl|ms-power-ctrl)"
+#define LCHAN_PWR_CTRL_STR \
+ BTS_T_T_L_STR "BS power control\n" "MS power control\n"
+
+DEFUN_ATTR(bts_t_t_l_power_ctrl_mode,
+ bts_t_t_l_power_ctrl_mode_cmd,
+ LCHAN_PWR_CTRL_CMD " mode (static|dynamic)",
+ LCHAN_PWR_CTRL_STR "Change power control mode\n"
+ "Disable the power control loop\n"
+ "Enable the power control loop\n",
+ CMD_ATTR_HIDDEN)
+{
+ const struct gsm_power_ctrl_params *params;
+ struct lchan_power_ctrl_state *state;
+ const char **args = argv + 4;
+ struct gsm_lchan *lchan;
+
+ lchan = resolve_lchan(argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(args[0], "bs-power-ctrl") == 0) {
+ params = &lchan->bs_dpc_params;
+ state = &lchan->bs_power_ctrl;
+ } else { /* ms-power-ctrl */
+ params = &lchan->ms_dpc_params;
+ state = &lchan->ms_power_ctrl;
+ }
+
+ if (strcmp(args[1], "dynamic") == 0)
+ state->dpc_params = params;
+ else
+ state->dpc_params = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(bts_t_t_l_power_ctrl_current_max,
+ bts_t_t_l_power_ctrl_current_max_cmd,
+ LCHAN_PWR_CTRL_CMD " value (current|max) <0-255>",
+ LCHAN_PWR_CTRL_STR "Change current power value\n"
+ "Current value (for both dynamic and static modes)\n"
+ "Maximum value (for dynamic mode only)\n"
+ "BS power reduction (in dB) or MS power level\n",
+ CMD_ATTR_HIDDEN)
{
- cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+ struct lchan_power_ctrl_state *state;
+ const char **args = argv + 4;
+ struct gsm_lchan *lchan;
+
+ lchan = resolve_lchan(argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(args[0], "bs-power-ctrl") == 0)
+ state = &lchan->bs_power_ctrl;
+ else /* ms-power-ctrl */
+ state = &lchan->ms_power_ctrl;
+
+ if (strcmp(args[1], "current") == 0)
+ state->current = atoi(args[2]);
+ else
+ state->max = atoi(args[2]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_l1_sapi, logging_fltr_l1_sapi_cmd, "HIDDEN", "HIDDEN")
+{
+ int sapi = get_string_value(l1sap_common_sapi_names, argv[0]);
+ struct log_target *tgt = osmo_log_vty2tgt(vty);
+ uint16_t **sapi_mask;
+
+ OSMO_ASSERT(sapi >= 0);
+ if (!tgt)
+ return CMD_WARNING;
+
+ sapi_mask = (uint16_t **)&tgt->filter_data[LOG_FLT_L1_SAPI];
+
+ if (!*sapi_mask)
+ *sapi_mask = talloc(tgt, uint16_t);
+
+ OSMO_ASSERT(sapi <= 31);
+ **sapi_mask |= (1 << sapi);
+ tgt->filter_map |= (1 << LOG_FLT_L1_SAPI);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_logging_fltr_l1_sapi, no_logging_fltr_l1_sapi_cmd, "HIDDEN", "HIDDEN")
+{
+ int sapi = get_string_value(l1sap_common_sapi_names, argv[0]);
+ struct log_target *tgt = osmo_log_vty2tgt(vty);
+ uint16_t *sapi_mask;
+
+ OSMO_ASSERT(sapi >= 0);
+ if (!tgt)
+ return CMD_WARNING;
+ if (!tgt->filter_data[LOG_FLT_L1_SAPI])
+ return CMD_SUCCESS;
+
+ OSMO_ASSERT(sapi <= 31);
+ sapi_mask = (uint16_t *)tgt->filter_data[LOG_FLT_L1_SAPI];
+ *sapi_mask &= ~(1 << sapi);
+
+ return CMD_SUCCESS;
+}
+
+int bts_vty_init(void *ctx)
+{
+ cfg_bts_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, 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",
+ cfg_bts_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, gsmtap_sapi_names,
+ "Enable sending of UL/DL messages over GSMTAP\n",
"\n", "", 0);
- cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+ cfg_bts_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, 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",
+ cfg_bts_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, gsmtap_sapi_names,
+ NO_STR "Disable sending of UL/DL messages over GSMTAP\n",
+ "\n", "", 0);
+
+ logging_fltr_l1_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names,
+ "logging filter l1-sapi (",
+ "|", ")", VTY_DO_LOWER);
+ logging_fltr_l1_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names,
+ LOGGING_STR FILTER_STR "L1 SAPI\n",
+ "\n", "", 0);
+
+ no_logging_fltr_l1_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names,
+ "no logging filter l1-sapi (",
+ "|", ")", VTY_DO_LOWER);
+ no_logging_fltr_l1_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names,
+ NO_STR LOGGING_STR FILTER_STR "L1 SAPI\n",
"\n", "", 0);
install_element_ve(&show_bts_cmd);
@@ -1621,19 +2733,30 @@ int bts_vty_init(struct gsm_bts *bts)
install_element_ve(&show_ts_cmd);
install_element_ve(&show_lchan_cmd);
install_element_ve(&show_lchan_summary_cmd);
+ install_element_ve(&show_bts_gprs_cmd);
- logging_vty_add_cmds();
- osmo_talloc_vty_add_cmds();
- osmo_stats_vty_add_cmds();
+ install_element_ve(&logging_fltr_l1_sapi_cmd);
+ install_element_ve(&no_logging_fltr_l1_sapi_cmd);
install_node(&bts_node, config_write_bts);
install_element(CONFIG_NODE, &cfg_bts_cmd);
install_element(CONFIG_NODE, &cfg_vty_telnet_port_cmd);
+
+ osmo_tdef_vty_groups_init(CONFIG_NODE, bts_tdef_groups);
+
install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
install_element(BTS_NODE, &cfg_bts_oml_ip_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_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_rtp_ip_dscp_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_priority_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_cont_stream_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_rtp_cont_stream_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_int_ul_ecu_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_rtp_int_ul_ecu_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_hr_format_cmd);
install_element(BTS_NODE, &cfg_bts_band_cmd);
install_element(BTS_NODE, &cfg_description_cmd);
install_element(BTS_NODE, &cfg_no_description_cmd);
@@ -1642,18 +2765,40 @@ int bts_vty_init(struct gsm_bts *bts)
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_ul_power_target_hysteresis_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_ul_power_filter_cmd);
+ install_element(BTS_NODE, &cfg_bts_ul_power_filter_ewma_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_pcu_sock_path_cmd);
+ install_element(BTS_NODE, &cfg_bts_pcu_sock_ql_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_bts_smscb_max_qlen_cmd);
install_element(BTS_NODE, &cfg_bts_smscb_tgt_qlen_cmd);
install_element(BTS_NODE, &cfg_bts_smscb_qhyst_cmd);
- install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd);
- install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd);
+ install_element(BTS_NODE, &cfg_bts_gsmtap_remote_host_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_gsmtap_remote_host_cmd);
+ install_element(BTS_NODE, &cfg_bts_gsmtap_local_host_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_gsmtap_local_host_cmd);
+ install_element(BTS_NODE, &cfg_bts_gsmtap_sapi_all_cmd);
+ install_element(BTS_NODE, &cfg_bts_gsmtap_sapi_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_gsmtap_sapi_cmd);
+ install_element(BTS_NODE, &cfg_bts_gsmtap_rlp_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_gsmtap_rlp_cmd);
+
+ /* Osmux Node */
+ install_element(BTS_NODE, &cfg_bts_osmux_cmd);
+ install_node(&osmux_node, config_write_dummy);
+
+ install_element(OSMUX_NODE, &cfg_bts_osmux_use_cmd);
+ install_element(OSMUX_NODE, &cfg_bts_osmux_ip_cmd);
+ install_element(OSMUX_NODE, &cfg_bts_osmux_port_cmd);
+ install_element(OSMUX_NODE, &cfg_bts_osmux_batch_factor_cmd);
+ install_element(OSMUX_NODE, &cfg_bts_osmux_batch_size_cmd);
+ install_element(OSMUX_NODE, &cfg_bts_osmux_dummy_padding_cmd);
/* add and link to TRX config node */
install_element(BTS_NODE, &cfg_bts_trx_cmd);
@@ -1664,11 +2809,17 @@ int bts_vty_init(struct gsm_bts *bts)
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_ta_ctrl_interval_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(ENABLE_NODE, &bts_t_t_l_power_ctrl_mode_cmd);
+ install_element(ENABLE_NODE, &bts_t_t_l_power_ctrl_current_max_cmd);
+ install_element(ENABLE_NODE, &test_send_failure_event_report_cmd);
+ install_element(ENABLE_NODE, &radio_link_timeout_cmd);
+ install_element(ENABLE_NODE, &bts_c0_power_red_cmd);
install_element(CONFIG_NODE, &cfg_phy_cmd);
install_node(&phy_node, config_write_phy);
@@ -1677,5 +2828,6 @@ int bts_vty_init(struct gsm_bts *bts)
install_node(&phy_inst_node, config_write_dummy);
- return 0;
+ /* Install variant-specific VTY options */
+ return bts_model_vty_init(ctx);
}
diff --git a/src/osmo-bts-lc15/Makefile.am b/src/osmo-bts-lc15/Makefile.am
new file mode 100644
index 00000000..58284e95
--- /dev/null
+++ b/src/osmo-bts-lc15/Makefile.am
@@ -0,0 +1,102 @@
+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) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBGPS_CFLAGS) \
+ $(LIBSYSTEMD_CFLAGS) \
+ $(NULL)
+
+COMMON_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(NULL)
+
+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 \
+ utils.h \
+ $(NULL)
+
+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 \
+ $(NULL)
+
+osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+osmo_bts_lc15_LDADD = \
+ $(top_builddir)/src/common/libbts.a \
+ $(COMMON_LDADD) \
+ $(NULL)
+
+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 \
+ $(NULL)
+
+lc15bts_mgr_LDADD = \
+ $(top_builddir)/src/common/libbts.a \
+ $(COMMON_LDADD) \
+ $(LIBGPS_LIBS) \
+ $(LIBSYSTEMD_LIBS) \
+ $(NULL)
+
+lc15bts_util_SOURCES = \
+ misc/lc15bts_util.c \
+ misc/lc15bts_par.c \
+ $(NULL)
+lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-litecell15/calib_file.c b/src/osmo-bts-lc15/calib_file.c
index b7049df1..543be90f 100644
--- a/src/osmo-bts-litecell15/calib_file.c
+++ b/src/osmo-bts-lc15/calib_file.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
@@ -317,7 +317,7 @@ static int calib_verify(struct lc15l1_hdl *fl1h, const struct calib_file_desc *d
fseek(st->fp, 0L, SEEK_END);
sz = ftell(st->fp);
- /* rewind read poiner */
+ /* rewind read pointer */
fseek(st->fp, 0L, SEEK_SET);
/* read file */
diff --git a/src/osmo-bts-litecell15/hw_misc.c b/src/osmo-bts-lc15/hw_misc.c
index 9f070bba..97ed3b75 100644
--- a/src/osmo-bts-litecell15/hw_misc.c
+++ b/src/osmo-bts-lc15/hw_misc.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/hw_misc.h b/src/osmo-bts-lc15/hw_misc.h
index 59ed04b7..59ed04b7 100644
--- a/src/osmo-bts-litecell15/hw_misc.h
+++ b/src/osmo-bts-lc15/hw_misc.h
diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-lc15/l1_if.c
index 2ac0b7ab..4ef90949 100644
--- a/src/osmo-bts-litecell15/l1_if.c
+++ b/src/osmo-bts-lc15/l1_if.c
@@ -17,7 +17,7 @@
* 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.
+ * 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/>.
@@ -54,6 +54,7 @@
#include <osmo-bts/l1sap.h>
#include <osmo-bts/msg_utils.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/nm_common_fsm.h>
#include <nrw/litecell15/litecell15.h>
#include <nrw/litecell15/gsml1prim.h>
@@ -346,7 +347,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
abort();
}
- len = msgb_l2len(msg);
+ len = (msg->l2h) ? msgb_l2len(msg) : 0;
chan_nr = l1sap->u.data.chan_nr;
link_id = l1sap->u.data.link_id;
@@ -394,9 +395,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
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);
+ LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "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;
}
@@ -457,9 +457,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
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));
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "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);
@@ -469,7 +468,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
/* 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");
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
msgb_free(l1msg);
} else
dtx_int_signal(lchan);
@@ -507,7 +506,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
/* create new message and fill data */
if (msg) {
- msgb_pull(msg, sizeof(*l1sap));
/* create new message */
nmsg = l1p_msgb_alloc();
if (!nmsg)
@@ -516,7 +514,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
rc = l1if_tch_encode(lchan,
l1p->u.phDataReq.msgUnitParam.u8Buffer,
&l1p->u.phDataReq.msgUnitParam.u8Size,
- msg->data, msg->len, u32Fn, use_cache,
+ msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache,
l1sap->u.tch.marker);
if (rc < 0) {
/* no data encoded for L1: smth will be generated below */
@@ -552,7 +550,12 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
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 (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) < 0) {
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "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
@@ -681,7 +684,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
if (ts->flags & TS_F_PDCH_ACTIVE)
return GSM_PCHAN_PDCH;
return GSM_PCHAN_TCH_F;
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
return ts->dyn.pchan_is;
default:
return ts->pchan;
@@ -695,7 +698,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
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);
+ OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);
switch (sapi) {
case GsmL1_Sapi_Bcch:
@@ -795,6 +798,45 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
return (cbits << 3) | u8Tn;
}
+static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = {
+ [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE,
+ [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH,
+ [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH,
+ [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH,
+ [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH,
+ [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH,
+ [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH,
+ [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH,
+ [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH,
+ [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH,
+ [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F,
+ [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F,
+ [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H,
+ [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H,
+ [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH,
+ [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH,
+ [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH,
+ [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH,
+ [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH,
+ [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH,
+ [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH,
+ [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH,
+ [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH,
+};
+
+static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi)
+{
+ if (sapi >= GsmL1_Sapi_NUM)
+ return L1SAP_COMMON_SAPI_UNKNOWN;
+ return common_sapi_by_sapi_t[sapi];
+}
+
+static void set_log_ctx_sapi(GsmL1_Sapi_t sapi)
+{
+ l1sap_log_ctx_sapi = get_common_sapi(sapi);
+ log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi);
+}
+
static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1,
GsmL1_PhReadyToSendInd_t *rts_ind,
struct msgb *l1p_msg)
@@ -811,6 +853,8 @@ static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1,
uint8_t chan_nr, link_id;
uint32_t fn;
+ set_log_ctx_sapi(rts_ind->sapi);
+
/* 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);
@@ -895,12 +939,8 @@ empty_frame:
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);
-}
+#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d"
+#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming
static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
GsmL1_MeasParam_t *m, uint32_t fn)
@@ -933,6 +973,8 @@ static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_in
int rc = 0;
int8_t rssi;
+ set_log_ctx_sapi(data_ind->sapi);
+
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;
@@ -949,10 +991,10 @@ static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_in
process_meas_res(trx, chan_nr, &data_ind->measParam, fn);
- DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\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);
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size),
+ LOG_PARAM_MEAS(&data_ind->measParam));
/* check for TCH */
if (data_ind->sapi == GsmL1_Sapi_TchF
@@ -985,11 +1027,10 @@ static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_in
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;
- }
+ 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);
}
@@ -1002,7 +1043,9 @@ static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
int rc;
struct ph_rach_ind_param rach_ind_param;
- dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+ set_log_ctx_sapi(ra_ind->sapi);
+ LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n",
+ LOG_PARAM_MEAS(&ra_ind->measParam));
if ((ra_ind->msgUnitParam.u8Size != 1) &&
(ra_ind->msgUnitParam.u8Size != 2)) {
@@ -1204,7 +1247,6 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
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;
@@ -1217,27 +1259,26 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
get_value_string(lc15bts_l1status_names, status));
+ struct bts_lc15_priv *bts_lc15 = trx->bts->model_priv;
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);
+ } else {
+ if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS)
+ 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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
} 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);
+ if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS)
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
}
msgb_free(resp);
@@ -1250,17 +1291,28 @@ int l1if_activate_rf(struct lc15l1_hdl *hdl, int on)
{
struct msgb *msg = sysp_msgb_alloc();
Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+ struct phy_instance *pinst = hdl->phy_inst;
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.u8UnusedTsMode = pinst->u.lc15.pedestal_mode;
+
sysp->u.activateRfReq.u8McCorrMode = 0;
+ /* diversity mode: 0: SISO-A, 1: SISO-B, 2: MRC */
+ sysp->u.activateRfReq.u8DiversityMode = pinst->u.lc15.diversity_mode;
+
/* maximum cell size in quarter-bits, 90 == 12.456 km */
- sysp->u.activateRfReq.u8MaxCellSize = 90;
+ sysp->u.activateRfReq.u8MaxCellSize = pinst->u.lc15.max_cell_size;
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ /* auto tx power adjustment mode 0:none, 1: automatic*/
+ sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.lc15.tx_pwr_adj_mode;
+#endif
+
} else {
sysp->id = Litecell15_PrimId_DeactivateRfReq;
}
@@ -1374,6 +1426,30 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
gsm_band_name(trx->bts->band));
+ /* Frequency bands indicated to the BSC */
+ switch (fl1h->hw_info.band_support) {
+ case GSM_BAND_450:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_450;
+ break;
+ case GSM_BAND_480:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_480;
+ break;
+ case GSM_BAND_850:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850;
+ break;
+ case GSM_BAND_900:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM;
+ /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */
+ /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */
+ break;
+ case GSM_BAND_1800:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS;
+ break;
+ case GSM_BAND_1900:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS;
+ break;
+ }
+
/* Request the activation */
l1if_activate_rf(fl1h, 1);
@@ -1549,6 +1625,57 @@ int l1if_close(struct lc15l1_hdl *fl1h)
return 0;
}
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+static int dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ Litecell15_IsAliveCnf_t *sac = &sysp->u.isAliveCnf;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ fl1h->hw_alive.dsp_alive_cnt++;
+ LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id), sac->status, trx->nr);
+
+ msgb_free(resp);
+ return 0;
+}
+
+static void dsp_alive_timer_cb(void *data)
+{
+ struct lc15l1_hdl *fl1h = data;
+ struct gsm_bts_trx *trx = fl1h->phy_inst->trx;
+ struct msgb *msg = sysp_msgb_alloc();
+ int rc;
+
+ Litecell15_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Litecell15_PrimId_IsAliveReq;
+
+ if (fl1h->hw_alive.dsp_alive_cnt == 0) {
+ LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n",
+ get_value_string(lc15bts_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;
+ }
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n",
+ get_value_string(lc15bts_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(lc15bts_sysprim_names, sys_prim->id));
+ return;
+ }
+
+ /* 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;
+}
+#endif
+
int bts_model_phy_link_open(struct phy_link *plink)
{
struct phy_instance *pinst = phy_instance_by_num(plink, 0);
@@ -1568,6 +1695,29 @@ int bts_model_phy_link_open(struct phy_link *plink)
return -EIO;
}
+ /* Set default PHY parameters */
+ if (!pinst->u.lc15.max_cell_size)
+ pinst->u.lc15.max_cell_size = LC15_BTS_MAX_CELL_SIZE_DEFAULT;
+
+ if (!pinst->u.lc15.diversity_mode)
+ pinst->u.lc15.diversity_mode = LC15_BTS_DIVERSITY_MODE_DEFAULT;
+
+ if (!pinst->u.lc15.pedestal_mode)
+ pinst->u.lc15.pedestal_mode = LC15_BTS_PEDESTAL_MODE_DEFAULT;
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ if (!pinst->u.lc15.dsp_alive_period)
+ pinst->u.lc15.dsp_alive_period = LC15_BTS_DSP_ALIVE_TMR_DEFAULT;
+
+ if (!pinst->u.lc15.tx_pwr_adj_mode)
+ pinst->u.lc15.tx_pwr_adj_mode = LC15_BTS_TX_PWR_ADJ_DEFAULT;
+
+ if (!pinst->u.lc15.tx_pwr_red_8psk)
+ pinst->u.lc15.tx_pwr_red_8psk = LC15_BTS_TX_RED_PWR_8PSK_DEFAULT;
+
+ if (!pinst->u.lc15.tx_c0_idle_pwr_red)
+ pinst->u.lc15.tx_c0_idle_pwr_red = LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT;
+#endif
struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl;
fl1h->dsp_trace_f = dsp_trace;
@@ -1576,5 +1726,26 @@ int bts_model_phy_link_open(struct phy_link *plink)
phy_link_state_set(plink, PHY_LINK_CONNECTED);
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ /* Send first IS_ALIVE primitive */
+ struct msgb *msg = sysp_msgb_alloc();
+ int rc;
+
+ Litecell15_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Litecell15_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(lc15bts_sysprim_names, sys_prim->id));
+ return -EIO;
+ }
+
+ /* initialize DSP heart beat alive timer */
+ osmo_timer_setup(&fl1h->hw_alive.dsp_alive_timer, dsp_alive_timer_cb, fl1h);
+ fl1h->hw_alive.dsp_alive_cnt = 0;
+ fl1h->hw_alive.dsp_alive_period = pinst->u.lc15.dsp_alive_period;
+ osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0);
+#endif
+
return 0;
}
diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-lc15/l1_if.h
index aac26075..655e63fb 100644
--- a/src/osmo-bts-litecell15/l1_if.h
+++ b/src/osmo-bts-lc15/l1_if.h
@@ -30,6 +30,12 @@ enum {
_NUM_MQ_WRITE
};
+/* gsm_bts->model_priv, specific to Litecell 1.5 BTS */
+struct bts_lc15_priv {
+ uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */
+ unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */
+};
+
struct calib_send_state {
FILE *fp;
const char *path;
@@ -62,6 +68,15 @@ struct lc15l1_hdl {
struct calib_send_state st;
uint8_t last_rf_mute[8];
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ struct {
+ struct osmo_timer_list dsp_alive_timer;
+ unsigned int dsp_alive_cnt;
+ uint8_t dsp_alive_period;
+ } hw_alive;
+#endif
+
};
#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
@@ -118,14 +133,14 @@ int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h,
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)
+static inline struct lc15l1_hdl *trx_lc15l1_hdl(const struct gsm_bts_trx *trx)
{
- struct phy_instance *pinst = trx_phy_instance(trx);
+ const 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)
+static inline struct gsm_bts_trx *lc15l1_hdl_trx(const struct lc15l1_hdl *fl1h)
{
OSMO_ASSERT(fl1h->phy_inst);
return fl1h->phy_inst->trx;
diff --git a/src/osmo-bts-litecell15/l1_transp.h b/src/osmo-bts-lc15/l1_transp.h
index 7d6772e8..7d6772e8 100644
--- a/src/osmo-bts-litecell15/l1_transp.h
+++ b/src/osmo-bts-lc15/l1_transp.h
diff --git a/src/osmo-bts-litecell15/l1_transp_hw.c b/src/osmo-bts-lc15/l1_transp_hw.c
index c8972be5..63b596b2 100644
--- a/src/osmo-bts-litecell15/l1_transp_hw.c
+++ b/src/osmo-bts-lc15/l1_transp_hw.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
@@ -88,18 +88,18 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
queue = container_of(fd, struct osmo_wqueue, bfd);
- if (what & BSC_FD_READ)
+ if (what & OSMO_FD_READ)
queue->read_cb(fd);
- if (what & BSC_FD_EXCEPT)
+ if (what & OSMO_FD_EXCEPT)
queue->except_cb(fd);
- if (what & BSC_FD_WRITE) {
+ if (what & OSMO_FD_WRITE) {
struct iovec iov[5];
struct msgb *msg, *tmp;
int written, count = 0;
- fd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(fd);
llist_for_each_entry(msg, &queue->msg_queue, list) {
/* more writes than we have */
@@ -117,7 +117,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
/* Nothing scheduled? This should not happen. */
if (count == 0) {
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
return 0;
}
@@ -125,7 +125,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
if (written < 0) {
/* nothing written?! */
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
return 0;
}
@@ -144,7 +144,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
}
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
}
return 0;
@@ -265,11 +265,7 @@ int l1if_transport_open(int q, struct lc15l1_hdl *hdl)
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;
+ osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q);
rc = osmo_fd_register(read_ofd);
if (rc < 0) {
close(read_ofd->fd);
@@ -288,11 +284,7 @@ int l1if_transport_open(int q, struct lc15l1_hdl *hdl)
}
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;
+ osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q);
rc = osmo_fd_register(write_ofd);
if (rc < 0) {
close(write_ofd->fd);
diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-lc15/lc15bts.c
index 172a7e45..40077902 100644
--- a/src/osmo-bts-litecell15/lc15bts.c
+++ b/src/osmo-bts-lc15/lc15bts.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
@@ -121,6 +121,14 @@ enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id)
case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF;
case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ;
case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF;
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ case Litecell15_PrimId_IsAliveReq: return L1P_T_REQ;
+ case Litecell15_PrimId_IsAliveCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetMaxCellSizeReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF;
+#endif
default: return L1P_T_INVALID;
}
}
@@ -142,6 +150,14 @@ const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = {
{ Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
{ Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
{ Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ { Litecell15_PrimId_IsAliveReq, "IS-ALIVE.req" },
+ { Litecell15_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" },
+ { Litecell15_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" },
+ { Litecell15_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" },
+ { Litecell15_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" },
+ { Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" },
+#endif
{ 0, NULL }
};
@@ -155,6 +171,11 @@ Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id)
case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf;
case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf;
case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf;
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ case Litecell15_PrimId_IsAliveReq: return Litecell15_PrimId_IsAliveCnf;
+ case Litecell15_PrimId_SetMaxCellSizeReq: return Litecell15_PrimId_SetMaxCellSizeCnf;
+ case Litecell15_PrimId_SetC0IdleSlotPowerReductionReq: return Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf;
+#endif
default: return -1; // Weak
}
}
diff --git a/src/osmo-bts-lc15/lc15bts.h b/src/osmo-bts-lc15/lc15bts.h
new file mode 100644
index 00000000..ecfeb3da
--- /dev/null
+++ b/src/osmo-bts-lc15/lc15bts.h
@@ -0,0 +1,101 @@
+#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 lc15_diversity_mode{
+ LC15_DIVERSITY_SISO_A = 0,
+ LC15_DIVERSITY_SISO_B,
+ LC15_DIVERSITY_MRC,
+};
+
+enum lc15_pedestal_mode{
+ LC15_PEDESTAL_OFF = 0,
+ LC15_PEDESTAL_ON,
+};
+
+enum lc15_led_control_mode{
+ LC15_LED_CONTROL_BTS = 0,
+ LC15_LED_CONTROL_EXT,
+};
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+enum lc15_auto_pwr_adjust_mode{
+ LC15_TX_PWR_ADJ_NONE = 0,
+ LC15_TX_PWR_ADJ_AUTO,
+};
+#endif
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id);
+extern 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);
+extern const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1];
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id);
+
+extern const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1];
+extern const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1];
+
+extern const struct value_string lc15bts_tracef_names[29];
+extern const struct value_string lc15bts_tracef_docs[29];
+
+extern const struct value_string lc15bts_tch_pl_names[15];
+
+extern const struct value_string lc15bts_clksrc_names[10];
+
+extern 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
+};
+
+extern const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+/* LC15 default parameters */
+#define LC15_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */
+#define LC15_BTS_DIVERSITY_MODE_DEFAULT 0 /* SISO-A is default mode */
+#define LC15_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */
+#define LC15_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */
+#define LC15_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+#define LC15_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */
+#define LC15_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */
+#define LC15_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */
+#define LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */
+#endif
+
+#endif /* LC15BTS_H */
diff --git a/src/osmo-bts-litecell15/lc15bts_vty.c b/src/osmo-bts-lc15/lc15bts_vty.c
index d27ec281..5efbfcc5 100644
--- a/src/osmo-bts-litecell15/lc15bts_vty.c
+++ b/src/osmo-bts-lc15/lc15bts_vty.c
@@ -2,7 +2,7 @@
/* 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
@@ -43,6 +43,11 @@
#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>
@@ -55,6 +60,7 @@
#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"
@@ -63,7 +69,32 @@ extern int lchan_activate(struct gsm_lchan *lchan);
TRX_STR
#define DSP_TRACE_F_STR "DSP Trace Flag\n"
-static struct gsm_bts *vty_bts;
+static const struct value_string lc15_diversity_mode_strs[] = {
+ { LC15_DIVERSITY_SISO_A, "siso-a" },
+ { LC15_DIVERSITY_SISO_B, "siso-b" },
+ { LC15_DIVERSITY_MRC, "mrc" },
+ { 0, NULL }
+};
+
+static const struct value_string lc15_pedestal_mode_strs[] = {
+ { LC15_PEDESTAL_OFF, "off" },
+ { LC15_PEDESTAL_ON, "on" },
+ { 0, NULL }
+};
+
+static const struct value_string lc15_led_mode_strs[] = {
+ { LC15_LED_CONTROL_BTS, "bts" },
+ { LC15_LED_CONTROL_EXT, "external" },
+ { 0, NULL }
+};
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+static const struct value_string lc15_auto_adj_pwr_strs[] = {
+ { LC15_TX_PWR_ADJ_NONE, "none" },
+ { LC15_TX_PWR_ADJ_AUTO, "auto" },
+ { 0, NULL }
+};
+#endif
/* configuration */
@@ -87,7 +118,7 @@ DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
struct phy_instance *pinst = vty->index;
unsigned int flag;
- flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ flag = get_string_value(lc15bts_tracef_names, argv[0]);
pinst->u.lc15.dsp_trace_f |= flag;
return CMD_SUCCESS;
@@ -99,7 +130,7 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
struct phy_instance *pinst = vty->index;
unsigned int flag;
- flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ flag = get_string_value(lc15bts_tracef_names, argv[0]);
pinst->u.lc15.dsp_trace_f &= ~flag;
return CMD_SUCCESS;
@@ -109,11 +140,11 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
/* runtime */
DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
- "show trx <0-0> dsp-trace-flags",
+ "show dsp-trace-flags trx <0-0>",
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 gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct lc15l1_hdl *fl1h;
int i;
@@ -235,7 +266,7 @@ DEFUN(activate_lchan, activate_lchan_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -256,9 +287,9 @@ DEFUN(set_tx_power, set_tx_power_cmd,
{
int trx_nr = atoi(argv[0]);
int power = atoi(argv[1]);
- struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
- power_ramp_start(trx, to_mdB(power), 1);
+ power_ramp_start(trx, to_mdB(power), 1, NULL);
return CMD_SUCCESS;
}
@@ -273,7 +304,7 @@ DEFUN(loopback, loopback_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -292,7 +323,7 @@ DEFUN(no_loopback, no_loopback_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -302,39 +333,155 @@ DEFUN(no_loopback, no_loopback_cmd,
}
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")
+ "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)
+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]);
+
+ pinst->u.lc15.max_cell_size = (uint8_t)cell_size;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_diversity_mode, cfg_phy_diversity_mode_cmd,
+ "diversity-mode (siso-a|siso-b|mrc)",
+ "Set reception diversity mode \n"
+ "Reception diversity mode can be (siso-a, siso-b, mrc)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = get_string_value(lc15_diversity_mode_strs, argv[0]);
+
+ OSMO_ASSERT(val != -EINVAL);
+
+ pinst->u.lc15.diversity_mode = (uint8_t)val;
+ 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(lc15_pedestal_mode_strs, argv[0]);
+
+ OSMO_ASSERT(val != -EINVAL);
+
+ pinst->u.lc15.pedestal_mode = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_led_mode, cfg_bts_led_mode_cmd,
+ "led-control-mode (bts|external)",
+ "Set LED controlled by BTS or external software\n"
+ "LED can be controlled by (bts, external)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int val = get_string_value(lc15_led_mode_strs, argv[0]);
+
+ OSMO_ASSERT(val != -EINVAL);
+
+ struct bts_lc15_priv *bts_lc15 = bts->model_priv;
+ bts_lc15->led_ctrl_mode = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+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]);
+
+ pinst->u.lc15.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(lc15_auto_adj_pwr_strs, argv[0]);
+
+ OSMO_ASSERT(val != -EINVAL);
+
+ pinst->u.lc15.tx_pwr_adj_mode = (uint8_t)val;
+ return CMD_SUCCESS;
}
-void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+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]);
+
+ pinst->u.lc15.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]);
+
+ pinst->u.lc15.tx_c0_idle_pwr_red = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+#endif
+
+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 bts_lc15_priv *bts_lc15 = bts->model_priv;
+ bts_lc15->rtp_drift_thres_ms = (unsigned int) atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts)
+{
+ const struct bts_lc15_priv *bts_lc15 = bts->model_priv;
+ vty_out(vty, " led-control-mode %s%s",
+ get_value_string(lc15_led_mode_strs, bts_lc15->led_ctrl_mode), VTY_NEWLINE);
+
+ vty_out(vty, " rtp-drift-threshold %d%s",
+ bts_lc15->rtp_drift_thres_ms, VTY_NEWLINE);
+
+}
+
+void bts_model_config_write_trx(struct vty *vty, const 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(struct vty *vty, const struct phy_link *plink)
{
}
-void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst)
{
int i;
@@ -349,41 +496,62 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst
if (pinst->u.lc15.calib_path)
vty_out(vty, " trx-calibration-path %s%s",
pinst->u.lc15.calib_path, VTY_NEWLINE);
+
+ vty_out(vty, " max-cell-size %d%s",
+ pinst->u.lc15.max_cell_size, VTY_NEWLINE);
+
+ vty_out(vty, " diversity-mode %s%s",
+ get_value_string(lc15_diversity_mode_strs, pinst->u.lc15.diversity_mode), VTY_NEWLINE);
+
+ vty_out(vty, " pedestal-mode %s%s",
+ get_value_string(lc15_pedestal_mode_strs, pinst->u.lc15.pedestal_mode) , VTY_NEWLINE);
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ vty_out(vty, " dsp-alive-period %d%s",
+ pinst->u.lc15.dsp_alive_period, VTY_NEWLINE);
+
+ vty_out(vty, " pwr-adj-mode %s%s",
+ get_value_string(lc15_auto_adj_pwr_strs, pinst->u.lc15.tx_pwr_adj_mode), VTY_NEWLINE);
+
+ vty_out(vty, " tx-red-pwr-8psk %d%s",
+ pinst->u.lc15.tx_pwr_red_8psk, VTY_NEWLINE);
+
+ vty_out(vty, " c0-idle-red-pwr %d%s",
+ pinst->u.lc15.tx_c0_idle_pwr_red, VTY_NEWLINE);
+#endif
}
-int bts_model_vty_init(struct gsm_bts *bts)
+int bts_model_vty_init(void *ctx)
{
- 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,
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, 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,
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, 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_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, 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_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, 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,
+ cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx,
lc15bts_tracef_names,
"dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
- cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx,
lc15bts_tracef_docs,
DSP_TRACE_F_STR,
"\n", "", 0);
- cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx,
lc15bts_tracef_names,
"no dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
- cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx,
lc15bts_tracef_docs,
NO_STR DSP_TRACE_F_STR,
"\n", "", 0);
@@ -401,6 +569,8 @@ int bts_model_vty_init(struct gsm_bts *bts)
install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_led_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd);
install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
@@ -408,6 +578,15 @@ int bts_model_vty_init(struct gsm_bts *bts)
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_diversity_mode_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd);
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ 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);
+#endif
return 0;
}
diff --git a/src/osmo-bts-litecell15/main.c b/src/osmo-bts-lc15/main.c
index c47d76d0..23601299 100644
--- a/src/osmo-bts-litecell15/main.c
+++ b/src/osmo-bts-lc15/main.c
@@ -2,7 +2,7 @@
/* 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>
*
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
@@ -76,50 +76,69 @@ static int write_status_file(char *status_file, char *status_str)
#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;
+ struct bts_lc15_priv *bts_lc15 = talloc(bts, struct bts_lc15_priv);
+
+ bts->model_priv = bts_lc15;
bts->variant = BTS_OSMO_LITECELL15;
bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS
+ | NM_IPAC_MASK_GPRS_CODING_MCS;
- 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);
- }
+ /* specific default values for LC15 platform */
+ bts_lc15->led_ctrl_mode = LC15_BTS_LED_CTRL_MODE_DEFAULT;
+ /* RTP drift threshold default */
+ bts_lc15->rtp_drift_thres_ms = LC15_BTS_RTP_DRIFT_THRES_DEFAULT;
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);
+ /* order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
+
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP);
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER);
+
+ /* The default HR codec output format in the absence of saved
+ * vty config needs to match what was implemented previously,
+ * for the sake of existing deployments, i.e., to avoid
+ * a surprise functional change upon software update. */
+ bts->emit_hr_rfc5993 = false;
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ /* Frequency bands indicated to the BSC */
+ trx->support.freq_bands = 0x00; /* updated in info_compl_cb() */
+
+ /* Channel types and modes indicated to the BSC */
+ trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON
+ | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH
+ | NM_IPAC_F_CHANT_SDCCH8_CBCH
+ | NM_IPAC_F_CHANT_PDCHF
+ | NM_IPAC_F_CHANT_TCHF_PDCHF;
+ trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH;
+
trx->nominal_power = 40;
trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power);
return 0;
@@ -159,9 +178,11 @@ void bts_update_status(enum bts_global_status which, int 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"
- " -p --dsp-trace Set DSP trace flags\n"
+ printf( "\nModel specific options:\n"
+ " -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"
);
}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.c b/src/osmo-bts-lc15/misc/lc15bts_bid.c
index 9284b62e..9267e06f 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_bid.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_bid.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.h b/src/osmo-bts-lc15/misc/lc15bts_bid.h
index a71fdd7e..a71fdd7e 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_bid.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_bid.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.c b/src/osmo-bts-lc15/misc/lc15bts_bts.c
index 0343e930..560320a0 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_bts.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_bts.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.h b/src/osmo-bts-lc15/misc/lc15bts_bts.h
index 3918b870..3918b870 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_bts.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_bts.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.c b/src/osmo-bts-lc15/misc/lc15bts_clock.c
index 71701496..23f8f021 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_clock.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_clock.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.h b/src/osmo-bts-lc15/misc/lc15bts_clock.h
index d9673598..d9673598 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_clock.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_clock.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.c b/src/osmo-bts-lc15/misc/lc15bts_led.c
index a93d3fb0..88b12207 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_led.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_led.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.h b/src/osmo-bts-lc15/misc/lc15bts_led.h
index b6d9d28b..b6d9d28b 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_led.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_led.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c b/src/osmo-bts-lc15/misc/lc15bts_mgr.c
index ccacc1e5..fb4f5c40 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_mgr.c
@@ -1,7 +1,7 @@
/* 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>
@@ -17,7 +17,7 @@
* 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.
+ * 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/>.
@@ -220,11 +220,11 @@ static int parse_options(int argc, char **argv)
return 0;
}
-static void signal_handler(int signal)
+static void signal_handler(int signum)
{
- fprintf(stderr, "signal %u received\n", signal);
+ fprintf(stderr, "signal %u received\n", signum);
- switch (signal) {
+ switch (signum) {
case SIGINT:
case SIGTERM:
lc15bts_check_temp(no_rom_write);
@@ -234,6 +234,16 @@ static void signal_handler(int signal)
exit(0);
break;
case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_mgr_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_mgr_ctx, stderr);
@@ -248,31 +258,31 @@ static struct log_info_cat mgr_log_info_cat[] = {
.name = "DTEMP",
.description = "Temperature monitoring",
.color = "\033[1;35m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DFW] = {
.name = "DFW",
.description = "Firmware management",
.color = "\033[1;36m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DFIND] = {
.name = "DFIND",
.description = "ipaccess-find handling",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DCALIB] = {
.name = "DCALIB",
.description = "Calibration handling",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSWD] = {
.name = "DSWD",
.description = "Software Watchdog",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
};
@@ -293,6 +303,7 @@ int main(int argc, char **argv)
osmo_init_ignore_signals();
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
+ signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
@@ -308,7 +319,7 @@ int main(int argc, char **argv)
exit(1);
}
- rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ rc = telnet_init_default(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h b/src/osmo-bts-lc15/misc/lc15bts_mgr.h
index 4bfbdbc9..4bfbdbc9 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_mgr.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c
index badb5455..f25955f8 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c
@@ -17,7 +17,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c
index 3a617dd7..b34becf1 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c
index 9665e1db..abffce3b 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c
index 80751fb0..65c1fe72 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
@@ -89,39 +89,10 @@ static int go_to_parent(struct vty *vty)
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,
};
@@ -584,39 +555,39 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager",
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",
+ vty_out(vty, " Main Supply : %4.2f Celsius%s",
temp/ 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp);
- vty_out(vty, " SoC : %4.2f Celcius%s",
+ vty_out(vty, " SoC : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp);
- vty_out(vty, " FPGA : %4.2f Celcius%s",
+ vty_out(vty, " FPGA : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp);
- vty_out(vty, " RMSDet : %4.2f Celcius%s",
+ vty_out(vty, " RMSDet : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp);
- vty_out(vty, " OCXO : %4.2f Celcius%s",
+ vty_out(vty, " OCXO : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp);
- vty_out(vty, " TX 0 : %4.2f Celcius%s",
+ vty_out(vty, " TX 0 : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp);
- vty_out(vty, " TX 1 : %4.2f Celcius%s",
+ vty_out(vty, " TX 1 : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp);
- vty_out(vty, " Power Amp #0: %4.2f Celcius%s",
+ vty_out(vty, " Power Amp #0: %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp);
- vty_out(vty, " Power Amp #1: %4.2f Celcius%s",
+ vty_out(vty, " Power Amp #1: %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.c b/src/osmo-bts-lc15/misc/lc15bts_misc.c
index 2cedc5d8..bd801099 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_misc.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_misc.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -363,7 +363,7 @@ int lc15bts_firmware_reload(enum lc15bts_firmware_type type)
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",
+ LOGP(DFW, LOGL_ERROR, "unable to open firmware device %s: %s\n",
fw_sysfs[type], strerror(errno));
close(fd);
return fd;
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.h b/src/osmo-bts-lc15/misc/lc15bts_misc.h
index 79e9e686..79e9e686 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_misc.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_misc.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.c b/src/osmo-bts-lc15/misc/lc15bts_nl.c
index 39f64aae..d1d1bd13 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_nl.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_nl.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.h b/src/osmo-bts-lc15/misc/lc15bts_nl.h
index 340cf117..b5a15403 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_nl.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_nl.h
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.c b/src/osmo-bts-lc15/misc/lc15bts_par.c
index af9d030f..e93c45b6 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_par.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_par.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.h b/src/osmo-bts-lc15/misc/lc15bts_par.h
index 74295653..74295653 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_par.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_par.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.c b/src/osmo-bts-lc15/misc/lc15bts_power.c
index 1a37d8e6..c28232b7 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_power.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_power.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.h b/src/osmo-bts-lc15/misc/lc15bts_power.h
index b48cfdcd..b48cfdcd 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_power.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_power.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.c b/src/osmo-bts-lc15/misc/lc15bts_swd.c
index 59c7b616..f0af6bca 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_swd.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_swd.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -161,7 +161,7 @@ int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events)
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.
+ it must be protected with a kind of mutex to avoid losing event notification.
*/
int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event)
{
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.h b/src/osmo-bts-lc15/misc/lc15bts_swd.h
index b78a2c2a..b78a2c2a 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_swd.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_swd.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.c b/src/osmo-bts-lc15/misc/lc15bts_temp.c
index 45602dcc..f69b9bb7 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_temp.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_temp.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.h b/src/osmo-bts-lc15/misc/lc15bts_temp.h
index 35d81f1b..35d81f1b 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_temp.h
+++ b/src/osmo-bts-lc15/misc/lc15bts_temp.h
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_util.c b/src/osmo-bts-lc15/misc/lc15bts_util.c
index 430ce0f7..a5ab6027 100644
--- a/src/osmo-bts-litecell15/misc/lc15bts_util.c
+++ b/src/osmo-bts-lc15/misc/lc15bts_util.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/oml.c b/src/osmo-bts-lc15/oml.c
index f084f1bf..6f312e80 100644
--- a/src/osmo-bts-litecell15/oml.c
+++ b/src/osmo-bts-lc15/oml.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -26,6 +26,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
#include <nrw/litecell15/gsml1prim.h>
#include <nrw/litecell15/gsml1const.h>
@@ -42,11 +43,14 @@
#include <osmo-bts/phy_link.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/nm_common_fsm.h>
#include "l1_if.h"
#include "lc15bts.h"
#include "utils.h"
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi);
+
static int mph_info_chan_confirm(struct gsm_lchan *lchan,
enum osmo_mph_info_type type, uint8_t cause)
{
@@ -91,7 +95,7 @@ static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
[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
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN should not be
* part of this, only "real" pchan values will be looked up here.
* See the callers of ts_connect_as().
*/
@@ -268,36 +272,48 @@ 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);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr);
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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
+ case NM_OC_CHANNEL:
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
+ default:
+ OSMO_ASSERT(0);
+ }
}
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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ case NM_OC_CHANNEL:
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (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);
+ }
}
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi,
+ NM_EV_OPSTART_ACK, NULL);
+ default:
+ OSMO_ASSERT(0);
}
-
- /* Send OPSTART ack */
- return oml_mo_opstart_ack(mo);
}
static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
@@ -355,7 +371,7 @@ static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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);
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
return opstart_compl(&trx->mo, l1_msg);
}
@@ -384,14 +400,24 @@ static int trx_init(struct gsm_bts_trx *trx)
struct msgb *msg;
GsmL1_MphInitReq_t *mi_req;
GsmL1_DeviceParam_t *dev_par;
- int lc15_band;
+ int rc, 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);
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ // (void*)(intptr_t)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;
}
lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn);
@@ -408,9 +434,16 @@ static int trx_init(struct gsm_bts_trx *trx)
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->u8NbTsc = BTS_TSC(trx->bts);
+
+ if (!trx_ms_pwr_ctrl_is_osmo(trx)) {
+ /* Target is in the middle between lower and upper RxLev thresholds */
+ int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh);
+ int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh);
+ dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2;
+ } else {
+ dev_par->fRxPowerLevel = 0.0;
+ }
dev_par->fTxPowerLevel = 0.0;
LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, "
@@ -421,9 +454,9 @@ static int trx_init(struct gsm_bts_trx *trx)
return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
}
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
- struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ const struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
return fl1h->hLayer1;
}
@@ -432,20 +465,24 @@ static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data)
{
msgb_free(l1_msg);
+ bts_model_trx_close_cb(trx, 0);
return 0;
}
-int bts_model_trx_close(struct gsm_bts_trx *trx)
+void bts_model_trx_close(struct gsm_bts_trx *trx)
{
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
struct msgb *msg;
+ int rc;
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);
+ rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+ if (rc < 0)
+ bts_model_trx_close_cb(trx, rc);
}
static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
@@ -490,7 +527,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts,
GsmL1_MphConnectReq_t *cr;
if (pchan == GSM_PCHAN_TCH_F_PDCH
- || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ || pchan == GSM_PCHAN_OSMO_DYN) {
LOGP(DL1C, LOGL_ERROR,
"%s Requested TS connect as %s,"
" expected a specific pchan instead\n",
@@ -510,7 +547,7 @@ 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:
+ case GSM_PCHAN_OSMO_DYN:
ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
/* First connect as NONE, until first RSL CHAN ACT. */
pchan = GSM_PCHAN_NONE;
@@ -534,8 +571,7 @@ GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
case GSM_LCHAN_TCH_H:
return GsmL1_Sapi_TchH;
default:
- LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n");
break;
}
return GsmL1_Sapi_Idle;
@@ -545,7 +581,7 @@ 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)
+ if (pchan == GSM_PCHAN_OSMO_DYN)
pchan = lchan->ts->dyn.pchan_want;
switch (pchan) {
@@ -564,7 +600,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
case GSM_PCHAN_PDCH:
case GSM_PCHAN_UNKNOWN:
default:
- /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ /* case GSM_PCHAN_OSMO_DYN: is caught above */
return GsmL1_SubCh_NA;
}
@@ -627,10 +663,6 @@ static const struct sapi_dir pdtch_sapis[] = {
#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;
@@ -663,11 +695,6 @@ static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
},
};
-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);
@@ -755,12 +782,8 @@ static void sapi_queue_dispatch(struct gsm_lchan *lchan, int 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.");
+ LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n",
+ llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending.");
return;
}
@@ -800,9 +823,8 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
- gsm_lchan_name(lchan),
- get_value_string(lc15bts_l1sapi_names, ic->sapi));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(lc15bts_dir_names, ic->dir));
@@ -823,19 +845,15 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n");
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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
+ cmd->sapi, cmd->dir, ic->sapi, ic->dir);
goto err;
}
@@ -909,15 +927,14 @@ 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)
+static int 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);
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s tch_mode=0x%02x\n", __func__, lchan->tch_mode);
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
@@ -945,7 +962,9 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
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? */
+ /* At call set-up, after every successful handover and after a channel mode modify, the
+ * default phase (odd) shall be used in downlink direction. */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd;
lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
/* initialize to clean state */
@@ -994,10 +1013,13 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
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;
+ default:
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n",
+ gsm48_chan_mode_name(lchan->tch_mode));
+ return -ENOTSUP;
}
+
+ return 0;
}
static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
@@ -1006,6 +1028,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
struct msgb *msg = l1p_msgb_alloc();
int sapi = cmd->sapi;
int dir = cmd->dir;
+ int rc;
GsmL1_MphActivateReq_t *act_req;
GsmL1_LogChParam_t *lch_par;
@@ -1028,7 +1051,10 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
break;
case GsmL1_Sapi_TchH:
case GsmL1_Sapi_TchF:
- lchan2lch_par(lch_par, lchan);
+ if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
/*
* Be sure that every packet is received, even if it
* fails. In this case the length might be lower or 0.
@@ -1061,9 +1087,9 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ (uint32_t)act_req->hLayer2, get_value_string(lc15bts_l1sapi_names, act_req->sapi));
+ dump_lch_par(LOGL_INFO, lch_par, act_req->sapi);
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(lc15bts_dir_names, act_req->dir));
@@ -1087,9 +1113,7 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
/* 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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", 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);
@@ -1136,14 +1160,12 @@ int lchan_activate(struct gsm_lchan *lchan)
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n");
- /* 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 handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl
+ * SACCH should be activated. Also, for HO, start the RACH SAPI. */
+ if (lchan->ho.active == HANDOVER_ENABLED || rsl_chan_rt_is_asci(lchan->rsl_chan_rt))
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
for (i = 0; i < s4l->num_sapis; i++) {
int sapi = s4l->sapis[i].sapi;
@@ -1156,12 +1178,14 @@ int lchan_activate(struct gsm_lchan *lchan)
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);
+ /* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */
+ if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink
+ && !lchan->want_dl_sacch_active)
+ continue;
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
return 0;
}
@@ -1170,6 +1194,9 @@ const struct value_string lc15bts_l1cfgt_names[] = {
{ GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" },
{ GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" },
{ GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" },
+#endif
{ 0, NULL }
};
@@ -1227,6 +1254,58 @@ static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
return 0;
}
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+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(lc15bts_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)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ Litecell15_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
+ gsm_trx_name(trx),
+ get_value_string(lc15bts_sysprim_names, sysp->id),
+ get_value_string(lc15bts_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)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ Litecell15_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
+ gsm_trx_name(trx),
+ get_value_string(lc15bts_sysprim_names, sysp->id),
+ get_value_string(lc15bts_l1status_names, sac->status));
+
+ msgb_free(resp);
+
+ return 0;
+}
+#endif
+
static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data)
{
@@ -1242,9 +1321,8 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
- gsm_lchan_name(lchan),
- get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ",
+ get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId));
switch (cc->cfgParamId) {
case GsmL1_ConfigParamId_SetLogChParams:
@@ -1276,9 +1354,7 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
break;
}
if (llist_empty(&lchan->sapi_cmds)) {
- LOGP(DL1C, LOGL_ERROR,
- "%s Got ciphering conf with empty queue\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n");
goto err;
}
@@ -1303,6 +1379,7 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
struct msgb *msg = l1p_msgb_alloc();
GsmL1_MphConfigReq_t *conf_req;
GsmL1_LogChParam_t *lch_par;
+ int rc;
/* channel mode, encryption and/or multirate have changed */
@@ -1317,7 +1394,10 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
- lchan2lch_par(lch_par, lchan);
+ if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
/* Update the MS Power Level */
if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
@@ -1325,10 +1405,8 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
/* 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ",
+ 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,
@@ -1368,6 +1446,49 @@ int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power)
return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
}
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+int l1if_set_txpower_backoff_8psk(struct lc15l1_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 lc15l1_hdl *fl1h, uint8_t cell_size)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Litecell15_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 lc15l1_hdl *fl1h, uint8_t red)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Litecell15_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);
+}
+#endif
+
const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
[0] = GsmL1_CipherId_A50,
[1] = GsmL1_CipherId_A50,
@@ -1395,11 +1516,9 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c
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),
+ LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n",
cfgr->cfgParams.setCipheringParams.cipherId,
- get_value_string(lc15bts_dir_names,
- cfgr->cfgParams.setCipheringParams.dir));
+ get_value_string(lc15bts_dir_names, cfgr->cfgParams.setCipheringParams.dir));
memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
lchan->encr.key, lchan->encr.key_len);
@@ -1477,9 +1596,8 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
- gsm_lchan_name(lchan),
- get_value_string(lc15bts_l1sapi_names, ic->sapi));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(lc15bts_dir_names, ic->dir));
@@ -1501,19 +1619,15 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
if (llist_empty(&lchan->sapi_cmds)) {
- LOGP(DL1C, LOGL_ERROR,
- "%s Got de-activation confirmation with empty queue\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n");
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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
+ cmd->sapi, cmd->dir, ic->sapi, ic->dir);
goto err;
}
@@ -1538,9 +1652,8 @@ static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ",
+ get_value_string(lc15bts_l1sapi_names, deact_req->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(lc15bts_dir_names, deact_req->dir));
@@ -1552,8 +1665,7 @@ 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n");
lchan_set_state(lchan, LCHAN_S_BROKEN);
sapi_clear_queue(&lchan->sapi_cmds);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
@@ -1610,17 +1722,9 @@ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
return enqueue_sapi_deact_cmd(lchan, sapi, dir);
}
-static int release_sapis_for_ho(struct gsm_lchan *lchan)
+static int release_sapi_ul_rach(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;
+ return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
}
static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
@@ -1642,12 +1746,11 @@ static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
}
/* always attempt to disable the RACH burst */
- res |= release_sapis_for_ho(lchan);
+ res |= release_sapi_ul_rach(lchan);
/* nothing was queued */
if (res == 0) {
- LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n");
lchan_set_state(lchan, LCHAN_S_BROKEN);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
}
@@ -1694,67 +1797,87 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
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);
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx;
+ struct lc15l1_hdl *fl1h;
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_RADIO_ATTR:
+ trx = obj;
+ fl1h = trx_lc15l1_hdl(trx);
+
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ {
+ /* 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.lc15.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.lc15.max_cell_size = cell_size;
+ }
+ }
+#endif
/* 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->hLayer1) {
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
+#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
+ if (fl1h->phy_inst->u.lc15.tx_pwr_red_8psk != trx->max_power_backoff_8psk) {
+ /* update current Tx power backoff for 8-PSK */
+ fl1h->phy_inst->u.lc15.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.lc15.tx_pwr_red_8psk);
+ }
+
+ if (fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red != trx->c0_idle_power_red) {
+ /* update current C0 idle slot Tx power reduction */
+ fl1h->phy_inst->u.lc15.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.lc15.tx_c0_idle_pwr_red);
+ }
+#endif
+ }
+ break;
}
- /* FIXME: we actaully need to send a ACK or NACK for the OML message */
- return oml_fom_ack_nack(msg, 0);
+ return 0;
}
/* callback from OML */
int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
void *obj)
{
+ struct gsm_bts_bb_trx *bb_transc;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
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_BTS:
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);
- }
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *) obj;
+ rc = trx_init(trx);
+ break;
+ case NM_OC_CHANNEL:
+ ts = (struct gsm_bts_trx_ts*) obj;
+ rc = ts_opstart(ts);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -1825,24 +1948,17 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan)
*/
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));
+ LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n");
/* Give up listening to RACH bursts */
- release_sapis_for_ho(lchan);
+ release_sapi_ul_rach(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);
- }
+ /* All the normal SAPIs have already been activated, only DL SACCH may still be missing. */
+ if (lchan->want_dl_sacch_active)
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink);
return 0;
}
@@ -1851,8 +1967,7 @@ 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n");
return 0;
}
@@ -1888,11 +2003,12 @@ static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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));
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n");
cb_ts_disconnected(ts);
+ msgb_free(l1_msg);
+
return 0;
}
@@ -1902,7 +2018,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx);
GsmL1_MphDisconnectReq_t *cr;
- DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n");
cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
l1p_handle_for_ts(ts));
cr->u8Tn = ts->nr;
@@ -1918,8 +2034,7 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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),
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n",
gsm_pchan_name(ts->pchan),
ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
@@ -1927,6 +2042,8 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
cb_ts_connected(ts, 0);
+ msgb_free(l1_msg);
+
return 0;
}
diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-lc15/tch.c
index 5eae7538..afd5b53f 100644
--- a/src/osmo-bts-litecell15/tch.c
+++ b/src/osmo-bts-lc15/tch.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
@@ -68,7 +68,7 @@ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len
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);
+ lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan);
return msg;
}
@@ -101,12 +101,8 @@ static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
/* 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);
+
+ lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan);
return msg;
}
@@ -259,7 +255,10 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
*payload_type = GsmL1_TchPlType_Efr;
rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
rtp_pl_len);
- /* FIXME: detect and save EFR SID */
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_efr_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_AMR:
if (use_cache) {
@@ -360,7 +359,7 @@ 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 };
+ uint8_t *payload, payload_type, payload_len;
struct msgb *rmsg = NULL;
struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
@@ -368,12 +367,12 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ data_ind->measParam.fLinkQuality * 10, 0, 0, 0);
}
payload_type = data_ind->msgUnitParam.u8Buffer[0];
@@ -399,6 +398,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received ONSET from L1 " "(%d bytes)\n",
+ payload_len);
/* according to 3GPP TS 26.093 ONSET frames precede the first
speech frame of a speech burst - set the marker for next RTP
frame */
@@ -407,33 +408,32 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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));
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type));
break;
}
@@ -449,27 +449,21 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ rmsg = l1_to_rtppayload_amr(payload, payload_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);
+ data_ind->measParam.fLinkQuality * 10, 0, 0, 0);
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));
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type));
return -EINVAL;
}
diff --git a/src/osmo-bts-litecell15/utils.c b/src/osmo-bts-lc15/utils.c
index 8d980ba8..b1906d12 100644
--- a/src/osmo-bts-litecell15/utils.c
+++ b/src/osmo-bts-lc15/utils.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-litecell15/utils.h b/src/osmo-bts-lc15/utils.h
index a2a22348..a2a22348 100644
--- a/src/osmo-bts-litecell15/utils.h
+++ b/src/osmo-bts-lc15/utils.h
diff --git a/src/osmo-bts-litecell15/Makefile.am b/src/osmo-bts-litecell15/Makefile.am
deleted file mode 100644
index 0cc124ab..00000000
--- a/src/osmo-bts-litecell15/Makefile.am
+++ /dev/null
@@ -1,38 +0,0 @@
-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/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h
deleted file mode 100644
index 4c40db0f..00000000
--- a/src/osmo-bts-litecell15/lc15bts.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#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/oml_router.c b/src/osmo-bts-litecell15/oml_router.c
deleted file mode 100644
index 198d5e30..00000000
--- a/src/osmo-bts-litecell15/oml_router.c
+++ /dev/null
@@ -1,132 +0,0 @@
-/* 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
deleted file mode 100644
index 8c08baaa..00000000
--- a/src/osmo-bts-litecell15/oml_router.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#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-oc2g/Makefile.am b/src/osmo-bts-oc2g/Makefile.am
index 54a8afab..29374efd 100644
--- a/src/osmo-bts-oc2g/Makefile.am
+++ b/src/osmo-bts-oc2g/Makefile.am
@@ -1,38 +1,104 @@
-AUTOMAKE_OPTIONS = subdir-objects
+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 = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBGPS_CFLAGS) \
+ $(LIBSYSTEMD_CFLAGS) \
+ $(ORTP_CFLAGS) \
+ $(NULL)
+
+COMMON_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(ORTP_LIBS) \
+ $(NULL)
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_bts.h misc/oc2gbts_nl.h misc/oc2gbts_swd.h \
- hw_misc.h l1_if.h l1_transp.h oc2gbts.h oml_router.h utils.h
+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_bts.h \
+ misc/oc2gbts_nl.h \
+ misc/oc2gbts_swd.h \
+ hw_misc.h \
+ l1_if.h \
+ l1_transp.h \
+ oc2gbts.h \
+ utils.h \
+ $(NULL)
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
+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 \
+ $(NULL)
osmo_bts_oc2g_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
-osmo_bts_oc2g_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+osmo_bts_oc2g_LDADD = \
+ $(top_builddir)/src/common/libbts.a \
+ $(COMMON_LDADD) \
+ $(NULL)
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.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
+ misc/oc2gbts_swd.c \
+ $(NULL)
-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_mgr_LDADD = \
+ $(top_builddir)/src/common/libbts.a \
+ $(COMMON_LDADD) \
+ $(LIBGPS_LIBS) \
+ $(LIBSYSTEMD_LIBS) \
+ $(NULL)
-oc2gbts_util_SOURCES = misc/oc2gbts_util.c misc/oc2gbts_par.c
+oc2gbts_util_SOURCES = \
+ misc/oc2gbts_util.c \
+ misc/oc2gbts_par.c \
+ $(NULL)
oc2gbts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-oc2g/calib_file.c b/src/osmo-bts-oc2g/calib_file.c
index 6d2d5610..df15ee99 100644
--- a/src/osmo-bts-oc2g/calib_file.c
+++ b/src/osmo-bts-oc2g/calib_file.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
@@ -307,7 +307,7 @@ static int calib_verify(struct oc2gl1_hdl *fl1h, const struct calib_file_desc *d
fseek(st->fp, 0L, SEEK_END);
sz = ftell(st->fp);
- /* rewind read poiner */
+ /* rewind read pointer */
fseek(st->fp, 0L, SEEK_SET);
/* read file */
diff --git a/src/osmo-bts-oc2g/hw_misc.c b/src/osmo-bts-oc2g/hw_misc.c
index 31daf078..d886effd 100644
--- a/src/osmo-bts-oc2g/hw_misc.c
+++ b/src/osmo-bts-oc2g/hw_misc.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/l1_if.c b/src/osmo-bts-oc2g/l1_if.c
index d987bb52..d43f31b7 100644
--- a/src/osmo-bts-oc2g/l1_if.c
+++ b/src/osmo-bts-oc2g/l1_if.c
@@ -17,7 +17,7 @@
* 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.
+ * 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/>.
@@ -37,6 +37,7 @@
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/write_queue.h>
+#include <osmocom/core/fsm.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/lapdm.h>
@@ -55,6 +56,7 @@
#include <osmo-bts/msg_utils.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
#include <osmo-bts/cbch.h>
+#include <osmo-bts/nm_common_fsm.h>
#include <nrw/oc2g/oc2g.h>
#include <nrw/oc2g/gsml1prim.h>
@@ -390,7 +392,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
abort();
}
- len = msgb_l2len(msg);
+ len = (msg->l2h) ? msgb_l2len(msg) : 0;
chan_nr = l1sap->u.data.chan_nr;
link_id = l1sap->u.data.link_id;
@@ -438,9 +440,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
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);
+ LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "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;
}
@@ -501,9 +502,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
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));
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "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)
@@ -521,7 +521,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
/* 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");
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
msgb_free(l1msg);
} else
dtx_int_signal(lchan);
@@ -559,7 +559,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
/* create new message and fill data */
if (msg) {
- msgb_pull(msg, sizeof(*l1sap));
/* create new message */
nmsg = l1p_msgb_alloc();
if (!nmsg)
@@ -568,7 +567,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
rc = l1if_tch_encode(lchan,
l1p->u.phDataReq.msgUnitParam.u8Buffer,
&l1p->u.phDataReq.msgUnitParam.u8Size,
- msg->data, msg->len, u32Fn, use_cache,
+ msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache,
l1sap->u.tch.marker);
if (rc < 0) {
/* no data encoded for L1: smth will be generated below */
@@ -604,7 +603,11 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
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 (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) < 0) {
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "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
@@ -733,7 +736,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
if (ts->flags & TS_F_PDCH_ACTIVE)
return GSM_PCHAN_PDCH;
return GSM_PCHAN_TCH_F;
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
return ts->dyn.pchan_is;
default:
return ts->pchan;
@@ -747,7 +750,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
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);
+ OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);
switch (sapi) {
case GsmL1_Sapi_Bcch:
@@ -847,6 +850,45 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
return (cbits << 3) | u8Tn;
}
+static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = {
+ [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE,
+ [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH,
+ [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH,
+ [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH,
+ [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH,
+ [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH,
+ [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH,
+ [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH,
+ [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH,
+ [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH,
+ [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F,
+ [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F,
+ [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H,
+ [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H,
+ [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH,
+ [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH,
+ [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH,
+ [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH,
+ [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH,
+ [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH,
+ [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH,
+ [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH,
+ [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH,
+};
+
+static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi)
+{
+ if (sapi >= GsmL1_Sapi_NUM)
+ return L1SAP_COMMON_SAPI_UNKNOWN;
+ return common_sapi_by_sapi_t[sapi];
+}
+
+static void set_log_ctx_sapi(GsmL1_Sapi_t sapi)
+{
+ l1sap_log_ctx_sapi = get_common_sapi(sapi);
+ log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi);
+}
+
static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1,
GsmL1_PhReadyToSendInd_t *rts_ind,
struct msgb *l1p_msg)
@@ -863,6 +905,8 @@ static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1,
uint8_t chan_nr, link_id;
uint32_t fn;
+ set_log_ctx_sapi(rts_ind->sapi);
+
/* 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);
@@ -951,12 +995,9 @@ empty_frame:
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);
-}
+
+#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d"
+#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming
static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
GsmL1_MeasParam_t *m, uint32_t fn)
@@ -989,6 +1030,8 @@ static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_in
int rc = 0;
int8_t rssi;
+ set_log_ctx_sapi(data_ind->sapi);
+
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;
@@ -1005,10 +1048,10 @@ static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_in
process_meas_res(trx, chan_nr, &data_ind->measParam, fn);
- DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\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);
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size),
+ LOG_PARAM_MEAS(&data_ind->measParam));
/* check for TCH */
if (data_ind->sapi == GsmL1_Sapi_TchF
@@ -1041,11 +1084,10 @@ static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_in
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;
- }
+ 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);
}
@@ -1058,7 +1100,9 @@ static int handle_ph_ra_ind(struct oc2gl1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
int rc;
struct ph_rach_ind_param rach_ind_param;
- dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+ set_log_ctx_sapi(ra_ind->sapi);
+ LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n",
+ LOG_PARAM_MEAS(&ra_ind->measParam));
if ((ra_ind->msgUnitParam.u8Size != 1) &&
(ra_ind->msgUnitParam.u8Size != 2)) {
@@ -1283,17 +1327,12 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
} 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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
}
msgb_free(resp);
@@ -1436,6 +1475,30 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
gsm_band_name(trx->bts->band));
+ /* Frequency bands indicated to the BSC */
+ switch (fl1h->hw_info.band_support) {
+ case GSM_BAND_450:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_450;
+ break;
+ case GSM_BAND_480:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_480;
+ break;
+ case GSM_BAND_850:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850;
+ break;
+ case GSM_BAND_900:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM;
+ /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */
+ /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */
+ break;
+ case GSM_BAND_1800:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS;
+ break;
+ case GSM_BAND_1900:
+ trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS;
+ break;
+ }
+
/* Request the activation */
l1if_activate_rf(fl1h, 1);
@@ -1489,7 +1552,7 @@ static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
return 0;
}
-/* FIXME: This delays the TRX initalization by 5 sec in order to avoid the
+/* FIXME: This delays the TRX initialization by 5 sec in order to avoid the
* occurrence of a race condition in the OML bringup. This a work around and
* should be fixed properly. See also OS#3782, OS#2470 and OS#2469 */
void l1if_reset_cb(void *arg)
@@ -1617,7 +1680,7 @@ int l1if_close(struct oc2gl1_hdl *fl1h)
/* TODO(oramadan) MERGE */
#ifdef MERGE_ME
-static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+static int 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;
@@ -1628,9 +1691,10 @@ static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void
get_value_string(oc2gbts_sysprim_names, sysp->id), sac->status, trx->nr);
msgb_free(resp);
+ return 0;
}
-static int dsp_alive_timer_cb(void *data)
+static void dsp_alive_timer_cb(void *data)
{
struct oc2gl1_hdl *fl1h = data;
struct gsm_bts_trx *trx = fl1h->phy_inst->trx;
@@ -1665,7 +1729,7 @@ static int dsp_alive_timer_cb(void *data)
/* allocate new list of sent alarms */
alarm_sent = talloc_zero(fl1h, struct oml_alarm_list);
if (!alarm_sent)
- return -EIO;
+ return;
alarm_sent->alarm_signal = S_NM_OML_BTS_DSP_ALIVE_ALARM;
/* add alarm to sent list */
@@ -1681,14 +1745,14 @@ static int dsp_alive_timer_cb(void *data)
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;
+ return;
}
/* 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;
+ return;
}
#endif
@@ -1752,8 +1816,7 @@ int bts_model_phy_link_open(struct phy_link *plink)
}
/ * 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;
+ osmo_timer_setup(&fl1h->hw_alive.dsp_alive_timer, dsp_alive_timer_cb, 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); */
diff --git a/src/osmo-bts-oc2g/l1_if.h b/src/osmo-bts-oc2g/l1_if.h
index 38699e01..8e8a2edb 100644
--- a/src/osmo-bts-oc2g/l1_if.h
+++ b/src/osmo-bts-oc2g/l1_if.h
@@ -30,6 +30,13 @@ enum {
_NUM_MQ_WRITE
};
+/* gsm_bts->model_priv, specific to Open Cellular 2G BTS */
+struct bts_oc2g_priv {
+ 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 */
+};
+
struct calib_send_state {
FILE *fp;
const char *path;
@@ -129,9 +136,9 @@ int bts_check_for_ciph_cmd(struct oc2gl1_hdl *fl1h,
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)
+static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(const struct gsm_bts_trx *trx)
{
- struct phy_instance *pinst = trx_phy_instance(trx);
+ const struct phy_instance *pinst = trx_phy_instance(trx);
OSMO_ASSERT(pinst);
return pinst->u.oc2g.hdl;
}
diff --git a/src/osmo-bts-oc2g/l1_transp_hw.c b/src/osmo-bts-oc2g/l1_transp_hw.c
index e1d46581..5ffd6568 100644
--- a/src/osmo-bts-oc2g/l1_transp_hw.c
+++ b/src/osmo-bts-oc2g/l1_transp_hw.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
@@ -88,18 +88,18 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
queue = container_of(fd, struct osmo_wqueue, bfd);
- if (what & BSC_FD_READ)
+ if (what & OSMO_FD_READ)
queue->read_cb(fd);
- if (what & BSC_FD_EXCEPT)
+ if (what & OSMO_FD_EXCEPT)
queue->except_cb(fd);
- if (what & BSC_FD_WRITE) {
+ if (what & OSMO_FD_WRITE) {
struct iovec iov[5];
struct msgb *msg, *tmp;
int written, count = 0;
- fd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(fd);
llist_for_each_entry(msg, &queue->msg_queue, list) {
/* more writes than we have */
@@ -117,7 +117,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
/* Nothing scheduled? This should not happen. */
if (count == 0) {
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
return 0;
}
@@ -125,7 +125,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
if (written < 0) {
/* nothing written?! */
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
return 0;
}
@@ -144,7 +144,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
}
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
}
return 0;
@@ -265,11 +265,7 @@ int l1if_transport_open(int q, struct oc2gl1_hdl *hdl)
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;
+ osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q);
rc = osmo_fd_register(read_ofd);
if (rc < 0) {
close(read_ofd->fd);
@@ -288,11 +284,7 @@ int l1if_transport_open(int q, struct oc2gl1_hdl *hdl)
}
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;
+ osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q);
rc = osmo_fd_register(write_ofd);
if (rc < 0) {
close(write_ofd->fd);
diff --git a/src/osmo-bts-oc2g/main.c b/src/osmo-bts-oc2g/main.c
index 5b66c6f3..75ad3149 100644
--- a/src/osmo-bts-oc2g/main.c
+++ b/src/osmo-bts-oc2g/main.c
@@ -2,7 +2,7 @@
/* 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>
*
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
@@ -76,7 +76,6 @@ static int write_status_file(char *status_file, char *status_str)
#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;
@@ -84,48 +83,63 @@ unsigned int dsp_trace = 0x00000000;
int bts_model_init(struct gsm_bts *bts)
{
struct stat st;
- static struct osmo_fd accept_fd, read_fd;
- int rc;
+ struct bts_oc2g_priv *bts_oc2g = talloc(bts, struct bts_oc2g_priv);
+ bts->model_priv = bts_oc2g;
bts->variant = BTS_OSMO_OC2G;
bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS
+ | NM_IPAC_MASK_GPRS_CODING_MCS;
/* specific default values for OC2G platform */
/* TODO(oramadan) MERGE
- bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT;
+ 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);
- }
+ /* bts_oc2g->rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT; */
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);
+ /* order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
+
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP);
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER);
+
+ /* The default HR codec output format in the absence of saved
+ * vty config needs to match what was implemented previously,
+ * for the sake of existing deployments, i.e., to avoid
+ * a surprise functional change upon software update. */
+ bts->emit_hr_rfc5993 = false;
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ /* Frequency bands indicated to the BSC */
+ trx->support.freq_bands = 0x00; /* updated in info_compl_cb() */
+
+ /* Channel types and modes indicated to the BSC */
+ trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON
+ | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH
+ | NM_IPAC_F_CHANT_SDCCH8_CBCH
+ | NM_IPAC_F_CHANT_PDCHF
+ | NM_IPAC_F_CHANT_TCHF_PDCHF;
+ trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH;
+
trx->nominal_power = 25;
trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power);
return 0;
@@ -165,9 +179,11 @@ void bts_update_status(enum bts_global_status which, int 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"
- " -p --dsp-trace Set DSP trace flags\n"
+ printf( "\nModel specific options:\n"
+ " -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"
);
}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
index c2cd483d..0589e3f0 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
index b3dae76e..3e5db829 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
index 5263e3ec..10b97a31 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
index 40d4b722..c2c807b6 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_led.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
index 25948b0e..843fbb38 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
@@ -1,7 +1,7 @@
/* 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>
@@ -17,7 +17,7 @@
* 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.
+ * 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/>.
@@ -201,11 +201,11 @@ static int parse_options(int argc, char **argv)
return 0;
}
-static void signal_handler(int signal)
+static void signal_handler(int signum)
{
- fprintf(stderr, "signal %u received\n", signal);
+ fprintf(stderr, "signal %u received\n", signum);
- switch (signal) {
+ switch (signum) {
case SIGINT:
oc2gbts_check_temp(no_rom_write);
oc2gbts_check_power(no_rom_write);
@@ -214,6 +214,16 @@ static void signal_handler(int signal)
exit(0);
break;
case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_mgr_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_mgr_ctx, stderr);
@@ -228,31 +238,31 @@ static struct log_info_cat mgr_log_info_cat[] = {
.name = "DTEMP",
.description = "Temperature monitoring",
.color = "\033[1;35m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DFW] = {
.name = "DFW",
.description = "Firmware management",
.color = "\033[1;36m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DFIND] = {
.name = "DFIND",
.description = "ipaccess-find handling",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DCALIB] = {
.name = "DCALIB",
.description = "Calibration handling",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSWD] = {
.name = "DSWD",
.description = "Software Watchdog",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
};
@@ -280,6 +290,7 @@ int main(int argc, char **argv)
osmo_init_ignore_signals();
signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
@@ -295,7 +306,7 @@ int main(int argc, char **argv)
exit(1);
}
- rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ rc = telnet_init_default(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
index 3ddf0e8c..cf904aea 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
@@ -17,7 +17,7 @@
* 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.
+ * 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/>.
@@ -171,7 +171,12 @@ static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr)
return;
}
+#if GPSD_API_MAJOR_VERSION >= 9
+ mgr->gps.gps_fix_now = data->fix.time.tv_sec;
+#else
mgr->gps.gps_fix_now = (time_t) data->fix.time;
+#endif
+
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);
@@ -220,10 +225,8 @@ static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr)
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;
+ osmo_fd_setup(&mgr->gps.gpsfd, mgr->gps.gpsdata.gps_fd, OSMO_FD_READ | OSMO_FD_EXCEPT,
+ mgr_gps_read, mgr, 0);
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);
@@ -607,7 +610,7 @@ static void bts_recon_timer_cb(void *data)
select_led_pattern(mgr);
/* The connection failures are to be expected during boot */
- mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(mgr->oc2gbts_ctrl.bts_conn->ofd);
rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn);
if (rc < 0) {
LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
index db67caf2..1091f622 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
index f9efd9cd..9a92a075 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
index 7e80e030..10dda21f 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
@@ -1,5 +1,5 @@
/* 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
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
@@ -86,35 +86,10 @@ static int go_to_parent(struct vty *vty)
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,
};
@@ -512,35 +487,35 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager",
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",
+ vty_out(vty, " Main Supply : %4.2f Celsius%s",
temp/ 1000.0f,
VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
- vty_out(vty, " SoC : %4.2f Celcius%s",
+ vty_out(vty, " SoC : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
- vty_out(vty, " FPGA : %4.2f Celcius%s",
+ vty_out(vty, " FPGA : %4.2f Celsius%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",
+ vty_out(vty, " RMSDet : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
}
oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
- vty_out(vty, " OCXO : %4.2f Celcius%s",
+ vty_out(vty, " OCXO : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
- vty_out(vty, " TX : %4.2f Celcius%s",
+ vty_out(vty, " TX : %4.2f Celsius%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",
+ vty_out(vty, " Power Amp : %4.2f Celsius%s",
temp / 1000.0f,
VTY_NEWLINE);
}
@@ -649,7 +624,7 @@ DEFUN(show_thresh, show_thresh_cmd, "show thresholds",
DEFUN(calibrate_clock, calibrate_clock_cmd,
"calibrate clock",
- "Calibration commands\n"
+ "Calibration commands\n"
"Calibrate clock against GPS PPS\n")
{
if (oc2gbts_mgr_calib_run(s_mgr) < 0) {
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
index bacf07bd..c3b91ec1 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -361,7 +361,7 @@ int oc2gbts_firmware_reload(enum oc2gbts_firmware_type 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",
+ LOGP(DFW, LOGL_ERROR, "unable to open firmware device %s: %s\n",
fw_sysfs[type], strerror(errno));
close(fd);
return fd;
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
index 39f64aae..d1d1bd13 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
index 340cf117..b5a15403 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
index 7dc77c90..fef350f2 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_par.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
index 4e2fc95a..46b8fc03 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_power.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
index 59b795ac..6358d4e2 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -161,7 +161,7 @@ int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events)
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.
+ it must be protected with a kind of mutex to avoid losing event notification.
*/
int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event)
{
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
index 8425dda3..d7afa4e6 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
index b71f0383..0919da93 100644
--- a/src/osmo-bts-oc2g/misc/oc2gbts_util.c
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/oc2gbts.c b/src/osmo-bts-oc2g/oc2gbts.c
index 012d705c..5860a566 100644
--- a/src/osmo-bts-oc2g/oc2gbts.c
+++ b/src/osmo-bts-oc2g/oc2gbts.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h
index 9eb87452..80fd1d59 100644
--- a/src/osmo-bts-oc2g/oc2gbts.h
+++ b/src/osmo-bts-oc2g/oc2gbts.h
@@ -39,26 +39,26 @@ enum oc2g_auto_pwr_adjust_mode{
};
enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id);
-const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1];
+extern 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];
+extern 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];
+extern const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1];
+extern 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];
+extern const struct value_string oc2gbts_tracef_names[29];
+extern const struct value_string oc2gbts_tracef_docs[29];
-const struct value_string oc2gbts_tch_pl_names[15];
+extern const struct value_string oc2gbts_tch_pl_names[15];
-const struct value_string oc2gbts_clksrc_names[10];
+extern const struct value_string oc2gbts_clksrc_names[10];
-const struct value_string oc2gbts_dir_names[6];
+extern const struct value_string oc2gbts_dir_names[6];
-const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX];
+extern const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX];
enum pdch_cs {
PDCH_CS_1,
@@ -77,7 +77,7 @@ enum pdch_cs {
_NUM_PDCH_CS
};
-const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+extern 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 */
diff --git a/src/osmo-bts-oc2g/oc2gbts_vty.c b/src/osmo-bts-oc2g/oc2gbts_vty.c
index 1f092dde..051528ab 100644
--- a/src/osmo-bts-oc2g/oc2gbts_vty.c
+++ b/src/osmo-bts-oc2g/oc2gbts_vty.c
@@ -2,7 +2,7 @@
/* 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
@@ -47,6 +47,7 @@
#include <osmo-bts/signal.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_sm.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/phy_link.h>
@@ -69,8 +70,6 @@ extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan);
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" },
@@ -113,7 +112,7 @@ DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
struct phy_instance *pinst = vty->index;
unsigned int flag;
- flag = get_string_value(oc2gbts_tracef_names, argv[1]);
+ flag = get_string_value(oc2gbts_tracef_names, argv[0]);
pinst->u.oc2g.dsp_trace_f |= flag;
return CMD_SUCCESS;
@@ -125,7 +124,7 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
struct phy_instance *pinst = vty->index;
unsigned int flag;
- flag = get_string_value(oc2gbts_tracef_names, argv[1]);
+ flag = get_string_value(oc2gbts_tracef_names, argv[0]);
pinst->u.oc2g.dsp_trace_f &= ~flag;
return CMD_SUCCESS;
@@ -135,11 +134,11 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
/* runtime */
DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
- "show trx <0-0> dsp-trace-flags",
+ "show dsp-trace-flags trx <0-0>",
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 gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct oc2gl1_hdl *fl1h;
int i;
@@ -261,7 +260,7 @@ DEFUN(activate_lchan, activate_lchan_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -282,9 +281,9 @@ DEFUN(set_tx_power, set_tx_power_cmd,
{
int trx_nr = atoi(argv[0]);
int power = atoi(argv[1]);
- struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
- power_ramp_start(trx, to_mdB(power), 1);
+ power_ramp_start(trx, to_mdB(power), 1, NULL);
return CMD_SUCCESS;
}
@@ -299,7 +298,7 @@ DEFUN(loopback, loopback_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -318,7 +317,7 @@ DEFUN(no_loopback, no_loopback_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -335,12 +334,6 @@ DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
int nominal_power = atoi(argv[0]);
struct gsm_bts_trx *trx = vty->index;
- if (( nominal_power > 25 ) || ( nominal_power < 0 )) {
- vty_out(vty, "Nominal Tx power level must be between 0 and 25 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);
@@ -354,12 +347,6 @@ DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd,
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;
}
@@ -372,10 +359,7 @@ DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd,
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;
- }
+ OSMO_ASSERT(val != -EINVAL);
pinst->u.oc2g.pedestal_mode = (uint8_t)val;
return CMD_SUCCESS;
@@ -388,12 +372,6 @@ DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd,
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;
}
@@ -405,10 +383,7 @@ DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd,
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;
- }
+ OSMO_ASSERT(val != -EINVAL);
pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val;
return CMD_SUCCESS;
@@ -421,12 +396,6 @@ DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd,
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;
}
@@ -438,19 +407,12 @@ DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd,
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;
@@ -460,7 +422,7 @@ DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR)
/* uint8_t old_ho_cause; */
/* get BTS pointer */
- bts = gsm_bts_num(net, 0);
+ bts = gsm_bts_num(g_bts_sm, 0);
if (!bts) {
vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE);
return CMD_WARNING;
@@ -541,7 +503,7 @@ DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd,
}
*/
-void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts)
{
/* TODO(oramadan) MERGE
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
@@ -555,16 +517,16 @@ 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_trx(struct vty *vty, const 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(struct vty *vty, const struct phy_link *plink)
{
}
-void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst)
{
int i;
@@ -599,44 +561,42 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst
pinst->u.oc2g.tx_c0_idle_pwr_red, VTY_NEWLINE);
}
-int bts_model_vty_init(struct gsm_bts *bts)
+int bts_model_vty_init(void *ctx)
{
- 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,
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, 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,
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, 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_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, 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_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, 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,
+ cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx,
oc2gbts_tracef_names,
"dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
- cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx,
oc2gbts_tracef_docs,
DSP_TRACE_F_STR,
"\n", "", 0);
- cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx,
oc2gbts_tracef_names,
"no dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
- cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx,
oc2gbts_tracef_docs,
NO_STR DSP_TRACE_F_STR,
"\n", "", 0);
- trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts,
+ trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(ctx,
oc2gbts_rsl_ho_causes,
"trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (",
"|",")", VTY_DO_LOWER);
diff --git a/src/osmo-bts-oc2g/oml.c b/src/osmo-bts-oc2g/oml.c
index 32024090..4b434e83 100644
--- a/src/osmo-bts-oc2g/oml.c
+++ b/src/osmo-bts-oc2g/oml.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -26,6 +26,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
#include <nrw/oc2g/gsml1prim.h>
#include <nrw/oc2g/gsml1const.h>
@@ -42,11 +43,14 @@
#include <osmo-bts/phy_link.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/nm_common_fsm.h>
#include "l1_if.h"
#include "oc2gbts.h"
#include "utils.h"
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi);
+
static int mph_info_chan_confirm(struct gsm_lchan *lchan,
enum osmo_mph_info_type type, uint8_t cause)
{
@@ -91,7 +95,7 @@ static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
[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
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN should not be
* part of this, only "real" pchan values will be looked up here.
* See the callers of ts_connect_as().
*/
@@ -268,36 +272,48 @@ 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);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr);
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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
+ case NM_OC_CHANNEL:
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
+ default:
+ OSMO_ASSERT(0);
+ }
}
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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ case NM_OC_CHANNEL:
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (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);
+ }
}
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi,
+ NM_EV_OPSTART_ACK, NULL);
+ default:
+ OSMO_ASSERT(0);
}
-
- /* Send OPSTART ack */
- return oml_mo_opstart_ack(mo);
}
static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
@@ -371,7 +387,7 @@ static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
*/
/* Begin to ramp up the power */
- power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
return opstart_compl(&trx->mo, l1_msg);
}
@@ -433,9 +449,16 @@ static int trx_init(struct gsm_bts_trx *trx)
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->u8NbTsc = BTS_TSC(trx->bts);
+
+ if (!trx_ms_pwr_ctrl_is_osmo(trx)) {
+ /* Target is in the middle between lower and upper RxLev thresholds */
+ int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh);
+ int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh);
+ dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2;
+ } else {
+ dev_par->fRxPowerLevel = 0.0;
+ }
dev_par->fTxPowerLevel = 0.0;
LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, "
@@ -446,9 +469,9 @@ static int trx_init(struct gsm_bts_trx *trx)
return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
}
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
- struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ const struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
return fl1h->hLayer1;
}
@@ -457,20 +480,24 @@ static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data)
{
msgb_free(l1_msg);
+ bts_model_trx_close_cb(trx, 0);
return 0;
}
-int bts_model_trx_close(struct gsm_bts_trx *trx)
+void bts_model_trx_close(struct gsm_bts_trx *trx)
{
struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
struct msgb *msg;
+ int rc;
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);
+ rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+ if (rc < 0)
+ bts_model_trx_close_cb(trx, rc);
}
static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
@@ -515,7 +542,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts,
GsmL1_MphConnectReq_t *cr;
if (pchan == GSM_PCHAN_TCH_F_PDCH
- || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ || pchan == GSM_PCHAN_OSMO_DYN) {
LOGP(DL1C, LOGL_ERROR,
"%s Requested TS connect as %s,"
" expected a specific pchan instead\n",
@@ -535,7 +562,7 @@ 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:
+ case GSM_PCHAN_OSMO_DYN:
ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
/* First connect as NONE, until first RSL CHAN ACT. */
pchan = GSM_PCHAN_NONE;
@@ -559,8 +586,7 @@ GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
case GSM_LCHAN_TCH_H:
return GsmL1_Sapi_TchH;
default:
- LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n");
break;
}
return GsmL1_Sapi_Idle;
@@ -570,7 +596,7 @@ 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)
+ if (pchan == GSM_PCHAN_OSMO_DYN)
pchan = lchan->ts->dyn.pchan_want;
switch (pchan) {
@@ -589,7 +615,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
case GSM_PCHAN_PDCH:
case GSM_PCHAN_UNKNOWN:
default:
- /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ /* case GSM_PCHAN_OSMO_DYN: is caught above */
return GsmL1_SubCh_NA;
}
@@ -652,10 +678,6 @@ static const struct sapi_dir pdtch_sapis[] = {
#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;
@@ -688,11 +710,6 @@ static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
},
};
-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);
@@ -780,12 +797,8 @@ static void sapi_queue_dispatch(struct gsm_lchan *lchan, int 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.");
+ LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n",
+ llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending.");
return;
}
@@ -825,9 +838,8 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
- gsm_lchan_name(lchan),
- get_value_string(oc2gbts_l1sapi_names, ic->sapi));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ",
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(oc2gbts_dir_names, ic->dir));
@@ -848,19 +860,15 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n");
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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
+ cmd->sapi, cmd->dir, ic->sapi, ic->dir);
goto err;
}
@@ -934,15 +942,14 @@ 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)
+static int 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);
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s tch_mode=0x%02x\n", __func__, lchan->tch_mode);
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
@@ -970,7 +977,9 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
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? */
+ /* At call set-up, after every successful handover and after a channel mode modify, the
+ * default phase (odd) shall be used in downlink direction. */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd;
lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
/* initialize to clean state */
@@ -1019,10 +1028,13 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
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;
+ default:
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n",
+ gsm48_chan_mode_name(lchan->tch_mode));
+ return -ENOTSUP;
}
+
+ return 0;
}
static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
@@ -1031,6 +1043,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
struct msgb *msg = l1p_msgb_alloc();
int sapi = cmd->sapi;
int dir = cmd->dir;
+ int rc;
GsmL1_MphActivateReq_t *act_req;
GsmL1_LogChParam_t *lch_par;
@@ -1053,7 +1066,10 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
break;
case GsmL1_Sapi_TchH:
case GsmL1_Sapi_TchF:
- lchan2lch_par(lch_par, lchan);
+ if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
/*
* Be sure that every packet is received, even if it
* fails. In this case the length might be lower or 0.
@@ -1086,9 +1102,9 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ (uint32_t)act_req->hLayer2, get_value_string(oc2gbts_l1sapi_names, act_req->sapi));
+ dump_lch_par(LOGL_INFO, lch_par, act_req->sapi);
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(oc2gbts_dir_names, act_req->dir));
@@ -1112,9 +1128,7 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
/* 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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", 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);
@@ -1161,14 +1175,12 @@ int lchan_activate(struct gsm_lchan *lchan)
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n");
- /* 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 handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl
+ * SACCH should be activated. Also, for HO, start the RACH SAPI. */
+ if (lchan->ho.active == HANDOVER_ENABLED || rsl_chan_rt_is_asci(lchan->rsl_chan_rt))
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
for (i = 0; i < s4l->num_sapis; i++) {
int sapi = s4l->sapis[i].sapi;
@@ -1181,12 +1193,14 @@ int lchan_activate(struct gsm_lchan *lchan)
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);
+ /* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */
+ if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink
+ && !lchan->want_dl_sacch_active)
+ continue;
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
return 0;
}
@@ -1318,9 +1332,8 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
- gsm_lchan_name(lchan),
- get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ",
+ get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));
switch (cc->cfgParamId) {
case GsmL1_ConfigParamId_SetLogChParams:
@@ -1352,9 +1365,7 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
break;
}
if (llist_empty(&lchan->sapi_cmds)) {
- LOGP(DL1C, LOGL_ERROR,
- "%s Got ciphering conf with empty queue\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n");
goto err;
}
@@ -1379,6 +1390,7 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
struct msgb *msg = l1p_msgb_alloc();
GsmL1_MphConfigReq_t *conf_req;
GsmL1_LogChParam_t *lch_par;
+ int rc;
/* channel mode, encryption and/or multirate have changed */
@@ -1393,7 +1405,10 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
- lchan2lch_par(lch_par, lchan);
+ if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
/* Update the MS Power Level */
if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
@@ -1401,10 +1416,8 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
/* 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ",
+ 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,
@@ -1512,11 +1525,9 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n",
+ 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);
@@ -1594,9 +1605,8 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
- gsm_lchan_name(lchan),
- get_value_string(oc2gbts_l1sapi_names, ic->sapi));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ",
+ get_value_string(oc2gbts_l1sapi_names, ic->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(oc2gbts_dir_names, ic->dir));
@@ -1618,19 +1628,15 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
if (llist_empty(&lchan->sapi_cmds)) {
- LOGP(DL1C, LOGL_ERROR,
- "%s Got de-activation confirmation with empty queue\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n");
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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
+ cmd->sapi, cmd->dir, ic->sapi, ic->dir);
goto err;
}
@@ -1655,9 +1661,8 @@ static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ",
+ get_value_string(oc2gbts_l1sapi_names, deact_req->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(oc2gbts_dir_names, deact_req->dir));
@@ -1669,8 +1674,7 @@ 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n");
lchan_set_state(lchan, LCHAN_S_BROKEN);
sapi_clear_queue(&lchan->sapi_cmds);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
@@ -1727,17 +1731,9 @@ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
return enqueue_sapi_deact_cmd(lchan, sapi, dir);
}
-static int release_sapis_for_ho(struct gsm_lchan *lchan)
+static int release_sapi_ul_rach(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;
+ return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
}
static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
@@ -1759,12 +1755,11 @@ static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
}
/* always attempt to disable the RACH burst */
- res |= release_sapis_for_ho(lchan);
+ res |= release_sapi_ul_rach(lchan);
/* nothing was queued */
if (res == 0) {
- LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n");
lchan_set_state(lchan, LCHAN_S_BROKEN);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
}
@@ -1811,33 +1806,24 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
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);
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx;
+ struct oc2gl1_hdl *fl1h;
+ uint8_t cell_size;
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_RADIO_ATTR:
+ trx = obj;
+ fl1h = trx_oc2gl1_hdl(trx);
/* convert max TA to max cell size in qbits */
- uint8_t cell_size = bts->max_ta << 2;
+ 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 */
@@ -1850,7 +1836,7 @@ int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
/* 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);
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) {
/* update current Tx power backoff for 8-PSK */
@@ -1866,40 +1852,37 @@ int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
}
}
-
+ break;
}
- /* FIXME: we actaully need to send a ACK or NACK for the OML message */
- return oml_fom_ack_nack(msg, 0);
+ return 0;
}
/* callback from OML */
int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
void *obj)
{
+ struct gsm_bts_bb_trx *bb_transc;
+ struct gsm_bts_trx* trx;
+ struct gsm_bts_trx_ts *ts;
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_BTS:
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);
- }
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *) obj;
+ rc = trx_init(trx);
+ break;
+ case NM_OC_CHANNEL:
+ ts = (struct gsm_bts_trx_ts*) obj;
+ rc = ts_opstart(ts);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -1970,24 +1953,17 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan)
*/
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));
+ LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n");
/* Give up listening to RACH bursts */
- release_sapis_for_ho(lchan);
+ release_sapi_ul_rach(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);
- }
+ /* All the normal SAPIs have already been activated, only DL SACCH may still be missing. */
+ if (lchan->want_dl_sacch_active)
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink);
return 0;
}
@@ -1996,8 +1972,7 @@ 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n");
return 0;
}
@@ -2033,8 +2008,7 @@ static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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));
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n");
cb_ts_disconnected(ts);
@@ -2049,7 +2023,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx);
GsmL1_MphDisconnectReq_t *cr;
- DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n");
cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
l1p_handle_for_ts(ts));
cr->u8Tn = ts->nr;
@@ -2065,12 +2039,11 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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 " : "");
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n",
+ 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);
diff --git a/src/osmo-bts-oc2g/oml_router.c b/src/osmo-bts-oc2g/oml_router.c
deleted file mode 100644
index 198d5e30..00000000
--- a/src/osmo-bts-oc2g/oml_router.c
+++ /dev/null
@@ -1,132 +0,0 @@
-/* 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
deleted file mode 100644
index 4b22e9c5..00000000
--- a/src/osmo-bts-oc2g/oml_router.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#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
index 1bd93e4b..4ea1eb61 100644
--- a/src/osmo-bts-oc2g/tch.c
+++ b/src/osmo-bts-oc2g/tch.c
@@ -15,7 +15,7 @@
* 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.
+ * 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/>.
@@ -68,7 +68,7 @@ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len
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);
+ lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan);
return msg;
}
@@ -101,12 +101,8 @@ static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
/* 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);
+
+ lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan);
return msg;
}
@@ -259,7 +255,10 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
*payload_type = GsmL1_TchPlType_Efr;
rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
rtp_pl_len);
- /* FIXME: detect and save EFR SID */
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_efr_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_AMR:
if (use_cache) {
@@ -360,7 +359,7 @@ 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 };
+ uint8_t *payload, payload_type, payload_len;
struct msgb *rmsg = NULL;
struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
@@ -368,12 +367,12 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ data_ind->measParam.fLinkQuality * 10, 0, 0, 0);
}
payload_type = data_ind->msgUnitParam.u8Buffer[0];
@@ -403,6 +402,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received ONSET from L1 " "(%d bytes)\n",
+ payload_len);
/* according to 3GPP TS 26.093 ONSET frames precede the first
speech frame of a speech burst - set the marker for next RTP
frame */
@@ -412,43 +413,40 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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));
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_NOTICE, "%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);
+ LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "%s lchan->rtp_tx_marker = %s, len=%u\n",
+ get_value_string(oc2gbts_tch_pl_names, payload_type),
+ lchan->rtp_tx_marker ? "true" : "false", payload_len);
switch (payload_type) {
case GsmL1_TchPlType_Fr:
@@ -461,27 +459,21 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ rmsg = l1_to_rtppayload_amr(payload, payload_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);
+ data_ind->measParam.fLinkQuality * 10, 0, 0, 0);
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));
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type));
return -EINVAL;
}
diff --git a/src/osmo-bts-oc2g/utils.c b/src/osmo-bts-oc2g/utils.c
index cb65f45a..f98e88af 100644
--- a/src/osmo-bts-oc2g/utils.c
+++ b/src/osmo-bts-oc2g/utils.c
@@ -16,7 +16,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am
index 43d9cd7d..fb3f6691 100644
--- a/src/osmo-bts-octphy/Makefile.am
+++ b/src/osmo-bts-octphy/Makefile.am
@@ -1,12 +1,42 @@
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)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(NULL)
+
+COMMON_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(NULL)
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
+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 \
+ $(NULL)
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
index 612c29ad..074a1a7f 100644
--- a/src/osmo-bts-octphy/l1_if.c
+++ b/src/osmo-bts-octphy/l1_if.c
@@ -36,6 +36,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/fsm.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/bts_model.h>
@@ -43,6 +44,8 @@
#include <osmo-bts/logging.h>
#include <osmo-bts/l1sap.h>
#include <osmo-bts/handover.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/nm_common_fsm.h>
#include "l1_if.h"
#include "l1_oml.h"
@@ -307,22 +310,13 @@ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg,
/* 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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
} 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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
}
return 0;
@@ -335,7 +329,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
if (ts->flags & TS_F_PDCH_ACTIVE)
return GSM_PCHAN_PDCH;
return GSM_PCHAN_TCH_F;
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
return ts->dyn.pchan_is;
default:
return ts->pchan;
@@ -350,7 +344,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
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);
+ OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);
switch (sapi) {
case cOCTVC1_GSM_SAPI_ENUM_BCCH:
@@ -485,7 +479,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
abort();
}
- len = msgb_l2len(msg);
+ len = (msg->l2h) ? msgb_l2len(msg) : 0;
chan_nr = l1sap->u.data.chan_nr;
link_id = l1sap->u.data.link_id;
@@ -595,11 +589,10 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
if (msg) {
nmsg = l1p_msgb_alloc();
if (!nmsg) {
- LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-TCH.req msg alloc failed\n");
+ LOGPLCFN(lchan, u32Fn, DL1C, LOGL_FATAL, "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));
@@ -621,7 +614,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
&data_req->Data.ulPayloadType,
data_req->Data.abyDataContent,
&data_req->Data.ulDataLength,
- msg->data, msg->len);
+ msgb_l2(msg), msgb_l2len(msg));
mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req);
} else {
@@ -775,25 +768,48 @@ int bts_model_init(struct gsm_bts *bts)
bts->variant = BTS_OSMO_OCTPHY;
bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS;
/* 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);
+ /* order alphabetically */
#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);
+ osmo_bts_set_feature(bts->features, 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);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ /* Frequency bands indicated to the BSC */
+ trx->support.freq_bands = NM_IPAC_F_FREQ_BAND_PGSM
+ | NM_IPAC_F_FREQ_BAND_EGSM
+ | NM_IPAC_F_FREQ_BAND_RGSM
+ | NM_IPAC_F_FREQ_BAND_DCS
+ | NM_IPAC_F_FREQ_BAND_PCS
+ | NM_IPAC_F_FREQ_BAND_850
+ | NM_IPAC_F_FREQ_BAND_480
+ | NM_IPAC_F_FREQ_BAND_450;
+
+ /* Channel types and modes indicated to the BSC */
+ trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON
+#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4)
+ | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH
+#endif
+#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8)
+ | NM_IPAC_F_CHANT_SDCCH8_CBCH
+#endif
+ | NM_IPAC_F_CHANT_PDCHF
+ | NM_IPAC_F_CHANT_TCHF_PDCHF;
+ trx->support.chan_modes = NM_IPAC_F_CHANM_SPEECH_FS
+ | NM_IPAC_F_CHANM_SPEECH_HS;
+
return 0;
}
@@ -906,14 +922,9 @@ static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
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);
-}
+
+#define LOG_FMT_MEAS "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits: %d, BER Error Count %d, BER Toatal Bit count %d in last decoded frame"
+#define LOG_PARAM_MEAS(meas_param) (meas_param)->sRSSIDbm, (meas_param)->sBurstTiming, (meas_param)->sBurstTiming4x, (meas_param)->usBERCnt, (meas_param)->usBERTotalBitCnt
static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn)
{
@@ -938,6 +949,47 @@ static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t
return 0;
}
+/* octv1_gsm_api.h does not have an end marker for CTVC1_GSM_SAPI_ENUM */
+#define _OCTVC1_GSM_SAPI_ENUM_LENGTH (cOCTVC1_GSM_SAPI_ENUM_PRACH + 1)
+
+static const enum l1sap_common_sapi common_sapi_by_oct_sapi[] = {
+ [cOCTVC1_GSM_SAPI_ENUM_IDLE] = L1SAP_COMMON_SAPI_IDLE,
+ [cOCTVC1_GSM_SAPI_ENUM_FCCH] = L1SAP_COMMON_SAPI_FCCH,
+ [cOCTVC1_GSM_SAPI_ENUM_SCH] = L1SAP_COMMON_SAPI_SCH,
+ [cOCTVC1_GSM_SAPI_ENUM_SACCH] = L1SAP_COMMON_SAPI_SACCH,
+ [cOCTVC1_GSM_SAPI_ENUM_SDCCH] = L1SAP_COMMON_SAPI_SDCCH,
+ [cOCTVC1_GSM_SAPI_ENUM_BCCH] = L1SAP_COMMON_SAPI_BCCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH] = L1SAP_COMMON_SAPI_PCH,
+ [cOCTVC1_GSM_SAPI_ENUM_CBCH] = L1SAP_COMMON_SAPI_CBCH,
+ [cOCTVC1_GSM_SAPI_ENUM_RACH] = L1SAP_COMMON_SAPI_RACH,
+ [cOCTVC1_GSM_SAPI_ENUM_TCHF] = L1SAP_COMMON_SAPI_TCH_F,
+ [cOCTVC1_GSM_SAPI_ENUM_FACCHF] = L1SAP_COMMON_SAPI_FACCH_F,
+ [cOCTVC1_GSM_SAPI_ENUM_TCHH] = L1SAP_COMMON_SAPI_TCH_H,
+ [cOCTVC1_GSM_SAPI_ENUM_FACCHH] = L1SAP_COMMON_SAPI_FACCH_H,
+ [cOCTVC1_GSM_SAPI_ENUM_NCH] = L1SAP_COMMON_SAPI_NCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PDTCH] = L1SAP_COMMON_SAPI_PDTCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PACCH] = L1SAP_COMMON_SAPI_PACCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PBCCH] = L1SAP_COMMON_SAPI_PBCCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PAGCH] = L1SAP_COMMON_SAPI_PAGCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PPCH] = L1SAP_COMMON_SAPI_PPCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PNCH] = L1SAP_COMMON_SAPI_PNCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PTCCH] = L1SAP_COMMON_SAPI_PTCCH,
+ [cOCTVC1_GSM_SAPI_ENUM_PRACH] = L1SAP_COMMON_SAPI_PRACH,
+};
+
+static enum l1sap_common_sapi get_common_sapi(tOCT_UINT8 sapi)
+{
+ if (sapi >= _OCTVC1_GSM_SAPI_ENUM_LENGTH)
+ return L1SAP_COMMON_SAPI_UNKNOWN;
+ return common_sapi_by_oct_sapi[sapi];
+}
+
+static void set_log_ctx_sapi(tOCT_UINT8 sapi)
+{
+ l1sap_log_ctx_sapi = get_common_sapi(sapi);
+ log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi);
+}
+
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)
@@ -955,7 +1007,9 @@ static int handle_ph_readytosend_ind(struct octphy_hdl *fl1,
struct msgb *resp_msg;
tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req;
- /* Retrive the data */
+ set_log_ctx_sapi(evt->LchId.bySAPI);
+
+ /* Retrieve the data */
fn = evt->ulFrameNumber;
ts_num = (uint8_t) evt->LchId.byTimeslotNb;
sc = (uint8_t) evt->LchId.bySubChannelNb;
@@ -1079,6 +1133,8 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1,
uint8_t ts_num = (uint8_t) data_ind->LchId.byTimeslotNb;
uint8_t sc = (uint8_t) data_ind->LchId.bySubChannelNb;
+ set_log_ctx_sapi(data_ind->LchId.bySAPI);
+
/* Need to combine two 16bit MSB and LSB to form 32bit FN */
fn = data_ind->Data.ulFrameNumber;
@@ -1149,7 +1205,7 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1,
/* 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?
+ /* FIXME: better conversion 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);
*/
@@ -1171,7 +1227,10 @@ static int handle_ph_rach_ind(struct octphy_hdl *fl1,
int rc;
struct ph_rach_ind_param rach_ind_param;
- dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo);
+ set_log_ctx_sapi(ra_ind->LchId.bySAPI);
+
+ LOGPFN(DL1C, LOGL_DEBUG, ra_ind->ulFrameNumber, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n",
+ LOG_PARAM_MEAS(&ra_ind->MeasurementInfo));
if (ra_ind->ulMsgLength != 1) {
LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber,
@@ -1267,7 +1326,11 @@ static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id)
wlc->num_retrans++;
msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans");
msg_set_retrans_flag(msg);
- osmo_wqueue_enqueue(&fl1h->phy_wq, msg);
+ if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Queue full on wlc retransmit\n");
+ msgb_free(msg);
+ return 0;
+ }
osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0);
count++;
LOGP(DL1C, LOGL_INFO, "Re-transmitting %s "
@@ -1634,7 +1697,7 @@ static int rx_octphy_msg(struct msgb *msg)
}
/* we first need to decode the common OCTPKT header and dispatch
- * based on contrl (command/resp) or data (event=indication) */
+ * based on control (command/resp) or data (event=indication) */
switch (format) {
case cOCTVOCNET_PKT_FORMAT_CTRL:
ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4);
@@ -1782,10 +1845,7 @@ struct octphy_hdl *l1if_open(struct phy_link *plink)
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;
+ osmo_fd_setup(&fl1h->phy_wq.bfd, sfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, fl1h, 0);
rc = osmo_fd_register(&fl1h->phy_wq.bfd);
if (rc < 0) {
close(sfd);
diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c
index d44f7211..bb519a04 100644
--- a/src/osmo-bts-octphy/l1_oml.c
+++ b/src/osmo-bts-octphy/l1_oml.c
@@ -28,6 +28,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
@@ -38,6 +39,7 @@
#include <osmo-bts/bts.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/nm_common_fsm.h>
#include "l1_if.h"
#include "l1_oml.h"
@@ -54,7 +56,7 @@
bool no_fw_check = 0;
-#define LOGPTRX(byTrxId, level, fmt, args...) \
+#define LOGPOCTTRX(byTrxId, level, fmt, args...) \
LOGP(DL1C, level, "(byTrxId %u) " fmt, byTrxId, ## args)
/* Map OSMOCOM logical channel type to OctPHY Logical channel type */
@@ -185,26 +187,31 @@ extern uint8_t rach_detected_Other_g;
static int opstart_compl(struct gsm_abis_mo *mo)
{
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr);
/* 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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ case NM_OC_CHANNEL:
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (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);
+ }
}
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi,
+ NM_EV_OPSTART_ACK, NULL);
+ default:
+ OSMO_ASSERT(0);
}
-
- /* Send OPSTART ack */
- return oml_mo_opstart_ack(mo);
}
static
@@ -243,7 +250,7 @@ static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config)
p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET;
}
-static void lchan2lch_par(struct gsm_lchan *lchan,
+static int lchan2lch_par(struct gsm_lchan *lchan,
tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config)
{
struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
@@ -252,7 +259,7 @@ static void lchan2lch_par(struct gsm_lchan *lchan,
int j;
LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
- gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+ gsm_lchan_name(lchan), __func__, lchan->tch_mode);
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
@@ -341,11 +348,13 @@ static void lchan2lch_par(struct gsm_lchan *lchan,
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;
-
+ default:
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n",
+ gsm48_chan_mode_name(lchan->tch_mode));
+ return -ENOTSUP;
}
+
+ return 0;
}
/***********************************************************************
@@ -381,7 +390,7 @@ static int lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *d
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");
+ LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan activation\n");
return -EINVAL;
}
@@ -437,6 +446,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
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;
+ int rc;
lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *)
msgb_put(msg, sizeof(*lac));
@@ -449,10 +459,12 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
lac->LchId.bySAPI = cmd->sapi;
lac->LchId.byDirection = cmd->dir;
- lac->Config.byTimingAdvance = lchan->rqd_ta;
+ lac->Config.byTimingAdvance = lchan->ta_ctrl.current;
lac->Config.byBSIC = lchan->ts->trx->bts->bsic;
-
- lchan2lch_par(lchan, &lac->Config);
+ if ((rc = lchan2lch_par(lchan, &lac->Config)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac);
@@ -496,7 +508,7 @@ static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *da
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");
+ LOGPOCTTRX(pcr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during cipher mode activation\n");
return -EINVAL;
}
@@ -506,7 +518,7 @@ static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *da
* sub-channel, only th request contains this information :( */
lchan = &ts->lchan[(unsigned long) data];
- /* TODO: This state machine should be shared accross BTS models? */
+ /* TODO: This state machine should be shared across BTS models? */
switch (lchan->ciph_state) {
case LCHAN_CIPH_RX_REQ:
lchan->ciph_state = LCHAN_CIPH_RX_CONF;
@@ -700,7 +712,7 @@ static int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void
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");
+ LOGPOCTTRX(ldr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan deactivation\n");
return -EINVAL;
}
@@ -1095,9 +1107,6 @@ int lchan_activate(struct gsm_lchan *lchan)
}
enqueue_sapi_act_cmd(lchan, sapi, dir);
}
-
- lchan_init_lapdm(lchan);
-
return 0;
}
@@ -1107,13 +1116,6 @@ int l1if_rsl_chan_act(struct gsm_lchan *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 =
@@ -1133,8 +1135,10 @@ static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, voi
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);
+ osmo_talloc_replace_string(fl1h, &fl1h->info.system.platform,
+ (const char *) aisr->szPlatform);
+ osmo_talloc_replace_string(fl1h, &fl1h->info.system.version,
+ (const char *) aisr->szVersion);
msgb_free(resp);
@@ -1169,7 +1173,7 @@ static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp,
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",
+ snprintf(ver_hdr, sizeof(ver_hdr), "%02d.%02d.%02d-B%d",
cOCTVC1_MAIN_VERSION_MAJOR, cOCTVC1_MAIN_VERSION_MINOR,
cOCTVC1_MAIN_VERSION_MAINTENANCE, cOCTVC1_MAIN_VERSION_BUILD);
@@ -1191,16 +1195,19 @@ static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp,
if (!no_fw_check) {
LOGP(DL1C, LOGL_ERROR,
- "use option -I to override the check (not recommened)\n");
+ "use option -I to override the check (not recommended)\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_talloc_replace_string(fl1h, &fl1h->info.app.name,
+ (const char *) air->szName);
+ osmo_talloc_replace_string(fl1h, &fl1h->info.app.description,
+ (const char *) air->szDescription);
+ osmo_talloc_replace_string(fl1h, &fl1h->info.app.version,
+ (const char *) air->szVersion);
OSMO_ASSERT(strlen(ver_hdr) < sizeof(pinst->version));
osmo_strlcpy(pinst->version, ver_hdr, strlen(ver_hdr));
@@ -1283,7 +1290,7 @@ static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *d
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");
+ LOGPOCTTRX(or->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during TRX opening procedure -- abort\n");
exit(1);
}
@@ -1340,13 +1347,13 @@ int l1if_trx_open(struct gsm_bts_trx *trx)
}
oc->Config.usBcchArfcn = trx->bts->c0->arfcn;
#endif
- oc->Config.usTsc = trx->bts->bsic & 0x7;
+ oc->Config.usTsc = BTS_TSC(trx->bts);
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
+ /* Take the Tx Attn received in set radio attributes
* x4 is for the value in db */
oc->RfConfig.ulTxAttndB = (trx->max_power_red) << 2;
}
@@ -1426,7 +1433,7 @@ static int l1if_over_sample_16x_modif(struct gsm_bts_trx *trx)
}
#endif
-uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
return 0;
}
@@ -1437,8 +1444,9 @@ static int trx_init(struct gsm_bts_trx *trx)
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); */
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ // (void*)(intptr_t)NM_NACK_CANT_PERFORM);
}
l1if_check_app_version(trx);
@@ -1470,7 +1478,7 @@ static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *d
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");
+ LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during physical channel activation -- abort\n");
exit(1);
}
@@ -1507,8 +1515,7 @@ static int ts_connect_as(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_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc =
- (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));
@@ -1553,7 +1560,7 @@ static int ts_disconnect_cb(struct octphy_hdl *fl1, struct msgb *resp,
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");
+ LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during ts disconnection\n");
return -EINVAL;
}
@@ -1581,7 +1588,7 @@ static int ts_connect_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
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");
+ LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id while connecting ts\n");
return -EINVAL;
}
@@ -1678,7 +1685,7 @@ int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
bts_model_trx_deact_rf(trx);
/* Close TRX */
- rc = bts_model_trx_close(trx);
+ rc = trx_close(trx);
if (rc != 0) {
LOGP(DL1C, LOGL_ERROR,
"Cannot close TRX %d, it is already closed.\n",
@@ -1716,10 +1723,11 @@ 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)
+void bts_model_trx_close(struct gsm_bts_trx *trx)
{
/* FIXME: close only one TRX */
- return trx_close(trx);
+ int rc = trx_close(trx);
+ bts_model_trx_close_cb(trx, rc);
}
@@ -1733,41 +1741,46 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
}
/* callback from OML */
-int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
- struct tlv_parsed *new_attr, int kind, void *obj)
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
{
- if (kind == NM_OC_RADIO_CARRIER) {
- struct gsm_bts_trx *trx = obj;
- /*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx;
- power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ switch (foh->msg_type) {
+ case NM_MT_SET_RADIO_ATTR:
+ trx = obj;
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
+ break;
}
- return oml_fom_ack_nack(msg, 0);
+
+ return 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* trx;
struct gsm_bts_trx_ts *ts;
+ int rc;
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_BTS:
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);
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx*) obj;
+ rc = trx_init(trx);
+ 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;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -1786,8 +1799,7 @@ 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;
+ tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *oc;
oc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *)
msgb_put(msg, sizeof(*oc));
@@ -1810,7 +1822,7 @@ void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
{
int rc;
if (as_pchan == GSM_PCHAN_TCH_F_PDCH
- || as_pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ || as_pchan == GSM_PCHAN_OSMO_DYN) {
LOGP(DL1C, LOGL_ERROR,
"%s Requested TS connect as %s,"
" expected a specific pchan instead\n",
diff --git a/src/osmo-bts-octphy/l1_oml.h b/src/osmo-bts-octphy/l1_oml.h
index 4729df5b..3c814c7f 100644
--- a/src/osmo-bts-octphy/l1_oml.h
+++ b/src/osmo-bts-octphy/l1_oml.h
@@ -10,7 +10,7 @@ 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);
+uint32_t trx_get_hlayer1(const 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);
diff --git a/src/osmo-bts-octphy/l1_tch.c b/src/osmo-bts-octphy/l1_tch.c
index df0469dd..4b542d11 100644
--- a/src/osmo-bts-octphy/l1_tch.c
+++ b/src/osmo-bts-octphy/l1_tch.c
@@ -146,12 +146,12 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
&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);
+ LOGPLCFN(lchan, fn, DL1P, LOGL_DEBUG, "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);
+ ber10k, lqual_cb, 0, 0, 0);
}
payload_len = data_ind->Data.ulDataLength;
@@ -173,13 +173,13 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
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);
+ LOGPLCFN(lchan, fn, DL1P, LOGL_NOTICE, "%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));
+ LOGPLCFN(lchan, fn, DL1P, LOGL_DEBUG, "%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:
@@ -201,7 +201,7 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
rmsg = l1_to_rtppayload_amr(payload, payload_len,
&lchan->tch.amr_mr);
#else
- LOGPFN(DL1P, LOGL_ERROR, fn, "OctPHY only supports FR!\n");
+ LOGPLCFN(lchan, fn, DL1P, LOGL_ERROR, "OctPHY only supports FR!\n");
return -1;
#endif
break;
@@ -210,13 +210,13 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr,
if (rmsg)
return add_l1sap_header(trx, rmsg, lchan, chan_nr,
data_ind->Data.ulFrameNumber,
- ber10k, lqual_cb);
+ ber10k, lqual_cb, 0, 0, 0);
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);
+ LOGPLCFN(lchan, fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %d incompatible with lchan\n",
+ gsm_lchan_name(lchan), payload_type);
return -EINVAL;
}
@@ -241,8 +241,7 @@ void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type,
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));
+ LOGPLCHAN(lchan, DRTP, LOGL_DEBUG, "RTP IN: %s\n", osmo_hexdump(rtp_pl, rtp_pl_len));
l1_payload = &data[0];
@@ -271,13 +270,11 @@ void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type,
}
if (rc < 0) {
- LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "unable to parse RTP payload\n");
return;
}
*len = rc;
- DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
- osmo_hexdump(data, *len));
+ LOGPLCHAN(lchan, DRTP, LOGL_DEBUG, "RTP->L1: %s\n", osmo_hexdump(data, *len));
}
diff --git a/src/osmo-bts-octphy/l1_utils.h b/src/osmo-bts-octphy/l1_utils.h
index d1a87170..1c1d2225 100644
--- a/src/osmo-bts-octphy/l1_utils.h
+++ b/src/osmo-bts-octphy/l1_utils.h
@@ -2,8 +2,8 @@
#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];
+extern const struct value_string octphy_l1sapi_names[23];
+extern const struct value_string octphy_dir_names[5];
+extern const struct value_string octphy_clkmgr_state_vals[8];
+extern const struct value_string octphy_cid_vals[37];
+extern const struct value_string octphy_eid_vals[7];
diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c
index 928a4c81..56849b55 100644
--- a/src/osmo-bts-octphy/main.c
+++ b/src/osmo-bts-octphy/main.c
@@ -55,7 +55,8 @@ extern bool no_fw_check;
int bts_model_print_help()
{
- printf(" -I --no-fw-check Override firmware version check\n");
+ printf("\nModel specific options:\n");
+ printf(" -I --no-fw-check Override firmware version check\n");
return 0;
}
diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c
index 6da038b1..271ed04f 100644
--- a/src/osmo-bts-octphy/octphy_hw_api.c
+++ b/src/osmo-bts-octphy/octphy_hw_api.c
@@ -119,7 +119,7 @@ static int rf_port_stats_compl_cb(struct octphy_hdl *fl1, struct msgb *resp,
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",
+ "Tx(Bytes=%d, 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,
diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c
index d250a957..308252bb 100644
--- a/src/osmo-bts-octphy/octphy_vty.c
+++ b/src/osmo-bts-octphy/octphy_vty.c
@@ -54,13 +54,11 @@
#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"
+ OCT_STR "Configure the hardware address of the OCTPHY\n"
"hardware address in aa:bb:cc:dd:ee:ff format\n")
{
struct phy_link *plink = vty->index;
@@ -360,7 +358,7 @@ DEFUN(show_clk_sync_stats, show_clk_sync_stats_cmd,
return CMD_SUCCESS;
}
-void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink)
{
if (plink->u.octphy.netdev_name)
vty_out(vty, " octphy net-device %s%s",
@@ -399,15 +397,15 @@ void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
#endif
}
-void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst)
{
}
-void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts)
{
}
-void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx)
{
}
@@ -437,10 +435,8 @@ DEFUN(show_sys_info, show_sys_info_cmd,
}
-int bts_model_vty_init(struct gsm_bts *bts)
+int bts_model_vty_init(void *ctx)
{
- 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);
diff --git a/src/osmo-bts-octphy/octpkt.c b/src/osmo-bts-octphy/octpkt.c
index d96d93d8..c6aea1f7 100644
--- a/src/osmo-bts-octphy/octpkt.c
+++ b/src/osmo-bts-octphy/octpkt.c
@@ -101,7 +101,7 @@ void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len,
#include <net/ethernet.h>
/*! \brief Initialize a packet socket
- * \param[in] tye Socket type like SOCK_RAW or SOCK_DGRAM
+ * \param[in] type 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
diff --git a/src/osmo-bts-omldummy/Makefile.am b/src/osmo-bts-omldummy/Makefile.am
index 5a4ce7c5..f7a05047 100644
--- a/src/osmo-bts-omldummy/Makefile.am
+++ b/src/osmo-bts-omldummy/Makefile.am
@@ -1,6 +1,28 @@
-AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+AM_CFLAGS = \
+ -Wall -fno-strict-aliasing \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBGPS_CFLAGS) \
+ $(NULL)
+
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude
-COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl
+
+COMMON_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ -ldl \
+ $(NULL)
bin_PROGRAMS = osmo-bts-omldummy
diff --git a/src/osmo-bts-omldummy/bts_model.c b/src/osmo-bts-omldummy/bts_model.c
index c0114015..0690d9db 100644
--- a/src/osmo-bts-omldummy/bts_model.c
+++ b/src/osmo-bts-omldummy/bts_model.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -23,6 +23,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/codec/codec.h>
+#include <osmocom/core/fsm.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/phy_link.h>
@@ -34,6 +35,7 @@
#include <osmo-bts/bts_model.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/nm_common_fsm.h>
/* TODO: check if dummy method is sufficient, else implement */
int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
@@ -50,10 +52,9 @@ int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr,
return -1;
}
-int bts_model_trx_close(struct gsm_bts_trx *trx)
+void bts_model_trx_close(struct gsm_bts_trx *trx)
{
- LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
- return 0;
+ bts_model_trx_close_cb(trx, 0);
}
int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
@@ -72,18 +73,11 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
}
return 0;
}
@@ -99,24 +93,28 @@ 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)
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
- int cause = 0;
+ int rc;
switch (foh->msg_type) {
case NM_MT_SET_BTS_ATTR:
- cause = vbts_set_bts(obj);
+ rc = vbts_set_bts(obj);
break;
case NM_MT_SET_RADIO_ATTR:
- cause = vbts_set_trx(obj);
+ rc = vbts_set_trx(obj);
break;
case NM_MT_SET_CHAN_ATTR:
- cause = vbts_set_ts(obj);
+ rc = vbts_set_ts(obj);
+ break;
+ default:
+ rc = 0;
break;
}
- return oml_fom_ack_nack(msg, cause);
+
+ return rc;
}
/* MO: TS 12.21 Managed Object */
@@ -125,16 +123,15 @@ 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_BASEB_TRANSC:
+ case NM_OC_RADIO_CARRIER:
+ case NM_OC_CHANNEL:
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);
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -151,14 +148,13 @@ int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *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__);
+ power_trx_change_compl(trx, p_trxout_mdBm);
return 0;
}
@@ -168,7 +164,7 @@ int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
return 0;
}
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
return 0;
}
@@ -176,11 +172,20 @@ uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
int bts_model_init(struct gsm_bts *bts)
{
bts->variant = BTS_OSMO_OMLDUMMY;
+ /* order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_BCCH_POWER_RED);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_HOPPING);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VBS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VGCS);
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ struct trx_power_params *tpp = &trx->power_params;
+ /* Speed up shutdown, we don't care about power ramping in omldummy */
+ tpp->ramp.step_interval_sec = 0;
return 0;
}
@@ -220,3 +225,8 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
{
return 0;
}
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ return 0;
+}
diff --git a/src/osmo-bts-omldummy/main.c b/src/osmo-bts-omldummy/main.c
index 3f1d58c5..167d43a2 100644
--- a/src/osmo-bts-omldummy/main.c
+++ b/src/osmo-bts-omldummy/main.c
@@ -1,35 +1,136 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
#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/bts_sm.h>
#include <osmo-bts/oml.h>
+static void print_usage(const char *prog_name)
+{
+ printf("Usage: %s [-h] [--features FOO,BAR,BAZ] dst_host site_id [trx_num]\n", prog_name);
+}
+
+static void print_help(const char *prog_name)
+{
+ print_usage(prog_name);
+ printf(" -h --help This text.\n");
+ printf(" -f --features FOO,BAR,BAZ BTS features to issue on OML startup.\n"
+ " The names correspond to BTS_FEAT_* constants\n"
+ " as defined in osmocom/gsm/bts_features.h,\n"
+ " e.g. '-f VAMOS'\n");
+}
+
+struct {
+ char *dst_host;
+ int site_id;
+ int trx_num;
+ char *features;
+} cmdline = {
+ .trx_num = 8,
+};
+
+void parse_cmdline(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"features", 1, 0, 'f'},
+ {0}
+ };
+
+ c = getopt_long(argc, argv, "hf:", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help(argv[0]);
+ exit(0);
+ case 'f':
+ cmdline.features = optarg;
+ break;
+ default:
+ /* catch unknown options *as well as* missing arguments. */
+ fprintf(stderr, "Error in command line options. Exiting.\n");
+ exit(-1);
+ }
+ }
+
+ if (optind + 2 > argc) {
+ print_usage(argv[0]);
+ exit(1);
+ }
+
+ cmdline.dst_host = argv[optind];
+ cmdline.site_id = atoi(argv[optind + 1]);
+ if (optind + 2 < argc)
+ cmdline.trx_num = atoi(argv[optind + 2]);
+
+ if (optind + 3 < argc) {
+ print_usage(argv[0]);
+ exit(1);
+ }
+}
+
+void set_bts_features(struct bitvec *features, char *features_str)
+{
+ char *saveptr = NULL;
+ char *token;
+
+ if (!features_str)
+ return;
+
+ while ((token = strtok_r(features_str, ",", &saveptr))) {
+ enum osmo_bts_features feat;
+ features_str = NULL;
+
+ feat = get_string_value(osmo_bts_features_names, token);
+
+ if ((int)feat < 0) {
+ fprintf(stderr, "Unknown BTS feature: '%s'\n", token);
+ exit(-1);
+ }
+
+ osmo_bts_set_feature(features, feat);
+ }
+}
int main(int argc, char **argv)
{
struct gsm_bts *bts;
struct gsm_bts_trx *trx;
- struct e1inp_line *line;
+ struct bsc_oml_host *bsc_oml_host;
int i;
- char *dst_host = argv[1];
- int site_id = atoi(argv[2]);
+ parse_cmdline(argc, argv);
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);
+ g_bts_sm = gsm_bts_sm_alloc(tall_bts_ctx);
+ if (!g_bts_sm)
+ exit(1);
+
+ bts = gsm_bts_alloc(g_bts_sm, 0);
if (!bts)
exit(1);
- bts->ip_access.site_id = site_id;
+
+ bts->ip_access.site_id = cmdline.site_id;
bts->ip_access.bts_id = 0;
/* Additional TRXs */
- for (i = 1; i < 8; i++) {
+ for (i = 1; i < cmdline.trx_num; i++) {
trx = gsm_bts_trx_alloc(bts);
if (!trx)
exit(1);
@@ -37,13 +138,25 @@ int main(int argc, char **argv)
if (bts_init(bts) < 0)
exit(1);
+
+ set_bts_features(bts->features, cmdline.features);
+
+ /* VAMOS: allocate shadow timeslots for each TRX */
+ if (osmo_bts_has_feature(bts->features, BTS_FEAT_VAMOS)) {
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ gsm_bts_trx_init_shadow_ts(trx);
+ }
+
//btsb = bts_role_bts(bts);
abis_init(bts);
-
- line = abis_open(bts, dst_host, "OMLdummy");
- if (!line)
- exit(2);
+ bsc_oml_host = talloc_zero(bts, struct bsc_oml_host);
+ OSMO_ASSERT(bsc_oml_host);
+ bsc_oml_host->addr = talloc_strdup(bsc_oml_host, cmdline.dst_host);
+ OSMO_ASSERT(bsc_oml_host->addr);
+ llist_add_tail(&bsc_oml_host->list, &bts->bsc_oml_hosts);
+ if (abis_open(bts, "OMLdummy") != 0)
+ exit(1);
while (1) {
osmo_select_main(0);
diff --git a/src/osmo-bts-sysmo/Makefile.am b/src/osmo-bts-sysmo/Makefile.am
index 4901ea3c..12dea3b4 100644
--- a/src/osmo-bts-sysmo/Makefile.am
+++ b/src/osmo-bts-sysmo/Makefile.am
@@ -1,16 +1,62 @@
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)
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBGPS_CFLAGS) \
+ $(NULL)
-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 \
+COMMON_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ misc/sysmobts_mgr.h \
+ misc/sysmobts_misc.h \
+ misc/sysmobts_par.h \
+ misc/sysmobts_eeprom.h \
+ misc/sysmobts_nl.h \
misc/sysmobts-layer1.h \
- l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h
+ femtobts.h \
+ hw_misc.h \
+ l1_fwd.h \
+ l1_if.h \
+ l1_transp.h \
+ eeprom.h \
+ utils.h \
+ $(NULL)
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
+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 \
+ sysmobts_ctrl.c \
+ $(NULL)
+
osmo_bts_sysmo_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
osmo_bts_sysmo_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
@@ -22,7 +68,7 @@ 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
+bin_PROGRAMS += sysmobts-calib
sysmobts_calib_SOURCES = misc/sysmobts-calib.c misc/sysmobts-layer1.c
sysmobts_calib_LDADD = -lrt $(COMMON_LDADD)
@@ -37,7 +83,15 @@ sysmobts_mgr_SOURCES = \
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_mgr_LDADD = \
+ $(LIBGPS_LIBS) \
+ $(top_builddir)/src/common/libbts.a \
+ $(COMMON_LDADD) \
+ $(NULL)
-sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c
+sysmobts_util_SOURCES = \
+ misc/sysmobts_util.c \
+ misc/sysmobts_par.c \
+ eeprom.c \
+ $(NULL)
sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-sysmo/calib_file.c b/src/osmo-bts-sysmo/calib_file.c
index 2f723dd0..bb6cabfd 100644
--- a/src/osmo-bts-sysmo/calib_file.c
+++ b/src/osmo-bts-sysmo/calib_file.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/eeprom.c b/src/osmo-bts-sysmo/eeprom.c
index 472b78e2..6c887d41 100644
--- a/src/osmo-bts-sysmo/eeprom.c
+++ b/src/osmo-bts-sysmo/eeprom.c
@@ -226,12 +226,12 @@ typedef struct
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 u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknown)
+ uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknown)
+ uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknown)
+ uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknown)
+ uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknown)
+ uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknown)
uint32_t : 12; ///< unused
} __attribute__((packed)) sysInfo;
@@ -307,12 +307,12 @@ typedef struct
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 u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknown)
+ uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknown)
+ uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknown)
+ uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknown)
+ uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknown)
+ uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknown)
uint32_t : 12; ///< unused
} __attribute__((packed)) sysInfo;
diff --git a/src/osmo-bts-sysmo/femtobts.c b/src/osmo-bts-sysmo/femtobts.c
index 480fe06b..7b02adc5 100644
--- a/src/osmo-bts-sysmo/femtobts.c
+++ b/src/osmo-bts-sysmo/femtobts.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/femtobts.h b/src/osmo-bts-sysmo/femtobts.h
index 9163ebbf..b048fc4e 100644
--- a/src/osmo-bts-sysmo/femtobts.h
+++ b/src/osmo-bts-sysmo/femtobts.h
@@ -68,25 +68,25 @@ enum uperfemto_clk_src {
};
#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];
+extern const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM];
+extern const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1];
+extern 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];
+extern const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM];
+extern const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1];
+extern 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];
+extern const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1];
+extern 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];
+extern const struct value_string femtobts_tracef_names[29];
+extern 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];
+extern const struct value_string femtobts_tch_pl_names[15];
+extern const struct value_string femtobts_chcomb_names[8];
+extern const struct value_string femtobts_clksrc_names[10];
-const struct value_string femtobts_dir_names[6];
+extern const struct value_string femtobts_dir_names[6];
enum pdch_cs {
PDCH_CS_1,
@@ -105,6 +105,6 @@ enum pdch_cs {
_NUM_PDCH_CS
};
-const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+extern 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
index 6aa3b83f..21d8dfdf 100644
--- a/src/osmo-bts-sysmo/hw_misc.c
+++ b/src/osmo-bts-sysmo/hw_misc.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/l1_fwd_main.c b/src/osmo-bts-sysmo/l1_fwd_main.c
index bc9fc21c..76c45e17 100644
--- a/src/osmo-bts-sysmo/l1_fwd_main.c
+++ b/src/osmo-bts-sysmo/l1_fwd_main.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -213,9 +213,7 @@ int main(int argc, char **argv)
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;
+ osmo_fd_setup(&wq->bfd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, l1fh, i);
rc = osmo_sock_init_ofd(&wq->bfd, AF_UNSPEC, SOCK_DGRAM,
IPPROTO_UDP, NULL, fwd_udp_ports[i],
OSMO_SOCK_F_BIND);
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
index df39e2f4..9ca1c323 100644
--- a/src/osmo-bts-sysmo/l1_if.c
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -51,6 +51,7 @@
#include <osmo-bts/msg_utils.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
#include <osmo-bts/tx_power.h>
+#include <osmo-bts/nm_common_fsm.h>
#include <sysmocom/femtobts/superfemto.h>
#include <sysmocom/femtobts/gsml1prim.h>
@@ -343,7 +344,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
abort();
}
- len = msgb_l2len(msg);
+ len = (msg->l2h) ? msgb_l2len(msg) : 0;
chan_nr = l1sap->u.data.chan_nr;
link_id = l1sap->u.data.link_id;
@@ -391,9 +392,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
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);
+ LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "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;
}
@@ -454,9 +454,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
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));
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "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);
@@ -466,7 +465,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
/* 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");
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
msgb_free(l1msg);
} else
dtx_int_signal(lchan);
@@ -504,7 +503,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
/* create new message and fill data */
if (msg) {
- msgb_pull(msg, sizeof(*l1sap));
/* create new message */
nmsg = l1p_msgb_alloc();
if (!nmsg)
@@ -513,7 +511,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
rc = l1if_tch_encode(lchan,
l1p->u.phDataReq.msgUnitParam.u8Buffer,
&l1p->u.phDataReq.msgUnitParam.u8Size,
- msg->data, msg->len, u32Fn, use_cache,
+ msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache,
l1sap->u.tch.marker);
if (rc < 0) {
/* no data encoded for L1: smth will be generated below */
@@ -550,7 +548,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
}
/* 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");
+ LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
msgb_free(nmsg);
return -ENOBUFS;
}
@@ -606,6 +604,15 @@ static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
else
l1if_rsl_chan_rel(lchan);
break;
+ case PRIM_INFO_ACT_UL_ACC:
+ case PRIM_INFO_DEACT_UL_ACC:
+ chan_nr = l1sap->u.info.u.ulacc_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACT_UL_ACC)
+ l1if_set_ul_acc(lchan, true);
+ else
+ l1if_set_ul_acc(lchan, false);
+ break;
default:
LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
l1sap->u.info.type);
@@ -682,7 +689,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
if (ts->flags & TS_F_PDCH_ACTIVE)
return GSM_PCHAN_PDCH;
return GSM_PCHAN_TCH_F;
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
return ts->dyn.pchan_is;
default:
return ts->pchan;
@@ -696,7 +703,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
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);
+ OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);
switch (sapi) {
case GsmL1_Sapi_Bcch:
@@ -796,6 +803,45 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
return (cbits << 3) | u8Tn;
}
+static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = {
+ [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE,
+ [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH,
+ [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH,
+ [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH,
+ [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH,
+ [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH,
+ [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH,
+ [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH,
+ [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH,
+ [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH,
+ [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F,
+ [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F,
+ [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H,
+ [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H,
+ [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH,
+ [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH,
+ [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH,
+ [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH,
+ [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH,
+ [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH,
+ [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH,
+ [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH,
+ [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH,
+};
+
+static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi)
+{
+ if (sapi >= GsmL1_Sapi_NUM)
+ return L1SAP_COMMON_SAPI_UNKNOWN;
+ return common_sapi_by_sapi_t[sapi];
+}
+
+static void set_log_ctx_sapi(GsmL1_Sapi_t sapi)
+{
+ l1sap_log_ctx_sapi = get_common_sapi(sapi);
+ log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi);
+}
+
static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1,
GsmL1_PhReadyToSendInd_t *rts_ind,
struct msgb *l1p_msg)
@@ -812,6 +858,8 @@ static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1,
uint8_t chan_nr, link_id;
uint32_t fn;
+ set_log_ctx_sapi(rts_ind->sapi);
+
/* 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);
@@ -896,31 +944,8 @@ empty_frame:
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);
-}
+#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d"
+#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming
static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
struct msgb *l1p_msg)
@@ -933,6 +958,8 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i
struct gsm_time g_time;
int rc = 0;
+ set_log_ctx_sapi(data_ind->sapi);
+
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) {
@@ -944,14 +971,12 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i
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",
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\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);
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size),
+ LOG_PARAM_MEAS(&data_ind->measParam));
/* check for TCH */
if (data_ind->sapi == GsmL1_Sapi_TchF
@@ -962,6 +987,25 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i
return rc;
}
+ /* If we got FACCH, the RTP clock needs to account for it,
+ * and if we have rtp continuous-streaming enabled,
+ * an actual BFI packet will be emitted.
+ *
+ * Only the case of TCH/F is currently handled; the task of
+ * supporting TCH/H is left as a FIXME for other/later
+ * developers. The difficulty with supporting FACCH/H is that
+ * only one GsmL1_Sapi_FacchH message will be received,
+ * covering 40 ms of time, but two RTP "tick" output calls
+ * will need to be made, with appropriately adjusted frame
+ * numbers. As a further consideration for rtp continuous-streaming
+ * mode, having the two RTP BFI packets sent directly back to back,
+ * as opposed to proper 20 ms timing, may be so undesirable
+ * that some deployments may rather disable TCH/H (use only TCH/F)
+ * than deal with the extra pain, or use TCH/H only on SDR-based
+ * BTS with osmo-bts-trx where correct timing is easily achieved. */
+ if (data_ind->sapi == GsmL1_Sapi_FacchF)
+ l1if_tch_rx_facch(trx, chan_nr, l1p_msg);
+
/* fill L1SAP header */
sap_msg = l1sap_msgb_alloc(data_ind->msgUnitParam.u8Size);
l1sap = msgb_l1sap_prim(sap_msg);
@@ -971,11 +1015,10 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i
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;
- }
+ 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,
@@ -996,7 +1039,9 @@ static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
int rc;
struct ph_rach_ind_param rach_ind_param;
- dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+ set_log_ctx_sapi(ra_ind->sapi);
+ LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n",
+ LOG_PARAM_MEAS(&ra_ind->measParam));
if ((ra_ind->msgUnitParam.u8Size != 1) &&
(ra_ind->msgUnitParam.u8Size != 2)) {
@@ -1192,13 +1237,127 @@ int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg)
return l1if_handle_ind(fl1h, msg);
}
+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));
+ if (trx->mo.fi->state != NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED)
+ 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]);
+ if (trx->mo.fi->state != NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED)
+ 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 */
+}
+
+static int activate_rf_mute_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+{
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 6, 0)
+ mute_rf_compl_cb(trx, resp, data);
+#endif
+
+ /* signal availability */
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
+
+ return 0;
+}
+
+int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);
+
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;
@@ -1217,21 +1376,18 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
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
+ } 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);
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 6, 0)
+ trx_rf_lock(trx, 1, activate_rf_mute_compl_cb);
+#else
+ activate_rf_mute_compl_cb(trx, resp, NULL);
+#endif
+ }
} 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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
}
msgb_free(resp);
@@ -1325,104 +1481,6 @@ int l1if_activate_rf(struct femtol1_hdl *hdl, int on)
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)
@@ -1444,10 +1502,12 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
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);
+ snprintf(trx->pinst->version, sizeof(trx->pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u",
+ fl1h->hw_info.ver_major, fl1h->hw_info.ver_minor,
+ 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]);
+
+ LOGP(DL1C, LOGL_INFO, "%s\n", trx->pinst->version);
#ifdef HW_SYSMOBTS_V1
if (sic->rfBand.gsm850)
@@ -1483,6 +1543,8 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
msgb_free(resp);
+ phy_link_state_set(trx->pinst->phy_link, PHY_LINK_CONNECTED);
+
/* FIXME: clock related */
return 0;
}
@@ -1846,7 +1908,8 @@ static void fill_trx_power_params(struct gsm_bts_trx *trx,
LOGP(DL1C, LOGL_NOTICE, "Assuming 1002 for sysmoBTS "
"Model number %u\n", fl1h->hw_info.model_nr);
/* fall-through */
- case 1002:
+ case 1002: /* sysmoBTS 1002 */
+ case 1003: /* sysmoBTS 1002 with GPS and PoE */
set_power_param(&trx->power_params, 23, 0);
}
}
@@ -1883,12 +1946,28 @@ int bts_model_phy_link_open(struct phy_link *plink)
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);
+ /* Frequency bands indicated to the BSC */
+ for (unsigned int i = 0; i < sizeof(hdl->hw_info.band_support) * 8; i++) {
+ if (~hdl->hw_info.band_support & (1 << i))
+ continue;
+ switch (1 << i) {
+ case GSM_BAND_850:
+ pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850;
+ break;
+ case GSM_BAND_900:
+ pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM;
+ /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */
+ /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */
+ break;
+ case GSM_BAND_1800:
+ pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS;
+ break;
+ case GSM_BAND_1900:
+ pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS;
+ break;
+ }
+ }
return 0;
}
diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h
index 1b214be7..c81b6bd4 100644
--- a/src/osmo-bts-sysmo/l1_if.h
+++ b/src/osmo-bts-sysmo/l1_if.h
@@ -129,6 +129,8 @@ 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_rx_facch(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);
@@ -143,6 +145,7 @@ 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);
+int l1if_set_ul_acc(struct gsm_lchan *lchan, bool active);
/* calibration loading */
int calib_load(struct femtol1_hdl *fl1h);
@@ -156,9 +159,9 @@ int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h);
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)
+static inline struct femtol1_hdl *trx_femtol1_hdl(const struct gsm_bts_trx *trx)
{
- struct phy_instance *pinst = trx_phy_instance(trx);
+ const struct phy_instance *pinst = trx_phy_instance(trx);
OSMO_ASSERT(pinst);
return pinst->u.sysmobts.hdl;
}
diff --git a/src/osmo-bts-sysmo/l1_transp_fwd.c b/src/osmo-bts-sysmo/l1_transp_fwd.c
index 87c230bb..ac510eda 100644
--- a/src/osmo-bts-sysmo/l1_transp_fwd.c
+++ b/src/osmo-bts-sysmo/l1_transp_fwd.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -126,10 +126,7 @@ int l1if_transport_open(int q, struct femtol1_hdl *fl1h)
wq->write_cb = prim_write_cb;
wq->read_cb = fwd_read_cb;
- ofd->data = fl1h;
- ofd->priv_nr = q;
- ofd->when |= BSC_FD_READ;
-
+ osmo_fd_setup(ofd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, fl1h, q);
rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
bts_host, fwd_udp_ports[q],
OSMO_SOCK_F_CONNECT);
diff --git a/src/osmo-bts-sysmo/l1_transp_hw.c b/src/osmo-bts-sysmo/l1_transp_hw.c
index 01bc2005..cfbc77c9 100644
--- a/src/osmo-bts-sysmo/l1_transp_hw.c
+++ b/src/osmo-bts-sysmo/l1_transp_hw.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -95,18 +95,18 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
queue = container_of(fd, struct osmo_wqueue, bfd);
- if (what & BSC_FD_READ)
+ if (what & OSMO_FD_READ)
queue->read_cb(fd);
- if (what & BSC_FD_EXCEPT)
+ if (what & OSMO_FD_EXCEPT)
queue->except_cb(fd);
- if (what & BSC_FD_WRITE) {
+ if (what & OSMO_FD_WRITE) {
struct iovec iov[5];
struct msgb *msg, *tmp;
int written, count = 0;
- fd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(fd);
llist_for_each_entry(msg, &queue->msg_queue, list) {
/* more writes than we have */
@@ -124,7 +124,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
/* Nothing scheduled? This should not happen. */
if (count == 0) {
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
return 0;
}
@@ -132,7 +132,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
if (written < 0) {
/* nothing written?! */
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
return 0;
}
@@ -151,7 +151,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
}
if (!llist_empty(&queue->msg_queue))
- fd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(fd);
}
return 0;
@@ -271,11 +271,7 @@ int l1if_transport_open(int q, struct femtol1_hdl *hdl)
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;
+ osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q);
rc = osmo_fd_register(read_ofd);
if (rc < 0) {
close(read_ofd->fd);
@@ -291,11 +287,7 @@ int l1if_transport_open(int q, struct femtol1_hdl *hdl)
}
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;
+ osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q);
rc = osmo_fd_register(write_ofd);
if (rc < 0) {
close(write_ofd->fd);
diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c
index ad7118ae..20b425da 100644
--- a/src/osmo-bts-sysmo/main.c
+++ b/src/osmo-bts-sysmo/main.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -53,47 +53,61 @@
#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);
- }
+ bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS
+ | NM_IPAC_MASK_GPRS_CODING_MCS;
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_CBCH);
- 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);
+ /* order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VBS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VGCS);
+
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB);
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP);
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER);
+
+ /* The default HR codec output format in the absence of saved
+ * vty config needs to match what was implemented previously,
+ * for the sake of existing deployments, i.e., to avoid
+ * a surprise functional change upon software update. */
+ bts->emit_hr_rfc5993 = false;
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ /* Frequency bands indicated to the BSC */
+ trx->support.freq_bands = 0x00; /* updated in bts_model_phy_link_open() */
+
+ /* Channel types and modes indicated to the BSC */
+ trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON
+ | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH
+ | NM_IPAC_F_CHANT_SDCCH8_CBCH
+ | NM_IPAC_F_CHANT_PDCHF
+ | NM_IPAC_F_CHANT_TCHF_PDCHF;
+ trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH;
+
return 0;
}
@@ -128,10 +142,10 @@ void bts_update_status(enum bts_global_status which, int 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"
+ printf( "\nModel specific options:\n"
+ " -w --hw-version Print the targeted HW Version\n"
+ " -M --pcu-direct Force PCU to access message queue for "
+ "PDCH dchannel directly\n"
);
};
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
index 4b34f50e..20ca6f51 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
@@ -622,7 +622,7 @@ int set_clock_cor(int clock_cor, int calib, int source)
return -1;
}
if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) {
- printf("Clock setup was not successfull.\n");
+ printf("Clock setup was not successful.\n");
return -2;
}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
index b7a27fb7..9c2b839a 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
+++ b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
@@ -32,6 +32,7 @@ struct sysmobts_eeprom { /* offset */
enum sysmobts_model_number {
MODEL_SYSMOBTS_1002 = 1002,
+ MODEL_SYSMOBTS_1003 = 1003,
MODEL_SYSMOBTS_1020 = 1020,
MODEL_SYSMOBTS_2050 = 2050,
};
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
index a8289c25..79bbf77e 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -192,11 +192,11 @@ static int parse_options(int argc, char **argv)
return 0;
}
-static void signal_handler(int signal)
+static void signal_handler(int signum)
{
- fprintf(stderr, "signal %u received\n", signal);
+ fprintf(stderr, "signal %u received\n", signum);
- switch (signal) {
+ switch (signum) {
case SIGINT:
case SIGTERM:
sysmobts_check_temp(no_eeprom_write);
@@ -204,6 +204,16 @@ static void signal_handler(int signal)
exit(0);
break;
case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_mgr_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_mgr_ctx, stderr);
@@ -218,25 +228,25 @@ static struct log_info_cat mgr_log_info_cat[] = {
.name = "DTEMP",
.description = "Temperature monitoring",
.color = "\033[1;35m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DFW] = {
.name = "DFW",
.description = "DSP/FPGA firmware management",
.color = "\033[1;36m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DFIND] = {
.name = "DFIND",
.description = "ipaccess-find handling",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DCALIB] = {
.name = "DCALIB",
.description = "Calibration handling",
.color = "\033[1;37m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
};
@@ -262,6 +272,7 @@ int main(int argc, char **argv)
osmo_init_ignore_signals();
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
+ signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
@@ -277,7 +288,7 @@ int main(int argc, char **argv)
exit(1);
}
- rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ rc = telnet_init_default(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
@@ -312,10 +323,10 @@ int main(int argc, char **argv)
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",
+ LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to localhost:%u\n",
OSMO_CTRL_PORT_BTS);
- sysmobts_mgr_temp_init(&manager, ccon);
+ sysmobts_mgr_temp_init(&manager, ccon);
if (sysmobts_mgr_calib_init(&manager) != 0)
exit(3);
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
index 12961e3f..45b024e1 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
index a0ba6493..ac35f392 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
@@ -54,7 +54,7 @@ enum calib_result {
CALIB_FAIL_START,
CALIB_FAIL_GPS,
CALIB_FAIL_CTRL,
- CALIB_SUCESS,
+ CALIB_SUCCESS,
};
static inline int compat_gps_read(struct gps_data_t *data)
@@ -167,10 +167,8 @@ static void mgr_gps_open(struct sysmobts_mgr_instance *mgr)
#else
gps_stream(mgr->calib.gpsdata, WATCH_ENABLE, NULL);
#endif
- 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;
+ osmo_fd_setup(&mgr->calib.gpsfd, mgr->calib.gpsdata->gps_fd, OSMO_FD_READ | OSMO_FD_EXCEPT,
+ mgr_gps_read, mgr, 0);
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);
@@ -271,7 +269,7 @@ static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int outcome)
* and in case of a failure in some minutes.
*/
int timeout = 2 * 60 * 60;
- if (outcome != CALIB_SUCESS)
+ if (outcome != CALIB_SUCCESS)
timeout = 5 * 60;
mgr->calib.calib_timeout.data = mgr;
@@ -390,7 +388,7 @@ static void handle_ctrl_set_cor(
LOGP(DCALIB, LOGL_NOTICE,
"Calibration process completed\n");
- calib_state_reset(mgr, CALIB_SUCESS);
+ calib_state_reset(mgr, CALIB_SUCCESS);
}
static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg)
@@ -459,7 +457,7 @@ static void bts_recon_timer_cb(void *data)
struct sysmobts_mgr_instance *mgr = data;
/* The connection failures are to be expected during boot */
- mgr->calib.bts_conn->ofd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(mgr->calib.bts_conn->ofd);
rc = ipa_client_conn_open(mgr->calib.bts_conn);
if (rc < 0) {
LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
@@ -543,8 +541,8 @@ int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr)
return 0;
}
- mgr->calib.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
- "localhost", 4238,
+ mgr->calib.bts_conn = ipa_client_conn_create2(tall_mgr_ctx, NULL, 0,
+ NULL, 0, "localhost", 4238,
bts_updown_cb, bts_read_cb,
NULL, mgr);
if (!mgr->calib.bts_conn) {
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
index 48a03124..fead4f27 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
index 1be56ac2..eea68793 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -233,7 +233,7 @@ static void sysmobts_mgr_temp_handle(struct sysmobts_mgr_instance *manager,
rep->variable = "oml-alert";
rep->value = oml_alert;
LOGP(DTEMP, LOGL_ERROR, "OML alert sent: %d\n",
- ctrl_cmd_send(&ctrl->write_queue, rep));
+ ctrl_cmd_send2(ctrl, rep));
talloc_free(rep);
}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
index 444ee7c3..33cf0dcb 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -69,28 +69,10 @@ static int go_to_parent(struct vty *vty)
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,
};
@@ -380,11 +362,11 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager",
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",
+ vty_out(vty, " Digital: %f Celsius%s",
sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL,
SYSMOBTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
- vty_out(vty, " RF: %f Celcius%s",
+ vty_out(vty, " RF: %f Celsius%s",
sysmobts_temp_get(SYSMOBTS_TEMP_RF,
SYSMOBTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
@@ -396,8 +378,8 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager",
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);
+ vty_out(vty, " sysmoBTS 2050 PA: %d Celsius%s", temp_pa, VTY_NEWLINE);
+ vty_out(vty, " sysmoBTS 2050 PA: %d Celsius%s", temp_board, VTY_NEWLINE);
sbts2050_uc_get_status(&status);
vty_out(vty, "Power Status%s", VTY_NEWLINE);
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.c b/src/osmo-bts-sysmo/misc/sysmobts_misc.c
index d996d644..b391ce66 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_misc.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -46,7 +46,7 @@
* Temperature handling
*********************************************************************/
-#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/temp%u_%s"
+#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/hwmon/hwmon0/temp%u_%s"
static const char *temp_type_str[_NUM_TEMP_TYPES] = {
[SYSMOBTS_TEMP_INPUT] = "input",
@@ -234,14 +234,14 @@ int sysmobts_firmware_reload(enum sysmobts_firmware_type type)
fd_in = open(name, O_RDONLY);
if (fd_in < 0) {
- LOGP(DFW, LOGL_ERROR, "unable ot open firmware file %s: %s\n",
+ LOGP(DFW, LOGL_ERROR, "unable to 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",
+ LOGP(DFW, LOGL_ERROR, "unable to open firmware device %s: %s\n",
fw_devs[type], strerror(errno));
close(fd_in);
return fd_out;
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_nl.c
index 67aa6636..5a937dcb 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_nl.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.h b/src/osmo-bts-sysmo/misc/sysmobts_nl.h
index 84f4d9cc..5dda4679 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_nl.h
+++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.h
@@ -11,7 +11,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.c b/src/osmo-bts-sysmo/misc/sysmobts_par.c
index de81fff5..0e8685f3 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_par.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_par.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -341,6 +341,8 @@ char *sysmobts_model(int bts_type, int trx_num)
case 0xffff:
case 1002:
return "sysmoBTS 1002";
+ case 1003:
+ return "sysmoBTS 1002+GPS+PoE";
case 2050:
switch(trx_num) {
case 0:
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_util.c b/src/osmo-bts-sysmo/misc/sysmobts_util.c
index c9930d8f..bd751431 100644
--- a/src/osmo-bts-sysmo/misc/sysmobts_util.c
+++ b/src/osmo-bts-sysmo/misc/sysmobts_util.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c
index ea7527dd..b4f6752d 100644
--- a/src/osmo-bts-sysmo/oml.c
+++ b/src/osmo-bts-sysmo/oml.c
@@ -11,7 +11,7 @@
* 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.
+ * 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/>.
@@ -23,6 +23,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
#include <sysmocom/femtobts/gsml1prim.h>
#include <sysmocom/femtobts/gsml1const.h>
@@ -39,11 +40,14 @@
#include <osmo-bts/phy_link.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/nm_common_fsm.h>
#include "l1_if.h"
#include "femtobts.h"
#include "utils.h"
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi);
+
static int mph_info_chan_confirm(struct gsm_lchan *lchan,
enum osmo_mph_info_type type, uint8_t cause)
{
@@ -88,13 +92,13 @@ static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
[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
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN 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);
+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)
@@ -267,36 +271,48 @@ 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);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr);
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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
+ case NM_OC_CHANNEL:
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
+ default:
+ OSMO_ASSERT(0);
+ }
}
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);
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ case NM_OC_CHANNEL:
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (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);
+ }
}
+ return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi,
+ NM_EV_OPSTART_ACK, NULL);
+ default:
+ OSMO_ASSERT(0);
}
-
- /* Send OPSTART ack */
- return oml_mo_opstart_ack(mo);
}
static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
@@ -325,6 +341,8 @@ static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
bts_shutdown(trx->bts, "RF-MUTE failure");
}
+ bts_update_status(BTS_STATUS_RF_MUTE, 1);
+
msgb_free(resp);
return 0;
@@ -358,7 +376,7 @@ static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
#endif
/* Begin to ramp up the power */
- power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
return opstart_compl(&trx->mo, l1_msg);
}
@@ -394,8 +412,9 @@ static int trx_init(struct gsm_bts_trx *trx)
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);
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
+ //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ // (void*)(intptr_t)NM_NACK_CANT_PERFORM);
}
femto_band = sysmobts_select_femto_band(trx, trx->arfcn);
@@ -412,9 +431,16 @@ static int trx_init(struct gsm_bts_trx *trx)
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->u8NbTsc = BTS_TSC(trx->bts);
+
+ if (!trx_ms_pwr_ctrl_is_osmo(trx)) {
+ /* Target is in the middle between lower and upper RxLev thresholds */
+ int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh);
+ int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh);
+ dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2;
+ } else {
+ dev_par->fRxPowerLevel = 0.0;
+ }
dev_par->fTxPowerLevel = ((float) initial_mdBm) / 1000;
LOGP(DL1C, LOGL_NOTICE, "Init TRX (ARFCN %u, TSC %u, RxPower % 2f dBm, "
@@ -426,9 +452,9 @@ static int trx_init(struct gsm_bts_trx *trx)
return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
}
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
- struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ const struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
return fl1h->hLayer1;
}
@@ -437,23 +463,27 @@ static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data)
{
msgb_free(l1_msg);
+ bts_model_trx_close_cb(trx, 0);
return 0;
}
-int bts_model_trx_close(struct gsm_bts_trx *trx)
+void bts_model_trx_close(struct gsm_bts_trx *trx)
{
struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
struct msgb *msg;
+ int rc;
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);
+ rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+ if (rc < 0)
+ bts_model_trx_close_cb(trx, rc);
}
-static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
+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];
@@ -495,7 +525,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts,
GsmL1_MphConnectReq_t *cr;
if (pchan == GSM_PCHAN_TCH_F_PDCH
- || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ || pchan == GSM_PCHAN_OSMO_DYN) {
LOGP(DL1C, LOGL_ERROR,
"%s Requested TS connect as %s,"
" expected a specific pchan instead\n",
@@ -508,10 +538,9 @@ static int ts_connect_as(struct gsm_bts_trx_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));
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "pchan=%s ts_connect_as(%s) logChComb=%s\n",
+ 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);
}
@@ -520,7 +549,7 @@ 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:
+ case GSM_PCHAN_OSMO_DYN:
ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
/* First connect as NONE, until first RSL CHAN ACT. */
pchan = GSM_PCHAN_NONE;
@@ -544,8 +573,7 @@ GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
case GSM_LCHAN_TCH_H:
return GsmL1_Sapi_TchH;
default:
- LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n");
break;
}
return GsmL1_Sapi_Idle;
@@ -555,7 +583,7 @@ 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)
+ if (pchan == GSM_PCHAN_OSMO_DYN)
pchan = lchan->ts->dyn.pchan_want;
switch (pchan) {
@@ -575,7 +603,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
case GSM_PCHAN_TCH_F_PDCH:
case GSM_PCHAN_UNKNOWN:
default:
- /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ /* case GSM_PCHAN_OSMO_DYN: is caught above */
return GsmL1_SubCh_NA;
}
@@ -638,10 +666,6 @@ static const struct sapi_dir pdtch_sapis[] = {
#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;
@@ -674,11 +698,6 @@ static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
},
};
-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);
@@ -766,12 +785,8 @@ static void sapi_queue_dispatch(struct gsm_lchan *lchan, int 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.");
+ LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n",
+ llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending.");
return;
}
@@ -811,8 +826,7 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
- gsm_lchan_name(lchan),
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ",
get_value_string(femtobts_l1sapi_names, ic->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(femtobts_dir_names, ic->dir));
@@ -834,19 +848,15 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n");
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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
+ cmd->sapi, cmd->dir, ic->sapi, ic->dir);
goto err;
}
@@ -926,15 +936,14 @@ static void set_payload_format(GsmL1_LogChParam_t *lch_par)
#endif /* L1_HAS_RTP_MODE */
}
-static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
+static int 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);
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, ": %s tch_mode=0x%02x\n", __func__, lchan->tch_mode);
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
@@ -962,7 +971,9 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
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? */
+ /* At call set-up, after every successful handover and after a channel mode modify, the
+ * default phase (odd) shall be used in downlink direction. */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd;
lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
/* initialize to clean state */
@@ -1011,10 +1022,13 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
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;
+ default:
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n",
+ gsm48_chan_mode_name(lchan->tch_mode));
+ return -ENOTSUP;
}
+
+ return 0;
}
static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
@@ -1023,6 +1037,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
struct msgb *msg = l1p_msgb_alloc();
int sapi = cmd->sapi;
int dir = cmd->dir;
+ int rc;
GsmL1_MphActivateReq_t *act_req;
GsmL1_LogChParam_t *lch_par;
@@ -1045,7 +1060,10 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
break;
case GsmL1_Sapi_TchH:
case GsmL1_Sapi_TchF:
- lchan2lch_par(lch_par, lchan);
+ if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
/*
* Be sure that every packet is received, even if it
* fails. In this case the length might be lower or 0.
@@ -1078,9 +1096,9 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ", act_req->hLayer2,
+ get_value_string(femtobts_l1sapi_names, act_req->sapi));
+ dump_lch_par(LOGL_INFO, lch_par, act_req->sapi);
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(femtobts_dir_names, act_req->dir));
@@ -1104,9 +1122,7 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
/* 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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", 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);
@@ -1153,14 +1169,12 @@ int lchan_activate(struct gsm_lchan *lchan)
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n");
- /* override the regular SAPIs if this is the first hand-over
- * related activation of the LCHAN */
+ /* For handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl
+ * SACCH should be activated. */
if (lchan->ho.active == HANDOVER_ENABLED)
- s4l = &sapis_for_ho;
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
for (i = 0; i < s4l->num_sapis; i++) {
int sapi = s4l->sapis[i].sapi;
@@ -1173,12 +1187,13 @@ int lchan_activate(struct gsm_lchan *lchan)
fl1h->alive_prim_cnt = 0;
osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
}
+
+ /* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */
+ if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink
+ && !lchan->want_dl_sacch_active)
+ continue;
enqueue_sapi_act_cmd(lchan, sapi, dir);
}
-
-#warning "FIXME: Should this be in sapi_activate_cb?"
- lchan_init_lapdm(lchan);
-
return 0;
}
@@ -1264,9 +1279,8 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
- gsm_lchan_name(lchan),
- get_value_string(femtobts_l1cfgt_names, cc->cfgParamId));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ",
+ get_value_string(femtobts_l1cfgt_names, cc->cfgParamId));
switch (cc->cfgParamId) {
case GsmL1_ConfigParamId_SetLogChParams:
@@ -1298,9 +1312,7 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
break;
}
if (llist_empty(&lchan->sapi_cmds)) {
- LOGP(DL1C, LOGL_ERROR,
- "%s Got ciphering conf with empty queue\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n");
goto err;
}
@@ -1325,6 +1337,7 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
struct msgb *msg = l1p_msgb_alloc();
GsmL1_MphConfigReq_t *conf_req;
GsmL1_LogChParam_t *lch_par;
+ int rc;
/* channel mode, encryption and/or multirate have changed */
@@ -1339,7 +1352,10 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
conf_req->hLayer3 = l1if_lchan_to_hLayer(lchan);
lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
- lchan2lch_par(lch_par, lchan);
+ if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
+ talloc_free(msg);
+ return rc;
+ }
/* Update the MS Power Level */
if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
@@ -1347,10 +1363,8 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm
/* 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ",
+ 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,
@@ -1417,11 +1431,9 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c
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));
+ LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n",
+ 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);
@@ -1458,6 +1470,16 @@ int l1if_set_ciphering(struct femtol1_hdl *fl1h,
return 0;
}
+int l1if_set_ul_acc(struct gsm_lchan *lchan, bool active)
+{
+ if (active)
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
+ else
+ check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
+
+ return 0;
+}
+
int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
{
if (lchan->state != LCHAN_S_ACTIVE)
@@ -1499,9 +1521,8 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
goto err;
}
- LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
- gsm_lchan_name(lchan),
- get_value_string(femtobts_l1sapi_names, ic->sapi));
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ",
+ get_value_string(femtobts_l1sapi_names, ic->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(femtobts_dir_names, ic->dir));
@@ -1523,19 +1544,15 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
if (llist_empty(&lchan->sapi_cmds)) {
- LOGP(DL1C, LOGL_ERROR,
- "%s Got de-activation confirmation with empty queue\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n");
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);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
+ cmd->sapi, cmd->dir, ic->sapi, ic->dir);
goto err;
}
@@ -1560,8 +1577,7 @@ static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd
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),
+ LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ",
get_value_string(femtobts_l1sapi_names, deact_req->sapi));
LOGPC(DL1C, LOGL_INFO, "%s)\n",
get_value_string(femtobts_dir_names, deact_req->dir));
@@ -1574,8 +1590,7 @@ 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n");
lchan_set_state(lchan, LCHAN_S_BROKEN);
sapi_clear_queue(&lchan->sapi_cmds);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
@@ -1632,17 +1647,9 @@ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
return enqueue_sapi_deact_cmd(lchan, sapi, dir);
}
-static int release_sapis_for_ho(struct gsm_lchan *lchan)
+static int release_sapi_ul_rach(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;
+ return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
}
static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
@@ -1664,12 +1671,11 @@ static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
}
/* always attempt to disable the RACH burst */
- res |= release_sapis_for_ho(lchan);
+ res |= release_sapi_ul_rach(lchan);
/* nothing was queued */
if (res == 0) {
- LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
- gsm_lchan_name(lchan));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n");
lchan_set_state(lchan, LCHAN_S_BROKEN);
mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
}
@@ -1716,67 +1722,55 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
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)
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
{
- if (kind == NM_OC_RADIO_CARRIER) {
- struct gsm_bts_trx *trx = obj;
- struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx;
+ struct femtol1_hdl *fl1h;
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_RADIO_ATTR:
+ trx = obj;
+ 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);
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);
+ break;
}
- /* FIXME: we actaully need to send a ACK or NACK for the OML message */
- return oml_fom_ack_nack(msg, 0);
+ return 0;
}
/* callback from OML */
int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
void *obj)
{
+ struct gsm_bts_trx* trx;
+ struct gsm_bts_trx_ts *ts;
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_BTS:
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);
- }
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *) obj;
+ rc = trx_init(trx);
+ break;
+ case NM_OC_CHANNEL:
+ ts = (struct gsm_bts_trx_ts*) obj;
+ rc = ts_opstart(ts);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -1847,24 +1841,26 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan)
*/
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));
+ LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n");
/* 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);
- }
+ release_sapi_ul_rach(lchan);
+
+ /* All the normal SAPIs have already been activated, only DL SACCH may still be missing.
+ *
+ * Note: theoretically, it would only be necessary to activate the DL SACCH when it is not active yet. With
+ * repeated HO RACH received, we shouldn't need to re-send the SAPI activation every time. However, tests with
+ * sysmoBTS show that when sending this SAPI activation only once, the lchan will release some seconds after a
+ * handover, with error messages indicating "Lost SACCH block, faking meas reports and ms pwr". When re-sending
+ * the SAPI activation for every RACH received, the problem goes away.
+ * Before introducing lchan->want_dl_sacch_active, this code here would activate all SAPIs for the main channel,
+ * which would also repeat for each RACH received. Now we only activate DL SACCH here.
+ */
+ if (lchan->want_dl_sacch_active)
+ enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink);
return 0;
}
@@ -1873,8 +1869,7 @@ 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));
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n");
return 0;
}
@@ -1910,8 +1905,7 @@ static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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));
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n");
cb_ts_disconnected(ts);
@@ -1924,7 +1918,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx);
GsmL1_MphDisconnectReq_t *cr;
- DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n");
cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
l1p_handle_for_ts(ts));
cr->u8Tn = ts->nr;
@@ -1940,8 +1934,7 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
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),
+ LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n",
gsm_pchan_name(ts->pchan),
ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
diff --git a/src/osmo-bts-sysmo/oml_router.c b/src/osmo-bts-sysmo/oml_router.c
deleted file mode 100644
index f3d08373..00000000
--- a/src/osmo-bts-sysmo/oml_router.c
+++ /dev/null
@@ -1,129 +0,0 @@
-/* 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
deleted file mode 100644
index 55f0681d..00000000
--- a/src/osmo-bts-sysmo/oml_router.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#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
index 21df88e5..6beeaf62 100644
--- a/src/osmo-bts-sysmo/sysmobts_ctrl.c
+++ b/src/osmo-bts-sysmo/sysmobts_ctrl.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -94,7 +94,7 @@ static int get_clock_info(struct ctrl_cmd *cmd, void *data)
SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
struct ctrl_cmd_def *cd;
- /* geneate a deferred control command */
+ /* generate a deferred control command */
cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
sysp->id = SuperFemto_PrimId_RfClockInfoReq;
@@ -136,7 +136,7 @@ static int set_clock_info(struct ctrl_cmd *cmd, void *data)
SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
struct ctrl_cmd_def *cd;
- /* geneate a deferred control command */
+ /* generate a deferred control command */
cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
/* Set GPS/PPS as reference */
@@ -197,7 +197,7 @@ static int get_clock_corr(struct ctrl_cmd *cmd, void *data)
* prefer to to ask the actual L1 about the currently used value to
* avoid any mistakes */
- /* geneate a deferred control command */
+ /* generate a deferred control command */
cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
sysp->id = SuperFemto_PrimId_RfClockInfoReq;
@@ -241,7 +241,7 @@ static int set_clock_corr(struct ctrl_cmd *cmd, void *data)
fl1h->clk_cal = atoi(cmd->value);
- /* geneate a deferred control command */
+ /* generate a deferred control command */
cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
sysp->id = SuperFemto_PrimId_RfClockSetupReq;
diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c
index 3199c8e2..2e853356 100644
--- a/src/osmo-bts-sysmo/sysmobts_vty.c
+++ b/src/osmo-bts-sysmo/sysmobts_vty.c
@@ -45,6 +45,7 @@
#include <osmo-bts/bts_model.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/rsl.h>
+#include <osmo-bts/bts.h>
#include "femtobts.h"
#include "l1_if.h"
@@ -59,8 +60,6 @@ extern int lchan_activate(struct gsm_lchan *lchan);
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,
@@ -153,9 +152,20 @@ DEFUN_DEPRECATED(cfg_trx_ul_power_target, cfg_trx_ul_power_target_cmd,
"Obsolete alias for bts uplink-power-target\n"
"Target uplink Rx level in dBm\n")
{
+ struct gsm_power_ctrl_meas_params *mp;
struct gsm_bts_trx *trx = vty->index;
+ int rxlev_dbm = atoi(argv[0]);
+
+ mp = &trx->bts->ms_dpc_params.rxlev_meas;
+ mp->lower_thresh = mp->upper_thresh = dbm2rxlev(rxlev_dbm);
- trx->bts->ul_power_target = atoi(argv[0]);
+ vty_out(vty, "%% Command '%s' has been deprecated.%s"
+ "%% MS/BS Power control parameters should be configured in osmo-bsc: "
+ "use 'rxlev-thresh lower %u upper %u'.%s",
+ self->string, VTY_NEWLINE,
+ mp->lower_thresh,
+ mp->upper_thresh,
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
@@ -178,7 +188,7 @@ DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
struct phy_instance *pinst = vty->index;
unsigned int flag;
- flag = get_string_value(femtobts_tracef_names, argv[1]);
+ flag = get_string_value(femtobts_tracef_names, argv[0]);
pinst->u.sysmobts.dsp_trace_f |= flag;
return CMD_SUCCESS;
@@ -190,7 +200,7 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
struct phy_instance *pinst = vty->index;
unsigned int flag;
- flag = get_string_value(femtobts_tracef_names, argv[1]);
+ flag = get_string_value(femtobts_tracef_names, argv[0]);
pinst->u.sysmobts.dsp_trace_f &= ~flag;
return CMD_SUCCESS;
@@ -216,11 +226,11 @@ DEFUN(show_phy_clksrc, show_trx_clksrc_cmd,
}
DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
- "show trx <0-0> dsp-trace-flags",
+ "show dsp-trace-flags trx <0-0>",
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 gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct femtol1_hdl *fl1h;
int i;
@@ -340,7 +350,7 @@ DEFUN(activate_lchan, activate_lchan_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -360,9 +370,9 @@ DEFUN(set_tx_power, set_tx_power_cmd,
{
int trx_nr = atoi(argv[0]);
int power = atoi(argv[1]);
- struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
- power_ramp_start(trx, to_mdB(power), 1);
+ power_ramp_start(trx, to_mdB(power), 1, NULL);
return CMD_SUCCESS;
}
@@ -373,7 +383,7 @@ DEFUN(reset_rf_clock_ctr, reset_rf_clock_ctr_cmd,
"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 gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
l1if_rf_clock_info_reset(fl1h);
@@ -386,7 +396,7 @@ DEFUN(correct_rf_clock_ctr, correct_rf_clock_ctr_cmd,
"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 gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
l1if_rf_clock_info_correct(fl1h);
@@ -403,7 +413,7 @@ DEFUN(loopback, loopback_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -422,7 +432,7 @@ DEFUN(no_loopback, no_loopback_cmd,
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 *trx = gsm_bts_trx_num(g_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
@@ -432,22 +442,22 @@ DEFUN(no_loopback, no_loopback_cmd,
}
-void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts)
{
}
-void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+void bts_model_config_write_trx(struct vty *vty, const 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(struct vty *vty, const struct phy_link *plink)
{
}
-void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst)
{
int i;
@@ -474,39 +484,37 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst
pinst->u.sysmobts.clk_src), VTY_NEWLINE);
}
-int bts_model_vty_init(struct gsm_bts *bts)
+int bts_model_vty_init(void *ctx)
{
- 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,
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, 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,
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, 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_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, 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_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, 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,
+ vty_cmd_string_from_valstr(ctx, 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,
+ vty_cmd_string_from_valstr(ctx, 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,
+ vty_cmd_string_from_valstr(ctx, 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,
+ vty_cmd_string_from_valstr(ctx, femtobts_tracef_docs,
NO_STR DSP_TRACE_F_STR, "\n",
"", 0);
diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c
index 54e73136..2cf784e6 100644
--- a/src/osmo-bts-sysmo/tch.c
+++ b/src/osmo-bts-sysmo/tch.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -77,7 +77,7 @@ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len
cur[0] |= 0xD0;
#endif /* USE_L1_RTP_MODE */
- lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan);
+ lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan);
return msg;
}
@@ -131,12 +131,8 @@ static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
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);
+
+ lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan);
return msg;
}
@@ -298,7 +294,7 @@ static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_le
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;
+ cmr = amr_mrc->mode[cmr_idx].mode;
lchan->tch.last_cmr = cmr;
}
@@ -403,7 +399,10 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
*payload_type = GsmL1_TchPlType_Efr;
rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
rtp_pl_len);
- /* FIXME: detect and save EFR SID */
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_efr_check_sid(rtp_pl, rtp_pl_len);
+ if (is_sid)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
break;
#endif
case GSM48_CMODE_SPEECH_AMR:
@@ -505,7 +504,7 @@ 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 };
+ uint8_t *payload, payload_type, payload_len;
struct msgb *rmsg = NULL;
struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
@@ -513,12 +512,15 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ data_ind->measParam.fLinkQuality * 10,
+ data_ind->measParam.fRssi,
+ data_ind->measParam.i16BurstTiming * 64,
+ 0);
}
payload_type = data_ind->msgUnitParam.u8Buffer[0];
@@ -546,6 +548,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received ONSET from L1 " "(%d bytes)\n",
+ payload_len);
/* according to 3GPP TS 26.093 ONSET frames precede the first
speech frame of a speech burst - set the marker for next RTP
frame */
@@ -554,33 +558,32 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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);
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "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));
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan), get_value_string(femtobts_tch_pl_names, payload_type));
break;
}
@@ -598,14 +601,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
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);
+ rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
break;
/* FIXME: what about GsmL1_TchPlType_Amr_SidBad? not well documented. */
}
@@ -613,17 +610,42 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
if (rmsg)
return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
data_ind->measParam.fBer * 10000,
- data_ind->measParam.fLinkQuality * 10);
+ data_ind->measParam.fLinkQuality * 10,
+ data_ind->measParam.fRssi,
+ data_ind->measParam.i16BurstTiming * 64,
+ 0);
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));
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan), get_value_string(femtobts_tch_pl_names, payload_type));
return -EINVAL;
}
+/*! \brief provide an RTP empty payload "tick" to upper layers upon FACCH */
+int l1if_tch_rx_facch(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;
+ 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;
+
+ LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "chan_nr %d Rx FACCH\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,
+ 0, /* suppress RSSI like in osmo-bts-trx */
+ data_ind->measParam.i16BurstTiming * 64,
+ 0);
+}
+
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn)
{
struct msgb *msg;
diff --git a/src/osmo-bts-sysmo/utils.c b/src/osmo-bts-sysmo/utils.c
index 0e3ef273..735ebe0e 100644
--- a/src/osmo-bts-sysmo/utils.c
+++ b/src/osmo-bts-sysmo/utils.c
@@ -14,7 +14,7 @@
* 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.
+ * 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/>.
diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am
index 19222405..63c00fec 100644
--- a/src/osmo-bts-trx/Makefile.am
+++ b/src/osmo-bts-trx/Makefile.am
@@ -1,10 +1,75 @@
-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
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ $(NULL)
-EXTRA_DIST = trx_if.h l1_if.h loops.h
+AM_CFLAGS = \
+ -Wall -fno-strict-aliasing \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBOSMOCODING_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(NULL)
+
+LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ $(LIBOSMOCODING_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ -ldl \
+ $(NULL)
+
+noinst_HEADERS = \
+ sched_utils.h \
+ trx_if.h \
+ l1_if.h \
+ amr_loop.h \
+ trx_provision_fsm.h \
+ $(NULL)
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)
+osmo_bts_trx_SOURCES = \
+ main.c \
+ trx_if.c \
+ l1_if.c \
+ scheduler_trx.c \
+ sched_lchan_fcch_sch.c \
+ sched_lchan_rach.c \
+ sched_lchan_xcch.c \
+ sched_lchan_pdtch.c \
+ sched_lchan_tchf.c \
+ sched_lchan_tchh.c \
+ trx_provision_fsm.c \
+ trx_vty.c \
+ amr_loop.c \
+ probes.d \
+ $(NULL)
+
+osmo_bts_trx_LDADD = \
+ $(top_builddir)/src/common/libl1sched.a \
+ $(top_builddir)/src/common/libbts.a \
+ $(LDADD) \
+ $(NULL)
+
+if ENABLE_SYSTEMTAP
+probes.h: probes.d
+ $(DTRACE) -C -h -s $< -o $@
+
+probes.lo: probes.d
+ $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@
+
+BUILT_SOURCES = probes.h probes.lo
+osmo_bts_trx_LDADD += probes.lo
+endif
diff --git a/src/osmo-bts-trx/amr_loop.c b/src/osmo-bts-trx/amr_loop.c
new file mode 100644
index 00000000..cf75a8fc
--- /dev/null
+++ b/src/osmo-bts-trx/amr_loop.c
@@ -0,0 +1,107 @@
+/* AMR link adaptation loop (see 3GPP TS 45.009, section 3) */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include "amr_loop.h"
+
+void trx_loop_amr_input(struct l1sched_chan_state *chan_state,
+ const struct l1sched_meas_set *meas_set)
+{
+ const struct gsm_lchan *lchan = chan_state->lchan;
+ const struct amr_multirate_conf *cfg = &lchan->tch.amr_mr;
+ const uint8_t mi = chan_state->ul_ft; /* mode index 0..3 */
+ int lqual_cb = meas_set->ci_cb; /* cB (centibel) */
+
+ /* count per-block C/I samples for further averaging */
+ if (lchan->type == GSM_LCHAN_TCH_H) {
+ chan_state->lqual_cb_num += 2;
+ chan_state->lqual_cb_sum += (lqual_cb + lqual_cb);
+ } else {
+ chan_state->lqual_cb_num++;
+ chan_state->lqual_cb_sum += lqual_cb;
+ }
+
+ /* wait for MS to use the requested codec */
+ if (mi != chan_state->dl_cmr)
+ return;
+
+ /* count frames */
+ if (chan_state->lqual_cb_num < 48)
+ return;
+
+ /* calculate average (reuse lqual_cb variable) */
+ lqual_cb = chan_state->lqual_cb_sum / chan_state->lqual_cb_num;
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "AMR link quality (C/I) is %d cB, "
+ "codec mode[%u]=%u\n", lqual_cb, mi, cfg->mode[mi].mode);
+
+ /* reset the link quality measurements */
+ chan_state->lqual_cb_num = 0;
+ chan_state->lqual_cb_sum = 0;
+
+ /* If the current codec mode can be degraded */
+ if (mi > 0) {
+ /* The threshold/hysteresis is in 0.5 dB steps, convert to cB:
+ * 1dB is 10cB, so 0.5dB is 5cB - this is why we multiply by 5. */
+ const int thresh_lower_cb = cfg->mode[mi - 1].threshold * 5;
+
+ /* Degrade if the link quality is below THR_MX_Dn(i - 1) */
+ if (lqual_cb < thresh_lower_cb) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Degrading AMR codec mode: "
+ "[%u]=%u -> [%u]=%u due to link quality %d cB < THR_MX_Dn=%d cB\n",
+ mi, cfg->mode[mi].mode, mi - 1, cfg->mode[mi - 1].mode,
+ lqual_cb, thresh_lower_cb);
+ chan_state->dl_cmr--;
+ return;
+ }
+ }
+
+ /* If the current codec mode can be upgraded */
+ if (mi < chan_state->codecs - 1) {
+ /* The threshold/hysteresis is in 0.5 dB steps, convert to cB:
+ * 1dB is 10cB, so 0.5dB is 5cB - this is why we multiply by 5. */
+ const int thresh_upper_cb = cfg->mode[mi].threshold * 5 \
+ + cfg->mode[mi].hysteresis * 5;
+
+ /* Upgrade if the link quality is above THR_MX_Up(i) */
+ if (lqual_cb > thresh_upper_cb) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Upgrading AMR codec mode: "
+ "[%u]=%u -> [%u]=%u due to link quality %d cB > THR_MX_Up=%d cB\n",
+ mi, cfg->mode[mi].mode, mi + 1, cfg->mode[mi + 1].mode,
+ lqual_cb, thresh_upper_cb);
+ chan_state->dl_cmr++;
+ return;
+ }
+ }
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Keeping the current AMR codec "
+ "mode[%u]=%u\n", mi, cfg->mode[mi].mode);
+}
diff --git a/src/osmo-bts-trx/amr_loop.h b/src/osmo-bts-trx/amr_loop.h
new file mode 100644
index 00000000..efff76cf
--- /dev/null
+++ b/src/osmo-bts-trx/amr_loop.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <osmo-bts/scheduler.h>
+
+/*
+ * calibration of loops
+ */
+
+/*
+ * loops api
+ */
+
+void trx_loop_amr_input(struct l1sched_chan_state *chan_state,
+ const struct l1sched_meas_set *meas_set);
+
+void trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop);
diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c
index db53d4c6..54f5bd24 100644
--- a/src/osmo-bts-trx/l1_if.c
+++ b/src/osmo-bts-trx/l1_if.c
@@ -29,8 +29,10 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/bits.h>
+#include <osmocom/core/fsm.h>
#include <osmocom/codec/ecu.h>
#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/gsm/rsl.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
@@ -41,10 +43,15 @@
#include <osmo-bts/amr.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/scheduler.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/handover.h>
#include "l1_if.h"
#include "trx_if.h"
+#include "trx_provision_fsm.h"
+#define RF_DISABLED_mdB to_mdB(-10)
static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = {
[GSM_PCHAN_NONE] = 8,
@@ -60,7 +67,7 @@ static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = {
[GSM_PCHAN_UNKNOWN] = 0,
};
-static enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type)
+enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type)
{
int i;
for (i = 0; i < _GSM_PCHAN_MAX; i++) {
@@ -75,221 +82,78 @@ 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;
+ l1h->provision_fi = osmo_fsm_inst_alloc(&trx_prov_fsm, l1h, l1h, LOGL_INFO, NULL);
+ OSMO_ASSERT(osmo_fsm_inst_update_id_f_sanitize(l1h->provision_fi, '-', phy_instance_name(pinst)) == 0);
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;
+ int rc;
+ /* set lchan inactive */
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ /* Disable it on the scheduler: */
+ rc = trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_DEDIC, false);
+
+ /* 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;
- /* 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;
+ trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_DEDIC, true);
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ return rc;
}
- /* set lchan inactive */
- lchan_set_state(lchan, LCHAN_S_NONE);
-
- return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan),
- LID_DEDIC, 0);
+ return rc;
}
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);
+ return trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_SACCH, false);
}
-static void l1if_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc)
+int l1if_trx_start_power_ramp(struct gsm_bts_trx *trx, ramp_compl_cb_t ramp_compl_cb)
{
- struct phy_instance *pinst = l1h->phy_inst;
- struct gsm_bts_trx *trx = pinst->trx;
- struct gsm_bts_trx_ts *ts;
- enum gsm_phys_chan_config pchan;
-
- if (tn >= TRX_NR_TS) {
- LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TN (%" PRIu8 ")\n",
- tn);
- return;
- }
-
- pchan = transceiver_chan_type_2_pchan(type);
- if (pchan == GSM_PCHAN_UNKNOWN) {
- LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TS_TYPE (%" PRIu8 ")\n",
- type);
- return;
- }
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
- ts = &trx->ts[tn];
- LOGPPHI(pinst, DL1C, LOGL_DEBUG, "%s l1if_setslot_cb(as_pchan=%s),"
- " calling cb_ts_connected(rc=%d)\n",
- gsm_ts_name(ts), gsm_pchan_name(pchan), rc);
- cb_ts_connected(ts, rc);
+ if (l1h->config.forced_max_power_red == -1)
+ return power_ramp_start(trx, get_p_nominal_mdBm(trx), 0, ramp_compl_cb);
+ else
+ return power_ramp_start(trx, get_p_max_out_mdBm(trx) - to_mdB(l1h->config.forced_max_power_red), 1, ramp_compl_cb);
}
-
-/*
- * transceiver provisioning
- */
-int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
+/* Sets the nominal power, in dB */
+void l1if_trx_set_nominal_power(struct gsm_bts_trx *trx, int nominal_power)
{
- uint8_t tn;
- struct phy_link *plink = l1h->phy_inst->phy_link;
-
- 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;
- }
-
- /* Ask transceiver to use the newest TRXD header version if not using it yet */
- if (!l1h->config.setformat_sent &&
- l1h->config.trxd_hdr_ver_use != plink->u.osmotrx.trxd_hdr_ver_max) {
- trx_if_cmd_setformat(l1h, plink->u.osmotrx.trxd_hdr_ver_max);
- l1h->config.trxd_hdr_ver_req = plink->u.osmotrx.trxd_hdr_ver_max;
- l1h->config.setformat_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;
- }
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ bool nom_pwr_changed = trx->nominal_power != nominal_power;
- 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], l1if_setslot_cb);
- l1h->config.slottype_sent[tn] = 1;
- }
- }
- return 0;
- }
+ trx->nominal_power = nominal_power;
+ trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power);
+ /* If we receive ultra-low nominal Tx power (<0dBm), make sure to update where we are */
+ trx->power_params.p_total_cur_mdBm = OSMO_MIN(trx->power_params.p_total_cur_mdBm,
+ trx->power_params.trx_p_max_out_mdBm);
- 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;
- }
+ /* If TRX is not yet powered, delay ramping until it's ON */
+ if (!nom_pwr_changed || !pinst->phy_link->u.osmotrx.powered ||
+ trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+ return;
- return 0;
+ /* We are already ON and we got new information about nominal power, so
+ * let's make sure we adapt the tx power to it
+ */
+ l1if_trx_start_power_ramp(trx, NULL);
}
-int l1if_provision_transceiver(struct gsm_bts *bts)
+static void l1if_setpower_att_cb(struct trx_l1h *l1h, int power_att_db, int rc)
{
- struct gsm_bts_trx *trx;
- uint8_t tn;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->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;
- l1h->config.trxd_hdr_ver_req = 0;
- l1h->config.trxd_hdr_ver_use = 0;
- l1h->config.setformat_sent = 0;
- 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;
+ LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_setpower_att_cb(power_att_db=%d, rc=%d)\n", power_att_db, rc);
+
+ power_trx_change_compl(trx, get_p_max_out_mdBm(trx) - to_mdB(power_att_db));
}
/*
@@ -301,57 +165,43 @@ 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;
+ int rc;
- /* 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);
-
+ rc = osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ENABLE, (void*)(intptr_t)true);
+ if (rc != 0)
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
+ (void*)(intptr_t)NM_NACK_CANT_PERFORM);
/* Send OPSTART ack */
- return oml_mo_opstart_ack(&trx->mo);
+ return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
}
-/* deactivate transceiver */
-int bts_model_trx_close(struct gsm_bts_trx *trx)
+/* Deact RF on transceiver */
+int bts_model_trx_deact_rf(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);
+ return trx_if_cmd_rfmute(l1h, true);
+}
- /* 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);
- }
+/* deactivate transceiver */
+void 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;
- /* power off transceiver, if not already */
- if (l1h->config.poweron) {
- l1h->config.poweron = 0;
- l1h->config.poweron_sent = 0;
- l1if_provision_transceiver_trx(l1h);
- }
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CLOSE, NULL);
/* Set to Operational State: Disabled */
- check_transceiver_availability_trx(l1h, 0);
-
- return 0;
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
}
-/* on RSL failure, deactivate transceiver */
void bts_model_abis_close(struct gsm_bts *bts)
{
- bts_shutdown(bts, "Abis close");
+ /* Go into shutdown state deactivating transceivers until Abis link
+ * becomes up again */
+ bts_shutdown_ext(bts, "Abis close", false, true);
}
int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
@@ -362,23 +212,22 @@ int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
}
/* set bts attributes */
-static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr)
+static uint8_t trx_set_bts(struct gsm_bts *bts)
{
- struct gsm_bts_trx *trx;
+ struct phy_instance *pinst = trx_phy_instance(bts->c0);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
uint8_t bsic = bts->bsic;
+ struct gsm_bts_trx *trx;
+
+ /* ARFCN for C0 is assigned during Set BTS Attr, see oml.c */
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ARFCN, (void *)(intptr_t)pinst->trx->arfcn);
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);
+ pinst = trx_phy_instance(trx);
+ l1h = pinst->u.osmotrx.hdl;
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_BSIC, (void*)(intptr_t)bsic);
+ }
return 0;
}
@@ -388,21 +237,19 @@ 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;
+ struct phy_link *plink = pinst->phy_link;
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);
- }
+ /* ARFCN for C0 is assigned during Set BTS Attr, see oml.c */
+ if (trx != trx->bts->c0)
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ARFCN, (void *)(intptr_t)arfcn);
- 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);
- }
+ /* Begin to ramp up the power if power reduction is set by OML and TRX
+ is already running. Otherwise skip, power ramping will be started
+ after TRX is running */
+ if (plink->u.osmotrx.powered && l1h->config.forced_max_power_red == -1 &&
+ trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+ power_ramp_start(pinst->trx, get_p_nominal_mdBm(pinst->trx), 0, NULL);
return 0;
}
@@ -414,20 +261,9 @@ static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts,
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;
@@ -435,28 +271,46 @@ static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts,
/* 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);
+ OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);
+ rc = trx_sched_set_pchan(ts, 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) {
+ /* activate lchans for [CBCH/]BCCH/CCCH */
+ switch (pchan) {
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* using RSL_CHAN_OSMO_CBCH4 is correct here, because the scheduler
+ * does not distinguish between SDCCH/4+CBCH abd SDCCH/8+CBCH. */
+ trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN],
+ RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true);
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN],
+ RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true);
+ /* fall-through */
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH:
+ trx_sched_set_bcch_ccch(&ts->lchan[CCCH_LCHAN], true);
ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML;
lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE);
+ break;
+ default:
+ break;
}
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);
+
+ struct trx_prov_ev_cfg_ts_data data = { .tn = tn, .slottype = slottype };
+ if (ts->tsc_set != 0) {
+ /* On TRXC we use 3GPP compliant numbering, so +1 */
+ data.tsc_set = ts->tsc_set + 1;
+ data.tsc_val = ts->tsc;
+ data.tsc_valid = true;
}
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_TS, &data);
+
return 0;
}
@@ -473,7 +327,7 @@ static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts)
pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH
: GSM_PCHAN_TCH_F;
break;
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want);
pchan = ts->dyn.pchan_is;
break;
@@ -491,37 +345,30 @@ static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts)
*/
/* enable ciphering */
-static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan,
- uint8_t chan_nr, int downlink)
+static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink)
{
- /* ciphering already enabled in both directions */
- if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF)
+ /* ignore the request when the channel is not active */
+ if (lchan->state != LCHAN_S_ACTIVE)
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);
+ trx_sched_set_cipher(lchan, chan_nr, false);
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);
+ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF)
+ trx_sched_set_cipher(lchan, chan_nr, false);
+ trx_sched_set_cipher(lchan, chan_nr, true);
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)
+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 phy_instance *pinst = l1h->phy_inst;
struct osmo_phsap_prim l1sap;
memset(&l1sap, 0, sizeof(l1sap));
@@ -531,7 +378,7 @@ static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr,
l1sap.u.info.u.act_cnf.chan_nr = chan_nr;
l1sap.u.info.u.act_cnf.cause = cause;
- return l1sap_up(pinst->trx, &l1sap);
+ return l1sap_up(trx, &l1sap);
}
int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn)
@@ -550,45 +397,9 @@ int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn)
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;
@@ -599,117 +410,115 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
if (!msg)
break;
/* put data into scheduler's queue */
- return trx_sched_ph_data_req(&l1h->l1s, l1sap);
+ return trx_sched_ph_data_req(trx, 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);
+ return trx_sched_tch_req(trx, 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);
+ break;
+ case PRIM_INFO_ACT_UL_ACC:
+ case PRIM_INFO_DEACT_UL_ACC:
+ chan_nr = l1sap->u.info.u.ulacc_req.chan_nr;
+ break;
+ default:
+ /* u.act_req used by PRIM_INFO_{ACTIVATE,DEACTIVATE,MODIFY} */
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ }
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (OSMO_UNLIKELY(lchan == NULL)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx MPH-INFO.req (type=0x%02x) for non-existent lchan (%s)\n",
+ l1sap->u.info.type, rsl_chan_nr_str(chan_nr));
+ rc = -ENODEV;
+ break;
+ }
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
if (l1sap->u.info.u.ciph_req.uplink)
- l1if_set_ciphering(l1h, lchan, chan_nr, 0);
+ l1if_set_ciphering(lchan, chan_nr, 0);
if (l1sap->u.info.u.ciph_req.downlink)
- l1if_set_ciphering(l1h, lchan, chan_nr, 1);
+ l1if_set_ciphering(lchan, chan_nr, 1);
+ break;
+ case PRIM_INFO_ACT_UL_ACC:
+ trx_sched_set_ul_access(lchan, chan_nr, true);
+ break;
+ case PRIM_INFO_DEACT_UL_ACC:
+ trx_sched_set_ul_access(lchan, chan_nr, false);
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;
- }
-
- /* attempt to allocate an Error Concealment Unit instance, if available */
- lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan));
-
- /* 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) {
- /* ECU for possibly new codec */
- if (lchan->ecu_state)
- osmo_ecu_destroy(lchan->ecu_state);
- lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan));
- /* 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);
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot activate"
+ " channel %s\n", rsl_chan_nr_str(chan_nr));
+ rc = -EPERM;
break;
}
- /* here, type == PRIM_INFO_DEACTIVATE */
+
+ /* activate dedicated channel */
+ trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, true);
+ /* activate associated channel */
+ trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, true);
+ /* set mode */
+ trx_sched_set_mode(lchan->ts, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.mode[0].mode,
+ lchan->tch.amr_mr.mode[1].mode,
+ lchan->tch.amr_mr.mode[2].mode,
+ lchan->tch.amr_mr.mode[3].mode,
+ amr_get_initial_mode(lchan),
+ (lchan->ho.active == HANDOVER_ENABLED));
+ /* 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;
+ case PRIM_INFO_MODIFY:
+ /* change mode */
+ trx_sched_set_mode(lchan->ts, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.mode[0].mode,
+ lchan->tch.amr_mr.mode[1].mode,
+ lchan->tch.amr_mr.mode[2].mode,
+ lchan->tch.amr_mr.mode[3].mode,
+ amr_get_initial_mode(lchan),
+ 0);
+ /* update ciphering params */
+ 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;
+ break;
+ case PRIM_INFO_DEACTIVATE:
if ((chan_nr & 0xE0) == 0x80) {
- LOGP(DL1C, LOGL_ERROR, "Cannot deactivate "
- "chan_nr 0x%02x\n", chan_nr);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot deactivate"
+ " channel %s\n", rsl_chan_nr_str(chan_nr));
+ rc = -EPERM;
break;
}
- /* clear ECU state (if any) */
- if (lchan->ecu_state) {
- osmo_ecu_destroy(lchan->ecu_state);
- lchan->ecu_state = NULL;
- }
/* 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);
+ mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0);
}
break;
default:
@@ -747,48 +556,51 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
}
/* callback from OML */
-int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
- struct tlv_parsed *new_attr, int kind, void *obj)
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
- int cause = 0;
+ int rc;
switch (foh->msg_type) {
case NM_MT_SET_BTS_ATTR:
- cause = trx_set_bts(obj, new_attr);
+ rc = trx_set_bts(obj);
break;
case NM_MT_SET_RADIO_ATTR:
- cause = trx_set_trx(obj);
+ rc = trx_set_trx(obj);
break;
case NM_MT_SET_CHAN_ATTR:
- cause = trx_set_ts(obj);
+ rc = trx_set_ts(obj);
+ break;
+ default:
+ rc = 0;
break;
}
- return oml_fom_ack_nack(msg, cause);
+ return rc;
}
/* callback from OML */
int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
void *obj)
{
+ struct gsm_bts_trx *trx;
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_BTS:
case NM_OC_BASEB_TRANSC:
+ case NM_OC_CHANNEL:
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);
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ /* activate transceiver */
+ trx = (struct gsm_bts_trx *) obj;
+ rc = trx_init(trx);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -796,17 +608,91 @@ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
return rc;
}
-int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
- void *obj, uint8_t adm_state)
+static void bts_model_chg_adm_state_ramp_compl_cb(struct gsm_bts_trx *trx)
{
- /* blindly accept all state changes */
- mo->nm_state.administrative = adm_state;
- return oml_mo_statechg_ack(mo);
+ LOGPTRX(trx, DL1C, LOGL_INFO, "power ramp due to ADM STATE change finished\n");
+ trx->mo.procedure_pending = 0;
+ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) {
+ bts_model_trx_deact_rf(trx);
+ pcu_tx_info_ind();
+ }
}
-int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
{
- return 0;
+ struct gsm_bts_trx *trx;
+ struct phy_instance *pinst;
+ struct trx_l1h *l1h;
+ int rc = 0;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *) obj;
+ pinst = trx_phy_instance(trx);
+ l1h = pinst->u.osmotrx.hdl;
+
+ /* Begin to ramp the power if TRX is already running. Otherwise
+ * skip, power ramping will be started after TRX is running.
+ * We still want to make sure to update RFMUTE status on the
+ * other side. */
+ if (!pinst->phy_link->u.osmotrx.powered) {
+ trx_if_cmd_rfmute(l1h, adm_state != NM_STATE_UNLOCKED);
+ break;
+ }
+
+ if (mo->procedure_pending) {
+ LOGPTRX(trx, DL1C, LOGL_INFO,
+ "ADM change received while previous one still WIP\n");
+
+ if (mo->nm_state.administrative == NM_STATE_LOCKED &&
+ adm_state == NM_STATE_UNLOCKED) {
+ /* Previous change was UNLOCKED->LOCKED, so we
+ * were ramping down and we didn't mute RF
+ * yet, so now simply skip old ramp down compl
+ * cb, skip RF unmute and go for ramp up
+ * directly. */
+ goto ramp_up;
+ } else if (mo->nm_state.administrative == NM_STATE_UNLOCKED &&
+ adm_state == NM_STATE_LOCKED) {
+ /* Previous change was LOCKED->UNLOCKED, so we
+ * simply need to skip ramping up and start
+ * ramping down instead, muting RF at the
+ * end as usual. Fall through usual procedure
+ * below. */
+ } else if (mo->nm_state.administrative == adm_state) {
+ OSMO_ASSERT(0);
+ }
+ }
+ switch (adm_state) {
+ case NM_STATE_LOCKED:
+ mo->procedure_pending = 1;
+ rc = power_ramp_start(trx, RF_DISABLED_mdB, 1, bts_model_chg_adm_state_ramp_compl_cb);
+ break;
+ case NM_STATE_UNLOCKED:
+ mo->procedure_pending = 1;
+ trx_if_cmd_rfmute(l1h, false);
+ramp_up:
+ rc = l1if_trx_start_power_ramp(trx, bts_model_chg_adm_state_ramp_compl_cb);
+ if (rc == 0) {
+ mo->nm_state.administrative = adm_state;
+ pcu_tx_info_ind();
+ return oml_mo_statechg_ack(mo);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (rc == 0) {
+ 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 bts_model_oml_estab(struct gsm_bts *bts)
@@ -816,9 +702,10 @@ int bts_model_oml_estab(struct gsm_bts *bts)
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;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ int power_att = (get_p_max_out_mdBm(trx) - p_trxout_mdBm) / 1000;
+ return trx_if_cmd_setpower_att(l1h, power_att, l1if_setpower_att_cb);
}
int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h
index 87df951b..84fd4b5b 100644
--- a/src/osmo-bts-trx/l1_if.h
+++ b/src/osmo-bts-trx/l1_if.h
@@ -1,58 +1,129 @@
#ifndef L1_IF_H_TRX
#define L1_IF_H_TRX
+#include <osmocom/core/rate_ctr.h>
+
#include <osmo-bts/scheduler.h>
#include <osmo-bts/phy_link.h>
#include "trx_if.h"
+/*
+ * 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.
+ */
+
+/* bts-trx specific rate counters */
+enum {
+ BTSTRX_CTR_SCHED_DL_MISS_FN,
+ BTSTRX_CTR_SCHED_DL_FH_NO_CARRIER,
+ BTSTRX_CTR_SCHED_DL_FH_CACHE_MISS,
+ BTSTRX_CTR_SCHED_UL_FH_NO_CARRIER,
+};
+
+/*! 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;
+};
+
+/* gsm_bts->model_priv, specific to osmo-bts-trx */
+struct bts_trx_priv {
+ struct osmo_trx_clock_state clk_s;
+ struct rate_ctr_group *ctrs; /* bts-trx specific rate counters */
+};
+
struct trx_config {
- uint8_t trxd_hdr_ver_req; /* requested TRXD header version */
- uint8_t trxd_hdr_ver_use; /* actual TRXD header version in use */
- int setformat_sent;
+ uint8_t trxd_pdu_ver_req; /* requested TRXD PDU version */
+ uint8_t trxd_pdu_ver_use; /* actual TRXD PDU version in use */
+ bool setformat_sent;
+ bool setformat_acked;
+
+ bool enabled;
- uint8_t poweron; /* poweron(1) or poweroff(0) */
- int poweron_sent;
- int arfcn_valid;
+ bool arfcn_valid;
uint16_t arfcn;
- int arfcn_sent;
+ bool rxtune_sent;
+ bool rxtune_acked;
+ bool txtune_sent;
+ bool txtune_acked;
- int tsc_valid;
+ bool tsc_valid;
uint8_t tsc;
- int tsc_sent;
+ bool tsc_sent;
+ bool tsc_acked;
- int bsic_valid;
+ bool bsic_valid;
uint8_t bsic;
- int bsic_sent;
+ bool bsic_sent;
+ bool bsic_acked;
- int rxgain_valid;
+ bool rxgain_valid;
uint8_t rxgain;
- int rxgain_sent;
+ bool rxgain_sent;
- int power_valid;
- uint8_t power;
- int power_oml;
- int power_sent;
+ int forced_max_power_red; /* -1 if not forced by VTY config (default) */
- int maxdly_valid;
+ bool nominal_power_set_by_vty; /* whether nominal trx power was enforced/retreived from VTY config "nominal-tx-power" */
+ bool nomtxpower_sent;
+ bool nomtxpower_acked;
+
+ bool maxdly_valid;
int maxdly;
- int maxdly_sent;
+ bool maxdly_sent;
- int maxdlynb_valid;
+ bool maxdlynb_valid;
int maxdlynb;
- int maxdlynb_sent;
+ bool maxdlynb_sent;
uint8_t slotmask;
- int slottype_valid[TRX_NR_TS];
- uint8_t slottype[TRX_NR_TS];
- int slottype_sent[TRX_NR_TS];
+ bool setslot_valid[TRX_NR_TS];
+ struct {
+ uint8_t slottype;
+ uint8_t tsc_set;
+ uint8_t tsc_val;
+ bool tsc_valid;
+ } setslot[TRX_NR_TS];
+ bool setslot_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;
+ /* Whether the code path is in the middle of handling a received message. */
+ bool in_trx_ctrl_read_cb;
+ /* Whether the l1h->trx_ctrl_list was flushed by the callback handling a received message */
+ bool flushed_while_in_trx_ctrl_read_cb;
//struct gsm_bts_trx *trx;
struct phy_instance *phy_inst;
@@ -63,23 +134,14 @@ struct trx_l1h {
/* transceiver config */
struct trx_config config;
-
- struct l1sched_trx l1s;
+ struct osmo_fsm_inst *provision_fi;
};
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;
-}
+void l1if_trx_set_nominal_power(struct gsm_bts_trx *trx, int nominal_power);
+int l1if_trx_start_power_ramp(struct gsm_bts_trx *trx, ramp_compl_cb_t ramp_compl_cb);
+enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type);
#endif /* L1_IF_H_TRX */
diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c
deleted file mode 100644
index 3fa2b3b4..00000000
--- a/src/osmo-bts-trx/loops.c
+++ /dev/null
@@ -1,322 +0,0 @@
-/* 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
- */
-
-/*! compute the new MS POWER LEVEL communicated to the MS and store it in lchan.
- * \param lchan logical channel for which to compute (and in which to store) new power value.
- * \param[in] diff input delta value (in dB) */
-static void ms_power_diff(struct gsm_lchan *lchan, 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;
-
- /* compute new target MS output power level based on current value subtracted by 'diff/2' */
- new_power = lchan->ms_power_ctrl.current - (diff >> 1);
-
- if (diff == 0)
- return;
-
- /* ms transmit power level cannot become negative */
- if (new_power < 0)
- new_power = 0;
-
- /* saturate at the maximum possible power level for the given band */
- // 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;
- }
-
- /* don't ever change more than MS_{LOWER,RAISE}_MAX during one loop iteration, i.e.
- * reduce the speed at which the MS transmit power can change */
- /* 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) {
- LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping MS new_power at control level %d (%d dBm)\n",
- new_power, ms_pwr_dbm(band, new_power));
- } else {
- LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s MS new_power from control level %d (%d dBm) to %d (%d dBm)\n",
- (diff > 0) ? "Raising" : "Lowering",
- lchan->ms_power_ctrl.current, ms_pwr_dbm(band, lchan->ms_power_ctrl.current),
- new_power, ms_pwr_dbm(band, new_power));
-
- /* store the resulting new MS power level in the lchan */
- lchan->ms_power_ctrl.current = new_power;
- }
-}
-
-/*! Input a new RSSI value into the MS power control loop for the given logical channel.
- * \param lchan logical channel
- * \param chan_state L1 channel state of the logical channel.
- * \param rssi Received Signal Strength Indication (in dBm) */
-static void ms_power_val(struct gsm_lchan *lchan, struct l1sched_chan_state *chan_state, int8_t rssi)
-{
- /* ignore inserted dummy frames, treat as lost frames */
- if (rssi < -127)
- return;
-
- LOGPLCHAN(lchan, 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;
- chan_state->meas.rssi[chan_state->meas.rssi_valid_count++] = rssi;
-}
-
-/*! Process a single clock tick of the MS power control loop.
- * \param lchan Logical channel to which the clock tick applies */
-static void ms_power_clock(struct gsm_lchan *lchan, 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;
-
- LOGPLCHAN(lchan, 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;
-
- /* if no burst was received from MS at clock */
- if (chan_state->meas.rssi_count == 0) {
- LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, "LOST SACCH frame, so we raise MS power\n");
- ms_power_diff(lchan, MS_RAISE_MAX);
- return;
- }
-
- /* 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;
- 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 */
- LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current "
- "MS power: %d (%d dBm)\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));
- ms_power_diff(lchan, pinst->phy_link->u.osmotrx.trx_target_rssi - rssi);
-}
-
-
-/* 90% of one bit duration in 1/256 symbols: 256*0.9 */
-#define TOA256_9OPERCENT 230
-
-/*
- * Timing Advance loop
- */
-
-void ta_val(struct gsm_lchan *lchan, struct l1sched_chan_state *chan_state, int16_t toa256)
-{
- /* check if the current L1 header acks to the current ordered TA */
- if (lchan->meas.l1_info[1] != lchan->rqd_ta)
- return;
-
- /* sum measurement */
- chan_state->meas.toa256_sum += toa256;
- if (++(chan_state->meas.toa_num) < 16)
- return;
-
- /* 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) {
- LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "TOA is too early (%d), now lowering TA from %d to %d\n",
- toa256, lchan->rqd_ta, lchan->rqd_ta - 1);
- lchan->rqd_ta--;
- } else if (toa256 > TOA256_9OPERCENT && lchan->rqd_ta < 63) {
- LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "TOA is too late (%d), now raising TA from %d to %d\n",
- toa256, lchan->rqd_ta, lchan->rqd_ta + 1);
- lchan->rqd_ta++;
- } else
- LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "TOA is correct (%d), keeping current TA of %d\n",
- toa256, lchan->rqd_ta);
-
- chan_state->meas.toa_num = 0;
- chan_state->meas.toa256_sum = 0;
-}
-
-/*! Process a SACCH event as input to the MS power control and TA loop. Function
- * is called once every uplink SACCH block is received.
- * \param l1t L1 TRX instance on which we operate
- * \param chan_nr RSL channel number on which we operate
- * \param chan_state L1 scheduler channel state of the channel on which we operate
- * \param[in] rssi Receive Signal Strength Indication
- * \param[in] toa256 Time of Arrival in 1/256 symbol periods */
-void 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 MS power control loop is enabled, handle it */
- if (pinst->phy_link->u.osmotrx.trx_ms_power_loop)
- ms_power_val(lchan, chan_state, rssi);
-
- /* if TA loop is enabled, handle it */
- if (pinst->phy_link->u.osmotrx.trx_ta_loop)
- ta_val(lchan, chan_state, toa256);
-}
-
-/*! Called once every downlink SACCH block needs to be sent. */
-void 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 (lchan->ms_power_ctrl.fixed)
- return;
-
- if (pinst->phy_link->u.osmotrx.trx_ms_power_loop)
- ms_power_clock(lchan, chan_state);
-
- /* count the number of SACCH clocks */
- chan_state->meas.clock++;
-}
-
-void 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;
-
- /* wait for MS to use the requested codec */
- if (chan_state->ul_ft != chan_state->dl_cmr)
- return;
-
- /* 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;
-
- /* 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;
-
- LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f "
- "codec id %d\n", ber, chan_state->ul_ft);
-
- /* 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) {
- LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f "
- "from codec id %d to %d\n", ber, chan_state->dl_cmr,
- chan_state->dl_cmr - 1);
- chan_state->dl_cmr--;
- }
- } else 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) {
- LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f "
- "from codec id %d to %d\n", ber, chan_state->dl_cmr,
- chan_state->dl_cmr + 1);
- chan_state->dl_cmr++;
- }
- }
-}
-
-void trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop)
-{
- if (chan_state->amr_loop && !loop) {
- chan_state->amr_loop = 0;
- return;
- }
-
- 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;
- }
-}
diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h
deleted file mode 100644
index 50a658d0..00000000
--- a/src/osmo-bts-trx/loops.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#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
- */
-
-void trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr,
- struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa);
-
-void trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr,
- struct l1sched_chan_state *chan_state);
-
-void trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr,
- struct l1sched_chan_state *chan_state, float ber);
-
-void 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
index b1fa2079..ddc44285 100644
--- a/src/osmo-bts-trx/main.c
+++ b/src/osmo-bts-trx/main.c
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -43,6 +43,8 @@
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/bits.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/phy_link.h>
@@ -59,8 +61,34 @@
#include "l1_if.h"
#include "trx_if.h"
+static const struct rate_ctr_desc btstrx_ctr_desc[] = {
+ [BTSTRX_CTR_SCHED_DL_MISS_FN] = {
+ "trx_clk:sched_dl_miss_fn",
+ "Downlink frames scheduled later than expected due to missed timerfd event (due to high system load)"
+ },
+ [BTSTRX_CTR_SCHED_DL_FH_NO_CARRIER] = {
+ "trx_sched:dl_fh_no_carrier",
+ "Frequency hopping: no carrier found for a Downlink burst (check hopping parameters)"
+ },
+ [BTSTRX_CTR_SCHED_DL_FH_CACHE_MISS] = {
+ "trx_sched:dl_fh_cache_miss",
+ "Frequency hopping: no Downlink carrier found in cache (lookup performed)"
+ },
+ [BTSTRX_CTR_SCHED_UL_FH_NO_CARRIER] = {
+ "trx_sched:ul_fh_no_carrier",
+ "Frequency hopping: no carrier found for an Uplink burst (check hopping parameters)"
+ },
+};
+static const struct rate_ctr_group_desc btstrx_ctrg_desc = {
+ "bts-trx",
+ "osmo-bts-trx specific counters",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(btstrx_ctr_desc),
+ btstrx_ctr_desc
+};
+
/* dummy, since no direct dsp support */
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
return 0;
}
@@ -97,29 +125,85 @@ int bts_model_handle_options(int argc, char **argv)
int bts_model_init(struct gsm_bts *bts)
{
+ struct bts_trx_priv *bts_trx = talloc_zero(bts, struct bts_trx_priv);
+ bts_trx->clk_s.fn_timer_ofd.fd = -1;
+ bts_trx->ctrs = rate_ctr_group_alloc(bts_trx, &btstrx_ctrg_desc, 0);
+
+ bts->model_priv = bts_trx;
bts->variant = BTS_OSMO_TRX;
- bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3) | CIPHER_A5(4);
+ bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS
+ | NM_IPAC_MASK_GPRS_CODING_MCS;
- /* FIXME: this needs to be overridden with the real hardrware
- * value */
+ /* The nominal value for each TRX is later overwritten through VTY cmd
+ * 'nominal-tx-power' if present, otherwise through TRXC cmd NOMTXPOWER.
+ */
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);
+ /* order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_ACCH_REP);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_ACCH_TEMP_OVP);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_BCCH_POWER_RED);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_HOPPING);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_MULTI_TSC);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VAMOS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VGCS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_VBS);
+
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB);
+ bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_INTERF_MEAS);
+
+ /* The default HR codec output format in the absence of saved
+ * vty config needs to match what was implemented previously,
+ * for the sake of existing deployments, i.e., to avoid
+ * a surprise functional change upon software update. */
+ bts->emit_hr_rfc5993 = true;
+
+ /* For the same reason as the above, rtp internal-uplink-ecu
+ * needs to be enabled by default on osmo-bts-trx model only. */
+ bts->use_ul_ecu = true;
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ /* Frequency bands indicated to the BSC */
+ trx->support.freq_bands = NM_IPAC_F_FREQ_BAND_PGSM
+ | NM_IPAC_F_FREQ_BAND_EGSM
+ | NM_IPAC_F_FREQ_BAND_RGSM
+ | NM_IPAC_F_FREQ_BAND_DCS
+ | NM_IPAC_F_FREQ_BAND_PCS
+ | NM_IPAC_F_FREQ_BAND_850
+ | NM_IPAC_F_FREQ_BAND_480
+ | NM_IPAC_F_FREQ_BAND_450;
+
+ /* Channel types and modes indicated to the BSC */
+ trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON
+ | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH
+ | NM_IPAC_F_CHANT_SDCCH8_CBCH
+ | NM_IPAC_F_CHANT_PDCHF
+ | NM_IPAC_F_CHANT_TCHF_PDCHF;
+ trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH
+ | NM_IPAC_MASK_CHANM_CSD_NT
+ | NM_IPAC_MASK_CHANM_CSD_T;
+ /* TODO: missing rate adaptation for TCH/F14.4 (see OS#6167) */
+ trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_T_14k4;
+ trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_NT_14k4;
+
+ /* The nominal value for each TRX is later overwritten through VTY cmd
+ * 'nominal-tx-power' if present, otherwise through TRXC cmd NOMTXPOWER.
+ */
+ l1if_trx_set_nominal_power(trx, trx->bts->c0->nominal_power);
return 0;
}
@@ -129,13 +213,10 @@ void bts_model_phy_link_set_defaults(struct phy_link *plink)
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;
+ plink->u.osmotrx.clock_advance = 2;
+ plink->u.osmotrx.rts_advance = 3;
/* attempt use newest TRXD version by default: */
- plink->u.osmotrx.trxd_hdr_ver_max = TRX_DATA_FORMAT_VER;
+ plink->u.osmotrx.trxd_pdu_ver_max = TRX_DATA_PDU_VER;
}
void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
@@ -144,7 +225,7 @@ void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
l1h = trx_l1h_alloc(tall_bts_ctx, pinst);
pinst->u.osmotrx.hdl = l1h;
- l1h->config.power_oml = 1;
+ l1h->config.forced_max_power_red = -1;
}
int main(int argc, char **argv)
diff --git a/src/osmo-bts-trx/probes.d b/src/osmo-bts-trx/probes.d
new file mode 100644
index 00000000..2f905bd1
--- /dev/null
+++ b/src/osmo-bts-trx/probes.d
@@ -0,0 +1,7 @@
+provider osmo_bts_trx {
+ probe ul_data_start(int, int, int); /* trx_nr, ts_nr, fn */
+ probe ul_data_done(int, int, int); /* trx_nr, ts_nr, fn */
+
+ probe dl_rts_start(int, int, int); /* trx_nr, ts_nr, fn */
+ probe dl_rts_done(int, int, int); /* trx_nr, ts_nr, fn */
+};
diff --git a/src/osmo-bts-trx/sched_lchan_fcch_sch.c b/src/osmo-bts-trx/sched_lchan_fcch_sch.c
new file mode 100644
index 00000000..5722b288
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_fcch_sch.c
@@ -0,0 +1,89 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * Contributions 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 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/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */
+int tx_fcch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting FCCH\n");
+
+ /* A frequency correction burst is basically a sequence of zeros */
+ memset(br->burst, 0x00, GSM_BURST_LEN);
+ br->burst_len = GSM_BURST_LEN;
+
+ return 0;
+}
+
+/* obtain a to-be-transmitted SCH (synchronization channel) burst */
+int tx_sch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ ubit_t burst[78];
+ uint8_t sb_info[4];
+ struct gsm_time t;
+ uint8_t t3p, bsic;
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting SCH\n");
+
+ /* BURST BYPASS */
+
+ /* create SB info from GSM time and BSIC */
+ gsm_fn2gsmtime(&t, br->fn);
+ t3p = t.t3 / 10;
+ bsic = l1ts->ts->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 */
+ memcpy(br->burst + 3, burst, 39);
+ memcpy(br->burst + 42, _sched_train_seq_gmsk_sb, 64);
+ memcpy(br->burst + 106, burst + 39, 39);
+
+ br->burst_len = GSM_BURST_LEN;
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_pdtch.c b/src/osmo-bts-trx/sched_lchan_pdtch.c
new file mode 100644
index 00000000..466a4144
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_pdtch.c
@@ -0,0 +1,221 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * Contributions 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 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/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* Maximum size of a EGPRS message in bytes */
+#define EGPRS_0503_MAX_BYTES 155
+
+/*! \brief a single PDTCH burst was received by the PHY, process it */
+int rx_pdtch_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ sbit_t *burst, *bursts_p = chan_state->ul_bursts;
+ uint32_t first_fn;
+ uint32_t *mask = &chan_state->ul_mask;
+ struct l1sched_meas_set meas_avg;
+ uint8_t l2[EGPRS_0503_MAX_BYTES];
+ int n_errors = 0;
+ int n_bursts_bits = 0;
+ int n_bits_total = 0;
+ uint16_t ber10k;
+ int rc;
+ enum osmo_ph_pres_info_type presence_info;
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received PDTCH bid=%u\n", bi->bid);
+
+ /* An MS may be polled to send an ACK in form of four Access Bursts */
+ if (bi->flags & TRX_BI_F_ACCESS_BURST)
+ return rx_rach_fn(l1ts, bi);
+
+ /* clear burst */
+ if (bi->bid == 0) {
+ memset(bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS);
+ *mask = 0x0;
+ }
+
+ /* update mask */
+ *mask |= (1 << bi->bid);
+
+ /* store measurements */
+ trx_sched_meas_push(chan_state, bi);
+
+ /* copy burst to buffer of 4 bursts */
+ switch (bi->burst_len) {
+ case EGPRS_BURST_LEN:
+ burst = bursts_p + bi->bid * 348;
+ memcpy(burst, bi->burst + 9, 174);
+ memcpy(burst + 174, bi->burst + 261, 174);
+ n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
+ break;
+ case GSM_BURST_LEN:
+ burst = bursts_p + bi->bid * 116;
+ memcpy(burst, bi->burst + 3, 58);
+ memcpy(burst + 58, bi->burst + 87, 58);
+ n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
+ break;
+ case 0:
+ /* NOPE.ind, assume GPRS? */
+ n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
+ }
+
+ /* wait until complete set of bursts */
+ if (bi->bid != 3)
+ return 0;
+
+ /* average measurements of the last 4 bursts */
+ trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S4N4);
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received incomplete frame (%u/%u)\n",
+ bi->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 ((bi->burst_len == GSM_BURST_LEN) && (rc < 0)) {
+ rc = gsm0503_pdtch_decode(l2, bursts_p, NULL,
+ &n_errors, &n_bits_total);
+ }
+
+ if (rc > 0) {
+ presence_info = PRES_INFO_BOTH;
+ } else {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS);
+ rc = 0;
+ presence_info = PRES_INFO_INVALID;
+ }
+
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+
+ first_fn = GSM_TDMA_FN_SUB(bi->fn, 3);
+ return _sched_compose_ph_data_ind(l1ts, first_fn, bi->chan,
+ &l2[0], rc,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ presence_info);
+}
+
+/* obtain a to-be-transmitted PDTCH (packet data) burst */
+int tx_pdtch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ struct msgb *msg = NULL; /* make GCC happy */
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ ubit_t *burst, *bursts_p = chan_state->dl_bursts;
+ enum trx_mod_type *mod = &chan_state->dl_mod_type;
+ uint8_t *mask = &chan_state->dl_mask;
+ int rc = 0;
+
+ /* send burst, if we already got a frame */
+ if (br->bid > 0) {
+ if ((*mask & 0x01) != 0x01)
+ return -ENOMSG;
+ goto send_burst;
+ }
+
+ *mask = *mask << 4;
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1ts, br);
+ if (!msg) {
+ /* It's totally fine to get no block here, since PCU may submit
+ * empty blocks when there's no MS listening. The scheduler will
+ * take care of filling C0 with dummy bursts to keep expected
+ * power transmit levels
+ */
+ return -ENODEV;
+ }
+
+ /* BURST BYPASS */
+
+ /* encode bursts */
+ rc = gsm0503_pdtch_egprs_encode(bursts_p, msg->l2h, msgb_l2len(msg));
+ if (rc < 0)
+ rc = gsm0503_pdtch_encode(bursts_p, msg->l2h, msgb_l2len(msg));
+
+ /* check validity of message */
+ if (rc < 0) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim invalid length, please FIX! "
+ "(len=%u)\n", msgb_l2len(msg));
+ /* free message */
+ msgb_free(msg);
+ return -EINVAL;
+ } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) {
+ *mod = TRX_MOD_T_8PSK;
+ } else {
+ *mod = TRX_MOD_T_GMSK;
+ }
+
+ /* free message */
+ msgb_free(msg);
+
+send_burst:
+ /* compose burst */
+ if (*mod == TRX_MOD_T_8PSK) {
+ burst = bursts_p + br->bid * 348;
+ memset(br->burst, 1, 9);
+ memcpy(br->burst + 9, burst, 174);
+ memcpy(br->burst + 183, TRX_8PSK_NB_TSC(br), 78);
+ memcpy(br->burst + 261, burst + 174, 174);
+ memset(br->burst + 435, 1, 9);
+
+ br->burst_len = EGPRS_BURST_LEN;
+ } else {
+ burst = bursts_p + br->bid * 116;
+ memcpy(br->burst + 3, burst, 58);
+ memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26);
+ memcpy(br->burst + 87, burst + 58, 58);
+
+ br->burst_len = GSM_BURST_LEN;
+ }
+
+ *mask |= (1 << br->bid);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid);
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_rach.c b/src/osmo-bts-trx/sched_lchan_rach.c
new file mode 100644
index 00000000..c92dfe24
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_rach.c
@@ -0,0 +1,214 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
+ * Contributions 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 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 <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* 3GPP TS 05.02, section 5.2.7 */
+#define RACH_EXT_TAIL_LEN 8
+#define RACH_SYNCH_SEQ_LEN 41
+
+enum rach_synch_seq_t {
+ RACH_SYNCH_SEQ_UNKNOWN = -1,
+ RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
+ RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
+ RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
+ RACH_SYNCH_SEQ_NUM
+};
+
+static struct value_string rach_synch_seq_names[] = {
+ { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
+ { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
+ { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
+ { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
+ { 0, NULL },
+};
+
+static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score)
+{
+ sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN;
+ enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0;
+ int score[RACH_SYNCH_SEQ_NUM] = { 0 };
+ int max_score = INT_MIN;
+ int i, j;
+
+ /* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
+ static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
+ [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
+ [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
+ [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
+ };
+
+ /* Get a multiplier for j-th bit of i-th synch. sequence */
+#define RACH_SYNCH_SEQ_MULT \
+ (synch_seq_ref[i][j] == '1' ? -1 : 1)
+
+ /* For each synch. sequence, count the bit match score. Since we deal with
+ * soft-bits (-127...127), we sum the absolute values of matching ones,
+ * and subtract the absolute values of different ones, so the resulting
+ * score is more accurate than it could be with hard-bits. */
+ for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) {
+ for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++)
+ score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j];
+
+ /* Keep the maximum value updated */
+ if (score[i] > max_score) {
+ max_score = score[i];
+ seq = i;
+ }
+ }
+
+ /* Calculate an approximate level of our confidence */
+ if (best_score != NULL)
+ *best_score = max_score;
+
+ /* At least 1/3 of a synch. sequence shall match */
+ if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3))
+ return RACH_SYNCH_SEQ_UNKNOWN;
+
+ return seq;
+}
+
+int rx_rach_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
+{
+ struct gsm_bts_trx *trx = l1ts->ts->trx;
+ struct osmo_phsap_prim l1sap;
+ int n_errors = 0;
+ int n_bits_total = 0;
+ uint16_t ra11;
+ uint8_t ra;
+ int rc;
+
+ /* Ignore NOPE indications, they're of no use here */
+ if (bi->flags & TRX_BI_F_NOPE_IND)
+ return 0;
+
+ /* TSC (Training Sequence Code) is an optional parameter of the UL burst
+ * indication. We need this information in order to decide whether an
+ * Access Burst is 11-bit encoded or not (see OS#1854). If this information
+ * is absent, we try to correlate the received synch. sequence with the
+ * known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default
+ * TS0 if it fails. */
+ enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0;
+ int best_score = 127 * RACH_SYNCH_SEQ_LEN;
+
+ /* If logical channel is not either of RACH, PDTCH or PTCCH, this is a
+ * handover Access Burst, which is always encoded as 8-bit and shall
+ * contain the generic training sequence (TS0). */
+ if (bi->chan == TRXC_RACH || bi->chan == TRXC_PDTCH || bi->chan == TRXC_PTCCH) {
+ if (bi->flags & TRX_BI_F_TS_INFO)
+ synch_seq = (enum rach_synch_seq_t) bi->tsc;
+ else
+ synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score);
+ }
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "Received%s RACH (%s): rssi=%d toa256=%d",
+ TRX_CHAN_IS_DEDIC(bi->chan) ? " handover" : "",
+ get_value_string(rach_synch_seq_names, synch_seq),
+ bi->rssi, bi->toa256);
+ if (bi->flags & TRX_BI_F_CI_CB)
+ LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb);
+ else
+ LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%",
+ best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN));
+ LOGPC(DL1P, LOGL_DEBUG, "\n");
+
+ /* Compose a new L1SAP primitive */
+ memset(&l1sap, 0x00, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL);
+ l1sap.u.rach_ind.chan_nr = trx_chan_desc[bi->chan].chan_nr | bi->tn;
+ l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0;
+ l1sap.u.rach_ind.acc_delay_256bits = bi->toa256;
+ l1sap.u.rach_ind.rssi = bi->rssi;
+ l1sap.u.rach_ind.fn = bi->fn;
+
+ /* Link quality is defined by C/I (Carrier-to-Interference ratio),
+ * which has optional presence. If it's absent, report the
+ * minimum acceptable value to pass L1SAP checks. */
+ if (bi->flags & TRX_BI_F_CI_CB)
+ l1sap.u.rach_ind.lqual_cb = bi->ci_cb;
+ else
+ l1sap.u.rach_ind.lqual_cb = trx->bts->min_qual_rach;
+
+ /* Decode RACH depending on its synch. sequence */
+ switch (synch_seq) {
+ case RACH_SYNCH_SEQ_TS1:
+ case RACH_SYNCH_SEQ_TS2:
+ rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
+ trx->bts->bsic, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received bad Access Burst\n");
+ return 0;
+ }
+
+ if (synch_seq == RACH_SYNCH_SEQ_TS1)
+ l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1;
+ else
+ l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2;
+
+ l1sap.u.rach_ind.is_11bit = 1;
+ l1sap.u.rach_ind.ra = ra11;
+ break;
+
+ case RACH_SYNCH_SEQ_TS0:
+ default:
+ /* Fall-back to the default TS0 if needed */
+ if (synch_seq != RACH_SYNCH_SEQ_TS0) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Falling-back to the default TS0\n");
+ synch_seq = RACH_SYNCH_SEQ_TS0;
+ }
+
+ rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
+ trx->bts->bsic, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received bad Access Burst\n");
+ return 0;
+ }
+
+ l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
+ l1sap.u.rach_ind.is_11bit = 0;
+ l1sap.u.rach_ind.ra = ra;
+ break;
+ }
+
+ l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
+
+ /* forward primitive */
+ l1sap_up(trx, &l1sap);
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_tchf.c b/src/osmo-bts-trx/sched_lchan_tchf.c
new file mode 100644
index 00000000..5a3e80ac
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_tchf.c
@@ -0,0 +1,677 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/codec/codec.h>
+
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
+
+#include <osmocom/netif/amr.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <sched_utils.h>
+#include <amr_loop.h>
+
+/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Uplink TCH/F.
+ *
+ * +---+---+---+---+---+---+---+---+
+ * | a | b | c | d | e | f | g | h | Burst 'a' received first
+ * +---+---+---+---+---+---+---+---+
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h')
+ *
+ * TDMA frame number of burst 'h' is always used as the table index. */
+static const uint8_t sched_tchf_ul_amr_cmi_map[26] = {
+ [7] = 1, /* TCH/F: a=0 / h=7 */
+ [16] = 1, /* TCH/F: a=8 / h=16 */
+ [24] = 1, /* TCH/F: a=17 / h=24 */
+};
+
+/* TDMA frame number of burst 'a' should be used as the table index. */
+static const uint8_t sched_tchf_dl_amr_cmi_map[26] = {
+ [4] = 1, /* TCH/F: a=4 */
+ [13] = 1, /* TCH/F: a=13 */
+ [21] = 1, /* TCH/F: a=21 */
+};
+
+extern const uint8_t sched_tchh_dl_amr_cmi_map[26];
+
+static int decode_fr_facch(struct l1sched_ts *l1ts,
+ const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ const sbit_t *bursts_p = chan_state->ul_bursts;
+ struct l1sched_meas_set meas_avg;
+ uint8_t data[GSM_MACBLOCK_LEN];
+ int n_errors, n_bits_total;
+ int rc;
+
+ rc = gsm0503_tch_fr_facch_decode(&data[0], BUFTAIL8(bursts_p),
+ &n_errors, &n_bits_total);
+ if (rc != GSM_MACBLOCK_LEN)
+ return rc;
+
+ /* average measurements of the last 8 bursts, obtain TDMA Fn of the first burst */
+ trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S8N8);
+
+ _sched_compose_ph_data_ind(l1ts, meas_avg.fn, bi->chan,
+ &data[0], GSM_MACBLOCK_LEN,
+ compute_ber10k(n_bits_total, n_errors),
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+ return GSM_MACBLOCK_LEN;
+}
+
+/* Process a single Uplink TCH/F burst received by the PHY.
+ * This function is visualized in file 'doc/trx_sched_tch.txt'. */
+int rx_tchf_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ struct gsm_lchan *lchan = chan_state->lchan;
+ sbit_t *burst, *bursts_p = chan_state->ul_bursts;
+ uint32_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[290]; /* large enough to hold 290 unpacked bits for CSD */
+ enum sched_meas_avg_mode meas_avg_mode = SCHED_MEAS_AVG_M_S8N8;
+ struct l1sched_meas_set meas_avg;
+ int rc, amr = 0;
+ int n_errors = 0;
+ int n_bits_total = 0;
+ unsigned int fn_begin;
+ uint16_t ber10k;
+ uint8_t is_sub = 0;
+ uint8_t ft;
+ bool amr_is_cmr;
+
+ /* If handover RACH detection is turned on, treat this burst as an Access Burst.
+ * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
+ if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
+ return rx_rach_fn(l1ts, bi);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received TCH/F, bid=%u\n", bi->bid);
+
+ /* shift the buffer by 4 bursts leftwards */
+ if (bi->bid == 0) {
+ memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN);
+ memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN);
+ *mask = *mask << 4;
+ }
+
+ /* update mask */
+ *mask |= (1 << bi->bid);
+
+ /* store measurements */
+ trx_sched_meas_push(chan_state, bi);
+
+ /* copy burst to end of buffer of 24 bursts */
+ burst = BUFPOS(bursts_p, 20 + bi->bid);
+ if (bi->burst_len > 0) {
+ memcpy(burst, bi->burst + 3, 58);
+ memcpy(burst + 58, bi->burst + 87, 58);
+ }
+
+ /* wait until complete set of bursts */
+ if (bi->bid != 3)
+ return 0;
+
+ /* fill up the burst buffer so that we have 8 bursts in there */
+ if (OSMO_UNLIKELY((*mask & 0xff) != 0xff)) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "UL burst buffer is not filled up: mask=0x%02x != 0xff\n",
+ *mask);
+ return 0; /* TODO: send BFI */
+ }
+
+ /* TCH/F: speech and signalling frames are interleaved over 8 bursts, while
+ * CSD frames are interleaved over 22 bursts. Unless we're in CSD mode,
+ * decode only the last 8 bursts to avoid introducing additional delays. */
+ switch (tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p),
+ 1, 0, &n_errors, &n_bits_total);
+ if (rc == GSM_FR_BYTES) /* only for valid *speech* frames */
+ lchan_set_marker(osmo_fr_is_any_sid(tch_data), lchan); /* DTXu */
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p),
+ 1, 1, &n_errors, &n_bits_total);
+ if (rc == GSM_EFR_BYTES) /* only for valid *speech* frames */
+ lchan_set_marker(osmo_efr_is_any_sid(tch_data), lchan); /* DTXu */
+ 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.
+ */
+ amr_is_cmr = !sched_tchf_ul_amr_cmi_map[bi->fn % 26];
+
+ /* The AFS_ONSET frame itself does not result into an RTP frame
+ * since it only contains a recognition pattern that marks the
+ * end of the DTX interval. To mark the end of the DTX interval
+ * in the RTP stream as well, the voice frame after the
+ * AFS_ONSET frame is used. */
+ if (chan_state->amr_last_dtx == AFS_ONSET)
+ lchan_set_marker(false, lchan);
+
+ /* Store AMR payload in tch-data with an offset of 2 bytes, so
+ * that we can easily prepend/fill the RTP AMR header (struct
+ * amr_hdr) with osmo_amr_rtp_enc() later on. The amr variable
+ * is used far below to account for the decoded offset in case
+ * we receive an FACCH frame instead of a voice frame (we
+ * do not know this before we actually decode the frame) */
+ amr = sizeof(struct amr_hdr);
+ rc = gsm0503_tch_afs_decode_dtx(tch_data + amr, BUFTAIL8(bursts_p),
+ amr_is_cmr, chan_state->codec, chan_state->codecs, &chan_state->ul_ft,
+ &chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
+
+ /* Tag all frames that are not regular AMR voice frames as
+ * SUB-Frames */
+ if (chan_state->amr_last_dtx != AMR_OTHER) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "Received AMR DTX frame (rc=%d, BER %d/%d): %s\n",
+ rc, n_errors, n_bits_total,
+ gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
+ is_sub = 1;
+ }
+
+ /* The occurrence of the following frames indicates that we
+ * are either at the beginning or in the middle of a talk
+ * spurt. We update the SID status accordingly, but we do
+ * not want the marker to be set, since this must only
+ * happen when the talk spurt is over (see above) */
+ switch (chan_state->amr_last_dtx) {
+ case AFS_SID_FIRST:
+ case AFS_SID_UPDATE:
+ case AFS_SID_UPDATE_CN:
+ lchan_set_marker(true, lchan);
+ lchan->rtp_tx_marker = false;
+ break;
+ }
+
+ switch (chan_state->amr_last_dtx) {
+ case AFS_SID_FIRST:
+ case AFS_SID_UPDATE_CN:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S8N4;
+ break;
+ case AFS_SID_UPDATE:
+ case AFS_ONSET:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S4N4;
+ break;
+ }
+
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ if (chan_state->amr_last_dtx == AMR_OTHER) {
+ ft = chan_state->codec[chan_state->ul_ft];
+ } else {
+ /* SID frames will always get Frame Type Index 8 (AMR_SID) */
+ ft = AMR_SID;
+ }
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->ul_cmr],
+ ft, AMR_GOOD);
+ }
+
+ break;
+ /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_12k0:
+ /* FACCH/F does not steal TCH/F9.6 frames, but only disturbs some bits */
+ decode_fr_facch(l1ts, bi);
+ rc = gsm0503_tch_fr96_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S24N22;
+ break;
+ /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_6k0:
+ /* FACCH/F does not steal TCH/F4.8 frames, but only disturbs some bits */
+ decode_fr_facch(l1ts, bi);
+ rc = gsm0503_tch_fr48_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S24N22;
+ break;
+ /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_3k6:
+ /* TCH/F2.4 employs the same interleaving as TCH/FS (8 bursts),
+ * so FACCH/F *does* steal TCH/F2.4 frames completely. */
+ if (decode_fr_facch(l1ts, bi) == GSM_MACBLOCK_LEN)
+ return 0; /* TODO: emit BFI */
+ rc = gsm0503_tch_fr24_decode(&tch_data[0], BUFTAIL8(bursts_p),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S8N8;
+ break;
+ /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_14k5:
+ /* FACCH/F does not steal TCH/F14.4 frames, but only disturbs some bits */
+ decode_fr_facch(l1ts, bi);
+ rc = gsm0503_tch_fr144_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S24N22;
+ break;
+ default:
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi,
+ "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+
+ /* average measurements of the last N (depends on mode) bursts */
+ trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode);
+ /* meas_avg.fn now contains TDMA frame number of the first burst */
+ fn_begin = meas_avg.fn;
+
+ if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ trx_loop_amr_input(chan_state, &meas_avg);
+
+ /* Check if the frame is bad */
+ if (rc < 4) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi,
+ BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS);
+ rc = 0; /* this is how we signal BFI to l1sap */
+ } else if (rc == GSM_MACBLOCK_LEN) { /* FACCH/F */
+ _sched_compose_ph_data_ind(l1ts, fn_begin, bi->chan,
+ &tch_data[amr], GSM_MACBLOCK_LEN,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+
+ /* If we are in SPEECH mode we will generate a fake (BFI) TCH
+ * indication as well. This indication is needed by the higher
+ * layers, however we already have reported the measurement
+ * result for the current block together with the FACCH.
+ * To avoid reporting the same measurement result again with
+ * the fake (BFI) TCH indication we set meas_avg.rssi to zero.
+ * Doing so tells l1sap.c to ignore the measurement result. */
+ meas_avg.rssi = 0;
+ rc = 0;
+ }
+
+ if (rsl_cmode == RSL_CMOD_SPD_SIGN)
+ return 0;
+
+ /* TCH or BFI */
+ return _sched_compose_tch_ind(l1ts, fn_begin, bi->chan,
+ &tch_data[0], rc,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ is_sub);
+}
+
+/* common section for generation of TCH bursts (TCH/H and TCH/F).
+ * FIXME: this function is over-complicated, refactor / get rid of it. */
+void tch_dl_dequeue(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br,
+ struct msgb **msg_tch, struct msgb **msg_facch)
+{
+ struct msgb *msg1, *msg2;
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ struct osmo_phsap_prim *l1sap;
+
+ /* get frame and unlink from queue */
+ msg1 = _sched_dequeue_prim(l1ts, br);
+ msg2 = _sched_dequeue_prim(l1ts, br);
+ 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) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "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) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "FACCH twice, please FIX!\n");
+ msgb_free(msg2);
+ } else
+ *msg_tch = msg2;
+ }
+ }
+ }
+
+ /* check validity of message */
+ if (*msg_facch != NULL && msgb_l2len(*msg_facch) != GSM_MACBLOCK_LEN) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n",
+ msgb_l2len(*msg_facch), GSM_MACBLOCK_LEN);
+ /* free message */
+ msgb_free(*msg_facch);
+ *msg_facch = NULL;
+ }
+
+ /* check validity of message, get AMR ft and cmr */
+ if (*msg_tch != NULL) {
+ int len;
+ uint8_t cmr_codec;
+ int ft, i;
+ enum osmo_amr_type ft_codec;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+ bool amr_is_cmr;
+
+ if (OSMO_UNLIKELY(rsl_cmode == RSL_CMOD_SPD_SIGN)) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Dropping a TCH frame, "
+ "because we are not in speech mode\n");
+ goto free_bad_msg;
+ }
+
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (br->chan != TRXC_TCHF) /* HR */
+ len = GSM_HR_BYTES;
+ else
+ len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (br->chan != TRXC_TCHF)
+ goto inval_mode2;
+ len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ len = osmo_amr_rtp_dec(msgb_l2((*msg_tch)), msgb_l2len(*msg_tch),
+ &cmr_codec, &cmi, &ft_codec,
+ &bfi, &sti);
+ if (len < 0) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send invalid AMR payload\n");
+ goto free_bad_msg;
+ }
+ ft = -1;
+ for (i = 0; i < chan_state->codecs; i++) {
+ if (chan_state->codec[i] == ft_codec)
+ ft = i;
+ }
+ if (ft < 0) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br,
+ "Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+ goto free_bad_msg;
+ }
+ if (br->chan == TRXC_TCHF)
+ amr_is_cmr = !sched_tchf_dl_amr_cmi_map[br->fn % 26];
+ else /* TRXC_TCHH_0 or TRXC_TCHH_1 */
+ amr_is_cmr = !sched_tchh_dl_amr_cmi_map[br->fn % 26];
+ if (amr_is_cmr && chan_state->dl_ft != ft) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "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) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Transmitting 'bad AMR frame'\n");
+ goto free_bad_msg;
+ }
+ /* pull the AMR header, it's not being sent over Um */
+ (*msg_tch)->l2h += sizeof(struct amr_hdr);
+ len -= sizeof(struct amr_hdr);
+ break;
+ case GSM48_CMODE_DATA_14k5: /* TCH/F14.4 */
+ if (OSMO_UNLIKELY(br->chan != TRXC_TCHF))
+ goto inval_mode2;
+ len = 290;
+ break;
+ case GSM48_CMODE_DATA_12k0: /* TCH/F9.6 */
+ if (OSMO_UNLIKELY(br->chan != TRXC_TCHF))
+ goto inval_mode2;
+ len = 4 * 60;
+ break;
+ case GSM48_CMODE_DATA_6k0: /* TCH/[FH]4.8 */
+ if (br->chan == TRXC_TCHF)
+ len = 2 * 60;
+ else
+ len = 4 * 60;
+ break;
+ case GSM48_CMODE_DATA_3k6: /* TCH/[FH]2.4 */
+ if (br->chan == TRXC_TCHF)
+ len = 2 * 36;
+ else
+ len = 4 * 36;
+ break;
+ default:
+inval_mode2:
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "TCH mode invalid, please fix!\n");
+ goto free_bad_msg;
+ }
+ if (msgb_l2len(*msg_tch) != len) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "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;
+ }
+ }
+}
+
+struct msgb *tch_dummy_msgb(size_t size, uint8_t pad)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc(size, __func__);
+ OSMO_ASSERT(msg != NULL);
+
+ msg->l2h = msgb_put(msg, size);
+ memset(msg->l2h, pad, size);
+
+ return msg;
+}
+
+/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
+int tx_tchf_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ uint8_t tch_mode = chan_state->tch_mode;
+ ubit_t *burst, *bursts_p = chan_state->dl_bursts;
+ uint8_t *mask = &chan_state->dl_mask;
+ struct msgb *msg_facch = NULL;
+ struct msgb *msg_tch = NULL;
+ struct msgb *msg = NULL;
+
+ /* send burst, if we already got a frame */
+ if (br->bid > 0) {
+ if ((*mask & 0x01) != 0x01)
+ return -ENOMSG;
+ goto send_burst;
+ }
+
+ *mask = *mask << 4;
+
+ /* BURST BYPASS */
+
+ /* shift buffer by 4 bursts for interleaving */
+ memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN);
+ memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN);
+
+ /* dequeue a TCH and/or a FACCH message to be transmitted */
+ tch_dl_dequeue(l1ts, br, &msg_tch, &msg_facch);
+ if (msg_tch == NULL && msg_facch == NULL) {
+ int rc;
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "No TCH or FACCH prim for transmit.\n");
+ /* - If the channel mode is TCH/FS or TCH/EFS, transmit a dummy
+ * speech block with inverted CRC3, designed to induce a BFI
+ * condition in the MS receiver.
+ * - If the channel mode is TCH/AFS, transmit a dummy speech
+ * block with inverted CRC6, designed to induce a BFI
+ * condition in the MS receiver.
+ * - If the channel mode is one of the CSD modes, transmit an
+ * idle frame as described in 3GPP TS 44.021, sections 8.1.6
+ * and 10.2.3 (all data, status and E-bits set to binary '1').
+ * - In all other channel modes, transmit dummy FACCH
+ * like we always did before.
+ */
+ switch (tch_mode) {
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ case GSM48_CMODE_DATA_14k5:
+ break; /* see below */
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ rc = gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), NULL, 0, 1);
+ if (rc == 0)
+ goto send_burst;
+ /* fall-through */
+ case GSM48_CMODE_SIGN:
+ default:
+ 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.
+ */
+ rc = gsm0503_tch_afs_encode(BUFPOS(bursts_p, 0),
+ NULL, 0,
+ !sched_tchf_dl_amr_cmi_map[br->fn % 26],
+ chan_state->codec,
+ chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ if (rc == 0)
+ goto send_burst;
+ }
+
+ /* TODO: use randomized padding */
+ msg_facch = tch_dummy_msgb(GSM_MACBLOCK_LEN, GSM_MACBLOCK_PADDING);
+ /* dummy LAPDm func=UI frame */
+ msg_facch->l2h[0] = 0x03;
+ msg_facch->l2h[1] = 0x03;
+ msg_facch->l2h[2] = 0x01;
+ break;
+ }
+ }
+
+ /* Unlike SACCH, FACCH has no dedicated slots on the multiframe layout.
+ * It's multiplexed together with TCH (speech or data) frames basically
+ * by replacing (stealing) their bits, either completely or partly. */
+ msg = (msg_facch != NULL) ? msg_facch : msg_tch;
+ if (msg == msg_facch)
+ chan_state->dl_facch_bursts = 8;
+
+ /* populate the buffer with bursts */
+ switch (tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), msg->l2h, msgb_l2len(msg), 1);
+ break;
+ case 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(BUFPOS(bursts_p, 0),
+ msgb_l2(msg), msgb_l2len(msg),
+ !sched_tchf_dl_amr_cmi_map[br->fn % 26],
+ chan_state->codec,
+ chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ break;
+ /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_12k0:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(4 * 60, 0x01);
+ gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_6k0:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(2 * 60, 0x01);
+ gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_3k6:
+ /* FACCH/F does steal a TCH/F2.4 frame completely */
+ if (msg_facch != NULL) {
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ } else {
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(2 * 36, 0x01);
+ gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ }
+ break;
+ /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_14k5:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(290, 0x01);
+ gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* free messages */
+ msgb_free(msg_tch);
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = BUFPOS(bursts_p, br->bid);
+ memcpy(br->burst + 3, burst, 58);
+ memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26);
+ memcpy(br->burst + 87, burst + 58, 58);
+
+ br->burst_len = GSM_BURST_LEN;
+
+ if (chan_state->dl_facch_bursts > 0) {
+ chan_state->dl_facch_bursts--;
+ br->flags |= TRX_BR_F_FACCH;
+ }
+
+ *mask |= (1 << br->bid);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid);
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_tchh.c b/src/osmo-bts-trx/sched_lchan_tchh.c
new file mode 100644
index 00000000..66244cb0
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_tchh.c
@@ -0,0 +1,580 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * 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/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/codec/codec.h>
+
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
+
+#include <osmocom/netif/amr.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <sched_utils.h>
+#include <amr_loop.h>
+
+/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Uplink TCH/H.
+ *
+ * +---+---+---+---+---+---+
+ * | a | b | c | d | e | f | Burst 'a' received first
+ * +---+---+---+---+---+---+
+ * ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f')
+ * ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd')
+ *
+ * TDMA frame number of burst 'f' is always used as the table index. */
+static const uint8_t sched_tchh_ul_amr_cmi_map[26] = {
+ [10] = 1, /* TCH/H(0): a=0 / d=6 / f=10 */
+ [19] = 1, /* TCH/H(0): a=8 / d=15 / f=19 */
+ [2] = 1, /* TCH/H(0): a=17 / d=23 / f=2 */
+
+ [11] = 1, /* TCH/H(1): a=1 / d=7 / f=11 */
+ [20] = 1, /* TCH/H(1): a=9 / d=16 / f=20 */
+ [3] = 1, /* TCH/H(1): a=18 / d=24 / f=3 */
+};
+
+/* TDMA frame number of burst 'a' should be used as the table index.
+ * This mapping is valid for both FACCH/H(0) and FACCH/H(1). */
+const uint8_t sched_tchh_dl_amr_cmi_map[26] = {
+ [4] = 1, /* TCH/H(0): a=4 */
+ [13] = 1, /* TCH/H(0): a=13 */
+ [21] = 1, /* TCH/H(0): a=21 */
+
+ [5] = 1, /* TCH/H(1): a=5 */
+ [14] = 1, /* TCH/H(1): a=14 */
+ [22] = 1, /* TCH/H(1): a=22 */
+};
+
+/* 3GPP TS 45.002, table 1 in clause 7: Mapping tables.
+ * TDMA frame number of burst 'f' is always used as the table index. */
+static const uint8_t sched_tchh_ul_facch_map[26] = {
+ [10] = 1, /* FACCH/H(0): B0(0,2,4,6,8,10) */
+ [11] = 1, /* FACCH/H(1): B0(1,3,5,7,9,11) */
+ [19] = 1, /* FACCH/H(0): B1(8,10,13,15,17,19) */
+ [20] = 1, /* FACCH/H(1): B1(9,11,14,16,18,20) */
+ [2] = 1, /* FACCH/H(0): B2(17,19,21,23,0,2) */
+ [3] = 1, /* FACCH/H(1): B2(18,20,22,24,1,3) */
+};
+
+/* TDMA frame number of burst 'a' is used as the table index. */
+extern const uint8_t sched_tchh_dl_facch_map[26];
+
+/* 3GPP TS 45.002, table 2 in clause 7: Mapping tables for TCH/H2.4 and TCH/H4.8.
+ *
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ * | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v |
+ * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ *
+ * TCH/H(0): B0(0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19)
+ * TCH/H(1): B0(1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20)
+ * TCH/H(0): B1(8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2)
+ * TCH/H(1): B1(9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3)
+ * TCH/H(0): B2(17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10)
+ * TCH/H(1): B2(18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11)
+ *
+ * TDMA frame number of burst 'v' % 26 is the table index.
+ * This mapping is valid for both TCH/H(0) and TCH/H(1). */
+static const uint8_t sched_tchh_ul_csd_map[26] = {
+ [19] = 1, /* TCH/H(0): B0(0 ... 19) */
+ [20] = 1, /* TCH/H(1): B0(1 ... 20) */
+ [2] = 1, /* TCH/H(0): B1(8 ... 2) */
+ [3] = 1, /* TCH/H(1): B1(9 ... 3) */
+ [10] = 1, /* TCH/H(0): B2(17 ... 10) */
+ [11] = 1, /* TCH/H(1): B2(18 ... 11) */
+};
+
+/* TDMA frame number of burst 'a' % 26 is the table index.
+ * This mapping is valid for both TCH/H(0) and TCH/H(1). */
+static const uint8_t sched_tchh_dl_csd_map[26] = {
+ [0] = 1, /* TCH/H(0): B0(0 ... 19) */
+ [1] = 1, /* TCH/H(1): B0(1 ... 20) */
+ [8] = 1, /* TCH/H(0): B1(8 ... 2) */
+ [9] = 1, /* TCH/H(1): B1(9 ... 3) */
+ [17] = 1, /* TCH/H(0): B2(17 ... 10) */
+ [18] = 1, /* TCH/H(1): B2(18 ... 11) */
+};
+
+static int decode_hr_facch(struct l1sched_ts *l1ts,
+ const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ const sbit_t *bursts_p = chan_state->ul_bursts;
+ struct l1sched_meas_set meas_avg;
+ uint8_t data[GSM_MACBLOCK_LEN];
+ int n_errors, n_bits_total;
+ int rc;
+
+ rc = gsm0503_tch_hr_facch_decode(&data[0], BUFTAIL8(bursts_p),
+ &n_errors, &n_bits_total);
+ if (rc != GSM_MACBLOCK_LEN)
+ return rc;
+
+ /* average measurements of the last 6 bursts, obtain TDMA Fn of the first burst */
+ trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S6N6);
+
+ _sched_compose_ph_data_ind(l1ts, meas_avg.fn, bi->chan,
+ &data[0], GSM_MACBLOCK_LEN,
+ compute_ber10k(n_bits_total, n_errors),
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+ return GSM_MACBLOCK_LEN;
+}
+
+/* Process a single Uplink TCH/H burst received by the PHY.
+ * This function is visualized in file 'doc/trx_sched_tch.txt'. */
+int rx_tchh_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ struct gsm_lchan *lchan = chan_state->lchan;
+ sbit_t *burst, *bursts_p = chan_state->ul_bursts;
+ uint32_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[240]; /* large enough to hold 240 unpacked bits for CSD */
+ int rc = 0; /* initialize to make gcc happy */
+ int amr = 0;
+ int n_errors = 0;
+ int n_bits_total = 0;
+ enum sched_meas_avg_mode meas_avg_mode = SCHED_MEAS_AVG_M_S6N4;
+ struct l1sched_meas_set meas_avg;
+ unsigned int fn_begin;
+ uint16_t ber10k = 0;
+ uint8_t is_sub = 0;
+ uint8_t ft;
+ bool fn_is_cmi;
+
+ /* If handover RACH detection is turned on, treat this burst as an Access Burst.
+ * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
+ if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
+ return rx_rach_fn(l1ts, bi);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received TCH/H, bid=%u\n", bi->bid);
+
+ /* shift the buffer by 2 bursts leftwards */
+ if (bi->bid == 0) {
+ memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN);
+ memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN);
+ *mask = *mask << 2;
+ }
+
+ /* update mask */
+ *mask |= (1 << bi->bid);
+
+ /* store measurements */
+ trx_sched_meas_push(chan_state, bi);
+
+ /* copy burst to end of buffer of 24 bursts */
+ burst = BUFPOS(bursts_p, 20 + bi->bid);
+ if (bi->burst_len > 0) {
+ memcpy(burst, bi->burst + 3, 58);
+ memcpy(burst + 58, bi->burst + 87, 58);
+ }
+
+ /* wait until complete set of bursts */
+ if (bi->bid != 1)
+ return 0;
+
+ /* fill up the burst buffer so that we have 6 bursts in there */
+ if (OSMO_UNLIKELY((*mask & 0x3f) != 0x3f)) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "UL burst buffer is not filled up: mask=0x%02x != 0x3f\n",
+ *mask);
+ return 0; /* TODO: send BFI */
+ }
+
+ /* skip decoding of the last 4 bursts of FACCH/H */
+ if (chan_state->ul_ongoing_facch) {
+ chan_state->ul_ongoing_facch = 0;
+ /* we have already sent the first BFI when a FACCH/H frame
+ * was decoded (see below), now send the second one. */
+ trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode);
+ /* meas_avg.fn now contains TDMA frame number of the first burst */
+ fn_begin = meas_avg.fn;
+ goto bfi;
+ }
+
+ /* TCH/H: speech and signalling frames are interleaved over 4 and 6 bursts,
+ * respectively, while CSD frames are interleaved over 22 bursts. Unless
+ * we're in CSD mode, decode only the last 6 bursts to avoid introducing
+ * additional delays. */
+ switch (tch_mode) {
+ case GSM48_CMODE_SIGN:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S6N6;
+ /* fall-through */
+ case GSM48_CMODE_SPEECH_V1: /* HR or signalling */
+ rc = gsm0503_tch_hr_decode2(tch_data, BUFTAIL8(bursts_p),
+ !sched_tchh_ul_facch_map[bi->fn % 26],
+ &n_errors, &n_bits_total);
+ if (rc == GSM_HR_BYTES) { /* only for valid *speech* frames */
+ bool is_sid = osmo_hr_check_sid(tch_data, GSM_HR_BYTES);
+ lchan_set_marker(is_sid, lchan); /* DTXu */
+ }
+ 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.
+ */
+
+ /* See comment in function rx_tchf_fn() */
+ switch (chan_state->amr_last_dtx) {
+ case AHS_ONSET:
+ case AHS_SID_FIRST_INH:
+ case AHS_SID_UPDATE_INH:
+ lchan_set_marker(false, lchan);
+ break;
+ }
+
+ fn_is_cmi = sched_tchh_ul_amr_cmi_map[bi->fn % 26];
+
+ /* See comment in function rx_tchf_fn() */
+ amr = sizeof(struct amr_hdr);
+ rc = gsm0503_tch_ahs_decode_dtx(tch_data + amr, BUFTAIL8(bursts_p),
+ !sched_tchh_ul_facch_map[bi->fn % 26],
+ !fn_is_cmi, chan_state->codec,
+ chan_state->codecs, &chan_state->ul_ft,
+ &chan_state->ul_cmr, &n_errors, &n_bits_total,
+ &chan_state->amr_last_dtx);
+
+ /* Tag all frames that are not regular AMR voice frames
+ as SUB-Frames */
+ if (chan_state->amr_last_dtx != AMR_OTHER) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "Received AMR DTX frame (rc=%d, BER %d/%d): %s\n",
+ rc, n_errors, n_bits_total,
+ gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
+ is_sub = 1;
+ }
+
+ /* See comment in function rx_tchf_fn() */
+ switch (chan_state->amr_last_dtx) {
+ case AHS_SID_FIRST_P1:
+ case AHS_SID_FIRST_P2:
+ case AHS_SID_UPDATE:
+ case AHS_SID_UPDATE_CN:
+ lchan_set_marker(true, lchan);
+ lchan->rtp_tx_marker = false;
+ break;
+ }
+
+ switch (chan_state->amr_last_dtx) {
+ case AHS_SID_FIRST_P1:
+ case AHS_SID_FIRST_P2:
+ case AHS_SID_UPDATE:
+ case AHS_SID_UPDATE_CN:
+ case AHS_SID_FIRST_INH:
+ case AHS_SID_UPDATE_INH:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S6N2;
+ break;
+ case AHS_ONSET:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S4N2;
+ break;
+ }
+
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ if (chan_state->amr_last_dtx == AMR_OTHER) {
+ ft = chan_state->codec[chan_state->ul_ft];
+ } else {
+ /* SID frames will always get Frame Type Index 8 (AMR_SID) */
+ ft = AMR_SID;
+ }
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->ul_cmr],
+ ft, AMR_GOOD);
+ }
+
+ break;
+ /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_6k0:
+ if (!sched_tchh_ul_csd_map[bi->fn % 26])
+ return 0; /* CSD: skip decoding attempt, need 2 more bursts */
+ /* FACCH/H does not steal TCH/H4.8 frames, but only disturbs some bits */
+ decode_hr_facch(l1ts, bi);
+ rc = gsm0503_tch_hr48_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S22N22;
+ break;
+ /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_3k6:
+ if (!sched_tchh_ul_csd_map[bi->fn % 26])
+ return 0; /* CSD: skip decoding attempt, need 2 more bursts */
+ /* FACCH/H does not steal TCH/H2.4 frames, but only disturbs some bits */
+ decode_hr_facch(l1ts, bi);
+ rc = gsm0503_tch_hr24_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S22N22;
+ break;
+ default:
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi,
+ "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+
+ /* average measurements of the last N (depends on mode) bursts */
+ trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode);
+ /* meas_avg.fn now contains TDMA frame number of the first burst */
+ fn_begin = meas_avg.fn;
+
+ if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ trx_loop_amr_input(chan_state, &meas_avg);
+
+ /* Check if the frame is bad */
+ if (rc < 4) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi,
+ BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS);
+ rc = 0; /* this is how we signal BFI to l1sap */
+ } else if (rc == GSM_MACBLOCK_LEN) { /* FACCH */
+ chan_state->ul_ongoing_facch = 1;
+ /* In order to provide an even stream of measurement reports in *speech*
+ * mode, here we intentionally invalidate RSSI for FACCH, so that this
+ * report gets dropped in process_l1sap_meas_data(). The averaged results
+ * will be sent with the first (see below) and second (see above) BFIs. */
+ _sched_compose_ph_data_ind(l1ts, fn_begin, bi->chan,
+ &tch_data[amr], GSM_MACBLOCK_LEN,
+ ber10k,
+ tch_mode == GSM48_CMODE_SIGN ? meas_avg.rssi : 0,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+ ber10k = 0;
+bfi:
+ /* A FACCH/H frame replaces two speech frames, so we need to send two BFIs.
+ * One is sent here, another will be sent two bursts later (see above). */
+ rc = 0;
+ }
+
+ if (rsl_cmode == RSL_CMOD_SPD_SIGN)
+ return 0;
+
+ /* TCH or BFI */
+ return _sched_compose_tch_ind(l1ts, fn_begin, bi->chan,
+ &tch_data[0], rc,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ is_sub);
+}
+
+/* common section for generation of TCH bursts (TCH/H and TCH/F).
+ * FIXME: this function is over-complicated, refactor / get rid of it. */
+extern void tch_dl_dequeue(struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br,
+ struct msgb **msg_tch, struct msgb **msg_facch);
+
+struct msgb *tch_dummy_msgb(size_t size, uint8_t pad);
+
+/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */
+int tx_tchh_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ uint8_t tch_mode = chan_state->tch_mode;
+ ubit_t *burst, *bursts_p = chan_state->dl_bursts;
+ uint8_t *mask = &chan_state->dl_mask;
+ struct msgb *msg_facch = NULL;
+ struct msgb *msg_tch = NULL;
+ struct msgb *msg = NULL;
+
+ /* send burst, if we already got a frame */
+ if (br->bid > 0) {
+ if ((*mask & 0x01) != 0x01)
+ return -ENOMSG;
+ goto send_burst;
+ }
+
+ *mask = *mask << 2;
+
+ /* BURST BYPASS */
+
+ /* shift buffer by 2 bursts for interleaving */
+ memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN);
+ memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN);
+
+ /* for half-rate CSD we dequeue every 4th burst */
+ if (chan_state->rsl_cmode == RSL_CMOD_SPD_DATA) {
+ if (!sched_tchh_dl_csd_map[br->fn % 26])
+ goto send_burst;
+ }
+
+ /* dequeue a TCH and/or a FACCH message to be transmitted */
+ tch_dl_dequeue(l1ts, br, &msg_tch, &msg_facch);
+
+ /* if we're sending 2 middle bursts of FACCH/H */
+ if (chan_state->dl_ongoing_facch) {
+ /* FACCH/H shall not be scheduled at wrong FNs */
+ OSMO_ASSERT(msg_facch == NULL);
+ msgb_free(msg_tch); /* drop 2nd speech frame */
+ chan_state->dl_ongoing_facch = 0;
+ goto send_burst;
+ }
+
+ /* no message at all, send a dummy L2 frame on FACCH */
+ if (msg_tch == NULL && msg_facch == NULL) {
+ int rc;
+
+ LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "No TCH or FACCH prim for transmit.\n");
+ /* - If the channel mode is TCH/HS, transmit a dummy speech block
+ * with inverted CRC3, designed to induce a BFI condition in
+ * the MS receiver.
+ * - If the channel mode is TCH/AHS, transmit a dummy speech
+ * block with inverted CRC6, designed to induce a BFI
+ * condition in the MS receiver.
+ * - If the channel mode is one of the CSD modes, transmit an
+ * idle frame as described in 3GPP TS 44.021, sections 8.1.6
+ * and 10.2.3 (all data, status and E-bits set to binary '1').
+ * - In all other channel modes, transmit dummy FACCH
+ * like we always did before.
+ */
+ switch (tch_mode) {
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ break; /* see below */
+ case GSM48_CMODE_SPEECH_V1:
+ rc = gsm0503_tch_hr_encode(BUFPOS(bursts_p, 0), NULL, 0);
+ if (rc == 0)
+ goto send_burst;
+ /* fall-through */
+ case GSM48_CMODE_SIGN:
+ default:
+ 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. */
+ rc = gsm0503_tch_ahs_encode(BUFPOS(bursts_p, 0),
+ NULL, 0,
+ !sched_tchh_dl_amr_cmi_map[br->fn % 26],
+ chan_state->codec,
+ chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ if (rc == 0)
+ goto send_burst;
+ }
+
+ /* FACCH/H can only be scheduled at specific TDMA offset */
+ if (!sched_tchh_dl_facch_map[br->fn % 26]) {
+ /* FACCH/H is not allowed, send half-filled bursts with even numbered
+ * bits contaning 232 encoded bits of the previous L2 frame, and 232
+ * odd numbered bits all set to 0. */
+ goto send_burst;
+ }
+
+ /* TODO: use randomized padding */
+ msg_facch = tch_dummy_msgb(GSM_MACBLOCK_LEN, GSM_MACBLOCK_PADDING);
+ /* dummy LAPDm func=UI frame */
+ msg_facch->l2h[0] = 0x03;
+ msg_facch->l2h[1] = 0x03;
+ msg_facch->l2h[2] = 0x01;
+ break;
+ }
+ }
+
+ /* Unlike SACCH, FACCH has no dedicated slots on the multiframe layout.
+ * It's multiplexed together with TCH (speech or data) frames basically
+ * by replacing (stealing) their bits, either completely or partly. */
+ msg = (msg_facch != NULL) ? msg_facch : msg_tch;
+ if (msg == msg_facch) {
+ if (chan_state->rsl_cmode != RSL_CMOD_SPD_DATA)
+ chan_state->dl_ongoing_facch = 1;
+ chan_state->dl_facch_bursts = 6;
+ }
+
+ /* populate the buffer with bursts */
+ switch (tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1:
+ gsm0503_tch_hr_encode(BUFPOS(bursts_p, 0), msg->l2h, msgb_l2len(msg));
+ break;
+ case 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(BUFPOS(bursts_p, 0),
+ msgb_l2(msg), msgb_l2len(msg),
+ !sched_tchh_dl_amr_cmi_map[br->fn % 26],
+ chan_state->codec,
+ chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ break;
+ /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_6k0:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(4 * 60, 0x01);
+ gsm0503_tch_hr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_3k6:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(4 * 36, 0x01);
+ gsm0503_tch_hr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* free messages */
+ msgb_free(msg_tch);
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = BUFPOS(bursts_p, br->bid);
+ memcpy(br->burst + 3, burst, 58);
+ memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26);
+ memcpy(br->burst + 87, burst + 58, 58);
+
+ br->burst_len = GSM_BURST_LEN;
+
+ if (chan_state->dl_facch_bursts > 0) {
+ chan_state->dl_facch_bursts--;
+ br->flags |= TRX_BR_F_FACCH;
+ }
+
+ *mask |= (1 << br->bid);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid);
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_xcch.c b/src/osmo-bts-trx/sched_lchan_xcch.c
new file mode 100644
index 00000000..0580d33b
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_xcch.c
@@ -0,0 +1,205 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * Contributions 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 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/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* Add two arrays of sbits */
+static void add_sbits(sbit_t *current, const sbit_t *previous)
+{
+ for (unsigned int i = 0; i < BPLEN * 4; i++)
+ current[i] = current[i] / 2 + previous[i] / 2;
+}
+
+/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */
+int rx_data_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ sbit_t *burst, *bursts_p = chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint32_t *mask = &chan_state->ul_mask;
+ uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
+ struct l1sched_meas_set meas_avg;
+ int n_errors = 0;
+ int n_bits_total = 0;
+ uint16_t ber10k;
+ int rc;
+ struct gsm_lchan *lchan = chan_state->lchan;
+ bool rep_sacch = L1SAP_IS_LINK_SACCH(trx_chan_desc[bi->chan].link_id) && lchan->rep_acch.ul_sacch_active;
+
+ /* If handover RACH detection is turned on, treat this burst as an Access Burst.
+ * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
+ if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
+ return rx_rach_fn(l1ts, bi);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received Data, bid=%u\n", bi->bid);
+
+ /* clear burst & store frame number of first burst */
+ if (bi->bid == 0) {
+ if (rep_sacch) /* Keep a copy to ease decoding in the next repetition pass */
+ memcpy(BUFPOS(bursts_p, 4), BUFPOS(bursts_p, 0), BPLEN * 4);
+ memset(BUFPOS(bursts_p, 0), 0, BPLEN * 4);
+ *mask = 0x0;
+ *first_fn = bi->fn;
+ }
+
+ /* update mask */
+ *mask |= (1 << bi->bid);
+
+ /* store measurements */
+ trx_sched_meas_push(chan_state, bi);
+
+ /* Copy burst to buffer of 4 bursts. If the burst indication contains
+ * no data, ensure that the buffer does not stay uninitialized */
+ burst = bursts_p + bi->bid * 116;
+ if (bi->burst_len > 0) {
+ memcpy(burst, bi->burst + 3, 58);
+ memcpy(burst + 58, bi->burst + 87, 58);
+ }
+
+ /* wait until complete set of bursts */
+ if (bi->bid != 3)
+ return 0;
+
+ /* average measurements of the last 4 bursts */
+ trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S4N4);
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, "Received incomplete data (%u/%u)\n",
+ bi->fn % l1ts->mf_period, 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, BUFPOS(bursts_p, 0), &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi,
+ BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS);
+ l2_len = 0;
+
+ /* When SACCH Repetition is active, we may try to decode the
+ * current SACCH block by including the information from the
+ * information from the previous SACCH block. See also:
+ * 3GPP TS 44.006, section 11.2 */
+ if (rep_sacch) {
+ add_sbits(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4));
+ rc = gsm0503_xcch_decode(l2, BUFPOS(bursts_p, 0), &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi,
+ "Combining current SACCH block with previous SACCH block also yields bad data (%u/%u)\n",
+ bi->fn % l1ts->mf_period, l1ts->mf_period);
+ } else {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "Combining current SACCH block with previous SACCH block yields good data (%u/%u)\n",
+ bi->fn % l1ts->mf_period, l1ts->mf_period);
+ l2_len = GSM_MACBLOCK_LEN;
+ }
+ }
+ } else
+ l2_len = GSM_MACBLOCK_LEN;
+
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+
+ return _sched_compose_ph_data_ind(l1ts, *first_fn, bi->chan,
+ &l2[0], l2_len,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+}
+
+/* obtain a to-be-transmitted xCCH (e.g SACCH or SDCCH) burst */
+int tx_data_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ struct msgb *msg = NULL; /* make GCC happy */
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ ubit_t *burst, *bursts_p = chan_state->dl_bursts;
+ uint8_t *mask = &chan_state->dl_mask;
+
+ /* send burst, if we already got a frame */
+ if (br->bid > 0) {
+ if ((*mask & 0x01) != 0x01)
+ return -ENOMSG;
+ goto send_burst;
+ }
+
+ *mask = *mask << 4;
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1ts, br);
+ if (msg == NULL) {
+ LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "No prim for transmit.\n");
+ return -ENODEV;
+ }
+
+ /* check validity of message */
+ if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n",
+ msgb_l2len(msg), GSM_MACBLOCK_LEN);
+ /* free message */
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ /* BURST BYPASS */
+
+ /* encode bursts */
+ gsm0503_xcch_encode(bursts_p, msg->l2h);
+
+ /* free message */
+ msgb_free(msg);
+
+send_burst:
+ /* compose burst */
+ burst = bursts_p + br->bid * 116;
+ memcpy(br->burst + 3, burst, 58);
+ memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26);
+ memcpy(br->burst + 87, burst + 58, 58);
+
+ br->burst_len = GSM_BURST_LEN;
+
+ *mask |= (1 << br->bid);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid);
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/sched_utils.h b/src/osmo-bts-trx/sched_utils.h
new file mode 100644
index 00000000..a88e06ee
--- /dev/null
+++ b/src/osmo-bts-trx/sched_utils.h
@@ -0,0 +1,51 @@
+/* Auxiliary scheduler utilities.
+ *
+ * (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 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/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/* Burst Payload LENgth (short alias) */
+#define BPLEN GSM_NBITS_NB_GMSK_PAYLOAD
+
+/* Burst BUFfer capacity (in BPLEN units). Why 24? Because CSD frames
+ * are interleaved over 22 bursts, but on TCH/F we decode every 4th burst,
+ * so it must be a multiple of 4. See also 'doc/trx_sched_tch.txt'. */
+#define BUFMAX 24
+
+/* Burst BUFfer position macros */
+#define BUFPOS(buf, n) &buf[(n) * BPLEN]
+#define BUFTAIL8(buf) BUFPOS(buf, (BUFMAX - 8))
+
+extern void *tall_bts_ctx;
+
+#define BAD_DATA_MSG_FMT "Received bad data (rc=%d, BER %d/%d) ending at fn=%u/%u"
+#define BAD_DATA_MSG_ARGS \
+ rc, n_errors, n_bits_total, bi->fn % l1ts->mf_period, l1ts->mf_period
+
+/* 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;
+}
diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c
index 45fc7012..00143abe 100644
--- a/src/osmo-bts-trx/scheduler_trx.c
+++ b/src/osmo-bts-trx/scheduler_trx.c
@@ -3,6 +3,7 @@
/* (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>
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -14,7 +15,7 @@
* 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.
+ * 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/>.
@@ -32,1598 +33,345 @@
#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 <osmocom/gsm/gsm0502.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 <osmo-bts/pcu_if.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;
-}
+#include "btsconfig.h"
-/* 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;
+#ifdef HAVE_SYSTEMTAP
+/* include the generated probes header and put markers in code */
+#include "probes.h"
+#define TRACE(probe) probe
+#define TRACE_ENABLED(probe) probe ## _ENABLED()
+#else
+/* Wrap the probe to allow it to be removed when no systemtap available */
+#define TRACE(probe)
+#define TRACE_ENABLED(probe) (0)
+#endif /* HAVE_SYSTEMTAP */
- /* BURST BYPASS */
+#define SCHED_FH_PARAMS_FMT "hsn=%u, maio=%u, ma_len=%u"
+#define SCHED_FH_PARAMS_VALS(ts) \
+ (ts)->hopping.hsn, (ts)->hopping.maio, (ts)->hopping.arfcn_num
- 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 void lchan_report_interf_meas(const struct gsm_lchan *lchan)
{
- 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);
+ const struct gsm_bts_trx_ts *ts = lchan->ts;
+ const struct l1sched_ts *l1ts = ts->priv;
+ enum trx_chan_type dcch, acch;
+ int interf_avg;
+
+ /* We're not interested in active CS channels */
+ if (lchan->state == LCHAN_S_ACTIVE) {
+ if (lchan->type != GSM_LCHAN_PDTCH)
+ return;
+ }
+
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) {
+ dcch = TRXC_SDCCH4_0 + lchan->nr;
+ acch = TRXC_SACCH4_0 + lchan->nr;
+ } else { /* SDCCH/8 otherwise */
+ dcch = TRXC_SDCCH8_0 + lchan->nr;
+ acch = TRXC_SACCH8_0 + lchan->nr;
}
+ break;
+ case GSM_LCHAN_TCH_F:
+ dcch = TRXC_TCHF;
+ acch = TRXC_SACCHTF;
+ break;
+ case GSM_LCHAN_TCH_H:
+ dcch = TRXC_TCHH_0 + lchan->nr;
+ acch = TRXC_SACCHTH_0 + lchan->nr;
+ break;
+ case GSM_LCHAN_PDTCH:
+ /* We use idle TDMA frames on PDCH */
+ dcch = TRXC_IDLE;
+ acch = TRXC_IDLE;
+ break;
+ default:
+ /* Skip other lchan types */
+ return;
}
- /* 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;
- }
+ OSMO_ASSERT(dcch < ARRAY_SIZE(l1ts->chan_state));
+ OSMO_ASSERT(acch < ARRAY_SIZE(l1ts->chan_state));
- LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+ interf_avg = (l1ts->chan_state[dcch].meas.interf_avg +
+ l1ts->chan_state[acch].meas.interf_avg) / 2;
- return bits;
+ gsm_lchan_interf_meas_push((struct gsm_lchan *) lchan, interf_avg);
}
-/* determine if the FN is transmitting a CMR (1) or not (0) */
-static inline int fn_is_codec_mode_request(uint32_t fn)
+static void bts_report_interf_meas(const struct gsm_bts *bts)
{
- return (((fn + 4) % 26) >> 2) & 1;
-}
+ const struct gsm_bts_trx *trx;
+ unsigned int tn, ln;
-/* 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) {
- LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
- "Failed to encode AMR_BAD frame (rc=%d), "
- "not sending BFI\n", len);
- return;
- }
- memset(tch_data + 2, 0, len - 2);
- 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);
- }
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ /* Skip pushing interf_meas for disabled TRX */
+ if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
+ trx->bb_transc.mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
- /* 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;
- }
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++)
+ lchan_report_interf_meas(&ts->lchan[ln]);
}
- } 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)
+/* Find a route (PHY instance) for a given Downlink burst request */
+static struct phy_instance *dlfh_route_br(const struct trx_dl_burst_req *br,
+ struct gsm_bts_trx_ts *ts)
{
- 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);
+ const struct gsm_bts_trx *trx;
+ struct gsm_time time;
+ uint16_t idx;
- if (nbits)
- *nbits = GSM_BURST_LEN;
+ gsm_fn2gsmtime(&time, br->fn);
- LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+ /* Check the "cache" first, so we eliminate frequent lookups */
+ idx = gsm0502_hop_seq_gen(&time, SCHED_FH_PARAMS_VALS(ts), NULL);
+ if (ts->fh_trx_list[idx] != NULL)
+ return ts->fh_trx_list[idx]->pinst;
- 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);
+ struct bts_trx_priv *priv = (struct bts_trx_priv *) ts->trx->bts->model_priv;
- /* 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);
+ /* The "cache" may not be filled yet, lookup the transceiver */
+ llist_for_each_entry(trx, &ts->trx->bts->trx_list, list) {
+ if (trx->arfcn == ts->hopping.arfcn_list[idx]) {
+ rate_ctr_inc2(priv->ctrs, BTSTRX_CTR_SCHED_DL_FH_CACHE_MISS);
+ ts->fh_trx_list[idx] = trx;
+ return trx->pinst;
}
}
- /* 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));
+ LOGPTRX(ts->trx, DL1C, LOGL_FATAL, "Failed to find the transceiver (RF carrier) "
+ "for a Downlink burst (fn=%u, tn=%u, " SCHED_FH_PARAMS_FMT ")\n",
+ br->fn, br->tn, SCHED_FH_PARAMS_VALS(ts));
- /* free message */
- if (msg_tch)
- msgb_free(msg_tch);
- if (msg_facch)
- msgb_free(msg_facch);
+ rate_ctr_inc2(priv->ctrs, BTSTRX_CTR_SCHED_DL_FH_NO_CARRIER);
-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;
+ return NULL;
}
+static void bts_sched_init_buffers(struct gsm_bts *bts, const uint32_t fn)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
-/*
- * RX on uplink (indication to upper layer)
- */
-
-/* 3GPP TS 05.02, section 5.2.7 */
-#define RACH_EXT_TAIL_LEN 8
-#define RACH_SYNCH_SEQ_LEN 41
-
-enum rach_synch_seq_t {
- RACH_SYNCH_SEQ_UNKNOWN = -1,
- RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
- RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
- RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
- RACH_SYNCH_SEQ_NUM
-};
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx->pinst;
+ const struct phy_link *plink = pinst->phy_link;
-static struct value_string rach_synch_seq_names[] = {
- { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" },
- { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" },
- { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" },
- { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" },
- { 0, NULL },
-};
+ /* Advance frame number, so the PHY has more time to process bursts */
+ const uint32_t sched_fn = GSM_TDMA_FN_SUM(fn, plink->u.osmotrx.clock_advance);
-static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score)
-{
- sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN;
- enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0;
- int score[RACH_SYNCH_SEQ_NUM] = { 0 };
- int max_score = INT_MIN;
- int i, j;
-
- /* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
- static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
- [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
- [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
- [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
- };
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct trx_dl_burst_req *br = &pinst->u.osmotrx.br[tn];
- /* Get a multiplier for j-th bit of i-th synch. sequence */
-#define RACH_SYNCH_SEQ_MULT \
- (synch_seq_ref[i][j] == '1' ? -1 : 1)
-
- /* For each synch. sequence, count the bit match score. Since we deal with
- * soft-bits (-127...127), we sum the absolute values of matching ones,
- * and subtract the absolute values of different ones, so the resulting
- * score is more accurate than it could be with hard-bits. */
- for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) {
- for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++)
- score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j];
-
- /* Keep the maximum value updated */
- if (score[i] > max_score) {
- max_score = score[i];
- seq = i;
+ *br = (struct trx_dl_burst_req) {
+ .trx_num = trx->nr,
+ .fn = sched_fn,
+ .tn = tn,
+ };
}
}
- /* Calculate an approximate level of our confidence */
- if (best_score != NULL)
- *best_score = max_score;
+ /* Initialize all timeslots on C0/TRX0 with dummy burst */
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct phy_instance *pinst = bts->c0->pinst;
+ struct trx_dl_burst_req *br = &pinst->u.osmotrx.br[tn];
+ const struct gsm_bts_trx_ts *ts = &bts->c0->ts[tn];
- /* At least 1/3 of a synch. sequence shall match */
- if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3))
- return RACH_SYNCH_SEQ_UNKNOWN;
+ memcpy(br->burst, _sched_dummy_burst, GSM_BURST_LEN);
+ br->burst_len = GSM_BURST_LEN;
- return seq;
+ /* BCCH carrier power reduction for this timeslot */
+ br->att = ts->c0_power_red_db;
+ }
}
-int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+static void bts_sched_flush_buffers(struct gsm_bts *bts)
{
- struct osmo_phsap_prim l1sap;
- int n_errors, n_bits_total;
- uint16_t ra11;
- uint8_t ra;
- int rc;
-
- /* TSC (Training Sequence Code) is an optional parameter of the UL burst
- * indication. We need this information in order to decide whether an
- * Access Burst is 11-bit encoded or not (see OS#1854). If this information
- * is absent, we try to correlate the received synch. sequence with the
- * known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default
- * TS0 if it fails. */
- enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0;
- int best_score = 127 * RACH_SYNCH_SEQ_LEN;
-
- /* If chan != TRXC_RACH, this is a handover RACH, which is always encoded
- * as 8-bit and should contain the generic training sequence (TS0). */
- if (chan == TRXC_RACH) {
- if (bi->flags & TRX_BI_F_TS_INFO)
- synch_seq = (enum rach_synch_seq_t) bi->tsc;
- else
- synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score);
- }
+ const struct gsm_bts_trx *trx;
+ unsigned int tn;
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
- "Received%s RACH (%s): rssi=%d toa256=%d",
- (chan != TRXC_RACH) ? " handover" : "",
- get_value_string(rach_synch_seq_names, synch_seq),
- bi->rssi, bi->toa256);
- if (bi->flags & TRX_BI_F_CI_CB)
- LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb);
- else
- LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%",
- best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN));
- LOGPC(DL1P, LOGL_DEBUG, "\n");
-
- /* Compose a new L1SAP primitive */
- memset(&l1sap, 0x00, sizeof(l1sap));
- osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL);
- l1sap.u.rach_ind.chan_nr = trx_chan_desc[chan].chan_nr | bi->tn;
- l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0;
- l1sap.u.rach_ind.acc_delay_256bits = bi->toa256;
- l1sap.u.rach_ind.rssi = bi->rssi;
- l1sap.u.rach_ind.fn = bi->fn;
-
- /* Link quality is defined by C/I (Carrier-to-Interference ratio),
- * which has optional presence. If it's absent, report the
- * minimum acceptable value to pass L1SAP checks. */
- if (bi->flags & TRX_BI_F_CI_CB)
- l1sap.u.rach_ind.lqual_cb = bi->ci_cb;
- else
- l1sap.u.rach_ind.lqual_cb = l1t->trx->bts->min_qual_rach;
-
- /* Decode RACH depending on its synch. sequence */
- switch (synch_seq) {
- case RACH_SYNCH_SEQ_TS1:
- case RACH_SYNCH_SEQ_TS2:
- rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
- l1t->trx->bts->bsic, &n_errors, &n_bits_total);
- if (rc) {
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
- "Received bad Access Burst\n");
- return 0;
- }
-
- if (synch_seq == RACH_SYNCH_SEQ_TS1)
- l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1;
- else
- l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2;
-
- l1sap.u.rach_ind.is_11bit = 1;
- l1sap.u.rach_ind.ra = ra11;
- break;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ const struct phy_instance *pinst = trx->pinst;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
- case RACH_SYNCH_SEQ_TS0:
- default:
- /* Fall-back to the default TS0 if needed */
- if (synch_seq != RACH_SYNCH_SEQ_TS0) {
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
- "Falling-back to the default TS0\n");
- synch_seq = RACH_SYNCH_SEQ_TS0;
- }
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ const struct trx_dl_burst_req *br;
- rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
- l1t->trx->bts->bsic, &n_errors, &n_bits_total);
- if (rc) {
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
- "Received bad Access Burst\n");
- return 0;
+ br = &pinst->u.osmotrx.br[tn];
+ if (!br->burst_len)
+ continue;
+ trx_if_send_burst(l1h, br);
}
- l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
- l1sap.u.rach_ind.is_11bit = 0;
- l1sap.u.rach_ind.ra = ra;
- break;
+ /* Batch all timeslots into a single TRXD PDU */
+ trx_if_send_burst(l1h, NULL);
}
-
- l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
-
- /* 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, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+/* schedule one frame for a shadow timeslot, merge bursts */
+static void _sched_dl_shadow_burst(const struct gsm_bts_trx_ts *ts,
+ struct trx_dl_burst_req *br)
{
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->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;
- int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
- uint8_t *ci_cb_num = &chan_state->ci_cb_num;
- uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
- int n_errors, n_bits_total;
- int16_t lqual_cb;
- 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, chan, bid, bi);
-
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->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 = bi->fn;
- *rssi_sum = 0;
- *rssi_num = 0;
- *toa256_sum = 0;
- *toa_num = 0;
- *ci_cb_sum = 0;
- *ci_cb_num = 0;
- }
-
- /* update mask + RSSI */
- *mask |= (1 << bid);
- *rssi_sum += bi->rssi;
- (*rssi_num)++;
- *toa256_sum += bi->toa256;
- (*toa_num)++;
-
- /* C/I: Carrier-to-Interference ratio (in centiBels) */
- if (bi->flags & TRX_BI_F_CI_CB) {
- *ci_cb_sum += bi->ci_cb;
- (*ci_cb_num)++;
- }
-
- /* copy burst to buffer of 4 bursts */
- burst = *bursts_p + bid * 116;
- memcpy(burst, bi->burst + 3, 58);
- memcpy(burst + 58, bi->burst + 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 | bi->tn,
- chan_state, bi->rssi, bi->toa256);
- }
-
- /* wait until complete set of bursts */
- if (bid != 3)
- return 0;
+ struct l1sched_ts *l1ts = ts->priv;
+
+ /* For the shadow timeslots, physical channel type can be either
+ * GSM_PCHAN_TCH_{F,H} or GSM_PCHAN_NONE. Even if the primary
+ * timeslot is a dynamic timeslot, it's always a concrete value. */
+ if (ts->pchan == GSM_PCHAN_NONE)
+ return;
+
+ struct trx_dl_burst_req sbr = {
+ .trx_num = br->trx_num,
+ .fn = br->fn,
+ .tn = br->tn,
+ };
- /* check for complete set of bursts */
- if ((*mask & 0xf) != 0xf) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received incomplete data (%u/%u)\n",
- *first_fn, (*first_fn) % l1ts->mf_period);
+ _sched_dl_burst(l1ts, &sbr);
- /* we require first burst to have correct FN */
- if (!(*mask & 0x1)) {
- *mask = 0x0;
- return 0;
- }
+ if (br->burst_len != 0 && sbr.burst_len != 0) { /* Both present */
+ memcpy(br->burst + GSM_BURST_LEN, sbr.burst, GSM_BURST_LEN);
+ br->burst_len = 2 * GSM_BURST_LEN;
+ br->mod = TRX_MOD_T_AQPSK;
+ /* FIXME: SCPIR is hard-coded to 0 */
+ } else if (br->burst_len == 0) {
+ /* No primary burst, send shadow burst alone */
+ memcpy(br, &sbr, sizeof(sbr));
+ } else if (sbr.burst_len == 0) {
+ /* No shadow burst, send primary burst alone */
+ return;
}
- *mask = 0x0;
-
- /* decode */
- rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total);
- if (rc) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->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, bi->tn, *first_fn,
- trx_chan_desc[chan].chan_nr | bi->tn,
- n_errors, n_bits_total,
- *rssi_sum / *rssi_num,
- *toa256_sum / *toa_num);
- lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
- ber10k = compute_ber10k(n_bits_total, n_errors);
- return _sched_compose_ph_data_ind(l1t, bi->tn, *first_fn,
- chan, l2, l2_len,
- *rssi_sum / *rssi_num,
- *toa256_sum / *toa_num,
- lqual_cb, ber10k,
- PRES_INFO_UNKNOWN);
}
-/*! \brief a single PDTCH burst was received by the PHY, process it */
-int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+/* schedule all frames of all TRX for given FN */
+static void bts_sched_fn(struct gsm_bts *bts, const uint32_t fn)
{
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->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;
- int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
- uint8_t *ci_cb_num = &chan_state->ci_cb_num;
- uint8_t l2[EGPRS_0503_MAX_BYTES];
- int n_errors, n_bursts_bits, n_bits_total;
- int16_t lqual_cb;
- uint16_t ber10k;
- int rc;
-
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->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 = bi->fn;
- *rssi_sum = 0;
- *rssi_num = 0;
- *toa256_sum = 0;
- *toa_num = 0;
- *ci_cb_sum = 0;
- *ci_cb_num = 0;
- }
-
- /* update mask + rssi */
- *mask |= (1 << bid);
- *rssi_sum += bi->rssi;
- (*rssi_num)++;
- *toa256_sum += bi->toa256;
- (*toa_num)++;
-
- /* C/I: Carrier-to-Interference ratio (in centiBels) */
- if (bi->flags & TRX_BI_F_CI_CB) {
- *ci_cb_sum += bi->ci_cb;
- (*ci_cb_num)++;
- }
-
- /* copy burst to buffer of 4 bursts */
- if (bi->burst_len == EGPRS_BURST_LEN) {
- burst = *bursts_p + bid * 348;
- memcpy(burst, bi->burst + 9, 174);
- memcpy(burst + 174, bi->burst + 261, 174);
- n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
- } else {
- burst = *bursts_p + bid * 116;
- memcpy(burst, bi->burst + 3, 58);
- memcpy(burst + 58, bi->burst + 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, bi->tn, chan, bi->fn,
- "Received incomplete frame (%u/%u)\n",
- bi->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 ((bi->burst_len == 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, bi->tn, *first_fn,
- trx_chan_desc[chan].chan_nr | bi->tn,
- n_errors, n_bits_total,
- *rssi_sum / *rssi_num,
- *toa256_sum / *toa_num);
-
- if (rc <= 0) {
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
- "Received bad PDTCH (%u/%u)\n",
- bi->fn % l1ts->mf_period, l1ts->mf_period);
- return 0;
- }
-
- lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
- ber10k = compute_ber10k(n_bits_total, n_errors);
- return _sched_compose_ph_data_ind(l1t, bi->tn,
- *first_fn, chan, l2, rc,
- *rssi_sum / *rssi_num,
- *toa256_sum / *toa_num,
- lqual_cb, ber10k,
- PRES_INFO_BOTH);
-}
+ struct gsm_bts_trx *trx;
+ unsigned int tn;
-/*! \brief a single TCH/F burst was received by the PHY, process it */
-int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
-{
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->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 | bi->tn);
-
- /* handle rach, if handover rach detection is turned on */
- if (chan_state->ho_rach_detect == 1)
- return rx_rach_fn(l1t, chan, bid, bi);
-
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->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;
- }
+ /* Report interference measurements */
+ if (fn % 104 == 0) /* SACCH period */
+ bts_report_interf_meas(bts);
- /* clear burst */
- if (bid == 0) {
- memset(*bursts_p + 464, 0, 464);
- *mask = 0x0;
- *first_fn = bi->fn;
- }
+ /* send time indication */
+ l1if_mph_time_ind(bts, fn);
- /* update mask */
- *mask |= (1 << bid);
+ /* Initialize Downlink burst buffers */
+ bts_sched_init_buffers(bts, fn);
- /* copy burst to end of buffer of 8 bursts */
- burst = *bursts_p + bid * 116 + 464;
- memcpy(burst, bi->burst + 3, 58);
- memcpy(burst + 58, bi->burst + 87, 58);
+ /* Populate Downlink burst buffers for each TRX/TS */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ const struct phy_link *plink = trx->pinst->phy_link;
+ struct trx_l1h *l1h = trx->pinst->u.osmotrx.hdl;
- /* wait until complete set of bursts */
- if (bid != 3)
- return 0;
+ /* we don't schedule, if power is off */
+ if (!trx_if_powered(l1h))
+ continue;
- /* check for complete set of bursts */
- if ((*mask & 0xf) != 0xf) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received incomplete frame (%u/%u)\n",
- bi->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,
- (((bi->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 | bi->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, bi->tn, chan, bi->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, bi->tn, *first_fn,
- trx_chan_desc[chan].chan_nr | bi->tn,
- n_errors, n_bits_total,
- bi->rssi, bi->toa256);
-
- /* Check if the frame is bad */
- if (rc < 0) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received bad data (%u/%u)\n",
- bi->fn % l1ts->mf_period, l1ts->mf_period);
- bfi_flag = true;
- } else if (rc < 4) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received bad data (%u/%u) with invalid codec mode %d\n",
- bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
- bfi_flag = true;
- }
+ /* process every TS of TRX */
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct phy_instance *pinst = trx->pinst;
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ struct l1sched_ts *l1ts = ts->priv;
+ struct trx_dl_burst_req *br;
- if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
- osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
-
- if (bfi_flag)
- goto bfi;
-
- /* FACCH */
- if (rc == GSM_MACBLOCK_LEN) {
- uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
- _sched_compose_ph_data_ind(l1t, bi->tn,
- /* FIXME: this calculation is wrong */
- (bi->fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan,
- tch_data + amr, GSM_MACBLOCK_LEN,
- /* FIXME: AVG RSSI and ToA256 */
- bi->rssi, bi->toa256,
- 0 /* FIXME: AVG C/I */,
- ber10k, PRES_INFO_UNKNOWN);
-bfi:
- if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
- /* indicate bad frame */
- if (lchan->tch.dtx.ul_sid) {
- /* DTXu: pause in progress. Push empty payload to upper layers */
- rc = 0;
- goto compose_l1sap;
+ /* ready-to-send */
+ TRACE(OSMO_BTS_TRX_DL_RTS_START(trx->nr, tn, fn));
+ _sched_rts(l1ts, GSM_TDMA_FN_SUM(fn, plink->u.osmotrx.clock_advance
+ + plink->u.osmotrx.rts_advance));
+ TRACE(OSMO_BTS_TRX_DL_RTS_DONE(trx->nr, tn, fn));
+
+ /* pre-initialized buffer for the Downlink burst */
+ br = &pinst->u.osmotrx.br[tn];
+
+ /* resolve PHY instance if freq. hopping is enabled */
+ if (ts->hopping.enabled) {
+ pinst = dlfh_route_br(br, ts);
+ if (pinst == NULL)
+ continue;
+ /* simply use a different buffer */
+ br = &pinst->u.osmotrx.br[tn];
}
- /* If there is an ECU active on this channel, use its output */
- if (lchan->ecu_state) {
- rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
- goto compose_l1sap;
- }
+ /* get burst for the primary timeslot */
+ _sched_dl_burst(l1ts, br);
- switch (tch_mode) {
- case GSM48_CMODE_SPEECH_V1: /* FR */
- 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) {
- LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
- "Failed to encode AMR_BAD frame (rc=%d), "
- "not sending BFI\n", rc);
- return -EINVAL;
- }
- memset(tch_data + 2, 0, rc - 2);
- break;
- default:
- LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
- "TCH mode %u invalid, please fix!\n", tch_mode);
- return -EINVAL;
- }
+ /* get burst for the shadow timeslot */
+ _sched_dl_shadow_burst(ts->vamos.peer, br);
}
}
- if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
- return 0;
-
- /* TCH or BFI */
-compose_l1sap:
- return _sched_compose_tch_ind(l1t, bi->tn,
- /* FIXME: this calculation is wrong */
- (bi->fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan,
- tch_data, rc);
+ /* Send everything to the PHY */
+ bts_sched_flush_buffers(bts);
}
-/*! \brief a single TCH/H burst was received by the PHY, process it */
-int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+/* Find a route (TRX instance) for a given Uplink burst indication */
+static struct gsm_bts_trx *ulfh_route_bi(const struct trx_ul_burst_ind *bi,
+ const struct gsm_bts_trx *src_trx)
{
- struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->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 | bi->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 = (((bi->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, chan, bid, bi);
-
- LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->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 = bi->fn;
- }
-
- /* update mask */
- *mask |= (1 << bid);
-
- /* copy burst to end of buffer of 6 bursts */
- burst = *bursts_p + bid * 116 + 464;
- memcpy(burst, bi->burst + 3, 58);
- memcpy(burst + 58, bi->burst + 87, 58);
+ struct gsm_bts_trx *trx;
+ struct gsm_time time;
+ uint16_t arfcn;
- /* wait until complete set of bursts */
- if (bid != 1)
- return 0;
+ gsm_fn2gsmtime(&time, bi->fn);
- /* check for complete set of bursts */
- if ((*mask & 0x3) != 0x3) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received incomplete frame (%u/%u)\n",
- bi->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;
- }
+ llist_for_each_entry(trx, &src_trx->bts->trx_list, list) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[bi->tn];
+ if (!ts->hopping.enabled)
+ continue;
- /* 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 >= 0) /* 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 | bi->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, bi->tn, chan, bi->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, bi->tn,
- *first_fn /* FIXME: this is wrong */,
- trx_chan_desc[chan].chan_nr | bi->tn,
- n_errors, n_bits_total, bi->rssi, bi->toa256);
-
- /* Check if the frame is bad */
- if (rc < 0) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received bad data (%u/%u)\n",
- bi->fn % l1ts->mf_period, l1ts->mf_period);
- bfi_flag = true;
- } else if (rc < 4) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
- "Received bad data (%u/%u) with invalid codec mode %d\n",
- bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
- bfi_flag = true;
+ arfcn = gsm0502_hop_seq_gen(&time, SCHED_FH_PARAMS_VALS(ts), ts->hopping.arfcn_list);
+ if (src_trx->arfcn == arfcn)
+ return trx;
}
- if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
- osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
-
- if (bfi_flag)
- 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, bi->tn,
- /* FIXME: what the hell is this?!? */
- (bi->fn + GSM_HYPERFRAME - 10 - ((bi->fn % 26) >= 19)) % GSM_HYPERFRAME, chan,
- tch_data + amr, GSM_MACBLOCK_LEN,
- /* FIXME: AVG both RSSI and ToA */
- bi->rssi, bi->toa256,
- 0 /* FIXME: AVG C/I */,
- ber10k, PRES_INFO_UNKNOWN);
-bfi:
- /* FIXME: a FACCH/H frame replaces two speech frames,
- * so we actually need to send two bad frame indications! */
- if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
- /* indicate bad frame */
- if (lchan->tch.dtx.ul_sid) {
- /* DTXu: pause in progress. Push empty payload to upper layers */
- rc = 0;
- goto compose_l1sap;
- }
+ LOGPTRX(src_trx, DL1C, LOGL_DEBUG, "Failed to find the transceiver (RF carrier) "
+ "for an Uplink burst (fn=%u, tn=%u, " SCHED_FH_PARAMS_FMT ")\n",
+ bi->fn, bi->tn, SCHED_FH_PARAMS_VALS(&src_trx->ts[bi->tn]));
- /* If there is an ECU active on this channel, use its output */
- if (lchan->ecu_state) {
- rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
- goto compose_l1sap;
- }
+ struct bts_trx_priv *priv = (struct bts_trx_priv *) src_trx->bts->model_priv;
+ rate_ctr_inc2(priv->ctrs, BTSTRX_CTR_SCHED_UL_FH_NO_CARRIER);
- switch (tch_mode) {
- case GSM48_CMODE_SPEECH_V1: /* HR */
- 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) {
- LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
- "Failed to encode AMR_BAD frame (rc=%d), "
- "not sending BFI\n", rc);
- return -EINVAL;
- }
- memset(tch_data + 2, 0, rc - 2);
- break;
- default:
- LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->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, bi->tn,
- /* FIXME: what the hell is this?!? */
- (bi->fn + GSM_HYPERFRAME - 10 - ((bi->fn%26)==19) - ((bi->fn%26)==20)) % GSM_HYPERFRAME,
- chan, tch_data, rc);
+ return NULL;
}
-/* schedule all frames of all TRX for given FN */
-static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn)
+/* Route a given Uplink burst indication to the scheduler depending on freq. hopping state */
+int trx_sched_route_burst_ind(const struct gsm_bts_trx *trx, struct trx_ul_burst_ind *bi)
{
- 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);
+ /* no frequency hopping => nothing to do */
+ if (!trx->ts[bi->tn].hopping.enabled)
+ return trx_sched_ul_burst(trx->ts[bi->tn].priv, bi);
- /* 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;
+ trx = ulfh_route_bi(bi, trx);
+ if (trx == NULL)
+ return -ENODEV;
- /* 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;
+ return trx_sched_ul_burst(trx->ts[bi->tn].priv, bi);
}
-/*
- * 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*/
@@ -1641,9 +389,9 @@ static inline int64_t compute_elapsed_us(const struct timespec *last, const stru
/*! 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;
+ int elapsed_fn = GSM_TDMA_FN_SUB(now, last);
if (elapsed_fn >= 135774)
- elapsed_fn -= GSM_HYPERFRAME;
+ elapsed_fn -= GSM_TDMA_HYPERFRAME;
return elapsed_fn;
}
@@ -1654,22 +402,18 @@ static inline void normalize_timespec(struct timespec *ts)
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 bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv;
+ struct osmo_trx_clock_state *tcs = &bts_trx->clk_s;
struct timespec tv_now;
uint64_t expire_count;
int64_t elapsed_us, error_us;
int rc, i;
- if (!(what & BSC_FD_READ))
+ if (!(what & OSMO_FD_READ))
return 0;
/* read from timerfd: number of expirations of periodic timer */
@@ -1680,7 +424,8 @@ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what)
if (expire_count > 1) {
LOGP(DL1C, LOGL_NOTICE, "FN timer expire_count=%"PRIu64": We missed %"PRIu64" timers\n",
- expire_count, expire_count-1);
+ expire_count, expire_count - 1);
+ rate_ctr_add(rate_ctr_group_get_ctr(bts_trx->ctrs, BTSTRX_CTR_SCHED_DL_MISS_FN), expire_count - 1);
}
/* check if transceiver is still alive */
@@ -1692,7 +437,7 @@ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what)
/* 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;
+ error_us = elapsed_us - GSM_TDMA_FN_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);
@@ -1700,45 +445,83 @@ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what)
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) {
+ if (elapsed_us > GSM_TDMA_FN_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);
- }
+ /* call bts_sched_fn() for all expired FN */
+ for (i = 0; i < expire_count; i++)
+ bts_sched_fn(bts, GSM_TDMA_FN_INC(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;
+}
+/*! \brief This is the cb of the initial timer set upon start. On timeout, it
+ * means it wasn't replaced and hence no CLOCK IND was received. */
+static int trx_start_noclockind_to_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct gsm_bts *bts = ofd->data;
+ struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv;
+ struct osmo_trx_clock_state *tcs = &bts_trx->clk_s;
+
+ osmo_fd_close(&tcs->fn_timer_ofd); /* Avoid being called again */
+ bts_shutdown(bts, "No clock since TRX was started");
return -1;
}
+/*! \brief PHY informs us clock indications should start to be received */
+int trx_sched_clock_started(struct gsm_bts *bts)
+{
+ struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv;
+ struct osmo_trx_clock_state *tcs = &bts_trx->clk_s;
+ const struct timespec it_val = {3, 0};
+ const struct timespec it_intval = {0, 0};
+
+ LOGP(DL1C, LOGL_NOTICE, "GSM clock started, waiting for clock indications\n");
+ osmo_fd_close(&tcs->fn_timer_ofd);
+ memset(tcs, 0, sizeof(*tcs));
+ tcs->fn_timer_ofd.fd = -1;
+ /* Set up timeout to shutdown BTS if no clock ind is received in a few
+ * seconds. Upon clock ind receival, fn_timer_ofd will be reused and
+ * timeout won't trigger.
+ */
+ osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_start_noclockind_to_cb, bts);
+ osmo_timerfd_schedule(&tcs->fn_timer_ofd, &it_val, &it_intval);
+ return 0;
+}
+
+/*! \brief PHY informs us no more clock indications should be received anymore */
+int trx_sched_clock_stopped(struct gsm_bts *bts)
+{
+ struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv;
+ struct osmo_trx_clock_state *tcs = &bts_trx->clk_s;
+
+ LOGP(DL1C, LOGL_NOTICE, "GSM clock stopped\n");
+ osmo_fd_close(&tcs->fn_timer_ofd);
+
+ return 0;
+}
+
/*! 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.fn = fn;
tcs->last_fn_timer.tv = *tv_now;
- tcs->last_clk_ind.tv = *tv_now;
- tcs->last_clk_ind.fn = fn;
+ /* call trx scheduler function for new 'last' FN */
+ bts_sched_fn(bts, tcs->last_fn_timer.fn);
return 0;
}
@@ -1746,36 +529,19 @@ static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs
/*! 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 bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv;
+ struct osmo_trx_clock_state *tcs = &bts_trx->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;
+ const struct timespec interval = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS };
/* 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);
@@ -1792,7 +558,7 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn)
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);
+ error_us_since_clk = elapsed_us_since_clk - (GSM_TDMA_FN_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);
@@ -1813,14 +579,14 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn)
}
LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %" PRId64 "us (elapsed_fn=%d)\n",
- elapsed_fn * FRAME_DURATION_uS - elapsed_us, elapsed_fn);
+ elapsed_fn * GSM_TDMA_FN_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;
+ first.tv_nsec += (0 - elapsed_fn) * GSM_TDMA_FN_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 */
@@ -1830,8 +596,7 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn)
/* 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);
+ bts_sched_fn(bts, GSM_TDMA_FN_INC(tcs->last_fn_timer.fn));
fn_caught_up++;
}
@@ -1843,9 +608,9 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn)
return 0;
}
-void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
+void _sched_act_rach_det(struct gsm_bts_trx *trx, uint8_t tn, uint8_t ss, int activate)
{
- struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+ struct phy_instance *pinst = trx_phy_instance(trx);
struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
if (activate)
@@ -1853,3 +618,92 @@ void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int ac
else
trx_if_cmd_nohandover(l1h, tn, ss);
}
+
+/* Add a set of UL burst measurements to the history */
+void trx_sched_meas_push(struct l1sched_chan_state *chan_state,
+ const struct trx_ul_burst_ind *bi)
+{
+ unsigned int hist_size = ARRAY_SIZE(chan_state->meas.buf);
+ unsigned int current = chan_state->meas.current;
+
+ chan_state->meas.buf[current] = (struct l1sched_meas_set) {
+ .fn = bi->fn,
+ .ci_cb = (bi->flags & TRX_BI_F_CI_CB) ? bi->ci_cb : 0,
+ .toa256 = bi->toa256,
+ .rssi = bi->rssi,
+ };
+
+ chan_state->meas.current = (current + 1) % hist_size;
+}
+
+/* Measurement averaging mode sets: [MODE] = { SHIFT, NUM } */
+static const uint8_t trx_sched_meas_modeset[][2] = {
+ [SCHED_MEAS_AVG_M_S24N22] = { 24, 22 },
+ [SCHED_MEAS_AVG_M_S22N22] = { 22, 22 },
+ [SCHED_MEAS_AVG_M_S4N4] = { 4, 4 },
+ [SCHED_MEAS_AVG_M_S8N8] = { 8, 8 },
+ [SCHED_MEAS_AVG_M_S6N4] = { 6, 4 },
+ [SCHED_MEAS_AVG_M_S6N6] = { 6, 6 },
+ [SCHED_MEAS_AVG_M_S8N4] = { 8, 4 },
+ [SCHED_MEAS_AVG_M_S6N2] = { 6, 2 },
+ [SCHED_MEAS_AVG_M_S4N2] = { 4, 2 },
+};
+
+/* Calculate the AVG of n measurements from the history */
+void trx_sched_meas_avg(const struct l1sched_chan_state *chan_state,
+ struct l1sched_meas_set *avg,
+ enum sched_meas_avg_mode mode)
+{
+ unsigned int hist_size = ARRAY_SIZE(chan_state->meas.buf);
+ unsigned int current = chan_state->meas.current;
+ const struct l1sched_meas_set *set;
+ unsigned int pos, i;
+
+ float rssi_sum = 0;
+ int toa256_sum = 0;
+ int ci_cb_sum = 0;
+
+ const unsigned int shift = trx_sched_meas_modeset[mode][0];
+ const unsigned int num = trx_sched_meas_modeset[mode][1];
+
+ /* Calculate the sum of n entries starting from pos */
+ for (i = 0; i < num; i++) {
+ pos = (current + hist_size - shift + i) % hist_size;
+ set = &chan_state->meas.buf[pos];
+
+ rssi_sum += set->rssi;
+ toa256_sum += set->toa256;
+ ci_cb_sum += set->ci_cb;
+ }
+
+ /* First sample contains TDMA frame number of the first burst */
+ pos = (current + hist_size - shift) % hist_size;
+ set = &chan_state->meas.buf[pos];
+
+ /* Calculate the average for each value */
+ *avg = (struct l1sched_meas_set) {
+ .fn = set->fn, /* first burst */
+ .rssi = (rssi_sum / num),
+ .toa256 = (toa256_sum / num),
+ .ci_cb = (ci_cb_sum / num),
+ };
+
+ LOGP(DMEAS, LOGL_DEBUG, "%s%sMeasurement AVG (num=%u, shift=%u): "
+ "RSSI %f, ToA256 %d, C/I %d cB\n",
+ chan_state->lchan ? gsm_lchan_name(chan_state->lchan) : "",
+ chan_state->lchan ? " " : "",
+ num, shift, avg->rssi, avg->toa256, avg->ci_cb);
+}
+
+/* Lookup TDMA frame number of the N-th sample in the history */
+uint32_t trx_sched_lookup_fn(const struct l1sched_chan_state *chan_state,
+ const unsigned int shift)
+{
+ const unsigned int hist_size = ARRAY_SIZE(chan_state->meas.buf);
+ const unsigned int current = chan_state->meas.current;
+ unsigned int pos;
+
+ /* First sample contains TDMA frame number of the first burst */
+ pos = (current + hist_size - shift) % hist_size;
+ return chan_state->meas.buf[pos].fn;
+}
diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c
index 6c6d5ad9..b6b20e94 100644
--- a/src/osmo-bts-trx/trx_if.c
+++ b/src/osmo-bts-trx/trx_if.c
@@ -8,6 +8,7 @@
* Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu>
* Copyright (C) 2016-2017 Harald Welte <laforge@gnumonks.org>
* Copyright (C) 2019 Vadim Yanitskiy <axilirator@gmail.com>
+ * Copyright (C) 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -39,6 +40,7 @@
#include <osmocom/core/timer.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/bits.h>
+#include <osmocom/core/fsm.h>
#include <osmo-bts/phy_link.h>
#include <osmo-bts/logging.h>
@@ -47,8 +49,20 @@
#include "l1_if.h"
#include "trx_if.h"
+#include "trx_provision_fsm.h"
-int transceiver_available = 0;
+#include "btsconfig.h"
+
+#ifdef HAVE_SYSTEMTAP
+/* include the generated probes header and put markers in code */
+#include "probes.h"
+#define TRACE(probe) probe
+#define TRACE_ENABLED(probe) probe ## _ENABLED()
+#else
+/* Wrap the probe to allow it to be removed when no systemtap available */
+#define TRACE(probe)
+#define TRACE_ENABLED(probe) (0)
+#endif /* HAVE_SYSTEMTAP */
/*
* socket helper functions
@@ -95,15 +109,19 @@ 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;
+ char buf[TRXC_MSG_BUF_SIZE];
+ ssize_t len;
uint32_t fn;
OSMO_ASSERT(pinst);
len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
- if (len <= 0)
+ if (len <= 0) {
+ strerror_r(errno, (char *)buf, sizeof(buf));
+ LOGPPHI(pinst, DTRX, LOGL_ERROR,
+ "recv() failed on TRXD with rc=%zd (%s)\n", len, buf);
return len;
+ }
buf[len] = '\0';
if (!!strncmp(buf, "IND CLOCK ", 10)) {
@@ -119,12 +137,16 @@ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
LOGPPHI(pinst, DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn);
- if (fn >= GSM_HYPERFRAME) {
- fn %= GSM_HYPERFRAME;
+ if (fn >= GSM_TDMA_HYPERFRAME) {
+ fn %= GSM_TDMA_HYPERFRAME;
LOGPPHI(pinst, DTRX, LOGL_ERROR, "Indicated clock's FN is not "
"wrapping correctly, correcting to fn=%u\n", fn);
}
+ if (!plink->u.osmotrx.powered) {
+ LOGPPHI(pinst, DTRX, LOGL_NOTICE, "Ignoring CLOCK IND %u, TRX not yet powered on\n", fn);
+ return 0;
+ }
/* inform core TRX clock handling code that a FN has been received */
trx_sched_clock(pinst->trx->bts, fn);
@@ -140,8 +162,9 @@ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
static void trx_ctrl_send(struct trx_l1h *l1h)
{
struct trx_ctrl_msg *tcm;
- char buf[1500];
+ char buf[TRXC_MSG_BUF_SIZE];
int len;
+ ssize_t snd_len;
/* get first command */
if (llist_empty(&l1h->trx_ctrl_list))
@@ -153,7 +176,12 @@ static void trx_ctrl_send(struct trx_l1h *l1h)
LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Sending control '%s'\n", buf);
/* send command */
- send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0);
+ snd_len = send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0);
+ if (snd_len <= 0) {
+ strerror_r(errno, (char *)buf, sizeof(buf));
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
+ "send() failed on TRXC with rc=%zd (%s)\n", snd_len, buf);
+ }
/* start timer */
osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0);
@@ -179,36 +207,32 @@ void trx_if_init(struct trx_l1h *l1h)
{
l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
l1h->trx_ctrl_timer.data = l1h;
+
+ /* initialize ctrl queue */
+ INIT_LLIST_HEAD(&l1h->trx_ctrl_list);
+
+ l1h->trx_ofd_ctrl.fd = -1;
+ l1h->trx_ofd_data.fd = -1;
}
/*! Send a new TRX control command.
* \param[inout] l1h TRX Layer1 handle to which to send command
- * \param[in] criticial
+ * \param[in] critical
* \param[in] cb callback function to be called when valid response is
* received. Type of cb depends on type of message.
* \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
+ * The new command will be added to the end of the control command
* queue.
*/
-static int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb, const char *cmd,
- const char *fmt, ...)
+int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb,
+ 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"))) {
- LOGPPHI(l1h->phy_inst, 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);
@@ -231,71 +255,60 @@ static int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb, const ch
tcm->cb = cb;
/* Avoid adding consecutive duplicate messages, eg: two consecutive POWEROFF */
- if(pending)
+ if (!llist_empty(&l1h->trx_ctrl_list))
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)) {
- LOGPPHI(l1h->phy_inst, 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);
+ if (prev != NULL && !strcmp(tcm->cmd, prev->cmd)
+ && !strcmp(tcm->params, prev->params)) {
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG,
+ "Not sending duplicate command '%s'\n", tcm->cmd);
+ talloc_free(tcm);
+ return 0;
}
- /* send message, if we didn't already have pending messages */
- if (!pending)
+ LOGPPHI(l1h->phy_inst, 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 we are in the rx_rsp callback code path, skip sending, the
+ * callback will do so when returning to it. */
+ if (prev == NULL && !l1h->in_trx_ctrl_read_cb)
trx_ctrl_send(l1h);
return 0;
}
-#define trx_ctrl_cmd(l1h, critical, cmd, fmt, ...) trx_ctrl_cmd_cb(l1h, critical, NULL, cmd, fmt, ##__VA_ARGS__)
/*! Send "POWEROFF" command to TRX */
-int trx_if_cmd_poweroff(struct trx_l1h *l1h)
+int trx_if_cmd_poweroff(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb)
{
- struct phy_instance *pinst = l1h->phy_inst;
- if (pinst->num == 0)
- return trx_ctrl_cmd(l1h, 1, "POWEROFF", "");
- else
- return 0;
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "POWEROFF", "");
}
/*! Send "POWERON" command to TRX */
-int trx_if_cmd_poweron(struct trx_l1h *l1h)
+int trx_if_cmd_poweron(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb)
{
- struct phy_instance *pinst = l1h->phy_inst;
- if (pinst->num == 0)
- return trx_ctrl_cmd(l1h, 1, "POWERON", "");
- else
- return 0;
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "POWERON", "");
}
-/*! Send "SETFORMAT" command to TRX: change TRXD header format version */
-int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver)
+/*! Send "SETFORMAT" command to TRX: change TRXD PDU version */
+int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver, trx_if_cmd_generic_cb *cb)
{
LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO,
- "Requesting TRXD header format version %u\n", ver);
+ "Requesting TRXD PDU version %u\n", ver);
- return trx_ctrl_cmd(l1h, 0, "SETFORMAT", "%u", ver);
+ return trx_ctrl_cmd_cb(l1h, 0, cb, "SETFORMAT", "%u", ver);
}
/*! Send "SETTSC" command to TRX */
-int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc)
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc, trx_if_cmd_generic_cb *cb)
{
- 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);
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "SETTSC", "%d", tsc);
}
/*! Send "SETBSIC" command to TRX */
-int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic)
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic, trx_if_cmd_generic_cb *cb)
{
- 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);
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "SETBSIC", "%d", bsic);
}
/*! Send "SETRXGAIN" command to TRX */
@@ -304,10 +317,16 @@ int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db)
return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db);
}
+/*! Send "NOMTXPOWER" command to TRX */
+int trx_if_cmd_getnompower(struct trx_l1h *l1h, trx_if_cmd_getnompower_cb *cb)
+{
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "NOMTXPOWER", "");
+}
+
/*! Send "SETPOWER" command to TRX */
-int trx_if_cmd_setpower(struct trx_l1h *l1h, int db)
+int trx_if_cmd_setpower_att(struct trx_l1h *l1h, int power_att_db, trx_if_cmd_setpower_att_cb *cb)
{
- return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db);
+ return trx_ctrl_cmd_cb(l1h, 0, cb, "SETPOWER", "%d", power_att_db);
}
/*! Send "SETMAXDLY" command to TRX, i.e. maximum delay for RACH bursts */
@@ -322,14 +341,27 @@ 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, trx_if_cmd_setslot_cb *cb)
+/*! Send "SETSLOT" command to TRX: Configure Channel Combination and TSC for TS */
+int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn,
+ trx_if_cmd_setslot_cb *cb)
{
- return trx_ctrl_cmd_cb(l1h, 1, cb, "SETSLOT", "%d %d", tn, type);
+ const struct trx_config *cfg = &l1h->config;
+ const struct phy_instance *pinst = l1h->phy_inst;
+
+ if (cfg->setslot[tn].tsc_valid && cfg->setslot[tn].tsc_val != BTS_TSC(pinst->trx->bts)) {
+ /* PHY is instructed to use a custom TSC */
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "SETSLOT", "%u %u C%u/S%u",
+ tn, cfg->setslot[tn].slottype,
+ cfg->setslot[tn].tsc_val,
+ cfg->setslot[tn].tsc_set);
+ } else { /* PHY is instructed to use the default TSC from 'SETTSC' */
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "SETSLOT", "%u %u",
+ tn, cfg->setslot[tn].slottype);
+ }
}
/*! Send "RXTUNE" command to TRX: Tune Receiver to given ARFCN */
-int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn)
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb)
{
struct phy_instance *pinst = l1h->phy_inst;
uint16_t freq10;
@@ -344,11 +376,11 @@ int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn)
return -ENOTSUP;
}
- return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100);
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "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)
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb)
{
struct phy_instance *pinst = l1h->phy_inst;
uint16_t freq10;
@@ -363,7 +395,7 @@ int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn)
return -ENOTSUP;
}
- return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100);
+ return trx_ctrl_cmd_cb(l1h, 1, cb, "TXTUNE", "%d", freq10 * 100);
}
/*! Send "HANDOVER" command to TRX: Enable handover RACH Detection on timeslot/sub-slot */
@@ -378,6 +410,12 @@ 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);
}
+/*! Send "RFMUTE" command to TRX: Mute or Unmute RF transmission */
+int trx_if_cmd_rfmute(struct trx_l1h *l1h, bool mute)
+{
+ return trx_ctrl_cmd(l1h, 0, "RFMUTE", mute ? "1" : "0");
+}
+
struct trx_ctrl_rsp {
char cmd[50];
char params[100];
@@ -387,6 +425,7 @@ struct trx_ctrl_rsp {
static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp)
{
+ size_t nlen, plen;
char *p, *k;
if (strncmp(buf_in, "RSP ", 4))
@@ -396,14 +435,17 @@ static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp
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));
+ /* Calculate length of the name part */
+ nlen = p - (buf_in + 4);
+
+ if (nlen >= sizeof(rsp->cmd)) {
+ LOGP(DTRX, LOGL_ERROR, "TRXC command name part is too long: "
+ "%zu >= %zu\n", nlen, sizeof(rsp->cmd));
goto parse_err;
}
- rsp->cmd[0] = '\0';
- strncat(rsp->cmd, buf_in + 4, p - buf_in - 4);
+ memcpy(&rsp->cmd[0], buf_in + 4, nlen);
+ rsp->cmd[nlen] = '\0';
/* Now comes the status code of the response */
p++;
@@ -417,18 +459,22 @@ static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp
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));
+ /* Calculate length of the parameters part */
+ plen = strlen(k);
+
+ if (plen >= sizeof(rsp->params)) {
+ LOGP(DTRX, LOGL_ERROR, "TRXC command parameters part is too long: "
+ "%zu >= %zu\n", plen, sizeof(rsp->params));
goto parse_err;
}
- rsp->params[0] = '\0';
- strcat(rsp->params, k);
+
+ memcpy(&rsp->params[0], k, plen);
+ rsp->params[plen] = '\0';
+
return 0;
parse_err:
- LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n",
- buf_in);
+ LOGP(DTRX, LOGL_NOTICE, "Unknown TRXC message: %s\n", buf_in);
return -1;
}
@@ -448,6 +494,35 @@ static bool cmd_matches_rsp(struct trx_ctrl_msg *tcm, struct trx_ctrl_rsp *rsp)
return true;
}
+static int trx_ctrl_rx_rsp_poweron(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp)
+{
+ trx_if_cmd_poweronoff_cb *cb = (trx_if_cmd_poweronoff_cb*) rsp->cb;
+
+ if (rsp->status != 0)
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE,
+ "transceiver rejected POWERON command (%d), re-trying in a few seconds\n",
+ rsp->status);
+
+ if (cb)
+ cb(l1h, true, rsp->status);
+
+ /* If TRX fails, try again after 5 sec */
+ return rsp->status == 0 ? 0 : 5;
+}
+
+static int trx_ctrl_rx_rsp_poweroff(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp)
+{
+ trx_if_cmd_poweronoff_cb *cb = (trx_if_cmd_poweronoff_cb*) rsp->cb;
+
+ if (rsp->status == 0) {
+ if (cb)
+ cb(l1h, false, rsp->status);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
static int trx_ctrl_rx_rsp_setslot(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp)
{
trx_if_cmd_setslot_cb *cb = (trx_if_cmd_setslot_cb*) rsp->cb;
@@ -471,7 +546,7 @@ static int trx_ctrl_rx_rsp_setslot(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp
return rsp->status == 0 ? 0 : -EINVAL;
}
-/* TRXD header format negotiation handler.
+/* TRXD PDU format negotiation handler.
*
* If the transceiver does not support the format negotiation, it would
* reject SETFORMAT with 'RSP ERR 1'. If the requested version is not
@@ -481,42 +556,72 @@ static int trx_ctrl_rx_rsp_setslot(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp
static int trx_ctrl_rx_rsp_setformat(struct trx_l1h *l1h,
struct trx_ctrl_rsp *rsp)
{
+ trx_if_cmd_generic_cb *cb;
+
/* Old transceivers reject 'SETFORMAT' with 'RSP ERR 1' */
if (strcmp(rsp->cmd, "SETFORMAT") != 0) {
LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE,
"Transceiver rejected the format negotiation command, "
- "using legacy TRXD header format version (0)\n");
- l1h->config.trxd_hdr_ver_use = 0;
+ "using legacy TRXD PDU version (0)\n");
+ if (rsp->cb) {
+ cb = (trx_if_cmd_generic_cb*) rsp->cb;
+ cb(l1h, 0);
+ }
return 0;
}
/* Status shall indicate a proper version supported by the transceiver */
- if (rsp->status < 0 || rsp->status > l1h->config.trxd_hdr_ver_req) {
+ if (rsp->status < 0 || rsp->status > l1h->config.trxd_pdu_ver_req) {
LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
"Transceiver indicated an out of range "
- "header format version %d (requested %u)\n",
- rsp->status, l1h->config.trxd_hdr_ver_req);
+ "PDU version %d (requested %u)\n",
+ rsp->status, l1h->config.trxd_pdu_ver_req);
return -EINVAL;
}
- /* Transceiver may suggest a lower version (than requested) */
- if (rsp->status == l1h->config.trxd_hdr_ver_req) {
- l1h->config.trxd_hdr_ver_use = rsp->status;
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO,
- "Using TRXD header format version %u\n",
- l1h->config.trxd_hdr_ver_use);
- } else {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG,
- "Transceiver suggests TRXD header version %u (requested %u)\n",
- rsp->status, l1h->config.trxd_hdr_ver_req);
- /* Send another SETFORMAT with suggested version */
- l1h->config.trxd_hdr_ver_req = rsp->status;
- trx_if_cmd_setformat(l1h, rsp->status);
+ if (rsp->cb) {
+ cb = (trx_if_cmd_generic_cb*) rsp->cb;
+ cb(l1h, rsp->status);
}
return 0;
}
+static int trx_ctrl_rx_rsp_nomtxpower(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp)
+{
+ trx_if_cmd_getnompower_cb *cb = (trx_if_cmd_getnompower_cb*) rsp->cb;
+ struct phy_instance *pinst = l1h->phy_inst;
+ int nominal_power;
+
+ if (rsp->status)
+ LOGPPHI(pinst, DTRX, LOGL_ERROR, "transceiver NOMTXPOWER failed "
+ "with status %d. If your transceiver doesn't support this "
+ "command, then please set the nominal transmit power manually "
+ "through VTY cmd 'nominal-tx-power'.\n",
+ rsp->status);
+ if (cb) {
+ sscanf(rsp->params, "%d", &nominal_power);
+ cb(l1h, nominal_power, rsp->status);
+ }
+ return 0;
+}
+
+static int trx_ctrl_rx_rsp_setpower(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp)
+{
+ trx_if_cmd_setpower_att_cb *cb = (trx_if_cmd_setpower_att_cb*) rsp->cb;
+ struct phy_instance *pinst = l1h->phy_inst;
+ int power_att;
+
+ if (rsp->status)
+ LOGPPHI(pinst, DTRX, LOGL_ERROR, "transceiver SETPOWER failed with status %d\n",
+ rsp->status);
+ if (cb) {
+ sscanf(rsp->params, "%d", &power_att);
+ cb(l1h, power_att, rsp->status);
+ }
+ return 0;
+}
+
/* -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
@@ -525,28 +630,28 @@ static int trx_ctrl_rx_rsp(struct trx_l1h *l1h,
struct trx_ctrl_rsp *rsp,
struct trx_ctrl_msg *tcm)
{
- struct phy_instance *pinst = l1h->phy_inst;
+ trx_if_cmd_generic_cb *cb;
- /* 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 {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE,
- "transceiver rejected POWERON command (%d), re-trying in a few seconds\n",
- rsp->status);
- if (pinst->phy_link->state != PHY_LINK_SHUTDOWN)
- phy_link_state_set(pinst->phy_link, PHY_LINK_SHUTDOWN);
- return 5;
- }
+ return trx_ctrl_rx_rsp_poweron(l1h, rsp);
+ } else if (strcmp(rsp->cmd, "POWEROFF") == 0) {
+ return trx_ctrl_rx_rsp_poweroff(l1h, rsp);
} else if (strcmp(rsp->cmd, "SETSLOT") == 0) {
return trx_ctrl_rx_rsp_setslot(l1h, rsp);
/* We may get 'RSP ERR 1' if 'SETFORMAT' is not supported,
* so that's why we should use tcm instead of rsp. */
} else if (strcmp(tcm->cmd, "SETFORMAT") == 0) {
return trx_ctrl_rx_rsp_setformat(l1h, rsp);
+ } else if (strcmp(tcm->cmd, "NOMTXPOWER") == 0) {
+ return trx_ctrl_rx_rsp_nomtxpower(l1h, rsp);
+ } else if (strcmp(tcm->cmd, "SETPOWER") == 0) {
+ return trx_ctrl_rx_rsp_setpower(l1h, rsp);
+ }
+
+ /* Generic callback if available */
+ if (rsp->cb) {
+ cb = (trx_if_cmd_generic_cb*) rsp->cb;
+ cb(l1h, rsp->status);
}
if (rsp->status) {
@@ -565,10 +670,11 @@ 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];
+ char buf[TRXC_MSG_BUF_SIZE];
struct trx_ctrl_rsp rsp;
int len, rc;
struct trx_ctrl_msg *tcm;
+ bool flushed;
len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
if (len <= 0)
@@ -581,8 +687,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
LOGPPHI(l1h->phy_inst, 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);
+ osmo_timer_del(&l1h->trx_ctrl_timer);
/* get command for response message */
if (llist_empty(&l1h->trx_ctrl_list)) {
@@ -619,21 +724,34 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
rsp.cb = tcm->cb;
/* check for response code */
+ l1h->in_trx_ctrl_read_cb = true;
rc = trx_ctrl_rx_rsp(l1h, &rsp, tcm);
+ /* Reset state: */
+ flushed = l1h->flushed_while_in_trx_ctrl_read_cb;
+ l1h->flushed_while_in_trx_ctrl_read_cb = false;
+ l1h->in_trx_ctrl_read_cb = false;
+
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);
+ /* The queue may have been flushed in the trx_ctrl_rx_rsp(): */
+ if (!llist_empty(&l1h->trx_ctrl_list))
+ 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;
+ if (!flushed) {
+ /* 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;
+ } /* else: tcm was freed by trx_if_flush(), do not access it. */
+
+ /* Send next message waiting in the list: */
trx_ctrl_send(l1h);
return 0;
@@ -649,93 +767,119 @@ rsp_error:
* TRX burst data socket
*/
-/* Maximum DATA message length (header + burst) */
-#define TRX_DATA_MSG_MAX_LEN 512
-
-/* Common header length: 1/2 VER + 1/2 TDMA TN + 4 TDMA FN */
-#define TRX_CHDR_LEN (1 + 4)
-/* Uplink v0 header length: 1 RSSI + 2 ToA256 */
-#define TRX_UL_V0HDR_LEN (TRX_CHDR_LEN + 1 + 2)
-/* Uplink v1 header length: + 1 MTS + 2 C/I */
+/* Uplink TRXDv0 header length: TDMA TN + FN + RSSI + ToA256 */
+#define TRX_UL_V0HDR_LEN (1 + 4 + 1 + 2)
+/* Uplink TRXDv1 header length: additional MTS + C/I */
#define TRX_UL_V1HDR_LEN (TRX_UL_V0HDR_LEN + 1 + 2)
+/* Uplink TRXDv2 header length: TDMA TN + TRXN + MTS + RSSI + ToA256 + C/I */
+#define TRX_UL_V2HDR_LEN (1 + 1 + 1 + 1 + 2 + 2)
+
+/* Minimum Uplink TRXD header length for all PDU versions */
+static const uint8_t trx_data_rx_hdr_len[] = {
+ TRX_UL_V0HDR_LEN, /* TRXDv0 */
+ TRX_UL_V1HDR_LEN, /* TRXDv1 */
+ TRX_UL_V2HDR_LEN, /* TRXDv2 */
+};
-/* TRXD header dissector for version 0 */
-static int trx_data_handle_hdr_v0(struct trx_l1h *l1h,
- struct trx_ul_burst_ind *bi,
- const uint8_t *buf, size_t buf_len)
-{
- /* Make sure we have enough data */
- if (buf_len < TRX_UL_V0HDR_LEN) {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "Short read on TRXD, missing version 0 header "
- "(len=%zu vs expected %d)\n", buf_len, TRX_UL_V0HDR_LEN);
- return -EIO;
- }
+static const uint8_t trx_data_mod_val[] = {
+ [TRX_MOD_T_GMSK] = 0x00, /* .00xx... */
+ [TRX_MOD_T_8PSK] = 0x20, /* .010x... */
+ [TRX_MOD_T_AQPSK] = 0x60, /* .11xx... */
+};
+/* Header dissector for TRXDv0 (and part of TRXDv1) */
+static inline void trx_data_handle_hdr_v0_part(struct trx_ul_burst_ind *bi,
+ const uint8_t *buf)
+{
bi->tn = buf[0] & 0b111;
bi->fn = osmo_load32be(buf + 1);
bi->rssi = -(int8_t)buf[5];
bi->toa256 = (int16_t) osmo_load16be(buf + 6);
-
- if (bi->fn >= GSM_HYPERFRAME) {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "Illegal TDMA fn=%u\n", bi->fn);
- return -EINVAL;
- }
-
- return TRX_UL_V0HDR_LEN;
}
-/* TRXD header dissector for version 0x01 */
-static int trx_data_handle_hdr_v1(struct trx_l1h *l1h,
+/* TRXD header dissector for version 0x00 */
+static int trx_data_handle_hdr_v0(struct phy_instance *phy_inst,
struct trx_ul_burst_ind *bi,
const uint8_t *buf, size_t buf_len)
{
- uint8_t mts;
- int rc;
+ /* Parse TRXDv0 specific header part */
+ trx_data_handle_hdr_v0_part(bi, buf);
+ buf_len -= TRX_UL_V0HDR_LEN;
- /* Make sure we have enough data */
- if (buf_len < TRX_UL_V1HDR_LEN) {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "Short read on TRXD, missing version 1 header "
- "(len=%zu vs expected %d)\n", buf_len, TRX_UL_V1HDR_LEN);
- return -EIO;
+ /* Guess modulation and burst length by the rest octets.
+ * NOTE: a legacy transceiver may append two garbage bytes. */
+ switch (buf_len) {
+ case EGPRS_BURST_LEN + 2:
+ case EGPRS_BURST_LEN:
+ bi->mod = TRX_MOD_T_8PSK;
+ break;
+ case GSM_BURST_LEN + 2:
+ case GSM_BURST_LEN:
+ bi->mod = TRX_MOD_T_GMSK;
+ break;
+ default:
+ LOGPPHI(phy_inst, DTRX, LOGL_NOTICE,
+ "Rx TRXD PDU with odd burst length %zu\n", buf_len);
+ return -EINVAL;
}
- /* Parse v0 specific part */
- rc = trx_data_handle_hdr_v0(l1h, bi, buf, buf_len);
- if (rc < 0)
- return rc;
-
- /* Move closer to the v1 specific part */
- buf_len -= rc;
- buf += rc;
+ return TRX_UL_V0HDR_LEN;
+}
- /* IDLE / NOPE frame indication */
- if (buf[0] & (1 << 7)) {
+/* Parser for MTS (Modulation and Training Sequence) */
+static inline int trx_data_parse_mts(struct phy_instance *phy_inst,
+ struct trx_ul_burst_ind *bi,
+ const uint8_t mts)
+{
+ if (mts & (1 << 7)) {
bi->flags |= TRX_BI_F_NOPE_IND;
- return TRX_UL_V1HDR_LEN;
+ return 0;
}
- /* Modulation info and TSC set */
- mts = (buf[0] >> 3) & 0b1111;
- if ((mts & 0b1100) == 0x00) {
- bi->bt = TRX_BURST_GMSK;
- bi->tsc_set = mts & 0b11;
- bi->flags |= TRX_BI_F_MOD_TYPE;
- } else if ((mts & 0b0100) == 0b0100) {
- bi->bt = TRX_BURST_8PSK;
- bi->tsc_set = mts & 0b1;
- bi->flags |= TRX_BI_F_MOD_TYPE;
+ /* | 7 6 5 4 3 2 1 0 | Bitmask / description
+ * | . 0 0 X X . . . | GMSK, 4 TSC sets (0..3)
+ * | . 0 1 0 X . . . | 8-PSK, 2 TSC sets (0..1)
+ * | . 0 1 1 0 . . . | GMSK, Access Burst */
+ if ((mts >> 5) == 0x00) {
+ bi->mod = TRX_MOD_T_GMSK;
+ bi->tsc_set = (mts >> 3) & 0x03;
+ } else if ((mts >> 4) == 0x02) {
+ bi->mod = TRX_MOD_T_8PSK;
+ bi->tsc_set = (mts >> 3) & 0x01;
+ } else if ((mts >> 3) == 0x06) {
+ bi->flags |= TRX_BI_F_ACCESS_BURST;
+ bi->mod = TRX_MOD_T_GMSK;
+ bi->tsc_set = 0;
} else {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "Indicated modulation 0x%02x is not supported\n", mts & 0b1110);
+ LOGPPHI(phy_inst, DTRX, LOGL_ERROR,
+ "Rx TRXD PDU with unknown or not supported "
+ "modulation (MTS=0x%02x)\n", mts);
return -ENOTSUP;
}
/* Training Sequence Code */
- bi->tsc = buf[0] & 0b111;
- bi->flags |= TRX_BI_F_TS_INFO;
+ bi->tsc = mts & 0x07;
+
+ bi->flags |= (TRX_BI_F_MOD_TYPE | TRX_BI_F_TS_INFO);
+
+ return 0;
+}
+
+/* TRXD header dissector for version 0x01 */
+static int trx_data_handle_hdr_v1(struct phy_instance *phy_inst,
+ struct trx_ul_burst_ind *bi,
+ const uint8_t *buf, size_t buf_len)
+{
+ int rc;
+
+ /* Parse TRXDv0 specific header part */
+ trx_data_handle_hdr_v0_part(bi, buf);
+ buf += TRX_UL_V0HDR_LEN;
+
+ /* MTS (Modulation and Training Sequence) */
+ rc = trx_data_parse_mts(phy_inst, bi, buf[0]);
+ if (OSMO_UNLIKELY(rc < 0))
+ return rc;
/* C/I: Carrier-to-Interference ratio (in centiBels) */
bi->ci_cb = (int16_t) osmo_load16be(buf + 1);
@@ -744,64 +888,82 @@ static int trx_data_handle_hdr_v1(struct trx_l1h *l1h,
return TRX_UL_V1HDR_LEN;
}
-/* TRXD burst handler for header version 0 */
-static int trx_data_handle_burst_v0(struct trx_l1h *l1h,
- struct trx_ul_burst_ind *bi,
- const uint8_t *buf, size_t buf_len)
+/* TRXD header dissector for version 0x01 */
+static int trx_data_handle_pdu_v2(struct phy_instance *phy_inst,
+ struct trx_ul_burst_ind *bi,
+ const uint8_t *buf, size_t buf_len)
{
- size_t i;
+ int rc;
- /* Verify burst length */
- switch (buf_len) {
- /* Legacy transceivers append two padding bytes */
- case EGPRS_BURST_LEN + 2:
- case GSM_BURST_LEN + 2:
- bi->burst_len = buf_len - 2;
- break;
- case EGPRS_BURST_LEN:
- case GSM_BURST_LEN:
- bi->burst_len = buf_len;
- break;
+ /* TDMA timeslot number (other bits are RFU) */
+ bi->tn = buf[0] & 0x07;
- default:
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE,
- "Rx TRXD message with odd burst length %zu\n", buf_len);
- return -EINVAL;
- }
+ if (buf[1] & (1 << 7)) /* BATCH.ind */
+ bi->flags |= TRX_BI_F_BATCH_IND;
+ if (buf[1] & (1 << 6)) /* VAMOS.ind */
+ bi->flags |= TRX_BI_F_SHADOW_IND;
- /* Convert unsigned soft-bits [254..0] to soft-bits [-127..127] */
- for (i = 0; i < bi->burst_len; i++) {
- if (buf[i] == 255)
- bi->burst[i] = -127;
- else
- bi->burst[i] = 127 - buf[i];
+ /* TRX (RF channel) number */
+ bi->trx_num = buf[1] & 0x3f;
+ bi->flags |= TRX_BI_F_TRX_NUM;
+
+ /* MTS (Modulation and Training Sequence) */
+ rc = trx_data_parse_mts(phy_inst, bi, buf[2]);
+ if (OSMO_UNLIKELY(rc < 0))
+ return rc;
+
+ bi->rssi = -(int8_t)buf[3];
+ bi->toa256 = (int16_t) osmo_load16be(&buf[4]);
+ bi->ci_cb = (int16_t) osmo_load16be(&buf[6]);
+ bi->flags |= TRX_BI_F_CI_CB;
+
+ /* TDMA frame number is absent in batched PDUs */
+ if (bi->_num_pdus == 0) {
+ if (OSMO_UNLIKELY(buf_len < sizeof(bi->fn) + TRX_UL_V2HDR_LEN)) {
+ LOGPPHI(phy_inst, DTRX, LOGL_ERROR,
+ "Rx malformed TRXDv2 PDU: not enough bytes "
+ "to parse TDMA frame number\n");
+ return -EINVAL;
+ }
+
+ bi->fn = osmo_load32be(buf + TRX_UL_V2HDR_LEN);
+ return TRX_UL_V2HDR_LEN + sizeof(bi->fn);
}
- return 0;
+ return TRX_UL_V2HDR_LEN;
}
-/* TRXD burst handler for header version 1 */
-static int trx_data_handle_burst_v1(struct trx_l1h *l1h,
- struct trx_ul_burst_ind *bi,
- const uint8_t *buf, size_t buf_len)
+/* TRXD burst handler (version independent) */
+static int trx_data_handle_burst(struct trx_ul_burst_ind *bi,
+ const uint8_t *buf, size_t buf_len)
{
+ size_t i;
+
+ /* NOPE.ind contains no burst */
+ if (bi->flags & TRX_BI_F_NOPE_IND) {
+ bi->burst_len = 0;
+ return 0;
+ }
+
/* Modulation types defined in 3GPP TS 45.002 */
static const size_t bl[] = {
- [TRX_BURST_GMSK] = 148, /* 1 bit per symbol */
- [TRX_BURST_8PSK] = 444, /* 3 bits per symbol */
+ [TRX_MOD_T_GMSK] = 148, /* 1 bit per symbol */
+ [TRX_MOD_T_8PSK] = 444, /* 3 bits per symbol */
};
- /* Verify burst length */
- if (bl[bi->bt] != buf_len) {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE,
- "Rx TRXD message with odd burst length %zu, "
- "expected %zu\n", buf_len, bl[bi->bt]);
+ bi->burst_len = bl[bi->mod];
+ if (OSMO_UNLIKELY(buf_len < bi->burst_len))
return -EINVAL;
+
+ /* Convert unsigned soft-bits [254..0] to soft-bits [-127..127] */
+ for (i = 0; i < bi->burst_len; i++) {
+ if (buf[i] == 255)
+ bi->burst[i] = -127;
+ else
+ bi->burst[i] = 127 - buf[i];
}
- /* The burst format is the same as for version 0.
- * NOTE: other modulation types to be handled separately. */
- return trx_data_handle_burst_v0(l1h, bi, buf, buf_len);
+ return 0;
}
static const char *trx_data_desc_msg(const struct trx_ul_burst_ind *bi)
@@ -811,8 +973,8 @@ static const char *trx_data_desc_msg(const struct trx_ul_burst_ind *bi)
/* Modulation types defined in 3GPP TS 45.002 */
static const char *mod_names[] = {
- [TRX_BURST_GMSK] = "GMSK",
- [TRX_BURST_8PSK] = "8-PSK",
+ [TRX_MOD_T_GMSK] = "GMSK",
+ [TRX_MOD_T_8PSK] = "8-PSK",
};
/* Initialize the string buffer */
@@ -821,279 +983,236 @@ static const char *trx_data_desc_msg(const struct trx_ul_burst_ind *bi)
/* Common TDMA parameters */
OSMO_STRBUF_PRINTF(sb, "tn=%u fn=%u", bi->tn, bi->fn);
- /* Nothing else to print for NOPE.ind */
- if (bi->flags & TRX_BI_F_NOPE_IND)
- return buf;
+ /* TRX (RF channel number) */
+ if (bi->flags & TRX_BI_F_TRX_NUM)
+ OSMO_STRBUF_PRINTF(sb, " trx_num=%u", bi->trx_num);
/* RSSI and ToA256 */
OSMO_STRBUF_PRINTF(sb, " rssi=%d toa256=%d", bi->rssi, bi->toa256);
+ /* C/I: Carrier-to-Interference ratio (in centiBels) */
+ if (bi->flags & TRX_BI_F_CI_CB)
+ OSMO_STRBUF_PRINTF(sb, " C/I=%d cB", bi->ci_cb);
+
+ /* Nothing else to print for NOPE.ind */
+ if (bi->flags & TRX_BI_F_NOPE_IND)
+ return buf;
+
/* Modulation and TSC set */
if (bi->flags & TRX_BI_F_MOD_TYPE)
- OSMO_STRBUF_PRINTF(sb, " mod=%s", mod_names[bi->bt]);
+ OSMO_STRBUF_PRINTF(sb, " mod=%s", mod_names[bi->mod]);
/* Training Sequence Code */
if (bi->flags & TRX_BI_F_TS_INFO)
OSMO_STRBUF_PRINTF(sb, " set=%u tsc=%u", bi->tsc_set, bi->tsc);
- /* C/I: Carrier-to-Interference ratio (in centiBels) */
- if (bi->flags & TRX_BI_F_CI_CB)
- OSMO_STRBUF_PRINTF(sb, " C/I=%d cB", bi->ci_cb);
-
/* Burst length */
OSMO_STRBUF_PRINTF(sb, " burst_len=%zu", bi->burst_len);
return buf;
}
-/* Parse TRXD message from transceiver, compose an UL burst indication.
- *
- * This message contains a demodulated Uplink burst with fixed-size
- * header preceding the burst bits. The header consists of the common
- * and message specific part.
- *
- * +---------------+-----------------+------------+
- * | common header | specific header | burst bits |
- * +---------------+-----------------+------------+
- *
- * Common header is the same as for Downlink message:
- *
- * +-----------------+----------------+-------------------+
- * | VER (1/2 octet) | TN (1/2 octet) | FN (4 octets, BE) |
- * +-----------------+----------------+-------------------+
- *
- * and among with TDMA parameters, contains the version indicator:
- *
- * +-----------------+------------------------+
- * | 7 6 5 4 3 2 1 0 | bit numbers |
- * +-----------------+------------------------+
- * | X X X X . . . . | header version (0..15) |
- * +-----------------+------------------------+
- * | . . . . . X X X | TDMA TN (0..7) |
- * +-----------------+------------------------+
- * | . . . . X . . . | RESERVED (0) |
- * +-----------------+------------------------+
- *
- * which is encoded in 4 MSB bits of the first octet, which used to be
- * zero-initialized due to the value range of TDMA TN. Therefore, the
- * old header format has implicit version 0x00.
- *
- * The message specific header has the following structure:
- *
- * == Version 0x00
- *
- * +------+-----+--------------------+
- * | RSSI | ToA | soft-bits (254..0) |
- * +------+-----+--------------------+
- *
- * == Version 0x01
- *
- * +------+-----+-----+-----+--------------------+
- * | RSSI | ToA | MTS | C/I | soft-bits (254..0) |
- * +------+-----+-----+-----+--------------------+
- *
- * where:
- *
- * - RSSI (1 octet) - Received Signal Strength Indication
- * encoded without the negative sign.
- * - ToA (2 octets) - Timing of Arrival in units of 1/256
- * of symbol (big endian).
- * - MTS (1 octet) - Modulation and Training Sequence info.
- * - C/I (2 octets) - Carrier-to-Interference ratio (big endian).
- *
- * == Coding of MTS: Modulation and Training Sequence info
- *
- * 3GPP TS 45.002 version 15.1.0 defines several modulation types,
- * and a few sets of training sequences for each type. The most
- * common are GMSK and 8-PSK (which is used in EDGE).
- *
- * +-----------------+---------------------------------------+
- * | 7 6 5 4 3 2 1 0 | bit numbers (value range) |
- * +-----------------+---------------------------------------+
- * | . . . . . X X X | Training Sequence Code (0..7) |
- * +-----------------+---------------------------------------+
- * | . X X X X . . . | Modulation, TS set number (see below) |
- * +-----------------+---------------------------------------+
- * | X . . . . . . . | IDLE / nope frame indication (0 or 1) |
- * +-----------------+---------------------------------------+
- *
- * The bit number 7 (MSB) is set to high when either nothing has been
- * detected, or during IDLE frames, so we can deliver noise levels,
- * and avoid clock gaps on the L1 side. Other bits are ignored,
- * and should be set to low (0) in this case. L16 shall be set to 0x00.
- *
- * == Coding of modulation and TS set number
- *
- * GMSK has 4 sets of training sequences (see tables 5.2.3a-d),
- * while 8-PSK (see tables 5.2.3f-g) and the others have 2 sets.
- * Access and Synchronization bursts also have several synch.
- * sequences.
- *
- * +-----------------+---------------------------------------+
- * | 7 6 5 4 3 2 1 0 | bit numbers (value range) |
- * +-----------------+---------------------------------------+
- * | . 0 0 X X . . . | GMSK, 4 TS sets (0..3) |
- * +-----------------+---------------------------------------+
- * | . 0 1 0 X . . . | 8-PSK, 2 TS sets (0..1) |
- * +-----------------+---------------------------------------+
- * | . 0 1 1 X . . . | AQPSK, 2 TS sets (0..1) |
- * +-----------------+---------------------------------------+
- * | . 1 0 0 X . . . | 16QAM, 2 TS sets (0..1) |
- * +-----------------+---------------------------------------+
- * | . 1 0 1 X . . . | 32QAM, 2 TS sets (0..1) |
- * +-----------------+---------------------------------------+
- * | . 1 1 1 X . . . | RESERVED (0) |
- * +-----------------+---------------------------------------+
- *
- * NOTE: we only support GMSK and 8-PSK.
- *
- * == C/I: Carrier-to-Interference ratio
- *
- * The C/I value can be computed from the training sequence of each
- * burst, where we can compare the "ideal" training sequence with
- * the actual training sequence and then express that in centiBels.
- *
- * == Coding of the burst bits
- *
- * Unlike to be transmitted bursts, the received bursts are designated
- * using the soft-bits notation, so the receiver can indicate its
- * assurance from 0 to -127 that a given bit is 1, and from 0 to +127
- * that a given bit is 0.
- *
- * Each soft-bit (-127..127) of the burst is encoded as an unsigned
- * value in range (254..0) respectively using the constant shift.
- *
- */
+/* TRXD buffer used by Rx/Tx handlers */
+static uint8_t trx_data_buf[TRXD_MSG_BUF_SIZE];
+
+/* Parse TRXD message from transceiver, compose an UL burst indication. */
static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what)
{
+ const uint8_t *buf = &trx_data_buf[0];
struct trx_l1h *l1h = ofd->data;
- uint8_t buf[TRX_DATA_MSG_MAX_LEN];
struct trx_ul_burst_ind bi;
ssize_t hdr_len, buf_len;
- uint8_t hdr_ver;
- int rc;
+ uint8_t pdu_ver;
- buf_len = recv(ofd->fd, buf, sizeof(buf), 0);
- if (buf_len <= 0) {
+ buf_len = recv(ofd->fd, trx_data_buf, sizeof(trx_data_buf), 0);
+ if (OSMO_UNLIKELY(buf_len <= 0)) {
+ strerror_r(errno, (char *) trx_data_buf, sizeof(trx_data_buf));
LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "recv() failed on TRXD with rc=%zd\n", buf_len);
+ "recv() failed on TRXD with rc=%zd (%s)\n",
+ buf_len, trx_data_buf);
return buf_len;
}
- /* Pre-clean (initialize) the flags */
- bi.flags = 0x00;
+ /* Parse PDU version first */
+ pdu_ver = buf[0] >> 4;
- /* Parse the header depending on its version */
- hdr_ver = buf[0] >> 4;
- switch (hdr_ver) {
- case 0:
- /* Legacy protocol has no version indicator */
- hdr_len = trx_data_handle_hdr_v0(l1h, &bi, buf, buf_len);
- break;
- case 1:
- hdr_len = trx_data_handle_hdr_v1(l1h, &bi, buf, buf_len);
- break;
- default:
+ /* Make sure that PDU version matches our expectations */
+ if (OSMO_UNLIKELY(pdu_ver != l1h->config.trxd_pdu_ver_use)) {
LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "TRXD header version %u is not supported\n", hdr_ver);
- return -ENOTSUP;
+ "Rx TRXD PDU with unexpected version %u (expected %u)\n",
+ pdu_ver, l1h->config.trxd_pdu_ver_use);
+ return -EIO;
}
- /* Header parsing error */
- if (hdr_len < 0)
- return hdr_len;
+ /* We're about to parse the first PDU */
+ bi._num_pdus = 0;
- /* TODO: we can use NOPE indications to get noise levels on IDLE
- * TDMA frames, and properly drive scheduler if nothing has been
- * detected on non-IDLE channels. */
- if (bi.flags & TRX_BI_F_NOPE_IND) {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE,
- "IDLE / NOPE indications are not (yet) supported\n");
- return -ENOTSUP;
- }
+ /* Starting from TRXDv2, there can be batched PDUs */
+ do {
+ /* (Re)initialize the flags */
+ bi.flags = 0x00;
- /* We're done with the header now */
- buf_len -= hdr_len;
+ /* Make sure that we have enough bytes to parse the header */
+ if (OSMO_UNLIKELY(buf_len < trx_data_rx_hdr_len[pdu_ver])) {
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
+ "Rx malformed TRXDv%u PDU: len=%zd < expected %u\n",
+ pdu_ver, buf_len, trx_data_rx_hdr_len[pdu_ver]);
+ return -EINVAL;
+ }
- /* Handle burst bits */
- switch (hdr_ver) {
- case 0:
- rc = trx_data_handle_burst_v0(l1h, &bi, buf + hdr_len, buf_len);
- break;
- case 1:
- rc = trx_data_handle_burst_v1(l1h, &bi, buf + hdr_len, buf_len);
- break;
- default:
- /* Shall not happen, just to make GCC happy */
- OSMO_ASSERT(0);
- }
+ /* Parse header depending on the PDU version */
+ switch (pdu_ver) {
+ case 0: /* TRXDv0 */
+ hdr_len = trx_data_handle_hdr_v0(l1h->phy_inst, &bi, buf, buf_len);
+ break;
+ case 1: /* TRXDv1 */
+ hdr_len = trx_data_handle_hdr_v1(l1h->phy_inst, &bi, buf, buf_len);
+ break;
+ case 2: /* TRXDv2 */
+ hdr_len = trx_data_handle_pdu_v2(l1h->phy_inst, &bi, buf, buf_len);
+ break;
+ default:
+ /* Shall not happen */
+ OSMO_ASSERT(0);
+ }
- /* Burst parsing error */
- if (rc < 0)
- return rc;
+ /* Header parsing error */
+ if (OSMO_UNLIKELY(hdr_len < 0))
+ return hdr_len;
- /* Print header & burst info */
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Rx %s (hdr_ver=%u): %s\n",
- (bi.flags & TRX_BI_F_NOPE_IND) ? "NOPE.ind" : "UL burst",
- hdr_ver, trx_data_desc_msg(&bi));
+ if (OSMO_UNLIKELY(bi.fn >= GSM_TDMA_HYPERFRAME)) {
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
+ "Rx malformed TRXDv%u PDU: illegal TDMA fn=%u\n",
+ pdu_ver, bi.fn);
+ return -EINVAL;
+ }
+
+ /* We're done with the header now */
+ buf_len -= hdr_len;
+ buf += hdr_len;
+
+ /* Calculate burst length and parse it (if present) */
+ if (OSMO_UNLIKELY(trx_data_handle_burst(&bi, buf, buf_len) != 0)) {
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
+ "Rx malformed TRXDv%u PDU: odd burst length=%zd\n",
+ pdu_ver, buf_len);
+ return -EINVAL;
+ }
- /* feed received burst into scheduler code */
- trx_sched_ul_burst(&l1h->l1s, &bi);
+ /* We're done with the burst bits now */
+ buf_len -= bi.burst_len;
+ buf += bi.burst_len;
+
+ /* Print header & burst info */
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Rx %s (pdu_ver=%u): %s\n",
+ (bi.flags & TRX_BI_F_NOPE_IND) ? "NOPE.ind" : "UL burst",
+ pdu_ver, trx_data_desc_msg(&bi));
+
+ /* Number of processed PDUs */
+ bi._num_pdus++;
+
+ /* feed received burst into scheduler code */
+ TRACE(OSMO_BTS_TRX_UL_DATA_START(l1h->phy_inst->trx->nr, bi.tn, bi.fn));
+ trx_sched_route_burst_ind(l1h->phy_inst->trx, &bi);
+ TRACE(OSMO_BTS_TRX_UL_DATA_DONE(l1h->phy_inst->trx->nr, bi.tn, bi.fn));
+ } while (bi.flags & TRX_BI_F_BATCH_IND);
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
+ * \param[in] br Downlink burst request structure
* \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)
+int trx_if_send_burst(struct trx_l1h *l1h, const struct trx_dl_burst_req *br)
{
- uint8_t hdr_ver = l1h->config.trxd_hdr_ver_use;
- uint8_t buf[TRX_DATA_MSG_MAX_LEN];
-
- if ((nbits != GSM_BURST_LEN) && (nbits != EGPRS_BURST_LEN)) {
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, "Tx burst length %u invalid\n", nbits);
- return -1;
+ uint8_t pdu_ver = l1h->config.trxd_pdu_ver_use;
+ static uint8_t *buf = &trx_data_buf[0];
+ static uint8_t *last_pdu = NULL;
+ static unsigned int pdu_num = 0;
+ ssize_t snd_len, buf_len;
+
+ /* Make sure that the PHY is powered on */
+ if (OSMO_UNLIKELY(!trx_if_powered(l1h))) {
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
+ "Ignoring Tx data, transceiver is powered off\n");
+ return -ENODEV;
}
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG,
- "Tx burst (hdr_ver=%u): tn=%u fn=%u pwr=%u\n",
- hdr_ver, tn, fn, pwr);
+ /* Burst batching breaker */
+ if (br == NULL) {
+ if (pdu_num > 0)
+ goto sendall;
+ return -ENOMSG;
+ }
- switch (hdr_ver) {
- case 0:
- case 1:
- /* Both versions have the same header format */
+ /* Pointer to the last encoded PDU */
+ last_pdu = &buf[0];
+
+ switch (pdu_ver) {
+ /* Both versions have the same PDU format */
+ case 0: /* TRXDv0 */
+ case 1: /* TRXDv1 */
+ buf[0] = ((pdu_ver & 0x0f) << 4) | br->tn;
+ osmo_store32be(br->fn, buf + 1);
+ buf[5] = br->att;
+ buf += 6;
+ break;
+ case 2: /* TRXDv2 */
+ buf[0] = br->tn;
+ /* BATCH.ind will be unset in the last PDU */
+ buf[1] = (br->trx_num & 0x3f) | (1 << 7);
+ buf[2] = trx_data_mod_val[br->mod]
+ | (br->tsc_set << 3)
+ | (br->tsc & 0x07);
+ buf[3] = br->att;
+ buf[4] = (uint8_t) br->scpir;
+ buf[5] = buf[6] = buf[7] = 0x00; /* Spare */
+ /* Some fields are not present in batched PDUs */
+ if (pdu_num == 0) {
+ buf[0] |= (pdu_ver & 0x0f) << 4;
+ osmo_store32be(br->fn, buf + 8);
+ buf += 4;
+ }
+ buf += 8;
break;
-
default:
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
- "Requested TRXD header version %u is not supported\n", hdr_ver);
- return -ENOTSUP;
+ /* Shall not happen */
+ OSMO_ASSERT(0);
}
- buf[0] = ((hdr_ver & 0x0f) << 4) | 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);
+ memcpy(buf, br->burst, br->burst_len);
+ buf += br->burst_len;
+
+ /* One more PDU in the buffer */
+ pdu_num++;
+
+ /* TRXDv2: wait for the batching breaker */
+ if (pdu_ver >= 2)
+ return 0;
+
+sendall:
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG,
+ "Tx TRXDv%u datagram with %u PDU(s)\n",
+ pdu_ver, pdu_num);
+
+ /* TRXDv2: unset BATCH.ind in the last PDU */
+ if (pdu_ver >= 2)
+ last_pdu[1] &= ~(1 << 7);
+
+ buf_len = buf - &trx_data_buf[0];
+ buf = &trx_data_buf[0];
+ pdu_num = 0;
- /* 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
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, "Ignoring TX data, transceiver offline.\n");
+ snd_len = send(l1h->trx_ofd_data.fd, trx_data_buf, buf_len, 0);
+ if (OSMO_UNLIKELY(snd_len <= 0)) {
+ strerror_r(errno, (char *) trx_data_buf, sizeof(trx_data_buf));
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR,
+ "send() failed on TRXD with rc=%zd (%s)\n",
+ snd_len, trx_data_buf);
+ return -2;
+ }
return 0;
}
@@ -1116,12 +1235,21 @@ void trx_if_flush(struct trx_l1h *l1h)
talloc_free(tcm);
}
talloc_free(l1h->last_acked);
+ l1h->last_acked = NULL;
+
+ /* Tx queue is now empty, so there's no point in keeping the retrans timer armed: */
+ osmo_timer_del(&l1h->trx_ctrl_timer);
+
+ /* If we are in read_cb, signal to the returning code path that we freed the list. */
+ if (l1h->in_trx_ctrl_read_cb)
+ l1h->flushed_while_in_trx_ctrl_read_cb = true;
}
/*! close the TRX for given handle (data + control socket) */
void trx_if_close(struct trx_l1h *l1h)
{
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Close transceiver\n");
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Closing TRXC/TRXD connections to %s\n",
+ l1h->phy_inst->phy_link->u.osmotrx.remote_ip);
trx_if_flush(l1h);
@@ -1131,7 +1259,7 @@ void trx_if_close(struct trx_l1h *l1h)
}
/*! compute UDP port number used for TRX protocol */
-static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data)
+static uint16_t compute_port(struct phy_instance *pinst, bool remote, bool is_data)
{
struct phy_link *plink = pinst->phy_link;
uint16_t inc = 1;
@@ -1145,46 +1273,32 @@ static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data
return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc;
}
-/*! open a TRX interface. creates contro + data sockets */
+/*! open a TRX interface. creates control + 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;
- LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Open transceiver\n");
-
- /* initialize ctrl queue */
- INIT_LLIST_HEAD(&l1h->trx_ctrl_list);
+ LOGPPHI(pinst, DTRX, LOGL_NOTICE, "Opening TRXC/TRXD connections to %s\n", plink->u.osmotrx.remote_ip);
/* open sockets */
rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl,
plink->u.osmotrx.local_ip,
- compute_port(pinst, 0, 0),
+ compute_port(pinst, false, false),
plink->u.osmotrx.remote_ip,
- compute_port(pinst, 1, 0), trx_ctrl_read_cb);
+ compute_port(pinst, true, false), trx_ctrl_read_cb);
if (rc < 0)
- goto err;
+ return rc;
rc = trx_udp_open(l1h, &l1h->trx_ofd_data,
plink->u.osmotrx.local_ip,
- compute_port(pinst, 0, 1),
+ compute_port(pinst, false, true),
plink->u.osmotrx.remote_ip,
- compute_port(pinst, 1, 1), trx_data_read_cb);
+ compute_port(pinst, true, true), 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 rc;
return 0;
-
-err:
- trx_if_close(l1h);
- return rc;
}
/*! close the control + burst data sockets for one phy_instance */
@@ -1193,7 +1307,8 @@ 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);
+ if (pinst->trx)
+ trx_sched_clean(pinst->trx);
}
/*! open the control + burst data sockets for one phy_instance */
@@ -1206,11 +1321,9 @@ static int trx_phy_inst_open(struct phy_instance *pinst)
if (!l1h)
return -EINVAL;
- rc = trx_sched_init(&l1h->l1s, pinst->trx);
- if (rc < 0) {
- LOGPPHI(l1h->phy_inst, DL1C, LOGL_FATAL, "Cannot initialize scheduler\n");
- return -EIO;
- }
+ /* PHY instance may be not associated with a TRX instance */
+ if (pinst->trx != NULL)
+ trx_sched_init(pinst->trx);
rc = trx_if_open(l1h);
if (rc < 0) {
@@ -1244,11 +1357,12 @@ int bts_model_phy_link_open(struct phy_link *plink)
/* open the individual instances with their ctrl+data sockets */
llist_for_each_entry(pinst, &plink->instances, list) {
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
if (trx_phy_inst_open(pinst) < 0)
goto cleanup;
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_OPEN, NULL);
}
- /* FIXME: is there better way to check/report TRX availability? */
- transceiver_available = 1;
+
return 0;
cleanup:
@@ -1263,8 +1377,27 @@ cleanup:
return -1;
}
+/*! close the PHY link using TRX protocol */
+int bts_model_phy_link_close(struct phy_link *plink)
+{
+ bool clock_stopped = false;
+ struct phy_instance *pinst;
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (!clock_stopped) {
+ clock_stopped = true;
+ trx_sched_clock_stopped(pinst->trx->bts);
+ }
+ trx_phy_inst_close(pinst);
+ }
+ trx_udp_close(&plink->u.osmotrx.trx_ofd_clk);
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ return 0;
+}
+
/*! determine if the TRX for given handle is powered up */
int trx_if_powered(struct trx_l1h *l1h)
{
- return l1h->config.poweron;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ return plink->u.osmotrx.powered;
}
diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h
index dda7116e..3a22a1b4 100644
--- a/src/osmo-bts-trx/trx_if.h
+++ b/src/osmo-bts-trx/trx_if.h
@@ -1,8 +1,11 @@
-#ifndef TRX_IF_H
-#define TRX_IF_H
+#pragma once
-extern int transceiver_available;
+/* TRXC read/send buffer size */
+#define TRXC_MSG_BUF_SIZE 1500
+/* TRXD read/send buffer size (max. lo MTU) */
+#define TRXD_MSG_BUF_SIZE 65536
+struct trx_dl_burst_req;
struct trx_l1h;
struct trx_ctrl_msg {
@@ -15,30 +18,37 @@ struct trx_ctrl_msg {
void *cb;
};
+typedef void trx_if_cmd_generic_cb(struct trx_l1h *l1h, int rc);
+typedef void trx_if_cmd_poweronoff_cb(struct trx_l1h *l1h, bool poweronoff, int rc);
typedef void trx_if_cmd_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc);
+typedef void trx_if_cmd_getnompower_cb(struct trx_l1h *l1h, int nominal_power, int rc);
+typedef void trx_if_cmd_setpower_att_cb(struct trx_l1h *l1h, int power_att_db, int rc);
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_poweroff(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb);
+int trx_if_cmd_poweron(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb);
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc, trx_if_cmd_generic_cb *cb);
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic, trx_if_cmd_generic_cb *cb);
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_getnompower(struct trx_l1h *l1h, trx_if_cmd_getnompower_cb *cb);
+int trx_if_cmd_setpower_att(struct trx_l1h *l1h, int power_att_db, trx_if_cmd_setpower_att_cb *cb);
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, trx_if_cmd_setslot_cb *cb);
-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_setslot(struct trx_l1h *l1h, uint8_t tn, trx_if_cmd_setslot_cb *cb);
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb);
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb);
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_cmd_rfmute(struct trx_l1h *l1h, bool mute);
+int trx_if_send_burst(struct trx_l1h *l1h, const struct trx_dl_burst_req *br);
int trx_if_powered(struct trx_l1h *l1h);
-/* The latest supported TRXD header format version */
-#define TRX_DATA_FORMAT_VER 1
+/* The latest supported TRXD PDU version */
+#define TRX_DATA_PDU_VER 2
/* Format negotiation command */
-int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver);
+int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver, trx_if_cmd_generic_cb *cb);
-#endif /* TRX_IF_H */
+int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb,
+ const char *cmd, const char *fmt, ...);
+#define trx_ctrl_cmd(l1h, critical, cmd, fmt, ...) trx_ctrl_cmd_cb(l1h, critical, NULL, cmd, fmt, ##__VA_ARGS__)
diff --git a/src/osmo-bts-trx/trx_provision_fsm.c b/src/osmo-bts-trx/trx_provision_fsm.c
new file mode 100644
index 00000000..5ca23e31
--- /dev/null
+++ b/src/osmo-bts-trx/trx_provision_fsm.c
@@ -0,0 +1,740 @@
+/* TRX provision FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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 <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/nm_common_fsm.h>
+#include <osmo-bts/signal.h>
+
+#include "l1_if.h"
+#include "trx_provision_fsm.h"
+
+#define X(s) (1 << (s))
+
+#define trx_prov_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static void l1if_poweronoff_cb(struct trx_l1h *l1h, bool poweronoff, int rc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+
+ plink->u.osmotrx.powered = poweronoff;
+
+ if (poweronoff) {
+ plink->u.osmotrx.poweron_sent = false;
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWERON_CNF, (void*)(intptr_t)rc);
+ } else {
+ plink->u.osmotrx.poweroff_sent = false;
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWEROFF_CNF, (void*)(intptr_t)rc);
+ }
+}
+
+
+void l1if_rxtune_cb(struct trx_l1h *l1h, int rc)
+{
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_RXTUNE_CNF, (void*)(intptr_t)rc);
+}
+
+void l1if_txtune_cb(struct trx_l1h *l1h, int rc)
+{
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_TXTUNE_CNF, (void*)(intptr_t)rc);
+}
+
+void l1if_settsc_cb(struct trx_l1h *l1h, int rc)
+{
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETTSC_CNF, (void*)(intptr_t)rc);
+}
+
+void l1if_setbsic_cb(struct trx_l1h *l1h, int rc)
+{
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETBSIC_CNF, (void*)(intptr_t)rc);
+}
+
+static void l1if_getnompower_cb(struct trx_l1h *l1h, int nominal_power, int rc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_getnompower_cb(nominal_power=%d, rc=%d)\n", nominal_power, rc);
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_NOMTXPOWER_CNF, (void*)(intptr_t)nominal_power);
+}
+
+void l1if_setformat_cb(struct trx_l1h *l1h, int rc)
+{
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETFORMAT_CNF, (void*)(intptr_t)rc);
+}
+
+/*
+ * transceiver provisioning
+ */
+
+static void trx_provision_reset(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ uint8_t tn;
+
+ l1h->config.trxd_pdu_ver_req = pinst->phy_link->u.osmotrx.trxd_pdu_ver_max;
+ l1h->config.trxd_pdu_ver_use = 0;
+ l1h->config.setformat_sent = false;
+ l1h->config.setformat_acked = false;
+
+ l1h->config.enabled = false;
+ l1h->config.arfcn_valid = false;
+ l1h->config.arfcn = 0;
+ l1h->config.rxtune_sent = false;
+ l1h->config.rxtune_acked = false;
+ l1h->config.txtune_sent = false;
+ l1h->config.txtune_acked = false;
+
+ l1h->config.tsc_valid = false;
+ l1h->config.tsc = 0;
+ l1h->config.tsc_sent = false;
+ l1h->config.tsc_acked = false;
+
+ l1h->config.bsic_valid = false;
+ l1h->config.bsic = 0;
+ l1h->config.bsic_sent = false;
+ l1h->config.bsic_acked = false;
+
+ l1h->config.rxgain_sent = false;
+
+ l1h->config.nomtxpower_sent = false;
+ l1h->config.nomtxpower_acked = false;
+
+ l1h->config.maxdly_sent = false;
+
+ l1h->config.maxdlynb_sent = false;
+
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ l1h->config.setslot_valid[tn] = false;
+ l1h->config.setslot_sent[tn] = false;
+ l1h->config.setslot[tn].slottype = 0;
+ l1h->config.setslot[tn].tsc_set = 0;
+ l1h->config.setslot[tn].tsc_val = 0;
+ l1h->config.setslot[tn].tsc_valid = 0;
+ }
+}
+int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+
+ /* During setup, pinst may still not be associated to a TRX nr */
+ if (!pinst->trx) {
+ LOGPPHI(pinst, DL1C, LOGL_INFO,
+ "Delaying provision, TRX not yet assigned to phy instance\n");
+ return -EIO;
+ }
+
+ if (phy_link_state_get(plink) == PHY_LINK_SHUTDOWN) {
+ LOGPPHI(pinst, DL1C, LOGL_INFO,
+ "Delaying provision, TRX not yet available\n");
+ return -EIO;
+ }
+
+ /* before power on */
+ if (l1h->config.arfcn_valid) {
+ if (!l1h->config.rxtune_sent) {
+ trx_if_cmd_rxtune(l1h, l1h->config.arfcn, l1if_rxtune_cb);
+ l1h->config.rxtune_sent = true;
+ l1h->config.rxtune_acked = false;
+ }
+ if (!l1h->config.txtune_sent) {
+ trx_if_cmd_txtune(l1h, l1h->config.arfcn, l1if_txtune_cb);
+ l1h->config.txtune_sent = true;
+ l1h->config.txtune_acked = false;
+ }
+ if (l1h->config.txtune_acked) {
+ /* After TXTUNE is sent to TRX, get the tx nominal power
+ * (which may vary precisly on band/arfcn. Avoid sending
+ * it if we are forced by VTY to use a specific nominal
+ * power (because TRX may not support the command or
+ * provide broken values) */
+ if (!l1h->config.nominal_power_set_by_vty && !l1h->config.nomtxpower_sent) {
+ trx_if_cmd_getnompower(l1h, l1if_getnompower_cb);
+ l1h->config.nomtxpower_sent = true;
+ l1h->config.nomtxpower_acked = false;
+ }
+ }
+ }
+ if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic &&
+ l1h->config.tsc_valid && !l1h->config.tsc_sent) {
+ trx_if_cmd_settsc(l1h, l1h->config.tsc, l1if_settsc_cb);
+ l1h->config.tsc_sent = true;
+ l1h->config.tsc_acked = false;
+ }
+ if (pinst->phy_link->u.osmotrx.use_legacy_setbsic &&
+ l1h->config.bsic_valid && !l1h->config.bsic_sent) {
+ trx_if_cmd_setbsic(l1h, l1h->config.bsic, l1if_setbsic_cb);
+ l1h->config.bsic_sent = true;
+ l1h->config.bsic_acked = false;
+ }
+
+ /* Ask transceiver to use the newest TRXD PDU version if not using it yet */
+ if (!l1h->config.setformat_sent) {
+ l1h->config.setformat_sent = true;
+ if (plink->u.osmotrx.trxd_pdu_ver_max == 0) {
+ LOGPPHI(pinst, DL1C, LOGL_INFO,
+ "No need to negotiate max TRXD version 0");
+ l1h->config.trxd_pdu_ver_use = 0;
+ l1h->config.setformat_acked = true;
+ } else {
+ trx_if_cmd_setformat(l1h, l1h->config.trxd_pdu_ver_req, l1if_setformat_cb);
+ l1h->config.setformat_acked = false;
+ }
+ }
+ return 0;
+}
+
+static void l1if_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->trx;
+ struct gsm_bts_trx_ts *ts;
+ enum gsm_phys_chan_config pchan;
+
+ if (tn >= TRX_NR_TS) {
+ LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TN (%" PRIu8 ")\n",
+ tn);
+ return;
+ }
+
+ pchan = transceiver_chan_type_2_pchan(type);
+ if (pchan == GSM_PCHAN_UNKNOWN) {
+ LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TS_TYPE (%" PRIu8 ")\n",
+ type);
+ return;
+ }
+
+ ts = &trx->ts[tn];
+ LOGPPHI(pinst, DL1C, LOGL_DEBUG, "%s l1if_setslot_cb(as_pchan=%s),"
+ " calling cb_ts_connected(rc=%d)\n",
+ gsm_ts_name(ts), gsm_pchan_name(pchan), rc);
+ cb_ts_connected(ts, rc);
+}
+
+static void update_ts_data(struct trx_l1h *l1h, struct trx_prov_ev_cfg_ts_data *data)
+{
+ l1h->config.setslot[data->tn].slottype = data->slottype;
+ l1h->config.setslot[data->tn].tsc_set = data->tsc_set;
+ l1h->config.setslot[data->tn].tsc_val = data->tsc_val;
+ l1h->config.setslot[data->tn].tsc_valid = data->tsc_valid;
+
+ l1h->config.setslot_valid[data->tn] = true;
+ l1h->config.setslot_sent[data->tn] = false;
+}
+
+/* Whether a given TRX is fully configured */
+static bool trx_is_provisioned(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (l1h->config.rxtune_acked && l1h->config.txtune_acked &&
+ (l1h->config.bsic_acked || !pinst->phy_link->u.osmotrx.use_legacy_setbsic) &&
+ (l1h->config.tsc_acked || pinst->phy_link->u.osmotrx.use_legacy_setbsic) &&
+ (l1h->config.nomtxpower_acked || l1h->config.nominal_power_set_by_vty) &&
+ (l1h->config.setformat_acked)) {
+ return true;
+ }
+ return false;
+}
+
+/* Whether a given TRX is fully configured and can be powered on */
+static bool trx_is_provisioned_and_enabled(struct trx_l1h *l1h)
+{
+ return l1h->config.enabled && trx_is_provisioned(l1h);
+}
+
+static void trx_signal_ready_trx0(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_instance *pinst_it;
+
+ llist_for_each_entry(pinst_it, &pinst->phy_link->instances, list) {
+ struct trx_l1h *l1h_it = pinst_it->u.osmotrx.hdl;
+ if (l1h_it->phy_inst->num != 0)
+ continue;
+ osmo_fsm_inst_dispatch(l1h_it->provision_fi, TRX_PROV_EV_OTHER_TRX_READY, NULL);
+ return;
+ }
+}
+
+/* Called from TRX0 to check if other TRX are already configured so POWERON can be sent */
+static bool trx_other_trx0_ready(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_instance *pinst_it;
+
+ /* Don't POWERON until all trx are ready */
+ llist_for_each_entry(pinst_it, &pinst->phy_link->instances, list) {
+ struct trx_l1h *l1h_it = pinst_it->u.osmotrx.hdl;
+ if (l1h_it->phy_inst->num == 0)
+ continue;
+ if (!trx_is_provisioned(l1h_it))
+ return false;
+ }
+ return true;
+}
+
+/* Closes a phy_link and all its associated TRX */
+static void trx_prov_fsm_apply_close(struct phy_link *plink, int rc)
+{
+ struct trx_l1h *l1h;
+ struct phy_instance *pinst;
+
+ if (plink->state == PHY_LINK_SHUTDOWN)
+ return;
+
+ bts_model_phy_link_close(plink);
+ /* Notify TRX close on all TRX associated with this phy */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ l1h = pinst->u.osmotrx.hdl;
+ trx_prov_fsm_state_chg(l1h->provision_fi, TRX_PROV_ST_CLOSED);
+ bts_model_trx_close_cb(pinst->trx, rc);
+ }
+}
+
+static int trx_prov_fsm_signal_cb(unsigned int subsys, unsigned int signal,
+ void *hdlr_data, void *signal_data)
+{
+ struct nm_statechg_signal_data *nsd;
+ struct gsm_bts_trx *trx;
+
+ if (subsys != SS_GLOBAL)
+ return -EINVAL;
+
+ if (signal != S_NEW_OP_STATE)
+ return 0;
+
+ nsd = (struct nm_statechg_signal_data *)signal_data;
+
+ if (nsd->mo->obj_class != NM_OC_RADIO_CARRIER)
+ return 0;
+
+ if (nsd->old_state != NM_OPSTATE_ENABLED && nsd->new_state == NM_OPSTATE_ENABLED) {
+ trx = gsm_objclass2obj(nsd->mo->bts, nsd->mo->obj_class, &nsd->mo->obj_inst, NULL);
+ l1if_trx_start_power_ramp(trx, NULL);
+ }
+ return 0;
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_closed(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+
+ switch (event) {
+ case TRX_PROV_EV_OPEN:
+ /* enable all slots */
+ l1h->config.slotmask = 0xff;
+ if (l1h->phy_inst->num == 0)
+ trx_if_cmd_poweroff(l1h, NULL); /* TODO: jump to poweroff upon cb received */
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWEROFF);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_open_poweroff_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+
+ trx_provision_reset(l1h);
+
+ if (pinst->trx == NULL) {
+ trx_if_cmd_rfmute(l1h, true);
+ return;
+ }
+
+ /* Apply initial RFMUTE state */
+ trx_if_cmd_rfmute(l1h, pinst->trx->mo.nm_state.administrative != NM_STATE_UNLOCKED);
+
+ osmo_fsm_inst_dispatch(pinst->trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(pinst->trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
+}
+
+static void st_open_poweroff(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->trx;
+ uint16_t arfcn;
+ int nominal_power;
+ int status;
+ bool waiting_other_trx;
+ bool was_ready = trx_is_provisioned(l1h);
+
+ switch (event) {
+ case TRX_PROV_EV_CLOSE:
+ /* In this state, we didn't for sure send a POWERON yet, hence we
+ are save directly applying the close as if we received a
+ POWEROFF RSP: */
+ if (pinst->num == 0)
+ trx_prov_fsm_apply_close(pinst->phy_link, 0);
+ return;
+ case TRX_PROV_EV_CFG_ENABLE:
+ l1h->config.enabled =(bool)data;
+ break;
+ case TRX_PROV_EV_CFG_BSIC:
+ /* We always get BSIC from the BSC, TSC can be derived from the BCC */
+ if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic) {
+ const uint8_t tsc = BSIC2BCC((uint8_t)(intptr_t)data);
+ if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) {
+ l1h->config.tsc = tsc;
+ l1h->config.tsc_valid = true;
+ l1h->config.tsc_sent = false;
+ }
+ } else {
+ const uint8_t bsic = (uint8_t)(intptr_t)data;
+ if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) {
+ l1h->config.bsic = bsic;
+ l1h->config.bsic_valid = true;
+ l1h->config.bsic_sent = false;
+ }
+ }
+ break;
+ case TRX_PROV_EV_CFG_ARFCN:
+ arfcn = (uint16_t)(intptr_t)data;
+ if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) {
+ l1h->config.arfcn = arfcn;
+ l1h->config.arfcn_valid = true;
+ l1h->config.txtune_sent = false;
+ l1h->config.rxtune_sent = false;
+ l1h->config.nomtxpower_sent = false;
+ }
+ break;
+ case TRX_PROV_EV_CFG_TS:
+ update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data);
+ break;
+
+ /* CONFIRMATIONS FROM TRXC */
+ case TRX_PROV_EV_RXTUNE_CNF:
+ if (l1h->config.rxtune_sent)
+ l1h->config.rxtune_acked = true;
+ break;
+ case TRX_PROV_EV_TXTUNE_CNF:
+ if (l1h->config.txtune_sent)
+ l1h->config.txtune_acked = true;
+ break;
+ case TRX_PROV_EV_NOMTXPOWER_CNF:
+ nominal_power = (int)(intptr_t)data;
+ if (l1h->config.nomtxpower_sent)
+ l1h->config.nomtxpower_acked = true;
+ l1if_trx_set_nominal_power(trx, nominal_power);
+ break;
+ case TRX_PROV_EV_SETBSIC_CNF:
+ if (l1h->config.bsic_sent)
+ l1h->config.bsic_acked = true;
+ break;
+ case TRX_PROV_EV_SETTSC_CNF:
+ if (l1h->config.tsc_sent)
+ l1h->config.tsc_acked = true;
+ break;
+ case TRX_PROV_EV_SETFORMAT_CNF:
+ status = (int)(intptr_t)data;
+ /* Transceiver may suggest a lower version (than requested) */
+ if (status == l1h->config.trxd_pdu_ver_req) {
+ l1h->config.trxd_pdu_ver_use = status;
+ l1h->config.setformat_acked = true;
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO,
+ "Using TRXD PDU version %u\n",
+ l1h->config.trxd_pdu_ver_use);
+ } else {
+ LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG,
+ "Transceiver suggests TRXD PDU version %u (requested %u)\n",
+ status, l1h->config.trxd_pdu_ver_req);
+ /* Send another SETFORMAT with suggested version */
+ l1h->config.trxd_pdu_ver_req = status;
+ l1h->config.setformat_sent = false;
+ }
+ break;
+ case TRX_PROV_EV_OTHER_TRX_READY:
+ OSMO_ASSERT(pinst->num == 0);
+ /* Do nothing here, we were triggered to see if we can finally poweron TRX0 below */
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ l1if_provision_transceiver_trx(l1h);
+
+ if (l1h->phy_inst->num == 0) {
+ waiting_other_trx = !trx_other_trx0_ready(l1h);
+ } else {
+ waiting_other_trx = false; /* we don't care about others in TRX!=0 */
+ /* If we just became ready for TRX0 POWERON (aka this TRX becomes provisioned), signal it to TRX0: */
+ if (l1h->phy_inst->num != 0 && (!was_ready && trx_is_provisioned(l1h)))
+ trx_signal_ready_trx0(l1h);
+ }
+
+ /* if we gathered all data and could go forward. For TRX0, only after
+ * all other TRX are prepared, since it will send POWERON commad */
+ if (trx_is_provisioned_and_enabled(l1h) && !waiting_other_trx) {
+ if (l1h->phy_inst->num != 0)
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON);
+ else
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWERON_CNF);
+ } else {
+ LOGPFSML(fi, LOGL_INFO, "Delay poweron, wait for:%s%s%s%s%s%s%s%s\n",
+ l1h->config.enabled ? "" :" enable",
+ pinst->phy_link->u.osmotrx.use_legacy_setbsic ?
+ (l1h->config.bsic_valid ? (l1h->config.bsic_acked ? "" : " bsic-ack") : " bsic") :
+ (l1h->config.tsc_valid ? (l1h->config.tsc_acked ? "" : " tsc-ack") : " tsc"),
+ l1h->config.arfcn_valid ? "" : " arfcn",
+ l1h->config.rxtune_acked ? "" : " rxtune-ack",
+ l1h->config.txtune_acked ? "" : " txtune-ack",
+ l1h->config.nominal_power_set_by_vty ? "" : (l1h->config.nomtxpower_acked ? "" : " nomtxpower-ack"),
+ l1h->config.setformat_acked ? "" : " setformat-ack",
+ waiting_other_trx ? "" : " other-trx"
+ );
+ }
+}
+
+
+static void st_open_wait_power_cnf_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+
+ trx_if_cmd_poweron(l1h, l1if_poweronoff_cb);
+ pinst->phy_link->u.osmotrx.poweron_sent = true;
+}
+
+static void st_open_wait_power_cnf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ int rc;
+
+ switch (event) {
+ case TRX_PROV_EV_POWERON_CNF:
+ rc = (uint16_t)(intptr_t)data;
+ if (rc == 0 && plink->state != PHY_LINK_CONNECTED) {
+ trx_sched_clock_started(pinst->trx->bts);
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON);
+ } else if (rc != 0 && plink->state != PHY_LINK_SHUTDOWN) {
+ trx_sched_clock_stopped(pinst->trx->bts);
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ }
+ break;
+ case TRX_PROV_EV_CFG_TS:
+ update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data);
+ break;
+ case TRX_PROV_EV_CLOSE:
+ /* power off transceiver, if not already */
+ if (pinst->num == 0 && !plink->u.osmotrx.poweroff_sent) {
+ trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb);
+ plink->u.osmotrx.poweroff_sent = true;
+ }
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_open_poweron_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ uint8_t tn;
+
+ /* after power on */
+ if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) {
+ trx_if_cmd_setrxgain(l1h, l1h->config.rxgain);
+ l1h->config.rxgain_sent = true;
+ }
+ if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) {
+ trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly);
+ l1h->config.maxdly_sent = true;
+ }
+ if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) {
+ trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb);
+ l1h->config.maxdlynb_sent = true;
+ }
+
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ if (l1h->config.setslot_valid[tn]
+ && !l1h->config.setslot_sent[tn]) {
+ trx_if_cmd_setslot(l1h, tn, l1if_setslot_cb);
+ l1h->config.setslot_sent[tn] = true;
+ }
+ }
+}
+
+static void st_open_poweron(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ struct trx_prov_ev_cfg_ts_data* ts_data;
+
+ switch (event) {
+ case TRX_PROV_EV_CLOSE:
+ /* power off transceiver, if not already */
+ if (pinst->num == 0 && plink->u.osmotrx.powered && !plink->u.osmotrx.poweroff_sent) {
+ trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb);
+ plink->u.osmotrx.poweroff_sent = true;
+ }
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF);
+ break;
+ case TRX_PROV_EV_CFG_TS:
+ ts_data = (struct trx_prov_ev_cfg_ts_data*)data;
+ update_ts_data(l1h, ts_data);
+ /* While in this state we can send SETSLOT immediately */
+ trx_if_cmd_setslot(l1h, ts_data->tn, l1if_setslot_cb);
+ l1h->config.setslot_sent[ts_data->tn] = true;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_open_wait_poweroff_cnf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ int rc;
+
+ switch (event) {
+ case TRX_PROV_EV_POWEROFF_CNF:
+ rc = (uint16_t)(intptr_t)data;
+ trx_prov_fsm_apply_close(plink, rc);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state trx_prov_fsm_states[] = {
+ [TRX_PROV_ST_CLOSED] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_OPEN),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_POWEROFF),
+ .name = "CLOSED",
+ .action = st_closed,
+ },
+ [TRX_PROV_ST_OPEN_POWEROFF] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_CLOSE) |
+ X(TRX_PROV_EV_OTHER_TRX_READY) |
+ X(TRX_PROV_EV_CFG_ENABLE) |
+ X(TRX_PROV_EV_CFG_BSIC) |
+ X(TRX_PROV_EV_CFG_ARFCN) |
+ X(TRX_PROV_EV_CFG_TS) |
+ X(TRX_PROV_EV_RXTUNE_CNF) |
+ X(TRX_PROV_EV_TXTUNE_CNF) |
+ X(TRX_PROV_EV_NOMTXPOWER_CNF) |
+ X(TRX_PROV_EV_SETBSIC_CNF) |
+ X(TRX_PROV_EV_SETTSC_CNF) |
+ X(TRX_PROV_EV_SETFORMAT_CNF),
+ .out_state_mask =
+ X(TRX_PROV_ST_CLOSED) |
+ X(TRX_PROV_ST_OPEN_WAIT_POWERON_CNF) |
+ X(TRX_PROV_ST_OPEN_POWERON),
+ .name = "OPEN_POWEROFF",
+ .onenter = st_open_poweroff_on_enter,
+ .action = st_open_poweroff,
+ },
+ [TRX_PROV_ST_OPEN_WAIT_POWERON_CNF] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_CLOSE) |
+ X(TRX_PROV_EV_POWERON_CNF) |
+ X(TRX_PROV_EV_CFG_TS),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_POWERON) |
+ X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF),
+ .name = "OPEN_WAIT_POWERON_CNF",
+ .onenter = st_open_wait_power_cnf_on_enter,
+ .action = st_open_wait_power_cnf,
+ },
+ [TRX_PROV_ST_OPEN_POWERON] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_CLOSE) |
+ X(TRX_PROV_EV_CFG_TS),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF),
+ .name = "OPEN_POWERON",
+ .onenter = st_open_poweron_on_enter,
+ .action = st_open_poweron,
+ },
+ [TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_POWEROFF_CNF),
+ .out_state_mask =
+ X(TRX_PROV_ST_CLOSED),
+ .name = "OPEN_WAIT_POWEROFF_CNF",
+ .action = st_open_wait_poweroff_cnf,
+ },
+};
+
+const struct value_string trx_prov_fsm_event_names[] = {
+ OSMO_VALUE_STRING(TRX_PROV_EV_OTHER_TRX_READY),
+ OSMO_VALUE_STRING(TRX_PROV_EV_OPEN),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_ENABLE),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_BSIC),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_ARFCN),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_TS),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_RXGAIN),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_SETMAXDLY),
+ OSMO_VALUE_STRING(TRX_PROV_EV_RXTUNE_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_TXTUNE_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_NOMTXPOWER_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_SETBSIC_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_SETTSC_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_SETFORMAT_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_POWERON_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_POWEROFF_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CLOSE),
+ { 0, NULL }
+};
+
+struct osmo_fsm trx_prov_fsm = {
+ .name = "TRX_PROV",
+ .states = trx_prov_fsm_states,
+ .num_states = ARRAY_SIZE(trx_prov_fsm_states),
+ .event_names = trx_prov_fsm_event_names,
+ .log_subsys = DL1C,
+};
+
+static __attribute__((constructor)) void trx_prov_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&trx_prov_fsm) == 0);
+ OSMO_ASSERT(osmo_signal_register_handler(SS_GLOBAL, trx_prov_fsm_signal_cb, NULL) == 0);
+}
diff --git a/src/osmo-bts-trx/trx_provision_fsm.h b/src/osmo-bts-trx/trx_provision_fsm.h
new file mode 100644
index 00000000..e89cd328
--- /dev/null
+++ b/src/osmo-bts-trx/trx_provision_fsm.h
@@ -0,0 +1,68 @@
+/* Provision TRX over TRXC protocol FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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/>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include <osmocom/core/fsm.h>
+
+enum trx_provision_fsm_states {
+ TRX_PROV_ST_CLOSED,
+ TRX_PROV_ST_OPEN_POWEROFF,
+ TRX_PROV_ST_OPEN_WAIT_POWERON_CNF,
+ TRX_PROV_ST_OPEN_POWERON,
+ TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF,
+};
+
+struct trx_prov_ev_cfg_ts_data {
+ uint8_t tn;
+ uint8_t slottype;
+
+ /* Training Sequence Code and Set */
+ uint8_t tsc_set;
+ uint8_t tsc_val;
+ bool tsc_valid;
+};
+
+enum trx_provision_fsm_events {
+ TRX_PROV_EV_OTHER_TRX_READY,
+ TRX_PROV_EV_OPEN,
+ TRX_PROV_EV_CFG_ENABLE,
+ TRX_PROV_EV_CFG_BSIC,
+ TRX_PROV_EV_CFG_ARFCN,
+ TRX_PROV_EV_CFG_TSC,
+ TRX_PROV_EV_CFG_TS,
+ TRX_PROV_EV_CFG_RXGAIN,
+ TRX_PROV_EV_CFG_SETMAXDLY,
+ TRX_PROV_EV_RXTUNE_CNF,
+ TRX_PROV_EV_TXTUNE_CNF,
+ TRX_PROV_EV_NOMTXPOWER_CNF,
+ TRX_PROV_EV_SETBSIC_CNF,
+ TRX_PROV_EV_SETTSC_CNF,
+ TRX_PROV_EV_SETFORMAT_CNF,
+ TRX_PROV_EV_POWERON_CNF,
+ TRX_PROV_EV_POWEROFF_CNF,
+ TRX_PROV_EV_CLOSE,
+};
+
+extern struct osmo_fsm trx_prov_fsm;
diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c
index c52908e0..9056f027 100644
--- a/src/osmo-bts-trx/trx_vty.c
+++ b/src/osmo-bts-trx/trx_vty.c
@@ -1,4 +1,4 @@
-/* VTY interface for sysmoBTS */
+/* VTY interface for osmo-bts-trx */
/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
*
@@ -42,37 +42,35 @@
#include <osmo-bts/logging.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/scheduler.h>
+#include <osmo-bts/bts.h>
#include "l1_if.h"
#include "trx_if.h"
-#include "loops.h"
+#include "amr_loop.h"
-#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n"
+#define X(x) (1 << x)
-static struct gsm_bts *vty_bts;
+#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n"
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;
+ unsigned int tn;
- 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) {
+ llist_for_each_entry(trx, &g_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);
+ struct phy_link *plink = pinst->phy_link;
+ char *sname = osmo_sock_get_name(NULL, plink->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",
+ trx_if_powered(l1h) ? "poweron":"poweroff",
VTY_NEWLINE);
+ vty_out(vty, "phy link state: %s%s",
+ phy_link_state_name(phy_link_state_get(plink)), VTY_NEWLINE);
if (l1h->config.arfcn_valid)
vty_out(vty, " arfcn : %d%s%s",
(l1h->config.arfcn & ~ARFCN_PCS),
@@ -89,7 +87,28 @@ DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
vty_out(vty, " bsic : %d%s", l1h->config.bsic,
VTY_NEWLINE);
else
- vty_out(vty, " bisc : undefined%s", VTY_NEWLINE);
+ vty_out(vty, " bsic : undefined%s", VTY_NEWLINE);
+
+ /* trx->ts[tn].priv is NULL in absence of the A-bis connection */
+ if (trx->bb_transc.rsl.link == NULL)
+ continue;
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ const struct l1sched_ts *l1ts = ts->priv;
+ const struct trx_sched_multiframe *mf;
+
+ OSMO_ASSERT(l1ts != NULL);
+ mf = &trx_sched_multiframes[l1ts->mf_index];
+
+ vty_out(vty, " timeslot #%u (%s)%s",
+ tn, mf->name, VTY_NEWLINE);
+ vty_out(vty, " pending DL prims : %u%s",
+ llist_count(&l1ts->dl_prims), VTY_NEWLINE);
+ vty_out(vty, " interference : %ddBm%s",
+ l1ts->chan_state[TRXC_IDLE].meas.interf_avg,
+ VTY_NEWLINE);
+ }
}
return CMD_SUCCESS;
@@ -100,20 +119,25 @@ static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst)
{
uint8_t tn;
struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ struct gsm_bts_trx *trx = pinst->trx;
- vty_out(vty, "PHY Instance %s%s",
- phy_instance_name(pinst), VTY_NEWLINE);
+ vty_out(vty, "PHY Instance '%s': bound to %s%s",
+ phy_instance_name(pinst),
+ gsm_trx_name(trx),
+ VTY_NEWLINE);
+
+ if (trx != NULL) {
+ const int actual = get_p_actual_mdBm(trx, trx->power_params.p_total_tgt_mdBm);
+ const int max = get_p_max_out_mdBm(trx);
+ vty_out(vty, " tx-attenuation : %d dB%s",
+ (max - actual) / 1000, 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);
@@ -125,16 +149,23 @@ static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst)
else
vty_out(vty, " maxdlynb : undefined%s", VTY_NEWLINE);
for (tn = 0; tn < TRX_NR_TS; tn++) {
- if (!((1 << tn) & l1h->config.slotmask))
+ 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
+ continue;
+ } else if (!l1h->config.setslot_valid[tn]) {
vty_out(vty, " slot #%d: undefined%s", tn,
VTY_NEWLINE);
+ continue;
+ }
+
+ vty_out(vty, " slot #%d: type %d", tn,
+ l1h->config.setslot[tn].slottype);
+ if (l1h->config.setslot[tn].tsc_valid)
+ vty_out(vty, " TSC-s%dc%d",
+ l1h->config.setslot[tn].tsc_set,
+ l1h->config.setslot[tn].tsc_val);
+ vty_out(vty, "%s", VTY_NEWLINE);
}
}
@@ -163,96 +194,146 @@ DEFUN(show_phy, show_phy_cmd, "show phy",
return CMD_SUCCESS;
}
-DEFUN(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd,
+DEFUN_HIDDEN(test_send_trxc,
+ test_send_trxc_cmd,
+ "test send-trxc-cmd <0-255> CMD [.ARGS]",
+ "Various testing commands\n"
+ "Send an arbitrary TRX command\n"
+ "Transceiver number\n"
+ "TRXC command\n" "TRXC command arguments\n")
+{
+ const struct gsm_bts_trx *trx;
+ const struct phy_instance *pinst;
+ struct trx_l1h *l1h;
+ int rc;
+
+ trx = gsm_bts_trx_num(g_bts, atoi(argv[0]));
+ if (trx == NULL) {
+ vty_out(vty, "%% Could not find TRX%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst = trx_phy_instance(trx);
+ l1h = pinst->u.osmotrx.hdl;
+
+ if (argc > 2) {
+ char *cmd_args = argv_concat(argv, argc, 2);
+ rc = trx_ctrl_cmd(l1h, 0, argv[1], "%s", cmd_args);
+ talloc_free(cmd_args);
+ } else {
+ rc = trx_ctrl_cmd(l1h, 0, argv[1], "");
+ }
+
+ return (rc == 0) ? CMD_SUCCESS : CMD_WARNING;
+}
+
+DEFUN_USRATTR(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "nominal-tx-power <-10-100>",
+ "Manually set (force) the nominal transmit output power in dBm\n"
+ "Nominal transmit output power level in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ int val = atoi(argv[0]);
+
+ l1if_trx_set_nominal_power(trx, val);
+ l1h->config.nominal_power_set_by_vty = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_trx_no_nominal_power, cfg_trx_no_nominal_power_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "no nominal-tx-power",
+ NO_STR
+ "Manually set (force) the nominal transmit output power; ask the TRX instead (default)\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.nominal_power_set_by_vty = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(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;
+ vty_out(vty, "'%s' is deprecated, MS Power Control is now managed by BSC%s",
+ self->string, VTY_NEWLINE);
- plink->u.osmotrx.trx_target_rssi = atoi(argv[0]);
- plink->u.osmotrx.trx_ms_power_loop = true;
+ uint8_t rxlev = dbm2rxlev(atoi(argv[0]));
+ g_bts->ms_dpc_params.rxlev_meas.lower_thresh = rxlev;
+ g_bts->ms_dpc_params.rxlev_meas.upper_thresh = rxlev;
return CMD_SUCCESS;
}
-DEFUN(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd,
+DEFUN_DEPRECATED(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;
+ vty_out(vty, "'%s' is deprecated, MS Power Control is now managed by BSC%s",
+ self->string, VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd,
+DEFUN_DEPRECATED(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;
+ vty_out(vty, "'%s' is deprecated, Timing Advance loop is now active by default%s",
+ self->string, VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd,
+DEFUN_DEPRECATED(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;
+ vty_out(vty, "'%s' is deprecated, Timing Advance loop is now active by default%s",
+ self->string, VTY_NEWLINE);
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")
+DEFUN_USRATTR(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "osmotrx maxdly <0-63>",
+ OSMOTRX_STR
+ "Set the maximum acceptable delay of an Access Burst\n"
+ "Delay in GSMK symbol periods (approx. 550m 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);
+ l1h->config.maxdly_sent = false;
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 controlled 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")
+DEFUN_ATTR_USRATTR(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd,
+ CMD_ATTR_HIDDEN, /* expert mode command */
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "osmotrx maxdlynb <0-63>",
+ OSMOTRX_STR
+ "Set the maximum acceptable delay of a Normal Burst\n"
+ "Delay in GMSK symbol periods (approx. 550m 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);
+ l1h->config.maxdlynb_sent = false;
return CMD_SUCCESS;
}
@@ -277,7 +358,7 @@ DEFUN(cfg_phyinst_slotmask, cfg_phyinst_slotmask_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd,
+DEFUN_DEPRECATED(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd,
"osmotrx power (on|off)",
OSMOTRX_STR
"Change TRX state\n"
@@ -286,21 +367,26 @@ DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd,
struct phy_instance *pinst = vty->index;
struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ vty_out (vty, "'osmotrx power' is deprecated, use OML's standard "
+ "Administrative State instead to control each TRX "
+ "('rf_locked' VTY cmd in osmo-bsc)%s", VTY_NEWLINE);
+
if (strcmp(argv[0], "on"))
- vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h), VTY_NEWLINE);
+ vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h, NULL), VTY_NEWLINE);
else {
- vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h), VTY_NEWLINE);
+ vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h, NULL), 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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct phy_link *plink = vty->index;
@@ -309,12 +395,13 @@ DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd,
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")
+DEFUN_ATTR(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",
+ CMD_ATTR_IMMEDIATE)
{
struct phy_link *plink = vty->index;
@@ -323,61 +410,49 @@ DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd,
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")
+DEFUN_USRATTR(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "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);
+ l1h->config.rxgain_sent = false;
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")
+DEFUN_ATTR(cfg_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd,
+ "osmotrx tx-attenuation (oml|<0-50>)",
+ OSMOTRX_STR
+ "Set the transmitter attenuation\n"
+ "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML (default)\n"
+ "Fixed attenuation in dB, overriding OML (default)\n",
+ CMD_ATTR_IMMEDIATE)
{
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;
+ if (strcmp(argv[0], "oml") == 0)
+ l1h->config.forced_max_power_red = -1;
+ else
+ l1h->config.forced_max_power_red = atoi(argv[0]);
- l1h->config.power_oml = 1;
- l1h->config.power_valid = 1;
- l1h->config.power_sent = 0;
- l1if_provision_transceiver_trx(l1h);
+ if (pinst->trx && pinst->trx->mo.nm_state.operational == NM_OPSTATE_ENABLED)
+ l1if_trx_start_power_ramp(pinst->trx, NULL);
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")
+DEFUN_USRATTR(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "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;
@@ -387,22 +462,11 @@ DEFUN(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd,
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")
+DEFUN_USRATTR(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "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;
@@ -412,10 +476,11 @@ DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd,
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")
+DEFUN_USRATTR(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "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;
@@ -472,9 +537,10 @@ DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd,
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")
+DEFUN_USRATTR(cfg_phy_setbsic, cfg_phy_setbsic_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "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;
@@ -486,9 +552,10 @@ DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd,
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")
+DEFUN_USRATTR(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "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;
@@ -496,30 +563,31 @@ DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_phy_trxd_max_version, cfg_phy_trxd_max_version_cmd,
- "osmotrx trxd-max-version (latest|<0-15>)", OSMOTRX_STR
- "Set maximum TRXD format version to negotiate with TRX\n"
- "Use latest supported TRXD format version (default)\n"
- "Maximum TRXD format version number\n")
+DEFUN_USRATTR(cfg_phy_trxd_max_version, cfg_phy_trxd_max_version_cmd,
+ X(BTS_VTY_TRX_POWERCYCLE),
+ "osmotrx trxd-max-version (latest|<0-15>)", OSMOTRX_STR
+ "Set maximum TRXD format version to negotiate with TRX\n"
+ "Use latest supported TRXD format version (default)\n"
+ "Maximum TRXD format version number\n")
{
struct phy_link *plink = vty->index;
int max_ver;
if (strcmp(argv[0], "latest") == 0)
- max_ver = TRX_DATA_FORMAT_VER;
+ max_ver = TRX_DATA_PDU_VER;
else
max_ver = atoi(argv[0]);
- if (max_ver > TRX_DATA_FORMAT_VER) {
+ if (max_ver > TRX_DATA_PDU_VER) {
vty_out(vty, "%% Format version %d is not supported, maximum supported is %d%s",
- max_ver, TRX_DATA_FORMAT_VER, VTY_NEWLINE);
+ max_ver, TRX_DATA_PDU_VER, VTY_NEWLINE);
return CMD_WARNING;
}
- plink->u.osmotrx.trxd_hdr_ver_max = max_ver;
+ plink->u.osmotrx.trxd_pdu_ver_max = max_ver;
return CMD_SUCCESS;
}
-void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink)
{
if (plink->u.osmotrx.local_ip)
vty_out(vty, " osmotrx ip local %s%s",
@@ -528,12 +596,6 @@ void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
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);
@@ -549,24 +611,22 @@ void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
if (plink->u.osmotrx.use_legacy_setbsic)
vty_out(vty, " osmotrx legacy-setbsic%s", VTY_NEWLINE);
- if (plink->u.osmotrx.trxd_hdr_ver_max != TRX_DATA_FORMAT_VER)
- vty_out(vty, " osmotrx trxd-max-version %d%s", plink->u.osmotrx.trxd_hdr_ver_max, VTY_NEWLINE);
+ if (plink->u.osmotrx.trxd_pdu_ver_max != TRX_DATA_PDU_VER)
+ vty_out(vty, " osmotrx trxd-max-version %d%s", plink->u.osmotrx.trxd_pdu_ver_max, VTY_NEWLINE);
}
-void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+void bts_model_config_write_phy_inst(struct vty *vty, const 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.forced_max_power_red == -1)
+ vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " osmotrx tx-attenuation %d%s",
+ l1h->config.forced_max_power_red, VTY_NEWLINE);
if (l1h->config.maxdly_valid)
vty_out(vty, " osmotrx maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE);
if (l1h->config.maxdlynb_valid)
@@ -584,21 +644,30 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst
VTY_NEWLINE);
}
-void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts)
{
}
-void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx)
{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (l1h->config.nominal_power_set_by_vty)
+ vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,
+ VTY_NEWLINE);
}
-int bts_model_vty_init(struct gsm_bts *bts)
+int bts_model_vty_init(void *ctx)
{
- vty_bts = bts;
-
install_element_ve(&show_transceiver_cmd);
install_element_ve(&show_phy_cmd);
+ install_element(ENABLE_NODE, &test_send_trxc_cmd);
+
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+ install_element(TRX_NODE, &cfg_trx_no_nominal_power_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);
@@ -614,9 +683,7 @@ int bts_model_vty_init(struct gsm_bts *bts)
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);
diff --git a/src/osmo-bts-virtual/Makefile.am b/src/osmo-bts-virtual/Makefile.am
index 070efed6..bbb79eca 100644
--- a/src/osmo-bts-virtual/Makefile.am
+++ b/src/osmo-bts-virtual/Makefile.am
@@ -1,10 +1,50 @@
-AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+AM_CFLAGS = \
+ -Wall -fno-strict-aliasing \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMOTRAU_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBOSMOCODEC_CFLAGS) \
+ $(LIBGPS_CFLAGS) \
+ $(NULL)
+
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
+COMMON_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOTRAU_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(LIBOSMOCODEC_LIBS) \
+ -ldl \
+ $(NULL)
+
+noinst_HEADERS = \
+ l1_if.h \
+ osmo_mcast_sock.h \
+ virtual_um.h \
+ $(NULL)
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)
+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 \
+ $(NULL)
+
+osmo_bts_virtual_LDADD = \
+ $(top_builddir)/src/common/libl1sched.a \
+ $(top_builddir)/src/common/libbts.a \
+ $(COMMON_LDADD) \
+ $(NULL)
diff --git a/src/osmo-bts-virtual/bts_model.c b/src/osmo-bts-virtual/bts_model.c
index b971af5c..a70abfb3 100644
--- a/src/osmo-bts-virtual/bts_model.c
+++ b/src/osmo-bts-virtual/bts_model.c
@@ -10,7 +10,7 @@
* 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.
+ * 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/>.
@@ -23,6 +23,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/codec/codec.h>
+#include <osmocom/core/fsm.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/phy_link.h>
@@ -34,6 +35,9 @@
#include <osmo-bts/bts_model.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/l1sap.h>
+#include <osmo-bts/nm_common_fsm.h>
+
+#include "virtual_um.h"
/* TODO: check if dummy method is sufficient, else implement */
int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
@@ -50,10 +54,17 @@ int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr,
return -1;
}
-int bts_model_trx_close(struct gsm_bts_trx *trx)
+void bts_model_trx_close(struct gsm_bts_trx *trx)
{
- LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
- return 0;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+
+ if (phy_link_state_get(plink) != PHY_LINK_SHUTDOWN) {
+ virt_um_destroy(plink->u.virt.virt_um);
+ plink->u.virt.virt_um = NULL;
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ }
+ bts_model_trx_close_cb(trx, 0);
}
int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
@@ -72,18 +83,11 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
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);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
}
return 0;
}
@@ -96,34 +100,78 @@ static uint8_t vbts_set_trx(struct gsm_bts_trx *trx)
static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts)
{
- struct phy_instance *pinst = trx_phy_instance(ts->trx);
- int rc;
+ 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);
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ pchan = GSM_PCHAN_PDCH;
+ else
+ pchan = GSM_PCHAN_TCH_F;
+ break;
+ case GSM_PCHAN_OSMO_DYN:
+ OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want);
+ pchan = ts->dyn.pchan_is;
+ break;
+ default:
+ pchan = ts->pchan;
+ break;
+ }
- rc = trx_sched_set_pchan(&pinst->u.virt.sched, ts->nr, ts->pchan);
- if (rc)
+ if (trx_sched_set_pchan(ts, pchan) != 0)
return NM_NACK_RES_NOTAVAIL;
+ /* activate lchans for [CBCH/]BCCH/CCCH */
+ switch (pchan) {
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* using RSL_CHAN_OSMO_CBCH4 is correct here, because the scheduler
+ * does not distinguish between SDCCH/4+CBCH abd SDCCH/8+CBCH. */
+ trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN],
+ RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true);
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN],
+ RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true);
+ /* fall-through */
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH:
+ trx_sched_set_bcch_ccch(&ts->lchan[CCCH_LCHAN], true);
+ ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE);
+ break;
+ default:
+ break;
+ }
+
return 0;
}
-int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
- struct tlv_parsed *new_attr, int kind, void *obj)
+int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
+ struct gsm_abis_mo *mo, void *obj)
{
struct abis_om_fom_hdr *foh = msgb_l3(msg);
- int cause = 0;
+ int rc;
switch (foh->msg_type) {
case NM_MT_SET_BTS_ATTR:
- cause = vbts_set_bts(obj);
+ rc = vbts_set_bts(obj);
break;
case NM_MT_SET_RADIO_ATTR:
- cause = vbts_set_trx(obj);
+ rc = vbts_set_trx(obj);
break;
case NM_MT_SET_CHAN_ATTR:
- cause = vbts_set_ts(obj);
+ rc = vbts_set_ts(obj);
+ break;
+ default:
+ rc = 0;
break;
}
- return oml_fom_ack_nack(msg, cause);
+
+ return rc;
}
/* MO: TS 12.21 Managed Object */
@@ -132,16 +180,15 @@ 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_RADIO_CARRIER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_CHANNEL:
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);
+ rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
break;
default:
rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
@@ -166,6 +213,7 @@ int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
{
LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ power_trx_change_compl(trx, p_trxout_mdBm);
return 0;
}
diff --git a/src/osmo-bts-virtual/l1_if.c b/src/osmo-bts-virtual/l1_if.c
index acd8ea2b..2408557d 100644
--- a/src/osmo-bts-virtual/l1_if.c
+++ b/src/osmo-bts-virtual/l1_if.c
@@ -42,6 +42,7 @@
#include <osmo-bts/amr.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/scheduler.h>
+#include <osmo-bts/handover.h>
#include "virtual_um.h"
extern int vbts_sched_start(struct gsm_bts *bts);
@@ -128,12 +129,7 @@ static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg)
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
+ /* This is TCH signalling, for voice frames see GSMTAP_CHANNEL_VOICE */
case GSMTAP_CHANNEL_SDCCH4:
case GSMTAP_CHANNEL_SDCCH8:
case GSMTAP_CHANNEL_PACCH:
@@ -151,6 +147,19 @@ static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg)
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_VOICE_F:
+ case GSMTAP_CHANNEL_VOICE_H:
+ /* the first byte indicates the type of voice codec (gsmtap_um_voice_type) */
+ msg->l2h = msgb_pull(msg, 1);
+ 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;
+ l1sap.u.tch.rssi = 0; /* Radio Signal Strength Indicator. Best -> 0 */
+ l1sap.u.tch.ber10k = 0; /* Bit Error Rate in 0.01%. Best -> 0 */
+ l1sap.u.tch.ta_offs_256bits = 0; /* Burst time of arrival in quarter bits. Probably used for Timing Advance calc. Best -> 0 */
+ l1sap.u.tch.lqual_cb = 10 * signal_dbm; /* Link quality in centiBel = 10 * dB. */
+ 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:
@@ -179,6 +188,11 @@ nomessage:
/* called by common part once OML link is established */
int bts_model_oml_estab(struct gsm_bts *bts)
{
+ struct phy_instance *pinst = trx_phy_instance(bts->c0);
+
+ if (vbts_sched_start(pinst->trx->bts) < 0)
+ return -ENOLINK;
+
return 0;
}
@@ -196,7 +210,7 @@ int bts_model_phy_link_open(struct phy_link *plink)
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);
+ plink->u.virt.ttl, plink->u.virt.mcast_dev, virt_um_rcv_cb);
if (!plink->u.virt.virt_um) {
phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
return -1;
@@ -206,20 +220,9 @@ int bts_model_phy_link_open(struct phy_link *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);
- }
+ if (pinst->trx == NULL)
+ continue;
+ trx_sched_init(pinst->trx);
}
/* this will automatically update the MO state of all associated TRX objects */
@@ -236,28 +239,19 @@ int bts_model_phy_link_open(struct phy_link *plink)
/* 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);
+ trx_sched_set_cipher(lchan, chan_nr, false);
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);
+ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF)
+ trx_sched_set_cipher(lchan, chan_nr, false);
+ trx_sched_set_cipher(lchan, chan_nr, true);
lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
}
@@ -318,12 +312,12 @@ static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t f
/* 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);
+ LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, "RX L1 frame chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS "
+ "ber=%.2f%% (%d/%d bits) L1_ta=%d ta_ctrl.current=%d toa=%.2f\n", chan_nr,
+ ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.max), rssi, ber * 100, n_errors,
+ n_bits_total, lchan->meas.l1_info.ta, lchan->ta_ctrl.current, toa);
- l1if_fill_meas_res(&l1sap, chan_nr, lchan->rqd_ta + toa, ber, rssi, fn);
+ l1if_fill_meas_res(&l1sap, chan_nr, lchan->ta_ctrl.current + toa, ber, rssi, fn);
return l1sap_up(trx, &l1sap);
}
@@ -333,11 +327,8 @@ static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t f
/* 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;
@@ -346,95 +337,94 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
if (!msg)
break;
/* put data into scheduler's queue */
- return trx_sched_ph_data_req(sched, l1sap);
+ return trx_sched_ph_data_req(trx, 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);
+ return trx_sched_tch_req(trx, l1sap);
case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ if (l1sap->u.info.type == PRIM_INFO_ACT_CIPH)
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ else /* u.act_req used by PRIM_INFO_{ACTIVATE,DEACTIVATE,MODIFY} */
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (OSMO_UNLIKELY(lchan == NULL)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Rx MPH-INFO.req (type=0x%02x) for non-existent lchan (%s)\n",
+ l1sap->u.info.type, rsl_chan_nr_str(chan_nr));
+ rc = -ENODEV;
+ break;
+ }
+
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);
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot activate"
+ " channel %s\n", rsl_chan_nr_str(chan_nr));
+ rc = -EPERM;
break;
}
+ /* activate dedicated channel */
+ trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, true);
+ /* activate associated channel */
+ trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, true);
+ /* set mode */
+ trx_sched_set_mode(lchan->ts, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.mode[0].mode,
+ lchan->tch.amr_mr.mode[1].mode,
+ lchan->tch.amr_mr.mode[2].mode,
+ lchan->tch.amr_mr.mode[3].mode,
+ amr_get_initial_mode(lchan),
+ (lchan->ho.active == HANDOVER_ENABLED ||
+ rsl_chan_rt_is_asci(lchan->rsl_chan_rt)));
+ /* 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;
+ case PRIM_INFO_MODIFY:
+ /* change mode */
+ trx_sched_set_mode(lchan->ts, chan_nr,
+ lchan->rsl_cmode, lchan->tch_mode,
+ lchan->tch.amr_mr.num_modes,
+ lchan->tch.amr_mr.mode[0].mode,
+ lchan->tch.amr_mr.mode[1].mode,
+ lchan->tch.amr_mr.mode[2].mode,
+ lchan->tch.amr_mr.mode[3].mode,
+ amr_get_initial_mode(lchan),
+ 0);
+ break;
+ case PRIM_INFO_DEACTIVATE:
if ((chan_nr & 0xE0) == 0x80) {
- LOGP(DL1C, LOGL_ERROR, "Cannot deactivate "
- "chan_nr 0x%02x\n", chan_nr);
+ LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot deactivate"
+ " channel %s\n", rsl_chan_nr_str(chan_nr));
+ rc = -EPERM;
break;
}
/* deactivate associated channel */
- trx_sched_set_lchan(sched, chan_nr, 0x40, 0);
+ trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, false);
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);
+ trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, false);
/* confirm only on dedicated channel */
mph_info_chan_confirm(trx, chan_nr,
PRIM_INFO_DEACTIVATE, 0);
diff --git a/src/osmo-bts-virtual/l1_if.h b/src/osmo-bts-virtual/l1_if.h
index 6a843b37..c55e2d38 100644
--- a/src/osmo-bts-virtual/l1_if.h
+++ b/src/osmo-bts-virtual/l1_if.h
@@ -5,9 +5,15 @@
#include "virtual_um.h"
+/* gsm_bts->model_priv, specific to osmo-bts-virtual */
+struct bts_virt_priv {
+ uint32_t last_fn;
+ struct timeval tv_clock;
+ struct osmo_timer_list fn_timer;
+};
+
struct vbts_l1h {
struct gsm_bts_trx *trx;
- struct l1sched_trx l1s;
struct virt_um_inst *virt_um;
};
diff --git a/src/osmo-bts-virtual/main.c b/src/osmo-bts-virtual/main.c
index aa1c608e..82becc37 100644
--- a/src/osmo-bts-virtual/main.c
+++ b/src/osmo-bts-virtual/main.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -47,39 +47,68 @@
#include <osmo-bts/l1sap.h>
#include <osmo-bts/phy_link.h>
#include "virtual_um.h"
+#include "l1_if.h"
/* dummy, since no direct dsp support */
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
return 0;
}
int bts_model_init(struct gsm_bts *bts)
{
+ struct bts_virt_priv *bts_virt = talloc_zero(bts, struct bts_virt_priv);
+ bts->model_priv = bts_virt;
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);
+ bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS
+ | NM_IPAC_MASK_GPRS_CODING_MCS;
+
+ /* order alphabetically */
+ osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR);
+ osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
return 0;
}
int bts_model_trx_init(struct gsm_bts_trx *trx)
{
+ /* Frequency bands indicated to the BSC */
+ trx->support.freq_bands = NM_IPAC_F_FREQ_BAND_PGSM
+ | NM_IPAC_F_FREQ_BAND_EGSM
+ | NM_IPAC_F_FREQ_BAND_RGSM
+ | NM_IPAC_F_FREQ_BAND_DCS
+ | NM_IPAC_F_FREQ_BAND_PCS
+ | NM_IPAC_F_FREQ_BAND_850
+ | NM_IPAC_F_FREQ_BAND_480
+ | NM_IPAC_F_FREQ_BAND_450;
+
+ /* Channel types and modes indicated to the BSC */
+ trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON
+ | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH
+ | NM_IPAC_F_CHANT_SDCCH8_CBCH
+ | NM_IPAC_F_CHANT_PDCHF
+ | NM_IPAC_F_CHANT_TCHF_PDCHF;
+ trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH
+ | NM_IPAC_MASK_CHANM_CSD_NT
+ | NM_IPAC_MASK_CHANM_CSD_T;
+ /* TODO: missing rate adaptation for TCH/F14.4 (see OS#6167) */
+ trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_T_14k4;
+ trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_NT_14k4;
+
return 0;
}
void bts_model_print_help()
{
- LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Unimplemented %s\n", __func__);
}
int bts_model_handle_options(int argc, char **argv)
@@ -116,26 +145,27 @@ void bts_model_abis_close(struct gsm_bts *bts)
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_group = talloc_strdup(plink, 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_group = talloc_strdup(plink, DEFAULT_MS_MCAST_GROUP);
plink->u.virt.ms_mcast_port = DEFAULT_MS_MCAST_PORT;
+ plink->u.virt.ttl = -1; /* initialize to -1 to prevent us setting the TTL */
}
void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
{
- LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__);
+ LOGP(DLGLOBAL, 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__);
+ LOGP(DLGLOBAL, 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__);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Unimplemented %s\n", __func__);
}
int main(int argc, char **argv)
diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.c b/src/osmo-bts-virtual/osmo_mcast_sock.c
index c0f0af58..c802e02d 100644
--- a/src/osmo-bts-virtual/osmo_mcast_sock.c
+++ b/src/osmo-bts-virtual/osmo_mcast_sock.c
@@ -3,6 +3,7 @@
#include <netdb.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/select.h>
+#include <osmocom/core/osmo_io.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -10,104 +11,133 @@
#include <unistd.h>
#include "osmo_mcast_sock.h"
+static void noop_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg)
+{
+ /* nothing */
+}
+
+static const struct osmo_io_ops srv_ioops = {
+ /* no read call-back as we don't read from the socket */
+ /* libosmcoore before change-id I0c071a29e508884bac331ada5e510bbfcf440bbf requires write call-back
+ * even if we don't care about it */
+ .write_cb = noop_write_cb,
+};
+
/* 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)
+static struct osmo_io_fd *
+mcast_server_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, bool loopback)
{
- int rc;
+ int rc, fd;
unsigned int flags = OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_UDP_REUSEADDR;
+ struct osmo_io_fd *iofd;
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);
+ rc = osmo_sock_init(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 NULL;
}
+ fd = rc;
- return 0;
+ iofd = osmo_iofd_setup(ctx, rc, "mcast_server_sock", OSMO_IO_FD_MODE_READ_WRITE, &srv_ioops, NULL);
+ if (!iofd) {
+ close(fd);
+ return NULL;
+ }
+
+ osmo_iofd_register(iofd, -1);
+ return iofd;
+}
+
+static void mcast_sock_read_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg)
+{
+ struct mcast_bidir_sock *bidir_sock = osmo_iofd_get_data(iofd);
+ bidir_sock->read_cb(res, msg, bidir_sock->data);
}
+const struct osmo_io_ops clnt_ioops = {
+ .read_cb = mcast_sock_read_cb,
+ /* no write call-back as we don't write to the socket */
+};
+
/* 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)
+static struct osmo_io_fd *
+mcast_client_sock_setup(void *ctx, const char *mcast_group, uint16_t mcast_port,
+ void (*read_cb)(int rc, struct msgb *msg, void *data))
{
- int rc;
+ int rc, fd;
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;
+ struct osmo_io_fd *iofd;
/* Create mcast client socket */
- rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
- NULL, mcast_port, flags);
+ rc = osmo_sock_init(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, mcast_port, flags);
if (rc < 0) {
perror("Could not create mcast client socket");
- return rc;
+ return NULL;
}
+ fd = rc;
/* Configure and join the multicast group */
- rc = osmo_sock_mcast_subscribe(ofd->fd, mcast_group);
+ rc = osmo_sock_mcast_subscribe(fd, mcast_group);
if (rc < 0) {
perror("Failed to join to mcast goup");
- osmo_fd_close(ofd);
- return rc;
+ close(fd);
+ return NULL;
+ }
+
+ iofd = osmo_iofd_setup(ctx, fd, "mcast_client_sock", OSMO_IO_FD_MODE_READ_WRITE, &clnt_ioops, ctx);
+ if (!iofd) {
+ close(fd);
+ return NULL;
}
- return 0;
+ osmo_iofd_register(iofd, -1);
+ return iofd;
}
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)
+ void (*read_cb)(int rc, struct msgb *msg, void *data),
+ void *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) {
+ bidir_sock->read_cb = read_cb;
+ bidir_sock->data = data;
+
+ bidir_sock->rx_iofd = mcast_client_sock_setup(bidir_sock, rx_mcast_group, rx_mcast_port, read_cb);
+ if (!bidir_sock->rx_iofd) {
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);
+ bidir_sock->tx_iofd = mcast_server_sock_setup(bidir_sock, tx_mcast_group, tx_mcast_port, loopback);
+ if (!bidir_sock->tx_iofd) {
+ osmo_iofd_free(bidir_sock->rx_iofd);
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)
+int mcast_bidir_sock_tx_msg(struct mcast_bidir_sock *bidir_sock, struct msgb *msg)
{
- return recv(bidir_sock->rx_ofd.fd, buf, buf_len, 0);
+ return osmo_iofd_write_msgb(bidir_sock->tx_iofd, msg);
}
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);
+ osmo_iofd_free(bidir_sock->tx_iofd);
+ osmo_iofd_free(bidir_sock->rx_iofd);
talloc_free(bidir_sock);
}
diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.h b/src/osmo-bts-virtual/osmo_mcast_sock.h
index aa2013c6..5f68415b 100644
--- a/src/osmo-bts-virtual/osmo_mcast_sock.h
+++ b/src/osmo-bts-virtual/osmo_mcast_sock.h
@@ -4,26 +4,20 @@
#include <stdint.h>
#include <netinet/in.h>
#include <osmocom/core/select.h>
+#include <osmocom/core/osmo_io.h>
struct mcast_bidir_sock {
- struct osmo_fd tx_ofd;
- struct osmo_fd rx_ofd;
+ struct osmo_io_fd *tx_iofd;
+ struct osmo_io_fd *rx_iofd;
+ void (*read_cb)(int rc, struct msgb *msg, void *data);
+ void *data;
};
-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 *
+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,
+ void (*read_cb)(int rc, struct msgb *msg, void *data),
+ void *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);
+int mcast_bidir_sock_tx_msg(struct mcast_bidir_sock *bidir_sock, struct msgb *msg);
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
index 259a573a..87596a79 100644
--- a/src/osmo-bts-virtual/scheduler_virtbts.c
+++ b/src/osmo-bts-virtual/scheduler_virtbts.c
@@ -1,7 +1,8 @@
-/* Scheduler worker functiosn for Virtua OsmoBTS */
+/* Scheduler worker functions for Virtua OsmoBTS */
/* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
* (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com>
+ * Contributions by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@@ -13,7 +14,7 @@
* 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.
+ * 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/>.
@@ -21,6 +22,7 @@
*/
#include <stdlib.h>
#include <unistd.h>
+#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <ctype.h>
@@ -46,25 +48,18 @@
#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)
+static void _tx_to_virt_um(struct l1sched_ts *l1ts,
+ struct trx_dl_burst_req *br,
+ struct msgb *msg, bool is_voice_frame)
{
- const struct trx_chan_desc *chdesc = &trx_chan_desc[chan];
+ const struct trx_chan_desc *chdesc = &trx_chan_desc[br->chan];
+ const struct gsm_bts_trx *trx = l1ts->ts->trx;
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 */
+ uint16_t arfcn = trx->arfcn; /* ARFCN of the transceiver 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) */
@@ -76,177 +71,181 @@ static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
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;
+ timeslot = br->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"))
+ l1sap_fn2ccch_block(br->fn) >= num_agch(trx, "PH-DATA-REQ"))
gsmtap_chantype = GSMTAP_CHANNEL_PCH;
else
- gsmtap_chantype = chantype_rsl2gsmtap(rsl_chantype, chdesc->link_id); /* the logical channel type */
+ gsmtap_chantype = chantype_rsl2gsmtap2(rsl_chantype, chdesc->link_id, is_voice_frame); /* the logical channel type */
+
+ if (gsmtap_chantype == GSMTAP_CHANNEL_UNKNOWN) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Tx GSMTAP for RSL channel type 0x%02x: cannot send, this"
+ " channel type is unknown in GSMTAP\n", rsl_chantype);
+ msgb_free(msg);
+ return;
+ }
#if MODULO_HYPERFRAME
/* Restart fn after every superframe (26 * 51 frames) to simulate hyperframe overflow each 6 seconds. */
- fn %= 26 * 51;
+ br->fn %= 26 * 51;
#endif
- outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, fn, signal_dbm, snr, data, data_len);
+ outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, br->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);
+ struct phy_instance *pinst = trx_phy_instance(trx);
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");
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br,
+ "GSMTAP msg could not send to virtual Um: %s\n", strerror(-rc));
else
- LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn,
- "%s Sending GSMTAP message to virtual Um\n", gsmtap_hdr_stringify(gh));
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br,
+ "Sending GSMTAP message to virtual Um\n");
} else
- LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "GSMTAP msg could not be created!\n");
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "GSMTAP msg could not be created!\n");
/* free incoming message */
msgb_free(msg);
}
-/*
- * TX on downlink
- */
+static void tx_to_virt_um(struct l1sched_ts *l1ts,
+ struct trx_dl_burst_req *br,
+ struct msgb *msg)
+{
+ _tx_to_virt_um(l1ts, br, msg, false);
+}
+
-/* 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)
+static struct gsm_lchan *lchan_from_l1t(const struct l1sched_ts *l1ts,
+ const enum trx_chan_type chan)
{
- return NULL;
+ struct gsm_bts_trx_ts *ts = l1ts->ts;
+ uint8_t subslot = 0;
+
+ if (chan == TRXC_TCHH_1)
+ subslot = 1;
+
+ return &ts->lchan[subslot];
}
-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)
+/* Determine the gsmtap_um_voice_type of a gsm_lchan */
+static int get_um_voice_type(const struct gsm_lchan *lchan)
{
- return NULL;
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_H)
+ return GSMTAP_UM_VOICE_HR;
+ else
+ return GSMTAP_UM_VOICE_FR;
+ case GSM48_CMODE_SPEECH_EFR:
+ return GSMTAP_UM_VOICE_EFR;
+ case GSM48_CMODE_SPEECH_AMR:
+ return GSMTAP_UM_VOICE_AMR;
+ default:
+ return -1;
+ }
}
-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 void tx_to_virt_um_voice_frame(struct l1sched_ts *l1ts,
+ struct trx_dl_burst_req *br,
+ struct msgb *msg)
{
- return NULL;
+ struct gsm_lchan *lchan = lchan_from_l1t(l1ts, br->chan);
+ int um_voice_type;
+
+ OSMO_ASSERT(lchan);
+ um_voice_type = get_um_voice_type(lchan);
+ if (um_voice_type < 0) {
+ LOGPLCHAN(lchan, DL1P, LOGL_ERROR, "Cannot determine Um voice type from lchan\n");
+ um_voice_type = 0xff;
+ }
+
+ /* the first byte indicates the type of voice codec (gsmtap_um_voice_type) */
+ msgb_pull_to_l2(msg);
+ msgb_push_u8(msg, um_voice_type);
+ msg->l2h = msg->data;
+ _tx_to_virt_um(l1ts, br, msg, true);
}
-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)
+/*
+ * TX on downlink
+ */
+
+int tx_fcch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ return 0;
+}
+
+int tx_sch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ return 0;
+}
+
+int tx_data_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
{
struct msgb *msg;
- if (bid > 0)
- return NULL;
+ if (br->bid > 0)
+ return 0;
/* get mac block from queue */
- msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ msg = _sched_dequeue_prim(l1ts, br);
if (!msg) {
- LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n");
- return NULL;
+ LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n");
+ return -ENODEV;
}
/* 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",
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim not 23 bytes, please FIX! (len=%d)\n",
msgb_l2len(msg));
/* free message */
msgb_free(msg);
- return NULL;
+ return -EINVAL;
}
/* transmit the msg received on dl from bsc to layer1 (virt Um) */
- tx_to_virt_um(l1t, tn, fn, chan, msg);
+ tx_to_virt_um(l1ts, br, msg);
- return NULL;
+ return 0;
}
-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)
+int tx_pdtch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
{
struct msgb *msg = NULL; /* make GCC happy */
- if (bid > 0)
- return NULL;
+ if (br->bid > 0)
+ return 0;
/* get mac block from queue */
- msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ msg = _sched_dequeue_prim(l1ts, br);
if (!msg) {
- LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n");
- return NULL;
+ LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n");
+ return -ENODEV;
}
- tx_to_virt_um(l1t, tn, fn, chan, msg);
+ tx_to_virt_um(l1ts, br, msg);
- return NULL;
+ return 0;
}
-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)
+static void tx_tch_common(struct l1sched_ts *l1ts,
+ const struct trx_dl_burst_req *br,
+ 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];
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->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);
+ msg1 = _sched_dequeue_prim(l1ts, br);
+ msg2 = _sched_dequeue_prim(l1ts, br);
if (msg1) {
l1sap = msgb_l1sap_prim(msg1);
if (l1sap->oph.primitive == PRIM_TCH) {
@@ -254,8 +253,8 @@ inval_mode1:
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! ");
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br,
+ "TCH twice, please FIX!\n");
msgb_free(msg2);
} else
msg_facch = msg2;
@@ -265,8 +264,8 @@ inval_mode1:
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! ");
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br,
+ "FACCH twice, please FIX!\n");
msgb_free(msg2);
} else
msg_tch = msg2;
@@ -282,8 +281,8 @@ inval_mode1:
/* 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));
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n",
+ msgb_l2len(msg_facch), GSM_MACBLOCK_LEN);
/* free message */
msgb_free(msg_facch);
msg_facch = NULL;
@@ -298,18 +297,18 @@ inval_mode1:
#endif
if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "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 */
+ if (br->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,
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br,
"Transmitting 'bad HR frame'\n");
goto free_bad_msg;
}
@@ -318,76 +317,37 @@ inval_mode1:
len = GSM_FR_BYTES;
if (msgb_l2len(msg_tch) >= 1
&& (msg_tch->l2h[0] >> 4) != 0xd) {
- LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br,
"Transmitting 'bad FR frame'\n");
goto free_bad_msg;
}
break;
case GSM48_CMODE_SPEECH_EFR: /* EFR */
- if (chan != TRXC_TCHF)
+ if (br->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,
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br,
"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
+ /* TODO: check length for consistency */
+ goto send_frame;
break;
default:
inval_mode2:
- LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "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");
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "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 "
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send payload with "
"invalid length! (expecing %d, received %d)\n", len, msgb_l2len(msg_tch));
free_bad_msg:
/* free message */
@@ -402,72 +362,56 @@ send_frame:
*_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)
+int tx_tchf_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
{
struct msgb *msg_tch = NULL, *msg_facch = NULL;
- if (bid > 0)
- return NULL;
+ if (br->bid > 0)
+ return 0;
- tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch,
- (((fn + 4) % 26) >> 2) & 1);
+ tx_tch_common(l1ts, br, &msg_tch, &msg_facch);
/* 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;
+ LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n");
+ return -ENODEV;
}
if (msg_facch) {
- tx_to_virt_um(l1t, tn, fn, chan, msg_facch);
+ tx_to_virt_um(l1ts, br, msg_facch);
msgb_free(msg_tch);
- } else
- tx_to_virt_um(l1t, tn, fn, chan, msg_tch);
-
-send_burst:
+ } else if (msg_tch)
+ tx_to_virt_um_voice_frame(l1ts, br, msg_tch);
- return NULL;
+ return 0;
}
-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 tx_tchh_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
{
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];
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
//uint8_t tch_mode = chan_state->tch_mode;
/* send burst, if we already got a frame */
- if (bid > 0)
- return NULL;
+ if (br->bid > 0)
+ return 0;
/* 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;
- }
+ tx_tch_common(l1ts, br, &msg_tch, &msg_facch);
/* 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;
+ LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n");
+ return -ENODEV;
}
if (msg_facch) {
- tx_to_virt_um(l1t, tn, fn, chan, msg_facch);
+ tx_to_virt_um(l1ts, br, msg_facch);
msgb_free(msg_tch);
} else if (msg_tch)
- tx_to_virt_um(l1t, tn, fn, chan, msg_tch);
+ tx_to_virt_um_voice_frame(l1ts, br, msg_tch);
-send_burst:
- return NULL;
+ return 0;
}
@@ -479,38 +423,33 @@ send_burst:
* directly into the L1SAP, bypassing the TDMA multiplex logic oriented
* towards receiving bursts */
-int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+int rx_rach_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
{
return 0;
}
/*! \brief a single burst was received by the PHY, process it */
-int rx_data_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+int rx_data_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
{
return 0;
}
-int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+int rx_pdtch_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
{
return 0;
}
-int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+int rx_tchf_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
{
return 0;
}
-int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
- uint8_t bid, const struct trx_ul_burst_ind *bi)
+int rx_tchh_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
{
return 0;
}
-void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
+void _sched_act_rach_det(struct gsm_bts_trx *trx, uint8_t tn, uint8_t ss, int activate)
{
}
@@ -519,7 +458,6 @@ void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int ac
***********************************************************************/
#define RTS_ADVANCE 5 /* about 20ms */
-#define FRAME_DURATION_uS 4615
static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn)
{
@@ -532,13 +470,11 @@ static int vbts_sched_fn(struct gsm_bts *bts, uint32_t 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;
+ struct trx_dl_burst_req br = { .fn = fn };
/* do for each of the 8 timeslots */
- for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ for (br.tn = 0; br.tn < ARRAY_SIZE(trx->ts); br.tn++) {
+ struct l1sched_ts *l1ts = trx->ts[br.tn].priv;
/* 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)
@@ -546,13 +482,13 @@ static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn)
* --> 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);
+ _sched_rts(l1ts, GSM_TDMA_FN_SUM(fn, RTS_ADVANCE));
/* 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);
+ _sched_dl_burst(l1ts, &br);
}
}
@@ -562,8 +498,9 @@ static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn)
static void vbts_fn_timer_cb(void *data)
{
struct gsm_bts *bts = data;
+ struct bts_virt_priv *bts_virt = (struct bts_virt_priv *)bts->model_priv;
struct timeval tv_now;
- struct timeval *tv_clock = &bts->vbts.tv_clock;
+ struct timeval *tv_clock = &bts_virt->tv_clock;
int32_t elapsed_us;
gettimeofday(&tv_now, NULL);
@@ -574,41 +511,44 @@ static void vbts_fn_timer_cb(void *data)
+ (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)
+ if (elapsed_us > 2 *GSM_TDMA_FN_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) {
+ while (elapsed_us > GSM_TDMA_FN_DURATION_uS / 2) {
const struct timeval tv_frame = {
.tv_sec = 0,
- .tv_usec = FRAME_DURATION_uS,
+ .tv_usec = GSM_TDMA_FN_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;
+ vbts_sched_fn(bts, GSM_TDMA_FN_INC(bts_virt->last_fn));
+ elapsed_us -= GSM_TDMA_FN_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);
+ osmo_timer_schedule(&bts_virt->fn_timer, 0, GSM_TDMA_FN_DURATION_uS - elapsed_us);
}
int vbts_sched_start(struct gsm_bts *bts)
{
+ struct bts_virt_priv *bts_virt = (struct bts_virt_priv *)bts->model_priv;
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;
+ if (!bts_virt)
+ return -EINVAL;
+
+ memset(&bts_virt->fn_timer, 0, sizeof(bts_virt->fn_timer));
+ bts_virt->fn_timer.cb = vbts_fn_timer_cb;
+ bts_virt->fn_timer.data = bts;
- gettimeofday(&bts->vbts.tv_clock, NULL);
+ gettimeofday(&bts_virt->tv_clock, NULL);
/* trigger the first timer after 4615us (a frame duration) */
- osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS);
+ osmo_timer_schedule(&bts_virt->fn_timer, 0, GSM_TDMA_FN_DURATION_uS);
return 0;
}
diff --git a/src/osmo-bts-virtual/virtual_um.c b/src/osmo-bts-virtual/virtual_um.c
index fd0940f0..711e75d3 100644
--- a/src/osmo-bts-virtual/virtual_um.c
+++ b/src/osmo-bts-virtual/virtual_um.c
@@ -12,7 +12,7 @@
* 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.
+ * 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/>.
@@ -27,45 +27,31 @@
#include <osmocom/core/talloc.h>
#include "osmo_mcast_sock.h"
#include "virtual_um.h"
+
#include <unistd.h>
+#include <errno.h>
/**
- * Virtual UM interface file descriptor callback.
- * Should be called by select.c when the fd is ready for reading.
+ * Virtual UM interface file descriptor read callback.
*/
-static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what)
+static void virt_um_read_cb(int rc, struct msgb *msg, void *data)
{
- 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;
+ struct virt_um_inst *vui = data;
+ msg->l1h = msg->data;
- /* 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;
+ /* call the l1 callback function for a received msg */
+ vui->recv_cb(vui, 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,
+ char *rx_mcast_group, uint16_t rx_mcast_port, int ttl, const char *dev_name,
void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg))
{
struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst);
+ int rc;
+
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);
+ rx_mcast_group, rx_mcast_port, 1, virt_um_read_cb, vui);
if (!vui->mcast_sock) {
perror("Unable to create VirtualUm multicast socket");
talloc_free(vui);
@@ -73,8 +59,37 @@ struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_m
}
vui->recv_cb = recv_cb;
+ /* -1 means default, i.e. no TTL explicitly configured in VTY */
+ if (ttl >= 0) {
+ int txfd = osmo_iofd_get_fd(vui->mcast_sock->tx_iofd);
+ rc = osmo_sock_mcast_ttl_set(txfd, ttl);
+ if (rc < 0) {
+ perror("Cannot set TTL of Virtual Um transmit socket");
+ goto out_close;
+ }
+ }
+
+ if (dev_name) {
+ int txfd = osmo_iofd_get_fd(vui->mcast_sock->tx_iofd);
+ rc = osmo_sock_mcast_iface_set(txfd, dev_name);
+ if (rc < 0) {
+ perror("Cannot bind multicast tx to given device");
+ goto out_close;
+ }
+ int rxfd = osmo_iofd_get_fd(vui->mcast_sock->rx_iofd);
+ rc = osmo_sock_mcast_iface_set(rxfd, dev_name);
+ if (rc < 0) {
+ perror("Cannot bind multicast rx to given device");
+ goto out_close;
+ }
+ }
+
return vui;
+out_close:
+ mcast_bidir_sock_close(vui->mcast_sock);
+ talloc_free(vui);
+ return NULL;
}
void virt_um_destroy(struct virt_um_inst *vui)
@@ -90,11 +105,11 @@ 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);
+ rc = mcast_bidir_sock_tx_msg(vui->mcast_sock, msg);
+ if (rc < 0) {
+ msgb_free(msg);
+ rc = -errno;
+ }
return rc;
}
diff --git a/src/osmo-bts-virtual/virtual_um.h b/src/osmo-bts-virtual/virtual_um.h
index ac098dd4..87cf03a5 100644
--- a/src/osmo-bts-virtual/virtual_um.h
+++ b/src/osmo-bts-virtual/virtual_um.h
@@ -23,7 +23,7 @@ struct virt_um_inst {
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,
+ char *rx_mcast_group, uint16_t rx_mcast_port, int ttl, const char *dev_name,
void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg));
void virt_um_destroy(struct virt_um_inst *vui);
diff --git a/src/osmo-bts-virtual/virtualbts_vty.c b/src/osmo-bts-virtual/virtualbts_vty.c
index 323222b4..3933bd27 100644
--- a/src/osmo-bts-virtual/virtualbts_vty.c
+++ b/src/osmo-bts-virtual/virtualbts_vty.c
@@ -50,25 +50,26 @@
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_bts(struct vty *vty, const struct gsm_bts *bts)
{
}
-void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+void bts_model_config_write_trx(struct vty *vty, const 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_inst(struct vty *vty, const struct phy_instance *pinst)
{
}
-void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+void bts_model_config_write_phy(struct vty *vty, const 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 (plink->u.virt.ttl != -1)
+ vty_out(vty, " virtual-um ttl %d%s",
+ plink->u.virt.ttl, 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);
@@ -171,15 +172,31 @@ DEFUN(cfg_phy_mcast_dev, cfg_phy_mcast_dev_cmd,
return CMD_SUCCESS;
}
-int bts_model_vty_init(struct gsm_bts *bts)
+DEFUN(cfg_phy_mcast_ttl, cfg_phy_mcast_ttl_cmd,
+ "virtual-um ttl <0-255>",
+ VUM_STR "Configure the TTL for transmitted multicast GSMTAP packets\n")
{
- vty_bts = bts;
+ 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.ttl = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+int bts_model_vty_init(void *ctx)
+{
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);
+ install_element(PHY_NODE, &cfg_phy_mcast_ttl_cmd);
return 0;
}