mirror of
				https://gitea.osmocom.org/cellular-infrastructure/osmo-trx.git
				synced 2025-11-04 06:03:17 +00:00 
			
		
		
		
	Compare commits
	
		
			29 Commits
		
	
	
		
			mstx_oldtr
			...
			2023q1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d0c1055051 | ||
| 
						 | 
					0ce64705e0 | ||
| 
						 | 
					d0b947a0c4 | ||
| 
						 | 
					4a867be165 | ||
| 
						 | 
					fdcce1fc6e | ||
| 
						 | 
					2ca77d7ff2 | ||
| 
						 | 
					b6fd0709f4 | ||
| 
						 | 
					d3e3bba2cd | ||
| 
						 | 
					5561f1129d | ||
| 
						 | 
					b7253c6fdc | ||
| 
						 | 
					5a23a24bb1 | ||
| 
						 | 
					b60fb8e1e3 | ||
| 
						 | 
					934e1016ff | ||
| 
						 | 
					ac726b1147 | ||
| 
						 | 
					508270d83d | ||
| 
						 | 
					7d897cb5b0 | ||
| 
						 | 
					94dcf6d29c | ||
| 
						 | 
					040497e0a4 | ||
| 
						 | 
					8984d7f2ca | ||
| 
						 | 
					aa7a40ee84 | ||
| 
						 | 
					f2f35fc592 | ||
| 
						 | 
					00ddcfaf50 | ||
| 
						 | 
					2f20c564bf | ||
| 
						 | 
					19faae85c6 | ||
| 
						 | 
					1c6a3459cd | ||
| 
						 | 
					32311d8635 | ||
| 
						 | 
					71c46e91df | ||
| 
						 | 
					a39fa875a3 | ||
| 
						 | 
					9a3e3fceb8 | 
							
								
								
									
										4
									
								
								.checkpatch.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.checkpatch.conf
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
--exclude osmocom-bb/.*
 | 
			
		||||
--exclude .*h
 | 
			
		||||
--exclude Transceiver52M/grgsm_vitac/.*
 | 
			
		||||
--ignore FUNCTION_WITHOUT_ARGS
 | 
			
		||||
@@ -515,7 +515,7 @@ SpacesInContainerLiterals: false
 | 
			
		||||
SpacesInCStyleCastParentheses: false
 | 
			
		||||
SpacesInParentheses: false
 | 
			
		||||
SpacesInSquareBrackets: false
 | 
			
		||||
Standard: Cpp03
 | 
			
		||||
Standard: Cpp11
 | 
			
		||||
TabWidth: 8
 | 
			
		||||
UseTab: Always
 | 
			
		||||
...
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -14,7 +14,7 @@ Transceiver52M/osmo-trx-syncthing-ipc
 | 
			
		||||
Transceiver52M/osmo-trx-ms-blade
 | 
			
		||||
Transceiver52M/osmo-trx-ms-uhd
 | 
			
		||||
Transceiver52M/osmo-trx-ms-ipc
 | 
			
		||||
 | 
			
		||||
Transceiver52M/device/ipc/uhddev_ipc.cpp
 | 
			
		||||
 | 
			
		||||
.clang-format
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
[submodule "osmocom-bb"]
 | 
			
		||||
	path = osmocom-bb
 | 
			
		||||
	url = https://gitea.osmocom.org/phone-side/osmocom-bb.git
 | 
			
		||||
@@ -312,8 +312,7 @@ static void threshold_timer_update_intv() {
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (llist_empty(&threshold_list)) {
 | 
			
		||||
		if (osmo_timer_pending(&threshold_timer))
 | 
			
		||||
			osmo_timer_del(&threshold_timer);
 | 
			
		||||
		osmo_timer_del(&threshold_timer);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,15 +55,12 @@ const BitVector GSM::gEdgeTrainingSequence[] = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const BitVector GSM::gDummyBurst("0001111101101110110000010100100111000001001000100000001111100011100010111000101110001010111010010100011001100111001111010011111000100101111101010000");
 | 
			
		||||
const BitVector GSM::gDummyBurstTSC("01110001011100010111000101");
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
 | 
			
		||||
const BitVector GSM::gRACHSynchSequenceTS0("01001011011111111001100110101010001111000");  /* GSM, GMSK (default) */
 | 
			
		||||
const BitVector GSM::gRACHSynchSequenceTS1("01010100111110001000011000101111001001101");  /* EGPRS, 8-PSK */
 | 
			
		||||
const BitVector GSM::gRACHSynchSequenceTS2("11101111001001110101011000001101101110111");  /* EGPRS, GMSK */
 | 
			
		||||
 | 
			
		||||
const BitVector GSM::gSCHSynchSequence("1011100101100010000001000000111100101101010001010111011000011011");
 | 
			
		||||
 | 
			
		||||
//                               |-head-||---------midamble----------------------||--------------data----------------||t|
 | 
			
		||||
const BitVector GSM::gRACHBurst("0011101001001011011111111001100110101010001111000110111101111110000111001001010110011000");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,16 +52,11 @@ extern const BitVector gEdgeTrainingSequence[];
 | 
			
		||||
 | 
			
		||||
/** C0T0 filler burst, GSM 05.02, 5.2.6 */
 | 
			
		||||
extern const BitVector gDummyBurst;
 | 
			
		||||
extern const BitVector gDummyBurstTSC;
 | 
			
		||||
 | 
			
		||||
/** Random access burst synch. sequence */
 | 
			
		||||
extern const BitVector gRACHSynchSequenceTS0;
 | 
			
		||||
extern const BitVector gRACHSynchSequenceTS1;
 | 
			
		||||
extern const BitVector gRACHSynchSequenceTS2;
 | 
			
		||||
 | 
			
		||||
/** Synchronization burst sync sequence */
 | 
			
		||||
extern const BitVector gSCHSynchSequence;
 | 
			
		||||
 | 
			
		||||
/** Random access burst synch. sequence, GSM 05.02 5.2.7 */
 | 
			
		||||
extern const BitVector gRACHBurst;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,9 +26,14 @@ AM_CXXFLAGS = -Wall -pthread
 | 
			
		||||
#AM_CXXFLAGS = -Wall -O2 -NDEBUG -pthread
 | 
			
		||||
#AM_CFLAGS = -Wall -O2 -NDEBUG -pthread
 | 
			
		||||
 | 
			
		||||
SUBDIRS =
 | 
			
		||||
 | 
			
		||||
if ENABLE_MS_TRX
 | 
			
		||||
SUBDIRS += $(LIBTRXCON_DIR)
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
# Order must be preserved
 | 
			
		||||
SUBDIRS = \
 | 
			
		||||
	trxcon \
 | 
			
		||||
SUBDIRS += \
 | 
			
		||||
	CommonLibs \
 | 
			
		||||
	GSM \
 | 
			
		||||
	Transceiver52M \
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,6 @@ STD_DEFINES_AND_INCLUDES = \
 | 
			
		||||
 | 
			
		||||
COMMON_LA = $(top_builddir)/CommonLibs/libcommon.la
 | 
			
		||||
GSM_LA = $(top_builddir)/GSM/libGSM.la
 | 
			
		||||
TRXCON_LA = $(top_builddir)/trxcon/libtrxcon.la
 | 
			
		||||
 | 
			
		||||
if ARCH_ARM
 | 
			
		||||
ARCH_LA = $(top_builddir)/Transceiver52M/arch/arm/libarch.la
 | 
			
		||||
 
 | 
			
		||||
@@ -27,9 +27,9 @@ GIT Repository
 | 
			
		||||
 | 
			
		||||
You can clone from the official osmo-trx.git repository using
 | 
			
		||||
 | 
			
		||||
        git clone git://git.osmocom.org/osmo-trx.git
 | 
			
		||||
        git clone https://gitea.osmocom.org/cellular-infrastructure/osmo-trx`
 | 
			
		||||
 | 
			
		||||
There is a cgit interface at <https://git.osmocom.org/osmo-trx/>
 | 
			
		||||
There is a web interface at <https://gitea.osmocom.org/cellular-infrastructure/osmo-trx>
 | 
			
		||||
 | 
			
		||||
Documentation
 | 
			
		||||
-------------
 | 
			
		||||
 
 | 
			
		||||
@@ -24,13 +24,12 @@ include $(top_srcdir)/Makefile.common
 | 
			
		||||
SUBDIRS = arch device
 | 
			
		||||
 | 
			
		||||
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/arch/common -I${srcdir}/device/common
 | 
			
		||||
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
 | 
			
		||||
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8
 | 
			
		||||
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
 | 
			
		||||
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS)
 | 
			
		||||
 | 
			
		||||
noinst_LTLIBRARIES = libtransceiver_common.la
 | 
			
		||||
 | 
			
		||||
COMMON_SOURCES = \
 | 
			
		||||
	l1if.cpp \
 | 
			
		||||
	radioInterface.cpp \
 | 
			
		||||
	radioVector.cpp \
 | 
			
		||||
	radioClock.cpp \
 | 
			
		||||
@@ -41,10 +40,7 @@ COMMON_SOURCES = \
 | 
			
		||||
	ChannelizerBase.cpp \
 | 
			
		||||
	Channelizer.cpp \
 | 
			
		||||
	Synthesis.cpp \
 | 
			
		||||
	proto_trxd.c \
 | 
			
		||||
	sch.c \
 | 
			
		||||
	grgsm_vitac/grgsm_vitac.cpp \
 | 
			
		||||
	grgsm_vitac/viterbi_detector.cc
 | 
			
		||||
	proto_trxd.c
 | 
			
		||||
 | 
			
		||||
libtransceiver_common_la_SOURCES = \
 | 
			
		||||
	$(COMMON_SOURCES) \
 | 
			
		||||
@@ -65,9 +61,7 @@ noinst_HEADERS = \
 | 
			
		||||
	ChannelizerBase.h \
 | 
			
		||||
	Channelizer.h \
 | 
			
		||||
	Synthesis.h \
 | 
			
		||||
	proto_trxd.h \
 | 
			
		||||
	grgsm_vitac/viterbi_detector.h \
 | 
			
		||||
	grgsm_vitac/constants.h
 | 
			
		||||
	proto_trxd.h
 | 
			
		||||
 | 
			
		||||
COMMON_LDADD = \
 | 
			
		||||
	libtransceiver_common.la \
 | 
			
		||||
@@ -76,10 +70,39 @@ COMMON_LDADD = \
 | 
			
		||||
	$(COMMON_LA) \
 | 
			
		||||
	$(FFTWF_LIBS) \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOCODING_LIBS) \
 | 
			
		||||
	$(LIBOSMOCTRL_LIBS) \
 | 
			
		||||
	$(LIBOSMOVTY_LIBS)
 | 
			
		||||
 | 
			
		||||
if ENABLE_MS_TRX
 | 
			
		||||
AM_CPPFLAGS += -I$(top_srcdir)/osmocom-bb/src/host/trxcon/include/
 | 
			
		||||
AM_CPPFLAGS += -I${srcdir}
 | 
			
		||||
 | 
			
		||||
TRXCON_LDADD = \
 | 
			
		||||
	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libtrxcon.a \
 | 
			
		||||
	$(top_builddir)/osmocom-bb/src/host/trxcon/src/.libs/libl1sched.a \
 | 
			
		||||
	$(LIBOSMOCODING_LIBS)
 | 
			
		||||
 | 
			
		||||
MS_SOURCES = \
 | 
			
		||||
	ms/sch.c \
 | 
			
		||||
	ms/ms.cpp \
 | 
			
		||||
	ms/ms_rx_lower.cpp \
 | 
			
		||||
	grgsm_vitac/grgsm_vitac.cpp \
 | 
			
		||||
	grgsm_vitac/viterbi_detector.cc
 | 
			
		||||
 | 
			
		||||
noinst_HEADERS += \
 | 
			
		||||
   	ms/ms.h \
 | 
			
		||||
	ms/bladerf_specific.h \
 | 
			
		||||
	ms/uhd_specific.h \
 | 
			
		||||
	ms/ms_rx_burst.h \
 | 
			
		||||
	ms/ms_upper.h \
 | 
			
		||||
	ms/itrq.h \
 | 
			
		||||
	ms/sch.h \
 | 
			
		||||
	grgsm_vitac/viterbi_detector.h \
 | 
			
		||||
	grgsm_vitac/constants.h \
 | 
			
		||||
	grgsm_vitac/grgsm_vitac.h
 | 
			
		||||
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS =
 | 
			
		||||
 | 
			
		||||
if DEVICE_UHD
 | 
			
		||||
@@ -91,23 +114,25 @@ osmo_trx_uhd_LDADD = \
 | 
			
		||||
	$(UHD_LIBS)
 | 
			
		||||
osmo_trx_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS)
 | 
			
		||||
 | 
			
		||||
if ENABLE_MS_TRX
 | 
			
		||||
bin_PROGRAMS += osmo-trx-ms-uhd
 | 
			
		||||
osmo_trx_ms_uhd_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
 | 
			
		||||
osmo_trx_ms_uhd_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.c ms/l1ctl_server_cb.cpp
 | 
			
		||||
osmo_trx_ms_uhd_LDADD = \
 | 
			
		||||
	$(builddir)/device/bladerf/libdevice.la \
 | 
			
		||||
	$(builddir)/device/uhd/libdevice.la \
 | 
			
		||||
	$(COMMON_LDADD) \
 | 
			
		||||
	$(UHD_LIBS) \
 | 
			
		||||
	$(TRXCON_LA)
 | 
			
		||||
	$(TRXCON_LDADD)
 | 
			
		||||
osmo_trx_ms_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DBUILDUHD
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS += osmo-trx-syncthing-uhd
 | 
			
		||||
osmo_trx_syncthing_uhd_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_burst.cpp
 | 
			
		||||
osmo_trx_syncthing_uhd_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp
 | 
			
		||||
osmo_trx_syncthing_uhd_LDADD = \
 | 
			
		||||
	$(builddir)/device/bladerf/libdevice.la \
 | 
			
		||||
	$(builddir)/device/uhd/libdevice.la \
 | 
			
		||||
	$(COMMON_LDADD) \
 | 
			
		||||
	$(UHD_LIBS)
 | 
			
		||||
	$(UHD_LIBS) \
 | 
			
		||||
	$(TRXCON_LDADD)
 | 
			
		||||
osmo_trx_syncthing_uhd_CPPFLAGS  = $(AM_CPPFLAGS) $(UHD_CFLAGS) -DSYNCTHINGONLY -DBUILDUHD
 | 
			
		||||
#osmo_trx_syncthing_LDFLAGS  = -fsanitize=address,undefined -shared-libsan
 | 
			
		||||
endif
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if DEVICE_USRP1
 | 
			
		||||
@@ -139,23 +164,25 @@ osmo_trx_blade_LDADD = \
 | 
			
		||||
	$(BLADE_LIBS)
 | 
			
		||||
osmo_trx_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(LMS_CFLAGS)
 | 
			
		||||
 | 
			
		||||
if ENABLE_MS_TRX
 | 
			
		||||
bin_PROGRAMS += osmo-trx-ms-blade
 | 
			
		||||
osmo_trx_ms_blade_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
 | 
			
		||||
osmo_trx_ms_blade_SOURCES = $(MS_SOURCES) ms/ms_upper.cpp ms/l1ctl_server.c ms/logging.c ms/l1ctl_server_cb.cpp
 | 
			
		||||
osmo_trx_ms_blade_LDADD = \
 | 
			
		||||
	$(builddir)/device/bladerf/libdevice.la \
 | 
			
		||||
	$(COMMON_LDADD) \
 | 
			
		||||
	$(BLADE_LIBS) \
 | 
			
		||||
	$(TRXCON_LA)
 | 
			
		||||
	$(TRXCON_LDADD)
 | 
			
		||||
osmo_trx_ms_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DBUILDBLADE
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS += osmo-trx-syncthing-blade
 | 
			
		||||
osmo_trx_syncthing_blade_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_burst.cpp
 | 
			
		||||
osmo_trx_syncthing_blade_SOURCES = $(MS_SOURCES) ms/ms_rx_burst_test.cpp
 | 
			
		||||
osmo_trx_syncthing_blade_LDADD =  \
 | 
			
		||||
	$(builddir)/device/bladerf/libdevice.la \
 | 
			
		||||
	$(COMMON_LDADD) \
 | 
			
		||||
	$(BLADE_LIBS)
 | 
			
		||||
osmo_trx_syncthing_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DSYNCTHINGONLY -DBUILDBLADE -mcpu=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8 -I../device/ipc
 | 
			
		||||
#osmo_trx_syncthing_LDFLAGS  = -fsanitize=address,undefined -shared-libsan
 | 
			
		||||
	$(BLADE_LIBS) \
 | 
			
		||||
	$(TRXCON_LDADD)
 | 
			
		||||
osmo_trx_syncthing_blade_CPPFLAGS  = $(AM_CPPFLAGS) $(BLADE_CFLAGS) -DSYNCTHINGONLY -DBUILDBLADE -I../device/ipc
 | 
			
		||||
endif
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if DEVICE_IPC
 | 
			
		||||
@@ -165,32 +192,4 @@ osmo_trx_ipc_LDADD = \
 | 
			
		||||
	$(builddir)/device/ipc/libdevice.la \
 | 
			
		||||
	$(COMMON_LDADD)
 | 
			
		||||
osmo_trx_ipc_CPPFLAGS  = $(AM_CPPFLAGS)
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS += osmo-trx-ipc2
 | 
			
		||||
osmo_trx_ipc2_SOURCES = osmo-trx.cpp
 | 
			
		||||
osmo_trx_ipc2_LDADD = \
 | 
			
		||||
	$(builddir)/device/ipc2/libdevice.la \
 | 
			
		||||
	$(COMMON_LDADD)
 | 
			
		||||
osmo_trx_ipc2_CPPFLAGS  = $(AM_CPPFLAGS)
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS += osmo-trx-ms-ipc
 | 
			
		||||
osmo_trx_ms_ipc_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_upper.cpp ms/ms_commandhandler.cpp
 | 
			
		||||
osmo_trx_ms_ipc_LDADD = \
 | 
			
		||||
	$(COMMON_LDADD) \
 | 
			
		||||
	$(TRXCON_LA)
 | 
			
		||||
osmo_trx_ms_ipc_CPPFLAGS  = $(AM_CPPFLAGS) -DBUILDIPC  -I./device/ipc2 -I../device/ipc2
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS += osmo-trx-syncthing-ipc
 | 
			
		||||
osmo_trx_syncthing_ipc_SOURCES = ms/syncthing.cpp ms/ms_rx_lower.cpp ms/ms_rx_burst.cpp
 | 
			
		||||
osmo_trx_syncthing_ipc_LDADD =  $(COMMON_LDADD)
 | 
			
		||||
osmo_trx_syncthing_ipc_CPPFLAGS  = $(AM_CPPFLAGS) -DSYNCTHINGONLY -DBUILDIPC  -I./device/ipc2 -I../device/ipc2
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
noinst_HEADERS += \
 | 
			
		||||
	ms/syncthing.h \
 | 
			
		||||
	ms/bladerf_specific.h \
 | 
			
		||||
	ms/uhd_specific.h \
 | 
			
		||||
	ms/ms_rx_upper.h \
 | 
			
		||||
	itrq.h
 | 
			
		||||
# -fsanitize=address,undefined -shared-libsan -O0
 | 
			
		||||
#
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ extern "C" {
 | 
			
		||||
#define M_PI			3.14159265358979323846264338327f
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define MAX_OUTPUT_LEN 4096 * 4
 | 
			
		||||
#define MAX_OUTPUT_LEN		4096
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -578,7 +578,7 @@ CorrType Transceiver::expectedCorrType(GSM::Time currTime,
 | 
			
		||||
  case XIII: {
 | 
			
		||||
    int mod52 = burstFN % 52;
 | 
			
		||||
    if ((mod52 == 12) || (mod52 == 38))
 | 
			
		||||
      return cfg->ext_rach ? EXT_RACH : RACH;
 | 
			
		||||
      return RACH; /* RACH is always 8-bit on PTCCH/U */
 | 
			
		||||
    else if ((mod52 == 25) || (mod52 == 51))
 | 
			
		||||
      return IDLE;
 | 
			
		||||
    else /* Enable 8-PSK burst detection if EDGE is enabled */
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@
 | 
			
		||||
 | 
			
		||||
#include "convert.h"
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void base_convert_float_short(short *out, const float *in,
 | 
			
		||||
			      float scale, int len)
 | 
			
		||||
{
 | 
			
		||||
@@ -25,7 +24,6 @@ void base_convert_float_short(short *out, const float *in,
 | 
			
		||||
		out[i] = in[i] * scale;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void base_convert_short_float(float *out, const short *in, int len)
 | 
			
		||||
{
 | 
			
		||||
	for (int i = 0; i < len; i++)
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/* Base multiply and accumulate complex-real */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
static void mac_real(const float *x, const float *h, float *y)
 | 
			
		||||
{
 | 
			
		||||
	y[0] += x[0] * h[0];
 | 
			
		||||
@@ -32,7 +31,6 @@ static void mac_real(const float *x, const float *h, float *y)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Base multiply and accumulate complex-complex */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
static void mac_cmplx(const float *x, const float *h, float *y)
 | 
			
		||||
{
 | 
			
		||||
	y[0] += x[0] * h[0] - x[1] * h[1];
 | 
			
		||||
@@ -40,7 +38,6 @@ static void mac_cmplx(const float *x, const float *h, float *y)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Base vector complex-complex multiply and accumulate */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
static void mac_real_vec_n(const float *x, const float *h, float *y,
 | 
			
		||||
			   int len)
 | 
			
		||||
{
 | 
			
		||||
@@ -49,7 +46,6 @@ static void mac_real_vec_n(const float *x, const float *h, float *y,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Base vector complex-complex multiply and accumulate */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
 | 
			
		||||
			    int len)
 | 
			
		||||
{
 | 
			
		||||
@@ -58,7 +54,6 @@ static void mac_cmplx_vec_n(const float *x, const float *h, float *y,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Base complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int _base_convolve_real(const float *x, int x_len,
 | 
			
		||||
			const float *h, int h_len,
 | 
			
		||||
			float *y, int y_len,
 | 
			
		||||
@@ -74,7 +69,6 @@ int _base_convolve_real(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Base complex-complex convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int _base_convolve_complex(const float *x, int x_len,
 | 
			
		||||
			   const float *h, int h_len,
 | 
			
		||||
			   float *y, int y_len,
 | 
			
		||||
@@ -91,7 +85,6 @@ int _base_convolve_complex(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Buffer validity checks */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int bounds_check(int x_len, int h_len, int y_len,
 | 
			
		||||
		 int start, int len)
 | 
			
		||||
{
 | 
			
		||||
@@ -112,7 +105,6 @@ int bounds_check(int x_len, int h_len, int y_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* API: Non-aligned (no SSE) complex-real */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int base_convolve_real(const float *x, int x_len,
 | 
			
		||||
		       const float *h, int h_len,
 | 
			
		||||
		       float *y, int y_len,
 | 
			
		||||
@@ -130,7 +122,6 @@ int base_convolve_real(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* API: Non-aligned (no SSE) complex-complex */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int base_convolve_complex(const float *x, int x_len,
 | 
			
		||||
			  const float *h, int h_len,
 | 
			
		||||
			  float *y, int y_len,
 | 
			
		||||
@@ -148,7 +139,6 @@ int base_convolve_complex(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Aligned filter tap allocation */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void *convolve_h_alloc(size_t len)
 | 
			
		||||
{
 | 
			
		||||
#ifdef HAVE_SSE3
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,6 @@ void convert_init(void)
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void convert_float_short(short *out, const float *in, float scale, int len)
 | 
			
		||||
{
 | 
			
		||||
	if (!(len % 16))
 | 
			
		||||
@@ -71,7 +70,6 @@ void convert_float_short(short *out, const float *in, float scale, int len)
 | 
			
		||||
		c.convert_scale_ps_si16(out, in, scale, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void convert_short_float(float *out, const short *in, int len)
 | 
			
		||||
{
 | 
			
		||||
	if (!(len % 16))
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@
 | 
			
		||||
#include <emmintrin.h>
 | 
			
		||||
 | 
			
		||||
/* 8*N single precision floats scaled and converted to 16-bit signed integer */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void _sse_convert_scale_ps_si16_8n(short *restrict out,
 | 
			
		||||
				   const float *restrict in,
 | 
			
		||||
				   float scale, int len)
 | 
			
		||||
@@ -55,7 +54,6 @@ void _sse_convert_scale_ps_si16_8n(short *restrict out,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 8*N single precision floats scaled and converted with remainder */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void _sse_convert_scale_ps_si16(short *restrict out,
 | 
			
		||||
				const float *restrict in, float scale, int len)
 | 
			
		||||
{
 | 
			
		||||
@@ -68,7 +66,6 @@ void _sse_convert_scale_ps_si16(short *restrict out,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 16*N single precision floats scaled and converted to 16-bit signed integer */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void _sse_convert_scale_ps_si16_16n(short *restrict out,
 | 
			
		||||
				    const float *restrict in,
 | 
			
		||||
				    float scale, int len)
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@
 | 
			
		||||
#include <smmintrin.h>
 | 
			
		||||
 | 
			
		||||
/* 16*N 16-bit signed integer converted to single precision floats */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void _sse_convert_si16_ps_16n(float *restrict out,
 | 
			
		||||
			      const short *restrict in, int len)
 | 
			
		||||
{
 | 
			
		||||
@@ -60,7 +59,6 @@ void _sse_convert_si16_ps_16n(float *restrict out,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 16*N 16-bit signed integer conversion with remainder */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void _sse_convert_si16_ps(float *restrict out,
 | 
			
		||||
			  const short *restrict in, int len)
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -91,7 +91,6 @@ void convolve_init(void)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* API: Aligned complex-real */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int convolve_real(const float *x, int x_len,
 | 
			
		||||
		  const float *h, int h_len,
 | 
			
		||||
		  float *y, int y_len, int start, int len)
 | 
			
		||||
@@ -131,7 +130,6 @@ int convolve_real(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* API: Aligned complex-complex */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
int convolve_complex(const float *x, int x_len,
 | 
			
		||||
		     const float *h, int h_len,
 | 
			
		||||
		     float *y, int y_len,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@
 | 
			
		||||
#include <pmmintrin.h>
 | 
			
		||||
 | 
			
		||||
/* 4-tap SSE complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_real4(const float *x, int x_len,
 | 
			
		||||
		    const float *h, int h_len,
 | 
			
		||||
		    float *y, int y_len,
 | 
			
		||||
@@ -69,7 +68,6 @@ void sse_conv_real4(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 8-tap SSE complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_real8(const float *x, int x_len,
 | 
			
		||||
		    const float *h, int h_len,
 | 
			
		||||
		    float *y, int y_len,
 | 
			
		||||
@@ -121,7 +119,6 @@ void sse_conv_real8(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 12-tap SSE complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_real12(const float *x, int x_len,
 | 
			
		||||
		     const float *h, int h_len,
 | 
			
		||||
		     float *y, int y_len,
 | 
			
		||||
@@ -188,7 +185,6 @@ void sse_conv_real12(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 16-tap SSE complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_real16(const float *x, int x_len,
 | 
			
		||||
		     const float *h, int h_len,
 | 
			
		||||
		     float *y, int y_len,
 | 
			
		||||
@@ -268,7 +264,6 @@ void sse_conv_real16(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 20-tap SSE complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_real20(const float *x, int x_len,
 | 
			
		||||
		     const float *h, int h_len,
 | 
			
		||||
		     float *y, int y_len,
 | 
			
		||||
@@ -359,7 +354,6 @@ void sse_conv_real20(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 4*N-tap SSE complex-real convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_real4n(const float *x, int x_len,
 | 
			
		||||
		     const float *h, int h_len,
 | 
			
		||||
		     float *y, int y_len,
 | 
			
		||||
@@ -407,7 +401,6 @@ void sse_conv_real4n(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 4*N-tap SSE complex-complex convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_cmplx_4n(const float *x, int x_len,
 | 
			
		||||
		       const float *h, int h_len,
 | 
			
		||||
		       float *y, int y_len,
 | 
			
		||||
@@ -466,7 +459,6 @@ void sse_conv_cmplx_4n(const float *x, int x_len,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 8*N-tap SSE complex-complex convolution */
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
void sse_conv_cmplx_8n(const float *x, int x_len,
 | 
			
		||||
		       const float *h, int h_len,
 | 
			
		||||
		       float *y, int y_len,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ include $(top_srcdir)/Makefile.common
 | 
			
		||||
SUBDIRS = common
 | 
			
		||||
 | 
			
		||||
if DEVICE_IPC
 | 
			
		||||
SUBDIRS += ipc ipc2
 | 
			
		||||
SUBDIRS += ipc
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
if DEVICE_USRP1
 | 
			
		||||
 
 | 
			
		||||
@@ -37,49 +37,16 @@ extern "C" {
 | 
			
		||||
#include <osmocom/vty/cpu_sched_vty.h>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define USRP_TX_AMPL     0.3
 | 
			
		||||
#define UMTRX_TX_AMPL    0.7
 | 
			
		||||
#define LIMESDR_TX_AMPL  0.3
 | 
			
		||||
#define SAMPLE_BUF_SZ    (1 << 20)
 | 
			
		||||
#define SAMPLE_BUF_SZ (1 << 20)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * UHD timeout value on streaming (re)start
 | 
			
		||||
 *
 | 
			
		||||
 * Allow some time for streaming to commence after the start command is issued,
 | 
			
		||||
 * but consider a wait beyond one second to be a definite error condition.
 | 
			
		||||
 */
 | 
			
		||||
#define UHD_RESTART_TIMEOUT     1.0
 | 
			
		||||
#define B2XX_TIMING_4_4SPS 6.18462e-5
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * UmTRX specific settings
 | 
			
		||||
 */
 | 
			
		||||
#define UMTRX_VGA1_DEF   -18
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * USRP version dependent device timings
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#define B2XX_TIMING_1SPS	1.7153e-4
 | 
			
		||||
#define B2XX_TIMING_4SPS	1.1696e-4
 | 
			
		||||
#define B2XX_TIMING_4_4SPS	6.18462e-5
 | 
			
		||||
#define B2XX_TIMING_MCBTS	7e-5
 | 
			
		||||
 | 
			
		||||
#define CHKRET() { \
 | 
			
		||||
	if(status !=0) \
 | 
			
		||||
	fprintf(stderr, "%s:%s:%d %s\n", __FILE__,__FUNCTION__, __LINE__, bladerf_strerror(status)); \
 | 
			
		||||
#define CHKRET()                                                                                                       \
 | 
			
		||||
	{                                                                                                              \
 | 
			
		||||
		if (status != 0)                                                                                       \
 | 
			
		||||
			LOGC(DDEV, ERROR) << bladerf_strerror(status);                                                 \
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Tx / Rx sample offset values. In a perfect world, there is no group delay
 | 
			
		||||
 * though analog components, and behaviour through digital filters exactly
 | 
			
		||||
 * matches calculated values. In reality, there are unaccounted factors,
 | 
			
		||||
 * which are captured in these empirically measured (using a loopback test)
 | 
			
		||||
 * timing correction values.
 | 
			
		||||
 *
 | 
			
		||||
 * Notes:
 | 
			
		||||
 *   USRP1 with timestamps is not supported by UHD.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* Device Type, Tx-SPS, Rx-SPS */
 | 
			
		||||
typedef std::tuple<blade_dev_type, int, int> dev_key;
 | 
			
		||||
 | 
			
		||||
@@ -92,22 +59,19 @@ struct dev_desc {
 | 
			
		||||
	std::string str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const std::map<dev_key, dev_desc> dev_param_map {
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2,  1, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_1SPS, "B200 1 SPS"   } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2,  4, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2,  4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } },
 | 
			
		||||
static const std::map<dev_key, dev_desc> dev_param_map{
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef std::tuple<blade_dev_type, enum gsm_band> dev_band_key;
 | 
			
		||||
typedef std::map<dev_band_key, dev_band_desc>::const_iterator dev_band_map_it;
 | 
			
		||||
static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map {
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850),	{ 89.75, 13.3, -7.5  } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900),	{ 89.75, 13.3, -7.5  } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800),	{ 89.75, 7.5,  -11.0 } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900),	{ 89.75, 7.7,  -11.0 } },
 | 
			
		||||
static const std::map<dev_band_key, dev_band_desc> dev_band_nom_power_param_map{
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_850), { 89.75, 13.3, -7.5 } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_900), { 89.75, 13.3, -7.5 } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1800), { 89.75, 7.5, -11.0 } },
 | 
			
		||||
	{ std::make_tuple(blade_dev_type::BLADE2, GSM_BAND_1900), { 89.75, 7.7, -11.0 } },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* So far measurements done for B210 show really close to linear relationship
 | 
			
		||||
 * between gain and real output power, so we simply adjust the measured offset
 | 
			
		||||
 */
 | 
			
		||||
@@ -120,21 +84,17 @@ static double TxPower2TxGain(const dev_band_desc &desc, double tx_power_dbm)
 | 
			
		||||
	return desc.nom_uhd_tx_gain - (desc.nom_out_tx_power - tx_power_dbm);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
blade_device::blade_device(size_t tx_sps, size_t rx_sps,
 | 
			
		||||
		       InterfaceType iface, size_t chan_num, double lo_offset,
 | 
			
		||||
		       const std::vector<std::string>& tx_paths,
 | 
			
		||||
		       const std::vector<std::string>& rx_paths)
 | 
			
		||||
	: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths),
 | 
			
		||||
	  dev(nullptr), rx_gain_min(0.0), rx_gain_max(0.0),
 | 
			
		||||
	  band_ass_curr_sess(false), band((enum gsm_band)0), tx_spp(0),
 | 
			
		||||
	  rx_spp(0), started(false), aligned(false),
 | 
			
		||||
	  drop_cnt(0), prev_ts(0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
 | 
			
		||||
blade_device::blade_device(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
 | 
			
		||||
			   const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
 | 
			
		||||
	: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), dev(nullptr), rx_gain_min(0.0),
 | 
			
		||||
	  rx_gain_max(0.0), band_ass_curr_sess(false), band((enum gsm_band)0), tx_spp(0), rx_spp(0), started(false),
 | 
			
		||||
	  aligned(false), drop_cnt(0), prev_ts(0), ts_initial(0), ts_offset(0), async_event_thrd(NULL)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
blade_device::~blade_device()
 | 
			
		||||
{
 | 
			
		||||
	if(dev) {
 | 
			
		||||
	if (dev) {
 | 
			
		||||
		bladerf_enable_module(dev, BLADERF_CHANNEL_RX(0), false);
 | 
			
		||||
		bladerf_enable_module(dev, BLADERF_CHANNEL_TX(0), false);
 | 
			
		||||
	}
 | 
			
		||||
@@ -152,9 +112,8 @@ void blade_device::assign_band_desc(enum gsm_band req_band)
 | 
			
		||||
	it = dev_band_nom_power_param_map.find(dev_band_key(dev_type, req_band));
 | 
			
		||||
	if (it == dev_band_nom_power_param_map.end()) {
 | 
			
		||||
		dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
 | 
			
		||||
		LOGC(DDEV, ERROR) << "No Power parameters exist for device "
 | 
			
		||||
				    << desc.str << " on band " << gsm_band_name(req_band)
 | 
			
		||||
				    << ", using B210 ones as fallback";
 | 
			
		||||
		LOGC(DDEV, ERROR) << "No Power parameters exist for device " << desc.str << " on band "
 | 
			
		||||
				  << gsm_band_name(req_band) << ", using B210 ones as fallback";
 | 
			
		||||
		it = dev_band_nom_power_param_map.find(dev_band_key(blade_dev_type::BLADE2, req_band));
 | 
			
		||||
	}
 | 
			
		||||
	OSMO_ASSERT(it != dev_band_nom_power_param_map.end())
 | 
			
		||||
@@ -164,8 +123,8 @@ void blade_device::assign_band_desc(enum gsm_band req_band)
 | 
			
		||||
bool blade_device::set_band(enum gsm_band req_band)
 | 
			
		||||
{
 | 
			
		||||
	if (band_ass_curr_sess && req_band != band) {
 | 
			
		||||
		LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band)
 | 
			
		||||
				  << " different from previous band " << gsm_band_name(band);
 | 
			
		||||
		LOGC(DDEV, ALERT) << "Requesting band " << gsm_band_name(req_band) << " different from previous band "
 | 
			
		||||
				  << gsm_band_name(band);
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -177,10 +136,11 @@ bool blade_device::set_band(enum gsm_band req_band)
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void blade_device::get_dev_band_desc(dev_band_desc& desc)
 | 
			
		||||
void blade_device::get_dev_band_desc(dev_band_desc &desc)
 | 
			
		||||
{
 | 
			
		||||
	if (band == 0) {
 | 
			
		||||
		LOGC(DDEV, ERROR) << "Power parameters requested before Tx Frequency was set! Providing band 900 by default...";
 | 
			
		||||
		LOGC(DDEV, ERROR)
 | 
			
		||||
			<< "Power parameters requested before Tx Frequency was set! Providing band 900 by default...";
 | 
			
		||||
		assign_band_desc(GSM_BAND_900);
 | 
			
		||||
	}
 | 
			
		||||
	desc = band_desc;
 | 
			
		||||
@@ -191,36 +151,36 @@ void blade_device::init_gains()
 | 
			
		||||
	double tx_gain_min, tx_gain_max;
 | 
			
		||||
	int status;
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	const struct bladerf_range* r;
 | 
			
		||||
	const struct bladerf_range *r;
 | 
			
		||||
	bladerf_get_gain_range(dev, BLADERF_RX, &r);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	rx_gain_min = r->min;
 | 
			
		||||
	rx_gain_max = r->max;
 | 
			
		||||
	LOGC(DDEV, INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < rx_gains.size(); i++) {
 | 
			
		||||
		double gain = (rx_gain_min + rx_gain_max) / 2;
 | 
			
		||||
		status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC );
 | 
			
		||||
		status = bladerf_set_gain_mode(dev, BLADERF_CHANNEL_RX(i), BLADERF_GAIN_MGC);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		bladerf_gain_mode m;
 | 
			
		||||
		bladerf_get_gain_mode(dev, BLADERF_CHANNEL_RX(i), &m);
 | 
			
		||||
		LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO") ;
 | 
			
		||||
		LOGC(DDEV, INFO) << (m == BLADERF_GAIN_MANUAL ? "gain manual" : "gain AUTO");
 | 
			
		||||
 | 
			
		||||
		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);//gain);
 | 
			
		||||
		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		int actual_gain;
 | 
			
		||||
		status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " << r->scale << " actual " << actual_gain;
 | 
			
		||||
		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale "
 | 
			
		||||
				 << r->scale << " actual " << actual_gain;
 | 
			
		||||
		rx_gains[i] = actual_gain;
 | 
			
		||||
 | 
			
		||||
		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);//gain);
 | 
			
		||||
		status = bladerf_set_gain(dev, BLADERF_CHANNEL_RX(i), 0);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		status = bladerf_get_gain(dev, BLADERF_CHANNEL_RX(i), &actual_gain);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale " << r->scale << " actual " << actual_gain;
 | 
			
		||||
		LOGC(DDEV, INFO) << "Default setting Rx gain for channel " << i << " to " << gain << " scale "
 | 
			
		||||
				 << r->scale << " actual " << actual_gain;
 | 
			
		||||
		rx_gains[i] = actual_gain;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -232,40 +192,35 @@ void blade_device::init_gains()
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < tx_gains.size(); i++) {
 | 
			
		||||
		double gain = (tx_gain_min + tx_gain_max) / 2;
 | 
			
		||||
		status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(i), 30);//gain);
 | 
			
		||||
		status = bladerf_set_gain(dev, BLADERF_CHANNEL_TX(i), 30);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		int actual_gain;
 | 
			
		||||
		status = bladerf_get_gain(dev, BLADERF_CHANNEL_TX(i), &actual_gain);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale " << r->scale << " actual " << actual_gain;
 | 
			
		||||
		LOGC(DDEV, INFO) << "Default setting Tx gain for channel " << i << " to " << gain << " scale "
 | 
			
		||||
				 << r->scale << " actual " << actual_gain;
 | 
			
		||||
		tx_gains[i] = actual_gain;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void blade_device::set_rates()
 | 
			
		||||
{
 | 
			
		||||
	//dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps));
 | 
			
		||||
 | 
			
		||||
	struct bladerf_rational_rate rate = {0, static_cast<uint64_t>((1625e3 * 4)), 6}, actual;
 | 
			
		||||
	struct bladerf_rational_rate rate = { 0, static_cast<uint64_t>((1625e3 * 4)), 6 }, actual;
 | 
			
		||||
	auto status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
 | 
			
		||||
	CHKRET()
 | 
			
		||||
	status = bladerf_set_rational_sample_rate(dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
 | 
			
		||||
	CHKRET()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	tx_rate = rx_rate = (double)rate.num/(double)rate.den;
 | 
			
		||||
	tx_rate = rx_rate = (double)rate.num / (double)rate.den;
 | 
			
		||||
 | 
			
		||||
	LOGC(DDEV, INFO) << "Rates set to" << tx_rate << " / " << rx_rate;
 | 
			
		||||
 | 
			
		||||
	bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL);
 | 
			
		||||
	bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth *)NULL);
 | 
			
		||||
 | 
			
		||||
    bladerf_set_bandwidth(dev, BLADERF_CHANNEL_RX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth*)NULL);
 | 
			
		||||
    bladerf_set_bandwidth(dev, BLADERF_CHANNEL_TX(0), (bladerf_bandwidth)2e6, (bladerf_bandwidth*)NULL);
 | 
			
		||||
 | 
			
		||||
	ts_offset = 60;//static_cast<TIMESTAMP>(desc.offset * rx_rate);
 | 
			
		||||
	//LOGC(DDEV, INFO) << "Rates configured for " << desc.str;
 | 
			
		||||
	ts_offset = 60; // FIXME: actual blade offset, should equal b2xx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double blade_device::setRxGain(double db, size_t chan)
 | 
			
		||||
@@ -275,7 +230,7 @@ double blade_device::setRxGain(double db, size_t chan)
 | 
			
		||||
		return 0.0f;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30);//db);
 | 
			
		||||
	bladerf_set_gain(dev, BLADERF_CHANNEL_RX(chan), 30); //db);
 | 
			
		||||
	int actual_gain;
 | 
			
		||||
	bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain);
 | 
			
		||||
 | 
			
		||||
@@ -311,7 +266,8 @@ double blade_device::rssiOffset(size_t chan)
 | 
			
		||||
	return rssiOffset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double blade_device::setPowerAttenuation(int atten, size_t chan) {
 | 
			
		||||
double blade_device::setPowerAttenuation(int atten, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	double tx_power, db;
 | 
			
		||||
	dev_band_desc desc;
 | 
			
		||||
 | 
			
		||||
@@ -324,19 +280,20 @@ double blade_device::setPowerAttenuation(int atten, size_t chan) {
 | 
			
		||||
	tx_power = desc.nom_out_tx_power - atten;
 | 
			
		||||
	db = TxPower2TxGain(desc, tx_power);
 | 
			
		||||
 | 
			
		||||
	bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30);//db);
 | 
			
		||||
	bladerf_set_gain(dev, BLADERF_CHANNEL_TX(chan), 30);
 | 
			
		||||
	int actual_gain;
 | 
			
		||||
	bladerf_get_gain(dev, BLADERF_CHANNEL_RX(chan), &actual_gain);
 | 
			
		||||
 | 
			
		||||
	tx_gains[chan] = actual_gain;
 | 
			
		||||
 | 
			
		||||
	LOGC(DDEV, INFO) << "Set TX gain to " << tx_gains[chan] << "dB, ~"
 | 
			
		||||
			 <<  TxGain2TxPower(desc, tx_gains[chan]) << " dBm "
 | 
			
		||||
			 << "(asked for " << db << " dB, ~" << tx_power << " dBm)";
 | 
			
		||||
	LOGC(DDEV, INFO)
 | 
			
		||||
		<< "Set TX gain to " << tx_gains[chan] << "dB, ~" << TxGain2TxPower(desc, tx_gains[chan]) << " dBm "
 | 
			
		||||
		<< "(asked for " << db << " dB, ~" << tx_power << " dBm)";
 | 
			
		||||
 | 
			
		||||
	return desc.nom_out_tx_power - TxGain2TxPower(desc, tx_gains[chan]);
 | 
			
		||||
}
 | 
			
		||||
double blade_device::getPowerAttenuation(size_t chan) {
 | 
			
		||||
double blade_device::getPowerAttenuation(size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	dev_band_desc desc;
 | 
			
		||||
	if (chan >= tx_gains.size()) {
 | 
			
		||||
		LOGC(DDEV, ALERT) << "Requested non-existent channel " << chan;
 | 
			
		||||
@@ -355,26 +312,24 @@ int blade_device::getNominalTxPower(size_t chan)
 | 
			
		||||
	return desc.nom_out_tx_power;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int blade_device::open(const std::string &args, int ref, bool swap_channels)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	bladerf_log_set_verbosity(BLADERF_LOG_LEVEL_VERBOSE);
 | 
			
		||||
	bladerf_set_usb_reset_on_open(true);
 | 
			
		||||
	auto success = bladerf_open(&dev, args.c_str());
 | 
			
		||||
	if(success != 0) {
 | 
			
		||||
		struct bladerf_devinfo* info;
 | 
			
		||||
	if (success != 0) {
 | 
			
		||||
		struct bladerf_devinfo *info;
 | 
			
		||||
		auto num_devs = bladerf_get_device_list(&info);
 | 
			
		||||
		LOGC(DDEV, ALERT) << "No bladerf devices found with identifier '" << args << "'";
 | 
			
		||||
		if(num_devs) {
 | 
			
		||||
			for(int i=0; i < num_devs; i++)
 | 
			
		||||
		if (num_devs) {
 | 
			
		||||
			for (int i = 0; i < num_devs; i++)
 | 
			
		||||
				LOGC(DDEV, ALERT) << "Found device:" << info[i].product << " serial " << info[i].serial;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	if(strcmp("bladerf2", bladerf_get_board_name(dev))) {
 | 
			
		||||
		LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev) ;
 | 
			
		||||
	if (strcmp("bladerf2", bladerf_get_board_name(dev))) {
 | 
			
		||||
		LOGC(DDEV, ALERT) << "Only BladeRF2 supported! found:" << bladerf_get_board_name(dev);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -383,10 +338,8 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels)
 | 
			
		||||
 | 
			
		||||
	struct bladerf_devinfo info;
 | 
			
		||||
	bladerf_get_devinfo(dev, &info);
 | 
			
		||||
	// Use the first found device
 | 
			
		||||
	LOGC(DDEV, INFO) << "Using discovered bladerf device " << info.serial;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	tx_freqs.resize(chans);
 | 
			
		||||
	rx_freqs.resize(chans);
 | 
			
		||||
	tx_gains.resize(chans);
 | 
			
		||||
@@ -402,20 +355,20 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels)
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(ref == REF_EXTERNAL) {
 | 
			
		||||
	if (ref == REF_EXTERNAL) {
 | 
			
		||||
		bool is_locked;
 | 
			
		||||
		int status = bladerf_set_pll_enable(dev, true);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		status = bladerf_set_pll_refclk(dev, 10000000);
 | 
			
		||||
		CHKRET()
 | 
			
		||||
		for(int i=0; i < 20; i++) {
 | 
			
		||||
			usleep(50*1000);
 | 
			
		||||
		for (int i = 0; i < 20; i++) {
 | 
			
		||||
			usleep(50 * 1000);
 | 
			
		||||
			status = bladerf_get_pll_lock_state(dev, &is_locked);
 | 
			
		||||
			CHKRET()
 | 
			
		||||
			if(is_locked)
 | 
			
		||||
			if (is_locked)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
		if(!is_locked) {
 | 
			
		||||
		if (!is_locked) {
 | 
			
		||||
			LOGC(DDEV, ALERT) << "unable to lock refclk!";
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
@@ -425,51 +378,41 @@ int blade_device::open(const std::string &args, int ref, bool swap_channels)
 | 
			
		||||
 | 
			
		||||
	set_rates();
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	/*
 | 
			
		||||
	1ts = 3/5200s
 | 
			
		||||
	1024*2 = small gap(~180us) every 9.23ms = every 16 ts? -> every 2 frames
 | 
			
		||||
	1024*1 = large gap(~627us) every 9.23ms = every 16 ts? -> every 2 frames
 | 
			
		||||
 | 
			
		||||
	rif convertbuffer = 625*4 = 2500 -> 4 ts
 | 
			
		||||
	rif rxtxbuf = 4 * segment(625*4) = 10000 -> 16 ts
 | 
			
		||||
*/
 | 
			
		||||
    const unsigned int num_buffers   = 256;
 | 
			
		||||
    const unsigned int buffer_size   = 1024*4; /* Must be a multiple of 1024 */
 | 
			
		||||
    const unsigned int num_transfers = 32;
 | 
			
		||||
    const unsigned int timeout_ms    = 3500;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META,
 | 
			
		||||
                                 num_buffers, buffer_size, num_transfers,
 | 
			
		||||
                                 timeout_ms);
 | 
			
		||||
 | 
			
		||||
    bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META,
 | 
			
		||||
                                 num_buffers, buffer_size, num_transfers,
 | 
			
		||||
                                 timeout_ms);
 | 
			
		||||
 | 
			
		||||
	*/
 | 
			
		||||
	const unsigned int num_buffers = 256;
 | 
			
		||||
	const unsigned int buffer_size = 1024 * 4; /* Must be a multiple of 1024 */
 | 
			
		||||
	const unsigned int num_transfers = 32;
 | 
			
		||||
	const unsigned int timeout_ms = 3500;
 | 
			
		||||
 | 
			
		||||
	bladerf_sync_config(dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers,
 | 
			
		||||
			    timeout_ms);
 | 
			
		||||
 | 
			
		||||
	bladerf_sync_config(dev, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11_META, num_buffers, buffer_size, num_transfers,
 | 
			
		||||
			    timeout_ms);
 | 
			
		||||
 | 
			
		||||
	/* Number of samples per over-the-wire packet */
 | 
			
		||||
	tx_spp = rx_spp = buffer_size;
 | 
			
		||||
 | 
			
		||||
	// Create receive buffer
 | 
			
		||||
	size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t);
 | 
			
		||||
	for (size_t i = 0; i < rx_buffers.size(); i++)
 | 
			
		||||
		rx_buffers[i] = new smpl_buf(buf_len);
 | 
			
		||||
 | 
			
		||||
	// Create vector buffer
 | 
			
		||||
	pkt_bufs = std::vector<std::vector<short> >(chans, std::vector<short>(2 * rx_spp));
 | 
			
		||||
	for (size_t i = 0; i < pkt_bufs.size(); i++)
 | 
			
		||||
		pkt_ptrs.push_back(&pkt_bufs[i].front());
 | 
			
		||||
 | 
			
		||||
	// Initialize and shadow gain values
 | 
			
		||||
	init_gains();
 | 
			
		||||
 | 
			
		||||
	return NORMAL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool blade_device::restart()
 | 
			
		||||
{
 | 
			
		||||
	/* Allow 100 ms delay to align multi-channel streams */
 | 
			
		||||
@@ -498,11 +441,9 @@ bool blade_device::start()
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Start streaming
 | 
			
		||||
	if (!restart())
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	started = true;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
@@ -522,8 +463,7 @@ bool blade_device::stop()
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun,
 | 
			
		||||
			    TIMESTAMP timestamp, bool *underrun)
 | 
			
		||||
int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun)
 | 
			
		||||
{
 | 
			
		||||
	ssize_t rc;
 | 
			
		||||
	uint64_t ts;
 | 
			
		||||
@@ -550,33 +490,25 @@ int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	struct bladerf_metadata meta = {};
 | 
			
		||||
	meta.timestamp = ts;
 | 
			
		||||
	//static bool first_rx = true;
 | 
			
		||||
	// meta.timestamp = (first_rx) ? ts : 0;
 | 
			
		||||
	// meta.flags = (!first_rx) ? 0:BLADERF_META_FLAG_RX_NOW;
 | 
			
		||||
	// if(first_rx)
 | 
			
		||||
	// 	first_rx = false;
 | 
			
		||||
 | 
			
		||||
	// Receive samples from the usrp until we have enough
 | 
			
		||||
	while (rx_buffers[0]->avail_smpls(timestamp) < len) {
 | 
			
		||||
		thread_enable_cancel(false);
 | 
			
		||||
		int status = bladerf_sync_rx(dev, pkt_ptrs[0], len, &meta, 200U);
 | 
			
		||||
		thread_enable_cancel(true);
 | 
			
		||||
 | 
			
		||||
		if(status != 0)
 | 
			
		||||
			std::cerr << "RX fucked: " << bladerf_strerror(status);
 | 
			
		||||
		if(meta.flags & BLADERF_META_STATUS_OVERRUN )
 | 
			
		||||
			std::cerr << "RX fucked OVER: " << bladerf_strerror(status);
 | 
			
		||||
		if (status != 0)
 | 
			
		||||
			LOGC(DDEV, ERROR) << "RX broken: " << bladerf_strerror(status);
 | 
			
		||||
		if (meta.flags & BLADERF_META_STATUS_OVERRUN)
 | 
			
		||||
			LOGC(DDEV, ERROR) << "RX borken, OVERRUN: " << bladerf_strerror(status);
 | 
			
		||||
 | 
			
		||||
		size_t num_smpls = meta.actual_count;
 | 
			
		||||
;		ts = meta.timestamp;
 | 
			
		||||
		;
 | 
			
		||||
		ts = meta.timestamp;
 | 
			
		||||
 | 
			
		||||
		for (size_t i = 0; i < rx_buffers.size(); i++) {
 | 
			
		||||
			rc = rx_buffers[i]->write((short *) &pkt_bufs[i].front(),
 | 
			
		||||
						  num_smpls,
 | 
			
		||||
						  ts);
 | 
			
		||||
			rc = rx_buffers[i]->write((short *)&pkt_bufs[i].front(), num_smpls, ts);
 | 
			
		||||
 | 
			
		||||
			// Continue on local overrun, exit on other errors
 | 
			
		||||
			if ((rc < 0)) {
 | 
			
		||||
@@ -587,10 +519,9 @@ int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		meta = {};
 | 
			
		||||
		meta.timestamp = ts+num_smpls;
 | 
			
		||||
		meta.timestamp = ts + num_smpls;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We have enough samples
 | 
			
		||||
	for (size_t i = 0; i < rx_buffers.size(); i++) {
 | 
			
		||||
		rc = rx_buffers[i]->read(bufs[i], len, timestamp);
 | 
			
		||||
		if ((rc < 0) || (rc != len)) {
 | 
			
		||||
@@ -603,27 +534,23 @@ int blade_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun
 | 
			
		||||
	return len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
 | 
			
		||||
			     unsigned long long timestamp)
 | 
			
		||||
int blade_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp)
 | 
			
		||||
{
 | 
			
		||||
	*underrun = false;
 | 
			
		||||
	static bool first_tx = true;
 | 
			
		||||
	struct bladerf_metadata meta = {};
 | 
			
		||||
	if(first_tx) {
 | 
			
		||||
	if (first_tx) {
 | 
			
		||||
		meta.timestamp = timestamp;
 | 
			
		||||
		meta.flags = BLADERF_META_FLAG_TX_BURST_START;
 | 
			
		||||
		first_tx = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	thread_enable_cancel(false);
 | 
			
		||||
	int status = bladerf_sync_tx(dev, (const void*)bufs[0], len, &meta, 200U);
 | 
			
		||||
	//size_t num_smpls = tx_stream->send(bufs, len, metadata);
 | 
			
		||||
	int status = bladerf_sync_tx(dev, (const void *)bufs[0], len, &meta, 200U);
 | 
			
		||||
	thread_enable_cancel(true);
 | 
			
		||||
 | 
			
		||||
	if(status != 0)
 | 
			
		||||
		std::cerr << "TX fucked: " << bladerf_strerror(status);
 | 
			
		||||
 | 
			
		||||
	// LOGCHAN(0, DDEV, INFO) << "tx " << timestamp << " " << len << " t+l: "<< timestamp+len << std::endl;
 | 
			
		||||
	if (status != 0)
 | 
			
		||||
		LOGC(DDEV, ERROR) << "TX broken: " << bladerf_strerror(status);
 | 
			
		||||
 | 
			
		||||
	return len;
 | 
			
		||||
}
 | 
			
		||||
@@ -635,20 +562,18 @@ bool blade_device::updateAlignment(TIMESTAMP timestamp)
 | 
			
		||||
 | 
			
		||||
bool blade_device::set_freq(double freq, size_t chan, bool tx)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	if (tx) {
 | 
			
		||||
		bladerf_set_frequency(dev, BLADERF_CHANNEL_TX(chan), freq);
 | 
			
		||||
		bladerf_frequency f;
 | 
			
		||||
		bladerf_get_frequency(dev,BLADERF_CHANNEL_TX(chan), &f);
 | 
			
		||||
		bladerf_get_frequency(dev, BLADERF_CHANNEL_TX(chan), &f);
 | 
			
		||||
		tx_freqs[chan] = f;
 | 
			
		||||
	} else {
 | 
			
		||||
		bladerf_set_frequency(dev, BLADERF_CHANNEL_RX(chan), freq);
 | 
			
		||||
		bladerf_frequency f;
 | 
			
		||||
		bladerf_get_frequency(dev,BLADERF_CHANNEL_RX(chan), &f);
 | 
			
		||||
		bladerf_get_frequency(dev, BLADERF_CHANNEL_RX(chan), &f);
 | 
			
		||||
		rx_freqs[chan] = f;
 | 
			
		||||
	}
 | 
			
		||||
	LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx  ? "TX" : "RX") << "): "  << std::endl;
 | 
			
		||||
 | 
			
		||||
	LOGCHAN(chan, DDEV, INFO) << "set_freq(" << freq << ", " << (tx ? "TX" : "RX") << "): " << std::endl;
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
@@ -664,14 +589,14 @@ bool blade_device::setTxFreq(double wFreq, size_t chan)
 | 
			
		||||
	}
 | 
			
		||||
	ScopedLock lock(tune_lock);
 | 
			
		||||
 | 
			
		||||
	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100 , 0);
 | 
			
		||||
	req_arfcn = gsm_freq102arfcn(wFreq / 1000 / 100, 0);
 | 
			
		||||
	if (req_arfcn == 0xffff) {
 | 
			
		||||
		LOGCHAN(chan, DDEV, ALERT) << "Unknown ARFCN for Tx Frequency " << wFreq / 1000 << " kHz";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
 | 
			
		||||
		LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Tx Frequency " << wFreq
 | 
			
		||||
					   << " Hz (ARFCN " << req_arfcn << " )";
 | 
			
		||||
		LOGCHAN(chan, DDEV, ALERT)
 | 
			
		||||
			<< "Unknown GSM band for Tx Frequency " << wFreq << " Hz (ARFCN " << req_arfcn << " )";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -701,8 +626,8 @@ bool blade_device::setRxFreq(double wFreq, size_t chan)
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (gsm_arfcn2band_rc(req_arfcn, &req_band) < 0) {
 | 
			
		||||
		LOGCHAN(chan, DDEV, ALERT) << "Unknown GSM band for Rx Frequency " << wFreq
 | 
			
		||||
					   << " Hz (ARFCN " << req_arfcn << " )";
 | 
			
		||||
		LOGCHAN(chan, DDEV, ALERT)
 | 
			
		||||
			<< "Unknown GSM band for Rx Frequency " << wFreq << " Hz (ARFCN " << req_arfcn << " )";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -737,12 +662,9 @@ bool blade_device::requiresRadioAlign()
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GSM::Time blade_device::minLatency() {
 | 
			
		||||
	/* Empirical data from a handful of
 | 
			
		||||
	relatively recent machines shows that the B100 will underrun when
 | 
			
		||||
	the transmit threshold is reduced to a time of 6 and a half frames,
 | 
			
		||||
	so we set a minimum 7 frame threshold. */
 | 
			
		||||
	return GSM::Time(6,7);
 | 
			
		||||
GSM::Time blade_device::minLatency()
 | 
			
		||||
{
 | 
			
		||||
	return GSM::Time(6, 7);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TIMESTAMP blade_device::initialWriteTimestamp()
 | 
			
		||||
@@ -757,21 +679,16 @@ TIMESTAMP blade_device::initialReadTimestamp()
 | 
			
		||||
 | 
			
		||||
double blade_device::fullScaleInputValue()
 | 
			
		||||
{
 | 
			
		||||
		return (double) 2047;
 | 
			
		||||
	return (double)2047;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double blade_device::fullScaleOutputValue()
 | 
			
		||||
{
 | 
			
		||||
	return (double) 2047;
 | 
			
		||||
	return (double)2047;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifndef IPCMAGIC
 | 
			
		||||
RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps,
 | 
			
		||||
			       InterfaceType iface, size_t chans, double lo_offset,
 | 
			
		||||
			       const std::vector<std::string>& tx_paths,
 | 
			
		||||
			       const std::vector<std::string>& rx_paths)
 | 
			
		||||
RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
 | 
			
		||||
			       const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
 | 
			
		||||
{
 | 
			
		||||
	return new blade_device(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -35,16 +35,13 @@ extern "C" {
 | 
			
		||||
 | 
			
		||||
#include <bladerf.h>
 | 
			
		||||
 | 
			
		||||
enum class blade_dev_type {
 | 
			
		||||
	BLADE1,
 | 
			
		||||
	BLADE2
 | 
			
		||||
};
 | 
			
		||||
enum class blade_dev_type { BLADE1, BLADE2 };
 | 
			
		||||
 | 
			
		||||
struct dev_band_desc {
 | 
			
		||||
	/* Maximum UHD Tx Gain which can be set/used without distorting the
 | 
			
		||||
	   output signal, and the resulting real output power measured when that
 | 
			
		||||
	   gain is used. Correct measured values only provided for B210 so far. */
 | 
			
		||||
	double nom_uhd_tx_gain;  /* dB */
 | 
			
		||||
	double nom_uhd_tx_gain; /* dB */
 | 
			
		||||
	double nom_out_tx_power; /* dBm */
 | 
			
		||||
	/* Factor used to infer base real RSSI offset on the Rx path based on current
 | 
			
		||||
	   configured RxGain. The resulting rssiOffset is added to the per burst
 | 
			
		||||
@@ -56,24 +53,23 @@ struct dev_band_desc {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class blade_device : public RadioDevice {
 | 
			
		||||
public:
 | 
			
		||||
	blade_device(size_t tx_sps, size_t rx_sps, InterfaceType type,
 | 
			
		||||
		   size_t chan_num, double offset,
 | 
			
		||||
		   const std::vector<std::string>& tx_paths,
 | 
			
		||||
		   const std::vector<std::string>& rx_paths);
 | 
			
		||||
    public:
 | 
			
		||||
	blade_device(size_t tx_sps, size_t rx_sps, InterfaceType type, size_t chan_num, double offset,
 | 
			
		||||
		     const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths);
 | 
			
		||||
	~blade_device();
 | 
			
		||||
 | 
			
		||||
	int open(const std::string &args, int ref, bool swap_channels);
 | 
			
		||||
	bool start();
 | 
			
		||||
	bool stop();
 | 
			
		||||
	bool restart();
 | 
			
		||||
	enum TxWindowType getWindowType() { return tx_window; }
 | 
			
		||||
	enum TxWindowType getWindowType()
 | 
			
		||||
	{
 | 
			
		||||
		return tx_window;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int readSamples(std::vector<short *> &bufs, int len, bool *overrun,
 | 
			
		||||
			TIMESTAMP timestamp, bool *underrun);
 | 
			
		||||
	int readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun);
 | 
			
		||||
 | 
			
		||||
	int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
 | 
			
		||||
			 TIMESTAMP timestamp);
 | 
			
		||||
	int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, TIMESTAMP timestamp);
 | 
			
		||||
 | 
			
		||||
	bool updateAlignment(TIMESTAMP timestamp);
 | 
			
		||||
 | 
			
		||||
@@ -88,8 +84,14 @@ public:
 | 
			
		||||
 | 
			
		||||
	double setRxGain(double db, size_t chan);
 | 
			
		||||
	double getRxGain(size_t chan);
 | 
			
		||||
	double maxRxGain(void) { return rx_gain_max; }
 | 
			
		||||
	double minRxGain(void) { return rx_gain_min; }
 | 
			
		||||
	double maxRxGain(void)
 | 
			
		||||
	{
 | 
			
		||||
		return rx_gain_max;
 | 
			
		||||
	}
 | 
			
		||||
	double minRxGain(void)
 | 
			
		||||
	{
 | 
			
		||||
		return rx_gain_min;
 | 
			
		||||
	}
 | 
			
		||||
	double rssiOffset(size_t chan);
 | 
			
		||||
 | 
			
		||||
	double setPowerAttenuation(int atten, size_t chan);
 | 
			
		||||
@@ -101,16 +103,31 @@ public:
 | 
			
		||||
	double getRxFreq(size_t chan);
 | 
			
		||||
	double getRxFreq();
 | 
			
		||||
 | 
			
		||||
	bool setRxAntenna(const std::string &ant, size_t chan) { return {};};
 | 
			
		||||
	std::string getRxAntenna(size_t chan) { return {};};
 | 
			
		||||
	bool setTxAntenna(const std::string &ant, size_t chan) { return {};};
 | 
			
		||||
	std::string getTxAntenna(size_t chan) { return {};};
 | 
			
		||||
	bool setRxAntenna(const std::string &ant, size_t chan)
 | 
			
		||||
	{
 | 
			
		||||
		return {};
 | 
			
		||||
	};
 | 
			
		||||
	std::string getRxAntenna(size_t chan)
 | 
			
		||||
	{
 | 
			
		||||
		return {};
 | 
			
		||||
	};
 | 
			
		||||
	bool setTxAntenna(const std::string &ant, size_t chan)
 | 
			
		||||
	{
 | 
			
		||||
		return {};
 | 
			
		||||
	};
 | 
			
		||||
	std::string getTxAntenna(size_t chan)
 | 
			
		||||
	{
 | 
			
		||||
		return {};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	bool requiresRadioAlign();
 | 
			
		||||
 | 
			
		||||
	GSM::Time minLatency();
 | 
			
		||||
 | 
			
		||||
	inline double getSampleRate() { return tx_rate; }
 | 
			
		||||
	inline double getSampleRate()
 | 
			
		||||
	{
 | 
			
		||||
		return tx_rate;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** Receive and process asynchronous message
 | 
			
		||||
	    @return true if message received or false on timeout or error
 | 
			
		||||
@@ -124,9 +141,9 @@ public:
 | 
			
		||||
		ERROR_UNHANDLED = -4,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	struct bladerf* dev;
 | 
			
		||||
	void* usrp_dev;
 | 
			
		||||
    protected:
 | 
			
		||||
	struct bladerf *dev;
 | 
			
		||||
	void *usrp_dev;
 | 
			
		||||
 | 
			
		||||
	enum TxWindowType tx_window;
 | 
			
		||||
	enum blade_dev_type dev_type;
 | 
			
		||||
@@ -162,7 +179,7 @@ protected:
 | 
			
		||||
	bool flush_recv(size_t num_pkts);
 | 
			
		||||
 | 
			
		||||
	bool set_freq(double freq, size_t chan, bool tx);
 | 
			
		||||
	void get_dev_band_desc(dev_band_desc& desc);
 | 
			
		||||
	void get_dev_band_desc(dev_band_desc &desc);
 | 
			
		||||
	bool set_band(enum gsm_band req_band);
 | 
			
		||||
	void assign_band_desc(enum gsm_band req_band);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -199,7 +199,7 @@ class IPCDevice : public RadioDevice {
 | 
			
		||||
	virtual double minRxGain(void) override;
 | 
			
		||||
 | 
			
		||||
	/* FIXME: return rx_gains[chan] ? receive factor from IPC Driver? */
 | 
			
		||||
	double rssiOffset(size_t chan) { return 0.0f; };
 | 
			
		||||
	double rssiOffset(size_t chan) override { return 0.0f; };
 | 
			
		||||
 | 
			
		||||
	double setPowerAttenuation(int atten, size_t chan) override;
 | 
			
		||||
	double getPowerAttenuation(size_t chan = 0) override;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,19 +17,11 @@ libdevice_la_SOURCES = IPCDevice.cpp shm.c ipc_shm.c ipc_chan.c ipc_sock.c
 | 
			
		||||
libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
 | 
			
		||||
libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC
 | 
			
		||||
 | 
			
		||||
#work around distclean issue on older autotools vers:
 | 
			
		||||
#a direct build of ../uhd/UHDDevice.cpp tries to clean
 | 
			
		||||
#../uhd/.dep/UHDDevice.Plo twice and fails
 | 
			
		||||
uhddev_ipc.cpp:
 | 
			
		||||
	echo "#include \"../uhd/UHDDevice.cpp\"" >$@
 | 
			
		||||
CLEANFILES= uhddev_ipc.cpp
 | 
			
		||||
BUILT_SOURCES = uhddev_ipc.cpp
 | 
			
		||||
 | 
			
		||||
if DEVICE_UHD
 | 
			
		||||
 | 
			
		||||
bin_PROGRAMS = ipc-driver-test
 | 
			
		||||
#ipc_driver_test_SHORTNAME = drvt
 | 
			
		||||
ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ipc_shm.c ipc_chan.c ipc_sock.c uhddev_ipc.cpp
 | 
			
		||||
ipc_driver_test_SOURCES = ipc-driver-test.c uhdwrap.cpp ipc_shm.c ipc_chan.c ipc_sock.c ../uhd/UHDDevice.cpp
 | 
			
		||||
ipc_driver_test_LDADD = \
 | 
			
		||||
        shm.lo \
 | 
			
		||||
	$(top_builddir)/Transceiver52M/device/common/libdevice_common.la \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,314 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
 | 
			
		||||
*
 | 
			
		||||
* SPDX-License-Identifier: AGPL-3.0+
 | 
			
		||||
*
 | 
			
		||||
* 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/>.
 | 
			
		||||
* See the COPYING file in the main directory for details.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#include <sys/time.h>
 | 
			
		||||
#include <osmocom/core/timer_compat.h>
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_CONFIG_H
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "Logger.h"
 | 
			
		||||
#include "Threads.h"
 | 
			
		||||
#include "IPCDevice.h"
 | 
			
		||||
#include "smpl_buf.h"
 | 
			
		||||
 | 
			
		||||
#define SAMPLE_BUF_SZ (1 << 20)
 | 
			
		||||
static const auto ONE_BIT_DURATION ((12./5200.)/(156.25*4.));
 | 
			
		||||
static const auto ONE_SAMPLE_DURATION_US ((ONE_BIT_DURATION/4.)*1000*1000);
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
IPCDevice2::IPCDevice2(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
 | 
			
		||||
		       const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
 | 
			
		||||
	: RadioDevice(tx_sps, rx_sps, iface, chan_num, lo_offset, tx_paths, rx_paths), rx_buffers(chans),
 | 
			
		||||
	  started(false), tx_gains(chans), rx_gains(chans)
 | 
			
		||||
{
 | 
			
		||||
	LOGC(DDEV, INFO) << "creating IPC device...";
 | 
			
		||||
 | 
			
		||||
	if (!(tx_sps == 4) || !(rx_sps == 4)) {
 | 
			
		||||
		LOGC(DDEV, FATAL) << "IPC shm if create failed!";
 | 
			
		||||
		exit(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Set up per-channel Rx timestamp based Ring buffers */
 | 
			
		||||
	for (size_t i = 0; i < rx_buffers.size(); i++)
 | 
			
		||||
		rx_buffers[i] = new smpl_buf(SAMPLE_BUF_SZ / sizeof(uint32_t));
 | 
			
		||||
 | 
			
		||||
	if (!m.create()) {
 | 
			
		||||
		LOGC(DDEV, FATAL) << "IPC shm if create failed!";
 | 
			
		||||
		exit(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IPCDevice2::~IPCDevice2()
 | 
			
		||||
{
 | 
			
		||||
	LOGC(DDEV, INFO) << "Closing IPC device";
 | 
			
		||||
	/* disable all channels */
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < rx_buffers.size(); i++)
 | 
			
		||||
		delete rx_buffers[i];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int IPCDevice2::open(const std::string &args, int ref, bool swap_channels)
 | 
			
		||||
{
 | 
			
		||||
	std::string k, v;
 | 
			
		||||
 | 
			
		||||
	/* configure antennas */
 | 
			
		||||
	if (!set_antennas()) {
 | 
			
		||||
		LOGC(DDEV, FATAL) << "IPC antenna setting failed";
 | 
			
		||||
		goto out_close;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return iface == MULTI_ARFCN ? MULTI_ARFCN : NORMAL;
 | 
			
		||||
 | 
			
		||||
out_close:
 | 
			
		||||
	LOGC(DDEV, FATAL) << "Error in IPC open, closing";
 | 
			
		||||
	return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::start()
 | 
			
		||||
{
 | 
			
		||||
	LOGC(DDEV, INFO) << "starting IPC...";
 | 
			
		||||
 | 
			
		||||
	if (started) {
 | 
			
		||||
		LOGC(DDEV, ERR) << "Device already started";
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int max_bufs_to_flush = 120;
 | 
			
		||||
	flush_recv(max_bufs_to_flush);
 | 
			
		||||
 | 
			
		||||
	started = true;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::stop()
 | 
			
		||||
{
 | 
			
		||||
	if (!started)
 | 
			
		||||
		return true;
 | 
			
		||||
 | 
			
		||||
	LOGC(DDEV, NOTICE) << "All channels stopped, terminating...";
 | 
			
		||||
 | 
			
		||||
	/* reset internal buffer timestamps */
 | 
			
		||||
	for (size_t i = 0; i < rx_buffers.size(); i++)
 | 
			
		||||
		rx_buffers[i]->reset();
 | 
			
		||||
 | 
			
		||||
	started = false;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double IPCDevice2::maxRxGain()
 | 
			
		||||
{
 | 
			
		||||
	return 70;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double IPCDevice2::minRxGain()
 | 
			
		||||
{
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int IPCDevice2::getNominalTxPower(size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double IPCDevice2::setPowerAttenuation(int atten, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return atten;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double IPCDevice2::getPowerAttenuation(size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
double IPCDevice2::setRxGain(double dB, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	if (dB > maxRxGain())
 | 
			
		||||
		dB = maxRxGain();
 | 
			
		||||
	if (dB < minRxGain())
 | 
			
		||||
		dB = minRxGain();
 | 
			
		||||
 | 
			
		||||
	LOGCHAN(chan, DDEV, NOTICE) << "Setting RX gain to " << dB << " dB";
 | 
			
		||||
 | 
			
		||||
	return dB;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::flush_recv(size_t num_pkts)
 | 
			
		||||
{
 | 
			
		||||
	ts_initial = 10000;
 | 
			
		||||
 | 
			
		||||
	LOGC(DDEV, INFO) << "Initial timestamp " << ts_initial << std::endl;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::setRxAntenna(const std::string &ant, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string IPCDevice2::getRxAntenna(size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::setTxAntenna(const std::string &ant, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string IPCDevice2::getTxAntenna(size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::requiresRadioAlign()
 | 
			
		||||
{
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GSM::Time IPCDevice2::minLatency()
 | 
			
		||||
{
 | 
			
		||||
	/* UNUSED */
 | 
			
		||||
	return GSM::Time(0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Returns the starting write Timestamp*/
 | 
			
		||||
TIMESTAMP IPCDevice2::initialWriteTimestamp(void)
 | 
			
		||||
{
 | 
			
		||||
	return ts_initial;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Returns the starting read Timestamp*/
 | 
			
		||||
TIMESTAMP IPCDevice2::initialReadTimestamp(void)
 | 
			
		||||
{
 | 
			
		||||
	return ts_initial;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static timespec readtime, writetime;
 | 
			
		||||
static void wait_for_sample_time(timespec* last, unsigned int len) {
 | 
			
		||||
	timespec ts, diff;
 | 
			
		||||
	clock_gettime(CLOCK_MONOTONIC, &ts);
 | 
			
		||||
	timespecsub(&ts, last, &diff);
 | 
			
		||||
	auto elapsed_us = (diff.tv_sec * 1000000) + (diff.tv_nsec / 1000);
 | 
			
		||||
	auto max_wait_us = ONE_SAMPLE_DURATION_US * len;
 | 
			
		||||
	if(elapsed_us < max_wait_us)
 | 
			
		||||
		usleep(max_wait_us-elapsed_us);
 | 
			
		||||
	*last = ts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NOTE: Assumes sequential reads
 | 
			
		||||
int IPCDevice2::readSamples(std::vector<short *> &bufs, int len, bool *overrun, TIMESTAMP timestamp, bool *underrun)
 | 
			
		||||
{
 | 
			
		||||
	int rc, num_smpls; //, expect_smpls;
 | 
			
		||||
	ssize_t avail_smpls;
 | 
			
		||||
	unsigned int i = 0;
 | 
			
		||||
 | 
			
		||||
	*overrun = false;
 | 
			
		||||
	*underrun = false;
 | 
			
		||||
 | 
			
		||||
	timestamp += 0;
 | 
			
		||||
 | 
			
		||||
	/* Check that timestamp is valid */
 | 
			
		||||
	rc = rx_buffers[0]->avail_smpls(timestamp);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGC(DDEV, ERROR) << rx_buffers[0]->str_code(rc);
 | 
			
		||||
		LOGC(DDEV, ERROR) << rx_buffers[0]->str_status(timestamp);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Receive samples from HW until we have enough */
 | 
			
		||||
	while ((avail_smpls = rx_buffers[i]->avail_smpls(timestamp)) < len) {
 | 
			
		||||
		uint64_t recv_timestamp = timestamp;
 | 
			
		||||
 | 
			
		||||
		m.read_ul(len - avail_smpls, &recv_timestamp, reinterpret_cast<sample_t *>(bufs[0]));
 | 
			
		||||
		num_smpls = len - avail_smpls;
 | 
			
		||||
		wait_for_sample_time(&readtime, num_smpls);
 | 
			
		||||
 | 
			
		||||
		if (num_smpls == -ETIMEDOUT)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		LOGCHAN(i, DDEV, DEBUG)
 | 
			
		||||
		"Received timestamp = " << (TIMESTAMP)recv_timestamp << " (" << num_smpls << ")";
 | 
			
		||||
 | 
			
		||||
		rc = rx_buffers[i]->write(bufs[i], num_smpls, (TIMESTAMP)recv_timestamp);
 | 
			
		||||
		if (rc < 0) {
 | 
			
		||||
			LOGCHAN(i, DDEV, ERROR)
 | 
			
		||||
				<< rx_buffers[i]->str_code(rc) << " num smpls: " << num_smpls << " chan: " << i;
 | 
			
		||||
			LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_status(timestamp);
 | 
			
		||||
			if (rc != smpl_buf::ERROR_OVERFLOW)
 | 
			
		||||
				return 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* We have enough samples */
 | 
			
		||||
 | 
			
		||||
	rc = rx_buffers[i]->read(bufs[i], len, timestamp);
 | 
			
		||||
	if ((rc < 0) || (rc != len)) {
 | 
			
		||||
		LOGCHAN(i, DDEV, ERROR) << rx_buffers[i]->str_code(rc) << ". " << rx_buffers[i]->str_status(timestamp)
 | 
			
		||||
					<< ", (len=" << len << ")";
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int IPCDevice2::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, unsigned long long timestamp)
 | 
			
		||||
{
 | 
			
		||||
	*underrun = false;
 | 
			
		||||
 | 
			
		||||
	LOGCHAN(0, DDEV, DEBUG) << "send buffer of len " << len << " timestamp " << std::hex << timestamp;
 | 
			
		||||
 | 
			
		||||
	// rc = ipc_shm_enqueue(shm_io_tx_streams[i], timestamp, len, (uint16_t *)bufs[i]);
 | 
			
		||||
	m.write_dl(len, timestamp, reinterpret_cast<sample_t *>(bufs[0]));
 | 
			
		||||
	wait_for_sample_time(&writetime, len);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::updateAlignment(TIMESTAMP timestamp)
 | 
			
		||||
{
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::setTxFreq(double wFreq, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IPCDevice2::setRxFreq(double wFreq, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chans, double lo_offset,
 | 
			
		||||
			       const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths)
 | 
			
		||||
{
 | 
			
		||||
	if (tx_sps != rx_sps) {
 | 
			
		||||
		LOGC(DDEV, ERROR) << "IPC Requires tx_sps == rx_sps";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	if (lo_offset != 0.0) {
 | 
			
		||||
		LOGC(DDEV, ERROR) << "IPC doesn't support lo_offset";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
	return new IPCDevice2(tx_sps, rx_sps, iface, chans, lo_offset, tx_paths, rx_paths);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,186 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
 | 
			
		||||
*
 | 
			
		||||
* SPDX-License-Identifier: AGPL-3.0+
 | 
			
		||||
*
 | 
			
		||||
* 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/>.
 | 
			
		||||
* See the COPYING file in the main directory for details.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#ifndef _IPC_DEVICE_H_
 | 
			
		||||
#define _IPC_DEVICE_H_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <climits>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_CONFIG_H
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "radioDevice.h"
 | 
			
		||||
#include "ipcif.h"
 | 
			
		||||
 | 
			
		||||
class smpl_buf;
 | 
			
		||||
 | 
			
		||||
class IPCDevice2 : public RadioDevice {
 | 
			
		||||
	trxmsif m;
 | 
			
		||||
    protected:
 | 
			
		||||
	std::vector<smpl_buf *> rx_buffers;
 | 
			
		||||
	double actualSampleRate;
 | 
			
		||||
 | 
			
		||||
	bool started;
 | 
			
		||||
 | 
			
		||||
	TIMESTAMP ts_initial;
 | 
			
		||||
 | 
			
		||||
	std::vector<double> tx_gains, rx_gains;
 | 
			
		||||
 | 
			
		||||
	bool flush_recv(size_t num_pkts);
 | 
			
		||||
	void update_stream_stats_rx(size_t chan, bool *overrun);
 | 
			
		||||
	void update_stream_stats_tx(size_t chan, bool *underrun);
 | 
			
		||||
 | 
			
		||||
	bool send_chan_wait_rsp(uint32_t chan, struct msgb *msg_to_send, uint32_t expected_rsp_msg_id);
 | 
			
		||||
	bool send_all_chan_wait_rsp(uint32_t msgid_to_send, uint32_t msgid_to_expect);
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	/** Object constructor */
 | 
			
		||||
	IPCDevice2(size_t tx_sps, size_t rx_sps, InterfaceType iface, size_t chan_num, double lo_offset,
 | 
			
		||||
		   const std::vector<std::string> &tx_paths, const std::vector<std::string> &rx_paths);
 | 
			
		||||
	virtual ~IPCDevice2() override;
 | 
			
		||||
 | 
			
		||||
	/** Instantiate the IPC */
 | 
			
		||||
	virtual int open(const std::string &args, int ref, bool swap_channels) override;
 | 
			
		||||
 | 
			
		||||
	/** Start the IPC */
 | 
			
		||||
	virtual bool start() override;
 | 
			
		||||
 | 
			
		||||
	/** Stop the IPC */
 | 
			
		||||
	virtual bool stop() override;
 | 
			
		||||
 | 
			
		||||
	/* FIXME: any != USRP1 will do for now... */
 | 
			
		||||
	enum TxWindowType getWindowType() override
 | 
			
		||||
	{
 | 
			
		||||
		return TX_WINDOW_FIXED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	Read samples from the IPC.
 | 
			
		||||
	@param buf preallocated buf to contain read result
 | 
			
		||||
	@param len number of samples desired
 | 
			
		||||
	@param overrun Set if read buffer has been overrun, e.g. data not being read fast enough
 | 
			
		||||
	@param timestamp The timestamp of the first samples to be read
 | 
			
		||||
	@param underrun Set if IPC does not have data to transmit, e.g. data not being sent fast enough
 | 
			
		||||
	@return The number of samples actually read
 | 
			
		||||
	*/
 | 
			
		||||
	virtual int readSamples(std::vector<short *> &buf, int len, bool *overrun, TIMESTAMP timestamp = 0xffffffff,
 | 
			
		||||
				bool *underrun = NULL) override;
 | 
			
		||||
	/**
 | 
			
		||||
	Write samples to the IPC.
 | 
			
		||||
	@param buf Contains the data to be written.
 | 
			
		||||
	@param len number of samples to write.
 | 
			
		||||
	@param underrun Set if IPC does not have data to transmit, e.g. data not being sent fast enough
 | 
			
		||||
	@param timestamp The timestamp of the first sample of the data buffer.
 | 
			
		||||
	@return The number of samples actually written
 | 
			
		||||
	*/
 | 
			
		||||
	virtual int writeSamples(std::vector<short *> &bufs, int len, bool *underrun,
 | 
			
		||||
				 TIMESTAMP timestamp = 0xffffffff) override;
 | 
			
		||||
 | 
			
		||||
	/** Update the alignment between the read and write timestamps */
 | 
			
		||||
	virtual bool updateAlignment(TIMESTAMP timestamp) override;
 | 
			
		||||
 | 
			
		||||
	/** Set the transmitter frequency */
 | 
			
		||||
	virtual bool setTxFreq(double wFreq, size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/** Set the receiver frequency */
 | 
			
		||||
	virtual bool setRxFreq(double wFreq, size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/** Returns the starting write Timestamp*/
 | 
			
		||||
	virtual TIMESTAMP initialWriteTimestamp(void) override;
 | 
			
		||||
 | 
			
		||||
	/** Returns the starting read Timestamp*/
 | 
			
		||||
	virtual TIMESTAMP initialReadTimestamp(void) override;
 | 
			
		||||
 | 
			
		||||
	/** returns the full-scale transmit amplitude **/
 | 
			
		||||
	virtual double fullScaleInputValue() override
 | 
			
		||||
	{
 | 
			
		||||
		return (double)SHRT_MAX * 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** returns the full-scale receive amplitude **/
 | 
			
		||||
	virtual double fullScaleOutputValue() override
 | 
			
		||||
	{
 | 
			
		||||
		return (double)SHRT_MAX * 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** sets the receive chan gain, returns the gain setting **/
 | 
			
		||||
	virtual double setRxGain(double dB, size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/** get the current receive gain */
 | 
			
		||||
	virtual double getRxGain(size_t chan = 0) override
 | 
			
		||||
	{
 | 
			
		||||
		return rx_gains[chan];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** return maximum Rx Gain **/
 | 
			
		||||
	virtual double maxRxGain(void) override;
 | 
			
		||||
 | 
			
		||||
	/** return minimum Rx Gain **/
 | 
			
		||||
	virtual double minRxGain(void) override;
 | 
			
		||||
 | 
			
		||||
	/* FIXME: return rx_gains[chan] ? receive factor from IPC Driver? */
 | 
			
		||||
	double rssiOffset(size_t chan) override
 | 
			
		||||
	{
 | 
			
		||||
		return 0.0f;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	double setPowerAttenuation(int atten, size_t chan) override;
 | 
			
		||||
	double getPowerAttenuation(size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	virtual int getNominalTxPower(size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/** sets the RX path to use, returns true if successful and false otherwise */
 | 
			
		||||
	virtual bool setRxAntenna(const std::string &ant, size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/* return the used RX path */
 | 
			
		||||
	virtual std::string getRxAntenna(size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/** sets the RX path to use, returns true if successful and false otherwise */
 | 
			
		||||
	virtual bool setTxAntenna(const std::string &ant, size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/* return the used RX path */
 | 
			
		||||
	virtual std::string getTxAntenna(size_t chan = 0) override;
 | 
			
		||||
 | 
			
		||||
	/** return whether user drives synchronization of Tx/Rx of USRP */
 | 
			
		||||
	virtual bool requiresRadioAlign() override;
 | 
			
		||||
 | 
			
		||||
	/** return whether user drives synchronization of Tx/Rx of USRP */
 | 
			
		||||
	virtual GSM::Time minLatency() override;
 | 
			
		||||
 | 
			
		||||
	/** Return internal status values */
 | 
			
		||||
	virtual inline double getTxFreq(size_t chan = 0) override
 | 
			
		||||
	{
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	virtual inline double getRxFreq(size_t chan = 0) override
 | 
			
		||||
	{
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	virtual inline double getSampleRate() override
 | 
			
		||||
	{
 | 
			
		||||
		return actualSampleRate;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // _IPC_DEVICE_H_
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
include $(top_srcdir)/Makefile.common
 | 
			
		||||
 | 
			
		||||
AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/../common
 | 
			
		||||
AM_CFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
 | 
			
		||||
AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS)
 | 
			
		||||
AM_LDFLAGS = -lpthread -lrt
 | 
			
		||||
 | 
			
		||||
noinst_HEADERS = IPCDevice.h
 | 
			
		||||
 | 
			
		||||
noinst_LTLIBRARIES = libdevice.la
 | 
			
		||||
 | 
			
		||||
libdevice_la_SOURCES = IPCDevice.cpp
 | 
			
		||||
libdevice_la_LIBADD = $(top_builddir)/Transceiver52M/device/common/libdevice_common.la
 | 
			
		||||
libdevice_la_CXXFLAGS = $(AM_CXXFLAGS) -DIPCMAGIC
 | 
			
		||||
@@ -1,161 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <complex>
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include "shmif.h"
 | 
			
		||||
 | 
			
		||||
const int max_ul_rdlen = 1024 * 10;
 | 
			
		||||
const int max_dl_rdlen = 1024 * 10;
 | 
			
		||||
using sample_t = std::complex<int16_t>;
 | 
			
		||||
struct shm_if {
 | 
			
		||||
	std::atomic<bool> ms_connected;
 | 
			
		||||
	struct {
 | 
			
		||||
		shm::shmmutex m;
 | 
			
		||||
		shm::shmcond c;
 | 
			
		||||
		std::atomic<uint64_t> ts;
 | 
			
		||||
		std::atomic<size_t> len_req; // <-
 | 
			
		||||
		std::atomic<size_t> len_written; // ->
 | 
			
		||||
		sample_t buffer[max_ul_rdlen];
 | 
			
		||||
	} ul;
 | 
			
		||||
	struct {
 | 
			
		||||
		shm::shmmutex writemutex;
 | 
			
		||||
		shm::shmcond rdy2write;
 | 
			
		||||
		shm::shmmutex readmutex;
 | 
			
		||||
		shm::shmcond rdy2read;
 | 
			
		||||
		std::atomic<uint64_t> ts;
 | 
			
		||||
		std::atomic<size_t> len_req;
 | 
			
		||||
		std::atomic<size_t> len_written;
 | 
			
		||||
		sample_t buffer[max_dl_rdlen];
 | 
			
		||||
	} dl;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// unique up to signed_type/2 diff
 | 
			
		||||
template <typename A> auto unsigned_diff(A a, A b) -> typename std::make_signed<A>::type
 | 
			
		||||
{
 | 
			
		||||
	using stype = typename std::make_signed<A>::type;
 | 
			
		||||
	return (a > b) ? static_cast<stype>(a - b) : -static_cast<stype>(b - a);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class trxmsif {
 | 
			
		||||
	shm::shm<shm_if> m;
 | 
			
		||||
	shm_if *ptr;
 | 
			
		||||
	int dl_readoffset;
 | 
			
		||||
 | 
			
		||||
	int samp2byte(int v)
 | 
			
		||||
	{
 | 
			
		||||
		return v * sizeof(sample_t);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	trxmsif() : m("trx-ms-if"), dl_readoffset(0)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool create()
 | 
			
		||||
	{
 | 
			
		||||
		m.create();
 | 
			
		||||
		ptr = m.p();
 | 
			
		||||
		return m.isgood();
 | 
			
		||||
	}
 | 
			
		||||
	bool connect()
 | 
			
		||||
	{
 | 
			
		||||
		m.open();
 | 
			
		||||
		ptr = m.p();
 | 
			
		||||
		ptr->ms_connected = true;
 | 
			
		||||
		return m.isgood();
 | 
			
		||||
	}
 | 
			
		||||
	bool good()
 | 
			
		||||
	{
 | 
			
		||||
		return m.isgood();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void write_dl(size_t howmany, uint64_t write_ts, sample_t *inbuf)
 | 
			
		||||
	{
 | 
			
		||||
		auto &dl = ptr->dl;
 | 
			
		||||
		auto buf = &dl.buffer[0];
 | 
			
		||||
		// if (ptr->ms_connected != true)
 | 
			
		||||
		// 	return;
 | 
			
		||||
 | 
			
		||||
		assert(sizeof(dl.buffer) >= samp2byte(howmany));
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			shm::signal_guard g(dl.writemutex, dl.rdy2write, dl.rdy2read);
 | 
			
		||||
 | 
			
		||||
			memcpy(buf, inbuf, samp2byte(howmany));
 | 
			
		||||
			dl.ts = write_ts;
 | 
			
		||||
			dl.len_written = howmany;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void read_dl(size_t howmany, uint64_t* read_ts, sample_t *outbuf)
 | 
			
		||||
	{
 | 
			
		||||
		auto &dl = ptr->dl;
 | 
			
		||||
		auto buf = &dl.buffer[0];
 | 
			
		||||
		size_t len_avail = dl.len_written;
 | 
			
		||||
		uint64_t ts = dl.ts;
 | 
			
		||||
 | 
			
		||||
		auto left_to_read = len_avail - dl_readoffset;
 | 
			
		||||
 | 
			
		||||
		// no data, wait for new buffer, maybe some data left afterwards
 | 
			
		||||
		if (!left_to_read) {
 | 
			
		||||
			shm::signal_guard g(dl.readmutex, dl.rdy2read, dl.rdy2write);
 | 
			
		||||
			*read_ts = dl.ts;
 | 
			
		||||
			len_avail = dl.len_written;
 | 
			
		||||
			dl_readoffset += howmany;
 | 
			
		||||
			assert(len_avail >= howmany);
 | 
			
		||||
			memcpy(outbuf, buf, samp2byte(howmany));
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		*read_ts = dl.ts + dl_readoffset;
 | 
			
		||||
		left_to_read = len_avail - dl_readoffset;
 | 
			
		||||
 | 
			
		||||
		// data left from prev read
 | 
			
		||||
		if (left_to_read >= howmany) {
 | 
			
		||||
			memcpy(outbuf, buf, samp2byte(howmany));
 | 
			
		||||
			dl_readoffset += howmany;
 | 
			
		||||
			return;
 | 
			
		||||
		} else {
 | 
			
		||||
			memcpy(outbuf, buf, samp2byte(left_to_read));
 | 
			
		||||
			dl_readoffset = 0;
 | 
			
		||||
			auto still_left_to_read = howmany - left_to_read;
 | 
			
		||||
			{
 | 
			
		||||
				shm::signal_guard g(dl.readmutex, dl.rdy2read, dl.rdy2write);
 | 
			
		||||
				len_avail = dl.len_written;
 | 
			
		||||
				dl_readoffset += still_left_to_read;
 | 
			
		||||
				assert(len_avail >= still_left_to_read);
 | 
			
		||||
				memcpy(outbuf + left_to_read, buf, samp2byte(still_left_to_read));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void read_ul(size_t howmany, uint64_t* read_ts, sample_t *outbuf)
 | 
			
		||||
	{
 | 
			
		||||
		// if (ptr->ms_connected != true) {
 | 
			
		||||
		memset(outbuf, 0, samp2byte(howmany));
 | 
			
		||||
		return;
 | 
			
		||||
		// }
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -1,219 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <pthread.h>
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
 | 
			
		||||
namespace shm
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
class shmmutex {
 | 
			
		||||
	pthread_mutex_t mutex;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	shmmutex()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_mutexattr_t attr;
 | 
			
		||||
		pthread_mutexattr_init(&attr);
 | 
			
		||||
		pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
 | 
			
		||||
		pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
 | 
			
		||||
		pthread_mutex_init(&mutex, &attr);
 | 
			
		||||
		pthread_mutexattr_destroy(&attr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	~shmmutex()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_mutex_destroy(&mutex);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void lock()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_mutex_lock(&mutex);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool try_lock()
 | 
			
		||||
	{
 | 
			
		||||
		return pthread_mutex_trylock(&mutex);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void unlock()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_mutex_unlock(&mutex);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pthread_mutex_t *p()
 | 
			
		||||
	{
 | 
			
		||||
		return &mutex;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class shmcond {
 | 
			
		||||
	pthread_cond_t cond;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	shmcond()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_condattr_t attr;
 | 
			
		||||
		pthread_condattr_init(&attr);
 | 
			
		||||
		pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
 | 
			
		||||
		pthread_cond_init(&cond, &attr);
 | 
			
		||||
		pthread_condattr_destroy(&attr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	~shmcond()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_cond_destroy(&cond);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void wait(shmmutex *lock)
 | 
			
		||||
	{
 | 
			
		||||
		pthread_cond_wait(&cond, lock->p());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void signal()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_cond_signal(&cond);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void signal_all()
 | 
			
		||||
	{
 | 
			
		||||
		pthread_cond_broadcast(&cond);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename IFT> class shm {
 | 
			
		||||
	char shmname[512];
 | 
			
		||||
	size_t IFT_sz = sizeof(IFT);
 | 
			
		||||
	IFT *shmptr;
 | 
			
		||||
	bool good;
 | 
			
		||||
	int ipc_shm_setup(const char *shm_name)
 | 
			
		||||
	{
 | 
			
		||||
		int fd;
 | 
			
		||||
		int rc;
 | 
			
		||||
		void *ptr;
 | 
			
		||||
 | 
			
		||||
		if ((fd = shm_open(shm_name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
 | 
			
		||||
			rc = -errno;
 | 
			
		||||
			return rc;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (ftruncate(fd, IFT_sz) < 0) {
 | 
			
		||||
			rc = -errno;
 | 
			
		||||
			shm_unlink(shm_name);
 | 
			
		||||
			::close(fd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ((ptr = mmap(NULL, IFT_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
 | 
			
		||||
			rc = -errno;
 | 
			
		||||
			shm_unlink(shm_name);
 | 
			
		||||
			::close(fd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		shmptr = new (ptr) IFT(); //static_cast<IFT *>(ptr);
 | 
			
		||||
		::close(fd);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int ipc_shm_connect(const char *shm_name)
 | 
			
		||||
	{
 | 
			
		||||
		int fd;
 | 
			
		||||
		int rc;
 | 
			
		||||
		void *ptr;
 | 
			
		||||
 | 
			
		||||
		if ((fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
 | 
			
		||||
			rc = -errno;
 | 
			
		||||
			return rc;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		struct stat shm_stat;
 | 
			
		||||
		if (fstat(fd, &shm_stat) < 0) {
 | 
			
		||||
			rc = -errno;
 | 
			
		||||
			shm_unlink(shm_name);
 | 
			
		||||
			::close(fd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ((ptr = mmap(NULL, shm_stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
 | 
			
		||||
			rc = -errno;
 | 
			
		||||
			shm_unlink(shm_name);
 | 
			
		||||
			::close(fd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		shmptr = static_cast<IFT *>(ptr);
 | 
			
		||||
		::close(fd);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	using IFT_t = IFT;
 | 
			
		||||
	explicit shm(const char *name) : good(false)
 | 
			
		||||
	{
 | 
			
		||||
		strncpy((char *)shmname, name, 512);
 | 
			
		||||
	}
 | 
			
		||||
	void create()
 | 
			
		||||
	{
 | 
			
		||||
		if (ipc_shm_setup(shmname) == 0)
 | 
			
		||||
			good = true;
 | 
			
		||||
	}
 | 
			
		||||
	void open()
 | 
			
		||||
	{
 | 
			
		||||
		if (ipc_shm_connect(shmname) == 0)
 | 
			
		||||
			good = true;
 | 
			
		||||
	}
 | 
			
		||||
	bool isgood() const
 | 
			
		||||
	{
 | 
			
		||||
		return good;
 | 
			
		||||
	}
 | 
			
		||||
	void close()
 | 
			
		||||
	{
 | 
			
		||||
		if (isgood())
 | 
			
		||||
			shm_unlink(shmname);
 | 
			
		||||
	}
 | 
			
		||||
	IFT *p()
 | 
			
		||||
	{
 | 
			
		||||
		return shmptr;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class signal_guard {
 | 
			
		||||
	shmmutex &m;
 | 
			
		||||
	shmcond &s;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	explicit signal_guard(shmmutex &m, shmcond &wait_for, shmcond &to_signal) : m(m), s(to_signal)
 | 
			
		||||
	{
 | 
			
		||||
		m.lock();
 | 
			
		||||
		wait_for.wait(&m);
 | 
			
		||||
	}
 | 
			
		||||
	~signal_guard()
 | 
			
		||||
	{
 | 
			
		||||
		s.signal();
 | 
			
		||||
		m.unlock();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace shm
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
#include "../uhd/UHDDevice.cpp"
 | 
			
		||||
@@ -1,255 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
* Author: Eric Wild <ewild@sysmocom.de>
 | 
			
		||||
*
 | 
			
		||||
* SPDX-License-Identifier: 0BSD
 | 
			
		||||
*
 | 
			
		||||
* Permission to use, copy, modify, and/or distribute this software for any purpose
 | 
			
		||||
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
 | 
			
		||||
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 | 
			
		||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
 | 
			
		||||
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 | 
			
		||||
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 | 
			
		||||
* USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
 | 
			
		||||
#include "shm.h"
 | 
			
		||||
#include "ipc_shm.h"
 | 
			
		||||
#include "ipc-driver-test.h"
 | 
			
		||||
}
 | 
			
		||||
#include "../uhd/UHDDevice.h"
 | 
			
		||||
#include "uhdwrap.h"
 | 
			
		||||
 | 
			
		||||
#include "Logger.h"
 | 
			
		||||
#include "Threads.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
int uhd_wrap::open(const std::string &args, int ref, bool swap_channels)
 | 
			
		||||
{
 | 
			
		||||
	int rv = uhd_device::open(args, ref, swap_channels);
 | 
			
		||||
	samps_per_buff_rx = rx_stream->get_max_num_samps();
 | 
			
		||||
	samps_per_buff_tx = tx_stream->get_max_num_samps();
 | 
			
		||||
	channel_count = usrp_dev->get_rx_num_channels();
 | 
			
		||||
 | 
			
		||||
	wrap_rx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * samps_per_buff_rx));
 | 
			
		||||
	for (size_t i = 0; i < wrap_rx_buffs.size(); i++)
 | 
			
		||||
		wrap_rx_buf_ptrs.push_back(&wrap_rx_buffs[i].front());
 | 
			
		||||
 | 
			
		||||
	wrap_tx_buffs = std::vector<std::vector<short> >(channel_count, std::vector<short>(2 * 5000));
 | 
			
		||||
	for (size_t i = 0; i < wrap_tx_buffs.size(); i++)
 | 
			
		||||
		wrap_tx_buf_ptrs.push_back(&wrap_tx_buffs[i].front());
 | 
			
		||||
 | 
			
		||||
	return rv;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uhd_wrap::~uhd_wrap()
 | 
			
		||||
{
 | 
			
		||||
	//	drvtest::gshutdown = 1;
 | 
			
		||||
	//t->join();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t uhd_wrap::bufsizerx()
 | 
			
		||||
{
 | 
			
		||||
	return samps_per_buff_rx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t uhd_wrap::bufsizetx()
 | 
			
		||||
{
 | 
			
		||||
	return samps_per_buff_tx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int uhd_wrap::chancount()
 | 
			
		||||
{
 | 
			
		||||
	return channel_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int uhd_wrap::wrap_read(TIMESTAMP *timestamp)
 | 
			
		||||
{
 | 
			
		||||
	uhd::rx_metadata_t md;
 | 
			
		||||
	size_t num_rx_samps = rx_stream->recv(wrap_rx_buf_ptrs, samps_per_buff_rx, md, 0.1, true);
 | 
			
		||||
	*timestamp = md.time_spec.to_ticks(rx_rate);
 | 
			
		||||
	return num_rx_samps; //uhd_device::readSamples(bufs, len, overrun, timestamp, underrun);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" void *uhdwrap_open(struct ipc_sk_if_open_req *open_req)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int rx_sps, tx_sps;
 | 
			
		||||
 | 
			
		||||
	/* FIXME: dev arg string* */
 | 
			
		||||
	/* FIXME: rx frontend bw? */
 | 
			
		||||
	/* FIXME: tx frontend bw? */
 | 
			
		||||
	ReferenceType cref;
 | 
			
		||||
	switch (open_req->clockref) {
 | 
			
		||||
	case FEATURE_MASK_CLOCKREF_EXTERNAL:
 | 
			
		||||
		cref = ReferenceType::REF_EXTERNAL;
 | 
			
		||||
		break;
 | 
			
		||||
	case FEATURE_MASK_CLOCKREF_INTERNAL:
 | 
			
		||||
	default:
 | 
			
		||||
		cref = ReferenceType::REF_INTERNAL;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::vector<std::string> tx_paths;
 | 
			
		||||
	std::vector<std::string> rx_paths;
 | 
			
		||||
	for (unsigned int i = 0; i < open_req->num_chans; i++) {
 | 
			
		||||
		tx_paths.push_back(open_req->chan_info[i].tx_path);
 | 
			
		||||
		rx_paths.push_back(open_req->chan_info[i].rx_path);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* FIXME: this is actually the sps value, not the sample rate!
 | 
			
		||||
	 * sample rate is looked up according to the sps rate by uhd backend */
 | 
			
		||||
	rx_sps = open_req->rx_sample_freq_num / open_req->rx_sample_freq_den;
 | 
			
		||||
	tx_sps = open_req->tx_sample_freq_num / open_req->tx_sample_freq_den;
 | 
			
		||||
	uhd_wrap *uhd_wrap_dev =
 | 
			
		||||
		new uhd_wrap(tx_sps, rx_sps, RadioDevice::NORMAL, open_req->num_chans, 0.0, tx_paths, rx_paths);
 | 
			
		||||
	uhd_wrap_dev->open("", cref, false);
 | 
			
		||||
 | 
			
		||||
	return uhd_wrap_dev;
 | 
			
		||||
}
 | 
			
		||||
extern "C" int32_t uhdwrap_get_bufsizerx(void *dev)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	return d->bufsizerx();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" int32_t uhdwrap_get_timingoffset(void *dev)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	return d->getTimingOffset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" int32_t uhdwrap_read(void *dev, uint32_t num_chans)
 | 
			
		||||
{
 | 
			
		||||
	TIMESTAMP t;
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
 | 
			
		||||
	if (num_chans != d->wrap_rx_buf_ptrs.size()) {
 | 
			
		||||
		perror("omg chans?!");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int32_t read = d->wrap_read(&t);
 | 
			
		||||
 | 
			
		||||
	/* multi channel rx on b210 will return 0 due to alignment adventures, do not put 0 samples into a ipc buffer... */
 | 
			
		||||
	if (read <= 0)
 | 
			
		||||
		return read;
 | 
			
		||||
 | 
			
		||||
	for (uint32_t i = 0; i < num_chans; i++) {
 | 
			
		||||
		ipc_shm_enqueue(ios_rx_from_device[i], t, read, (uint16_t *)&d->wrap_rx_buffs[i].front());
 | 
			
		||||
	}
 | 
			
		||||
	return read;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
 | 
			
		||||
	uint64_t timestamp;
 | 
			
		||||
	int32_t len = -1;
 | 
			
		||||
	for (uint32_t i = 0; i < num_chans; i++) {
 | 
			
		||||
		len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, ×tamp, 1);
 | 
			
		||||
		if (len < 0)
 | 
			
		||||
			return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	if (for_tx)
 | 
			
		||||
		return d->setTxFreq(f, chan);
 | 
			
		||||
	else
 | 
			
		||||
		return d->setRxFreq(f, chan);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	//	if (for_tx)
 | 
			
		||||
	//		return d->setTxGain(f, chan);
 | 
			
		||||
	//	else
 | 
			
		||||
	return d->setRxGain(f, chan);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" double uhdwrap_set_txatt(void *dev, double a, size_t chan)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	return d->setPowerAttenuation(a, chan);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" int32_t uhdwrap_start(void *dev, int chan)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	return d->start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" int32_t uhdwrap_stop(void *dev, int chan)
 | 
			
		||||
{
 | 
			
		||||
	uhd_wrap *d = (uhd_wrap *)dev;
 | 
			
		||||
	return d->stop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim)
 | 
			
		||||
{
 | 
			
		||||
	struct ipc_sk_if_info_chan *chan_info;
 | 
			
		||||
 | 
			
		||||
	uhd::device_addr_t args("");
 | 
			
		||||
	uhd::device_addrs_t devs_found = uhd::device::find(args);
 | 
			
		||||
	if (devs_found.size() < 1) {
 | 
			
		||||
		std::cout << "\n No device found!";
 | 
			
		||||
		exit(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(devs_found[0]);
 | 
			
		||||
	auto rxchans = usrp->get_rx_num_channels();
 | 
			
		||||
	auto txchans = usrp->get_tx_num_channels();
 | 
			
		||||
	auto rx_range = usrp->get_rx_gain_range();
 | 
			
		||||
	auto tx_range = usrp->get_tx_gain_range();
 | 
			
		||||
 | 
			
		||||
	//auto nboards = usrp->get_num_mboards();
 | 
			
		||||
	auto refs = usrp->get_clock_sources(0);
 | 
			
		||||
	auto devname = usrp->get_mboard_name(0);
 | 
			
		||||
 | 
			
		||||
	ipc_prim->u.info_cnf.feature_mask = 0;
 | 
			
		||||
	if (std::find(refs.begin(), refs.end(), "internal") != refs.end())
 | 
			
		||||
		ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_INTERNAL;
 | 
			
		||||
	if (std::find(refs.begin(), refs.end(), "external") != refs.end())
 | 
			
		||||
		ipc_prim->u.info_cnf.feature_mask |= FEATURE_MASK_CLOCKREF_EXTERNAL;
 | 
			
		||||
 | 
			
		||||
	// at least one duplex channel
 | 
			
		||||
	auto num_chans = rxchans == txchans ? txchans : 1;
 | 
			
		||||
 | 
			
		||||
	ipc_prim->u.info_cnf.iq_scaling_val_rx = 0.3;
 | 
			
		||||
	ipc_prim->u.info_cnf.iq_scaling_val_tx = 1;
 | 
			
		||||
	ipc_prim->u.info_cnf.max_num_chans = num_chans;
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, devname.c_str());
 | 
			
		||||
	chan_info = ipc_prim->u.info_cnf.chan_info;
 | 
			
		||||
	for (unsigned int i = 0; i < ipc_prim->u.info_cnf.max_num_chans; i++) {
 | 
			
		||||
		auto rxant = usrp->get_rx_antennas(i);
 | 
			
		||||
		auto txant = usrp->get_tx_antennas(i);
 | 
			
		||||
		for (unsigned int j = 0; j < txant.size(); j++) {
 | 
			
		||||
			OSMO_STRLCPY_ARRAY(chan_info->tx_path[j], txant[j].c_str());
 | 
			
		||||
		}
 | 
			
		||||
		for (unsigned int j = 0; j < rxant.size(); j++) {
 | 
			
		||||
			OSMO_STRLCPY_ARRAY(chan_info->rx_path[j], rxant[j].c_str());
 | 
			
		||||
		}
 | 
			
		||||
		chan_info->min_rx_gain = rx_range.start();
 | 
			
		||||
		chan_info->max_rx_gain = rx_range.stop();
 | 
			
		||||
		chan_info->min_tx_gain = tx_range.start();
 | 
			
		||||
		chan_info->max_tx_gain = tx_range.stop();
 | 
			
		||||
		chan_info->nominal_tx_power = 7.5; // FIXME: would require uhd dev + freq info
 | 
			
		||||
		chan_info++;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,83 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
* Author: Eric Wild <ewild@sysmocom.de>
 | 
			
		||||
*
 | 
			
		||||
* SPDX-License-Identifier: 0BSD
 | 
			
		||||
*
 | 
			
		||||
* Permission to use, copy, modify, and/or distribute this software for any purpose
 | 
			
		||||
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
 | 
			
		||||
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 | 
			
		||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
 | 
			
		||||
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 | 
			
		||||
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 | 
			
		||||
* USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
#ifndef IPC_UHDWRAP_H
 | 
			
		||||
#define IPC_UHDWRAP_H
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
#include "../uhd/UHDDevice.h"
 | 
			
		||||
 | 
			
		||||
class uhd_wrap : public uhd_device {
 | 
			
		||||
    public:
 | 
			
		||||
	//	std::thread *t;
 | 
			
		||||
	size_t samps_per_buff_rx;
 | 
			
		||||
	size_t samps_per_buff_tx;
 | 
			
		||||
	int channel_count;
 | 
			
		||||
 | 
			
		||||
	std::vector<std::vector<short> > wrap_rx_buffs;
 | 
			
		||||
	std::vector<std::vector<short> > wrap_tx_buffs;
 | 
			
		||||
	std::vector<short *> wrap_rx_buf_ptrs;
 | 
			
		||||
	std::vector<short *> wrap_tx_buf_ptrs;
 | 
			
		||||
 | 
			
		||||
	template <typename... Args> uhd_wrap(Args... args) : uhd_device(args...)
 | 
			
		||||
	{
 | 
			
		||||
		//	t = new std::thread(magicthread);
 | 
			
		||||
		// give the thread some time to start and set up
 | 
			
		||||
		//	std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
			
		||||
	}
 | 
			
		||||
	virtual ~uhd_wrap();
 | 
			
		||||
 | 
			
		||||
	//    void ipc_sock_close() override {};
 | 
			
		||||
	int wrap_read(TIMESTAMP *timestamp);
 | 
			
		||||
	virtual int open(const std::string &args, int ref, bool swap_channels) override;
 | 
			
		||||
 | 
			
		||||
	//	bool start() override;
 | 
			
		||||
	//	bool stop() override;
 | 
			
		||||
	//	virtual TIMESTAMP initialWriteTimestamp() override;
 | 
			
		||||
	//	virtual TIMESTAMP initialReadTimestamp() override;
 | 
			
		||||
 | 
			
		||||
	int getTimingOffset()
 | 
			
		||||
	{
 | 
			
		||||
		return ts_offset;
 | 
			
		||||
	}
 | 
			
		||||
	size_t bufsizerx();
 | 
			
		||||
	size_t bufsizetx();
 | 
			
		||||
	int chancount();
 | 
			
		||||
};
 | 
			
		||||
#else
 | 
			
		||||
void *uhdwrap_open(struct ipc_sk_if_open_req *open_req);
 | 
			
		||||
 | 
			
		||||
int32_t uhdwrap_get_bufsizerx(void *dev);
 | 
			
		||||
 | 
			
		||||
int32_t uhdwrap_get_timingoffset(void *dev);
 | 
			
		||||
 | 
			
		||||
int32_t uhdwrap_read(void *dev, uint32_t num_chans);
 | 
			
		||||
 | 
			
		||||
int32_t uhdwrap_write(void *dev, uint32_t num_chans, bool *underrun);
 | 
			
		||||
 | 
			
		||||
double uhdwrap_set_freq(void *dev, double f, size_t chan, bool for_tx);
 | 
			
		||||
 | 
			
		||||
double uhdwrap_set_gain(void *dev, double f, size_t chan, bool for_tx);
 | 
			
		||||
 | 
			
		||||
int32_t uhdwrap_start(void *dev, int chan);
 | 
			
		||||
 | 
			
		||||
int32_t uhdwrap_stop(void *dev, int chan);
 | 
			
		||||
 | 
			
		||||
void uhdwrap_fill_info_cnf(struct ipc_sk_if *ipc_prim);
 | 
			
		||||
 | 
			
		||||
double uhdwrap_set_txatt(void *dev, double a, size_t chan);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif // IPC_B210_H
 | 
			
		||||
@@ -1,4 +1,25 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
/* -*- c++ -*- */
 | 
			
		||||
/*
 | 
			
		||||
 * @file
 | 
			
		||||
 * @author (C) 2009-2017  by Piotr Krysik <ptrkrysik@gmail.com>
 | 
			
		||||
 * @section LICENSE
 | 
			
		||||
 *
 | 
			
		||||
 * Gr-gsm is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 3, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Gr-gsm is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with gr-gsm; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <complex>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
/*
 | 
			
		||||
 * @file
 | 
			
		||||
 * @author (C) 2009-2017  by Piotr Krysik <ptrkrysik@gmail.com>
 | 
			
		||||
 * @author Contributions by sysmocom - s.f.m.c. GmbH / Eric Wild <ewild@sysmocom.de>
 | 
			
		||||
 * @section LICENSE
 | 
			
		||||
 *
 | 
			
		||||
 * Gr-gsm is free software; you can redistribute it and/or modify
 | 
			
		||||
@@ -73,11 +74,8 @@ void initvita() {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MULTI_VER_TARGET_ATTR
 | 
			
		||||
void
 | 
			
		||||
detect_burst(const gr_complex* input,
 | 
			
		||||
	gr_complex* chan_imp_resp, int burst_start,
 | 
			
		||||
	unsigned char* output_binary)
 | 
			
		||||
MULTI_VER_TARGET_ATTR NO_UBSAN
 | 
			
		||||
void detect_burst(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary)
 | 
			
		||||
{
 | 
			
		||||
	std::vector<gr_complex> rhh_temp(CHAN_IMP_RESP_LENGTH * d_OSR);
 | 
			
		||||
	unsigned int stop_states[2] = { 4, 12 };
 | 
			
		||||
@@ -102,44 +100,7 @@ detect_burst(const gr_complex* input,
 | 
			
		||||
		start_state, stop_states, 2, output);
 | 
			
		||||
 | 
			
		||||
	for (int i = 0; i < BURST_SIZE; i++)
 | 
			
		||||
		output_binary[i] = output[i] > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int process_vita_burst(gr_complex* input, int tsc, unsigned char* output_binary) {
 | 
			
		||||
	gr_complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
	int normal_burst_start, dummy_burst_start;
 | 
			
		||||
	float dummy_corr_max, normal_corr_max;
 | 
			
		||||
 | 
			
		||||
	dummy_burst_start = get_norm_chan_imp_resp(input,
 | 
			
		||||
		&channel_imp_resp[0], &dummy_corr_max, TS_DUMMY);
 | 
			
		||||
	normal_burst_start = get_norm_chan_imp_resp(input,
 | 
			
		||||
		&channel_imp_resp[0], &normal_corr_max, tsc);
 | 
			
		||||
 | 
			
		||||
	if (normal_corr_max > dummy_corr_max) {
 | 
			
		||||
		/* Perform MLSE detection */
 | 
			
		||||
		detect_burst(input, &channel_imp_resp[0],
 | 
			
		||||
			normal_burst_start, output_binary);
 | 
			
		||||
		
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
		memcpy(output_binary, dummy_burst, 148);
 | 
			
		||||
		//std::cerr << std::endl << "#NOPE#" << dd.fpath << std::endl << std::endl;
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int process_vita_sc_burst(gr_complex* input, int tsc, unsigned char* output_binary, int* offset) {
 | 
			
		||||
	gr_complex channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
 | 
			
		||||
	/* Get channel impulse response */
 | 
			
		||||
	int d_c0_burst_start = get_sch_chan_imp_resp(input, &channel_imp_resp[0]);
 | 
			
		||||
	//	*offset = d_c0_burst_start;
 | 
			
		||||
	/* Perform MLSE detection */
 | 
			
		||||
	detect_burst(input, &channel_imp_resp[0],
 | 
			
		||||
	d_c0_burst_start, output_binary);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
		output_binary[i] = output[i] * -127; // pre flip bits!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,55 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
/* -*- c++ -*- */
 | 
			
		||||
/*
 | 
			
		||||
 * @file
 | 
			
		||||
 * @author (C) 2009-2017  by Piotr Krysik <ptrkrysik@gmail.com>
 | 
			
		||||
 * @section LICENSE
 | 
			
		||||
 *
 | 
			
		||||
 * Gr-gsm is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 3, or (at your option)
 | 
			
		||||
 * any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * Gr-gsm is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with gr-gsm; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, Inc., 51 Franklin Street,
 | 
			
		||||
 * Boston, MA 02110-1301, USA.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "constants.h"
 | 
			
		||||
 | 
			
		||||
/* may only be used for for the DEFINITIONS!
 | 
			
		||||
* see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91664
 | 
			
		||||
*/
 | 
			
		||||
#if defined(__has_attribute)
 | 
			
		||||
#if __has_attribute(target_clones) && defined(__x86_64) && false
 | 
			
		||||
#if __has_attribute(target_clones) && defined(__x86_64) && true
 | 
			
		||||
#define MULTI_VER_TARGET_ATTR __attribute__((target_clones("avx", "sse4.2", "sse3", "sse2", "sse", "default")))
 | 
			
		||||
#else
 | 
			
		||||
#define MULTI_VER_TARGET_ATTR
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/* ... but apparently clang disagrees... */
 | 
			
		||||
#if defined(__clang__)
 | 
			
		||||
#define MULTI_VER_TARGET_ATTR_CLANGONLY MULTI_VER_TARGET_ATTR
 | 
			
		||||
#else
 | 
			
		||||
#define MULTI_VER_TARGET_ATTR_CLANGONLY
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if defined(__has_attribute)
 | 
			
		||||
#if __has_attribute(no_sanitize)
 | 
			
		||||
#define NO_UBSAN __attribute__((no_sanitize("undefined")))
 | 
			
		||||
#endif
 | 
			
		||||
#else
 | 
			
		||||
#define NO_UBSAN
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define SYNC_SEARCH_RANGE 30
 | 
			
		||||
const int d_OSR(4);
 | 
			
		||||
 | 
			
		||||
@@ -20,8 +58,8 @@ void initvita();
 | 
			
		||||
int process_vita_burst(gr_complex *input, int tsc, unsigned char *output_binary);
 | 
			
		||||
int process_vita_sc_burst(gr_complex *input, int tsc, unsigned char *output_binary, int *offset);
 | 
			
		||||
 | 
			
		||||
MULTI_VER_TARGET_ATTR
 | 
			
		||||
void detect_burst(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, unsigned char *output_binary);
 | 
			
		||||
MULTI_VER_TARGET_ATTR_CLANGONLY
 | 
			
		||||
void detect_burst(const gr_complex *input, gr_complex *chan_imp_resp, int burst_start, char *output_binary);
 | 
			
		||||
void gmsk_mapper(const unsigned char *input, int nitems, gr_complex *gmsk_output, gr_complex start_point);
 | 
			
		||||
gr_complex correlate_sequence(const gr_complex *sequence, int length, const gr_complex *input);
 | 
			
		||||
inline void autocorrelation(const gr_complex *input, gr_complex *out, int nitems);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,124 +0,0 @@
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <queue>
 | 
			
		||||
#include <deque>
 | 
			
		||||
#include <condition_variable>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <sys/eventfd.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
}
 | 
			
		||||
#include "l1if.h"
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
using namespace std::chrono_literals;
 | 
			
		||||
 | 
			
		||||
template<typename Data>
 | 
			
		||||
class spsc_q{
 | 
			
		||||
 | 
			
		||||
    std::queue<Data> m_q;
 | 
			
		||||
    std::mutex m_mtx;
 | 
			
		||||
    std::condition_variable m_cond;
 | 
			
		||||
    bool killme;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    spsc_q() : killme{ false } { }
 | 
			
		||||
 | 
			
		||||
    void push(Data i){
 | 
			
		||||
        std::unique_lock<std::mutex> lock(m_mtx);
 | 
			
		||||
        m_q.push(i);
 | 
			
		||||
        m_cond.notify_one();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Data pop(){
 | 
			
		||||
        std::unique_lock<std::mutex> lock(m_mtx);
 | 
			
		||||
        m_cond.wait_for(lock, 100ms, [&](){ return !m_q.empty() || killme; });
 | 
			
		||||
 | 
			
		||||
        if (killme || m_q.empty()){
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        Data x = m_q.front();
 | 
			
		||||
        m_q.pop();
 | 
			
		||||
 | 
			
		||||
        return x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void stop(){
 | 
			
		||||
        killme = true;
 | 
			
		||||
        m_cond.notify_all();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto sz() { return m_q.size(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * trxif_from_trx_c <-> push_c
 | 
			
		||||
 * trxif_to_trx_c <-> pop_c
 | 
			
		||||
 * trxif_from_trx_d <-> push_d
 | 
			
		||||
 * trxif_to_trx_d <-> pop_d
 | 
			
		||||
 * ...
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class trxl1if {
 | 
			
		||||
public:
 | 
			
		||||
    spsc_q<TRX_C*> c_to_trx;
 | 
			
		||||
    spsc_q<TRX_C*> c_from_trx;
 | 
			
		||||
 | 
			
		||||
    spsc_q<trxd_to_trx*> d_to_trx;
 | 
			
		||||
    spsc_q<trxd_from_trx*> d_from_trx;
 | 
			
		||||
 | 
			
		||||
    struct osmo_fd g_event_ofd_C;
 | 
			
		||||
    struct osmo_fd g_event_ofd_D;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
trxl1if trxif;
 | 
			
		||||
 | 
			
		||||
void push_c(TRX_C* i) {
 | 
			
		||||
	uint64_t one = 1;
 | 
			
		||||
	int rc;
 | 
			
		||||
	trxif.c_from_trx.push(i);
 | 
			
		||||
	// std::clog << trxif.c_from_trx.sz() << std::endl;
 | 
			
		||||
	rc = ::write(trxif.g_event_ofd_C.fd, &one, sizeof(one));
 | 
			
		||||
	return;
 | 
			
		||||
};
 | 
			
		||||
TRX_C* pop_c() {
 | 
			
		||||
	return trxif.c_to_trx.pop();
 | 
			
		||||
};
 | 
			
		||||
void push_d(trxd_from_trx* i) {
 | 
			
		||||
	uint64_t one = 1;
 | 
			
		||||
	int rc;
 | 
			
		||||
	trxif.d_from_trx.push(i);
 | 
			
		||||
	rc = ::write(trxif.g_event_ofd_D.fd, &one, sizeof(one));
 | 
			
		||||
	return;
 | 
			
		||||
};
 | 
			
		||||
trxd_to_trx* pop_d() {
 | 
			
		||||
	return trxif.d_to_trx.pop();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
char* trxif_from_trx_c() {
 | 
			
		||||
    uint64_t one = 1;
 | 
			
		||||
    ::read(trxif.g_event_ofd_C.fd, &one, sizeof(one));
 | 
			
		||||
    return (char*)trxif.c_from_trx.pop();
 | 
			
		||||
}
 | 
			
		||||
void trxif_to_trx_c(char* msg) {
 | 
			
		||||
    trxif.c_to_trx.push((TRX_C*)msg);
 | 
			
		||||
}
 | 
			
		||||
trxd_from_trx* trxif_from_trx_d() {
 | 
			
		||||
    uint64_t one = 1;
 | 
			
		||||
    ::read(trxif.g_event_ofd_D.fd, &one, sizeof(one));
 | 
			
		||||
    return trxif.d_from_trx.pop();
 | 
			
		||||
}
 | 
			
		||||
void trxif_to_trx_d(trxd_to_trx* msg) {
 | 
			
		||||
    trxif.d_to_trx.push(msg);
 | 
			
		||||
}
 | 
			
		||||
struct osmo_fd* get_c_fd() { return &trxif.g_event_ofd_C;}
 | 
			
		||||
struct osmo_fd* get_d_fd() { return &trxif.g_event_ofd_D;}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Data interface handlers                                                  */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* DATA interface                                                           */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Messages on the data interface carry one radio burst per UDP message.    */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Received Data Burst:                                                     */
 | 
			
		||||
/* 1 byte timeslot index                                                    */
 | 
			
		||||
/* 4 bytes GSM frame number, BE                                             */
 | 
			
		||||
/* 1 byte RSSI in -dBm                                                      */
 | 
			
		||||
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE     */
 | 
			
		||||
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1"  */
 | 
			
		||||
/* 2 bytes are not used, but being sent by OsmoTRX                          */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Transmit Data Burst:                                                     */
 | 
			
		||||
/* 1 byte timeslot index                                                    */
 | 
			
		||||
/* 4 bytes GSM frame number, BE                                             */
 | 
			
		||||
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation)                   */
 | 
			
		||||
/* 148 bytes output symbol values, 0 & 1                                    */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
struct __attribute__((packed)) trxd_to_trx {
 | 
			
		||||
	uint8_t ts;
 | 
			
		||||
	uint32_t fn;
 | 
			
		||||
	uint8_t txlev;
 | 
			
		||||
	uint8_t symbols[148];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct __attribute__((packed)) trxd_from_trx {
 | 
			
		||||
	uint8_t ts;
 | 
			
		||||
	uint32_t fn;
 | 
			
		||||
	uint8_t rssi;
 | 
			
		||||
	uint16_t toa;
 | 
			
		||||
	uint8_t symbols[148];
 | 
			
		||||
	uint8_t pad[2];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define TRXC_BUF_SIZE	1024
 | 
			
		||||
 | 
			
		||||
struct TRX_C {
 | 
			
		||||
    char cmd[TRXC_BUF_SIZE];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
void push_c(TRX_C* i);
 | 
			
		||||
TRX_C* pop_c();
 | 
			
		||||
 | 
			
		||||
void push_d(trxd_from_trx* i);
 | 
			
		||||
trxd_to_trx* pop_d();
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
char* trxif_from_trx_c();
 | 
			
		||||
void trxif_to_trx_c(char* msg);
 | 
			
		||||
 | 
			
		||||
struct trxd_from_trx* trxif_from_trx_d();
 | 
			
		||||
void trxif_to_trx_d(struct trxd_to_trx* msg);
 | 
			
		||||
 | 
			
		||||
struct osmo_fd* get_c_fd();
 | 
			
		||||
struct osmo_fd* get_d_fd();
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
@@ -39,17 +38,16 @@ const size_t BLADE_NUM_BUFFERS = 32 * 1;
 | 
			
		||||
const size_t NUM_TRANSFERS = 16 * 2;
 | 
			
		||||
const int SAMPLE_SCALE_FACTOR = 15; // actually 16 but sigproc complains about clipping..
 | 
			
		||||
 | 
			
		||||
template <typename Arg, typename... Args> void doPrint(std::ostream &out, Arg &&arg, Args &&...args)
 | 
			
		||||
// see https://en.cppreference.com/w/cpp/language/parameter_pack  "Brace-enclosed initializers" example
 | 
			
		||||
template <typename Arg, typename... Args> void expand_args(std::ostream &out, Arg &&arg, Args &&...args)
 | 
			
		||||
{
 | 
			
		||||
	out << '(' << std::forward<Arg>(arg);
 | 
			
		||||
	using expander = int[];
 | 
			
		||||
	(void)expander{ 0, (void(out << ',' << std::forward<Args>(args)), 0)... };
 | 
			
		||||
	(void)(int[]){ 0, (void((out << "," << std::forward<Args>(args))), 0)... };
 | 
			
		||||
	out << ')' << std::endl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <class R, class... Args> using RvalFunc = R (*)(Args...);
 | 
			
		||||
 | 
			
		||||
// specialisation for funcs which return a value
 | 
			
		||||
template <class R, class... Args>
 | 
			
		||||
R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finame, const char *funcname, int line,
 | 
			
		||||
		 Args... args)
 | 
			
		||||
@@ -58,7 +56,7 @@ R exec_and_check(RvalFunc<R, Args...> func, const char *fname, const char *finam
 | 
			
		||||
	if (rval != 0) {
 | 
			
		||||
		std::cerr << ((rval >= 0) ? "OK:" : bladerf_strerror(rval)) << ':' << finame << ':' << line << ':'
 | 
			
		||||
			  << funcname << ':' << fname;
 | 
			
		||||
		doPrint(std::cerr, args...);
 | 
			
		||||
		expand_args(std::cerr, args...);
 | 
			
		||||
	}
 | 
			
		||||
	return rval;
 | 
			
		||||
}
 | 
			
		||||
@@ -110,7 +108,7 @@ template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer {
 | 
			
		||||
	int readall(blade_sample_type *outaddr)
 | 
			
		||||
	{
 | 
			
		||||
		blade_sample_type *addr = outaddr;
 | 
			
		||||
		for (int i = 0; i < SZ; i++) {
 | 
			
		||||
		for (unsigned int i = 0; i < SZ; i++) {
 | 
			
		||||
			memcpy(addr, &m[i].d[0], actual_samples_per_msg() * sizeof(blade_sample_type));
 | 
			
		||||
			addr += actual_samples_per_msg();
 | 
			
		||||
		}
 | 
			
		||||
@@ -158,7 +156,7 @@ template <unsigned int SZ, blade_speed_buffer_type T> struct blade_otw_buffer {
 | 
			
		||||
	{
 | 
			
		||||
		assert(num <= actual_samples_per_buffer());
 | 
			
		||||
		int len_rem = num;
 | 
			
		||||
		for (int i = 0; i < SZ; i++) {
 | 
			
		||||
		for (unsigned int i = 0; i < SZ; i++) {
 | 
			
		||||
			m[i] = {};
 | 
			
		||||
			m[i].ts = first_ts + i * actual_samples_per_msg();
 | 
			
		||||
			if (len_rem) {
 | 
			
		||||
@@ -193,6 +191,7 @@ template <typename T> struct blade_hw {
 | 
			
		||||
	const int rxtxdelay;
 | 
			
		||||
 | 
			
		||||
	float rxgain, txgain;
 | 
			
		||||
	static std::atomic<bool> stop_me_flag;
 | 
			
		||||
 | 
			
		||||
	struct ms_trx_config {
 | 
			
		||||
		int tx_freq;
 | 
			
		||||
@@ -224,14 +223,14 @@ template <typename T> struct blade_hw {
 | 
			
		||||
	void close_device()
 | 
			
		||||
	{
 | 
			
		||||
		if (dev) {
 | 
			
		||||
			if (rx_stream) {
 | 
			
		||||
				bladerf_deinit_stream(rx_stream);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (tx_stream) {
 | 
			
		||||
				bladerf_deinit_stream(tx_stream);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (rx_stream) {
 | 
			
		||||
				bladerf_deinit_stream(rx_stream);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			bladerf_enable_module(dev, BLADERF_MODULE_RX, false);
 | 
			
		||||
			bladerf_enable_module(dev, BLADERF_MODULE_TX, false);
 | 
			
		||||
 | 
			
		||||
@@ -273,15 +272,6 @@ template <typename T> struct blade_hw {
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// bladerf_sample_rate r = (1625e3 * 4)/6, act;
 | 
			
		||||
		// blade_check(bladerf_set_sample_rate,dev, BLADERF_CHANNEL_RX(0), r, &act);
 | 
			
		||||
		// blade_check(bladerf_set_sample_rate,dev, BLADERF_CHANNEL_TX(0), r, &act);
 | 
			
		||||
 | 
			
		||||
		// auto ratrate = (1625e3 * 4) / 6;
 | 
			
		||||
		// rate.integer = (uint32_t)ratrate;
 | 
			
		||||
		// rate.den = 10000;
 | 
			
		||||
		// rate.num = (ratrate - rate.integer) * rate.den;
 | 
			
		||||
 | 
			
		||||
		blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_RX(0), &rate, &actual);
 | 
			
		||||
		blade_check(bladerf_set_rational_sample_rate, dev, BLADERF_CHANNEL_TX(0), &rate, &actual);
 | 
			
		||||
 | 
			
		||||
@@ -294,8 +284,8 @@ template <typename T> struct blade_hw {
 | 
			
		||||
			    (bladerf_bandwidth *)NULL);
 | 
			
		||||
 | 
			
		||||
		blade_check(bladerf_set_gain_mode, dev, BLADERF_CHANNEL_RX(0), BLADERF_GAIN_MGC);
 | 
			
		||||
		// blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)30);
 | 
			
		||||
		// blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)50);
 | 
			
		||||
		blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_RX(0), (bladerf_gain)30);
 | 
			
		||||
		blade_check(bladerf_set_gain, dev, BLADERF_CHANNEL_TX(0), (bladerf_gain)30);
 | 
			
		||||
		usleep(1000);
 | 
			
		||||
		blade_check(bladerf_enable_module, dev, BLADERF_MODULE_RX, true);
 | 
			
		||||
		usleep(1000);
 | 
			
		||||
@@ -307,7 +297,7 @@ template <typename T> struct blade_hw {
 | 
			
		||||
		blade_check(bladerf_init_stream, &tx_stream, dev, gettxcb(txh), &buf_mgmt.tx_samples, BLADE_NUM_BUFFERS,
 | 
			
		||||
			    BLADERF_FORMAT_SC16_Q11_META, BLADE_BUFFER_SIZE, NUM_TRANSFERS, (void *)this);
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < BLADE_NUM_BUFFERS; i++) {
 | 
			
		||||
		for (unsigned int i = 0; i < BLADE_NUM_BUFFERS; i++) {
 | 
			
		||||
			auto cur_buffer = reinterpret_cast<tx_buf_q_type::elem_t *>(buf_mgmt.tx_samples);
 | 
			
		||||
			buf_mgmt.bufptrqueue.spsc_push(&cur_buffer[i]);
 | 
			
		||||
		}
 | 
			
		||||
@@ -389,6 +379,9 @@ template <typename T> struct blade_hw {
 | 
			
		||||
			static int to_skip = 0;
 | 
			
		||||
			dev_buf_t *rcd = (dev_buf_t *)samples;
 | 
			
		||||
 | 
			
		||||
			if (stop_me_flag)
 | 
			
		||||
				return BLADERF_STREAM_SHUTDOWN;
 | 
			
		||||
 | 
			
		||||
			if (to_skip < 120) // prevents weird overflows on startup
 | 
			
		||||
				to_skip++;
 | 
			
		||||
			else {
 | 
			
		||||
@@ -411,6 +404,9 @@ template <typename T> struct blade_hw {
 | 
			
		||||
			if (samples) // put buffer address back into queue, ready to be reused
 | 
			
		||||
				trx->buf_mgmt.bufptrqueue.spsc_push(&ptr);
 | 
			
		||||
 | 
			
		||||
			if (stop_me_flag)
 | 
			
		||||
				return BLADERF_STREAM_SHUTDOWN;
 | 
			
		||||
 | 
			
		||||
			return BLADERF_STREAM_NO_DATA;
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
@@ -419,12 +415,11 @@ template <typename T> struct blade_hw {
 | 
			
		||||
	{
 | 
			
		||||
		auto fn = [this] {
 | 
			
		||||
			int status;
 | 
			
		||||
			set_name_aff_sched("rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2);
 | 
			
		||||
			status = bladerf_stream(rx_stream, BLADERF_RX_X1);
 | 
			
		||||
			if (status < 0)
 | 
			
		||||
				std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
 | 
			
		||||
 | 
			
		||||
			return NULL;
 | 
			
		||||
			return 0;
 | 
			
		||||
		};
 | 
			
		||||
		return fn;
 | 
			
		||||
	}
 | 
			
		||||
@@ -432,12 +427,11 @@ template <typename T> struct blade_hw {
 | 
			
		||||
	{
 | 
			
		||||
		auto fn = [this] {
 | 
			
		||||
			int status;
 | 
			
		||||
			set_name_aff_sched("txrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
 | 
			
		||||
			status = bladerf_stream(tx_stream, BLADERF_TX_X1);
 | 
			
		||||
			if (status < 0)
 | 
			
		||||
				std::cerr << "rx stream error! " << bladerf_strerror(status) << std::endl;
 | 
			
		||||
 | 
			
		||||
			return NULL;
 | 
			
		||||
			return 0;
 | 
			
		||||
		};
 | 
			
		||||
		return fn;
 | 
			
		||||
	}
 | 
			
		||||
@@ -452,31 +446,6 @@ template <typename T> struct blade_hw {
 | 
			
		||||
		assert(rcd != nullptr);
 | 
			
		||||
 | 
			
		||||
		rcd->write_n_burst(buffer, len, ts + rxtxdelay); // blade xa4 specific delay!
 | 
			
		||||
		// blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd, 100U);
 | 
			
		||||
		blade_check(bladerf_submit_stream_buffer_nb, tx_stream, (void *)rcd);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
 | 
			
		||||
	{
 | 
			
		||||
		pthread_setname_np(pthread_self(), name);
 | 
			
		||||
 | 
			
		||||
		cpu_set_t cpuset;
 | 
			
		||||
 | 
			
		||||
		CPU_ZERO(&cpuset);
 | 
			
		||||
		CPU_SET(cpunum, &cpuset);
 | 
			
		||||
 | 
			
		||||
		auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
 | 
			
		||||
		if (rv < 0) {
 | 
			
		||||
			std::cerr << name << " affinity: errreur! " << std::strerror(errno);
 | 
			
		||||
			return exit(0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sched_param sch_params;
 | 
			
		||||
		sch_params.sched_priority = prio;
 | 
			
		||||
		rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
 | 
			
		||||
		if (rv < 0) {
 | 
			
		||||
			std::cerr << name << " sched: errreur! " << std::strerror(errno);
 | 
			
		||||
			return exit(0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,259 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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 <complex>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
#include <Timeval.h>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include <ipcif.h>
 | 
			
		||||
 | 
			
		||||
// typedef unsigned long long TIMESTAMP;
 | 
			
		||||
using blade_sample_type = std::complex<int16_t>;
 | 
			
		||||
const int SAMPLE_SCALE_FACTOR = 1;
 | 
			
		||||
 | 
			
		||||
struct uhd_buf_wrap {
 | 
			
		||||
	uint64_t ts;
 | 
			
		||||
	uint32_t num_samps;
 | 
			
		||||
	blade_sample_type *buf;
 | 
			
		||||
	auto actual_samples_per_buffer()
 | 
			
		||||
	{
 | 
			
		||||
		return num_samps;
 | 
			
		||||
	}
 | 
			
		||||
	long get_first_ts()
 | 
			
		||||
	{
 | 
			
		||||
		return ts; //md->time_spec.to_ticks(rxticks);
 | 
			
		||||
	}
 | 
			
		||||
	int readall(blade_sample_type *outaddr)
 | 
			
		||||
	{
 | 
			
		||||
		memcpy(outaddr, buf, num_samps * sizeof(blade_sample_type));
 | 
			
		||||
		return num_samps;
 | 
			
		||||
	}
 | 
			
		||||
	int read_n(blade_sample_type *outaddr, int start, int num)
 | 
			
		||||
	{
 | 
			
		||||
		// assert(start >= 0);
 | 
			
		||||
		auto to_read = std::min((int)num_samps - start, num);
 | 
			
		||||
		// assert(to_read >= 0);
 | 
			
		||||
		memcpy(outaddr, buf + start, to_read * sizeof(blade_sample_type));
 | 
			
		||||
		return to_read;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using dev_buf_t = uhd_buf_wrap;
 | 
			
		||||
using bh_fn_t = std::function<int(dev_buf_t *)>;
 | 
			
		||||
 | 
			
		||||
template <typename T> struct ipc_hw {
 | 
			
		||||
	// uhd::usrp::multi_usrp::sptr dev;
 | 
			
		||||
	// uhd::rx_streamer::sptr rx_stream;
 | 
			
		||||
	// uhd::tx_streamer::sptr tx_stream;
 | 
			
		||||
	blade_sample_type *one_pkt_buf;
 | 
			
		||||
	std::vector<blade_sample_type *> pkt_ptrs;
 | 
			
		||||
	size_t rx_spp;
 | 
			
		||||
	double rxticks;
 | 
			
		||||
	const unsigned int rxFullScale, txFullScale;
 | 
			
		||||
	const int rxtxdelay;
 | 
			
		||||
	float rxgain, txgain;
 | 
			
		||||
	trxmsif m;
 | 
			
		||||
 | 
			
		||||
	virtual ~ipc_hw()
 | 
			
		||||
	{
 | 
			
		||||
		delete[] one_pkt_buf;
 | 
			
		||||
	}
 | 
			
		||||
	ipc_hw() : rxFullScale(32767), txFullScale(32767), rxtxdelay(-67)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool tuneTx(double freq, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		// dev->set_tx_freq(freq, chan);
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
	bool tuneRx(double freq, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		// dev->set_rx_freq(freq, chan);
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
	bool tuneRxOffset(double offset, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	double setRxGain(double dB, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		rxgain = dB;
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		// dev->set_rx_gain(dB, chan);
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		return dB;
 | 
			
		||||
	};
 | 
			
		||||
	double setTxGain(double dB, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		txgain = dB;
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		// dev->set_tx_gain(dB, chan);
 | 
			
		||||
		msleep(25);
 | 
			
		||||
		return dB;
 | 
			
		||||
	};
 | 
			
		||||
	int setPowerAttenuation(int atten, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		return atten;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	int init_device(bh_fn_t rxh, bh_fn_t txh)
 | 
			
		||||
	{
 | 
			
		||||
		// std::thread([] {
 | 
			
		||||
		// 	osmo_ctx_init("bernd");
 | 
			
		||||
		// 	osmo_select_init();
 | 
			
		||||
		// 	main_ipc();
 | 
			
		||||
		// 	while (true)
 | 
			
		||||
		// 		osmo_select_main(0);
 | 
			
		||||
		// }).detach();
 | 
			
		||||
 | 
			
		||||
		return m.connect() ? 0: -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void *rx_cb(bh_fn_t burst_handler)
 | 
			
		||||
	{
 | 
			
		||||
		void *ret;
 | 
			
		||||
		static int to_skip = 0;
 | 
			
		||||
 | 
			
		||||
		blade_sample_type pbuf[508 * 2];
 | 
			
		||||
 | 
			
		||||
		uint64_t t;
 | 
			
		||||
 | 
			
		||||
		int len = 508 * 2;
 | 
			
		||||
		m.read_dl(508 * 2, &t, pbuf);
 | 
			
		||||
		// auto len = ipc_shm_read(ios_tx_to_device[0], (uint16_t *)&pbuf, 508 * 2, &t, 1);
 | 
			
		||||
		// if(len < 0) {
 | 
			
		||||
		// 	std::cerr << "fuck, rx fail!" << std::endl;
 | 
			
		||||
		// 	exit(0);
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		// uhd::rx_metadata_t md;
 | 
			
		||||
		// auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 3.0, true);
 | 
			
		||||
 | 
			
		||||
		// if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
 | 
			
		||||
		// 	std::cerr << boost::format("Timeout while streaming") << std::endl;
 | 
			
		||||
		// 	exit(0);
 | 
			
		||||
		// }
 | 
			
		||||
		// if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) {
 | 
			
		||||
		// 	std::cerr << boost::format("Got an overflow indication. Please consider the following:\n"
 | 
			
		||||
		// 				   "  Your write medium must sustain a rate of %fMB/s.\n"
 | 
			
		||||
		// 				   "  Dropped samples will not be written to the file.\n"
 | 
			
		||||
		// 				   "  Please modify this example for your purposes.\n"
 | 
			
		||||
		// 				   "  This message will not appear again.\n") %
 | 
			
		||||
		// 			     1.f;
 | 
			
		||||
		// 	exit(0);
 | 
			
		||||
		// 	;
 | 
			
		||||
		// }
 | 
			
		||||
		// if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) {
 | 
			
		||||
		// 	std::cerr << str(boost::format("Receiver error: %s") % md.strerror());
 | 
			
		||||
		// 	exit(0);
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		dev_buf_t rcd = { t, static_cast<uint32_t>(len), pbuf };
 | 
			
		||||
 | 
			
		||||
		if (to_skip < 120) // prevents weird overflows on startup
 | 
			
		||||
			to_skip++;
 | 
			
		||||
		else {
 | 
			
		||||
			burst_handler(&rcd);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto get_rx_burst_handler_fn(bh_fn_t burst_handler)
 | 
			
		||||
	{
 | 
			
		||||
		auto fn = [this, burst_handler] {
 | 
			
		||||
			pthread_setname_np(pthread_self(), "rxrun");
 | 
			
		||||
			// wait_for_shm_open();
 | 
			
		||||
			// uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
 | 
			
		||||
			// stream_cmd.stream_now = true;
 | 
			
		||||
			// stream_cmd.time_spec = uhd::time_spec_t();
 | 
			
		||||
			// rx_stream->issue_stream_cmd(stream_cmd);
 | 
			
		||||
 | 
			
		||||
			while (1) {
 | 
			
		||||
				rx_cb(burst_handler);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
		return fn;
 | 
			
		||||
	}
 | 
			
		||||
	auto get_tx_burst_handler_fn(bh_fn_t burst_handler)
 | 
			
		||||
	{
 | 
			
		||||
		auto fn = [] {
 | 
			
		||||
			// wait_for_shm_open();
 | 
			
		||||
			// dummy
 | 
			
		||||
		};
 | 
			
		||||
		return fn;
 | 
			
		||||
	}
 | 
			
		||||
	void submit_burst_ts(blade_sample_type *buffer, int len, uint64_t ts)
 | 
			
		||||
	{
 | 
			
		||||
		// uhd::tx_metadata_t m = {};
 | 
			
		||||
		// m.end_of_burst = true;
 | 
			
		||||
		// m.start_of_burst = true;
 | 
			
		||||
		// m.has_time_spec = true;
 | 
			
		||||
		// m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay!
 | 
			
		||||
		// std::vector<void *> ptrs(1, buffer);
 | 
			
		||||
 | 
			
		||||
		// tx_stream->send(ptrs, len, m);
 | 
			
		||||
 | 
			
		||||
		// uhd::async_metadata_t async_md;
 | 
			
		||||
		// bool tx_ack = false;
 | 
			
		||||
		// while (!tx_ack && tx_stream->recv_async_msg(async_md)) {
 | 
			
		||||
		// 	tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK);
 | 
			
		||||
		// }
 | 
			
		||||
		// std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
 | 
			
		||||
	{
 | 
			
		||||
		pthread_setname_np(pthread_self(), name);
 | 
			
		||||
 | 
			
		||||
		// cpu_set_t cpuset;
 | 
			
		||||
 | 
			
		||||
		// CPU_ZERO(&cpuset);
 | 
			
		||||
		// CPU_SET(cpunum, &cpuset);
 | 
			
		||||
 | 
			
		||||
		// auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
 | 
			
		||||
		// if (rv < 0) {
 | 
			
		||||
		// 	std::cerr << name << " affinity: errreur! " << std::strerror(errno);
 | 
			
		||||
		// 	return exit(0);
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		// sched_param sch_params;
 | 
			
		||||
		// sch_params.sched_priority = prio;
 | 
			
		||||
		// rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
 | 
			
		||||
		// if (rv < 0) {
 | 
			
		||||
		// 	std::cerr << name << " sched: errreur! " << std::strerror(errno);
 | 
			
		||||
		// 	return exit(0);
 | 
			
		||||
		// }
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -1,473 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* Copyright 2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
 | 
			
		||||
*
 | 
			
		||||
* SPDX-License-Identifier: 0BSD
 | 
			
		||||
*
 | 
			
		||||
* Permission to use, copy, modify, and/or distribute this software for any purpose
 | 
			
		||||
* with or without fee is hereby granted.THE SOFTWARE IS PROVIDED "AS IS" AND THE
 | 
			
		||||
* AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 | 
			
		||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
 | 
			
		||||
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 | 
			
		||||
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 | 
			
		||||
* USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
#include <pthread.h>
 | 
			
		||||
 | 
			
		||||
#include <debug.h>
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
#include <inttypes.h>
 | 
			
		||||
#include <sys/mman.h>
 | 
			
		||||
#include <sys/stat.h> /* For mode constants */
 | 
			
		||||
#include <fcntl.h> /* For O_* constants */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
 | 
			
		||||
#include <shm.h>
 | 
			
		||||
#include <ipc_shm.h>
 | 
			
		||||
#include <ipc_chan.h>
 | 
			
		||||
#include <ipc_sock.h>
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_SHM_NAME "/osmo-trx-ipc-driver-shm2"
 | 
			
		||||
#define IPC_SOCK_PATH_PREFIX "/tmp"
 | 
			
		||||
 | 
			
		||||
static void *tall_ctx;
 | 
			
		||||
struct ipc_sock_state *global_ipc_sock_state;
 | 
			
		||||
 | 
			
		||||
/* 8 channels are plenty */
 | 
			
		||||
struct ipc_sock_state *global_ctrl_socks[8];
 | 
			
		||||
struct ipc_shm_io *ios_tx_to_device[8];
 | 
			
		||||
struct ipc_shm_io *ios_rx_from_device[8];
 | 
			
		||||
 | 
			
		||||
void *shm;
 | 
			
		||||
// void *global_dev;
 | 
			
		||||
 | 
			
		||||
static pthread_mutex_t wait_open_lock;
 | 
			
		||||
static pthread_cond_t wait_open_cond;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct ipc_shm_region *decoded_region;
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
	int msocknum;
 | 
			
		||||
	char *ud_prefix_dir;
 | 
			
		||||
} cmdline_cfg = { 1, IPC_SOCK_PATH_PREFIX };
 | 
			
		||||
 | 
			
		||||
static const struct log_info_cat default_categories[] = {
 | 
			
		||||
	[DMAIN] = {
 | 
			
		||||
		.name = "DMAIN",
 | 
			
		||||
		.color = NULL,
 | 
			
		||||
		.description = "Main generic category",
 | 
			
		||||
		.loglevel = LOGL_DEBUG,.enabled = 1,
 | 
			
		||||
	},
 | 
			
		||||
	[DDEV] = {
 | 
			
		||||
		.name = "DDEV",
 | 
			
		||||
		.description = "Device/Driver specific code",
 | 
			
		||||
		.color = NULL,
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_DEBUG,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const struct log_info log_infox = {
 | 
			
		||||
	.cat = default_categories,
 | 
			
		||||
	.num_cat = ARRAY_SIZE(default_categories),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
volatile int ipc_exit_requested = 0;
 | 
			
		||||
 | 
			
		||||
static int ipc_shm_setup(const char *shm_name, size_t shm_len)
 | 
			
		||||
{
 | 
			
		||||
	int fd;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	LOGP(DMAIN, LOGL_NOTICE, "Opening shm path %s\n", shm_name);
 | 
			
		||||
	if ((fd = shm_open(shm_name, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "shm_open %d: %s\n", errno, strerror(errno));
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
		goto err_shm_open;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DMAIN, LOGL_NOTICE, "Truncating %d to size %zu\n", fd, shm_len);
 | 
			
		||||
	if (ftruncate(fd, shm_len) < 0) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "ftruncate %d: %s\n", errno, strerror(errno));
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
		goto err_mmap;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DMAIN, LOGL_NOTICE, "mmaping shared memory fd %d\n", fd);
 | 
			
		||||
	if ((shm = mmap(NULL, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "mmap %d: %s\n", errno, strerror(errno));
 | 
			
		||||
		rc = -errno;
 | 
			
		||||
		goto err_mmap;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DMAIN, LOGL_NOTICE, "mmap'ed shared memory at addr %p\n", shm);
 | 
			
		||||
	/* After a call to mmap(2) the file descriptor may be closed without affecting the memory mapping. */
 | 
			
		||||
	close(fd);
 | 
			
		||||
	return 0;
 | 
			
		||||
err_mmap:
 | 
			
		||||
	shm_unlink(shm_name);
 | 
			
		||||
	close(fd);
 | 
			
		||||
err_shm_open:
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct msgb *ipc_msgb_alloc(uint8_t msg_type)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_if *ipc_prim;
 | 
			
		||||
 | 
			
		||||
	msg = msgb_alloc(sizeof(struct ipc_sk_if) + 1000, "ipc_sock_tx");
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	msgb_put(msg, sizeof(struct ipc_sk_if) + 1000);
 | 
			
		||||
	ipc_prim = (struct ipc_sk_if *)msg->data;
 | 
			
		||||
	ipc_prim->msg_type = msg_type;
 | 
			
		||||
 | 
			
		||||
	return msg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int ipc_tx_info_cnf()
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_if *ipc_prim;
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_INFO_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_if *)msg->data;
 | 
			
		||||
 | 
			
		||||
	ipc_prim->u.info_cnf.feature_mask = FEATURE_MASK_CLOCKREF_EXTERNAL;
 | 
			
		||||
	ipc_prim->u.info_cnf.iq_scaling_val_rx = 1;
 | 
			
		||||
	ipc_prim->u.info_cnf.iq_scaling_val_tx = 1;
 | 
			
		||||
	ipc_prim->u.info_cnf.max_num_chans = 1;
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(ipc_prim->u.info_cnf.dev_desc, "bernd");
 | 
			
		||||
 | 
			
		||||
	struct ipc_sk_if_info_chan *chan_info = ipc_prim->u.info_cnf.chan_info;
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(chan_info->tx_path[0], "TX/RX");
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(chan_info->rx_path[0], "RX2");
 | 
			
		||||
	chan_info->min_rx_gain = 0;
 | 
			
		||||
	chan_info->max_rx_gain = 60;
 | 
			
		||||
	chan_info->min_tx_gain = 0;
 | 
			
		||||
	chan_info->max_tx_gain = 60;
 | 
			
		||||
	chan_info->nominal_tx_power = 10;
 | 
			
		||||
 | 
			
		||||
	return ipc_sock_send(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int ipc_tx_open_cnf(int rc, uint32_t num_chans, int32_t timingoffset)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_if *ipc_prim;
 | 
			
		||||
	struct ipc_sk_if_open_cnf_chan *chan_info;
 | 
			
		||||
	unsigned int i;
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_OPEN_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.open_cnf.return_code = rc;
 | 
			
		||||
	ipc_prim->u.open_cnf.path_delay = timingoffset; // 6.18462e-5 * 1625e3 / 6;
 | 
			
		||||
	OSMO_STRLCPY_ARRAY(ipc_prim->u.open_cnf.shm_name, DEFAULT_SHM_NAME);
 | 
			
		||||
 | 
			
		||||
	chan_info = ipc_prim->u.open_cnf.chan_info;
 | 
			
		||||
	for (i = 0; i < num_chans; i++) {
 | 
			
		||||
		snprintf(chan_info->chan_ipc_sk_path, sizeof(chan_info->chan_ipc_sk_path), "%s/ipc_sock%d_%d",
 | 
			
		||||
			 cmdline_cfg.ud_prefix_dir, cmdline_cfg.msocknum, i);
 | 
			
		||||
		/* FIXME: dynamc chan limit, currently 8 */
 | 
			
		||||
		if (i < 8)
 | 
			
		||||
			ipc_sock_init(chan_info->chan_ipc_sk_path, &global_ctrl_socks[i], ipc_chan_sock_accept, i);
 | 
			
		||||
		chan_info++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ipc_sock_send(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_rx_greeting_req(struct ipc_sk_if_greeting *greeting_req)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_if *ipc_prim;
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_GREETING_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.greeting_cnf.req_version =
 | 
			
		||||
		greeting_req->req_version == IPC_SOCK_API_VERSION ? IPC_SOCK_API_VERSION : 0;
 | 
			
		||||
 | 
			
		||||
	ipc_sock_send(msg);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_rx_info_req(struct ipc_sk_if_info_req *info_req)
 | 
			
		||||
{
 | 
			
		||||
	ipc_tx_info_cnf();
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_rx_open_req(struct ipc_sk_if_open_req *open_req)
 | 
			
		||||
{
 | 
			
		||||
	/* calculate size needed */
 | 
			
		||||
	unsigned int len;
 | 
			
		||||
	unsigned int i;
 | 
			
		||||
 | 
			
		||||
	// global_dev = uhdwrap_open(open_req);
 | 
			
		||||
 | 
			
		||||
	/* b210 packet size is 2040, but our tx size is 2500, so just do *2 */
 | 
			
		||||
	int shmbuflen = 5000 * 2;
 | 
			
		||||
 | 
			
		||||
	len = ipc_shm_encode_region(NULL, open_req->num_chans, 4, shmbuflen);
 | 
			
		||||
	/* Here we verify num_chans, rx_path, tx_path, clockref, etc. */
 | 
			
		||||
	int rc = ipc_shm_setup(DEFAULT_SHM_NAME, len);
 | 
			
		||||
	len = ipc_shm_encode_region((struct ipc_shm_raw_region *)shm, open_req->num_chans, 4, shmbuflen);
 | 
			
		||||
	//	LOGP(DMAIN, LOGL_NOTICE, "%s\n", osmo_hexdump((const unsigned char *)shm, 80));
 | 
			
		||||
 | 
			
		||||
	/* set up our own copy of the decoded area, we have to do it here,
 | 
			
		||||
	* since the uhd wrapper does not allow starting single channels
 | 
			
		||||
	* additionally go for the producer init for both, so only we are responsible for the init, instead
 | 
			
		||||
	* of splitting it with the client and causing potential races if one side uses it too early */
 | 
			
		||||
	decoded_region = ipc_shm_decode_region(0, (struct ipc_shm_raw_region *)shm);
 | 
			
		||||
	for (i = 0; i < open_req->num_chans; i++) {
 | 
			
		||||
		//		ios_tx_to_device[i] = ipc_shm_init_consumer(decoded_region->channels[i]->dl_stream);
 | 
			
		||||
		ios_tx_to_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->dl_stream);
 | 
			
		||||
		ios_rx_from_device[i] = ipc_shm_init_producer(decoded_region->channels[i]->ul_stream);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ipc_tx_open_cnf(-rc, open_req->num_chans, 0);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
volatile bool ul_running = false;
 | 
			
		||||
volatile bool dl_running = false;
 | 
			
		||||
 | 
			
		||||
void *uplink_thread(void *x_void_ptr)
 | 
			
		||||
{
 | 
			
		||||
	uint32_t chann = decoded_region->num_chans;
 | 
			
		||||
	ul_running = true;
 | 
			
		||||
	pthread_setname_np(pthread_self(), "uplink_rx");
 | 
			
		||||
 | 
			
		||||
	while (!ipc_exit_requested) {
 | 
			
		||||
		// int32_t read = uhdwrap_read(global_dev, chann);
 | 
			
		||||
		// ipc_shm_enqueue(ios_rx_from_device[i], tstamp, num_rx_samps, (uint16_t *)&d->wrap_rx_buffs[i].front());
 | 
			
		||||
		if (read < 0)
 | 
			
		||||
			return 0;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void *downlink_thread(void *x_void_ptr)
 | 
			
		||||
{
 | 
			
		||||
	int chann = decoded_region->num_chans;
 | 
			
		||||
	dl_running = true;
 | 
			
		||||
	pthread_setname_np(pthread_self(), "downlink_tx");
 | 
			
		||||
 | 
			
		||||
	while (!ipc_exit_requested) {
 | 
			
		||||
		bool underrun;
 | 
			
		||||
		// uhdwrap_write(global_dev, chann, &underrun);
 | 
			
		||||
 | 
			
		||||
		// len = ipc_shm_read(ios_tx_to_device[i], (uint16_t *)&d->wrap_tx_buffs[i].front(), 5000, ×tamp, 1);
 | 
			
		||||
		// return d->writeSamples(d->wrap_tx_buf_ptrs, len, underrun, timestamp);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_rx_chan_start_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_chan_if *ipc_prim;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	// rc = uhdwrap_start(global_dev, chan_nr);
 | 
			
		||||
 | 
			
		||||
	pthread_cond_broadcast(&wait_open_cond);
 | 
			
		||||
	// /* no per-chan start/stop */
 | 
			
		||||
	// if (!dl_running || !ul_running) {
 | 
			
		||||
	// 	/* chan != first chan start will "fail", which is fine, usrp can't start/stop chans independently */
 | 
			
		||||
	// 	if (rc) {
 | 
			
		||||
	// 		LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads.. req for chan:%d\n", chan_nr);
 | 
			
		||||
	// 		pthread_t rx, tx;
 | 
			
		||||
	// 		pthread_create(&rx, NULL, uplink_thread, 0);
 | 
			
		||||
	// 		pthread_create(&tx, NULL, downlink_thread, 0);
 | 
			
		||||
	// 	}
 | 
			
		||||
	// } else
 | 
			
		||||
	// 	LOGP(DMAIN, LOGL_INFO, "starting rx/tx threads request ignored.. req for chan:%d\n", chan_nr);
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_START_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_chan_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.start_cnf.return_code = rc ? 0 : -1;
 | 
			
		||||
 | 
			
		||||
	return ipc_chan_sock_send(msg, chan_nr);
 | 
			
		||||
}
 | 
			
		||||
int ipc_rx_chan_stop_req(struct ipc_sk_chan_if_op_void *req, uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_chan_if *ipc_prim;
 | 
			
		||||
	int rc = true;
 | 
			
		||||
 | 
			
		||||
	/* no per-chan start/stop */
 | 
			
		||||
	// rc = uhdwrap_stop(global_dev, chan_nr);
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_STOP_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_chan_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.stop_cnf.return_code = rc ? 0 : -1;
 | 
			
		||||
 | 
			
		||||
	return ipc_chan_sock_send(msg, chan_nr);
 | 
			
		||||
}
 | 
			
		||||
int ipc_rx_chan_setgain_req(struct ipc_sk_chan_if_gain *req, uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_chan_if *ipc_prim;
 | 
			
		||||
	double rv = req->gain;
 | 
			
		||||
 | 
			
		||||
	// rv = uhdwrap_set_gain(global_dev, req->gain, chan_nr, req->is_tx);
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_SETGAIN_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_chan_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.set_gain_cnf.is_tx = req->is_tx;
 | 
			
		||||
	ipc_prim->u.set_gain_cnf.gain = rv;
 | 
			
		||||
 | 
			
		||||
	return ipc_chan_sock_send(msg, chan_nr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_rx_chan_setfreq_req(struct ipc_sk_chan_if_freq_req *req, uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_chan_if *ipc_prim;
 | 
			
		||||
	bool rv = true;
 | 
			
		||||
 | 
			
		||||
	// rv = uhdwrap_set_freq(global_dev, req->freq, chan_nr, req->is_tx);
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_SETFREQ_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_chan_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.set_freq_cnf.return_code = rv ? 0 : 1;
 | 
			
		||||
 | 
			
		||||
	return ipc_chan_sock_send(msg, chan_nr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_rx_chan_settxatten_req(struct ipc_sk_chan_if_tx_attenuation *req, uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct ipc_sk_chan_if *ipc_prim;
 | 
			
		||||
	double rv = req->attenuation;
 | 
			
		||||
 | 
			
		||||
	// rv = uhdwrap_set_txatt(global_dev, req->attenuation, chan_nr);
 | 
			
		||||
 | 
			
		||||
	msg = ipc_msgb_alloc(IPC_IF_MSG_SETTXATTN_CNF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	ipc_prim = (struct ipc_sk_chan_if *)msg->data;
 | 
			
		||||
	ipc_prim->u.txatten_cnf.attenuation = rv;
 | 
			
		||||
 | 
			
		||||
	return ipc_chan_sock_send(msg, chan_nr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ipc_sock_init(const char *path, struct ipc_sock_state **global_state_var,
 | 
			
		||||
		  int (*sock_callback_fn)(struct osmo_fd *fd, unsigned int what), int n)
 | 
			
		||||
{
 | 
			
		||||
	struct ipc_sock_state *state;
 | 
			
		||||
	struct osmo_fd *bfd;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	state = talloc_zero(NULL, struct ipc_sock_state);
 | 
			
		||||
	if (!state)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	*global_state_var = state;
 | 
			
		||||
 | 
			
		||||
	INIT_LLIST_HEAD(&state->upqueue);
 | 
			
		||||
	state->conn_bfd.fd = -1;
 | 
			
		||||
 | 
			
		||||
	bfd = &state->listen_bfd;
 | 
			
		||||
 | 
			
		||||
	bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND);
 | 
			
		||||
	if (bfd->fd < 0) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "Could not create %s unix socket: %s\n", path, strerror(errno));
 | 
			
		||||
		talloc_free(state);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, sock_callback_fn, state, n);
 | 
			
		||||
 | 
			
		||||
	rc = osmo_fd_register(bfd);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGP(DMAIN, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
 | 
			
		||||
		close(bfd->fd);
 | 
			
		||||
		talloc_free(state);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DMAIN, LOGL_INFO, "Started listening on IPC socket: %s\n", path);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main_ipc()
 | 
			
		||||
{
 | 
			
		||||
	char *ipc_msock_path = "/tmp/ipc_sock1";
 | 
			
		||||
	tall_ctx = talloc_named_const(NULL, 0, "trx-ipc-ms");
 | 
			
		||||
	msgb_talloc_ctx_init(tall_ctx, 0);
 | 
			
		||||
	osmo_init_logging2(tall_ctx, &log_infox);
 | 
			
		||||
	log_enable_multithread();
 | 
			
		||||
 | 
			
		||||
	LOGP(DMAIN, LOGL_INFO, "Starting %s\n", "bernd");
 | 
			
		||||
	ipc_sock_init(ipc_msock_path, &global_ipc_sock_state, ipc_sock_accept, 0);
 | 
			
		||||
	// while (!ipc_exit_requested)
 | 
			
		||||
	// 	osmo_select_main(0);
 | 
			
		||||
 | 
			
		||||
	// if (global_dev) {
 | 
			
		||||
	// 	unsigned int i;
 | 
			
		||||
	// 	for (i = 0; i < decoded_region->num_chans; i++)
 | 
			
		||||
	// 		uhdwrap_stop(global_dev, i);
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
	// ipc_sock_close(global_ipc_sock_state);
 | 
			
		||||
 | 
			
		||||
	int rv;
 | 
			
		||||
	pthread_condattr_t t2;
 | 
			
		||||
	rv = pthread_condattr_setpshared(&t2, PTHREAD_PROCESS_SHARED);
 | 
			
		||||
	rv = pthread_cond_init(&wait_open_cond, &t2);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int wait_for_shm_open()
 | 
			
		||||
{
 | 
			
		||||
	struct timespec tv;
 | 
			
		||||
	int rv;
 | 
			
		||||
	clock_gettime(CLOCK_REALTIME, &tv);
 | 
			
		||||
	tv.tv_sec += 15;
 | 
			
		||||
 | 
			
		||||
	rv = pthread_mutex_timedlock(&wait_open_lock, &tv);
 | 
			
		||||
	if (rv != 0)
 | 
			
		||||
		return -rv;
 | 
			
		||||
 | 
			
		||||
	rv = pthread_cond_timedwait(&wait_open_cond, &wait_open_lock, &tv);
 | 
			
		||||
	return rv;
 | 
			
		||||
}
 | 
			
		||||
@@ -27,33 +27,15 @@
 | 
			
		||||
#include <sys/eventfd.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
#include <stdatomic.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
classic lamport circular lockfree spsc queue:
 | 
			
		||||
every "side" only writes its own ptr, but may read the other sides ptr
 | 
			
		||||
 | 
			
		||||
notify reader using eventfd as soon as element is added, reader then reads until
 | 
			
		||||
read fails
 | 
			
		||||
-> reader pops in a loop until FALSE and might get spurious events because it
 | 
			
		||||
read before it was notified, which is fine
 | 
			
		||||
-> writing pushes *the same data* in a loop until TRUE, blocks
 | 
			
		||||
 | 
			
		||||
shutting this down requires
 | 
			
		||||
1) to stop reading and pushing
 | 
			
		||||
2) ONE side to take care of the eventfds
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
namespace spsc_detail
 | 
			
		||||
{
 | 
			
		||||
template <bool block_read, bool block_write> class spsc_cond_detail {
 | 
			
		||||
	std::condition_variable cond_r, cond_w;
 | 
			
		||||
	std::mutex l;
 | 
			
		||||
	std::mutex lr, lw;
 | 
			
		||||
	std::atomic_int r_flag, w_flag;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	explicit spsc_cond_detail()
 | 
			
		||||
	explicit spsc_cond_detail() : r_flag(0), w_flag(0)
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -63,22 +45,30 @@ template <bool block_read, bool block_write> class spsc_cond_detail {
 | 
			
		||||
 | 
			
		||||
	ssize_t spsc_check_r()
 | 
			
		||||
	{
 | 
			
		||||
		std::unique_lock<std::mutex> lk(l);
 | 
			
		||||
		cond_r.wait(lk);
 | 
			
		||||
		std::unique_lock<std::mutex> lk(lr);
 | 
			
		||||
		while (r_flag == 0)
 | 
			
		||||
			cond_r.wait(lk);
 | 
			
		||||
		r_flag--;
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	ssize_t spsc_check_w()
 | 
			
		||||
	{
 | 
			
		||||
		std::unique_lock<std::mutex> lk(l);
 | 
			
		||||
		cond_w.wait(lk);
 | 
			
		||||
		std::unique_lock<std::mutex> lk(lw);
 | 
			
		||||
		while (w_flag == 0)
 | 
			
		||||
			cond_w.wait(lk);
 | 
			
		||||
		w_flag--;
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	void spsc_notify_r()
 | 
			
		||||
	{
 | 
			
		||||
		std::unique_lock<std::mutex> lk(lr);
 | 
			
		||||
		r_flag++;
 | 
			
		||||
		cond_r.notify_one();
 | 
			
		||||
	}
 | 
			
		||||
	void spsc_notify_w()
 | 
			
		||||
	{
 | 
			
		||||
		std::unique_lock<std::mutex> lk(lw);
 | 
			
		||||
		w_flag++;
 | 
			
		||||
		cond_w.notify_one();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -151,7 +141,7 @@ class spsc : public T<block_read, block_write> {
 | 
			
		||||
	/*! Adds element to the queue by copying the data.
 | 
			
		||||
 *  \param[in] elem input buffer, must match the originally configured queue buffer size!.
 | 
			
		||||
 *  \returns true if queue was not full and element was successfully pushed */
 | 
			
		||||
	bool spsc_push(ELEM *elem)
 | 
			
		||||
	bool spsc_push(const ELEM *elem)
 | 
			
		||||
	{
 | 
			
		||||
		size_t cur_wp, cur_rp;
 | 
			
		||||
		cur_wp = writeptr.load(std::memory_order_relaxed);
 | 
			
		||||
							
								
								
									
										275
									
								
								Transceiver52M/ms/l1ctl_server.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								Transceiver52M/ms/l1ctl_server.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,275 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * UNIX socket server for L1CTL
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
 | 
			
		||||
 * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 * (C) 2022 by 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 General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/write_queue.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/bb/trxcon/logging.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
 | 
			
		||||
 | 
			
		||||
#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args)
 | 
			
		||||
 | 
			
		||||
static int l1ctl_client_read_cb(struct osmo_fd *ofd)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	uint16_t len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Attempt to read from socket */
 | 
			
		||||
	rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
 | 
			
		||||
	if (rc != L1CTL_MSG_LEN_FIELD) {
 | 
			
		||||
		if (rc <= 0) {
 | 
			
		||||
			LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc,
 | 
			
		||||
				 strerror(errno));
 | 
			
		||||
		} else {
 | 
			
		||||
			LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n");
 | 
			
		||||
			rc = -EIO;
 | 
			
		||||
		}
 | 
			
		||||
		l1ctl_client_conn_close(client);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check message length */
 | 
			
		||||
	len = ntohs(len);
 | 
			
		||||
	if (len > L1CTL_LENGTH) {
 | 
			
		||||
		LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new msg */
 | 
			
		||||
	msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg");
 | 
			
		||||
	if (!msg) {
 | 
			
		||||
		LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg->l1h = msgb_put(msg, len);
 | 
			
		||||
	rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
 | 
			
		||||
	if (rc != len) {
 | 
			
		||||
		LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno));
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Debug print */
 | 
			
		||||
	LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
 | 
			
		||||
 | 
			
		||||
	/* Call L1CTL handler */
 | 
			
		||||
	client->server->cfg->conn_read_cb(client, msg);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
 | 
			
		||||
	int len;
 | 
			
		||||
 | 
			
		||||
	if (ofd->fd <= 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	len = write(ofd->fd, msg->data, msg->len);
 | 
			
		||||
	if (len != msg->len) {
 | 
			
		||||
		LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len,
 | 
			
		||||
			 msg->len);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Connection handler */
 | 
			
		||||
static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
 | 
			
		||||
	struct l1ctl_client *client;
 | 
			
		||||
	int rc, client_fd;
 | 
			
		||||
 | 
			
		||||
	client_fd = accept(sfd->fd, NULL, NULL);
 | 
			
		||||
	if (client_fd < 0) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR,
 | 
			
		||||
		     "Failed to accept() a new connection: "
 | 
			
		||||
		     "%s\n",
 | 
			
		||||
		     strerror(errno));
 | 
			
		||||
		return client_fd;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
 | 
			
		||||
	    server->num_clients >= server->cfg->num_clients_max) {
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE,
 | 
			
		||||
		     "L1CTL server cannot accept more "
 | 
			
		||||
		     "than %u connection(s)\n",
 | 
			
		||||
		     server->cfg->num_clients_max);
 | 
			
		||||
		close(client_fd);
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client = talloc_zero(server, struct l1ctl_client);
 | 
			
		||||
	if (client == NULL) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
 | 
			
		||||
		close(client_fd);
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Init the client's write queue */
 | 
			
		||||
	osmo_wqueue_init(&client->wq, 100);
 | 
			
		||||
	INIT_LLIST_HEAD(&client->wq.bfd.list);
 | 
			
		||||
 | 
			
		||||
	client->wq.write_cb = &l1ctl_client_write_cb;
 | 
			
		||||
	client->wq.read_cb = &l1ctl_client_read_cb;
 | 
			
		||||
	osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
 | 
			
		||||
 | 
			
		||||
	/* Register the client's write queue */
 | 
			
		||||
	rc = osmo_fd_register(&client->wq.bfd);
 | 
			
		||||
	if (rc != 0) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
 | 
			
		||||
		close(client->wq.bfd.fd);
 | 
			
		||||
		talloc_free(client);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	llist_add_tail(&client->list, &server->clients);
 | 
			
		||||
	client->id = server->next_client_id++;
 | 
			
		||||
	client->server = server;
 | 
			
		||||
	server->num_clients++;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
 | 
			
		||||
 | 
			
		||||
	if (client->server->cfg->conn_accept_cb != NULL)
 | 
			
		||||
		client->server->cfg->conn_accept_cb(client);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *len;
 | 
			
		||||
 | 
			
		||||
	/* Debug print */
 | 
			
		||||
	LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
 | 
			
		||||
 | 
			
		||||
	if (msg->l1h != msg->data)
 | 
			
		||||
		LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
 | 
			
		||||
 | 
			
		||||
	/* Prepend 16-bit length before sending */
 | 
			
		||||
	len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
 | 
			
		||||
	osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
 | 
			
		||||
 | 
			
		||||
	if (osmo_wqueue_enqueue(&client->wq, msg) != 0) {
 | 
			
		||||
		LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void l1ctl_client_conn_close(struct l1ctl_client *client)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_server *server = client->server;
 | 
			
		||||
 | 
			
		||||
	LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
 | 
			
		||||
 | 
			
		||||
	if (server->cfg->conn_close_cb != NULL)
 | 
			
		||||
		server->cfg->conn_close_cb(client);
 | 
			
		||||
 | 
			
		||||
	/* Close connection socket */
 | 
			
		||||
	osmo_fd_unregister(&client->wq.bfd);
 | 
			
		||||
	close(client->wq.bfd.fd);
 | 
			
		||||
	client->wq.bfd.fd = -1;
 | 
			
		||||
 | 
			
		||||
	/* Clear pending messages */
 | 
			
		||||
	osmo_wqueue_clear(&client->wq);
 | 
			
		||||
 | 
			
		||||
	client->server->num_clients--;
 | 
			
		||||
	llist_del(&client->list);
 | 
			
		||||
	talloc_free(client);
 | 
			
		||||
 | 
			
		||||
	/* If this was the last client, reset the client IDs generator to 0.
 | 
			
		||||
	 * This way avoid assigning huge unreadable client IDs like 26545. */
 | 
			
		||||
	if (llist_empty(&server->clients))
 | 
			
		||||
		server->next_client_id = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_server *server;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
 | 
			
		||||
 | 
			
		||||
	server = talloc(ctx, struct l1ctl_server);
 | 
			
		||||
	OSMO_ASSERT(server != NULL);
 | 
			
		||||
 | 
			
		||||
	*server = (struct l1ctl_server){
 | 
			
		||||
		.clients = LLIST_HEAD_INIT(server->clients),
 | 
			
		||||
		.cfg = cfg,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* conn_read_cb shall not be NULL */
 | 
			
		||||
	OSMO_ASSERT(cfg->conn_read_cb != NULL);
 | 
			
		||||
 | 
			
		||||
	/* Bind connection handler */
 | 
			
		||||
	osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
 | 
			
		||||
 | 
			
		||||
	rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno));
 | 
			
		||||
		talloc_free(server);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return server;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void l1ctl_server_free(struct l1ctl_server *server)
 | 
			
		||||
{
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
 | 
			
		||||
 | 
			
		||||
	/* Close all client connections */
 | 
			
		||||
	while (!llist_empty(&server->clients)) {
 | 
			
		||||
		struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list);
 | 
			
		||||
		l1ctl_client_conn_close(client);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Unbind listening socket */
 | 
			
		||||
	if (server->ofd.fd != -1) {
 | 
			
		||||
		osmo_fd_unregister(&server->ofd);
 | 
			
		||||
		close(server->ofd.fd);
 | 
			
		||||
		server->ofd.fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	talloc_free(server);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								Transceiver52M/ms/l1ctl_server_cb.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Transceiver52M/ms/l1ctl_server_cb.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <osmocom/bb/trxcon/trxcon.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/trxcon_fsm.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct l1ctl_server_cfg server_cfg;
 | 
			
		||||
static struct l1ctl_server *server = NULL;
 | 
			
		||||
namespace trxcon
 | 
			
		||||
{
 | 
			
		||||
extern struct trxcon_inst *g_trxcon;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_cb(struct l1ctl_client *l1c, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
 | 
			
		||||
 | 
			
		||||
	return trxcon_l1ctl_receive(trxcon, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void l1ctl_conn_accept_cb(struct l1ctl_client *l1c)
 | 
			
		||||
{
 | 
			
		||||
	l1c->log_prefix = talloc_strdup(l1c, trxcon::g_trxcon->log_prefix);
 | 
			
		||||
	l1c->priv = trxcon::g_trxcon;
 | 
			
		||||
	trxcon::g_trxcon->l2if = l1c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void l1ctl_conn_close_cb(struct l1ctl_client *l1c)
 | 
			
		||||
{
 | 
			
		||||
	struct trxcon_inst *trxcon = (struct trxcon_inst *)l1c->priv;
 | 
			
		||||
 | 
			
		||||
	if (trxcon == NULL || trxcon->fi == NULL)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_L2IF_FAILURE, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace trxcon
 | 
			
		||||
{
 | 
			
		||||
bool trxc_l1ctl_init(void *tallctx)
 | 
			
		||||
{
 | 
			
		||||
	/* Start the L1CTL server */
 | 
			
		||||
	server_cfg = (struct l1ctl_server_cfg){
 | 
			
		||||
		/* TODO: make path configurable */
 | 
			
		||||
		.sock_path = "/tmp/osmocom_l2",
 | 
			
		||||
		.num_clients_max = 1,
 | 
			
		||||
		.conn_read_cb = &l1ctl_rx_cb,
 | 
			
		||||
		.conn_accept_cb = &l1ctl_conn_accept_cb,
 | 
			
		||||
		.conn_close_cb = &l1ctl_conn_close_cb,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	server = l1ctl_server_alloc(tallctx, &server_cfg);
 | 
			
		||||
	if (server == NULL) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
} // namespace trxcon
 | 
			
		||||
							
								
								
									
										81
									
								
								Transceiver52M/ms/logging.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								Transceiver52M/ms/logging.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2016-2022 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/bb/trxcon/logging.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/trxcon.h>
 | 
			
		||||
 | 
			
		||||
static struct log_info_cat trxcon_log_info_cat[] = {
 | 
			
		||||
	[DAPP] = {
 | 
			
		||||
		.name = "DAPP",
 | 
			
		||||
		.color = "\033[1;35m",
 | 
			
		||||
		.description = "Application",
 | 
			
		||||
		.loglevel = LOGL_NOTICE,
 | 
			
		||||
		.enabled = 1,
 | 
			
		||||
	},
 | 
			
		||||
	[DL1C] = {
 | 
			
		||||
		.name = "DL1C",
 | 
			
		||||
		.color = "\033[1;31m",
 | 
			
		||||
		.description = "Layer 1 control interface",
 | 
			
		||||
		.loglevel = LOGL_NOTICE,
 | 
			
		||||
		.enabled = 1,
 | 
			
		||||
	},
 | 
			
		||||
	[DL1D] = {
 | 
			
		||||
		.name = "DL1D",
 | 
			
		||||
		.color = "\033[1;31m",
 | 
			
		||||
		.description = "Layer 1 data",
 | 
			
		||||
		.loglevel = LOGL_NOTICE,
 | 
			
		||||
		.enabled = 1,
 | 
			
		||||
	},
 | 
			
		||||
	[DSCH] = {
 | 
			
		||||
		.name = "DSCH",
 | 
			
		||||
		.color = "\033[1;36m",
 | 
			
		||||
		.description = "Scheduler management",
 | 
			
		||||
		.loglevel = LOGL_NOTICE,
 | 
			
		||||
		.enabled = 0,
 | 
			
		||||
	},
 | 
			
		||||
	[DSCHD] = {
 | 
			
		||||
		.name = "DSCHD",
 | 
			
		||||
		.color = "\033[1;36m",
 | 
			
		||||
		.description = "Scheduler data",
 | 
			
		||||
		.loglevel = LOGL_NOTICE,
 | 
			
		||||
		.enabled = 0,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct log_info trxcon_log_info = {
 | 
			
		||||
	.cat = trxcon_log_info_cat,
 | 
			
		||||
	.num_cat = ARRAY_SIZE(trxcon_log_info_cat),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const int trxcon_log_cfg[] = {
 | 
			
		||||
	[TRXCON_LOGC_FSM] = DAPP,
 | 
			
		||||
	[TRXCON_LOGC_L1C] = DL1C,
 | 
			
		||||
	[TRXCON_LOGC_L1D] = DL1D,
 | 
			
		||||
	[TRXCON_LOGC_SCHC] = DSCH,
 | 
			
		||||
	[TRXCON_LOGC_SCHD] = DSCHD,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void trxc_log_init(void *tallctx)
 | 
			
		||||
{
 | 
			
		||||
	osmo_init_logging2(tallctx, &trxcon_log_info);
 | 
			
		||||
 | 
			
		||||
	trxcon_set_log_cfg(&trxcon_log_cfg[0], ARRAY_SIZE(trxcon_log_cfg));
 | 
			
		||||
}
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
 | 
			
		||||
#include "sigProcLib.h"
 | 
			
		||||
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
#include "ms.h"
 | 
			
		||||
#include "ms_rx_burst.h"
 | 
			
		||||
#include "grgsm_vitac/grgsm_vitac.h"
 | 
			
		||||
 | 
			
		||||
@@ -43,9 +43,13 @@ extern "C" {
 | 
			
		||||
 | 
			
		||||
dummylog ms_trx::dummy_log;
 | 
			
		||||
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
const int offsetrange = 200;
 | 
			
		||||
const int offset_start = -15;
 | 
			
		||||
int offsetrange = 200;
 | 
			
		||||
static int offset_ctr = 0;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
template <> std::atomic<bool> ms_trx::base::stop_me_flag(false);
 | 
			
		||||
 | 
			
		||||
void tx_test(ms_trx *t, ts_hitter_q_t *q, unsigned int *tsc)
 | 
			
		||||
{
 | 
			
		||||
@@ -96,36 +100,40 @@ void tx_test(ms_trx *t, ts_hitter_q_t *q, unsigned int *tsc)
 | 
			
		||||
		int64_t send_ts =
 | 
			
		||||
			now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
 | 
			
		||||
 | 
			
		||||
		// std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn
 | 
			
		||||
		// 			<< " tn LOCAL: " << now_time.TN() << " tn OTHER: " << target_tn
 | 
			
		||||
		// 			<< " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
 | 
			
		||||
		// 			<< " calc: " << check.FN() << ":" <<check.TN()
 | 
			
		||||
		// 			<< " target: " << target.FN() << ":" <<target.TN()
 | 
			
		||||
		// 			<< " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn << " tn LOCAL: " << now_time.TN()
 | 
			
		||||
			  << " tn OTHER: " << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":"
 | 
			
		||||
			  << tosend.TN() << " calc: " << check.FN() << ":" << check.TN() << " target: " << target.FN()
 | 
			
		||||
			  << ":" << target.TN() << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
		unsigned int pad = 4 * 25;
 | 
			
		||||
		blade_sample_type buf2[burst->size() + pad];
 | 
			
		||||
		memset(buf2, 0, pad * sizeof(blade_sample_type));
 | 
			
		||||
		std::fill(buf2, buf2 + pad, 0);
 | 
			
		||||
 | 
			
		||||
		memcpy(&buf2[pad], burst_buf, burst->size() * sizeof(blade_sample_type));
 | 
			
		||||
 | 
			
		||||
		assert(target.FN() == check.FN());
 | 
			
		||||
		assert(target.TN() == check.TN());
 | 
			
		||||
		assert(target.FN() % 51 == 21);
 | 
			
		||||
 | 
			
		||||
		// auto this_offset = offset_start + (offset_ctr++ % offsetrange);
 | 
			
		||||
		// std::cerr << "-- O " << this_offset << std::endl;
 | 
			
		||||
		// send_ts = now_ts + ((target.FN() * 8 + (int)target.TN()) - (now_time.FN() * 8 + (int)now_time.TN())) * ONE_TS_BURST_LEN - timing_advance;
 | 
			
		||||
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		auto this_offset = offset_start + (offset_ctr++ % offsetrange);
 | 
			
		||||
		std::cerr << "-- O " << this_offset << std::endl;
 | 
			
		||||
		send_ts = now_ts - timing_advance +
 | 
			
		||||
			  ((target.FN() * 8 + (int)target.TN()) - (now_time.FN() * 8 + (int)now_time.TN())) *
 | 
			
		||||
				  ONE_TS_BURST_LEN;
 | 
			
		||||
#endif
 | 
			
		||||
		t->submit_burst_ts(buf2, burst->size() + pad, send_ts - pad);
 | 
			
		||||
 | 
			
		||||
		// signalVector test(burst->size() + pad);
 | 
			
		||||
		// convert_and_scale<float, int16_t>(test.begin(), buf2, burst->size() * 2 + pad, 1.f / float(scale));
 | 
			
		||||
		// estim_burst_params ebp;
 | 
			
		||||
		// auto det = detectAnyBurst(test, 0, 4, 4, CorrType::RACH, 40, &ebp);
 | 
			
		||||
		// if (det > 0)
 | 
			
		||||
		// 	std::cerr << "## Y " << ebp.toa << std::endl;
 | 
			
		||||
		// else
 | 
			
		||||
		// 	std::cerr << "## NOOOOOOOOO " << ebp.toa << std::endl;
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		signalVector test(burst->size() + pad);
 | 
			
		||||
		convert_and_scale<float, int16_t>(test.begin(), buf2, burst->size() * 2 + pad, 1.f / float(scale));
 | 
			
		||||
		estim_burst_params ebp;
 | 
			
		||||
		auto det = detectAnyBurst(test, 0, 4, 4, CorrType::RACH, 40, &ebp);
 | 
			
		||||
		if (det > 0)
 | 
			
		||||
			std::cerr << "## Y " << ebp.toa << std::endl;
 | 
			
		||||
		else
 | 
			
		||||
			std::cerr << "## NOOOOOOOOO " << ebp.toa << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#ifdef SYNCTHINGONLY
 | 
			
		||||
@@ -203,7 +211,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
		  << "gain rx " << grx << " gain tx " << gtx << std::endl
 | 
			
		||||
		  << (tx_flag ? "##!!## RACH TX ACTIVE ##!!##" : "-- no rach tx --") << std::endl;
 | 
			
		||||
 | 
			
		||||
	status = trx->init_dev_and_streams(0, 0);
 | 
			
		||||
	status = trx->init_dev_and_streams();
 | 
			
		||||
	if (status < 0)
 | 
			
		||||
		return status;
 | 
			
		||||
	trx->tuneRx(default_rx_freq);
 | 
			
		||||
@@ -230,12 +238,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
int ms_trx::init_streams(void *rx_cb, void *tx_cb)
 | 
			
		||||
{
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ms_trx::init_dev_and_streams(void *rx_cb, void *tx_cb)
 | 
			
		||||
int ms_trx::init_dev_and_streams()
 | 
			
		||||
{
 | 
			
		||||
	int status = 0;
 | 
			
		||||
	status = base::init_device(rx_bh(), tx_bh());
 | 
			
		||||
@@ -258,9 +261,10 @@ bh_fn_t ms_trx::rx_bh()
 | 
			
		||||
bh_fn_t ms_trx::tx_bh()
 | 
			
		||||
{
 | 
			
		||||
	return [this](dev_buf_t *rcd) -> int {
 | 
			
		||||
#pragma unused(rcd)
 | 
			
		||||
#pragma GCC diagnostic push
 | 
			
		||||
#pragma GCC diagnostic ignored "-Wunused-variable"
 | 
			
		||||
		auto y = this;
 | 
			
		||||
#pragma unused(y)
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
		/* nothing to do here */
 | 
			
		||||
		return 0;
 | 
			
		||||
	};
 | 
			
		||||
@@ -270,10 +274,12 @@ void ms_trx::start()
 | 
			
		||||
{
 | 
			
		||||
	auto fn = get_rx_burst_handler_fn(rx_bh());
 | 
			
		||||
	rx_task = std::thread(fn);
 | 
			
		||||
	set_name_aff_sched(rx_task.native_handle(), "rxrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 2);
 | 
			
		||||
 | 
			
		||||
	usleep(1000);
 | 
			
		||||
	auto fn2 = get_tx_burst_handler_fn(tx_bh());
 | 
			
		||||
	tx_task = std::thread(fn2);
 | 
			
		||||
	set_name_aff_sched(tx_task.native_handle(), "txrun", 2, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ms_trx::set_upper_ready(bool is_ready)
 | 
			
		||||
@@ -284,7 +290,10 @@ void ms_trx::set_upper_ready(bool is_ready)
 | 
			
		||||
void ms_trx::stop_threads()
 | 
			
		||||
{
 | 
			
		||||
	std::cerr << "killing threads...\r\n" << std::endl;
 | 
			
		||||
	stop_me_flag = true;
 | 
			
		||||
	close_device();
 | 
			
		||||
	rx_task.join();
 | 
			
		||||
	tx_task.join();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target)
 | 
			
		||||
@@ -305,7 +314,7 @@ void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target)
 | 
			
		||||
	else
 | 
			
		||||
		tosend.decTN(-diff_tn);
 | 
			
		||||
 | 
			
		||||
	// in thory fn equal and tn+3 equal is also a problem...
 | 
			
		||||
	// in theory fn equal and tn+3 equal is also a problem...
 | 
			
		||||
	if (diff_fn < 0 || (diff_fn == 0 && (now_time.TN() - target_tn < 1))) {
 | 
			
		||||
		std::cerr << "## TX too late?! fn DIFF:" << diff_fn << " tn LOCAL: " << now_time.TN()
 | 
			
		||||
			  << " tn OTHER: " << target_tn << std::endl;
 | 
			
		||||
@@ -314,18 +323,16 @@ void ms_trx::submit_burst(blade_sample_type *buffer, int len, GSM::Time target)
 | 
			
		||||
 | 
			
		||||
	auto check = now_time + tosend;
 | 
			
		||||
	int64_t send_ts = now_ts + tosend.FN() * 8 * ONE_TS_BURST_LEN + tosend.TN() * ONE_TS_BURST_LEN - timing_advance;
 | 
			
		||||
 | 
			
		||||
	// std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn
 | 
			
		||||
	// 			<< " tn LOCAL/OTHER: " << now_time.TN() << "/" << target_tn
 | 
			
		||||
	// 			<< " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
 | 
			
		||||
	// 			<< " check: " << check.FN() << ":" <<check.TN()
 | 
			
		||||
	// 			<< " target: " << target.FN() << ":" <<target.TN()
 | 
			
		||||
	// 			<< " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
 | 
			
		||||
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
	std::cerr << "## fn DIFF: " << diff_fn << " ## tn DIFF: " << diff_tn << " tn LOCAL/OTHER: " << now_time.TN()
 | 
			
		||||
		  << "/" << target_tn << " tndiff" << diff_tn << " tosend:" << tosend.FN() << ":" << tosend.TN()
 | 
			
		||||
		  << " check: " << check.FN() << ":" << check.TN() << " target: " << target.FN() << ":" << target.TN()
 | 
			
		||||
		  << " ts now: " << now_ts << " target ts:" << send_ts << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
#if 1
 | 
			
		||||
	unsigned int pad = 4 * 4;
 | 
			
		||||
	blade_sample_type buf2[len + pad];
 | 
			
		||||
	memset(buf2, 0, pad * sizeof(blade_sample_type));
 | 
			
		||||
	std::fill(buf2, buf2 + pad, 0);
 | 
			
		||||
	memcpy(&buf2[pad], buffer, len * sizeof(blade_sample_type));
 | 
			
		||||
 | 
			
		||||
	assert(target.FN() == check.FN());
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
@@ -35,9 +34,6 @@
 | 
			
		||||
#elif defined(BUILDUHD)
 | 
			
		||||
#include "uhd_specific.h"
 | 
			
		||||
#define BASET uhd_hw<ms_trx>
 | 
			
		||||
#elif defined(BUILDIPC)
 | 
			
		||||
#include "ipc_specific.h"
 | 
			
		||||
#define BASET ipc_hw<ms_trx>
 | 
			
		||||
#else
 | 
			
		||||
#error wat? no device..
 | 
			
		||||
#endif
 | 
			
		||||
@@ -75,7 +71,7 @@ struct one_burst {
 | 
			
		||||
	GSM::Time gsmts;
 | 
			
		||||
	union {
 | 
			
		||||
		blade_sample_type burst[ONE_TS_BURST_LEN];
 | 
			
		||||
		unsigned char sch_bits[148];
 | 
			
		||||
		char sch_bits[148];
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -205,8 +201,7 @@ struct ms_trx : public BASET {
 | 
			
		||||
	void grab_bursts(dev_buf_t *rcd);
 | 
			
		||||
 | 
			
		||||
	int init_device();
 | 
			
		||||
	int init_streams(void *rx_cb, void *tx_cb);
 | 
			
		||||
	int init_dev_and_streams(void *rx_cb, void *tx_cb);
 | 
			
		||||
	int init_dev_and_streams();
 | 
			
		||||
	void stop_threads();
 | 
			
		||||
	void *rx_cb(ms_trx *t);
 | 
			
		||||
	void *tx_cb();
 | 
			
		||||
@@ -232,4 +227,34 @@ struct ms_trx : public BASET {
 | 
			
		||||
		assert(val > -127 && val < 128);
 | 
			
		||||
		timing_advance = val * 4;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
 | 
			
		||||
	{
 | 
			
		||||
		set_name_aff_sched(pthread_self(), name, cpunum, schedtype, prio);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_name_aff_sched(std::thread::native_handle_type h, const char *name, int cpunum, int schedtype,
 | 
			
		||||
				int prio)
 | 
			
		||||
	{
 | 
			
		||||
		pthread_setname_np(h, name);
 | 
			
		||||
 | 
			
		||||
		cpu_set_t cpuset;
 | 
			
		||||
 | 
			
		||||
		CPU_ZERO(&cpuset);
 | 
			
		||||
		CPU_SET(cpunum, &cpuset);
 | 
			
		||||
 | 
			
		||||
		auto rv = pthread_setaffinity_np(h, sizeof(cpuset), &cpuset);
 | 
			
		||||
		if (rv < 0) {
 | 
			
		||||
			std::cerr << name << " affinity: errreur! " << std::strerror(errno);
 | 
			
		||||
			return exit(0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sched_param sch_params;
 | 
			
		||||
		sch_params.sched_priority = prio;
 | 
			
		||||
		rv = pthread_setschedparam(h, schedtype, &sch_params);
 | 
			
		||||
		if (rv < 0) {
 | 
			
		||||
			std::cerr << name << " sched: errreur! " << std::strerror(errno);
 | 
			
		||||
			return exit(0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -1,227 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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 <radioInterface.h>
 | 
			
		||||
#include "ms_rx_upper.h"
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
 | 
			
		||||
void upper_trx::driveControl()
 | 
			
		||||
{
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	auto m = pop_c();
 | 
			
		||||
	if (!m)
 | 
			
		||||
		return;
 | 
			
		||||
#else
 | 
			
		||||
	TRX_C cmd;
 | 
			
		||||
 | 
			
		||||
	socklen_t addr_len = sizeof(ctrlsrc);
 | 
			
		||||
	int rdln = recvfrom(mCtrlSockets, (void *)cmd.cmd, sizeof(cmd) - 1, 0, &ctrlsrc, &addr_len);
 | 
			
		||||
	if (rdln < 0 && errno == EAGAIN) {
 | 
			
		||||
		std::cerr << "fuck, send ctrl?" << std::endl;
 | 
			
		||||
		exit(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TRX_C *m = &cmd;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	auto response = (TRX_C *)malloc(sizeof(TRX_C));
 | 
			
		||||
	response->cmd[0] = '\0';
 | 
			
		||||
	commandhandler(m->cmd, response->cmd);
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	free(m);
 | 
			
		||||
#endif
 | 
			
		||||
	std::clog << "response is " << response->cmd << std::endl;
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	push_c(response);
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
	int rv = sendto(mCtrlSockets, response, strlen(response->cmd) + 1, 0, &ctrlsrc, sizeof(struct sockaddr_in));
 | 
			
		||||
	if (rv < 0) {
 | 
			
		||||
		std::cerr << "fuck, rcv ctrl?" << std::endl;
 | 
			
		||||
		exit(0);
 | 
			
		||||
	}
 | 
			
		||||
	free(response);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::commandhandler(char *buffer, char *response)
 | 
			
		||||
{
 | 
			
		||||
	int MAX_PACKET_LENGTH = TRXC_BUF_SIZE;
 | 
			
		||||
 | 
			
		||||
	char cmdcheck[4];
 | 
			
		||||
	char command[MAX_PACKET_LENGTH];
 | 
			
		||||
 | 
			
		||||
	sscanf(buffer, "%3s %s", cmdcheck, command);
 | 
			
		||||
 | 
			
		||||
	if (strcmp(cmdcheck, "CMD") != 0) {
 | 
			
		||||
		LOG(WARNING) << "bogus message on control interface";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	std::clog << "command is " << buffer << std::endl << std::flush;
 | 
			
		||||
 | 
			
		||||
	if (strcmp(command, "MEASURE") == 0) {
 | 
			
		||||
		msleep(100);
 | 
			
		||||
		int freq;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &freq);
 | 
			
		||||
		sprintf(response, "RSP MEASURE 0 %d -80", freq);
 | 
			
		||||
	} else if (strcmp(command, "ECHO") == 0) {
 | 
			
		||||
		msleep(100);
 | 
			
		||||
		sprintf(response, "RSP ECHO 0");
 | 
			
		||||
	} else if (strcmp(command, "POWEROFF") == 0) {
 | 
			
		||||
		set_ta(0);
 | 
			
		||||
		// turn off transmitter/demod
 | 
			
		||||
		// set_upper_ready(false);
 | 
			
		||||
 | 
			
		||||
		sprintf(response, "RSP POWEROFF 0");
 | 
			
		||||
	} else if (strcmp(command, "POWERON") == 0) {
 | 
			
		||||
		// turn on transmitter/demod
 | 
			
		||||
		if (!mTxFreq || !mRxFreq)
 | 
			
		||||
			sprintf(response, "RSP POWERON 1");
 | 
			
		||||
		else {
 | 
			
		||||
			sprintf(response, "RSP POWERON 0");
 | 
			
		||||
			if (!mOn) {
 | 
			
		||||
				// Prepare for thread start
 | 
			
		||||
				mPower = -20;
 | 
			
		||||
				// start_ms();
 | 
			
		||||
				set_upper_ready(true);
 | 
			
		||||
 | 
			
		||||
				writeClockInterface();
 | 
			
		||||
				mOn = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if (strcmp(command, "SETMAXDLY") == 0) {
 | 
			
		||||
		//set expected maximum time-of-arrival
 | 
			
		||||
		int maxDelay;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &maxDelay);
 | 
			
		||||
		mMaxExpectedDelay = maxDelay; // 1 GSM symbol is approx. 1 km
 | 
			
		||||
		sprintf(response, "RSP SETMAXDLY 0 %d", maxDelay);
 | 
			
		||||
	} else if (strcmp(command, "SETRXGAIN") == 0) {
 | 
			
		||||
		//set expected maximum time-of-arrival
 | 
			
		||||
		int newGain;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &newGain);
 | 
			
		||||
		newGain = setRxGain(newGain);
 | 
			
		||||
		sprintf(response, "RSP SETRXGAIN 0 %d", newGain);
 | 
			
		||||
	} else if (strcmp(command, "NOISELEV") == 0) {
 | 
			
		||||
		if (mOn) {
 | 
			
		||||
			float lev = 0; //mStates[chan].mNoiseLev;
 | 
			
		||||
			sprintf(response, "RSP NOISELEV 0 %d", (int)round(20.0 * log10(rxFullScale / lev)));
 | 
			
		||||
		} else {
 | 
			
		||||
			sprintf(response, "RSP NOISELEV 1  0");
 | 
			
		||||
		}
 | 
			
		||||
	} else if (!strcmp(command, "SETPOWER")) {
 | 
			
		||||
		// set output power in dB
 | 
			
		||||
		int dbPwr;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbPwr);
 | 
			
		||||
		if (!mOn)
 | 
			
		||||
			sprintf(response, "RSP SETPOWER 1 %d", dbPwr);
 | 
			
		||||
		else {
 | 
			
		||||
			mPower = dbPwr;
 | 
			
		||||
			setPowerAttenuation(mPower);
 | 
			
		||||
			sprintf(response, "RSP SETPOWER 0 %d", dbPwr);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (!strcmp(command, "ADJPOWER")) {
 | 
			
		||||
		// adjust power in dB steps
 | 
			
		||||
		int dbStep;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &dbStep);
 | 
			
		||||
		if (!mOn)
 | 
			
		||||
			sprintf(response, "RSP ADJPOWER 1 %d", mPower);
 | 
			
		||||
		else {
 | 
			
		||||
			mPower += dbStep;
 | 
			
		||||
			setPowerAttenuation(mPower);
 | 
			
		||||
			sprintf(response, "RSP ADJPOWER 0 %d", mPower);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (strcmp(command, "RXTUNE") == 0) {
 | 
			
		||||
		// tune receiver
 | 
			
		||||
		int freqKhz;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqKhz);
 | 
			
		||||
		mRxFreq = freqKhz * 1e3;
 | 
			
		||||
		if (!tuneRx(mRxFreq)) {
 | 
			
		||||
			LOG(ALERT) << "RX failed to tune";
 | 
			
		||||
			sprintf(response, "RSP RXTUNE 1 %d", freqKhz);
 | 
			
		||||
		} else
 | 
			
		||||
			sprintf(response, "RSP RXTUNE 0 %d", freqKhz);
 | 
			
		||||
	} else if (strcmp(command, "TXTUNE") == 0) {
 | 
			
		||||
		// tune txmtr
 | 
			
		||||
		int freqKhz;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &freqKhz);
 | 
			
		||||
		mTxFreq = freqKhz * 1e3;
 | 
			
		||||
		if (!tuneTx(mTxFreq)) {
 | 
			
		||||
			LOG(ALERT) << "TX failed to tune";
 | 
			
		||||
			sprintf(response, "RSP TXTUNE 1 %d", freqKhz);
 | 
			
		||||
		} else
 | 
			
		||||
			sprintf(response, "RSP TXTUNE 0 %d", freqKhz);
 | 
			
		||||
	} else if (!strcmp(command, "SETTSC")) {
 | 
			
		||||
		// set TSC
 | 
			
		||||
		unsigned TSC;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC);
 | 
			
		||||
		if (mOn)
 | 
			
		||||
			sprintf(response, "RSP SETTSC 1 %d", TSC);
 | 
			
		||||
		// else if (chan && (TSC != mTSC))
 | 
			
		||||
		// 	sprintf(response, "RSP SETTSC 1 %d", TSC);
 | 
			
		||||
		else {
 | 
			
		||||
			mTSC = TSC;
 | 
			
		||||
			//generateMidamble(rx_sps, TSC);
 | 
			
		||||
			sprintf(response, "RSP SETTSC 0 %d", TSC);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (!strcmp(command, "GETBSIC")) {
 | 
			
		||||
		if (mBSIC < 0)
 | 
			
		||||
			sprintf(response, "RSP GETBSIC 1");
 | 
			
		||||
		else
 | 
			
		||||
			sprintf(response, "RSP GETBSIC 0 %d", mBSIC);
 | 
			
		||||
	} else if (strcmp(command, "SETSLOT") == 0) {
 | 
			
		||||
		// set TSC
 | 
			
		||||
		int corrCode;
 | 
			
		||||
		int timeslot;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d %d", cmdcheck, command, ×lot, &corrCode);
 | 
			
		||||
		if ((timeslot < 0) || (timeslot > 7)) {
 | 
			
		||||
			LOG(WARNING) << "bogus message on control interface";
 | 
			
		||||
			sprintf(response, "RSP SETSLOT 1 %d %d", timeslot, corrCode);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		sprintf(response, "RSP SETSLOT 0 %d %d", timeslot, corrCode);
 | 
			
		||||
	} else if (!strcmp(command, "SETRXMASK")) {
 | 
			
		||||
		int slot;
 | 
			
		||||
		unsigned long long mask;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d 0x%llx", cmdcheck, command, &slot, &mask);
 | 
			
		||||
		if ((slot < 0) || (slot > 7)) {
 | 
			
		||||
			sprintf(response, "RSP SETRXMASK 1");
 | 
			
		||||
		} else {
 | 
			
		||||
			mRxSlotMask[slot] = mask;
 | 
			
		||||
			sprintf(response, "RSP SETRXMASK 0 %d 0x%llx", slot, mask);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (!strcmp(command, "SYNC")) {
 | 
			
		||||
		// msleep(10);
 | 
			
		||||
		sprintf(response, "RSP SYNC 0");
 | 
			
		||||
		mMaxExpectedDelay = 48;
 | 
			
		||||
		// setRxGain(30);
 | 
			
		||||
		// msleep(10);
 | 
			
		||||
	} else if (!strcmp(command, "SETTA")) {
 | 
			
		||||
		int ta;
 | 
			
		||||
		sscanf(buffer, "%3s %s %d", cmdcheck, command, &ta);
 | 
			
		||||
		set_ta(ta);
 | 
			
		||||
		sprintf(response, "RSP SETTA 0 %d", ta);
 | 
			
		||||
	} else {
 | 
			
		||||
		LOG(WARNING) << "bogus command " << command << " on control interface.";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//mCtrlSockets[chan]->write(response, strlen(response) + 1);
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +20,6 @@
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
#include "ms.h"
 | 
			
		||||
 | 
			
		||||
void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale);
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
#include "ms.h"
 | 
			
		||||
#include "sigProcLib.h"
 | 
			
		||||
#include "signalVector.h"
 | 
			
		||||
#include "grgsm_vitac/grgsm_vitac.h"
 | 
			
		||||
@@ -38,8 +38,7 @@ extern "C" {
 | 
			
		||||
#define DBGLG2(...) std::cerr
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) static bool decode_sch(float *bits,
 | 
			
		||||
											 bool update_global_clock)
 | 
			
		||||
static bool decode_sch(float *bits, bool update_global_clock)
 | 
			
		||||
{
 | 
			
		||||
	struct sch_info sch;
 | 
			
		||||
	ubit_t info[GSM_SCH_INFO_LEN];
 | 
			
		||||
@@ -70,7 +69,7 @@ static void check_rcv_fn(GSM::Time t, bool first, unsigned int &lastfn, unsigned
 | 
			
		||||
		fnbm = 1 << 0;
 | 
			
		||||
		first = false;
 | 
			
		||||
	}
 | 
			
		||||
	if (!first && t.FN() != lastfn) {
 | 
			
		||||
	if (!first && t.FN() != (int)lastfn) {
 | 
			
		||||
		if (fnbm != 255)
 | 
			
		||||
			std::cerr << "rx " << lastfn << ":" << fnbm << " " << __builtin_popcount(fnbm) << std::endl;
 | 
			
		||||
		lastfn = t.FN();
 | 
			
		||||
@@ -80,21 +79,17 @@ static void check_rcv_fn(GSM::Time t, bool first, unsigned int &lastfn, unsigned
 | 
			
		||||
	fnbm |= 1 << t.TN();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) static void
 | 
			
		||||
handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
 | 
			
		||||
static void handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
 | 
			
		||||
{
 | 
			
		||||
	memset(burst.begin(), 0, burst.size() * sizeof(std::complex<float>));
 | 
			
		||||
	auto is_sch = gsm_sch_check_fn(e.gsmts.FN()) && e.gsmts.TN() == 0;
 | 
			
		||||
	auto is_fcch = gsm_fcch_check_fn(e.gsmts.FN()) && e.gsmts.TN() == 0;
 | 
			
		||||
 | 
			
		||||
	// if (is_sch)
 | 
			
		||||
	// 	return;
 | 
			
		||||
	std::fill(burst.begin(), burst.begin() + burst.size(), 0.0);
 | 
			
		||||
	const auto is_sch = gsm_sch_check_ts(e.gsmts.TN(), e.gsmts.FN());
 | 
			
		||||
	const auto is_fcch = gsm_fcch_check_ts(e.gsmts.TN(), e.gsmts.FN());
 | 
			
		||||
 | 
			
		||||
	if (is_fcch)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (is_sch) {
 | 
			
		||||
		unsigned char outbin[148];
 | 
			
		||||
		char outbin[148];
 | 
			
		||||
		convert_and_scale_default<float, int16_t>(burst.begin(), e.burst, ONE_TS_BURST_LEN * 2);
 | 
			
		||||
		std::stringstream dbgout;
 | 
			
		||||
#if 0
 | 
			
		||||
@@ -125,7 +120,7 @@ handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
 | 
			
		||||
			SoftVector bits;
 | 
			
		||||
			bits.resize(148);
 | 
			
		||||
			for (int i = 0; i < 148; i++) {
 | 
			
		||||
				bits[i] = (!outbin[i]) < 1 ? -1 : 1;
 | 
			
		||||
				bits[i] = (!outbin[i]); // < 1 ? -1 : 1;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto rv = decode_sch(bits.begin(), false);
 | 
			
		||||
@@ -142,7 +137,7 @@ handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
 | 
			
		||||
	// std::cerr << "@" << tsc << " " << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << " "
 | 
			
		||||
	// 	  << std::endl;
 | 
			
		||||
 | 
			
		||||
	unsigned char outbin[148];
 | 
			
		||||
	char outbin[148];
 | 
			
		||||
	auto ss = reinterpret_cast<std::complex<float> *>(burst.begin());
 | 
			
		||||
	float ncmax, dcmax;
 | 
			
		||||
	std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR], chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
@@ -158,15 +153,15 @@ handle_it(one_burst &e, signalVector &burst, unsigned int tsc, int scale)
 | 
			
		||||
	else
 | 
			
		||||
		detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
 | 
			
		||||
	;
 | 
			
		||||
 | 
			
		||||
	auto bits = SoftVector(148);
 | 
			
		||||
	for (int i = 0; i < 148; i++)
 | 
			
		||||
		(bits)[i] = outbin[i] < 1 ? -1 : 1;
 | 
			
		||||
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
	// auto bits = SoftVector(148);
 | 
			
		||||
	// for (int i = 0; i < 148; i++)
 | 
			
		||||
	// 	(bits)[i] = outbin[i] < 1 ? -1 : 1;
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale)
 | 
			
		||||
void rcv_bursts_test(rx_queue_t *q, unsigned int *tsc, int scale)
 | 
			
		||||
{
 | 
			
		||||
	static bool first = true;
 | 
			
		||||
	unsigned int lastfn = 0;
 | 
			
		||||
@@ -202,10 +197,11 @@ __attribute__((xray_always_instrument)) __attribute__((noinline)) void rcv_burst
 | 
			
		||||
		check_rcv_fn(e.gsmts, first, lastfn, fnbm);
 | 
			
		||||
 | 
			
		||||
		handle_it(e, burst, *tsc, scale);
 | 
			
		||||
 | 
			
		||||
		// rv = detectSCHBurst(*burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
 | 
			
		||||
		// if (rv > 0)
 | 
			
		||||
		// 	std::cerr << "#" << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << std::endl;
 | 
			
		||||
		// sched_yield();
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		rv = detectSCHBurst(*burst, 4, 4, sch_detect_type::SCH_DETECT_FULL, &ebp);
 | 
			
		||||
		if (rv > 0)
 | 
			
		||||
			std::cerr << "#" << e.gsmts.FN() << ":" << e.gsmts.TN() << " " << ebp.toa << std::endl;
 | 
			
		||||
		sched_yield();
 | 
			
		||||
#endif
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <future>
 | 
			
		||||
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
#include "ms.h"
 | 
			
		||||
#include "grgsm_vitac/grgsm_vitac.h"
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
@@ -51,16 +51,15 @@ extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define PRINT_Q_OVERFLOW
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) bool ms_trx::decode_sch(float *bits,
 | 
			
		||||
											  bool update_global_clock)
 | 
			
		||||
bool ms_trx::decode_sch(float *bits, bool update_global_clock)
 | 
			
		||||
{
 | 
			
		||||
	int fn;
 | 
			
		||||
	struct sch_info sch;
 | 
			
		||||
	ubit_t info[GSM_SCH_INFO_LEN];
 | 
			
		||||
	sbit_t data[GSM_SCH_CODED_LEN];
 | 
			
		||||
 | 
			
		||||
	float_to_sbit(&bits[3], &data[0], 62, 39);
 | 
			
		||||
	float_to_sbit(&bits[106], &data[39], 62, 39);
 | 
			
		||||
	float_to_sbit(&bits[3], &data[0], 1, 39);
 | 
			
		||||
	float_to_sbit(&bits[106], &data[39], 1, 39);
 | 
			
		||||
 | 
			
		||||
	if (!gsm_sch_decode(info, data)) {
 | 
			
		||||
		gsm_sch_parse(info, &sch);
 | 
			
		||||
@@ -100,8 +99,7 @@ __attribute__((xray_always_instrument)) __attribute__((noinline)) bool ms_trx::d
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
#endif
 | 
			
		||||
		// auto sch11 = gsm_sch_check_fn(fn + 11);
 | 
			
		||||
		// DBGLG() << "next sch: "<< (sch11 ? "11":"10")<<" first ts " << first_sch_buf_rcv_ts << std::endl;
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
@@ -135,15 +133,13 @@ void ms_trx::maybe_update_gain(one_burst &brst)
 | 
			
		||||
	gain_check = (gain_check + 1) % avgburst_num;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static unsigned char sch_demod_bits[148];
 | 
			
		||||
static char sch_demod_bits[148];
 | 
			
		||||
 | 
			
		||||
bool ms_trx::handle_sch_or_nb()
 | 
			
		||||
{
 | 
			
		||||
	one_burst brst;
 | 
			
		||||
	auto current_gsm_time = timekeeper.gsmtime();
 | 
			
		||||
	auto is_sch = gsm_sch_check_fn(current_gsm_time.FN()) && current_gsm_time.TN() == 0;
 | 
			
		||||
	auto is_fcch = gsm_fcch_check_fn(current_gsm_time.FN()) && current_gsm_time.TN() == 0;
 | 
			
		||||
#pragma unused(is_fcch)
 | 
			
		||||
	const auto current_gsm_time = timekeeper.gsmtime();
 | 
			
		||||
	const auto is_sch = gsm_sch_check_ts(current_gsm_time.TN(), current_gsm_time.FN());
 | 
			
		||||
 | 
			
		||||
	//either pass burst to upper layer for demod, OR pass demodded SCH to upper layer so we don't waste time processing it twice
 | 
			
		||||
	brst.gsmts = current_gsm_time;
 | 
			
		||||
@@ -154,7 +150,6 @@ bool ms_trx::handle_sch_or_nb()
 | 
			
		||||
		handle_sch(false);
 | 
			
		||||
		memcpy(brst.sch_bits, sch_demod_bits, sizeof(sch_demod_bits));
 | 
			
		||||
	}
 | 
			
		||||
	// auto pushok = rxqueue.spsc_push(&brst);
 | 
			
		||||
#ifndef SYNCTHINGONLY
 | 
			
		||||
	if (upper_is_ready) { // this is blocking, so only submit if there is a reader - only if upper exists!
 | 
			
		||||
#endif
 | 
			
		||||
@@ -164,10 +159,6 @@ bool ms_trx::handle_sch_or_nb()
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	// #ifdef PRINT_Q_OVERFLOW
 | 
			
		||||
	// 	if (!pushok)
 | 
			
		||||
	// 		std::cout << "F" << std::endl;
 | 
			
		||||
	// #endif
 | 
			
		||||
	if (do_auto_gain)
 | 
			
		||||
		maybe_update_gain(brst);
 | 
			
		||||
 | 
			
		||||
@@ -185,10 +176,10 @@ bool ms_trx::handle_sch(bool is_first_sch_acq)
 | 
			
		||||
	const auto ss = reinterpret_cast<std::complex<float> *>(which_out_buffer);
 | 
			
		||||
	std::complex<float> channel_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
 | 
			
		||||
	float max_corr = 0;
 | 
			
		||||
	int start;
 | 
			
		||||
	memset((void *)&sch_acq_buffer[0], 0, sizeof(sch_acq_buffer));
 | 
			
		||||
	if (is_first_sch_acq) {
 | 
			
		||||
		float max_corr = 0;
 | 
			
		||||
		convert_and_scale<float, int16_t>(which_out_buffer, which_in_buffer, buf_len * 2,
 | 
			
		||||
						  1.f / float(rxFullScale));
 | 
			
		||||
		start = get_sch_buffer_chan_imp_resp(ss, &channel_imp_resp[0], buf_len, &max_corr);
 | 
			
		||||
@@ -204,7 +195,7 @@ bool ms_trx::handle_sch(bool is_first_sch_acq)
 | 
			
		||||
 | 
			
		||||
	SoftVector bitss(148);
 | 
			
		||||
	for (int i = 0; i < 148; i++) {
 | 
			
		||||
		bitss[i] = (!sch_demod_bits[i]) < 1 ? -1 : 1;
 | 
			
		||||
		bitss[i] = (sch_demod_bits[i]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto sch_decode_success = decode_sch(bitss.begin(), is_first_sch_acq);
 | 
			
		||||
@@ -228,7 +219,7 @@ bool ms_trx::handle_sch(bool is_first_sch_acq)
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd)
 | 
			
		||||
SCH_STATE ms_trx::search_for_sch(dev_buf_t *rcd)
 | 
			
		||||
{
 | 
			
		||||
	static unsigned int sch_pos = 0;
 | 
			
		||||
	if (sch_thread_done)
 | 
			
		||||
@@ -251,7 +242,7 @@ __attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_
 | 
			
		||||
			auto ptr = reinterpret_cast<const int16_t *>(first_sch_buf);
 | 
			
		||||
			const auto target_val = rxFullScale / 8;
 | 
			
		||||
			float sum = 0;
 | 
			
		||||
			for (int i = 0; i < SCH_LEN_SPS * 2; i++)
 | 
			
		||||
			for (unsigned int i = 0; i < SCH_LEN_SPS * 2; i++)
 | 
			
		||||
				sum += std::abs(ptr[i]);
 | 
			
		||||
			sum /= SCH_LEN_SPS * 2;
 | 
			
		||||
 | 
			
		||||
@@ -271,14 +262,15 @@ __attribute__((xray_never_instrument)) SCH_STATE ms_trx::search_for_sch(dev_buf_
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto spsmax = rcd->actual_samples_per_buffer();
 | 
			
		||||
	if (to_copy > spsmax)
 | 
			
		||||
	if (to_copy > (unsigned int)spsmax)
 | 
			
		||||
		sch_pos += rcd->readall(first_sch_buf + sch_pos);
 | 
			
		||||
	else
 | 
			
		||||
		sch_pos += rcd->read_n(first_sch_buf + sch_pos, 0, to_copy);
 | 
			
		||||
 | 
			
		||||
	return SCH_STATE::SEARCHING;
 | 
			
		||||
}
 | 
			
		||||
__attribute__((optnone)) void ms_trx::grab_bursts(dev_buf_t *rcd)
 | 
			
		||||
 | 
			
		||||
void ms_trx::grab_bursts(dev_buf_t *rcd)
 | 
			
		||||
{
 | 
			
		||||
	// partial burst samples read from the last buffer
 | 
			
		||||
	static int partial_rdofs = 0;
 | 
			
		||||
@@ -292,7 +284,7 @@ __attribute__((optnone)) void ms_trx::grab_bursts(dev_buf_t *rcd)
 | 
			
		||||
		const auto fracts = next_burst_start % ONE_TS_BURST_LEN;
 | 
			
		||||
		to_skip = ONE_TS_BURST_LEN - fracts;
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < fullts; i++)
 | 
			
		||||
		for (unsigned int i = 0; i < fullts; i++)
 | 
			
		||||
			timekeeper.inc_and_update(first_sch_ts_start + i * ONE_TS_BURST_LEN);
 | 
			
		||||
 | 
			
		||||
		if (fracts)
 | 
			
		||||
@@ -311,9 +303,8 @@ __attribute__((optnone)) void ms_trx::grab_bursts(dev_buf_t *rcd)
 | 
			
		||||
 | 
			
		||||
	if (partial_rdofs) {
 | 
			
		||||
		auto first_remaining = ONE_TS_BURST_LEN - partial_rdofs;
 | 
			
		||||
		// memcpy(burst_copy_buffer, partial_buf, partial_rdofs * sizeof(blade_sample_type));
 | 
			
		||||
		auto rd = rcd->read_n(burst_copy_buffer + partial_rdofs, 0, first_remaining);
 | 
			
		||||
		if (rd != first_remaining) {
 | 
			
		||||
		if (rd != (int)first_remaining) {
 | 
			
		||||
			partial_rdofs += rd;
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,301 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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 "sigProcLib.h"
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
#include <signalVector.h>
 | 
			
		||||
#include <radioVector.h>
 | 
			
		||||
#include <radioInterface.h>
 | 
			
		||||
#include "grgsm_vitac/grgsm_vitac.h"
 | 
			
		||||
#include "ms_rx_upper.h"
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include "sch.h"
 | 
			
		||||
#include "convolve.h"
 | 
			
		||||
#include "convert.h"
 | 
			
		||||
#include "proto_trxd.h"
 | 
			
		||||
 | 
			
		||||
void __lsan_do_recoverable_leak_check();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace trxcon
 | 
			
		||||
{
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <trxcon/trx_if.h>
 | 
			
		||||
}
 | 
			
		||||
trx_instance *trxcon_instance; // local handle
 | 
			
		||||
static tx_queue_t txq;
 | 
			
		||||
} // namespace trxcon
 | 
			
		||||
 | 
			
		||||
#ifdef LOG
 | 
			
		||||
#undef LOG
 | 
			
		||||
#define LOG(...) upper_trx::dummy_log()
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void upper_trx::start_threads()
 | 
			
		||||
{
 | 
			
		||||
	thr_control = std::thread([this] {
 | 
			
		||||
		set_name_aff_sched("upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR));
 | 
			
		||||
		while (1) {
 | 
			
		||||
			driveControl();
 | 
			
		||||
			pthread_testcancel();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	msleep(1);
 | 
			
		||||
	thr_tx = std::thread([this] {
 | 
			
		||||
		set_name_aff_sched("upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
 | 
			
		||||
 | 
			
		||||
		while (1) {
 | 
			
		||||
			driveTx();
 | 
			
		||||
			pthread_testcancel();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// atomic ensures data is not written to q until loop reads
 | 
			
		||||
	start_ms();
 | 
			
		||||
 | 
			
		||||
	set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5);
 | 
			
		||||
	while (1) {
 | 
			
		||||
		// set_upper_ready(true);
 | 
			
		||||
		driveReceiveFIFO();
 | 
			
		||||
		pthread_testcancel();
 | 
			
		||||
		osmo_select_main(1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// std::thread([this] {
 | 
			
		||||
	// 	set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10);
 | 
			
		||||
 | 
			
		||||
	// 	while (1) {
 | 
			
		||||
	// 		std::this_thread::sleep_for(std::chrono::seconds{ 5 });
 | 
			
		||||
	// 		__lsan_do_recoverable_leak_check();
 | 
			
		||||
	// 	}
 | 
			
		||||
	// }).detach();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::start_ms()
 | 
			
		||||
{
 | 
			
		||||
	ms_trx::start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SoftVector *upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset) __attribute__((optnone))
 | 
			
		||||
{
 | 
			
		||||
	float pow, avg = 1.0;
 | 
			
		||||
	static SoftVector bits(148);
 | 
			
		||||
	static complex workbuf[40 + 625 + 40];
 | 
			
		||||
	static signalVector sv(workbuf, 40, 625);
 | 
			
		||||
 | 
			
		||||
	GSM::Time burst_time;
 | 
			
		||||
	auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[40]);
 | 
			
		||||
 | 
			
		||||
	memset((void *)&workbuf[0], 0, sizeof(workbuf));
 | 
			
		||||
	// assert(sv.begin() == &workbuf[40]);
 | 
			
		||||
 | 
			
		||||
	one_burst e;
 | 
			
		||||
	unsigned char outbin[148];
 | 
			
		||||
 | 
			
		||||
	std::stringstream dbgout;
 | 
			
		||||
 | 
			
		||||
	while (!rxqueue.spsc_pop(&e)) {
 | 
			
		||||
		rxqueue.spsc_prep_pop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	burst_time = e.gsmts;
 | 
			
		||||
	wTime = burst_time;
 | 
			
		||||
 | 
			
		||||
	auto is_sch = (burst_time.TN() == 0 && gsm_sch_check_fn(burst_time.FN()));
 | 
			
		||||
	auto is_fcch = (burst_time.TN() == 0 && gsm_fcch_check_fn(burst_time.FN()));
 | 
			
		||||
 | 
			
		||||
	if (is_fcch) {
 | 
			
		||||
		// return trash
 | 
			
		||||
		// fprintf(stderr, "c %d\n",burst_time.FN());
 | 
			
		||||
		return &bits;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (is_sch) {
 | 
			
		||||
		for (int i = 0; i < 148; i++)
 | 
			
		||||
			(bits)[i] = (!e.sch_bits[i]) < 1 ? -1 : 1;
 | 
			
		||||
		RSSI = 10;
 | 
			
		||||
		timingOffset = 0;
 | 
			
		||||
		// fprintf(stderr, "s %d\n", burst_time.FN());
 | 
			
		||||
		return &bits;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto ts = trxcon::trxcon_instance->ts_list[burst_time.TN()];
 | 
			
		||||
	if (ts == NULL || ts->mf_layout == NULL)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
 | 
			
		||||
 | 
			
		||||
	pow = energyDetect(sv, 20 * rx_sps);
 | 
			
		||||
	if (pow < -1) {
 | 
			
		||||
		LOG(ALERT) << "Received empty burst";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	avg = sqrt(pow);
 | 
			
		||||
	{
 | 
			
		||||
		float ncmax, dcmax;
 | 
			
		||||
		std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
		std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
		auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
 | 
			
		||||
		auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
 | 
			
		||||
		auto is_nb = ncmax > dcmax;
 | 
			
		||||
 | 
			
		||||
		// std::cerr << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start
 | 
			
		||||
		// 	  << " o db: " << dummy_burst_start << std::endl;
 | 
			
		||||
 | 
			
		||||
		normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39;
 | 
			
		||||
		normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39;
 | 
			
		||||
 | 
			
		||||
		// fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN());
 | 
			
		||||
		// if (is_nb)
 | 
			
		||||
		detect_burst(ss, &chan_imp_resp[0], normal_burst_start, outbin);
 | 
			
		||||
		// else
 | 
			
		||||
		// 	detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < 148; i++)
 | 
			
		||||
			(bits)[i] = (outbin[i]) < 1 ? -1 : 1;
 | 
			
		||||
	}
 | 
			
		||||
	RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
 | 
			
		||||
	timingOffset = (int)round(0);
 | 
			
		||||
 | 
			
		||||
	return &bits;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::driveReceiveFIFO()
 | 
			
		||||
{
 | 
			
		||||
	int RSSI;
 | 
			
		||||
	int TOA; // in 1/256 of a symbol
 | 
			
		||||
	GSM::Time burstTime;
 | 
			
		||||
 | 
			
		||||
	if (!mOn)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	SoftVector *rxBurst = pullRadioVector(burstTime, RSSI, TOA);
 | 
			
		||||
 | 
			
		||||
	if (rxBurst) {
 | 
			
		||||
		trxd_from_trx response;
 | 
			
		||||
		response.ts = burstTime.TN();
 | 
			
		||||
		response.fn = htonl(burstTime.FN());
 | 
			
		||||
		response.rssi = RSSI;
 | 
			
		||||
		response.toa = htons(TOA);
 | 
			
		||||
 | 
			
		||||
		SoftVector::const_iterator burstItr = rxBurst->begin();
 | 
			
		||||
		if (burstTime.TN() == 0 && gsm_sch_check_fn(burstTime.FN())) {
 | 
			
		||||
			clamp_array(rxBurst->begin(), 148, 1.5f);
 | 
			
		||||
			for (unsigned int i = 0; i < gSlotLen; i++) {
 | 
			
		||||
				auto val = *burstItr++;
 | 
			
		||||
				auto vval = isnan(val) ? 0 : val;
 | 
			
		||||
				((int8_t *)response.symbols)[i] = round((vval - 0.5) * 64.0);
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// invert and fix to +-127 sbits
 | 
			
		||||
			for (int i = 0; i < 148; i++)
 | 
			
		||||
				((int8_t *)response.symbols)[i] = *burstItr++ > 0.0f ? -127 : 127;
 | 
			
		||||
		}
 | 
			
		||||
		trxcon::trx_data_rx_handler(trxcon::trxcon_instance, (uint8_t *)&response);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::driveTx()
 | 
			
		||||
{
 | 
			
		||||
	trxd_to_trx e;
 | 
			
		||||
	while (!trxcon::txq.spsc_pop(&e)) {
 | 
			
		||||
		trxcon::txq.spsc_prep_pop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	trxd_to_trx *burst = &e;
 | 
			
		||||
 | 
			
		||||
	auto proper_fn = ntohl(burst->fn);
 | 
			
		||||
	// std::cerr << "got burst!" << proper_fn << ":" << burst->ts
 | 
			
		||||
	// 	  << " current: " << timekeeper.gsmtime().FN()
 | 
			
		||||
	// 	  << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)proper_fn)
 | 
			
		||||
	// 	  << std::endl;
 | 
			
		||||
 | 
			
		||||
	auto currTime = GSM::Time(proper_fn, burst->ts);
 | 
			
		||||
	int RSSI = (int)burst->txlev;
 | 
			
		||||
 | 
			
		||||
	static BitVector newBurst(gSlotLen);
 | 
			
		||||
	BitVector::iterator itr = newBurst.begin();
 | 
			
		||||
	auto *bufferItr = burst->symbols;
 | 
			
		||||
	while (itr < newBurst.end())
 | 
			
		||||
		*itr++ = *bufferItr++;
 | 
			
		||||
 | 
			
		||||
	auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
 | 
			
		||||
	scaleVector(*txburst, txFullScale * 0.7 /* * pow(10, -RSSI / 10)*/);
 | 
			
		||||
 | 
			
		||||
	// float -> int16
 | 
			
		||||
	blade_sample_type burst_buf[txburst->size()];
 | 
			
		||||
	convert_and_scale<int16_t, float>(burst_buf, txburst->begin(), txburst->size() * 2, 1);
 | 
			
		||||
 | 
			
		||||
	// auto check = signalVector(txburst->size(), 40);
 | 
			
		||||
	// convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2);
 | 
			
		||||
	// estim_burst_params ebp;
 | 
			
		||||
	// auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp);
 | 
			
		||||
	// if(d)
 | 
			
		||||
	// 	std::cerr << "RACH D! " << ebp.toa << std::endl;
 | 
			
		||||
	// else
 | 
			
		||||
	// 	std::cerr << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
 | 
			
		||||
 | 
			
		||||
	// memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
 | 
			
		||||
 | 
			
		||||
	submit_burst(burst_buf, txburst->size(), currTime);
 | 
			
		||||
	delete txburst;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trxc_main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	pthread_setname_np(pthread_self(), "main_trxc");
 | 
			
		||||
 | 
			
		||||
	convolve_init();
 | 
			
		||||
	convert_init();
 | 
			
		||||
	sigProcLibSetup();
 | 
			
		||||
	initvita();
 | 
			
		||||
 | 
			
		||||
	int status = 0;
 | 
			
		||||
	auto trx = new upper_trx();
 | 
			
		||||
	trx->do_auto_gain = true;
 | 
			
		||||
 | 
			
		||||
	status = trx->init_dev_and_streams(0, 0);
 | 
			
		||||
	trx->start_threads();
 | 
			
		||||
 | 
			
		||||
	return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
void init_external_transceiver(struct trx_instance *trx, int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	trxcon::trxcon_instance = (trxcon::trx_instance *)trx;
 | 
			
		||||
	std::cout << "init?" << std::endl;
 | 
			
		||||
	trxc_main(argc, argv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void close_external_transceiver(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	std::cout << "Shutting down transceiver..." << std::endl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void tx_external_transceiver(uint8_t *burst)
 | 
			
		||||
{
 | 
			
		||||
	trxcon::txq.spsc_push((trxd_to_trx *)burst);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,111 +0,0 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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 <netdb.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
 | 
			
		||||
#include "GSMCommon.h"
 | 
			
		||||
#include "radioClock.h"
 | 
			
		||||
#include "syncthing.h"
 | 
			
		||||
#include "l1if.h"
 | 
			
		||||
 | 
			
		||||
using tx_queue_t = spsc_cond<8 * 1, trxd_to_trx, true, false>;
 | 
			
		||||
class upper_trx : public ms_trx {
 | 
			
		||||
	int rx_sps, tx_sps;
 | 
			
		||||
 | 
			
		||||
	bool mOn; ///< flag to indicate that transceiver is powered on
 | 
			
		||||
	double mTxFreq; ///< the transmit frequency
 | 
			
		||||
	double mRxFreq; ///< the receive frequency
 | 
			
		||||
	int mPower; ///< the transmit power in dB
 | 
			
		||||
	unsigned mMaxExpectedDelay; ///< maximum TOA offset in GSM symbols
 | 
			
		||||
	unsigned long long mRxSlotMask[8]; ///< MS - enabled multiframe slot mask
 | 
			
		||||
 | 
			
		||||
	int mCtrlSockets;
 | 
			
		||||
	sockaddr_in ctrldest;
 | 
			
		||||
	sockaddr ctrlsrc;
 | 
			
		||||
 | 
			
		||||
	void openudp(int *mSocketFD, unsigned short localPort, const char *wlocalIP)
 | 
			
		||||
	{
 | 
			
		||||
		*mSocketFD = socket(AF_INET, SOCK_DGRAM, 0);
 | 
			
		||||
		int on = 1;
 | 
			
		||||
		setsockopt(*mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
 | 
			
		||||
 | 
			
		||||
		struct sockaddr_in address;
 | 
			
		||||
		size_t length = sizeof(address);
 | 
			
		||||
		bzero(&address, length);
 | 
			
		||||
		address.sin_family = AF_INET;
 | 
			
		||||
		address.sin_addr.s_addr = inet_addr(wlocalIP);
 | 
			
		||||
		address.sin_port = htons(localPort);
 | 
			
		||||
		if (bind(*mSocketFD, (struct sockaddr *)&address, length) < 0) {
 | 
			
		||||
			std::cerr << "bind fail!" << std::endl;
 | 
			
		||||
			exit(0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool resolveAddress(struct sockaddr_in *address, const char *host, unsigned short port)
 | 
			
		||||
	{
 | 
			
		||||
		struct hostent *hp;
 | 
			
		||||
		int h_errno_local;
 | 
			
		||||
 | 
			
		||||
		struct hostent hostData;
 | 
			
		||||
		char tmpBuffer[2048];
 | 
			
		||||
 | 
			
		||||
		auto rc = gethostbyname2_r(host, AF_INET, &hostData, tmpBuffer, sizeof(tmpBuffer), &hp, &h_errno_local);
 | 
			
		||||
		if (hp == NULL || hp->h_addrtype != AF_INET || rc != 0) {
 | 
			
		||||
			std::cerr << "WARNING -- gethostbyname() failed for " << host << ", "
 | 
			
		||||
				  << hstrerror(h_errno_local);
 | 
			
		||||
			exit(0);
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		address->sin_family = hp->h_addrtype;
 | 
			
		||||
		assert(sizeof(address->sin_addr) == hp->h_length);
 | 
			
		||||
		memcpy(&(address->sin_addr), hp->h_addr_list[0], hp->h_length);
 | 
			
		||||
		address->sin_port = htons(port);
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void driveControl();
 | 
			
		||||
	void driveReceiveFIFO();
 | 
			
		||||
	void driveTx();
 | 
			
		||||
	void commandhandler(char *buffer, char *response);
 | 
			
		||||
	void writeClockInterface(){};
 | 
			
		||||
 | 
			
		||||
	SoftVector *pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset);
 | 
			
		||||
 | 
			
		||||
	std::thread thr_control, thr_rx, thr_tx;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	void start_threads();
 | 
			
		||||
	void start_ms();
 | 
			
		||||
 | 
			
		||||
	upper_trx() : rx_sps(4), tx_sps(4)
 | 
			
		||||
	{
 | 
			
		||||
		auto c_srcport = 6700 + 2 * 0 + 1;
 | 
			
		||||
		auto c_dstport = 6700 + 2 * 0 + 101;
 | 
			
		||||
 | 
			
		||||
		openudp(&mCtrlSockets, c_srcport, "127.0.0.1");
 | 
			
		||||
		resolveAddress(&ctrldest, "127.0.0.1", c_dstport);
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										496
									
								
								Transceiver52M/ms/ms_upper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										496
									
								
								Transceiver52M/ms/ms_upper.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,496 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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 "sigProcLib.h"
 | 
			
		||||
#include "ms.h"
 | 
			
		||||
#include <signalVector.h>
 | 
			
		||||
#include <radioVector.h>
 | 
			
		||||
#include <radioInterface.h>
 | 
			
		||||
#include <grgsm_vitac/grgsm_vitac.h>
 | 
			
		||||
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <getopt.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <fenv.h>
 | 
			
		||||
 | 
			
		||||
#include "sch.h"
 | 
			
		||||
#include "convolve.h"
 | 
			
		||||
#include "convert.h"
 | 
			
		||||
 | 
			
		||||
#ifdef LSANDEBUG
 | 
			
		||||
void __lsan_do_recoverable_leak_check();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#include "ms_upper.h"
 | 
			
		||||
 | 
			
		||||
namespace trxcon
 | 
			
		||||
{
 | 
			
		||||
extern "C" {
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/signal.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/logging.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/bb/trxcon/trxcon.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/trxcon_fsm.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/phyif.h>
 | 
			
		||||
#include <osmocom/bb/trxcon/l1ctl_server.h>
 | 
			
		||||
}
 | 
			
		||||
struct trxcon_inst *g_trxcon;
 | 
			
		||||
// trx_instance *trxcon_instance; // local handle
 | 
			
		||||
struct internal_q_tx_buf {
 | 
			
		||||
	trxcon_phyif_burst_req r;
 | 
			
		||||
	uint8_t buf[148];
 | 
			
		||||
};
 | 
			
		||||
using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>;
 | 
			
		||||
using cmd_queue_t = spsc_cond<8 * 1, trxcon_phyif_cmd, true, false>;
 | 
			
		||||
using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>;
 | 
			
		||||
static tx_queue_t txq;
 | 
			
		||||
static cmd_queue_t cmdq_to_phy;
 | 
			
		||||
static cmdr_queue_t cmdq_from_phy;
 | 
			
		||||
 | 
			
		||||
extern bool trxc_l1ctl_init(void *tallctx);
 | 
			
		||||
 | 
			
		||||
} // namespace trxcon
 | 
			
		||||
extern "C" void trxc_log_init(void *tallctx);
 | 
			
		||||
 | 
			
		||||
#ifdef LOG
 | 
			
		||||
#undef LOG
 | 
			
		||||
#define LOG(...) upper_trx::dummy_log()
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define DBGLG(...) upper_trx::dummy_log()
 | 
			
		||||
 | 
			
		||||
std::atomic<bool> g_exit_flag;
 | 
			
		||||
 | 
			
		||||
void upper_trx::start_threads()
 | 
			
		||||
{
 | 
			
		||||
	thr_control = std::thread([this] {
 | 
			
		||||
		set_name_aff_sched("upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR));
 | 
			
		||||
		while (!g_exit_flag) {
 | 
			
		||||
			driveControl();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	msleep(1);
 | 
			
		||||
	thr_tx = std::thread([this] {
 | 
			
		||||
		set_name_aff_sched("upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
 | 
			
		||||
		while (!g_exit_flag) {
 | 
			
		||||
			driveTx();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// atomic ensures data is not written to q until loop reads
 | 
			
		||||
	start_lower_ms();
 | 
			
		||||
 | 
			
		||||
	set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5);
 | 
			
		||||
	while (!g_exit_flag) {
 | 
			
		||||
		// set_upper_ready(true);
 | 
			
		||||
		driveReceiveFIFO();
 | 
			
		||||
		trxcon::osmo_select_main(1);
 | 
			
		||||
 | 
			
		||||
		trxcon::trxcon_phyif_rsp r;
 | 
			
		||||
		if (trxcon::cmdq_from_phy.spsc_pop(&r)) {
 | 
			
		||||
			DBGLG() << "HAVE RESP:" << r.type << std::endl;
 | 
			
		||||
			trxcon_phyif_handle_rsp(trxcon::g_trxcon, &r);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef LSANDEBUG
 | 
			
		||||
	std::thread([this] {
 | 
			
		||||
		set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10);
 | 
			
		||||
 | 
			
		||||
		while (1) {
 | 
			
		||||
			std::this_thread::sleep_for(std::chrono::seconds{ 5 });
 | 
			
		||||
			__lsan_do_recoverable_leak_check();
 | 
			
		||||
		}
 | 
			
		||||
	}).detach();
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::start_lower_ms()
 | 
			
		||||
{
 | 
			
		||||
	ms_trx::start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// signalvector is owning despite claiming not to, but we can pretend, too..
 | 
			
		||||
static void static_free(void *wData){};
 | 
			
		||||
static void *static_alloc(size_t newSize)
 | 
			
		||||
{
 | 
			
		||||
	return 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset)
 | 
			
		||||
{
 | 
			
		||||
	float pow, avg = 1.0;
 | 
			
		||||
	const auto zero_pad_len = 40; // give the VA some runway for misaligned bursts
 | 
			
		||||
	const auto workbuf_size = zero_pad_len + ONE_TS_BURST_LEN + zero_pad_len;
 | 
			
		||||
	static complex workbuf[workbuf_size];
 | 
			
		||||
 | 
			
		||||
	static signalVector sv(workbuf, zero_pad_len, ONE_TS_BURST_LEN, static_alloc, static_free);
 | 
			
		||||
	one_burst e;
 | 
			
		||||
	auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[zero_pad_len]);
 | 
			
		||||
	std::fill(workbuf, workbuf + workbuf_size, 0);
 | 
			
		||||
	// assert(sv.begin() == &workbuf[40]);
 | 
			
		||||
 | 
			
		||||
	while (!rxqueue.spsc_pop(&e)) {
 | 
			
		||||
		rxqueue.spsc_prep_pop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wTime = e.gsmts;
 | 
			
		||||
 | 
			
		||||
	const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN());
 | 
			
		||||
	const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN());
 | 
			
		||||
 | 
			
		||||
	trxcon::trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) };
 | 
			
		||||
	trxcon::trxcon_phyif_rtr_rsp r = {};
 | 
			
		||||
	trxcon_phyif_handle_rtr_ind(trxcon::g_trxcon, &i, &r);
 | 
			
		||||
	if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE))
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
	if (is_fcch) {
 | 
			
		||||
		// return trash
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (is_sch) {
 | 
			
		||||
		for (int i = 0; i < 148; i++)
 | 
			
		||||
			(demodded_softbits)[i] = (e.sch_bits[i]);
 | 
			
		||||
		RSSI = 10;
 | 
			
		||||
		timingOffset = 0;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
 | 
			
		||||
 | 
			
		||||
	pow = energyDetect(sv, 20 * 4 /*sps*/);
 | 
			
		||||
	if (pow < -1) {
 | 
			
		||||
		LOG(ALERT) << "Received empty burst";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	avg = sqrt(pow);
 | 
			
		||||
	{
 | 
			
		||||
		float ncmax;
 | 
			
		||||
		std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
		auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		float dcmax;
 | 
			
		||||
		std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
 | 
			
		||||
		auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
 | 
			
		||||
		auto is_nb = ncmax > dcmax;
 | 
			
		||||
		// DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start
 | 
			
		||||
		// 	  << " o db: " << dummy_burst_start << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
		normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39;
 | 
			
		||||
		normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39;
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		// fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN());
 | 
			
		||||
		// if (is_nb)
 | 
			
		||||
#endif
 | 
			
		||||
		detect_burst(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits);
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		// else
 | 
			
		||||
		// 	detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
 | 
			
		||||
#endif
 | 
			
		||||
	}
 | 
			
		||||
	RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
 | 
			
		||||
	// FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway...
 | 
			
		||||
	timingOffset = (int)round(0);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::driveReceiveFIFO()
 | 
			
		||||
{
 | 
			
		||||
	int RSSI;
 | 
			
		||||
	int TOA; // in 1/256 of a symbol
 | 
			
		||||
	GSM::Time burstTime;
 | 
			
		||||
 | 
			
		||||
	if (!mOn)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (pullRadioVector(burstTime, RSSI, TOA)) {
 | 
			
		||||
		trxcon::trxcon_phyif_burst_ind bi;
 | 
			
		||||
		bi.fn = burstTime.FN();
 | 
			
		||||
		bi.tn = burstTime.TN();
 | 
			
		||||
		bi.rssi = RSSI;
 | 
			
		||||
		bi.toa256 = TOA;
 | 
			
		||||
		bi.burst = (sbit_t *)demodded_softbits;
 | 
			
		||||
		bi.burst_len = sizeof(demodded_softbits);
 | 
			
		||||
		trxcon_phyif_handle_burst_ind(trxcon::g_trxcon, &bi);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	struct trxcon::trxcon_phyif_rts_ind rts {
 | 
			
		||||
		static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN())
 | 
			
		||||
	};
 | 
			
		||||
	trxcon_phyif_handle_rts_ind(trxcon::g_trxcon, &rts);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void upper_trx::driveTx()
 | 
			
		||||
{
 | 
			
		||||
	trxcon::internal_q_tx_buf e;
 | 
			
		||||
	static BitVector newBurst(sizeof(e.buf));
 | 
			
		||||
	while (!trxcon::txq.spsc_pop(&e)) {
 | 
			
		||||
		trxcon::txq.spsc_prep_pop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// ensure our tx cb is tickled and can exit
 | 
			
		||||
	if (g_exit_flag) {
 | 
			
		||||
		blade_sample_type dummy[10] = {};
 | 
			
		||||
		submit_burst_ts(dummy, 10, 1);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	trxcon::internal_q_tx_buf *burst = &e;
 | 
			
		||||
 | 
			
		||||
#ifdef TXDEBUG
 | 
			
		||||
	DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN()
 | 
			
		||||
		<< " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	auto currTime = GSM::Time(burst->r.fn, burst->r.tn);
 | 
			
		||||
	int RSSI = (int)burst->r.pwr;
 | 
			
		||||
 | 
			
		||||
	BitVector::iterator itr = newBurst.begin();
 | 
			
		||||
	auto *bufferItr = burst->buf;
 | 
			
		||||
	while (itr < newBurst.end())
 | 
			
		||||
		*itr++ = *bufferItr++;
 | 
			
		||||
 | 
			
		||||
	auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
 | 
			
		||||
	scaleVector(*txburst, txFullScale * pow(10, -RSSI / 10));
 | 
			
		||||
 | 
			
		||||
	// float -> int16
 | 
			
		||||
	blade_sample_type burst_buf[txburst->size()];
 | 
			
		||||
	convert_and_scale<int16_t, float>(burst_buf, txburst->begin(), txburst->size() * 2, 1);
 | 
			
		||||
#ifdef TXDEBUG
 | 
			
		||||
	auto check = signalVector(txburst->size(), 40);
 | 
			
		||||
	convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2);
 | 
			
		||||
	estim_burst_params ebp;
 | 
			
		||||
	auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp);
 | 
			
		||||
	if (d)
 | 
			
		||||
		DBGLG() << "RACH D! " << ebp.toa << std::endl;
 | 
			
		||||
	else
 | 
			
		||||
		DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
 | 
			
		||||
 | 
			
		||||
		// memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
 | 
			
		||||
#endif
 | 
			
		||||
	submit_burst(burst_buf, txburst->size(), currTime);
 | 
			
		||||
	delete txburst;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef TXDEBUG
 | 
			
		||||
static const char *cmd2str(trxcon::trxcon_phyif_cmd_type c)
 | 
			
		||||
{
 | 
			
		||||
	switch (c) {
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_RESET:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_RESET";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_POWERON:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_POWERON";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_POWEROFF:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_POWEROFF";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_MEASURE:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_MEASURE";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_SETFREQ_H0";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_SETFREQ_H1";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETSLOT:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_SETSLOT";
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETTA:
 | 
			
		||||
		return "TRXCON_PHYIF_CMDT_SETTA";
 | 
			
		||||
	default:
 | 
			
		||||
		return "UNKNOWN COMMAND!";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void print_cmd(trxcon::trxcon_phyif_cmd_type c)
 | 
			
		||||
{
 | 
			
		||||
	DBGLG() << cmd2str(c) << std::endl;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
bool upper_trx::driveControl()
 | 
			
		||||
{
 | 
			
		||||
	trxcon::trxcon_phyif_rsp r;
 | 
			
		||||
	trxcon::trxcon_phyif_cmd cmd;
 | 
			
		||||
	while (!trxcon::cmdq_to_phy.spsc_pop(&cmd)) {
 | 
			
		||||
		trxcon::cmdq_to_phy.spsc_prep_pop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (g_exit_flag)
 | 
			
		||||
		return false;
 | 
			
		||||
 | 
			
		||||
#ifdef TXDEBUG
 | 
			
		||||
	print_cmd(cmd.type);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	switch (cmd.type) {
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_RESET:
 | 
			
		||||
		set_ta(0);
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_POWERON:
 | 
			
		||||
 | 
			
		||||
		if (!mOn) {
 | 
			
		||||
			set_upper_ready(true);
 | 
			
		||||
			mOn = true;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_POWEROFF:
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_MEASURE:
 | 
			
		||||
		r.type = trxcon::trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE;
 | 
			
		||||
		r.param.measure.band_arfcn = cmd.param.measure.band_arfcn;
 | 
			
		||||
		// FIXME: do we want to measure anything, considering the transceiver just syncs by.. syncing?
 | 
			
		||||
		r.param.measure.dbm = -80;
 | 
			
		||||
		tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100);
 | 
			
		||||
		tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100);
 | 
			
		||||
		trxcon::cmdq_from_phy.spsc_push(&r);
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0:
 | 
			
		||||
		tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100);
 | 
			
		||||
		tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100);
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1:
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETSLOT:
 | 
			
		||||
		break;
 | 
			
		||||
	case trxcon::TRXCON_PHYIF_CMDT_SETTA:
 | 
			
		||||
		set_ta(cmd.param.setta.ta);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// trxcon C call(back) if
 | 
			
		||||
extern "C" {
 | 
			
		||||
int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon::trxcon_phyif_burst_req *br)
 | 
			
		||||
{
 | 
			
		||||
	if (br->burst_len == 0) // dummy/nope
 | 
			
		||||
		return 0;
 | 
			
		||||
	OSMO_ASSERT(br->burst != 0);
 | 
			
		||||
 | 
			
		||||
	trxcon::internal_q_tx_buf b;
 | 
			
		||||
	b.r = *br;
 | 
			
		||||
	memcpy(b.buf, (void *)br->burst, br->burst_len);
 | 
			
		||||
 | 
			
		||||
	if (!g_exit_flag)
 | 
			
		||||
		trxcon::txq.spsc_push(&b);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon::trxcon_phyif_cmd *cmd)
 | 
			
		||||
{
 | 
			
		||||
#ifdef TXDEBUG
 | 
			
		||||
	DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl;
 | 
			
		||||
#endif
 | 
			
		||||
	if (!g_exit_flag)
 | 
			
		||||
		trxcon::cmdq_to_phy.spsc_push(cmd);
 | 
			
		||||
	// q for resp polling happens in main loop
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void trxcon_phyif_close(void *phyif)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void trxcon_l1ctl_close(struct trxcon::trxcon_inst *trxcon)
 | 
			
		||||
{
 | 
			
		||||
	/* Avoid use-after-free: both *fi and *trxcon are children of
 | 
			
		||||
	 * the L2IF (L1CTL connection), so we need to re-parent *fi
 | 
			
		||||
	 * to NULL before calling l1ctl_client_conn_close(). */
 | 
			
		||||
	talloc_steal(NULL, trxcon->fi);
 | 
			
		||||
	trxcon::l1ctl_client_conn_close((struct trxcon::l1ctl_client *)trxcon->l2if);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trxcon_l1ctl_send(struct trxcon::trxcon_inst *trxcon, struct trxcon::msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct trxcon::l1ctl_client *l1c = (struct trxcon::l1ctl_client *)trxcon->l2if;
 | 
			
		||||
 | 
			
		||||
	return trxcon::l1ctl_client_send(l1c, msg);
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void sighandler(int sigset)
 | 
			
		||||
{
 | 
			
		||||
	// we might get a sigpipe in case the l1ctl ud socket disconnects because mobile quits
 | 
			
		||||
	if (sigset == SIGPIPE) {
 | 
			
		||||
		g_exit_flag = true;
 | 
			
		||||
 | 
			
		||||
		// we know the flag is atomic and it prevents the trxcon cb handlers from writing
 | 
			
		||||
		// to the queues, so submit some trash to unblock the threads & exit
 | 
			
		||||
		trxcon::trxcon_phyif_cmd cmd = {};
 | 
			
		||||
		trxcon::internal_q_tx_buf b = {};
 | 
			
		||||
		trxcon::txq.spsc_push(&b);
 | 
			
		||||
		trxcon::cmdq_to_phy.spsc_push(&cmd);
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char *argv[])
 | 
			
		||||
{
 | 
			
		||||
	auto tall_trxcon_ctx = talloc_init("trxcon context");
 | 
			
		||||
	signal(SIGPIPE, sighandler);
 | 
			
		||||
	fesetround(FE_TOWARDZERO);
 | 
			
		||||
 | 
			
		||||
	trxcon::msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
 | 
			
		||||
	trxc_log_init(tall_trxcon_ctx);
 | 
			
		||||
 | 
			
		||||
	trxcon::g_trxcon = trxcon::trxcon_inst_alloc(tall_trxcon_ctx, 0, 0);
 | 
			
		||||
	trxcon::g_trxcon->gsmtap = nullptr;
 | 
			
		||||
	trxcon::g_trxcon->phyif = nullptr;
 | 
			
		||||
	trxcon::g_trxcon->phy_quirks.fbsb_extend_fns = 866; // 4 seconds, known to work.
 | 
			
		||||
 | 
			
		||||
	convolve_init();
 | 
			
		||||
	convert_init();
 | 
			
		||||
	sigProcLibSetup();
 | 
			
		||||
	initvita();
 | 
			
		||||
 | 
			
		||||
	int status = 0;
 | 
			
		||||
	auto trx = new upper_trx();
 | 
			
		||||
	trx->do_auto_gain = true;
 | 
			
		||||
 | 
			
		||||
	status = trx->init_dev_and_streams();
 | 
			
		||||
	trx->set_name_aff_sched("main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5);
 | 
			
		||||
 | 
			
		||||
	if (!trxcon::trxc_l1ctl_init(tall_trxcon_ctx)) {
 | 
			
		||||
		std::cerr << "Error initializing l1ctl, quitting.." << std::endl;
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	trx->start_threads();
 | 
			
		||||
	trx->stop_threads();
 | 
			
		||||
 | 
			
		||||
	return status;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								Transceiver52M/ms/ms_upper.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Transceiver52M/ms/ms_upper.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * Author: Eric Wild <ewild@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 <netdb.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
 | 
			
		||||
#include "GSMCommon.h"
 | 
			
		||||
#include "radioClock.h"
 | 
			
		||||
#include "ms.h"
 | 
			
		||||
 | 
			
		||||
class upper_trx : public ms_trx {
 | 
			
		||||
	bool mOn;
 | 
			
		||||
	char demodded_softbits[444];
 | 
			
		||||
 | 
			
		||||
	// void driveControl();
 | 
			
		||||
	bool driveControl();
 | 
			
		||||
	void driveReceiveFIFO();
 | 
			
		||||
	void driveTx();
 | 
			
		||||
 | 
			
		||||
	bool pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset);
 | 
			
		||||
 | 
			
		||||
	std::thread thr_control, thr_rx, thr_tx;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
	void start_threads();
 | 
			
		||||
	void start_lower_ms();
 | 
			
		||||
 | 
			
		||||
	upper_trx(){};
 | 
			
		||||
};
 | 
			
		||||
@@ -1,3 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
 | 
			
		||||
 * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
 | 
			
		||||
 * (C) 2016 by Tom Tsou <tom.tsou@ettus.com>
 | 
			
		||||
 * (C) 2017 by Harald Welte <laforge@gnumonks.org>
 | 
			
		||||
 * (C) 2022 by 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <complex.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <math.h>
 | 
			
		||||
@@ -12,6 +34,9 @@
 | 
			
		||||
 | 
			
		||||
#include "sch.h"
 | 
			
		||||
 | 
			
		||||
#pragma GCC diagnostic push
 | 
			
		||||
#pragma GCC diagnostic ignored "-Wunused-variable"
 | 
			
		||||
 | 
			
		||||
/* GSM 04.08, 9.1.30 Synchronization channel information */
 | 
			
		||||
struct sch_packed_info {
 | 
			
		||||
	ubit_t t1_hi[2];
 | 
			
		||||
@@ -105,6 +130,14 @@ int gsm_sch_check_fn(int fn)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gsm_fcch_check_ts(int ts, int fn) {
 | 
			
		||||
	return ts == 0 && gsm_fcch_check_fn(fn);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int gsm_sch_check_ts(int ts, int fn) {
 | 
			
		||||
	return ts == 0 && gsm_sch_check_fn(fn);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* SCH (T1, T2, T3p) to full FN value */
 | 
			
		||||
int gsm_sch_to_fn(struct sch_info *sch)
 | 
			
		||||
{
 | 
			
		||||
@@ -152,7 +185,7 @@ int gsm_sch_parse(const uint8_t *info, struct sch_info *desc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* From osmo-bts */
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) int gsm_sch_decode(uint8_t *info, sbit_t *data)
 | 
			
		||||
int gsm_sch_decode(uint8_t *info, sbit_t *data)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	ubit_t uncoded[GSM_SCH_UNCODED_LEN];
 | 
			
		||||
@@ -292,3 +325,5 @@ static __attribute__((constructor)) void init()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#pragma GCC diagnostic pop
 | 
			
		||||
							
								
								
									
										48
									
								
								Transceiver52M/ms/sch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Transceiver52M/ms/sch.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
 | 
			
		||||
 * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
 | 
			
		||||
 * (C) 2016 by Tom Tsou <tom.tsou@ettus.com>
 | 
			
		||||
 * (C) 2017 by Harald Welte <laforge@gnumonks.org>
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> / Eric Wild <ewild@sysmocom.de>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
struct sch_info  {
 | 
			
		||||
	int bsic;
 | 
			
		||||
	int t1;
 | 
			
		||||
	int t2;
 | 
			
		||||
	int t3p;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define GSM_SCH_INFO_LEN		25
 | 
			
		||||
#define GSM_SCH_UNCODED_LEN		35
 | 
			
		||||
#define GSM_SCH_CODED_LEN		78
 | 
			
		||||
 | 
			
		||||
int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst);
 | 
			
		||||
int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc);
 | 
			
		||||
int gsm_sch_to_fn(struct sch_info *sch);
 | 
			
		||||
int gsm_sch_check_fn(int fn);
 | 
			
		||||
int gsm_fcch_check_fn(int fn);
 | 
			
		||||
int gsm_fcch_check_ts(int ts, int fn);
 | 
			
		||||
int gsm_sch_check_ts(int ts, int fn);
 | 
			
		||||
 | 
			
		||||
double gsm_fcch_offset(float *burst, int len);
 | 
			
		||||
 | 
			
		||||
int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
 | 
			
		||||
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
@@ -77,6 +76,7 @@ template <typename T> struct uhd_hw {
 | 
			
		||||
	const unsigned int rxFullScale, txFullScale;
 | 
			
		||||
	const int rxtxdelay;
 | 
			
		||||
	float rxgain, txgain;
 | 
			
		||||
	static std::atomic<bool> stop_me_flag;
 | 
			
		||||
 | 
			
		||||
	virtual ~uhd_hw()
 | 
			
		||||
	{
 | 
			
		||||
@@ -86,6 +86,10 @@ template <typename T> struct uhd_hw {
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void close_device()
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool tuneTx(double freq, size_t chan = 0)
 | 
			
		||||
	{
 | 
			
		||||
		msleep(25);
 | 
			
		||||
@@ -136,7 +140,8 @@ template <typename T> struct uhd_hw {
 | 
			
		||||
		auto const freq = 931.4e6; // 936.8e6
 | 
			
		||||
		auto bw = 0.5e6;
 | 
			
		||||
		auto const channel = 0;
 | 
			
		||||
		std::string args = {};
 | 
			
		||||
		// aligned to blade: 1020 samples per transfer
 | 
			
		||||
		std::string args = { "recv_frame_size=4092,send_frame_size=4092" };
 | 
			
		||||
 | 
			
		||||
		dev = uhd::usrp::multi_usrp::make(args);
 | 
			
		||||
		std::cout << "Using Device: " << dev->get_pp_string() << std::endl;
 | 
			
		||||
@@ -170,25 +175,19 @@ template <typename T> struct uhd_hw {
 | 
			
		||||
 | 
			
		||||
	void *rx_cb(bh_fn_t burst_handler)
 | 
			
		||||
	{
 | 
			
		||||
		void *ret;
 | 
			
		||||
		void *ret = nullptr;
 | 
			
		||||
		static int to_skip = 0;
 | 
			
		||||
 | 
			
		||||
		uhd::rx_metadata_t md;
 | 
			
		||||
		auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 3.0, true);
 | 
			
		||||
		auto num_rx_samps = rx_stream->recv(pkt_ptrs.front(), rx_spp, md, 1.0, true);
 | 
			
		||||
 | 
			
		||||
		if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) {
 | 
			
		||||
			std::cerr << boost::format("Timeout while streaming") << std::endl;
 | 
			
		||||
			exit(0);
 | 
			
		||||
		}
 | 
			
		||||
		if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW) {
 | 
			
		||||
			std::cerr << boost::format("Got an overflow indication. Please consider the following:\n"
 | 
			
		||||
						   "  Your write medium must sustain a rate of %fMB/s.\n"
 | 
			
		||||
						   "  Dropped samples will not be written to the file.\n"
 | 
			
		||||
						   "  Please modify this example for your purposes.\n"
 | 
			
		||||
						   "  This message will not appear again.\n") %
 | 
			
		||||
					     1.f;
 | 
			
		||||
			std::cerr << boost::format("Got an overflow indication\n") << std::endl;
 | 
			
		||||
			exit(0);
 | 
			
		||||
			;
 | 
			
		||||
		}
 | 
			
		||||
		if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE) {
 | 
			
		||||
			std::cerr << str(boost::format("Receiver error: %s") % md.strerror());
 | 
			
		||||
@@ -216,7 +215,7 @@ template <typename T> struct uhd_hw {
 | 
			
		||||
			stream_cmd.time_spec = uhd::time_spec_t();
 | 
			
		||||
			rx_stream->issue_stream_cmd(stream_cmd);
 | 
			
		||||
 | 
			
		||||
			while (1) {
 | 
			
		||||
			while (!stop_me_flag) {
 | 
			
		||||
				rx_cb(burst_handler);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
@@ -238,37 +237,14 @@ template <typename T> struct uhd_hw {
 | 
			
		||||
		m.time_spec = m.time_spec.from_ticks(ts + rxtxdelay, rxticks); // uhd specific b210 delay!
 | 
			
		||||
		std::vector<void *> ptrs(1, buffer);
 | 
			
		||||
 | 
			
		||||
		tx_stream->send(ptrs, len, m);
 | 
			
		||||
 | 
			
		||||
		tx_stream->send(ptrs, len, m, 1.0);
 | 
			
		||||
#ifdef DBGXX
 | 
			
		||||
		uhd::async_metadata_t async_md;
 | 
			
		||||
		bool tx_ack = false;
 | 
			
		||||
		while (!tx_ack && tx_stream->recv_async_msg(async_md)) {
 | 
			
		||||
			tx_ack = (async_md.event_code == uhd::async_metadata_t::EVENT_CODE_BURST_ACK);
 | 
			
		||||
		}
 | 
			
		||||
		std::cout << (tx_ack ? "yay" : "nay") << " " << async_md.time_spec.to_ticks(rxticks) << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void set_name_aff_sched(const char *name, int cpunum, int schedtype, int prio)
 | 
			
		||||
	{
 | 
			
		||||
		pthread_setname_np(pthread_self(), name);
 | 
			
		||||
 | 
			
		||||
		cpu_set_t cpuset;
 | 
			
		||||
 | 
			
		||||
		CPU_ZERO(&cpuset);
 | 
			
		||||
		CPU_SET(cpunum, &cpuset);
 | 
			
		||||
 | 
			
		||||
		auto rv = pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
 | 
			
		||||
		if (rv < 0) {
 | 
			
		||||
			std::cerr << name << " affinity: errreur! " << std::strerror(errno);
 | 
			
		||||
			return exit(0);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sched_param sch_params;
 | 
			
		||||
		sch_params.sched_priority = prio;
 | 
			
		||||
		rv = pthread_setschedparam(pthread_self(), schedtype, &sch_params);
 | 
			
		||||
		if (rv < 0) {
 | 
			
		||||
			std::cerr << name << " sched: errreur! " << std::strerror(errno);
 | 
			
		||||
			return exit(0);
 | 
			
		||||
		}
 | 
			
		||||
#endif
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
@@ -655,7 +655,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		exit(1);
 | 
			
		||||
 | 
			
		||||
	g_ctrlh = ctrl_interface_setup_dynip(NULL, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_TRX, NULL);
 | 
			
		||||
	g_ctrlh = ctrl_interface_setup(NULL, OSMO_CTRL_PORT_TRX, NULL);
 | 
			
		||||
	if (!g_ctrlh) {
 | 
			
		||||
		LOG(ERROR) << "Failed to create CTRL interface.\n";
 | 
			
		||||
		exit(1);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
#ifndef _SCH_H_
 | 
			
		||||
#define _SCH_H_
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
struct sch_info  {
 | 
			
		||||
	int bsic;
 | 
			
		||||
	int t1;
 | 
			
		||||
	int t2;
 | 
			
		||||
	int t3p;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define GSM_SCH_INFO_LEN		25
 | 
			
		||||
#define GSM_SCH_UNCODED_LEN		35
 | 
			
		||||
#define GSM_SCH_CODED_LEN		78
 | 
			
		||||
 | 
			
		||||
int gsm_sch_decode(uint8_t *sb_info, sbit_t *burst);
 | 
			
		||||
int gsm_sch_parse(const uint8_t *sb_info, struct sch_info *desc);
 | 
			
		||||
int gsm_sch_to_fn(struct sch_info *sch);
 | 
			
		||||
int gsm_sch_check_fn(int fn);
 | 
			
		||||
int gsm_fcch_check_fn(int fn);
 | 
			
		||||
 | 
			
		||||
double gsm_fcch_offset(float *burst, int len);
 | 
			
		||||
 | 
			
		||||
int float_to_sbit(const float *in, sbit_t *out, float scale, int len);
 | 
			
		||||
 | 
			
		||||
#endif /* _SCH_H_ */
 | 
			
		||||
@@ -87,19 +87,17 @@ static Resampler *dnsampler = NULL;
 | 
			
		||||
 * perform 16-byte memory alignment required by many SSE instructions.
 | 
			
		||||
 */
 | 
			
		||||
struct CorrelationSequence {
 | 
			
		||||
  CorrelationSequence() : sequence(NULL), buffer(NULL), toa(0.0), history(nullptr)
 | 
			
		||||
  CorrelationSequence() : sequence(NULL), buffer(NULL), toa(0.0)
 | 
			
		||||
  {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ~CorrelationSequence()
 | 
			
		||||
  {
 | 
			
		||||
    delete sequence;
 | 
			
		||||
    delete[] history;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  signalVector *sequence;
 | 
			
		||||
  void         *buffer;
 | 
			
		||||
  complex      *history;
 | 
			
		||||
  float        toa;
 | 
			
		||||
  complex      gain;
 | 
			
		||||
};
 | 
			
		||||
@@ -131,8 +129,6 @@ struct PulseSequence {
 | 
			
		||||
static CorrelationSequence *gMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
 | 
			
		||||
static CorrelationSequence *gEdgeMidambles[] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
 | 
			
		||||
static CorrelationSequence *gRACHSequences[] = {NULL,NULL,NULL};
 | 
			
		||||
static CorrelationSequence *gSCHSequence = NULL;
 | 
			
		||||
static CorrelationSequence *gDummySequence = NULL;
 | 
			
		||||
static PulseSequence *GSMPulse1 = NULL;
 | 
			
		||||
static PulseSequence *GSMPulse4 = NULL;
 | 
			
		||||
 | 
			
		||||
@@ -155,12 +151,6 @@ void sigProcLibDestroy()
 | 
			
		||||
    gRACHSequences[i] = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delete gSCHSequence;
 | 
			
		||||
  gSCHSequence = NULL;
 | 
			
		||||
 | 
			
		||||
  delete gDummySequence;
 | 
			
		||||
  gDummySequence = NULL;
 | 
			
		||||
 | 
			
		||||
  delete GMSKRotation1;
 | 
			
		||||
  delete GMSKReverseRotation1;
 | 
			
		||||
  delete GMSKRotation4;
 | 
			
		||||
@@ -325,8 +315,6 @@ static signalVector *convolve(const signalVector *x, const signalVector *h,
 | 
			
		||||
    append = true;
 | 
			
		||||
    break;
 | 
			
		||||
  case CUSTOM:
 | 
			
		||||
 | 
			
		||||
  // FIXME: x->getstart?
 | 
			
		||||
    if (start < h->size() - 1) {
 | 
			
		||||
      head = h->size() - start;
 | 
			
		||||
      append = true;
 | 
			
		||||
@@ -1100,7 +1088,6 @@ signalVector *delayVector(const signalVector *in, signalVector *out, float delay
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_never_instrument))
 | 
			
		||||
static complex interpolatePoint(const signalVector &inSig, float ix)
 | 
			
		||||
{
 | 
			
		||||
  int start = (int) (floor(ix) - 10);
 | 
			
		||||
@@ -1302,77 +1289,6 @@ release:
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool generateDummyMidamble(int sps)
 | 
			
		||||
{
 | 
			
		||||
  bool status = true;
 | 
			
		||||
  float toa;
 | 
			
		||||
  complex *data = NULL;
 | 
			
		||||
  signalVector *autocorr = NULL, *midamble = NULL;
 | 
			
		||||
  signalVector *midMidamble = NULL, *_midMidamble = NULL;
 | 
			
		||||
 | 
			
		||||
  delete gDummySequence;
 | 
			
		||||
 | 
			
		||||
  /* Use middle 16 bits of each TSC. Correlation sequence is not pulse shaped */
 | 
			
		||||
  midMidamble = modulateBurst(gDummyBurstTSC.segment(5,16), 0, sps, true);
 | 
			
		||||
  if (!midMidamble)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  /* Simulated receive sequence is pulse shaped */
 | 
			
		||||
  midamble = modulateBurst(gDummyBurstTSC, 0, sps, false);
 | 
			
		||||
  if (!midamble) {
 | 
			
		||||
    status = false;
 | 
			
		||||
    goto release;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // NOTE: Because ideal TSC 16-bit midamble is 66 symbols into burst,
 | 
			
		||||
  //       the ideal TSC has an + 180 degree phase shift,
 | 
			
		||||
  //       due to the pi/2 frequency shift, that
 | 
			
		||||
  //       needs to be accounted for.
 | 
			
		||||
  //       26-midamble is 61 symbols into burst, has +90 degree phase shift.
 | 
			
		||||
  scaleVector(*midMidamble, complex(-1.0, 0.0));
 | 
			
		||||
  scaleVector(*midamble, complex(0.0, 1.0));
 | 
			
		||||
 | 
			
		||||
  conjugateVector(*midMidamble);
 | 
			
		||||
 | 
			
		||||
  /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
 | 
			
		||||
  data = (complex *) convolve_h_alloc(midMidamble->size());
 | 
			
		||||
  _midMidamble = new signalVector(data, 0, midMidamble->size(), convolve_h_alloc, free);
 | 
			
		||||
  _midMidamble->setAligned(true);
 | 
			
		||||
  midMidamble->copyTo(*_midMidamble);
 | 
			
		||||
 | 
			
		||||
  autocorr = convolve(midamble, _midMidamble, NULL, NO_DELAY);
 | 
			
		||||
  if (!autocorr) {
 | 
			
		||||
    status = false;
 | 
			
		||||
    goto release;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  gDummySequence = new CorrelationSequence;
 | 
			
		||||
  gDummySequence->sequence = _midMidamble;
 | 
			
		||||
  gDummySequence->gain = peakDetect(*autocorr, &toa, NULL);
 | 
			
		||||
 | 
			
		||||
  /* For 1 sps only
 | 
			
		||||
   *     (Half of correlation length - 1) + midpoint of pulse shape + remainder
 | 
			
		||||
   *     13.5 = (16 / 2 - 1) + 1.5 + (26 - 10) / 2
 | 
			
		||||
   */
 | 
			
		||||
  if (sps == 1)
 | 
			
		||||
    gDummySequence->toa = toa - 13.5;
 | 
			
		||||
  else
 | 
			
		||||
    gDummySequence->toa = 0;
 | 
			
		||||
 | 
			
		||||
release:
 | 
			
		||||
  delete autocorr;
 | 
			
		||||
  delete midamble;
 | 
			
		||||
  delete midMidamble;
 | 
			
		||||
 | 
			
		||||
  if (!status) {
 | 
			
		||||
    delete _midMidamble;
 | 
			
		||||
    free(data);
 | 
			
		||||
    gDummySequence = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static CorrelationSequence *generateEdgeMidamble(int tsc)
 | 
			
		||||
{
 | 
			
		||||
  complex *data = NULL;
 | 
			
		||||
@@ -1468,70 +1384,6 @@ release:
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool generateSCHSequence(int sps)
 | 
			
		||||
{
 | 
			
		||||
  bool status = true;
 | 
			
		||||
  float toa;
 | 
			
		||||
  complex *data = NULL;
 | 
			
		||||
  signalVector *autocorr = NULL;
 | 
			
		||||
  signalVector *seq0 = NULL, *seq1 = NULL, *_seq1 = NULL;
 | 
			
		||||
 | 
			
		||||
  delete gSCHSequence;
 | 
			
		||||
 | 
			
		||||
  seq0 = modulateBurst(gSCHSynchSequence, 0, sps, false);
 | 
			
		||||
  if (!seq0)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  seq1 = modulateBurst(gSCHSynchSequence, 0, sps, true);
 | 
			
		||||
  if (!seq1) {
 | 
			
		||||
    status = false;
 | 
			
		||||
    goto release;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  conjugateVector(*seq1);
 | 
			
		||||
 | 
			
		||||
  /* For SSE alignment, reallocate the midamble sequence on 16-byte boundary */
 | 
			
		||||
  data = (complex *) convolve_h_alloc(seq1->size());
 | 
			
		||||
  _seq1 = new signalVector(data, 0, seq1->size(), convolve_h_alloc, free);
 | 
			
		||||
  _seq1->setAligned(true);
 | 
			
		||||
  memcpy(_seq1->begin(), seq1->begin(), seq1->size() * sizeof(complex));
 | 
			
		||||
 | 
			
		||||
  autocorr = convolve(seq0, _seq1, autocorr, NO_DELAY);
 | 
			
		||||
  if (!autocorr) {
 | 
			
		||||
    status = false;
 | 
			
		||||
    goto release;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  gSCHSequence = new CorrelationSequence;
 | 
			
		||||
  gSCHSequence->sequence = _seq1;
 | 
			
		||||
  gSCHSequence->buffer = data;
 | 
			
		||||
  gSCHSequence->gain = peakDetect(*autocorr, &toa, NULL);
 | 
			
		||||
  gSCHSequence->history = new complex[_seq1->size()];
 | 
			
		||||
 | 
			
		||||
  /* For 1 sps only
 | 
			
		||||
   *     (Half of correlation length - 1) + midpoint of pulse shaping filer
 | 
			
		||||
   *     20.5 = (64 / 2 - 1) + 1.5
 | 
			
		||||
   */
 | 
			
		||||
  if (sps == 1)
 | 
			
		||||
    gSCHSequence->toa = toa - 32.5;
 | 
			
		||||
  else
 | 
			
		||||
    gSCHSequence->toa = 0.0;
 | 
			
		||||
 | 
			
		||||
release:
 | 
			
		||||
  delete autocorr;
 | 
			
		||||
  delete seq0;
 | 
			
		||||
  delete seq1;
 | 
			
		||||
 | 
			
		||||
  if (!status) {
 | 
			
		||||
    delete _seq1;
 | 
			
		||||
    free(data);
 | 
			
		||||
    gSCHSequence = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Peak-to-average computation +/- range from peak in symbols
 | 
			
		||||
 */
 | 
			
		||||
@@ -1589,18 +1441,17 @@ float energyDetect(const signalVector &rxBurst, unsigned windowLength)
 | 
			
		||||
  return energy/windowLength;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static signalVector *downsampleBurst(const signalVector &burst, int in_len = DOWNSAMPLE_IN_LEN,
 | 
			
		||||
				     int out_len = DOWNSAMPLE_OUT_LEN)
 | 
			
		||||
static signalVector *downsampleBurst(const signalVector &burst)
 | 
			
		||||
{
 | 
			
		||||
	signalVector in(in_len, dnsampler->len());
 | 
			
		||||
	// gSCHSequence->sequence->size(), ensure next conv has no realloc
 | 
			
		||||
	signalVector *out = new signalVector(out_len, 64);
 | 
			
		||||
	burst.copyToSegment(in, 0, in_len);
 | 
			
		||||
  signalVector in(DOWNSAMPLE_IN_LEN, dnsampler->len());
 | 
			
		||||
  signalVector *out = new signalVector(DOWNSAMPLE_OUT_LEN);
 | 
			
		||||
  burst.copyToSegment(in, 0, DOWNSAMPLE_IN_LEN);
 | 
			
		||||
 | 
			
		||||
	if (dnsampler->rotate((float *)in.begin(), in_len, (float *)out->begin(), out_len) < 0) {
 | 
			
		||||
		delete out;
 | 
			
		||||
		out = NULL;
 | 
			
		||||
	}
 | 
			
		||||
  if (dnsampler->rotate((float *) in.begin(), DOWNSAMPLE_IN_LEN,
 | 
			
		||||
                        (float *) out->begin(), DOWNSAMPLE_OUT_LEN) < 0) {
 | 
			
		||||
    delete out;
 | 
			
		||||
    out = NULL;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return out;
 | 
			
		||||
};
 | 
			
		||||
@@ -1618,12 +1469,6 @@ static float computeCI(const signalVector *burst, const CorrelationSequence *syn
 | 
			
		||||
  /* Integer position where the sequence starts */
 | 
			
		||||
  const int ps = start + 1 - N + (int)roundf(toa);
 | 
			
		||||
 | 
			
		||||
  if(ps < 0) // might be -22 for toa 40 with N=64, if off by a lot during sch ms sync
 | 
			
		||||
    return 0;
 | 
			
		||||
 | 
			
		||||
  if (ps + N > burst->size())
 | 
			
		||||
	  return 0;
 | 
			
		||||
 | 
			
		||||
  /* Estimate Signal power */
 | 
			
		||||
  S = 0.0f;
 | 
			
		||||
  for (int i=0, j=ps; i<(int)N; i++,j++)
 | 
			
		||||
@@ -1666,11 +1511,11 @@ static int detectBurst(const signalVector &burst,
 | 
			
		||||
    corr_in = &burst;
 | 
			
		||||
    break;
 | 
			
		||||
  case 4:
 | 
			
		||||
	  dec = downsampleBurst(burst, len * 4, len);
 | 
			
		||||
	  /* Running at the downsampled rate at this point: */
 | 
			
		||||
	  corr_in = dec;
 | 
			
		||||
	  sps = 1;
 | 
			
		||||
	  break;
 | 
			
		||||
    dec = downsampleBurst(burst);
 | 
			
		||||
     /* Running at the downsampled rate at this point: */
 | 
			
		||||
     corr_in = dec;
 | 
			
		||||
     sps = 1;
 | 
			
		||||
     break;
 | 
			
		||||
  default:
 | 
			
		||||
     osmo_panic("%s:%d SPS %d not supported! Only 1 or 4 supported", __FILE__, __LINE__, sps);
 | 
			
		||||
  }
 | 
			
		||||
@@ -1748,11 +1593,11 @@ static int detectGeneralBurst(const signalVector &rxBurst, float thresh, int sps
 | 
			
		||||
  // Detect potential clipping
 | 
			
		||||
  // We still may be able to demod the burst, so we'll give it a try
 | 
			
		||||
  // and only report clipping if we can't demod.
 | 
			
		||||
  // float maxAmpl = maxAmplitude(rxBurst);
 | 
			
		||||
  // if (maxAmpl > CLIP_THRESH) {
 | 
			
		||||
  //   LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
 | 
			
		||||
  //   clipping = true;
 | 
			
		||||
  // }
 | 
			
		||||
  float maxAmpl = maxAmplitude(rxBurst);
 | 
			
		||||
  if (maxAmpl > CLIP_THRESH) {
 | 
			
		||||
    LOG(INFO) << "max burst amplitude: " << maxAmpl << " is above the clipping threshold: " << CLIP_THRESH << std::endl;
 | 
			
		||||
    clipping = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  start = target - head - 1;
 | 
			
		||||
  len = head + tail;
 | 
			
		||||
@@ -1807,86 +1652,6 @@ static int detectRACHBurst(const signalVector &burst, float threshold, int sps,
 | 
			
		||||
  return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int detectSCHBurst(signalVector &burst,
 | 
			
		||||
		    float thresh,
 | 
			
		||||
		    int sps,
 | 
			
		||||
        sch_detect_type state, struct estim_burst_params *ebp)
 | 
			
		||||
{
 | 
			
		||||
  int rc, start, target, head, tail, len;
 | 
			
		||||
  float _toa;
 | 
			
		||||
  complex _amp;
 | 
			
		||||
  CorrelationSequence *sync;
 | 
			
		||||
 | 
			
		||||
  if ((sps != 1) && (sps != 4))
 | 
			
		||||
    return -1;
 | 
			
		||||
 | 
			
		||||
  target = 3 + 39 + 64;
 | 
			
		||||
 | 
			
		||||
  switch (state) {
 | 
			
		||||
  case sch_detect_type::SCH_DETECT_NARROW:
 | 
			
		||||
    head = 4;
 | 
			
		||||
    tail = 4;
 | 
			
		||||
    break;
 | 
			
		||||
  case sch_detect_type::SCH_DETECT_BUFFER:
 | 
			
		||||
	  target = 1;
 | 
			
		||||
	  head = 0;
 | 
			
		||||
	  tail = (12 * 8 * 625) / 4; // 12 frames, downsampled /4 to 1 sps
 | 
			
		||||
	  break;
 | 
			
		||||
  case sch_detect_type::SCH_DETECT_FULL:
 | 
			
		||||
  default:
 | 
			
		||||
    head = target - 1;
 | 
			
		||||
    tail = 39 + 3 + 9;
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  start = (target - head) * 1 - 1;
 | 
			
		||||
  len = (head + tail) * 1;
 | 
			
		||||
  sync = gSCHSequence;
 | 
			
		||||
  signalVector corr(len);
 | 
			
		||||
 | 
			
		||||
  signalVector _burst(burst, sync->sequence->size(), 5);
 | 
			
		||||
 | 
			
		||||
  memcpy(_burst.begin() - sync->sequence->size(), sync->history, sync->sequence->size() * sizeof(complex));
 | 
			
		||||
 | 
			
		||||
  memcpy(sync->history, &burst.begin()[burst.size() - sync->sequence->size()],
 | 
			
		||||
         sync->sequence->size() * sizeof(complex));
 | 
			
		||||
 | 
			
		||||
  rc = detectBurst(_burst, corr, sync, thresh, sps, start, len, ebp);
 | 
			
		||||
 | 
			
		||||
  if (rc < 0) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  } else if (!rc) {
 | 
			
		||||
      ebp->amp = 0.0f;
 | 
			
		||||
      ebp->toa = 0.0f;
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (state == sch_detect_type::SCH_DETECT_BUFFER)
 | 
			
		||||
	  ebp->toa = ebp->toa - (3 + 39 + 64);
 | 
			
		||||
  else {
 | 
			
		||||
	  /* Subtract forward search bits from delay */
 | 
			
		||||
	  ebp->toa = ebp->toa - head;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int detectDummyBurst(const signalVector &burst, float threshold,
 | 
			
		||||
                               int sps, unsigned max_toa, struct estim_burst_params *ebp)
 | 
			
		||||
{
 | 
			
		||||
  int rc, target, head, tail;
 | 
			
		||||
  CorrelationSequence *sync;
 | 
			
		||||
 | 
			
		||||
  target = 3 + 58 + 16 + 5;
 | 
			
		||||
  head = 10;
 | 
			
		||||
  tail = 6 + max_toa;
 | 
			
		||||
  sync = gDummySequence;
 | 
			
		||||
 | 
			
		||||
  ebp->tsc = 0;
 | 
			
		||||
  rc = detectGeneralBurst(burst, threshold, sps, target, head, tail, sync, ebp);
 | 
			
		||||
  return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Normal burst detection
 | 
			
		||||
 *
 | 
			
		||||
@@ -1905,7 +1670,7 @@ static int analyzeTrafficBurst(const signalVector &burst, unsigned tsc, float th
 | 
			
		||||
    return -SIGERR_UNSUPPORTED;
 | 
			
		||||
 | 
			
		||||
  target = 3 + 58 + 16 + 5;
 | 
			
		||||
  head = 10;
 | 
			
		||||
  head = 6;
 | 
			
		||||
  tail = 6 + max_toa;
 | 
			
		||||
  sync = gMidambles[tsc];
 | 
			
		||||
 | 
			
		||||
@@ -1954,9 +1719,6 @@ int detectAnyBurst(const signalVector &burst, unsigned tsc, float threshold,
 | 
			
		||||
  case RACH:
 | 
			
		||||
    rc = detectRACHBurst(burst, threshold, sps, max_toa, type == EXT_RACH, ebp);
 | 
			
		||||
    break;
 | 
			
		||||
  case IDLE:
 | 
			
		||||
    rc = detectDummyBurst(burst, threshold, sps, max_toa, ebp);
 | 
			
		||||
    break;
 | 
			
		||||
  default:
 | 
			
		||||
    LOG(ERR) << "Invalid correlation type";
 | 
			
		||||
  }
 | 
			
		||||
@@ -2159,9 +1921,6 @@ bool sigProcLibSetup()
 | 
			
		||||
  generateRACHSequence(&gRACHSequences[1], gRACHSynchSequenceTS1, 1);
 | 
			
		||||
  generateRACHSequence(&gRACHSequences[2], gRACHSynchSequenceTS2, 1);
 | 
			
		||||
 | 
			
		||||
  generateSCHSequence(1);
 | 
			
		||||
  generateDummyMidamble(1);
 | 
			
		||||
 | 
			
		||||
  for (int tsc = 0; tsc < 8; tsc++) {
 | 
			
		||||
    generateMidamble(1, tsc);
 | 
			
		||||
    gEdgeMidambles[tsc] = generateEdgeMidamble(tsc);
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ enum CorrType{
 | 
			
		||||
  TSC,         ///< timeslot should contain a normal burst
 | 
			
		||||
  EXT_RACH,    ///< timeslot should contain an extended access burst
 | 
			
		||||
  RACH,        ///< timeslot should contain an access burst
 | 
			
		||||
  SCH,
 | 
			
		||||
  EDGE,        ///< timeslot should contain an EDGE burst
 | 
			
		||||
  IDLE         ///< timeslot is an idle (or dummy) burst
 | 
			
		||||
};
 | 
			
		||||
@@ -94,8 +93,6 @@ signalVector *generateDummyBurst(int sps, int tn);
 | 
			
		||||
void scaleVector(signalVector &x,
 | 
			
		||||
                 complex scale);
 | 
			
		||||
 | 
			
		||||
signalVector *delayVector(const signalVector *in, signalVector *out, float delay);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
        Rough energy estimator.
 | 
			
		||||
        @param rxBurst A GSM burst.
 | 
			
		||||
@@ -136,17 +133,6 @@ int detectAnyBurst(const signalVector &burst,
 | 
			
		||||
                   unsigned max_toa,
 | 
			
		||||
                   struct estim_burst_params *ebp);
 | 
			
		||||
 | 
			
		||||
enum class sch_detect_type {
 | 
			
		||||
	SCH_DETECT_FULL,
 | 
			
		||||
	SCH_DETECT_NARROW,
 | 
			
		||||
	SCH_DETECT_BUFFER,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int detectSCHBurst(signalVector &rxBurst,
 | 
			
		||||
                    float detectThreshold,
 | 
			
		||||
                    int sps,
 | 
			
		||||
                    sch_detect_type state, struct estim_burst_params *ebp);
 | 
			
		||||
 | 
			
		||||
/** Demodulate burst basde on type and output soft bits */
 | 
			
		||||
SoftVector *demodAnyBurst(const signalVector &burst, CorrType type,
 | 
			
		||||
                          int sps, struct estim_burst_params *ebp);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								configure.ac
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								configure.ac
									
									
									
									
									
								
							@@ -143,6 +143,11 @@ AC_ARG_WITH(bladerf, [
 | 
			
		||||
        [enable bladeRF])
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
AC_ARG_WITH(mstrx, [
 | 
			
		||||
    AS_HELP_STRING([--with-mstrx],
 | 
			
		||||
        [enable MS TRX])
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
AC_ARG_WITH(singledb, [
 | 
			
		||||
    AS_HELP_STRING([--with-singledb],
 | 
			
		||||
        [enable single daughterboard use on USRP1])
 | 
			
		||||
@@ -190,12 +195,34 @@ AS_IF([test "x$with_uhd" = "xyes"],[
 | 
			
		||||
            [PKG_CHECK_MODULES(UHD, uhd >= 003.005)]
 | 
			
		||||
        )]
 | 
			
		||||
    )
 | 
			
		||||
    # OS#5608: libuhd < 4.2.0 includes boost/thread/thread.hpp in its logging
 | 
			
		||||
    # code and therefore requires linking against boost_thread. It's missing in
 | 
			
		||||
    # uhd.pc, so work around it here.
 | 
			
		||||
    # https://github.com/EttusResearch/uhd/commit/04a83b6e76beef970854da69ba882d717669b49c
 | 
			
		||||
    PKG_CHECK_MODULES(UHD, uhd < 004.002,
 | 
			
		||||
        [LIBS="$LIBS -lboost_thread"],
 | 
			
		||||
        []
 | 
			
		||||
    )
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
AS_IF([test "x$with_bladerf" = "xyes"], [
 | 
			
		||||
    PKG_CHECK_MODULES(BLADE, libbladeRF >= 2.0)
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
AC_MSG_CHECKING([whether to enable building MS TRX])
 | 
			
		||||
AS_IF([test "x$with_mstrx" = "xyes"], [
 | 
			
		||||
    AC_CONFIG_SUBDIRS([osmocom-bb/src/host/trxcon])
 | 
			
		||||
    AC_SUBST(LIBTRXCON_DIR, "osmocom-bb/src/host/trxcon")
 | 
			
		||||
    AC_MSG_RESULT([yes])
 | 
			
		||||
], [
 | 
			
		||||
    # Despite LIBTRXCON_DIR is added to SUBDIRS conditionally,
 | 
			
		||||
    # autoconf/automake still requires the directory to be present
 | 
			
		||||
    # and thus the submodule to be fetched (even if MS TRX is not needed).
 | 
			
		||||
    # Work this around by pointing it to an empty dir.
 | 
			
		||||
    AC_SUBST(LIBTRXCON_DIR, "osmocom-bb")
 | 
			
		||||
    AC_MSG_RESULT([no])
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
AS_IF([test "x$with_singledb" = "xyes"], [
 | 
			
		||||
    AC_DEFINE(SINGLEDB, 1, Define to 1 for single daughterboard)
 | 
			
		||||
])
 | 
			
		||||
@@ -252,6 +279,7 @@ AM_CONDITIONAL(DEVICE_IPC, [test "x$with_ipc" = "xyes"])
 | 
			
		||||
AM_CONDITIONAL(DEVICE_BLADE, [test "x$with_bladerf" = "xyes"])
 | 
			
		||||
AM_CONDITIONAL(ARCH_ARM, [test "x$with_neon" = "xyes" || test "x$with_neon_vfpv4" = "xyes"])
 | 
			
		||||
AM_CONDITIONAL(ARCH_ARM_A15, [test "x$with_neon_vfpv4" = "xyes"])
 | 
			
		||||
AM_CONDITIONAL(ENABLE_MS_TRX, [test "x$with_mstrx" = "xyes"])
 | 
			
		||||
 | 
			
		||||
PKG_CHECK_MODULES(LIBUSB, libusb-1.0)
 | 
			
		||||
PKG_CHECK_MODULES(FFTWF, fftw3f)
 | 
			
		||||
@@ -319,10 +347,10 @@ AC_MSG_RESULT([CFLAGS="$CFLAGS"])
 | 
			
		||||
AC_MSG_RESULT([CXXFLAGS="$CXXFLAGS"])
 | 
			
		||||
AC_MSG_RESULT([LDFLAGS="$LDFLAGS"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
dnl Output files
 | 
			
		||||
AC_CONFIG_FILES([\
 | 
			
		||||
    Makefile \
 | 
			
		||||
    trxcon/Makefile \
 | 
			
		||||
    CommonLibs/Makefile \
 | 
			
		||||
    GSM/Makefile \
 | 
			
		||||
    Transceiver52M/Makefile \
 | 
			
		||||
@@ -336,7 +364,6 @@ AC_CONFIG_FILES([\
 | 
			
		||||
    Transceiver52M/device/usrp1/Makefile \
 | 
			
		||||
    Transceiver52M/device/lms/Makefile \
 | 
			
		||||
    Transceiver52M/device/ipc/Makefile \
 | 
			
		||||
    Transceiver52M/device/ipc2/Makefile \
 | 
			
		||||
    Transceiver52M/device/bladerf/Makefile \
 | 
			
		||||
    tests/Makefile \
 | 
			
		||||
    tests/CommonLibs/Makefile \
 | 
			
		||||
@@ -346,8 +373,7 @@ AC_CONFIG_FILES([\
 | 
			
		||||
    doc/examples/Makefile \
 | 
			
		||||
    contrib/Makefile \
 | 
			
		||||
    contrib/systemd/Makefile \
 | 
			
		||||
    doc/manuals/Makefile \
 | 
			
		||||
    contrib/osmo-trx.spec \
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
AC_OUTPUT(
 | 
			
		||||
	doc/manuals/Makefile
 | 
			
		||||
	contrib/osmo-trx.spec)
 | 
			
		||||
AC_OUTPUT
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,7 @@ export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
 | 
			
		||||
export LD_LIBRARY_PATH="$inst/lib"
 | 
			
		||||
export PATH="$inst/bin:$PATH"
 | 
			
		||||
 | 
			
		||||
CONFIG="--enable-sanitize --enable-werror --with-uhd --with-usrp1 --with-lms --with-ipc $INSTR"
 | 
			
		||||
CONFIG="--enable-sanitize --enable-werror --with-uhd --with-usrp1 --with-lms --with-ipc --with-mstrx $INSTR"
 | 
			
		||||
 | 
			
		||||
# Additional configure options and depends
 | 
			
		||||
if [ "$WITH_MANUALS" = "1" ]; then
 | 
			
		||||
@@ -101,13 +101,17 @@ echo
 | 
			
		||||
set -x
 | 
			
		||||
 | 
			
		||||
cd "$base"
 | 
			
		||||
git submodule status
 | 
			
		||||
autoreconf --install --force
 | 
			
		||||
./configure $CONFIG
 | 
			
		||||
$MAKE $PARALLEL_MAKE
 | 
			
		||||
$MAKE check \
 | 
			
		||||
  || cat-testlogs.sh
 | 
			
		||||
DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck \
 | 
			
		||||
  || cat-testlogs.sh
 | 
			
		||||
 | 
			
		||||
if arch | grep -v -q arm; then
 | 
			
		||||
	DISTCHECK_CONFIGURE_FLAGS="$CONFIG" $MAKE $PARALLEL_MAKE distcheck \
 | 
			
		||||
	  || cat-testlogs.sh
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
 | 
			
		||||
	make -C "$base/doc/manuals" publish
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (IPC Backend)
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
Restart=always
 | 
			
		||||
StateDirectory=osmocom
 | 
			
		||||
WorkingDirectory=%S/osmocom
 | 
			
		||||
ExecStart=/usr/bin/osmo-trx-ipc -C /etc/osmocom/osmo-trx-ipc.cfg
 | 
			
		||||
RestartSec=2
 | 
			
		||||
# CPU scheduling policy:
 | 
			
		||||
CPUSchedulingPolicy=rr
 | 
			
		||||
# For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority):
 | 
			
		||||
CPUSchedulingPriority=21
 | 
			
		||||
# See sched(7) for further details on real-time policies and priorities
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (LimeSuite backend)
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
Restart=always
 | 
			
		||||
StateDirectory=osmocom
 | 
			
		||||
WorkingDirectory=%S/osmocom
 | 
			
		||||
ExecStart=/usr/bin/osmo-trx-lms -C /etc/osmocom/osmo-trx-lms.cfg
 | 
			
		||||
RestartSec=2
 | 
			
		||||
# CPU scheduling policy:
 | 
			
		||||
CPUSchedulingPolicy=rr
 | 
			
		||||
# For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority):
 | 
			
		||||
CPUSchedulingPriority=21
 | 
			
		||||
# See sched(7) for further details on real-time policies and priorities
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (UHD Backend)
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
Restart=always
 | 
			
		||||
StateDirectory=osmocom
 | 
			
		||||
WorkingDirectory=%S/osmocom
 | 
			
		||||
ExecStart=/usr/bin/osmo-trx-uhd -C /etc/osmocom/osmo-trx-uhd.cfg
 | 
			
		||||
RestartSec=2
 | 
			
		||||
# CPU scheduling policy:
 | 
			
		||||
CPUSchedulingPolicy=rr
 | 
			
		||||
# For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority):
 | 
			
		||||
CPUSchedulingPriority=21
 | 
			
		||||
# See sched(7) for further details on real-time policies and priorities
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,15 @@ Description=Osmocom SDR BTS L1 Transceiver (libusrp backend)
 | 
			
		||||
[Service]
 | 
			
		||||
Type=simple
 | 
			
		||||
Restart=always
 | 
			
		||||
StateDirectory=osmocom
 | 
			
		||||
WorkingDirectory=%S/osmocom
 | 
			
		||||
ExecStart=/usr/bin/osmo-trx-usrp1 -C /etc/osmocom/osmo-trx-usrp1.cfg
 | 
			
		||||
RestartSec=2
 | 
			
		||||
# CPU scheduling policy:
 | 
			
		||||
CPUSchedulingPolicy=rr
 | 
			
		||||
# For real-time scheduling policies an integer between 1 (lowest priority) and 99 (highest priority):
 | 
			
		||||
CPUSchedulingPriority=21
 | 
			
		||||
# See sched(7) for further details on real-time policies and priorities
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,16 @@
 | 
			
		||||
osmo-trx (1.4.1) unstable; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Oliver Smith ]
 | 
			
		||||
  * treewide: remove FSF address
 | 
			
		||||
 | 
			
		||||
  [ Vadim Yanitskiy ]
 | 
			
		||||
  * tests: use 'check_PROGRAMS' instead of 'noinst_PROGRAMS'
 | 
			
		||||
 | 
			
		||||
  [ Harald Welte ]
 | 
			
		||||
  * update git URLs (git -> https; gitea)
 | 
			
		||||
 | 
			
		||||
 -- Pau Espin Pedrol <pespin@sysmocom.de>  Wed, 29 Jun 2022 09:32:56 +0200
 | 
			
		||||
 | 
			
		||||
osmo-trx (1.4.0) unstable; urgency=medium
 | 
			
		||||
 | 
			
		||||
  [ Pau Espin Pedrol ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								debian/control
									
									
									
									
										vendored
									
									
								
							@@ -17,8 +17,8 @@ Build-Depends: debhelper (>= 9),
 | 
			
		||||
               libosmocore-dev (>= 1.6.0),
 | 
			
		||||
               osmo-gsm-manuals-dev
 | 
			
		||||
Standards-Version: 3.9.6
 | 
			
		||||
Vcs-Browser: http://cgit.osmocom.org/osmo-trx
 | 
			
		||||
Vcs-Git: git://git.osmocom.org/osmo-trx
 | 
			
		||||
Vcs-Browser: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx
 | 
			
		||||
Vcs-Git: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx
 | 
			
		||||
Homepage: https://projects.osmocom.org/projects/osmotrx
 | 
			
		||||
 | 
			
		||||
Package: osmo-trx
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								debian/copyright
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 | 
			
		||||
Upstream-Name: OsmoTRX
 | 
			
		||||
Source: http://cgit.osmocom.org/osmo-trx/
 | 
			
		||||
Source: https://gitea.osmocom.org/cellular-infrastructure/osmo-trx
 | 
			
		||||
Files-Excluded: Transceiver52M/device/usrp1/std_inband.rbf
 | 
			
		||||
 | 
			
		||||
Files: *
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								osmocom-bb
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								osmocom-bb
									
									
									
									
									
										Submodule
									
								
							 Submodule osmocom-bb added at 040bf41028
									
								
							@@ -29,9 +29,9 @@
 | 
			
		||||
#include "Threads.h"
 | 
			
		||||
#include "Interthread.h"
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
std::mutex dbg_cout;
 | 
			
		||||
 | 
			
		||||
InterthreadQueue<int> gQ;
 | 
			
		||||
InterthreadMap<int,int> gMap;
 | 
			
		||||
@@ -41,6 +41,8 @@ int q_last_write_val;
 | 
			
		||||
int m_last_read_val;
 | 
			
		||||
int m_last_write_val;
 | 
			
		||||
 | 
			
		||||
#define CERR(text) { dbg_cout.lock() ; std::cerr << text; dbg_cout.unlock(); }
 | 
			
		||||
 | 
			
		||||
void* qWriter(void*)
 | 
			
		||||
{
 | 
			
		||||
	int *p;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								trxcon/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								trxcon/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,27 +0,0 @@
 | 
			
		||||
# autoreconf by-products
 | 
			
		||||
*.in
 | 
			
		||||
 | 
			
		||||
aclocal.m4
 | 
			
		||||
autom4te.cache/
 | 
			
		||||
configure
 | 
			
		||||
depcomp
 | 
			
		||||
install-sh
 | 
			
		||||
missing
 | 
			
		||||
compile
 | 
			
		||||
 | 
			
		||||
# configure by-products
 | 
			
		||||
.deps/
 | 
			
		||||
Makefile
 | 
			
		||||
 | 
			
		||||
config.status
 | 
			
		||||
version.h
 | 
			
		||||
 | 
			
		||||
# build by-products
 | 
			
		||||
*.o
 | 
			
		||||
*.a
 | 
			
		||||
 | 
			
		||||
trxcon
 | 
			
		||||
 | 
			
		||||
# various
 | 
			
		||||
.version
 | 
			
		||||
.tarball-version
 | 
			
		||||
@@ -1,58 +0,0 @@
 | 
			
		||||
#AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
 | 
			
		||||
 | 
			
		||||
include $(top_srcdir)/Makefile.common
 | 
			
		||||
 | 
			
		||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES)
 | 
			
		||||
AM_CXXFLAGS = -Wall -O3 -g -ldl -lpthread
 | 
			
		||||
 | 
			
		||||
# versioning magic
 | 
			
		||||
BUILT_SOURCES = $(top_srcdir)/.version
 | 
			
		||||
$(top_srcdir)/.version:
 | 
			
		||||
	echo $(VERSION) > $@-t && mv $@-t $@
 | 
			
		||||
dist-hook:
 | 
			
		||||
	echo $(VERSION) > $(distdir)/.tarball-version
 | 
			
		||||
 | 
			
		||||
AM_CPPFLAGS = \
 | 
			
		||||
	$(all_includes) \
 | 
			
		||||
	-I$(top_srcdir)/include \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
AM_CFLAGS = \
 | 
			
		||||
	-Wall \
 | 
			
		||||
	$(LIBOSMOCORE_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOCODING_CFLAGS) \
 | 
			
		||||
	$(LIBOSMOGSM_CFLAGS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
noinst_LTLIBRARIES = libtrxcon.la 
 | 
			
		||||
 | 
			
		||||
libtrxcon_la_SOURCES = \
 | 
			
		||||
	l1ctl_link.c \
 | 
			
		||||
	l1ctl.c \
 | 
			
		||||
	trx_if.c \
 | 
			
		||||
	logging.c \
 | 
			
		||||
	trxcon.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
# Scheduler
 | 
			
		||||
libtrxcon_la_SOURCES += \
 | 
			
		||||
	sched_lchan_common.c \
 | 
			
		||||
	sched_lchan_pdtch.c \
 | 
			
		||||
	sched_lchan_desc.c \
 | 
			
		||||
	sched_lchan_xcch.c \
 | 
			
		||||
	sched_lchan_tchf.c \
 | 
			
		||||
	sched_lchan_tchh.c \
 | 
			
		||||
	sched_lchan_rach.c \
 | 
			
		||||
	sched_lchan_sch.c \
 | 
			
		||||
	sched_mframe.c \
 | 
			
		||||
	sched_clck.c \
 | 
			
		||||
	sched_prim.c \
 | 
			
		||||
	sched_trx.c \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
libtrxcon_la_LIBADD = \
 | 
			
		||||
	$(LIBOSMOCORE_LIBS) \
 | 
			
		||||
	$(LIBOSMOCODING_LIBS) \
 | 
			
		||||
	$(LIBOSMOGSM_LIBS) \
 | 
			
		||||
	$(NULL)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										917
									
								
								trxcon/l1ctl.c
									
									
									
									
									
								
							
							
						
						
									
										917
									
								
								trxcon/l1ctl.c
									
									
									
									
									
								
							@@ -1,917 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * GSM L1 control interface handlers
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
 | 
			
		||||
 * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "l1ctl_link.h"
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
 | 
			
		||||
static const char *arfcn2band_name(uint16_t arfcn)
 | 
			
		||||
{
 | 
			
		||||
	enum gsm_band band;
 | 
			
		||||
 | 
			
		||||
	if (gsm_arfcn2band_rc(arfcn, &band) < 0)
 | 
			
		||||
		return "(invalid)";
 | 
			
		||||
 | 
			
		||||
	return gsm_band_name(band);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct msgb *l1ctl_alloc_msg(uint8_t msg_type)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_hdr *l1h;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Each L1CTL message gets its own length pushed in front
 | 
			
		||||
	 * before sending. This is why we need this small headroom.
 | 
			
		||||
	 */
 | 
			
		||||
	msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD,
 | 
			
		||||
		L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg");
 | 
			
		||||
	if (!msg) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg->l1h = msgb_put(msg, sizeof(*l1h));
 | 
			
		||||
	l1h = (struct l1ctl_hdr *) msg->l1h;
 | 
			
		||||
	l1h->msg_type = msg_type;
 | 
			
		||||
 | 
			
		||||
	return msg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
 | 
			
		||||
	int dbm, int last)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_pm_conf *pmc;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_DEBUG, "Send PM Conf (%s %d = %d dBm)\n",
 | 
			
		||||
		arfcn2band_name(band_arfcn),
 | 
			
		||||
		band_arfcn &~ ARFCN_FLAG_MASK, dbm);
 | 
			
		||||
 | 
			
		||||
	pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc));
 | 
			
		||||
	pmc->band_arfcn = htons(band_arfcn);
 | 
			
		||||
	pmc->pm[0] = dbm2rxlev(dbm);
 | 
			
		||||
	pmc->pm[1] = 0;
 | 
			
		||||
 | 
			
		||||
	if (last) {
 | 
			
		||||
		struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
 | 
			
		||||
		l1h->flags |= L1CTL_F_DONE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct l1ctl_reset *res;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
 | 
			
		||||
 | 
			
		||||
	res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
 | 
			
		||||
	res->type = type;
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	struct l1ctl_reset *res;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
 | 
			
		||||
	if (!msg)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
 | 
			
		||||
	res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
 | 
			
		||||
	res->type = type;
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct l1ctl_info_dl *put_dl_info_hdr(struct msgb *msg, struct l1ctl_info_dl *dl_info)
 | 
			
		||||
{
 | 
			
		||||
	size_t len = sizeof(struct l1ctl_info_dl);
 | 
			
		||||
	struct l1ctl_info_dl *dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
 | 
			
		||||
 | 
			
		||||
	if (dl_info) /* Copy DL info provided by handler */
 | 
			
		||||
		memcpy(dl, dl_info, len);
 | 
			
		||||
	else /* Init DL info header */
 | 
			
		||||
		memset(dl, 0x00, len);
 | 
			
		||||
 | 
			
		||||
	return dl;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Fill in FBSB payload: BSIC and sync result */
 | 
			
		||||
static struct l1ctl_fbsb_conf *fbsb_conf_make(struct msgb *msg, uint8_t result, uint8_t bsic)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_fbsb_conf *conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n", result, bsic);
 | 
			
		||||
 | 
			
		||||
	conf->result = result;
 | 
			
		||||
	conf->bsic = bsic;
 | 
			
		||||
 | 
			
		||||
	return conf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
 | 
			
		||||
	struct l1ctl_info_dl *dl_info, uint8_t bsic)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_fbsb_conf *conf;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
 | 
			
		||||
	if (msg == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	put_dl_info_hdr(msg, dl_info);
 | 
			
		||||
	talloc_free(dl_info);
 | 
			
		||||
 | 
			
		||||
	conf = fbsb_conf_make(msg, result, bsic);
 | 
			
		||||
 | 
			
		||||
	/* FIXME: set proper value */
 | 
			
		||||
	conf->initial_freq_err = 0;
 | 
			
		||||
 | 
			
		||||
	/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
 | 
			
		||||
	l1l->fbsb_conf_sent = true;
 | 
			
		||||
 | 
			
		||||
	/* Abort FBSB expire timer */
 | 
			
		||||
	if (osmo_timer_pending(&l1l->fbsb_timer))
 | 
			
		||||
		osmo_timer_del(&l1l->fbsb_timer);
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_ccch_mode_conf *conf;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF);
 | 
			
		||||
	if (msg == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf));
 | 
			
		||||
	conf->ccch_mode = mode;
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
 | 
			
		||||
 */
 | 
			
		||||
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
 | 
			
		||||
	uint8_t *l2, size_t l2_len, bool traffic)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	uint8_t *msg_l2;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(traffic ?
 | 
			
		||||
		L1CTL_TRAFFIC_IND : L1CTL_DATA_IND);
 | 
			
		||||
	if (msg == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	put_dl_info_hdr(msg, data);
 | 
			
		||||
 | 
			
		||||
	/* Copy the L2 payload if preset */
 | 
			
		||||
	if (l2 && l2_len > 0) {
 | 
			
		||||
		msg_l2 = (uint8_t *) msgb_put(msg, l2_len);
 | 
			
		||||
		memcpy(msg_l2, l2, l2_len);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Put message to upper layers */
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
 | 
			
		||||
	uint16_t band_arfcn, uint32_t fn)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_info_dl *dl;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_RACH_CONF);
 | 
			
		||||
	if (msg == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	dl = put_dl_info_hdr(msg, NULL);
 | 
			
		||||
	memset(dl, 0x00, sizeof(*dl));
 | 
			
		||||
 | 
			
		||||
	dl->band_arfcn = htons(band_arfcn);
 | 
			
		||||
	dl->frame_nr = htonl(fn);
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
 | 
			
		||||
 */
 | 
			
		||||
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
 | 
			
		||||
	struct l1ctl_info_dl *data, bool traffic)
 | 
			
		||||
{
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(traffic ?
 | 
			
		||||
		L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF);
 | 
			
		||||
	if (msg == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/* Copy DL frame header from source message */
 | 
			
		||||
	put_dl_info_hdr(msg, data);
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static enum gsm_phys_chan_config l1ctl_ccch_mode2pchan_config(enum ccch_mode mode)
 | 
			
		||||
{
 | 
			
		||||
	switch (mode) {
 | 
			
		||||
	/* TODO: distinguish extended BCCH */
 | 
			
		||||
	case CCCH_MODE_NON_COMBINED:
 | 
			
		||||
	case CCCH_MODE_NONE:
 | 
			
		||||
		return GSM_PCHAN_CCCH;
 | 
			
		||||
 | 
			
		||||
	case CCCH_MODE_COMBINED:
 | 
			
		||||
		return GSM_PCHAN_CCCH_SDCCH4;
 | 
			
		||||
	case CCCH_MODE_COMBINED_CBCH:
 | 
			
		||||
		return GSM_PCHAN_CCCH_SDCCH4_CBCH;
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE, "Undandled CCCH mode (%u), "
 | 
			
		||||
			"assuming non-combined configuration\n", mode);
 | 
			
		||||
		return GSM_PCHAN_CCCH;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* FBSB expire timer */
 | 
			
		||||
static void fbsb_timer_cb(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_link *l1l = (struct l1ctl_link *) data;
 | 
			
		||||
	struct l1ctl_info_dl *dl;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
 | 
			
		||||
	msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
 | 
			
		||||
	if (msg == NULL)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "FBSB timer fired for ARFCN %u\n", l1l->trx->band_arfcn &~ ARFCN_FLAG_MASK);
 | 
			
		||||
 | 
			
		||||
	dl = put_dl_info_hdr(msg, NULL);
 | 
			
		||||
 | 
			
		||||
	/* Fill in current ARFCN */
 | 
			
		||||
	dl->band_arfcn = htons(l1l->trx->band_arfcn);
 | 
			
		||||
 | 
			
		||||
	fbsb_conf_make(msg, 255, 0);
 | 
			
		||||
 | 
			
		||||
	/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
 | 
			
		||||
	l1l->fbsb_conf_sent = true;
 | 
			
		||||
 | 
			
		||||
	l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	enum gsm_phys_chan_config ch_config;
 | 
			
		||||
	struct l1ctl_fbsb_req *fbsb;
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	uint16_t timeout;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
 | 
			
		||||
	if (msgb_l1len(msg) < sizeof(*fbsb)) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "MSG too short FBSB Req: %u\n",
 | 
			
		||||
			msgb_l1len(msg));
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ch_config = l1ctl_ccch_mode2pchan_config(fbsb->ccch_mode);
 | 
			
		||||
	band_arfcn = ntohs(fbsb->band_arfcn);
 | 
			
		||||
	timeout = ntohs(fbsb->timeout);
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n",
 | 
			
		||||
		arfcn2band_name(band_arfcn),
 | 
			
		||||
		band_arfcn &~ ARFCN_FLAG_MASK);
 | 
			
		||||
 | 
			
		||||
	/* Reset scheduler and clock counter */
 | 
			
		||||
	sched_trx_reset(l1l->trx, true);
 | 
			
		||||
 | 
			
		||||
	/* Configure a single timeslot */
 | 
			
		||||
	sched_trx_configure_ts(l1l->trx, 0, ch_config);
 | 
			
		||||
 | 
			
		||||
	/* Ask SCH handler to send L1CTL_FBSB_CONF */
 | 
			
		||||
	l1l->fbsb_conf_sent = false;
 | 
			
		||||
 | 
			
		||||
	/* Only if current ARFCN differs */
 | 
			
		||||
	if (l1l->trx->band_arfcn != band_arfcn) {
 | 
			
		||||
		/* Update current ARFCN */
 | 
			
		||||
		l1l->trx->band_arfcn = band_arfcn;
 | 
			
		||||
 | 
			
		||||
		/* Tune transceiver to required ARFCN */
 | 
			
		||||
		trx_if_cmd_rxtune(l1l->trx, band_arfcn);
 | 
			
		||||
		trx_if_cmd_txtune(l1l->trx, band_arfcn);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Transceiver might have been powered on before, e.g.
 | 
			
		||||
	 * in case of sending L1CTL_FBSB_REQ due to signal loss. */
 | 
			
		||||
	if (!l1l->trx->powered_up)
 | 
			
		||||
		trx_if_cmd_poweron(l1l->trx);
 | 
			
		||||
 | 
			
		||||
	trx_if_cmd_sync(l1l->trx);
 | 
			
		||||
 | 
			
		||||
	/* Start FBSB expire timer */
 | 
			
		||||
	l1l->fbsb_timer.data = l1l;
 | 
			
		||||
	l1l->fbsb_timer.cb = fbsb_timer_cb;
 | 
			
		||||
	LOGP(DL1C, LOGL_INFO, "Starting FBSB timer %u ms\n", timeout * GSM_TDMA_FN_DURATION_uS / 1000);
 | 
			
		||||
	osmo_timer_schedule(&l1l->fbsb_timer, 2, timeout * GSM_TDMA_FN_DURATION_uS);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_pm_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t band_arfcn_start, band_arfcn_stop;
 | 
			
		||||
	struct l1ctl_pm_req *pmr;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	pmr = (struct l1ctl_pm_req *) msg->l1h;
 | 
			
		||||
	if (msgb_l1len(msg) < sizeof(*pmr)) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "MSG too short PM Req: %u\n",
 | 
			
		||||
			msgb_l1len(msg));
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	band_arfcn_start = ntohs(pmr->range.band_arfcn_from);
 | 
			
		||||
	band_arfcn_stop  = ntohs(pmr->range.band_arfcn_to);
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received power measurement "
 | 
			
		||||
		"request (%s: %d -> %d)\n",
 | 
			
		||||
		arfcn2band_name(band_arfcn_start),
 | 
			
		||||
		band_arfcn_start &~ ARFCN_FLAG_MASK,
 | 
			
		||||
		band_arfcn_stop &~ ARFCN_FLAG_MASK);
 | 
			
		||||
 | 
			
		||||
	/* Send measurement request to transceiver */
 | 
			
		||||
	rc = trx_if_cmd_measure(l1l->trx, band_arfcn_start, band_arfcn_stop);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_reset_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_reset *res;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	res = (struct l1ctl_reset *) msg->l1h;
 | 
			
		||||
	if (msgb_l1len(msg) < sizeof(*res)) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
 | 
			
		||||
			msgb_l1len(msg));
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received reset request (%u)\n",
 | 
			
		||||
		res->type);
 | 
			
		||||
 | 
			
		||||
	switch (res->type) {
 | 
			
		||||
	case L1CTL_RES_T_FULL:
 | 
			
		||||
		/* TODO: implement trx_if_reset() */
 | 
			
		||||
		trx_if_cmd_poweroff(l1l->trx);
 | 
			
		||||
		trx_if_cmd_echo(l1l->trx);
 | 
			
		||||
 | 
			
		||||
		/* Fall through */
 | 
			
		||||
	case L1CTL_RES_T_SCHED:
 | 
			
		||||
		sched_trx_reset(l1l->trx, true);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Unknown L1CTL_RESET_REQ type\n");
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Confirm */
 | 
			
		||||
	rc = l1ctl_tx_reset_conf(l1l, res->type);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_echo_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_hdr *l1h;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Recv Echo Req\n");
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Send Echo Conf\n");
 | 
			
		||||
 | 
			
		||||
	/* Nothing to do, just send it back */
 | 
			
		||||
	l1h = (struct l1ctl_hdr *) msg->l1h;
 | 
			
		||||
	l1h->msg_type = L1CTL_ECHO_CONF;
 | 
			
		||||
	msg->data = msg->l1h;
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_ccch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	enum gsm_phys_chan_config ch_config;
 | 
			
		||||
	struct l1ctl_ccch_mode_req *req;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	req = (struct l1ctl_ccch_mode_req *) msg->l1h;
 | 
			
		||||
	if (msgb_l1len(msg) < sizeof(*req)) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
 | 
			
		||||
			msgb_l1len(msg));
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received CCCH mode request (%u)\n",
 | 
			
		||||
		req->ccch_mode); /* TODO: add value-string for ccch_mode */
 | 
			
		||||
 | 
			
		||||
	/* Make sure that TS0 is allocated and configured */
 | 
			
		||||
	ts = l1l->trx->ts_list[0];
 | 
			
		||||
	if (ts == NULL || ts->mf_layout == NULL) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "TS0 is not configured");
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Choose corresponding channel combination */
 | 
			
		||||
	ch_config = l1ctl_ccch_mode2pchan_config(req->ccch_mode);
 | 
			
		||||
 | 
			
		||||
	/* Do nothing if the current mode matches required */
 | 
			
		||||
	if (ts->mf_layout->chan_config != ch_config)
 | 
			
		||||
		rc = sched_trx_configure_ts(l1l->trx, 0, ch_config);
 | 
			
		||||
 | 
			
		||||
	/* Confirm reconfiguration */
 | 
			
		||||
	if (!rc)
 | 
			
		||||
		rc = l1ctl_tx_ccch_mode_conf(l1l, req->ccch_mode);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_rach_req(struct l1ctl_link *l1l, struct msgb *msg, bool ext)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_ext_rach_req *ext_req;
 | 
			
		||||
	struct l1ctl_rach_req *req;
 | 
			
		||||
	struct l1ctl_info_ul *ul;
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
	size_t len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	ul = (struct l1ctl_info_ul *) msg->l1h;
 | 
			
		||||
 | 
			
		||||
	/* Is it extended (11-bit) RACH or not? */
 | 
			
		||||
	if (ext) {
 | 
			
		||||
		ext_req = (struct l1ctl_ext_rach_req *) ul->payload;
 | 
			
		||||
		ext_req->offset = ntohs(ext_req->offset);
 | 
			
		||||
		ext_req->ra11 = ntohs(ext_req->ra11);
 | 
			
		||||
		len = sizeof(*ext_req);
 | 
			
		||||
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE, "Received extended (11-bit) RACH request "
 | 
			
		||||
			"(offset=%u, synch_seq=%u, ra11=0x%02hx)\n",
 | 
			
		||||
			ext_req->offset, ext_req->synch_seq, ext_req->ra11);
 | 
			
		||||
	} else {
 | 
			
		||||
		req = (struct l1ctl_rach_req *) ul->payload;
 | 
			
		||||
		req->offset = ntohs(req->offset);
 | 
			
		||||
		len = sizeof(*req);
 | 
			
		||||
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE, "Received regular (8-bit) RACH request "
 | 
			
		||||
			"(offset=%u, ra=0x%02x)\n", req->offset, req->ra);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* The controlling L1CTL side always does include the UL info header,
 | 
			
		||||
	 * but may leave it empty. We assume RACH is on TS0 in this case. */
 | 
			
		||||
	if (ul->chan_nr == 0x00) {
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE, "The UL info header is empty, "
 | 
			
		||||
					"assuming RACH is on TS0\n");
 | 
			
		||||
		ul->chan_nr = RSL_CHAN_RACH;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Init a new primitive */
 | 
			
		||||
	rc = sched_prim_init(l1l->trx, &prim, len, ul->chan_nr, ul->link_id);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		goto exit;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Push this primitive to the transmit queue.
 | 
			
		||||
	 * Indicated timeslot needs to be configured.
 | 
			
		||||
	 */
 | 
			
		||||
	rc = sched_prim_push(l1l->trx, prim, ul->chan_nr);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		talloc_free(prim);
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Fill in the payload */
 | 
			
		||||
	memcpy(prim->payload, ul->payload, len);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_proc_est_req_h0(struct trx_instance *trx, struct l1ctl_h0 *h)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	band_arfcn = ntohs(h->band_arfcn);
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a single "
 | 
			
		||||
		"ARFCN=%u channel\n", band_arfcn &~ ARFCN_FLAG_MASK);
 | 
			
		||||
 | 
			
		||||
	/* Do we need to retune? */
 | 
			
		||||
	if (trx->band_arfcn == band_arfcn)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Tune transceiver to required ARFCN */
 | 
			
		||||
	rc |= trx_if_cmd_rxtune(trx, band_arfcn);
 | 
			
		||||
	rc |= trx_if_cmd_txtune(trx, band_arfcn);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* Update current ARFCN */
 | 
			
		||||
	trx->band_arfcn = band_arfcn;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_proc_est_req_h1(struct trx_instance *trx, struct l1ctl_h1 *h)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t ma[64];
 | 
			
		||||
	int i, rc;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "L1CTL_DM_EST_REQ indicates a Frequency "
 | 
			
		||||
		"Hopping (hsn=%u, maio=%u, chans=%u) channel\n",
 | 
			
		||||
		h->hsn, h->maio, h->n);
 | 
			
		||||
 | 
			
		||||
	/* No channels?!? */
 | 
			
		||||
	if (!h->n) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "No channels in mobile allocation?!?\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	} else if (h->n > ARRAY_SIZE(ma)) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "More than 64 channels in mobile allocation?!?\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Convert from network to host byte order */
 | 
			
		||||
	for (i = 0; i < h->n; i++)
 | 
			
		||||
		ma[i] = ntohs(h->ma[i]);
 | 
			
		||||
 | 
			
		||||
	/* Forward hopping parameters to TRX */
 | 
			
		||||
	rc = trx_if_cmd_setfh(trx, h->hsn, h->maio, ma, h->n);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * TODO: update the state of trx_instance somehow
 | 
			
		||||
	 * in order to indicate that it is in hopping mode...
 | 
			
		||||
	 */
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_dm_est_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	enum gsm_phys_chan_config config;
 | 
			
		||||
	struct l1ctl_dm_est_req *est_req;
 | 
			
		||||
	struct l1ctl_info_ul *ul;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
	uint8_t chan_nr, tn;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	ul = (struct l1ctl_info_ul *) msg->l1h;
 | 
			
		||||
	est_req = (struct l1ctl_dm_est_req *) ul->payload;
 | 
			
		||||
 | 
			
		||||
	chan_nr = ul->chan_nr;
 | 
			
		||||
	tn = chan_nr & 0x07;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ "
 | 
			
		||||
		"(tn=%u, chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n",
 | 
			
		||||
		tn, chan_nr, est_req->tsc, est_req->tch_mode);
 | 
			
		||||
 | 
			
		||||
	/* Determine channel config */
 | 
			
		||||
	config = sched_trx_chan_nr2pchan_config(chan_nr);
 | 
			
		||||
	if (config == GSM_PCHAN_NONE) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Couldn't determine channel config\n");
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Frequency hopping? */
 | 
			
		||||
	if (est_req->h)
 | 
			
		||||
		rc = l1ctl_proc_est_req_h1(l1l->trx, &est_req->h1);
 | 
			
		||||
	else /* Single ARFCN */
 | 
			
		||||
		rc = l1ctl_proc_est_req_h0(l1l->trx, &est_req->h0);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		goto exit;
 | 
			
		||||
 | 
			
		||||
	/* Update TSC (Training Sequence Code) */
 | 
			
		||||
	l1l->trx->tsc = est_req->tsc;
 | 
			
		||||
 | 
			
		||||
	/* Configure requested TS */
 | 
			
		||||
	rc = sched_trx_configure_ts(l1l->trx, tn, config);
 | 
			
		||||
	ts = l1l->trx->ts_list[tn];
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Deactivate all lchans */
 | 
			
		||||
	sched_trx_deactivate_all_lchans(ts);
 | 
			
		||||
 | 
			
		||||
	/* Activate only requested lchans */
 | 
			
		||||
	rc = sched_trx_set_lchans(ts, chan_nr, 1, est_req->tch_mode);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n");
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_dm_rel_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ, "
 | 
			
		||||
		"switching back to CCCH\n");
 | 
			
		||||
 | 
			
		||||
	/* Reset scheduler */
 | 
			
		||||
	sched_trx_reset(l1l->trx, false);
 | 
			
		||||
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
 | 
			
		||||
 */
 | 
			
		||||
static int l1ctl_rx_dt_req(struct l1ctl_link *l1l,
 | 
			
		||||
	struct msgb *msg, bool traffic)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_info_ul *ul;
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
	uint8_t chan_nr, link_id;
 | 
			
		||||
	size_t payload_len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Extract UL frame header */
 | 
			
		||||
	ul = (struct l1ctl_info_ul *) msg->l1h;
 | 
			
		||||
 | 
			
		||||
	/* Calculate the payload len */
 | 
			
		||||
	msg->l2h = ul->payload;
 | 
			
		||||
	payload_len = msgb_l2len(msg);
 | 
			
		||||
 | 
			
		||||
	/* Obtain channel description */
 | 
			
		||||
	chan_nr = ul->chan_nr;
 | 
			
		||||
	link_id = ul->link_id & 0x40;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, "
 | 
			
		||||
		"link_id=0x%02x, len=%zu)\n", traffic ? "TRAFFIC" : "DATA",
 | 
			
		||||
		chan_nr, link_id, payload_len);
 | 
			
		||||
 | 
			
		||||
	/* Init a new primitive */
 | 
			
		||||
	rc = sched_prim_init(l1l->trx, &prim, payload_len,
 | 
			
		||||
		chan_nr, link_id);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		goto exit;
 | 
			
		||||
 | 
			
		||||
	/* Push this primitive to transmit queue */
 | 
			
		||||
	rc = sched_prim_push(l1l->trx, prim, chan_nr);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		talloc_free(prim);
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Fill in the payload */
 | 
			
		||||
	memcpy(prim->payload, ul->payload, payload_len);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_param_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_par_req *par_req;
 | 
			
		||||
	struct l1ctl_info_ul *ul;
 | 
			
		||||
 | 
			
		||||
	ul = (struct l1ctl_info_ul *) msg->l1h;
 | 
			
		||||
	par_req = (struct l1ctl_par_req *) ul->payload;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_PARAM_REQ "
 | 
			
		||||
		"(ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power);
 | 
			
		||||
 | 
			
		||||
	/* Instruct TRX to use new TA value */
 | 
			
		||||
	if (l1l->trx->ta != par_req->ta) {
 | 
			
		||||
		trx_if_cmd_setta(l1l->trx, par_req->ta);
 | 
			
		||||
		l1l->trx->ta = par_req->ta;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l1l->trx->tx_power = par_req->tx_power;
 | 
			
		||||
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_tch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_tch_mode_req *req;
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	req = (struct l1ctl_tch_mode_req *) msg->l1h;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ "
 | 
			
		||||
		"(tch_mode=%u, audio_mode=%u)\n", req->tch_mode, req->audio_mode);
 | 
			
		||||
 | 
			
		||||
	/* Iterate over timeslot list */
 | 
			
		||||
	for (i = 0; i < TRX_TS_COUNT; i++) {
 | 
			
		||||
		/* Timeslot is not allocated */
 | 
			
		||||
		ts = l1l->trx->ts_list[i];
 | 
			
		||||
		if (ts == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Timeslot is not configured */
 | 
			
		||||
		if (ts->mf_layout == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Iterate over all allocated lchans */
 | 
			
		||||
		llist_for_each_entry(lchan, &ts->lchans, list) {
 | 
			
		||||
			/* Omit inactive channels */
 | 
			
		||||
			if (!lchan->active)
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			/* Set TCH mode */
 | 
			
		||||
			lchan->tch_mode = req->tch_mode;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* TODO: do we need to care about audio_mode? */
 | 
			
		||||
 | 
			
		||||
	/* Re-use the original message as confirmation */
 | 
			
		||||
	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->data;
 | 
			
		||||
	l1h->msg_type = L1CTL_TCH_MODE_CONF;
 | 
			
		||||
 | 
			
		||||
	return l1ctl_link_send(l1l, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_rx_crypto_req(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_crypto_req *req;
 | 
			
		||||
	struct l1ctl_info_ul *ul;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
	uint8_t tn;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	ul = (struct l1ctl_info_ul *) msg->l1h;
 | 
			
		||||
	req = (struct l1ctl_crypto_req *) ul->payload;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
 | 
			
		||||
		req->algo, req->key_len);
 | 
			
		||||
 | 
			
		||||
	/* Determine TS index */
 | 
			
		||||
	tn = ul->chan_nr & 0x7;
 | 
			
		||||
 | 
			
		||||
	/* Make sure that required TS is allocated and configured */
 | 
			
		||||
	ts = l1l->trx->ts_list[tn];
 | 
			
		||||
	if (ts == NULL || ts->mf_layout == NULL) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "TS %u is not configured\n", tn);
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Poke scheduler */
 | 
			
		||||
	rc = sched_trx_start_ciphering(ts, req->algo, req->key, req->key_len);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Couldn't configure ciphering\n");
 | 
			
		||||
		rc = -EINVAL;
 | 
			
		||||
		goto exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	msgb_free(msg);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_hdr *l1h;
 | 
			
		||||
 | 
			
		||||
	l1h = (struct l1ctl_hdr *) msg->l1h;
 | 
			
		||||
	msg->l1h = l1h->data;
 | 
			
		||||
 | 
			
		||||
	switch (l1h->msg_type) {
 | 
			
		||||
	case L1CTL_FBSB_REQ:
 | 
			
		||||
		return l1ctl_rx_fbsb_req(l1l, msg);
 | 
			
		||||
	case L1CTL_PM_REQ:
 | 
			
		||||
		return l1ctl_rx_pm_req(l1l, msg);
 | 
			
		||||
	case L1CTL_RESET_REQ:
 | 
			
		||||
		return l1ctl_rx_reset_req(l1l, msg);
 | 
			
		||||
	case L1CTL_ECHO_REQ:
 | 
			
		||||
		return l1ctl_rx_echo_req(l1l, msg);
 | 
			
		||||
	case L1CTL_CCCH_MODE_REQ:
 | 
			
		||||
		return l1ctl_rx_ccch_mode_req(l1l, msg);
 | 
			
		||||
	case L1CTL_RACH_REQ:
 | 
			
		||||
		return l1ctl_rx_rach_req(l1l, msg, false);
 | 
			
		||||
	case L1CTL_EXT_RACH_REQ:
 | 
			
		||||
		return l1ctl_rx_rach_req(l1l, msg, true);
 | 
			
		||||
	case L1CTL_DM_EST_REQ:
 | 
			
		||||
		return l1ctl_rx_dm_est_req(l1l, msg);
 | 
			
		||||
	case L1CTL_DM_REL_REQ:
 | 
			
		||||
		return l1ctl_rx_dm_rel_req(l1l, msg);
 | 
			
		||||
	case L1CTL_DATA_REQ:
 | 
			
		||||
		return l1ctl_rx_dt_req(l1l, msg, false);
 | 
			
		||||
	case L1CTL_TRAFFIC_REQ:
 | 
			
		||||
		return l1ctl_rx_dt_req(l1l, msg, true);
 | 
			
		||||
	case L1CTL_PARAM_REQ:
 | 
			
		||||
		return l1ctl_rx_param_req(l1l, msg);
 | 
			
		||||
	case L1CTL_TCH_MODE_REQ:
 | 
			
		||||
		return l1ctl_rx_tch_mode_req(l1l, msg);
 | 
			
		||||
	case L1CTL_CRYPTO_REQ:
 | 
			
		||||
		return l1ctl_rx_crypto_req(l1l, msg);
 | 
			
		||||
 | 
			
		||||
	/* Not (yet) handled messages */
 | 
			
		||||
	case L1CTL_NEIGH_PM_REQ:
 | 
			
		||||
	case L1CTL_DATA_TBF_REQ:
 | 
			
		||||
	case L1CTL_TBF_CFG_REQ:
 | 
			
		||||
	case L1CTL_DM_FREQ_REQ:
 | 
			
		||||
	case L1CTL_SIM_REQ:
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE, "Ignoring unsupported message "
 | 
			
		||||
			"(type=%u)\n", l1h->msg_type);
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n", l1h->msg_type,
 | 
			
		||||
			osmo_hexdump(msgb_data(msg), msgb_length(msg)));
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void l1ctl_shutdown_cb(struct l1ctl_link *l1l)
 | 
			
		||||
{
 | 
			
		||||
	/* Abort FBSB expire timer */
 | 
			
		||||
	if (osmo_timer_pending(&l1l->fbsb_timer))
 | 
			
		||||
		osmo_timer_del(&l1l->fbsb_timer);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_link.h"
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
 | 
			
		||||
/* Event handlers */
 | 
			
		||||
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg);
 | 
			
		||||
void l1ctl_shutdown_cb(struct l1ctl_link *l1l);
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
 | 
			
		||||
	struct l1ctl_info_dl *dl_info, uint8_t bsic);
 | 
			
		||||
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode);
 | 
			
		||||
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
 | 
			
		||||
	int dbm, int last);
 | 
			
		||||
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type);
 | 
			
		||||
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type);
 | 
			
		||||
 | 
			
		||||
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
 | 
			
		||||
	uint8_t *l2, size_t l2_len, bool traffic);
 | 
			
		||||
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
 | 
			
		||||
	struct l1ctl_info_dl *data, bool traffic);
 | 
			
		||||
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l,
 | 
			
		||||
	uint16_t band_arfcn, uint32_t fn);
 | 
			
		||||
@@ -1,316 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * GSM L1 control socket (/tmp/osmocom_l2) handlers
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
 | 
			
		||||
 * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <sys/socket.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/write_queue.h>
 | 
			
		||||
 | 
			
		||||
#include "trxcon.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "l1ctl_link.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
static struct value_string l1ctl_evt_names[] = {
 | 
			
		||||
	{ 0, NULL } /* no events? */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm_state l1ctl_fsm_states[] = {
 | 
			
		||||
	[L1CTL_STATE_IDLE] = {
 | 
			
		||||
		.out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED),
 | 
			
		||||
		.name = "IDLE",
 | 
			
		||||
	},
 | 
			
		||||
	[L1CTL_STATE_CONNECTED] = {
 | 
			
		||||
		.out_state_mask = GEN_MASK(L1CTL_STATE_IDLE),
 | 
			
		||||
		.name = "CONNECTED",
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm l1ctl_fsm = {
 | 
			
		||||
	.name = "l1ctl_link_fsm",
 | 
			
		||||
	.states = l1ctl_fsm_states,
 | 
			
		||||
	.num_states = ARRAY_SIZE(l1ctl_fsm_states),
 | 
			
		||||
	.log_subsys = DL1C,
 | 
			
		||||
	.event_names = l1ctl_evt_names,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int l1ctl_link_read_cb(struct osmo_fd *bfd)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
 | 
			
		||||
	struct msgb *msg;
 | 
			
		||||
	uint16_t len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Attempt to read from socket */
 | 
			
		||||
	rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD);
 | 
			
		||||
	if (rc < L1CTL_MSG_LEN_FIELD) {
 | 
			
		||||
		LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n");
 | 
			
		||||
		if (rc >= 0)
 | 
			
		||||
			rc = -EIO;
 | 
			
		||||
		l1ctl_link_close_conn(l1l);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check message length */
 | 
			
		||||
	len = ntohs(len);
 | 
			
		||||
	if (len > L1CTL_LENGTH) {
 | 
			
		||||
		LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new msg */
 | 
			
		||||
	msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
 | 
			
		||||
		L1CTL_HEADROOM, "l1ctl_rx_msg");
 | 
			
		||||
	if (!msg) {
 | 
			
		||||
		LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n");
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msg->l1h = msgb_put(msg, len);
 | 
			
		||||
	rc = read(bfd->fd, msg->l1h, msgb_l1len(msg));
 | 
			
		||||
	if (rc != len) {
 | 
			
		||||
		LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: "
 | 
			
		||||
			"%s\n", len, rc, strerror(errno));
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Debug print */
 | 
			
		||||
	LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n",
 | 
			
		||||
		osmo_hexdump(msg->data, msg->len));
 | 
			
		||||
 | 
			
		||||
	/* Call L1CTL handler */
 | 
			
		||||
	l1ctl_rx_cb(l1l, msg);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	int len;
 | 
			
		||||
 | 
			
		||||
	if (bfd->fd <= 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	len = write(bfd->fd, msg->data, msg->len);
 | 
			
		||||
	if (len != msg->len) {
 | 
			
		||||
		LOGP(DL1D, LOGL_ERROR, "Failed to write data: "
 | 
			
		||||
			"written (%d) < msg_len (%d)\n", len, msg->len);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Connection handler */
 | 
			
		||||
static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
 | 
			
		||||
	struct osmo_fd *conn_bfd = &l1l->wq.bfd;
 | 
			
		||||
	struct sockaddr_un un_addr;
 | 
			
		||||
	socklen_t len;
 | 
			
		||||
	int cfd;
 | 
			
		||||
 | 
			
		||||
	len = sizeof(un_addr);
 | 
			
		||||
	cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
 | 
			
		||||
	if (cfd < 0) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n");
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check if we already have an active connection */
 | 
			
		||||
	if (conn_bfd->fd != -1) {
 | 
			
		||||
		LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: "
 | 
			
		||||
			"we already have another active\n");
 | 
			
		||||
		close(cfd);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_wqueue_init(&l1l->wq, 100);
 | 
			
		||||
	INIT_LLIST_HEAD(&conn_bfd->list);
 | 
			
		||||
 | 
			
		||||
	l1l->wq.write_cb = l1ctl_link_write_cb;
 | 
			
		||||
	l1l->wq.read_cb = l1ctl_link_read_cb;
 | 
			
		||||
	osmo_fd_setup(conn_bfd, cfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, l1l, 0);
 | 
			
		||||
 | 
			
		||||
	if (osmo_fd_register(conn_bfd) != 0) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n");
 | 
			
		||||
		close(conn_bfd->fd);
 | 
			
		||||
		conn_bfd->fd = -1;
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l);
 | 
			
		||||
	osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0);
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n");
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t *len;
 | 
			
		||||
 | 
			
		||||
	/* Debug print */
 | 
			
		||||
	LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n",
 | 
			
		||||
		osmo_hexdump(msg->data, msg->len));
 | 
			
		||||
 | 
			
		||||
	if (msg->l1h != msg->data)
 | 
			
		||||
		LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
 | 
			
		||||
 | 
			
		||||
	/* Prepend 16-bit length before sending */
 | 
			
		||||
	len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
 | 
			
		||||
	osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
 | 
			
		||||
 | 
			
		||||
	if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) {
 | 
			
		||||
		LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
 | 
			
		||||
		msgb_free(msg);
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int l1ctl_link_close_conn(struct l1ctl_link *l1l)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_fd *conn_bfd = &l1l->wq.bfd;
 | 
			
		||||
 | 
			
		||||
	if (conn_bfd->fd <= 0)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	/* Close connection socket */
 | 
			
		||||
	osmo_fd_unregister(conn_bfd);
 | 
			
		||||
	close(conn_bfd->fd);
 | 
			
		||||
	conn_bfd->fd = -1;
 | 
			
		||||
 | 
			
		||||
	/* Clear pending messages */
 | 
			
		||||
	osmo_wqueue_clear(&l1l->wq);
 | 
			
		||||
 | 
			
		||||
	osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l);
 | 
			
		||||
	osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_link *l1l;
 | 
			
		||||
	struct osmo_fd *bfd;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path);
 | 
			
		||||
 | 
			
		||||
	l1l = talloc_zero(tall_ctx, struct l1ctl_link);
 | 
			
		||||
	if (!l1l) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new dedicated state machine */
 | 
			
		||||
	l1l->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l,
 | 
			
		||||
		NULL, LOGL_DEBUG, "l1ctl_link");
 | 
			
		||||
	if (l1l->fsm == NULL) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
 | 
			
		||||
			"of FSM '%s'\n", l1ctl_fsm.name);
 | 
			
		||||
		talloc_free(l1l);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Create a socket and bind handlers */
 | 
			
		||||
	bfd = &l1l->listen_bfd;
 | 
			
		||||
 | 
			
		||||
	/* Bind connection handler */
 | 
			
		||||
	osmo_fd_setup(bfd, -1, OSMO_FD_READ, l1ctl_link_accept, l1l, 0);
 | 
			
		||||
 | 
			
		||||
	rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path,
 | 
			
		||||
		OSMO_SOCK_F_BIND);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
 | 
			
		||||
			strerror(errno));
 | 
			
		||||
		osmo_fsm_inst_free(l1l->fsm);
 | 
			
		||||
		talloc_free(l1l);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Bind shutdown handler */
 | 
			
		||||
	l1l->shutdown_cb = l1ctl_shutdown_cb;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * To be able to accept first connection and
 | 
			
		||||
	 * drop others, it should be set to -1
 | 
			
		||||
	 */
 | 
			
		||||
	l1l->wq.bfd.fd = -1;
 | 
			
		||||
 | 
			
		||||
	return l1l;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void l1ctl_link_shutdown(struct l1ctl_link *l1l)
 | 
			
		||||
{
 | 
			
		||||
	struct osmo_fd *listen_bfd;
 | 
			
		||||
 | 
			
		||||
	/* May be unallocated due to init error */
 | 
			
		||||
	if (!l1l)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n");
 | 
			
		||||
 | 
			
		||||
	/* Call shutdown callback */
 | 
			
		||||
	if (l1l->shutdown_cb != NULL)
 | 
			
		||||
		l1l->shutdown_cb(l1l);
 | 
			
		||||
 | 
			
		||||
	listen_bfd = &l1l->listen_bfd;
 | 
			
		||||
 | 
			
		||||
	/* Check if we have an established connection */
 | 
			
		||||
	if (l1l->wq.bfd.fd != -1)
 | 
			
		||||
		l1ctl_link_close_conn(l1l);
 | 
			
		||||
 | 
			
		||||
	/* Unbind listening socket */
 | 
			
		||||
	if (listen_bfd->fd != -1) {
 | 
			
		||||
		osmo_fd_unregister(listen_bfd);
 | 
			
		||||
		close(listen_bfd->fd);
 | 
			
		||||
		listen_bfd->fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_fsm_inst_free(l1l->fsm);
 | 
			
		||||
	talloc_free(l1l);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static __attribute__((constructor)) void on_dso_load(void)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(osmo_fsm_register(&l1ctl_fsm) == 0);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/write_queue.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
 | 
			
		||||
#define L1CTL_LENGTH 256
 | 
			
		||||
#define L1CTL_HEADROOM 32
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Each L1CTL message gets its own length pushed
 | 
			
		||||
 * as two bytes in front before sending.
 | 
			
		||||
 */
 | 
			
		||||
#define L1CTL_MSG_LEN_FIELD 2
 | 
			
		||||
 | 
			
		||||
/* Forward declaration to avoid mutual include */
 | 
			
		||||
struct trx_instance;
 | 
			
		||||
 | 
			
		||||
enum l1ctl_fsm_states {
 | 
			
		||||
	L1CTL_STATE_IDLE = 0,
 | 
			
		||||
	L1CTL_STATE_CONNECTED,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct l1ctl_link {
 | 
			
		||||
	struct osmo_fsm_inst *fsm;
 | 
			
		||||
	struct osmo_fd listen_bfd;
 | 
			
		||||
	struct osmo_wqueue wq;
 | 
			
		||||
 | 
			
		||||
	/* Bind TRX instance */
 | 
			
		||||
	struct trx_instance *trx;
 | 
			
		||||
 | 
			
		||||
	/* L1CTL handlers specific */
 | 
			
		||||
	struct osmo_timer_list fbsb_timer;
 | 
			
		||||
	bool fbsb_conf_sent;
 | 
			
		||||
 | 
			
		||||
	/* Shutdown callback */
 | 
			
		||||
	void (*shutdown_cb)(struct l1ctl_link *l1l);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path);
 | 
			
		||||
void l1ctl_link_shutdown(struct l1ctl_link *l1l);
 | 
			
		||||
 | 
			
		||||
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg);
 | 
			
		||||
int l1ctl_link_close_conn(struct l1ctl_link *l1l);
 | 
			
		||||
@@ -1,387 +0,0 @@
 | 
			
		||||
/* Messages to be sent between the different layers */
 | 
			
		||||
 | 
			
		||||
/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
 | 
			
		||||
 * (C) 2010 by Holger Hans Peter Freyther
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef __L1CTL_PROTO_H__
 | 
			
		||||
#define __L1CTL_PROTO_H__
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	_L1CTL_NONE		= 0,
 | 
			
		||||
	L1CTL_FBSB_REQ,
 | 
			
		||||
	L1CTL_FBSB_CONF,
 | 
			
		||||
	L1CTL_DATA_IND,
 | 
			
		||||
	L1CTL_RACH_REQ,
 | 
			
		||||
	L1CTL_DM_EST_REQ,
 | 
			
		||||
	L1CTL_DATA_REQ,
 | 
			
		||||
	L1CTL_RESET_IND,
 | 
			
		||||
	L1CTL_PM_REQ,		/* power measurement */
 | 
			
		||||
	L1CTL_PM_CONF,		/* power measurement */
 | 
			
		||||
	L1CTL_ECHO_REQ,
 | 
			
		||||
	L1CTL_ECHO_CONF,
 | 
			
		||||
	L1CTL_RACH_CONF,
 | 
			
		||||
	L1CTL_RESET_REQ,
 | 
			
		||||
	L1CTL_RESET_CONF,
 | 
			
		||||
	L1CTL_DATA_CONF,
 | 
			
		||||
	L1CTL_CCCH_MODE_REQ,
 | 
			
		||||
	L1CTL_CCCH_MODE_CONF,
 | 
			
		||||
	L1CTL_DM_REL_REQ,
 | 
			
		||||
	L1CTL_PARAM_REQ,
 | 
			
		||||
	L1CTL_DM_FREQ_REQ,
 | 
			
		||||
	L1CTL_CRYPTO_REQ,
 | 
			
		||||
	L1CTL_SIM_REQ,
 | 
			
		||||
	L1CTL_SIM_CONF,
 | 
			
		||||
	L1CTL_TCH_MODE_REQ,
 | 
			
		||||
	L1CTL_TCH_MODE_CONF,
 | 
			
		||||
	L1CTL_NEIGH_PM_REQ,
 | 
			
		||||
	L1CTL_NEIGH_PM_IND,
 | 
			
		||||
	L1CTL_TRAFFIC_REQ,
 | 
			
		||||
	L1CTL_TRAFFIC_CONF,
 | 
			
		||||
	L1CTL_TRAFFIC_IND,
 | 
			
		||||
	L1CTL_BURST_IND,
 | 
			
		||||
 | 
			
		||||
	/* configure TBF for uplink/downlink */
 | 
			
		||||
	L1CTL_TBF_CFG_REQ,
 | 
			
		||||
	L1CTL_TBF_CFG_CONF,
 | 
			
		||||
 | 
			
		||||
	L1CTL_DATA_TBF_REQ,
 | 
			
		||||
	L1CTL_DATA_TBF_CONF,
 | 
			
		||||
 | 
			
		||||
	/* Extended (11-bit) RACH (see 3GPP TS 05.02, section 5.2.7) */
 | 
			
		||||
	L1CTL_EXT_RACH_REQ,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum ccch_mode {
 | 
			
		||||
	CCCH_MODE_NONE = 0,
 | 
			
		||||
	CCCH_MODE_NON_COMBINED,
 | 
			
		||||
	CCCH_MODE_COMBINED,
 | 
			
		||||
	CCCH_MODE_COMBINED_CBCH,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum neigh_mode {
 | 
			
		||||
	NEIGH_MODE_NONE = 0,
 | 
			
		||||
	NEIGH_MODE_PM,
 | 
			
		||||
	NEIGH_MODE_SB,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum l1ctl_coding_scheme {
 | 
			
		||||
	L1CTL_CS_NONE,
 | 
			
		||||
	L1CTL_CS1,
 | 
			
		||||
	L1CTL_CS2,
 | 
			
		||||
	L1CTL_CS3,
 | 
			
		||||
	L1CTL_CS4,
 | 
			
		||||
	L1CTL_MCS1,
 | 
			
		||||
	L1CTL_MCS2,
 | 
			
		||||
	L1CTL_MCS3,
 | 
			
		||||
	L1CTL_MCS4,
 | 
			
		||||
	L1CTL_MCS5,
 | 
			
		||||
	L1CTL_MCS6,
 | 
			
		||||
	L1CTL_MCS7,
 | 
			
		||||
	L1CTL_MCS8,
 | 
			
		||||
	L1CTL_MCS9,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * NOTE: struct size. We do add manual padding out of the believe
 | 
			
		||||
 * that it will avoid some unaligned access.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* there are no more messages in a sequence */
 | 
			
		||||
#define L1CTL_F_DONE	0x01
 | 
			
		||||
 | 
			
		||||
struct l1ctl_hdr {
 | 
			
		||||
	uint8_t msg_type;
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
	uint8_t padding[2];
 | 
			
		||||
	uint8_t data[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * downlink info ... down from the BTS..
 | 
			
		||||
 */
 | 
			
		||||
struct l1ctl_info_dl {
 | 
			
		||||
	/* GSM 08.58 channel number (9.3.1) */
 | 
			
		||||
	uint8_t chan_nr;
 | 
			
		||||
	/* GSM 08.58 link identifier (9.3.2) */
 | 
			
		||||
	uint8_t link_id;
 | 
			
		||||
	/* the ARFCN and the band. FIXME: what about MAIO? */
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
 | 
			
		||||
	uint32_t frame_nr;
 | 
			
		||||
 | 
			
		||||
	uint8_t rx_level;	/* 0 .. 63 in typical GSM notation (dBm+110) */
 | 
			
		||||
	uint8_t snr;		/* Signal/Noise Ration (dB) */
 | 
			
		||||
	uint8_t num_biterr;
 | 
			
		||||
	uint8_t fire_crc;
 | 
			
		||||
 | 
			
		||||
	uint8_t payload[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* new CCCH was found. This is following the header */
 | 
			
		||||
struct l1ctl_fbsb_conf {
 | 
			
		||||
	int16_t initial_freq_err;
 | 
			
		||||
	uint8_t result;
 | 
			
		||||
	uint8_t bsic;
 | 
			
		||||
	/* FIXME: contents of cell_info ? */
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* CCCH mode was changed */
 | 
			
		||||
struct l1ctl_ccch_mode_conf {
 | 
			
		||||
	uint8_t ccch_mode;	/* enum ccch_mode */
 | 
			
		||||
	uint8_t padding[3];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 44.014, section 5.1 (Calypso specific numbers) */
 | 
			
		||||
enum l1ctl_tch_loop_mode {
 | 
			
		||||
	L1CTL_TCH_LOOP_OPEN	= 0x00,
 | 
			
		||||
	L1CTL_TCH_LOOP_A	= 0x01,
 | 
			
		||||
	L1CTL_TCH_LOOP_B	= 0x02,
 | 
			
		||||
	L1CTL_TCH_LOOP_C	= 0x03,
 | 
			
		||||
	L1CTL_TCH_LOOP_D	= 0x04,
 | 
			
		||||
	L1CTL_TCH_LOOP_E	= 0x05,
 | 
			
		||||
	L1CTL_TCH_LOOP_F	= 0x06,
 | 
			
		||||
	L1CTL_TCH_LOOP_I	= 0x07,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* TCH mode was changed */
 | 
			
		||||
struct l1ctl_tch_mode_conf {
 | 
			
		||||
	uint8_t tch_mode;	/* enum tch_mode */
 | 
			
		||||
	uint8_t audio_mode;
 | 
			
		||||
	uint8_t tch_loop_mode;	/* enum l1ctl_tch_loop_mode */
 | 
			
		||||
	uint8_t padding[1];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* data on the CCCH was found. This is following the header */
 | 
			
		||||
struct l1ctl_data_ind {
 | 
			
		||||
	uint8_t data[23];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* traffic from the network */
 | 
			
		||||
struct l1ctl_traffic_ind {
 | 
			
		||||
	uint8_t data[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * uplink info
 | 
			
		||||
 */
 | 
			
		||||
struct l1ctl_info_ul {
 | 
			
		||||
	/* GSM 08.58 channel number (9.3.1) */
 | 
			
		||||
	uint8_t chan_nr;
 | 
			
		||||
	/* GSM 08.58 link identifier (9.3.2) */
 | 
			
		||||
	uint8_t link_id;
 | 
			
		||||
	uint8_t padding[2];
 | 
			
		||||
 | 
			
		||||
	uint8_t payload[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_info_ul_tbf {
 | 
			
		||||
	/* references l1ctl_tbf_cfg_req.tbf_nr */
 | 
			
		||||
	uint8_t tbf_nr;
 | 
			
		||||
	uint8_t coding_scheme;
 | 
			
		||||
	uint8_t padding[2];
 | 
			
		||||
	/* RLC/MAC block, size determines CS */
 | 
			
		||||
	uint8_t payload[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * msg for FBSB_REQ
 | 
			
		||||
 * the l1_info_ul header is in front
 | 
			
		||||
 */
 | 
			
		||||
struct l1ctl_fbsb_req {
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	uint16_t timeout;	/* in TDMA frames */
 | 
			
		||||
 | 
			
		||||
	uint16_t freq_err_thresh1;
 | 
			
		||||
	uint16_t freq_err_thresh2;
 | 
			
		||||
 | 
			
		||||
	uint8_t num_freqerr_avg;
 | 
			
		||||
	uint8_t flags;		/* L1CTL_FBSB_F_* */
 | 
			
		||||
	uint8_t sync_info_idx;
 | 
			
		||||
	uint8_t ccch_mode;	/* enum ccch_mode */
 | 
			
		||||
	uint8_t rxlev_exp;	/* expected signal level */
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
#define L1CTL_FBSB_F_FB0	(1 << 0)
 | 
			
		||||
#define L1CTL_FBSB_F_FB1	(1 << 1)
 | 
			
		||||
#define L1CTL_FBSB_F_SB		(1 << 2)
 | 
			
		||||
#define L1CTL_FBSB_F_FB01SB	(L1CTL_FBSB_F_FB0|L1CTL_FBSB_F_FB1|L1CTL_FBSB_F_SB)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * msg for CCCH_MODE_REQ
 | 
			
		||||
 * the l1_info_ul header is in front
 | 
			
		||||
 */
 | 
			
		||||
struct l1ctl_ccch_mode_req {
 | 
			
		||||
	uint8_t ccch_mode;	/* enum ccch_mode */
 | 
			
		||||
	uint8_t padding[3];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * msg for TCH_MODE_REQ
 | 
			
		||||
 * the l1_info_ul header is in front
 | 
			
		||||
 */
 | 
			
		||||
struct l1ctl_tch_mode_req {
 | 
			
		||||
	uint8_t tch_mode;	/* enum gsm48_chan_mode */
 | 
			
		||||
#define AUDIO_TX_MICROPHONE	(1<<0)
 | 
			
		||||
#define AUDIO_TX_TRAFFIC_REQ	(1<<1)
 | 
			
		||||
#define AUDIO_RX_SPEAKER	(1<<2)
 | 
			
		||||
#define AUDIO_RX_TRAFFIC_IND	(1<<3)
 | 
			
		||||
	uint8_t audio_mode;
 | 
			
		||||
	uint8_t tch_loop_mode;	/* enum l1ctl_tch_loop_mode */
 | 
			
		||||
	uint8_t padding[1];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* the l1_info_ul header is in front */
 | 
			
		||||
struct l1ctl_rach_req {
 | 
			
		||||
	uint8_t ra;
 | 
			
		||||
	uint8_t combined;
 | 
			
		||||
	uint16_t offset;
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* the l1_info_ul header is in front */
 | 
			
		||||
struct l1ctl_ext_rach_req {
 | 
			
		||||
	uint16_t ra11;
 | 
			
		||||
	uint8_t synch_seq;
 | 
			
		||||
	uint8_t combined;
 | 
			
		||||
	uint16_t offset;
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* the l1_info_ul header is in front */
 | 
			
		||||
struct l1ctl_par_req {
 | 
			
		||||
	int8_t ta;
 | 
			
		||||
	uint8_t tx_power;
 | 
			
		||||
	uint8_t padding[2];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_h0 {
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_h1 {
 | 
			
		||||
	uint8_t hsn;
 | 
			
		||||
	uint8_t maio;
 | 
			
		||||
	uint8_t n;
 | 
			
		||||
	uint8_t _padding[1];
 | 
			
		||||
	uint16_t ma[64];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_dm_est_req {
 | 
			
		||||
	uint8_t tsc;
 | 
			
		||||
	uint8_t h;
 | 
			
		||||
	union {
 | 
			
		||||
		struct l1ctl_h0 h0;
 | 
			
		||||
		struct l1ctl_h1 h1;
 | 
			
		||||
	};
 | 
			
		||||
	uint8_t tch_mode;
 | 
			
		||||
	uint8_t audio_mode;
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_dm_freq_req {
 | 
			
		||||
	uint16_t fn;
 | 
			
		||||
	uint8_t tsc;
 | 
			
		||||
	uint8_t h;
 | 
			
		||||
	union {
 | 
			
		||||
		struct l1ctl_h0 h0;
 | 
			
		||||
		struct l1ctl_h1 h1;
 | 
			
		||||
	};
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_crypto_req {
 | 
			
		||||
	uint8_t algo;
 | 
			
		||||
	uint8_t key_len;
 | 
			
		||||
	uint8_t key[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_pm_req {
 | 
			
		||||
	uint8_t type;
 | 
			
		||||
	uint8_t padding[3];
 | 
			
		||||
 | 
			
		||||
	union {
 | 
			
		||||
		struct {
 | 
			
		||||
			uint16_t band_arfcn_from;
 | 
			
		||||
			uint16_t band_arfcn_to;
 | 
			
		||||
		} range;
 | 
			
		||||
	};
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
#define BI_FLG_DUMMY (1 << 4)
 | 
			
		||||
#define BI_FLG_SACCH (1 << 5)
 | 
			
		||||
 | 
			
		||||
struct l1ctl_burst_ind {
 | 
			
		||||
	uint32_t frame_nr;
 | 
			
		||||
	uint16_t band_arfcn;    /* ARFCN + band + ul indicator               */
 | 
			
		||||
	uint8_t chan_nr;        /* GSM 08.58 channel number (9.3.1)          */
 | 
			
		||||
	uint8_t flags;          /* BI_FLG_xxx + burst_id = 2LSBs             */
 | 
			
		||||
	uint8_t rx_level;       /* 0 .. 63 in typical GSM notation (dBm+110) */
 | 
			
		||||
	uint8_t snr;            /* Reported SNR >> 8 (0-255)                 */
 | 
			
		||||
	uint8_t bits[15];       /* 114 bits + 2 steal bits. Filled MSB first */
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* a single L1CTL_PM response */
 | 
			
		||||
struct l1ctl_pm_conf {
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	uint8_t pm[2];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
enum l1ctl_reset_type {
 | 
			
		||||
	L1CTL_RES_T_BOOT,	/* only _IND */
 | 
			
		||||
	L1CTL_RES_T_FULL,
 | 
			
		||||
	L1CTL_RES_T_SCHED,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* argument to L1CTL_RESET_REQ and L1CTL_RESET_IND */
 | 
			
		||||
struct l1ctl_reset {
 | 
			
		||||
	uint8_t type;
 | 
			
		||||
	uint8_t pad[3];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_neigh_pm_req {
 | 
			
		||||
	uint8_t n;
 | 
			
		||||
	uint8_t padding[1];
 | 
			
		||||
	uint16_t band_arfcn[64];
 | 
			
		||||
	uint8_t tn[64];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* neighbour cell measurement results */
 | 
			
		||||
struct l1ctl_neigh_pm_ind {
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	uint8_t pm[2];
 | 
			
		||||
	uint8_t tn;
 | 
			
		||||
	uint8_t padding;
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
/* traffic data to network */
 | 
			
		||||
struct l1ctl_traffic_req {
 | 
			
		||||
	uint8_t data[0];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
struct l1ctl_tbf_cfg_req {
 | 
			
		||||
	/* future support for multiple concurrent TBFs. 0 for now */
 | 
			
		||||
	uint8_t tbf_nr;
 | 
			
		||||
	/* is this about an UL TBF (1) or DL (0) */
 | 
			
		||||
	uint8_t is_uplink;
 | 
			
		||||
	uint8_t padding[2];
 | 
			
		||||
 | 
			
		||||
	/* one USF for each TN, or 255 for invalid/unused */
 | 
			
		||||
	uint8_t usf[8];
 | 
			
		||||
} __attribute__((packed));
 | 
			
		||||
 | 
			
		||||
#endif /* __L1CTL_PROTO_H__ */
 | 
			
		||||
@@ -1,88 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
 | 
			
		||||
static struct log_info_cat trx_log_info_cat[] = {
 | 
			
		||||
	[DAPP] = {
 | 
			
		||||
		.name = "DAPP",
 | 
			
		||||
		.description = "Application",
 | 
			
		||||
		.color = "\033[1;35m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
	[DL1C] = {
 | 
			
		||||
		.name = "DL1C",
 | 
			
		||||
		.description = "Layer 1 control interface",
 | 
			
		||||
		.color = "\033[1;31m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
	[DL1D] = {
 | 
			
		||||
		.name = "DL1D",
 | 
			
		||||
		.description = "Layer 1 data",
 | 
			
		||||
		.color = "\033[1;31m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
	[DTRX] = {
 | 
			
		||||
		.name = "DTRX",
 | 
			
		||||
		.description = "Transceiver control interface",
 | 
			
		||||
		.color = "\033[1;33m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
	[DTRXD] = {
 | 
			
		||||
		.name = "DTRXD",
 | 
			
		||||
		.description = "Transceiver data interface",
 | 
			
		||||
		.color = "\033[1;33m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
	[DSCH] = {
 | 
			
		||||
		.name = "DSCH",
 | 
			
		||||
		.description = "Scheduler management",
 | 
			
		||||
		.color = "\033[1;36m",
 | 
			
		||||
		.enabled = 0, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
	[DSCHD] = {
 | 
			
		||||
		.name = "DSCHD",
 | 
			
		||||
		.description = "Scheduler data",
 | 
			
		||||
		.color = "\033[1;36m",
 | 
			
		||||
		.enabled = 1, .loglevel = LOGL_NOTICE,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct log_info trx_log_info = {
 | 
			
		||||
	.cat = trx_log_info_cat,
 | 
			
		||||
	.num_cat = ARRAY_SIZE(trx_log_info_cat),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int trx_log_init(void *tall_ctx, const char *category_mask)
 | 
			
		||||
{
 | 
			
		||||
	osmo_init_logging2(tall_ctx, &trx_log_info);
 | 
			
		||||
 | 
			
		||||
	if (category_mask)
 | 
			
		||||
		log_parse_category_mask(osmo_stderr_target, category_mask);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
 | 
			
		||||
#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD"
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	DAPP,
 | 
			
		||||
	DL1C,
 | 
			
		||||
	DL1D,
 | 
			
		||||
	DTRX,
 | 
			
		||||
	DTRXD,
 | 
			
		||||
	DSCH,
 | 
			
		||||
	DSCHD,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int trx_log_init(void *tall_ctx, const char *category_mask);
 | 
			
		||||
@@ -1,206 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: clock synchronization
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
 | 
			
		||||
 * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
 | 
			
		||||
 * (C) 2015 by Harald Welte <laforge@gnumonks.org>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <inttypes.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/core/timer_compat.h>
 | 
			
		||||
#include <osmocom/gsm/a5.h>
 | 
			
		||||
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
 | 
			
		||||
#define MAX_FN_SKEW		50
 | 
			
		||||
#define TRX_LOSS_FRAMES	400
 | 
			
		||||
 | 
			
		||||
static void sched_clck_tick(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_sched *sched = (struct trx_sched *) data;
 | 
			
		||||
	struct timespec tv_now, *tv_clock, elapsed;
 | 
			
		||||
	int64_t elapsed_us;
 | 
			
		||||
	const struct timespec frame_duration = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS };
 | 
			
		||||
 | 
			
		||||
	/* Check if transceiver is still alive */
 | 
			
		||||
	if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) {
 | 
			
		||||
		LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n");
 | 
			
		||||
		sched->state = SCH_CLCK_STATE_WAIT;
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Get actual / previous frame time */
 | 
			
		||||
	osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
 | 
			
		||||
	tv_clock = &sched->clock;
 | 
			
		||||
 | 
			
		||||
	timespecsub(&tv_now, tv_clock, &elapsed);
 | 
			
		||||
	elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
 | 
			
		||||
 | 
			
		||||
	/* If someone played with clock, or if the process stalled */
 | 
			
		||||
	if (elapsed_us > GSM_TDMA_FN_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) {
 | 
			
		||||
		LOGP(DSCH, LOGL_NOTICE, "PC clock skew: "
 | 
			
		||||
			"elapsed uS %" PRId64 "\n", elapsed_us);
 | 
			
		||||
 | 
			
		||||
		sched->state = SCH_CLCK_STATE_WAIT;
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Schedule next FN clock */
 | 
			
		||||
	while (elapsed_us > GSM_TDMA_FN_DURATION_uS / 2) {
 | 
			
		||||
		timespecadd(tv_clock, &frame_duration, tv_clock);
 | 
			
		||||
		elapsed_us -= GSM_TDMA_FN_DURATION_uS;
 | 
			
		||||
 | 
			
		||||
		GSM_TDMA_FN_INC(sched->fn_counter_proc);
 | 
			
		||||
 | 
			
		||||
		/* Call frame callback */
 | 
			
		||||
		if (sched->clock_cb)
 | 
			
		||||
			sched->clock_cb(sched);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	osmo_timer_schedule(&sched->clock_timer, 0,
 | 
			
		||||
		GSM_TDMA_FN_DURATION_uS - elapsed_us);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sched_clck_correct(struct trx_sched *sched,
 | 
			
		||||
	struct timespec *tv_now, uint32_t fn)
 | 
			
		||||
{
 | 
			
		||||
	sched->fn_counter_proc = fn;
 | 
			
		||||
 | 
			
		||||
	/* Call frame callback */
 | 
			
		||||
	if (sched->clock_cb)
 | 
			
		||||
		sched->clock_cb(sched);
 | 
			
		||||
 | 
			
		||||
	/* Schedule first FN clock */
 | 
			
		||||
	sched->clock = *tv_now;
 | 
			
		||||
	memset(&sched->clock_timer, 0, sizeof(sched->clock_timer));
 | 
			
		||||
 | 
			
		||||
	sched->clock_timer.cb = sched_clck_tick;
 | 
			
		||||
	sched->clock_timer.data = sched;
 | 
			
		||||
	osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
 | 
			
		||||
{
 | 
			
		||||
	struct timespec tv_now, *tv_clock, elapsed;
 | 
			
		||||
	int64_t elapsed_us, elapsed_fn;
 | 
			
		||||
 | 
			
		||||
	/* Reset lost counter */
 | 
			
		||||
	sched->fn_counter_lost = 0;
 | 
			
		||||
 | 
			
		||||
	/* Get actual / previous frame time */
 | 
			
		||||
	osmo_clock_gettime(CLOCK_MONOTONIC, &tv_now);
 | 
			
		||||
	tv_clock = &sched->clock;
 | 
			
		||||
 | 
			
		||||
	/* If this is the first CLCK IND */
 | 
			
		||||
	if (sched->state == SCH_CLCK_STATE_WAIT) {
 | 
			
		||||
		sched_clck_correct(sched, &tv_now, fn);
 | 
			
		||||
 | 
			
		||||
		LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
 | 
			
		||||
		sched->state = SCH_CLCK_STATE_OK;
 | 
			
		||||
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
 | 
			
		||||
 | 
			
		||||
	osmo_timer_del(&sched->clock_timer);
 | 
			
		||||
 | 
			
		||||
	/* Calculate elapsed time / frames since last processed fn */
 | 
			
		||||
	timespecsub(&tv_now, tv_clock, &elapsed);
 | 
			
		||||
	elapsed_us = (elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
 | 
			
		||||
	elapsed_fn = GSM_TDMA_FN_SUB(fn, sched->fn_counter_proc);
 | 
			
		||||
 | 
			
		||||
	if (elapsed_fn >= 135774)
 | 
			
		||||
		elapsed_fn -= GSM_TDMA_HYPERFRAME;
 | 
			
		||||
 | 
			
		||||
	/* Check for max clock skew */
 | 
			
		||||
	if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
 | 
			
		||||
		LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
 | 
			
		||||
			"new fn=%u\n", sched->fn_counter_proc, fn);
 | 
			
		||||
 | 
			
		||||
		sched_clck_correct(sched, &tv_now, fn);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %" PRId64 "\n",
 | 
			
		||||
		elapsed_fn * GSM_TDMA_FN_DURATION_uS - elapsed_us);
 | 
			
		||||
 | 
			
		||||
	/* Too many frames have been processed already */
 | 
			
		||||
	if (elapsed_fn < 0) {
 | 
			
		||||
		struct timespec duration;
 | 
			
		||||
		/**
 | 
			
		||||
		 * Set clock to the time or last FN should
 | 
			
		||||
		 * have been transmitted
 | 
			
		||||
		 */
 | 
			
		||||
		duration.tv_nsec = (0 - elapsed_fn) * GSM_TDMA_FN_DURATION_nS;
 | 
			
		||||
		duration.tv_sec = duration.tv_nsec / 1000000000;
 | 
			
		||||
		duration.tv_nsec = duration.tv_nsec % 1000000000;
 | 
			
		||||
		timespecadd(&tv_now, &duration, tv_clock);
 | 
			
		||||
 | 
			
		||||
		/* Set time to the time our next FN has to be transmitted */
 | 
			
		||||
		osmo_timer_schedule(&sched->clock_timer, 0,
 | 
			
		||||
			GSM_TDMA_FN_DURATION_uS * (1 - elapsed_fn));
 | 
			
		||||
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Transmit what we still need to transmit */
 | 
			
		||||
	while (fn != sched->fn_counter_proc) {
 | 
			
		||||
		GSM_TDMA_FN_INC(sched->fn_counter_proc);
 | 
			
		||||
 | 
			
		||||
		/* Call frame callback */
 | 
			
		||||
		if (sched->clock_cb)
 | 
			
		||||
			sched->clock_cb(sched);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Schedule next FN to be transmitted */
 | 
			
		||||
	*tv_clock = tv_now;
 | 
			
		||||
	osmo_timer_schedule(&sched->clock_timer, 0, GSM_TDMA_FN_DURATION_uS);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void sched_clck_reset(struct trx_sched *sched)
 | 
			
		||||
{
 | 
			
		||||
	/* Reset internal state */
 | 
			
		||||
	sched->state = SCH_CLCK_STATE_WAIT;
 | 
			
		||||
 | 
			
		||||
	/* Stop clock timer */
 | 
			
		||||
	osmo_timer_del(&sched->clock_timer);
 | 
			
		||||
 | 
			
		||||
	/* Flush counters */
 | 
			
		||||
	sched->fn_counter_proc = 0;
 | 
			
		||||
	sched->fn_counter_lost = 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,232 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: common routines for lchan handlers
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <talloc.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/gsmtap_util.h>
 | 
			
		||||
#include <osmocom/core/gsmtap.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/codec/codec.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trxcon.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
 | 
			
		||||
const uint8_t sched_nb_training_bits[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,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Get a string representation of the burst buffer's completeness.
 | 
			
		||||
 * Examples: "  ****.." (incomplete, 4/6 bursts)
 | 
			
		||||
 *           "    ****" (complete, all 4 bursts)
 | 
			
		||||
 *           "**.***.." (incomplete, 5/8 bursts) */
 | 
			
		||||
const char *burst_mask2str(const uint8_t *mask, int bits)
 | 
			
		||||
{
 | 
			
		||||
	/* TODO: CSD is interleaved over 22 bursts, so the mask needs to be extended */
 | 
			
		||||
	static char buf[8 + 1];
 | 
			
		||||
	char *ptr = buf;
 | 
			
		||||
 | 
			
		||||
	OSMO_ASSERT(bits <= 8 && bits > 0);
 | 
			
		||||
 | 
			
		||||
	while (--bits >= 0)
 | 
			
		||||
		*(ptr++) = (*mask & (1 << bits)) ? '*' : '.';
 | 
			
		||||
	*ptr = '\0';
 | 
			
		||||
 | 
			
		||||
	return buf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
 | 
			
		||||
		      uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
 | 
			
		||||
		      const uint8_t *data, size_t data_len)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[lchan_type];
 | 
			
		||||
 | 
			
		||||
	/* GSMTAP logging may not be enabled */
 | 
			
		||||
	if (gsmtap == NULL)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Omit frames with unknown channel type */
 | 
			
		||||
	if (lchan_desc->gsmtap_chan_type == GSMTAP_CHANNEL_UNKNOWN)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* TODO: distinguish GSMTAP_CHANNEL_PCH and GSMTAP_CHANNEL_AGCH */
 | 
			
		||||
	return gsmtap_send(gsmtap, band_arfcn, tn, lchan_desc->gsmtap_chan_type,
 | 
			
		||||
			   lchan_desc->ss_nr, fn, signal_dbm, snr, data, data_len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
 | 
			
		||||
	int bit_error_count, bool dec_failed, bool traffic)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_meas_set *meas = &lchan->meas_avg;
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	struct l1ctl_info_dl dl_hdr;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
 | 
			
		||||
	/* Fill in known downlink info */
 | 
			
		||||
	dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
 | 
			
		||||
	dl_hdr.link_id = lchan_desc->link_id;
 | 
			
		||||
	dl_hdr.band_arfcn = htons(trx->band_arfcn);
 | 
			
		||||
	dl_hdr.num_biterr = bit_error_count;
 | 
			
		||||
 | 
			
		||||
	/* sched_trx_meas_avg() gives us TDMA frame number of the first burst */
 | 
			
		||||
	dl_hdr.frame_nr = htonl(meas->fn);
 | 
			
		||||
 | 
			
		||||
	/* RX level: 0 .. 63 in typical GSM notation (dBm + 110) */
 | 
			
		||||
	dl_hdr.rx_level = dbm2rxlev(meas->rssi);
 | 
			
		||||
 | 
			
		||||
	/* FIXME: set proper values */
 | 
			
		||||
	dl_hdr.snr = 0;
 | 
			
		||||
 | 
			
		||||
	/* Mark frame as broken if so */
 | 
			
		||||
	dl_hdr.fire_crc = dec_failed ? 2 : 0;
 | 
			
		||||
 | 
			
		||||
	/* Put a packet to higher layers */
 | 
			
		||||
	l1ctl_tx_dt_ind(trx->l1l, &dl_hdr, l2, l2_len, traffic);
 | 
			
		||||
 | 
			
		||||
	/* Optional GSMTAP logging */
 | 
			
		||||
	if (l2_len > 0 && (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH)) {
 | 
			
		||||
		sched_gsmtap_send(lchan->type, meas->fn, ts->index,
 | 
			
		||||
				  trx->band_arfcn, meas->rssi, 0, l2, l2_len);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, bool traffic)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	struct l1ctl_info_dl dl_hdr;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
 | 
			
		||||
	/* Zero-initialize DL header, because we don't set all fields */
 | 
			
		||||
	memset(&dl_hdr, 0x00, sizeof(struct l1ctl_info_dl));
 | 
			
		||||
 | 
			
		||||
	/* Fill in known downlink info */
 | 
			
		||||
	dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
 | 
			
		||||
	dl_hdr.link_id = lchan_desc->link_id;
 | 
			
		||||
	dl_hdr.band_arfcn = htons(trx->band_arfcn);
 | 
			
		||||
	dl_hdr.frame_nr = htonl(fn);
 | 
			
		||||
 | 
			
		||||
	l1ctl_tx_dt_conf(trx->l1l, &dl_hdr, traffic);
 | 
			
		||||
 | 
			
		||||
	/* Optional GSMTAP logging */
 | 
			
		||||
	if (!traffic || lchan_desc->chan_nr == RSL_CHAN_OSMO_PDCH) {
 | 
			
		||||
		sched_gsmtap_send(lchan->type, fn, ts->index,
 | 
			
		||||
				  trx->band_arfcn | ARFCN_UPLINK,
 | 
			
		||||
				  0, 0, lchan->prim->payload,
 | 
			
		||||
				  lchan->prim->payload_len);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Composes a bad frame indication message
 | 
			
		||||
 * according to the current tch_mode.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  l2       Caller-allocated byte array
 | 
			
		||||
 * @param  lchan    Logical channel to generate BFI for
 | 
			
		||||
 * @return          How much bytes were written
 | 
			
		||||
 */
 | 
			
		||||
size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	switch (lchan->tch_mode) {
 | 
			
		||||
	case GSM48_CMODE_SPEECH_V1:
 | 
			
		||||
		if (lchan->type == TRXC_TCHF) { /* Full Rate */
 | 
			
		||||
			memset(l2, 0x00, GSM_FR_BYTES);
 | 
			
		||||
			l2[0] = 0xd0;
 | 
			
		||||
			return GSM_FR_BYTES;
 | 
			
		||||
		} else { /* Half Rate */
 | 
			
		||||
			memset(l2 + 1, 0x00, GSM_HR_BYTES);
 | 
			
		||||
			l2[0] = 0x70; /* F = 0, FT = 111 */
 | 
			
		||||
			return GSM_HR_BYTES + 1;
 | 
			
		||||
		}
 | 
			
		||||
	case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */
 | 
			
		||||
		memset(l2, 0x00, GSM_EFR_BYTES);
 | 
			
		||||
		l2[0] = 0xc0;
 | 
			
		||||
		return GSM_EFR_BYTES;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
 | 
			
		||||
		/* FIXME: AMR is not implemented yet */
 | 
			
		||||
		return 0;
 | 
			
		||||
	case GSM48_CMODE_SIGN:
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
 | 
			
		||||
		return 0;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,622 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: logical channels, RX / TX handlers
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
 | 
			
		||||
 * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
 | 
			
		||||
 * (C) 2015 by Harald Welte <laforge@gnumonks.org>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
 | 
			
		||||
#include <osmocom/core/gsmtap.h>
 | 
			
		||||
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
 | 
			
		||||
/* Forward declaration of handlers */
 | 
			
		||||
int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas);
 | 
			
		||||
 | 
			
		||||
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
 | 
			
		||||
 | 
			
		||||
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas);
 | 
			
		||||
 | 
			
		||||
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
 | 
			
		||||
 | 
			
		||||
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas);
 | 
			
		||||
 | 
			
		||||
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
 | 
			
		||||
 | 
			
		||||
int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas);
 | 
			
		||||
 | 
			
		||||
int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
 | 
			
		||||
 | 
			
		||||
int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas);
 | 
			
		||||
 | 
			
		||||
int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
 | 
			
		||||
 | 
			
		||||
const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
 | 
			
		||||
	[TRXC_IDLE] = {
 | 
			
		||||
		.name = "IDLE",
 | 
			
		||||
		.desc = "Idle channel",
 | 
			
		||||
		/* The MS needs to perform neighbour measurements during
 | 
			
		||||
		 * IDLE slots, however this is not implemented (yet). */
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_FCCH] = {
 | 
			
		||||
		.name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */
 | 
			
		||||
		.desc = "Frequency correction channel",
 | 
			
		||||
		/* Handled by transceiver, nothing to do. */
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SCH] = {
 | 
			
		||||
		.name = "SCH", /* 3GPP TS 05.02, section 3.3.2.2 */
 | 
			
		||||
		.desc = "Synchronization channel",
 | 
			
		||||
 | 
			
		||||
		/* 3GPP TS 05.03, section 4.7. Handled by transceiver,
 | 
			
		||||
		 * however we still need to parse BSIC (BCC / NCC). */
 | 
			
		||||
		.flags = TRX_CH_FLAG_AUTO,
 | 
			
		||||
		.rx_fn = rx_sch_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_BCCH] = {
 | 
			
		||||
		.name = "BCCH", /* 3GPP TS 05.02, section 3.3.2.3 */
 | 
			
		||||
		.desc = "Broadcast control channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_BCCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_BCCH,
 | 
			
		||||
 | 
			
		||||
		/* Rx 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. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_AUTO,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_RACH] = {
 | 
			
		||||
		.name = "RACH", /* 3GPP TS 05.02, section 3.3.3.1 */
 | 
			
		||||
		.desc = "Random access channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_RACH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_RACH,
 | 
			
		||||
 | 
			
		||||
		/* Tx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */
 | 
			
		||||
		.flags = TRX_CH_FLAG_AUTO,
 | 
			
		||||
		.tx_fn = tx_rach_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_CCCH] = {
 | 
			
		||||
		.name = "CCCH", /* 3GPP TS 05.02, section 3.3.3.1 */
 | 
			
		||||
		.desc = "Common control channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_CCCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_PCH_AGCH,
 | 
			
		||||
 | 
			
		||||
		/* Rx 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. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_AUTO,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_TCHF] = {
 | 
			
		||||
		.name = "TCH/F", /* 3GPP TS 05.02, section 3.2 */
 | 
			
		||||
		.desc = "Full Rate traffic channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F,
 | 
			
		||||
		.chan_nr = RSL_CHAN_Bm_ACCHs,
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
 | 
			
		||||
		/* 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 8 consecutive bursts
 | 
			
		||||
		 *     using the even numbered bits of the first 4 bursts
 | 
			
		||||
		 *     and odd numbered bits of the last 4 bursts;
 | 
			
		||||
		 *   - a FACCH/F frame 'steals' (replaces) one traffic frame,
 | 
			
		||||
		 *     interleaving is done in the same way.
 | 
			
		||||
		 *
 | 
			
		||||
		 * The MS shall continuously transmit bursts, even if there is nothing
 | 
			
		||||
		 * to send, unless DTX (Discontinuous Transmission) is used. */
 | 
			
		||||
		.burst_buf_size = 8 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_tchf_fn,
 | 
			
		||||
		.tx_fn = tx_tchf_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_TCHH_0] = {
 | 
			
		||||
		.name = "TCH/H(0)", /* 3GPP TS 05.02, section 3.2 */
 | 
			
		||||
		.desc = "Half Rate traffic channel (sub-channel 0)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
 | 
			
		||||
		.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 0,
 | 
			
		||||
 | 
			
		||||
		/* 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 4 non-consecutive bursts
 | 
			
		||||
		 *     using the even numbered bits of the first 2 bursts,
 | 
			
		||||
		 *     and odd numbered bits of the last 2 bursts;
 | 
			
		||||
		 *   - a FACCH/H frame is interleaved over 6 non-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.
 | 
			
		||||
		 *
 | 
			
		||||
		 * The MS shall continuously transmit bursts, even if there is nothing
 | 
			
		||||
		 * to send, unless DTX (Discontinuous Transmission) is used. */
 | 
			
		||||
		.burst_buf_size = 6 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_tchh_fn,
 | 
			
		||||
		.tx_fn = tx_tchh_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_TCHH_1] = {
 | 
			
		||||
		.name = "TCH/H(1)", /* 3GPP TS 05.02, section 3.2 */
 | 
			
		||||
		.desc = "Half Rate traffic channel (sub-channel 1)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H,
 | 
			
		||||
		.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 1,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_TCHH_0, see above. */
 | 
			
		||||
		.burst_buf_size = 6 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_tchh_fn,
 | 
			
		||||
		.tx_fn = tx_tchh_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH4_0] = {
 | 
			
		||||
		.name = "SDCCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 0)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 0,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH4_1] = {
 | 
			
		||||
		.name = "SDCCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 1)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 1,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH4_2] = {
 | 
			
		||||
		.name = "SDCCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 2)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 2,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH4_3] = {
 | 
			
		||||
		.name = "SDCCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 3)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 3,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_0] = {
 | 
			
		||||
		.name = "SDCCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 0)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 0,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_1] = {
 | 
			
		||||
		.name = "SDCCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 1)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 1,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_2] = {
 | 
			
		||||
		.name = "SDCCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 2)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 2,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_3] = {
 | 
			
		||||
		.name = "SDCCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 3)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 3,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_4] = {
 | 
			
		||||
		.name = "SDCCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 4)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 4,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_5] = {
 | 
			
		||||
		.name = "SDCCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 5)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 5,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_6] = {
 | 
			
		||||
		.name = "SDCCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 6)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 6,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_7] = {
 | 
			
		||||
		.name = "SDCCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Stand-alone dedicated control channel (sub-channel 7)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_DEDIC,
 | 
			
		||||
		.ss_nr = 7,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCHTF] = {
 | 
			
		||||
		.name = "SACCH/TF", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow TCH/F associated control channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_F | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_Bm_ACCHs,
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCHTH_0] = {
 | 
			
		||||
		.name = "SACCH/TH(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow TCH/H associated control channel (sub-channel 0)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_Lm_ACCHs + (0 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 0,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCHTH_1] = {
 | 
			
		||||
		.name = "SACCH/TH(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow TCH/H associated control channel (sub-channel 1)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_TCH_H | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_Lm_ACCHs + (1 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 1,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH4_0] = {
 | 
			
		||||
		.name = "SACCH/4(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/4 associated control channel (sub-channel 0)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (0 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 0,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH4_1] = {
 | 
			
		||||
		.name = "SACCH/4(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/4 associated control channel (sub-channel 1)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (1 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 1,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH4_2] = {
 | 
			
		||||
		.name = "SACCH/4(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/4 associated control channel (sub-channel 2)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (2 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 2,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH4_3] = {
 | 
			
		||||
		.name = "SACCH/4(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/4 associated control channel (sub-channel 3)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH4_ACCH + (3 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 3,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH4_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_0] = {
 | 
			
		||||
		.name = "SACCH/8(0)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 0)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (0 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 0,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_1] = {
 | 
			
		||||
		.name = "SACCH/8(1)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 1)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (1 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 1,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_2] = {
 | 
			
		||||
		.name = "SACCH/8(2)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 2)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (2 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 2,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_3] = {
 | 
			
		||||
		.name = "SACCH/8(3)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 3)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (3 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 3,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_4] = {
 | 
			
		||||
		.name = "SACCH/8(4)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 4)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (4 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 4,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_5] = {
 | 
			
		||||
		.name = "SACCH/8(5)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 5)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (5 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 5,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_6] = {
 | 
			
		||||
		.name = "SACCH/8(6)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 6)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (6 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 6,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SACCH8_7] = {
 | 
			
		||||
		.name = "SACCH/8(7)", /* 3GPP TS 05.02, section 3.3.4.1 */
 | 
			
		||||
		.desc = "Slow SDCCH/8 associated control channel (sub-channel 7)",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_SDCCH8 | GSMTAP_CHANNEL_ACCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_SDCCH8_ACCH + (7 << 3),
 | 
			
		||||
		.link_id = TRX_CH_LID_SACCH,
 | 
			
		||||
		.ss_nr = 7,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH and TRXC_SDCCH8_* (xCCH), see above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_CBTX,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
		.tx_fn = tx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_PDTCH] = {
 | 
			
		||||
		.name = "PDTCH", /* 3GPP TS 05.02, sections 3.2.4, 3.3.2.4 */
 | 
			
		||||
		.desc = "Packet data traffic & control channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_PDTCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_OSMO_PDCH,
 | 
			
		||||
 | 
			
		||||
		/* Rx and Tx, multiple coding schemes: CS-1..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. */
 | 
			
		||||
		.burst_buf_size = 3 * 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_PDCH,
 | 
			
		||||
		.rx_fn = rx_pdtch_fn,
 | 
			
		||||
		.tx_fn = tx_pdtch_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_PTCCH] = {
 | 
			
		||||
		.name = "PTCCH", /* 3GPP TS 05.02, section 3.3.4.2 */
 | 
			
		||||
		.desc = "Packet Timing advance control channel",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_PTCCH,
 | 
			
		||||
		.chan_nr = RSL_CHAN_OSMO_PDCH,
 | 
			
		||||
		.link_id = TRX_CH_LID_PTCCH,
 | 
			
		||||
 | 
			
		||||
		/* 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. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_PDCH,
 | 
			
		||||
		.rx_fn = rx_pdtch_fn,
 | 
			
		||||
		.tx_fn = tx_rach_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH4_CBCH] = {
 | 
			
		||||
		.name = "SDCCH/4(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
 | 
			
		||||
		.desc = "Cell Broadcast channel on SDCCH/4",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH51,
 | 
			
		||||
		.chan_nr = RSL_CHAN_OSMO_CBCH4,
 | 
			
		||||
		.ss_nr = 2,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.flags = TRX_CH_FLAG_AUTO,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXC_SDCCH8_CBCH] = {
 | 
			
		||||
		.name = "SDCCH/8(CBCH)", /* 3GPP TS 05.02, section 3.3.5 */
 | 
			
		||||
		.desc = "Cell Broadcast channel on SDCCH/8",
 | 
			
		||||
		.gsmtap_chan_type = GSMTAP_CHANNEL_CBCH52,
 | 
			
		||||
		.chan_nr = RSL_CHAN_OSMO_CBCH8,
 | 
			
		||||
		.ss_nr = 2,
 | 
			
		||||
 | 
			
		||||
		/* Same as for TRXC_BCCH (xCCH), but Rx only. See above. */
 | 
			
		||||
		.burst_buf_size = 4 * GSM_BURST_PL_LEN,
 | 
			
		||||
		.rx_fn = rx_data_fn,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
@@ -1,201 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: handlers for DL / UL bursts on logical channels
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
#include <osmocom/coding/gsm0503_coding.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
int rx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	uint8_t l2[GPRS_L2_MAX_LEN], *mask;
 | 
			
		||||
	int n_errors, n_bits_total, rc;
 | 
			
		||||
	sbit_t *buffer, *offset;
 | 
			
		||||
	size_t l2_len;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->rx_burst_mask;
 | 
			
		||||
	buffer = lchan->rx_bursts;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Packet data received on %s: "
 | 
			
		||||
		"fn=%u ts=%u bid=%u\n", lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Align to the first burst of a block */
 | 
			
		||||
	if (*mask == 0x00 && bid != 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Store the measurements */
 | 
			
		||||
	sched_trx_meas_push(lchan, meas);
 | 
			
		||||
 | 
			
		||||
	/* Copy burst to buffer of 4 bursts */
 | 
			
		||||
	offset = buffer + bid * 116;
 | 
			
		||||
	memcpy(offset, bits + 3, 58);
 | 
			
		||||
	memcpy(offset + 58, bits + 87, 58);
 | 
			
		||||
 | 
			
		||||
	/* Wait until complete set of bursts */
 | 
			
		||||
	if (bid != 3)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Calculate AVG of the measurements */
 | 
			
		||||
	sched_trx_meas_avg(lchan, 4);
 | 
			
		||||
 | 
			
		||||
	/* Check for complete set of bursts */
 | 
			
		||||
	if ((*mask & 0xf) != 0xf) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
 | 
			
		||||
			"fn=%u (%u/%u) for %s\n",
 | 
			
		||||
			burst_mask2str(mask, 4), lchan->meas_avg.fn,
 | 
			
		||||
			lchan->meas_avg.fn % ts->mf_layout->period,
 | 
			
		||||
			ts->mf_layout->period,
 | 
			
		||||
			lchan_desc->name);
 | 
			
		||||
		/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Keep the mask updated */
 | 
			
		||||
	*mask = *mask << 4;
 | 
			
		||||
 | 
			
		||||
	/* Attempt to decode */
 | 
			
		||||
	rc = gsm0503_pdtch_decode(l2, buffer,
 | 
			
		||||
		NULL, &n_errors, &n_bits_total);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received bad packet data frame "
 | 
			
		||||
			"at fn=%u (%u/%u) for %s\n", lchan->meas_avg.fn,
 | 
			
		||||
			lchan->meas_avg.fn % ts->mf_layout->period,
 | 
			
		||||
			ts->mf_layout->period,
 | 
			
		||||
			lchan_desc->name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Determine L2 length */
 | 
			
		||||
	l2_len = rc > 0 ? rc : 0;
 | 
			
		||||
 | 
			
		||||
	/* Send a L2 frame to the higher layers */
 | 
			
		||||
	sched_send_dt_ind(trx, ts, lchan,
 | 
			
		||||
		l2, l2_len, n_errors, rc < 0, true);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int tx_pdtch_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	ubit_t burst[GSM_BURST_LEN];
 | 
			
		||||
	ubit_t *buffer, *offset;
 | 
			
		||||
	const uint8_t *tsc;
 | 
			
		||||
	uint8_t *mask;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->tx_burst_mask;
 | 
			
		||||
	buffer = lchan->tx_bursts;
 | 
			
		||||
 | 
			
		||||
	if (bid > 0) {
 | 
			
		||||
		/* If we have encoded bursts */
 | 
			
		||||
		if (*mask)
 | 
			
		||||
			goto send_burst;
 | 
			
		||||
		else
 | 
			
		||||
			return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Encode payload */
 | 
			
		||||
	rc = gsm0503_pdtch_encode(buffer, lchan->prim->payload,
 | 
			
		||||
		lchan->prim->payload_len);
 | 
			
		||||
	if (rc < 0) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
 | 
			
		||||
		     lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
 | 
			
		||||
							    lchan->prim->payload_len));
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
send_burst:
 | 
			
		||||
	/* Determine which burst should be sent */
 | 
			
		||||
	offset = buffer + bid * 116;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Choose proper TSC */
 | 
			
		||||
	tsc = sched_nb_training_bits[trx->tsc];
 | 
			
		||||
 | 
			
		||||
	/* Compose a new burst */
 | 
			
		||||
	memset(burst, 0, 3); /* TB */
 | 
			
		||||
	memcpy(burst + 3, offset, 58); /* Payload 1/2 */
 | 
			
		||||
	memcpy(burst + 61, tsc, 26); /* TSC */
 | 
			
		||||
	memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
 | 
			
		||||
	memset(burst + 145, 0, 3); /* TB */
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Forward burst to scheduler */
 | 
			
		||||
	rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Reset mask */
 | 
			
		||||
		*mask = 0x00;
 | 
			
		||||
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* If we have sent the last (4/4) burst */
 | 
			
		||||
	if ((*mask & 0x0f) == 0x0f) {
 | 
			
		||||
		/* Confirm data / traffic sending */
 | 
			
		||||
		sched_send_dt_conf(trx, ts, lchan, fn, true);
 | 
			
		||||
 | 
			
		||||
		/* Forget processed primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Reset mask */
 | 
			
		||||
		*mask = 0x00;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,187 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: handlers for DL / UL bursts on logical channels
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/coding/gsm0503_coding.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)" */
 | 
			
		||||
#define RACH_EXT_TAIL_BITS_LEN	8
 | 
			
		||||
#define RACH_SYNCH_SEQ_LEN	41
 | 
			
		||||
#define RACH_PAYLOAD_LEN	36
 | 
			
		||||
 | 
			
		||||
/* Extended tail bits (BN0..BN7) */
 | 
			
		||||
static const ubit_t rach_ext_tail_bits[] = {
 | 
			
		||||
	0, 0, 1, 1, 1, 0, 1, 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Synchronization (training) sequence types */
 | 
			
		||||
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
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Synchronization (training) sequence bits */
 | 
			
		||||
static const char rach_synch_seq_bits[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
 | 
			
		||||
	[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
 | 
			
		||||
	[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
 | 
			
		||||
	[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Synchronization (training) sequence names */
 | 
			
		||||
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 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Obtain a to-be-transmitted RACH burst */
 | 
			
		||||
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
 | 
			
		||||
{
 | 
			
		||||
	struct l1ctl_ext_rach_req *ext_req = NULL;
 | 
			
		||||
	struct l1ctl_rach_req *req = NULL;
 | 
			
		||||
	enum rach_synch_seq_t synch_seq;
 | 
			
		||||
	uint8_t burst[GSM_BURST_LEN];
 | 
			
		||||
	uint8_t *burst_ptr = burst;
 | 
			
		||||
	uint8_t payload[36];
 | 
			
		||||
	int i, rc;
 | 
			
		||||
 | 
			
		||||
	/* Is it extended (11-bit) RACH or not? */
 | 
			
		||||
	if (PRIM_IS_RACH11(lchan->prim)) {
 | 
			
		||||
		ext_req = (struct l1ctl_ext_rach_req *) lchan->prim->payload;
 | 
			
		||||
		synch_seq = ext_req->synch_seq;
 | 
			
		||||
 | 
			
		||||
		/* Check requested synch. sequence */
 | 
			
		||||
		if (synch_seq >= RACH_SYNCH_SEQ_NUM) {
 | 
			
		||||
			LOGP(DSCHD, LOGL_ERROR, "Unknown RACH synch. sequence=0x%02x\n", synch_seq);
 | 
			
		||||
 | 
			
		||||
			/* Forget this primitive */
 | 
			
		||||
			sched_prim_drop(lchan);
 | 
			
		||||
			return -ENOTSUP;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Delay sending according to offset value */
 | 
			
		||||
		if (ext_req->offset-- > 0)
 | 
			
		||||
			return 0;
 | 
			
		||||
 | 
			
		||||
		/* Encode extended (11-bit) payload */
 | 
			
		||||
		rc = gsm0503_rach_ext_encode(payload, ext_req->ra11, trx->bsic, true);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			LOGP(DSCHD, LOGL_ERROR, "Could not encode extended RACH burst "
 | 
			
		||||
						"(ra=%u bsic=%u)\n", ext_req->ra11, trx->bsic);
 | 
			
		||||
 | 
			
		||||
			/* Forget this primitive */
 | 
			
		||||
			sched_prim_drop(lchan);
 | 
			
		||||
			return rc;
 | 
			
		||||
		}
 | 
			
		||||
	} else if (PRIM_IS_RACH8(lchan->prim)) {
 | 
			
		||||
		req = (struct l1ctl_rach_req *) lchan->prim->payload;
 | 
			
		||||
		synch_seq = RACH_SYNCH_SEQ_TS0;
 | 
			
		||||
 | 
			
		||||
		/* Delay sending according to offset value */
 | 
			
		||||
		if (req->offset-- > 0)
 | 
			
		||||
			return 0;
 | 
			
		||||
 | 
			
		||||
		/* Encode regular (8-bit) payload */
 | 
			
		||||
		rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false);
 | 
			
		||||
		if (rc) {
 | 
			
		||||
			LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst "
 | 
			
		||||
						"(ra=%u bsic=%u)\n", req->ra, trx->bsic);
 | 
			
		||||
 | 
			
		||||
			/* Forget this primitive */
 | 
			
		||||
			sched_prim_drop(lchan);
 | 
			
		||||
			return rc;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %zu or %zu), "
 | 
			
		||||
			"so dropping...\n", lchan->prim->payload_len,
 | 
			
		||||
			sizeof(*req), sizeof(*ext_req));
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/* BN0-7: extended tail bits */
 | 
			
		||||
	memcpy(burst_ptr, rach_ext_tail_bits, RACH_EXT_TAIL_BITS_LEN);
 | 
			
		||||
	burst_ptr += RACH_EXT_TAIL_BITS_LEN;
 | 
			
		||||
 | 
			
		||||
	/* BN8-48: chosen synch. (training) sequence */
 | 
			
		||||
	for (i = 0; i < RACH_SYNCH_SEQ_LEN; i++)
 | 
			
		||||
		*(burst_ptr++) = rach_synch_seq_bits[synch_seq][i] == '1';
 | 
			
		||||
 | 
			
		||||
	/* BN49-84: encrypted bits (the payload) */
 | 
			
		||||
	memcpy(burst_ptr, payload, RACH_PAYLOAD_LEN);
 | 
			
		||||
	burst_ptr += RACH_PAYLOAD_LEN;
 | 
			
		||||
 | 
			
		||||
	/* BN85-156: tail bits & extended guard period */
 | 
			
		||||
	memset(burst_ptr, 0, burst + GSM_BURST_LEN - burst_ptr);
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_NOTICE, "Transmitting %s RACH (%s) on fn=%u, tn=%u, lchan=%s\n",
 | 
			
		||||
		PRIM_IS_RACH11(lchan->prim) ? "extended (11-bit)" : "regular (8-bit)",
 | 
			
		||||
		get_value_string(rach_synch_seq_names, synch_seq), fn,
 | 
			
		||||
		ts->index, trx_lchan_desc[lchan->type].name);
 | 
			
		||||
 | 
			
		||||
	/* Forward burst to scheduler */
 | 
			
		||||
	rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Confirm RACH request */
 | 
			
		||||
	l1ctl_tx_rach_conf(trx->l1l, trx->band_arfcn, fn);
 | 
			
		||||
 | 
			
		||||
	/* Optional GSMTAP logging */
 | 
			
		||||
	sched_gsmtap_send(lchan->type, fn, ts->index,
 | 
			
		||||
			  trx->band_arfcn | ARFCN_UPLINK, 0, 0,
 | 
			
		||||
			  PRIM_IS_RACH11(lchan->prim) ? (uint8_t *) &ext_req->ra11 : &req->ra,
 | 
			
		||||
			  PRIM_IS_RACH11(lchan->prim) ? 2 : 1);
 | 
			
		||||
 | 
			
		||||
	/* Forget processed primitive */
 | 
			
		||||
	sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,139 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: handlers for DL / UL bursts on logical channels
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <talloc.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/coding/gsm0503_coding.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline))
 | 
			
		||||
static int gsm0503_sch_decode_xray(uint8_t *sb_info, const sbit_t *burst)
 | 
			
		||||
{
 | 
			
		||||
	return gsm0503_sch_decode(sb_info, burst);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t t3p;
 | 
			
		||||
	uint32_t sb;
 | 
			
		||||
 | 
			
		||||
	sb = ((uint32_t)sb_info[3] << 24)
 | 
			
		||||
	   | (sb_info[2] << 16)
 | 
			
		||||
	   | (sb_info[1] << 8)
 | 
			
		||||
	   | sb_info[0];
 | 
			
		||||
 | 
			
		||||
	*bsic = (sb >> 2) & 0x3f;
 | 
			
		||||
 | 
			
		||||
	/* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */
 | 
			
		||||
	time->t1 = ((sb >> 23) & 0x01)
 | 
			
		||||
		 | ((sb >> 7) & 0x1fe)
 | 
			
		||||
		 | ((sb << 9) & 0x600);
 | 
			
		||||
 | 
			
		||||
	time->t2 = (sb >> 18) & 0x1f;
 | 
			
		||||
 | 
			
		||||
	t3p = ((sb >> 24) & 0x01) | ((sb >> 15) & 0x06);
 | 
			
		||||
	time->t3 = t3p * 10 + 1;
 | 
			
		||||
 | 
			
		||||
	/* TS 05.02 Chapter 4.3.3 TDMA frame number */
 | 
			
		||||
	time->fn = gsm_gsmtime2fn(time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	sbit_t payload[2 * 39];
 | 
			
		||||
	struct gsm_time time;
 | 
			
		||||
	uint8_t sb_info[4];
 | 
			
		||||
	uint8_t bsic;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Obtain payload from burst */
 | 
			
		||||
	memcpy(payload, bits + 3, 39);
 | 
			
		||||
	memcpy(payload + 39, bits + 3 + 39 + 64, 39);
 | 
			
		||||
 | 
			
		||||
	/* Attempt to decode */
 | 
			
		||||
	rc = gsm0503_sch_decode_xray(sb_info, payload);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Decode BSIC and TDMA frame number */
 | 
			
		||||
	decode_sb(&time, &bsic, sb_info);
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
 | 
			
		||||
		bsic, time.fn, trx->sched.fn_counter_proc);
 | 
			
		||||
 | 
			
		||||
	/* Check if decoded frame number matches */
 | 
			
		||||
	if (time.fn != fn) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Decoded fn=%u does not match "
 | 
			
		||||
			"fn=%u provided by scheduler\n", time.fn, fn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* We don't need to send L1CTL_FBSB_CONF */
 | 
			
		||||
	if (trx->l1l->fbsb_conf_sent)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Send L1CTL_FBSB_CONF to higher layers */
 | 
			
		||||
	struct l1ctl_info_dl *data;
 | 
			
		||||
	data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl));
 | 
			
		||||
	if (data == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/* Fill in some downlink info */
 | 
			
		||||
	data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index;
 | 
			
		||||
	data->link_id = trx_lchan_desc[lchan->type].link_id;
 | 
			
		||||
	data->band_arfcn = htons(trx->band_arfcn);
 | 
			
		||||
	data->frame_nr = htonl(fn);
 | 
			
		||||
	data->rx_level = -(meas->rssi);
 | 
			
		||||
 | 
			
		||||
	/* FIXME: set proper values */
 | 
			
		||||
	data->num_biterr = 0;
 | 
			
		||||
	data->fire_crc = 0;
 | 
			
		||||
	data->snr = 0;
 | 
			
		||||
 | 
			
		||||
	l1ctl_tx_fbsb_conf(trx->l1l, 0, data, bsic);
 | 
			
		||||
 | 
			
		||||
	/* Update BSIC value of trx_instance */
 | 
			
		||||
	trx->bsic = bsic;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,303 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: handlers for DL / UL bursts on logical channels
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/coding/gsm0503_coding.h>
 | 
			
		||||
#include <osmocom/codec/codec.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	int n_errors = -1, n_bits_total, rc;
 | 
			
		||||
	sbit_t *buffer, *offset;
 | 
			
		||||
	uint8_t l2[128], *mask;
 | 
			
		||||
	size_t l2_len;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->rx_burst_mask;
 | 
			
		||||
	buffer = lchan->rx_bursts;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Align to the first burst of a block */
 | 
			
		||||
	if (*mask == 0x00 && bid != 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Store the measurements */
 | 
			
		||||
	sched_trx_meas_push(lchan, meas);
 | 
			
		||||
 | 
			
		||||
	/* Copy burst to end of buffer of 8 bursts */
 | 
			
		||||
	offset = buffer + bid * 116 + 464;
 | 
			
		||||
	memcpy(offset, bits + 3, 58);
 | 
			
		||||
	memcpy(offset + 58, bits + 87, 58);
 | 
			
		||||
 | 
			
		||||
	/* Wait until complete set of bursts */
 | 
			
		||||
	if (bid != 3)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Calculate AVG of the measurements */
 | 
			
		||||
	sched_trx_meas_avg(lchan, 8);
 | 
			
		||||
 | 
			
		||||
	/* Check for complete set of bursts */
 | 
			
		||||
	if ((*mask & 0xff) != 0xff) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) traffic frame at "
 | 
			
		||||
			"fn=%u (%u/%u) for %s\n",
 | 
			
		||||
			burst_mask2str(mask, 8), lchan->meas_avg.fn,
 | 
			
		||||
			lchan->meas_avg.fn % ts->mf_layout->period,
 | 
			
		||||
			ts->mf_layout->period,
 | 
			
		||||
			lchan_desc->name);
 | 
			
		||||
		/* NOTE: do not abort here, give it a try. Maybe we're lucky ;) */
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Keep the mask updated */
 | 
			
		||||
	*mask = *mask << 4;
 | 
			
		||||
 | 
			
		||||
	switch (lchan->tch_mode) {
 | 
			
		||||
	case GSM48_CMODE_SIGN:
 | 
			
		||||
	case GSM48_CMODE_SPEECH_V1: /* FR */
 | 
			
		||||
		rc = gsm0503_tch_fr_decode(l2, buffer,
 | 
			
		||||
			1, 0, &n_errors, &n_bits_total);
 | 
			
		||||
		break;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_EFR: /* EFR */
 | 
			
		||||
		rc = gsm0503_tch_fr_decode(l2, buffer,
 | 
			
		||||
			1, 1, &n_errors, &n_bits_total);
 | 
			
		||||
		break;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_AMR: /* AMR */
 | 
			
		||||
		/**
 | 
			
		||||
		 * TODO: AMR requires a dedicated loop,
 | 
			
		||||
		 * which will be implemented later...
 | 
			
		||||
		 */
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Shift buffer by 4 bursts for interleaving */
 | 
			
		||||
	memcpy(buffer, buffer + 464, 464);
 | 
			
		||||
 | 
			
		||||
	/* Check decoding result */
 | 
			
		||||
	if (rc < 4) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at "
 | 
			
		||||
			"fn=%u for %s\n", fn, lchan_desc->name);
 | 
			
		||||
 | 
			
		||||
		/* Send BFI */
 | 
			
		||||
		goto bfi;
 | 
			
		||||
	} else if (rc == GSM_MACBLOCK_LEN) {
 | 
			
		||||
		/* FACCH received, forward it to the higher layers */
 | 
			
		||||
		sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
 | 
			
		||||
			n_errors, false, false);
 | 
			
		||||
 | 
			
		||||
		/* Send BFI substituting a stolen TCH frame */
 | 
			
		||||
		n_errors = -1; /* ensure fake measurements */
 | 
			
		||||
		goto bfi;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* A good TCH frame received */
 | 
			
		||||
		l2_len = rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send a traffic frame to the higher layers */
 | 
			
		||||
	return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
 | 
			
		||||
		n_errors, false, true);
 | 
			
		||||
 | 
			
		||||
bfi:
 | 
			
		||||
	/* Didn't try to decode, fake measurements */
 | 
			
		||||
	if (n_errors < 0) {
 | 
			
		||||
		lchan->meas_avg = (struct trx_meas_set) {
 | 
			
		||||
			.fn = lchan->meas_avg.fn,
 | 
			
		||||
			.toa256 = 0,
 | 
			
		||||
			.rssi = -110,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/* No bursts => no errors */
 | 
			
		||||
		n_errors = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* BFI is not applicable in signalling mode */
 | 
			
		||||
	if (lchan->tch_mode == GSM48_CMODE_SIGN)
 | 
			
		||||
		return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
 | 
			
		||||
			n_errors, true, false);
 | 
			
		||||
 | 
			
		||||
	/* Bad frame indication */
 | 
			
		||||
	l2_len = sched_bad_frame_ind(l2, lchan);
 | 
			
		||||
 | 
			
		||||
	/* Send a BFI frame to the higher layers */
 | 
			
		||||
	return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
 | 
			
		||||
		n_errors, true, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	ubit_t burst[GSM_BURST_LEN];
 | 
			
		||||
	ubit_t *buffer, *offset;
 | 
			
		||||
	const uint8_t *tsc;
 | 
			
		||||
	uint8_t *mask;
 | 
			
		||||
	size_t l2_len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->tx_burst_mask;
 | 
			
		||||
	buffer = lchan->tx_bursts;
 | 
			
		||||
 | 
			
		||||
	/* If we have encoded bursts */
 | 
			
		||||
	if (*mask)
 | 
			
		||||
		goto send_burst;
 | 
			
		||||
 | 
			
		||||
	/* Wait until a first burst in period */
 | 
			
		||||
	if (bid > 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Check the current TCH mode */
 | 
			
		||||
	switch (lchan->tch_mode) {
 | 
			
		||||
	case GSM48_CMODE_SIGN:
 | 
			
		||||
	case GSM48_CMODE_SPEECH_V1: /* FR */
 | 
			
		||||
		l2_len = GSM_FR_BYTES;
 | 
			
		||||
		break;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_EFR: /* EFR */
 | 
			
		||||
		l2_len = GSM_EFR_BYTES;
 | 
			
		||||
		break;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_AMR: /* AMR */
 | 
			
		||||
		/**
 | 
			
		||||
		 * TODO: AMR requires a dedicated loop,
 | 
			
		||||
		 * which will be implemented later...
 | 
			
		||||
		 */
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
 | 
			
		||||
			"dropping frame...\n");
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
 | 
			
		||||
			"dropping frame...\n", lchan->tch_mode);
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Determine and check the payload length */
 | 
			
		||||
	if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) {
 | 
			
		||||
		l2_len = GSM_MACBLOCK_LEN; /* FACCH */
 | 
			
		||||
	} else if (lchan->prim->payload_len != l2_len) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
 | 
			
		||||
			"(expected %zu for TCH or %u for FACCH), so dropping...\n",
 | 
			
		||||
			lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
 | 
			
		||||
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Shift buffer by 4 bursts back for interleaving */
 | 
			
		||||
	memcpy(buffer, buffer + 464, 464);
 | 
			
		||||
 | 
			
		||||
	/* Encode payload */
 | 
			
		||||
	rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
 | 
			
		||||
		     lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
 | 
			
		||||
							    lchan->prim->payload_len));
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
send_burst:
 | 
			
		||||
	/* Determine which burst should be sent */
 | 
			
		||||
	offset = buffer + bid * 116;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Choose proper TSC */
 | 
			
		||||
	tsc = sched_nb_training_bits[trx->tsc];
 | 
			
		||||
 | 
			
		||||
	/* Compose a new burst */
 | 
			
		||||
	memset(burst, 0, 3); /* TB */
 | 
			
		||||
	memcpy(burst + 3, offset, 58); /* Payload 1/2 */
 | 
			
		||||
	memcpy(burst + 61, tsc, 26); /* TSC */
 | 
			
		||||
	memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
 | 
			
		||||
	memset(burst + 145, 0, 3); /* TB */
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Forward burst to scheduler */
 | 
			
		||||
	rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Reset mask */
 | 
			
		||||
		*mask = 0x00;
 | 
			
		||||
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* If we have sent the last (4/4) burst */
 | 
			
		||||
	if (*mask == 0x0f) {
 | 
			
		||||
		/* Confirm data / traffic sending */
 | 
			
		||||
		sched_send_dt_conf(trx, ts, lchan, fn, PRIM_IS_TCH(lchan->prim));
 | 
			
		||||
 | 
			
		||||
		/* Forget processed primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Reset mask */
 | 
			
		||||
		*mask = 0x00;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,501 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: handlers for DL / UL bursts on logical channels
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2018-2020 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 * (C) 2018 by Harald Welte <laforge@gnumonks.org>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/coding/gsm0503_coding.h>
 | 
			
		||||
#include <osmocom/codec/codec.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
static const uint8_t tch_h0_traffic_block_map[3][4] = {
 | 
			
		||||
	/* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
 | 
			
		||||
	{ 0, 2, 4, 6 },
 | 
			
		||||
	{ 4, 6, 8, 10 },
 | 
			
		||||
	{ 8, 10, 0, 2 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t tch_h1_traffic_block_map[3][4] = {
 | 
			
		||||
	/* B0(1,3,5,7), B1(5,7,9,11), B2(9,11,1,3) */
 | 
			
		||||
	{ 1, 3, 5, 7 },
 | 
			
		||||
	{ 5, 7, 9, 11 },
 | 
			
		||||
	{ 9, 11, 1, 3 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t tch_h0_dl_facch_block_map[3][6] = {
 | 
			
		||||
	/* B0(4,6,8,10,13,15), B1(13,15,17,19,21,23), B2(21,23,0,2,4,6) */
 | 
			
		||||
	{ 4, 6, 8, 10, 13, 15 },
 | 
			
		||||
	{ 13, 15, 17, 19, 21, 23 },
 | 
			
		||||
	{ 21, 23, 0, 2, 4, 6 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t tch_h0_ul_facch_block_map[3][6] = {
 | 
			
		||||
	/* B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2) */
 | 
			
		||||
	{ 0, 2, 4, 6, 8, 10 },
 | 
			
		||||
	{ 8, 10, 13, 15, 17, 19 },
 | 
			
		||||
	{ 17, 19, 21, 23, 0, 2 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t tch_h1_dl_facch_block_map[3][6] = {
 | 
			
		||||
	/* B0(5,7,9,11,14,16), B1(14,16,18,20,22,24), B2(22,24,1,3,5,7) */
 | 
			
		||||
	{ 5, 7, 9, 11, 14, 16 },
 | 
			
		||||
	{ 14, 16, 18, 20, 22, 24 },
 | 
			
		||||
	{ 22, 24, 1, 3, 5, 7 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const uint8_t tch_h1_ul_facch_block_map[3][6] = {
 | 
			
		||||
	/* B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3) */
 | 
			
		||||
	{ 1, 3, 5, 7, 9, 11 },
 | 
			
		||||
	{ 9, 11, 14, 16, 18, 20 },
 | 
			
		||||
	{ 18, 20, 22, 24, 1, 3 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Can a TCH/H block transmission be initiated / finished
 | 
			
		||||
 * on a given frame number and a given channel type?
 | 
			
		||||
 *
 | 
			
		||||
 * See GSM 05.02, clause 7, table 1
 | 
			
		||||
 *
 | 
			
		||||
 * @param  chan   channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
 | 
			
		||||
 * @param  fn     the current frame number
 | 
			
		||||
 * @param  ul     Uplink or Downlink?
 | 
			
		||||
 * @param  facch  FACCH/H or traffic?
 | 
			
		||||
 * @param  start  init or end of transmission?
 | 
			
		||||
 * @return        true (yes) or false (no)
 | 
			
		||||
 */
 | 
			
		||||
bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
 | 
			
		||||
	uint32_t fn, bool ul, bool facch, bool start)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t fn_mf;
 | 
			
		||||
	int i = 0;
 | 
			
		||||
 | 
			
		||||
	/* Just to be sure */
 | 
			
		||||
	OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
 | 
			
		||||
 | 
			
		||||
	/* Calculate a modulo */
 | 
			
		||||
	fn_mf = facch ? (fn % 26) : (fn % 13);
 | 
			
		||||
 | 
			
		||||
#define MAP_GET_POS(map) \
 | 
			
		||||
	(start ? 0 : ARRAY_SIZE(map[i]) - 1)
 | 
			
		||||
 | 
			
		||||
#define BLOCK_MAP_FN(map) \
 | 
			
		||||
	do { \
 | 
			
		||||
		if (map[i][MAP_GET_POS(map)] == fn_mf) \
 | 
			
		||||
			return true; \
 | 
			
		||||
	} while (++i < ARRAY_SIZE(map))
 | 
			
		||||
 | 
			
		||||
	/* Choose a proper block map */
 | 
			
		||||
	if (facch) {
 | 
			
		||||
		if (ul) {
 | 
			
		||||
			if (chan == TRXC_TCHH_0)
 | 
			
		||||
				BLOCK_MAP_FN(tch_h0_ul_facch_block_map);
 | 
			
		||||
			else
 | 
			
		||||
				BLOCK_MAP_FN(tch_h1_ul_facch_block_map);
 | 
			
		||||
		} else {
 | 
			
		||||
			if (chan == TRXC_TCHH_0)
 | 
			
		||||
				BLOCK_MAP_FN(tch_h0_dl_facch_block_map);
 | 
			
		||||
			else
 | 
			
		||||
				BLOCK_MAP_FN(tch_h1_dl_facch_block_map);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (chan == TRXC_TCHH_0)
 | 
			
		||||
			BLOCK_MAP_FN(tch_h0_traffic_block_map);
 | 
			
		||||
		else
 | 
			
		||||
			BLOCK_MAP_FN(tch_h1_traffic_block_map);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Calculates a frame number of the first burst
 | 
			
		||||
 * using given frame number of the last burst.
 | 
			
		||||
 *
 | 
			
		||||
 * See GSM 05.02, clause 7, table 1
 | 
			
		||||
 *
 | 
			
		||||
 * @param  chan      channel type (TRXC_TCHH_0 or TRXC_TCHH_1)
 | 
			
		||||
 * @param  last_fn   frame number of the last burst
 | 
			
		||||
 * @param  facch     FACCH/H or traffic?
 | 
			
		||||
 * @return           either frame number of the first burst,
 | 
			
		||||
 *                   or fn=last_fn if calculation failed
 | 
			
		||||
 */
 | 
			
		||||
uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
 | 
			
		||||
	uint32_t last_fn, bool facch)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t fn_mf, fn_diff;
 | 
			
		||||
	int i = 0;
 | 
			
		||||
 | 
			
		||||
	/* Just to be sure */
 | 
			
		||||
	OSMO_ASSERT(chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1);
 | 
			
		||||
 | 
			
		||||
	/* Calculate a modulo */
 | 
			
		||||
	fn_mf = facch ? (last_fn % 26) : (last_fn % 13);
 | 
			
		||||
 | 
			
		||||
#define BLOCK_FIRST_FN(map) \
 | 
			
		||||
	do { \
 | 
			
		||||
		if (map[i][ARRAY_SIZE(map[i]) - 1] == fn_mf) { \
 | 
			
		||||
			fn_diff = GSM_TDMA_FN_DIFF(fn_mf, map[i][0]); \
 | 
			
		||||
			return GSM_TDMA_FN_SUB(last_fn, fn_diff); \
 | 
			
		||||
		} \
 | 
			
		||||
	} while (++i < ARRAY_SIZE(map))
 | 
			
		||||
 | 
			
		||||
	/* Choose a proper block map */
 | 
			
		||||
	if (facch) {
 | 
			
		||||
		if (chan == TRXC_TCHH_0)
 | 
			
		||||
			BLOCK_FIRST_FN(tch_h0_dl_facch_block_map);
 | 
			
		||||
		else
 | 
			
		||||
			BLOCK_FIRST_FN(tch_h1_dl_facch_block_map);
 | 
			
		||||
	} else {
 | 
			
		||||
		if (chan == TRXC_TCHH_0)
 | 
			
		||||
			BLOCK_FIRST_FN(tch_h0_traffic_block_map);
 | 
			
		||||
		else
 | 
			
		||||
			BLOCK_FIRST_FN(tch_h1_traffic_block_map);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_ERROR, "Failed to calculate TDMA "
 | 
			
		||||
		"frame number of the first burst of %s block, "
 | 
			
		||||
		"using the current fn=%u\n", facch ?
 | 
			
		||||
			"FACCH/H" : "TCH/H", last_fn);
 | 
			
		||||
 | 
			
		||||
	/* Couldn't calculate the first fn, return the last */
 | 
			
		||||
	return last_fn;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int rx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	int n_errors = -1, n_bits_total, rc;
 | 
			
		||||
	sbit_t *buffer, *offset;
 | 
			
		||||
	uint8_t l2[128], *mask;
 | 
			
		||||
	size_t l2_len;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->rx_burst_mask;
 | 
			
		||||
	buffer = lchan->rx_bursts;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	if (*mask == 0x00) {
 | 
			
		||||
		/* Align to the first burst */
 | 
			
		||||
		if (bid > 0)
 | 
			
		||||
			return 0;
 | 
			
		||||
 | 
			
		||||
		/* Align reception of the first FACCH/H frame */
 | 
			
		||||
		if (lchan->tch_mode == GSM48_CMODE_SIGN) {
 | 
			
		||||
			if (!sched_tchh_facch_start(lchan->type, fn, 0))
 | 
			
		||||
				return 0;
 | 
			
		||||
		} else { /* or TCH/H traffic frame */
 | 
			
		||||
			if (!sched_tchh_traffic_start(lchan->type, fn, 0))
 | 
			
		||||
				return 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Store the measurements */
 | 
			
		||||
	sched_trx_meas_push(lchan, meas);
 | 
			
		||||
 | 
			
		||||
	/* Copy burst to the end of buffer of 6 bursts */
 | 
			
		||||
	offset = buffer + bid * 116 + 464;
 | 
			
		||||
	memcpy(offset, bits + 3, 58);
 | 
			
		||||
	memcpy(offset + 58, bits + 87, 58);
 | 
			
		||||
 | 
			
		||||
	/* Wait until the second burst */
 | 
			
		||||
	if (bid != 1)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Wait for complete set of bursts */
 | 
			
		||||
	if (lchan->tch_mode == GSM48_CMODE_SIGN) {
 | 
			
		||||
		/* FACCH/H is interleaved over 6 bursts */
 | 
			
		||||
		if ((*mask & 0x3f) != 0x3f)
 | 
			
		||||
			goto bfi_shift;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Traffic is interleaved over 4 bursts */
 | 
			
		||||
		if ((*mask & 0x0f) != 0x0f)
 | 
			
		||||
			goto bfi_shift;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Skip decoding attempt in case of FACCH/H */
 | 
			
		||||
	if (lchan->dl_ongoing_facch) {
 | 
			
		||||
		lchan->dl_ongoing_facch = false;
 | 
			
		||||
		goto bfi_shift; /* 2/2 BFI */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (lchan->tch_mode) {
 | 
			
		||||
	case GSM48_CMODE_SIGN:
 | 
			
		||||
	case GSM48_CMODE_SPEECH_V1: /* HR */
 | 
			
		||||
		rc = gsm0503_tch_hr_decode(l2, buffer,
 | 
			
		||||
			!sched_tchh_facch_end(lchan->type, fn, 0),
 | 
			
		||||
			&n_errors, &n_bits_total);
 | 
			
		||||
		break;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_AMR: /* AMR */
 | 
			
		||||
		/**
 | 
			
		||||
		 * TODO: AMR requires a dedicated loop,
 | 
			
		||||
		 * which will be implemented later...
 | 
			
		||||
		 */
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Shift buffer by 4 bursts for interleaving */
 | 
			
		||||
	memcpy(buffer, buffer + 232, 232);
 | 
			
		||||
	memcpy(buffer + 232, buffer + 464, 232);
 | 
			
		||||
 | 
			
		||||
	/* Shift burst mask */
 | 
			
		||||
	*mask = *mask << 2;
 | 
			
		||||
 | 
			
		||||
	/* Check decoding result */
 | 
			
		||||
	if (rc < 4) {
 | 
			
		||||
		/* Calculate AVG of the measurements (assuming 4 bursts) */
 | 
			
		||||
		sched_trx_meas_avg(lchan, 4);
 | 
			
		||||
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame (%s) "
 | 
			
		||||
			"at fn=%u on %s (rc=%d)\n", burst_mask2str(mask, 6),
 | 
			
		||||
			lchan->meas_avg.fn, lchan_desc->name, rc);
 | 
			
		||||
 | 
			
		||||
		/* Send BFI */
 | 
			
		||||
		goto bfi;
 | 
			
		||||
	} else if (rc == GSM_MACBLOCK_LEN) {
 | 
			
		||||
		/* Skip decoding of the next 2 stolen bursts */
 | 
			
		||||
		lchan->dl_ongoing_facch = true;
 | 
			
		||||
 | 
			
		||||
		/* Calculate AVG of the measurements (FACCH/H takes 6 bursts) */
 | 
			
		||||
		sched_trx_meas_avg(lchan, 6);
 | 
			
		||||
 | 
			
		||||
		/* FACCH/H received, forward to the higher layers */
 | 
			
		||||
		sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
 | 
			
		||||
			n_errors, false, false);
 | 
			
		||||
 | 
			
		||||
		/* Send BFI substituting 1/2 stolen TCH frames */
 | 
			
		||||
		n_errors = -1; /* ensure fake measurements */
 | 
			
		||||
		goto bfi;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* A good TCH frame received */
 | 
			
		||||
		l2_len = rc;
 | 
			
		||||
 | 
			
		||||
		/* Calculate AVG of the measurements (traffic takes 4 bursts) */
 | 
			
		||||
		sched_trx_meas_avg(lchan, 4);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send a traffic frame to the higher layers */
 | 
			
		||||
	return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
 | 
			
		||||
		n_errors, false, true);
 | 
			
		||||
 | 
			
		||||
bfi_shift:
 | 
			
		||||
	/* Shift buffer */
 | 
			
		||||
	memcpy(buffer, buffer + 232, 232);
 | 
			
		||||
	memcpy(buffer + 232, buffer + 464, 232);
 | 
			
		||||
 | 
			
		||||
	/* Shift burst mask */
 | 
			
		||||
	*mask = *mask << 2;
 | 
			
		||||
 | 
			
		||||
bfi:
 | 
			
		||||
	/* Didn't try to decode, fake measurements */
 | 
			
		||||
	if (n_errors < 0) {
 | 
			
		||||
		lchan->meas_avg = (struct trx_meas_set) {
 | 
			
		||||
			.fn = sched_tchh_block_dl_first_fn(lchan->type, fn, false),
 | 
			
		||||
			.toa256 = 0,
 | 
			
		||||
			.rssi = -110,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		/* No bursts => no errors */
 | 
			
		||||
		n_errors = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* BFI is not applicable in signalling mode */
 | 
			
		||||
	if (lchan->tch_mode == GSM48_CMODE_SIGN)
 | 
			
		||||
		return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
 | 
			
		||||
			n_errors, true, false);
 | 
			
		||||
 | 
			
		||||
	/* Bad frame indication */
 | 
			
		||||
	l2_len = sched_bad_frame_ind(l2, lchan);
 | 
			
		||||
 | 
			
		||||
	/* Send a BFI frame to the higher layers */
 | 
			
		||||
	return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
 | 
			
		||||
		n_errors, true, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int tx_tchh_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	ubit_t burst[GSM_BURST_LEN];
 | 
			
		||||
	ubit_t *buffer, *offset;
 | 
			
		||||
	const uint8_t *tsc;
 | 
			
		||||
	uint8_t *mask;
 | 
			
		||||
	size_t l2_len;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->tx_burst_mask;
 | 
			
		||||
	buffer = lchan->tx_bursts;
 | 
			
		||||
 | 
			
		||||
	if (bid > 0) {
 | 
			
		||||
		/* Align to the first burst */
 | 
			
		||||
		if (*mask == 0x00)
 | 
			
		||||
			return 0;
 | 
			
		||||
		goto send_burst;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (*mask == 0x00) {
 | 
			
		||||
		/* Align transmission of the first FACCH/H frame */
 | 
			
		||||
		if (lchan->tch_mode == GSM48_CMODE_SIGN)
 | 
			
		||||
			if (!sched_tchh_facch_start(lchan->type, fn, 1))
 | 
			
		||||
				return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Shift buffer by 2 bursts back for interleaving */
 | 
			
		||||
	memcpy(buffer, buffer + 232, 232);
 | 
			
		||||
 | 
			
		||||
	/* Also shift TX burst mask */
 | 
			
		||||
	*mask = *mask << 2;
 | 
			
		||||
 | 
			
		||||
	/* If FACCH/H blocks are still pending */
 | 
			
		||||
	if (lchan->ul_facch_blocks > 2) {
 | 
			
		||||
		memcpy(buffer + 232, buffer + 464, 232);
 | 
			
		||||
		goto send_burst;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check the current TCH mode */
 | 
			
		||||
	switch (lchan->tch_mode) {
 | 
			
		||||
	case GSM48_CMODE_SIGN:
 | 
			
		||||
	case GSM48_CMODE_SPEECH_V1: /* HR */
 | 
			
		||||
		l2_len = GSM_HR_BYTES + 1;
 | 
			
		||||
		break;
 | 
			
		||||
	case GSM48_CMODE_SPEECH_AMR: /* AMR */
 | 
			
		||||
		/**
 | 
			
		||||
		 * TODO: AMR requires a dedicated loop,
 | 
			
		||||
		 * which will be implemented later...
 | 
			
		||||
		 */
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
 | 
			
		||||
			"dropping frame...\n");
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
 | 
			
		||||
			"dropping frame...\n", lchan->tch_mode);
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Determine payload length */
 | 
			
		||||
	if (PRIM_IS_FACCH(lchan->prim)) {
 | 
			
		||||
		l2_len = GSM_MACBLOCK_LEN; /* FACCH */
 | 
			
		||||
	} else if (lchan->prim->payload_len != l2_len) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu "
 | 
			
		||||
			"(expected %zu for TCH or %u for FACCH), so dropping...\n",
 | 
			
		||||
			lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Encode the payload */
 | 
			
		||||
	rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
 | 
			
		||||
		     lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
 | 
			
		||||
							    lchan->prim->payload_len));
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* A FACCH/H frame occupies 6 bursts */
 | 
			
		||||
	if (PRIM_IS_FACCH(lchan->prim))
 | 
			
		||||
		lchan->ul_facch_blocks = 6;
 | 
			
		||||
 | 
			
		||||
send_burst:
 | 
			
		||||
	/* Determine which burst should be sent */
 | 
			
		||||
	offset = buffer + bid * 116;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Choose proper TSC */
 | 
			
		||||
	tsc = sched_nb_training_bits[trx->tsc];
 | 
			
		||||
 | 
			
		||||
	/* Compose a new burst */
 | 
			
		||||
	memset(burst, 0, 3); /* TB */
 | 
			
		||||
	memcpy(burst + 3, offset, 58); /* Payload 1/2 */
 | 
			
		||||
	memcpy(burst + 61, tsc, 26); /* TSC */
 | 
			
		||||
	memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
 | 
			
		||||
	memset(burst + 145, 0, 3); /* TB */
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Forward burst to transceiver */
 | 
			
		||||
	sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
 | 
			
		||||
 | 
			
		||||
	/* In case of a FACCH/H frame, one block less */
 | 
			
		||||
	if (lchan->ul_facch_blocks)
 | 
			
		||||
		lchan->ul_facch_blocks--;
 | 
			
		||||
 | 
			
		||||
	if ((*mask & 0x0f) == 0x0f) {
 | 
			
		||||
		/**
 | 
			
		||||
		 * If no more FACCH/H blocks pending,
 | 
			
		||||
		 * confirm data / traffic sending
 | 
			
		||||
		 */
 | 
			
		||||
		if (!lchan->ul_facch_blocks)
 | 
			
		||||
			sched_send_dt_conf(trx, ts, lchan, fn,
 | 
			
		||||
				PRIM_IS_TCH(lchan->prim));
 | 
			
		||||
 | 
			
		||||
		/* Forget processed primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,217 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: handlers for DL / UL bursts on logical channels
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017-2020 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
#include <osmocom/coding/gsm0503_coding.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline))
 | 
			
		||||
static int gsm0503_xcch_decode_xray(uint8_t *l2_data, const sbit_t *bursts,
 | 
			
		||||
							 int *n_errors, int *n_bits_total) {
 | 
			
		||||
	 return gsm0503_xcch_decode(l2_data, bursts, n_errors, n_bits_total);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
__attribute__((xray_always_instrument)) __attribute__((noinline)) int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
 | 
			
		||||
	const sbit_t *bits, const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	uint8_t l2[GSM_MACBLOCK_LEN], *mask;
 | 
			
		||||
	int n_errors, n_bits_total, rc;
 | 
			
		||||
	sbit_t *buffer, *offset;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->rx_burst_mask;
 | 
			
		||||
	buffer = lchan->rx_bursts;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Data received on %s: fn=%u ts=%u bid=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Align to the first burst of a block */
 | 
			
		||||
	if (*mask == 0x00 && bid != 0)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Store the measurements */
 | 
			
		||||
	sched_trx_meas_push(lchan, meas);
 | 
			
		||||
 | 
			
		||||
	/* Copy burst to buffer of 4 bursts */
 | 
			
		||||
	offset = buffer + bid * 116;
 | 
			
		||||
	memcpy(offset, bits + 3, 58);
 | 
			
		||||
	memcpy(offset + 58, bits + 87, 58);
 | 
			
		||||
 | 
			
		||||
	/* Wait until complete set of bursts */
 | 
			
		||||
	if (bid != 3)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Calculate AVG of the measurements */
 | 
			
		||||
	sched_trx_meas_avg(lchan, 4);
 | 
			
		||||
 | 
			
		||||
	/* Check for complete set of bursts */
 | 
			
		||||
	if ((*mask & 0xf) != 0xf) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received incomplete (%s) data frame at "
 | 
			
		||||
			"fn=%u (%u/%u) for %s\n",
 | 
			
		||||
			burst_mask2str(mask, 4), lchan->meas_avg.fn,
 | 
			
		||||
			lchan->meas_avg.fn % ts->mf_layout->period,
 | 
			
		||||
			ts->mf_layout->period,
 | 
			
		||||
			lchan_desc->name);
 | 
			
		||||
		/* NOTE: xCCH has an insane amount of redundancy for error
 | 
			
		||||
		 * correction, so even just 2 valid bursts might be enough
 | 
			
		||||
		 * to reconstruct some L2 frames. This is why we do not
 | 
			
		||||
		 * abort here. */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Keep the mask updated */
 | 
			
		||||
	*mask = *mask << 4;
 | 
			
		||||
 | 
			
		||||
	/* Attempt to decode */
 | 
			
		||||
	rc = gsm0503_xcch_decode_xray(l2, buffer, &n_errors, &n_bits_total);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Received bad data frame with %d errors at fn=%u "
 | 
			
		||||
			"(%u/%u) for %s\n", n_errors, lchan->meas_avg.fn,
 | 
			
		||||
			lchan->meas_avg.fn % ts->mf_layout->period,
 | 
			
		||||
			ts->mf_layout->period,
 | 
			
		||||
			lchan_desc->name);
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * We should anyway send dummy frame for
 | 
			
		||||
		 * proper measurement reporting...
 | 
			
		||||
		 */
 | 
			
		||||
		return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
 | 
			
		||||
			n_errors, true, false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send a L2 frame to the higher layers */
 | 
			
		||||
	return sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
 | 
			
		||||
		n_errors, false, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	ubit_t burst[GSM_BURST_LEN];
 | 
			
		||||
	ubit_t *buffer, *offset;
 | 
			
		||||
	const uint8_t *tsc;
 | 
			
		||||
	uint8_t *mask;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Set up pointers */
 | 
			
		||||
	lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
	mask = &lchan->tx_burst_mask;
 | 
			
		||||
	buffer = lchan->tx_bursts;
 | 
			
		||||
 | 
			
		||||
	if (bid > 0) {
 | 
			
		||||
		/* If we have encoded bursts */
 | 
			
		||||
		if (*mask)
 | 
			
		||||
			goto send_burst;
 | 
			
		||||
		else
 | 
			
		||||
			return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check the prim payload length */
 | 
			
		||||
	if (lchan->prim->payload_len != GSM_MACBLOCK_LEN) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Primitive has odd length %zu (expected %u), "
 | 
			
		||||
			"so dropping...\n", lchan->prim->payload_len, GSM_MACBLOCK_LEN);
 | 
			
		||||
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Encode payload */
 | 
			
		||||
	rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
 | 
			
		||||
		     lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
 | 
			
		||||
							    lchan->prim->payload_len));
 | 
			
		||||
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
send_burst:
 | 
			
		||||
	/* Determine which burst should be sent */
 | 
			
		||||
	offset = buffer + bid * 116;
 | 
			
		||||
 | 
			
		||||
	/* Update mask */
 | 
			
		||||
	*mask |= (1 << bid);
 | 
			
		||||
 | 
			
		||||
	/* Choose proper TSC */
 | 
			
		||||
	tsc = sched_nb_training_bits[trx->tsc];
 | 
			
		||||
 | 
			
		||||
	/* Compose a new burst */
 | 
			
		||||
	memset(burst, 0, 3); /* TB */
 | 
			
		||||
	memcpy(burst + 3, offset, 58); /* Payload 1/2 */
 | 
			
		||||
	memcpy(burst + 61, tsc, 26); /* TSC */
 | 
			
		||||
	memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
 | 
			
		||||
	memset(burst + 145, 0, 3); /* TB */
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
 | 
			
		||||
		lchan_desc->name, fn, ts->index, bid);
 | 
			
		||||
 | 
			
		||||
	/* Forward burst to scheduler */
 | 
			
		||||
	rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		/* Forget this primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Reset mask */
 | 
			
		||||
		*mask = 0x00;
 | 
			
		||||
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* If we have sent the last (4/4) burst */
 | 
			
		||||
	if ((*mask & 0x0f) == 0x0f) {
 | 
			
		||||
		/* Confirm data sending */
 | 
			
		||||
		sched_send_dt_conf(trx, ts, lchan, fn, false);
 | 
			
		||||
 | 
			
		||||
		/* Forget processed primitive */
 | 
			
		||||
		sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Reset mask */
 | 
			
		||||
		*mask = 0x00;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,617 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: primitive management
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <talloc.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Initializes a new primitive by allocating memory
 | 
			
		||||
 * and filling some meta-information (e.g. lchan type).
 | 
			
		||||
 *
 | 
			
		||||
 * @param  ctx     parent talloc context
 | 
			
		||||
 * @param  prim    external prim pointer (will point to the allocated prim)
 | 
			
		||||
 * @param  pl_len  prim payload length
 | 
			
		||||
 * @param  chan_nr RSL channel description (used to set a proper chan)
 | 
			
		||||
 * @param  link_id RSL link description (used to set a proper chan)
 | 
			
		||||
 * @return         zero in case of success, otherwise a error number
 | 
			
		||||
 */
 | 
			
		||||
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
 | 
			
		||||
	size_t pl_len, uint8_t chan_nr, uint8_t link_id)
 | 
			
		||||
{
 | 
			
		||||
	enum trx_lchan_type lchan_type;
 | 
			
		||||
	struct trx_ts_prim *new_prim;
 | 
			
		||||
	uint8_t len;
 | 
			
		||||
 | 
			
		||||
	/* Determine lchan type */
 | 
			
		||||
	lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id);
 | 
			
		||||
	if (!lchan_type) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type "
 | 
			
		||||
			"for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* How much memory do we need? */
 | 
			
		||||
	len  = sizeof(struct trx_ts_prim); /* Primitive header */
 | 
			
		||||
	len += pl_len; /* Requested payload size */
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new primitive */
 | 
			
		||||
	new_prim = talloc_zero_size(ctx, len);
 | 
			
		||||
	if (new_prim == NULL) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n");
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Init primitive header */
 | 
			
		||||
	new_prim->payload_len = pl_len;
 | 
			
		||||
	new_prim->chan = lchan_type;
 | 
			
		||||
 | 
			
		||||
	/* Set external pointer */
 | 
			
		||||
	*prim = new_prim;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds a primitive to the end of transmit queue of a particular
 | 
			
		||||
 * timeslot, whose index is parsed from chan_nr.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  trx     TRX instance
 | 
			
		||||
 * @param  prim    to be enqueued primitive
 | 
			
		||||
 * @param  chan_nr RSL channel description
 | 
			
		||||
 * @return         zero in case of success, otherwise a error number
 | 
			
		||||
 */
 | 
			
		||||
int sched_prim_push(struct trx_instance *trx,
 | 
			
		||||
	struct trx_ts_prim *prim, uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
	uint8_t tn;
 | 
			
		||||
 | 
			
		||||
	/* Determine TS index */
 | 
			
		||||
	tn = chan_nr & 0x7;
 | 
			
		||||
 | 
			
		||||
	/* Check whether required timeslot is allocated and configured */
 | 
			
		||||
	ts = trx->ts_list[tn];
 | 
			
		||||
	if (ts == NULL || ts->mf_layout == NULL) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Change talloc context of primitive
 | 
			
		||||
	 * from trx to the parent ts
 | 
			
		||||
	 */
 | 
			
		||||
	talloc_steal(ts, prim);
 | 
			
		||||
 | 
			
		||||
	/* Add primitive to TS transmit queue */
 | 
			
		||||
	llist_add_tail(&prim->list, &ts->tx_prims);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Composes a new primitive using either cached (if populated),
 | 
			
		||||
 * or "dummy" Measurement Report message.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  lchan lchan to assign a primitive
 | 
			
		||||
 * @return       SACCH primitive to be transmitted
 | 
			
		||||
 */
 | 
			
		||||
static struct trx_ts_prim *prim_compose_mr(struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
	uint8_t *mr_src_ptr;
 | 
			
		||||
	bool cached;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* "Dummy" Measurement Report */
 | 
			
		||||
	static const uint8_t meas_rep_dummy[] = {
 | 
			
		||||
		/* L1 SACCH pseudo-header */
 | 
			
		||||
		0x0f, 0x00,
 | 
			
		||||
 | 
			
		||||
		/* LAPDm header */
 | 
			
		||||
		0x01, 0x03, 0x49,
 | 
			
		||||
 | 
			
		||||
		/* RR Management messages, Measurement Report */
 | 
			
		||||
		0x06, 0x15,
 | 
			
		||||
 | 
			
		||||
		/* Measurement results (see 3GPP TS 44.018, section 10.5.2.20):
 | 
			
		||||
		 *   0... .... = BA-USED: 0
 | 
			
		||||
		 *   .0.. .... = DTX-USED: DTX was not used
 | 
			
		||||
		 *   ..11 0110 = RXLEV-FULL-SERVING-CELL: -57 <= x < -56 dBm (54)
 | 
			
		||||
		 *   0... .... = 3G-BA-USED: 0
 | 
			
		||||
		 *   .1.. .... = MEAS-VALID: The measurement results are not valid
 | 
			
		||||
		 *   ..11 0110 = RXLEV-SUB-SERVING-CELL: -57 <= x < -56 dBm (54)
 | 
			
		||||
		 *   0... .... = SI23_BA_USED: 0
 | 
			
		||||
		 *   .000 .... = RXQUAL-FULL-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
 | 
			
		||||
		 *   .... 000. = RXQUAL-SUB-SERVING-CELL: BER < 0.2%, Mean value 0.14% (0)
 | 
			
		||||
		 *   .... ...1  11.. .... = NO-NCELL-M: Neighbour cell information not available */
 | 
			
		||||
		0x36, 0x76, 0x01, 0xc0,
 | 
			
		||||
 | 
			
		||||
		/* 0** -- Padding with zeroes */
 | 
			
		||||
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new primitive */
 | 
			
		||||
	rc = sched_prim_init(lchan, &prim, GSM_MACBLOCK_LEN,
 | 
			
		||||
		trx_lchan_desc[lchan->type].chan_nr, TRX_CH_LID_SACCH);
 | 
			
		||||
	OSMO_ASSERT(rc == 0);
 | 
			
		||||
 | 
			
		||||
	/* Check if the MR cache is populated (verify LAPDm header) */
 | 
			
		||||
	cached = (lchan->sacch.mr_cache[2] != 0x00
 | 
			
		||||
		&& lchan->sacch.mr_cache[3] != 0x00
 | 
			
		||||
		&& lchan->sacch.mr_cache[4] != 0x00);
 | 
			
		||||
	if (cached) { /* Use the cached one */
 | 
			
		||||
		mr_src_ptr = lchan->sacch.mr_cache;
 | 
			
		||||
		lchan->sacch.mr_cache_usage++;
 | 
			
		||||
	} else { /* Use "dummy" one */
 | 
			
		||||
		mr_src_ptr = (uint8_t *) meas_rep_dummy;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Compose a new Measurement Report primitive */
 | 
			
		||||
	memcpy(prim->payload, mr_src_ptr, GSM_MACBLOCK_LEN);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Update the L1 SACCH pseudo-header (only for cached MRs)
 | 
			
		||||
	 *
 | 
			
		||||
	 * TODO: filling of the actual values into cached Measurement
 | 
			
		||||
	 * Reports would break the distance spoofing feature. If it
 | 
			
		||||
	 * were known whether the spoofing is enabled or not, we could
 | 
			
		||||
	 * decide whether to update the cached L1 SACCH header here.
 | 
			
		||||
	 */
 | 
			
		||||
	if (!cached) {
 | 
			
		||||
		prim->payload[0] = lchan->ts->trx->tx_power;
 | 
			
		||||
		prim->payload[1] = lchan->ts->trx->ta;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Inform about the cache usage count */
 | 
			
		||||
	if (cached && lchan->sacch.mr_cache_usage > 5) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_NOTICE, "SACCH MR cache usage count=%u > 5 "
 | 
			
		||||
			"on lchan=%s => ancient measurements, please fix!\n",
 | 
			
		||||
			lchan->sacch.mr_cache_usage,
 | 
			
		||||
			trx_lchan_desc[lchan->type].name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_NOTICE, "Using a %s Measurement Report "
 | 
			
		||||
		"on lchan=%s\n", (cached ? "cached" : "dummy"),
 | 
			
		||||
		trx_lchan_desc[lchan->type].name);
 | 
			
		||||
 | 
			
		||||
	return prim;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dequeues a SACCH primitive from transmit queue, if present.
 | 
			
		||||
 * Otherwise dequeues a cached Measurement Report (the last
 | 
			
		||||
 * received one). Finally, if the cache is empty, a "dummy"
 | 
			
		||||
 * measurement report is used.
 | 
			
		||||
 *
 | 
			
		||||
 * According to 3GPP TS 04.08, section 3.4.1, SACCH channel
 | 
			
		||||
 * accompanies either a traffic or a signaling channel. It
 | 
			
		||||
 * has the particularity that continuous transmission must
 | 
			
		||||
 * occur in both directions, so on the Uplink direction
 | 
			
		||||
 * measurement result messages are sent at each possible
 | 
			
		||||
 * occasion when nothing else has to be sent. The LAPDm
 | 
			
		||||
 * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not
 | 
			
		||||
 * applicable on SACCH channels!
 | 
			
		||||
 *
 | 
			
		||||
 * Unfortunately, 3GPP TS 04.08 doesn't clearly state
 | 
			
		||||
 * which "else messages" besides Measurement Reports
 | 
			
		||||
 * can be send by the MS on SACCH channels. However,
 | 
			
		||||
 * in sub-clause 3.4.1 it's stated that the interval
 | 
			
		||||
 * between two successive measurement result messages
 | 
			
		||||
 * shall not exceed one L2 frame.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  queue transmit queue to take a prim from
 | 
			
		||||
 * @param  lchan lchan to assign a primitive
 | 
			
		||||
 * @return       SACCH primitive to be transmitted
 | 
			
		||||
 */
 | 
			
		||||
static struct trx_ts_prim *prim_dequeue_sacch(struct llist_head *queue,
 | 
			
		||||
	struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *prim_nmr = NULL;
 | 
			
		||||
	struct trx_ts_prim *prim_mr = NULL;
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
	bool mr_now;
 | 
			
		||||
 | 
			
		||||
	/* Shall we transmit MR now? */
 | 
			
		||||
	mr_now = !lchan->sacch.mr_tx_last;
 | 
			
		||||
 | 
			
		||||
#define PRIM_IS_MR(prim) \
 | 
			
		||||
	(prim->payload[5] == GSM48_PDISC_RR \
 | 
			
		||||
		&& prim->payload[6] == GSM48_MT_RR_MEAS_REP)
 | 
			
		||||
 | 
			
		||||
	/* Iterate over all primitives in the queue */
 | 
			
		||||
	llist_for_each_entry(prim, queue, list) {
 | 
			
		||||
		/* We are looking for particular channel */
 | 
			
		||||
		if (prim->chan != lchan->type)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Look for a Measurement Report */
 | 
			
		||||
		if (!prim_mr && PRIM_IS_MR(prim))
 | 
			
		||||
			prim_mr = prim;
 | 
			
		||||
 | 
			
		||||
		/* Look for anything else */
 | 
			
		||||
		if (!prim_nmr && !PRIM_IS_MR(prim))
 | 
			
		||||
			prim_nmr = prim;
 | 
			
		||||
 | 
			
		||||
		/* Should we look further? */
 | 
			
		||||
		if (mr_now && prim_mr)
 | 
			
		||||
			break; /* MR was found */
 | 
			
		||||
		else if (!mr_now && prim_nmr)
 | 
			
		||||
			break; /* something else was found */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "SACCH MR selection on lchan=%s: "
 | 
			
		||||
		"mr_tx_last=%d prim_mr=%p prim_nmr=%p\n",
 | 
			
		||||
		trx_lchan_desc[lchan->type].name,
 | 
			
		||||
		lchan->sacch.mr_tx_last,
 | 
			
		||||
		prim_mr, prim_nmr);
 | 
			
		||||
 | 
			
		||||
	/* Prioritize non-MR prim if possible */
 | 
			
		||||
	if (mr_now && prim_mr)
 | 
			
		||||
		prim = prim_mr;
 | 
			
		||||
	else if (!mr_now && prim_nmr)
 | 
			
		||||
		prim = prim_nmr;
 | 
			
		||||
	else if (!mr_now && prim_mr)
 | 
			
		||||
		prim = prim_mr;
 | 
			
		||||
	else /* Nothing was found */
 | 
			
		||||
		prim = NULL;
 | 
			
		||||
 | 
			
		||||
	/* Have we found what we were looking for? */
 | 
			
		||||
	if (prim) /* Dequeue if so */
 | 
			
		||||
		llist_del(&prim->list);
 | 
			
		||||
	else /* Otherwise compose a new MR */
 | 
			
		||||
		prim = prim_compose_mr(lchan);
 | 
			
		||||
 | 
			
		||||
	/* Update the cached report */
 | 
			
		||||
	if (prim == prim_mr) {
 | 
			
		||||
		memcpy(lchan->sacch.mr_cache,
 | 
			
		||||
			prim->payload, GSM_MACBLOCK_LEN);
 | 
			
		||||
		lchan->sacch.mr_cache_usage = 0;
 | 
			
		||||
 | 
			
		||||
		LOGP(DSCHD, LOGL_DEBUG, "SACCH MR cache has been updated "
 | 
			
		||||
			"for lchan=%s\n", trx_lchan_desc[lchan->type].name);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Update the MR transmission state */
 | 
			
		||||
	lchan->sacch.mr_tx_last = PRIM_IS_MR(prim);
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "SACCH decision on lchan=%s: %s\n",
 | 
			
		||||
		trx_lchan_desc[lchan->type].name, PRIM_IS_MR(prim) ?
 | 
			
		||||
			"Measurement Report" : "data frame");
 | 
			
		||||
 | 
			
		||||
	return prim;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Dequeues a primitive of a given channel type */
 | 
			
		||||
static struct trx_ts_prim *prim_dequeue_one(struct llist_head *queue,
 | 
			
		||||
	enum trx_lchan_type lchan_type)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * There is no need to use the 'safe' list iteration here
 | 
			
		||||
	 * as an item removal is immediately followed by return.
 | 
			
		||||
	 */
 | 
			
		||||
	llist_for_each_entry(prim, queue, list) {
 | 
			
		||||
		if (prim->chan == lchan_type) {
 | 
			
		||||
			llist_del(&prim->list);
 | 
			
		||||
			return prim;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dequeues either a FACCH, or a speech TCH primitive
 | 
			
		||||
 * of a given channel type (Lm or Bm).
 | 
			
		||||
 *
 | 
			
		||||
 * Note: we could avoid 'lchan_type' parameter and just
 | 
			
		||||
 * check the prim's channel type using CHAN_IS_TCH(),
 | 
			
		||||
 * but the current approach is a bit more flexible,
 | 
			
		||||
 * and allows one to have both sub-slots of TCH/H
 | 
			
		||||
 * enabled on same timeslot e.g. for testing...
 | 
			
		||||
 *
 | 
			
		||||
 * @param  queue      transmit queue to take a prim from
 | 
			
		||||
 * @param  lchan_type required channel type of a primitive,
 | 
			
		||||
 *                    e.g. TRXC_TCHF, TRXC_TCHH_0, or TRXC_TCHH_1
 | 
			
		||||
 * @param  facch      FACCH (true) or speech (false) prim?
 | 
			
		||||
 * @return            either a FACCH, or a TCH primitive if found,
 | 
			
		||||
 *                    otherwise NULL
 | 
			
		||||
 */
 | 
			
		||||
static struct trx_ts_prim *prim_dequeue_tch(struct llist_head *queue,
 | 
			
		||||
	enum trx_lchan_type lchan_type, bool facch)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * There is no need to use the 'safe' list iteration here
 | 
			
		||||
	 * as an item removal is immediately followed by return.
 | 
			
		||||
	 */
 | 
			
		||||
	llist_for_each_entry(prim, queue, list) {
 | 
			
		||||
		if (prim->chan != lchan_type)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Either FACCH, or not FACCH */
 | 
			
		||||
		if (PRIM_IS_FACCH(prim) != facch)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		llist_del(&prim->list);
 | 
			
		||||
		return prim;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dequeues either a TCH/F, or a FACCH/F prim (preferred).
 | 
			
		||||
 * If a FACCH/F prim is found, one TCH/F prim is being
 | 
			
		||||
 * dropped (i.e. replaced).
 | 
			
		||||
 *
 | 
			
		||||
 * @param  queue a transmit queue to take a prim from
 | 
			
		||||
 * @return       either a FACCH/F, or a TCH/F primitive,
 | 
			
		||||
 *               otherwise NULL
 | 
			
		||||
 */
 | 
			
		||||
static struct trx_ts_prim *prim_dequeue_tch_f(struct llist_head *queue)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *facch;
 | 
			
		||||
	struct trx_ts_prim *tch;
 | 
			
		||||
 | 
			
		||||
	/* Attempt to find a pair of both FACCH/F and TCH/F frames */
 | 
			
		||||
	facch = prim_dequeue_tch(queue, TRXC_TCHF, true);
 | 
			
		||||
	tch = prim_dequeue_tch(queue, TRXC_TCHF, false);
 | 
			
		||||
 | 
			
		||||
	/* Prioritize FACCH/F, if found */
 | 
			
		||||
	if (facch) {
 | 
			
		||||
		/* One TCH/F prim is replaced */
 | 
			
		||||
		if (tch)
 | 
			
		||||
			talloc_free(tch);
 | 
			
		||||
		return facch;
 | 
			
		||||
	} else if (tch) {
 | 
			
		||||
		/* Only TCH/F prim was found */
 | 
			
		||||
		return tch;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Nothing was found, e.g. when only SACCH frames are in queue */
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dequeues either a TCH/H, or a FACCH/H prim (preferred).
 | 
			
		||||
 * If a FACCH/H prim is found, two TCH/H prims are being
 | 
			
		||||
 * dropped (i.e. replaced).
 | 
			
		||||
 *
 | 
			
		||||
 * According to GSM 05.02, the following blocks can be used
 | 
			
		||||
 * to carry FACCH/H data (see clause 7, table 1 of 9):
 | 
			
		||||
 *
 | 
			
		||||
 * UL FACCH/H0:
 | 
			
		||||
 * B0(0,2,4,6,8,10), B1(8,10,13,15,17,19), B2(17,19,21,23,0,2)
 | 
			
		||||
 *
 | 
			
		||||
 * UL FACCH/H1:
 | 
			
		||||
 * B0(1,3,5,7,9,11), B1(9,11,14,16,18,20), B2(18,20,22,24,1,3)
 | 
			
		||||
 *
 | 
			
		||||
 * where the numbers within brackets are fn % 26.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  queue      transmit queue to take a prim from
 | 
			
		||||
 * @param  fn         the current frame number
 | 
			
		||||
 * @param  lchan_type required channel type of a primitive,
 | 
			
		||||
 * @return            either a FACCH/H, or a TCH/H primitive,
 | 
			
		||||
 *                    otherwise NULL
 | 
			
		||||
 */
 | 
			
		||||
static struct trx_ts_prim *prim_dequeue_tch_h(struct llist_head *queue,
 | 
			
		||||
	uint32_t fn, enum trx_lchan_type lchan_type)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *facch;
 | 
			
		||||
	struct trx_ts_prim *tch;
 | 
			
		||||
	bool facch_now;
 | 
			
		||||
 | 
			
		||||
	/* May we initiate an UL FACCH/H frame transmission now? */
 | 
			
		||||
	facch_now = sched_tchh_facch_start(lchan_type, fn, true);
 | 
			
		||||
	if (!facch_now) /* Just dequeue a TCH/H prim */
 | 
			
		||||
		goto no_facch;
 | 
			
		||||
 | 
			
		||||
	/* If there are no FACCH/H prims in the queue */
 | 
			
		||||
	facch = prim_dequeue_tch(queue, lchan_type, true);
 | 
			
		||||
	if (!facch) /* Just dequeue a TCH/H prim */
 | 
			
		||||
		goto no_facch;
 | 
			
		||||
 | 
			
		||||
	/* FACCH/H prim replaces two TCH/F prims */
 | 
			
		||||
	tch = prim_dequeue_tch(queue, lchan_type, false);
 | 
			
		||||
	if (tch) {
 | 
			
		||||
		/* At least one TCH/H prim is dropped */
 | 
			
		||||
		talloc_free(tch);
 | 
			
		||||
 | 
			
		||||
		/* Attempt to find another */
 | 
			
		||||
		tch = prim_dequeue_tch(queue, lchan_type, false);
 | 
			
		||||
		if (tch) /* Drop the second TCH/H prim */
 | 
			
		||||
			talloc_free(tch);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return facch;
 | 
			
		||||
 | 
			
		||||
no_facch:
 | 
			
		||||
	return prim_dequeue_tch(queue, lchan_type, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Dequeues a single primitive of required type
 | 
			
		||||
 * from a specified transmit queue.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  queue      a transmit queue to take a prim from
 | 
			
		||||
 * @param  fn         the current frame number (used for FACCH/H)
 | 
			
		||||
 * @param  lchan      logical channel state
 | 
			
		||||
 * @return            a primitive or NULL if not found
 | 
			
		||||
 */
 | 
			
		||||
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
 | 
			
		||||
	uint32_t fn, struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	/* SACCH is unorthodox, see 3GPP TS 04.08, section 3.4.1 */
 | 
			
		||||
	if (CHAN_IS_SACCH(lchan->type))
 | 
			
		||||
		return prim_dequeue_sacch(queue, lchan);
 | 
			
		||||
 | 
			
		||||
	/* There is nothing to dequeue */
 | 
			
		||||
	if (llist_empty(queue))
 | 
			
		||||
		return NULL;
 | 
			
		||||
 | 
			
		||||
	switch (lchan->type) {
 | 
			
		||||
	/* TCH/F requires FACCH/F prioritization */
 | 
			
		||||
	case TRXC_TCHF:
 | 
			
		||||
		return prim_dequeue_tch_f(queue);
 | 
			
		||||
 | 
			
		||||
	/* FACCH/H prioritization is a bit more complex */
 | 
			
		||||
	case TRXC_TCHH_0:
 | 
			
		||||
	case TRXC_TCHH_1:
 | 
			
		||||
		return prim_dequeue_tch_h(queue, fn, lchan->type);
 | 
			
		||||
 | 
			
		||||
	/* Other kinds of logical channels */
 | 
			
		||||
	default:
 | 
			
		||||
		return prim_dequeue_one(queue, lchan->type);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Drops the current primitive of specified logical channel
 | 
			
		||||
 *
 | 
			
		||||
 * @param lchan a logical channel to drop prim from
 | 
			
		||||
 */
 | 
			
		||||
void sched_prim_drop(struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	/* Forget this primitive */
 | 
			
		||||
	talloc_free(lchan->prim);
 | 
			
		||||
	lchan->prim = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Assigns a dummy primitive to a lchan depending on its type.
 | 
			
		||||
 * Could be used when there is nothing to transmit, but
 | 
			
		||||
 * CBTX (Continuous Burst Transmission) is assumed.
 | 
			
		||||
 *
 | 
			
		||||
 * @param  lchan lchan to assign a primitive
 | 
			
		||||
 * @return       zero in case of success, otherwise a error code
 | 
			
		||||
 */
 | 
			
		||||
int sched_prim_dummy(struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	enum trx_lchan_type chan = lchan->type;
 | 
			
		||||
	uint8_t tch_mode = lchan->tch_mode;
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
	uint8_t prim_buffer[40];
 | 
			
		||||
	size_t prim_len = 0;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * TS 144.006, section 8.4.2.3 "Fill frames"
 | 
			
		||||
	 * A fill frame is a UI command frame for SAPI 0, P=0
 | 
			
		||||
	 * and with an information field of 0 octet length.
 | 
			
		||||
	 */
 | 
			
		||||
	static const uint8_t lapdm_fill_frame[] = {
 | 
			
		||||
		0x01, 0x03, 0x01, 0x2b,
 | 
			
		||||
		/* Pending part is to be randomized */
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Make sure that there is no existing primitive */
 | 
			
		||||
	OSMO_ASSERT(lchan->prim == NULL);
 | 
			
		||||
	/* Not applicable for SACCH! */
 | 
			
		||||
	OSMO_ASSERT(!CHAN_IS_SACCH(lchan->type));
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determine what actually should be generated:
 | 
			
		||||
	 * TCH in GSM48_CMODE_SIGN: LAPDm fill frame;
 | 
			
		||||
	 * TCH in other modes: silence frame;
 | 
			
		||||
	 * other channels: LAPDm fill frame.
 | 
			
		||||
	 */
 | 
			
		||||
	if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) {
 | 
			
		||||
		/* Bad frame indication */
 | 
			
		||||
		prim_len = sched_bad_frame_ind(prim_buffer, lchan);
 | 
			
		||||
	} else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) {
 | 
			
		||||
		/* FIXME: should we do anything for CSD? */
 | 
			
		||||
		return 0;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Copy LAPDm fill frame's header */
 | 
			
		||||
		memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame));
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * TS 144.006, section 5.2 "Frame delimitation and fill bits"
 | 
			
		||||
		 * Except for the first octet containing fill bits which shall
 | 
			
		||||
		 * be set to the binary value "00101011", each fill bit should
 | 
			
		||||
		 * be set to a random value when sent by the network.
 | 
			
		||||
		 */
 | 
			
		||||
		for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++)
 | 
			
		||||
			prim_buffer[i] = (uint8_t) rand();
 | 
			
		||||
 | 
			
		||||
		/* Define a prim length */
 | 
			
		||||
		prim_len = GSM_MACBLOCK_LEN;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Nothing to allocate / assign */
 | 
			
		||||
	if (!prim_len)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new primitive */
 | 
			
		||||
	prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len);
 | 
			
		||||
	if (prim == NULL)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/* Init primitive header */
 | 
			
		||||
	prim->payload_len = prim_len;
 | 
			
		||||
	prim->chan = lchan->type;
 | 
			
		||||
 | 
			
		||||
	/* Fill in the payload */
 | 
			
		||||
	memcpy(prim->payload, prim_buffer, prim_len);
 | 
			
		||||
 | 
			
		||||
	/* Assign the current prim */
 | 
			
		||||
	lchan->prim = prim;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame "
 | 
			
		||||
		"on lchan=%s\n", trx_lchan_desc[chan].name);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Flushes a queue of primitives
 | 
			
		||||
 *
 | 
			
		||||
 * @param list list of prims going to be flushed
 | 
			
		||||
 */
 | 
			
		||||
void sched_prim_flush_queue(struct llist_head *list)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ts_prim *prim, *prim_next;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry_safe(prim, prim_next, list, list) {
 | 
			
		||||
		llist_del(&prim->list);
 | 
			
		||||
		talloc_free(prim);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,841 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * TDMA scheduler: GSM PHY routines
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2017-2019 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <error.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <talloc.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/a5.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_08_58.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
 | 
			
		||||
static void sched_frame_clck_cb(struct trx_sched *sched)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = (struct trx_instance *) sched->data;
 | 
			
		||||
	const struct trx_frame *frame;
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
	trx_lchan_tx_func *handler;
 | 
			
		||||
	enum trx_lchan_type chan;
 | 
			
		||||
	uint8_t offset, bid;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
	uint32_t fn;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	/* Iterate over timeslot list */
 | 
			
		||||
	for (i = 0; i < TRX_TS_COUNT; i++) {
 | 
			
		||||
		/* Timeslot is not allocated */
 | 
			
		||||
		ts = trx->ts_list[i];
 | 
			
		||||
		if (ts == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Timeslot is not configured */
 | 
			
		||||
		if (ts->mf_layout == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Advance frame number, giving the transceiver more
 | 
			
		||||
		 * time until a burst must be transmitted...
 | 
			
		||||
		 */
 | 
			
		||||
		fn = GSM_TDMA_FN_SUM(sched->fn_counter_proc, sched->fn_counter_advance);
 | 
			
		||||
 | 
			
		||||
		/* Get frame from multiframe */
 | 
			
		||||
		offset = fn % ts->mf_layout->period;
 | 
			
		||||
		frame = ts->mf_layout->frames + offset;
 | 
			
		||||
 | 
			
		||||
		/* Get required info from frame */
 | 
			
		||||
		bid = frame->ul_bid;
 | 
			
		||||
		chan = frame->ul_chan;
 | 
			
		||||
		handler = trx_lchan_desc[chan].tx_fn;
 | 
			
		||||
 | 
			
		||||
		/* Omit lchans without handler */
 | 
			
		||||
		if (!handler)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Make sure that lchan was allocated and activated */
 | 
			
		||||
		lchan = sched_trx_find_lchan(ts, chan);
 | 
			
		||||
		if (lchan == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Omit inactive lchans */
 | 
			
		||||
		if (!lchan->active)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * If we aren't processing any primitive yet,
 | 
			
		||||
		 * attempt to obtain a new one from queue
 | 
			
		||||
		 */
 | 
			
		||||
		if (lchan->prim == NULL)
 | 
			
		||||
			lchan->prim = sched_prim_dequeue(&ts->tx_prims, fn, lchan);
 | 
			
		||||
 | 
			
		||||
		/* TODO: report TX buffers health to the higher layers */
 | 
			
		||||
 | 
			
		||||
		/* If CBTX (Continuous Burst Transmission) is assumed */
 | 
			
		||||
		if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) {
 | 
			
		||||
			/**
 | 
			
		||||
			 * Probably, a TX buffer is empty. Nevertheless,
 | 
			
		||||
			 * we shall continuously transmit anything on
 | 
			
		||||
			 * CBTX channels.
 | 
			
		||||
			 */
 | 
			
		||||
			if (lchan->prim == NULL)
 | 
			
		||||
				sched_prim_dummy(lchan);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* If there is no primitive, do nothing */
 | 
			
		||||
		if (lchan->prim == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Handover RACH needs to be handled regardless of the
 | 
			
		||||
		 * current channel type and the associated handler. */
 | 
			
		||||
		if (PRIM_IS_RACH(lchan->prim) && lchan->prim->chan != TRXC_RACH)
 | 
			
		||||
			handler = trx_lchan_desc[TRXC_RACH].tx_fn;
 | 
			
		||||
 | 
			
		||||
		/* Poke lchan handler */
 | 
			
		||||
		handler(trx, ts, lchan, fn, bid);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_sched *sched;
 | 
			
		||||
 | 
			
		||||
	if (!trx)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n");
 | 
			
		||||
 | 
			
		||||
	/* Obtain a scheduler instance from TRX */
 | 
			
		||||
	sched = &trx->sched;
 | 
			
		||||
 | 
			
		||||
	/* Register frame clock callback */
 | 
			
		||||
	sched->clock_cb = sched_frame_clck_cb;
 | 
			
		||||
 | 
			
		||||
	/* Set pointers */
 | 
			
		||||
	sched = &trx->sched;
 | 
			
		||||
	sched->data = trx;
 | 
			
		||||
 | 
			
		||||
	/* Set frame counter advance */
 | 
			
		||||
	sched->fn_counter_advance = fn_advance;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_shutdown(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (!trx)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
 | 
			
		||||
 | 
			
		||||
	/* Free all potentially allocated timeslots */
 | 
			
		||||
	for (i = 0; i < TRX_TS_COUNT; i++)
 | 
			
		||||
		sched_trx_del_ts(trx, i);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_reset(struct trx_instance *trx, bool reset_clock)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (!trx)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
 | 
			
		||||
		reset_clock ? "and clock counter" : "");
 | 
			
		||||
 | 
			
		||||
	/* Free all potentially allocated timeslots */
 | 
			
		||||
	for (i = 0; i < TRX_TS_COUNT; i++)
 | 
			
		||||
		sched_trx_del_ts(trx, i);
 | 
			
		||||
 | 
			
		||||
	/* Stop and reset clock counter if required */
 | 
			
		||||
	if (reset_clock)
 | 
			
		||||
		sched_clck_reset(&trx->sched);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn)
 | 
			
		||||
{
 | 
			
		||||
	/* Make sure that ts isn't allocated yet */
 | 
			
		||||
	if (trx->ts_list[tn] != NULL) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new one */
 | 
			
		||||
	trx->ts_list[tn] = talloc_zero(trx, struct trx_ts);
 | 
			
		||||
 | 
			
		||||
	/* Add backpointer */
 | 
			
		||||
	trx->ts_list[tn]->trx = trx;
 | 
			
		||||
 | 
			
		||||
	/* Assign TS index */
 | 
			
		||||
	trx->ts_list[tn]->index = tn;
 | 
			
		||||
 | 
			
		||||
	return trx->ts_list[tn];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void sched_trx_del_ts(struct trx_instance *trx, int tn)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan, *lchan_next;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
 | 
			
		||||
	/* Find ts in list */
 | 
			
		||||
	ts = trx->ts_list[tn];
 | 
			
		||||
	if (ts == NULL)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
 | 
			
		||||
 | 
			
		||||
	/* Deactivate all logical channels */
 | 
			
		||||
	sched_trx_deactivate_all_lchans(ts);
 | 
			
		||||
 | 
			
		||||
	/* Free channel states */
 | 
			
		||||
	llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
 | 
			
		||||
		llist_del(&lchan->list);
 | 
			
		||||
		talloc_free(lchan);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Flush queue primitives for TX */
 | 
			
		||||
	sched_prim_flush_queue(&ts->tx_prims);
 | 
			
		||||
 | 
			
		||||
	/* Remove ts from list and free memory */
 | 
			
		||||
	trx->ts_list[tn] = NULL;
 | 
			
		||||
	talloc_free(ts);
 | 
			
		||||
 | 
			
		||||
	/* Notify transceiver about that */
 | 
			
		||||
	trx_if_cmd_setslot(trx, tn, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define LAYOUT_HAS_LCHAN(layout, lchan) \
 | 
			
		||||
	(layout->lchan_mask & ((uint64_t) 0x01 << lchan))
 | 
			
		||||
 | 
			
		||||
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
 | 
			
		||||
	enum gsm_phys_chan_config config)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
	enum trx_lchan_type type;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
 | 
			
		||||
	/* Try to find specified ts */
 | 
			
		||||
	ts = trx->ts_list[tn];
 | 
			
		||||
	if (ts != NULL) {
 | 
			
		||||
		/* Reconfiguration of existing one */
 | 
			
		||||
		sched_trx_reset_ts(trx, tn);
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Allocate a new one if doesn't exist */
 | 
			
		||||
		ts = sched_trx_add_ts(trx, tn);
 | 
			
		||||
		if (ts == NULL)
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Choose proper multiframe layout */
 | 
			
		||||
	ts->mf_layout = sched_mframe_layout(config, tn);
 | 
			
		||||
	if (!ts->mf_layout)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	if (ts->mf_layout->chan_config != config)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n",
 | 
			
		||||
		tn, ts->mf_layout->name);
 | 
			
		||||
 | 
			
		||||
	/* Init queue primitives for TX */
 | 
			
		||||
	INIT_LLIST_HEAD(&ts->tx_prims);
 | 
			
		||||
	/* Init logical channels list */
 | 
			
		||||
	INIT_LLIST_HEAD(&ts->lchans);
 | 
			
		||||
 | 
			
		||||
	/* Allocate channel states */
 | 
			
		||||
	for (type = 0; type < _TRX_CHAN_MAX; type++) {
 | 
			
		||||
		if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Allocate a channel state */
 | 
			
		||||
		lchan = talloc_zero(ts, struct trx_lchan_state);
 | 
			
		||||
		if (!lchan)
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
		/* set backpointer */
 | 
			
		||||
		lchan->ts = ts;
 | 
			
		||||
 | 
			
		||||
		/* Set channel type */
 | 
			
		||||
		lchan->type = type;
 | 
			
		||||
 | 
			
		||||
		/* Add to the list of channel states */
 | 
			
		||||
		llist_add_tail(&lchan->list, &ts->lchans);
 | 
			
		||||
 | 
			
		||||
		/* Enable channel automatically if required */
 | 
			
		||||
		if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO)
 | 
			
		||||
			sched_trx_activate_lchan(ts, type);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Notify transceiver about TS activation */
 | 
			
		||||
	/* FIXME: set proper channel type */
 | 
			
		||||
	trx_if_cmd_setslot(trx, tn, 1);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_reset_ts(struct trx_instance *trx, int tn)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan, *lchan_next;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
 | 
			
		||||
	/* Try to find specified ts */
 | 
			
		||||
	ts = trx->ts_list[tn];
 | 
			
		||||
	if (ts == NULL)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	/* Undefine multiframe layout */
 | 
			
		||||
	ts->mf_layout = NULL;
 | 
			
		||||
 | 
			
		||||
	/* Flush queue primitives for TX */
 | 
			
		||||
	sched_prim_flush_queue(&ts->tx_prims);
 | 
			
		||||
 | 
			
		||||
	/* Deactivate all logical channels */
 | 
			
		||||
	sched_trx_deactivate_all_lchans(ts);
 | 
			
		||||
 | 
			
		||||
	/* Free channel states */
 | 
			
		||||
	llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
 | 
			
		||||
		llist_del(&lchan->list);
 | 
			
		||||
		talloc_free(lchan);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Notify transceiver about that */
 | 
			
		||||
	trx_if_cmd_setslot(trx, tn, 0);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
 | 
			
		||||
	uint8_t *key, uint8_t key_len)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
 | 
			
		||||
	/* Prevent NULL-pointer deference */
 | 
			
		||||
	if (!ts)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	/* Make sure we can store this key */
 | 
			
		||||
	if (key_len > MAX_A5_KEY_LEN)
 | 
			
		||||
		return -ERANGE;
 | 
			
		||||
 | 
			
		||||
	/* Iterate over all allocated logical channels */
 | 
			
		||||
	llist_for_each_entry(lchan, &ts->lchans, list) {
 | 
			
		||||
		/* Omit inactive channels */
 | 
			
		||||
		if (!lchan->active)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Set key length and algorithm */
 | 
			
		||||
		lchan->a5.key_len = key_len;
 | 
			
		||||
		lchan->a5.algo = algo;
 | 
			
		||||
 | 
			
		||||
		/* Copy requested key */
 | 
			
		||||
		if (key_len)
 | 
			
		||||
			memcpy(lchan->a5.key, key, key_len);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
 | 
			
		||||
	enum trx_lchan_type chan)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(lchan, &ts->lchans, list)
 | 
			
		||||
		if (lchan->type == chan)
 | 
			
		||||
			return lchan;
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc;
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	/* Prevent NULL-pointer deference */
 | 
			
		||||
	if (ts == NULL) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Iterate over all allocated lchans */
 | 
			
		||||
	llist_for_each_entry(lchan, &ts->lchans, list) {
 | 
			
		||||
		lchan_desc = &trx_lchan_desc[lchan->type];
 | 
			
		||||
 | 
			
		||||
		if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
 | 
			
		||||
			if (active) {
 | 
			
		||||
				rc |= sched_trx_activate_lchan(ts, lchan->type);
 | 
			
		||||
				lchan->tch_mode = tch_mode;
 | 
			
		||||
			} else
 | 
			
		||||
				rc |= sched_trx_deactivate_lchan(ts, lchan->type);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan];
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
 | 
			
		||||
	/* Try to find requested logical channel */
 | 
			
		||||
	lchan = sched_trx_find_lchan(ts, chan);
 | 
			
		||||
	if (lchan == NULL)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (lchan->active) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated "
 | 
			
		||||
			"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
 | 
			
		||||
		"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
 | 
			
		||||
 | 
			
		||||
	/* Conditionally allocate memory for bursts */
 | 
			
		||||
	if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
 | 
			
		||||
		lchan->rx_bursts = talloc_zero_size(lchan,
 | 
			
		||||
			lchan_desc->burst_buf_size);
 | 
			
		||||
		if (lchan->rx_bursts == NULL)
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
 | 
			
		||||
		lchan->tx_bursts = talloc_zero_size(lchan,
 | 
			
		||||
			lchan_desc->burst_buf_size);
 | 
			
		||||
		if (lchan->tx_bursts == NULL)
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Finally, update channel status */
 | 
			
		||||
	lchan->active = 1;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
 | 
			
		||||
{
 | 
			
		||||
	/* Prevent NULL-pointer deference */
 | 
			
		||||
	OSMO_ASSERT(lchan != NULL);
 | 
			
		||||
 | 
			
		||||
	/* Print some TDMA statistics for Downlink */
 | 
			
		||||
	if (trx_lchan_desc[lchan->type].rx_fn && lchan->active) {
 | 
			
		||||
		LOGP(DSCH, LOGL_DEBUG, "TDMA statistics for lchan=%s on ts=%u: "
 | 
			
		||||
				       "%lu DL frames have been processed, "
 | 
			
		||||
				       "%lu lost (compensated), last fn=%u\n",
 | 
			
		||||
		     trx_lchan_desc[lchan->type].name, lchan->ts->index,
 | 
			
		||||
		     lchan->tdma.num_proc, lchan->tdma.num_lost,
 | 
			
		||||
		     lchan->tdma.last_proc);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Reset internal state variables */
 | 
			
		||||
	lchan->rx_burst_mask = 0x00;
 | 
			
		||||
	lchan->tx_burst_mask = 0x00;
 | 
			
		||||
 | 
			
		||||
	/* Free burst memory */
 | 
			
		||||
	talloc_free(lchan->rx_bursts);
 | 
			
		||||
	talloc_free(lchan->tx_bursts);
 | 
			
		||||
 | 
			
		||||
	lchan->rx_bursts = NULL;
 | 
			
		||||
	lchan->tx_bursts = NULL;
 | 
			
		||||
 | 
			
		||||
	/* Forget the current prim */
 | 
			
		||||
	sched_prim_drop(lchan);
 | 
			
		||||
 | 
			
		||||
	/* Channel specific stuff */
 | 
			
		||||
	if (CHAN_IS_TCH(lchan->type)) {
 | 
			
		||||
		lchan->dl_ongoing_facch = 0;
 | 
			
		||||
		lchan->ul_facch_blocks = 0;
 | 
			
		||||
 | 
			
		||||
		lchan->tch_mode = GSM48_CMODE_SIGN;
 | 
			
		||||
 | 
			
		||||
		/* Reset AMR state */
 | 
			
		||||
		memset(&lchan->amr, 0x00, sizeof(lchan->amr));
 | 
			
		||||
	} else if (CHAN_IS_SACCH(lchan->type)) {
 | 
			
		||||
		/* Reset SACCH state */
 | 
			
		||||
		memset(&lchan->sacch, 0x00, sizeof(lchan->sacch));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Reset ciphering state */
 | 
			
		||||
	memset(&lchan->a5, 0x00, sizeof(lchan->a5));
 | 
			
		||||
 | 
			
		||||
	/* Reset TDMA frame statistics */
 | 
			
		||||
	memset(&lchan->tdma, 0x00, sizeof(lchan->tdma));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
 | 
			
		||||
	/* Try to find requested logical channel */
 | 
			
		||||
	lchan = sched_trx_find_lchan(ts, chan);
 | 
			
		||||
	if (lchan == NULL)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (!lchan->active) {
 | 
			
		||||
		LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated "
 | 
			
		||||
			"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
 | 
			
		||||
		"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
 | 
			
		||||
 | 
			
		||||
	/* Reset internal state, free memory */
 | 
			
		||||
	sched_trx_reset_lchan(lchan);
 | 
			
		||||
 | 
			
		||||
	/* Update activation flag */
 | 
			
		||||
	lchan->active = 0;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
 | 
			
		||||
	LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
 | 
			
		||||
		"on ts=%d\n", ts->index);
 | 
			
		||||
 | 
			
		||||
	llist_for_each_entry(lchan, &ts->lchans, list) {
 | 
			
		||||
		/* Omit inactive channels */
 | 
			
		||||
		if (!lchan->active)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		/* Reset internal state, free memory */
 | 
			
		||||
		sched_trx_reset_lchan(lchan);
 | 
			
		||||
 | 
			
		||||
		/* Update activation flag */
 | 
			
		||||
		lchan->active = 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t cbits = chan_nr >> 3;
 | 
			
		||||
 | 
			
		||||
	if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
 | 
			
		||||
		return GSM_PCHAN_TCH_F;
 | 
			
		||||
	else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
 | 
			
		||||
		return GSM_PCHAN_TCH_H;
 | 
			
		||||
	else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
 | 
			
		||||
		return GSM_PCHAN_CCCH_SDCCH4;
 | 
			
		||||
	else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
 | 
			
		||||
		return GSM_PCHAN_SDCCH8_SACCH8C;
 | 
			
		||||
	else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
 | 
			
		||||
		return GSM_PCHAN_CCCH_SDCCH4_CBCH;
 | 
			
		||||
	else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
 | 
			
		||||
		return GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
 | 
			
		||||
	else if ((cbits & 0x1f) == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
 | 
			
		||||
		return GSM_PCHAN_PDCH;
 | 
			
		||||
 | 
			
		||||
	return GSM_PCHAN_NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
 | 
			
		||||
	uint8_t link_id)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	/* Iterate over all known lchan types */
 | 
			
		||||
	for (i = 0; i < _TRX_CHAN_MAX; i++)
 | 
			
		||||
		if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
 | 
			
		||||
			if (trx_lchan_desc[i].link_id == link_id)
 | 
			
		||||
				return i;
 | 
			
		||||
 | 
			
		||||
	return TRXC_IDLE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
 | 
			
		||||
	uint32_t fn, sbit_t *burst)
 | 
			
		||||
{
 | 
			
		||||
	ubit_t ks[114];
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	/* Generate keystream for a DL burst */
 | 
			
		||||
	osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL);
 | 
			
		||||
 | 
			
		||||
	/* Apply keystream over ciphertext */
 | 
			
		||||
	for (i = 0; i < 57; i++) {
 | 
			
		||||
		if (ks[i])
 | 
			
		||||
			burst[i + 3] *= -1;
 | 
			
		||||
		if (ks[i + 57])
 | 
			
		||||
			burst[i + 88] *= -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan,
 | 
			
		||||
	uint32_t fn, ubit_t *burst)
 | 
			
		||||
{
 | 
			
		||||
	ubit_t ks[114];
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	/* Generate keystream for an UL burst */
 | 
			
		||||
	osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks);
 | 
			
		||||
 | 
			
		||||
	/* Apply keystream over plaintext */
 | 
			
		||||
	for (i = 0; i < 57; i++) {
 | 
			
		||||
		burst[i + 3] ^= ks[i];
 | 
			
		||||
		burst[i + 88] ^= ks[i + 57];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int subst_frame_loss(struct trx_lchan_state *lchan,
 | 
			
		||||
			    trx_lchan_rx_func *handler,
 | 
			
		||||
			    uint32_t fn)
 | 
			
		||||
{
 | 
			
		||||
	const struct trx_multiframe *mf;
 | 
			
		||||
	const struct trx_frame *fp;
 | 
			
		||||
	int elapsed, i;
 | 
			
		||||
 | 
			
		||||
	/* Wait until at least one TDMA frame is processed */
 | 
			
		||||
	if (lchan->tdma.num_proc == 0)
 | 
			
		||||
		return -EAGAIN;
 | 
			
		||||
 | 
			
		||||
	/* Short alias for the current multiframe */
 | 
			
		||||
	mf = lchan->ts->mf_layout;
 | 
			
		||||
 | 
			
		||||
	/* Calculate how many frames elapsed since the last received one.
 | 
			
		||||
	 * The algorithm is based on GSM::FNDelta() from osmo-trx. */
 | 
			
		||||
	elapsed = fn - lchan->tdma.last_proc;
 | 
			
		||||
	if (elapsed >= GSM_TDMA_HYPERFRAME / 2)
 | 
			
		||||
		elapsed -= GSM_TDMA_HYPERFRAME;
 | 
			
		||||
	else if (elapsed < -GSM_TDMA_HYPERFRAME / 2)
 | 
			
		||||
		elapsed += GSM_TDMA_HYPERFRAME;
 | 
			
		||||
 | 
			
		||||
	/* Check TDMA frame order (wrong order is possible with fake_trx.py, see OS#4658) */
 | 
			
		||||
	if (elapsed < 0) {
 | 
			
		||||
		/* This burst has already been substituted by a dummy burst (all bits set to zero),
 | 
			
		||||
		 * so better drop it. Otherwise we risk to get undefined behavior in handler(). */
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "(%s) Rx burst with fn=%u older than the last "
 | 
			
		||||
					"processed fn=%u (see OS#4658) => dropping\n",
 | 
			
		||||
					trx_lchan_desc[lchan->type].name,
 | 
			
		||||
					fn, lchan->tdma.last_proc);
 | 
			
		||||
		return -EALREADY;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check how many frames we (potentially) need to compensate */
 | 
			
		||||
	if (elapsed > mf->period) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_NOTICE, "Too many (>%u) contiguous TDMA frames elapsed (%d) "
 | 
			
		||||
					 "since the last processed fn=%u (current %u)\n",
 | 
			
		||||
					 mf->period, elapsed, lchan->tdma.last_proc, fn);
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	} else if (elapsed == 0) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "No TDMA frames elapsed since the last processed "
 | 
			
		||||
					"fn=%u, must be a bug?\n", lchan->tdma.last_proc);
 | 
			
		||||
		return -EIO;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static const sbit_t bits[148] = { 0 };
 | 
			
		||||
	struct trx_meas_set fake_meas = {
 | 
			
		||||
		.fn = lchan->tdma.last_proc,
 | 
			
		||||
		.rssi = -120,
 | 
			
		||||
		.toa256 = 0,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Traverse from fp till the current frame */
 | 
			
		||||
	for (i = 0; i < elapsed - 1; i++) {
 | 
			
		||||
		fp = &mf->frames[GSM_TDMA_FN_INC(fake_meas.fn) % mf->period];
 | 
			
		||||
		if (fp->dl_chan != lchan->type)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		LOGP(DSCHD, LOGL_NOTICE, "Substituting lost TDMA frame %u on %s\n",
 | 
			
		||||
		     fake_meas.fn, trx_lchan_desc[lchan->type].name);
 | 
			
		||||
 | 
			
		||||
		handler(lchan->ts->trx, lchan->ts, lchan,
 | 
			
		||||
			fake_meas.fn, fp->dl_bid,
 | 
			
		||||
			bits, &fake_meas);
 | 
			
		||||
 | 
			
		||||
		/* Update TDMA frame statistics */
 | 
			
		||||
		lchan->tdma.last_proc = fake_meas.fn;
 | 
			
		||||
		lchan->tdma.num_proc++;
 | 
			
		||||
		lchan->tdma.num_lost++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
 | 
			
		||||
	uint32_t fn, sbit_t *bits, uint16_t nbits,
 | 
			
		||||
	const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_state *lchan;
 | 
			
		||||
	const struct trx_frame *frame;
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
 | 
			
		||||
	trx_lchan_rx_func *handler;
 | 
			
		||||
	enum trx_lchan_type chan;
 | 
			
		||||
	uint8_t offset, bid;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Check whether required timeslot is allocated and configured */
 | 
			
		||||
	ts = trx->ts_list[tn];
 | 
			
		||||
	if (ts == NULL || ts->mf_layout == NULL) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
 | 
			
		||||
			"ignoring burst...\n", tn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Get frame from multiframe */
 | 
			
		||||
	offset = fn % ts->mf_layout->period;
 | 
			
		||||
	frame = ts->mf_layout->frames + offset;
 | 
			
		||||
 | 
			
		||||
	/* Get required info from frame */
 | 
			
		||||
	bid = frame->dl_bid;
 | 
			
		||||
	chan = frame->dl_chan;
 | 
			
		||||
	handler = trx_lchan_desc[chan].rx_fn;
 | 
			
		||||
 | 
			
		||||
	/* Omit bursts which have no handler, like IDLE bursts.
 | 
			
		||||
	 * TODO: handle noise indications during IDLE frames. */
 | 
			
		||||
	if (!handler)
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Find required channel state */
 | 
			
		||||
	lchan = sched_trx_find_lchan(ts, chan);
 | 
			
		||||
	if (lchan == NULL)
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Ensure that channel is active */
 | 
			
		||||
	if (!lchan->active)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	/* Compensate lost TDMA frames (if any) */
 | 
			
		||||
	rc = subst_frame_loss(lchan, handler, fn);
 | 
			
		||||
	if (rc == -EALREADY)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* Perform A5/X decryption if required */
 | 
			
		||||
	if (lchan->a5.algo)
 | 
			
		||||
		sched_trx_a5_burst_dec(lchan, fn, bits);
 | 
			
		||||
 | 
			
		||||
	/* Put burst to handler */
 | 
			
		||||
	handler(trx, ts, lchan, fn, bid, bits, meas);
 | 
			
		||||
 | 
			
		||||
	/* Update TDMA frame statistics */
 | 
			
		||||
	lchan->tdma.last_proc = fn;
 | 
			
		||||
 | 
			
		||||
	if (++lchan->tdma.num_proc == 0) {
 | 
			
		||||
		/* Theoretically, we may have an integer overflow of num_proc counter.
 | 
			
		||||
		 * As a consequence, subst_frame_loss() will be unable to compensate
 | 
			
		||||
		 * one (potentionally lost) Downlink burst. On practice, it would
 | 
			
		||||
		 * happen once in 4615 * 10e-6 * (2 ^ 32 - 1) seconds or ~6 years. */
 | 
			
		||||
		LOGP(DSCHD, LOGL_NOTICE, "Too many TDMA frames have been processed. "
 | 
			
		||||
					 "Are you running trxcon for more than 6 years?!?\n");
 | 
			
		||||
		lchan->tdma.num_proc = 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int sched_trx_handle_tx_burst(struct trx_instance *trx,
 | 
			
		||||
	struct trx_ts *ts, struct trx_lchan_state *lchan,
 | 
			
		||||
	uint32_t fn, ubit_t *bits)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	/* Perform A5/X burst encryption if required */
 | 
			
		||||
	if (lchan->a5.algo)
 | 
			
		||||
		sched_trx_a5_burst_enc(lchan, fn, bits);
 | 
			
		||||
 | 
			
		||||
	/* Forward burst to transceiver */
 | 
			
		||||
	rc = trx_if_tx_burst(trx, ts->index, fn, trx->tx_power, bits);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		LOGP(DSCHD, LOGL_ERROR, "Could not send burst to transceiver\n");
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define MEAS_HIST_FIRST(hist) \
 | 
			
		||||
	(&hist->buf[0])
 | 
			
		||||
#define MEAS_HIST_LAST(hist) \
 | 
			
		||||
	(MEAS_HIST_FIRST(hist) + ARRAY_SIZE(hist->buf) - 1)
 | 
			
		||||
 | 
			
		||||
/* Add a new set of measurements to the history */
 | 
			
		||||
void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
 | 
			
		||||
 | 
			
		||||
	/* Find a new position where to store the measurements */
 | 
			
		||||
	if (hist->head == MEAS_HIST_LAST(hist) || hist->head == NULL)
 | 
			
		||||
		hist->head = MEAS_HIST_FIRST(hist);
 | 
			
		||||
	else
 | 
			
		||||
		hist->head++;
 | 
			
		||||
 | 
			
		||||
	*hist->head = *meas;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Calculate the AVG of n measurements from the history */
 | 
			
		||||
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_lchan_meas_hist *hist = &lchan->meas_hist;
 | 
			
		||||
	struct trx_meas_set *meas = hist->head;
 | 
			
		||||
	int toa256_sum = 0;
 | 
			
		||||
	int rssi_sum = 0;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	OSMO_ASSERT(n > 0 && n <= ARRAY_SIZE(hist->buf));
 | 
			
		||||
	OSMO_ASSERT(meas != NULL);
 | 
			
		||||
 | 
			
		||||
	/* Traverse backwards up to n entries, calculate the sum */
 | 
			
		||||
	for (i = 0; i < n; i++) {
 | 
			
		||||
		toa256_sum += meas->toa256;
 | 
			
		||||
		rssi_sum += meas->rssi;
 | 
			
		||||
 | 
			
		||||
		/* Do not go below the first burst */
 | 
			
		||||
		if (i + 1 == n)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		if (meas == MEAS_HIST_FIRST(hist))
 | 
			
		||||
			meas = MEAS_HIST_LAST(hist);
 | 
			
		||||
		else
 | 
			
		||||
			meas--;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Calculate the AVG */
 | 
			
		||||
	lchan->meas_avg.toa256 = toa256_sum / n;
 | 
			
		||||
	lchan->meas_avg.rssi = rssi_sum / n;
 | 
			
		||||
 | 
			
		||||
	/* As a bonus, store TDMA frame number of the first burst */
 | 
			
		||||
	lchan->meas_avg.fn = meas->fn;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,405 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/utils.h>
 | 
			
		||||
#include <osmocom/gsm/protocol/gsm_04_08.h>
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
 | 
			
		||||
#define GSM_BURST_LEN		148
 | 
			
		||||
#define GSM_BURST_PL_LEN	116
 | 
			
		||||
 | 
			
		||||
#define GPRS_BURST_LEN		GSM_BURST_LEN
 | 
			
		||||
#define EDGE_BURST_LEN		444
 | 
			
		||||
 | 
			
		||||
#define GPRS_L2_MAX_LEN		54
 | 
			
		||||
#define EDGE_L2_MAX_LEN		155
 | 
			
		||||
 | 
			
		||||
#define TRX_CH_LID_DEDIC	0x00
 | 
			
		||||
#define TRX_CH_LID_SACCH	0x40
 | 
			
		||||
 | 
			
		||||
/* Osmocom-specific extension for PTCCH (see 3GPP TS 45.002, section 3.3.4.2).
 | 
			
		||||
 * Shall be used to distinguish PTCCH and PDTCH channels on a PDCH time-slot. */
 | 
			
		||||
#define TRX_CH_LID_PTCCH	0x80
 | 
			
		||||
 | 
			
		||||
/* Is a channel related to PDCH (GPRS) */
 | 
			
		||||
#define TRX_CH_FLAG_PDCH	(1 << 0)
 | 
			
		||||
/* Should a channel be activated automatically */
 | 
			
		||||
#define TRX_CH_FLAG_AUTO	(1 << 1)
 | 
			
		||||
/* Is continuous burst transmission assumed */
 | 
			
		||||
#define TRX_CH_FLAG_CBTX	(1 << 2)
 | 
			
		||||
 | 
			
		||||
#define MAX_A5_KEY_LEN		(128 / 8)
 | 
			
		||||
#define TRX_TS_COUNT		8
 | 
			
		||||
 | 
			
		||||
/* Forward declaration to avoid mutual include */
 | 
			
		||||
struct trx_lchan_state;
 | 
			
		||||
struct trx_meas_set;
 | 
			
		||||
struct trx_instance;
 | 
			
		||||
struct trx_ts;
 | 
			
		||||
 | 
			
		||||
enum trx_burst_type {
 | 
			
		||||
	TRX_BURST_GMSK,
 | 
			
		||||
	TRX_BURST_8PSK,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * These types define the different channels on a multiframe.
 | 
			
		||||
 * Each channel has queues and can be activated individually.
 | 
			
		||||
 */
 | 
			
		||||
enum trx_lchan_type {
 | 
			
		||||
	TRXC_IDLE = 0,
 | 
			
		||||
	TRXC_FCCH,
 | 
			
		||||
	TRXC_SCH,
 | 
			
		||||
	TRXC_BCCH,
 | 
			
		||||
	TRXC_RACH,
 | 
			
		||||
	TRXC_CCCH,
 | 
			
		||||
	TRXC_TCHF,
 | 
			
		||||
	TRXC_TCHH_0,
 | 
			
		||||
	TRXC_TCHH_1,
 | 
			
		||||
	TRXC_SDCCH4_0,
 | 
			
		||||
	TRXC_SDCCH4_1,
 | 
			
		||||
	TRXC_SDCCH4_2,
 | 
			
		||||
	TRXC_SDCCH4_3,
 | 
			
		||||
	TRXC_SDCCH8_0,
 | 
			
		||||
	TRXC_SDCCH8_1,
 | 
			
		||||
	TRXC_SDCCH8_2,
 | 
			
		||||
	TRXC_SDCCH8_3,
 | 
			
		||||
	TRXC_SDCCH8_4,
 | 
			
		||||
	TRXC_SDCCH8_5,
 | 
			
		||||
	TRXC_SDCCH8_6,
 | 
			
		||||
	TRXC_SDCCH8_7,
 | 
			
		||||
	TRXC_SACCHTF,
 | 
			
		||||
	TRXC_SACCHTH_0,
 | 
			
		||||
	TRXC_SACCHTH_1,
 | 
			
		||||
	TRXC_SACCH4_0,
 | 
			
		||||
	TRXC_SACCH4_1,
 | 
			
		||||
	TRXC_SACCH4_2,
 | 
			
		||||
	TRXC_SACCH4_3,
 | 
			
		||||
	TRXC_SACCH8_0,
 | 
			
		||||
	TRXC_SACCH8_1,
 | 
			
		||||
	TRXC_SACCH8_2,
 | 
			
		||||
	TRXC_SACCH8_3,
 | 
			
		||||
	TRXC_SACCH8_4,
 | 
			
		||||
	TRXC_SACCH8_5,
 | 
			
		||||
	TRXC_SACCH8_6,
 | 
			
		||||
	TRXC_SACCH8_7,
 | 
			
		||||
	TRXC_PDTCH,
 | 
			
		||||
	TRXC_PTCCH,
 | 
			
		||||
	TRXC_SDCCH4_CBCH,
 | 
			
		||||
	TRXC_SDCCH8_CBCH,
 | 
			
		||||
	_TRX_CHAN_MAX
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef int trx_lchan_rx_func(struct trx_instance *trx,
 | 
			
		||||
	struct trx_ts *ts, struct trx_lchan_state *lchan,
 | 
			
		||||
	uint32_t fn, uint8_t bid, const sbit_t *bits,
 | 
			
		||||
	const struct trx_meas_set *meas);
 | 
			
		||||
 | 
			
		||||
typedef int trx_lchan_tx_func(struct trx_instance *trx,
 | 
			
		||||
	struct trx_ts *ts, struct trx_lchan_state *lchan,
 | 
			
		||||
	uint32_t fn, uint8_t bid);
 | 
			
		||||
 | 
			
		||||
struct trx_lchan_desc {
 | 
			
		||||
	/*! \brief Human-readable name */
 | 
			
		||||
	const char *name;
 | 
			
		||||
	/*! \brief Human-readable description */
 | 
			
		||||
	const char *desc;
 | 
			
		||||
 | 
			
		||||
	/*! \brief Channel Number (like in RSL) */
 | 
			
		||||
	uint8_t chan_nr;
 | 
			
		||||
	/*! \brief Link ID (like in RSL) */
 | 
			
		||||
	uint8_t link_id;
 | 
			
		||||
	/*! \brief Sub-slot number (for SDCCH and TCH/H) */
 | 
			
		||||
	uint8_t ss_nr;
 | 
			
		||||
	/*! \brief GSMTAP channel type (see GSMTAP_CHANNEL_*) */
 | 
			
		||||
	uint8_t gsmtap_chan_type;
 | 
			
		||||
 | 
			
		||||
	/*! \brief How much memory do we need to store bursts */
 | 
			
		||||
	size_t burst_buf_size;
 | 
			
		||||
	/*! \brief Channel specific flags */
 | 
			
		||||
	uint8_t flags;
 | 
			
		||||
 | 
			
		||||
	/*! \brief Function to call when burst received from PHY */
 | 
			
		||||
	trx_lchan_rx_func *rx_fn;
 | 
			
		||||
	/*! \brief Function to call when data received from L2 */
 | 
			
		||||
	trx_lchan_tx_func *tx_fn;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_frame {
 | 
			
		||||
	/*! \brief Downlink TRX channel type */
 | 
			
		||||
	enum trx_lchan_type dl_chan;
 | 
			
		||||
	/*! \brief Downlink block ID */
 | 
			
		||||
	uint8_t dl_bid;
 | 
			
		||||
	/*! \brief Uplink TRX channel type */
 | 
			
		||||
	enum trx_lchan_type ul_chan;
 | 
			
		||||
	/*! \brief Uplink block ID */
 | 
			
		||||
	uint8_t ul_bid;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_multiframe {
 | 
			
		||||
	/*! \brief Channel combination */
 | 
			
		||||
	enum gsm_phys_chan_config chan_config;
 | 
			
		||||
	/*! \brief Human-readable name */
 | 
			
		||||
	const char *name;
 | 
			
		||||
	/*! \brief Repeats how many frames */
 | 
			
		||||
	uint8_t period;
 | 
			
		||||
	/*! \brief Applies to which timeslots */
 | 
			
		||||
	uint8_t slotmask;
 | 
			
		||||
	/*! \brief Contains which lchans */
 | 
			
		||||
	uint64_t lchan_mask;
 | 
			
		||||
	/*! \brief Pointer to scheduling structure */
 | 
			
		||||
	const struct trx_frame *frames;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_meas_set {
 | 
			
		||||
	/*! \brief TDMA frame number of the first burst this set belongs to */
 | 
			
		||||
	uint32_t fn;
 | 
			
		||||
	/*! \brief ToA256 (Timing of Arrival, 1/256 of a symbol) */
 | 
			
		||||
	int16_t toa256;
 | 
			
		||||
	/*! \brief RSSI (Received Signal Strength Indication) */
 | 
			
		||||
	int8_t rssi;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Simple ring buffer (up to 8 unique measurements) */
 | 
			
		||||
struct trx_lchan_meas_hist {
 | 
			
		||||
	struct trx_meas_set buf[8];
 | 
			
		||||
	struct trx_meas_set *head;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* States each channel on a multiframe */
 | 
			
		||||
struct trx_lchan_state {
 | 
			
		||||
	/*! \brief Channel type */
 | 
			
		||||
	enum trx_lchan_type type;
 | 
			
		||||
	/*! \brief Channel status */
 | 
			
		||||
	uint8_t active;
 | 
			
		||||
	/*! \brief Link to a list of channels */
 | 
			
		||||
	struct llist_head list;
 | 
			
		||||
 | 
			
		||||
	/*! \brief Burst type: GMSK or 8PSK */
 | 
			
		||||
	enum trx_burst_type burst_type;
 | 
			
		||||
	/*! \brief Mask of received bursts */
 | 
			
		||||
	uint8_t rx_burst_mask;
 | 
			
		||||
	/*! \brief Mask of transmitted bursts */
 | 
			
		||||
	uint8_t tx_burst_mask;
 | 
			
		||||
	/*! \brief Burst buffer for RX */
 | 
			
		||||
	sbit_t *rx_bursts;
 | 
			
		||||
	/*! \brief Burst buffer for TX */
 | 
			
		||||
	ubit_t *tx_bursts;
 | 
			
		||||
 | 
			
		||||
	/*! \brief A primitive being sent */
 | 
			
		||||
	struct trx_ts_prim *prim;
 | 
			
		||||
 | 
			
		||||
	/*! \brief Mode for TCH channels (see GSM48_CMODE_*) */
 | 
			
		||||
	uint8_t	tch_mode;
 | 
			
		||||
 | 
			
		||||
	/*! \brief FACCH/H on downlink */
 | 
			
		||||
	bool dl_ongoing_facch;
 | 
			
		||||
	/*! \brief pending FACCH/H blocks on Uplink */
 | 
			
		||||
	uint8_t ul_facch_blocks;
 | 
			
		||||
 | 
			
		||||
	/*! \brief Downlink measurements history */
 | 
			
		||||
	struct trx_lchan_meas_hist meas_hist;
 | 
			
		||||
	/*! \brief AVG measurements of the last received block */
 | 
			
		||||
	struct trx_meas_set meas_avg;
 | 
			
		||||
 | 
			
		||||
	/*! \brief TDMA loss detection state */
 | 
			
		||||
	struct {
 | 
			
		||||
		/*! \brief Last processed TDMA frame number */
 | 
			
		||||
		uint32_t last_proc;
 | 
			
		||||
		/*! \brief Number of processed TDMA frames */
 | 
			
		||||
		unsigned long num_proc;
 | 
			
		||||
		/*! \brief Number of lost TDMA frames */
 | 
			
		||||
		unsigned long num_lost;
 | 
			
		||||
	} tdma;
 | 
			
		||||
 | 
			
		||||
	/*! \brief SACCH state */
 | 
			
		||||
	struct {
 | 
			
		||||
		/*! \brief Cached measurement report (last received) */
 | 
			
		||||
		uint8_t mr_cache[GSM_MACBLOCK_LEN];
 | 
			
		||||
		/*! \brief Cache usage counter */
 | 
			
		||||
		uint8_t mr_cache_usage;
 | 
			
		||||
		/*! \brief Was a MR transmitted last time? */
 | 
			
		||||
		bool mr_tx_last;
 | 
			
		||||
	} sacch;
 | 
			
		||||
 | 
			
		||||
	/* AMR specific */
 | 
			
		||||
	struct {
 | 
			
		||||
		/*! \brief 4 possible codecs for AMR */
 | 
			
		||||
		uint8_t codec[4];
 | 
			
		||||
		/*! \brief Number of possible codecs */
 | 
			
		||||
		uint8_t codecs;
 | 
			
		||||
		/*! \brief Current uplink FT index */
 | 
			
		||||
		uint8_t ul_ft;
 | 
			
		||||
		/*! \brief Current downlink FT index */
 | 
			
		||||
		uint8_t dl_ft;
 | 
			
		||||
		/*! \brief Current uplink CMR index */
 | 
			
		||||
		uint8_t ul_cmr;
 | 
			
		||||
		/*! \brief Current downlink CMR index */
 | 
			
		||||
		uint8_t dl_cmr;
 | 
			
		||||
		/*! \brief If AMR loop is enabled */
 | 
			
		||||
		uint8_t amr_loop;
 | 
			
		||||
		/*! \brief Number of bit error rates */
 | 
			
		||||
		uint8_t ber_num;
 | 
			
		||||
		/*! \brief Sum of bit error rates */
 | 
			
		||||
		float ber_sum;
 | 
			
		||||
	} amr;
 | 
			
		||||
 | 
			
		||||
	/*! \brief A5/X encryption state */
 | 
			
		||||
	struct {
 | 
			
		||||
		uint8_t key[MAX_A5_KEY_LEN];
 | 
			
		||||
		uint8_t key_len;
 | 
			
		||||
		uint8_t algo;
 | 
			
		||||
	} a5;
 | 
			
		||||
 | 
			
		||||
	/* TS that this lchan belongs to */
 | 
			
		||||
	struct trx_ts *ts;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_ts {
 | 
			
		||||
	/*! \brief Timeslot index within a frame (0..7) */
 | 
			
		||||
	uint8_t index;
 | 
			
		||||
 | 
			
		||||
	/*! \brief Pointer to multiframe layout */
 | 
			
		||||
	const struct trx_multiframe *mf_layout;
 | 
			
		||||
	/*! \brief Channel states for logical channels */
 | 
			
		||||
	struct llist_head lchans;
 | 
			
		||||
	/*! \brief Queue primitives for TX */
 | 
			
		||||
	struct llist_head tx_prims;
 | 
			
		||||
	/* backpointer to its TRX */
 | 
			
		||||
	struct trx_instance *trx;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Represents one TX primitive in the queue of trx_ts */
 | 
			
		||||
struct trx_ts_prim {
 | 
			
		||||
	/*! \brief Link to queue of TS */
 | 
			
		||||
	struct llist_head list;
 | 
			
		||||
	/*! \brief Logical channel type */
 | 
			
		||||
	enum trx_lchan_type chan;
 | 
			
		||||
	/*! \brief Payload length */
 | 
			
		||||
	size_t payload_len;
 | 
			
		||||
	/*! \brief Payload */
 | 
			
		||||
	uint8_t payload[0];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX];
 | 
			
		||||
const struct trx_multiframe *sched_mframe_layout(
 | 
			
		||||
	enum gsm_phys_chan_config config, int tn);
 | 
			
		||||
 | 
			
		||||
/* Scheduler management functions */
 | 
			
		||||
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance);
 | 
			
		||||
int sched_trx_reset(struct trx_instance *trx, bool reset_clock);
 | 
			
		||||
int sched_trx_shutdown(struct trx_instance *trx);
 | 
			
		||||
 | 
			
		||||
/* Timeslot management functions */
 | 
			
		||||
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn);
 | 
			
		||||
void sched_trx_del_ts(struct trx_instance *trx, int tn);
 | 
			
		||||
int sched_trx_reset_ts(struct trx_instance *trx, int tn);
 | 
			
		||||
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
 | 
			
		||||
	enum gsm_phys_chan_config config);
 | 
			
		||||
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
 | 
			
		||||
	uint8_t *key, uint8_t key_len);
 | 
			
		||||
 | 
			
		||||
/* Logical channel management functions */
 | 
			
		||||
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr);
 | 
			
		||||
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
 | 
			
		||||
	uint8_t link_id);
 | 
			
		||||
 | 
			
		||||
void sched_trx_deactivate_all_lchans(struct trx_ts *ts);
 | 
			
		||||
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode);
 | 
			
		||||
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
 | 
			
		||||
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
 | 
			
		||||
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
 | 
			
		||||
	enum trx_lchan_type chan);
 | 
			
		||||
 | 
			
		||||
/* Primitive management functions */
 | 
			
		||||
int sched_prim_init(void *ctx, struct trx_ts_prim **prim,
 | 
			
		||||
	size_t pl_len, uint8_t chan_nr, uint8_t link_id);
 | 
			
		||||
int sched_prim_push(struct trx_instance *trx,
 | 
			
		||||
	struct trx_ts_prim *prim, uint8_t chan_nr);
 | 
			
		||||
 | 
			
		||||
#define TCH_MODE_IS_SPEECH(mode)       \
 | 
			
		||||
	  (mode == GSM48_CMODE_SPEECH_V1   \
 | 
			
		||||
	|| mode == GSM48_CMODE_SPEECH_EFR  \
 | 
			
		||||
	|| mode == GSM48_CMODE_SPEECH_AMR)
 | 
			
		||||
 | 
			
		||||
#define TCH_MODE_IS_DATA(mode)        \
 | 
			
		||||
	  (mode == GSM48_CMODE_DATA_14k5  \
 | 
			
		||||
	|| mode == GSM48_CMODE_DATA_12k0  \
 | 
			
		||||
	|| mode == GSM48_CMODE_DATA_6k0   \
 | 
			
		||||
	|| mode == GSM48_CMODE_DATA_3k6)
 | 
			
		||||
 | 
			
		||||
#define CHAN_IS_TCH(chan) \
 | 
			
		||||
	(chan == TRXC_TCHF || chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1)
 | 
			
		||||
 | 
			
		||||
#define CHAN_IS_SACCH(chan) \
 | 
			
		||||
	(trx_lchan_desc[chan].link_id & TRX_CH_LID_SACCH)
 | 
			
		||||
 | 
			
		||||
/* FIXME: we need a better way to identify / distinguish primitives */
 | 
			
		||||
#define PRIM_IS_RACH11(prim) \
 | 
			
		||||
	(prim->payload_len == sizeof(struct l1ctl_ext_rach_req))
 | 
			
		||||
 | 
			
		||||
#define PRIM_IS_RACH8(prim) \
 | 
			
		||||
	(prim->payload_len == sizeof(struct l1ctl_rach_req))
 | 
			
		||||
 | 
			
		||||
#define PRIM_IS_RACH(prim) \
 | 
			
		||||
	(PRIM_IS_RACH8(prim) || PRIM_IS_RACH11(prim))
 | 
			
		||||
 | 
			
		||||
#define PRIM_IS_TCH(prim) \
 | 
			
		||||
	(CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN)
 | 
			
		||||
 | 
			
		||||
#define PRIM_IS_FACCH(prim) \
 | 
			
		||||
	(CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN)
 | 
			
		||||
 | 
			
		||||
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
 | 
			
		||||
	uint32_t fn, struct trx_lchan_state *lchan);
 | 
			
		||||
int sched_prim_dummy(struct trx_lchan_state *lchan);
 | 
			
		||||
void sched_prim_drop(struct trx_lchan_state *lchan);
 | 
			
		||||
void sched_prim_flush_queue(struct llist_head *list);
 | 
			
		||||
 | 
			
		||||
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
 | 
			
		||||
	uint32_t fn, sbit_t *bits, uint16_t nbits,
 | 
			
		||||
	const struct trx_meas_set *meas);
 | 
			
		||||
int sched_trx_handle_tx_burst(struct trx_instance *trx,
 | 
			
		||||
	struct trx_ts *ts, struct trx_lchan_state *lchan,
 | 
			
		||||
	uint32_t fn, ubit_t *bits);
 | 
			
		||||
 | 
			
		||||
/* Shared declarations for lchan handlers */
 | 
			
		||||
extern const uint8_t sched_nb_training_bits[8][26];
 | 
			
		||||
 | 
			
		||||
const char *burst_mask2str(const uint8_t *mask, int bits);
 | 
			
		||||
size_t sched_bad_frame_ind(uint8_t *l2, struct trx_lchan_state *lchan);
 | 
			
		||||
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
 | 
			
		||||
	int bit_error_count, bool dec_failed, bool traffic);
 | 
			
		||||
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
 | 
			
		||||
	struct trx_lchan_state *lchan, uint32_t fn, bool traffic);
 | 
			
		||||
int sched_gsmtap_send(enum trx_lchan_type lchan_type, uint32_t fn, uint8_t tn,
 | 
			
		||||
		      uint16_t band_arfcn, int8_t signal_dbm, uint8_t snr,
 | 
			
		||||
		      const uint8_t *data, size_t data_len);
 | 
			
		||||
 | 
			
		||||
/* Interleaved TCH/H block TDMA frame mapping */
 | 
			
		||||
uint32_t sched_tchh_block_dl_first_fn(enum trx_lchan_type chan,
 | 
			
		||||
	uint32_t last_fn, bool facch);
 | 
			
		||||
bool sched_tchh_block_map_fn(enum trx_lchan_type chan,
 | 
			
		||||
	uint32_t fn, bool ul, bool facch, bool start);
 | 
			
		||||
 | 
			
		||||
#define sched_tchh_traffic_start(chan, fn, ul) \
 | 
			
		||||
	sched_tchh_block_map_fn(chan, fn, ul, 0, 1)
 | 
			
		||||
#define sched_tchh_traffic_end(chan, fn, ul) \
 | 
			
		||||
	sched_tchh_block_map_fn(chan, fn, ul, 0, 0)
 | 
			
		||||
 | 
			
		||||
#define sched_tchh_facch_start(chan, fn, ul) \
 | 
			
		||||
	sched_tchh_block_map_fn(chan, fn, ul, 1, 1)
 | 
			
		||||
#define sched_tchh_facch_end(chan, fn, ul) \
 | 
			
		||||
	sched_tchh_block_map_fn(chan, fn, ul, 1, 0)
 | 
			
		||||
 | 
			
		||||
/* Measurement history */
 | 
			
		||||
void sched_trx_meas_push(struct trx_lchan_state *lchan, const struct trx_meas_set *meas);
 | 
			
		||||
void sched_trx_meas_avg(struct trx_lchan_state *lchan, unsigned int n);
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/gsm/gsm0502.h>
 | 
			
		||||
 | 
			
		||||
enum tdma_sched_clck_state {
 | 
			
		||||
	SCH_CLCK_STATE_WAIT,
 | 
			
		||||
	SCH_CLCK_STATE_OK,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Forward structure declaration */
 | 
			
		||||
struct trx_sched;
 | 
			
		||||
 | 
			
		||||
/*! \brief One scheduler instance */
 | 
			
		||||
struct trx_sched {
 | 
			
		||||
	/*! \brief Clock state */
 | 
			
		||||
	enum tdma_sched_clck_state state;
 | 
			
		||||
	/*! \brief Local clock source */
 | 
			
		||||
	struct timespec clock;
 | 
			
		||||
	/*! \brief Count of processed frames */
 | 
			
		||||
	uint32_t fn_counter_proc;
 | 
			
		||||
	/*! \brief Local frame counter advance */
 | 
			
		||||
	uint32_t fn_counter_advance;
 | 
			
		||||
	/*! \brief Count of lost frames */
 | 
			
		||||
	uint32_t fn_counter_lost;
 | 
			
		||||
	/*! \brief Frame callback timer */
 | 
			
		||||
	struct osmo_timer_list clock_timer;
 | 
			
		||||
	/*! \brief Frame callback */
 | 
			
		||||
	void (*clock_cb)(struct trx_sched *sched);
 | 
			
		||||
	/*! \brief Private data (e.g. pointer to trx instance) */
 | 
			
		||||
	void *data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int sched_clck_handle(struct trx_sched *sched, uint32_t fn);
 | 
			
		||||
void sched_clck_reset(struct trx_sched *sched);
 | 
			
		||||
							
								
								
									
										826
									
								
								trxcon/trx_if.c
									
									
									
									
									
								
							
							
						
						
									
										826
									
								
								trxcon/trx_if.c
									
									
									
									
									
								
							@@ -1,826 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * Transceiver interface handlers
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
 | 
			
		||||
 * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
 | 
			
		||||
#include <sys/eventfd.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
#include "trxcon.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
#include "../Transceiver52M/l1if.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static struct value_string trx_evt_names[] = {
 | 
			
		||||
	{ 0, NULL } /* no events? */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm_state trx_fsm_states[] = {
 | 
			
		||||
	[TRX_STATE_OFFLINE] = {
 | 
			
		||||
		.out_state_mask = (
 | 
			
		||||
			GEN_MASK(TRX_STATE_IDLE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_RSP_WAIT)),
 | 
			
		||||
		.name = "OFFLINE",
 | 
			
		||||
	},
 | 
			
		||||
	[TRX_STATE_IDLE] = {
 | 
			
		||||
		.out_state_mask = UINT32_MAX,
 | 
			
		||||
		.name = "IDLE",
 | 
			
		||||
	},
 | 
			
		||||
	[TRX_STATE_ACTIVE] = {
 | 
			
		||||
		.out_state_mask = (
 | 
			
		||||
			GEN_MASK(TRX_STATE_IDLE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_RSP_WAIT)),
 | 
			
		||||
		.name = "ACTIVE",
 | 
			
		||||
	},
 | 
			
		||||
	[TRX_STATE_RSP_WAIT] = {
 | 
			
		||||
		.out_state_mask = (
 | 
			
		||||
			GEN_MASK(TRX_STATE_IDLE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_ACTIVE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_OFFLINE)),
 | 
			
		||||
		.name = "RSP_WAIT",
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm trx_fsm = {
 | 
			
		||||
	.name = "trx_interface_fsm",
 | 
			
		||||
	.states = trx_fsm_states,
 | 
			
		||||
	.num_states = ARRAY_SIZE(trx_fsm_states),
 | 
			
		||||
	.log_subsys = DTRX,
 | 
			
		||||
	.event_names = trx_evt_names,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
 | 
			
		||||
	uint16_t port_local, const char *host_remote, uint16_t port_remote,
 | 
			
		||||
	int (*cb)(struct osmo_fd *fd, unsigned int what))
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	ofd->data = priv;
 | 
			
		||||
	ofd->fd = -1;
 | 
			
		||||
	ofd->cb = cb;
 | 
			
		||||
 | 
			
		||||
	/* Init UDP Connection */
 | 
			
		||||
	rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
 | 
			
		||||
				 host_remote, port_remote,
 | 
			
		||||
				 OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trx_udp_close(struct osmo_fd *ofd)
 | 
			
		||||
{
 | 
			
		||||
	if (ofd->fd > 0) {
 | 
			
		||||
		osmo_fd_unregister(ofd);
 | 
			
		||||
		close(ofd->fd);
 | 
			
		||||
		ofd->fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Control (CTRL) interface handlers                                        */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Commands on the Per-ARFCN Control Interface                              */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* The per-ARFCN control interface uses a command-response protocol.        */
 | 
			
		||||
/* Commands are NULL-terminated ASCII strings, one per UDP socket.          */
 | 
			
		||||
/* Each command has a corresponding response.                               */
 | 
			
		||||
/* Every command is of the form:                                            */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* CMD <cmdtype> [params]                                                   */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* The <cmdtype> is the actual command.                                     */
 | 
			
		||||
/* Parameters are optional depending on the commands type.                  */
 | 
			
		||||
/* Every response is of the form:                                           */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* RSP <cmdtype> <status> [result]                                          */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* The <status> is 0 for success and a non-zero error code for failure.     */
 | 
			
		||||
/* Successful responses may include results, depending on the command type. */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
static void trx_ctrl_timer_cb(void *data);
 | 
			
		||||
 | 
			
		||||
/* Send first CTRL message and start timer */
 | 
			
		||||
static void trx_ctrl_send(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
 | 
			
		||||
	if (llist_empty(&trx->trx_ctrl_list))
 | 
			
		||||
		return;
 | 
			
		||||
	tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
 | 
			
		||||
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	char* cmd = malloc(TRXC_BUF_SIZE);
 | 
			
		||||
	memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
 | 
			
		||||
 | 
			
		||||
	/* Send command */
 | 
			
		||||
	LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
 | 
			
		||||
	trxif_to_trx_c(cmd);
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
	/* Send command */
 | 
			
		||||
	LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
 | 
			
		||||
	send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	/* Trigger state machine */
 | 
			
		||||
	if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
 | 
			
		||||
		trx->prev_state = trx->fsm->state;
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Start expire timer */
 | 
			
		||||
	trx->trx_ctrl_timer.data = trx;
 | 
			
		||||
	trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
 | 
			
		||||
	osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trx_ctrl_timer_cb(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = (struct trx_instance *) data;
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
 | 
			
		||||
	/* Queue may be cleaned at this moment */
 | 
			
		||||
	if (llist_empty(&trx->trx_ctrl_list))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
 | 
			
		||||
 | 
			
		||||
	tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
 | 
			
		||||
	if (++tcm->retry_cnt > 3) {
 | 
			
		||||
		LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
 | 
			
		||||
		osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Attempt to send a command again */
 | 
			
		||||
	trx_ctrl_send(trx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Add a new CTRL command to the trx_ctrl_list */
 | 
			
		||||
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
 | 
			
		||||
	const char *cmd, const char *fmt, ...)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
	int len, pending = 0;
 | 
			
		||||
	va_list ap;
 | 
			
		||||
 | 
			
		||||
	/* TODO: make sure that transceiver online */
 | 
			
		||||
 | 
			
		||||
	if (!llist_empty(&trx->trx_ctrl_list))
 | 
			
		||||
		pending = 1;
 | 
			
		||||
 | 
			
		||||
	/* Allocate a message */
 | 
			
		||||
	tcm = talloc_zero(trx, struct trx_ctrl_msg);
 | 
			
		||||
	if (!tcm)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/* Fill in command arguments */
 | 
			
		||||
	if (fmt && fmt[0]) {
 | 
			
		||||
		len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
 | 
			
		||||
		va_start(ap, fmt);
 | 
			
		||||
		vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
 | 
			
		||||
		va_end(ap);
 | 
			
		||||
	} else {
 | 
			
		||||
		snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tcm->cmd_len = strlen(cmd);
 | 
			
		||||
	tcm->critical = critical;
 | 
			
		||||
	llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
 | 
			
		||||
	LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
 | 
			
		||||
 | 
			
		||||
	/* Send message, if no pending messages */
 | 
			
		||||
	if (!pending)
 | 
			
		||||
		trx_ctrl_send(trx);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Power Control
 | 
			
		||||
 *
 | 
			
		||||
 * ECHO is used to check transceiver availability.
 | 
			
		||||
 * CMD ECHO
 | 
			
		||||
 * RSP ECHO <status>
 | 
			
		||||
 *
 | 
			
		||||
 * POWEROFF shuts off transmitter power and stops the demodulator.
 | 
			
		||||
 * CMD POWEROFF
 | 
			
		||||
 * RSP POWEROFF <status>
 | 
			
		||||
 *
 | 
			
		||||
 * POWERON starts the transmitter and starts the demodulator.
 | 
			
		||||
 * Initial power level is very low.
 | 
			
		||||
 * This command fails if the transmitter and receiver are not yet tuned.
 | 
			
		||||
 * This command fails if the transmit or receive frequency creates a conflict
 | 
			
		||||
 * with another ARFCN that is already running.
 | 
			
		||||
 * If the transceiver is already on, it response with success to this command.
 | 
			
		||||
 * CMD POWERON
 | 
			
		||||
 * RSP POWERON <status>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_sync(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "SYNC", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_echo(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "ECHO", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_poweroff(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_poweron(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	if (trx->powered_up) {
 | 
			
		||||
		/* FIXME: this should be handled by the FSM, not here! */
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
 | 
			
		||||
		return -EAGAIN;
 | 
			
		||||
	}
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "POWERON", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Timeslot Control
 | 
			
		||||
 *
 | 
			
		||||
 * SETSLOT sets the format of the uplink timeslots in the ARFCN.
 | 
			
		||||
 * The <timeslot> indicates the timeslot of interest.
 | 
			
		||||
 * The <chantype> indicates the type of channel that occupies the timeslot.
 | 
			
		||||
 * A chantype of zero indicates the timeslot is off.
 | 
			
		||||
 * CMD SETSLOT <timeslot> <chantype>
 | 
			
		||||
 * RSP SETSLOT <status> <timeslot> <chantype>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Tuning Control
 | 
			
		||||
 *
 | 
			
		||||
 * (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
 | 
			
		||||
 * This command fails if the receiver is already running.
 | 
			
		||||
 * (To re-tune you stop the radio, re-tune, and restart.)
 | 
			
		||||
 * This command fails if the transmit or receive frequency
 | 
			
		||||
 * creates a conflict with another ARFCN that is already running.
 | 
			
		||||
 * CMD (RX/TX)TUNE <kHz>
 | 
			
		||||
 * RSP (RX/TX)TUNE <status> <kHz>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t freq10;
 | 
			
		||||
 | 
			
		||||
	/* RX is downlink on MS side */
 | 
			
		||||
	freq10 = gsm_arfcn2freq10(band_arfcn, 0);
 | 
			
		||||
	if (freq10 == 0xffff) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t freq10;
 | 
			
		||||
 | 
			
		||||
	/* TX is uplink on MS side */
 | 
			
		||||
	freq10 = gsm_arfcn2freq10(band_arfcn, 1);
 | 
			
		||||
	if (freq10 == 0xffff) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Power measurement
 | 
			
		||||
 *
 | 
			
		||||
 * MEASURE instructs the transceiver to perform a power
 | 
			
		||||
 * measurement on specified frequency. After receiving this
 | 
			
		||||
 * request, transceiver should quickly re-tune to requested
 | 
			
		||||
 * frequency, measure power level and re-tune back to the
 | 
			
		||||
 * previous frequency.
 | 
			
		||||
 * CMD MEASURE <kHz>
 | 
			
		||||
 * RSP MEASURE <status> <kHz> <dB>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_measure(struct trx_instance *trx,
 | 
			
		||||
	uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t freq10;
 | 
			
		||||
 | 
			
		||||
	/* Update ARFCN range for measurement */
 | 
			
		||||
	trx->pm_band_arfcn_start = band_arfcn_start;
 | 
			
		||||
	trx->pm_band_arfcn_stop = band_arfcn_stop;
 | 
			
		||||
 | 
			
		||||
	/* Calculate a frequency for current ARFCN (DL) */
 | 
			
		||||
	freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
 | 
			
		||||
	if (freq10 == 0xffff) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int freq10;
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	int dbm;
 | 
			
		||||
 | 
			
		||||
	/* Parse freq. and power level */
 | 
			
		||||
	sscanf(resp, "%u %d", &freq10, &dbm);
 | 
			
		||||
	freq10 /= 100;
 | 
			
		||||
 | 
			
		||||
	/* Check received ARFCN against expected */
 | 
			
		||||
	band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
 | 
			
		||||
	if (band_arfcn != trx->pm_band_arfcn_start) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
 | 
			
		||||
			"response ARFCN=%u doesn't match expected ARFCN=%u\n",
 | 
			
		||||
			band_arfcn &~ ARFCN_FLAG_MASK,
 | 
			
		||||
			trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send L1CTL_PM_CONF */
 | 
			
		||||
	l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
 | 
			
		||||
		band_arfcn == trx->pm_band_arfcn_stop);
 | 
			
		||||
 | 
			
		||||
	/* Schedule a next measurement */
 | 
			
		||||
	if (band_arfcn != trx->pm_band_arfcn_stop)
 | 
			
		||||
		trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Timing Advance control
 | 
			
		||||
 *
 | 
			
		||||
 * SETTA instructs the transceiver to transmit bursts in
 | 
			
		||||
 * advance calculated from requested TA value. This value is
 | 
			
		||||
 * normally between 0 and 63, with each step representing
 | 
			
		||||
 * an advance of one bit period (about 3.69 microseconds).
 | 
			
		||||
 * Since OsmocomBB has a special feature, which allows one
 | 
			
		||||
 * to spoof the distance from BTS, the range is extended.
 | 
			
		||||
 * CMD SETTA <-128..127>
 | 
			
		||||
 * RSP SETTA <status> <TA>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Frequency Hopping parameters indication.
 | 
			
		||||
 *
 | 
			
		||||
 * SETFH instructs transceiver to enable frequency hopping mode
 | 
			
		||||
 * using the given HSN, MAIO, and Mobile Allocation parameters.
 | 
			
		||||
 *
 | 
			
		||||
 * CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
 | 
			
		||||
 *
 | 
			
		||||
 * where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
 | 
			
		||||
 * corresponding to one ARFCN the Mobile Allocation. Note that the
 | 
			
		||||
 * channel list is expected to be sorted in ascending order.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
 | 
			
		||||
	uint8_t maio, uint16_t *ma, size_t ma_len)
 | 
			
		||||
{
 | 
			
		||||
	/* Reserve some room for CMD SETFH <HSN> <MAIO> */
 | 
			
		||||
	char ma_buf[TRXC_BUF_SIZE - 24];
 | 
			
		||||
	size_t ma_buf_len = sizeof(ma_buf) - 1;
 | 
			
		||||
	uint16_t rx_freq, tx_freq;
 | 
			
		||||
	char *ptr;
 | 
			
		||||
	int i, rc;
 | 
			
		||||
 | 
			
		||||
	/* Make sure that Mobile Allocation has at least one ARFCN */
 | 
			
		||||
	if (!ma_len || ma == NULL) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
 | 
			
		||||
	for (i = 0, ptr = ma_buf; i < ma_len; i++) {
 | 
			
		||||
		/* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
 | 
			
		||||
		rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
 | 
			
		||||
		tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
 | 
			
		||||
		if (rx_freq == 0xffff || tx_freq == 0xffff) {
 | 
			
		||||
			LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
 | 
			
		||||
			     "to a pair of Rx/Tx frequencies\n",
 | 
			
		||||
			     ma[i] & ~ARFCN_FLAG_MASK);
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
 | 
			
		||||
		rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
 | 
			
		||||
		if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
 | 
			
		||||
			LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
 | 
			
		||||
			     "Mobile Allocation (N=%zu)\n", ma_len);
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Move pointer */
 | 
			
		||||
		ma_buf_len -= rc;
 | 
			
		||||
		ptr += rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Overwrite the last space */
 | 
			
		||||
	*(ptr - 1) = '\0';
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Get response from CTRL socket */
 | 
			
		||||
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = ofd->data;
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
	int resp, rsp_len;
 | 
			
		||||
	char buf[TRXC_BUF_SIZE], *p;
 | 
			
		||||
	ssize_t read_len;
 | 
			
		||||
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	char* response = trxif_from_trx_c();
 | 
			
		||||
	if (!response) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
 | 
			
		||||
		goto rsp_error;
 | 
			
		||||
	}
 | 
			
		||||
	memcpy(buf, response, TRXC_BUF_SIZE);
 | 
			
		||||
	free(response);
 | 
			
		||||
#else
 | 
			
		||||
	read_len = read(ofd->fd, buf, sizeof(buf) - 1);
 | 
			
		||||
	if (read_len <= 0) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
 | 
			
		||||
		return read_len;
 | 
			
		||||
	}
 | 
			
		||||
	buf[read_len] = '\0';
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	if (!!strncmp(buf, "RSP ", 4)) {
 | 
			
		||||
		LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Calculate the length of response item */
 | 
			
		||||
	p = strchr(buf + 4, ' ');
 | 
			
		||||
	rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
 | 
			
		||||
 | 
			
		||||
	/* Abort expire timer */
 | 
			
		||||
	if (osmo_timer_pending(&trx->trx_ctrl_timer))
 | 
			
		||||
		osmo_timer_del(&trx->trx_ctrl_timer);
 | 
			
		||||
 | 
			
		||||
	/* Get command for response message */
 | 
			
		||||
	if (llist_empty(&trx->trx_ctrl_list)) {
 | 
			
		||||
		LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tcm = llist_entry(trx->trx_ctrl_list.next,
 | 
			
		||||
		struct trx_ctrl_msg, list);
 | 
			
		||||
 | 
			
		||||
	/* Check if response matches command */
 | 
			
		||||
	if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
 | 
			
		||||
		LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
 | 
			
		||||
			"Response message '%s' does not match command "
 | 
			
		||||
			"message '%s'\n", buf, tcm->cmd);
 | 
			
		||||
		goto rsp_error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check for response code */
 | 
			
		||||
	sscanf(p + 1, "%d", &resp);
 | 
			
		||||
	if (resp) {
 | 
			
		||||
		LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
 | 
			
		||||
			"Transceiver rejected TRX command with "
 | 
			
		||||
			"response: '%s'\n", buf);
 | 
			
		||||
 | 
			
		||||
		if (tcm->critical)
 | 
			
		||||
			goto rsp_error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Trigger state machine */
 | 
			
		||||
	if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
 | 
			
		||||
		trx->powered_up = true;
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
 | 
			
		||||
		trx->powered_up = false;
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
 | 
			
		||||
		trx_if_measure_rsp_cb(trx, buf + 14);
 | 
			
		||||
	else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
 | 
			
		||||
	else
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
 | 
			
		||||
 | 
			
		||||
	/* Remove command from list */
 | 
			
		||||
	llist_del(&tcm->list);
 | 
			
		||||
	talloc_free(tcm);
 | 
			
		||||
 | 
			
		||||
	/* Send next message, if any */
 | 
			
		||||
	trx_ctrl_send(trx);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
 | 
			
		||||
rsp_error:
 | 
			
		||||
	/* Notify higher layers about the problem */
 | 
			
		||||
	osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
 | 
			
		||||
	return -EIO;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Data interface handlers                                                  */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* DATA interface                                                           */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Messages on the data interface carry one radio burst per UDP message.    */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Received Data Burst:                                                     */
 | 
			
		||||
/* 1 byte timeslot index                                                    */
 | 
			
		||||
/* 4 bytes GSM frame number, BE                                             */
 | 
			
		||||
/* 1 byte RSSI in -dBm                                                      */
 | 
			
		||||
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE     */
 | 
			
		||||
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1"  */
 | 
			
		||||
/* 2 bytes are not used, but being sent by OsmoTRX                          */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Transmit Data Burst:                                                     */
 | 
			
		||||
/* 1 byte timeslot index                                                    */
 | 
			
		||||
/* 4 bytes GSM frame number, BE                                             */
 | 
			
		||||
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation)                   */
 | 
			
		||||
/* 148 bytes output symbol values, 0 & 1                                    */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = ofd->data;
 | 
			
		||||
	uint8_t buf[TRXD_BUF_SIZE];
 | 
			
		||||
	ssize_t read_len;
 | 
			
		||||
 | 
			
		||||
	read_len = read(ofd->fd, buf, sizeof(buf));
 | 
			
		||||
	if (read_len <= 0) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
 | 
			
		||||
		return read_len;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (read_len != 158) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR,
 | 
			
		||||
		     "Got data message with invalid "
 | 
			
		||||
		     "length '%zd'\n",
 | 
			
		||||
		     read_len);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_data_rx_handler(trx, buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_data_rx_handler(struct trx_instance *trx, uint8_t *buf)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_meas_set meas;
 | 
			
		||||
	sbit_t bits[148];
 | 
			
		||||
	int8_t rssi, tn;
 | 
			
		||||
	int16_t toa256;
 | 
			
		||||
	uint32_t fn;
 | 
			
		||||
 | 
			
		||||
	tn = buf[0];
 | 
			
		||||
	fn = osmo_load32be(buf + 1);
 | 
			
		||||
	rssi = -(int8_t)buf[5];
 | 
			
		||||
	toa256 = ((int16_t)(buf[6] << 8) | buf[7]);
 | 
			
		||||
 | 
			
		||||
	/* Copy and convert bits {254..0} to sbits {-127..127} */
 | 
			
		||||
	//osmo_ubit2sbit(bits, buf + 8, 148);
 | 
			
		||||
	memcpy(bits, buf + 8, 148);
 | 
			
		||||
 | 
			
		||||
	if (tn >= 8) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (fn >= 2715648) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
 | 
			
		||||
		tn, fn, rssi, toa256);
 | 
			
		||||
 | 
			
		||||
	/* Group the measurements together */
 | 
			
		||||
	meas = (struct trx_meas_set) {
 | 
			
		||||
		.toa256 = toa256,
 | 
			
		||||
		.rssi = rssi,
 | 
			
		||||
		.fn = fn,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Poke scheduler */
 | 
			
		||||
	sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
 | 
			
		||||
 | 
			
		||||
	/* Correct local clock counter */
 | 
			
		||||
	if (fn % 51 == 0)
 | 
			
		||||
		sched_clck_handle(&trx->sched, fn);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern void tx_external_transceiver(uint8_t *burst) __attribute__((weak));
 | 
			
		||||
 | 
			
		||||
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
 | 
			
		||||
	uint8_t pwr, const ubit_t *bits)
 | 
			
		||||
{
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
 | 
			
		||||
	t->ts = tn;
 | 
			
		||||
	t->fn = fn;
 | 
			
		||||
	t->txlev = pwr;
 | 
			
		||||
	memcpy(t->symbols, bits, 148);
 | 
			
		||||
	trxif_to_trx_d(t);
 | 
			
		||||
#else
 | 
			
		||||
	uint8_t buf[TRXD_BUF_SIZE];
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * We must be sure that we have clock,
 | 
			
		||||
	 * and we have sent all control data
 | 
			
		||||
	 *
 | 
			
		||||
	 * TODO: introduce proper state machines for both
 | 
			
		||||
	 *       transceiver and its TRXC interface.
 | 
			
		||||
	 */
 | 
			
		||||
#if 0
 | 
			
		||||
	if (trx->fsm->state != TRX_STATE_ACTIVE) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
 | 
			
		||||
			"transceiver isn't ready\n");
 | 
			
		||||
		return -EAGAIN;
 | 
			
		||||
	}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
 | 
			
		||||
 | 
			
		||||
	buf[0] = tn;
 | 
			
		||||
	osmo_store32be(fn, buf + 1);
 | 
			
		||||
	buf[5] = pwr;
 | 
			
		||||
 | 
			
		||||
	/* Copy ubits {0,1} */
 | 
			
		||||
	memcpy(buf + 6, bits, 148);
 | 
			
		||||
 | 
			
		||||
	/* Send data to transceiver */
 | 
			
		||||
	if (tx_external_transceiver)
 | 
			
		||||
		tx_external_transceiver(buf);
 | 
			
		||||
	else
 | 
			
		||||
		send(trx->trx_ofd_data.fd, buf, 154, 0);
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
 | 
			
		||||
struct trx_instance *trx_if_open(void *tall_ctx,
 | 
			
		||||
	const char *local_host, const char *remote_host,
 | 
			
		||||
	uint16_t base_port)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
 | 
			
		||||
		"(%s:%u)\n", remote_host, base_port);
 | 
			
		||||
 | 
			
		||||
	/* Try to allocate memory */
 | 
			
		||||
	trx = talloc_zero(tall_ctx, struct trx_instance);
 | 
			
		||||
	if (!trx) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new dedicated state machine */
 | 
			
		||||
	trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
 | 
			
		||||
		NULL, LOGL_DEBUG, "trx_interface");
 | 
			
		||||
	if (trx->fsm == NULL) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
 | 
			
		||||
			"of FSM '%s'\n", trx_fsm.name);
 | 
			
		||||
		talloc_free(trx);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Initialize CTRL queue */
 | 
			
		||||
	INIT_LLIST_HEAD(&trx->trx_ctrl_list);
 | 
			
		||||
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	rc = eventfd(0, 0);
 | 
			
		||||
	osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
 | 
			
		||||
	osmo_fd_register(get_c_fd());
 | 
			
		||||
 | 
			
		||||
	rc = eventfd(0, 0);
 | 
			
		||||
	osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
 | 
			
		||||
	osmo_fd_register(get_d_fd());
 | 
			
		||||
#else
 | 
			
		||||
	/* Open sockets */
 | 
			
		||||
	rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host, base_port + 101, remote_host, base_port + 1,
 | 
			
		||||
			  trx_ctrl_read_cb);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		goto udp_error;
 | 
			
		||||
 | 
			
		||||
	rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host, base_port + 102, remote_host, base_port + 2,
 | 
			
		||||
			  trx_data_rx_cb);
 | 
			
		||||
	if (rc < 0)
 | 
			
		||||
		goto udp_error;
 | 
			
		||||
#endif
 | 
			
		||||
	return trx;
 | 
			
		||||
 | 
			
		||||
udp_error:
 | 
			
		||||
	LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
 | 
			
		||||
	osmo_fsm_inst_free(trx->fsm);
 | 
			
		||||
	talloc_free(trx);
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Flush pending control messages */
 | 
			
		||||
void trx_if_flush_ctrl(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
 | 
			
		||||
	/* Reset state machine */
 | 
			
		||||
	osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
 | 
			
		||||
 | 
			
		||||
	/* Clear command queue */
 | 
			
		||||
	while (!llist_empty(&trx->trx_ctrl_list)) {
 | 
			
		||||
		tcm = llist_entry(trx->trx_ctrl_list.next,
 | 
			
		||||
			struct trx_ctrl_msg, list);
 | 
			
		||||
		llist_del(&tcm->list);
 | 
			
		||||
		talloc_free(tcm);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void trx_if_close(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	/* May be unallocated due to init error */
 | 
			
		||||
	if (!trx)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
 | 
			
		||||
 | 
			
		||||
	/* Flush CTRL message list */
 | 
			
		||||
	trx_if_flush_ctrl(trx);
 | 
			
		||||
 | 
			
		||||
	/* Close sockets */
 | 
			
		||||
#ifdef IPCIF
 | 
			
		||||
	close(get_c_fd()->fd);
 | 
			
		||||
	close(get_d_fd()->fd);
 | 
			
		||||
#else
 | 
			
		||||
	trx_udp_close(&trx->trx_ofd_ctrl);
 | 
			
		||||
	trx_udp_close(&trx->trx_ofd_data);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	/* Free memory */
 | 
			
		||||
	osmo_fsm_inst_free(trx->fsm);
 | 
			
		||||
	talloc_free(trx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static __attribute__((constructor)) void on_dso_load(void)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,816 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 * Transceiver interface handlers
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
 | 
			
		||||
 * Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
#include <netinet/in.h>
 | 
			
		||||
 | 
			
		||||
#include <sys/eventfd.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/logging.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/socket.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/bits.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
#include "trxcon.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
 | 
			
		||||
#include "../Transceiver52M/l1if.h"
 | 
			
		||||
 | 
			
		||||
static struct value_string trx_evt_names[] = {
 | 
			
		||||
	{ 0, NULL } /* no events? */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm_state trx_fsm_states[] = {
 | 
			
		||||
	[TRX_STATE_OFFLINE] = {
 | 
			
		||||
		.out_state_mask = (
 | 
			
		||||
			GEN_MASK(TRX_STATE_IDLE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_RSP_WAIT)),
 | 
			
		||||
		.name = "OFFLINE",
 | 
			
		||||
	},
 | 
			
		||||
	[TRX_STATE_IDLE] = {
 | 
			
		||||
		.out_state_mask = UINT32_MAX,
 | 
			
		||||
		.name = "IDLE",
 | 
			
		||||
	},
 | 
			
		||||
	[TRX_STATE_ACTIVE] = {
 | 
			
		||||
		.out_state_mask = (
 | 
			
		||||
			GEN_MASK(TRX_STATE_IDLE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_RSP_WAIT)),
 | 
			
		||||
		.name = "ACTIVE",
 | 
			
		||||
	},
 | 
			
		||||
	[TRX_STATE_RSP_WAIT] = {
 | 
			
		||||
		.out_state_mask = (
 | 
			
		||||
			GEN_MASK(TRX_STATE_IDLE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_ACTIVE) |
 | 
			
		||||
			GEN_MASK(TRX_STATE_OFFLINE)),
 | 
			
		||||
		.name = "RSP_WAIT",
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm trx_fsm = {
 | 
			
		||||
	.name = "trx_interface_fsm",
 | 
			
		||||
	.states = trx_fsm_states,
 | 
			
		||||
	.num_states = ARRAY_SIZE(trx_fsm_states),
 | 
			
		||||
	.log_subsys = DTRX,
 | 
			
		||||
	.event_names = trx_evt_names,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
 | 
			
		||||
	uint16_t port_local, const char *host_remote, uint16_t port_remote,
 | 
			
		||||
	int (*cb)(struct osmo_fd *fd, unsigned int what))
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	ofd->data = priv;
 | 
			
		||||
	ofd->fd = -1;
 | 
			
		||||
	ofd->cb = cb;
 | 
			
		||||
 | 
			
		||||
	/* Init UDP Connection */
 | 
			
		||||
	rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
 | 
			
		||||
				 host_remote, port_remote,
 | 
			
		||||
				 OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trx_udp_close(struct osmo_fd *ofd)
 | 
			
		||||
{
 | 
			
		||||
	if (ofd->fd > 0) {
 | 
			
		||||
		osmo_fd_unregister(ofd);
 | 
			
		||||
		close(ofd->fd);
 | 
			
		||||
		ofd->fd = -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Control (CTRL) interface handlers                                        */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Commands on the Per-ARFCN Control Interface                              */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* The per-ARFCN control interface uses a command-response protocol.        */
 | 
			
		||||
/* Commands are NULL-terminated ASCII strings, one per UDP socket.          */
 | 
			
		||||
/* Each command has a corresponding response.                               */
 | 
			
		||||
/* Every command is of the form:                                            */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* CMD <cmdtype> [params]                                                   */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* The <cmdtype> is the actual command.                                     */
 | 
			
		||||
/* Parameters are optional depending on the commands type.                  */
 | 
			
		||||
/* Every response is of the form:                                           */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* RSP <cmdtype> <status> [result]                                          */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* The <status> is 0 for success and a non-zero error code for failure.     */
 | 
			
		||||
/* Successful responses may include results, depending on the command type. */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
static void trx_ctrl_timer_cb(void *data);
 | 
			
		||||
 | 
			
		||||
/* Send first CTRL message and start timer */
 | 
			
		||||
static void trx_ctrl_send(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
 | 
			
		||||
	if (llist_empty(&trx->trx_ctrl_list))
 | 
			
		||||
		return;
 | 
			
		||||
	tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
 | 
			
		||||
 | 
			
		||||
	char* cmd = malloc(TRXC_BUF_SIZE);
 | 
			
		||||
	memcpy(cmd, tcm->cmd, TRXC_BUF_SIZE);
 | 
			
		||||
 | 
			
		||||
	/* Send command */
 | 
			
		||||
	LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
 | 
			
		||||
	trxif_to_trx_c(cmd);
 | 
			
		||||
//	send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
 | 
			
		||||
 | 
			
		||||
	/* Trigger state machine */
 | 
			
		||||
	if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
 | 
			
		||||
		trx->prev_state = trx->fsm->state;
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Start expire timer */
 | 
			
		||||
	trx->trx_ctrl_timer.data = trx;
 | 
			
		||||
	trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
 | 
			
		||||
	osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trx_ctrl_timer_cb(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = (struct trx_instance *) data;
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
 | 
			
		||||
	/* Queue may be cleaned at this moment */
 | 
			
		||||
	if (llist_empty(&trx->trx_ctrl_list))
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
 | 
			
		||||
 | 
			
		||||
	tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
 | 
			
		||||
	if (++tcm->retry_cnt > 3) {
 | 
			
		||||
		LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
 | 
			
		||||
		osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Attempt to send a command again */
 | 
			
		||||
	trx_ctrl_send(trx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Add a new CTRL command to the trx_ctrl_list */
 | 
			
		||||
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
 | 
			
		||||
	const char *cmd, const char *fmt, ...)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
	int len, pending = 0;
 | 
			
		||||
	va_list ap;
 | 
			
		||||
 | 
			
		||||
	/* TODO: make sure that transceiver online */
 | 
			
		||||
 | 
			
		||||
	if (!llist_empty(&trx->trx_ctrl_list))
 | 
			
		||||
		pending = 1;
 | 
			
		||||
 | 
			
		||||
	/* Allocate a message */
 | 
			
		||||
	tcm = talloc_zero(trx, struct trx_ctrl_msg);
 | 
			
		||||
	if (!tcm)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/* Fill in command arguments */
 | 
			
		||||
	if (fmt && fmt[0]) {
 | 
			
		||||
		len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
 | 
			
		||||
		va_start(ap, fmt);
 | 
			
		||||
		vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 1, fmt, ap);
 | 
			
		||||
		va_end(ap);
 | 
			
		||||
	} else {
 | 
			
		||||
		snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s", cmd);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tcm->cmd_len = strlen(cmd);
 | 
			
		||||
	tcm->critical = critical;
 | 
			
		||||
	llist_add_tail(&tcm->list, &trx->trx_ctrl_list);
 | 
			
		||||
	LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
 | 
			
		||||
 | 
			
		||||
	/* Send message, if no pending messages */
 | 
			
		||||
	if (!pending)
 | 
			
		||||
		trx_ctrl_send(trx);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Power Control
 | 
			
		||||
 *
 | 
			
		||||
 * ECHO is used to check transceiver availability.
 | 
			
		||||
 * CMD ECHO
 | 
			
		||||
 * RSP ECHO <status>
 | 
			
		||||
 *
 | 
			
		||||
 * POWEROFF shuts off transmitter power and stops the demodulator.
 | 
			
		||||
 * CMD POWEROFF
 | 
			
		||||
 * RSP POWEROFF <status>
 | 
			
		||||
 *
 | 
			
		||||
 * POWERON starts the transmitter and starts the demodulator.
 | 
			
		||||
 * Initial power level is very low.
 | 
			
		||||
 * This command fails if the transmitter and receiver are not yet tuned.
 | 
			
		||||
 * This command fails if the transmit or receive frequency creates a conflict
 | 
			
		||||
 * with another ARFCN that is already running.
 | 
			
		||||
 * If the transceiver is already on, it response with success to this command.
 | 
			
		||||
 * CMD POWERON
 | 
			
		||||
 * RSP POWERON <status>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_sync(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "SYNC", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_echo(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "ECHO", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_poweroff(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_poweron(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	if (trx->powered_up) {
 | 
			
		||||
		/* FIXME: this should be handled by the FSM, not here! */
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Suppressing POWERON as we're already powered up\n");
 | 
			
		||||
		return -EAGAIN;
 | 
			
		||||
	}
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "POWERON", "");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Timeslot Control
 | 
			
		||||
 *
 | 
			
		||||
 * SETSLOT sets the format of the uplink timeslots in the ARFCN.
 | 
			
		||||
 * The <timeslot> indicates the timeslot of interest.
 | 
			
		||||
 * The <chantype> indicates the type of channel that occupies the timeslot.
 | 
			
		||||
 * A chantype of zero indicates the timeslot is off.
 | 
			
		||||
 * CMD SETSLOT <timeslot> <chantype>
 | 
			
		||||
 * RSP SETSLOT <status> <timeslot> <chantype>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "SETSLOT", "%u %u", tn, type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Tuning Control
 | 
			
		||||
 *
 | 
			
		||||
 * (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
 | 
			
		||||
 * This command fails if the receiver is already running.
 | 
			
		||||
 * (To re-tune you stop the radio, re-tune, and restart.)
 | 
			
		||||
 * This command fails if the transmit or receive frequency
 | 
			
		||||
 * creates a conflict with another ARFCN that is already running.
 | 
			
		||||
 * CMD (RX/TX)TUNE <kHz>
 | 
			
		||||
 * RSP (RX/TX)TUNE <status> <kHz>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t freq10;
 | 
			
		||||
 | 
			
		||||
	/* RX is downlink on MS side */
 | 
			
		||||
	freq10 = gsm_arfcn2freq10(band_arfcn, 0);
 | 
			
		||||
	if (freq10 == 0xffff) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "RXTUNE", "%u", freq10 * 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t freq10;
 | 
			
		||||
 | 
			
		||||
	/* TX is uplink on MS side */
 | 
			
		||||
	freq10 = gsm_arfcn2freq10(band_arfcn, 1);
 | 
			
		||||
	if (freq10 == 0xffff) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "TXTUNE", "%u", freq10 * 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Power measurement
 | 
			
		||||
 *
 | 
			
		||||
 * MEASURE instructs the transceiver to perform a power
 | 
			
		||||
 * measurement on specified frequency. After receiving this
 | 
			
		||||
 * request, transceiver should quickly re-tune to requested
 | 
			
		||||
 * frequency, measure power level and re-tune back to the
 | 
			
		||||
 * previous frequency.
 | 
			
		||||
 * CMD MEASURE <kHz>
 | 
			
		||||
 * RSP MEASURE <status> <kHz> <dB>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_measure(struct trx_instance *trx,
 | 
			
		||||
	uint16_t band_arfcn_start, uint16_t band_arfcn_stop)
 | 
			
		||||
{
 | 
			
		||||
	uint16_t freq10;
 | 
			
		||||
 | 
			
		||||
	/* Update ARFCN range for measurement */
 | 
			
		||||
	trx->pm_band_arfcn_start = band_arfcn_start;
 | 
			
		||||
	trx->pm_band_arfcn_stop = band_arfcn_stop;
 | 
			
		||||
 | 
			
		||||
	/* Calculate a frequency for current ARFCN (DL) */
 | 
			
		||||
	freq10 = gsm_arfcn2freq10(band_arfcn_start, 0);
 | 
			
		||||
	if (freq10 == 0xffff) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", band_arfcn_start);
 | 
			
		||||
		return -ENOTSUP;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "MEASURE", "%u", freq10 * 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int freq10;
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	int dbm;
 | 
			
		||||
 | 
			
		||||
	/* Parse freq. and power level */
 | 
			
		||||
	sscanf(resp, "%u %d", &freq10, &dbm);
 | 
			
		||||
	freq10 /= 100;
 | 
			
		||||
 | 
			
		||||
	/* Check received ARFCN against expected */
 | 
			
		||||
	band_arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
 | 
			
		||||
	if (band_arfcn != trx->pm_band_arfcn_start) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
 | 
			
		||||
			"response ARFCN=%u doesn't match expected ARFCN=%u\n",
 | 
			
		||||
			band_arfcn &~ ARFCN_FLAG_MASK,
 | 
			
		||||
			trx->pm_band_arfcn_start &~ ARFCN_FLAG_MASK);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Send L1CTL_PM_CONF */
 | 
			
		||||
	l1ctl_tx_pm_conf(trx->l1l, band_arfcn, dbm,
 | 
			
		||||
		band_arfcn == trx->pm_band_arfcn_stop);
 | 
			
		||||
 | 
			
		||||
	/* Schedule a next measurement */
 | 
			
		||||
	if (band_arfcn != trx->pm_band_arfcn_stop)
 | 
			
		||||
		trx_if_cmd_measure(trx, ++band_arfcn, trx->pm_band_arfcn_stop);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Timing Advance control
 | 
			
		||||
 *
 | 
			
		||||
 * SETTA instructs the transceiver to transmit bursts in
 | 
			
		||||
 * advance calculated from requested TA value. This value is
 | 
			
		||||
 * normally between 0 and 63, with each step representing
 | 
			
		||||
 * an advance of one bit period (about 3.69 microseconds).
 | 
			
		||||
 * Since OsmocomBB has a special feature, which allows one
 | 
			
		||||
 * to spoof the distance from BTS, the range is extended.
 | 
			
		||||
 * CMD SETTA <-128..127>
 | 
			
		||||
 * RSP SETTA <status> <TA>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
 | 
			
		||||
{
 | 
			
		||||
	return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Frequency Hopping parameters indication.
 | 
			
		||||
 *
 | 
			
		||||
 * SETFH instructs transceiver to enable frequency hopping mode
 | 
			
		||||
 * using the given HSN, MAIO, and Mobile Allocation parameters.
 | 
			
		||||
 *
 | 
			
		||||
 * CMD SETFH <HSN> <MAIO> <RXF1> <TXF1> [... <RXFN> <TXFN>]
 | 
			
		||||
 *
 | 
			
		||||
 * where <RXFN> and <TXFN> is a pair of Rx/Tx frequencies (in kHz)
 | 
			
		||||
 * corresponding to one ARFCN the Mobile Allocation. Note that the
 | 
			
		||||
 * channel list is expected to be sorted in ascending order.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
 | 
			
		||||
	uint8_t maio, uint16_t *ma, size_t ma_len)
 | 
			
		||||
{
 | 
			
		||||
	/* Reserve some room for CMD SETFH <HSN> <MAIO> */
 | 
			
		||||
	char ma_buf[TRXC_BUF_SIZE - 24];
 | 
			
		||||
	size_t ma_buf_len = sizeof(ma_buf) - 1;
 | 
			
		||||
	uint16_t rx_freq, tx_freq;
 | 
			
		||||
	char *ptr;
 | 
			
		||||
	int i, rc;
 | 
			
		||||
 | 
			
		||||
	/* Make sure that Mobile Allocation has at least one ARFCN */
 | 
			
		||||
	if (!ma_len || ma == NULL) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Mobile Allocation is empty?!?\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Compose a sequence of Rx/Tx frequencies (mobile allocation) */
 | 
			
		||||
	for (i = 0, ptr = ma_buf; i < ma_len; i++) {
 | 
			
		||||
		/* Convert ARFCN to a pair of Rx/Tx frequencies (Hz * 10) */
 | 
			
		||||
		rx_freq = gsm_arfcn2freq10(ma[i], 0); /* Rx: Downlink */
 | 
			
		||||
		tx_freq = gsm_arfcn2freq10(ma[i], 1); /* Tx: Uplink */
 | 
			
		||||
		if (rx_freq == 0xffff || tx_freq == 0xffff) {
 | 
			
		||||
			LOGP(DTRX, LOGL_ERROR, "Failed to convert ARFCN %u "
 | 
			
		||||
			     "to a pair of Rx/Tx frequencies\n",
 | 
			
		||||
			     ma[i] & ~ARFCN_FLAG_MASK);
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Append a pair of Rx/Tx frequencies (in kHz) to the buffer */
 | 
			
		||||
		rc = snprintf(ptr, ma_buf_len, "%u %u ", rx_freq * 100, tx_freq * 100);
 | 
			
		||||
		if (rc < 0 || rc > ma_buf_len) { /* Prevent buffer overflow */
 | 
			
		||||
			LOGP(DTRX, LOGL_ERROR, "Not enough room to encode "
 | 
			
		||||
			     "Mobile Allocation (N=%zu)\n", ma_len);
 | 
			
		||||
			return -ENOSPC;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Move pointer */
 | 
			
		||||
		ma_buf_len -= rc;
 | 
			
		||||
		ptr += rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Overwrite the last space */
 | 
			
		||||
	*(ptr - 1) = '\0';
 | 
			
		||||
 | 
			
		||||
	return trx_ctrl_cmd(trx, 1, "SETFH", "%u %u %s", hsn, maio, ma_buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Get response from CTRL socket */
 | 
			
		||||
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = ofd->data;
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
	int resp, rsp_len;
 | 
			
		||||
	char buf[TRXC_BUF_SIZE], *p;
 | 
			
		||||
	ssize_t read_len;
 | 
			
		||||
 | 
			
		||||
//	read_len = read(ofd->fd, buf, sizeof(buf) - 1);
 | 
			
		||||
//	if (read_len <= 0) {
 | 
			
		||||
//		LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
 | 
			
		||||
//		return read_len;
 | 
			
		||||
//	}
 | 
			
		||||
//	buf[read_len] = '\0';
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "C wat: %d\n", what);
 | 
			
		||||
	char* response = trxif_from_trx_c();
 | 
			
		||||
	if (!response) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", response);
 | 
			
		||||
		return response;
 | 
			
		||||
	}
 | 
			
		||||
	memcpy(buf, response, TRXC_BUF_SIZE);
 | 
			
		||||
	free(response);
 | 
			
		||||
 | 
			
		||||
	if (!!strncmp(buf, "RSP ", 4)) {
 | 
			
		||||
		LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Calculate the length of response item */
 | 
			
		||||
	p = strchr(buf + 4, ' ');
 | 
			
		||||
	rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
 | 
			
		||||
 | 
			
		||||
	/* Abort expire timer */
 | 
			
		||||
	if (osmo_timer_pending(&trx->trx_ctrl_timer))
 | 
			
		||||
		osmo_timer_del(&trx->trx_ctrl_timer);
 | 
			
		||||
 | 
			
		||||
	/* Get command for response message */
 | 
			
		||||
	if (llist_empty(&trx->trx_ctrl_list)) {
 | 
			
		||||
		LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tcm = llist_entry(trx->trx_ctrl_list.next,
 | 
			
		||||
		struct trx_ctrl_msg, list);
 | 
			
		||||
 | 
			
		||||
	/* Check if response matches command */
 | 
			
		||||
	if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
 | 
			
		||||
		LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
 | 
			
		||||
			"Response message '%s' does not match command "
 | 
			
		||||
			"message '%s'\n", buf, tcm->cmd);
 | 
			
		||||
		goto rsp_error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Check for response code */
 | 
			
		||||
	sscanf(p + 1, "%d", &resp);
 | 
			
		||||
	if (resp) {
 | 
			
		||||
		LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
 | 
			
		||||
			"Transceiver rejected TRX command with "
 | 
			
		||||
			"response: '%s'\n", buf);
 | 
			
		||||
 | 
			
		||||
		if (tcm->critical)
 | 
			
		||||
			goto rsp_error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Trigger state machine */
 | 
			
		||||
	if (!strncmp(tcm->cmd + 4, "POWERON", 7)) {
 | 
			
		||||
		trx->powered_up = true;
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8)) {
 | 
			
		||||
		trx->powered_up = false;
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
 | 
			
		||||
		trx_if_measure_rsp_cb(trx, buf + 14);
 | 
			
		||||
	else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
 | 
			
		||||
	else
 | 
			
		||||
		osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
 | 
			
		||||
 | 
			
		||||
	/* Remove command from list */
 | 
			
		||||
	llist_del(&tcm->list);
 | 
			
		||||
	talloc_free(tcm);
 | 
			
		||||
 | 
			
		||||
	/* Send next message, if any */
 | 
			
		||||
	trx_ctrl_send(trx);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
 | 
			
		||||
rsp_error:
 | 
			
		||||
	/* Notify higher layers about the problem */
 | 
			
		||||
	osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
 | 
			
		||||
	return -EIO;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* Data interface handlers                                                  */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
/* DATA interface                                                           */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Messages on the data interface carry one radio burst per UDP message.    */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Received Data Burst:                                                     */
 | 
			
		||||
/* 1 byte timeslot index                                                    */
 | 
			
		||||
/* 4 bytes GSM frame number, BE                                             */
 | 
			
		||||
/* 1 byte RSSI in -dBm                                                      */
 | 
			
		||||
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE     */
 | 
			
		||||
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1"  */
 | 
			
		||||
/* 2 bytes are not used, but being sent by OsmoTRX                          */
 | 
			
		||||
/*                                                                          */
 | 
			
		||||
/* Transmit Data Burst:                                                     */
 | 
			
		||||
/* 1 byte timeslot index                                                    */
 | 
			
		||||
/* 4 bytes GSM frame number, BE                                             */
 | 
			
		||||
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation)                   */
 | 
			
		||||
/* 148 bytes output symbol values, 0 & 1                                    */
 | 
			
		||||
/* ------------------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx = ofd->data;
 | 
			
		||||
	struct trx_meas_set meas;
 | 
			
		||||
	uint8_t buf[TRXD_BUF_SIZE];
 | 
			
		||||
	sbit_t bits[148];
 | 
			
		||||
	int8_t rssi, tn;
 | 
			
		||||
	int16_t toa256;
 | 
			
		||||
	uint32_t fn;
 | 
			
		||||
	ssize_t read_len;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//	read_len = read(ofd->fd, buf, sizeof(buf));
 | 
			
		||||
//	if (read_len <= 0) {
 | 
			
		||||
//		LOGP(DTRXD, LOGL_ERROR, "read() failed with rc=%zd\n", read_len);
 | 
			
		||||
//		return read_len;
 | 
			
		||||
//	}
 | 
			
		||||
 | 
			
		||||
//	if (read_len != 158) {
 | 
			
		||||
//		LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid "
 | 
			
		||||
//			"length '%zd'\n", read_len);
 | 
			
		||||
//		return -EINVAL;
 | 
			
		||||
//	}
 | 
			
		||||
 | 
			
		||||
//	tn = buf[0];
 | 
			
		||||
//	fn = osmo_load32be(buf + 1);
 | 
			
		||||
//	rssi = -(int8_t) buf[5];
 | 
			
		||||
//	toa256 = ((int16_t) (buf[6] << 8) | buf[7]);
 | 
			
		||||
 | 
			
		||||
//	/* Copy and convert bits {254..0} to sbits {-127..127} */
 | 
			
		||||
//	osmo_ubit2sbit(bits, buf + 8, 148);
 | 
			
		||||
 | 
			
		||||
	struct trxd_from_trx* rcvd = trxif_from_trx_d();
 | 
			
		||||
	if (!rcvd) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "read() failed with rc=%zd\n", rcvd);
 | 
			
		||||
		return rcvd;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tn = rcvd->ts;
 | 
			
		||||
	fn = rcvd->fn;
 | 
			
		||||
	rssi = -(int8_t) rcvd->rssi;
 | 
			
		||||
	toa256 = (int16_t) rcvd->toa;
 | 
			
		||||
 | 
			
		||||
	/* Copy and convert bits {254..0} to sbits {-127..127} */
 | 
			
		||||
	//osmo_ubit2sbit(bits, rcvd->symbols, 148);
 | 
			
		||||
	memcpy(bits, rcvd->symbols, 148);
 | 
			
		||||
 | 
			
		||||
	free(rcvd);
 | 
			
		||||
 | 
			
		||||
	if (tn >= 8) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (fn >= 2715648) {
 | 
			
		||||
		LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
 | 
			
		||||
		tn, fn, rssi, toa256);
 | 
			
		||||
 | 
			
		||||
	/* Group the measurements together */
 | 
			
		||||
	meas = (struct trx_meas_set) {
 | 
			
		||||
		.toa256 = toa256,
 | 
			
		||||
		.rssi = rssi,
 | 
			
		||||
		.fn = fn,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/* Poke scheduler */
 | 
			
		||||
	sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, &meas);
 | 
			
		||||
 | 
			
		||||
	/* Correct local clock counter */
 | 
			
		||||
	if (fn % 51 == 0)
 | 
			
		||||
		sched_clck_handle(&trx->sched, fn);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
 | 
			
		||||
	uint8_t pwr, const ubit_t *bits)
 | 
			
		||||
{
 | 
			
		||||
//	uint8_t buf[TRXD_BUF_SIZE];
 | 
			
		||||
 | 
			
		||||
//	/**
 | 
			
		||||
//	 * We must be sure that we have clock,
 | 
			
		||||
//	 * and we have sent all control data
 | 
			
		||||
//	 *
 | 
			
		||||
//	 * TODO: introduce proper state machines for both
 | 
			
		||||
//	 *       transceiver and its TRXC interface.
 | 
			
		||||
//	 */
 | 
			
		||||
//#if 0
 | 
			
		||||
//	if (trx->fsm->state != TRX_STATE_ACTIVE) {
 | 
			
		||||
//		LOGP(DTRXD, LOGL_ERROR, "Ignoring TX data, "
 | 
			
		||||
//			"transceiver isn't ready\n");
 | 
			
		||||
//		return -EAGAIN;
 | 
			
		||||
//	}
 | 
			
		||||
//#endif
 | 
			
		||||
 | 
			
		||||
//	LOGP(DTRXD, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
 | 
			
		||||
 | 
			
		||||
//	buf[0] = tn;
 | 
			
		||||
//	osmo_store32be(fn, buf + 1);
 | 
			
		||||
//	buf[5] = pwr;
 | 
			
		||||
 | 
			
		||||
//	/* Copy ubits {0,1} */
 | 
			
		||||
//	memcpy(buf + 6, bits, 148);
 | 
			
		||||
 | 
			
		||||
//	/* Send data to transceiver */
 | 
			
		||||
//	send(trx->trx_ofd_data.fd, buf, 154, 0);
 | 
			
		||||
 | 
			
		||||
	struct trxd_to_trx* t = malloc(sizeof(struct trxd_to_trx));
 | 
			
		||||
	t->ts = tn;
 | 
			
		||||
	t->fn = fn;
 | 
			
		||||
	t->txlev = pwr;
 | 
			
		||||
	memcpy(t->symbols, bits, 148);
 | 
			
		||||
	trxif_to_trx_d(t);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Init TRX interface (TRXC, TRXD sockets and FSM) */
 | 
			
		||||
struct trx_instance *trx_if_open(void *tall_ctx,
 | 
			
		||||
	const char *local_host, const char *remote_host,
 | 
			
		||||
	uint16_t base_port)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_instance *trx;
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface "
 | 
			
		||||
		"(%s:%u)\n", remote_host, base_port);
 | 
			
		||||
 | 
			
		||||
	/* Try to allocate memory */
 | 
			
		||||
	trx = talloc_zero(tall_ctx, struct trx_instance);
 | 
			
		||||
	if (!trx) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Allocate a new dedicated state machine */
 | 
			
		||||
	trx->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx,
 | 
			
		||||
		NULL, LOGL_DEBUG, "trx_interface");
 | 
			
		||||
	if (trx->fsm == NULL) {
 | 
			
		||||
		LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
 | 
			
		||||
			"of FSM '%s'\n", trx_fsm.name);
 | 
			
		||||
		talloc_free(trx);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Initialize CTRL queue */
 | 
			
		||||
	INIT_LLIST_HEAD(&trx->trx_ctrl_list);
 | 
			
		||||
 | 
			
		||||
	rc = eventfd(0, 0);
 | 
			
		||||
	osmo_fd_setup(get_c_fd(), rc, OSMO_FD_READ, trx_ctrl_read_cb, trx, 0);
 | 
			
		||||
	osmo_fd_register(get_c_fd());
 | 
			
		||||
 | 
			
		||||
	rc = eventfd(0, 0);
 | 
			
		||||
	osmo_fd_setup(get_d_fd(), rc, OSMO_FD_READ, trx_data_rx_cb, trx, 0);
 | 
			
		||||
	osmo_fd_register(get_d_fd());
 | 
			
		||||
 | 
			
		||||
//	/* Open sockets */
 | 
			
		||||
//	rc = trx_udp_open(trx, &trx->trx_ofd_ctrl, local_host,
 | 
			
		||||
//		base_port + 101, remote_host, base_port + 1, trx_ctrl_read_cb);
 | 
			
		||||
//	if (rc < 0)
 | 
			
		||||
//		goto udp_error;
 | 
			
		||||
 | 
			
		||||
//	rc = trx_udp_open(trx, &trx->trx_ofd_data, local_host,
 | 
			
		||||
//		base_port + 102, remote_host, base_port + 2, trx_data_rx_cb);
 | 
			
		||||
//	if (rc < 0)
 | 
			
		||||
//		goto udp_error;
 | 
			
		||||
 | 
			
		||||
	return trx;
 | 
			
		||||
 | 
			
		||||
//udp_error:
 | 
			
		||||
//	LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
 | 
			
		||||
//	osmo_fsm_inst_free(trx->fsm);
 | 
			
		||||
//	talloc_free(trx);
 | 
			
		||||
//	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Flush pending control messages */
 | 
			
		||||
void trx_if_flush_ctrl(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	struct trx_ctrl_msg *tcm;
 | 
			
		||||
 | 
			
		||||
	/* Reset state machine */
 | 
			
		||||
	osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
 | 
			
		||||
 | 
			
		||||
	/* Clear command queue */
 | 
			
		||||
	while (!llist_empty(&trx->trx_ctrl_list)) {
 | 
			
		||||
		tcm = llist_entry(trx->trx_ctrl_list.next,
 | 
			
		||||
			struct trx_ctrl_msg, list);
 | 
			
		||||
		llist_del(&tcm->list);
 | 
			
		||||
		talloc_free(tcm);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void trx_if_close(struct trx_instance *trx)
 | 
			
		||||
{
 | 
			
		||||
	/* May be unallocated due to init error */
 | 
			
		||||
	if (!trx)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
 | 
			
		||||
 | 
			
		||||
	/* Flush CTRL message list */
 | 
			
		||||
	trx_if_flush_ctrl(trx);
 | 
			
		||||
 | 
			
		||||
	/* Close sockets */
 | 
			
		||||
	close(get_c_fd()->fd);
 | 
			
		||||
	close(get_d_fd()->fd);
 | 
			
		||||
 | 
			
		||||
//	trx_udp_close(&trx->trx_ofd_ctrl);
 | 
			
		||||
//	trx_udp_close(&trx->trx_ofd_data);
 | 
			
		||||
 | 
			
		||||
	/* Free memory */
 | 
			
		||||
	osmo_fsm_inst_free(trx->fsm);
 | 
			
		||||
	talloc_free(trx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static __attribute__((constructor)) void on_dso_load(void)
 | 
			
		||||
{
 | 
			
		||||
	OSMO_ASSERT(osmo_fsm_register(&trx_fsm) == 0);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,87 +0,0 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/linuxlist.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/timer.h>
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
 | 
			
		||||
#define TRXC_BUF_SIZE	1024
 | 
			
		||||
#define TRXD_BUF_SIZE	512
 | 
			
		||||
 | 
			
		||||
/* Forward declaration to avoid mutual include */
 | 
			
		||||
struct l1ctl_link;
 | 
			
		||||
 | 
			
		||||
enum trx_fsm_states {
 | 
			
		||||
	TRX_STATE_OFFLINE = 0,
 | 
			
		||||
	TRX_STATE_IDLE,
 | 
			
		||||
	TRX_STATE_ACTIVE,
 | 
			
		||||
	TRX_STATE_RSP_WAIT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_instance {
 | 
			
		||||
#ifndef IPCIF
 | 
			
		||||
	struct osmo_fd trx_ofd_ctrl;
 | 
			
		||||
	struct osmo_fd trx_ofd_data;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
	struct osmo_timer_list trx_ctrl_timer;
 | 
			
		||||
	struct llist_head trx_ctrl_list;
 | 
			
		||||
	struct osmo_fsm_inst *fsm;
 | 
			
		||||
 | 
			
		||||
	/* HACK: we need proper state machines */
 | 
			
		||||
	uint32_t prev_state;
 | 
			
		||||
	bool powered_up;
 | 
			
		||||
 | 
			
		||||
	/* GSM L1 specific */
 | 
			
		||||
	uint16_t pm_band_arfcn_start;
 | 
			
		||||
	uint16_t pm_band_arfcn_stop;
 | 
			
		||||
	uint16_t band_arfcn;
 | 
			
		||||
	uint8_t tx_power;
 | 
			
		||||
	uint8_t bsic;
 | 
			
		||||
	uint8_t tsc;
 | 
			
		||||
	int8_t ta;
 | 
			
		||||
 | 
			
		||||
	/* Scheduler stuff */
 | 
			
		||||
	struct trx_sched sched;
 | 
			
		||||
	struct trx_ts *ts_list[TRX_TS_COUNT];
 | 
			
		||||
 | 
			
		||||
	/* Bind L1CTL link */
 | 
			
		||||
	struct l1ctl_link *l1l;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_ctrl_msg {
 | 
			
		||||
	struct llist_head list;
 | 
			
		||||
	char cmd[TRXC_BUF_SIZE];
 | 
			
		||||
	int retry_cnt;
 | 
			
		||||
	int critical;
 | 
			
		||||
	int cmd_len;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct trx_instance *trx_if_open(void *tall_ctx,
 | 
			
		||||
	const char *local_host, const char *remote_host, uint16_t port);
 | 
			
		||||
void trx_if_flush_ctrl(struct trx_instance *trx);
 | 
			
		||||
void trx_if_close(struct trx_instance *trx);
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_poweron(struct trx_instance *trx);
 | 
			
		||||
int trx_if_cmd_poweroff(struct trx_instance *trx);
 | 
			
		||||
int trx_if_cmd_echo(struct trx_instance *trx);
 | 
			
		||||
int trx_if_cmd_sync(struct trx_instance *trx);
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta);
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t band_arfcn);
 | 
			
		||||
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t band_arfcn);
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type);
 | 
			
		||||
int trx_if_cmd_setfh(struct trx_instance *trx, uint8_t hsn,
 | 
			
		||||
	uint8_t maio, uint16_t *ma, size_t ma_len);
 | 
			
		||||
 | 
			
		||||
int trx_if_cmd_measure(struct trx_instance *trx,
 | 
			
		||||
	uint16_t band_arfcn_start, uint16_t band_arfcn_stop);
 | 
			
		||||
 | 
			
		||||
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
 | 
			
		||||
	uint8_t pwr, const ubit_t *bits);
 | 
			
		||||
int trx_data_rx_handler(struct trx_instance *trx, uint8_t *buf);
 | 
			
		||||
							
								
								
									
										407
									
								
								trxcon/trxcon.c
									
									
									
									
									
								
							
							
						
						
									
										407
									
								
								trxcon/trxcon.c
									
									
									
									
									
								
							@@ -1,407 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * OsmocomBB <-> SDR connection bridge
 | 
			
		||||
 *
 | 
			
		||||
 * (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * All Rights Reserved
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License along
 | 
			
		||||
 * with this program; if not, write to the Free Software Foundation, Inc.,
 | 
			
		||||
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <getopt.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <signal.h>
 | 
			
		||||
#include <time.h>
 | 
			
		||||
#include <pthread.h>
 | 
			
		||||
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/core/fsm.h>
 | 
			
		||||
#include <osmocom/core/msgb.h>
 | 
			
		||||
#include <osmocom/core/talloc.h>
 | 
			
		||||
#include <osmocom/core/signal.h>
 | 
			
		||||
#include <osmocom/core/select.h>
 | 
			
		||||
#include <osmocom/core/application.h>
 | 
			
		||||
#include <osmocom/core/gsmtap_util.h>
 | 
			
		||||
#include <osmocom/core/gsmtap.h>
 | 
			
		||||
 | 
			
		||||
#include <osmocom/gsm/gsm_utils.h>
 | 
			
		||||
 | 
			
		||||
#include "trxcon.h"
 | 
			
		||||
#include "trx_if.h"
 | 
			
		||||
#include "logging.h"
 | 
			
		||||
#include "l1ctl.h"
 | 
			
		||||
#include "l1ctl_link.h"
 | 
			
		||||
#include "l1ctl_proto.h"
 | 
			
		||||
#include "scheduler.h"
 | 
			
		||||
#include "sched_trx.h"
 | 
			
		||||
 | 
			
		||||
#define COPYRIGHT \
 | 
			
		||||
	"Copyright (C) 2016-2020 by Vadim Yanitskiy <axilirator@gmail.com>\n" \
 | 
			
		||||
	"License GPLv2+: GNU GPL version 2 or later " \
 | 
			
		||||
	"<http://gnu.org/licenses/gpl.html>\n" \
 | 
			
		||||
	"This is free software: you are free to change and redistribute it.\n" \
 | 
			
		||||
	"There is NO WARRANTY, to the extent permitted by law.\n\n"
 | 
			
		||||
 | 
			
		||||
static struct {
 | 
			
		||||
	const char *debug_mask;
 | 
			
		||||
	int daemonize;
 | 
			
		||||
	int quit;
 | 
			
		||||
 | 
			
		||||
	/* L1CTL specific */
 | 
			
		||||
	struct l1ctl_link *l1l;
 | 
			
		||||
	const char *bind_socket;
 | 
			
		||||
 | 
			
		||||
	/* TRX specific */
 | 
			
		||||
	struct trx_instance *trx;
 | 
			
		||||
	const char *trx_bind_ip;
 | 
			
		||||
	const char *trx_remote_ip;
 | 
			
		||||
	uint16_t trx_base_port;
 | 
			
		||||
	uint32_t trx_fn_advance;
 | 
			
		||||
	const char *gsmtap_ip;
 | 
			
		||||
} app_data;
 | 
			
		||||
 | 
			
		||||
static void *tall_trxcon_ctx = NULL;
 | 
			
		||||
struct gsmtap_inst *gsmtap = NULL;
 | 
			
		||||
struct osmo_fsm_inst *trxcon_fsm;
 | 
			
		||||
 | 
			
		||||
static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi,
 | 
			
		||||
	uint32_t event, void *data)
 | 
			
		||||
{
 | 
			
		||||
	if (event == L1CTL_EVENT_CONNECT)
 | 
			
		||||
		osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_MANAGED, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi,
 | 
			
		||||
	uint32_t event, void *data)
 | 
			
		||||
{
 | 
			
		||||
	switch (event) {
 | 
			
		||||
	case L1CTL_EVENT_DISCONNECT:
 | 
			
		||||
		osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_IDLE, 0, 0);
 | 
			
		||||
 | 
			
		||||
		if (app_data.trx->fsm->state != TRX_STATE_OFFLINE) {
 | 
			
		||||
			/* Reset scheduler and clock counter */
 | 
			
		||||
			sched_trx_reset(app_data.trx, true);
 | 
			
		||||
 | 
			
		||||
			/* TODO: implement trx_if_reset() */
 | 
			
		||||
			trx_if_cmd_poweroff(app_data.trx);
 | 
			
		||||
			trx_if_cmd_echo(app_data.trx);
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	case TRX_EVENT_RSP_ERROR:
 | 
			
		||||
	case TRX_EVENT_OFFLINE:
 | 
			
		||||
		/* TODO: notify L2 & L3 about that */
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm_state trxcon_fsm_states[] = {
 | 
			
		||||
	[TRXCON_STATE_IDLE] = {
 | 
			
		||||
		.in_event_mask = GEN_MASK(L1CTL_EVENT_CONNECT),
 | 
			
		||||
		.out_state_mask = GEN_MASK(TRXCON_STATE_MANAGED),
 | 
			
		||||
		.name = "IDLE",
 | 
			
		||||
		.action = trxcon_fsm_idle_action,
 | 
			
		||||
	},
 | 
			
		||||
	[TRXCON_STATE_MANAGED] = {
 | 
			
		||||
		.in_event_mask = (
 | 
			
		||||
			GEN_MASK(L1CTL_EVENT_DISCONNECT) |
 | 
			
		||||
			GEN_MASK(TRX_EVENT_RSP_ERROR) |
 | 
			
		||||
			GEN_MASK(TRX_EVENT_OFFLINE)),
 | 
			
		||||
		.out_state_mask = GEN_MASK(TRXCON_STATE_IDLE),
 | 
			
		||||
		.name = "MANAGED",
 | 
			
		||||
		.action = trxcon_fsm_managed_action,
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct value_string app_evt_names[] = {
 | 
			
		||||
	OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT),
 | 
			
		||||
	OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT),
 | 
			
		||||
	OSMO_VALUE_STRING(TRX_EVENT_OFFLINE),
 | 
			
		||||
	OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR),
 | 
			
		||||
	{ 0, NULL }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct osmo_fsm trxcon_fsm_def = {
 | 
			
		||||
	.name = "trxcon_app_fsm",
 | 
			
		||||
	.states = trxcon_fsm_states,
 | 
			
		||||
	.num_states = ARRAY_SIZE(trxcon_fsm_states),
 | 
			
		||||
	.log_subsys = DAPP,
 | 
			
		||||
	.event_names = app_evt_names,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void print_usage(const char *app)
 | 
			
		||||
{
 | 
			
		||||
	printf("Usage: %s\n", app);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void print_help(void)
 | 
			
		||||
{
 | 
			
		||||
	printf(" Some help...\n");
 | 
			
		||||
	printf("  -h --help         this text\n");
 | 
			
		||||
	printf("  -d --debug        Change debug flags. Default: %s\n", DEBUG_DEFAULT);
 | 
			
		||||
	printf("  -b --trx-bind     TRX bind IP address (default 0.0.0.0)\n");
 | 
			
		||||
	printf("  -i --trx-remote   TRX remote IP address (default 127.0.0.1)\n");
 | 
			
		||||
	printf("  -p --trx-port     Base port of TRX instance (default 6700)\n");
 | 
			
		||||
	printf("  -f --trx-advance  Uplink burst scheduling advance (default 3)\n");
 | 
			
		||||
	printf("  -s --socket       Listening socket for layer23 (default /tmp/osmocom_l2)\n");
 | 
			
		||||
	printf("  -g --gsmtap-ip    The destination IP used for GSMTAP (disabled by default)\n");
 | 
			
		||||
	printf("  -D --daemonize    Run as daemon\n");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handle_options(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	while (1) {
 | 
			
		||||
		int option_index = 0, c;
 | 
			
		||||
		static struct option long_options[] = {
 | 
			
		||||
			{"help", 0, 0, 'h'},
 | 
			
		||||
			{"debug", 1, 0, 'd'},
 | 
			
		||||
			{"socket", 1, 0, 's'},
 | 
			
		||||
			{"trx-bind", 1, 0, 'b'},
 | 
			
		||||
			/* NOTE: 'trx-ip' is now an alias for 'trx-remote'
 | 
			
		||||
			 * due to backward compatibility reasons! */
 | 
			
		||||
			{"trx-ip", 1, 0, 'i'},
 | 
			
		||||
			{"trx-remote", 1, 0, 'i'},
 | 
			
		||||
			{"trx-port", 1, 0, 'p'},
 | 
			
		||||
			{"trx-advance", 1, 0, 'f'},
 | 
			
		||||
			{"gsmtap-ip", 1, 0, 'g'},
 | 
			
		||||
			{"daemonize", 0, 0, 'D'},
 | 
			
		||||
			{0, 0, 0, 0}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		c = getopt_long(argc, argv, "d:b:i:p:f:s:g:Dh",
 | 
			
		||||
				long_options, &option_index);
 | 
			
		||||
		if (c == -1)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		switch (c) {
 | 
			
		||||
		case 'h':
 | 
			
		||||
			print_usage(argv[0]);
 | 
			
		||||
			print_help();
 | 
			
		||||
			exit(0);
 | 
			
		||||
			break;
 | 
			
		||||
		case 'd':
 | 
			
		||||
			app_data.debug_mask = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'b':
 | 
			
		||||
			app_data.trx_bind_ip = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'i':
 | 
			
		||||
			app_data.trx_remote_ip = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'p':
 | 
			
		||||
			app_data.trx_base_port = atoi(optarg);
 | 
			
		||||
			break;
 | 
			
		||||
		case 'f':
 | 
			
		||||
			app_data.trx_fn_advance = atoi(optarg);
 | 
			
		||||
			break;
 | 
			
		||||
		case 's':
 | 
			
		||||
			app_data.bind_socket = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'g':
 | 
			
		||||
			app_data.gsmtap_ip = optarg;
 | 
			
		||||
			break;
 | 
			
		||||
		case 'D':
 | 
			
		||||
			app_data.daemonize = 1;
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void init_defaults(void)
 | 
			
		||||
{
 | 
			
		||||
	app_data.bind_socket = "/tmp/osmocom_l2";
 | 
			
		||||
	app_data.trx_remote_ip = "127.0.0.1";
 | 
			
		||||
	app_data.trx_bind_ip = "0.0.0.0";
 | 
			
		||||
	app_data.trx_base_port = 6700;
 | 
			
		||||
	app_data.trx_fn_advance = 3;
 | 
			
		||||
 | 
			
		||||
	app_data.debug_mask = NULL;
 | 
			
		||||
	app_data.gsmtap_ip = NULL;
 | 
			
		||||
	app_data.daemonize = 0;
 | 
			
		||||
	app_data.quit = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void signal_handler(int signum)
 | 
			
		||||
{
 | 
			
		||||
	fprintf(stderr, "signal %u received\n", signum);
 | 
			
		||||
 | 
			
		||||
	switch (signum) {
 | 
			
		||||
	case SIGINT:
 | 
			
		||||
		app_data.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_trxcon_ctx, stderr);
 | 
			
		||||
		signal(SIGABRT, SIG_DFL);
 | 
			
		||||
		raise(SIGABRT);
 | 
			
		||||
		break;
 | 
			
		||||
	case SIGUSR1:
 | 
			
		||||
	case SIGUSR2:
 | 
			
		||||
		talloc_report_full(tall_trxcon_ctx, stderr);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extern void init_external_transceiver(struct trx_instance *trx, int argc, char **argv) __attribute__((weak));
 | 
			
		||||
extern void close_external_transceiver(int argc, char **argv) __attribute__((weak));
 | 
			
		||||
 | 
			
		||||
int main(int argc, char **argv)
 | 
			
		||||
{
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	cpu_set_t cpuset;
 | 
			
		||||
 | 
			
		||||
	CPU_ZERO(&cpuset);
 | 
			
		||||
	CPU_SET(3, &cpuset);
 | 
			
		||||
	pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
 | 
			
		||||
 | 
			
		||||
	int prio = sched_get_priority_max(SCHED_RR) - 5;
 | 
			
		||||
	struct sched_param param;
 | 
			
		||||
	param.sched_priority = prio;
 | 
			
		||||
	int rv = sched_setscheduler(0, SCHED_RR, ¶m);
 | 
			
		||||
	if (rv < 0) {
 | 
			
		||||
		LOGP(DAPP, LOGL_ERROR, "Failed to set sched!\n");
 | 
			
		||||
		exit(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printf("%s", COPYRIGHT);
 | 
			
		||||
	init_defaults();
 | 
			
		||||
	handle_options(argc, argv);
 | 
			
		||||
 | 
			
		||||
	/* Track the use of talloc NULL memory contexts */
 | 
			
		||||
	talloc_enable_null_tracking();
 | 
			
		||||
 | 
			
		||||
	/* Init talloc memory management system */
 | 
			
		||||
	tall_trxcon_ctx = talloc_init("trxcon context");
 | 
			
		||||
	msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
 | 
			
		||||
 | 
			
		||||
	/* Setup signal handlers */
 | 
			
		||||
//	signal(SIGINT, &signal_handler);
 | 
			
		||||
//	signal(SIGABRT, &signal_handler);
 | 
			
		||||
//	signal(SIGUSR1, &signal_handler);
 | 
			
		||||
//	signal(SIGUSR2, &signal_handler);
 | 
			
		||||
	osmo_init_ignore_signals();
 | 
			
		||||
 | 
			
		||||
	/* Init logging system */
 | 
			
		||||
	trx_log_init(tall_trxcon_ctx, app_data.debug_mask);
 | 
			
		||||
 | 
			
		||||
	/* Configure pretty logging */
 | 
			
		||||
	log_set_print_extended_timestamp(osmo_stderr_target, 1);
 | 
			
		||||
	log_set_print_category_hex(osmo_stderr_target, 0);
 | 
			
		||||
	log_set_print_category(osmo_stderr_target, 1);
 | 
			
		||||
	log_set_print_level(osmo_stderr_target, 1);
 | 
			
		||||
 | 
			
		||||
	/* Optional GSMTAP  */
 | 
			
		||||
	if (app_data.gsmtap_ip != NULL) {
 | 
			
		||||
		gsmtap = gsmtap_source_init(app_data.gsmtap_ip, GSMTAP_UDP_PORT, 1);
 | 
			
		||||
		if (!gsmtap) {
 | 
			
		||||
			LOGP(DAPP, LOGL_ERROR, "Failed to init GSMTAP\n");
 | 
			
		||||
			goto exit;
 | 
			
		||||
		}
 | 
			
		||||
		/* Suppress ICMP "destination unreachable" errors */
 | 
			
		||||
		gsmtap_source_add_sink(gsmtap);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Allocate the application state machine */
 | 
			
		||||
	OSMO_ASSERT(osmo_fsm_register(&trxcon_fsm_def) == 0);
 | 
			
		||||
	trxcon_fsm = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trxcon_ctx,
 | 
			
		||||
		NULL, LOGL_DEBUG, "main");
 | 
			
		||||
 | 
			
		||||
	/* Init L1CTL server */
 | 
			
		||||
	app_data.l1l = l1ctl_link_init(tall_trxcon_ctx,
 | 
			
		||||
		app_data.bind_socket);
 | 
			
		||||
	if (app_data.l1l == NULL)
 | 
			
		||||
		goto exit;
 | 
			
		||||
 | 
			
		||||
	/* Init transceiver interface */
 | 
			
		||||
	app_data.trx = trx_if_open(tall_trxcon_ctx,
 | 
			
		||||
		app_data.trx_bind_ip, app_data.trx_remote_ip,
 | 
			
		||||
		app_data.trx_base_port);
 | 
			
		||||
	if (!app_data.trx)
 | 
			
		||||
		goto exit;
 | 
			
		||||
 | 
			
		||||
	/* Bind L1CTL with TRX and vice versa */
 | 
			
		||||
	app_data.l1l->trx = app_data.trx;
 | 
			
		||||
	app_data.trx->l1l = app_data.l1l;
 | 
			
		||||
 | 
			
		||||
	/* Init scheduler */
 | 
			
		||||
	rc = sched_trx_init(app_data.trx, app_data.trx_fn_advance);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		goto exit;
 | 
			
		||||
 | 
			
		||||
	LOGP(DAPP, LOGL_NOTICE, "Init complete\n");
 | 
			
		||||
 | 
			
		||||
	if (app_data.daemonize) {
 | 
			
		||||
		rc = osmo_daemonize();
 | 
			
		||||
		if (rc < 0) {
 | 
			
		||||
			perror("Error during daemonize");
 | 
			
		||||
			goto exit;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Initialize pseudo-random generator */
 | 
			
		||||
	srand(time(NULL));
 | 
			
		||||
 | 
			
		||||
	if (init_external_transceiver)
 | 
			
		||||
		init_external_transceiver(app_data.trx, argc, argv);
 | 
			
		||||
	else
 | 
			
		||||
		while (!app_data.quit)
 | 
			
		||||
			osmo_select_main(0);
 | 
			
		||||
 | 
			
		||||
	if (close_external_transceiver)
 | 
			
		||||
		close_external_transceiver(argc, argv);
 | 
			
		||||
 | 
			
		||||
exit:
 | 
			
		||||
	/* Close active connections */
 | 
			
		||||
	l1ctl_link_shutdown(app_data.l1l);
 | 
			
		||||
	sched_trx_shutdown(app_data.trx);
 | 
			
		||||
	trx_if_close(app_data.trx);
 | 
			
		||||
 | 
			
		||||
	/* Shutdown main state machine */
 | 
			
		||||
	osmo_fsm_inst_free(trxcon_fsm);
 | 
			
		||||
 | 
			
		||||
	/* Deinitialize logging */
 | 
			
		||||
	log_fini();
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Print report for the root talloc context in order
 | 
			
		||||
	 * to be able to find and fix potential memory leaks.
 | 
			
		||||
	 */
 | 
			
		||||
	talloc_report_full(tall_trxcon_ctx, stderr);
 | 
			
		||||
	talloc_free(tall_trxcon_ctx);
 | 
			
		||||
 | 
			
		||||
	/* Make both Valgrind and ASAN happy */
 | 
			
		||||
	talloc_report_full(NULL, stderr);
 | 
			
		||||
	talloc_disable_null_tracking();
 | 
			
		||||
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user